The estate's approvers
Superseded by docs/v2 chapter 9, "Who approves", which follows main. This page is kept for its history; where the two differ, chapter 9 is right. Corrected here (HOL-DOCFIND, 2026-11-01): an invite into an approver group needs a stepped-up passkey session; the console bearer is refused (
holger-auth-6).
Every Holger estate boots the same server declaration. It is generated by holger_recipe::estate_ron and staged on the holger and holger-keep media by cargo xtask estate-media. That declaration turns on the intake chain with promotion.intake.approvals: 2. An artifact therefore reaches APPROVED only after two different people approve it, and neither of them may be the principal that imported it.
Who holds which duty
| Who | Role | Duties |
console (the bearer the installer feeds every box) |
Admin | Importer, RepositoryAdmin, Auditor |
group holger-admins (the first passkey admin) |
Admin | Developer, SecurityApprover, RepositoryAdmin, Auditor. Never Importer. |
group holger-writers |
Writer | Developer, Importer |
group holger-readers |
Reader | Developer |
group holger-security-approvers |
Reader | Developer, SecurityApprover |
group holger-security-auditors |
Reader | Developer, Auditor |
The console principal never approves, because it holds no SecurityApprover duty and because it is the importer of everything it uploads. With only the console login, nothing can be approved. Two approvals need two people.
Adding a second approver
The owner is the estate's first passkey admin and holds SecurityApprover. The second approver is added through the existing invite path:
- An admin mints an invite into the approver group. This needs the admin's own passkey session, stepped up (a fresh user-verified touch of the passkey). A machine credential, the console bearer included, is refused with
holger-auth-6:
curl -sS -X POST https://<estate>/-/auth/passkey/invites \
-H "Authorization: Bearer <admin session>" \
-H 'Content-Type: application/json' \
-d '{"display": "bert", "groups": ["holger-security-approvers"]}'
The answer carries the invite secret once. It is single-use and expires.
-
The invitee opens the console's invite link with that secret and registers a passkey. The invite decides the group; the invitee cannot choose another one.
-
The invitee logs in. From then on, two approvals are possible: the owner's and the invitee's. For a third person, mint another invite in the same way.
An admin may also invite by duty instead of naming the group: {"display": "bert", "duty": "security_approver"} lands in holger-security-approvers, and {"duty": "importer"} lands in holger-writers. The group is the one with the fewest duties that grants the duty, and an admin group is never picked.
The first passkey admin holds no Importer duty. With duties configured, only an Importer ingests into incoming (HTTP and gRPC), so the admin never imports and may approve every upload. Uploads come from the console bearer or from an invited Importer.
An invite never grants admin. An invite by role ("role": "reader") lands in holger-readers and gives no approval duty. The approver group's name sorts after holger-readers on purpose, so an invite by role cannot pick it.
Walking an artifact
- An Importer (the console bearer, or a writer) uploads to
incoming. The artifact is INGESTED, and nothing serves it. - The Importer advances it to SCANNED. The required scanners run there:
vulnerabilityreads the local intel database at/data/holger/intel, andsbomreads the wheel. Then it advances to POLICY_CHECKED. - Two SecurityApprovers advance it. The first vote is recorded (1 of 2), and the second makes it APPROVED. Each vote is an attested statement in the approval evidence slot.
- A SecurityApprover or the RepositoryAdmin seals it onward: SEALED, PROMOTED, AVAILABLE. Production then serves it, and
production/simple/lists it for pip.
The wire verb is POST /-/intake/advance?name=<file>&verb=advance (HTTP) or PromotionService.AdvanceArtifact (gRPC). tests/the_estate_declaration_runs_the_intake_chain.rs in server/lib walks exactly this with the shipped declaration.
The estate's name
One medium serves every estate. The declaration says https://repo.{estate} for the crates repository's base URL, and the box fills in the estate name that the ONE box's installer feeds it (the join reply's estate, such as test3.holger.rs). An unpaired boot drops the placeholder, and the default applies.
What is not on the estate
network: Offlineis the secure side's setting. A sold estate is online, so its declaration does not set it.- Intel is online (
intel.offline: false). The box'sintel.refreshjera job fetches OSV, NVD and KEV once at boot and then daily, and lands the set through the import door's verify, anti-rollback floor and audit-first path (lane W4). Until the first refresh lands, the vulnerability scan BLOCKS every artifact at SCANNED (a missing database is never a pass). A scan never dials; it reads the local database. A signed intel bundle can still be imported throughPOST /-/intel/import,AdminService.ImportIntelBundleor theintel.inboxdrop directory. - Audit checkpoints are written every 256 records and are unsigned, because the estate has no attestation signing key.
A box whose declaration grants no duty
A RON written before duties existed (or by hand without them) would give every Admin all five duties. The box's boot then takes the estate's table itself (server_lib::auth::estate_duties, called from server/cli/src/paired.rs): an Admin subject or group holds RepositoryAdmin and SecurityApprover and never Importer, other subjects and groups keep the duties their role implies, and the groups holger-importers, holger-approvers and holger-auditors are added for invites by duty. A declaration that grants any duty, as the shipped one does, is left exactly as written. The proof is server/lib/tests/an_estate_admin_invites_its_importer_and_approver.rs: on a fresh box the first admin invites an Importer and a second approver, and a wheel reaches AVAILABLE.