Files
pad/internal/models
xarmian ffc11387e1 feat(verify): verification evidence tied to a revision, slice 1 (TASK-3617) (#2228)
* feat(verify): verification evidence tied to a revision, slice 1 (TASK-3617)

Store, REST and the read-time judgment for per-criterion verdicts:
- item_verifications (SQLite 150, PG 120), append-only, with the NUL
  census classified and trigger file 151 generated.
- internal/verification: criterion hash (whitespace-collapsed), the
  removed / changed / superseded / current state, and the advisory
  possibly_stale hint from a linked PR merged after the verdict.
- POST/GET /items/{ref}/verifications: atomic batch, seq-guarded so a
  criterion hash is stored against the body it came from, via resolved
  in-workspace and visibility-checked, activity "verified", item_updated
  SSE.
- github_pr carries merge_sha / merged_at, fetched by pad github link
  and reconcile.

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* test(verify): router tests, body-order sort, compress census, CLI merge fields (TASK-3617)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* test(verify): close mutant holes M5 (re-spacing) and M8 (merged before the verdict); placeholder folds into missing (TASK-3617)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* docs(api): the verifications endpoint (TASK-3617)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* fix(verify): r1 findings: the retry re-checks access; a moved item is told from a gone via (TASK-3617)

- F1: when the item moves between the criteria read and the write, the
  retry re-runs visibility and edit permission on the fresh item.
- F2: a zero-row insert re-checks the target in the transaction, so a
  Postgres seq race retries (or 404s) instead of answering unknown_via.
- F3: verificationBeforeInsertHook test seam; tests for one move, an
  item that keeps moving (409), and a grant revoked mid-retry (404).

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* fix(verify): r2 L1, the retry re-resolves via; gate findings (TASK-3617)

- The retry re-runs the via resolution and its visibility check, the same
  re-check F1 added for the item; TestTASK3617_RetryRechecksVia.
- The state-history test asserted by row position, and one batch's rows
  share a created_at, so their order is by random id: assert by criterion.
- nulscan.go's protected-column count (146: 27 JSON, 119 text).

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* test(store): the scan-cost pin follows the list, 146 columns (TASK-3617)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* chore(verify): drop the comment resolveVia's doc comment superseded (TASK-3617 r3)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* fix(verify): r4, verifications travel with a workspace export/import (TASK-3617)

- F1: VerificationExport, exported for the bundle's live items and
  imported with the item and via ids remapped, every other value
  verbatim. Each bundle row is checked before its insert (a refused
  statement on Postgres loses the whole import); a row that fails, or
  whose item did not import, is dropped and counted. Tests: a SQLite round
  trip, the migrate-to-pg path (SQLite export into a Postgres import with
  the slug preserved), and a hostile bundle on both backends.
- F2: docs/api.md says a criterion repeated in one request is
  validation_error, as the code answers.
- Q2: gh's mergedAt zero time is read as not merged.

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* fix(store): item_verifications is a migrated table (TASK-3617 r4 gate)

The export now carries verifications, so the three lists that name what
`pad db migrate-to-pg` copies must name it too: MigratedTables (the NUL
preflight scans it), its export-section test map, and
migratedRefusalTables (the post-migration write refusal covers it).
Swept by item_reminders across cmd/, internal/ and docs/: those three
are the population.

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* chore(store): renumber TASK-3617 migrations after #2230 (sqlite 151/152, pg 121)

#2230 took sqlite 150 and pg 120 for decision_jobs.not_before. The
verification table is now sqlite 151 / pg 121, and its NUL trigger file
is 152_nul_invariant_triggers_post151 (regenerated, so the trigger names
carry pad_nul152_). References swept by the old file names and the bare
numbers in this branch's diff.

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW

* test(verify): an id that is both a criterion and a placeholder is refused (TASK-3617, lead spot mutant)

Claude-Session: https://claude.ai/code/session_01DYmGZ1nCgxXCX4jPsQu5MW
2026-10-19 10:25:43 -04:00
..