feat(audit): attribute workflow audits to their run and merge a run's repeated row audits

Merged
WA
3d ago
+528-28

Summary

  • Executor-delegated audit actors now carry workflowId and executionId, so a workflow-made audit entry traces back to its workflow and run (before, only a per-call delegationId was recorded)
  • New recordAuditRollup / flushAuditRollups in @sim/audit: repeats of the same event within a 2s window (max 100 per row) become one row. Nothing is dropped:
    • a lone occurrence is written exactly as it is today
    • a merged row sums the counters, collects every identifier (rowIds, delegationIds), and records rollup: { entries, firstAt, lastAt }
  • Table row audits (table.updated from insert/update/delete/upsert/replace) opt in, but only for an executor delegation that names its run and has no client request. They merge only within the same execution, table, op, and metadata; session, API key, CLI, and copilot writes are unchanged
  • Server-side credential.accessed (no client request, e.g. a run refreshing a token per tool call) merges per actor + credential; requests with a client still write one row each
  • No-loss guarantees:
    • executeWorkflowCore awaits flushAuditRollups() before returning, so a background run's process can't freeze or exit with rows pending; the flush also waits for writes already in flight
    • window timers are ref'd, so a natural process exit still writes them
    • each pending row is its own insert, so one bad row can't take others with it
    • created_at stays the DB default, so drains' 5-minute stability cursor can't skip a delayed row
    • the surface is captured when the event is recorded, not when it's flushed
  • Docs: audit-logs page describes merged entries and the new actor fields

Type of Change

  • New feature

Testing

  • row-audit.integration.ts (real Postgres): 3 new cases
    • one run's repeated inserts → 1 row with all row ids, summed count, distinct delegation ids
    • two runs never merge
    • a lone run write keeps today's exact shape
    • all 3 fail with the rollup branch disabled and pass with it; the 8 existing cases still pass
  • Unit: principal.test.ts, token-resolution.test.ts (negative cases now also assert no rollup), authorized-workspace-use-case.test.ts, execution-core.test.ts, rows.test.ts, selectors (427 tests)
  • bun run lint; type-check and check:audits left to CI

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)