# 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](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.
SQLite uses one writer connection, foreign_keys ON, secure_delete 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](https://github.com/FiloSottile/age/tree/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}`. signed_authorization is a
closed signature envelope capped at16KiB; operation_input 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.

1. 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.
2. 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.
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. 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,
   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.
5. 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.
6. 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.
7. 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.
8. 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 team_instances, 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
roster_operator_receipts CHECK enum unchanged. A signed operator
`verify-consumer` operation may grant one pending binding at most600 seconds of
roster reads, using an absolute issued_at/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.
