diff --git a/CMakeLists.txt b/CMakeLists.txt index 5703bb0..5676b29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ set_target_properties(info PROPERTIES add_dependencies(dpm info) # --------------------------------------------------------------------- -# Test fixture modules: one known-good stub, three deliberately broken +# Test fixture modules: one known-good stub, two deliberately broken # --------------------------------------------------------------------- enable_testing() @@ -95,7 +95,7 @@ file(WRITE ${CMAKE_BINARY_DIR}/DartConfiguration.tcl set(FIXTURE_MODULE_DIR ${CMAKE_BINARY_DIR}/tests/fixtures) -foreach(fixture good missing_symbols core_too_new bad_version) +foreach(fixture good missing_symbols bad_version) add_library(fixture_${fixture} MODULE tests/fixtures/src/${fixture}.cpp) set_target_properties(fixture_${fixture} PROPERTIES PREFIX "" @@ -123,7 +123,7 @@ set_target_properties(test_core PROPERTIES BUILD_RPATH "$ORIGIN/../lib" ) -foreach(fixture good missing_symbols core_too_new bad_version) +foreach(fixture good missing_symbols bad_version) add_dependencies(test_core fixture_${fixture}) endforeach() diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index c97deb2..ecec2a7 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -58,7 +58,7 @@ libdpm-core.so validates the module completely at load; a handle is returned onl ``` dpm_module_info info; -dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description, .core_min */ +dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description */ ``` Deciding whether that version is suitable is yours. libdpm-core.so applies no version criterion of its own — a handle means the module is valid, not that it suits you. @@ -77,7 +77,7 @@ This is the only path into module code, and it is the same path the `dpm` binary dpm_cursor* cur = dpm_list_modules(ctx); dpm_module_info info; while (dpm_cursor_next(cur, &info) == 0) { - /* info.name, info.version, info.description, info.core_min */ + /* info.name, info.version, info.description */ } dpm_cursor_free(cur); ``` @@ -87,7 +87,7 @@ The cursor covers every valid module in the module path; invalid candidates are ## Services - **`dpm_core_version`()** — the library version; callable without a context. -- **`dpm_module_info_of`(ctx, mod, out)** — the name, version, description, and minimum-library version read from a loaded module. +- **`dpm_module_info_of`(ctx, mod, out)** — the name, version, and description read from a loaded module. - **`dpm_config_get`(ctx, module, section, key)** — a value from a module's configuration namespace, or NULL if unset. - **`dpm_log`(ctx, level, message)** — writes to the context's configured log targets; levels are `DPM_LOG_FATAL` through `DPM_LOG_DEBUG`. - **`dpm_module_path`(ctx)** — the resolved module directory. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 485da59..7baa8aa 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -28,10 +28,10 @@ Modules are loaded with `RTLD_LOCAL`, so a module's symbols never enter the glob ## Versioning model -Compatibility is directional, and the module is the one that declares it: +Every version question is settled by the party that has to live with the answer. libdpm-core.so reports; it rules on nothing. -- **Each module reports the minimum library version it supports** (a reserved contract symbol). At load, the running library compares its own version against that minimum: if it is older → refuse with an explicit "library too old for this module" report; otherwise load. -- **Consuming modules judge module versions; libdpm-core.so does not.** The library reports the version it saw at load and draws no conclusion from it. A module that depends on another requires it by name, reads the reported version, and decides for itself whether that version is suitable for the commands it intends to issue. A load is a statement that the module is valid, never that it is compatible with a particular caller. +- **A module determines its own compatibility with the library.** It is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. `dpm_core_version()` reports the running version to any module that needs to act on it, and that module proceeds or fails on its own judgement. +- **Consuming modules judge module versions.** A module that depends on another requires it by name, reads the reported version, and decides for itself whether that version is suitable for the commands it intends to issue. A load is a statement that the module is valid, never that it is compatible with a particular caller. ## libdpm-core.so @@ -63,7 +63,7 @@ Releases the context: unloads every module handle it issued, closes log targets, Resolves the module `name` in the module path and runs the full load-time validation sequence (see Load-time enforcement) if the module is not already loaded in this context. On success returns a module handle owned by the context (repeated calls return the same handle — modules are loaded at most once per context). On failure returns NULL and records the precise reason: not found, or validation step failed with the step and detail. No version criterion is applied here; compatibility is the caller's to determine from the reported version. **`int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)`** -Fills `out` with the loaded module's name, version, description, and minimum-library version exactly as they were read at load (string pointers valid until context close). This is how a consumer obtains the version it will judge. The library attaches no meaning to the values. Returns 0 on success, nonzero if the module cannot be reported on. +Fills `out` with the loaded module's name, version, and description exactly as they were read at load (string pointers valid until context close). This is how a consumer obtains the version it will judge. The library attaches no meaning to the values. Returns 0 on success, nonzero if the module cannot be reported on. **`int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command, int argc, char** argv)`** Dispatch: invokes the module's `dpm_module_execute` with the context, `command`, and the argument vector. Returns the module's return value verbatim (0 = success). The library adds nothing to the call besides delivery; argument semantics beyond "argv[0] is the command" are the module's to define. @@ -76,7 +76,7 @@ This is the entire path into module code. A caller addresses a module by name an Scans the module path and returns a cursor over all *valid* modules (each candidate .so is validated on first scan; failures are logged and excluded). Returns NULL on an unreadable module path. **`int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out)`** -Advances the cursor. Fills `out` with the next module's name, version, description, and minimum-library version (string pointers valid until context close). Returns 0 and fills `out` while entries remain; returns nonzero at end. +Advances the cursor. Fills `out` with the next module's name, version, and description (string pointers valid until context close). Returns 0 and fills `out` while entries remain; returns nonzero at end. **`void dpm_cursor_free(dpm_cursor* cur)`** Releases the cursor. NULL is a no-op. @@ -111,8 +111,7 @@ Returns the module's own version as an X.Y.Z string. Must be constant for the li **`const char* dpm_module_description(void)`** Returns a one-line human-readable description, used in module listings. -**`const char* dpm_module_core_min(void)`** -Returns the minimum library version (X.Y.Z) this module supports — the oldest one whose contract and services the module was written against. A library older than that refuses to load the module, and says so. +**Version compatibility with the library is the module's own to determine.** A module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. Where it needs to act on what it is running under, `dpm_core_version()` reports the running version and the module decides what to do with that. **Symbol naming**: functional exports are prefixed with the module's name (raw_\*, pkg_\*); the dpm_ prefix is reserved for the contract and for libdpm-core.so. @@ -123,8 +122,7 @@ Returns the minimum library version (X.Y.Z) this module supports — the oldest libdpm-core.so is the sole authority on module validity; the contract above is enforced by its validator, not by any SDK. Validation is all-or-nothing; a module is registered only after passing every step: 1. **Resolve all reserved contract symbols.** Any missing → refuse, log the exact list, `dlclose`. -2. **Minimum-version handshake.** `dpm_module_core_min`() must be ≤ the running library version. If the library is too old, refuse and say so — the remedy is updating the library, and the message names it. -3. **Probe the cheap calls.** `dpm_module_version`() and `dpm_module_description`() are invoked immediately; NULL or malformed returns → refuse. +2. **Probe the cheap calls.** `dpm_module_version`() and `dpm_module_description`() are invoked immediately; NULL or malformed returns → refuse. Failures happen at install/load time, loudly and itemized. Consumers never receive a partially valid module: if a handle was handed out, the contract already validated. @@ -232,7 +230,7 @@ Peers are reached at runtime by name and command string. Building pkg does not r - Working on **pkg**: edit, run unit tests (instant, zero environment), harness run with a raw stub before merge. A real raw is never needed, or even possessed, until integration. - Working on **raw**: same, except its tests need only fixture package files and a scratch tree. -- Working on **libdpm-core.so**: its test fixtures are deliberately broken modules — missing symbols, a too-new minimum-version declaration, a malformed version — plus one known-good stub. Development never needs any real package module. +- Working on **libdpm-core.so**: its test fixtures are deliberately broken modules — missing symbols, a malformed version — plus one known-good stub. Development never needs any real package module. - **Debugging** is the layer-2 harness under a debugger — it is the "run the module without the system" mechanism, so no separate standalone build exists or is maintained. The discipline that keeps this honest: stubs are written to the command vocabulary the real peer documents, and layer-2 validation plus the layer-3 bootstrap run in CI, so a stub that drifts from reality is caught by the first integration pass rather than shipped. @@ -256,7 +254,7 @@ The config-dir override matters most: once the context reads config from the loc - **A module's interface is its command vocabulary.** Retiring or changing the meaning of a command is a version change in the module that owns it, judged by every consumer that dispatches to it. - **Breaking the library's exported ABI means a new symbol version generation**: the export set is what consumers link against, so a break is a deliberate, versioned event rather than an incidental one. -- **Compatibility is decided by the consumer**: modules declare the minimum library version they support, and libdpm-core.so enforces that one handshake because it is the host. Everything else is reported, not enforced — the library hands a consumer the version it saw and the consumer decides whether to proceed. +- **Compatibility is decided by the party that has to live with it**: a module determines whether it works with the library it is running against, and a consuming module determines whether a peer's version suits it. libdpm-core.so reports the versions it saw and enforces nothing beyond validity. ## Invariants diff --git a/docs/MODULES.md b/docs/MODULES.md index dd5aedb..29472b9 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -15,15 +15,22 @@ The module's own version as an X.Y.Z string. libdpm-core.so reports this value t **`const char* dpm_module_description(void)`** A one-line human-readable description, shown in module listings. -**`const char* dpm_module_core_min(void)`** -The minimum library version (X.Y.Z) the module supports — the oldest one whose contract and services it was written against. A libdpm-core.so older than that refuses to load the module, and the refusal message says so. - The `dpm_ctx` type and the service declarations all come from the installed public header: ``` #include ``` +## You determine your own compatibility with the library + +Your module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. Where you need to act on what you are running under, `dpm_core_version()` reports the running version and you decide what to do: + +``` +const char* running = dpm_core_version(); +``` + +Check it, proceed or fail on your own terms, and report through `dpm_log` and your return code. + ## Your interface is your command vocabulary A module publishes no headers, no struct layouts, and no symbols to anything that calls it. Everything it offers is reached through `dpm_module_execute`, addressed by command string, with arguments passed as an argument vector and a status returned as an int. @@ -57,7 +64,7 @@ A module that depends on a peer is the party that judges the peer's version. Req ## Validation at load -libdpm-core.so is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, it verifies, in order: every reserved contract symbol resolves; the minimum-version handshake passes; and the version and description probes return well-formed values. A module failing any step is refused with an itemized reason, visible in the load-failure output. A module that loads is fully valid — consumers never defend against partial states. +libdpm-core.so is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, it verifies that every reserved contract symbol resolves and that the version and description probes return well-formed values. A module failing either step is refused with an itemized reason, visible in the load-failure output. A module that loads is fully valid — consumers never defend against partial states. ## Building @@ -99,7 +106,7 @@ dpm --module-path mymodule libdpm-core.so runs the full validation sequence on every load, so a contract mistake surfaces here, immediately and itemized, rather than after installation. The `--config-dir` flag points the module's configuration namespace at local files during development, and `--root` directs package operations at a scratch tree. -Where your module calls a peer, put a **stub module** in the fixture module path: a small .so exporting the four reserved symbols and answering the commands your module issues. libdpm-core.so validates and dispatches to it exactly as it would the real peer. Because a peer is addressed only by name and command string, the stub is a complete substitute — there is nothing else about the real peer your module could have depended on. +Where your module calls a peer, put a **stub module** in the fixture module path: a small .so exporting the three reserved symbols and answering the commands your module issues. libdpm-core.so validates and dispatches to it exactly as it would the real peer. Because a peer is addressed only by name and command string, the stub is a complete substitute — there is nothing else about the real peer your module could have depended on. ## Installing diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index 8eb7834..fbd3c61 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -53,7 +53,7 @@ The context owns everything it hands out. Every string a caller receives stays v **Acquiring a module.** `dpm_require` resolves a module by name, validates it completely, and returns a handle — or returns NULL, with `dpm_last_error` carrying the precise reason. Modules load at most once per context, and repeated calls return the same handle. -**Reading what the library saw.** `dpm_module_info_of` fills in a module's name, version, description, and minimum-library version. Those values are reported, and no conclusion is drawn from them. +**Reading what the library saw.** `dpm_module_info_of` fills in a module's name, version, and description. Those values are reported, and no conclusion is drawn from them. **Calling into a module.** `dpm_execute` passes a command name and an argument vector to the module's entry point and returns its result. This is the only path into module code, and it is the same one the `dpm` binary takes. @@ -104,17 +104,18 @@ The `ctx` a module needs is the one handed to it in its own entry point, so it r *What a module author implements.* -A module is one `.so` in the module directory exporting four reserved symbols: a command entry point, its own version, a one-line description, and the minimum library version it supports. It includes `` for those declarations and links `-ldpm-core`, and that is its entire build dependency. +A module is one `.so` in the module directory exporting three reserved symbols: a command entry point, its own version, and a one-line description. It includes `` for those declarations and links `-ldpm-core`, and that is its entire build dependency. The entry point receives the context that dispatched the call, the command name, and an argument vector, and returns an int. Everything a module offers the rest of the system is reachable through that one function, addressed by command name — which is what keeps a caller free of any compile-time knowledge of the module it is calling. +A module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. Where it needs to know what it is running on, `dpm_core_version()` reports the running version and the module acts on that itself. + ## Load-time enforcement libdpm-core.so validates a module completely before offering it to anyone: 1. Every reserved contract symbol resolves. -2. The module's minimum library version is not newer than the running library's own. -3. The version and description probes return well-formed values. +2. The version and description probes return well-formed values. A module failing any step is refused with an itemized reason and its library handle closed. Validation is all-or-nothing: if a handle comes back, the contract already passed. Consumers never defend against partially valid modules, because they cannot receive one. @@ -122,9 +123,11 @@ From the command line this shows up as a module missing from `--list-modules` wi ## Versioning -libdpm-core.so enforces exactly one version rule, the one where it is the host: each module states the oldest library version it supports, and a library older than that refuses to load the module, with a message naming the remedy. +Every version question belongs to the party that has to live with the answer. -Every other version question belongs to the consumer. A module that depends on another requires it, reads the version reported back, and decides for itself whether that version is suitable for the commands it intends to issue. libdpm-core.so reports what it saw and draws no conclusion from it, so a handle means the module is valid rather than that it suits any particular caller. +A module determines its own compatibility with the library it is running against. `dpm_core_version()` reports the running version, and the module proceeds or fails on its own judgement. A module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. + +A module that depends on another module requires it, reads the version reported back, and decides for itself whether that version is suitable for the commands it intends to issue. libdpm-core.so reports what it saw and draws no conclusion from it, so a handle means the module is valid rather than that it suits any particular caller. ## Why it works this way @@ -157,7 +160,7 @@ One repository per module, plus the libdpm-core.so repository. A module links li - A harness that links the real libdpm-core.so and points the module path at build output plus fixture stubs exercises the real validation on a bare builder. - Only final integration needs the whole system, and because alternate roots are first-class, it needs a directory rather than a virtual machine. -This repository's own fixtures are deliberately broken stub modules — missing symbols, a too-new minimum version, a malformed version — plus one known-good stub. Developing libdpm-core.so never requires a real package module to exist. +This repository's own fixtures are deliberately broken stub modules — missing symbols, a malformed version — plus one known-good stub. Developing libdpm-core.so never requires a real package module to exist. ## What this repository produces diff --git a/include/dpm/core.h b/include/dpm/core.h index 6bc7bbf..633933d 100644 --- a/include/dpm/core.h +++ b/include/dpm/core.h @@ -92,7 +92,6 @@ typedef struct dpm_module_info { const char* name; /* module name (filename minus .so) */ const char* version; /* module's own X.Y.Z */ const char* description; /* one-line description */ - const char* core_min; /* minimum libdpm-core version it supports */ } dpm_module_info; /* ------------------------------------------------------------------ */ @@ -139,9 +138,9 @@ DPM_API void dpm_close(dpm_ctx* ctx); * context. Modules are loaded at most once per context; repeated * calls return the same handle. * - * Version compatibility is the caller's judgement, not this library's: - * read the loaded module's version with dpm_module_info_of() and - * decide whether it is acceptable. + * Version compatibility is the caller's judgement: read the loaded + * module's version with dpm_module_info_of() and decide whether it is + * acceptable. * * @param ctx The libdpm-core context * @param name The module name (its filename minus .so) @@ -151,13 +150,11 @@ DPM_API void dpm_close(dpm_ctx* ctx); DPM_API dpm_module* dpm_require(dpm_ctx* ctx, const char* name); /** - * @brief Reports what libdpm-core sees in a loaded module + * @brief Reports what the library sees in a loaded module * - * Fills `out` with the module's name, version, description, and - * minimum-libdpm-core version, exactly as they were read at load. No - * compatibility conclusion is drawn from these values; the caller - * decides whether the version it is looking at is too new or too - * old for its purposes. + * Fills `out` with the module's name, version, and description, + * exactly as they were read at load. The caller decides whether the + * version it is looking at suits its purposes. * * @param ctx The libdpm-core context * @param mod A module handle from dpm_require() @@ -210,9 +207,8 @@ DPM_API dpm_cursor* dpm_list_modules(dpm_ctx* ctx); /** * @brief Advances an enumeration cursor * - * Fills `out` with the next module's name, version, description, and - * minimum-libdpm-core version; the string pointers remain valid until - * context close. + * Fills `out` with the next module's name, version, and description; + * the string pointers remain valid until context close. * * @param cur The cursor from dpm_list_modules() * @param out Receives the next module's information @@ -296,11 +292,17 @@ DPM_API const char* dpm_last_error(dpm_ctx* ctx); * int argc, char** argv); * const char* dpm_module_version(void); * const char* dpm_module_description(void); - * const char* dpm_module_core_min(void); * * dpm_module_execute is the module's entire functional surface; its * capabilities are addressed by command string, so a module publishes * no headers, struct layouts, or symbols to anything that calls it. + * + * A module determines for itself whether it can work with the library + * it is running against: dpm_core_version() reports the running + * version, and the module proceeds or fails on its own judgement. A + * module is built against the system-installed libdpm-core.so and is + * responsible for being correct against it. + * * The library refuses to load any module that does not validate * completely (see the DPM specification: load-time enforcement). */ diff --git a/include/internal/modules.hpp b/include/internal/modules.hpp index f045e17..c783176 100644 --- a/include/internal/modules.hpp +++ b/include/internal/modules.hpp @@ -34,7 +34,6 @@ struct dpm_module { void* handle = nullptr; std::string version; std::string description; - std::string core_min; int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr; }; diff --git a/include/internal/version.hpp b/include/internal/version.hpp index f3b3e94..87b64be 100644 --- a/include/internal/version.hpp +++ b/include/internal/version.hpp @@ -1,6 +1,6 @@ /** * @file version.hpp - * @brief X.Y.Z version parsing and comparison + * @brief X.Y.Z version parsing * * @copyright Copyright (c) 2026 SILO GROUP LLC * @author Chris Punches @@ -33,13 +33,4 @@ namespace dpmcore { */ bool parse_version(const char* s, long out[3]); -/** - * @brief Compares two valid X.Y.Z version strings - * - * @param a First version - * @param b Second version - * @return -1 if a < b, 0 if equal, 1 if a > b - */ -int compare_versions(const char* a, const char* b); - } // namespace dpmcore diff --git a/src/bundled-modules/info/info.cpp b/src/bundled-modules/info/info.cpp index 53ff747..d9a639b 100644 --- a/src/bundled-modules/info/info.cpp +++ b/src/bundled-modules/info/info.cpp @@ -4,7 +4,7 @@ * * Bundles with the dpm binary and libdpm-core.so; used for testing and * reporting functionality of libdpm-core.so. Implements the full DPM - * module contract: the four reserved symbols, with every capability + * module contract: the three reserved symbols, with every capability * reached by command string through the entry point. * * @copyright Copyright (c) 2026 SILO GROUP LLC @@ -28,7 +28,6 @@ #include "commands.hpp" #define INFO_MODULE_VERSION "0.1.0" -#define INFO_CORE_MIN "0.1.0" /* ------------------------------------------------------------------ */ /* Reserved contract symbols */ @@ -50,14 +49,6 @@ extern "C" const char* dpm_module_description(void) return "Reports and tests libdpm-core functionality."; } -/** - * @brief Returns the minimum libdpm-core version this module supports - */ -extern "C" const char* dpm_module_core_min(void) -{ - return INFO_CORE_MIN; -} - /** * @brief Command entry point * diff --git a/src/libdpm-core.map b/src/libdpm-core.map index 758bb79..ba97de6 100644 --- a/src/libdpm-core.map +++ b/src/libdpm-core.map @@ -1,18 +1,46 @@ /* - * libdpm-core.map — the export list for libdpm-core.so + * libdpm-core.map — the linker version script for libdpm-core.so * - * Passed to the linker with --version-script. Names under `global` are - * exported; `local: *` hides everything else, so the public C API is the - * only surface a consumer or a loaded module can bind to. + * Passed to the linker with --version-script; see CMakeLists.txt, which + * also lists it as a link dependency so edits force a relink. It does + * two separate jobs. * - * `global` is every function in include/dpm/core.h and nothing else. A - * new public function is added here in the commit that adds it to the - * header. * - * The node name stamps each symbol (dpm_open@@DPM_CORE_1.0), so a break - * can ship as a new node while already-linked binaries keep resolving - * the old one. Newest node first. DPM_CORE_0.1 was never released; it is - * the shape a retired node takes. + * 1. It limits the export set. + * + * `global` names every function declared in . `local: *` + * hides everything else, including the template instantiations that + * libstdc++ headers emit with default visibility. + * + * + * 2. It versions the exports. + * + * Every symbol here is stamped with the node name: dpm_open becomes + * dpm_open@@DPM_CORE_1.0. The linker reads that node off the library a + * consumer links against and records it in the consumer's own binary, + * and at startup the dynamic linker verifies the library still provides + * it. + * + * That keeps an already-installed module working after the library + * changes underneath it, which is what makes updating libdpm-core.so + * safe mid-bootstrap. + * + * A breaking change ships as a new node while the old node keeps its + * original definitions, so binaries built against the old node keep + * resolving them. Both live in one .so under one soname, preserving the + * invariant the routing model depends on: exactly one instance of the + * library mapped per process. + * + * + * Changing this file: + * + * - A new public function goes into `global` in the same commit that + * adds it to . + * - A breaking change to an existing function adds a new node above + * DPM_CORE_1.0, leaving this node and its definitions intact. + * + * Newest node first. DPM_CORE_0.1 was never released; it is here as the + * shape a retired node takes. */ DPM_CORE_1.0 { global: diff --git a/src/modules.cpp b/src/modules.cpp index 107a998..f7710e8 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -96,7 +96,6 @@ std::unique_ptr validate_and_load(dpm_ctx* ctx, "dpm_module_execute", "dpm_module_version", "dpm_module_description", - "dpm_module_core_min", }; std::string missing; @@ -114,28 +113,12 @@ std::unique_ptr validate_and_load(dpm_ctx* ctx, return nullptr; } - auto exec_f = reinterpret_cast(resolve(handle, "dpm_module_execute")); - auto version_f = reinterpret_cast(resolve(handle, "dpm_module_version")); - auto desc_f = reinterpret_cast(resolve(handle, "dpm_module_description")); - auto core_min_f = reinterpret_cast(resolve(handle, "dpm_module_core_min")); + auto exec_f = reinterpret_cast(resolve(handle, "dpm_module_execute")); + auto version_f = reinterpret_cast(resolve(handle, "dpm_module_version")); + auto desc_f = reinterpret_cast(resolve(handle, "dpm_module_description")); - /* Step 2: minimum-version handshake. */ - const char* core_min = core_min_f(); + /* Step 2: probe the cheap calls. */ long parsed[3]; - if (!core_min || !parse_version(core_min, parsed)) { - reason = "dpm_module_core_min() returned a malformed version"; - dlclose(handle); - return nullptr; - } - if (compare_versions(core_min, DPM_CORE_VERSION_STR) > 0) { - reason = std::string("requires libdpm-core >= ") + core_min + - ", running libdpm-core is " DPM_CORE_VERSION_STR - " — update libdpm-core"; - dlclose(handle); - return nullptr; - } - - /* Step 3: probe the cheap calls. */ const char* version = version_f(); if (!version || !parse_version(version, parsed)) { reason = "dpm_module_version() returned a malformed version"; @@ -154,7 +137,6 @@ std::unique_ptr validate_and_load(dpm_ctx* ctx, mod->handle = handle; mod->version = version; mod->description = description; - mod->core_min = core_min; mod->execute = exec_f; return mod; } @@ -196,7 +178,6 @@ int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out) out->name = mod->name.c_str(); out->version = mod->version.c_str(); out->description = mod->description.c_str(); - out->core_min = mod->core_min.c_str(); return 0; } @@ -264,7 +245,6 @@ dpm_cursor* dpm_list_modules(dpm_ctx* ctx) info.name = mod->name.c_str(); info.version = mod->version.c_str(); info.description = mod->description.c_str(); - info.core_min = mod->core_min.c_str(); cur->infos.push_back(info); } diff --git a/src/version.cpp b/src/version.cpp index 6ff1d4b..4d09177 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,6 +1,6 @@ /** * @file version.cpp - * @brief X.Y.Z version parsing and comparison + * @brief X.Y.Z version parsing * * @copyright Copyright (c) 2026 SILO GROUP LLC * @author Chris Punches @@ -60,24 +60,4 @@ bool parse_version(const char* s, long out[3]) return true; } -int compare_versions(const char* a, const char* b) -{ - long va[3] = {0, 0, 0}; - long vb[3] = {0, 0, 0}; - - parse_version(a, va); - parse_version(b, vb); - - for (int i = 0; i < 3; i++) { - if (va[i] < vb[i]) { - return -1; - } - if (va[i] > vb[i]) { - return 1; - } - } - - return 0; -} - } // namespace dpmcore diff --git a/tests/fixtures/src/bad_version.cpp b/tests/fixtures/src/bad_version.cpp index 0d9cdc2..e29ca58 100644 --- a/tests/fixtures/src/bad_version.cpp +++ b/tests/fixtures/src/bad_version.cpp @@ -30,11 +30,6 @@ extern "C" const char* dpm_module_description(void) return "Fixture with a malformed version."; } -extern "C" const char* dpm_module_core_min(void) -{ - return "0.1.0"; -} - extern "C" int dpm_module_execute(void* ctx, const char* command, int argc, char** argv) { diff --git a/tests/fixtures/src/core_too_new.cpp b/tests/fixtures/src/core_too_new.cpp deleted file mode 100644 index 86bd614..0000000 --- a/tests/fixtures/src/core_too_new.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file core_too_new.cpp - * @brief Broken fixture: demands a libdpm-core newer than any that exists - * - * Contract-complete, but dpm_module_core_min() reports 99.0.0. - * libdpm-core must refuse it at validation step 2 with a message - * naming the remedy (update libdpm-core). - * - * Part of the Dark Horse Linux Package Manager (DPM) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -extern "C" const char* dpm_module_version(void) -{ - return "1.0.0"; -} - -extern "C" const char* dpm_module_description(void) -{ - return "Fixture demanding a future libdpm-core."; -} - -extern "C" const char* dpm_module_core_min(void) -{ - return "99.0.0"; -} - -extern "C" int dpm_module_execute(void* ctx, const char* command, - int argc, char** argv) -{ - (void)ctx; - (void)command; - (void)argc; - (void)argv; - return 0; -} diff --git a/tests/fixtures/src/good.cpp b/tests/fixtures/src/good.cpp index 48b5fdc..fffc07c 100644 --- a/tests/fixtures/src/good.cpp +++ b/tests/fixtures/src/good.cpp @@ -34,11 +34,6 @@ extern "C" const char* dpm_module_description(void) return "Known-good stub module."; } -extern "C" const char* dpm_module_core_min(void) -{ - return "0.1.0"; -} - /* Answers "ping" with 42 so a dispatch round trip is observable, and 0 for anything else. */ extern "C" int dpm_module_execute(void* ctx, const char* command, diff --git a/tests/fixtures/src/missing_symbols.cpp b/tests/fixtures/src/missing_symbols.cpp index f53c82f..acd0e2d 100644 --- a/tests/fixtures/src/missing_symbols.cpp +++ b/tests/fixtures/src/missing_symbols.cpp @@ -2,9 +2,8 @@ * @file missing_symbols.cpp * @brief Broken fixture: exports only part of the module contract * - * Missing dpm_module_execute, dpm_module_core_min, and - * dpm_module_manifest. libdpm-core must refuse it at step 1 and - * name the missing symbols. + * Exports the version and description probes and nothing else. + * libdpm-core.so refuses it at step 1 and names dpm_module_execute. * * Part of the Dark Horse Linux Package Manager (DPM) * diff --git a/tests/test_core.cpp b/tests/test_core.cpp index f2b3dbb..f15ea29 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -102,9 +102,6 @@ int main(void) CHECK(error_contains(ctx, "missing required contract symbols")); CHECK(error_contains(ctx, "dpm_module_execute")); - CHECK(dpm_require(ctx, "core_too_new") == nullptr); - CHECK(error_contains(ctx, "update libdpm-core")); - CHECK(dpm_require(ctx, "bad_version") == nullptr); CHECK(error_contains(ctx, "malformed version")); @@ -125,7 +122,6 @@ int main(void) CHECK(dpm_module_info_of(ctx, good, &seen) == 0); CHECK(std::strcmp(seen.name, "good") == 0); CHECK(std::strcmp(seen.version, "1.2.3") == 0); - CHECK(std::strcmp(seen.core_min, "0.1.0") == 0); CHECK(seen.description != nullptr && *seen.description); CHECK(dpm_module_info_of(ctx, nullptr, &seen) != 0); @@ -150,7 +146,6 @@ int main(void) count++; CHECK(std::strcmp(info.name, "good") == 0); CHECK(std::strcmp(info.version, "1.2.3") == 0); - CHECK(std::strcmp(info.core_min, "0.1.0") == 0); CHECK(info.description != nullptr && *info.description); } CHECK(count == 1);