CI does not run pnpm lint:i18n-gate — the one gate that catches a missing translation key is the one a human has to remember - #72
Merged
Conversation
CI ran three of this project's four gates. `pnpm lint:i18n-gate` existed as a script, `scripts/check-lint-i18n-gate.mjs` implemented it, and every dev ran it by hand — nothing made it unskippable, which is the one thing this workflow exists to do. A missing translation key is exactly the silent class the file's header names. `fallbackLocale: 'en'` renders a key missing from `zh-CN` as its English source string: no error, no warning, nothing on screen that tells a gap from a translation. `pnpm lint` does not catch it either — `objectstack lint` exits non-zero only on rule-level ERRORS and a non-default-locale gap is a WARNING, so the fallback hides the gap on screen and the exit code hides it in CI. This is a two-locale repo by rule (DESIGN.md 11: en default, zh-CN full second bundle) and the two bundles are edited together on nearly every card that touches a user-visible string. This gate is the only check that they stayed in step, and it was the only one left to memory. One step, appended after `pnpm typecheck` in the existing `verify` job. No new job, no caching change, no action-version bump, no existing step reordered. `package.json`'s `verify` script already composes all four; the divergence was CI's alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
zhuangjianguo
marked this pull request as ready for review
September 10, 2026 11:33
This was referenced Sep 10, 2026
zhuangjianguo
pushed a commit
that referenced
this pull request
Sep 10, 2026
…ated PR #72 (#67) added `pnpm lint:i18n-gate` to `.github/workflows/ci.yml`, so CI enforces four gates. `package.json`'s `verify` has composed four for some time. The documents were the last thing in the repository saying three. Five surfaces, not the four the card listed — a repository-wide grep for the gate set also turned up `docs/backlog/README.md`, the acceptance floor every work card inherits. All five now state the same list in the same order: validate -> lint -> typecheck -> lint:i18n-gate. `AGENTS.md` "Verify your work" is rewritten rather than appended to: the count was welded into the prose twice ("all three exit non-zero", "the three green tails"), so a bare fourth command would have left the section contradicting itself within three lines. The replacement prose is deliberately count-free, so a fifth gate cannot reopen this defect there. The enumerations are kept; collapsing any of them to `pnpm verify` is a judgement the PR body raises and does not take. Documentation only. No gate reads any of these files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
This was referenced Sep 10, 2026
Open
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 #67
CI ran three of this project's four gates.
pnpm lint:i18n-gateexisted as a script,scripts/check-lint-i18n-gate.mjsimplemented it, and every dev ran it by hand — nothing made it unskippable, which is the one thing this workflow exists to do.The change — 8 lines, all appended, one
runstep1 file changed, 8 insertions(+), 0 deletions(-). Every insertion is after the last pre-existing line, so no existing step moved. Parsed rather than eyeballed —yaml.safe_loadon the result:One job, still. No caching change, no action-version bump, no reordering. The comment follows the file's own convention —
validateandlinteach carry a why,typecheckdoes not because it needs none; this step needs one, and the reason it needs one is measured below.The reverse check — the deliverable
A gate nobody has watched fail is not known to be a gate. Run with the fix already committed, so the restore leg points at
HEAD; the mutation runs undertrap 'restore' EXIT INT TERMwith an absolute path resolved fromgit rev-parse --show-toplevel, so an interrupted run still restores.Mutation: delete the leaf
dashboards.executive_overview.widgets.expiring_90_days.description(one line, 123) fromsrc/translations/zh-CN/app.ts.Proof the mutation reached disk — never an editor's exit code:
The anchor was asserted to match exactly once before deletion, so an edit that silently matched nothing could not have reported success.
The gate fails, and names the key (exit captured before any pipe):
Restore verified by STATE, not by an exit code:
⛔ And the half that says why this step is not redundant — all four gates against the same mutation
The card asserts a missing key passes
validate,lintandtypecheck. Measured rather than inherited, same mutation, same proof-on-disk preamble, same trap:validatestill prints✓ Validation passed (991ms);typecheckprints nothing at all.One correction to the card's wording, and it sharpens the case rather than weakening it.
pnpm lintis not blind to the gap — it reports it:It sees it, prints it, and exits 0 anyway — a non-default-locale gap is a WARNING and
objectstack lintexits non-zero only on ERRORS. So the fallback hides the gap on screen and the exit code hides it in CI, and a reviewer's only remaining defence is noticing that one number went 21 → 22 in a wall of pre-existing warnings. That is the failure this step removes. It also means the step is not a duplicatelintrun: the two share the finding and differ on the only thing CI reads.Gates — all four
0, exit codes captured before any pipeEach redirected to its own file,
$?read on the next statement:validate's 27⚠andlint's 21 warnings are all pre-existing and none is reachable from this diff, which changes no metadata — the gates' walk root isobjectstack.config.ts+src/**(pertsconfig.jsoninclude), and.github/workflows/ci.ymlis in neither.The step is green in CI on this PR's own head — self-demonstrating
The workflow that this PR edits is the workflow that ran it. Run #145,
fff1a29— https://github.com/objectstack-ai/hotclm/actions/runs/34471319104 — jobValidate, conclusionsuccess, step 9 of 9Run pnpm lint:i18n-gate,11:27:07 → 11:27:13. Its verdict, from the runner's own log:Identical numbers to the local run — 12 surfaces, 1541 keys expected, 0 missing.
The check attached to this PR is run #146 — https://github.com/objectstack-ai/hotclm/actions/runs/34471782986 — the
pull_request-event run on the samefff1a29: jobValidateconclusionsuccess, step 9Run pnpm lint:i18n-gate11:32:36 → 11:32:41, success. Two runs, two events, same commit, both green with the new step executed.The contrast is on this same branch: run #143, the empty-branch push at
30fd863, ran four steps and stopped atpnpm typecheck. Runs #145 and #146 run five.Three readings — reported, not changed
1.
package.json'sverifyalready composes all four. Line 21, verbatim:So the divergence was CI's alone. Anyone who ran
pnpm verifywas running the i18n gate; only the machine that cannot be forgotten was not. Left untouched, per the card.2.⚠️ It should say four.
CLAUDE.mdnames three gates, and after this merges it names one fewer than CI enforces. Line 8:pnpm validate && pnpm lint && pnpm typecheckmust all pass.CLAUDE.mdis a governed surface, so this PR does not touch it — flagged here for a maintainer-merged PR.3.
AGENTS.mdstates the gate set in two places, both three. Also governed, also untouched:## Verify your work — after every metadata change: a three-command block, then「Never report a change as done, and never open a PR, until all three pass. Paste the three green tails into the PR body.」 The count is welded into the prose twice —all three,three green tails— so this is a rewrite, not a line insert.| Gates | pnpm validate && pnpm lint && pnpm typecheck. |Acceptance notes
README.mdlines 47–49 carry the same three-command block and is not on the governed list, so it is editable — but it is out of this card's scope, and it is the same sentence as readings 2 and 3. Noted rather than filed, because the natural owner is exactly the maintainer-merged PR that renumbersCLAUDE.mdandAGENTS.md: doing it there keeps one edit instead of three, and filing a separate card invites the README half to land while the governed half waits. Four surfaces state the gate set —README.md47–49,CLAUDE.md8,AGENTS.md30–37 andAGENTS.md129 — and one PR should move all four.Not filed as a defect, deliberately: a dev who copies those three commands is not led into an error, only into an omission — the three still pass and still mean what they say.
The card's provenance claim re-measured:
*.test.*/*.spec.*/__tests__across the tree, excludingnode_modules→ 0 files. (Sevengrephits for.test./.spec.are prose inside.tsand.mdfiles, not test files.) The gates really are the whole of this repository's automated verification, which is the argument for not leaving one of them to memory.Scope
.github/workflows/ci.ymlonly. Nosrc/file is in the diff — the translation bundle was mutated transiently for the ablation and restored to itsHEADblob byte-for-byte, in this task's own worktree, and Two app-source comments quote aDESIGN.md§09 that no longer says any of it — the stale-pointer pattern, pointing the other way #71's two files were never opened.HEADvsorigin/main:DESIGN.md4bf1529b,AGENTS.md1ff636e3,CLAUDE.md16a72f5c,LICENSE16bc23f4,CHANGELOG.md43b63172,README.md5bfff001,package.jsondd06b914— all identical on both sides, andgit diff --name-status origin/main..HEADis the single lineM .github/workflows/ci.yml..github/**at all.🤖 Generated with Claude Code
https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
Generated by Claude Code