fix(gate): the inert-seam ratchet never scanned plugins/, where real lane logic lives (#3002)
#3000 showed this gate never scanned `scripts/`. I went auditing my own instrument after that, and the roots have a **second** hole: the walk was rooted at `packages/` alone, so every lane parameter a plugin declares or calls sat outside the ratchet entirely. ## Measured with a control | probe | before | after | |---|---|---| | unwired seam under `packages/` | caught | caught | | **identical** seam under `plugins/` | **missed** | caught | | clean tree | exit 0 | exit 0 | ## The sibling gate already knew `check-lane-wiring` lists `plugins` in its roots, and its header records the incident that put it there: an unwired `completeColumnsByTaskId` sat on `main` unreported because the glasses plugin wasn't scanned. This gate re-opened the same hole rather than inheriting the lesson. Two scope holes in one instrument is the actual finding — **the roots deserve the same scrutiny as the matcher, and until now they had none.** Every blind spot found in this gate so far has been in the matcher; nobody, me included, thought to probe what it walks. ## Newly visible — audited, not blind-baselined `plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx` calls `isTaskStuck()` without the resolved flags, while **six of the seven** other call sites supply them. That's exactly the partial-supply shape this gate exists to catch, hidden purely by scope. It's exempted rather than wired, and the reason is the interesting part. The plugin has **no lane-trait source anywhere**: it's mounted as a dashboard view through `PluginDashboardViewContext`, and `DependencyGraph` receives `tasks: Task[]` and nothing else. Passing the argument here would pass `undefined` — an unsupplied optional parameter, which the learnings doc's first failure shape calls strictly worse than the literal it replaces, because it reads as converted and answers legacy forever. Correct supply needs the plugin **view context** to carry per-task flags: a published-API change. That's the same "needs a data change" category as the existing `TaskDetailModal` entry, not the "awkward means wire it" case the exemption rule refuses. I checked that distinction against my own rule before taking the exemption, because the rule exists to stop exactly this kind of convenient reading. Filed for the plugin-API owner rather than bodged here. ## Measured - seam population **22 → 23** with plugins in scope - gate's own suite: **18/18 green** - lint and the FNXC gate green Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,7 +36,25 @@ import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
|
||||
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const PACKAGES = join(REPO, "packages");
|
||||
/*
|
||||
FNXC:InertFlagSeams 2026-07-30-23:30:
|
||||
`plugins/` IS SCANNED, because plugins hold real lane logic and this gate could not see any of it.
|
||||
|
||||
The walk was rooted at `packages/` alone. Measured with a control: the identical unwired seam is
|
||||
caught under `packages/` and MISSED under `plugins/` — so every lane parameter a plugin declares or
|
||||
calls was outside the ratchet entirely.
|
||||
|
||||
Not hypothetical. `plugins/fusion-plugin-even-realities-glasses` resolves workflow IRs and filters by
|
||||
column in `src/cards.ts` and `src/routes/board-routes.ts`. The sibling `check-lane-wiring` already
|
||||
lists `plugins` in its roots, and its header records the incident that put it there: an unwired
|
||||
`completeColumnsByTaskId` sat on `main` unreported because that plugin was not scanned. This gate
|
||||
re-opened the same hole rather than learning from it.
|
||||
|
||||
Found while auditing my own gates after #3000 showed this one never scanned `scripts/` either. Two
|
||||
scope holes in one instrument is a pattern: the roots deserve the same scrutiny as the matcher, and
|
||||
they had none.
|
||||
*/
|
||||
const SCAN_ROOTS = [join(REPO, "packages"), join(REPO, "plugins")];
|
||||
const SKIP_DIRS = new Set(["node_modules", "dist", "__tests__", "__mocks__", "e2e", ".gate-bundle", "coverage"]);
|
||||
const TRAILING_FLAG_PARAM = /([Cc]olumnFlags|[Ll]ifecycleColumns|[Rr]eviewColumns|[Tt]erminalColumns|[Pp]lannerLanes)$/;
|
||||
|
||||
@@ -121,6 +139,17 @@ An omission earns an entry only when supplying the argument would be WRONG, not
|
||||
"Awkward" means wire it; the check exists to make that the cheaper path.
|
||||
*/
|
||||
const ALLOWED_OMISSIONS = new Map([
|
||||
[
|
||||
"plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx::isTaskStuck",
|
||||
{ count: 1, reason: "Surfaced the moment this gate started scanning `plugins/` (2026-07-31). The plugin has NO "
|
||||
+ "lane-trait source anywhere: it is mounted as a dashboard view through `PluginDashboardViewContext`, "
|
||||
+ "and `DependencyGraph` receives `tasks: Task[]` and nothing else. Supplying the argument here would "
|
||||
+ "pass `undefined` — an unsupplied optional parameter, which this document's first failure shape says "
|
||||
+ "is strictly worse than the literal it replaces, because it reads as converted and answers legacy "
|
||||
+ "forever. Correct supply needs the plugin VIEW CONTEXT to carry per-task flags: a published-API "
|
||||
+ "change, not local wiring, which is the same 'needs a data change' category as the entry below. "
|
||||
+ "Filed for the plugin-API owner rather than bodged here." },
|
||||
],
|
||||
[
|
||||
"packages/dashboard/app/components/TaskDetailModal.tsx::isNearDuplicateCanonicalInactive",
|
||||
{ count: 1, reason: "The flags in scope describe the MODAL'S task; the canonical is a different task on a column this "
|
||||
@@ -177,7 +206,7 @@ missing suppliers in ListView and Column — review did.
|
||||
Declarations still use the prefilter: a file DECLARING a flags parameter necessarily contains the
|
||||
name, so that half is safe and keeps the scan quick.
|
||||
*/
|
||||
for (const file of walkAll(PACKAGES)) {
|
||||
for (const file of SCAN_ROOTS.flatMap((root) => [...walkAll(root)])) {
|
||||
const source = readFileSync(file, "utf8");
|
||||
const fileIsTest = isTestFile(relative(REPO, file).split("\\").join("/"));
|
||||
const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
||||
|
||||
Reference in New Issue
Block a user