oficina / home Public
Local Home operator seed and provisioning
Status: design approved by configured Claude Fable (claude-fable-5, provider event metadata verified), 2026-09-08, exact reviewed commit 898fe219f4da5092b2cc28210b344ede6a478e50. Home discussion 37 retains the verdict and earlier quota/permission refusals. This branch starts at directory runtime171. It defines an application package and local command protocol; the companion home-provisioner.md defines the authority adapters and staging proposal. No implementation, platform117 or live provisioning approval is implied.
Contracts and scope
The governing sources are Home spec §7, §10, §14 and rulings 8, 22 and 23 in docs/superpowers/specs/2026-08-30-home-design.md; approved directory design fa64e286990762aecc6913bb9076a3cb75b088c3, implementation/team-directory.md (Home discussion 24); approved roster contract 4e7b3ff, implementation/roster-contract.md (discussion 28); and approved producer 32fb49c1d5a4f0317b7291f441f78e21b7f0a11c, implementation/roster-producer-storage.md and internal/home/directorydata/roster_migrations/migrations/0001_roster.sql (discussion 33; use the migration embedded by RosterSchema if its filename changes). Shared roster 667dc8218dd5 supplies Provisioning, SignProvisioning and VerifyProvisioning. HTTP runtime design 5fbfdf0 is a separate integration review, not authority for this writer. Home discussion 35 records the source audit.
The credential role alone resolves an address with its pepper. The directory alone creates teams and memberships. An audited operator selects an approved sealed request and its intended owner; a browser assertion, a visitor, an instance bearer and the platform account owner alone supply no team-owner identity proof. The operator transport authenticates the operator's authority to run an approved action on this exact deployment. The credential receipt below binds the intended owner across roles. This preserves the operator seed exception in §14; it does not assert that provisioning is a browser fresh-owner ceremony.
This slice supports generation initialization when no generation or consumer exists, a new team with its first owner, initial consumer provisioning and activation. It provides no roster editing, token rotation, restore, owner confirmation, key rotation, sibling owner seeding or ownership transfer command. Those require their own reviewed operation and fresh-owner rules. In particular, missing generation alongside existing consumers is a refusal, never a repair.
Transport and command boundary
Platform discussion 117 owns stopping traffic and all background work, holding the database lease, selecting the immutable executable and exact database, and resuming safely. Its private-input/result protocol is generic. The proposed entry point is home operator-v1 --db <platform-selected-existing-db>, with standard output captured as the private result by the transport. The fixed command and role permissions belong to reviewed deployment configuration. No request chooses a binary, path, SQL, environment variable, shell fragment or network destination. The command never starts an HTTP server, sends mail or makes network calls.
Read one UTF-8 JSON object from private stdin with a 64 KiB hard limit including whitespace, then require EOF. Write one JSON result of at most 64 KiB to the private standard-output channel only, after transaction commit. The transport never tees or logs stdout and discards stderr. Input and result are encrypted at rest by the transport, available only through its authenticated private result channel, and never included in public status, receipts or logs. The application does not implement its own remote encryption protocol.
Within the signed private wrapper defined in home-provisioner.md, operation-input common fields are version:1, operation, and operation_id (random canonical 32-byte base64url). Operation-specific closed objects are specified below. Reject unknown or duplicate fields, trailing JSON, alternate numeric forms, invalid UTF-8, malformed canonical base64 and oversized decoded values before mutation. Strings are bounded before allocating their decoded contents. No error includes supplied values. Exit 0 means a complete committed result is available; exit 2 is fixed refusal and exit 3 unavailable/ambiguous. Neither exit code permits automatic mutation retry. The platform queries the original result or explicitly reruns the exact input under a fresh fence; Home idempotency then returns the committed result without performing the action again.
The invocation must be authorized outside the request. The local adapter accepts an immutable operation allowlist and expected role/origin/database identity from reviewed launch configuration. A request's operation only selects within that allowlist. Package constructors are private to the command adapter or receive an opaque validated operator context; exported structs cannot fabricate that context. The transport's operation ID is distinct from Home operation_id. Both must be bound by the adapter's verified invocation context. Current platform source has no signed Home invocation receipt, and this design does not assert otherwise. No body field can claim an operator identity or maintenance state. The platform must provide an authenticated invocation context or equivalent private launch configuration before command wiring is approved; an environment boolean supplied by the caller is insufficient.
Keys and durable private results
Use the existing Rastrillo family key encoding and crypto.Sign/Verify operations, with strict canonical key decoding and derived key IDs. Reuse shared seal for stored private envelopes. Do not copy signing arithmetic or browser assertion validation to create operator authority.
Credential operator signing uses a separately provisioned private key and context oficina/home/owner-seed/v1; the directory pins its public key outside every receipt. Directory provisioning signing uses its separately provisioned operator key and shared context oficina/roster/provisioning/v1. Roster response keys are also distinct. Keys arrive through the existing protected per-instance config mechanism; runtime browser assertion keys and existing data keys remain unchanged. Ordinary startup neither creates nor rotates any of these keys. Reject identical public keys across the roles within the command's configured material. Public key pin replacement is an explicit future operator migration, not an input field.
Add role-specific operator result tables through additive migrations. Credential and directory databases never receive each other's tables. Each result row has operation_id primary key, fixed action, opaque operator reference from verified invocation context, creation time, a keyed request digest, and a sealed result. The request digest is HMAC-SHA256 using an independently provisioned 32-byte idempotency key. The actual shared sealer exposes Seal/Open, not a key derivation API. No custom KDF is introduced. Digest canonical typed operation input, not raw JSON whitespace. This prevents a stored bare address/name digest becoming an offline dictionary.
Seal result bytes with the existing role sealer and purpose operator.result/v1/<exact-origin>/<operation-id>/<action>. A result can contain an authority-bearing receipt or bearer and therefore never appears plaintext in SQLite/WAL, logs or public receipts. Never persist the original address/input. Repeated exact input under the same operation ID returns the stored result; changed semantic input or different action refuses. A lost post-commit response therefore does not mint a new bearer, person or receipt. Keep successful result rows until an explicit reviewed retention policy exists; no rolling cleanup may make an old operation reusable. This initial low-volume administrative table has a hard 10,000-operation limit, refusing further new operations at capacity.
The existing rosteroperatorreceipts table retains its approved public metadata shape and SHA256 digest of the signed provisioning record; no bearer, display name, input digest containing address material or private result belongs there. The new directory result row and that receipt commit in the same transaction. Private results are bounded to 48 KiB before sealing, leaving transport space.
Operations
All origins come from validated configuration or must equal it exactly. Production team destinations reuse the compiled registry and canonical label policy from directoryFeatures and directorydata.Config.ValidateRoute; for Docs the origin is https://<label>.docs.oficina.works. Reject alternate ports, paths, aliases, userinfo, fragments, queries and unapproved app slugs. This design adds no pilot manifest or per-team config exception.
directory.inspect accepts no payload and returns only current generation (or explicit absent), the next proposed team integer, and derived team sqid. It writes no authority and is not a reservation. Derive next ID from the maximum existing team ID and sqlite_sequence, then add one, refusing signed-64-bit overflow. No shared Home/suite codec exists today. Pin official github.com/sqids/sqids-go v0.4.1 (upstream commit 2ba95c7920dcf08340e099c7b22beb1178da6106), using sqids.New() and that version's fixed default alphabet, blocklist and zero minimum length. Encode exactly one positive integer. Require Decode to return exactly one integer in 1..MaxInt64 and Encode(decoded) to equal the original string. This preserves the integer-encoding sqid required by §10; Docs' random document IDs are unrelated. Golden vectors are 1=Uk, 2=gb, 3=Ef, 1000=pnd and MaxInt64=AqkYhRmbHpEX. Refuse zero, negative/multiple numbers, alternate encodings and reserved DNS labels independently. Dependency/options changes require explicit compatibility review and must preserve existing team sqids. The upstream canonical encoding guidance requires re-encoding; a disposable local pinned-library probe verified these vectors. The seed checks this proposal again under the write lock. A concurrent seed makes the old proposal fail; the operator obtains a new proposal and new owner receipt.
credential.owner-seed accepts directory, team_id, team_sqid, and a private address bounded to 320 UTF-8 bytes from the operator-approved decrypted queue request. The address is transported privately into credential Home; queue unsealing and request approval remain the external drain's duty. The command normalizes and blind-indexes with the same credential helper as sign-in, resolves or creates the normal opaque person stub, and never mints a session. Synthetic live previews require already authenticated synthetic identities and an existing_only:true mode; that mode refuses a missing person and sends no mail. The directory cannot request address resolution over HTTP. Refactor the existing findOrMintPerson SQL into a transaction-capable internal helper: its current a.db.Writer() call cannot participate in the required operator transaction. Keep sign-in semantics, blindIndex and day-coarsened created_at unchanged.
The result is {version:1, operation_id, outcome:"committed", receipt}. receipt uses the shared closed {v,kid,payload,signature} envelope pattern with canonical base64url bytes and raw 64-byte signature, at most 8 KiB total. Its signed payload contains version, purpose="owner-seed", operationid, credential issuer, intended directory, proposed integer team ID and sqid, resolved personref, role="owner", issuedat and expiresat. Duration is at most 300 seconds. The credential operation and result are atomic with person-stub creation. Retry returns identical signed bytes, even after expiry; it never renews authority. An expired receipt requires a newly approved operation ID. The payload contains no address or team name.
directory.initialize accepts expected_generation:null. Inside its transaction require no generation, no roster consumers, no roster tokens and no previous bootstrap receipt; existing teams/memberships may be retained. Accept the exact random32-byte canonical base64url generation from the verified external baseline record, persist it once and a bootstrap receipt plus sealed result atomically. Require explicit deployment precondition that no consumer has ever been provisioned; a database which has lost generation/receipt history cannot satisfy this precondition based on its own empty tables. An independently signed external installation record and its monotonic registry floor must attest that this exact directory database has never had a roster generation provisioned. The record issuer, signature verification and durable external registry are not implemented by current Home or platform. The proposed companion home-provisioner.md defines them and the explicit trusted fresh-owner baseline attestation; that amendment requires approval before constructing initial-install authority. Platform maintenance provides exclusion, not this Home-specific historical evidence. If that evidence is unavailable, initialization is unavailable. Restore cannot use this verb. Ordinary boot only applies empty schema and never invokes it.
directory.seed-team accepts expected_generation, proposed team_id and team_sqid, canonical label, display_name (1..255 UTF-8 bytes), and the owner receipt (at most 8 KiB). Check receipt signature, configured issuer/directory, exact team proposal, fixed owner role, canonical person ref, valid issue time and five-minute expiry. Verify time again under the write lock. Persist the consumed credential receipt operation ID under a unique constraint in this same transaction. Expired or malformed input makes no change. A committed exact replay returns its stored result despite receipt expiry; changed input under either consumed owner receipt or directory operation refuses. Distinct directory operation IDs cannot reuse the same owner receipt.
Require generation equality, absent team/label and unchanged next ID. Allocate using AUTOINCREMENT, verify the returned integer and corresponding sqid, seal name under the existing ID-bound helper, insert exactly the receipt's person as first owner, and persist result plus consumed-receipt metadata atomically. No unsealed placeholder is written; an encrypted placeholder of the existing test/store shape may only exist within the transaction before the final seal. Membership triggers advance epoch; do not add a second increment. Return opaque team/sqid, generation and resulting epoch. No team_instances row is live at this point.
directory.provision-consumer accepts expected generation, team sqid, app slug, consumer ID (1..64 ASCII letters/digits//-), exact expected audience and expected sequence 0 for this initial-only operation. Require an existing team with an owner, no existing binding for team/app, no consumer ID/origin collision and exact compiled route. Insert pending teaminstance, enabled consumer, current token hash and successful provisioning receipt in one transaction. Generate a random 32-byte canonical bearer; store only SHA256 of its exact 43-character string in token rows. The signed shared Provisioning has sequence 1, all configured bindings, current generation, actual epoch and configured roster public keys (1..8); owner_confirmation is empty for this initial generation. Verify the produced record against the independently pinned directory operator public key before commit. Seal the private result containing {record, bearer} for exact retries. Pending bindings do not serve roster pages or appear in the chooser. No content key is copied or generated by Home. The external provisioner creates the isolated Docs DB and its own keys.
directory.activate-consumer accepts expected generation, consumer ID, expected record sequence/digest, and an opaque deployment verification operation reference. The verified operator context must attest successful exact-host/body/header build verification, assertion registration, isolated instance database/config, and the reviewed TeamMode acceptance gates. A string supplied by the request is not that attestation. Requery pending binding, consumer enabled state, token, generation and record metadata, then switch only that existing pending binding to live and commit its audit/result atomically. This package verifies an opaque approved activation context; it does not fetch a URL or trust arbitrary deployment evidence JSON. Current platform receipts do not attest all these Home-specific facts. The external provisioner/verification adapter and its pinned evidence verifier require the companion home-provisioner.md authority design and approval; no constructor may accept caller-controlled booleans as a substitute. Until that adapter exists, activation remains unwired. A compatibility account-link preview is not sufficient for activation.
Transaction and existing-database checks
Open an existing regular database with creation disabled, through the pinned platform path. Check application identity, expected role/origin, migration ledger and required additive schema versions before any action. Never call the general web startup migrator to repair an unexpected DB from this command. The deployment applies reviewed role-specific additive migrations before operator execution.
Acquire one dedicated database/sql connection. Set foreignkeys ON before beginning and read it back as 1 on that exact connection; pooled connections cannot substitute for this check. Also require securedelete ON, expected journal configuration, no foreignkeycheck violations, and the verified transport fence/lease. Take an immediate write transaction with a bounded lock timeout; all idempotency, generation, sequence, owner receipt and registry preconditions are rechecked inside it. Do not perform network work while holding the lock. Refuse wrong clocks, time rollback relative to persisted operator completion time, exhausted counters or corrupt rows. Write all related authority and audit rows together. Close/checkpoint through the supported transport protocol before resume; an uncertain commit remains fenced until querying the same operation establishes its outcome.
No success path changes existing sessions, assertion keys, content encryption, existing team/member rows or unrelated consumers. Rollback leaves no orphaned team, owner, token, receipt or result. Read-only inspect has no minting side effect.
Review and acceptance
Implement narrow package tests before a command adapter. Exercise actual additive schemas and sealer, signed owner receipts, actual shared provisioning verification, and real credential normalization/blind indexing. Require meaningful failures for FK off on the actual connection, wrong DB/role/origin, missing fence context, forged operator context, unknown key/purpose, browser assertion substitution, stale and future receipts, receipt reuse, raced integer proposal, changed-payload retry, commit/output loss, name-seal failure, generation mismatch, false initial install, consumer collision, roster epoch overflow and transaction rollback.
Extend gate 2's expensive privacy half when this seed verb lands, as required by the Home spec. Scan SQLite, WAL, captured stdout/stderr and public receipt serialization for known synthetic addresses, names, bearer strings, receipt payloads and private results. Prove current Home session acceptance/signout and all existing team ciphertexts survive applying the additive tables. Mutation tests must fail when signature, consumed-receipt uniqueness, FK verification or idempotency checks are removed. Demonstrate the private-stdout command with a disposable local fence fixture only; that fixture is not evidence of platform maintenance correctness. Full repo gates, exact CI and Fable whole-code review precede integration. Live use additionally requires approved/shipped platform 117, operator authority and invocation/activation context adapters, actual two-team TeamMode acceptance and canary verification.