oficina / home Public
Operator registry and first team provisioning
Status: design approved by configured Claude Fable (claude-fable-5, verified provider metadata), 2026-09-08, reviewed commit898fe219f4da5092b2cc28210b344ede6a478e50, Home discussion37. Implementation and live-use gates below remain. This makes home-operator-seed.md's invocation, baseline and activation adapters concrete. It adds no live records, keys, configuration or command implementation.
Operator and authority
Implement a separate local Go command, oficina provision, callable directly by a human. It uses supported CARLOS APIs/CLI and the fixed Home operator command. No LLM, remote shell, SQL console or manual database edit is required. Home keeps no platform provisioning credential. The command never serves a website.
The operator authenticates to the existing CARLOS console. Every maintenance mutation still requires its actual browser session, CSRF, current account-owner 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 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 outside the signed request. It is distinct from credential owner-seed signing, directory provisioning signing and roster response keys. Compromise of this key is operator compromise; its public signature alone cannot execute anything without the maintenance fence and fresh console approval. The credential owner receipt still resolves the requested owner's identity. The operator cannot submit a person_ref in place of that receipt.
The initial baseline is an explicit trusted owner attestation: this installation has never issued a roster generation or consumer record. Deployment history, current configured versions, and fenced schema inspection inform that decision. They cannot mathematically prove historical absence after a possible restore. The review screen states this distinction and shows the exact installation and baseline digest. If the owner cannot attest it, enrollment refuses and recovery requires its separately reviewed procedure. This is the only initial-install exception; retries and ordinary startup cannot repeat it.
Registry custody and persistence
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 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. SQLite uses one writer connection, foreignkeys ON, securedelete ON and synchronous FULL. Schema includes registry identity/version; installations keyed by exact console/account/app/primary host; immutable operation records; encrypted private inputs/results; and evidence records. Every installation has one current sequence, head digest, enrolled directory identity, baseline operation and current generation. Only the provisioner command writes these rows.
Every record carries registry/device ID, installation ID, monotonically increasing sequence, previous record digest, random operation ID, fixed action, expected state, created time and digest of randomized sealed private payload. Sign the canonical closed record with Rastrillo crypto.Sign context oficina/provisioner/record/v1. Write the immutable signed record and ciphertext with the installation's new head in one transaction using sequence+head CAS. No update/delete API exists for records. A reused operation ID with identical typed request returns its stored record/result; changed input refuses. Retain 10,000 operations per registry and refuse new work at capacity until a reviewed archive policy exists. Advance state before dispatch, then append outcome evidence after receiving the exact private result. An ambiguous remote result leaves an in-flight journal record; it never causes an automatic second Home operation.
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. 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 floor CAS primitive may be serialized by the exclusive device lock; re-read before write 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 records, private payloads and wrapped recovery keys through a password-protected standard age container using pinned filippo.io/age v1.3.2 and its scrypt recipient/identity APIs. No custom password encryption is introduced. 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 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 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.
Signed private operation contract
The Home command receives one closed private JSON body of at most64KiB: {version:1, signed_authorization, operation_input}. signedauthorization is a closed signature envelope capped at16KiB; operationinput is bounded canonical JSON capped at48KiB and also subject to the64KiB total. Its exact typed encoding is reused for digest and dispatch; alternate encodings, duplicate/unknown fields and trailing JSON refuse. The existing operator operation fields live inside operation_input. The signed authorization binds SHA256 of those exact canonical bytes plus role, exact configured origin, installation ID, registry/device ID, per-role sequence, previous authorization head, operation ID, issued/expires times and purpose. The local journal's global sequence is bound as a reference as well. The platform encrypts this body for its worker through its approved private input protocol, decrypts there and supplies it on private stdin. Home-specific code does not parse the platform envelope or assume it can decrypt platform-held material. The public journal stores only a digest of randomized ciphertext; the signature with a potentially guessable plaintext digest stays inside encrypted private input. The Home command receives no registry decryption key.
Sign authorization using context oficina/home/operator-input/v1, with duration at most300 seconds and exact fixed operation allowlist. Each Home role validates the pinned public key, envelope bounds, exact origin/role, receipt clock and typed request digest before constructing its unexported operator context. Persist the accepted registry identity, device identity, per-role sequence and head in its own operator schema with the operation result. Require sequence exactly previous+1, correct previous head, or exact already-committed replay. Each role has its own ordered authorization stream because credential and directory actions interleave; the outer journal's global sequence remains independent. Do not advance the per-role accepted stream when merely preparing a request. A refused/expired request gets an immutable outcome in the global journal, while the next per-role slot stays unaccepted. A replacement authorization for that slot requires proof the old attempt is not running and its authorization has expired; otherwise the provisioner waits/refuses. An ambiguous attempt must be reconciled, never replaced. Successful Home acceptance updates its stream and result in the same transaction. A lost accepted operation result is recovered by replay of the same signed authorization, which may return an already-committed result after expiry but cannot perform a new mutation.
Read-only inspect has no accepted-stream mutation: it validates a separately signed inspect challenge bound to role/origin and expiry, returns challenge plus observation time with the state, and never advances authority or mints a seed. The private platform result channel binds that observation to the exact attempt. It cannot initialize an absent authority stream or be submitted as owner proof.
Initial trust enrollment pins registry/device identity and operator public key through the reviewed per-instance protected configuration, preserving all existing keys. A changed pinned identity/key does not reset stored sequence/head. The first operation requires a create-only expected-empty operator authority table and a signed baseline, approved through the same fresh console gate. Thereafter absent operator state alongside an existing baseline/generation is a refusal. Provisioner and Home pin both key ID and exact encoded public key; no trust-on-first-request.
The platform supplies only its accepted maintenance execution boundary. Home validates the private signed authorization itself; it needs no invented signed platform receipt. The fixed operator entry point is enabled only for maintenance execution of the immutable configured release. Normal HTTP startup does not expose it. Tests must prove direct app requests cannot invoke it.
Installation and evidence sequence
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 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 are retained. No credential sign-in is fabricated for the owner.
- Apply reviewed additive operator/roster schemas using a normal verified release, preserving role boundaries. Load separately generated operator keys through protected per-instance configuration. Verify actual host version in both body and X-Carlos-Version before maintenance; record response digests and time.
- Fenced directory inspect confirms exact DB/schema/role/config and no generation or consumer history. The external signed baseline binds this observation and the owner's never-provisioned attestation. Generate a random32-byte generation in the registry and include it in the signed initialize request. Directory persists that exact authorized generation once; it does not substitute another random value.
- 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. 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.
- Submit directory.seed-team with that receipt, exact proposed ID, reserved label, sealed-input display name and generation. A raced proposal or expired receipt requires a new explicit plan/approval. A receipt cannot seed a sibling team's owner.
- Create an isolated Docs platform instance through existing supported instance APIs, with a distinct DB namespace, instance/content/DEK keys, per-instance environment and consumer ID. Never clone existing Home/Docs keys or documents. Attach the exact canonical team alias using
carlos domains attach --app ... --route ...; verify the returned current domain binding, primary route and environment. - Register the exact consumer with credential Home by updating its bounded immutable HOME_CONSUMERS manifest through protected environment configuration. Preserve every existing registration and all current assertion keys. Read back the public manifest and protected configuration delivery result, then use the fixed credential compatibility checker under the maintenance environment binding to verify the active manifest digest and public key fingerprints. Verify the actual restarted credential host version. Its normal GET /authorize validation can confirm that this consumer/audience pair reaches the expected sign-in flow without sending mail; it cannot alone prove key custody. Current source allows16 entries/8192bytes and requires directory registration; stop at the bound. Scalable registration is separate work.
- Provision the pending directory consumer. Verify its returned signed record using the independent pinned directory operator public key. Install the exact record and private bearer into the isolated Docs config using supported protected delivery. The local journal records ciphertext digests and the checked maintenance environment binding, never secret values in public receipts.
- Collect activation evidence and publish only through the approved activation rule below. Verify completed two-team browser acceptance using real synthetic identities and actual Home credential authentication; no pre-minted session is live evidence.
Evidence is collected by typed adapters, not CLI sentence parsing: use the supported console/domain/instance/version response structs or add a reviewed JSON CLI output for a currently text-only command. Source platform dcf0cad instances_cli.go, domains_cli.go, cli.go:routeVersionCheck, internal/console/apicli.go; Home171 internal/home/runtime.go HOME_CONSUMERS validation and exchange.go authorization validation. Current domain responses distinguish derived State from legacy Status: require State=live (not merely Status=live), then verify actual TLS/version. Domain claim alone does not prove materialized TLS. Current configGet has no revision field and never returns secret values; absence is not a successful secret/config comparison. Use maintenance's immutable protected environment binding and a fixed non-serving application compatibility checker against that snapshot. The Home checker reports only exact origins, active public operator/assertion/roster key fingerprints and consumer-manifest digest. The Docs checker reports exact TeamMode bindings, verified provisioning record digest, generation and SHA256 of the canonical bearer already present in protected config. These are bounded private results, never public logs. Checkers make no network calls, mint no sessions and change no DB/key/authority. They require separate small implementation/review slices; no current API is claimed to supply these application-specific facts. Existing secret fingerprints are compared privately to enrolled fingerprints, without copying existing private keys out. Dial only configured canonical HTTPS hosts with normal certificate validation, no redirects unless the specific sign-in probe validates every expected hop. Compare full expected version in body/header and current platform route/alias mapping. Store bounded response digests, typed binding facts and timestamps in the encrypted signed journal; token, email, assertion and unrelated response content are never retained in evidence.
Activation gate and pending roster integration
An activation authorization signs exact consumer/team/app/audience, generation, record sequence/digest, deployed release/artifact digest, checked maintenance environment binding, credential registration digest, materialized alias binding, verification start/end time and the versioned acceptance policy. The local provisioner only emits it after its typed collectors pass. It is bounded to16KiB, expires within300 seconds, and is approved in the fresh console maintenance request. The Home command verifies this signed authorization and current stored bindings again before switching live; it never trusts a request's boolean or opaque reference as evidence.
There is an ordering conflict to resolve explicitly: current roster producer32fb serves only live teaminstances, but a real TeamMode startup/read needs a roster before its instance can be accepted for live entry. Proposed narrow amendment for review: add a separate directory-only consumer verification-window table with consumer primary key/FK, expiry and audited opening operation. Verification and activation audit/history rows use new additive tables; preserve the existing rosteroperatorreceipts CHECK enum unchanged. A signed operator verify-consumer operation may grant one pending binding at most600 seconds of roster reads, using an absolute issuedat/expires_at signed into its authorization. This grants actual app standing to that team's membership holders through its exact consumer; it is not merely a read-only probe. It grants no item permissions. The producer still validates its exact enabled bearer/registry/route and generation; it additionally accepts pending only while this audited window is current. The chooser remains live-only. No browser assertion or consumer request can open/extend the window. Only the exact enabled consumer's current/approved overlap bearer and exact team/app/origin receive these signed pages. Other pending bindings, siblings and anonymous requests remain refused. The stored expiry is immutable; failure, read, retry and exact replay cannot extend it. Consumer disable or expiry stops subsequent pending roster reads; existing client leases retain their already-approved60-second ceiling from the last successful fetch start. Tests measure that ceiling for silent SSE and protected commits as well as requests. The activation operation clears the window atomically with live state. A failed verification leaves the instance pending; a distinct deliberately approved verification attempt needs a new signed/fresh- approved operation and audit record, and cannot overwrite the old window/history. Opening while an earlier attempt is active refuses. Completed activation is idempotent by operation ID/digest; later verification attempts for a live binding refuse. Retain opening, expiry and activation outcome records after clearing the active window. Activation compares the accepted verification attempt, exact epoch, current owner membership and evidence digest under its transaction lock; a changed epoch/owner requires new evidence and approval.
This amendment changes the approved producer contract and requires explicit Fable approval before implementation. No pending read exception is implied by approval of the core operator package alone. It enables actual two-team authentication/standing and isolation checks without listing unaccepted instances in the chooser. Initial pilot teams contain only the specifically authenticated synthetic owners. Their canary browser evidence does not claim general production or restore readiness.
Failure and acceptance
Every step records its intent before dispatch and its exact result before preparing the next step. Resume inspects the journal and supported maintenance status; it never infers success from a timeout or deletes an ambiguous record. Cross-role operations are not one database transaction: a credential stub or pending team can remain after a later failure. The journal identifies it, and only the exact reviewed retry can 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; 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 synthetic CLI rehearsal, registry export/restore at the current floor and stale-copy refusal, then actual supported platform maintenance canary. The eventual operator runbook must bound each deliberate Home maintenance outage; credential Home downtime prevents suite sign-in. All source changes need normal full gates, Fable whole-code review and verified canary before merge/deploy.