Skip to content

Backend validation batch: ScheduleHeat guards, membership pruning, DQ standings symmetry (#335, #336, #339, #341)#346

Merged
ryan-johnson2 merged 1 commit into
develfrom
backend-validation-batch2
Jul 3, 2026
Merged

Backend validation batch: ScheduleHeat guards, membership pruning, DQ standings symmetry (#335, #336, #339, #341)#346
ryan-johnson2 merged 1 commit into
develfrom
backend-validation-batch2

Conversation

@ryan-johnson2

Copy link
Copy Markdown
Contributor

Fixes #335, #336, #339 and the two confirmed verify-first items from #341 (user-approved fix batch).

16 new tests (4 engine, 12 server) + the heats contract rebuilt on a real fixture. Workspace green, clippy clean, contract 90/90.

🤖 Generated with Claude Code

…ship pruning, DQ metrics, h2h points (#335 #336 #339 #341)

Release-review hardening, each fix with tests:

- ScheduleHeat validation (#335): reject a duplicate competitor in the
  lineup; a round tag must name one of the event's rounds; a class tag must
  be selected by the event and (when a round is also tagged) eligible for
  it; on a tagged heat, every lineup ref naming a directory pilot must be a
  member of the tagged round's eligible classes' membership (mirroring the
  FillRound field / console eligible-members picker). node-{i} timer seats
  and sim free-text refs still pass, so practice-style heats keep working.
- ScheduleHeat duplicate id (#341, VERIFIED by probe): the fold re-seeds a
  repeated HeatScheduled back to Scheduled, so re-using an id silently reset
  a finished/Final heat and orphaned its result. Only genuinely-new ids are
  accepted now (re-runs go through Discard/Restart), on both the event-aware
  and bare command paths.
- Stale membership on shrink (#336): the roster PUT (and the per-pilot
  DELETE, same hole) now prune classes_membership slots whose pilot left the
  roster; the classes PUT drops deselected classes' membership entries — so
  FillRound can no longer seat removed pilots past the #330 membership
  guard. Surviving members keep their channels; empty entries are dropped.
- DQ standings-metric asymmetry (#339): a disqualified placement contributes
  NO best-lap and NO win-condition metric (lap count / consecutive window)
  to the standings row — the ranking already excluded it (#331), so the row
  no longer surfaces values the position ignored. The pilot's clean heats
  still count; class standings share the fixed best-lap fold.
- head_to_head linear points (#341, VERIFIED): the no-table fallback priced
  a finish by heat.result.places.len(), so a no-show shrank every present
  pilot's points in that heat. It now prices by the round's group_size (an
  explicit points table was and is unaffected). Added the missing odd-field
  tests pinning the [2,2,1] one-pilot trailing heat and the [4,2] short
  trailing group under both scorings.
- heats.contract.ts now builds a real class/roster/membership/round before
  scheduling a tagged heat (required by the #335 validation) and pins the
  new rejections over the wire.

cargo test --workspace green; cargo fmt + clippy --all-targets -D warnings
clean; frontend npm run contract green (9 files, 90 tests) against a
rebuilt Director.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ScheduleHeat accepts unvalidated lineup/class/round (duplicates, non-roster pilots, arbitrary round injection)

1 participant