Files
acdl/core/audit_ledger_design.md
T
Jon Chery 0d2cbdb423 feat(P1): remove gitea/gitlab from synced files + simplify docs (REQ-230,231,232)
Genericize forge-detection code: gitea→forge/generic_forge, GITEA_ACTOR→FORGE_ACTOR.
Drop .gitea byte-identity test assertions (keep GitHub-side + contract conformance).
Add test_no_forge_mentions.py guard test (REQ-230).
Delete completed migration docs (NOVA_MIGRATION.md, NOVA_AWS_MIGRATION.md).
Move NO_HUMANS_THESIS.md to .ciagent/ (internal artifact).
Strip ciagent-internal provenance from synced docs (REQ-/D-/P-/CAP- IDs,
milestone headers, .ciagent/PROJECT.md citations).
Trim README.md (reusable deploy section, local key rotation paragraph).
Fix version-tag drift (@v1.13→@v1.19, acdl/→nova/).

---ci---
project: acdl
phase: 1
milestone: v1.20
status: execute
requirements: [REQ-230, REQ-231, REQ-232]
---/ci---
2026-08-07 18:20:29 +00:00

5.8 KiB

ACDL Tiered Audit Ledger Design (REQ-20)

Status: design authored in Phase 07 (milestone v1.1); the hash-chain + DynamoDB-outbox path is shipped + production since v1.8. The S3 Object Lock + JWS + async worker + DLQ + daily checkpoints build-out is deferred to a future milestone (D-083) — it requires non-offline-testable AWS infrastructure (Object Lock bucket, KMS signing key, SQS DLQ, Lambda worker) and is not in v1.9.

The audit stream is the platform's tamper-evident record of every delivery action. The vision's "Audit truth lives outside the repository" bet [1] and "Not a mutable audit log" anti-goal [1] are the binding constraints. Version-control history does not satisfy regulatory evidence; the ledger is the source of truth.

Three tiers

  • Cold tier (source of truth): S3 with Object Lock in compliance mode, 7-year retention (ARCHITECTURE.md §9). No one — including root — can delete or overwrite until retention expires. The regulatory record. Deferred to a future milestone (D-083).
  • Hot tier (query index): the acdl-evidence audit repo (unchanged from the v1.0 demo). Not part of the chain; a queryable mirror the evidence UI (evidence-ui/index.html) reads. Lightweight attestation linkage lives in the repo; the regulatory event body lives in S3.
  • Outbox (write path): DynamoDB, RPO = 0 (synchronous write before contract submission ack). Single-region in v1 (us-east-1). Shipped + production since v1.8.

Shipped scope (D-041) — production since v1.8

  • DynamoDB outbox: table acdl-outbox, PAY_PER_REQUEST (D-044), PK contractId, SK eventType#eventTs, TTL expire_at = now + 365d (1-year storage per ARCHITECTURE.md §8).
  • prev_event_hash chain: SHA-256 over canonical JSON (json.dumps(event, sort_keys=True, separators=(",", ":"))), lifted from the v1.0 demo's evidence_writer.py. Auto-genesis: first event has prev_hash="GENESIS".
  • Synchronous write via boto3 put_item (strong-consistent by default). No separate async worker / DLQ in v1.9 (RTO = workflow re-run).
  • Mirror to acdl-evidence: unchanged from v1.0 — the finalize step commits audit.json to the evidence repo (the hot tier).
  • Evidence event shape: {seq, ts, stage, event, prev_hash, hash, contractId, environment, stack, score, band}.

Deferred to a future milestone (D-083)

The following build-out was authored as design in Phase 07 and is not in v1.9. It requires AWS infrastructure that cannot be exercised offline (Object Lock bucket, KMS signing key, SQS DLQ, Lambda worker) and is deferred to a future milestone. The hash-chain + DynamoDB-outbox path above remains the v1.9 production audit record.

  • S3 Object Lock: bucket acdl-evidence-lock-<account-id>, Object Lock enabled at creation, compliance mode, 7-yr retention (RetainUntilDate = now + 7y). The outbox→S3 path is an async worker that reads from the outbox and writes to Object Lock.
  • JWS detached signature (RFC 7515): the event payload is canonical-JSON-serialized, SHA-256 hashed, signed with a private key; the signature is stored detached alongside the payload. Signing key = platform-level KMS key (not per-contract — a per-contract key would explode the key-management surface), rotated quarterly. The jws field is added to the event shape when this ships.
  • Async worker + DLQ: a Lambda (or a forge Actions scheduled workflow) reads the outbox, writes to S3 Object Lock, signs with KMS. DLQ = an SQS dead-letter queue for failed writes. RTO = DLQ replay.
  • Daily checkpoints (§9): a daily job reads the last event hash and writes a "checkpoint" event to the ledger (+ optionally to a public notarization service).

JWS vs chain — orthogonality note

The prev_event_hash chain gives ordering/tamper-evidence within the log (a deleted event breaks the chain visibly); JWS gives authenticity per event (a forged event is detectable without re-reading the whole chain). The chain is shipped (v1.8+); JWS is deferred (D-083). Together they cover both integrity properties the vision's "Not a mutable audit log" anti-goal requires.

Outbox item shape (shipped + deferred fields marked)

  • PK contractId (UUID).
  • SK eventType#eventTs (e.g. POLICY_CHECKED#2026-07-21T12:00:00Z).
  • payload (the event body — hash-chained in v1.8+; JWS-signed when D-083 ships).
  • prev_event_hash (chain link; GENESIS for the first event).
  • hash (this event's SHA-256 over canonical JSON).
  • approver_qa (CI username of the QA approver; populated on qa-promotion by v1.9's hitl_gates.attest — D-042).
  • approver_prod (SRE username; populated on prod-promotion by v1.9's hitl_gates.attest).
  • approver_dr (SRE username; populated on dr-promotion by v1.9's hitl_gates.attest).
  • environment, stack, score, band.
  • expire_at (TTL = now + 365d).
  • Deferred (D-083): jws (detached signature), checkpoint_ref.

RPO / RTO table

Phase RPO RTO
v1.8+ (production, shipped) 0 (sync outbox write) workflow re-run
Future milestone (D-083) 0 (sync outbox) async worker DLQ replay

Decision trail

  • D-041 — shipped scope = hash chain + outbox write; Object Lock + JWS + worker + DLQ are deferred (D-083).
  • D-044 — outbox mode PAY_PER_REQUEST; PK/SK; TTL expire_at = now + 365d; no separate async worker in v1.9.
  • D-042 — approver identities (approver_qa, approver_prod, approver_dr) live in the outbox; the separation-of-duties check (core/separation_of_duties.py) reads approver_qa and compares to the prod-dispatch CI actor. v1.9's hitl_gates.attest populates these attributes.
  • D-083 (v1.9) — S3 Object Lock + JWS + async worker + DLQ + daily checkpoints deferred to a future milestone. Requires non-offline- testable AWS infra.