---ci--- project: acdl phase: 39 milestone: v1.9 status: execute ---/ci--- Phase 39 — design-doc-refresh-and-p1-1-parameterization: Design docs (REQ-100, REQ-101): - hitl_matrix_design.md: 'dev-only spike'/'v1.2 wires the gates' framing replaced with v1.9 wired-gates reality; 8-concern matrix marked implemented (offline-testable subset + signed evidence artifacts, D-084); v1.9 wiring section cross-references hitl_gates.py + attestation_matrix.py; approver_dr noted. - audit_ledger_design.md: outbox marked shipped+production since v1.8; S3 Object Lock + JWS + async worker + DLQ + daily checkpoints clearly labeled 'Deferred to a future milestone (D-083)'; RPO/RTO table updated; approver fields note v1.9 hitl_gates.attest. P1-1 adapter parameterization (REQ-102, D-085): - ecs-service interface.json: desired_count (default 1), launch_type (FARGATE), family (app) inputs added. - alb interface.json: load_balancer_type (application), target_type (ip). - adapter.py: hardcoded defaults replaced with inputs.get(<name>, <default>); hardcoded 'acdl-microservice-rt'/'acdl-microservice-igw' Name tags derive from the VPC name input. - contract_resolver.py: child_input_map routes wires to the sub-resource that declares the input (desired_count → aws:ecs:service, family → aws:ecs:task_definition, target_type → targetgroup, etc.). - microservice composition.json: wires added for the new inputs. Tests: +21 (test_p1_1_adapter_parameterization.py, test_design_docs_current.py). 371 passed; run_ci.sh green; run_platform.sh --check-only green; v1.1 S3 regression preserved.
5.9 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-evidenceaudit 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), PKcontractId, SKeventType#eventTs, TTLexpire_at= now + 365d (1-year storage per ARCHITECTURE.md §8). prev_event_hashchain: SHA-256 over canonical JSON (json.dumps(event, sort_keys=True, separators=(",", ":"))), lifted from the v1.0 demo'sevidence_writer.py. Auto-genesis: first event hasprev_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 commitsaudit.jsonto 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
jwsfield is added to the event shape when this ships. - Async worker + DLQ: a Lambda (or a Gitea 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;GENESISfor the first event).hash(this event's SHA-256 over canonical JSON).approver_qa(Gitea/GitHub username of the QA approver; populated on qa-promotion by v1.9'shitl_gates.attest— D-042).approver_prod(SRE username; populated on prod-promotion by v1.9'shitl_gates.attest).approver_dr(SRE username; populated on dr-promotion by v1.9'shitl_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; TTLexpire_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) readsapprover_qaand compares to the prod-dispatchgitea.actor/github.actor. v1.9'shitl_gates.attestpopulates 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.