Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4160,32 +4160,6 @@ jobs:
- name: Ref'd-timer probe containment
run: pnpm check:refd-timer-probe

# The TYPE axis of the same invariant (#8180). The gate above reads
# `vitest.config.*` and nothing else, so the identical exposure on the
# type axis was unguarded repo-wide — and its symptom is likewise a GREEN
# `typecheck`. Measured on `packages/qa/downstream-contract` (#8021):
# identical checkout, identical stale `dist`, a narrowing injected into
# `packages/spec/src` in SOURCE only — without a `paths` block
# `tsc --noEmit` exited 0 and clean; with one it reported the `TS2322` the
# frozen fixture should always have produced. The comment beside the
# type-check-debt step below documents the same mechanism as expected
# state ("tsc resolves workspace imports through each dependency's built
# `dist/*.d.ts`"), and turbo's `typecheck` dependsOn `^build` does not
# reach it for the same reason it does not reach the runtime axis.
# NOT a port of the gate above: tsc's `paths` algorithm is exact-key-wins
# then longest-prefix (Vite's is first-match-wins), type-only imports
# COUNT here, and `tsconfig.json` is JSONC that inherits through
# `extends`. The trap is also different and worse — `@objectstack/spec*`,
# star not after a separator, folds every namespace onto one target and
# type-checks GREEN against the wrong module instead of crashing.
# The registry is measured (51 of 76 packages on the day it landed),
# shrink-only and audited in BOTH directions. It finds no new offender
# today: it is a ratchet, so the remediation list is finite and cannot
# grow behind anyone's back. Static scan of sources and tsconfigs; no
# build needed (~1.2s), so it belongs in this job.
- name: Type-source resolution gate
run: pnpm check:type-source-resolution

# #4248 packaging-hygiene guard. Without a `files` whitelist npm packs the
# whole package directory, and 20 of the 49 publishable packages declared
# none — so consumers installed TypeScript sources, unit tests and build
Expand Down
9 changes: 5 additions & 4 deletions docs/audits/gate-census-2026-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ Zod validation inside `packages/spec` and the runtime is the **product**, not a

| verdict | rows |
|:--|--:|
| `keep` | 133 |
| `keep` | 132 |
| `retire` | 58 |
| `report-only` | 16 |
| `keep (session hook; see Method)` | 5 |
| `keep (too new)` | 5 |
| `keep (with its hook)` | 5 |
| `keep (ruled)` | 2 |
| `retire (ruled)` | 1 |
| **retire (all spellings)** | **59** |
| **keep (all spellings)** | **150** |
| `retire · maintainer ruling 2026-09-18 on #18373` | 1 |
| **retire (all spellings)** | **60** |
| **keep (all spellings)** | **149** |
| **report-only** | **16** |

| class | rows |
Expand Down Expand Up @@ -212,7 +213,7 @@ Zod validation inside `packages/spec` and the runtime is the **product**, not a
| `scripts/check-tier-file-adoption.mjs` | correctness | a package adopting a nightly tier reads the tier file · origin no card in header · landed 2026-09-10 | required · whole-repo (Lint & Repo Gates) | unmeasured (landed 2026-09-10; <7 days of window coverage) | `keep` |
| `scripts/check-turbo-task-graph.mjs` | correctness | turbo.json package tasks name real packages and scripts · origin #12046 · landed 2026-08-25 | required · whole-repo (Lint & Repo Gates) | 0 in window | `keep` |
| `scripts/check-type-check-coverage.mjs` | correctness | every package is type-check covered or in the shrink-only DEBT ledger · origin #4311 · landed 2026-07-31 | required · whole-repo (TypeScript Type Check ← debt lane); required · whole-repo (TypeScript Type Check ← source lane) | 20 PRs / 11 days (2026-08-21..09-03; `--re-measure` debt-ledger lane) | `keep` |
| `scripts/check-type-source-resolution.mjs` | correctness | a package's types resolve from source, not stale dist · origin #7849 · landed 2026-08-12 | required · whole-repo (Lint & Repo Gates) | 3 PRs / 3 days (2026-08-20..2026-09-16); 1 self excluded | `keep` |
| `scripts/check-type-source-resolution.mjs` | correctness | a package's types resolve from source, not stale dist · origin #7849 · landed 2026-08-12 | required · whole-repo (Lint & Repo Gates) | 3 PRs / 3 days (2026-08-20..2026-09-16); 1 self excluded | `retire · maintainer ruling 2026-09-18 on #18373` |
| `scripts/check-undeclared-dep-imports.mjs` | contract | published `src/**` imports only declared workspace deps · origin #10062 · landed 2026-08-28 | required · whole-repo (Lint & Repo Gates) | 0 in window | `keep` |
| `scripts/check-vendor-export-contract.mjs` | contract | a consumer-resolved vendor version exports what we import · origin #10909 · landed 2026-09-07 | required · whole-repo (Lint & Repo Gates); advisory (Validate Package Depen) | 0 in window | `keep` |
| `scripts/check-vendor-version-stamps.mjs` | process | none (prose version stamps about a pinned vendor stay equal to the pin) · docs · origin #10073 · landed 2026-08-31 | required · whole-repo (Lint & Repo Gates) | 0 in window | `retire` |
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@
"check:rest-log-declared": "node scripts/check-rest-log-declared.mjs --self-test && node scripts/check-rest-log-declared.mjs",
"check:rest-log-spy-declared": "node scripts/check-rest-log-spy-declared.mjs --self-test && node scripts/check-rest-log-spy-declared.mjs",
"check:refd-timer-probe": "node scripts/check-refd-timer-probe.mjs --self-test && node scripts/check-refd-timer-probe.mjs",
"check:type-source-resolution": "node scripts/check-type-source-resolution.mjs --self-test && node scripts/check-type-source-resolution.mjs",
"check:undeclared-dep-imports": "node scripts/check-undeclared-dep-imports.mjs --self-test && node scripts/check-undeclared-dep-imports.mjs",
"check:keyed-text-bounds": "node scripts/check-keyed-text-bounds.mjs --self-test && node scripts/check-keyed-text-bounds.mjs",
"check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs",
Expand Down
35 changes: 17 additions & 18 deletions scripts/check-ratchet-remedy-authority.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// This gate enforces that DISJUNCTION. (b) is not a lesser form of (a): a gate
// that refuses satisfies the convention MORE strongly than one that marks, and
// this detector must never push a refusing gate onto the marking shape. The
// precedents are check-type-source-resolution.mjs, check-test-source-alias.mjs,
// check-adr-links.mjs and check-driver-memory-census.mjs.
// precedents are check-test-source-alias.mjs, check-adr-links.mjs and
// check-driver-memory-census.mjs.
//
// ── The ruled EXCEPTION: an authorised cross-file move (2026-09-03) ─────────
//
Expand Down Expand Up @@ -205,7 +205,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
'(9) Stage 2 reaches a real ratchet, by each limb, so (8) is not vacuous.': 2,
'(10) NON-CIRCULARITY. The authority token must never be its own anchor: a': 1,
'(11) Refusal, BOUND shape — check-adr-links.mjs / check-driver-memory-census.mjs.': 1,
'(12) Refusal, PREDICATION shape — check-type-source-resolution.mjs / check-test-source-alias.mjs.': 1,
'(12) Refusal, PREDICATION shape — check-test-source-alias.mjs.': 1,
'(13) Refusal DISCRIMINATES. A marking gate\'s closing discouragement is not a': 1,
'(14) End-to-end: an anchored, unrefused, unmarked offer is a VIOLATION. This': 1,
'(15) …and the same text carrying the token classifies as MARKED. Paired with': 1,
Expand Down Expand Up @@ -897,25 +897,24 @@ const CONTROL = {
expect: 'refused',
why: 'Refuses by binding a negation to the verb, over a shrink-only registry.',
},
// The two gates below refuse by PREDICATION (the act named as subject and
// denied) — self-test (12) pins that predicate on their exact sentence.
// The gate below refuses by PREDICATION (the act named as subject and
// denied) — self-test (12) pins that predicate on its exact sentence.
//
// Until #8576 both were recorded here as `excluded`, and that was the honest
// reading: stage 2 declined them FIRST, because each named its target in a
// Until #8576 it was recorded here as `excluded`, and that was the honest
// reading: stage 2 declined it FIRST, because it named its target in a
// message carrying no testimony about the registry's nature — the testimony
// sat in a comment, where no author and no detector reads it. #8576 mirrored
// one clause of each gate's own shrink-only comment into that same message, so
// one clause of the gate's own shrink-only comment into that same message, so
// the target is now established as a ratchet and the refusal limb is reached.
// Growing the refusal limb's sample from one gate to three was the point: a
// regression in that limb used to be measured against a sample of one.
// That card grew the refusal limb's sample from one gate to three, so a
// regression in that limb is no longer measured against a sample of one. It
// had a second PREDICATION entry, check-type-source-resolution.mjs, until that
// gate was retired under the maintainer ruling of 2026-09-18 on #18373; the
// entry went with the file, which is the remedy the STALE branch below names.
'check-test-source-alias.mjs': {
expect: 'refused',
why: 'Refuses by predication. Its registry states its own nature in the same message since #8576, so stage 2 reaches it and the refusal limb is consulted.',
},
'check-type-source-resolution.mjs': {
expect: 'refused',
why: 'The other refusal precedent, refusing by the same predication shape. Its registry states its own nature in the same message since #8576, so stage 2 reaches it rather than declining on a path target.',
},
// The fourth refusal instance, and the second gate to join the convention as a
// NEW gate rather than by retrofit (#10619). Refusal — not marking — is the
// honest shape here because there is no legitimate act for anyone to own: its
Expand Down Expand Up @@ -1218,7 +1217,7 @@ function main() {
+ ` remedy: …${r.live[0].window.slice(0, 140).trim()}…\n`
+ ` registry: ${r.live[0].target.name} (testimony: ${r.anchors[0]})\n`
+ ' Fix: say in the same message that this path belongs to a maintainer, or turn it down\n'
+ ' outright the way check-type-source-resolution.mjs does. Turning it down is the stronger\n'
+ ' outright the way check-test-source-alias.mjs does. Turning it down is the stronger\n'
+ ' shape and this gate treats it as fully compliant — it is not a lesser option.',
);
}
Expand Down Expand Up @@ -1427,10 +1426,10 @@ function selfTest() {
+ 'check-adr-links.mjs and check-driver-memory-census.mjs use',
offerIsRefused({ context: 'fix the link; do not add it to KNOWN_DEAD_TARGETS to make this green.' }));

// (12) Refusal, PREDICATION shape — check-type-source-resolution.mjs / check-test-source-alias.mjs.
battery('(12) Refusal, PREDICATION shape — check-type-source-resolution.mjs / check-test-source-alias.mjs.');
// (12) Refusal, PREDICATION shape — check-test-source-alias.mjs.
battery('(12) Refusal, PREDICATION shape — check-test-source-alias.mjs.');
expect('refusal — an act named as subject and denied is a refusal ("widening the registry entry '
+ 'is not the fix"), the shape the two registry gates use',
+ 'is not the fix"), the shape check-test-source-alias.mjs uses',
offerIsRefused({ context: 'Add the rules to its tsconfig.json — widening the registry entry is not the fix.' }));

// (13) Refusal DISCRIMINATES. A marking gate's closing discouragement is not a
Expand Down
43 changes: 26 additions & 17 deletions scripts/check-type-check-coverage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,14 @@ import {
workspaceEnumeratorFloorFailures,
workspacePackageDirs,
} from './workspace-enumerator.mjs';
// `typecheck`-script -> tsconfig program set. Shared with
// `check-type-source-resolution.mjs` since #11490, which needs the identical
// answer to decide its POPULATION: two copies of this predicate drift, and the
// symptom of drift is a green gate on either side.
// `typecheck`-script -> tsconfig program set. It moved into its own module in
// #11490, when `check-type-source-resolution.mjs` needed the identical answer
// to decide its POPULATION; that gate was retired under the maintainer ruling
// of 2026-09-18 on #18373, so this file is the only consumer left. ⛔ Folding
// the predicate back in here is a separate decision, not a consequence of that
// retirement -- it floors its own cases in its own battery (PR #15327), which
// this file deliberately does not re-pin. Two copies of this predicate drift,
// and the symptom of drift is a green gate on either side.
import {
configsNamedByTypecheck,
typecheckScriptChain,
Expand Down Expand Up @@ -4510,12 +4514,13 @@ function graduationRemedy({ ledger, isRoot = false }) {
` (a) The #5286 sibling route: add a \`tsconfig.test.json\` that reaches the ` +
`tests and NAME it in the \`typecheck\` script. Always available -- it leaves \`tsconfig.json\` alone.\n` +
` (b) Drop the \`**/*.test.ts\` entry from \`exclude\` in \`tsconfig.json\` (or widen \`include\` to ` +
`reach the test tree). Available ONLY while \`pnpm check:type-source-resolution\` still passes with ` +
`the tests re-admitted: that gate reads \`tsconfig.json\` and nothing else, the re-admitted tests ` +
`import workspace packages this package's src program never held, and its registry is ⛔ SHRINK-ONLY ` +
`-- registering the new ones is not the way out. Measured red on 14 of the 18 entries that have an ` +
`exclusion to drop, so assume (b) is unavailable until that gate says otherwise. Run it before you ` +
`commit; nothing in this gate's own verdict will tell you.`
`reach the test tree). ⛔ The gate that DECIDED whether this route was available, ` +
`\`check:type-source-resolution\`, was RETIRED under the maintainer ruling of 2026-09-18 on #18373 ` +
`-- so nothing measures it for you any more, and ⛔ its silence is not a clearance. What it measured ` +
`has not changed: the re-admitted tests import workspace packages this package's src program never ` +
`held, its registry was ⛔ SHRINK-ONLY so registering the new ones was never the way out, and it read ` +
`red on 14 of the 18 entries that have an exclusion to drop. Treat (b) as the worse route and prefer ` +
`(a), which leaves \`tsconfig.json\` alone; nothing in this gate's own verdict will tell you.`
);
}
if (ledger === 'DEBT') {
Expand Down Expand Up @@ -5620,9 +5625,11 @@ function selfTest() {
// The observation half is where the :267 blind spot lived: `excludesTests`
// read only `tsconfig.json`, so a sibling test config was invisible however
// it was wired. `configsNamedByTypecheck` and `typecheckScriptChain` now
// decide it, and since #11490 they live in `scripts/typecheck-configs.mjs`
// because `check-type-source-resolution.mjs` needs the same answer for its
// population. Their cases moved WITH them -- one rule, one home, one battery
// decide it, and since #11490 they live in `scripts/typecheck-configs.mjs`,
// where they moved because `check-type-source-resolution.mjs` needed the same
// answer for its population -- that gate was retired on 2026-09-18 (#18373),
// leaving this file its only consumer. Their cases moved WITH them -- one
// rule, one home, one battery
// -- and are folded in here so this gate still fails when the predicate it
// depends on breaks.
//
Expand Down Expand Up @@ -6398,13 +6405,15 @@ function selfTest() {
+ 'that remedy is a no-op on every one of them -- the misfire #11491 was filed on.',
},
{
label: 'TEST_DEBT graduation names the gate that DECIDES whether the exclusion route is available',
label: 'TEST_DEBT graduation still carries what the retired gate measured about the exclusion route',
message: testDebtGrad,
present: ['check:type-source-resolution', 'SHRINK-ONLY', 'tsconfig.test.json'],
absent: [],
why: 'the exclusion route reds that gate on 14 of the 18 entries that have an exclusion, and this '
+ 'gate never runs it. A message the author has to read a second gate\'s SOURCE to act on is the '
+ 'half of #11491 that a correct-but-terse rewrite would leave unfixed.',
why: 'the exclusion route read red on 14 of the 18 entries that have an exclusion, and this gate '
+ 'never ran it. A message the author has to read a second gate\'s SOURCE to act on is the half '
+ 'of #11491 that a correct-but-terse rewrite would leave unfixed -- and once that gate was '
+ 'retired (2026-09-18, #18373) the measurement is the ONLY thing left warning the author, so '
+ 'these needles stay exactly as they were.',
},
{
label: 'the workspace root graduates through `typecheck:root`, never through `typecheck`',
Expand Down
Loading
Loading