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
19 changes: 19 additions & 0 deletions scripts/check-dev-prereqs.mjs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
#!/usr/bin/env node
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
//
// Note on gate derivation (#10542), because this file looks like it needs a
// population declaration and does not. It enumerates the workspace at runtime
// out of pnpm-workspace.yaml, which is the shape that card is about — but two
// facts make a declaration the wrong move here, and both were read off the
// source rather than assumed:
//
// - lint.yml runs `--self-test` ONLY, and says so in the step name. The
// EXISTENCE and FRESHNESS scans below are for `pnpm dev`, a local preflight
// no pull request schedules. A workspace-wide declaration would name this
// gate for every packages/ card in the tree for a scan CI never runs.
// - the one hint this file does contribute is honest and load-bearing: the
// FRESHNESS half really does read the spec package's dist, so a card there
// names this gate for a read it genuinely performs. It scores `silent` for
// cards elsewhere, and that is the correct verdict rather than a blind spot.
//
// So there is nothing to declare and no marker to carry: a `no-path-population`
// declaration would contradict the hint above, and dispatch-gates' self-test
// asserts exactly that pair cannot coexist.

/**
* check:dev-prereqs — confirm the workspace is BUILT, and that the one artifact
Expand Down
65 changes: 65 additions & 0 deletions scripts/check-pnpm-filter-targets.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -110,6 +110,44 @@ const JS_EXTENSIONS = ['.mjs', '.mts', '.cjs', '.js', '.ts'];
const HASH_COMMENT_EXTENSIONS = ['.sh', '.bash', '.yml', '.yaml'];
const SCANNED_EXTENSIONS = [...JS_EXTENSIONS, ...HASH_COMMENT_EXTENSIONS, '.json'];

/**
* The half of `scannedFiles`' population that `scripts/pm/dispatch-gates.mjs`
* could not see, written in the syntax that derivation CAN read (#10542).
*
* ── The defect this repairs ─────────────────────────────────────────────────
*
* `scannedFiles` walks THREE carriers, and the derivation saw only one of them.
* `.github/workflows/**` is already declared below in a spelling that carries a
* separator, so a workflow card names this gate. The scripts/ walk is spelled
* `join(root, 'scripts')` — a bare single-segment word, which `extractWatchHints`
* drops before `hintCovers` is ever consulted — so a card editing any script in
* the tree named this gate NOWHERE, including the cards most likely to add the
* very `--filter` spelling it exists to judge.
*
* ── Why `scripts/**` is honest here, with the measurement ───────────────────
*
* This is the `subtree` case: the walk descends the whole of scripts/ and every
* file carrying a scanned extension is judged. Measured on this tree, the
* declaration names 235 tracked files under scripts/ and this gate reads 228 of
* them — 97.0%. The 7 it skips are the non-code files the extension filter
* drops, not a subtree it never opens.
*
* ── Why the workspace manifests stay UNDECLARED ─────────────────────────────
*
* `scannedFiles` also reads every workspace member's `package.json` — a real
* read, and one this declaration deliberately does not reach. The instrument
* cannot express it: a root hint covers a whole SUBTREE, so declaring the
* workspace globs (the shape check-published-files.mjs legitimately takes,
* because it walks every file of every member) would name this gate for all
* 5263 tracked files under packages/, apps/ and examples/ in order to reach the
* ~78 manifests it actually opens — 1.5% precision, pasted into every card
* whose surface brushes a package. `hintCovers`' docblock prices a fabricated
* lead above a missing one, so the manifest half stays a documented blind spot
* rather than a wholesale claim. The refusal is pinned below, so a later author
* who adds the workspace globs meets an assertion instead of this paragraph.
*/
const ROOT_DIR_WATCH_HINTS = ['scripts/**'];

/**
* Blank the regions whose `--filter` spellings are prose rather than commands.
*
Expand DownExpand Up@@ -455,6 +493,33 @@ export function selfTest() {
),
);

// ---- the dispatch-gates declaration (#10542) -----------------------------
//
// Enforcement cannot hold any of these: ROOT_DIR_WATCH_HINTS 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 scripts/ card with this gate
// missing from the brief. Reconciled against the LIVE sweep rather than
// re-spelled, so a carrier that moves cannot leave the declaration behind.
const sweptRoots = new Set(
scannedFiles(root).map((f) => f.split('/')[0]).filter((s) => s.length > 0),
);
ok(
'the declared subtree is one this gate really walks',
ROOT_DIR_WATCH_HINTS.every((h) => sweptRoots.has(h.replace(/\/\*+$/, ''))),
);
ok(
'scripts/ is declared in the subtree spelling (hintCovers refuses the bare word, so a tidy-up back to a directory name re-opens the blind spot silently)',
ROOT_DIR_WATCH_HINTS.includes('scripts/**'),
);
ok(
'every declared entry carries a path separator',
ROOT_DIR_WATCH_HINTS.every((h) => h.includes('/')),
);
ok(
'the workspace globs stay UNDECLARED (they would name 5263 files to reach ~78 manifests — the measurement is in the docblock)',
!ROOT_DIR_WATCH_HINTS.some((h) => /^(packages|apps|examples)(\/|$)/.test(h)),
);

if (failures.length === 0) {
console.log(
`✓ check-pnpm-filter-targets --self-test: ${checked} assertions — a dead filter observed RED in all `
Expand Down
12 changes: 12 additions & 0 deletions scripts/check-prerelease-pin-watch.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,6 +114,18 @@ import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

// This gate's whole population is ONE repo-root file, and the derivation
// already reaches it — through the trigger key, not through a hint. Read from
// the source rather than assumed: `prerelease-pin-watch.yml` declares
// pnpm-workspace.yaml in its `paths:` filter, so a card that edits the pins
// gets this gate named with `CI trigger in prerelease-pin-watch.yml` as its
// provenance. What it carries no hint for is a bare repo-root FILENAME, which
// `hintCovers` refuses as too generic; the subtree-spelling escape would be a
// second, weaker route to a lead the workflow already delivers, and a second
// spelling of one population is what drifts. Nothing else in the tree moves
// this gate: it reads the `overrides` block and npm, and nothing more.
//
// dispatch-gates: no-path-population -- population is pnpm-workspace.yaml alone, and prerelease-pin-watch.yml already names it in `paths:` — the CI-trigger key reaches it, so a hint would be a second spelling of one population
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const DEFAULT_WORKSPACE = join(REPO_ROOT, 'pnpm-workspace.yaml');
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
Expand Down
104 changes: 102 additions & 2 deletions scripts/check-published-files.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -190,6 +190,71 @@ function matcher(pattern) {
return (rel) => rx.test(rel) || rel.startsWith(prefix);
}

/**
* This gate's population, written in the syntax `scripts/pm/dispatch-gates.mjs`
* can read (#10542).
*
* ── The defect this repairs ─────────────────────────────────────────────────
*
* The dispatch derivation names a gate for a card by scanning the gate's own
* module body for the path literals it operates on. This gate computes its
* population at RUNTIME instead — `workspaceGlobs()` below parses
* pnpm-workspace.yaml — so it spelled no workspace path literal anywhere, and
* the derivation therefore named it for NO card in the tree. Measured against
* the four layout specimens #10542 uses (a flat package, a nested package, an
* app manifest, an example manifest), `coveringKey` returned null for all four.
*
* That is a strictly worse failure than a gate with a stale hardcoded list: a
* runtime-computed population is invisible rather than wrong, so nothing in
* the output says the gate was ever considered.
*
* ── Why the glob spelling, and why it is not a second source of truth ───────
*
* `hintCovers` refuses a literal with no path separator (`packages`, `apps`,
* `examples`) as too generic — measured, at +139084 fabricated (gate, file)
* pairs if bare top-level words were admitted, because those words are path
* COMPONENTS in dozens of gates that never read the root. The sanctioned escape
* is for a gate to declare its own subtree in a spelling that carries a
* separator, which is what these entries do. They are the workspace globs
* VERBATIM, so the glob collapse reduces each back to the root it names and to
* nothing else.
*
* Nothing in this gate reads this array — `workspaceGlobs()` still parses the
* YAML, and remains the only thing the scan walks. The self-test reconciles the
* two in BOTH directions against that live parse, so a workspace root added to
* or removed from pnpm-workspace.yaml fails here rather than leaving this
* declaration describing a workspace that moved. A declaration that can drift
* from the scan is worse than none: it replaces a silent gate with a lying one.
*
* ── Why the WHOLE workspace is honest here, with the measurement ────────────
*
* This is the `subtree` case, not the `filtered` one check-examples-live-imports
* refuses. `walk()` below enumerates EVERY non-build file of every publishable
* member and MINIMAL judges each of them against FORBIDDEN, so the declaration
* names files this gate really opens. Measured on this tree: the declaration
* names 5263 tracked files and the gate judges 4803 of them — 91.3%. The 460 it
* does not judge are the members whose OWN manifests this gate read in order to
* exclude them (`private`), which is itself a read of the declared subtree, so
* a manifest card there is a true lead rather than a fabricated one.
*
* The contrast that sets the boundary is in check-published-readme-exports.mjs,
* which enumerates the same members and scores 2.8% — its refusal docblock
* carries that measurement and declines the same declaration.
*/
const ROOT_DIR_WATCH_HINTS = [
'packages/*',
'packages/adapters/*',
'packages/apps/*',
'packages/connectors/*',
'packages/drivers/*',
'packages/plugins/*',
'packages/qa/*',
'packages/services/*',
'packages/triggers/*',
'apps/*',
'examples/*',
];

/**
* The `packages:` globs from pnpm-workspace.yaml. Blank lines and comments are
* skipped rather than treated as the end of the list: stopping early would drop
Expand DownExpand Up@@ -330,14 +395,49 @@ function selfTest() {
failures.push(`FORBIDDEN("${path}") === ${actual}, expected ${expected}`);
}
}

// ── the dispatch-gates declaration (#10542) ───────────────────────────────
//
// Enforcement cannot hold any of these: ROOT_DIR_WATCH_HINTS 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 packaging card with this gate
// missing from the brief. Both directions are reconciled against the LIVE
// parse rather than re-spelled, so a workspace root that moves cannot leave
// the declaration describing the old one.
const declaredRoots = ROOT_DIR_WATCH_HINTS.map((h) => h.replace(/\/\*+$/, ''));
const liveGlobs = workspaceGlobs();
const liveRoots = liveGlobs.map((g) => g.replace(/\/\*+$/, ''));
const declarationCases = [
[
'every workspace glob this gate walks is declared (a root with no path separator is refused as too generic, so the population needs the glob spelling)',
liveRoots.every((r) => declaredRoots.includes(r)),
],
[
'and it declares no root the workspace does not have (a declaration that can drift from the scan is worse than none — it replaces a silent gate with a lying one)',
declaredRoots.every((r) => liveRoots.includes(r)),
],
[
'every declared entry carries a path separator (the whole point of the spelling: hintCovers refuses a bare top-level word, so a tidy-up back to directory names re-opens the blind spot silently)',
ROOT_DIR_WATCH_HINTS.every((h) => h.includes('/')),
],
[
'no declared entry is the bare root itself (provenance, never a lookup key)',
ROOT_DIR_WATCH_HINTS.every((h) => !liveRoots.includes(h)),
],
];
for (const [name, ok] of declarationCases) {
if (!ok) failures.push(`ROOT_DIR_WATCH_HINTS: ${name}`);
}

if (failures.length > 0) {
console.error(`✗ check:published-files --self-test — ${failures.length} failure(s)\n`);
for (const f of failures) console.error(` ${f}`);
process.exit(1);
}
console.log(
`✓ check:published-files --self-test — ${cases.length} pattern case(s) and ` +
`${forbidden.length} classification case(s).`,
`✓ check:published-files --self-test — ${cases.length} pattern case(s), ` +
`${forbidden.length} classification case(s) and ${declarationCases.length} ` +
`population-declaration case(s) over ${liveGlobs.length} live workspace glob(s).`,
);
}

Expand Down
68 changes: 68 additions & 0 deletions scripts/check-published-readme-exports.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -300,6 +300,60 @@ const WORKSPACE_FILE = 'pnpm-workspace.yaml';
const SELF = 'scripts/check-published-readme-exports.mjs';
const BASELINE_REL = 'scripts/published-readme-exports.baseline.json';

/**
* ⛔ THIS GATE DECLARES NO WORKSPACE POPULATION, DELIBERATELY (#10542).
*
* There is no `ROOT_DIR_WATCH_HINTS` array below, and adding one would be a
* regression rather than a fix. This docblock is that decision, with the
* measurement that made it, because the card that dispatched the work assumed
* the opposite and the source says otherwise.
*
* ── What #10542 expected ────────────────────────────────────────────────────
*
* `scripts/pm/dispatch-gates.mjs` names a gate for a card by scanning the
* gate's module body for path literals. This gate computes its population at
* RUNTIME (it parses pnpm-workspace.yaml, exactly as check-published-files.mjs
* does), so it names no workspace path and the derivation named it for no card.
* #10542 grouped it with check-published-files.mjs and said of the pair that
* they "really do read every published package's manifest, so their population
* genuinely is the workspace and the #10114 escape applies directly".
*
* ── What the source actually does, and the measurement that decides it ──────
*
* The two gates enumerate the same members and then diverge completely.
* check-published-files.mjs JUDGES every non-build file it walks — MINIMAL
* tests each one against FORBIDDEN — so a declaration of the workspace globs
* names files it really opens: 4803 of the 5263 tracked files the declaration
* would name, 91.3%.
*
* This gate walks the same trees and then narrows twice, to published markdown
* and to the manifests: `publishedMarkdown` keeps only `.md` paths the `files`
* whitelist admits, and the rest of the read surface is `<member>/package.json`
* plus the built type entry under `<member>/dist/`. Measured on the same tree,
* the same declaration would name 5263 tracked files to reach 149 — 2.8%.
*
* That is the `filtered` shape check-examples-live-imports.mjs refuses by name
* at 1.6%, and `hintCovers`' docblock prices a fabricated lead above a missing
* one: a `packages/**`-class declaration here would paste this gate into every
* dispatch prompt whose surface brushes any package source file, and 97 of
* every 100 of those leads would name a gate that never opens the file. The
* "22 leads is the same as none" failure, bought at a worse ratio than the
* wholesale admission the derivation already refuses.
*
* ── What this gate is left with, and why that is the honest state ───────────
*
* A card editing this script names it by identity. A card editing a published
* README or a manifest names it by nothing — a real blind spot, and one the
* instrument cannot close: a root hint covers a whole SUBTREE, and there is no
* spelling for "the README of each workspace member". Recorded here rather than
* repaired falsely; if the derivation ever grows a narrower key than a subtree
* root, this is the gate to revisit first.
*
* The refusal is pinned in `--self-test` rather than left in this paragraph, so
* a later author who adds the workspace globs meets an assertion.
*/
const DECLARED_WORKSPACE_POPULATION = [];

// ⛔ SHRINK-ONLY. The authority token the #8435 convention requires; the
// baseline is a maintainer's registry, never an author's escape hatch.
const RATCHET_AUTHORITY_MARKER = '⛔ MAINTAINER-ONLY';
Expand DownExpand Up@@ -3400,6 +3454,20 @@ function selfTest() {
);
}

// The dispatch-gates population refusal (#10542). Enforcement cannot hold
// this: the declaration is read by another tool entirely, so a wrongly-added
// one runs green here forever and pays itself out as a fabricated lead in
// every packages/** dispatch prompt. See DECLARED_WORKSPACE_POPULATION's
// docblock for the 2.8% measurement that refused it.
if (DECLARED_WORKSPACE_POPULATION.length !== 0) {
failures.push(
'this gate must declare NO workspace population: it narrows the walk to published ' +
'markdown and manifests, so the workspace globs would name 5263 tracked files to ' +
'reach 149 (2.8%). check-published-files.mjs walks the same members and judges every ' +
'file it finds (91.3%), which is why the declaration is honest THERE and not here.',
);
}

if (failures.length > 0) {
console.error(`✗ check:published-readme-exports --self-test — ${failures.length} failure(s)\n`);
for (const f of failures) console.error(` ${f}`);
Expand Down
Loading
Loading