oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1# Operator registry and first team provisioning
2
3Status: design approved by configured Claude Fable (`claude-fable-5`, verified
4provider metadata), 2026-09-08, reviewed commit898fe219f4da5092b2cc28210b344ede6a478e50,
5Home discussion37. Implementation and live-use gates below remain. This makes
6`home-operator-seed.md`'s invocation, baseline and activation adapters concrete.
7It adds no live records, keys, configuration or command implementation.
8
9## Operator and authority
10
11Implement a separate local Go command, `oficina provision`, callable directly by
12a human. It uses supported CARLOS APIs/CLI and the fixed Home operator command.
13No LLM, remote shell, SQL console or manual database edit is required. Home keeps
14no platform provisioning credential. The command never serves a website.
15
16The operator authenticates to the existing CARLOS console. Every maintenance
17mutation still requires its actual browser session, CSRF, current account-owner
18lookup and `requireFreshAuth`, in that order. The reviewed maintenance request
19binds its immutable encrypted input and selected artifact/config/instance. The
20local signer cannot bypass that gate. A bearer has no invented auth_time. Source:
21platform dcf0cad `internal/console/stepup.go`, and platform117 revised maintenance
22design (currently 0c59ea2; wire API remains under review).
23
24A separately generated provisioner signing key attests the operator's selected
25installation baseline and collected evidence. Both Home roles pin this public key
26outside the signed request. It is distinct from credential owner-seed signing,
27directory provisioning signing and roster response keys. Compromise of this key
28is operator compromise; its public signature alone cannot execute anything without
29the maintenance fence and fresh console approval. The credential owner receipt
30still resolves the requested owner's identity. The operator cannot submit a
31person_ref in place of that receipt.
32
33The initial baseline is an explicit trusted owner attestation: this installation
34has never issued a roster generation or consumer record. Deployment history,
35current configured versions, and fenced schema inspection inform that decision.
36They cannot mathematically prove historical absence after a possible restore.
37The review screen states this distinction and shows the exact installation and
38baseline digest. If the owner cannot attest it, enrollment refuses and recovery
39requires its separately reviewed procedure. This is the only initial-install
40exception; retries and ordinary startup cannot repeat it.
41
42## Registry custody and persistence
43
44The first supported workflow runs natively on this Linux operator workspace.
45`oficina provision init --state <directory> --floor-dir <independent-directory>`
46requires empty regular directories on persistent local ext4/XFS filesystems,
47mode0700, no symlinks or shared/network filesystem. The floor directory must be
48outside the state directory and its backup scope. Init creates random device and
49registry IDs plus signing/encryption keys, and stores the keys only in an age
50scrypt-encrypted credential file. Unlock through a private TTY prompt or explicit
51private password FD, never an argv/env value. Keys remain in process memory only
52while the command runs. No secret is printed or committed. Initialization writes a signed initializing
53genesis and encrypted key file before creating the registry; an interrupted init
54can resume only that exact genesis after unlocking its keys. It never silently
55replaces an existing key, registry ID or partially created floor.
56
57The independent floor backend writes signed immutable sequence/head files with
58exclusive creation, file fsync and parent-directory fsync; a durable head marker
59is updated by rename under the process lock only after the immutable file is
60complete. It stores no secret and never appears inside a registry backup. Bind
61registry/device IDs and signer public-key fingerprint in the credential file,
62registry and floor genesis. Missing or mismatched identity refuses. On open, validate all bounded immutable
63floor records and their contiguous signed chain, not only the replaceable head
64marker; an old marker cannot hide a newer completed floor file. Linux filesystem
65semantics are tested directly with process/crash injection on a supported local
66filesystem; an in-memory backend is insufficient. Unsupported filesystem types
67refuse. Copying only state or rolling back its encrypted keys cannot roll back the
68independent floor. A disk failure losing both is an unrecoverable condition in this
69slice, not permission to initialize again.
70
71An optional macOS backend can use pinned
72[github.com/keybase/go-keychain v0.0.1](https://github.com/keybase/go-keychain/tree/v0.0.1)
73through native Security-framework calls. It must satisfy the identical floor
74contract before being supported; this is not a prerequisite for the Linux pilot.
75Home itself remains CGO_ENABLED=0. No secret-bearing security-command argv or
76plaintext-key fallback exists. Keys and floor identity are bound to one operator
77device. Copying them to a second device is not a supported concurrent writer.
78
79Use a dedicated local SQLite registry outside Home's database, deployment directory
80and backup schedule. Acquire an exclusive process lock for the entire operation.
81SQLite uses one writer connection, foreign_keys ON, secure_delete ON and synchronous
82FULL. Schema includes registry identity/version; installations keyed by exact
83console/account/app/primary host; immutable operation records; encrypted private
84inputs/results; and evidence records. Every installation has one current sequence,
85head digest, enrolled directory identity, baseline operation and current generation.
86Only the provisioner command writes these rows.
87
88Every record carries registry/device ID, installation ID, monotonically increasing
89sequence, previous record digest, random operation ID, fixed action, expected state,
90created time and digest of randomized sealed private payload. Sign the canonical
91closed record with Rastrillo crypto.Sign context `oficina/provisioner/record/v1`.
92Write the immutable signed record and ciphertext with the installation's new head
93in one transaction using sequence+head CAS. No update/delete API exists for records.
94A reused operation ID with identical typed request returns its stored record/result;
95changed input refuses. Retain 10,000 operations per registry and refuse new work at
96capacity until a reviewed archive policy exists. Advance state before dispatch,
97then append outcome evidence after receiving the exact private result. An ambiguous
98remote result leaves an in-flight journal record; it never causes an automatic
99second Home operation.
100
101The independent floor backend holds the latest registry sequence and signed head
102digest outside the registry backup scope. A new head follows a two-phase local protocol: commit a signed
103prepared record with old/new floor to SQLite and fsync; CAS the independent floor from
104old to new; commit finalized state and checkpoint/fsync the parent directory. Before
105any remote dispatch, require finalized matching floor. Crash between steps resumes
106only the single matching prepared record. Floor ahead without the exact prepared
107record, DB ahead without a valid adjacent preparation, competing device or any
108older/mismatched head refuses. No sequence reuse follows a failed write. The floor
109CAS primitive may be serialized by the exclusive device lock; re-read before write
110and refuse unexpected values. A floor backend without durable update semantics
111is unsupported. Tests inject crashes at every boundary.
112
113`oficina provision backup --output <exclusive-file>` exports encrypted registry
114records, private payloads and wrapped recovery keys through a password-protected
115standard age container using pinned
116[filippo.io/age v1.3.2](https://github.com/FiloSottile/age/tree/v1.3.2)
117and its scrypt recipient/identity APIs. No custom password encryption is introduced.
118Use mode0600 and atomic fsync/rename. Print only the archive digest and sequence.
119Require independent backup storage selected by the operator and a verified restore
120drill before enrolling a non-synthetic installation. Never include a plaintext key
121or decrypted owner address. A lost local registry does not make init available for
122the same surviving floor identity. A restored archive must meet the surviving independent
123floor and verify every signature/hash-chain entry; an older archive refuses. If both
124registry and independent floor are lost, this slice cannot recover or re-enroll;
125explicit recovery design and fresh owner confirmation are required. Export/import
126must be human-usable commands and carry no secrets through argv, terminal or shell
127history. Passphrases use a private terminal prompt or file descriptor.
128
129The separate durable floor protects against registry-backup rollback, not an
130attacker controlling the operator device and unlocked signing keys. Copying the
131registry and floor together and rolling both back is excluded from safe restore; document this and test
132the refusal achievable with a surviving floor. Production recovery readiness still
133requires the independent backup/restore rehearsal, not merely a second database.
134
135## Signed private operation contract
136
137The Home command receives one closed private JSON body of at most64KiB:
138`{version:1, signed_authorization, operation_input}`. signed_authorization is a
139closed signature envelope capped at16KiB; operation_input is bounded canonical
140JSON capped at48KiB and also subject to the64KiB total. Its exact typed encoding
141is reused for digest and dispatch; alternate encodings, duplicate/unknown fields
142and trailing JSON refuse. The existing operator operation fields live inside
143operation_input. The signed authorization binds SHA256 of those exact canonical
144bytes plus role, exact configured origin, installation ID, registry/device ID,
145per-role sequence, previous authorization head, operation ID, issued/expires times
146and purpose. The local journal's global sequence is bound as a reference as well.
147The platform encrypts this body for its worker through its approved private input
148protocol, decrypts there and supplies it on private stdin. Home-specific code does
149not parse the platform envelope or assume it can decrypt platform-held material.
150The public journal stores only a digest of randomized ciphertext; the signature
151with a potentially guessable plaintext digest stays inside encrypted private input.
152The Home command receives no registry decryption key.
153
154Sign authorization using context `oficina/home/operator-input/v1`, with duration
155at most300 seconds and exact fixed operation allowlist. Each Home role validates
156the pinned public key, envelope bounds, exact origin/role, receipt clock and typed
157request digest before constructing its unexported operator context. Persist the
158accepted registry identity, device identity, per-role sequence and head in its own
159operator schema with the operation result. Require sequence exactly previous+1,
160correct previous head, or exact already-committed replay. Each role has its own
161ordered authorization stream because credential and directory actions interleave;
162the outer journal's global sequence remains independent. Do not advance the
163per-role accepted stream when merely preparing a request. A refused/expired request
164gets an immutable outcome in the global journal, while the next per-role slot stays
165unaccepted. A replacement authorization for that slot requires proof the old
166attempt is not running and its authorization has expired; otherwise the provisioner
167waits/refuses. An ambiguous attempt must be reconciled, never replaced. Successful
168Home acceptance updates its stream and result in the same transaction. A lost accepted operation
169result is recovered by replay of the same signed authorization, which may return an
170already-committed result after expiry but cannot perform a new mutation.
171
172Read-only inspect has no accepted-stream mutation: it validates a separately
173signed inspect challenge bound to role/origin and expiry, returns challenge plus
174observation time with the state, and never advances authority or mints a seed.
175The private platform result channel binds that observation to the exact attempt.
176It cannot initialize an absent authority stream or be submitted as owner proof.
177
178Initial trust enrollment pins registry/device identity and operator public key
179through the reviewed per-instance protected configuration, preserving all existing
180keys. A changed pinned identity/key does not reset stored sequence/head. The first
181operation requires a create-only expected-empty operator authority table and a
182signed baseline, approved through the same fresh console gate. Thereafter absent
183operator state alongside an existing baseline/generation is a refusal. Provisioner
184and Home pin both key ID and exact encoded public key; no trust-on-first-request.
185
186The platform supplies only its accepted maintenance execution boundary. Home
187validates the private signed authorization itself; it needs no invented signed
188platform receipt. The fixed operator entry point is enabled only for maintenance
189execution of the immutable configured release. Normal HTTP startup does not expose
190it. Tests must prove direct app requests cannot invoke it.
191
192## Installation and evidence sequence
193
194`oficina provision enroll` prepares a human-readable baseline plan and immutable
195private request. It identifies existing credential/directory primary hosts,
196canonical origins, account, existing data-key fingerprints, operator public pins,
197exact release/artifact and checked configuration bindings plus registry identity. Use supported
198`carlos instances list`, route/version checks and maintenance preflight;
199restart's asleep output is not exec eligibility proof. The operator's fresh approval
200is required on the console's exact request. Existing Home databases and identities
201are retained. No credential sign-in is fabricated for the owner.
202
2031. Apply reviewed additive operator/roster schemas using a normal verified release,
204 preserving role boundaries. Load separately generated operator keys through
205 protected per-instance configuration. Verify actual host version in both body
206 and X-Carlos-Version before maintenance; record response digests and time.
2072. Fenced directory inspect confirms exact DB/schema/role/config and no generation
208 or consumer history. The external signed baseline binds this observation and the
209 owner's never-provisioned attestation. Generate a random32-byte generation in
210 the registry and include it in the signed initialize request. Directory persists
211 that exact authorized generation once; it does not substitute another random value.
2123. Obtain the proposed team integer/sqid through inspect. Collect an approved sealed
213 queue request, decrypt locally only in bounded private memory, and bind its approval
214 to the operation. The synthetic pilot instead uses an explicit operator-authored
215 synthetic plan containing already-authenticated synthetic addresses and labels,
216 signed in this same journal and approved through the same fresh console gate;
217 this creates no fake queue row or alternate Home identity authority. For synthetic previews require the already-authenticated synthetic
218 identity and existing_only credential resolution. Fresh console approval runs
219 credential.owner-seed; its private signed receipt is encrypted into the journal.
2204. Submit directory.seed-team with that receipt, exact proposed ID, reserved label,
221 sealed-input display name and generation. A raced proposal or expired receipt
222 requires a new explicit plan/approval. A receipt cannot seed a sibling team's owner.
2235. Create an isolated Docs platform instance through existing supported instance APIs,
224 with a distinct DB namespace, instance/content/DEK keys, per-instance environment
225 and consumer ID. Never clone existing Home/Docs keys or documents. Attach the exact
226 canonical team alias using `carlos domains attach --app ... --route ...`; verify
227 the returned current domain binding, primary route and environment.
2286. Register the exact consumer with credential Home by updating its bounded immutable
229 HOME_CONSUMERS manifest through protected environment configuration. Preserve every
230 existing registration and all current assertion keys. Read back the public manifest and protected configuration delivery result,
231 then use the fixed credential compatibility checker under the maintenance
232 environment binding to verify the active manifest digest and public key
233 fingerprints. Verify the actual restarted credential host version. Its normal
234 GET /authorize validation can confirm that this consumer/audience pair reaches the
235 expected sign-in flow without sending mail; it cannot alone prove key custody.
236 Current source allows16 entries/8192bytes and requires directory registration;
237 stop at the bound. Scalable registration is separate work.
2387. Provision the pending directory consumer. Verify its returned signed record using
239 the independent pinned directory operator public key. Install the exact record and
240 private bearer into the isolated Docs config using supported protected delivery.
241 The local journal records ciphertext digests and the checked maintenance
242 environment binding, never secret values in public receipts.
2438. Collect activation evidence and publish only through the approved activation rule
244 below. Verify completed two-team browser acceptance using real synthetic identities
245 and actual Home credential authentication; no pre-minted session is live evidence.
246
247Evidence is collected by typed adapters, not CLI sentence parsing: use the supported
248console/domain/instance/version response structs or add a reviewed JSON CLI output
249for a currently text-only command. Source platform dcf0cad `instances_cli.go`,
250`domains_cli.go`, `cli.go:routeVersionCheck`, `internal/console/apicli.go`; Home171
251`internal/home/runtime.go` HOME_CONSUMERS validation and `exchange.go` authorization
252validation. Current domain responses distinguish derived State from legacy Status: require
253State=live (not merely Status=live), then verify actual TLS/version. Domain claim
254alone does not prove materialized TLS. Current configGet has no revision field and
255never returns secret values; absence is not a successful secret/config comparison.
256Use maintenance's immutable protected environment binding and a fixed non-serving
257application compatibility checker against that snapshot. The Home checker reports
258only exact origins, active public operator/assertion/roster key fingerprints and
259consumer-manifest digest. The Docs checker reports exact TeamMode bindings, verified
260provisioning record digest, generation and SHA256 of the canonical bearer already
261present in protected config. These are bounded private results, never public logs.
262Checkers make no network calls, mint no sessions and change no DB/key/authority.
263They require separate small implementation/review slices; no current API is claimed
264to supply these application-specific facts. Existing secret fingerprints are compared
265privately to enrolled fingerprints, without copying existing private keys out. Dial only configured
266canonical HTTPS hosts with normal certificate validation, no redirects unless the
267specific sign-in probe validates every expected hop. Compare full expected version
268in body/header and current platform route/alias mapping. Store bounded response
269digests, typed binding facts and timestamps in the encrypted signed journal; token,
270email, assertion and unrelated response content are never retained in evidence.
271
272## Activation gate and pending roster integration
273
274An activation authorization signs exact consumer/team/app/audience, generation,
275record sequence/digest, deployed release/artifact digest, checked maintenance environment binding,
276credential registration digest, materialized alias binding, verification
277start/end time and the versioned acceptance policy. The local provisioner only emits
278it after its typed collectors pass. It is bounded to16KiB, expires within300 seconds,
279and is approved in the fresh console maintenance request. The Home command verifies
280this signed authorization and current stored bindings again before switching live;
281it never trusts a request's boolean or opaque reference as evidence.
282
283There is an ordering conflict to resolve explicitly: current roster producer32fb
284serves only live team_instances, but a real TeamMode startup/read needs a roster
285before its instance can be accepted for live entry. Proposed narrow amendment for
286review: add a separate directory-only consumer verification-window table with
287consumer primary key/FK, expiry and audited opening operation. Verification and
288activation audit/history rows use new additive tables; preserve the existing
289roster_operator_receipts CHECK enum unchanged. A signed operator
290`verify-consumer` operation may grant one pending binding at most600 seconds of
291roster reads, using an absolute issued_at/expires_at signed into its authorization.
292This grants actual app standing to that team's membership holders through its
293exact consumer; it is not merely a read-only probe. It grants no item permissions. The producer still validates its exact enabled bearer/registry/route
294and generation; it additionally accepts pending only while this audited window is
295current. The chooser remains live-only. No browser assertion or consumer request
296can open/extend the window. Only the exact enabled consumer's current/approved
297overlap bearer and exact team/app/origin receive these signed pages. Other pending
298bindings, siblings and anonymous requests remain refused. The stored expiry is
299immutable; failure, read, retry and exact replay cannot extend it. Consumer disable
300or expiry stops subsequent pending roster reads; existing client leases retain
301their already-approved60-second ceiling from the last successful fetch start.
302Tests measure that ceiling for silent SSE and protected commits as well as requests. The activation operation clears the
303window atomically with live state. A failed verification leaves the instance pending;
304a distinct deliberately approved verification attempt needs a new signed/fresh-
305approved operation and audit record, and cannot overwrite the old window/history.
306Opening while an earlier attempt is active refuses. Completed activation is
307idempotent by operation ID/digest; later verification attempts for a live binding
308refuse. Retain opening, expiry and activation outcome records after clearing the
309active window. Activation compares the accepted verification attempt, exact epoch,
310current owner membership and evidence digest under its transaction lock; a changed
311epoch/owner requires new evidence and approval.
312
313This amendment changes the approved producer contract and requires explicit Fable
314approval before implementation. No pending read exception is implied by approval of
315the core operator package alone. It enables actual two-team authentication/standing
316and isolation checks without listing unaccepted instances in the chooser. Initial
317pilot teams contain only the specifically authenticated synthetic owners. Their
318canary browser evidence does not claim general production or restore readiness.
319
320## Failure and acceptance
321
322Every step records its intent before dispatch and its exact result before preparing
323the next step. Resume inspects the journal and supported maintenance status; it never
324infers success from a timeout or deletes an ambiguous record. Cross-role operations
325are not one database transaction: a credential stub or pending team can remain after
326a later failure. The journal identifies it, and only the exact reviewed retry can
327continue. Automatic compensating deletion is prohibited.
328
329Tests cover fresh-owner refusal/removal; wrong signer/role/origin/digest; cross-role
330stream confusion; replay after expiry versus new action after expiry; floor/DB
331crashes; stale backups; lost floor/registry; concurrent processes/devices;
332create-only baseline and initial generation; exact protected config/alias/version
333mismatch; pending verification expiry; wrong team/token; activation replay and
334membership/owner changes between observation and commit. Run an actual human-usable
335synthetic CLI rehearsal, registry export/restore at the current floor and stale-copy
336refusal, then actual supported platform maintenance canary. The eventual operator runbook must bound each deliberate Home maintenance outage;
337credential Home downtime prevents suite sign-in. All source changes need
338normal full gates, Fable whole-code review and verified canary before merge/deploy.
339