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
22 changes: 22 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -377,6 +377,28 @@ jobs:
- name: PM dispatch-gates self-test
run: pnpm check:pm-dispatch-gates

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
# tree. A gate whose population is spelled as a bare single-segment word
# builds no watch hint at all — not a dead hint, not a silent verdict,
# nothing — so it is unnameable by any dispatch brief and leaves no residue
# saying so. The worklist enumerates that species and carries one recorded
# verdict per row; this step fails when a verdict outlives its row (the
# shrink) or when a new gate joins the species unjudged.
#
# ⛔ It runs the SELF-TEST only, never the sweep as a verdict: recognising
# this species needs a heuristic over constant NAMES, and #10705 refused to
# put one on the path that derives every PR's gate list. Nothing in
# `dispatch-gates.mjs` reads the worklist, and no verdict in it reaches a
# dispatch prompt.
#
# Unconditional, for the same reason as the step above: a self-test that
# can be skipped is the gap moving rather than closing. Reads the workflow
# tree and every gate source once; ~0.5s.
- name: PM bare-root worklist self-test
run: node scripts/pm/bare-root-worklist.mjs --self-test

# Part-of/closing-keyword guard self-test (#8476). The guard itself is a
# PR-scoped blocking check in its own workflow — it needs a pull request
# body to judge, which this job does not have — so what runs HERE is its
Expand Down
95 changes: 94 additions & 1 deletion scripts/check-driver-conformance.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -119,6 +119,65 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const DRIVERS_DIR = join(ROOT, 'packages', 'drivers');
const CASE_SETS_DIR = join(ROOT, 'packages', 'spec', 'src', 'data');

/**
* The half of this gate's two scan roots that `scripts/pm/dispatch-gates.mjs`
* cannot see, written in the syntax that derivation CAN read. Provenance ONLY:
* nothing in this gate reads this array, and both walks behave exactly as they
* did without it.
*
* ## The defect this repairs (#10840's worklist, the #10114 / #10314 idiom)
*
* The dispatch derivation scans a gate's module body for the path literals it
* operates on, and "looks like a path" there means "carries a separator". Both
* roots above are assembled by `join()` from separate single-segment arguments,
* so the only literals this file offers the extractor are the bare words
* `packages`, `drivers`, `spec`, `src` and `data` — every one of which
* `extractWatchHints` drops BEFORE `hintCovers` is ever consulted. They are not
* dead hints; they are nothing at all, which is why no residue line ever named
* them. Measured on this tree, a derivation for
* `packages/drivers/postgres/src/index.ts` named six gates and this was not one
* of them — a driver card, and the driver-conformance matrix is invisible to it.
*
* ## Why `packages/drivers/**` and NOT `packages/**`
*
* `hintCovers` refuses a bare single-segment literal as too generic BY DESIGN,
* and the refusal is measured: accepting bare top-level directory words was
* priced at +139084 fabricated (gate, file) pairs, precisely because `packages`
* is a path COMPONENT in dozens of gates that never read that root — this gate
* among them. The literal `'packages'` here is such a component and nothing
* more. Declaring the top-level root would be the fabrication one level up:
*
* packages/** 259 files this gate reads, of 4903 tracked — 5.3%,
* pasted into every packages/** prompt in the repo.
* packages/drivers/** 259 of 291 — 89%, over a subtree 17x smaller.
*
* The remaining 32 files under `packages/drivers/` are the per-package
* manifests, licences and changelogs; adding or removing a driver package moves
* `discoverDrivers` through exactly those, so 89% is a floor rather than an
* estimate.
*
* ## Why CASE_SETS_DIR is deliberately NOT declared
*
* The instrument can only express a SUBTREE — `collapseHint` strips globs, so
* `packages/spec/src/data/*-conformance.ts` collapses to a path that names
* nothing, and the only spellable claim is the whole directory. That directory
* holds 143 tracked files of which this gate reads 7 (4.9%): a subtree
* declaration there would name this gate for every Zod schema and unit test
* beside the case sets. A missing lead costs one card one CI round; a
* fabricated one is pasted into every prompt whose surface brushes it. So the
* case-set side stays undeclared, and the refusal is pinned in the self-test
* rather than left in this paragraph.
*
* ## Provenance, never a lookup key
*
* `assertRootsResolvable([DRIVERS_DIR, CASE_SETS_DIR])` stats both roots and
* throws DeadRootError when one is missing, so the glob form appearing in either
* constant would turn this gate red on a directory that never existed. The
* self-test pins that apart, and derives both halves of the coupling from
* DRIVERS_DIR rather than re-spelling it.
*/
const ROOT_DIR_WATCH_HINTS = ['packages/drivers/**'];

// ── The case-sets ───────────────────────────────────────────────────────────
//
// `marker` is the export a suite cannot run the case-set without naming, so its
Expand DownExpand Up@@ -1074,6 +1133,39 @@ function selfTest() {
}
}

// -- The dispatch-gates declaration (#10840) --------------------------------
//
// Enforcement cannot hold any of these: the declaration is read by another
// tool entirely, so a wrong or stale one runs green here forever and pays
// itself out as a dev dispatched on a driver card with this gate missing from
// the brief. Both halves are DERIVED from DRIVERS_DIR rather than re-spelled,
// so moving the driver tree cannot leave the declaration describing the old
// location -- the failure mode a hand-kept copy has.
const driversRel = DRIVERS_DIR.slice(ROOT.length + 1);
expect('the declaration names the driver subtree this gate actually walks, derived from '
+ 'DRIVERS_DIR rather than re-spelled beside it',
ROOT_DIR_WATCH_HINTS.includes(`${driversRel}/**`));
expect('and declares nothing else (a declaration that can drift from the scan is worse than '
+ 'none -- it replaces a silent gate with a lying one)',
ROOT_DIR_WATCH_HINTS.every((h) => h.replace(/\/\*+$/, '') === driversRel));
// The non-vacuity half, and the reason this file needs a declaration at all:
// every literal it offers the extractor is a bare `join()` argument, so the
// real population reaches the derivation as nothing whatever.
expect('the population really is unseeable as spelled -- DRIVERS_DIR is built from bare '
+ 'single-segment join() arguments, none of which the extractor keeps',
driversRel.includes('/') && !ROOT_DIR_WATCH_HINTS.includes(driversRel));
// The REFUSALS, pinned. Neither is an oversight to be tidied up later.
expect('the bare top-level root is deliberately NOT declared -- `packages` is a path COMPONENT '
+ 'here, and a subtree hint on it would name this gate for 4903 files to reach 259',
!ROOT_DIR_WATCH_HINTS.some((h) => h.replace(/\/\*+$/, '') === 'packages'));
expect('CASE_SETS_DIR is deliberately NOT declared -- its population is a FILENAME pattern '
+ '(*-conformance.ts, 7 of 143 files) and a subtree hint cannot express one',
!ROOT_DIR_WATCH_HINTS.some((h) => CASE_SETS_DIR.slice(ROOT.length + 1).startsWith(h.replace(/\/\*+$/, ''))));
// Provenance, never a lookup key: assertRootsResolvable stats both roots, so
// the glob form appearing in either constant is a hard red on a dead root.
expect('the declared form is NOT a scan-root value',
!ROOT_DIR_WATCH_HINTS.some((h) => h === driversRel || h === DRIVERS_DIR));

if (failures.length) {
for (const f of failures) console.error(` x self-test: ${f}`);
console.error(`\ncheck-driver-conformance --self-test: ${failures.length} failure(s).\n`);
Expand All@@ -1083,7 +1175,8 @@ function selfTest() {
'OK self-test: detects driven / unused / re-declared fixtures, discovers both axes, accounts for '
+ 'every entry under DRIVERS_DIR (a dropped or manifestless row is red, not a smaller matrix), '
+ 'holds the dead-root hard error (red when a scan root is renamed, green when restored), and '
+ 'keeps CONSUMED\'s ledger offer marked maintainer-only (#8435).',
+ 'keeps CONSUMED\'s ledger offer marked maintainer-only (#8435). It also declares the driver '
+ 'subtree dispatch-gates derives from, refusing the bare root and the case-set dir by name.',
);
}

Expand Down
92 changes: 91 additions & 1 deletion scripts/check-skill-compatibility-version.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,6 +75,54 @@ const SKILLS_DIR = 'skills';
// guarantees that for the next package someone cites.
const PACKAGE_ROOTS = ['packages', 'apps', 'examples'];

/**
* The half of this gate's two populations that `scripts/pm/dispatch-gates.mjs`
* cannot see, written in the syntax that derivation CAN read. Provenance ONLY:
* nothing in this gate reads this array, and both walks behave exactly as they
* did without it.
*
* ## The defect this repairs (#10840's worklist, the #10114 / #10314 idiom)
*
* The dispatch derivation scans a gate's module body for path-ish string
* literals, and "path-ish" there means "carries a separator". Both `SKILLS_DIR`
* and every entry of `PACKAGE_ROOTS` are bare single-segment words, so
* `extractWatchHints` drops all four BEFORE `hintCovers` is ever consulted —
* they are not dead hints, they are nothing at all, which is why no residue line
* ever named them. Measured on this tree, a derivation for
* `skills/objectstack-platform/SKILL.md` named four gates and this was not one
* of them, though a `compatibility:` pin in that file is the whole subject here.
*
* ## Why `skills/**` is declared and the three PACKAGE_ROOTS are NOT
*
* The instrument can only express a SUBTREE: `hintCovers` collapses globs, so a
* declared hint names every tracked file beneath it and there is no way to spell
* "the package manifests under this root". That makes the two sides of this gate
* completely different trades, and both were measured on this tree:
*
* skills/** 49 of 50 tracked files are skill directories this gate
* reads — 98% precision over a 50-file subtree.
* packages/** 73 package.json files out of 4903 tracked files — 1.5%,
* pasted into every packages/** dispatch prompt in the repo.
* apps/** 1 of 35 (2.9%) · examples/** 4 of 238 (1.7%).
*
* The three package roots are therefore the +139084 fabrication one level up —
* the very measurement in `hintCovers`' docblock, which prices accepting bare
* top-level directory words at that many fabricated (gate, file) pairs because
* `packages`, `apps` and `examples` are path COMPONENTS in dozens of gates that
* never read those roots. A missing lead costs one card one CI round; a
* fabricated one is pasted into every prompt whose surface brushes it and the
* dev cannot tell it from a real one. So the manifest side stays undeclared,
* deliberately, and the refusal is pinned in the self-test rather than left in
* this paragraph — a later author who adds `packages/**` meets an assertion.
*
* ## Provenance, never a lookup key
*
* `readSkillFiles` joins SKILLS_DIR and then `statSync`s each entry, so the glob
* form appearing in that constant would throw on a directory that does not
* exist. The self-test pins that apart too.
*/
const ROOT_DIR_WATCH_HINTS = ['skills/**'];

/**
* A `compatibility:` pin, e.g. `@objectstack/spec 17.x`.
*
Expand DownExpand Up@@ -658,11 +706,53 @@ function selfTest() {
console.log(` ✓ real-tree discovery: ${disc.files.length} SKILL.md file(s), ${disc.problems.length} layout problem(s)`);
}

// ── The dispatch-gates declaration (#10840) ───────────────────────────────
//
// Enforcement cannot hold any of these: the declaration is read by another
// tool entirely, so a wrong or stale one runs green here forever and pays
// itself out as a dev dispatched on a skills card with this gate missing from
// the brief. Both halves are DERIVED from the population constants rather than
// re-spelled, so renaming or re-scoping a root cannot leave the declaration
// describing the old population.
//
// The predicate is `hintCovers`' own refusal (dispatch-gates.mjs: a hint with
// no `/` and no leading `.` is rejected as too generic), spelled here rather
// than imported because this gate must not take a dependency on the PM tool to
// state what it reads. If that refusal ever changes, this line moves with it.
const unseeable = (r) => !r.includes('/') && !r.startsWith('.');
const declFailures = [];
const decl = (label, ok) => { if (!ok) declFailures.push(label); };
decl('SKILLS_DIR is invisible to the derivation, which is why it needs a declaration at all',
unseeable(SKILLS_DIR));
decl('and it declares exactly that root, in the subtree spelling',
ROOT_DIR_WATCH_HINTS.includes(`${SKILLS_DIR}/**`));
decl('and declares no root this gate does not read (a declaration that can drift from the scan '
+ 'is worse than none — it replaces a silent gate with a lying one)',
ROOT_DIR_WATCH_HINTS.every((h) => h.replace(/\/\*+$/, '') === SKILLS_DIR));
// The REFUSAL, pinned. The manifest walk really does read all three roots, so
// this is not an oversight to be tidied up later — it is a priced decision:
// 73 of 4903 files under packages/ (1.5%), 1 of 35 under apps/, 4 of 238 under
// examples/. A subtree hint cannot say "the manifests"; it can only say "all
// of it", and all of it is false here.
decl('the three PACKAGE_ROOTS are deliberately NOT declared — a subtree hint would name this '
+ 'gate for every file under them to reach the package.json files, which is the fabricated '
+ 'lead hintCovers is measured against',
PACKAGE_ROOTS.every((r) => !ROOT_DIR_WATCH_HINTS.includes(`${r}/**`)));
decl('the non-vacuity half of that refusal: those roots really are invisible, so the refusal is '
+ 'a live choice rather than a description of something the extractor already handles',
PACKAGE_ROOTS.every(unseeable));
// Provenance, never a lookup key: readSkillFiles joins SKILLS_DIR and statSyncs
// each entry, so the glob form appearing there would throw on a missing dir.
decl('the declared form is NOT the SKILLS_DIR value itself',
!ROOT_DIR_WATCH_HINTS.includes(SKILLS_DIR));
for (const f of declFailures) console.error(` ✗ dispatch-gates declaration: ${f}`);
failed += declFailures.length;

if (failed > 0) {
console.error(`\n✗ check-skill-compatibility-version self-test failed (${failed} case(s)).`);
process.exit(1);
}
console.log(`\n✓ check-skill-compatibility-version self-test: ${cases.length} cases pass.`);
console.log(`\n✓ check-skill-compatibility-version self-test: ${cases.length} cases pass, plus 7 dispatch-gates declaration cases.`);
}

// ---------------------------------------------------------------------------
Expand Down
Loading
Loading