Reconcile odds-router branch with current main #27

Merged
kristofferopsahl merged 5 commits from fix/issue-22 into main 2026-10-15 22:40:30 +02:00
Collaborator

This branch is now synchronized with main at 2184143 while preserving its original commits. Its final file tree is identical to main: merging this PR integrates branch history and makes no application or data changes.

The original registration-only odds router was superseded by the complete domain extraction in 06e1db5 (PR #43). Conflicts in main.py and the routes package were resolved to that current implementation, including its explicit service dependencies. The older asset guard and runner-label changes were also superseded.

Validation:

  • Exact tree equality with main verified; no unresolved conflicts.
  • Focused route-boundary and asset-graph checks passed.
  • Forgejo reports the PR mergeable.
  • Fresh CI run 122 passed on 3948ee7: syntax, 2,144 tests passed / 145 skipped, and wheel build/install/import checks.

No feature cherry-picks, baseline changes or history rewrites were needed. This PR remains open for merging.

This branch is now synchronized with main at `2184143` while preserving its original commits. Its final file tree is **identical to main**: merging this PR integrates branch history and makes no application or data changes. The original registration-only odds router was superseded by the complete domain extraction in `06e1db5` (PR #43). Conflicts in `main.py` and the routes package were resolved to that current implementation, including its explicit service dependencies. The older asset guard and runner-label changes were also superseded. Validation: - Exact tree equality with main verified; no unresolved conflicts. - Focused route-boundary and asset-graph checks passed. - Forgejo reports the PR mergeable. - Fresh [CI run 122](https://git.kristofferopsahl.com/solvreven/FPL/actions/runs/122) passed on `3948ee7`: syntax, **2,144 tests passed / 145 skipped**, and wheel build/install/import checks. No feature cherry-picks, baseline changes or history rewrites were needed. This PR remains open for merging.
refactor: register odds endpoints through a domain router (#22)
Some checks failed
CI / syntax (pull_request) Failing after 1s
CI / tests (pull_request) Has been skipped
CI / wheel (pull_request) Has been skipped
CI / browser (pull_request) Has been skipped
89eba0d34f
Owner

Reviewed. Your diagnosis was right, and so was ours — that is the actual bug here, and neither side could see it.

test_asset_version.py hashes the four assets byte-for-byte. They are text files, the repo has no .gitattributes, and CI checks out LF on debian-trixie-python while a Windows clone with core.autocrlf=true holds CRLF. So the digest differs by platform, and the guard is green only for whoever recorded it last.

Measured at each commit, the same content under both conventions:

commit recorded LF (CI) CRLF (Windows) CI Windows
1c94db7 (ours, v135) d20d1fa11d2d9a94 a932c205c61efb9b d20d1fa11d2d9a94 RED green
bbf3c01 (yours, v136) a932c205c61efb9b a932c205c61efb9b d20d1fa11d2d9a94 green RED
9c2faca (ours, v137) d9ca43b746e10252 350840c64b1a1aa0 d9ca43b746e10252 RED green

Two things follow.

main has been red in CI since 9c2faca, exactly as this PR says. We could not see it because we only ever ran the suite on Windows, where it passes.

Your bbf3c01 is not wrong — it is correct for Linux. We would have "fixed" it straight back to a CRLF digest and broken CI again, which is the third leg of a ping-pong that has already run 135 → 136 → 137. Apologies for the round trip; the conflict on this PR is that ping-pong, not a disagreement about the code.

What we changed instead

Opened as #28.

Branch fix/asset-guard-line-endings normalises line endings before hashing, so the platform difference is unrepresentable rather than documented:

h.update((FRONTEND / name).read_bytes().replace(b"\r\n", b"\n"))

EXPECTED_DIGEST becomes the normalised value, which is identical under both conventions. This is not a new approach — the repo had already solved it one digest over. projection_digest in pipeline.py normalises `

to
and explains why in terms that describe this bug exactly: *"git stash/checkoutrewrite tracked files between LF and CRLF undercore.autocrlf=true, and on Windows that flipped this digest with zero code difference: same source, two different hashes, one commit apart."* It is covered by test_crlf_and_lf_produce_the_same_digest`. The asset digest is simply the one place that remedy was never applied.

We chose this over a .gitattributes entry deliberately: .gitattributes would leave the guard's correctness depending on every contributor's checkout config, which is the disconnected-mechanism shape this repo keeps paying for.

Proved RED on both the property fixed and the one that must not break:

case result
CRLF checkout (Windows) GREEN
LF checkout (CI debian-trixie) GREEN
app.js changed, ?v= not bumped RED
tokens.css changed, ?v= not bumped RED

Could you rebase on that branch once it lands? bbf3c01 then becomes unnecessary rather than conflicting, and neither side has to concede a digest that was correct where it was measured.

One note on the verification line in the description — "full python -m pytest -q suite passes". It does, on Linux. On Windows this PR fails test_the_version_moved_when_the_assets_did. Not a criticism of the run; it is the same platform split, seen from the other side. Worth stating because it is the second time in this repo a green suite has meant "green where I ran it" (the earlier one was test_css_characterization at a single viewport width).

On the router half (#22)

Held back from this merge, and we would rather not take it as-is:

  • main.py goes 2105 → 2110 lines. #22 is a child of #7, "split oversized modules", and the module grew.
  • The bodies did not move: market_response (51 lines) and matches_response (284) are still in main.py. Only the two @app.get decorators left.
  • The dependency is now a cycle — main.py → routes/odds.py → (lazily) main.py — deferred to call time by the function-scoped import. That has to be unwound by the next step rather than built on.

Against the issue's own wording, "main.py no longer owns these handlers", this moves registration rather than ownership. The description is honest that it "advances #22 but does not close it", so this is not a disagreement about intent — we would just rather the first commit on this boundary move the calculation body behind the existing odds/load service seam, so the router is thin because the logic is elsewhere, not because it delegates back.

Also flagging an ordering point from #25 that we should settle before more of this ladder lands: the asset guard currently works because there are exactly four files and one version string, and splitting app.js into modules breaks both halves at once. Worth confirming the guard rebuild comes first, as agreed there.

Reviewed. **Your diagnosis was right, and so was ours — that is the actual bug here, and neither side could see it.** `test_asset_version.py` hashes the four assets byte-for-byte. They are text files, the repo has no `.gitattributes`, and CI checks out LF on `debian-trixie-python` while a Windows clone with `core.autocrlf=true` holds CRLF. So the digest differs by platform, and the guard is green only for whoever recorded it last. Measured at each commit, the same content under both conventions: | commit | recorded | LF (CI) | CRLF (Windows) | CI | Windows | |---|---|---|---|---|---| | `1c94db7` (ours, v135) | `d20d1fa11d2d9a94` | `a932c205c61efb9b` | `d20d1fa11d2d9a94` | **RED** | green | | `bbf3c01` (yours, v136) | `a932c205c61efb9b` | `a932c205c61efb9b` | `d20d1fa11d2d9a94` | green | **RED** | | `9c2faca` (ours, v137) | `d9ca43b746e10252` | `350840c64b1a1aa0` | `d9ca43b746e10252` | **RED** | green | Two things follow. **`main` has been red in CI since `9c2faca`**, exactly as this PR says. We could not see it because we only ever ran the suite on Windows, where it passes. **Your `bbf3c01` is not wrong** — it is correct for Linux. We would have "fixed" it straight back to a CRLF digest and broken CI again, which is the third leg of a ping-pong that has already run 135 → 136 → 137. Apologies for the round trip; the conflict on this PR is that ping-pong, not a disagreement about the code. ### What we changed instead Opened as **#28**. Branch `fix/asset-guard-line-endings` normalises line endings before hashing, so the platform difference is unrepresentable rather than documented: ```python h.update((FRONTEND / name).read_bytes().replace(b"\r\n", b"\n")) ``` `EXPECTED_DIGEST` becomes the normalised value, which is identical under both conventions. **This is not a new approach — the repo had already solved it one digest over.** `projection_digest` in `pipeline.py` normalises ` ` to ` ` and explains why in terms that describe this bug exactly: *"`git stash`/`checkout` rewrite tracked files between LF and CRLF under `core.autocrlf=true`, and on Windows that flipped this digest with zero code difference: same source, two different hashes, one commit apart."* It is covered by `test_crlf_and_lf_produce_the_same_digest`. The asset digest is simply the one place that remedy was never applied. We chose this over a `.gitattributes` entry deliberately: `.gitattributes` would leave the guard's correctness depending on every contributor's checkout config, which is the disconnected-mechanism shape this repo keeps paying for. Proved RED on both the property fixed and the one that must not break: | case | result | |---|---| | CRLF checkout (Windows) | GREEN | | LF checkout (CI debian-trixie) | GREEN | | `app.js` changed, `?v=` not bumped | **RED** | | `tokens.css` changed, `?v=` not bumped | **RED** | **Could you rebase on that branch once it lands?** `bbf3c01` then becomes unnecessary rather than conflicting, and neither side has to concede a digest that was correct where it was measured. One note on the verification line in the description — "full `python -m pytest -q` suite passes". It does, on Linux. On Windows this PR fails `test_the_version_moved_when_the_assets_did`. Not a criticism of the run; it is the same platform split, seen from the other side. Worth stating because it is the second time in this repo a green suite has meant "green where I ran it" (the earlier one was `test_css_characterization` at a single viewport width). ### On the router half (#22) Held back from this merge, and we would rather not take it as-is: - `main.py` goes **2105 → 2110 lines**. #22 is a child of #7, "split oversized modules", and the module grew. - The bodies did not move: `market_response` (51 lines) and `matches_response` (284) are still in `main.py`. Only the two `@app.get` decorators left. - The dependency is now a cycle — `main.py` → `routes/odds.py` → (lazily) `main.py` — deferred to call time by the function-scoped import. That has to be unwound by the next step rather than built on. Against the issue's own wording, "`main.py` no longer owns these handlers", this moves registration rather than ownership. The description is honest that it "advances #22 but does not close it", so this is not a disagreement about intent — we would just rather the first commit on this boundary move the calculation body behind the existing odds/load service seam, so the router is thin because the logic is elsewhere, not because it delegates back. Also flagging an ordering point from #25 that we should settle before more of this ladder lands: the asset guard currently works because there are exactly four files and one version string, and splitting `app.js` into modules breaks both halves at once. Worth confirming the guard rebuild comes first, as agreed there.
ci: run on debian-latest
Some checks failed
CI / syntax (pull_request) Successful in 7s
CI / tests (pull_request) Successful in 1m7s
CI / wheel (pull_request) Successful in 29s
CI / browser (pull_request) Failing after 15m1s
8b6de2a892
Same label swap as on the default branch: the debian-trixie-<stack> runner
labels are being removed; debian-latest is the consolidated debian-ci image.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merge branch 'main' into fix/issue-22
All checks were successful
CI / syntax (pull_request) Successful in 6s
CI / tests (pull_request) Successful in 1m4s
CI / wheel (pull_request) Successful in 29s
66c932c933
Takes main's asset guard: #28 normalises line endings before hashing, which supersedes bbf3c01's LF digest. Takes main's ci.yml (this branch's only change there was runs-on: debian-latest). The PR's diff is now the odds router alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
Collaborator

Updated now that #28 has landed. main is merged in (66c932c), taking #28's line-ending-normalised guard over bbf3c01's LF digest. The diff is now the odds router alone (main.py, routes/__init__.py, routes/odds.py), and the [test] suite passes on the merged result: 1236 passed, 83 skipped.

The review points on the router half still stand and aren't addressed here: the bodies haven't moved, main.py grows, and routes/odds.py imports back into main. One more reason to hold it: #29 and #31 both edit main.matches / _unpriced_but_ours. Renaming or moving those now would conflict with both, so the body move for #22 is easier to do after they land.

🤖 Generated with Claude Code

Updated now that #28 has landed. `main` is merged in (66c932c), taking #28's line-ending-normalised guard over `bbf3c01`'s LF digest. The diff is now the odds router alone (`main.py`, `routes/__init__.py`, `routes/odds.py`), and the `[test]` suite passes on the merged result: 1236 passed, 83 skipped. The review points on the router half still stand and aren't addressed here: the bodies haven't moved, `main.py` grows, and `routes/odds.py` imports back into `main`. One more reason to hold it: #29 and #31 both edit `main.matches` / `_unpriced_but_ours`. Renaming or moving those now would conflict with both, so the body move for #22 is easier to do after they land. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Owner

Agreed, this waits until #29 and #31 land.

One thing makes the order simpler. #31 already contains everything in #29 and #30: the only commits they have that it lacks are the ci: run on debian-latest cherry-pick and a merge of main. So once #31 is in, the body move for #22 has one base to work against, not three.

main.py in #31 has also moved since your review. The round plan changed the captaincy and squad routes, and #34 changed /api/autocomplete. The router's diff will want a fresh look against it then. The full list is on #31.

🤖 Generated with Claude Code

Agreed, this waits until #29 and #31 land. One thing makes the order simpler. #31 already contains everything in #29 and #30: the only commits they have that it lacks are the `ci: run on debian-latest` cherry-pick and a merge of `main`. So once #31 is in, the body move for #22 has one base to work against, not three. `main.py` in #31 has also moved since your review. The round plan changed the captaincy and squad routes, and #34 changed `/api/autocomplete`. The router's diff will want a fresh look against it then. The full list is on #31. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Merge main into odds-router PR and retain the completed extraction
All checks were successful
CI / syntax (pull_request) Successful in 9s
CI / tests (pull_request) Successful in 2m23s
CI / wheel (pull_request) Successful in 29s
3948ee7995
The original registration-only router is superseded by 06e1db5 in PR #43. Resolve main.py and the routes package to the current explicit service implementation. The resulting tree exactly matches main at 2184143; the original PR history is retained.
kristofferopsahl changed title from Fix the asset guard and establish the odds router boundary to Reconcile odds-router branch with current main 2026-10-15 22:30:40 +02:00
Sign in to join this conversation.
No description provided.