Migration note: This page documents a migration path or historical transition. It is not the current 3.2 happy path.
Migration: Verdict-Provenance Backfill
Status: Shipped with mission verdict-seam-write-unification-01KZ9Q35.
Migration id: verdict_provenance_backfill
(src/specify_cli/upgrade/migrations/m_zz_verdict_provenance_backfill.py).
Audience: Operators upgrading existing Spec Kitty projects across the
verdict single-authority cutover.
Why This Matters
The verdict-seam-write-unification-01KZ9Q35 mission made the append-only
event log (status.events.jsonl) the sole authority for a work package's
review verdict. Every verdict reader was collapsed onto the event-sourced
review_result slot, and the review-cycle-N.md frontmatter readers were
deleted — the irreversible half of single-authority.
That deletion is only safe if the event log already carries every verdict a
mission ever recorded. A mission created before the cutover can carry a
terminal .md verdict with no review_result event slot — a stranded
verdict. Once the frontmatter readers are gone, a consumer reading the event
authority mid-upgrade would simply not see that historical rejection.
This migration closes that gap (FR-012 / SC-008): it recovers each stranded
.md verdict into the event log so the authority is complete before anyone
reads it.
What It Does
On spec-kitty upgrade, for every mission under kitty-specs/:
- Find each WP with a terminal
review-cycle-N.mdverdict but no eventreview_resultslot (thestranded_verdict_findingspredicate). - Reduce that historical verdict into a hand-constructed
review_resultStatusEventappended tostatus.events.jsonl(backfill_verdict_provenance). The event's timestamp is the artifact's own historicalreviewed_at— nevernow()— so it sorts correctly in the reducer's(at, event_id)fold and never resurrects over a later approval.
The backfill spine is unchanged; this migration only adds the corpus walk.
When It Runs
Automatically, as an auto-discovered upgrade migration
(@MigrationRegistry.register + pkgutil.iter_modules), during
spec-kitty upgrade. It targets the current package version and runs after the
charter-fold migrations at the same version (the m_zz_ filename encodes that
ordering). No operator action beyond running upgrade is required.
Idempotency
The backfill is keyed on a deterministic ULID over
(mission_id, wp_id, verdict, cycle) and skips any WP that already carries a
review_result event slot. Re-running upgrade over an already-migrated
corpus therefore seeds nothing:
detect()returnsFalseonce every mission is converged.- A live
apply()appends zero events; a--dry-runreports a would-seed count of0.
Running it more than once is safe.
Verifying
spec-kitty accept carries a non-blocking diagnostic that reports any
remaining stranded verdict for the mission under acceptance (naming the WPs and
pointing at spec-kitty upgrade). A clean upgrade leaves it silent. The
diagnostic is advisory only — it never blocks acceptance and never raises.
Rationale / References
- Requirement: FR-012 (backfill) / SC-008 (safe reader collapse).
- Backfill library:
src/specify_cli/migration/verdict_provenance_backfill.py. - Follow-up: #3236
now tracks only the function-level census-exclusion narrowing for
_legacy_frontmatter_verdict, not the wiring — which this migration closes.