fix(compliance): normalize anchor timestamps#925
Merged
Conversation
ericodom
added a commit
that referenced
this pull request
May 7, 2026
`AnchorFn` is now `=> Promise<...>` in U8b. The timestamp-normalization test added in #925 used a sync stub, which fails typecheck against the new contract. Switch the stub to `async () => ({ anchored: false })` — test still exercises the same path (recorded_at coercion → drainer update) since runAnchorPass awaits the result either way. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ericodom
added a commit
that referenced
this pull request
May 7, 2026
…k retention) (#927) * feat(compliance): U8b — anchor Lambda live (S3 PutObject + Object Lock retention) Replaces `_anchor_fn_inert` with `_anchor_fn_live`, which performs the actual S3 PutObject of per-tenant proof slices and the global anchor JSON to the WORM-locked compliance bucket. The anchor object carries an explicit `ObjectLockMode` + `ObjectLockRetainUntilDate` per-object override (mirroring the bucket-default), so the retention contract is portable across buckets and visible at the call site. Slices write under `proofs/tenant-{id}/cadence-{cadence_id}.json` (no per-object lock; bucket default applies); anchor writes last so a partial failure never publishes a verifier-discoverable commit point. Five guards land alongside the body swap: * **Deterministic cadence_id** — sha256 of canonical chain-head fingerprint, reshaped to UUIDv7 form. Same heads produce the same cadence_id, so a retry after a partial failure overwrites its own slice keys instead of orphaning slices for the full 365-day retention window. * **Merkle self-check** — `_anchor_fn_live` recomputes the root from the received leaves and asserts equality before any PutObject. Cheap insurance against latent runAnchorPass arithmetic bugs becoming WORM-locked poisoned evidence. * **Layer 2 body-swap test** — `compliance-anchor-s3-spy.test.ts` mocks S3Client.send and asserts the live function actually issues PutObjectCommand for both slices and anchor (with SHA256 checksum, SSE-KMS, and ObjectLock retention on the anchor key only). Pairs with the Layer 1 identity assertion (`getWiredAnchorFn() === _anchor_fn_live`) in the integration test. * **Sibling watchdog IAM role** — watchdog moves OFF the shared lambda role onto a dedicated role with `kms:DescribeKey` only on the bucket CMK (NOT `kms:Decrypt` — the watchdog never reads object bodies), `s3:ListBucket` prefix-conditioned on `anchors/`, and an explicit Deny on every Delete + Bypass + Lock-mutation action so future role broadening cannot turn the watchdog into a deletion vector. * **Dev-COMPLIANCE precondition** — `var.allow_compliance_in_non_prod` (default false) blocks accidentally locking a dev bucket into irreversible COMPLIANCE bytes via a stage typo. Watchdog flips to live: `mode: "live"`, ListObjectsV2 with 1000-key truncation warning, max-LastModified pick, `ComplianceAnchorGap` metric emission (suppressed on greenfield-empty bucket), heartbeat unchanged. The CloudWatch alarm cuts over: gap → `treat_missing_data = breaching` (catches both real gaps and a watchdog-down regression); a sibling heartbeat-missing alarm is born `notBreaching` so deploy-time gaps don't fire it before the first heartbeat lands (Decision #7). Operator pre-merge step: `terraform state mv` the watchdog from the for_each handler set to the new standalone resource address. Without it, the next `terraform apply` fails with ResourceConflictException on the function name. Plan documents the exact command. Plan: docs/plans/2026-05-07-012-feat-compliance-u8b-anchor-lambda-live-plan.md Master plan: docs/plans/2026-05-06-011-feat-compliance-audit-event-log-plan.md (U8b) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(review): apply autofix feedback Drop unused drizzle-orm imports flagged by ce-code-review: - compliance-anchor.ts: `and`, `eq`, `gt`, plus the `auditEvents` schema import (raw SQL via `` sql`...` `` is the actual codepath there) - compliance-anchor.integration.test.ts: `and`, `gt`, `auditOutbox` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(compliance): make compliance-anchor.test.ts stub anchorFn async `AnchorFn` is now `=> Promise<...>` in U8b. The timestamp-normalization test added in #925 used a sync stub, which fails typecheck against the new contract. Switch the stub to `async () => ({ anchored: false })` — test still exercises the same path (recorded_at coercion → drainer update) since runAnchorPass awaits the result either way. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
ericodom
added a commit
that referenced
this pull request
May 8, 2026
Comprehensive docs for the compliance audit-event-log feature shipped via U1–U11. Eight files covering four audience cuts (operator, auditor, developer, on-call) plus three structural docs (README, overview, architecture) and a chronological changelog. - docs/compliance/README.md — entry point + navigation table. - docs/compliance/overview.md — what the module does + non-goals + U1–U11 roster with PR links + strategic context. - docs/compliance/architecture.md — Mermaid substrate diagram + ASCII fallback + Aurora role split table + S3 prefix contract + RFC 6962 hash chain explainer + 14-event slate + tier semantics. - docs/compliance/operator-runbook.md — 8 procedures (inspect events, request export, apply hand-rolled migration, rotate Aurora roles, GOVERNANCE→COMPLIANCE flip, drain anchor/exports DLQs, re-run failed export). Each procedure starts with "When to use this". - docs/compliance/auditor-walkthrough.md — 10-step SOC2 Type 1 narrative with screenshot placeholders to fill in during U11.U5 rehearsal. Covers admin browse, walk-back-10-events, export + download, audit-verifier CLI run. - docs/compliance/developer-guide.md — adding a new event type (6 numbered steps from schema to test), tier semantics table, cross-runtime emit path (Strands), adding a new compliance Lambda (5-step checklist), where the tests live (15-row table). - docs/compliance/oncall.md — alarm-to-playbook quick reference + procedures for anchor DLQ, exports DLQ, watchdog heartbeat missing, anchor gap, drift gate failure, audit_outbox runaway, Strands env shadowing. Plus irreversibility warnings list. - docs/compliance/changelog.md — chronological PR table for every compliance commit + bordering fixes (#895, #905, #925, #942). - CLAUDE.md — one-line pointer to docs/compliance/README.md added to the existing docs/ bullet. Plan: docs/plans/2026-05-08-008-docs-compliance-module-documentation-plan.md Verification: - All 26 cited file paths verified to resolve on origin/main HEAD. - All 13 cited test paths verified. - All 17 cited PR numbers verified MERGED via gh pr view. - Aurora role GRANTs transcribed verbatim from packages/database-pg/drizzle/0070_compliance_aurora_roles.sql. - 14-event slate transcribed verbatim from packages/database-pg/src/schema/compliance.ts COMPLIANCE_EVENT_TYPES. - Markdown renders cleanly (no broken table syntax in GitHub preview). Deferred: U11.U5 SOC2 rehearsal will capture the 10 screenshots listed in auditor-walkthrough.md and validate the walkthrough script against actual deployed-dev UI. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
recorded_atvalues from compliance chain-head reads before Drizzle writes them back totenant_anchor_statereaderDb.executeRoot cause
The dev deploy failed in
Compliance Anchor SmokebecausereadChainHeadstyped raw SQLrecorded_atasDate, but the deployed pg path returned it as a string. Drizzle then attempted to serialize that string into a timestamp column during thetenant_anchor_stateupsert and threwe.toISOString is not a function.Verification
pnpm --filter @thinkwork/lambda test -- compliance-anchor.test.tspnpm --filter @thinkwork/lambda typecheckgit diff --checkbash scripts/build-lambdas.sh compliance-anchor