Repository navigation
Conversation
The audit log can be exported to a file for compliance evidence (API Reference → Audit → Exporting), in the shape the console needs (#56): - POST /v1/audit-exports: the same filters and caller scope as GET /v1/audit-events, plus format ndjson|csv; Idempotency-Key required; 202 with the export. 20 per caller per day. - GET /v1/audit-exports/{id}: status, and once ready a download link signed for 15 minutes. Requester and audit.view only. Kept 7 days. - A jobs worker (audit-exports, every minute; every 15 s in the devserver) pages through the events into a temp file and uploads it through a new ExportStore: a private S3 bucket with a 7-day lifecycle rule in AWS, .dev/audit-exports locally, in memory in tests. - audit.exported is written when the file is ready, with the requester as actor, in the export's own scope. - audit.go's scope and filters are shared by the list and the worker. - Migration 0007_audit_exports (after 0005 and 0006, keeping user.expired in the action check). Terraform: the exports bucket, env, IAM, and schedule. Retention needed no code: the hot window and archival already enforce it; the docs now say so. Closes #33 Closes #56 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
terraform plan (production)This is exactly what merging will change in AWS. Plan |
Brings in the Substratal-Version header and root paths (#37), the plan-job secret fix (#59), and the versioning docs (#63). - Conflicts in Audit, Orders & Audit, and NFR: this branch's rows, with root paths; main's side only differed by the removed /v1 prefix. The Changelog puts this branch's entry above main's two 2026-10-21 entries - openapi.yaml 1.0.0-draft.5 (main took draft.4 for the header change) - Root paths in audit_exports_test.go and in comments, descriptions, and docs this branch added; tools.json regenerated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
terraform plan (production)This is exactly what merging will change in AWS. Plan |
- Migration 0007_audit_exports → 0011_audit_exports: 0008_scim_tokens reached production first; #58 and #60 are now 0009 and 0010. Its audit action check is the full set, SCIM's actions included. - openapi.yaml 1.0.0-draft.8 (after #58 draft.6 and #60 draft.7), 123 operations; Changelog, OpenAPI index, roadmap, schema, action catalog, ids, and plan-remaining.md merged with SCIM's additions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
terraform plan (production)This is exactly what merging will change in AWS. Plan |
This branch has not been deployed
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.
Closes #33. Closes #56 (the console's minimum export shape, which this follows).
What's new
POST /audit-exports:GET /audit-events, as body fields (actionis an array,target_user_idmay beme), plusformat:ndjson(the default) orcsv.audit.viewonly their own trail or a Tenant they own.Idempotency-Keyis required. Returns202with the export at once. Limited to 20 per caller per day.GET /audit-exports/{id}: status, plus once it'sreadya download link signed for 15 minutes, freshly signed on each call. Only the requester andaudit.viewcan see an export; anyone else gets404 audit_export_not_found. Records and files are deleted after 7 days.audit-exportsjob runs every minute; the devserver runs it every 15 seconds.for update skip locked, and retries arunningexport whose worker died once it's 20 minutes old.too_many_rows.ExportStoreinterface: S3 in AWS (AUDIT_EXPORT_BUCKET),.dev/audit-exportslocally, and in memory in tests.audit.exported(targetaudit_export) is written when the file is ready. The requester is the actor,afterholds the filters, format, and row count, and the event is written in the export's own scope, so it shows up in the trail that was exported.audit.go's scope and filter building is shared by the list and the worker. List behavior is unchanged.Retention
#33 also asked for retention-policy enforcement. That's already built: the one-year hot window, and the nightly
audit-archivejob that moves older events, shape-only, to indefinite Glacier Deep Archive storage. The Changelog and roadmap now say so; no new code was needed.Infrastructure (Terraform)
substratal-prod-audit-exports-<account>: SSE-S3, public access blocked, with a lifecycle rule that deletes objects after 7 days.s3:PutObjecton it.s3:GetObjecton it, to sign download links.AUDIT_EXPORT_BUCKETis set on both Lambdas.audit-exportsschedule,rate(1 minute).terraform validatepasses. Cost is negligible: one schedule a minute, and short-lived objects.Not resolved here
Decision #37 (#7: audit immutability through a trigger, or a separate database role) is still your call. #33 listed it, but export doesn't depend on it: an export only reads.
Tests
TestAuditExport:audit.viewmatches the list, row for row, oldest first.audit.exportedis written with the requester as actor.me, can export their own trail, and can't see someone else's export.The full integration and contract suite, gen-check, and terraform fmt and validate pass locally.
Merge order
0011comes after0009(Build test-mode User expiry (expires_at) #58) and0010(Open POST /applications to self-service registration #60), all after0008_scim_tokensonmain.draft.8(123 operations), after Build test-mode User expiry (expires_at) #58 and Open POST /applications to self-service registration #60. Stack the Changelog entries in merge order.Update (2026-10-22): rebased on the current API
main: header versioning (Move the API version from the URL to a Substratal-Version header #37, Spell out how API versioning works across the docs #63) and SCIM (Add SCIM 2.0 provisioning for Organizations #62). Every path is at the host root withSubstratal-Version: 1, and this description now uses root paths.0007_audit_exports→0011_audit_exports.0008_scim_tokensreached production first, and migrations are numbered in the order they ship. Its audit action check is now the full set, including SCIM'sorganization.scim_token_*and the other open PRs' actions, so no merge order can drop an action.info.versionis1.0.0-draft.8. SCIM tookdraft.5; the open PRs are numbered Build test-mode User expiry (expires_at) #58 →draft.6, Open POST /applications to self-service registration #60 →draft.7, Add Audit Event exports (POST/GET /audit-exports) #61 →draft.8, assuming they merge in that order.🤖 Generated with Claude Code