R-17 — pooled live rider move; schema v3 audit migration; podium entire hand - #73
Merged
Merged
Conversation
Add Entry.key (a persisted UUID surrogate) and key the engine's crossings, laps, hands and tie-breaks by it instead of the mutable derived plate. Replay resolves recorded entries by key, so a mid-ride pooled move no longer re-keys the roster's plates out from under the engine. The red replay-divergence test is now green.
Relax extract_rider_to_solo to can_move_rider (pooled RUNNING/REOPENED) and let move_rider take a solo source so a rider with data can move onto a team without the has-data delete guard. A dissolved solo entry now logs a distinct dissolve_entry action.
Add RideEngine.move_rider (team->team, solo->team) and extract_rider_to_solo (team->solo), gated on Stop/Reopen, that re-attribute the rider's crossings, credited cards, held cards and reset their voided laps onto the destination key, with replayed move_rider/extract_rider_to_solo events. Known gap (documented in the class docstring): a solo->team move that dissolves the source entry cannot reload yet, because the dissolved entry's key is not persisted.
Persist dissolved entries that carry recorded data so the pre-move crossings of a solo->team move stay resolvable after a reload; the ride reopens instead of raising UnknownPlateError. Bump SCHEMA_VERSION to 2 and add store/migrations.py, whose v1->v2 step rebuilds the entry table to add the stable key and retired columns and the live-only plate unique index. ensure_schema now migrates older files and still refuses a newer one.
A team change in the Rider Editor now confirms first, is refused with 'Stop the ride first' while the ride is live, and routes through the engine move methods when the ride is stopped or reopened. Closing the editor refreshes the console so standings and the review list recalculate. DRAFT editing keeps the roster-only path.
A pooled membership reshape is now a preview conflict outside DRAFT, so a mid-ride re-import can no longer move a rider roster-only and strand their crossings on the entry left behind. The Rider Editor remains the one live (Stop/Reopen-gated) move surface. Also surface a roster refusal from a stale commit as a validation message instead of an uncaught raise.
The CSV import applies no pooled reshape after the start (DRAFT-only), the Rider Editor is the one live Stop/Reopen-gated move surface, R-15/R-17 name the retired-entry persistence, module-skeletons carry Entry.key, the roster retired members and the engine move signatures, and the dead run_move_rider/RiderMove picker is removed.
Record in the engine docstring that a moved crossing's seq is record/void order, not time order, and that void_card's entry_id is a plate live and a key on replay. State only; no behaviour change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Three changes on one branch, landed together:
Live rider moves on a pooled ride (R-17). A
rider_pooledride can move a rider betweenteams, team → solo, or solo → team after the start — but only from Stop mode
(RUNNING + stopped) or REOPENED. A live-RUNNING move is refused with "Stop the ride first".
The move re-credits the rider's laps and cards to the destination, resets their voided laps,
recomputes the review surface, and replays exactly.
team_relaykeeps its permanent start lock.Whole hand on the podium. The podium poster (HTML + PDF) and the results-page podium cards
now list each entry's entire hand — every drawn card, in draw order — beneath the best-5,
using the same
All N cards, in draw orderline the full-field reports already carry. The podiumposter stays a single page.
Schema v3 — legacy rides reopen. The v1 → v2 migration added the stable entry key but left
the
auditpayloads' plate-basedentry_ids, so a ride recorded before the change failed toload ("unknown entry key: 93"). The new data-only v2 → v3 step rewrites those audit identities to
the entries' keys, in place, on open.
Why it was needed
Roster.move_riderwas reachable mid-ride on a pooled ride but changed membership only — it neverre-keyed the engine, which keyed crossings and cards by the team's derived (mutable) plate, so a
mid-ride move stranded a rider's laps on the old key and team → solo was DRAFT-only. The stable-key
refactor that fixes this changed what a persisted event's
entry_idmeans, which is why a v1database's audit trail then needed the v3 rewrite. Separately, the podium showed only the best-5
where every other report listed the whole hand.
Design
Entry.key(a persisted UUID) replaces the derived plate as the engine'sidentity for crossings, laps, hands and tie-breaks; replay resolves recorded entries by key rather
than by re-resolving a mutable plate.
RideEngine.move_rider(team/solosource → team) and
RideEngine.extract_rider_to_solo(team → solo), sharing one_reattribute_ridercore that moves live crossings, credited and held cards, and resets voidedlaps.
(
entry.retired = 1) so its key and the pre-move crossings still resolve after a reload.entry(addskey/retired, the live-only plate index); v3 isa data-only step that rewrites legacy
auditentry ids to keys. Both upgrade a database in place;a file newer than the build is still refused.
with the re-attribution. The CSV import now refuses every pooled reshape outside DRAFT.
drawn from the same
ResultRow.drawnthe full-field reports use; the poster's card geometry istrimmed so the extra line keeps all six cards on one page.
Requirements
cards; relay keeps the start lock.
spec.md§2 (schema note), §3, §7, §8/§8b (podium).Tests
Test-first throughout.
nox -s unit: 6655 passed, 1 skipped, 99.85% coverage;nox -s lint typecheck importlint ids_drift css_driftgreen. The move's replay equivalence (live vsStore.load_engine) is pinned for held, credited and voided dispositions; the v1→v2 and v2→v3migrations each have their own suite; the podium whole-hand line is pinned by content tests plus a
worst-case one-page fit test.
Noted residuals (recorded, not hidden)
csvio.committrusts its preview snapshot; the stale-commit window is UI-unreachable (thepreview dialog is modal).
seqis record/void order, not time order — correct for standings, a footgunfor corrections that address a crossing by
(entry_id, seq).No functional test was added: the repo permits one (the open/quit smoke), and this change touches
it only indirectly.