Title-case every page and section heading

DPM is public-facing, and its headings read as titles: capitalized
throughout, with articles, conjunctions, and short prepositions left
lowercase in the middle.

Applies to the markdown documents and to the Doxygen page and section
titles in the public header, so the generated reference matches. One
prose cross-reference in DESIGN.md follows the section it names.
This commit is contained in:
2026-08-15 20:49:29 -04:00
parent 38bd9314cc
commit 99173d27ea
6 changed files with 57 additions and 57 deletions

View File

@@ -7,7 +7,7 @@
- Every implementation exists exactly once, and is consumable by the `dpm` binary, by other layers, and by external programs (build systems, Dark Horse tooling) through C interfaces.
- No module ever links, includes, or hardcodes anything belonging to another module. Every module-to-module interaction passes through libdpm-core.so.
## Architecture overview
## Architecture Overview
```
the dpm binary -> <dpm/core.h> -> module A -> <dpm/core.h> -> module B
@@ -18,7 +18,7 @@ a build system -> <dpm/core.h> -> module A -> <dpm/core.h> -> module B
The header names no module and carries no module-specific type. It offers discovery and interaction, and that is the entire vocabulary — which is what allows the `dpm` binary, a build system, and a module to all use it without any of them being privileged.
### One process, one library instance
### One Process, One Library Instance
The whole chain executes in the caller's address space. The `dpm` binary (or the build system) is the process; libdpm-core.so is mapped into it; every module the library loads is mapped into it as well. Calls are direct function calls — no subprocess, no serialization, no output parsing.
@@ -26,7 +26,7 @@ A module links libdpm-core.so like any other consumer, and when the module is lo
Modules are loaded with `RTLD_LOCAL`, so a module's symbols never enter the global namespace. The only symbols a module can resolve are libdpm-core.so's. No path from one module to another exists that does not pass through the library.
## Versioning model
## Versioning Model
Every version question is settled by the party that has to live with the answer. libdpm-core.so reports; it rules on nothing.
@@ -49,7 +49,7 @@ It provides:
All functions are extern "C". All returned strings are owned by the library (or by the module that produced them), are valid until the context is closed, and are never freed by the caller. All functions returning int use 0 for success and nonzero error codes; details of the most recent failure are retrievable per-context.
### Context lifecycle
### Context Lifecycle
**`dpm_ctx* dpm_open(const dpm_open_overrides* overrides)`**
Creates a context. Reads configuration from `/etc/dpm/conf.d/` (or the config directory named in overrides), resolves the module path (overrides take precedence over config, config over the built-in default), and initializes logging per configuration. Performs no module loading. Returns NULL only on allocation failure or an unreadable/invalid explicit override; a missing config directory is not an error — defaults apply. `overrides` may be NULL, and may specify: config directory, module path, target root (for chroot/image/sysroot operation), and log level. Multiple simultaneous contexts with different roots are legal.
@@ -57,10 +57,10 @@ Creates a context. Reads configuration from `/etc/dpm/conf.d/` (or the config di
**`void dpm_close(dpm_ctx* ctx)`**
Releases the context: unloads every module handle it issued, closes log targets, frees all memory owned by the context. All handles and strings obtained through the context are invalid after this call. NULL is a no-op.
### Module acquisition
### Module Acquisition
**`dpm_module* dpm_require(dpm_ctx* ctx, const char* name)`**
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.
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, 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.
@@ -81,7 +81,7 @@ Advances the cursor. Fills `out` with the next module's name, version, and descr
**`void dpm_cursor_free(dpm_cursor* cur)`**
Releases the cursor. NULL is a no-op.
### Services (available to modules and external consumers alike)
### Services (Available to Modules and External Consumers Alike)
**`const char* dpm_core_version(void)`**
Returns the library version as a static X.Y.Z string. Callable without a context.
@@ -98,7 +98,7 @@ Returns the resolved module directory path for this context.
**`const char* dpm_last_error(dpm_ctx* ctx)`**
Returns a human-readable description of the most recent failure recorded on this context, or NULL if none. Overwritten by the next failing call on the same context.
## Module contract
## Module Contract
A module is one .so in the module directory. It includes `<dpm/core.h>`, links `-ldpm-core`, and exports the following reserved symbols as extern "C". Returned strings are static or module-owned, non-NULL, and valid for the lifetime of the loaded module; the library and consumers never free them.
@@ -117,7 +117,7 @@ Returns a one-line human-readable description, used in module listings.
**A module publishes no headers to other modules.** Its capabilities are addressed by command string through `dpm_execute`, so nothing about its internals — types, struct layouts, symbol names — is ever compiled into a caller. A module's documented command vocabulary is its interface.
## Load-time enforcement
## Load-Time Enforcement
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:
@@ -126,7 +126,7 @@ libdpm-core.so is the sole authority on module validity; the contract above is e
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.
## Module: raw — the file-based installer
## Module: raw — The File-Based Installer
Ships with the base system alongside libdpm-core.so. Depends on the baseline only; archive decompression is vendored in, and the package format is chosen to keep that small. This is what makes barren-environment operation possible: libdpm-core.so plus raw function with nothing else present.
@@ -134,7 +134,7 @@ Ships with the base system alongside libdpm-core.so. Depends on the baseline onl
- **Operations**, addressed as commands: install a package file, remove, verify, and queries answered by walking the tree — slow but always correct, zero dependencies.
- **Owns the lock file and a transaction journal** with a generation counter. Every mutation in the entire system ultimately passes through raw, so locking and journaling are implemented exactly once and inherited by every layer above.
## Module: pkg — the full package manager
## Module: pkg — The Full Package Manager
Ships as a package, installed by raw once sqlite3 is installed. Requires raw — by name, through libdpm-core.so, judging raw's reported version itself — and libsqlite3.
@@ -143,11 +143,11 @@ Ships as a package, installed by raw once sqlite3 is installed. Requires raw —
- Adds what the cache enables: fast queries, dependency resolution against the installed set, multi-package transactions with rollback.
- Consumers that want dependency-aware operations dispatch to pkg by name; they transitively get raw's guarantees because there is no second code path to the tree.
## The dpm binary
## The dpm Binary
`dpm` is argument parsing and printing. It includes `<dpm/core.h>`, links `-ldpm-core`, enumerates modules, and forwards subcommands through dispatch. Its command surface is exactly the set of loadable modules — in the barren case that's raw's commands; on a full system, everything installed. No capability logic lives in it.
## External consumers
## External Consumers
Build systems and Dark Horse components link `libdpm-core.so` as an ordinary shared library dependency — `#include <dpm/core.h>`, `-ldpm-core`, the same as any other library they link:
@@ -158,7 +158,7 @@ Build systems and Dark Horse components link `libdpm-core.so` as an ordinary sha
The header installs to the standard include path and the library to the standard lib path. A consumer that opens a default context (no overrides) is working against system configuration, the system module path, the system tree, and system locking — the same environment the installed `dpm` binary sees, because that binary is just another caller of the same library. Overrides redirect individual paths only when a caller explicitly sets them. Whether a consumer addresses raw only (image builders that just deploy trees) or pkg (dependency-aware tooling) is their choice of require(); behavior is identical to the `dpm` binary's because it is the same implementation.
## Bootstrap chain
## Bootstrap Chain
```
minimal start: dpm + libdpm-core.so + raw module (baseline deps only)
@@ -169,7 +169,7 @@ now: libdpm-core.so discovers pkg, validates it, full management is
Every layer is a package installed and upgraded by the layer beneath it; the package manager maintains itself with the same mechanism it offers the OS. Future modules follow the identical pattern — a repo/network module declares its requirements (pkg, a TLS library), lands as a package, and the capability appears on next discovery.
## Repository structure
## Repository Structure
Modules are developed independently from each other and independently from the library — one repository per module, plus the libdpm-core.so repository. Each repo owns its source, build, and tests, and produces exactly one artifact:
@@ -179,7 +179,7 @@ Modules are developed independently from each other and independently from the l
No repository can block another's development: a module builds and its full pre-integration test surface runs with nothing present but its own checkout and an installed or vendored libdpm-core.so. Release coordination happens through the versioning model — each consumer judging the versions it is handed — rather than through lockstep builds.
### Layout of the libdpm-core.so repository
### Layout of the libdpm-core.so Repository
```
include/dpm/ public headers — installed to the system include path; the
@@ -208,17 +208,17 @@ Terminology: **the `dpm` binary** names the command-line tool; **libdpm-core.so*
| `info.so` | `/usr/lib/dpm/modules/info.so` |
| modules (`raw.so`, `pkg.so`, `repo.so`, `source.so`, ...) | `/usr/lib/dpm/modules/<name>.so` |
## Development and testing
## Development and Testing
Development works because the design has no build-time coupling between peers: nothing links against a peer module, ever, and nothing includes a peer's headers, ever. "Not all the pieces are there" is the normal, permanent condition at build time. What remains resolves into three test layers, each needing strictly less than the full system.
### What a build requires
### What a Build Requires
A module compiles against `<dpm/core.h>` and links `libdpm-core.so` — the one real link dependency, and by definition the stable, always-present, baseline-only piece. Cheap to have in every dev environment, trivially vendorable as a checkout.
Peers are reached at runtime by name and command string. Building pkg does not require raw to exist anywhere, and there is no compile-time knowledge of raw to acquire — not a header, not a struct, not a symbol. A module repo therefore builds self-contained, always.
### Test layers
### Test Layers
**1. Unit tests — need nothing.** The module's implementation compiles once as an object library, linked into both the .so and a test binary. Pure logic, error paths, parsing — no library, no peers.
@@ -226,7 +226,7 @@ Peers are reached at runtime by name and command string. Building pkg does not r
**3. Integration — the only layer that needs everything, and it builds itself.** Real libdpm-core.so plus real raw, then the actual bootstrap chain into a scratch root: `dpm_open` against an alternate root, raw installs sqlite3 and the pkg package into it, the library discovers pkg, real operations run against the throwaway tree. Because alternate roots are first-class in the API, this needs a directory, not a VM. Full-distribution CI does the same with real packages.
### Day-to-day workflow
### Day-to-Day Workflow
- 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.
@@ -235,7 +235,7 @@ Peers are reached at runtime by name and command string. Building pkg does not r
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.
## Development capabilities
## Development Capabilities
During development the `dpm` binary must be pointable at a locally built library, and that library must be configurable to local paths (module path, config dir, etc.). Two mechanisms provide this:
@@ -250,7 +250,7 @@ The config-dir override matters most: once the context reads config from the loc
**Rule**: every field of the `dpm_open` overrides struct must be exposed as a flag on the `dpm` binary, so anything a linked consumer can redirect, a developer at the shell can redirect too. This holds for any future override field — nothing ships reachable from code but not from the command line.
## Evolution rules
## Evolution Rules
- **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.