You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed from aicers/bootler#192. The RFC 0004 decomposition (umbrella aicers/bootler#208) produced this as the upstream half of a paired change, and aicers/bootler#172 settled that work touching these types lands here rather than in bootler. The tracking node stays in that tree; the pull requests for this work are opened in this repository. Cross-references below are resolved: a bare #n is an issue in this repository, and aicers/bootler#n is a node of that tree.
Add the trust-set generation document and generalize the generation engine
Summary
src/verify.rs holds a shared package verifier that takes its trust material as a caller-injected value — anchors with their revoked flags, the withdrawn-build list, the minimum accepted manifest format_version, and an active epoch — and reads nothing from disk. Nothing yet defines what that material looks like on disk, reads it safely, installs a new copy of it atomically, or decides when a delivered copy may be installed at all. This effort adds all four.
The release-signing trust set lives in its own root-owned generation tree at <etc>/release-trust/ — release-provenance trust, not PKI — and reuses the staged to validate to activate discipline that src/roxyd_trust.rs already runs for the roxyd mTLS anchor tree. Reuse means generalizing that engine, not copying it: two copies of an activation sequence drift, and only one of them gets the next fix.
Both consumers of the verifier read this material, and one of them is a root daemon that never links bootler, so the document, its reader, the engine and the install-time admission paths all live in this crate. bootler's install-time seeding of the first generation, and its operator-facing input for it, are the paired bootler issue's.
Children
Generalize the trust generation engine into a tree-neutral core — foundation
Define the trust-set generation document and its refusing reader — foundation
Install and read release-trust generations — mechanics
The two foundations touch disjoint code and can be implemented in parallel. The mechanics child needs both. The policy child needs the mechanics child and the document.
Shared background
Where this lands. Every child's pull request lands in aicers/deploy-core. None of them edits the bootler repository or moves any pin here; bootler bumps its pinned rev afterwards, and the docs/ note produced by aicers/bootler#172 is the authoritative record of that choice and of the re-pin procedure.
What the verifier owns, and what no child may change.src/verify.rs holds every cryptographic rule: Ed25519 over the manifest member's verbatim bytes, the anchor selection order, the key_id derivation, the fourteen-variant VerifyError taxonomy that downstream repositories match on, and the rule that no canonicalization algorithm exists. No child restates, reimplements or varies any of it, and no child adds a variant to that taxonomy. One child adds a pub(crate) request constructor to that file; that is the only change any of them makes to it.
Public surface. These children are a refactor plus new internal machinery, and they add almost nothing a dependent can call. The whole intended public gain is five things: the release-trust tree's layout in src/layout.rs — its path accessors, the require-trust-pin marker's name constant and that marker's accessor — the trust-set document type with its entry types and its refusal taxonomy TrustSetDocumentError, the release-trust epoch reader, the admission constructor that produces the verifier's injected trust set, and the two install-time admission entry points, together with the error types those exported functions return.
Two of those five are public for reasons the others do not share, and no child may generalise from either. The layout items, because a second repository's installer resolves the same paths and the marker has its reader in one repository and its writer in another, so a name each side spells for itself is a drift that fails silently; composing a path is not a capability, since the accessors return a PathBuf and read nothing. The document type and its taxonomy, because they are named in the signatures of the exported entry points and the admission constructor, whose consumer is a root daemon that links this crate and cannot construct any type from the installer's repository — a pub item naming a pub(crate) type is private_interfaces, fatal under -D warnings, so keeping them internal would not inconvenience that consumer but make those signatures impossible. Publishing them commits to nothing the on-disk format has not already fixed: the schema carries deny_unknown_fields at both levels and versions its own additions through trust_set_version. A public error taxonomy is also this crate's existing convention — TrustError, PayloadError and ManifestError are all pub today.
Everything else — the generalized activation core, its GenerationError, the release-trust installer, the epoch writer, the self-admission request form, the document reader and its helpers — is crate-internal, because every one of its callers is in this crate. There is no exception: no child adds a public type, function or error variant beyond that list. In particular the engine refactor publishes nothing outside src/layout.rs — its faults map onto TrustError's existing taxonomy, so a downstream exhaustive match keeps compiling. deploy-core is pinned at a git rev by two repositories, so an item that is published becomes something a consumer may depend on before anyone has decided it should be supported. Start at the most restrictive visibility and widen only when a caller outside the crate is actually named.
The envelope, common to every child that touches one. A generation is delivered as a signed .pkg whose archive block holds exactly one member, trust-set.json, and whose manifest carries exactly one artifact entry: component = the reserved trust package-id (TRUST_TARGET), version = the epoch in decimal, commit = lowercase-hex SHA-256 over that member's bytes, kind = StaticAssets. The manifest, its detached signature and its key_id hint are footer-located and are not members. StaticAssets is deliberate: the verifier pins a four-row ArtifactKind table enforced by a wildcard-free match, and a fifth variant for one document would fork that table across two repositories. The reserved trust package-id stays out of any product registry — it is trust-plane only and is never a UI-installable component.
Two trees, never conflated. The release-trust tree is a sibling of the roxyd mTLS tree at <etc>/roxyd-tls/, never inside it, and shares no directory, no generation index and no active link with it. gen-<n> is the engine's local directory index, allocated from the tree's own on-disk state; epoch is the global release-ops sequence carried inside the signed document. A host seeded at release epoch 4711 has gen-1/. Conflating them would break the engine's pruning arithmetic.
What this effort deliberately does not do. It does not implement the runtime accept path, chain replay, or re-bootstrapping a host that has fallen past the retention floor. Those belong to the runtime channel's contract and are a later effort's, built on top of what lands here; nothing in these children may foreclose them, and the last child carries an explicit compatibility requirement to that effect. It does not mint or sign a generation outside test fixtures; producing one for real is release tooling in the other repository. And it changes nothing in the bootler repository: the caller of the install-time seed, the operator input for it, and the wizard and preflight surfaces are all that repository's.
Part of aicers/bootler#194.
Execution order
Issues in the same wave have no unmet dependencies among these children and can run in parallel.
Add the trust-set generation document and generalize the generation engine
Summary
src/verify.rsholds a shared package verifier that takes its trust material as a caller-injected value — anchors with their revoked flags, the withdrawn-build list, the minimum accepted manifestformat_version, and an activeepoch— and reads nothing from disk. Nothing yet defines what that material looks like on disk, reads it safely, installs a new copy of it atomically, or decides when a delivered copy may be installed at all. This effort adds all four.The release-signing trust set lives in its own root-owned generation tree at
<etc>/release-trust/— release-provenance trust, not PKI — and reuses the staged tovalidatetoactivatediscipline thatsrc/roxyd_trust.rsalready runs for the roxyd mTLS anchor tree. Reuse means generalizing that engine, not copying it: two copies of an activation sequence drift, and only one of them gets the next fix.Both consumers of the verifier read this material, and one of them is a root daemon that never links bootler, so the document, its reader, the engine and the install-time admission paths all live in this crate. bootler's install-time seeding of the first generation, and its operator-facing input for it, are the paired bootler issue's.
Children
The two foundations touch disjoint code and can be implemented in parallel. The mechanics child needs both. The policy child needs the mechanics child and the document.
Shared background
Where this lands. Every child's pull request lands in
aicers/deploy-core. None of them edits the bootler repository or moves any pin here; bootler bumps its pinnedrevafterwards, and thedocs/note produced byaicers/bootler#172is the authoritative record of that choice and of the re-pin procedure.What the verifier owns, and what no child may change.
src/verify.rsholds every cryptographic rule: Ed25519 over the manifest member's verbatim bytes, the anchor selection order, thekey_idderivation, the fourteen-variantVerifyErrortaxonomy that downstream repositories match on, and the rule that no canonicalization algorithm exists. No child restates, reimplements or varies any of it, and no child adds a variant to that taxonomy. One child adds apub(crate)request constructor to that file; that is the only change any of them makes to it.Public surface. These children are a refactor plus new internal machinery, and they add almost nothing a dependent can call. The whole intended public gain is five things: the release-trust tree's layout in
src/layout.rs— its path accessors, therequire-trust-pinmarker's name constant and that marker's accessor — the trust-set document type with its entry types and its refusal taxonomyTrustSetDocumentError, the release-trust epoch reader, the admission constructor that produces the verifier's injected trust set, and the two install-time admission entry points, together with the error types those exported functions return.Two of those five are public for reasons the others do not share, and no child may generalise from either. The layout items, because a second repository's installer resolves the same paths and the marker has its reader in one repository and its writer in another, so a name each side spells for itself is a drift that fails silently; composing a path is not a capability, since the accessors return a
PathBufand read nothing. The document type and its taxonomy, because they are named in the signatures of the exported entry points and the admission constructor, whose consumer is a root daemon that links this crate and cannot construct any type from the installer's repository — apubitem naming apub(crate)type isprivate_interfaces, fatal under-D warnings, so keeping them internal would not inconvenience that consumer but make those signatures impossible. Publishing them commits to nothing the on-disk format has not already fixed: the schema carriesdeny_unknown_fieldsat both levels and versions its own additions throughtrust_set_version. A public error taxonomy is also this crate's existing convention —TrustError,PayloadErrorandManifestErrorare allpubtoday.Everything else — the generalized activation core, its
GenerationError, the release-trust installer, the epoch writer, the self-admission request form, the document reader and its helpers — is crate-internal, because every one of its callers is in this crate. There is no exception: no child adds a public type, function or error variant beyond that list. In particular the engine refactor publishes nothing outsidesrc/layout.rs— its faults map ontoTrustError's existing taxonomy, so a downstream exhaustivematchkeeps compiling.deploy-coreis pinned at a git rev by two repositories, so an item that is published becomes something a consumer may depend on before anyone has decided it should be supported. Start at the most restrictive visibility and widen only when a caller outside the crate is actually named.The envelope, common to every child that touches one. A generation is delivered as a signed
.pkgwhose archive block holds exactly one member,trust-set.json, and whose manifest carries exactly one artifact entry:component= the reservedtrustpackage-id (TRUST_TARGET),version= theepochin decimal,commit= lowercase-hex SHA-256 over that member's bytes,kind=StaticAssets. The manifest, its detached signature and itskey_idhint are footer-located and are not members.StaticAssetsis deliberate: the verifier pins a four-rowArtifactKindtable enforced by a wildcard-free match, and a fifth variant for one document would fork that table across two repositories. The reservedtrustpackage-id stays out of any product registry — it is trust-plane only and is never a UI-installable component.Two trees, never conflated. The release-trust tree is a sibling of the roxyd mTLS tree at
<etc>/roxyd-tls/, never inside it, and shares no directory, no generation index and noactivelink with it.gen-<n>is the engine's local directory index, allocated from the tree's own on-disk state;epochis the global release-ops sequence carried inside the signed document. A host seeded at release epoch 4711 hasgen-1/. Conflating them would break the engine's pruning arithmetic.What this effort deliberately does not do. It does not implement the runtime accept path, chain replay, or re-bootstrapping a host that has fallen past the retention floor. Those belong to the runtime channel's contract and are a later effort's, built on top of what lands here; nothing in these children may foreclose them, and the last child carries an explicit compatibility requirement to that effect. It does not mint or sign a generation outside test fixtures; producing one for real is release tooling in the other repository. And it changes nothing in the bootler repository: the caller of the install-time seed, the operator input for it, and the wizard and preflight surfaces are all that repository's.
Part of
aicers/bootler#194.Execution order
Issues in the same wave have no unmet dependencies among these children and can run in parallel.