Skip to content

Commit 53fe481

Browse files
claude[bot]claude
andauthored
fix(devx): declare the objectui-pin-citations population so a .objectui-sha change set derives it (#15698)
* fix(devx): declare the objectui-pin-citations population so a `.objectui-sha` change set derives it The gate reads the root pin file and walks `packages/spec/src`, but every path it opens is computed from `REPO`/`SPEC`, so `extractWatchHints` found nothing and dispatch-gates had no population for it at all. A change set consisting only of the pin file — exactly the class this gate exists for — derived zero commands naming it, and a pin bump ran 47 derived commands green before CI redded here on 8 stale citations. Declares both halves in the root-file idiom (`check-doc-anchors.mjs`, `git-merge-regen.mjs`) and pins them against what the gate really reads with the self-consistency assertion shape from `check-doc-anchors.mjs`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * docs(devx): cite the commit the before/after derivation was actually measured on The declaration docblock quoted `53edffe09`, the branch's original base. The before/after specimen was re-measured on `7bf96cfd0` (current origin/main) in a detached worktree, so the docblock now cites that commit and records both legs — 12 commands / 5 matched by path without the declaration, 13 / 6 with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ab8cb6 commit 53fe481

1 file changed

Lines changed: 111 additions & 1 deletion

File tree

packages/spec/scripts/check-objectui-pin-citations.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,69 @@ const REPO = path.resolve(SPEC, '../..');
127127
const SRC = path.join(SPEC, 'src');
128128
const PIN_FILE = path.join(REPO, '.objectui-sha');
129129

130+
/**
131+
* ## The dispatch-gates declaration — the root-FILE idiom
132+
*
133+
* `scripts/pm/dispatch-gates.mjs` derives the gate families a card must run
134+
* from the paths it touches, and it learns a gate's population by scanning the
135+
* gate's own SOURCE TEXT for path-shaped literals (`extractWatchHints`). Every
136+
* path this gate reads is COMPUTED — `PIN_FILE` from `REPO`, `SRC` from
137+
* `SPEC` — so the extractor found nothing here and this gate had no population
138+
* at all. Measured on `7bf96cfd0`, before this declaration existed:
139+
*
140+
* node scripts/pm/dispatch-gates.mjs --commands .objectui-sha --repo objectstack-ai/objectstack
141+
* -> 12 commands (5 matched by path), 0 of them naming check:objectui-pin-citations
142+
*
143+
* and on the same specimen with it, 13 commands (6 matched by path), the new
144+
* one being `pnpm --filter @objectstack/spec run check:objectui-pin-citations`.
145+
*
146+
* A change set consisting only of the pin file is EXACTLY the class this gate
147+
* exists for — a pin bump is the one event that invalidates an asserting
148+
* citation — and it was the class that never derived it. The measured cost was
149+
* a full CI cycle: a pin bump ran 47 derived commands green and then redded
150+
* here on 8 stale citations, discovered a cycle late.
151+
*
152+
* ⛔ The declaration is the POPULATION, never a hand list of the citing files.
153+
* They are discovered by regex on purpose (`sourceFiles(SRC)` + `scanFile`), so
154+
* a list of today's citers is stale the moment a record is written or moved —
155+
* the same reason `check:merge-driver` refuses a hand list of generator names.
156+
*
157+
* A bare `.objectui-sha` carries no path separator and reaches the extractor as
158+
* a bare word; the trailing `/` + `**` suffix is the sanctioned escape for a
159+
* repo-ROOT file — `collapseHint` reduces it back to the literal filename, so
160+
* it claims the root file and no same-named file inside a directory. Nothing in
161+
* this tree lives under `.objectui-sha/`, so it claims no directory either.
162+
* Measured through `hintCovers`, the sole predicate:
163+
*
164+
* hintCovers('.objectui-sha/**', '.objectui-sha') -> true
165+
* collapseHint('.objectui-sha/**') -> '.objectui-sha'
166+
*
167+
* `check-doc-anchors.mjs` declares `README.md`/`ARCHITECTURE.md` this way and
168+
* `git-merge-regen.mjs` declares `package.json` this way; the sibling package-
169+
* scoped gate `check-llms-txt.ts` proves the idiom reaches a gate invoked
170+
* through a pnpm filter — dispatch-gates resolves the `--filter` script back to
171+
* this file and reads it as `gate source`, exactly as it does for that one.
172+
*
173+
* ⚠️ Provenance, NOT a lookup key. Nothing here is joined with `REPO` and
174+
* stat'd: `PIN_FILE` and `SRC` remain the paths this gate opens. The glob form
175+
* used as a path would resolve to nothing and `existsSync` would drop it
176+
* SILENTLY — the "checked nothing, reported green" disease this gate's own
177+
* vacuous-green guard exists to refuse. `selfTest` pins both halves.
178+
*/
179+
const ROOT_FILE_WATCH_HINTS = ['.objectui-sha/**'];
180+
181+
/**
182+
* The other half of the population: the SUBTREE this gate walks for citations.
183+
*
184+
* A pin bump is not the only way a citation goes stale — writing a new record,
185+
* or moving one, changes what this gate has to say about the SAME pin. The
186+
* glob sits in the FINAL segment, so `hintCovers` reaches every source beneath
187+
* `packages/spec/src` and nothing outside it, which is precisely the scope
188+
* `sourceFiles(SRC)` walks (the header's "everything outside `packages/spec/src`"
189+
* exclusion is the same boundary, stated from the other side).
190+
*/
191+
const ROOT_DIR_WATCH_HINTS = ['packages/spec/src/**'];
192+
130193
const LIST = process.argv.includes('--list');
131194

132195
/** How far past a `.objectui-sha` mention a citation may reach. */
@@ -379,6 +442,51 @@ function selfTest(): never {
379442
check(found > 0, 'discovery: found zero citations in the real tree — the matcher or the scope has moved');
380443
}
381444

445+
// ── the dispatch-gates declaration ──────────────────────────────────────
446+
// Enforcement cannot hold any of this: the declaration is read by ANOTHER
447+
// tool entirely (`extractWatchHints` in scripts/pm/dispatch-gates.mjs, off
448+
// source text), so a wrong, reworded or deleted entry runs perfectly green
449+
// here and shows up only as a dev dispatched on a `.objectui-sha` pin bump
450+
// who is never told this gate reads it — which is the failure that bought
451+
// this declaration, measured as a full CI cycle after 47 green commands.
452+
{
453+
const declared = [...ROOT_FILE_WATCH_HINTS, ...ROOT_DIR_WATCH_HINTS];
454+
const collapse = (h: string): string => h.replace(/\/\*+$/, '');
455+
456+
// The population the declaration CLAIMS is the population this gate READS.
457+
check(
458+
ROOT_FILE_WATCH_HINTS.length === 1 && collapse(ROOT_FILE_WATCH_HINTS[0]!) === path.relative(REPO, PIN_FILE),
459+
`the declared root file is the pin file this gate opens: ${ROOT_FILE_WATCH_HINTS.join(', ')} vs ${path.relative(REPO, PIN_FILE)}`,
460+
);
461+
check(
462+
ROOT_DIR_WATCH_HINTS.length === 1 && collapse(ROOT_DIR_WATCH_HINTS[0]!) === path.relative(REPO, SRC),
463+
`the declared subtree is the tree this gate walks: ${ROOT_DIR_WATCH_HINTS.join(', ')} vs ${path.relative(REPO, SRC)}`,
464+
);
465+
466+
// The separator is what makes `hintCovers` admit the entry at all — a bare
467+
// filename reaches it as a bare word. A reword back to `.objectui-sha`
468+
// leaves every other signal this gate emits green.
469+
check(
470+
declared.every((h) => h.includes('/')),
471+
`every declared entry carries a path separator: ${declared.join(', ')}`,
472+
);
473+
474+
// Provenance, never a lookup key: joined with REPO these resolve to
475+
// nothing, and `existsSync` would drop them SILENTLY.
476+
check(
477+
declared.every((h) => !existsSync(path.join(REPO, h))),
478+
`the declared spellings are provenance, not paths: ${declared.join(', ')}`,
479+
);
480+
481+
// …and the declared subtree really covers the live population, so a
482+
// narrowing of either side cannot pass unnoticed.
483+
const declaredSrc = path.join(REPO, collapse(ROOT_DIR_WATCH_HINTS[0] ?? ''));
484+
check(
485+
sourceFiles(SRC).every((f) => f.startsWith(declaredSrc + path.sep)),
486+
'every source this gate scans lies under the declared subtree',
487+
);
488+
}
489+
382490
if (failures.length) {
383491
for (const f of failures) console.error(`✗ self-test: ${f}`);
384492
console.error(`\ncheck-objectui-pin-citations --self-test: ${failures.length} failure(s).\n`);
@@ -388,7 +496,9 @@ function selfTest(): never {
388496
'✅ self-test: asserting citations are checked against the pin and historical ones are not;\n' +
389497
' a citation wrapped across comment lines is still found (both wrap positions); a sha in\n' +
390498
' neither spelling and a sha missing its backticks both FAIL; a mention naming no sha is\n' +
391-
' skipped; hex-looking prose without a digit is not a sha; a missing pin file throws.',
499+
' skipped; hex-looking prose without a digit is not a sha; a missing pin file throws; and\n' +
500+
' the dispatch-gates watch-hint declaration names the pin file and the subtree this gate\n' +
501+
' really reads, in the separator-carrying spelling `hintCovers` admits.',
392502
);
393503
process.exit(0);
394504
}

0 commit comments

Comments
 (0)