Context
PR #16678 (#16628) fixed one instance of a defect class: the config-template-ssot lint scans ai/**/*.mjs + test/**/*.mjs, but its workflow's path filters watched neither, so an introducing PR could land a violation ungated and turn the NEXT unrelated run red (late, misattributed enforcement — "guard present, correct, and never run"). That fix made scanned ⊆ watched TRUE for one workflow — but only as YAML text. Vega's Depth-Floor on the PR review names the recurrence vector precisely: the invariant is documented, not enforced — if a lint gains a third scan root, nothing makes its filter follow, and the failure is silent in exactly the way #16628 documents. This ticket is that successor, generalized. Live latest-open sweep: latest 20 checked 2026-08-08 ~12:07Z, no equivalent; A2A herd-window sweep clean.
The Problem
Three path-filtered lint workflows exist (config-template-ssot-lint.yml, mcp-test-location-lint.yml, retry-bound-classification-lint.yml), each running a lint under ai/scripts/lint/ that declares its own scan surface in code. The workflow filter and the lint's scan roots are two hand-maintained copies of one fact. The class ships silently: nothing red exists at the moment of divergence.
The Architectural Reality
- Scan roots live as module constants (e.g.
SCAN_ROOT_REL = 'ai', TEST_SCAN_ROOT_REL = 'test' in lint-config-template-ssot.mjs:61-62); siblings declare equivalents (verify each at implementation — the constants may need exporting where module-private).
- The enforcement home must itself be un-filtered: the Playwright unit lane runs on every PR (empirically: it ran on
#16678's yml+md-only diff), so a unit spec is the always-on tooth; a fourth path-filtered workflow would re-import the defect one level up.
- Sibling precedent for placement:
test/playwright/unit/ai/scripts/lint/lintConfigTemplateSsot.spec.mjs (structural fast-path match; ai:structure-map cites ai/scripts/lint — 14 files — as the owning lint folder).
The Fix
One unit spec (e.g. test/playwright/unit/ai/scripts/lint/lintWorkflowScanRootParity.spec.mjs) that, per lint workflow: imports/reads the lint's declared scan roots, parses the workflow YAML's pull_request.paths + push.paths, and asserts every scan root is covered by at least one watch pattern on BOTH triggers. Export scan-root constants from the lint scripts where needed (pure constant exports, no behavior change). Building the guard IS the sibling audit: any gap it reds on arrival gets its filter fixed in the same PR.
Acceptance Criteria
Out of Scope
- New lint rules; the lints' own semantics.
- Non-lint workflows' path filters.
Decision Record impact
none.
Related
#16628 / PR #16678 (the shipped instance + Vega's Depth-Floor provenance), #12451 (lint origin).
Origin Session ID: e7da18d8-1563-4ab8-9b88-75afc13aa74e
Retrieval Hint: query_raw_memories("scanned watched invariant lint workflow path filter parity guard")
Context
PR
#16678(#16628) fixed one instance of a defect class: the config-template-ssot lint scansai/**/*.mjs+test/**/*.mjs, but its workflow's path filters watched neither, so an introducing PR could land a violation ungated and turn the NEXT unrelated run red (late, misattributed enforcement — "guard present, correct, and never run"). That fix made scanned ⊆ watched TRUE for one workflow — but only as YAML text. Vega's Depth-Floor on the PR review names the recurrence vector precisely: the invariant is documented, not enforced — if a lint gains a third scan root, nothing makes its filter follow, and the failure is silent in exactly the way#16628documents. This ticket is that successor, generalized. Live latest-open sweep: latest 20 checked 2026-08-08 ~12:07Z, no equivalent; A2A herd-window sweep clean.The Problem
Three path-filtered lint workflows exist (
config-template-ssot-lint.yml,mcp-test-location-lint.yml,retry-bound-classification-lint.yml), each running a lint underai/scripts/lint/that declares its own scan surface in code. The workflow filter and the lint's scan roots are two hand-maintained copies of one fact. The class ships silently: nothing red exists at the moment of divergence.The Architectural Reality
SCAN_ROOT_REL = 'ai',TEST_SCAN_ROOT_REL = 'test'inlint-config-template-ssot.mjs:61-62); siblings declare equivalents (verify each at implementation — the constants may need exporting where module-private).#16678's yml+md-only diff), so a unit spec is the always-on tooth; a fourth path-filtered workflow would re-import the defect one level up.test/playwright/unit/ai/scripts/lint/lintConfigTemplateSsot.spec.mjs(structural fast-path match;ai:structure-mapcitesai/scripts/lint— 14 files — as the owning lint folder).The Fix
One unit spec (e.g.
test/playwright/unit/ai/scripts/lint/lintWorkflowScanRootParity.spec.mjs) that, per lint workflow: imports/reads the lint's declared scan roots, parses the workflow YAML'spull_request.paths+push.paths, and asserts every scan root is covered by at least one watch pattern on BOTH triggers. Export scan-root constants from the lint scripts where needed (pure constant exports, no behavior change). Building the guard IS the sibling audit: any gap it reds on arrival gets its filter fixed in the same PR.Acceptance Criteria
ai/scripts/lint/lint-*.mjs+ workflow pair either self-registers or fails the spec's completeness walk (prefer deriving the workflow list from.github/workflows/*-lint.ymlrather than a hardcoded trio).Out of Scope
Decision Record impact
none.
Related
#16628/ PR#16678(the shipped instance + Vega's Depth-Floor provenance),#12451(lint origin).Origin Session ID: e7da18d8-1563-4ab8-9b88-75afc13aa74e
Retrieval Hint:
query_raw_memories("scanned watched invariant lint workflow path filter parity guard")