dotCMS / dotCMS/core

Wire the diff-scoped strict typecheck gate into CI — merged on main but never executes

Open
#37,536 3 comments 0 reactions 1 assignee View on GitHub

@nicobytes is already working on this.

Since Sep 14, 2026.

dotCMS : Build Team : Falcon Type : CI/CD
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-lintno strict-gate
core-web/lint-staged.config.mjs only nx affected -t lint / nx format:writeno 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: true does 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? Notsconfig.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.xml gains a strict-gate <execution> in the generate-resources phase, beside lint-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> accepts 0 and 1 (findings do not fail the build) but not 2 — a harness that could not run must still fail loudly. Exit-code contract: contracts/cli.md
  • A pull request touching core-web TypeScript shows inline annotations on the diff and a job summary on the run page (--format github writes to $GITHUB_STEP_SUMMARY)
  • A pull request touching only backend code is a no-op pass — verified, not assumed (the frontend filter in .github/filters.yaml already gates the job on core-web/**)
  • core-web/lint-staged.config.mjs runs the gate on staged TS files, in a non-overlapping glob key so it does not race the existing nx affected -t lint on git's index.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.md no 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
  • strict is the repo convention (strict + noPropertyAccessFromIndexSignature, noImplicitOverride, noImplicitReturns, noFallthroughCasesInSwitch) — the same yardstick as tsconfig.base.json on the strict-mode branch. At line granularity it costs one extra finding over the narrow set across the whole corpus.
  • line because 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.md
  • specs/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.