write-once row and TRUNCATE triggers on audit, audit_detail and the five worm_audit_* tables, with the error worm_audit: audit rows are write-once; UPDATE of "audit" is not allowed (id=…);
lookup tables: id and name changes are rejected;
grants: the app role gets INSERT and SELECT only;
worm_audit_purge(), a SECURITY DEFINER function executable only by worm_audit_purger. It purges only after retention and only a chain prefix, records PURGED and REFUSED rows, and the trigger also checks PG_CONTEXT, so a single-role host can't spoof the flag.
The DDL assumes snake_case columns (documented).
Guard: update and delete capabilities are removed from these tables in QQQ, and Postgres P0001 maps to a user-facing error.
Chain:
RFC 8785 canonical content (§5.1, agreed with secured-data);
a single-writer chainer: advisory lock, settle horizon, contiguous ids, late arrivals and late details through a GIN-indexed coverage check, batched reads;
Ed25519 checkpoints with Merkle roots, capped per checkpoint.
Verify:
a verifier paged by keyset with bounded memory, plus SQL completeness anti-joins, naming the exact break;
"Verify audit chain", a streamed-ETL table action with Review/Result lines;
the daily scheduled verification, which raises and resolves worm_audit.chain.break and sends a heartbeat through core QAlerts.
Assistant wall:worm_audit_* is registered reflectively until the qbit builds on RC.6. Core default: qqq#1163 (merged).
Stand-ins:
the secured-data audit token hook is mimicked in PhiCanaryTest; I'll switch to the real AuditValueProtectorInterface once the core PR is up (secured-data agent);
ClientChannelsMetaData is called reflectively (RC.6).
Tests (CI=true ./mvnw verify locally with Docker): 50 tests pass. They include 7 Postgres 16 Testcontainers tests: triggers, grants, the purge (refusal, anchor, spoofed flag rejected), real-backend tamper detection, 8 writers with 3 chainers in parallel, and the America/Chicago JVM zone. Coverage is 95.5% instructions and 100% classes.
## Summary
Phase (a) of `docs/DESIGN.md`: append-only enforced in the database, the hash chain, signed checkpoints and verification.
- **DDL** (`ddl/postgres/worm-audit.sql`, rendered default `docs/ddl/worm-audit-postgres.sql`, `WormAuditDdl`):
- write-once row and TRUNCATE triggers on `audit`, `audit_detail` and the five `worm_audit_*` tables, with the error `worm_audit: audit rows are write-once; UPDATE of "audit" is not allowed (id=…)`;
- lookup tables: id and name changes are rejected;
- grants: the app role gets INSERT and SELECT only;
- `worm_audit_purge()`, a SECURITY DEFINER function executable only by `worm_audit_purger`. It purges only after retention and only a chain prefix, records PURGED and REFUSED rows, and the trigger also checks `PG_CONTEXT`, so a single-role host can't spoof the flag.
- The DDL assumes snake_case columns (documented).
- **Guard:** update and delete capabilities are removed from these tables in QQQ, and Postgres P0001 maps to a user-facing error.
- **Chain:**
- RFC 8785 canonical content (§5.1, agreed with secured-data);
- a single-writer chainer: advisory lock, settle horizon, contiguous ids, late arrivals and late details through a GIN-indexed coverage check, batched reads;
- Ed25519 checkpoints with Merkle roots, capped per checkpoint.
- **Verify:**
- a verifier paged by keyset with bounded memory, plus SQL completeness anti-joins, naming the exact break;
- "Verify audit chain", a streamed-ETL table action with Review/Result lines;
- the daily scheduled verification, which raises and resolves `worm_audit.chain.break` and sends a heartbeat through core `QAlerts`.
- **Assistant wall:** `worm_audit_*` is registered reflectively until the qbit builds on RC.6. Core default: qqq#1163 (merged).
**Stand-ins:**
- the secured-data audit token hook is mimicked in `PhiCanaryTest`; I'll switch to the real `AuditValueProtectorInterface` once the core PR is up (secured-data agent);
- `ClientChannelsMetaData` is called reflectively (RC.6).
**Tests** (`CI=true ./mvnw verify` locally with Docker): 50 tests pass. They include 7 Postgres 16 Testcontainers tests: triggers, grants, the purge (refusal, anchor, spoofed flag rejected), real-backend tamper detection, 8 writers with 3 chainers in parallel, and the America/Chicago JVM zone. Coverage is 95.5% instructions and 100% classes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase (a) of
docs/DESIGN.md: append-only enforced in the database, the hash chain, signed checkpoints and verification.ddl/postgres/worm-audit.sql, rendered defaultdocs/ddl/worm-audit-postgres.sql,WormAuditDdl):audit,audit_detailand the fiveworm_audit_*tables, with the errorworm_audit: audit rows are write-once; UPDATE of "audit" is not allowed (id=…);worm_audit_purge(), a SECURITY DEFINER function executable only byworm_audit_purger. It purges only after retention and only a chain prefix, records PURGED and REFUSED rows, and the trigger also checksPG_CONTEXT, so a single-role host can't spoof the flag.worm_audit.chain.breakand sends a heartbeat through coreQAlerts.worm_audit_*is registered reflectively until the qbit builds on RC.6. Core default: qqq#1163 (merged).Stand-ins:
PhiCanaryTest; I'll switch to the realAuditValueProtectorInterfaceonce the core PR is up (secured-data agent);ClientChannelsMetaDatais called reflectively (RC.6).Tests (
CI=true ./mvnw verifylocally with Docker): 50 tests pass. They include 7 Postgres 16 Testcontainers tests: triggers, grants, the purge (refusal, anchor, spoofed flag rejected), real-backend tamper detection, 8 writers with 3 chainers in parallel, and the America/Chicago JVM zone. Coverage is 95.5% instructions and 100% classes.🤖 Generated with Claude Code