Files
fusion/scripts/__tests__/check-inert-sync-lane-conversions.test.mjs
gsxdsm fa62c951cf fix(gate): the inert ratchet went quiet exactly when the code improved (conditional initializer) (#3169)
Found by dogfooding my own change: I wrote `executor.ts` in the
payload-first/sync-fallback shape while adopting #3140's better
fallback, **predicted in a comment that the guards would stay counted**,
and the gate reported **zero**. The prediction was wrong in the
direction that matters — the gate under-reports.

## The gap

`syncLaneLocals` registered a local only when its initializer **was** a
call expression:

```ts
const sync = payload ? undefined : localSync(store, id);
return column === sync?.hold;          // inert, and counted as nothing
```

Conditionals and `??`/`||` chains are now unwrapped, so a sync call in
any branch registers the local. Still a **name** match, not dataflow —
the file's LIMITS section still applies.

## Why this shape matters more than the inline one already guarded

**The missed shape is the one authors are steered toward.** Falling back
to the sync resolver is *better* than falling back to legacy literals —
it is best-effort under legacy SQLite, whereas a literal can never be
right on a renamed board. So writing the guard well is what made it
invisible.

A ratchet that goes quiet exactly when the code improves is worse than
none: it rewards the worse degraded path with a tidier number.

## Known remaining gap, stated in the test rather than implied

Only **one hop** is followed. The two-hop form is still uncounted:

```ts
const sync  = payload ? undefined : localSync(store, id);
const lanes = { hold: payload?.hold ?? sync?.hold ?? "todo" };
if (from !== lanes.hold) …            // still invisible
```

`executor.ts` is written that way today, which is why it reads 0 while
the sync call is still present. Closing it needs propagation through
object-literal construction — a larger change than this one, and I would
rather ship the one-hop fix with the gap documented than imply full
coverage.

## Verification

| | result |
|---|---|
| gate on `main` | **exit 0**, output unchanged (11 = triage 7 +
executor 4) |
| test suite | **5 pass** |
| new case against the **unfixed** gate | **fails** — `the
conditional-initializer shape must be counted` |

The regression case drives a real file through the scanned tree rather
than calling a helper, because the bug was in which nodes the scan
**visits**. A helper-level assertion would have been written against the
same wrong mental model that produced the gap — which is how the
inline-spelling hole in this same file survived its first draft.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved detection of sync-lane conversions in conditional
expressions, fallback logic, awaited and parenthesized values, and
object-literal relays.
  * Corrected matching for identifiers containing special characters.
* Updated validation results to include two additional findings that
were previously missed.

* **Tests**
* Added integration coverage for conditional initializers, chained
object-literal conversions, and special-character identifiers.
  * Ensured temporary test files are cleaned up automatically.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:20:06 -07:00

306 lines
14 KiB
JavaScript

// Exit-code coverage for the inert-sync-lane ratchet: a RISE fails, and an unrecorded DROP fails too.
/*
FNXC:WorkflowResolvedColumns 2026-07-31-23:45:
A RATCHET THAT ONLY TIGHTENS ON REQUEST DOES NOT RATCHET, and nothing tested this one's exit codes.
The rise path was always an error. The DROP path only warned and exited 0, which leaves the
allowance stale-high and the gate slack by exactly the size of the drop.
Measured, and the example is mine: #3065 replaced three
`to === parked.complete || to === parked.archived` guards with `parked.terminal.has(to)` and took the
count 20 -> 18. I did not re-record, nothing failed, and `main` then carried a baseline of 20 against
a real count of 18 — two free slots in the gate whose whole purpose is to stop this class growing.
Two new inert conversions would have passed.
Its sibling `check-lane-wiring.mjs` already exits 1 on an unrecorded drop and says why. These two
gates guard the same program and must not disagree about how seriously they take their own ledger.
Driven by RUNNING the script against a temp baseline, not by importing a helper: the exit code IS the
contract — a version that printed the right warning and still exited 0 would satisfy any assertion
about its output.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, writeFileSync, readFileSync, copyFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
const REPO_ROOT = resolve(import.meta.dirname, "../..");
const SCRIPT = join(REPO_ROOT, "scripts/check-inert-sync-lane-conversions.mjs");
const BASELINE = join(REPO_ROOT, "scripts/lib/inert-sync-lane-baseline.json");
function runGate() {
return spawnSync(process.execPath, [SCRIPT], { cwd: REPO_ROOT, encoding: "utf8" });
}
/** The gate's own view of the tree, so cases start from reality rather than the committed file. */
function liveCounts() {
const backup = join(mkdtempSync(join(tmpdir(), "inert-gate-")), "baseline.json");
copyFileSync(BASELINE, backup);
try {
spawnSync(process.execPath, [SCRIPT, "--update-baseline"], { cwd: REPO_ROOT, encoding: "utf8" });
return JSON.parse(readFileSync(BASELINE, "utf8"));
} finally {
copyFileSync(backup, BASELINE);
}
}
/**
* Swaps in a baseline derived from the LIVE counts, runs the gate, restores the real file.
* Deriving from live counts is what keeps these cases independent of whatever the committed
* baseline currently says.
*/
function withBaseline(mutate, fn) {
const backup = join(mkdtempSync(join(tmpdir(), "inert-gate-")), "baseline.json");
copyFileSync(BASELINE, backup);
try {
writeFileSync(BASELINE, JSON.stringify(mutate(liveCounts()), null, 2) + "\n");
return fn();
} finally {
copyFileSync(backup, BASELINE);
}
}
/*
FNXC:WorkflowResolvedColumns 2026-07-31-23:50:
DELIBERATELY NOT "the committed baseline matches the tree". That asserts a property of the REPO at a
moment, not of this gate, so it goes red whenever someone else lands an unrecorded change — which is
exactly what happened here: #3114 took `triage.ts` 7 -> 8 and my first version of this file failed for
a reason that has nothing to do with the code under test.
CI already runs the gate itself; a unit test that duplicates that check only adds a second, more
confusing way to learn the same thing. Every case below drives the gate against a baseline it
constructs, so they pass or fail on the gate's behaviour alone.
*/
test("a baseline that matches the tree exactly passes", () => {
const result = withBaseline((baseline) => baseline, runGate);
/* Constructed from the live counts rather than the committed file, so an unrelated unrecorded
change elsewhere in the repo cannot turn this red. */
assert.equal(result.status, 0, `gate should pass on a matching baseline:\n${result.stdout}${result.stderr}`);
});
test("an unrecorded DROP fails, so the allowance cannot stay stale-high", () => {
const result = withBaseline(
(baseline) => ({
...baseline,
total: baseline.total + 2,
byFile: Object.fromEntries(Object.entries(baseline.byFile).map(([f, n]) => [f, n + 2])),
}),
runGate,
);
assert.equal(result.status, 1, "a baseline higher than the real count must fail, not warn");
/* The message has to name the fix, or the failure is just noise to whoever hits it. */
assert.match(`${result.stdout}${result.stderr}`, /--update-baseline/);
});
test("a RISE still fails, and names the file it rose in", () => {
/*
FNXC:WorkflowResolvedColumns 2026-07-31-23:55:
The expected filename is DERIVED, not written down. My first version asserted `scheduler.ts`, which
#3128 then took to zero inert guards — so the case failed for a reason unrelated to the gate. The
same coupling mistake as asserting the committed baseline matches the tree, one line lower.
*/
const live = liveCounts();
const [someFile] = Object.keys(live.byFile);
const result = withBaseline(
(baseline) => ({
...baseline,
total: Math.max(0, baseline.total - 1),
byFile: Object.fromEntries(Object.entries(baseline.byFile).map(([f, n]) => [f, Math.max(0, n - 1)])),
}),
runGate,
);
assert.equal(result.status, 1, "more inert conversions than the baseline must fail");
assert.match(`${result.stdout}${result.stderr}`, new RegExp(someFile.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
});
/*
ANTI-VACUITY. The two cases above mutate the baseline, so they would both keep passing if the gate
stopped scanning any source at all and simply compared a number to itself. This asserts the scan
still finds the guards it is supposed to be counting.
*/
test("the gate is still actually scanning source, not just comparing numbers", () => {
const result = runGate();
assert.match(`${result.stdout}${result.stderr}`, /guard\(s\) consuming a sync-resolved lane/);
assert.ok(JSON.parse(readFileSync(BASELINE, "utf8")).total > 0, "baseline should not be empty");
});
/*
FNXC:WorkflowResolvedColumns 2026-07-31-23:59:
THE INITIALIZER IS NOT ALWAYS THE CALL — the shape that evaded this gate, now pinned.
`syncLaneLocals` registered a local only when its initializer WAS a call expression, so the
payload-first/sync-fallback form slipped past entirely:
const sync = payload ? undefined : localSync(store, id);
return column === sync?.hold; // inert, and counted as nothing
That matters more than the inline spelling the header already covers, because this shape is the one
authors are STEERED toward: falling back to the sync resolver is better than falling back to legacy
literals (it is best-effort under legacy SQLite; a literal can never be right on a renamed board), so
writing the guard well is what made it invisible. A ratchet that goes quiet exactly when the code
improves is worse than none.
Driven through a real file in the scanned tree rather than a unit call, because the bug was in which
nodes the scan VISITS — a helper-level assertion would have been written against the same wrong
mental model that produced the gap.
The two-hop form this case originally listed as an open gap — sync local -> object literal ->
comparison — is covered by the chained case below; the gap was closed in the same branch.
*/
test("counts a sync lane reached through a CONDITIONAL initializer, not just a direct call", () => {
const probe = join(REPO_ROOT, "packages/engine/src/__probe-inert-conditional.ts");
writeFileSync(probe, [
`import { resolveTaskWorkflowIrSync } from "@fusion/core";`,
`function localSync(store: unknown, id: string) { return resolveTaskWorkflowIrSync(store as never, id); }`,
`export function probe(store: unknown, id: string, column: string, payload: { hold?: string } | undefined): boolean {`,
` const sync = payload ? undefined : localSync(store, id);`,
` return column === sync?.hold;`,
`}`,
"",
].join("\n"));
try {
const counts = liveCounts();
assert.equal(
counts.byFile["packages/engine/src/__probe-inert-conditional.ts"],
1,
"the conditional-initializer shape must be counted; before this fix the scan reported nothing for it",
);
} finally {
rmSync(probe, { force: true });
}
});
/*
FNXC:WorkflowResolvedColumns 2026-07-31-23:59:
THE SECOND HOP, which the case above explicitly left open.
A sync local laundered through an object literal reached the guards while the scan saw nothing:
const sync = payload ? undefined : localSync(store, id);
const lanes = { hold: payload?.hold ?? sync?.hold ?? "todo" };
if (from !== lanes.hold) … // inert, counted as nothing
`executor.ts` is written exactly this way, and MEASURED it reported ZERO counted guards while the
sync resolver was still present and still answering with the default board whenever the payload is
absent. With the fix that file reports 2 — the two guards that genuinely remain after its dead
helper is deleted.
The chained case is in the probe on purpose: laundering can go `a -> b -> c`, and a single pass
would catch only the first link. That is the same one-pass mistake this file's earlier cases record,
made twice already in this scanner.
*/
test("counts a sync lane laundered through an object literal, including a chain", () => {
const probe = join(REPO_ROOT, "packages/engine/src/__probe-inert-twohop.ts");
writeFileSync(probe, [
`import { resolveTaskWorkflowIrSync } from "@fusion/core";`,
`function localSync(store: unknown, id: string) { return resolveTaskWorkflowIrSync(store as never, id); }`,
`export function probe(store: unknown, id: string, from: string, payload: { hold?: string } | undefined): boolean {`,
` const sync = payload ? undefined : localSync(store, id);`,
` const lanes = { hold: payload?.hold ?? sync?.hold ?? "todo" };`,
` const relayed = { hold: lanes.hold };`,
` return from !== relayed.hold;`,
`}`,
"",
].join("\n"));
try {
const counts = liveCounts();
assert.equal(
counts.byFile["packages/engine/src/__probe-inert-twohop.ts"],
1,
"a sync lane rebuilt into an object (and relayed again) must still be counted",
);
} finally {
rmSync(probe, { force: true });
}
});
/*
FNXC:LifecycleColumnCensus 2026-07-31-23:59 (review finding on #3169):
A local named `$sync` could not be matched at all. The propagation step built `\b${name}\b` from raw
source text, so `$` was read as an ANCHOR and the pattern never fired — a laundered guard silently
uncounted, which is the exact failure this scanner exists to prevent. `_sync` is wrong for the
related reason that `\b` does not assert an identifier boundary next to `_`.
`$` is a legal and common identifier character, so this is a shape the codebase can produce today.
*/
test("matches a laundered sync local whose name contains regex metacharacters", () => {
const probe = join(REPO_ROOT, "packages/engine/src/__probe-inert-dollar.ts");
writeFileSync(probe, [
`import { resolveTaskWorkflowIrSync } from "@fusion/core";`,
`function localSync(store: unknown, id: string) { return resolveTaskWorkflowIrSync(store as never, id); }`,
`export function probe(store: unknown, id: string, from: string, payload: { hold?: string } | undefined): boolean {`,
` const $sync = payload ? undefined : localSync(store, id);`,
` const lanes = { hold: payload?.hold ?? $sync?.hold ?? "todo" };`,
` return from !== lanes.hold;`,
`}`,
"",
].join("\n"));
try {
const counts = liveCounts();
assert.equal(
counts.byFile["packages/engine/src/__probe-inert-dollar.ts"],
1,
"a sync local named `$sync` must still be followed into the object it is laundered through",
);
} finally {
rmSync(probe, { force: true });
}
});
/*
FNXC:LifecycleColumnCensus 2026-07-31-23:59 (review finding on #3169 — OVER-approximation):
Propagation is PER PROPERTY, and these are the negative cases that prove it.
Marking a whole object sync-derived because its text mentioned a local counted guards that read a
sibling LITERAL (`{ hold: sync?.hold, review: "todo" }` made `lanes.review` inert) and matched a
local's NAME appearing as a KEY (`{ sync: "todo" }`) without anything reading it.
Over-counting is not the safe direction for this gate. It inflates the baseline — so the allowance
absorbs real inert conversions later — and it trains readers to skip the report, which this
program's learnings record as exactly how the next genuine finding gets missed.
*/
test("does NOT count a sibling literal in an object that also carries a sync lane", () => {
const probe = join(REPO_ROOT, "packages/engine/src/__probe-inert-mixed.ts");
writeFileSync(probe, [
`import { resolveTaskWorkflowIrSync } from "@fusion/core";`,
`function localSync(store: unknown, id: string) { return resolveTaskWorkflowIrSync(store as never, id); }`,
`export function probe(store: unknown, id: string, from: string, payload: { hold?: string } | undefined): boolean {`,
` const sync = payload ? undefined : localSync(store, id);`,
` const lanes = { hold: payload?.hold ?? sync?.hold ?? "todo", review: "in-review" };`,
` return from !== lanes.review;`,
`}`,
"",
].join("\n"));
try {
/* `lanes.hold` IS sync-derived, but nothing reads it here; the only guard reads `lanes.review`,
which is a literal. So the file must contribute nothing. */
assert.equal(liveCounts().byFile["packages/engine/src/__probe-inert-mixed.ts"] ?? 0, 0);
} finally {
rmSync(probe, { force: true });
}
});
test("does NOT count an object whose KEY merely shares a sync local's name", () => {
const probe = join(REPO_ROOT, "packages/engine/src/__probe-inert-keyname.ts");
writeFileSync(probe, [
`import { resolveTaskWorkflowIrSync } from "@fusion/core";`,
`function localSync(store: unknown, id: string) { return resolveTaskWorkflowIrSync(store as never, id); }`,
`export function probe(store: unknown, id: string, from: string, payload: { hold?: string } | undefined): boolean {`,
` const sync = payload ? undefined : localSync(store, id);`,
` void sync;`,
` const lanes = { sync: "todo", hold: "todo" };`,
` return from !== lanes.hold;`,
`}`,
"",
].join("\n"));
try {
assert.equal(liveCounts().byFile["packages/engine/src/__probe-inert-keyname.ts"] ?? 0, 0);
} finally {
rmSync(probe, { force: true });
}
});