oficina / home Public

Specify a native Linux operator custody and durable floor backend

Paul Campbell pushed by paul@keymail.dev 898fe219f4da5092b2cc28210b344ede6a478e50 parent 81040ce
1 file changed, +52 −29
  • implementation/home-provisioner.md +52 −29

CI passed — run details

diff --git a/implementation/home-provisioner.md b/implementation/home-provisioner.md
index e4c6860..2551d2a 100644
--- a/implementation/home-provisioner.md
+++ b/implementation/home-provisioner.md
@@ -17,7 +17,7 @@ lookup and `requireFreshAuth`, in that order. The reviewed maintenance request
binds its immutable encrypted input and selected artifact/config/instance. The
local signer cannot bypass that gate. A bearer has no invented auth_time. Source:
platform dcf0cad `internal/console/stepup.go`, and platform117 revised maintenance
-design (currently e899a47; wire API remains under review).
+design (currently 0c59ea2; wire API remains under review).
A separately generated provisioner signing key attests the operator's selected
installation baseline and collected evidence. Both Home roles pin this public key
@@ -39,20 +39,40 @@ exception; retries and ordinary startup cannot repeat it.
## Registry custody and persistence
-The operator runs `oficina provision init --state <directory>` once. Require an
-empty regular directory on a persistent local filesystem; create with mode0700,
-refuse symlinks, network filesystems and a preexisting registry. Init creates a
-random device ID, registry ID, signer key and encryption key in an explicitly
-selected OS keychain, then creates the registry. No secret is printed or committed.
-The first human-operated build supports macOS Keychain through pinned
-[github.com/keybase/go-keychain v0.0.1](https://github.com/keybase/go-keychain/tree/v0.0.1),
-using its native Security-framework calls, with no secret-bearing security-command
-argv. This separate local CLI may use cgo; Home itself remains CGO_ENABLED=0.
-Unsupported platforms refuse rather than saving plaintext key files. A Linux
-Secret Service adapter is separate work and must pass the same custody tests. Test
-backends exist only in disposable tests. Keys are separate entries bound to the
-registry/device ID. A DB copied without its corresponding keychain identity is
-not an initialized registry.
+The first supported workflow runs natively on this Linux operator workspace.
+`oficina provision init --state <directory> --floor-dir <independent-directory>`
+requires empty regular directories on persistent local ext4/XFS filesystems,
+mode0700, no symlinks or shared/network filesystem. The floor directory must be
+outside the state directory and its backup scope. Init creates random device and
+registry IDs plus signing/encryption keys, and stores the keys only in an age
+scrypt-encrypted credential file. Unlock through a private TTY prompt or explicit
+private password FD, never an argv/env value. Keys remain in process memory only
+while the command runs. No secret is printed or committed. Initialization writes a signed initializing
+genesis and encrypted key file before creating the registry; an interrupted init
+can resume only that exact genesis after unlocking its keys. It never silently
+replaces an existing key, registry ID or partially created floor.
+
+The independent floor backend writes signed immutable sequence/head files with
+exclusive creation, file fsync and parent-directory fsync; a durable head marker
+is updated by rename under the process lock only after the immutable file is
+complete. It stores no secret and never appears inside a registry backup. Bind
+registry/device IDs and signer public-key fingerprint in the credential file,
+registry and floor genesis. Missing or mismatched identity refuses. On open, validate all bounded immutable
+floor records and their contiguous signed chain, not only the replaceable head
+marker; an old marker cannot hide a newer completed floor file. Linux filesystem
+semantics are tested directly with process/crash injection on a supported local
+filesystem; an in-memory backend is insufficient. Unsupported filesystem types
+refuse. Copying only state or rolling back its encrypted keys cannot roll back the
+independent floor. A disk failure losing both is an unrecoverable condition in this
+slice, not permission to initialize again.
+
+An optional macOS backend can use pinned
+[github.com/keybase/go-keychain v0.0.1](https://github.com/keybase/go-keychain/tree/v0.0.1)
+through native Security-framework calls. It must satisfy the identical floor
+contract before being supported; this is not a prerequisite for the Linux pilot.
+Home itself remains CGO_ENABLED=0. No secret-bearing security-command argv or
+plaintext-key fallback exists. Keys and floor identity are bound to one operator
+device. Copying them to a second device is not a supported concurrent writer.
Use a dedicated local SQLite registry outside Home's database, deployment directory
and backup schedule. Acquire an exclusive process lock for the entire operation.
@@ -76,16 +96,16 @@ then append outcome evidence after receiving the exact private result. An ambigu
remote result leaves an in-flight journal record; it never causes an automatic
second Home operation.
-The keychain also holds the latest registry sequence and signed head digest as a
-rollback floor. A new head follows a two-phase local protocol: commit a signed
-prepared record with old/new floor to SQLite and fsync; CAS the keychain floor from
+The independent floor backend holds the latest registry sequence and signed head
+digest outside the registry backup scope. A new head follows a two-phase local protocol: commit a signed
+prepared record with old/new floor to SQLite and fsync; CAS the independent floor from
old to new; commit finalized state and checkpoint/fsync the parent directory. Before
any remote dispatch, require finalized matching floor. Crash between steps resumes
-only the single matching prepared record. Keychain ahead without the exact prepared
+only the single matching prepared record. Floor ahead without the exact prepared
record, DB ahead without a valid adjacent preparation, competing device or any
-older/mismatched head refuses. No sequence reuse follows a failed write. The keychain
+older/mismatched head refuses. No sequence reuse follows a failed write. The floor
CAS primitive may be serialized by the exclusive device lock; re-read before write
-and refuse unexpected values. A keychain backend without durable update semantics
+and refuse unexpected values. A floor backend without durable update semantics
is unsupported. Tests inject crashes at every boundary.
`oficina provision backup --output <exclusive-file>` exports encrypted registry
@@ -97,16 +117,16 @@ Use mode0600 and atomic fsync/rename. Print only the archive digest and sequence
Require independent backup storage selected by the operator and a verified restore
drill before enrolling a non-synthetic installation. Never include a plaintext key
or decrypted owner address. A lost local registry does not make init available for
-the same keychain identity. A restored archive must meet the surviving keychain
+the same surviving floor identity. A restored archive must meet the surviving independent
floor and verify every signature/hash-chain entry; an older archive refuses. If both
registry and independent floor are lost, this slice cannot recover or re-enroll;
explicit recovery design and fresh owner confirmation are required. Export/import
must be human-usable commands and carry no secrets through argv, terminal or shell
history. Passphrases use a private terminal prompt or file descriptor.
-The keychain is an independent rollback floor, not protection against an attacker
-who controls the operator's device and signing keys. Backing up registry and keychain
-together and rolling both back is excluded from safe restore; document this and test
+The separate durable floor protects against registry-backup rollback, not an
+attacker controlling the operator device and unlocked signing keys. Copying the
+registry and floor together and rolling both back is excluded from safe restore; document this and test
the refusal achievable with a surviving floor. Production recovery readiness still
requires the independent backup/restore rehearsal, not merely a second database.
@@ -172,7 +192,7 @@ it. Tests must prove direct app requests cannot invoke it.
`oficina provision enroll` prepares a human-readable baseline plan and immutable
private request. It identifies existing credential/directory primary hosts,
canonical origins, account, existing data-key fingerprints, operator public pins,
-exact release/artifact/config revisions and registry identity. Use supported
+exact release/artifact and checked configuration bindings plus registry identity. Use supported
`carlos instances list`, route/version checks and maintenance preflight;
restart's asleep output is not exec eligibility proof. The operator's fresh approval
is required on the console's exact request. Existing Home databases and identities
@@ -189,7 +209,10 @@ are retained. No credential sign-in is fabricated for the owner.
that exact authorized generation once; it does not substitute another random value.
3. Obtain the proposed team integer/sqid through inspect. Collect an approved sealed
queue request, decrypt locally only in bounded private memory, and bind its approval
- to the operation. For synthetic previews require the already-authenticated synthetic
+ to the operation. The synthetic pilot instead uses an explicit operator-authored
+ synthetic plan containing already-authenticated synthetic addresses and labels,
+ signed in this same journal and approved through the same fresh console gate;
+ this creates no fake queue row or alternate Home identity authority. For synthetic previews require the already-authenticated synthetic
identity and existing_only credential resolution. Fresh console approval runs
credential.owner-seed; its private signed receipt is encrypted into the journal.
4. Submit directory.seed-team with that receipt, exact proposed ID, reserved label,
@@ -300,8 +323,8 @@ a later failure. The journal identifies it, and only the exact reviewed retry ca
continue. Automatic compensating deletion is prohibited.
Tests cover fresh-owner refusal/removal; wrong signer/role/origin/digest; cross-role
-stream confusion; replay after expiry versus new action after expiry; keychain/DB
-floor crashes; stale backups; lost keychain/registry; concurrent processes/devices;
+stream confusion; replay after expiry versus new action after expiry; floor/DB
+crashes; stale backups; lost floor/registry; concurrent processes/devices;
create-only baseline and initial generation; exact protected config/alias/version
mismatch; pending verification expiry; wrong team/token; activation replay and
membership/owner changes between observation and commit. Run an actual human-usable