Skip to content

Commit 40b40bd

Browse files
tooling(pm): dispatch-gates git maxBuffer for the merged tree; register matchers proven on the nested ui/ rows (4b PR 1, inert) (#25732)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoVVzFG2pTBdWK9NbS3kt8yY)_ Part of #25712 — PR 1 of 2 (the inert half). The card stays open for PR 2. Part of objectstack#25677 Clause-②: no Metadata-protocol: none Also closes the PR A2 item dispatch-gates maxBuffer (the PR A2 bill lives on the CI-wiring card of this epic, which stays open for its other items). ## What this changes Two inert changes in `scripts/pm/**`. Nothing changes for today's tree: no register row, no instruction file, no CODEOWNERS line and no workflow is touched. 1. **`dispatch-gates.mjs`: git children get a 64 MiB stdout ceiling** (`GIT_MAX_BUFFER_BYTES`), in `runGit` and in `ignoreVerdicts`' `git check-ignore`. `spawnSync`'s default is 1 MiB, and past it the child dies with `ENOBUFS`. Measured: `git ls-files -z` on this tree at 43edf90 is 724,439 bytes (11,967 paths). objectui's tree under a `ui/` prefix adds 631,073 bytes (9,197 paths), so the merged listing is about 1.36 MB, past the default. That is the Lint · PM dispatch-gates red on the verification run of the import. A tree that already fit gets the same answer as before. 2. **The register's matchers take a register handed in.** `governedSurfacesFor(repo, register)`, `governedPathsIn(paths, repo, register)`, `governedTierFor(paths, repo, register)` and the queue guard's `codeownersMirrorVerdict({ …, register })` default to `GOVERNED_SURFACES`. No production caller passes one. It is the seam the self-tests use to drive the six nested `ui/` rows through the real matchers before those rows land on the register. The prose gate's `verdict(region, globs)` already took its register as an argument, so it only gains pins. ## Inventory: `scripts/pm/**` and objectui, what PR 1 does with each | File | What it holds about objectui | PR 1 | Why that is inert | |---|---|---|---| | `check-governed-merges.mjs` | `GOVERNED_REPOS` row `objectui`; `workflows` row scoped to objectstack, objectui, cloud | matchers take an optional `register`; new self-test battery on the six `ui/` rows | default argument is the live register; no row added | | `check-governed-queue-guard.mjs` | imports the register; sibling-copy repo resolution (`guardRepository`) | `codeownersMirrorVerdict` takes an optional `register`; four pins | default argument; CODEOWNERS on disk still mirrors the live register (pinned) | | `check-governed-prose.mjs` | reads the register's globs for objectstack | three `verdict` pins on the `ui/` globs | no prose region or register change | | `dispatch-gates.mjs` | `--repo` sister-repo slugs; one comment on `objectui-changeset-digest` | git `maxBuffer` raised | same output on any listing under 1 MiB | | `dispatch-gates.self-test.mjs` | fixtures | one slow section: a 16,000-path temporary index | slow tier only (push/schedule in CI) | | `check-half-states.mjs` | sibling board (`OWN_BOARD === 'objectui'`), repo-qualified refs | none | objectui repo stays open until archive; removal is sub-issue 6 | | `fleet-roster.mjs` · `fleet-write/{validate,execute,dispatch}.mjs` · `post-stamped.mjs` · `issue-transfer.mjs` · `ensure-pm-labels.sh` · `check-prior-rulings.mjs` | per-repo rows and fixtures keyed `objectstack-ai/objectui` | none | the relay must keep writing to the objectui repo until it is archived (sub-issue 5); rows are removed in sub-issue 6 | | `check-expected-skips.mjs` | `ui_tree` skip reasons from PR A | none | already inert while `ui/` is absent | | `ci-failure.mjs` · `sweep-*.mjs` · `rerun-failed.mjs` · `record-recognisers.mjs` · `os-regen-merge.sh` · `check-skill-line-ratchet.mjs` · `changeset-deadline-census.mjs` · `board-snapshot.mjs` | comments and fixtures | none | text only | ## Deferred to PR 2: the six `ui/` register rows Decided by the epic PM: the rows land in PR 2, not here. Adding a register row is not a `scripts/pm`-only change. Measured on 43edf90: - `check:pm-governed-prose` requires every register glob as a code span in `AGENTS.md` Prime Directive #14 and in the pm-dispatch `SKILL.md` ACCEPT region. - The queue guard's CODEOWNERS mirror pin requires a root-anchored line for every Tier H row. - `check:pm-skill-ratchet` has `AGENTS.md` at 1117/1117 lines and `.claude/skills/pm-dispatch/SKILL.md` at 319/319, both with headroom 0 and a 120-byte line cap. The six globs come to about 115 bytes, which is one more line in each file. PR 2's `AGENTS.md` rewrite (the 41 `objectui#` mentions and the sibling-repo sections) frees those lines without a ceiling raise. The rows, as PR 2 lands them (all Tier H, `repos: ['objectstack']`). They mirror objectui's own six-row register under `ui/`: | id | match | glob | |---|---|---| | `ui-adr` | prefix `ui/docs/adr/` | `ui/docs/adr/**` | | `ui-claude-tree` | prefix `ui/.claude/` | `ui/.claude/**` | | `ui-skills-catalog` | prefix `ui/skills/` | `ui/skills/**` | | `ui-agents-md` | exact `ui/AGENTS.md` | `ui/AGENTS.md` | | `ui-claude-md` | exact `ui/CLAUDE.md` | `ui/CLAUDE.md` | | `ui-workflows` | prefix `ui/.github/workflows/` | `ui/.github/workflows/**` | - `ui/.claude/**` is Tier H, not S. That content lands Tier H in objectui today, and Tier S would loosen its landing, which is a maintainer ruling (one field, `tier`). - `ui/.changeset/config.json` is left out. No register in either repository governs `.changeset/config.json`. - PR 2 also adds the six CODEOWNERS lines (`/ui/docs/adr/`, `/ui/.claude/`, `/ui/skills/`, `/ui/AGENTS.md`, `/ui/CLAUDE.md`, `/ui/.github/workflows/`) and flips this PR's pin `today-the-register-does-not-know-ui-the-pr-adding-the-rows-flips-this`. - **The window:** between the subtree import and PR 2, nothing in this repository governs those `ui/` paths. The cutover freeze sequences it as subtree PR, then PR B of the CI-wiring card, then this card's PR 2, then 4a. So `ui/**` is governed before anything else merges on top of it. ## Verification Tree: `faaeaeb` on `main` 43edf90 (merge base = `origin/main`). - `node scripts/pm/check-governed-merges.mjs --self-test`: exit 0, 527 assertions (512 before). New battery: 15 cases. Each `ui/` row's specimen is governed by its own row alone and lands Tier H, including `ui/.claude/hooks/…` with a root `.claude/**` path beside it. `ui/packages/…/src`, `ui/package.json` and `ui/.changeset/config.json` are not governed. Nested copies (`ui/examples/AGENTS.md`, `ui/packages/x/.github/workflows/w.yml`) and near misses stay out. Root rows and `ui/` rows do not reach each other. The rows govern objectstack alone. Today's register knows no `ui/` path. Roster floor 34 → 35. - `node scripts/pm/check-governed-queue-guard.mjs --self-test`: exit 0, 339 cases (335 before). With the `ui/` rows handed in, the CODEOWNERS file on disk is short by exactly one line per `ui/` row, and each problem names the row and its `/ui/…` pattern. A fixture carrying those lines passes. The sibling repository's verdict needs no `/ui/` line. - `node scripts/pm/check-governed-prose.mjs --self-test` and the gate: exit 0, 31 cases. With the `ui/` globs named in the prose, both halves pass. With the globs on the register but not in the prose, all six are missing. With them in the prose ahead of the register, the four `**` globs are over-claims. - `node scripts/pm/dispatch-gates.mjs --self-test` (both tiers): exit 0, 2178 cases pass (fast 1913 in 261 s, slow 265 in 744 s). The new slow section stages a 16,000-path index (1.5 MiB listing). CONTROL: the same `git ls-files -z` spawned with the default buffer dies with `ENOBUFS`. `trackedFiles` returns all 16,000 paths, first and last byte-identical. - `node scripts/pm/check-governed-merges.mjs --branch HEAD`: exit 0, not governed, +136 / −11 over 5 files. - Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 39 commands): every one exit 0. Record: ``` node scripts/check-ci-filter-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 node scripts/check-position-name-fold-loaders.mjs :: exit 0 node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0 node scripts/check-scripts-symbol-anchors.mjs :: exit 0 node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0 node scripts/check-self-test-wired.mjs :: exit 0 node scripts/check-self-test-wired.mjs --self-test :: exit 0 node scripts/check-self-test-workflow-commands.mjs :: exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 node scripts/check-skills-token-ratchet.mjs :: exit 0 node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0 node scripts/check-whole-set-label-write.mjs :: exit 0 node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:bash32-floor :: exit 0 pnpm check:cli-command-ids :: exit 0 pnpm check:closing-target-claim :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:declared-population-live :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:entry-guard :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:parse-guard :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-governed-prose :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:pnpm-flag-spellings :: exit 0 pnpm check:ratchet-remedy-authority :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:pm-dispatch-gates :: exit 0 ``` Reconciliation (`--ran`): `39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3)`, exit 0. ## Acceptance notes - `check:pm-dispatch-gates` runs the fast tier locally and on PR/queue CI. The new maxBuffer section is slow-tier, so CI runs it on `push` to `main` and on `schedule`. The local both-tier run above is the PR-time evidence. - `.github/CODEOWNERS` routes `scripts/pm/check-governed-merges.mjs` and `check-governed-queue-guard.mjs` to the approvers (enforcement-chain rows), so a code-owner review is requested when this leaves draft. Neither file is a register surface, and this PR is not Tier H (`--branch` above). - No changeset: `scripts/pm/**` publishes nothing (`skip-changeset`). --- _Generated by [Claude Code](https://claude.ai/code/session_01JjL3Jp6KnsA4vDma1b4KDa)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c882092 commit 40b40bd

5 files changed

Lines changed: 136 additions & 11 deletions

File tree

‎scripts/pm/check-governed-merges.mjs‎

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,11 +1036,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
10361036
'⭐ the FORK predicate: head repo ≠ base repo is a proposal, never a delivery': 6,
10371037
'⭐ the argv is CLOSED: a flag the mode does not read, or a second repository beside a bare --pr, is refused': 22,
10381038
'the per-row repository scope (ruled B2, 2026-10-13)': 20,
1039+
'the nested `ui/` workspace rows, through a register handed in (#25712)': 15,
10391040
});
10401041

10411042
// DELETING an entry silences that battery's floor exactly as effectively as
10421043
// zeroing it, so the roster's own size is pinned too.
1043-
const SELF_TEST_BATTERY_FLOOR = 34;
1044+
const SELF_TEST_BATTERY_FLOOR = 35;
10441045

10451046
// The key an assertion is filed under when no battery is open. It is not a
10461047
// declared battery, so it reds by the same set difference rather than silently
@@ -1462,10 +1463,16 @@ export function assertGovernedRepo(repo, caller = 'governedPathsIn') {
14621463
* (`surfaceTierIn`): a row whose tier holds there is handed over as the table's
14631464
* own frozen row, any other as a frozen copy carrying Tier H. Refuses a missing
14641465
* or unknown repository.
1466+
*
1467+
* `register` is the table read, `GOVERNED_SURFACES` unless a caller hands in
1468+
* another. ⛔ No production caller does: it is the seam the self-tests drive
1469+
* with rows that are not on the register yet (the nested `ui/` workspace rows,
1470+
* objectstack#25712), so the matchers are proven on those rows before the
1471+
* register carries them. A register handed in is read exactly as the real one.
14651472
*/
1466-
export function governedSurfacesFor(repo) {
1473+
export function governedSurfacesFor(repo, register = GOVERNED_SURFACES) {
14671474
assertGovernedRepo(repo, 'governedSurfacesFor');
1468-
return GOVERNED_SURFACES.filter((surface) => surfaceRepos(surface).includes(repo)).map((surface) => {
1475+
return register.filter((surface) => surfaceRepos(surface).includes(repo)).map((surface) => {
14691476
const tier = surfaceTierIn(surface, repo);
14701477
return tier === surface.tier ? surface : Object.freeze({ ...surface, tier });
14711478
});
@@ -1477,10 +1484,10 @@ export function governedSurfacesFor(repo) {
14771484
* is REQUIRED: the rows consulted are the ones that govern it, and a call
14781485
* without one is refused (`assertGovernedRepo`).
14791486
*/
1480-
export function governedPathsIn(paths, repo) {
1487+
export function governedPathsIn(paths, repo, register = GOVERNED_SURFACES) {
14811488
assertGovernedRepo(repo, 'governedPathsIn');
14821489
const list = Array.isArray(paths) ? paths : [];
1483-
return governedSurfacesFor(repo).map((surface) => ({
1490+
return governedSurfacesFor(repo, register).map((surface) => ({
14841491
...surface,
14851492
files: list.filter((p) =>
14861493
typeof p === 'string' && (surface.prefix ? p.startsWith(surface.prefix) : p === surface.exact),
@@ -1514,8 +1521,8 @@ export function landingTierOf(matched) {
15141521
* list answers `H` (see `landingTierOf`). `repo` is required, as for
15151522
* `governedPathsIn`: the rows consulted are the ones that govern it.
15161523
*/
1517-
export function governedTierFor(paths, repo) {
1518-
return landingTierOf(governedPathsIn(paths, assertGovernedRepo(repo, 'governedTierFor')));
1524+
export function governedTierFor(paths, repo, register = GOVERNED_SURFACES) {
1525+
return landingTierOf(governedPathsIn(paths, assertGovernedRepo(repo, 'governedTierFor'), register));
15191526
}
15201527

15211528
/** `owner/name` out of any git remote spelling, or null. Pure. */
@@ -4458,6 +4465,43 @@ async function selfTest() {
44584465
assert('the-checkout-note-names-the-repository-judged-and-the---root-remedy', /judged for cloud \(objectstack-ai\/cloud, the checkout at --root\)/.test(checkoutRepoNote({ repo: 'cloud', slug: 'objectstack-ai/cloud' })) && /--root <its path>/.test(checkoutRepoNote({ repo: 'cloud', slug: 'objectstack-ai/cloud' })));
44594466
assert('the-verdict-head-names-the-repository-and-counts-its-rows', renderTestVerdict(testVerdict([wf], { repo: 'cloud' })).split('\n')[0] === 'governed-surface predicate: 1 of 1 path(s) hit the register (7 surfaces govern cloud).' && renderTestVerdict(testVerdict([wf], { repo: 'objectos' })).split('\n')[0] === 'governed-surface predicate: 0 of 1 path(s) hit the register (6 surfaces govern objectos).', renderTestVerdict(testVerdict([wf], { repo: 'objectos' })).split('\n')[0]);
44604467

4468+
// ── the nested `ui/` workspace rows (#25712) ─────────────────────────────
4469+
//
4470+
// The objectui tree arrives as the `ui/` workspace of this repository, and
4471+
// its own register's six rows have to govern it here under that prefix. The
4472+
// rows land on the register in the PR that also names them in the two
4473+
// instruction surfaces `check-governed-prose.mjs` pins (both at their line
4474+
// ceiling); until then they are THIS fixture, read through the matchers'
4475+
// `register` seam, so the matchers are proven on them first. Both directions
4476+
// are pinned: each row's specimen is governed Tier H in objectstack, and a
4477+
// `ui/` source path, a nested copy of an instruction file and every other
4478+
// repository answer NOT governed. The last case pins today's register: it
4479+
// does not know `ui/` yet — the PR that adds the rows flips that case.
4480+
battery('the nested `ui/` workspace rows, through a register handed in (#25712)');
4481+
const UI_ROWS = Object.freeze([
4482+
Object.freeze({ id: 'ui-adr', prefix: 'ui/docs/adr/', glob: 'ui/docs/adr/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's architecture decision records" }),
4483+
Object.freeze({ id: 'ui-claude-tree', prefix: 'ui/.claude/', glob: 'ui/.claude/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction tree" }),
4484+
Object.freeze({ id: 'ui-skills-catalog', prefix: 'ui/skills/', glob: 'ui/skills/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's published skills catalog" }),
4485+
Object.freeze({ id: 'ui-agents-md', exact: 'ui/AGENTS.md', glob: 'ui/AGENTS.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's agent instruction file" }),
4486+
Object.freeze({ id: 'ui-claude-md', exact: 'ui/CLAUDE.md', glob: 'ui/CLAUDE.md', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's Claude instruction file" }),
4487+
Object.freeze({ id: 'ui-workflows', prefix: 'ui/.github/workflows/', glob: 'ui/.github/workflows/**', tier: GOVERNED_TIER_H, repos: Object.freeze(['objectstack']), what: "the ui workspace's workflow files" }),
4488+
]);
4489+
const WITH_UI = Object.freeze([...GOVERNED_SURFACES, ...UI_ROWS]);
4490+
const uiIds = (paths, repo = REPO) => governedPathsIn(paths, repo, WITH_UI).map((s) => s.id);
4491+
const UI_SPECIMENS = { 'ui-adr': 'ui/docs/adr/0001-master-detail-subform.md', 'ui-claude-tree': 'ui/.claude/hooks/guard-main-checkout.sh', 'ui-skills-catalog': 'ui/skills/objectui/SKILL.md', 'ui-agents-md': 'ui/AGENTS.md', 'ui-claude-md': 'ui/CLAUDE.md', 'ui-workflows': 'ui/.github/workflows/ci.yml' };
4492+
assert('the-fixture-rows-are-well-formed-register-rows', UI_ROWS.every((r) => (r.prefix ? r.prefix.endsWith('/') && r.glob === `${r.prefix}**` && r.exact === undefined : r.glob === r.exact) && r.tier === GOVERNED_TIER_H && surfaceRepos(r).join() === 'objectstack') && new Set(WITH_UI.map((r) => r.id)).size === WITH_UI.length, JSON.stringify(UI_ROWS.map((r) => r.id)));
4493+
assert('the-fixture-mirrors-the-six-rows-objectui-governs-each-under-ui', UI_ROWS.map((r) => r.glob).join() === GOVERNED_SURFACES.filter((s) => s.id !== 'north-star').map((s) => `ui/${s.glob}`).join(), UI_ROWS.map((r) => r.glob).join());
4494+
for (const row of UI_ROWS) {
4495+
assert(`the-${row.id}-specimen-is-governed-by-its-own-row-alone`, uiIds([UI_SPECIMENS[row.id]]).join() === row.id, uiIds([UI_SPECIMENS[row.id]]).join());
4496+
}
4497+
assert('every-ui-row-lands-Tier-H-the-ui-claude-tree-included', Object.values(UI_SPECIMENS).every((p) => governedTierFor([p], REPO, WITH_UI) === GOVERNED_TIER_H) && governedTierFor([UI_SPECIMENS['ui-claude-tree'], '.claude/agents/os-dev.md'], REPO, WITH_UI) === GOVERNED_TIER_H);
4498+
assert('a-ui-source-path-is-NOT-governed', uiIds(['ui/packages/components/src/index.ts', 'ui/apps/console/src/App.tsx', 'ui/package.json', 'ui/.changeset/config.json']).length === 0);
4499+
assert('nested-copies-and-near-misses-stay-out', uiIds(['ui/examples/AGENTS.md', 'ui/packages/x/AGENTS.md', 'ui/packages/x/.github/workflows/w.yml', 'ui/.claude-x/y.md', 'ui/skillsx/a.md', 'ui/docs/adrs/z.md', 'ui/.github/CODEOWNERS', 'xui/AGENTS.md']).length === 0);
4500+
assert('the-root-rows-do-not-reach-into-ui-and-the-ui-rows-do-not-reach-the-root', uiIds(['ui/.github/workflows/ci.yml', '.github/workflows/lint.yml', 'ui/AGENTS.md', 'AGENTS.md']).join() === 'agents-md,workflows,ui-agents-md,ui-workflows');
4501+
assert('the-ui-rows-govern-objectstack-alone', ['objectui', 'cloud', 'objectos', 'hotcrm'].every((r) => uiIds(Object.values(UI_SPECIMENS), r).length === 0));
4502+
assert('a-register-handed-in-is-counted-by-the-same-reader', governedSurfacesFor(REPO, WITH_UI).length === governedSurfacesFor(REPO).length + UI_ROWS.length);
4503+
assert('today-the-register-does-not-know-ui-the-pr-adding-the-rows-flips-this', ids(Object.values(UI_SPECIMENS)).length === 0 && governedSurfacesFor(REPO).length === 7);
4504+
44614505
// ── the dispatch-gates declaration (#9979) ───────────────────────────────
44624506
//
44634507
// Enforcement cannot hold any of these: the declaration is read by another

‎scripts/pm/check-governed-prose.mjs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
106106
'sliceRegion': 5,
107107
'codeSpansIn': 3,
108108
'looksLikeGovernedGlob': 5,
109-
'verdict': 6,
109+
'verdict': 9,
110110
'the shipped register + regions, end to end': 5,
111111
'the dispatch-gates declaration (#9979)': 4,
112112
});
@@ -382,6 +382,15 @@ function selfTest() {
382382
// Non-enumeration spans in the same region are ignored by both halves.
383383
assert('unrelated spans are ignored', verdict(`${good} run \`pnpm check:adr-anchors\``, register), { missing: [], unknown: [] });
384384

385+
// The nested `ui/` workspace rows (#25712) are ordinary globs to both halves:
386+
// named, they pass; on the register but not in the prose, containment names
387+
// them; in the prose but not on the register, the over-claim half does.
388+
const uiGlobs = ['ui/docs/adr/**', 'ui/.claude/**', 'ui/skills/**', 'ui/AGENTS.md', 'ui/CLAUDE.md', 'ui/.github/workflows/**'];
389+
const withUi = `${good} Under the \`ui/\` workspace: ${uiGlobs.map((g) => `\`${g}\``).join(' + ')}.`;
390+
assert('ui rows named in the prose pass both halves', verdict(withUi, [...register, ...uiGlobs]), { missing: [], unknown: [] });
391+
assert('ui rows on the register but not in the prose are each missing', verdict(good, [...register, ...uiGlobs]).missing, uiGlobs);
392+
assert('ui globs in the prose ahead of the register are each an over-claim', verdict(withUi, register).unknown, uiGlobs.filter((g) => g.endsWith('**')));
393+
385394
// --- the shipped register + regions, end to end ------------------------
386395
battery('the shipped register + regions, end to end');
387396
const globs = registerGlobs();

‎scripts/pm/check-governed-queue-guard.mjs‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,13 +670,14 @@ export function codeownersPatternFor(surface) {
670670
* Every problem is a sentence naming the row or line; the list is whole, never
671671
* the first hit. Pure.
672672
*/
673-
export function codeownersMirrorVerdict({ text, approvers = GOVERNED_APPROVERS, repo = SELF_REPO_ID } = {}) {
673+
export function codeownersMirrorVerdict({ text, approvers = GOVERNED_APPROVERS, repo = SELF_REPO_ID, register = GOVERNED_SURFACES } = {}) {
674674
const rows = codeownersRows(text);
675675
const wanted = [...approvers].map((a) => `@${a}`.toLowerCase()).sort();
676676
const sameOwners = (owners) => JSON.stringify([...owners].map((o) => o.toLowerCase()).sort()) === JSON.stringify(wanted);
677677
const problems = [];
678678
// The rows that govern THIS repository, read from the register (ruling B2: the question has a repository in it).
679-
const governing = governedSurfacesFor(repo);
679+
// `register` is the register's own seam (`governedSurfacesFor`): the self-test hands in rows not on it yet.
680+
const governing = governedSurfacesFor(repo, register);
680681
const tierH = governing.filter((s) => s.tier === GOVERNED_TIER_H);
681682
const tierHPatterns = tierH.map(codeownersPatternFor);
682683
for (const surface of tierH) {
@@ -4657,6 +4658,19 @@ export async function selfTest() {
46574658
assert('⛔ a-file-that-never-names-the-constant-is-a-problem', !silent.ok && silent.problems.length === 1 && silent.problems[0].includes('GOVERNED_APPROVERS'), silent.problems.join(' | '));
46584659
const other = codeownersMirrorVerdict({ text: onDisk, approvers: ['someone-else'] });
46594660
assert('⭐ the-approver-list-is-READ-not-restated-another-list-reds-the-file-on-disk', !other.ok && other.problems.length >= live.tierHPatterns.length, String(other.problems.length));
4661+
// The nested `ui/` workspace rows (#25712), handed in through the register's seam: each lands
4662+
// Tier H, so each needs its root-anchored `/ui/…` line, and the file on disk carries none yet.
4663+
const uiRows = GOVERNED_SURFACES.filter((s) => s.id !== 'north-star').map((s) => Object.freeze({
4664+
...s, id: `ui-${s.id}`, ...(s.prefix ? { prefix: `ui/${s.prefix}` } : { exact: `ui/${s.exact}` }), glob: `ui/${s.glob}`,
4665+
tier: GOVERNED_TIER_H, tierRepos: undefined, repos: Object.freeze(['objectstack']),
4666+
}));
4667+
const withUi = [...GOVERNED_SURFACES, ...uiRows];
4668+
const uiLive = codeownersMirrorVerdict({ text: onDisk, register: withUi });
4669+
assert('⛔ with-the-ui-rows-handed-in-the-file-on-disk-is-short-one-line-per-ui-row', !uiLive.ok && uiLive.problems.length === uiRows.length && uiRows.every((r) => uiLive.problems.some((p) => p.includes(`\`${r.id}\``) && p.includes(`\`/${r.prefix ?? r.exact}\``))), uiLive.problems.join(' | '));
4670+
assert('the-ui-patterns-are-root-anchored-under-ui-the-ui-claude-tree-included', uiLive.tierHPatterns.slice(live.tierHPatterns.length).join() === '/ui/docs/adr/,/ui/.claude/,/ui/skills/,/ui/AGENTS.md,/ui/CLAUDE.md,/ui/.github/workflows/', uiLive.tierHPatterns.join());
4671+
const uiFull = `# mirrors GOVERNED_APPROVERS\n${uiLive.tierHPatterns.map((p) => `${p} ${owners}`).join('\n')}\n`;
4672+
assert('a-fixture-carrying-the-ui-lines-too-passes-against-the-ui-register', codeownersMirrorVerdict({ text: uiFull, register: withUi }).ok);
4673+
assert('the-ui-rows-govern-objectstack-alone-so-a-sibling-copy-needs-no-ui-line', codeownersMirrorVerdict({ text: full(), repo: 'objectui', register: withUi }).tierHPatterns.every((p) => !p.startsWith('/ui/')));
46604674
}
46614675

46624676
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────

‎scripts/pm/dispatch-gates.mjs‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9656,6 +9656,7 @@ export function ignoreVerdicts(paths, { cwd = ROOT } = {}) {
96569656
const r = spawnSync('git', ['check-ignore', '-v', '--non-matching', '--no-index', '--stdin'], {
96579657
cwd,
96589658
encoding: 'utf8',
9659+
maxBuffer: GIT_MAX_BUFFER_BYTES,
96599660
input: `${unique.join('\n')}\n`,
96609661
});
96619662
if (r.error) throw new Error(`could not run git check-ignore — ${r.error.message}`);
@@ -15044,8 +15045,24 @@ export const DEFAULT_BASE_REMOTE = 'origin';
1504415045
export const DEFAULT_BASE_BRANCH = 'main';
1504515046
export const DEFAULT_BASE_REF = `${DEFAULT_BASE_REMOTE}/${DEFAULT_BASE_BRANCH}`;
1504615047

15048+
/**
15049+
* The stdout ceiling every git child of this tool is spawned with.
15050+
*
15051+
* `spawnSync`'s default is 1 MiB, and past it node kills the child and hands
15052+
* back `error.code === 'ENOBUFS'` — which `runGit` turns into a thrown "could
15053+
* not run git", so the tool refuses instead of answering. `trackedFiles`
15054+
* (`git ls-files -z`) is the reader that hits it first: this tree's listing is
15055+
* under the default, and the same tree with the objectui workspace imported
15056+
* under `ui/` is not (the Lint job's dispatch-gates step went red on ENOBUFS
15057+
* on the verification run of that import). 64 MiB is headroom for a listing
15058+
* an order of magnitude past that one; the bound stays finite so a runaway
15059+
* child is still stopped. Raising it changes no answer on a tree that already
15060+
* fit — only whether a larger tree gets one.
15061+
*/
15062+
export const GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
15063+
1504715064
function runGit(args, cwd) {
15048-
const r = spawnSync('git', args, { cwd, encoding: 'utf8' });
15065+
const r = spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER_BYTES });
1504915066
if (r.error) throw new Error(`could not run git — ${r.error.message}`);
1505015067
return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: (r.stderr ?? '').trim() };
1505115068
}

‎scripts/pm/dispatch-gates.self-test.mjs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import {
6969
DURABILITY_SEAM_GATE,
7070
ESCAPABLE_LITERAL_LEDGER,
7171
GENERATED_MODULE_SOURCES,
72+
GIT_MAX_BUFFER_BYTES,
7273
HARVEST_SNIPPET,
7374
INHERITED_POPULATION_MARKER,
7475
MANDATORY_TIER_GLOBS,
@@ -10908,6 +10909,46 @@ export function selfTest({ tier = 'full' } = {}) {
1090810909
.lines.join('\n').includes('UNVERIFIABLE'),
1090910910
);
1091010911

10912+
slow("a tracked-file listing past spawnSync's default 1 MiB stdout buffer — one temporary index", () => {
10913+
// ── The git stdout ceiling (objectstack#25712) ────────────────────────────
10914+
// `git ls-files -z` over this tree plus an imported `ui/` workspace is past
10915+
// node's default `maxBuffer`, and the child dies with ENOBUFS. The fixture is
10916+
// an index alone — `update-index --index-info` stages paths with no files on
10917+
// disk — sized past 1 MiB. The CONTROL spawns the same listing with the
10918+
// default buffer and must see ENOBUFS, so the pass below cannot be a fixture
10919+
// that never reached the size.
10920+
const bufTmp = mkdtempSync(nodePath.join(tmpdir(), 'dispatch-gates-maxbuffer-'));
10921+
try {
10922+
const init = spawnSync('git', ['init', '--initial-branch=main', '.'], { cwd: bufTmp, encoding: 'utf8' });
10923+
if (init.status !== 0) throw new Error(`fixture git init failed: ${init.stderr}`);
10924+
const EMPTY_BLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
10925+
const paths = Array.from({ length: 16000 }, (_, i) =>
10926+
`ui/packages/fixture-${String(i).padStart(5, '0')}/src/a-long-enough-component-name-for-a-wide-listing.tsx`);
10927+
const staged = spawnSync('git', ['update-index', '--index-info'], {
10928+
cwd: bufTmp,
10929+
encoding: 'utf8',
10930+
input: paths.map((p) => `100644 ${EMPTY_BLOB} 0\t${p}`).join('\n') + '\n',
10931+
});
10932+
if (staged.status !== 0) throw new Error(`fixture git update-index failed: ${staged.stderr}`);
10933+
const listingBytes = paths.reduce((n, p) => n + Buffer.byteLength(p) + 1, 0);
10934+
t('the fixture listing is past the 1 MiB default buffer', listingBytes > 1024 * 1024, String(listingBytes));
10935+
const atDefault = spawnSync('git', ['ls-files', '-z'], { cwd: bufTmp, encoding: 'utf8' });
10936+
t('CONTROL: the same listing spawned with the default buffer dies with ENOBUFS', atDefault.error?.code === 'ENOBUFS', String(atDefault.error?.code ?? atDefault.status));
10937+
let listed = null;
10938+
let thrown = null;
10939+
try {
10940+
listed = trackedFiles({ cwd: bufTmp });
10941+
} catch (e) {
10942+
thrown = e.message;
10943+
}
10944+
t('trackedFiles reads the whole listing instead of refusing on ENOBUFS', thrown === null && listed?.length === paths.length, thrown);
10945+
t('the listing comes back byte-for-byte, first and last path included', listed?.[0] === paths[0] && listed?.at(-1) === paths.at(-1));
10946+
t('the ceiling stays finite and above this fixture', Number.isFinite(GIT_MAX_BUFFER_BYTES) && GIT_MAX_BUFFER_BYTES > listingBytes);
10947+
} finally {
10948+
rmSync(bufTmp, { recursive: true, force: true });
10949+
}
10950+
});
10951+
1091110952
slow("base drift — temporary repositories and spawned children", () => {
1091210953
// ── Base drift (#11540) ───────────────────────────────────────────────────
1091310954
// The banner names the commit an answer came from; on a stale checkout that

0 commit comments

Comments
 (0)