Skip to content

Commit 6dfa3ea

Browse files
test(cli): derive the build/validate gate roster from what the commands call, not from how a gate is spelled (#18675)
Fixes #18491 `packages/cli/test/validate-build-gate-parity.test.ts` holds one invariant: every artifact-level gate is wired into **both** `os build` and `os validate`, or into neither — *"There is no third option — that is the whole point of the file."* Its drift detector extracted call sites by spelling (the two prefixes `lint` and `validate` followed by a capital), so it saw **2 of the 47** bare-identifier call sites `compile.ts` has, and **2 of 39** in `validate.ts`. A gate named anything else was invisible, and the failure direction is FALSE CLEAN: a gate wired into one command only passed, which is the file's entire reason to exist. ## Premises checked at the tree, not taken from the card | Premise | Verdict | Evidence | |:--|:--|:--| | `findNavGroupDiagnostics` is really wired into both commands | **holds** | `compile.ts:51` import, `compile.ts:496` call; `validate.ts:46` import, `validate.ts:401` call | | `find*` is the only invisible family | **FALSIFIED** | `checkProtocolVersionGap` — `check*` — is a second one, live on the tree: `compile.ts:822`, `validate.ts:514`, and in zero roster rows. It is a non-registry gate by this file's own definition: it resolves the `@objectstack/spec` installed in the APP's `node_modules`, which is the filesystem, not the stack | | the roster held the gates it lists | holds, but for a narrower reason than it reads | the by-name `it.each` rows were held; the *drift scan* that was supposed to catch unlisted ones compared two 2-element sets that are identical in both commands, so it could not fail | Both lists, measured on `objectstack-ai/objectstack` at `62d830e54`: what the two commands actually call is 47 and 39 names; what the old detector could see is `lintUnknownStackKeys` and `lintUnknownAuthoringKeys`. The gap is 45 and 37 names. ## The choice: invert the direction, do not widen the pattern Widening the regex to cover `find*` was the obvious move and the wrong stopping point — two families were invisible *at once*, which is the evidence that widening moves the blind spot to whatever the repo names a gate next rather than closing it. So the scan now asks the opposite question. It extracts **every bare-identifier call site** from comment- and string-blanked source and requires each one to land in exactly one of three ledgers: - `SHARED_NON_REGISTRY_GATES` — hand-wired artifact-level gates, asserted present in BOTH commands; - `BUILD_ONLY_GATES` — gates `os build` may legitimately run alone, each with a reason; - `NOT_A_GATE` (new) — everything else, keyed by the reason it is not a gate. It reads no names, so no naming choice defeats it, and a name nobody classified fails its own PR. `findNavGroupDiagnostics` and `checkProtocolVersionGap` join the roster **because the scan demands a row for them** — not by hand. ## Also closed, same file and same class `calls()` matched **raw** source text, so a comment or a string writing a gate name with a parenthesis after it satisfied the roster's "both commands run it" assertion for a command that had stopped calling it. It now reads the blanked source. Pinned by `a comment or a string literal is not a wiring`. The inline-conversion-notice assertion at the foot of the file deliberately keeps reading RAW source — an inline copy of that sentence IS a string literal, and blanking strings there would make that assertion pass on exactly the file it exists to reject. ## What the scan still cannot see — asserted absent, not written down and hoped for `the command sources use no call shape this scan cannot read` refuses each shape that would hide a gate, so introducing one is a RED rather than a silent return to the blind spot: namespace imports, dynamic `import(...)`, `require(...)`, an import statement the parse skipped (the consumed count is asserted equal to the statement count: 22 of 22 and 18 of 18), and a value import handed on rather than called — the shape `formatUnknownAuthoringKey` has today. ## Evidence: two ablations, on the committed tree, each restored by blob hash **Positive control (the card's).** Delete the `findNavGroupDiagnostics` roster row; on-disk occurrences 1 to 0, verified before the run: ``` LEG A1 new-detector exit=1 (expect 1) AssertionError: compile.ts / validate.ts call 1 name(s) this file has not classified: findNavGroupDiagnostics. Tests 1 failed | 18 passed (19) ``` So the row is demanded by the detector, not hand-added. **The defect direction, two legs on one mutation.** A `find*`-named gate wired into `compile.ts` only (`findArtifactSomethingNew`, on-disk occurrences 0 to 1, verified), with the detector from the base commit `99fcb4ac1` restored beside the new one: ``` LEG A2 OLD-detector exit=0 (expect 0 — the blind spot) Test Files 1 passed (1) Tests 13 passed (13) LEG A2 NEW-detector exit=1 (expect 1) AssertionError: compile.ts / validate.ts call 1 name(s) this file has not classified: findArtifactSomethingNew. AssertionError: os build runs 1 gate(s) that os validate does not: findArtifactSomethingNew. Tests 2 failed | 18 passed (20) ``` Restore verified by blob hash, not by exit code: test `0d676ed9ef…` and compile `32b23f5fc5…` both match HEAD, `git diff HEAD` empty. The ablation script carried a `trap ... EXIT INT TERM` restoring with `git checkout HEAD -- path` against an absolute repo root. ## Verification (all at `02f3bfadb`) - `pnpm --filter @objectstack/cli exec vitest run test/validate-build-gate-parity.test.ts` — **20 passed** (was 13). - `pnpm --filter @objectstack/cli exec vitest run --project unit` — **211 files / 3026 tests**, 29 skipped. Two suites first failed `packages/cli is not built` (PREREQUISITE NOT MET, not a red); after `pnpm --filter @objectstack/cli build` both pass — `published-subpath-console.pin` and `published-subpath-hook-body.pin`, 29 tests. The `integration` tier is declared to CI: no path in this diff touches it, `bin/`, or a spawn entry point. - `pnpm --filter @objectstack/cli typecheck` — exit 0. `pnpm --filter @objectstack/cli check:test-typecheck` — OK, 3 files / 28 errors / 6 pinned signatures, unmoved. - `pnpm --filter '@objectstack/cli^...' build` — exit 0 (the dependency closure). - Derived gate families — `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, reconciled with `--ran`: **47 derived, 45 run green, 2 NOT MEASURED, 0 UNRUN.** The two are `check:type-check-debt` and `check:dual-build-cjs-loads`, both exit **3** = PREREQUISITE NOT MET: each needs a repo-wide build, and `check:type-check-debt`'s own internal build was OOM-killed (exit 137) on this shared container. Declared to CI; neither is a finding and neither is a pass. - `pnpm lint` repo-wide (`eslint . --no-inline-config`) — exit 0. Run whole, so no narrowing to justify. ## Clause-②: no Three readings on the measured diff: **no new exports** (a test file; it exports nothing), **`ERROR_CODE_LEDGER` / `StandardErrorCode` untouched**, **no accept-set change** (no schema widened or narrowed; the roster is a test ledger, not an accept set). `skip-changeset` measured rather than assumed: `@objectstack/cli` ships `files: ["dist","README.md","CHANGELOG.md"]`, `test/` is not among them, a symbol unique to this diff (`NOT_A_GATE`) has **0** hits across `dist` and `bin` after a build, and the positive control (`findNavGroupDiagnostics`, which does ship) has **4**. Nothing published moves. ## Acceptance notes **To file (class (a)/(b)) — `os build` runs a per-package rule walk `os validate` does not.** `compile.ts` runs a SECOND `runAuthoringRules('build', ...)` pass over each entry of `artifactPackages(...)` with `packageBodyAsStack(...)` resolution context, de-duplicated against the union run; `validate.ts` has one `runAuthoringRules('validate', ...)` call and no package walk. `compile.ts`'s own comment says what survives that de-duplication is "exactly the set the union could not see" — so `os build` reports findings `os validate` structurally cannot, in the false-clean direction, one layer past the gate roster this PR repairs. Not wired up here: wiring a real gate into the other door is a decision, not a test fix. It is recorded in the `NOT_A_GATE` ledger next to the two names, so it is visible rather than silent. Dedupe words: `per-package authoring rules` · `artifactPackages` · `packageBodyAsStack` · `runAuthoringRules per package` · `os validate package walk`. **noted, not filed** — `os lint` hand-wires gates of its own (`checkHookBodyLowering`, `computeI18nCoverage`, `scoreMetadata`, `runMetadataEval`) and is outside the parity question by design: it emits no artifact. The closed-ledger mechanism added here would extend to it unchanged if that ever becomes a parity question. Carrier: the next PR that touches this file. **noted, not filed** — local build-state only, no carrier: `check:dts-closure` went red mid-verification naming `driver-mongodb`, `plugin-reports` and `service-messaging` as missing `.d.ts`. Cause was the OOM-killed repo-wide build inside `check:type-check-debt` on this shared container leaving three packages with JS and no declarations. Rebuilding the three restored it (61 packages swept, 153/153 declaration files present). CI checks out fresh, so there is nothing to carry. --- _Generated by [Claude Code](https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 551139b commit 6dfa3ea

1 file changed

Lines changed: 522 additions & 20 deletions

File tree

0 commit comments

Comments
 (0)