Skip to content

Commit 801415a

Browse files
os-teslaclaude
andauthored
fix(pm): check-half-states takes the checkout that SERVES the board, and refuses a foreign one (#19288)
Fixes #19191 Clause-②: no `scripts/pm/check-half-states.mjs` took three readings off the LOCAL checkout with plain `execFileSync` git calls and no `cwd`, so each inherited `process.cwd()` while the board it reports on is named by `PM_SWEEP_REPO`. The filer measured both directions on the same board with the same script (#19191, comment 5740947564): with `cwd` in objectstack, H17's trigger-file index validated objectui's candidate paths against **objectstack's 8888** tracked files (objectui had **7945**) and H57 read NOTHING for the whole run — and the row it never read was hiding a scheduled lane dead through five consecutive fires (objectstack-ai/objectui#10010). Internally consistent, externally wrong. ## 维护者速读(草稿) **改了什么** —— 给这个巡检工具加了一个环境变量 `PM_SWEEP_CHECKOUT`,用来回答「哪个本地检出服务于这块 board」。设了它,三处本地 git 读取(H17 的 tracked-file 判据、H57 的 workflow 文件、`origin`)就都在那棵 树上读;那棵树的 `origin` 不是被扫的仓时,整轮巡检响亮拒绝(退出码 3),⛔ 绝不静默换一棵树读。不设它, 行为与今天逐字一致 —— 只多了一句:H17 页脚现在写明它到底在哪棵树上读的(路径 + `origin`)。 **为什么改** —— 这不是「跑错目录」的操作失误,而是一个没有症状的读数缺陷。跨仓巡检是常态(工具住在 objectstack,board 可以是 objectui),而 H17 的页脚正是在告诉派发席位「派发前请拿你的文件面和这张清单求 交集」。清单是拿另一个仓的文件列表验过的,只在另一个仓存在的触发文件被静默丢弃 —— 席位读到的是一份干净 清单,而那份干净是假的。H57 那半反而是诚实的:它明说自己什么都没读,代价是那一轮真有红行没人看见 —— 本 PR 的实测里那是两行,其中一行连卡都没提到。 **风险与代价(含回滚)** —— 风险面很窄:变量不设时是逐字旧行为(自测里有一条专门盯这件事),巡检 workflow 本身不设它,所以线上那条 lane 的行为不变。新增的唯一失败模式是「变量设错路径」,而那正好是本卡 要的那个响亮拒绝。回滚 = revert 这两个 commit,没有数据迁移、没有已发布面(`scripts/pm/**` 不随任何包 发布,故无 changeset)。 **席位意见** —— **你要做的** —— 无需维护者动手。若希望巡检 workflow 把这棵树写明(目前不需要,因为 runner 的检出就是被扫 的仓),那是 `.github/workflows/half-state-patrol.yml` 的一行 env,已写在下面的 Acceptance notes 里,留给 单独一个由人合的改动 —— 本 PR ⛔ 不碰 workflow(#19259、#19225 正在改它)。 ## What changed, mechanically - **`PM_SWEEP_CHECKOUT`** — one knob, beside `PM_SWEEP_REPO` and `PM_SWEEP_CLOSED_FLOOR` in `--help`, carrying the PATH of the checkout that serves the board. `resolveSweepCheckout(env)` resolves and trims it; whitespace is unset. - **One git read site.** All three readers (`readTrackedFiles`, `readRepoRoot`, `readOriginUrl`) now go through a single `gitRead(args, extra)` helper that passes that path as `cwd`. That is the only `execFileSync` git call left in the file, and the self-test pins the count at one — so a fourth reader cannot be added later without the `cwd`. That is the card's mechanism assumption turned into a measured property instead of a belief. - **The refusal.** `checkoutPrerequisite(sweepRepo, env, originUrl)` is a pure verdict in the shape `reportPrerequisiteNotMet` prints: `null` when the knob is unset (today's behaviour is not a prerequisite) or when the named tree really serves the board, otherwise the file's own exit-3 PREREQUISITE NOT MET, named. It is answered FIRST in `sweep()` — ahead of the transport probe — so a foreign checkout costs zero requests and reads nothing. - **`localCheckoutServes` resolves the same way**: the knob leg first (it names the tree, so the `origin` read IN that tree is the authority), then `GITHUB_REPOSITORY` (which names the RUNNER's tree, i.e. exactly not the knob's), then the checkout's `origin`. H57 therefore judges when the knob names the right tree, instead of refusing for the whole run. - **The H17 footer names the tree** it read — path plus `origin` — beside its oracle size, in BOTH oracle states (read, and EMPTY BY FAILURE), knob set or not. A wrong-tree reading is internally consistent; the tree it names is the only thing that tells it from a right one. ## Readings | reading | before | after | |:--|--:|--:| | `scripts/pm/check-half-states.mjs` lines | 36,176 | 36,256 — net **+80**, budget +80 | | `--self-test` cases | 5,063 | 5,081 (+18), exit 0 | | battery roster | 6 batteries, each above its pin | unchanged, each above its pin | Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at this branch → 39 runnable commands, all run locally with the exit code captured BEFORE any pipe, all 0. The per-command verdicts are in this card's `os-dev-report` comment. Control-character self-scan over the changed file: no match. No package contains `scripts/pm/**`, so no dependency-closure build and no package test suite is owed; repo-wide scans stay CI's. ## The firing pair, live Both legs ran at this branch's head against the objectui board, from the objectstack tool. **Knob = the tree that serves the board** — `PM_SWEEP_REPO=objectstack-ai/objectui` with `PM_SWEEP_CHECKOUT=/home/user/objectui`, detached under the shared heavy-verify lock (held 398s, waited 0s — shared-box seconds, not idle-box figures): **exit 0**, 502 half-states over 374 open pm-/p0-labeled issues. The H17 footer: ```text (read on 75 of 75 open `pm:on-hold` card(s); 8142 tracked file(s) in the oracle.) Read in `/home/user/objectui` (`origin` `https://github.com/objectstack-ai/objectui`). ``` 8142 is objectui's LIVE tracked count today (`git -C /home/user/objectui ls-files | wc -l` = 8142); the filer read 7945 on 2026-09-19 and the board has moved since. This worktree reads 9031, so the two trees are still 889 apart — the number that never used to appear is now the one printed, with the tree it came from beside it. H57 is judged rather than UNRESOLVED: `10 workflow(s) on the swept repo declare a schedule; 8 were judged against their latest event=schedule run and 0 are UNJUDGED rather than clean because that read failed.` It filed **two** red rows, neither reachable by any wrong-cwd sweep: - `.github/workflows/changeset-release.yml` — latest scheduled run `35493590744`, started 2026-09-20T06:11:39Z, concluded `failure`. This is the lane the card's re-run traced to objectstack-ai/objectui#10010, still dead today. - `.github/workflows/check-links.yml` — latest scheduled run `35489310168`, started 2026-09-20T04:30:22Z, concluded `failure`. Not named anywhere in the card. **Knob = a foreign tree** — same board, `PM_SWEEP_CHECKOUT=/home/user/objectstack`: **exit 3**, nothing swept, nothing spent: ```text check-half-states: PREREQUISITE NOT MET — PM_SWEEP_CHECKOUT="/home/user/objectstack" does not serve `objectstack-ai/objectui` this checkout's `origin` is `objectstack-ai/objectstack` while the sweep reads `objectstack-ai/objectui`. H17 validates every on-hold trigger path against that checkout and H57 classifies its workflow files there, so a foreign tree renders an index whose paths were checked against another repo. Fix: point PM_SWEEP_CHECKOUT at the checkout whose `origin` IS the swept board, or unset it and run the sweep from inside that checkout. ``` ## Reverse verification — both new pins can fail Each ran from the COMMITTED state, mutated through `scripts/ablation-replace.mjs` (which proves the write landed on disk and restores byte-exactly), and each turned exactly ONE case red out of 5,081. The committed blob is `f49d566ed276`. | ablation | mutation landed | case that went red | restore | |:--|:--|:--|:--| | strip the `cwd` from the one git-read site | anchor 1 to 0, blob `f49d566ed276` to `994882b2fcfc` | `#19191 knob: …and that one site takes the knob as its cwd` — got false, want true | blob back to `f49d566ed276` == HEAD, `git diff HEAD` empty | | re-spell `readRepoRoot` with its own `execFileSync` git call — a fourth unrouted reader | anchor 1 to 0, blob `f49d566ed276` to `ecd24ab63593` | `#19191 knob: ONE git read site in the file, so a fourth reader cannot skip the cwd` — got 2, want 1 | same | The first is the one that mattered most: before that pin existed, stripping the `cwd` left the whole 5,080-case suite green, i.e. the fix's central wiring had no test that could fail. The second is what makes the card's "the three readers are the ONLY local-tree reads" a property the suite enforces. ## Acceptance notes - **Knob name**: `PM_SWEEP_CHECKOUT`. It joins the established 12-member `PM_*` PM-tooling family (`PM_SWEEP_REPO`, `PM_SWEEP_CLOSED_FLOOR`, `PM_STATE_LABELS`, `PM_TOKEN`, …) rather than the product's `OS_{DOMAIN}_{NAME}` runtime family of Prime Directive #9 — the card asked for a knob "beside `PM_SWEEP_REPO`", and `--help` groups the three under one heading where a reader looks. Flagged because that directive's wording is categorical; a maintainer who prefers `OS_PM_SWEEP_CHECKOUT` gets it for the price of one rename. - **The patrol workflow needs no env line, and this PR writes none.** On every real patrol fire the runner's own checkout IS the swept board (`PM_SWEEP_REPO: ${{ github.repository }}`, per-repo installs), so the knob would be a no-op there and `GITHUB_REPOSITORY` already answers `localCheckoutServes`. If it should be stated explicitly anyway, the exact line for the sweep step's `env:` block in `.github/workflows/half-state-patrol.yml` is `PM_SWEEP_CHECKOUT: ${{ github.workspace }}` — a separate, human-merged change, since #19259 and #19225 are open on that file. - **`origin/main` was NOT merged into this branch.** It moved to `e6a03e6` while this ran and touched `scripts/pm/check-half-states.mjs` in none of those commits (verified against a fetch into a ref this worktree owns, not against the shared `origin/main` pointer), so the line budget's 36,176 baseline still holds and no serial writer conflict exists. The merge queue rebuilds the PR onto current `main` and re-runs the required contexts there, which is where a jointly-wrong merge would surface. - **Noted, not filed**: `sweep()` and `sweepScheduledWorkflows` each perform their own `readOriginUrl()` / `readRepoRoot()` — two duplicate local git reads per sweep (microseconds, zero requests). Threading one reading through `sweepInto` would widen that function's signature, which is the kind of change this card was told to keep out of. Successor: whoever lands #19177 (H59) or #19160 (H52) is next on this writer. - **Nothing else in the file was touched**: #19230, #19177, #19160, #19203, #19255 and #19108 queue behind this card on the same writer. - `scripts/pm/**` publishes nothing from any released package, so this diff owes no changeset (`Clause-②: no`). No label writes. - Tier S: this stops at the draft PR. The owning seat writes the `## Contract review` record, reads `--pair`, and lands it. --- _Generated by [Claude Code](https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5636641 commit 801415a

1 file changed

Lines changed: 93 additions & 13 deletions

File tree

‎scripts/pm/check-half-states.mjs‎

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -961,8 +961,10 @@
961961
* ## Exit codes
962962
*
963963
* 0 the sweep completed — 0 or 40 findings alike (report-only, see above).
964-
* 3 PREREQUISITE NOT MET — a classified transport failure. Nothing was swept,
965-
* and the report says so instead of implying a clean board.
964+
* 3 PREREQUISITE NOT MET — a classified transport failure, or a
965+
* `PM_SWEEP_CHECKOUT` naming a checkout that does not serve the swept
966+
* board. Nothing was swept, and the report says so instead of implying a
967+
* clean board.
966968
* 2 the sweep could not run for a reason this file cannot classify. The
967969
* pre-existing catch-all, kept so an unfamiliar failure stays loud (#4690).
968970
*
@@ -1048,6 +1050,50 @@ export function resolveSweepRepo(env = {}) {
10481050
return { repo: DEFAULT_SWEEP_REPO, source: 'default', valid: true };
10491051
}
10501052

1053+
/**
1054+
* WHICH checkout serves that board — the half `PM_SWEEP_REPO` never answered. The three local git
1055+
* reads below feed H17's oracle and H57's workflow files, and each inherited whatever tree the shell
1056+
* stood in: measured 2026-09-19 (#19191), a sweep of objectui validated its H17 trigger-file index
1057+
* against OBJECTSTACK's 8888 tracked files (objectui has 7945) and H57 refused for the whole run,
1058+
* hiding a lane dead through five scheduled fires — internally consistent and externally wrong, the
1059+
* #11217 disease one layer down. Set: the three reads take it as `cwd`, and a checkout whose `origin`
1060+
* is not the swept board REFUSES (exit 3) rather than reading a second repo's tree. Unset: today's
1061+
* behaviour exactly, plus the H17 footer NAMING the tree it read. Returns `{ path, source, set }`.
1062+
*/
1063+
export function resolveSweepCheckout(env = {}) {
1064+
const value = String(env?.PM_SWEEP_CHECKOUT ?? '').trim();
1065+
return value ? { path: value, source: 'PM_SWEEP_CHECKOUT', set: true } : { path: null, source: 'cwd', set: false };
1066+
}
1067+
1068+
// The ONE local git read in this file, so the knob above answers for every reader at once. ⛔ A
1069+
// fourth reader spelled with its own `execFileSync` keeps the defect — the self-test pins the count.
1070+
function gitRead(args, extra = {}) {
1071+
const cwd = resolveSweepCheckout(process.env).path ?? undefined;
1072+
return execFileSync('git', args, { encoding: 'utf8', cwd, ...extra });
1073+
}
1074+
1075+
/**
1076+
* The knob's refusal, in the shape `reportPrerequisiteNotMet` prints (exit 3). `null` when the knob is
1077+
* unset — today's behaviour is not a prerequisite — or when the named checkout really serves the board.
1078+
* Otherwise a NAMED failure: ⛔ never a silent fall back to `cwd`, because a report built from another
1079+
* repo's tree reads exactly like a report about this one. `originUrl` is read IN that tree.
1080+
*/
1081+
export function checkoutPrerequisite(sweepRepo, env = {}, originUrl = null) {
1082+
const checkout = resolveSweepCheckout(env);
1083+
if (!checkout.set) return null;
1084+
const serves = localCheckoutServes(sweepRepo, env, originUrl);
1085+
if (serves.serves) return null;
1086+
return {
1087+
kind: 'checkout-does-not-serve',
1088+
headline: `PM_SWEEP_CHECKOUT=${JSON.stringify(checkout.path)} does not serve \`${sweepRepo}\``,
1089+
detail: [`${serves.reason}.`, '',
1090+
'H17 validates every on-hold trigger path against that checkout and H57 classifies its workflow',
1091+
'files there, so a foreign tree renders an index whose paths were checked against another repo.'],
1092+
fix: ['point PM_SWEEP_CHECKOUT at the checkout whose `origin` IS the swept board,',
1093+
'or unset it and run the sweep from inside that checkout.'],
1094+
};
1095+
}
1096+
10511097
const SWEEP_REPO = resolveSweepRepo(process.env);
10521098
const OWNER_REPO = SWEEP_REPO.repo;
10531099
const API = 'https://api.github.com';
@@ -4563,10 +4609,7 @@ export function decisionDependentIndex(issues, index) {
45634609
*/
45644610
function readTrackedFiles() {
45654611
try {
4566-
const out = execFileSync('git', ['ls-files', '-z'], {
4567-
encoding: 'utf8',
4568-
maxBuffer: 64 * 1024 * 1024,
4569-
});
4612+
const out = gitRead(['ls-files', '-z'], { maxBuffer: 64 * 1024 * 1024 });
45704613
const set = new Set(out.split('\0').filter(Boolean));
45714614
return set.size > 0 ? set : null;
45724615
} catch {
@@ -13709,6 +13752,7 @@ export function h57ScheduledWorkflowRed(entry, nowMs = Date.now()) {
1370913752
* runs would classify by one repo and alarm about another, with no symptom at
1371013753
* all. So the answer is two definite readings and a refusal, never a guess:
1371113754
*
13755+
* the knob leg `PM_SWEEP_CHECKOUT` names the tree — the `origin` below is read IN it (#19191).
1371213756
* the runner leg `GITHUB_REPOSITORY` names the repo a runner checks out, so
1371313757
* when it equals the swept repo the disk is that repo. This
1371413758
* is the leg that answers on every real patrol run.
@@ -13722,7 +13766,8 @@ export function h57ScheduledWorkflowRed(entry, nowMs = Date.now()) {
1372213766
*/
1372313767
export function localCheckoutServes(sweepRepo, env = {}, originUrl = null) {
1372413768
const want = String(sweepRepo ?? '').trim();
13725-
const fromEnv = String(env?.GITHUB_REPOSITORY ?? '').trim();
13769+
// The knob outranks the runner leg: `GITHUB_REPOSITORY` names the tree the RUNNER checked out.
13770+
const fromEnv = resolveSweepCheckout(env).set ? '' : String(env?.GITHUB_REPOSITORY ?? '').trim();
1372613771
if (fromEnv) {
1372713772
return fromEnv === want
1372813773
? { serves: true, source: 'GITHUB_REPOSITORY', reason: null }
@@ -18674,8 +18719,8 @@ export function summaryClause(summary, key) {
1867418719
* - read, nothing found → says the holds were READ and name no tracked file
1867518720
* - read, rows → the index
1867618721
*
18677-
* @param {{ rows: Array<{issue: object, files: string[]}>, candidates?: number,
18678-
* probed?: number, tracked?: number|null }} [index]
18722+
* @param {{ rows: Array<{issue: object, files: string[]}>, candidates?: number, probed?: number,
18723+
* tracked?: number|null, checkoutRoot?: string|null, checkoutOrigin?: string|null }} [index]
1867918724
* @param {{ markdown?: boolean }} [options]
1868018725
*/
1868118726
export function renderTriggerIndex(index, { markdown = false } = {}) {
@@ -18684,6 +18729,10 @@ export function renderTriggerIndex(index, { markdown = false } = {}) {
1868418729
const probed = index.probed ?? 0;
1868518730
const candidates = index.candidates ?? 0;
1868618731
const read = `read on ${probed} of ${candidates} open \`pm:on-hold\` card(s)`;
18732+
// WHICH tree the oracle was read in (#19191) — printed knob or no knob, because a wrong-tree read
18733+
// is internally consistent and the tree it names is the only thing that distinguishes it.
18734+
const where = ` Read in ${index.checkoutRoot ? `\`${index.checkoutRoot}\`` : 'an unnamed tree'}` +
18735+
` (\`origin\` ${index.checkoutOrigin ? `\`${index.checkoutOrigin}\`` : 'unresolved'}).`;
1868718736
const head = markdown
1868818737
? ['### On-hold trigger-file index (H17)', '']
1868918738
: ['', 'On-hold trigger-file index (H17)'];
@@ -18692,7 +18741,7 @@ export function renderTriggerIndex(index, { markdown = false } = {}) {
1869218741
head.push(
1869318742
`⚠️ The tracked-file oracle (\`git ls-files\`) could not be read, so NO candidate path was ` +
1869418743
`validated and this index is EMPTY BY FAILURE, not by finding. Run the patrol from inside a ` +
18695-
`checkout. (${read}.)`,
18744+
`checkout. (${read}.)${where}`,
1869618745
);
1869718746
return head;
1869818747
}
@@ -18704,7 +18753,7 @@ export function renderTriggerIndex(index, { markdown = false } = {}) {
1870418753
`measured at 0-for-19 while it lived only as a remembered protocol step (#10034). Report-only: ` +
1870518754
`a card here is a hold in good standing, never a finding. Extraction is deterministic — every ` +
1870618755
`path shown is a tracked file; anything unverifiable was dropped rather than guessed, so this ` +
18707-
`list under-reports and never invents. (${read}; ${index.tracked} tracked file(s) in the oracle.)`;
18756+
`list under-reports and never invents. (${read}; ${index.tracked} tracked file(s) in the oracle.)${where}`;
1870818757
head.push(intro, '');
1870918758

1871018759
if (rows.length === 0) {
@@ -20740,6 +20789,10 @@ async function listIssues(label, stats = {}) {
2074020789
}
2074120790

2074220791
async function sweep(options = {}) {
20792+
// WHICH tree serves this board (#19191), BEFORE the probe: a foreign checkout reads nothing.
20793+
const originUrl = readOriginUrl();
20794+
const foreignTree = checkoutPrerequisite(OWNER_REPO, process.env, originUrl);
20795+
if (foreignTree) reportPrerequisiteNotMet(foreignTree);
2074320796
// Answered once, before any listing — so an unusable transport costs ONE
2074420797
// classified verdict instead of a raw HTTP status from whichever label page
2074520798
// happened to go first (`pm:dispatched`, in the failure #7412 recorded).
@@ -20936,6 +20989,8 @@ async function sweep(options = {}) {
2093620989
candidates: hold.candidates,
2093720990
probed: hold.probed,
2093820991
tracked: tracked ? tracked.size : null,
20992+
checkoutRoot: readRepoRoot(),
20993+
checkoutOrigin: originUrl,
2093920994
};
2094020995
// Instruction ④'s NOT-MEASURED population: H4's OWN rows, counted rather than
2094120996
// re-derived (one computation, two readers). It has to be read HERE because
@@ -22398,15 +22453,15 @@ export const SEEN_LABEL_PAGES = Object.freeze([
2239822453
*/
2239922454
function readRepoRoot() {
2240022455
try {
22401-
return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim() || null;
22456+
return gitRead(['rev-parse', '--show-toplevel']).trim() || null;
2240222457
} catch {
2240322458
return null;
2240422459
}
2240522460
}
2240622461

2240722462
function readOriginUrl() {
2240822463
try {
22409-
return execFileSync('git', ['remote', 'get-url', 'origin'], { encoding: 'utf8' }).trim() || null;
22464+
return gitRead(['remote', 'get-url', 'origin']).trim() || null;
2241022465
} catch {
2241122466
return null;
2241222467
}
@@ -29099,6 +29154,12 @@ async function selfTest() {
2909929154
const noOracle = renderMarkdown([], counts, { triggerIndex: { rows: idxRows, candidates: 79, probed: 79, tracked: null } });
2910029155
t('H17 no-oracle: says the index is empty BY FAILURE', noOracle.includes('EMPTY BY FAILURE, not by finding'), true);
2910129156
t('H17 no-oracle: …and renders no row, so nothing unvalidated leaks out', noOracle.includes('#8331'), false);
29157+
// The tree the oracle was read in (#19191) — named in BOTH oracle states, knob set or not.
29158+
const tree19191 = (over) => renderTriggerIndex({ ...triggerIdx, ...over }).join('\n');
29159+
t('#19191 footer: the H17 footer NAMES the tree the oracle was read in', renderMarkdown([], counts, { triggerIndex: { ...triggerIdx, checkoutRoot: '/home/user/objectui', checkoutOrigin: 'https://github.com/objectstack-ai/objectui' } }).includes('Read in `/home/user/objectui` (`origin` `https://github.com/objectstack-ai/objectui`)'), true);
29160+
t('#19191 footer: …beside the oracle size, so the two are read together', tree19191({ checkoutRoot: '/r', checkoutOrigin: 'o' }).includes('6360 tracked file(s) in the oracle.) Read in `/r`'), true);
29161+
t('#19191 footer: an unnamed tree says so rather than reading as this one', tree19191({}).includes('Read in an unnamed tree (`origin` unresolved).'), true);
29162+
t('#19191 footer: …and the EMPTY-BY-FAILURE branch names its tree too', tree19191({ tracked: null, checkoutRoot: '/r', checkoutOrigin: 'o' }).includes('EMPTY BY FAILURE, not by finding. Run the patrol from inside a checkout. (read on 79 of 79 open `pm:on-hold` card(s).) Read in `/r`'), true);
2910229163
// The partial-read gap is stated, never implied.
2910329164
t('H17 partial: a partial hold read says so', renderMarkdown([], counts, { triggerIndex: { rows: [], candidates: 79, probed: 12, tracked: 10 } }).includes('read on 12 of 79'), true);
2910429165
// Budget: the index is RESERVED, so a board noisy enough to truncate the
@@ -34144,6 +34205,24 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
3414434205
t('H57 checkout: a DIFFERENT origin refuses and names both repos', localCheckoutServes('o/r', {}, 'https://github.com/o/other').reason.includes('o/other'), true);
3414534206
t('H57 checkout: no origin at all refuses', localCheckoutServes('o/r', {}, null).serves, false);
3414634207

34208+
// The checkout knob (#19191) — WHICH tree the three local git reads take.
34209+
const SELF19191 = readFileSync(SELF_PATH, 'utf8');
34210+
t('#19191 knob: ONE git read site in the file, so a fourth reader cannot skip the cwd', SELF19191.split(['execFileSync', "('git'"].join('')).length - 1, 1);
34211+
t('#19191 knob: …and no `spawnSync` git read beside it', SELF19191.split(['spawnSync', "('git'"].join('')).length - 1, 0);
34212+
t('#19191 knob: …and that one site takes the knob as its `cwd`', /resolveSweepCheckout\(process\.env\)\.path[\s\S]{0,120}cwd,/.test(SELF19191), true);
34213+
t('#19191 knob: unset is the inherited cwd — no path is handed to git', resolveSweepCheckout({}).path, null);
34214+
t('#19191 knob: whitespace is unset too', resolveSweepCheckout({ PM_SWEEP_CHECKOUT: ' ' }).set, false);
34215+
t('#19191 knob: a path becomes the cwd every local git read takes', resolveSweepCheckout({ PM_SWEEP_CHECKOUT: '/home/user/objectui' }).path, '/home/user/objectui');
34216+
t('#19191 knob: the knob outranks GITHUB_REPOSITORY, which names the RUNNER\'s tree', localCheckoutServes('o/r', { PM_SWEEP_CHECKOUT: '/t', GITHUB_REPOSITORY: 'o/r' }, 'https://github.com/o/other').serves, false);
34217+
t('#19191 knob: …so a knob tree whose `origin` IS the board serves, whatever the runner says', localCheckoutServes('o/r', { PM_SWEEP_CHECKOUT: '/t', GITHUB_REPOSITORY: 'o/other' }, 'https://github.com/o/r').serves, true);
34218+
t('#19191 refusal: unset is no prerequisite at all — today\'s behaviour, unchanged', checkoutPrerequisite('o/r', {}, 'https://github.com/o/other'), null);
34219+
t('#19191 refusal: knob + matching origin passes, and the sweep runs', checkoutPrerequisite('o/r', { PM_SWEEP_CHECKOUT: '/t' }, 'https://github.com/o/r'), null);
34220+
const FOREIGN19191 = checkoutPrerequisite('o/r', { PM_SWEEP_CHECKOUT: '/t' }, 'https://github.com/o/other') ?? { headline: '', detail: [], fix: [] };
34221+
t('#19191 refusal: knob + mismatching origin REFUSES, naming the checkout it was given', String(FOREIGN19191.headline).includes('"/t"'), true);
34222+
t('#19191 refusal: …and names the repo that tree actually is', FOREIGN19191.detail.join(' ').includes('o/other'), true);
34223+
t('#19191 refusal: …and carries a fix, ⛔ never a fall back to cwd', String(FOREIGN19191.fix[1]).includes('unset it'), true);
34224+
t('#19191 refusal: a knob path with no `origin` at all refuses too', checkoutPrerequisite('o/r', { PM_SWEEP_CHECKOUT: '/nope' }, null)?.kind, 'checkout-does-not-serve');
34225+
3414734226
// Census and forwarding.
3414834227
t('H57 census: every count key rides the enumerated forwarding contract', ['scheduledDeclared', 'scheduledJudged', 'scheduledUnreadRuns', 'scheduledGating', 'scheduledInactive', 'scheduledInactiveNames', 'scheduledUnreadable', 'scheduledRequests', 'scheduledListingShort', 'scheduledUnresolved'].every((k) => SWEEP_COUNT_KEYS.includes(k)), true);
3414934228
const SUM57 = saidBy('h57Scheduled', summaryLine({ scheduledDeclared: 22, scheduledJudged: 7, scheduledUnreadRuns: 0, scheduledGating: 15, scheduledInactive: 0, scheduledUnreadable: 0, scheduledRequests: 8 }, 0));
@@ -36031,6 +36110,7 @@ export const USAGE = [
3603136110
'',
3603236111
'the board is named by the ENVIRONMENT — there is no --repo and no positional argument:',
3603336112
' PM_SWEEP_REPO `owner/name` to sweep; else GITHUB_REPOSITORY, else the built-in default',
36113+
' PM_SWEEP_CHECKOUT the checkout SERVING that board — `cwd` for the three local git reads; a foreign `origin` refuses (exit 3)',
3603436114
' PM_SWEEP_CLOSED_FLOOR YYYY-MM-DD floor for the closed-card pass',
3603536115
' GITHUB_TOKEN/GH_TOKEN the credential the sweep reads with',
3603636116
' NODE_OPTIONS=--use-env-proxy node reads its proxy flag at process START, so it goes there or',

0 commit comments

Comments
 (0)