You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] nothing pins the generated sitemap — the seven-locales-for-every-page defect was green on every gate #175
On the pre-#169 tree, apps/docs/app/sitemap.ts emitted 3892 entries covering 574 distinct URLs — 3318 exact duplicates — against 335 real MDX source files. Every gate was green the entire time. Measured on both trees through the same pipeline:
Nothing in the repo asserts anything about the generated sitemap, so the defect was undetectable short of reading the XML by hand.
The machinery already exists: tools/ci-scripts/run-self-tests.mjs runs --self-test on each .github/scripts/*.mjs, CI runs it as pnpm turbo run test, and turbo.json already names apps/docs/lib/i18n.ts as an input to that task — so a locale-surface gate is already wired to re-run when the locale list changes.
Suggested gate: count .mdx files under content/docs per locale and assert the built sitemap's per-locale entry counts match. On the #169 tree that is 79 / 62 / 39 / 38 / 39 / 39 / 39 = 335 docs entries, plus 11 for the root and the two legal pages. Drift in either direction is a real defect: too many means untranslated URLs are being advertised again, too few means a shipped translation is invisible to crawlers.
One design note worth pricing before writing it: the check needs the built sitemap.xml, so it is a post-build step rather than a standalone zero-dependency self-test like check-node-floor.mjs. The alternative — importing sitemap.ts directly — pulls in the whole MDX collection and the @/ alias, which is why the measurement above was taken off the build artifact.
Out of #169's declared file surface, hence a separate card.
Graded: promoted to pm:queue / Task.finding removed — grading is what that label waits for.
Promoted rather than held, and the case got stronger after this card was written. The same missing-language-argument defect has now been found and fixed in four places, every one of them green on every gate for its entire lifetime: sitemap.ts (#169), llms.txt (#170), llms-full.txt (#177), and the OG card (#185). On #177 that meant 4.6 MB of seven-locale text served under a robots.txt that had just been opened to crawlers.
Four instances is no longer a pattern worth noting. It is a gate that should exist.
The card already prices the one real design constraint — the check needs the built output, so it is a post-build step rather than a standalone --self-test, because importing sitemap.ts pulls in the whole MDX collection and the @/ alias. That is the decision the implementer inherits rather than rediscovers.
Scope note for dispatch:#184 is blocked on this card and carries the assertion shape for the two llms endpoints, already measured. Whatever harness this card settles on should be able to carry those two — build the harness with that in mind, but do not implement #184's assertions here.
Claim: PM loop round 6
Session: session_01G5zjYc2BoFV2NjKBBapC7C
Branch: claude/issue-175-pin-generated-locale-surface
Worktree: objectos-issue-175
Domain: n/a (objectos carries no domain:* lanes)
File surface: a new gate under .github/scripts/ or tools/ci-scripts/, plus whatever wiring it needs (turbo.json, root package.json, .github/workflows/ci.yml). No apps/docs/** source file — this card asserts behaviour, it does not change it. (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — PM judgement.
Clause-②: no
Serial constraints cleared: #173 and #178 are dispatched alongside this one. #173 changes where sitemap.ts gets the privacy/terms locale set from, not what it is (en + zh-Hans either way), so a gate asserting counts is unaffected by which lands first. #178 changes llms.txt links from relative to absolute, which does not touch the per-locale composition this gate asserts. Neither touches the gate surface.
Read coupling worth naming: if this gate asserts the exact text of a llms.txt link line, #178 will turn it red for a correct reason. Assert composition (which pages, which locales), not link formatting.
Session: session_01G5zjYc2BoFV2NjKBBapC7C
Branch: claude/issue-175-pin-generated-locale-surface
Worktree: /home/user/objectos-issue-175, off origin/main at 6653744
PR: #193 (draft)
File surface actually touched, within the declared bounds:
.github/scripts/check-locale-surface.mjs (new)
.github/workflows/ci.yml
tools/ci-scripts/run-self-tests.mjs
package.json
No apps/docs source file. turbo.json was in the allowed surface but needed no change — its test inputs already name $TURBO_ROOT$/.github/scripts/**, so the new self-test re-runs when the script changes, and adding content/docs/** there would declare an input the self-test does not read.
{
"issue": 175,
"status": "done",
"branch": "claude/issue-175-pin-generated-locale-surface",
"pr": "https://github.com/objectstack-ai/objectos/pull/193",
"premise_still_valid": true,
"head_sha": "fe6abaf",
"files_changed": [
".github/scripts/check-locale-surface.mjs (new, +689)",
".github/workflows/ci.yml (+21)",
"tools/ci-scripts/run-self-tests.mjs (+15/-2)",
"package.json (+3/-1)"
],
"gates": [
{
"gate": "node .github/scripts/check-locale-surface.mjs --self-test",
"exit": 0,
"verdict_line": "self-test: 7 case(s) over 6 rule(s) - every rule demonstrated able to fail, on fixtures read through the real readers"
},
{
"gate": "pnpm turbo run test (runs tools/ci-scripts/run-self-tests.mjs)",
"exit": 0,
"verdict_line": "3 self-test(s) passed / Tasks: 1 successful, 1 total",
"note": "was 2 self-tests before this PR; the runner's unlisted-script guard makes the wiring self-enforcing"
},
{
"gate": "pnpm turbo run type-check --continue",
"exit": 0,
"verdict_line": "Tasks: 1 successful, 1 total"
},
{
"gate": "pnpm turbo run build --filter=@objectos/docs",
"exit": 0,
"verdict_line": "Tasks: 1 successful, 1 total"
},
{
"gate": "node .github/scripts/check-locale-surface.mjs (the new gate, good tree)",
"exit": 0,
"verdict_line": "every advertised URL has a source file, and every source file is advertised",
"measured": "sitemap.xml advertised 346, distinct 346, expected 346, unexpected 0, missing 0, duplicated 0"
},
{
"gate": "node .github/scripts/check-locale-surface.mjs (reconstructed pre-#169 defect)",
"exit": 1,
"verdict_line": "locale surface: 781 finding(s)",
"measured": "sitemap.xml advertised 3892, distinct 574, expected 346, unexpected 228, missing 0, duplicated 553"
}
],
"summary": "New gate .github/scripts/check-locale-surface.mjs asserts the locale composition of the built sitemap.xml against an oracle derived every run from content/docs/ and apps/docs/lib/i18n.ts - no hardcoded counts, nothing read from app code. Six rules, each with a red fixture: unexpected-url, missing-url, duplicate-url, artifact-missing, artifact-empty, translation-orphan. Wired as a post-build CI step (deliberately not a turbo task: turbo would hash it against inputs excluding apps/docs/.next/, and a cached-green locale gate is the failure this card exists to end), with its --self-test registered in run-self-tests.mjs so pnpm turbo run test keeps proving the rules can go red. ARTIFACTS is the extension point #184 adds the two llms endpoints to; #184's assertions are deliberately not implemented here.",
"tests": "Oracle validated against the real artifact before any code was written: derived per-locale docs entries en 79, zh-Hans 62, ja 39, de 38, es 39, fr 39, ko 39 = 335, plus 7 root + 2 privacy + 2 terms = 346, matching the built sitemap exactly with zero set difference in either direction - the card's measured values, derived not declared. REVERSE VERIFICATION, run as one foreground script with a restore trap, all under the shared verify lock. Leg 1: apps/docs/app/sitemap.ts checked out verbatim at ee74379, mutation proven on disk by git hash-object == 079b27b96c92ef934367e1d04a6dded3bb67e66a; it does not compile against today's tree - 'app/sitemap.ts(25,23): error TS2554: Expected 2 arguments, but got 1.' / 'Exit status 2' - because #169 made languageAlternates' locales argument required, and restoring lib/seo.ts to ee74379 too was not an option (translatedLocales did not exist there and the OG route plus the docs page both import it now). Leg 2: the same defect reconstructed against today's signature (source.getPages() with no argument, languageAlternates(path, i18n.languages), iterate i18n.languages), mutation proven on disk by blob hash ae4b5d9adcf90f308d478271b298930421672f3b != HEAD blob and by grep counts of injected text (getPages() with no argument: 1) and removed text (translatedLocales(: 0, STATIC_PAGE_LOCALES: 0); rebuilt with --force; artifact re-derived to loc count 3892 over 574 distinct - the issue body's numbers exactly (556 logical paths x 7 = 3892; 79 docs + 3 static, x 7 = 574). Gate exit 1 with 781 findings naming what is wrong: 228 unexpected-url ('https://docs.objectos.ai/de/docs/architecture is advertised but the content tree has no source file for it - an untranslated page is being advertised as a translation') and 553 duplicate-url ('https://docs.objectos.ai/docs/architecture advertised 7 times'). RESTORE proven by git checkout HEAD -- (absolute path), git hash-object back to 197084a227ec741fe275594bf9cb5dbe92b86870, and git diff HEAD returning 0 lines; rebuilt to loc count 346; gate exit 0. Final tree clean, sitemap source byte-identical to origin/main. Final union re-run at the final commit fe6abaf after all edits: type-check and test both 'Tasks: 1 successful, 1 total', gate exit 0.",
"deviations": [
"The card's verification recipe says to check out sitemap.ts at ee74379 and rebuild. That exact file cannot build against today's tree - it calls languageAlternates with one argument and #169 made the second required (TS2554), and next.config.mjs sets typescript.ignoreBuildErrors: false. Reported as leg 1 with its compiler output rather than worked around, then the DEFECT (not the file) was reconstructed against today's signature for leg 2. The reconstruction is provably the same emission: 3892 entries over 574 distinct URLs, matching the issue body.",
"turbo.json is in the declared file surface but was not changed. Its test inputs already name $TURBO_ROOT$/.github/scripts/**, so the new self-test re-runs when the script changes; adding content/docs/** would declare an input the self-test does not read, and the real gate runs outside turbo on purpose."
],
"open_questions": [],
"out_of_scope_findings": []
}
new gate + its wiring only; no apps/docs/** source file, as required ✓
CI
build and Node floor both completed/success ✓
Path-surface fork
.github/** and tools/ — not governed surface (.claude/**, skills/**, AGENTS.md, CLAUDE.md, docs/adr/**) ✓
Self-test
registered in run-self-tests.mjs; pnpm turbo run test went from 2 self-tests to 3 ✓
My verification recipe was wrong, and it was reported rather than worked around
I said: check out sitemap.ts at ee74379, rebuild, watch the gate go red. That file cannot build against today's tree — it calls languageAlternates with one argument and #169 made the second required, with typescript.ignoreBuildErrors: false. TS2554, exit 2, no build, no measurement.
The easy paths were to quietly skip the reverse verification, or to loosen something until it compiled. Instead: leg 1 reported with the compiler's own output, then the defect — not the file — reconstructed against today's signature, with the mutation proven on disk by blob hash and by grep counts of both injected and removed text. The rebuilt artifact came back at 3892 entries over 574 distinct URLs: the issue body's numbers exactly, arrived at independently.
Gate then exits 1 with 781 findings that name what is wrong — 228 unexpected-url, 553 duplicate-url, each naming the URL. Restore proven by blob hash back to 197084a and an empty git diff HEAD, rebuild back to 346, gate green.
That is a gate demonstrated able to fail on the real defect it exists to catch, not on a fixture that resembles it.
Design decisions worth keeping
The oracle reads no app code. Stated in the header as the reason: "The gate must be able to go red when app code is wrong, which it cannot do if app code is where it gets its expectations." The same principle drives re-declaring SITE_URL instead of importing it — "a check that shares a constant with the thing it checks cannot catch that constant being wrong" — with the mismatch designed to surface loudly as every URL landing in both unexpected-url and missing-url.
The reader deliberately does not de-duplicate. 3318 exact duplicates would otherwise hide behind a correct distinct count. The duplicate-url rule only exists because of that choice.
Not a turbo task, on purpose. Turbo would hash it against the ci-scripts package's inputs, which do not include apps/docs/.next/ — so a locale regression would replay a cached green. That is the same shared-cache hazard two devs hit independently on this backlog, anticipated rather than suffered.
shell: bash on the piped step. The default bash -e {0} has no pipefail, so node … | tee takes tee's status and a gate exiting 1 passes the job silently. I checked the provenance: this is PR #74's finding, measured there against a real gate script and fixed the same way. Inheriting an existing adjudication rather than rediscovering it, and the comment says so.
artifact-empty as its own rule. A format change that leaves the reader matching nothing must fail rather than pass over zero measurements. That is the failure mode that would have made this entire gate worthless.
The privacy/terms locale set is a declared constant in the gate rather than derived — correctly, since those pages are not in content/docs/ at all and the adjudication put the oracle there. But #173 is in flight making sitemap.ts derive that same set from the route modules.
After both land: translating the privacy policy makes sitemap.ts advertise a third locale automatically, and the gate goes red naming the page until its constant is updated. That is the right outcome, not a defect — the gate has to hold an expectation independent of the thing it checks, or it cannot catch that thing being wrong. But it means translating a legal page is a two-file action by design, and whoever does it should find that out from a red gate naming the page rather than from surprise. Flagging it to #173's dev.
Deviations — accepted
turbo.json untouched despite being in the declared surface, with a real reason (its test inputs already name .github/scripts/**; adding content/docs/** would declare an input the self-test does not read). The recipe correction above. The privacy/terms constant, reasoned above.
Proceeding to ready and merge. #184 is the increment on ARTIFACTS and stays blocked until this lands.
Activity
hotlong commentedon Aug 26, 2026
Graded: promoted to
pm:queue/ Task.findingremoved — grading is what that label waits for.Promoted rather than held, and the case got stronger after this card was written. The same missing-language-argument defect has now been found and fixed in four places, every one of them green on every gate for its entire lifetime:
sitemap.ts(#169),llms.txt(#170),llms-full.txt(#177), and the OG card (#185). On #177 that meant 4.6 MB of seven-locale text served under arobots.txtthat had just been opened to crawlers.Four instances is no longer a pattern worth noting. It is a gate that should exist.
The card already prices the one real design constraint — the check needs the built output, so it is a post-
buildstep rather than a standalone--self-test, because importingsitemap.tspulls in the whole MDX collection and the@/alias. That is the decision the implementer inherits rather than rediscovers.Scope note for dispatch: #184 is blocked on this card and carries the assertion shape for the two
llmsendpoints, already measured. Whatever harness this card settles on should be able to carry those two — build the harness with that in mind, but do not implement #184's assertions here.Generated by Claude Code
hotlong commentedon Aug 26, 2026
Claim: PM loop round 6
Session:
session_01G5zjYc2BoFV2NjKBBapC7CBranch:
claude/issue-175-pin-generated-locale-surfaceWorktree:
objectos-issue-175Domain: n/a (objectos carries no
domain:*lanes)File surface: a new gate under
.github/scripts/ortools/ci-scripts/, plus whatever wiring it needs (turbo.json, rootpackage.json,.github/workflows/ci.yml). Noapps/docs/**source file — this card asserts behaviour, it does not change it. (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opus— PM judgement.Clause-②: no
Serial constraints cleared: #173 and #178 are dispatched alongside this one. #173 changes where
sitemap.tsgets the privacy/terms locale set from, not what it is (en+zh-Hanseither way), so a gate asserting counts is unaffected by which lands first. #178 changesllms.txtlinks from relative to absolute, which does not touch the per-locale composition this gate asserts. Neither touches the gate surface.Read coupling worth naming: if this gate asserts the exact text of a
llms.txtlink line, #178 will turn it red for a correct reason. Assert composition (which pages, which locales), not link formatting.Generated by Claude Code
ci: pin the generated sitemap's locale composition against the conten…
hotlong commentedon Aug 26, 2026
Dev claim (os-dev, this card).
Session:
session_01G5zjYc2BoFV2NjKBBapC7CBranch:
claude/issue-175-pin-generated-locale-surfaceWorktree:
/home/user/objectos-issue-175, offorigin/mainat6653744PR: #193 (draft)
File surface actually touched, within the declared bounds:
.github/scripts/check-locale-surface.mjs(new).github/workflows/ci.ymltools/ci-scripts/run-self-tests.mjspackage.jsonNo
apps/docssource file.turbo.jsonwas in the allowed surface but needed no change — itstestinputs already name$TURBO_ROOT$/.github/scripts/**, so the new self-test re-runs when the script changes, and addingcontent/docs/**there would declare an input the self-test does not read.Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 26, 2026
os-dev-report
{ "issue": 175, "status": "done", "branch": "claude/issue-175-pin-generated-locale-surface", "pr": "https://github.com/objectstack-ai/objectos/pull/193", "premise_still_valid": true, "head_sha": "fe6abaf", "files_changed": [ ".github/scripts/check-locale-surface.mjs (new, +689)", ".github/workflows/ci.yml (+21)", "tools/ci-scripts/run-self-tests.mjs (+15/-2)", "package.json (+3/-1)" ], "gates": [ { "gate": "node .github/scripts/check-locale-surface.mjs --self-test", "exit": 0, "verdict_line": "self-test: 7 case(s) over 6 rule(s) - every rule demonstrated able to fail, on fixtures read through the real readers" }, { "gate": "pnpm turbo run test (runs tools/ci-scripts/run-self-tests.mjs)", "exit": 0, "verdict_line": "3 self-test(s) passed / Tasks: 1 successful, 1 total", "note": "was 2 self-tests before this PR; the runner's unlisted-script guard makes the wiring self-enforcing" }, { "gate": "pnpm turbo run type-check --continue", "exit": 0, "verdict_line": "Tasks: 1 successful, 1 total" }, { "gate": "pnpm turbo run build --filter=@objectos/docs", "exit": 0, "verdict_line": "Tasks: 1 successful, 1 total" }, { "gate": "node .github/scripts/check-locale-surface.mjs (the new gate, good tree)", "exit": 0, "verdict_line": "every advertised URL has a source file, and every source file is advertised", "measured": "sitemap.xml advertised 346, distinct 346, expected 346, unexpected 0, missing 0, duplicated 0" }, { "gate": "node .github/scripts/check-locale-surface.mjs (reconstructed pre-#169 defect)", "exit": 1, "verdict_line": "locale surface: 781 finding(s)", "measured": "sitemap.xml advertised 3892, distinct 574, expected 346, unexpected 228, missing 0, duplicated 553" } ], "summary": "New gate .github/scripts/check-locale-surface.mjs asserts the locale composition of the built sitemap.xml against an oracle derived every run from content/docs/ and apps/docs/lib/i18n.ts - no hardcoded counts, nothing read from app code. Six rules, each with a red fixture: unexpected-url, missing-url, duplicate-url, artifact-missing, artifact-empty, translation-orphan. Wired as a post-build CI step (deliberately not a turbo task: turbo would hash it against inputs excluding apps/docs/.next/, and a cached-green locale gate is the failure this card exists to end), with its --self-test registered in run-self-tests.mjs so pnpm turbo run test keeps proving the rules can go red. ARTIFACTS is the extension point #184 adds the two llms endpoints to; #184's assertions are deliberately not implemented here.", "tests": "Oracle validated against the real artifact before any code was written: derived per-locale docs entries en 79, zh-Hans 62, ja 39, de 38, es 39, fr 39, ko 39 = 335, plus 7 root + 2 privacy + 2 terms = 346, matching the built sitemap exactly with zero set difference in either direction - the card's measured values, derived not declared. REVERSE VERIFICATION, run as one foreground script with a restore trap, all under the shared verify lock. Leg 1: apps/docs/app/sitemap.ts checked out verbatim at ee74379, mutation proven on disk by git hash-object == 079b27b96c92ef934367e1d04a6dded3bb67e66a; it does not compile against today's tree - 'app/sitemap.ts(25,23): error TS2554: Expected 2 arguments, but got 1.' / 'Exit status 2' - because #169 made languageAlternates' locales argument required, and restoring lib/seo.ts to ee74379 too was not an option (translatedLocales did not exist there and the OG route plus the docs page both import it now). Leg 2: the same defect reconstructed against today's signature (source.getPages() with no argument, languageAlternates(path, i18n.languages), iterate i18n.languages), mutation proven on disk by blob hash ae4b5d9adcf90f308d478271b298930421672f3b != HEAD blob and by grep counts of injected text (getPages() with no argument: 1) and removed text (translatedLocales(: 0, STATIC_PAGE_LOCALES: 0); rebuilt with --force; artifact re-derived to loc count 3892 over 574 distinct - the issue body's numbers exactly (556 logical paths x 7 = 3892; 79 docs + 3 static, x 7 = 574). Gate exit 1 with 781 findings naming what is wrong: 228 unexpected-url ('https://docs.objectos.ai/de/docs/architecture is advertised but the content tree has no source file for it - an untranslated page is being advertised as a translation') and 553 duplicate-url ('https://docs.objectos.ai/docs/architecture advertised 7 times'). RESTORE proven by git checkout HEAD -- (absolute path), git hash-object back to 197084a227ec741fe275594bf9cb5dbe92b86870, and git diff HEAD returning 0 lines; rebuilt to loc count 346; gate exit 0. Final tree clean, sitemap source byte-identical to origin/main. Final union re-run at the final commit fe6abaf after all edits: type-check and test both 'Tasks: 1 successful, 1 total', gate exit 0.", "deviations": [ "The card's verification recipe says to check out sitemap.ts at ee74379 and rebuild. That exact file cannot build against today's tree - it calls languageAlternates with one argument and #169 made the second required (TS2554), and next.config.mjs sets typescript.ignoreBuildErrors: false. Reported as leg 1 with its compiler output rather than worked around, then the DEFECT (not the file) was reconstructed against today's signature for leg 2. The reconstruction is provably the same emission: 3892 entries over 574 distinct URLs, matching the issue body.", "turbo.json is in the declared file surface but was not changed. Its test inputs already name $TURBO_ROOT$/.github/scripts/**, so the new self-test re-runs when the script changes; adding content/docs/** would declare an input the self-test does not read, and the real gate runs outside turbo on purpose." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 26, 2026
ACCEPT — PM review of record. PR #193 at
fe6abaf.mainat6653744,Fixes #175✓apps/docs/**source file, as required ✓buildandNode floorbothcompleted/success✓.github/**andtools/— not governed surface (.claude/**,skills/**,AGENTS.md,CLAUDE.md,docs/adr/**) ✓run-self-tests.mjs;pnpm turbo run testwent from 2 self-tests to 3 ✓My verification recipe was wrong, and it was reported rather than worked around
I said: check out
sitemap.tsatee74379, rebuild, watch the gate go red. That file cannot build against today's tree — it callslanguageAlternateswith one argument and #169 made the second required, withtypescript.ignoreBuildErrors: false.TS2554, exit 2, no build, no measurement.The easy paths were to quietly skip the reverse verification, or to loosen something until it compiled. Instead: leg 1 reported with the compiler's own output, then the defect — not the file — reconstructed against today's signature, with the mutation proven on disk by blob hash and by grep counts of both injected and removed text. The rebuilt artifact came back at 3892 entries over 574 distinct URLs: the issue body's numbers exactly, arrived at independently.
Gate then exits 1 with 781 findings that name what is wrong — 228
unexpected-url, 553duplicate-url, each naming the URL. Restore proven by blob hash back to197084aand an emptygit diff HEAD, rebuild back to 346, gate green.That is a gate demonstrated able to fail on the real defect it exists to catch, not on a fixture that resembles it.
Design decisions worth keeping
The oracle reads no app code. Stated in the header as the reason: "The gate must be able to go red when app code is wrong, which it cannot do if app code is where it gets its expectations." The same principle drives re-declaring
SITE_URLinstead of importing it — "a check that shares a constant with the thing it checks cannot catch that constant being wrong" — with the mismatch designed to surface loudly as every URL landing in bothunexpected-urlandmissing-url.The reader deliberately does not de-duplicate. 3318 exact duplicates would otherwise hide behind a correct distinct count. The
duplicate-urlrule only exists because of that choice.Not a turbo task, on purpose. Turbo would hash it against the ci-scripts package's inputs, which do not include
apps/docs/.next/— so a locale regression would replay a cached green. That is the same shared-cache hazard two devs hit independently on this backlog, anticipated rather than suffered.shell: bashon the piped step. The defaultbash -e {0}has no pipefail, sonode … | teetakes tee's status and a gate exiting 1 passes the job silently. I checked the provenance: this is PR #74's finding, measured there against a real gate script and fixed the same way. Inheriting an existing adjudication rather than rediscovering it, and the comment says so.artifact-emptyas its own rule. A format change that leaves the reader matching nothing must fail rather than pass over zero measurements. That is the failure mode that would have made this entire gate worthless.One tension to name, for #173
The privacy/terms locale set is a declared constant in the gate rather than derived — correctly, since those pages are not in
content/docs/at all and the adjudication put the oracle there. But #173 is in flight makingsitemap.tsderive that same set from the route modules.After both land: translating the privacy policy makes
sitemap.tsadvertise a third locale automatically, and the gate goes red naming the page until its constant is updated. That is the right outcome, not a defect — the gate has to hold an expectation independent of the thing it checks, or it cannot catch that thing being wrong. But it means translating a legal page is a two-file action by design, and whoever does it should find that out from a red gate naming the page rather than from surprise. Flagging it to #173's dev.Deviations — accepted
turbo.jsonuntouched despite being in the declared surface, with a real reason (its test inputs already name.github/scripts/**; addingcontent/docs/**would declare an input the self-test does not read). The recipe correction above. The privacy/terms constant, reasoned above.Proceeding to ready and merge. #184 is the increment on
ARTIFACTSand stays blocked until this lands.Generated by Claude Code
ci: pin the generated sitemap's locale composition against the conten…
ci: pin the locale composition of llms.txt and llms-full.txt (#202)