wiki_lint Transaction Isolation

Lint() in api/internal/storage/postgres.go wraps both SQL queries in a REPEATABLE READ, READ ONLY transaction.

Why

Without this, a page deleted between the orphan query and the dead-link query silently vanishes from both result sets — the lint report looks clean when it shouldn't.

  • Orphan query: finds pages with no inbound links
  • Dead-link query: finds links pointing to non-existent pages

A page deleted mid-flight no longer appears in wiki_pages, so it's excluded from both checks. Result: false-clean report.

Implementation

tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{
    IsoLevel:   pgx.RepeatableRead,
    AccessMode: pgx.ReadOnly,
})
defer tx.Rollback(ctx) // safe — read-only tx, no commit needed

Both pool.Query() calls replaced with tx.Query(). The transaction's consistent snapshot guarantees both queries see the same DB state.

Race frequency

Rare in practice. The fix prevents false-clean reports during concurrent bulk edits or deletes running alongside a lint check.

  • lint-index — composite index for query performance
  • lint-cooldown — cooldown to limit lint call frequency