Commit 801415a
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
961 | 961 | | |
962 | 962 | | |
963 | 963 | | |
964 | | - | |
965 | | - | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
966 | 968 | | |
967 | 969 | | |
968 | 970 | | |
| |||
1048 | 1050 | | |
1049 | 1051 | | |
1050 | 1052 | | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
1051 | 1097 | | |
1052 | 1098 | | |
1053 | 1099 | | |
| |||
4563 | 4609 | | |
4564 | 4610 | | |
4565 | 4611 | | |
4566 | | - | |
4567 | | - | |
4568 | | - | |
4569 | | - | |
| 4612 | + | |
4570 | 4613 | | |
4571 | 4614 | | |
4572 | 4615 | | |
| |||
13709 | 13752 | | |
13710 | 13753 | | |
13711 | 13754 | | |
| 13755 | + | |
13712 | 13756 | | |
13713 | 13757 | | |
13714 | 13758 | | |
| |||
13722 | 13766 | | |
13723 | 13767 | | |
13724 | 13768 | | |
13725 | | - | |
| 13769 | + | |
| 13770 | + | |
13726 | 13771 | | |
13727 | 13772 | | |
13728 | 13773 | | |
| |||
18674 | 18719 | | |
18675 | 18720 | | |
18676 | 18721 | | |
18677 | | - | |
18678 | | - | |
| 18722 | + | |
| 18723 | + | |
18679 | 18724 | | |
18680 | 18725 | | |
18681 | 18726 | | |
| |||
18684 | 18729 | | |
18685 | 18730 | | |
18686 | 18731 | | |
| 18732 | + | |
| 18733 | + | |
| 18734 | + | |
| 18735 | + | |
18687 | 18736 | | |
18688 | 18737 | | |
18689 | 18738 | | |
| |||
18692 | 18741 | | |
18693 | 18742 | | |
18694 | 18743 | | |
18695 | | - | |
| 18744 | + | |
18696 | 18745 | | |
18697 | 18746 | | |
18698 | 18747 | | |
| |||
18704 | 18753 | | |
18705 | 18754 | | |
18706 | 18755 | | |
18707 | | - | |
| 18756 | + | |
18708 | 18757 | | |
18709 | 18758 | | |
18710 | 18759 | | |
| |||
20740 | 20789 | | |
20741 | 20790 | | |
20742 | 20791 | | |
| 20792 | + | |
| 20793 | + | |
| 20794 | + | |
| 20795 | + | |
20743 | 20796 | | |
20744 | 20797 | | |
20745 | 20798 | | |
| |||
20936 | 20989 | | |
20937 | 20990 | | |
20938 | 20991 | | |
| 20992 | + | |
| 20993 | + | |
20939 | 20994 | | |
20940 | 20995 | | |
20941 | 20996 | | |
| |||
22398 | 22453 | | |
22399 | 22454 | | |
22400 | 22455 | | |
22401 | | - | |
| 22456 | + | |
22402 | 22457 | | |
22403 | 22458 | | |
22404 | 22459 | | |
22405 | 22460 | | |
22406 | 22461 | | |
22407 | 22462 | | |
22408 | 22463 | | |
22409 | | - | |
| 22464 | + | |
22410 | 22465 | | |
22411 | 22466 | | |
22412 | 22467 | | |
| |||
29099 | 29154 | | |
29100 | 29155 | | |
29101 | 29156 | | |
| 29157 | + | |
| 29158 | + | |
| 29159 | + | |
| 29160 | + | |
| 29161 | + | |
| 29162 | + | |
29102 | 29163 | | |
29103 | 29164 | | |
29104 | 29165 | | |
| |||
34144 | 34205 | | |
34145 | 34206 | | |
34146 | 34207 | | |
| 34208 | + | |
| 34209 | + | |
| 34210 | + | |
| 34211 | + | |
| 34212 | + | |
| 34213 | + | |
| 34214 | + | |
| 34215 | + | |
| 34216 | + | |
| 34217 | + | |
| 34218 | + | |
| 34219 | + | |
| 34220 | + | |
| 34221 | + | |
| 34222 | + | |
| 34223 | + | |
| 34224 | + | |
| 34225 | + | |
34147 | 34226 | | |
34148 | 34227 | | |
34149 | 34228 | | |
| |||
36031 | 36110 | | |
36032 | 36111 | | |
36033 | 36112 | | |
| 36113 | + | |
36034 | 36114 | | |
36035 | 36115 | | |
36036 | 36116 | | |
| |||
0 commit comments