Skip to content

Test the append-only guard the database actually runs - #154

Merged
davidmckayv merged 2 commits into
mainfrom
fix/the-append-only-test-reads-the-whole-migration-chain
Aug 22, 2026
Merged

Test the append-only guard the database actually runs#154
davidmckayv merged 2 commits into
mainfrom
fix/the-append-only-test-reads-the-whole-migration-chain

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

Two findings from @beardthelion, from #138 and their PR #139. Both are real and neither was covered by the fix that merged as #148.

The immutability test described a definition nothing runs

audit.test.ts read 0000_schema.sql and asserted on the function defined there:

const migration = await readFile(new URL("../drizzle/0000_schema.sql", ...));
expect(migration).toContain("CREATE FUNCTION prevent_audit_event_mutation");

0007 replaced that function. 0012 replaced it again and added the truncate trigger. So the assertions described a definition no database has run for a while, and would have gone on passing if the live one were edited out from under them — including if a later migration dropped the truncate trigger entirely.

A mirror test pinned to one file is a test of that file, not of the deployment. It now reads the whole chain, requires both triggers, and fails if any migration drops either.

What the guard does is still proved against a real database in audit-retention.integration.test.ts. This only holds that it is installed.

The trigger's own malformed-window branch was never reached

test("keeps everything when no policy is set", ...)   // window 0, -1, NaN

That one stops in TypeScript. sweepAuditTrail returns {deleted: null} before it ever opens a connection, so it proves the caller's guard and says nothing about the trigger — which has a branch of its own for this that nothing exercised.

That is the shape where a guard reads as covered and is not, and it matters because the trigger is the only thing standing between the table and anything that does not go through the sweep. The new test sets the value the sweep would have set and then deletes directly, for "0", "-1", "" and "garbage".

Changelog

Catches it up with tonight's five fixes: the truncate hole, the declined take-the-wheel, the routing trail, the partial connector grant, and answer provenance.

The truncate entry keeps the caveat the report was careful to state — the application connects as the database owner in the shipped compose file, and an owner can still disable or drop a trigger, so this closes the one-statement path rather than making the guarantee absolute.


On process: #139 was open before I built #148, and I did not check. The fix that merged is the same trigger with the same TG_OP-first ordering, because the issue laid out both. Their PR covered ground mine did not, and this is that ground, credited.

The immutability test read `0000_schema.sql` and asserted on the function
defined there. 0007 replaced that function and 0012 replaced it again and
added the truncate trigger, so the assertions described a definition no
database runs, and would have gone on passing if the live one were edited
out from under them. A mirror test pinned to one file is a test of that
file. It now reads the whole migration chain, requires both triggers, and
fails if a later migration drops either.

Also covers the trigger's own branch for a malformed retention window.
`sweepAuditTrail` returns before it opens a connection, so the existing
test proves the caller's guard and never reaches the trigger, which is
the branch anything not going through the sweep would meet.

Both reported by @beardthelion in #138 and written in their PR #139,
which arrived before the fix that merged and covered ground it did not.

Catches the changelog up with tonight's fixes, including the truncate
hole with the caveat about the owner role that the report was careful to
state.
The mention row's reason read "you chose them yourself". True in the
conversation and false on the audit page, where every row belongs to
somebody else and an administrator is the one reading it. The person is
already on the row as actorUserId; the reason only has to say what kind
of decision it was.

@zopeVaibhav had this right in #134.
@davidmckayv
davidmckayv merged commit 1d83969 into main Aug 22, 2026
8 checks passed
@davidmckayv
davidmckayv deleted the fix/the-append-only-test-reads-the-whole-migration-chain branch August 22, 2026 04:35
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