Doctor active-generation-to-provisioner handoff v1
September 11, 2026 · View on GitHub
Status: specified, not implemented. No code in this repository yet turns an installed signed generation into the fixed descriptor inventory the production provisioner consumes. This document records the design so implementation does not have to rediscover it, per issue #61's required sequencing (specify before implementing). It grants no authority by existing.
Audience: whoever implements the residual "active-generation integration" item on issue #61.
The gap this closes
Doctor signed install v1 turns one
independently authenticated release into an immutable local generation and
lets a caller activate/rollback/inspect_active it. Its nine release
members (request, bundle, launcher, worker, collector,
provisioner, the signed capsule, the release manifest, and the manifest
signature — see crates/semaprax-doctor-release/src/directory.rs) sit as
ordinary files under a held store directory.
The production provisioner never reads
a pathname. It is a dedicated process with no arguments whose only
admitted state is exactly eleven inherited descriptors (0–2 pipes, 3–5 sealed
capsule/request/bundle, 6–8 executable launcher/worker/collector images, 9 an
empty delegated cgroup-v2 directory, 10 a trusted procfs root) — see
crates/semaprax-native-rust-interop-platform-sys/src/doctor/offline_provisioner.rs::provisioned_doctor_provisioner_entry.
Nothing in the repository connects the two. crates/semaprax-doctor-release/src/main.rs
exposes only create / verify / key-info; install / activate /
rollback / recover / inspect_active are library functions with unit-test
callers only (crates/semaprax-doctor-release/src/install/unix.rs::tests).
That is the residual named "active-generation integration" in issue #61's
reframed scope.
Design
A new generation activation supervisor (proposed binary or library entry,
Linux-only, #[cfg(all(target_os = "linux", target_pointer_width = "64", target_endian = "little", any(target_arch = "x86_64", target_arch = "aarch64")))] matching the provisioner's own gate) does, in order, refusing
before any mutation on failure of any step:
- Resolve the generation. Open the store (
open_store), callinspect_active. AbsentACTIVErefuses; it never falls back to an arbitrary generation or an ambient binary. - Hold and revalidate every member. Reuse the store's existing
held-descriptor revalidation shape (
hold_members/revalidate_membersininstall/unix.rs, currentlypub(super); widen their visibility rather than re-implementing the no-follow, single-link-regular-file, digest-bound read). This is the "digest/signature/ policy bound, rejects substitution" property the issue's required evidence names: the generation directory name is already the SHA-256 digest binding every member's name, executable bit, length and bytes (DOCTOR-SIGNED-INSTALL-V1.md#layout-and-publication), so re-deriving that digest from the held descriptors before use is substitution detection, not new trust. - Reseal for the fixed handoff. Feed the held
capsule/request/bundlebytes through the existingcreate_doctor_offline_inputfactory (produces the sealed, immutable, non-executable memory file the provisioner's descriptors 3–5 require) and the heldlauncher/worker/collectorbytes throughcreate_doctor_offline_executable(descriptors 6–8). Both factories already exist and are HOSTED GREEN (DOCTOR-SEALED-INPUT-V1.md); this step reuses them rather than adding a second sealing path. Reading generation-store bytes into these factories is not the same operation as installing a release from a caller-supplied directory — the store is trusted local state under an exclusive lock, not an externally supplied path — and that distinction must stay explicit in any implementation and its tests. - Delegate the cgroup-v2 scope (descriptor 9). This is genuinely new
host-privileged work, not a reuse of an existing library function: create
an empty scope under an operator-prepared, already-delegated parent
(
SEMAPRAX_DOCTOR_GATE_PARENTis the precedent fromscripts/doctor-provisioned-linux-provision.sh, for the test gate only) and write+cpu +memory +pidsbefore handoff. The production supervisor must not invent its own delegation source (no walking/sys/fs/cgroupfor a writable parent, nosudo); an undelegated parent refuses. The provisioner independently rereads the scope's control-file inventory after inheriting descriptor 9 (offline_provisioner/admission.rs::validate_cgroup_files), so the supervisor's own correctness here is defense in depth, not the only check. - Open the trusted procfs root (descriptor 10). A plain open of the
supervisor's own
/proc; the provisioner independently requires it to reportPROC_SUPER_MAGIC. - Exec the generation's own
provisionerimage — never the checked-outdoctor_provisionerbinary — as the new process image with exactly descriptors 0–10 open (anonymous pipes 0–2, the five items above at 3–10) and every other descriptor closed first. This is the same "closed inventory, held images, no pathname exec" disciplineDOCTOR-PRODUCTION-PROVISIONER-V1.md#namespace-and-cgroup-provisioningalready requires of the provisioner's own launcher handoff; the supervisor must meet it for its own exec too, including usingfexecve/execveatover the held executable descriptor rather than a path, exactly ascreate_doctor_offline_executable's sealed,0500, no-PT_INTERPimage is designed to support.
Refusal behavior (fail-closed, no ambient fallback)
- No
ACTIVEgeneration, a corrupted/substituted member (digest disagreement on reread), a locked/contended store, a non-Linux/non-x86-64/AArch64 host, an undelegated or non-empty cgroup parent, or any sealing/exec failure all refuse before invoking the provisioner. None of these conditions may select an ambient tool, a different generation, or the checked-out provisioner binary — matchingDOCTOR-SIGNED-INSTALL-V1.md's "no pathname-only fallback" invariant and the ordinary CLI's existing "reports unavailable checks" behavior insrc/doctor.rs, which this supervisor does not touch. - Rollback and restart: because
ACTIVEis a single held record whose pivot is proven sticky under injected faults (post_pivot_faults_are_sticky_and_resolvable_by_inspection,crates/semaprax-doctor-release/src/install/unix.rs), a supervisor that merely readsinspect_activebefore each invocation — rather than caching a generation id across restarts — automatically picks up an operator'srollbackand never reactivates a revoked generation from stale state. That "readACTIVEfresh every invocation, never cache" rule is the whole restart-safety argument and should be stated as a requirement on the supervisor, not left implicit.
Nonclaims
- This does not activate the ordinary
semaprax doctorCLI.src/doctor.rshas no reference to the install store,ACTIVE, or the provisioner and this document does not change that; ordinary CLI activation remains a separately gated, unimplemented decision (issue #61's acceptance criterion "Ordinary CLI activation remains unavailable until its own acceptance criteria pass"). - This does not implement macOS or Windows confinement. It is Linux-only by construction, same as the provisioner it feeds.
- This does not grant cgroup delegation authority; it consumes a
precondition the trusted host/operator must already have prepared, the same
precondition
scripts/doctor-provisioned-linux-provision.shstates for the test gate. - This document is not implementation evidence. No supervisor code exists;
every module referenced above under
#[cfg(target_os = "linux", ...)]cannot even be type-checked from a non-Linux development host, so writing and claiming this code without a Linux build/test cycle would be exactly the "unrun evidence presented as evidence" pattern issue #61's reframing exists to prevent. Implementation needs a session with real Linux build/test access (the hostedubuntu-latestrunner used for the provisioned-gate work is precedent that this is available, just not from this worktree), its own hostile-input fixtures for every refusal case above, and its own executable evidence gate before any status line here or in the owning specs may claim more than "specified."
Open judgement calls for whoever implements this
- Whether the supervisor is a new dedicated binary (matching the provisioner's own "no argument surface" discipline) or a mode of an existing tool; a new dedicated binary is the safer default given the fixed-descriptor contract's "no environment-selected behavior" requirement.
- Whether
hold_members/revalidate_membersshould be widened topub(crate-visible) for reuse, or whether the activation supervisor should live insidesemaprax-doctor-releaseitself to keep them private. Keeping them private and adding the supervisor to the same crate avoids widening a security-relevant internal API's visibility for a single external caller.