ci: pin the locale composition of llms.txt and llms-full.txt - #202
Merged
Conversation
The missing-language-argument defect has now been fixed in three generated artifacts and asserted in one. `check-locale-surface.mjs` gained a locale oracle for the sitemap; this extends the same harness to the two `llms` endpoints, which had no assertion of any kind on which locales they serve. The `llms` bodies never emit a page's own URL, so the sitemap's URL-set comparison cannot be reused verbatim. They do carry page titles, so the comparison runs over the titles that belong to exactly one locale: every `en`-exclusive title must be in the body, and none of the other six locales' exclusive titles may be. Both directions are defects, as they are for the sitemap. Composition only, never sequence. `llms-full.txt`'s page order differs between two builds of the same commit, so a golden file or a recorded-body diff would flake today and break when #196 lands its navigation-tree ordering. `llms.txt` is not exposed to that, and is written the same way anyway. `ARTIFACTS` entries now name the vocabulary they are compared in. `BY_URL` encodes exactly what the sitemap already did, so its findings, its rules and its report row are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5zjYc2BoFV2NjKBBapC7C
hotlong
marked this pull request as ready for review
August 26, 2026 02:18
This was referenced Aug 26, 2026
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.
Fixes #184
.github/scripts/check-locale-surface.mjsgained a locale oracle for the sitemap in #193. This adds the twollmsendpoints to the same harness. Nothing in the repo asserted which locales they serve, and the same missing-language-argument defect has now been found and fixed in all three of them (#176 / #179 / #183).One file changed:
.github/scripts/check-locale-surface.mjs.tools/ci-scripts/run-self-tests.mjsneeded no edit — the script was already registered there, andci.ymlalready runs the gate afterbuild. Noapps/docs/**source file is touched.The assertion
The
llmsbodies never emit a page's own URL —llms-full.txtis page texts concatenated, each opening with the#heading linegetLLMTextprepends — so the sitemap's URL-set comparison cannot be reused verbatim. They do carry page titles, so the comparison runs over the titles that belong to exactly one locale:en-exclusive title must be in the body — one going missing means English pages stopped being served;Both directions, as #175 argued for the sitemap. Measured on this tree: 60 / 52 / 31 / 25 / 32 / 28 / 31 exclusive titles for
en/zh-Hans/ja/de/es/fr/ko, with both bodies reading 60 / 0 / 0 / 0 / 0 / 0 / 0. The card recorded 63/53/31/25/32/28/31 → 63/0×6 one tree earlier; the numbers move with the content and nothing here pins them.Restricting to exclusive titles is what makes it stable rather than merely strict. A plain "every title in the body equals every English title" comparison would go red on a correct build twice over: three pairs of English pages share a title today (
Approvals,Dashboards,Notifications), and any page growing an#heading in its own body would read as an extra page.Composition, never sequence.
llms-full.txt's page order is non-deterministic across builds — observed directly here, twice: two builds of an identical tree producedllms-full.txt.bodyhashesa6b849f1…and28cccb8b…while the gate stayed green on both. #196 is changing that order on purpose. Any assertion on sequence, a golden file, or a diff against a recorded body would flake today and break when #196 lands.llms.txtwalks the page tree, whose order comes frommeta.json, so it is not exposed to the same non-determinism; it is written the same way anyway.Rules
Two new rules,
unexpected-locale-titleandmissing-locale-title, rather than reusingunexpected-url/missing-url. A shared message would be a wrong message: telling a reader thatデータモデル"is advertised but the content tree has no source file for it" is false twice — it is not a URL and it has a source file.artifact-missingandartifact-emptyare reused as-is, with the existing fixtures extended to cover the new artifacts.A third rule,
nothing-expected, closes a hole this design opens: if no English title were exclusive to English, the comparison would have nothing to compare and both artifacts would pass without measuring anything. That is the same failureartifact-emptyalready treats as a failure rather than a skip.Verification
Every claim below is a captured exit code read before any pipe, not a bare
$?after one.The real defect, reconstructed in
llms-full.txt—source.getPages(LANG)→source.getPages(), proven on disk (anchor count 1 → 0; blob722f1479…→3d6440d4…), rebuilt with--force, then restored and proven restored (blob back to722f1479…,git diff HEADempty):readgoes 79 → 553, matching the 553 thatlib/seo.tsdocuments for a baregetPages(). The other two artifacts stay green: the failure localizes to the one that broke.The real defect, reconstructed in
llms.txt. Worth recording precisely, because the obvious mutation is a no-op. Dropping the argument fromsource.getPageTree(LANG)produces a byte-identicalllms.txt.body(883ebd38…both ways) —getPageTree()resolves to the default language in fumadocs-core 16.8.12, so the gate staying green there is correct, not a miss. The actual historical defect is a baresource.getPages()flat list, which is what9c29df6^contains. Restoring that exact blob (6639c252…, verified equal to the historical blob on disk) and rebuilding:llms.txt.bodymoved to21c61abb…, so the mutation reached the build output and not just the source. Restored, blob back to790d942a…,git diff HEADempty.The other direction, on the real tree.
getPages(LANG)→getPages('ja'):23 rather than 60 missing, because the 37 English pages with no Japanese translation still fall back to English — the behaviour
lib/seo.tsdocuments.The sitemap is unchanged. The pre-change script, run from the same directory against the same build, reports the same row it reports now —
346 | 346 | 346 | 0 | 0 | 0— and its--self-testfires the identical rule set per case.BY_URLencodes exactly the previous behaviour, includingduplicate-url, which the title vocabulary deliberately does not use (a title is a label, not an identity).Self-test, on
f4a7902, exit 0 — 16 cases over 9 rules and 3 artifacts:The last three green cases are assertions, not filler. Both readers are structured rather than substring searches, because a substring search over these bodies produces four false hits on today's tree —
GlossarinsideGlossary, andDatasources,Roles,Licencesitting in English prose — each reading as a foreign page being served. Thellms-full.txtreader is fence-aware for the same reason: 14 lines in the corpus open with#inside a shell fence.The self-test's own new check was proven able to fail.
RULEScoverage alone stopped being sufficient once one rule could fire for any of three artifacts, so the self-test now also asserts everyARTIFACTSentry was driven red on its own content. Injecting a fourth, always-green artifact into a copy outside the repo made exactly that check fail (exit 1, all 21 other assertions passing) — and closing a loophole found while testing it:artifact-missingdoes not count toward the tally, since theno built artifactcase omits every file at once and would hand any new entry free coverage.Gates run on
f4a7902:pnpm turbo run type-check build(2 tasks successful),pnpm turbo run test(✓ 3 self-test(s) passed), and the gate itself (exit 0). All heavy runs went through the container's shared verify lock.Notes for review
#196and#194are in flight and neither touches this file.#196is not addressed here — it remains open, and this gate is deliberately blind to the ordering it changes.compared ascolumn and splitadvertisedintoread/in scope, so a vocabulary that restricts the comparison cannot do so invisibly. The sitemap's numbers are identical in both columns.Generated by Claude Code
Generated by Claude Code