Modules determine their own compatibility with the library
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 the version it is running under, dpm_core_version() reports that and the module decides for itself. dpm_module_core_min() is removed. It was a declaration handed to the library to enforce on the module's behalf, and enforcement of that kind belongs nowhere in a library that routes and hosts. The contract is now three reserved symbols and load validation is two steps: the reserved symbols resolve, and the version and description probes return well-formed values. compare_versions had no remaining caller and is removed; parse_version stays for the well-formedness probe. The core_too_new fixture went with the handshake it existed to exercise.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user