Deferred from the PR #99 review. This is a product decision, not a bug — filing it so the current behavior is chosen rather than inherited.
The inconsistency
Two halves of one concern ended up with opposite immutability policies:
Season.rulesetId is a live reference. Editing a ScoringSystem immediately restates standings for every season pointing at it. A dropCount edit silently rewrites prior championship results.
- PAX factors went the opposite way and were frozen per entry (
Entry.paxIndexApplied, read via appliedPaxIndex()), specifically so history can't be rewritten by accident. A PAX-table edit only affects existing entries after an explicit per-season re-apply.
Defaults amplify it
Both createSeason (apps/web/src/lib/create-season.ts) and resolveOrCreateSeason (apps/web/src/lib/season-resolve.ts) attach the league's oldest ScoringSystem row when none is named. So sharing one ruleset across every season is the default state, and a single edit reaches all of them.
The preset.update audit does snapshot before/after, which is good and clearly deliberate — but there is no freeze on completed seasons and no copy-on-write.
Proposal
For a club championship archive: refuse a ruleset edit that would alter a season whose status is completed, and provide an explicit clone-and-re-point path (duplicate the ruleset, assign the copy to the active season, edit the copy).
Alternative worth considering: snapshot the policy onto the Season at completion, mirroring what Entry.paxIndexApplied already does for PAX.
Deferred from the PR #99 review. This is a product decision, not a bug — filing it so the current behavior is chosen rather than inherited.
The inconsistency
Two halves of one concern ended up with opposite immutability policies:
Season.rulesetIdis a live reference. Editing aScoringSystemimmediately restates standings for every season pointing at it. AdropCountedit silently rewrites prior championship results.Entry.paxIndexApplied, read viaappliedPaxIndex()), specifically so history can't be rewritten by accident. A PAX-table edit only affects existing entries after an explicit per-season re-apply.Defaults amplify it
Both
createSeason(apps/web/src/lib/create-season.ts) andresolveOrCreateSeason(apps/web/src/lib/season-resolve.ts) attach the league's oldestScoringSystemrow when none is named. So sharing one ruleset across every season is the default state, and a single edit reaches all of them.The
preset.updateaudit does snapshot before/after, which is good and clearly deliberate — but there is no freeze oncompletedseasons and no copy-on-write.Proposal
For a club championship archive: refuse a ruleset edit that would alter a season whose
statusiscompleted, and provide an explicit clone-and-re-point path (duplicate the ruleset, assign the copy to the active season, edit the copy).Alternative worth considering: snapshot the policy onto the Season at completion, mirroring what
Entry.paxIndexAppliedalready does for PAX.