Skip to content

ci: gate objectstack lint's i18n/missing-* warnings to zero - #1098

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-1018-i18n-gate
Aug 11, 2026
Merged

ci: gate objectstack lint's i18n/missing-* warnings to zero#1098
huangyiirene merged 1 commit into
mainfrom
claude/issue-1018-i18n-gate

Conversation

@huangyiirene

@huangyiirene huangyiirene commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1018

Description

objectstack lint only fails its own exit code on rule-level errors — warnings and suggestions are printed but the process exits 0 regardless of how many there are. Most i18n/missing-* findings are warnings (only a default-locale gap is an error), so pnpm verify's lint step can never go red on a translation-coverage regression. PR #1080 shipped 25 enumerated i18n/missing-page warnings through a green Quality Checks run as a dated instance of this exact gap; #1084 zeroed that debt the same day, which is why today is the moment to gate it — the baseline is zero.

This PR is the app-side half described in #1018: a new CI step that asserts the i18n/missing-* rule-hit count is zero. Making the underlying lint rule itself error-severity is a packages/lint (upstream, platform) change and is out of this repo's reach.

⚠️ Note on the issue thread: the PM's cluster-analysis comment on #1018 originally cited #1091 (nine files, an "unused import" that no gate caught) as a third supporting instance. That evidence was struck in a follow-up correction comment — #1091's premise was falsified (F/P are @objectstack/spec tagged-template aliases, used productively everywhere; the card's own grep reproduction could never return non-zero). This PR does not repeat that three-instance framing anywhere and relies only on the corrected evidence (#1060/PR #1080 as the primary, dated instance).

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Code refactoring
  • Performance improvement
  • CI/CD update

Related Issues

Fixes #1018

Changes Made

  • Added scripts/check-lint-i18n-gate.mjs: runs objectstack lint --json, filters issues whose rule starts with i18n/missing-, and exits non-zero if that count is not zero (regardless of severity — a default-locale gap is already severity: "error" and already fails plain pnpm lint, but folding it in here too costs nothing and keeps the assertion about the rule family, not about severity plumbing).
  • Added pnpm run lint:i18n-gate and wired it into pnpm verify, immediately after the existing lint step.
  • Added the same step (i18n lint gate (zero i18n/missing-*)) to both .github/workflows/ci.yml (Build and Test job) and .github/workflows/code-quality.yml (Quality Checks job), right after Lint metadata conventions.
  • Added test/lint-i18n-gate.test.ts: synthetic-fixture subprocess runs of the real script (via a --fixture flag (pointing at a JSON file) testing seam, so no real translation file is ever touched by the suite) covering the pass/fail/parse-error paths, plus one real end-to-end run of the script against this repo's current metadata, asserting today's true baseline is zero — so a regression is caught by pnpm test too, not only by the dedicated CI step.
  • Added a changeset (patch) — this is a tooling/CI change, not a user-visible product change, but the repo's convention is a changeset regardless.

Deliberately not in scope: the ~153 other pre-existing lint warnings/suggestions in this repo (component-props-invalid, absolute-colspan-discouraged, relationship/line-item-should-be-master-detail, etc.) are untouched — this gate only asserts the i18n/missing-* family is zero, not "no worse than yesterday" for everything else.

Premise verification (measured against origin/main @ 44067a0c)

$ pnpm exec objectstack lint --json
{
  "passed": true,
  "total": 163,
  "errors": 0,
  "warnings": 153,
  "suggestions": 10,
  "hiddenPlatform": 2265,
  ...
}
$ echo $?
0
  • objectstack lint (plain, non-JSON) exit code: 0 — confirmed both by direct run and by reading @objectstack/cli's lint.js: if (errors.length > 0) process.exit(1); is the only place the non-JSON path exits non-zero; warnings/suggestions never reach it.
  • i18n/missing-* count today: 0 (matches the PM's assumption). Total warnings: 153, suggestions: 10, total: 163 — also matches. (10 PRs merged today per the dispatch note; re-measured live rather than trusting the assumption.)
  • --json is machine-readable and reliable: { passed, total, errors, warnings, suggestions, hiddenPlatform, issues: [{ severity, rule, message, path, fix? }] }. Rule ids for the i18n family follow the pattern i18n/missing-${source} (i18n/missing-page, i18n/missing-view, i18n/missing-section, …), confirmed by reading @objectstack/cli/dist/commands/lint.js and .../utils/i18n-coverage.js directly. This made "count hits by rule name" viable exactly as PM's suggested route proposed, no redesign needed.

Reverse verification — the gate must be provable to go red

Per this card's binding clause, I deliberately manufactured a violation and confirmed the new gate fails while the underlying objectstack lint process does not.

Isolation, since src/translations/** is explicitly off-limits (#597 concurrently editing those four files) and src/ is off-limits entirely for this PR's diff: I made a disposable, fully-isolated copy of this worktree (tar copy, excluding node_modules/.git, with node_modules symlinked back in) under /tmp, ran the baseline there to confirm parity with the real tree, temporarily deleted one already-translated view label from the copy's src/translations/ja-JP.ts, ran both the real CLI and the new gate against the copy, then deleted the whole copy. The real worktree's src/translations/** was never written to — confirmed with git status --porcelain src/translations/ (clean) and a diff against origin/main's copy of the file (identical) immediately afterward.

Sandbox baseline (parity check):

$ pnpm exec objectstack lint --json   # in the disposable copy
passed True errors 0 warnings 153
i18n count 0

Violation injected (_views.at_risk_accounts label removed from the copy's ja-JP.ts):

$ pnpm exec objectstack lint --json   # objectstack lint itself, unpatched
EXIT:0
passed True errors 0 warnings 154
i18n count 1
{'severity': 'warning', 'rule': 'i18n/missing-view',
 'message': 'View "crm_account" _views.at_risk_accounts.label missing translation for locale "ja-JP"',
 'path': 'translations.ja-JP.objects.crm_account._views.at_risk_accounts.label'}

New gate against the same violation:

$ node scripts/check-lint-i18n-gate.mjs
✗ i18n lint gate: 1 `i18n/missing-*` issue(s) found — must be zero.

  [warning] i18n/missing-view  View "crm_account" _views.at_risk_accounts.label missing translation for locale "ja-JP"
    at translations.ja-JP.objects.crm_account._views.at_risk_accounts.label

These are translation-coverage gaps that `objectstack lint`'s own exit code does not fail on (non-default-locale misses are warnings, not errors) — see #1018. Run:
  pnpm exec objectstack lint --json
to see the full report, or `pnpm exec objectstack i18n extract` to scaffold the missing keys.
EXIT:1

That is exactly the split #1018 describes: objectstack lint itself stayed at exit 0 on the injected gap; the new gate caught it and exited 1.

Real-repo cleanliness after the sandbox exercise:

$ git status --porcelain src/translations/
$ diff <(git show origin/main:src/translations/ja-JP.ts) src/translations/ja-JP.ts && echo "IDENTICAL - untouched"
IDENTICAL - untouched

Testing

  • Unit tests pass — pnpm exec vitest run test/lint-i18n-gate.test.ts (under the shared verify lock): 6 passed (6), including the real end-to-end run asserting today's i18n/missing-* count is 0.
  • Linting passes — pnpm run lint (153 warnings / 10 suggestions, unchanged baseline, exit 0) and the new pnpm run lint:i18n-gate (0 i18n/missing-*, exit 0).
  • Typecheck passes — pnpm run typecheck (tsc --noEmit), no output, exit 0.
  • Hygiene passes — pnpm run hygiene (source-hygiene gate), clean.
  • Build / full test suite — not run (out of this card's affected-surface: no src/, objectstack.config.ts, or runtime code touched; pnpm typecheck + the targeted vitest file are the load-bearing checks for a CI-only change). Full pnpm verify is left to CI's real gate-job run.
  • New tests added — test/lint-i18n-gate.test.ts (6 cases: 4 synthetic-fixture pass/fail/parse-error paths via the script's --fixture seam, 1 unrelated-rule-family guard, 1 real end-to-end run against current metadata).
  • Reverse verification — see above; the gate is proven to go red on an injected violation the real objectstack lint process does not fail on.

CI on this PR (first-round observation)

I cannot idle-poll CI convergence (out of scope for this card's report timing), but this PR's own diff is the CI config change, so noting what I could see at draft-PR time: both ci.yml's Build and Test job and code-quality.yml's Quality Checks job now carry the new i18n lint gate (zero i18n/missing-*) step right after Lint metadata conventions. Locally, the exact command those steps run (pnpm run lint:i18n-gate) passes cleanly against origin/main's current metadata (see Premise verification above) — I expect both jobs' new step to be green on this PR's own run, but have not observed the live run's conclusion; that read is the PM's per the standard reporting contract.

Checklist

  • I have added a changeset (.changeset/i18n-lint-zero-tolerance-gate.md)
  • My code follows the style guidelines of this project (matches scripts/check-source-hygiene.mjs's header-comment / CI-step-comment conventions)
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • Documentation — none needed, this is an internal CI gate
  • My changes generate no new warnings (the pre-existing 153 warnings/10 suggestions are unaffected — this gate only watches i18n/missing-*)
  • I have added tests that prove my fix is effective
  • New and existing unit tests pass locally with my changes
  • Dependent changes — none

Additional Notes

File surface for this PR: .github/workflows/ci.yml, .github/workflows/code-quality.yml, package.json (scripts only), scripts/check-lint-i18n-gate.mjs, test/lint-i18n-gate.test.ts, .changeset/i18n-lint-zero-tolerance-gate.md. No file under src/ (including src/translations/**) was committed or left modified.


Generated by Claude Code

`objectstack lint` only fails its exit code on rule-level errors; warnings
and suggestions are printed but never gate. Most `i18n/missing-*` findings
are warnings (only a default-locale gap is an error), so a translation gap
could merge through fully green CI — PR #1080 shipped 25 enumerated
`i18n/missing-page` warnings that way, and #1084 zeroed that debt the same
day, which is what makes today the moment to gate it.

Adds scripts/check-lint-i18n-gate.mjs, which runs `objectstack lint --json`
and fails when any issue's rule starts with `i18n/missing-`. Wired into
`pnpm verify`, both CI workflows, and covered by
test/lint-i18n-gate.test.ts (synthetic-fixture cases plus a real
end-to-end run against today's baseline).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NM6o28jmBgsyTRQHutn7LC
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 11, 2026 5:25pm

Request Review

@github-actions github-actions Bot added ci/cd CI plumbing and the verification pipeline dependencies Dependency bumps and lockfile changes labels Aug 11, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 11, 2026 17:29
@huangyiirene
huangyiirene added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 12e15dd Aug 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline dependencies Dependency bumps and lockfile changes

Projects

None yet

2 participants