Repository navigation
Keep the generated API client in sync with freeshard, automatically - #35
Merged
Merged
Conversation
…ally Added a just get-openapi recipe (sibling-clone freeshard, dump a fresh OpenAPI spec from create_app().openapi(), no server boot needed) plus openapi-drift.yml, a PR-time CI check that re-dumps the spec, regenerates the client, and fails on any diff — the same pattern types-drift.yml already uses for freeshard's vendored Python models. js/api/openapi.json was a one-time offline dump from scaffold, never refreshed since, so this also catches it up: a user-settings/email surface and an OIDC provider surface that exist in freeshard today were entirely missing. Checked the regenerated client for breaking renames — four callManagementProtectedManagementRest* variants got reordered suffixes, none of them called anywhere outside client.js itself, so nothing breaks. 83 unit + 45 e2e tests pass unchanged.
…eshard main Two problems found testing the previous commit: 1. The dump wasn't reproducible. Three catch-all passthrough routes (call_backend, call_peer, call_management) register every HTTP method on one path; FastAPI derives operationId from that shared declaration rather than per-method, and which method's name lands in the (duplicated) id depends on a Python set()'s per-process hash-seed iteration order. Confirmed by diffing repeated dumps of the same commit. dump_openapi.py now sorts each path's methods into a fixed order and rewrites each operation's operationId to match its own method — deterministic across four consecutive runs, checksums identical. 2. The previous commit's 'catch-up' was dumped against the feature branch for freeshard#246, not freeshard's actual main — freeshard#246 is still open, unmerged. Against true main, a fresh regen removes getVersion(), which sundial#34 (already merged to this repo's main) already depends on in appstore.js's fetchShardVersion(). Shipping that removal would break currently-working code to make a brand-new check pass, which is backwards. openapi.json and client.js are therefore reverted to exactly match main in this PR — this adds the tool only. The drift-check job will correctly start red on main until freeshard#246 merges and someone reruns the resync; that's accurate, not a bug, and the PR description says so.
Catches up the client to everything merged on freeshard main since the last regen on 2026-10-09, including the minimum_freeshard_version field from freeshard#246 and the OIDC/user-model surface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Without a pin, uv picks the newest Python satisfying freeshard's ">=3.13", which just started breaking: CPython 3.15.0 has no psycopg-binary wheel yet. freeshard's own CI (test.yml) already pins 3.13; match it here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 & why
js/api/openapi.jsonis a one-time offline dump from scaffold — never refreshed since, nothing watches it, and nothing resyncs it. That's exactly how the/public/meta/versionfollow-up (sundial#34, already merged) ended up hand-rolling a second fetch path instead of using the generated client: the instruction to skip codegen traced back to an unverified assumption, and the real reason it felt necessary was that regenerating wasn't a one-command thing.freeshardalready solves the equivalent problem for its vendored Python models fromfreeshard-controller: ajust get-typesrecipe plus atypes-drift.ymlCI check that fails if the vendored copy and a fresh resync disagree. This PR builds the same two pieces here.What's new
just get-openapi— sibling-clonesfreeshard(override path via--set SOURCE_DIR), dumps a fresh spec viatools/dump_openapi.py(create_app().openapi()— no server boot, no DB needed, confirmed empirically) intojs/api/openapi.json. Pair with the existingjust gen-client..github/workflows/openapi-drift.yml— on every PR: checkoutfreeshardmain, run both recipes, fail the build if anything differs from what's committed. Same shape astypes-drift.yml, no secrets needed (both repos are public).tools/dump_openapi.pyalso fixes a real non-determinism bug I found while testing this: three catch-all passthrough routes (call_backend,call_peer,call_management) share oneoperationIdacross every HTTP method on their path, and which method's name lands in that shared id depends on aset()'s per-process hash-seed iteration order — same commit, different dump every run. Confirmed by diffing repeated dumps; fixed by sorting each path's methods into a fixed order and rewriting each operation's id to match its own method. Checked every other path in the spec — only these three ever mismatched. Verified deterministic across four consecutive runs (identical checksums).What's deliberately not in this PR
I initially also committed a one-time catch-up (freeshard has grown an OIDC provider surface and a user-settings surface since scaffold that Sundial's client has no wrappers for). Reverted it: I'd dumped that catch-up against the
freeshard#246feature branch, not freeshard's actualmain— #246 is still open. Against true main, a fresh regen removesgetVersion(), which the already-merged sundial#34 depends on inappstore.js. Shipping a "fix" that breaks working code to make a new check pass would be backwards, soopenapi.json/client.jsin this PR are byte-identical to currentmain— this PR adds the tool only.Consequence worth knowing:
drift-checkwill start red onmainonce this merges, and stay red until freeshard#246 merges and someone rerunsjust get-openapi && just gen-client. That's accurate — sundial#34 merged slightly ahead of its own backing endpoint — not a bug in the check. Whoever resyncs after #246 lands should expect the OIDC/user-settings catch-up to land then too, now that the dump is deterministic.Verification
Tested
get-openapiandgen-clientagainst a real siblingfreeshardcheckout at both the (wrong) feature branch and truemain; diffed every exported function name before/after to check for breakage before ever trusting a regen; ran the full test suite after each attempt. 83 unit tests pass (e2e unaffected since these two files are unchanged from main).🤖 Generated with Claude Code