Move version compatibility to the consumer; name libdpm-core explicitly

dpm_require no longer takes a minimum version and applies no version
criterion of its own. A handle now means the module is valid, not that
it suits the caller. dpm_module_info_of is added alongside it, reporting
the name, version, description, and minimum-libdpm-core version read at
load, so a consuming module can judge a dependency's version for itself.
The one rule still enforced is the minimum-version handshake, where
libdpm-core is the host and refuses a module that demands a newer library
than the one running.

dpm_module_info_of joins the version script, so the exported surface is
now fourteen symbols under DPM_CORE_1.0.

Separately, the bare word "core" is gone from prose everywhere. It named
both the command-line tool and the library, so every use forced the
reader to guess which. Text now says "the dpm binary" or "libdpm-core".
Identifiers keep their spelling: libdpm-core, core.h, core.conf, the
"core" configuration namespace, dpm_core_version, core_min, DPM_CORE_1.0,
the dpmcore namespace, test_core, core_api.

Three user-visible strings changed with it: the load-refusal message now
reads "requires libdpm-core >= X, running libdpm-core is Y — update
libdpm-core", and the info module's description and help text name the
library. The test asserting on the refusal text was updated to match.

DESIGN.md's terminology line no longer defines "DPM Core" as the CLI,
which was the source of the ambiguity. OVERVIEW.md is restructured around
the three layers a reader meets DPM at — user, developer, filesystem —
so a code-level symbol never appears without saying whose layer it is.
MODULES.md describes the bundled info module as testing and demonstrating
full DPM system functionality rather than as a reference implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 00:38:21 -04:00
parent da35fe4758
commit c41adc2498
22 changed files with 327 additions and 243 deletions

View File

@@ -1,27 +1,27 @@
# Developing DPM Modules
A DPM module is one shared object in the module directory. Core loads it, validates it completely, routes CLI commands to it, and serves its functions to other modules and to programs linked against libdpm-core. This document covers writing, building, testing, and installing a module.
A DPM module is one shared object in the module directory. libdpm-core loads it, validates it completely, routes commands to it, and serves its functions to other modules and to programs linked against libdpm-core. This document covers writing, building, testing, and installing a module.
## The module contract
Every module exports the following symbols as extern "C". All returned strings must be non-NULL, static or module-owned, and valid for the lifetime of the loaded module; callers never free them.
**int dpm_module_execute(dpm_ctx\* ctx, const char\* command, int argc, char\*\* argv)**
The generic command entry point. `ctx` is the host context that dispatched the call; the module reaches every core service (dpm_log, dpm_config_get, dpm_module_path, ...) through it. `command` is the subcommand name, equal to argv[0] when argc > 0. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup.
The generic command entry point. `ctx` is the host context that dispatched the call; the module reaches every libdpm-core service (dpm_log, dpm_config_get, dpm_module_path, ...) through it. `command` is the subcommand name, equal to argv[0] when argc > 0. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup.
**const char\* dpm_module_version(void)**
The module's own version as an X.Y.Z string. Consumers state minimum-version requirements against this value.
The module's own version as an X.Y.Z string. libdpm-core reports this value to consumers, and each consumer decides for itself whether the version suits it.
**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 core version (X.Y.Z) the module supports — the oldest core whose contract and services it was written against. Core refuses to load the module if its own version is lower, and the refusal message says so.
The minimum libdpm-core version (X.Y.Z) the module supports — the oldest one whose contract and services it was written against. A libdpm-core older than that refuses to load the module, and the refusal message says so.
**const dpm_manifest\* dpm_module_manifest(void)**
A static table declaring the module's entire functional surface: an entry count and, per entry, the API name, its table version, and the exact exported symbol carrying the table — for example { "mymodule", 1, "mymodule_api_v1" }. Only manifest-declared tables are ever handed to consumers; an API absent from the manifest does not exist, even if its symbol does. A module providing no API tables returns a manifest with a count of zero.
The dpm_manifest and dpm_manifest_entry types, the table header, and the core service declarations all come from the installed public header:
The dpm_manifest and dpm_manifest_entry types, the table header, and the service declarations all come from the installed public header:
```
#include <dpm/core.h>
@@ -33,11 +33,11 @@ A module's functions are published to consumers as API tables: one exported symb
All parameters and return values crossing a table are C types only. State passes through opaque handles; errors are int codes.
**Symbol naming**: every functional export is prefixed with the module's name (mymodule_\*). The dpm_ prefix is reserved for the contract symbols and core.
**Symbol naming**: every functional export is prefixed with the module's name (mymodule_\*). The dpm_ prefix is reserved for the contract symbols and libdpm-core.
## Validation at load
Core is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, core verifies, in order: every reserved contract symbol resolves; the core-minimum handshake passes; the version and description probes return well-formed values; every manifest-declared symbol resolves; and every declared table carries the correct magic and a sane size. 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 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; the version and description probes return well-formed values; every manifest-declared symbol resolves; and every declared table carries the correct magic and a sane size. 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.
## Building
@@ -67,7 +67,9 @@ cmake -B <build-dir>
cmake --build <build-dir>
```
libdpm-core is the only DPM link dependency a module ever has. Dependencies on other modules are runtime concerns, resolved through core's require/get_api — a peer module is never linked and never needs to be present to build.
libdpm-core is the only DPM link dependency a module ever has. Dependencies on other modules are runtime concerns, resolved through the require/get_api pair — a peer module is never linked and never needs to be present to build.
A module that depends on a peer is the party that judges the peer's version. Require it, read its reported version with dpm_module_info_of, and decide whether it is too new or too old for the calls you intend to make. libdpm-core reports; it does not rule.
## Running and testing locally
@@ -77,12 +79,12 @@ Load the freshly built module through a locally run dpm without installing anyth
dpm --module-path <build-dir> mymodule <command>
```
Core 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.
libdpm-core 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.
## Installing
Modules install to lib/dpm/modules under the install prefix (/usr/lib/dpm/modules on a distribution install). Core discovers the module on its next scan; no registration step exists beyond the file being present and valid.
Modules install to lib/dpm/modules under the install prefix (/usr/lib/dpm/modules on a distribution install). libdpm-core discovers the module on its next scan; no registration step exists beyond the file being present and valid.
## Reference implementation
## A working example
The info module bundled with the core repository at src/bundled-modules/info/ is a complete working example of the contract, an API table, and this build structure.
The info module bundled with the dpm binary and libdpm-core, at src/bundled-modules/info/, tests and demonstrates full DPM system functionality, and in doing so shows the contract, an API table, and this build structure in working form.