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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <dpm/core.h>
|
||||
```
|
||||
|
||||
## 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 <build-dir> mymodule <command>
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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 `<dpm/core.h>` 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 `<dpm/core.h>` 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user