approval-engine/docs/storage-operations.md
tegwick 4aa0b75449 APPROVAL-WP-0007: record the verified revoker and park the inactive plans
Schema v9 stores who revoked an approval and an optional bounded reason.
The bounded-execution carrier example is proven in process. APPROVAL-WP-0005,
0006, and 0007 are blocked because they are not current work. Task statuses
stay as they were, apart from T03 and T04, which are done in source.

Assistant: grok
Assistant-Session: 01a1b5fd-0c33-7ba1-b383-53937d525595
2026-11-07 14:10:01 +01:00

109 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Storage operations
Production uses one SQLite writer on persistent `ReadWriteOnce` storage.
Production serve disables automatic migration and refuses schema drift or an
in-memory database.
```bash
approval-engine migrate --db /data/approvals.sqlite
approval-engine verify --db /data/approvals.sqlite
approval-engine backup --db /data/approvals.sqlite --output /backup/approval.sqlite
```
Migration is repeatable and sets an explicit `PRAGMA user_version`. Backup uses
SQLite's online backup API, verifies `PRAGMA integrity_check`, writes mode 0600,
and refuses to overwrite a target.
Restore is a stopped-single-writer operation:
1. Stop the StatefulSet and confirm no approval-engine or migration process has
the PVC open.
2. Preserve the failed database and its `-wal`/`-shm` companions for analysis.
3. Copy the verified backup to a new database path with owner 10001 and mode
0600. Do not merge a backup with old WAL files.
4. Run `verify`, then `migrate` if the release schema is newer, then `verify`
again.
5. Start exactly one replica and prove approval/entry/outbox counts, readiness,
claim retrieval, and idempotent audit drain before reopening callers.
Approval mutation and outbox insertion share `BEGIN IMMEDIATE` and one commit;
a failed outbox insert rolls the mutation back. Delivery occurs afterward and
does not roll back a committed mutation.
## Schema v3 — declared PDP-path intent
`GH-DEC-2026-008` added `approvals.pdp_path` (INTEGER NOT NULL DEFAULT 0).
Migration is the usual additive `ALTER TABLE`; run `approval-engine migrate`
before a production start, which refuses an unmigrated store.
Legacy rows default to `0`. Intent is **not** back-filled from a recorded
`pdp_digest`: an approval issued before the ruling was never declared for the
PDP path, and inferring the declaration from an incidental digest would
manufacture a statement nobody made. Such approvals stay usable by consumers in
this engine's own vocabulary and are simply not usable on the PDP path — which
is the ruling's intended cost, not a migration defect.
## Schema v4 — recorded approver principal type
`entries.principal_type` (TEXT, nullable) records what kind of principal bound
an approval, taken only from the verified token. Migration is the usual additive
`ALTER TABLE`; run `approval-engine migrate` before a production start, which
refuses an unmigrated store.
Legacy entries stay `NULL` and are **not** back-filled — the same reasoning as
`pdp_path` in v3. An entry written before this column existed carries no
verified statement about the principal that made it, and reading `user:`-shaped
`subject_id` values as `human` would manufacture approver evidence nobody
presented. A `NULL` here means *unclassified*, never *human*.
Downgrade is not supported: an older server refuses the store on the version
check rather than reading the column, which is the intended direction. Restore
from a v4 backup onto a v3 release requires re-pinning forward, not editing
`user_version`.
## Schema v5 — explicit human-control declaration
GH-DEC-2026-016 adds `approvals.human_control`, constrained to 0 or 1, with default
0. Existing objects remain undeclared regardless of recorded approver types;
there is no retrospective declaration or change to their five-field binding
digest. A successor inherits the declaration. Linking an existing successor with
a different declaration conflicts and rolls back the parent transition.
New human-control entries require verified type human at the engine boundary.
The declaration is retained on object/claim and audit events. Claim and consume
also refuse inconsistent persisted human evidence. Tests cover a persistent v4
upgrade and preservation, signed API refusal, quorum and outbox rollback. Take
and verify the existing backup before migration. The old v3 image must not serve
a v5 database; rollback requires the matching pre-migration backup and the
existing single-writer recovery procedure. No live database is migrated by the
source test run.
## Schema v6 — verified tenant provenance
`entries.tenant_source` is nullable TEXT, recording only the verified issuer claim
(`directory`, `registration`, or `default`). Missing claims and pre-v6 entries
stay NULL; migration never infers membership or changes bindings, quorum,
human-control declarations, or existing outbox evidence. New issuance/use
threshold evidence retains this field, including null for unknown provenance.
Migration is additive and must precede serving the v6 release. The deployed
schema-v5 image remains pinned; source changes do not migrate production.
Publish/scan and pin the same v6 artifact for both containers, take and verify
the pre-migration backup, and obtain per-change rollout approval. An old v5
server refuses a v6 store; rollback uses the verified pre-migration backup with
the matching release and the stopped single-writer restore procedure above.
## Schema v7–v9 — assurance floor, actor consumption, revoker evidence
v7 adds nullable `approvals.required_assurance`. v8 adds `approvals.actor_consumes`
(0 or 1, default 0). v9 adds nullable `revoked_by_subject`,
`revoked_by_principal_type`, `revoked_by_assurance`, and `revocation_reason`.
Migration is additive. Legacy rows stay NULL, and `actor_consumes` stays 0.
Nothing is inferred for old approvals, entries, or revocations.
A v6 server refuses a v9 store. Rollback uses the verified pre-v9 backup with
the matching v6 release and the stopped single-writer restore procedure above.
Take and verify that backup, and open an isolated copy with the new image,
before the sole pod is replaced.