Files
Julian Knutsen 362eecc6bf test(migration): wisp-plane upgrade corpus — seed and assert the ignored-plane classes the 1.3.0 chain depends on (#6049)
* test(migration): seed and assert the wisp plane on the v1.1.2 upgrade lane

The migration-test harness has never seeded a wisp. Its shared fixture is three
issues, a dependency, a label and a comment, and thirteen version lanes assert
its exact `length == 3` shape — so the plane that 1.3.0's chain actually
rewrites (wisps, wisp_dependencies, wisp_comments, leases, events and the
ignored-migration cursor) reaches the candidate empty, and every migration in
main 0054-0066 plus ignored 0012-0025 runs as DDL over zero rows.

Add a separate `run_wisp_plane_upgrade` lane in its own workspace, dispatched
after the shared embedded lane the way the v0.55.4 second lane already is, so
no existing assertion changes. It seeds two issues and five wisps covering the
open, closed, blocked-by-issue, blocked-by-wisp and conditional-blocks classes,
plus a wisp comment, a wisp label, an issue-blocked-by-wisp edge through
dependencies.depends_on_wisp_id, and a file:// Dolt remote published with the
old binary.

There is no `bd dolt sql`, so plane state is read with the already-pinned
external Dolt v2.1.8 CLI as a read-only oracle. `dolt sql -q` can exit 0 on
failure, so every read is decided by its CSV payload rather than its status.

Assertions and the trap each one guards:

  - wisp survival and row counts        gc must not collect a fresh closed wisp
  - updated_at stability                0059/ignored-0015 derived-state restamp
  - is_blocked truth table + bd blocked the wisp-aware blocker predicate
  - dolt_ignore superset                the #5947 read-before-write clobber
  - dolt_status clean                   migrations must not leave dirty tables
  - events row count across 0062        the drop/commit/recreate flip
  - leases + granted_node present       the #5366 contradicted-cursor replay
  - unblock on close                    blocks vs conditional-blocks release
  - journal shape + wisp writability    0064/0066 and 0060 storage_class
  - full-fingerprint second open        the ignored track, which `Schema
                                        already at vN` cannot see at all

That last one matters most: MigrateUp reports `applied` from the main track
only and never adds appliedIgnored, so the harness's existing no-op check is
structurally blind to an ignored-track replay. The fingerprint is the only
guard there is.

Extend the pinned-runtime gate and the workflow's Dolt install step to the
wisp-capable versions, which now need that binary as an oracle. Cache keys are
untouched: corpora are seeded fresh per run and the cache holds only release
binaries keyed by asset checksum.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(migration): extend the wisp lane to v1.1.0 and the pre-split v1.0.1 regime

v1.1.2 alone only proves the post-split regime. v1.0.1 is the one source in the
corpus that still stores wisp dependencies in the legacy single-target shape
(issue_id, depends_on_id) and has no ignored-migration cursor at all, so it is
the only lane where the candidate has to perform the ignored/0003-0005 split,
the 0047 delegate repair and the 0058 heal on populated rows, and bootstrap the
ignored cursor from nothing rather than find both already done.

Add verify_wisp_dependency_split: the three split target columns exist, the
legacy column is gone, and each seeded edge lands on the right target —
wisp-on-issue and conditional-blocks in depends_on_issue_id, wisp-on-wisp in
depends_on_wisp_id, and the issue-on-wisp edge in the main dependencies table's
depends_on_wisp_id. Mis-homing those rows is the "duplicate primary key given"
hazard class in migrations/README.md, and it is invisible to every count-based
check.

Make verify_no_restamp regime-aware, because which migrations replay is a
property of the source cursor. 0059 and ignored/0015 self-assign
`updated_at = updated_at`; their predecessors 0046, 0047 and ignored/0007 do
not. A v1.0.1 store replays all of them, so every non-closed row legitimately
moves. A v1.1.x store has 0046/0047 behind its main cursor, so issues must not
move at all — but its ignored cursor says 0011 while leases.granted_node does
not exist, the sentinel disbelieves the cursor, and the ignored series replays
anyway (#5366), dragging in unguarded ignored/0007.

What is gated in both regimes is the part that is unconditionally true: closed
rows never move, because every recompute is scoped
`WHERE status NOT IN ('closed', 'pinned')`. That bites the moment one forgets
its scope. The tighter guard on everything else is the idempotence fingerprint,
which requires a second open to move nothing at all.

Record the per-version capability probe results as a table rather than leaving
them as assumptions, and run bd doctor quick on every wisp lane instead of only
the ones with a remote.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(smoke): add a tolerant wisp-plane scenario to the release gate

cross-version-smoke.yml runs upgrade-smoke-test.sh as the release gate, and all
six of its scenarios work entirely in the versioned plane. The wisp tables
therefore reach the candidate empty, and 0054-0066 plus ignored 0012-0025 only
ever run as DDL over zero rows — the same blind spot Scenario 6 was written to
close for the dependencies split.

Scenario 7 seeds a wisp with the old binary, gives it a blocker edge so the
is_blocked recompute has real work to do, upgrades, and then checks the
surfaces that break when the replay is wrong: the wisp is still listed, the
blocker queries stay wisp-aware without erroring, the wisp is still writable
through the migrated storage_class default, and doctor quick is happy.

The matrix reaches back thirty releases on a tag push, so a binary that cannot
create a wisp or cannot produce a parseable id passes with a SKIP instead of
failing. Same tolerant, grep-based, dependency-free style as the rest of the
file: no jq, no Dolt CLI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(migration): cover the #5816 dirty tracked wisp-table state

#5816 is still open: an ignored-named table that is tracked at HEAD *and* dirty,
with ignored migrations pending, hard-locks the open. Nothing in either upgrade
harness has ever constructed that state, so the containment surface is
untested.

It is not reachable from clean OSS lineage — every release since v0.63.3
registers the dolt_ignore patterns before creating the tables — so the scenario
builds it deliberately: force-add and commit the wisp tables through the pinned
Dolt CLI, then dirty one again with an ordinary old-binary wisp write. That is
the shape real clones from forks and older-behavior lineages arrive in, and it
is the only place this file issues DML through the oracle.

Because the bug is open, the scenario deliberately does not gate on whether the
candidate accepts or refuses. It gates on what must hold either way: the
command terminates rather than hard-locking, and no wisp rows are lost. A
refusal additionally has to name the dirty tables instead of failing opaquely.
Carries a TODO(#5816) to tighten to must-succeed once the fix lands.

Observed on the current candidate: a clean refusal in about a second naming
`wisps`, with all rows intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(migration): add v1.2.2 to the corpus and make the lane dispatch array-driven

v1.2.2 is the tag field upgrades actually start from — the recovery release that
superseded the accidental v1.2.1 — and it has never been in this corpus. It is
the v1.1.2 tree re-tagged, so it adds no new storage regime; it closes the
"the exact tag in the field was never tested" gap for the price of one matrix
job.

Adding it exposed a latent trap: the dispatch loop carried its own hardcoded
copy of the embedded-Dolt version list, so a version added to
EMBEDDED_DOLT_VERSIONS in lib/versions.sh was silently routed into the
server-Dolt bridge lane instead, and failed there with a confusing "candidate
list did not refuse with explicit migration is required". Dispatch on
membership of the array that already declares the corpus, so the list exists
once.

Asset checksum verified against the published beads_1.2.2_linux_amd64.tar.gz.
No existing cache-identity string changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 23:24:39 -07:00
..