oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

Document body sealing implementation spec

Status: approved for implementation by Claude Fable 5.1 (claude-fable-5-1), session 8aa40554-38b1-4597-9cb8-439ab594e268. The final rollout amendment and approval are recorded in Amadan Docs discussion 39. Whole-branch review, exact-head CI, release integration, canary and deployment evidence are separate gates.

Scope and encoding

Seal doc_updates.update_bin and doc_snapshots.state. Titles and other metadata are outside this slice. Historical plaintext backups remain preserved and are not claimed encrypted by this change.

Use a separate DOCS_CONTENT_KEY: strict standard-base64 encoding of 32 random bytes. Require it in production before opening the database, even before activation. Only explicit development mode may use the fixed public development key; maintenance always validates as production.

Encoding 1 uses AES-256-GCM with a random 12-byte nonce prepended to the ciphertext and tag. Associated data binds the fixed content domain, row kind, immutable document UUID, sequence and snapshot ABI version. Allocate the sequence inside the writer transaction before sealing the update. Never send ciphertext to the CRDT core or sync clients. Decrypt and validate all response rows before writing HTTP success headers.

The additive migration creates encoding columns defaulting to 0 and a singleton activation marker containing encoding, key fingerprint, authenticated keycheck and scrubbed bookkeeping. Keycheck binds a fixed sentinel and remains meaningful for an empty database. Wrong-key errors are distinct from authentication failures and never include content bytes.

Runtime states

A database without the marker is in legacy compatibility mode. Refuse startup if it already contains nonzero encoding rows. Log an Error on every legacy boot with structured encoding and key-fingerprint fields. Legacy reads accept only encoding 0. Both update and snapshot inserts re-read the marker through the same writer transaction as the insert and refuse if activation has occurred. A process never switches modes live.

A database with the marker verifies its keycheck and refuses startup if an EXISTS query finds any update or snapshot with encoding other than 1. Activated reads reject other encodings and authenticate every body used. An unusable snapshot may be abandoned for the retained update log; it is an accelerator and is never the only copy of document history.

Explicit maintenance

Only the docs-seal subcommand invokes conversion or scrubbing. It requires an existing regular database path and the expected key fingerprint. Read the key only from DOCS_CONTENT_KEY. Reuse the exact server BootSchema. Never resolve serving options, bind a listener or start replication. There is no deactivate or marker-removal operation.

Conversion is one transaction. Process bounded batches, bind bodies to their stored document UUID, verify already-sealed rows and reject unknown encodings or orphaned rows. On first conversion write the keycheck marker atomically with the converted rows. On explicit repair of an activated mixed database, convert remaining encoding-0 rows while leaving the marker, fingerprint and keycheck unchanged. Clear only scrubbed bookkeeping when new legacy bytes have been converted. Any failure rolls back all changes.

Scrubbing is an explicit operation after conversion. The operator stops the server and replication first. Set scrubbed false, use a pinned writer connection, check the complete TRUNCATE-checkpoint result, VACUUM, check a second TRUNCATE checkpoint, and set scrubbed true only on success. A busy checkpoint is failure. Never put conversion or VACUUM on normal startup. Every restored copy needs another explicit scrub; its stored scrubbed bit is not evidence about the restored file's pages or WAL.

Rollout constraints

Deploy and smoke-test the compatibility-capable release with the real production key before activation. Retain that exact release as the rollback target. Verify key fingerprint and backup/key recovery before conversion. Stop serving and replication for maintenance, then restart the same compatibility-capable release after activation. Never roll back to an older plaintext-only binary once sealed rows exist. Preserve historical backups. A restored pre-conversion backup is visibly legacy and needs activation; it must never be silently presented as encrypted.

Required evidence

Use actual Yjs fixtures and real SQLite. Cover authenticated row identity, wrong keys and tampering; encrypted HTTP read/write and snapshots; stale legacy reads and writes; both tables' mixed-state startup checks; explicit maintenance inputs; atomic repair and marker preservation; raw database and WAL canaries; incomplete checkpoints; process-exit recovery; and an independent restored-copy rehearsal. Observe meaningful tests fail when their safeguards are removed, then restore the code. Full repository CI and an independent complete-diff review remain required before merge.