docs(website): settle phase 4's shape — delivery #141
Reference in New Issue
Block a user
No description provided.
Delete Branch "docs/module-phase4-plan"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 3 closed with core at 158 routes and
module-uoreleasable. Phase 4 is the first phase that is not about the boundary — it answers how a module gets onto a box an operator owns, and how it comes off again.Adds
MODULE_SYSTEM.md§2.7.2, and amends §2.4 and §2.5 where this phase changes what they say.The measured starting state
The producer side is finished and the consumer side is empty.
module-uo'srelease.ymlalready publishes the tarball, an install manifest ({id, name, version, coreApi, artifact, url, sha256, size}) andSHA256SUMS, andmodule.jsonalready declarespurge.sql. Core already has theinstalled_modulesprovenance columnssource/sha256that §2.4 carved out and nothing has ever written non-null, plus the livestateGuardand the model'senable/disable/remove.Core has no code that fetches, verifies, unpacks, removes or purges anything; no admin route; no client screen; and no container-start resolution —
docker-compose.yml's own comment still tells the operator to untar by hand.The four decisions
Restart is a button, not an instruction. Install, uninstall and re-enable only take effect at boot (§1.12). Recording a pending change and telling the operator to go restart the container contradicts §2.4's "recoverable from the admin panel, with no shell access to the box", so an admin action runs
moduleLifecycle.shutdown()and exits cleanly — therestart: unless-stoppedthe shipped compose file already declares brings it back. Not universally true (a barenpm startdoes not come back), so the screen says what it is about to do.The install source is a pasted manifest URL, not a catalog. A catalog would make core's release cadence decide which modules exist, and would make a module from outside this org uninstallable. The safety is not the catalog — it is the declared
sha256(module-uo's own CHANGELOG already calls it the trust anchor) plus anhttpshost allowlist, which is also what stops the endpoint being an SSRF primitive.Disable stops the module; enable asks for a restart. As Phase 2 built it, disable flips one field and the guard 404s — the module becomes invisible, not stopped. Concretely, module-uo's
uoLinkSocketstays connected and keeps ingesting shard events intoshard_*tables, andshardBroadcast's SSE streams stay open, becauseonShutdownis dispatched only from the signal handler. That is fine for "not today" and wrong for the case the button exists for. Disable now dispatches that one module'sonShutdownfirst.Enable cannot mirror it: there is no
onBootre-dispatch path and the contract never promised the hooks are re-entrant, so enable flips the row and offers the restart. Re-entrant hooks are recorded as a candidate for a futureMODULE_API_VERSIONmajor bump, beside the identity-provider gap the rust dry run found.The declarative Docker set is an environment variable — declared in the file a compose-managed host already edits and version-controls, rather than a second config file on the volume. Resolution runs before the server starts and is idempotent and offline-safe: an already-unpacked module at the declared version is a no-op, so a restart with the network down comes up unchanged.
Slices
module-uo(keyModule-uo) — 75 files of extracted code that has never been scannedModule-uo,docswebsite,docswebsite,docswebsite,docsdocs,websiteSlice 0 is independent and lands first. Unlike Phase 3 this is mostly core-only, so the two-repo merge dance does not apply —
module-uois touched only by slice 0.The unpack is the dangerous part, and it is not the download
The download is solved: allowlisted
https, a declaredsha256, a size cap. Unpacking is where the archive chooses filenames — and core writes into a bind-mounted directory it shares with the host, so an escape is not confined to the container.The extractor rejects rather than sanitises (an archive that needs correcting is an archive that should not be trusted): absolute paths,
..segments, any entry that is not a regular file or directory, more than one top-level directory, a top-level name that is not the manifest'sid, and caps on unpacked size and entry count. It unpacks to a temporary directory beside the target and moves into place only once the whole archive is accepted, so a rejected or interrupted install never leaves a half-module for the next boot's scan to find.taris a real dependency rather than a hand-rolled parser — the format has enough shape (long-name extensions, PAX headers, sparse entries) that parsing it is exactly the kind of code this list defends against.Amendments
Docs only; no code in this PR.
AI disclosure
🤖 Generated with Claude Code