Wire the diff-scoped strict typecheck gate into CI — merged on main but never executes
@nicobytes is already working on this.
Since Sep 14, 2026.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
The diff-scoped strict typecheck gate exists on main and has never executed once.
PR #37403 (spike #37401) merged on 2026-09-08. It added 37 files: 31 under core-web/tools/scripts/strict-gate/ and 6 under specs/37401-diff-scoped-strict-typecheck-gate/. It touched zero files under .github/ and zero lines of core-web/pom.xml. The harness README says so plainly: "The spike wires the harness into nothing."
Verified on origin/main @ de342798f4
| Check | Result |
|---|---|
git grep -l "strict-gate" outside its own directory and spec dir |
no matches |
<execution> ids in core-web/pom.xml |
validate-dist-paths, pnpm-install, lint-test, format-test, build-test, build-analytics, unit-test, nx-reset, prod, do-nx-reset, validate, format, auto-format, auto-lint — no strict-gate |
core-web/lint-staged.config.mjs |
only nx affected -t lint / nx format:write — no strict-gate |
Any workflow in .github/workflows/ mentioning strict |
none |
Consequence: since 2026-09-08 no pull request has been annotated, reported on, or blocked by the gate. It is dead code on main, and main has kept accumulating strict debt exactly as before the spike.
Why this is worth finishing rather than deleting
The obvious objection is "#37198 turns the whole workspace strict, so drop it." DECOMMISSION.md §2 documents why that does not close the hole:
Declaring
strict: truedoes not mean anything compiles it.
| Fact | Value |
|---|---|
Nx projects in core-web |
57 |
…with a build target |
18 — the other 39 have nothing that compiles them |
…with a typecheck target |
5, all inferred by @nx/vite/plugin, 0 declared in a project.json |
Does the build see .spec.ts? |
No — tsconfig.lib.json carries exclude: ["src/**/*.spec.ts", …] |
| Where the spike's findings lived | 8 of 11 in .spec.ts (73 %) |
#37198 changes tsconfigs, .ts and .html files — no project.json, no nx.json, no pom.xml, no workflow. It makes the baseline strict and fixes existing violations, but adds no mechanism that runs a type-check over the 39 projects with no build plus every .spec.ts. Lint does not type-check.
So either this gate ships as the interim mechanism, or #37198 grows a typecheck target. Doing neither leaves the gap open in both directions.
Scope decision
Non-blocking (reporting only) in this issue. The blocking flip stays out of scope: measured runtime is 8.4–9.4 s average with a 12 s tail against the 10 s budget SC-005 set to protect what ADR-0013 bought (frontend merge time ~45 min → ~15 min). Templates are a separate no-go (2.2× compiler time on the largest app) and are excluded entirely.
This issue supersedes #37448, which carries the same scope but was never picked up (opened 2026-09-07, no assignee, no milestone, 0 comments).
Acceptance Criteria
-
core-web/pom.xmlgains astrict-gate<execution>in thegenerate-resourcesphase, besidelint-test/format-test, gated by<skip>${skip.validate}</skip>so it runs only under-Pvalidate - The execution runs
node tools/scripts/strict-gate/run.mjs --base=${git.origin.branch} --flags=strict --granularity=line --scope=core-web --format=github - Non-blocking semantics are explicit, not accidental:
<successCodes>accepts0and1(findings do not fail the build) but not2— a harness that could not run must still fail loudly. Exit-code contract:contracts/cli.md - A pull request touching
core-webTypeScript shows inline annotations on the diff and a job summary on the run page (--format githubwrites to$GITHUB_STEP_SUMMARY) - A pull request touching only backend code is a no-op pass — verified, not assumed (the
frontendfilter in.github/filters.yamlalready gates the job oncore-web/**) -
core-web/lint-staged.config.mjsruns the gate on staged TS files, in a non-overlapping glob key so it does not race the existingnx affected -t linton git'sindex.lock - The harness test suite (31 files) still passes and is documented as needing
--test-concurrency=1 - Observed runtime is recorded on this issue from at least 5 real pull requests, so the blocking decision has a measured tail instead of the spike's corpus number
-
core-web/tools/scripts/strict-gate/README.mdno longer says the gate is wired into nothing; it states where the hook lives and that it is non-blocking - #37448 is closed as superseded by this issue
- Out of scope, stated in the PR description: flipping to blocking, the template arm, and the three untried closure optimisations
Priority
Medium
Additional Context
No workflow change is required
cicd_comp_test-phase.yml already fetches origin/main (the Frontend Unit Tests job needs it for nx affected), and .github/filters.yaml already carries the frontend filter (core-web/**). The hook belongs in core-web/pom.xml like every other frontend gate — this is why grepping the workflows for "strict" finds nothing and always would.
Shape of the pom.xml execution
Model it on lint-test. The exec plugin's executable is ${node.install.dir}/pnpm, so the arguments start with exec:
<execution>
<id>strict-gate</id>
<goals><goal>exec</goal></goals>
<phase>generate-resources</phase>
<configuration>
<skip>${skip.validate}</skip>
<!-- 0 = clean, 1 = findings (non-blocking for now), 2 = harness failure (must fail) -->
<successCodes>
<successCode>0</successCode>
<successCode>1</successCode>
</successCodes>
<arguments>
<argument>exec</argument>
<argument>node</argument>
<argument>tools/scripts/strict-gate/run.mjs</argument>
<argument>--base=${git.origin.branch}</argument>
<argument>--flags=strict</argument>
<argument>--granularity=line</argument>
<argument>--scope=core-web</argument>
<argument>--format=github</argument>
</arguments>
</configuration>
</execution>
parseArgs in run.mjs splits on =, so the inline --flag=value form used by lint-test works as-is. Unknown options are rejected by name.
Why --flags=strict --granularity=line
strictis the repo convention (strict+noPropertyAccessFromIndexSignature,noImplicitOverride,noImplicitReturns,noFallthroughCasesInSwitch) — the same yardstick astsconfig.base.jsonon the strict-mode branch. At line granularity it costs one extra finding over the narrow set across the whole corpus.linebecause whole-file makes an author inherit 83 % of what it reports from lines they did not write. New files are unaffected — every line of an added file is a changed line.
Measured evidence from the spike
- 11 findings across a 5-PR corpus, all 11 real, 0 false positives
- 99.1 % of diagnostics discarded on a representative portlet (217 of 219)
- Zero changes to any version-controlled file — strictness is forced in memory, never through a temporary config
- No new dependency; not an Nx project (registering one would put the harness into the graph it measures)
Local repro
cd core-web
nvm use
node tools/scripts/strict-gate/run.mjs --base origin/main --head HEAD
node --test --test-concurrency=1 'tools/scripts/strict-gate/*.test.mjs'
Verification for the reviewer
# Before the PR both come back empty. After it, both must match.
git grep -n "strict-gate" -- core-web/pom.xml core-web/lint-staged.config.mjs
References
- Spike: #37401 · Spike PR: #37403 · Superseded follow-up: #37448
- Strict-mode migration this gate is scaffolding for: #37198
core-web/tools/scripts/strict-gate/README.mdspecs/37401-diff-scoped-strict-typecheck-gate/findings.md(§5 runtime, §6 decisions, §7 templates, §10 go/no-go, §12 follow-up)specs/37401-diff-scoped-strict-typecheck-gate/DECOMMISSION.md(§2 precondition, §3.3 what to remove if promoted)specs/37401-diff-scoped-strict-typecheck-gate/contracts/cli.md(options, exit codes, output formats)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.