| new file mode 100644 |
| index 0000000..2828f5c |
| --- /dev/null |
| +++ b/docs/superpowers/specs/2026-09-07-content-sealing.md |
| @@ -0,0 +1,93 @@ |
| +# 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. |