Close managed metadata readers and define admission selection
1 file changed,
+12
−4
implementation/runtime-contract-signing.md+12 −4
CI passed — run details
diff --git a/implementation/runtime-contract-signing.md b/implementation/runtime-contract-signing.md| index b115541d9..45361699c 100644 |
| --- a/implementation/runtime-contract-signing.md |
| +++ b/implementation/runtime-contract-signing.md |
| @@ -10,7 +10,7 @@ Adding RuntimeContracts alone would not satisfy the approved design. The new man |
| ## Additive wire fields |
| -Manifest gains runtime_contracts,omitempty: a sorted unique set of at most16 canonical names, using the existing internal/maintenanceguard/types.go ValidContracts: each name is 1–64 ASCII bytes matching ^[a-z][a-z0-9]*([.-][a-z0-9]+)*$, sorted strictly ascending, at most 16. Names are opaque declarations, not a platform registry: a syntactically valid unfamiliar name is preserved and compares by exact equality, never implies a known contract. Absent or an empty array means no declaration; explicit null, wrong types, duplicates, unsorted or invalid names refuse. Writers omit an empty set, preserving old emitted bytes. Contract-bearing manifests are binary releases. Declarations express the trusted promoter's approval of exact release metadata at signing time, not immutable build provenance or proof that the implementation meets the contract. If metadata was changed before promotion, the signature authenticates what the promoter then approved. Review and actual compatibility tests remain separate. The meaning and approval of oficina.docs.team-authorization.v1 belongs to its separately reviewed Docs consumer contract and trusted promoter. Platform authenticates the declaration and compares the required set; it does not certify Docs behavior, infer review completion or grant consumer authority. |
| +Manifest gains runtime_contracts,omitempty: a sorted unique set of at most16 canonical names, using the existing internal/maintenanceguard/types.go ValidContracts: each name is 1–64 ASCII bytes matching ^[a-z][a-z0-9]*([.-][a-z0-9]+)*$, sorted strictly ascending, at most 16. Names are opaque declarations, not a platform registry: a syntactically valid unfamiliar name is preserved and compares by exact equality, never implies a known contract. Absent or an empty array means no declaration; explicit null, wrong types, duplicates, unsorted or invalid names refuse. Writers omit an empty set, preserving old emitted bytes. Contract-bearing manifests are binary releases; validateManifest rejects a nonbinary kind with a nonempty declaration before ship or signing, and the verified-result constructor repeats this check. Declarations express the trusted promoter's approval of exact release metadata at signing time, not immutable build provenance or proof that the implementation meets the contract. If metadata was changed before promotion, the signature authenticates what the promoter then approved. Review and actual compatibility tests remain separate. The meaning and approval of oficina.docs.team-authorization.v1 belongs to its separately reviewed Docs consumer contract and trusted promoter. Platform authenticates the declaration and compares the required set; it does not certify Docs behavior, infer review completion or grant consumer authority. |
| ChannelPointer gains runtime_manifest,omitempty: an optional object with account and sha256. Account is the exact validated account containing the release. SHA256 is lowercase64-character hash of the exact manifest bytes read and validated by the signer. The pointer already binds app/channel/version and its signing grant; together these bind account, release identity, declared contracts and every artifact hash. The runtime_manifest object has exactly account and sha256, both required and nonempty. Explicit null, unknown members, duplicate members or case aliases, wrong types, incomplete objects and noncanonical digests refuse; only absent preserves legacy pointer parsing and bytes. Strict readers reject duplicate/case-aliased occurrences of either new outer field rather than accepting last-wins JSON. The signing payload includes the new object; old software ignoring it is excluded by the already required maintenance-aware writer/serving floor. |
| @@ -34,11 +34,17 @@ On managed wake/adoption/resume, reverify staged evidence against the current tr |
| A different binary, modified declaration, unknown signing key/grant, changed account, absent evidence, missing required declaration, malformed or truncated evidence refuses before any managed process start or capability mutation. The outside-app guard's required set is supplied by the approved lifecycle integration, not inferred from an editable manifest. A nonempty binding/declaration also selects strict verification even where the required set is empty, so removing a floor alone cannot turn contract-bearing selection into legacy fallback. No nil-manifest leave-alone or best-effort backfill is managed success. |
| +The backend must obtain an explicit admission classification before Seed, Start or StartSidecar, rather than infer it from registry.Route alone. First consult the authoritative guard decision for the primary host; a read/validation error refuses, and Managed selects strict verification. Otherwise, any present staged evidence selects strict verification (malformed evidence refuses). Otherwise inspect the staged sibling: a declaration selects strict verification; malformed or unreadable present metadata refuses. Only an affirmatively unguarded route with neither evidence nor declared contracts can use legacy execution. A caller cannot inject this classification through route fields or CLI options. The selected opaque handle carries the classification and immutable verified release data through the backend seam. The guard-wiring implementation remains a prerequisite, not a claim it exists today. |
| + |
| +For managed or contract-bearing selection, ensureStaged's binary-present/evidence-absent branch must fetch the selected signed pointer and exact manifest, verify them and the existing artifact, and atomically publish matching evidence. It must not return a parsed sibling as success. If no current pointer can prove the required exact selected version, it refuses pending an authorized selection/restaging; it cannot invent historical evidence. This recovery is required after an artifact/evidence publication crash. A signed bound pointer or fetched declaration also selects strict staging before any legacy early return. |
| + |
| +The same verified manifest source is mandatory in internal/host/caps_pass.go stagedManifestOfLiveVersion/buildPlans and internal/host/memtier.go memTier, not only release.capabilityPlan. The hourly managed caps pass verifies the live selected version's evidence and derives capability changes exclusively from that copy; disagreement or unavailable proof yields no plan and no capability or DB-path mutation for the route. It does not authorize continued serving. Managed spawn derives the cgroup tier from its verified handle; it must not call the current no-error memTier callback, which silently defaults after a read failure. Verification failure refuses the spawn. Legacy readers retain their current leave-alone/default behavior. Tests mutate the sibling after verified adoption and prove neither the hourly pass nor subsequent serving/seed/sidecar spawn consumes the changed capabilities or tier. |
| + |
| ## Normal execution handoff |
| This is a new implementation contract, not an existing feature or the maintenance launch fd3/fd4 ABI. At the pinned baseline, ExecBackend.Seed, Start and StartSidecar in internal/activator/backend_exec.go each construct a command from a pathname inside spawnInCgroup. Start reads LiveVersion before spawning; that is not an artifact identity guarantee. All three managed paths must instead receive the opaque verified release/artifact handle above and construct every command from it. Preserve each path's existing argv, environment filtering, logging and cancellation policy; derive reported version from the verified handle, not a later symlink read. |
| -For native Linux managed binary execution, reserve child fd3 solely as the read-only verified executable: ExtraFiles contains exactly that file, and exec path is /proc/self/fd/3. There is no launch-context pipe or maintenance fd4. The parent retains its handle until the child has completed; the child may inherit fd3 for its lifetime. Seed, serving and sidecar each acquire a separate verified handle and cannot share an ownership-close race. The same descriptor supplies the hash and kernel execution. Reject an unexpected ExtraFiles occupant, unsupported OS/backend, unavailable descriptor execution or any failure to retain the descriptor; never retry by pathname. No shell/script or nonbinary release is supported by this path. A pathname rename/replacement must execute the verified original inode or refuse. This assumes platform-controlled artifacts are not modified in place by trusted same-UID writers during execution; it is not isolation against malicious privileged inode modification. |
| +For native Linux managed binary execution, reserve child fd3 solely as the read-only verified executable: ExtraFiles contains exactly that file, and exec path is /proc/self/fd/3. There is no launch-context pipe or maintenance fd4. The app selects normal versus maintenance startup by the explicit argv/mode contract; it never probes fd3 to choose an authority path. The parent retains its handle until the child has completed; the child may inherit fd3 for its lifetime. Seed, serving and sidecar each acquire a separate verified handle and cannot share an ownership-close race. The same descriptor supplies the hash and kernel execution. Reject an unexpected ExtraFiles occupant, unsupported OS/backend, unavailable descriptor execution or any failure to retain the descriptor; never retry by pathname. No shell/script or nonbinary release is supported by this path. A pathname rename/replacement must execute the verified original inode or refuse. This assumes platform-controlled artifacts are not modified in place by trusted same-UID writers during execution; it is not isolation against malicious privileged inode modification. |
| Normal serving retains its existing cgroup policy; this design does not reuse maintenance PrepareStrict, whose exclusive host reservation cannot accommodate a concurrent serving child and sidecar. The existing spawnInCgroup build callback may run twice. Every invocation must build a fresh command using the same retained verified artifact descriptor, the same verified version and copied argv/environment; the callback must not re-resolve a pathname or transfer/close the handle. A cgroup fallback may change placement under the existing normal policy but cannot change executable identity or use a pathname. Test the clone3 fallback branch with a replaced staged path. Maintenance retains its separate strict no-fallback requirement. Ordinary unguarded, unbound legacy releases keep their current spawn behavior. UnitBackend/systemd and other execution paths that cannot carry this verified descriptor refuse contract-enabled releases until a separately reviewed equivalent exists. Adoption must reject such a target before capability changes, symlink publication or restart. Maintenance execute/resume uses its separately reviewed worker launch adapter, consuming the same opaque verified handle but applying its distinct fd layout; this paragraph does not mount that adapter. |
| @@ -51,9 +57,11 @@ A crash between binary staging and evidence publication leaves managed admission |
| At the pinned source, production signPointer calls are in release.go promote, Signer.Rollback and FastTrack; PromoteOnLadder delegates promotion after its same-head check. accounts.go runAccountsMigrate/isChannelPointer/verifyMigratedPointers copies signed wrappers and remains in the parser/copy inventory even though it does not call signPointer. FastTrack must preserve the unchanged account/app/version binding only after strict signature/account validation; it must never drop or replace it. If it creates a binding for previously unbound declared metadata, it uses the same checked target helper as promotion. Its existing already-waived no-op remains a no-op, not a binding repair mechanism. |
| -PromoteOnLadder's same-head shortcut stays unchanged for legacy metadata. For a contract-bearing target with an absent binding, an explicit authorized promotion to that same head must perform a CAS binding upgrade after fetching and validating exact target bytes and current pointer, retaining history and enforcing current signer/channel authorization. It is not automatic staging repair. An already valid equal binding remains idempotent; a different same-version digest refuses and requires supported restaging/release selection. A malformed present binding never upgrades through a permissive parse. |
| +PromoteOnLadder's same-head shortcut stays unchanged for legacy metadata. For a contract-bearing target with an absent binding, an explicit authorized promotion to that same head must perform a CAS binding upgrade after fetching and validating exact target bytes and current pointer, retaining history and enforcing current signer/channel authorization. It is not automatic staging repair. Preserve a current FastTrack waiver only under an authorized root signer; a scoped binding upgrade of a waived head refuses rather than silently dropping the waiver or bypassing signPointer restrictions. An already valid equal binding remains idempotent; a different same-version digest refuses and requires supported restaging/release selection. A malformed present binding never upgrades through a permissive parse. |
| + |
| +Rollback remains a channel pointer operation; it does not query or assert absence of per-host guards. For an unbound legacy current pointer, preserve the existing version-only rollback when the target manifest is missing/pruned. If the target manifest is available, validate it and attach a checked binding when it declares contracts; malformed readable metadata refuses. For a bound current pointer, require the target manifest and construct its binding afresh; unavailable/pruned metadata refuses before pointer mutation. Neither case inherits another version's binding. |
| -Rollback outside any managed required-contract floor and from an unbound legacy pointer retains the existing version-only/pruned-target behavior. If a target manifest can be read and declares contracts, it must receive a checked binding; malformed readable metadata refuses. A missing/pruned target can retain legacy pointer behavior only when the caller has affirmatively established that no managed floor applies. Missing floor authority is not that proof. When a current binding or required floor exists, rollback must fetch and validate the target and construct its binding afresh; unavailable/pruned metadata refuses before pointer mutation. This intentional restriction on managed rollback is necessary evidence, not a claim that old history records contain manifests. The caller's required floor is an authenticated lifecycle dependency, not a CLI boolean. |
| +This preserves legacy pruned-target pointer selection without claiming the selected target can serve. Each managed route independently enforces its durable floor during adoption/wake/resume; a legacy unbound rollback cannot satisfy that floor and therefore cannot start or resume the managed instance. Existing maintenance guard phases continue denying admission. Console rollback success means its pointer CAS succeeded, not that a managed route accepted it. This does not invent an app/channel-wide floor-absence authority or a host enumeration API. Tests prove an unbound legacy rollback can still update its pointer while the same selected target is refused by managed admission. |
| A retained wrapper's own signed channel is used both when retaining and replacing evidence. A replacement wrapper may use another permitted channel but must independently verify under its own channel and prove identical account/app/version/manifest/artifact. Neither wrapper substitutes for current target-channel selection authorization. Account migration cannot rewrite the account of bound evidence; it must perform an authorized target-account promotion or refuse the contract-bearing migration. |