feat(auditlog): write organization events to audit_log - #2791
Merged
Conversation
Adds organization_id to the domain event and stores organization-level (admin) events in the audit_log table with an empty environment_id, scoped by their organization id, instead of admin_audit_log. All NewAdminEvent producers now pass their entity's organization id; system-level admin subscription events pass an empty one. This freezes admin_audit_log: nothing writes to it anymore. The read path and console filters follow in a separate PR. Step 2 of #1982. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Routes organization-level administrative events into the organization-scoped audit_log table.
Changes:
- Adds organization IDs to domain events and all producers.
- Persists all events through
AuditLogStorage. - Updates account transactions, database inserts, wiring, and tests.
Reviewed changes
Copilot reviewed 23 out of 25 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
proto/proto.lock |
Records the new event field. |
proto/event/domain/event.proto |
Adds organization_id. |
pkg/web/cmd/server/server.go |
Wires account audit storage. |
pkg/team/api/api.go |
Scopes team events. |
pkg/subscription/command/admin_subscription.go |
Marks subscriptions system-level. |
pkg/subscriber/processor/auditlog_persister.go |
Unifies persistence routing. |
pkg/subscriber/processor/auditlog_persister_test.go |
Tests unified extraction. |
pkg/subscriber/cmd/server/server.go |
Removes admin persister wiring. |
pkg/environment/api/project.go |
Scopes project events. |
pkg/environment/api/organization.go |
Scopes organization events. |
pkg/environment/api/environment_v2.go |
Scopes environment events. |
pkg/domainevent/domain/event.go |
Populates organization IDs. |
pkg/auditlog/storage/v2/postgres/sql/auditlog/insert_audit_logs_v2.sql |
Adds bulk insert column. |
pkg/auditlog/storage/v2/postgres/sql/auditlog/insert_audit_log_v2.sql |
Adds single insert column. |
pkg/auditlog/storage/v2/postgres/audit_log.go |
Supplies PostgreSQL organization values. |
pkg/auditlog/storage/v2/mysql/sql/auditlog/insert_audit_logs_v2.sql |
Adds bulk insert column. |
pkg/auditlog/storage/v2/mysql/sql/auditlog/insert_audit_log_v2.sql |
Adds single insert column. |
pkg/auditlog/storage/v2/mysql/audit_log.go |
Supplies MySQL organization values. |
pkg/auditlog/domain/auditlog.go |
Carries organization scope. |
pkg/account/command/account_v2.go |
Scopes account events. |
pkg/account/api/api.go |
Uses regular audit storage. |
pkg/account/api/api_test.go |
Updates service mock wiring. |
pkg/account/api/account.go |
Writes account audits to audit_log. |
pkg/account/api/account_test.go |
Updates audit-write expectations. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Address review comments on PR #2791: - is_admin_event said admin events are stored in the AdminDomainEvent and AdminAuditLog tables; they are now stored in the audit_log table with an empty environment_id, scoped by organization_id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines
+134
to
+136
| // Organization-level (admin) events are stored in the audit_log table | ||
| // with an empty environment id, scoped by their organization id. | ||
| auditlogs = append(auditlogs, domain.NewAuditLog(event, event.EnvironmentId)) |
…ucers Address review comments on PR #2791: - An event published by a producer that predates the organization_id field unmarshals with an empty organization id. Routing it to audit_log would strand it with both scopes empty, unrecoverable by the admin_audit_log history migration. Such events now fall back to admin_audit_log until old producers and queued events are drained. - System-level entities (admin account, admin subscription) legitimately have no organization and still go to audit_log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
t-kikuc
approved these changes
Sep 1, 2026
| } | ||
| // The legacy organization-scoped event goes to admin_audit_log so the | ||
| // history migration can resolve its organization later. | ||
| assert.Len(t, adminAuditLogs, 1) |
Contributor
There was a problem hiding this comment.
assert.Len does not stop the test, so if a routing regression sends the legacy event to auditlogs instead, adminAuditLogs[0].Id on the next line panics with an index-out-of-range and the package's other tests never report. Use require.Len here.
Suggested change
| assert.Len(t, adminAuditLogs, 1) | |
| require.Len(t, adminAuditLogs, 1) |
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.
Part of #1982, Follows #2789
What this PR does
Adds
organization_idto the domain event and stores organization-level (admin) events in theaudit_logtable — with an emptyenvironment_id, scoped by their organization id — instead ofadmin_audit_log.Background / Why this PR is needed
Organization-level events (account changes, environment/project creation, organization updates) go to
admin_audit_log, which only system admins can read, so organization admins cannot see their own organization's activity. With theorganization_idcolumn in place (#2789), this switches the write path so new organization events land inaudit_logwhere they can be served per-organization. After this PR the only writes toadmin_audit_logare a rollout-compatibility fallback for organization-scoped events published by older producers (noorganization_idyet), so the history migration that follows can still resolve them; once old producers and queued events are drained, the fallback goes away and the table is frozen. The read path and console filters come in a separate PR.Points
NewAdminEventproducer now passes its entity's organization id (organization, project, environment, account, team). Admin subscription events are system-level and pass an empty organization id, so they land inaudit_logwith bothenvironment_idandorganization_idempty — visible to system admins only.admin_audit_loginstead of being stranded inaudit_logwith both scopes empty; a TODO marks the fallback for removal once old messages drain.Event.IsAdminEventis kept and its notification semantics are untouched (the domain event informer still uses it); only the storage routing changed.admin_audit_logtoaudit_log. The dedup between that write and the published event is unchanged: same event id, duplicate-key insert → ack.AdminAuditLogStorageis removed from the subscriber persister and wiring, but the storage itself and the read APIs stay until the read-path switch and cleanup.admin_audit_login production.