Skip to content

Preserve audit logs when a connection is deleted - #15

Merged
dsbaars merged 2 commits into
mainfrom
feat/preserve-audit-logs-on-connection-delete
Jun 24, 2026
Merged

Preserve audit logs when a connection is deleted#15
dsbaars merged 2 commits into
mainfrom
feat/preserve-audit-logs-on-connection-delete

Conversation

@dsbaars

@dsbaars dsbaars commented Jun 24, 2026

Copy link
Copy Markdown
Owner

What

Audit logs (SigningLog) currently cascade-delete with their connection. This decouples them (Option B) so the signing history survives connection deletion.

  • SigningLog.connectionId → nullable, FK ON DELETE CASCADESET NULL
  • Denormalized userId / connectionName / clientPubkey onto each log so an orphaned record stays scoped + readable
  • New user FK (ON DELETE CASCADE) so deleting a user still purges their logs
  • Dashboard activity + stats scope by the denormalized userId instead of the connection relation (otherwise orphaned logs vanish from the feed)

Tests

  • Unit: getDashboardActivity scopes by userId (regression guard)
  • DB integration (RUN_DB_TESTS=1): proves SET NULL retention + user-delete cascade against a real Postgres
  • CI: new integration job runs migrate deploy (full chain from scratch), a schema/migration drift check, and the integration tests against a migration-built Postgres

Notes

  • Migration includes a backfill from existing connections (no orphan logs exist yet).
  • Deleted connections still show in the feed by name; the filter dropdown still lists live connections only (can extend if wanted).

dsbaars added 2 commits June 24, 2026 21:47
Decouple SigningLog from the connection lifecycle (Option B): the
connection FK becomes nullable with ON DELETE SET NULL, and userId /
connectionName / clientPubkey are denormalized onto each log so an
orphaned record stays scoped to its user and readable in the dashboard.
A new user FK (ON DELETE CASCADE) keeps user-deletion purging logs.

Dashboard activity + stats now scope by the denormalized userId instead
of the connection relation, so deleted-connection logs no longer vanish
from the feed.

Tests:
- unit: getDashboardActivity scopes by userId (regression guard)
- DB integration (RUN_DB_TESTS=1): SET NULL retention + user-delete cascade
- CI: new `integration` job runs migrate deploy (full chain), a
  schema/migration drift check, and the integration tests against a
  migration-built Postgres
…tests

Panel-review follow-ups:
- stats: connectionNames now unions live connection names with the names
  preserved on signing_logs, so a deleted connection (logs orphaned with
  connectionId=null) stays selectable in the dashboard filter.
- unit: assert every signingLog count/groupBy in getDashboardStats scopes
  by the denormalized userId (guards against a revert to connection.userId
  silently dropping orphaned logs from charts).
- integration: prove orphaned logs are still aggregated into dashboard
  stats and the deleted name remains in connectionNames after delete.

@dsbaars dsbaars left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-engine panel review — checked-out branch @ 05a857d

Independent review by four engines (each read the real code on the branch; cursor-agent and GLM also reproduced the migration chain + integration tests against a throwaway Postgres).

Engine Verdict
Claude (primary) ✅ PASS
Sonnet 4.6 ✅ PASS
cursor-agent (auto) ✅ PASS
opencode / GLM 5.2 ✅ PASS

Verified correct (with evidence)

  • Migration is atomic & correct — backfill runs before SET NOT NULL; all source columns are NOT NULL on BunkerConnection, so no nulls slip through; FK swap to SET NULL + new user-cascade FK + index match schema.prisma; prisma migrate diff reports no drift (reproduced live by 3 engines). Prisma runs the migration in a single transaction on Postgres, so a concurrent insert cannot interleave between ADD COLUMN and SET NOT NULL — there is no mid-migration rollback race.
  • Scoping change is complete — every query that previously scoped SigningLog via the connection relation (Prisma calls + all three $queryRaw blocks) now scopes by the denormalized userId. No call site missed → orphaned logs neither vanish nor leak.
  • Single writer (bunker-rpc.handler.ts) sets all required fields from the server-derived connection row; TypeScript enforces them. Tenancy holds: reads scope by JWT req.user.sub; userId is written from connection.userId, never client input.
  • Integration tests prove ON DELETE SET NULL retention + user-delete cascade at the DB level.

Findings & resolution

  • [was P2] Filter dropdown excluded deleted-connection namesRESOLVED in 05a857d: connectionNames now unions live connection names with the names preserved on signing_logs, so a deleted connection stays filterable.
  • [was P2 / coverage] stats path under-testedRESOLVED in 05a857d: a unit test now asserts userId-scoping on every count/groupBy in getDashboardStats (guards against a silent revert), and an integration test proves orphaned logs stay aggregated into stats and the deleted name stays in connectionNames.
  • [P3] getLogsForConnection scopes by connectionId after an ownership pre-check — defence-in-depth only; pre-existing.
  • [LOW] connectionName is frozen at write time (a rename won't rewrite historical logs) — intentional audit immutability.
  • [LOW] a deleted connection's per-connection logs endpoint 404s — orphaned history is dashboard-only by design.
  • [nit, pre-existing] LogResult.DENIED is never written (denied actions log as ERROR) — not touched by this PR.

Bottom line

Correct, complete, and well-tested. The central safety claim was verified live by three independent engines plus the primary pass. The two substantive follow-ups are fixed in 05a857d. Verdict: APPROVE (posted as a comment — GitHub blocks self-approval since the reviewer is the PR author).

@dsbaars
dsbaars merged commit 17c72cb into main Jun 24, 2026
4 checks passed
@dsbaars
dsbaars deleted the feat/preserve-audit-logs-on-connection-delete branch June 24, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant