oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1# Local Home operator seed and provisioning
2
3Status: design approved by configured Claude Fable (`claude-fable-5`, provider
4event metadata verified), 2026-09-08, exact reviewed commit
5898fe219f4da5092b2cc28210b344ede6a478e50. Home discussion 37 retains the verdict
6and earlier quota/permission refusals. This branch starts at directory runtime171.
7It defines an application package and local command protocol; the companion
8`home-provisioner.md` defines the authority adapters and staging proposal. No
9implementation, platform117 or live provisioning approval is implied.
10
11## Contracts and scope
12
13The governing sources are Home spec §7, §10, §14 and rulings 8, 22 and 23 in
14`docs/superpowers/specs/2026-08-30-home-design.md`; approved directory design
15fa64e286990762aecc6913bb9076a3cb75b088c3, `implementation/team-directory.md`
16(Home discussion 24); approved roster contract 4e7b3ff,
17`implementation/roster-contract.md` (discussion 28); and approved producer
1832fb49c1d5a4f0317b7291f441f78e21b7f0a11c,
19`implementation/roster-producer-storage.md` and
20`internal/home/directorydata/roster_migrations/migrations/0001_roster.sql`
21(discussion 33; use the migration embedded by RosterSchema if its filename
22changes). Shared roster 667dc8218dd5 supplies Provisioning, SignProvisioning and
23VerifyProvisioning. HTTP runtime design 5fbfdf0 is a separate integration review,
24not authority for this writer. Home discussion 35 records the source audit.
25
26The credential role alone resolves an address with its pepper. The directory
27alone creates teams and memberships. An audited operator selects an approved
28sealed request and its intended owner; a browser assertion, a visitor, an
29instance bearer and the platform account owner alone supply no team-owner
30identity proof. The operator transport authenticates the operator's authority to
31run an approved action on this exact deployment. The credential receipt below
32binds the intended owner across roles. This preserves the operator seed exception
33in §14; it does not assert that provisioning is a browser fresh-owner ceremony.
34
35This slice supports generation initialization when no generation or consumer
36exists, a new team with its first owner, initial consumer provisioning and
37activation. It provides no roster editing, token rotation, restore, owner
38confirmation, key rotation, sibling owner seeding or ownership transfer command.
39Those require their own reviewed operation and fresh-owner rules. In particular,
40missing generation alongside existing consumers is a refusal, never a repair.
41
42## Transport and command boundary
43
44Platform discussion 117 owns stopping traffic and all background work, holding
45the database lease, selecting the immutable executable and exact database, and
46resuming safely. Its private-input/result protocol is generic. The proposed
47entry point is `home operator-v1 --db <platform-selected-existing-db>`, with
48standard output captured as the private result by the transport. The fixed command and
49role permissions belong to reviewed deployment configuration. No request chooses
50a binary, path, SQL, environment variable, shell fragment or network destination.
51The command never starts an HTTP server, sends mail or makes network calls.
52
53Read one UTF-8 JSON object from private stdin with a 64 KiB hard limit including
54whitespace, then require EOF. Write one JSON result of at most 64 KiB to the
55private standard-output channel only, after transaction commit. The transport
56never tees or logs stdout and discards stderr. Input and result
57are encrypted at rest by the transport, available only through its authenticated
58private result channel, and never included in public status, receipts or logs.
59The application does not implement its own remote encryption protocol.
60
61Within the signed private wrapper defined in home-provisioner.md, operation-input
62common fields are `version:1`, `operation`, and `operation_id` (random
63canonical 32-byte base64url). Operation-specific closed objects are specified
64below. Reject unknown or duplicate fields, trailing JSON, alternate numeric
65forms, invalid UTF-8, malformed canonical base64 and oversized decoded values
66before mutation. Strings are bounded before allocating their decoded contents.
67No error includes supplied values. Exit 0 means a complete committed result is
68available; exit 2 is fixed refusal and exit 3 unavailable/ambiguous. Neither exit
69code permits automatic mutation retry. The platform queries the original result
70or explicitly reruns the exact input under a fresh fence; Home idempotency then
71returns the committed result without performing the action again.
72
73The invocation must be authorized outside the request. The local adapter accepts
74an immutable operation allowlist and expected role/origin/database identity from
75reviewed launch configuration. A request's `operation` only selects within that
76allowlist. Package constructors are private to the command adapter or receive an
77opaque validated operator context; exported structs cannot fabricate that context.
78The transport's operation ID is distinct from Home operation_id. Both must be
79bound by the adapter's verified invocation context. Current platform source has
80no signed Home invocation receipt, and this design does not assert otherwise. No body field can claim an
81operator identity or maintenance state. The platform must provide an authenticated
82invocation context or equivalent private launch configuration before command
83wiring is approved; an environment boolean supplied by the caller is insufficient.
84
85## Keys and durable private results
86
87Use the existing Rastrillo family key encoding and crypto.Sign/Verify operations,
88with strict canonical key decoding and derived key IDs. Reuse shared seal for
89stored private envelopes. Do not copy signing arithmetic or browser assertion
90validation to create operator authority.
91
92Credential operator signing uses a separately provisioned private key and context
93`oficina/home/owner-seed/v1`; the directory pins its public key outside every
94receipt. Directory provisioning signing uses its separately provisioned operator
95key and shared context `oficina/roster/provisioning/v1`. Roster response keys are
96also distinct. Keys arrive through the existing protected per-instance config
97mechanism; runtime browser assertion keys and existing data keys remain unchanged.
98Ordinary startup neither creates nor rotates any of these keys. Reject identical
99public keys across the roles within the command's configured material. Public
100key pin replacement is an explicit future operator migration, not an input field.
101
102Add role-specific operator result tables through additive migrations. Credential
103and directory databases never receive each other's tables. Each result row has
104operation_id primary key, fixed action, opaque operator reference from verified
105invocation context, creation time, a keyed request digest, and a sealed result.
106The request digest is HMAC-SHA256 using an independently provisioned 32-byte
107idempotency key. The actual shared sealer exposes Seal/Open, not a key derivation
108API. No custom KDF is introduced. Digest canonical typed operation input, not raw JSON whitespace.
109This prevents a stored bare address/name digest becoming an offline dictionary.
110
111Seal result bytes with the existing role sealer and purpose
112`operator.result/v1/<exact-origin>/<operation-id>/<action>`. A result can contain
113an authority-bearing receipt or bearer and therefore never appears plaintext in
114SQLite/WAL, logs or public receipts. Never persist the original address/input.
115Repeated exact input under the same operation ID returns the stored result;
116changed semantic input or different action refuses. A lost post-commit response
117therefore does not mint a new bearer, person or receipt. Keep successful result
118rows until an explicit reviewed retention policy exists; no rolling cleanup may
119make an old operation reusable. This initial low-volume administrative table has
120a hard 10,000-operation limit, refusing further new operations at capacity.
121
122The existing roster_operator_receipts table retains its approved public metadata
123shape and SHA256 digest of the signed provisioning record; no bearer, display
124name, input digest containing address material or private result belongs there.
125The new directory result row and that receipt commit in the same transaction.
126Private results are bounded to 48 KiB before sealing, leaving transport space.
127
128## Operations
129
130All origins come from validated configuration or must equal it exactly. Production
131team destinations reuse the compiled registry and canonical label policy from
132`directoryFeatures` and `directorydata.Config.ValidateRoute`; for Docs the origin
133is `https://<label>.docs.oficina.works`. Reject alternate ports, paths, aliases,
134userinfo, fragments, queries and unapproved app slugs. This design adds no pilot
135manifest or per-team config exception.
136
137`directory.inspect` accepts no payload and returns only current generation (or
138explicit absent), the next proposed team integer, and derived team sqid. It writes
139no authority and is not a reservation. Derive next ID from the maximum existing
140team ID and sqlite_sequence, then add one, refusing signed-64-bit overflow. No
141shared Home/suite codec exists today. Pin official github.com/sqids/sqids-go
142v0.4.1 (upstream commit 2ba95c7920dcf08340e099c7b22beb1178da6106), using
143sqids.New() and that version's fixed default alphabet, blocklist and zero minimum
144length. Encode exactly one positive integer. Require Decode to return exactly
145one integer in 1..MaxInt64 and Encode(decoded) to equal the original string.
146This preserves the integer-encoding sqid required by §10; Docs' random document
147IDs are unrelated. Golden vectors are 1=Uk, 2=gb, 3=Ef, 1000=pnd and
148MaxInt64=AqkYhRmbHpEX. Refuse zero, negative/multiple numbers, alternate encodings
149and reserved DNS labels independently. Dependency/options changes require explicit
150compatibility review and must preserve existing team sqids. The upstream
151[canonical encoding guidance](https://github.com/sqids/sqids-go/tree/v0.4.1)
152requires re-encoding; a disposable local pinned-library probe verified these vectors.
153The seed checks this proposal again under the write lock. A concurrent seed makes
154the old proposal fail; the operator obtains a new proposal and new owner receipt.
155
156`credential.owner-seed` accepts `directory`, `team_id`, `team_sqid`, and a private
157`address` bounded to 320 UTF-8 bytes from the operator-approved decrypted queue
158request. The address is transported privately into credential Home; queue
159unsealing and request approval remain the external drain's duty. The command
160normalizes and blind-indexes with the same credential helper as sign-in, resolves
161or creates the normal opaque person stub, and never mints a session. Synthetic
162live previews require already authenticated synthetic identities and an
163`existing_only:true` mode; that mode refuses a missing person and sends no mail.
164The directory cannot request address resolution over HTTP. Refactor the existing
165findOrMintPerson SQL into a transaction-capable internal helper: its current
166a.db.Writer() call cannot participate in the required operator transaction.
167Keep sign-in semantics, blindIndex and day-coarsened created_at unchanged.
168
169The result is `{version:1, operation_id, outcome:"committed", receipt}`. receipt
170uses the shared closed `{v,kid,payload,signature}` envelope pattern with canonical
171base64url bytes and raw 64-byte signature, at most 8 KiB total. Its signed payload
172contains version, purpose="owner-seed", operation_id, credential issuer, intended
173directory, proposed integer team ID and sqid, resolved person_ref, role="owner",
174issued_at and expires_at. Duration is at most 300 seconds. The credential operation
175and result are atomic with person-stub creation. Retry returns identical signed
176bytes, even after expiry; it never renews authority. An expired receipt requires
177a newly approved operation ID. The payload contains no address or team name.
178
179`directory.initialize` accepts `expected_generation:null`. Inside its transaction
180require no generation, no roster consumers, no roster tokens and no previous
181bootstrap receipt; existing teams/memberships may be retained. Accept the exact random32-byte canonical base64url generation from the verified
182external baseline record, persist it once and a bootstrap receipt
183plus sealed result atomically. Require explicit deployment precondition that no
184consumer has ever been provisioned; a database which has lost generation/receipt
185history cannot satisfy this precondition based on its own empty tables. An independently
186signed external installation record and its monotonic registry floor must attest
187that this exact directory database has never had a roster generation provisioned.
188The record issuer, signature verification and durable external registry are not
189implemented by current Home or platform. The proposed companion home-provisioner.md
190defines them and the explicit trusted fresh-owner baseline attestation; that
191amendment requires approval before constructing initial-install authority. Platform
192maintenance provides exclusion, not this Home-specific historical evidence. If
193that evidence is unavailable, initialization is unavailable. Restore cannot use
194this verb. Ordinary boot only applies empty schema and never invokes it.
195
196`directory.seed-team` accepts `expected_generation`, proposed `team_id` and
197`team_sqid`, canonical `label`, `display_name` (1..255 UTF-8 bytes), and the owner
198`receipt` (at most 8 KiB). Check receipt signature, configured issuer/directory,
199exact team proposal, fixed owner role, canonical person ref, valid issue time and
200five-minute expiry. Verify time again under the write lock. Persist the consumed
201credential receipt operation ID under a unique constraint in this same transaction.
202Expired or malformed input makes no change. A committed exact replay returns its
203stored result despite receipt expiry; changed input under either consumed owner
204receipt or directory operation refuses. Distinct directory operation IDs cannot
205reuse the same owner receipt.
206
207Require generation equality, absent team/label and unchanged next ID. Allocate
208using AUTOINCREMENT, verify the returned integer and corresponding sqid, seal name
209under the existing ID-bound helper, insert exactly the receipt's person as first
210owner, and persist result plus consumed-receipt metadata atomically. No unsealed
211placeholder is written; an encrypted placeholder of the existing test/store shape
212may only exist within the transaction before the final seal. Membership triggers
213advance epoch; do not add a second increment. Return opaque team/sqid, generation
214and resulting epoch. No team_instances row is live at this point.
215
216`directory.provision-consumer` accepts expected generation, team sqid, app slug,
217consumer ID (1..64 ASCII letters/digits/_/-), exact expected audience and expected
218sequence 0 for this initial-only operation. Require an existing team with an owner,
219no existing binding for team/app, no consumer ID/origin collision and exact compiled
220route. Insert pending team_instance, enabled consumer, current token hash and
221successful provisioning receipt in one transaction. Generate a random 32-byte
222canonical bearer; store only SHA256 of its exact 43-character string in token rows.
223The signed shared Provisioning has sequence 1, all configured bindings, current
224generation, actual epoch and configured roster public keys (1..8); owner_confirmation
225is empty for this initial generation. Verify the produced record against the
226independently pinned directory operator public key before commit. Seal the private
227result containing `{record, bearer}` for exact retries. Pending bindings do not
228serve roster pages or appear in the chooser. No content key is copied or generated
229by Home. The external provisioner creates the isolated Docs DB and its own keys.
230
231`directory.activate-consumer` accepts expected generation, consumer ID, expected
232record sequence/digest, and an opaque deployment verification operation reference.
233The verified operator context must attest successful exact-host/body/header build
234verification, assertion registration, isolated instance database/config, and the
235reviewed TeamMode acceptance gates. A string supplied by the request is not that
236attestation. Requery pending binding, consumer enabled state, token, generation and
237record metadata, then switch only that existing pending binding to live and commit
238its audit/result atomically. This package verifies an opaque approved activation
239context; it does not fetch a URL or trust arbitrary deployment evidence JSON.
240Current platform receipts do not attest all these Home-specific facts. The
241external provisioner/verification adapter and its pinned evidence verifier require
242the companion home-provisioner.md authority design and approval; no constructor may accept caller-controlled
243booleans as a substitute. Until that adapter exists, activation remains unwired. A compatibility account-link preview is not sufficient for activation.
244
245## Transaction and existing-database checks
246
247Open an existing regular database with creation disabled, through the pinned
248platform path. Check application identity, expected role/origin, migration ledger
249and required additive schema versions before any action. Never call the general
250web startup migrator to repair an unexpected DB from this command. The deployment
251applies reviewed role-specific additive migrations before operator execution.
252
253Acquire one dedicated database/sql connection. Set foreign_keys ON before beginning
254and read it back as 1 on that exact connection; pooled connections cannot substitute
255for this check. Also require secure_delete ON, expected journal configuration,
256no foreign_key_check violations, and the verified transport fence/lease. Take an
257immediate write transaction with a bounded lock timeout; all idempotency, generation,
258sequence, owner receipt and registry preconditions are rechecked inside it. Do not
259perform network work while holding the lock. Refuse wrong clocks, time rollback
260relative to persisted operator completion time, exhausted counters or corrupt rows.
261Write all related authority and audit rows together. Close/checkpoint through the
262supported transport protocol before resume; an uncertain commit remains fenced
263until querying the same operation establishes its outcome.
264
265No success path changes existing sessions, assertion keys, content encryption,
266existing team/member rows or unrelated consumers. Rollback leaves no orphaned team,
267owner, token, receipt or result. Read-only inspect has no minting side effect.
268
269## Review and acceptance
270
271Implement narrow package tests before a command adapter. Exercise actual additive
272schemas and sealer, signed owner receipts, actual shared provisioning verification,
273and real credential normalization/blind indexing. Require meaningful failures for
274FK off on the actual connection, wrong DB/role/origin, missing fence context, forged
275operator context, unknown key/purpose, browser assertion substitution, stale and
276future receipts, receipt reuse, raced integer proposal, changed-payload retry,
277commit/output loss, name-seal failure, generation mismatch, false initial install,
278consumer collision, roster epoch overflow and transaction rollback.
279
280Extend gate 2's expensive privacy half when this seed verb lands, as required
281by the Home spec. Scan SQLite, WAL, captured stdout/stderr and public receipt serialization for known
282synthetic addresses, names, bearer strings, receipt payloads and private results.
283Prove current Home session acceptance/signout and all existing team ciphertexts
284survive applying the additive tables. Mutation tests must fail when signature,
285consumed-receipt uniqueness, FK verification or idempotency checks are removed.
286Demonstrate the private-stdout command with a disposable local fence fixture only; that
287fixture is not evidence of platform maintenance correctness. Full repo gates, exact
288CI and Fable whole-code review precede integration. Live use additionally requires
289approved/shipped platform 117, operator authority and invocation/activation context
290adapters, actual two-team TeamMode acceptance and canary verification.
291