Make audit events immutable

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 22:35:35 -04:00
1 parent 20c8ef6ef3
commit 689fe861f1
4 files changed
+41 -1

No files matched your search

@@ -0,0 +1,16 @@
export const auditEventImmutabilitySchema = `
create trigger if not exists trg_audit_events_no_update
before update on audit_events
begin
select raise(abort, 'Audit events are immutable.');
end;
create trigger if not exists trg_audit_events_no_delete
before delete on audit_events
begin
select raise(abort, 'Audit events are immutable.');
end;
insert into schema_migrations(version, name, applied_at)
values (33, 'audit_event_immutability', datetime('now'));
`;
@@ -31,6 +31,7 @@ import { fileHandoffRecordsSchema } from "./migrations/029-file-handoff-records"
import { approvalSourceRefsSchema } from "./migrations/030-approval-source-refs";
import { providerRequestWorkspaceSchema } from "./migrations/031-provider-request-workspace";
import { browserActionMetadataSchema } from "./migrations/032-browser-action-metadata";
import { auditEventImmutabilitySchema } from "./migrations/033-audit-event-immutability";
interface Migration {
version: number;
@@ -71,6 +72,7 @@ const MIGRATIONS: Migration[] = [
{ version: 30, name: "approval_source_refs", sql: approvalSourceRefsSchema },
{ version: 31, name: "provider_request_workspace", sql: providerRequestWorkspaceSchema },
{ version: 32, name: "browser_action_metadata", sql: browserActionMetadataSchema },
{ version: 33, name: "audit_event_immutability", sql: auditEventImmutabilitySchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
@@ -13,6 +13,28 @@ function createConnection(): DatabaseConnection {
}
describe("audit service", () => {
test("keeps core audit events immutable", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
let auditId = "";
connection.transaction(() => {
const orgId = organization.createOrganization({
organizationName: "Acme Operations",
employeeName: "Dana Lee",
mission: "Coordinate agents with auditable approvals.",
goal: "Ship a verified control plane.",
});
auditId = (connection.db.prepare("select id from audit_events where org_id = ? limit 1").get(orgId) as { id: string }).id;
});
expect(() => connection.db.prepare("update audit_events set action = ? where id = ?")
.run("changed", auditId)).toThrow("Audit events are immutable.");
expect(() => connection.db.prepare("delete from audit_events where id = ?")
.run(auditId)).toThrow("Audit events are immutable.");
connection.close();
});
test("creates thread scoped replay runs from immutable audit events", () => {
const connection = createConnection();
const organization = new OrganizationService(connection.db);
@@ -23,7 +23,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]);
connection.close();
});