Repository navigation
Commit 63fc947
fix(pm): board-snapshot reconciles a disagreeing census and settles its cursor, so the patrol is green again (#27769)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D2xUqzuBY2k9Vz3LKcMkDdW)_
Fixes #27726
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:看板快照脚本在计数对不上时,先逐号核对再下结论;并把下次读取的起点往回留 15 分钟。
- 为什么改:巡检从 10-26 起每次都红。实测是 1 张转仓卡和 2 张「GitHub 列表返回旧数据」漏掉的关单,存档永远以为它们还开着。
- 风险与回滚:只动
`scripts/pm/board-snapshot.mjs`,不发布任何包。被销毁(404)的卡照样报红,没有放宽。回滚就是 revert
这一个提交。
- 先看:`scripts/pm/board-snapshot.mjs` 里的 `reconcileOpenSet`(核对逻辑),然后是
`settledCursor` 和 `numberReadVerdict`。
- 两分钟验证:`node scripts/pm/board-snapshot.mjs --self-test`,预期最后一行是 `OK …
191 cases pass across 14 batteries`。
- 需要你做的:无,席位落地。
## What changes
`scripts/pm/board-snapshot.mjs` only.
- **The cursor settles.** A live walk's stored cursor (the delta's, or
the incremental walk's) is now no later than `LISTING_SETTLE_MS` (15
min) before the instant its last listing page was read
(`settledCursor`). The next run re-reads that trailing window. `since`
is inclusive, so this is an over-read that writes identical bytes unless
a row really changed. On a quiet board the cursor is already older and
does not move.
- **A disagreeing census is reconciled before it is reported**
(`reconcileOpenSet`). When the first count check reads SHORTFALL or
SURPLUS, the open listing (`openSetPath`, creation order) is compared
with the archive's open records (`reconcileCandidates`; gone-ledger
numbers excluded, capped at `RECONCILE_MAX_NUMBERS`). Each number the
two disagree on is read on its own, with redirects not followed
(`numberReadVerdict`):
- 200: archived again.
- A 301 to an issue in another repository: written onto the archived
record as `transferred_to`. The record stays, and the census counts it
apart (`counts.issues_transferred`).
- 404 or 410: left exactly as archived. It is still a SURPLUS, still
exit 2, and the summary names it with its `--restore`.
The board's arithmetic is then read again and the census retaken. The
reported verdict is that second reading. The first one is kept in
`run.reconcile` and printed on the `reconcile` line.
- The read helper gains a `reply` mode for the per-number read, and
stays the file's only `fetch`. The header gains § "A listing that lags,
and a card that moved", and the SURPLUS wording now says that a transfer
is recorded rather than ledgered.
## Why — measured
Every scheduled snapshot since 2026-10-26T15:11Z reported SURPLUS 1 to
3, read from the 22 manifests on `board-archive` (ok through
10-26T08:38Z, then 1, 2, 3). I diffed the archive tip (run 40947805554,
20:21Z: archived 323, gone 62, board 258) against the API. Exactly three
open issues were archived that the board does not hold:
| number | what the API says | why the archive kept it open |
|---|---|---|
| #25896 | 301 → objectstack-ai/hotcrm#2307 | the listing never returns
a transferred number |
| #26084 | closed 2026-10-27T02:32:52Z | the 02:33Z run's listing served
its pre-close row (updated 01:52) while serving rows updated 02:33:14
and 02:33:18 fresh. The cursor became 02:33:18, so the close was outside
every later window. |
| #26638 | closed 2026-10-28T02:25:16Z, touched 02:26:13 | same, in the
02:26Z run, whose cursor was 02:26:16 |
So it was not a count race. Both candidates on the card are partly true.
It is a persistent drift with two classes: a transferred number, and a
row the listing served stale inside the read window, after which the
newest-row cursor skipped it for good. The same lag had left four closed
PRs archived open (#20052, #20072, #20074, #22635). The issue-only count
check could not see those.
## Tests
New battery *the lagging listing and the moved card* (22 cases, floor 13
→ 14 batteries) in `--self-test`. The fake board gains
`lagging`/`lagListings` (serves a row's pre-update version), `moved`
(301) and `destroyed` (404):
- **Today's red, reproduced:** a stale close plus a transfer on a level
archive. The first census reads SURPLUS 2, and after reconcile the check
reads ok. The transferred record is kept whole with `transferred_to`.
The board is read twice, and the second read comes after the repair. The
next run reconciles nothing and never reads the moved number again.
- **The settle window alone:** a lagged relabel moves no count, so no
reconcile runs. It is caught by the next run only because the stored
cursor settled.
- **⛔ Not a tolerance:** a 404 number stays archived open, the check
stays SURPLUS and the run exits 2, naming `--restore=301`. A gone-ledger
number is never read. A blank reconcile listing compares nothing and
reads no number.
- Pure helpers: `settledCursor`, `reconcileCandidates`,
`numberReadVerdict` (cross-repo 301, same-repo 301, 404/410, 5xx) and
`openSetPath`.
**Ablation:** with the reconcile disabled, 9 cases go red. With the
settle disabled, 2 cases go red. Restored, all 191 pass.
## Acceptance notes
- The five archived-open PRs that answer 404 (#19374, #19471, #19478,
#19538, #19540) are destroyed records outside the issue count check. A
reconcile reads them each time it runs (5 requests) and names them as
destroyed. Nothing quiets them except the gone ledger, which is
unchanged.
- The earlier transfers recorded by hand in `gone.json` stay there and
are excluded from the reconcile, so nothing is counted twice.
- Acceptance proper is the next scheduled `Fleet Patrols` snapshot run
going green. The live reading below predicts it.
## Verification
<details><summary>Live run of this head against a scratch copy of
board-archive (read-only against GitHub, wrote only the copy)</summary>
```
archive 25639 record(s): 321 open / 12026 closed issue(s) / 1 transferred, 22 open / 13269 closed pull request(s)
reconcile the first census disagreed (SURPLUS of 3: archived 324, board 259), so it was reconciled before the verdict below:
the open listing carried 276 number(s); 13 disagreed with the archive and were read on their own.
archived again: #20052 closed, #20072 closed, #20074 closed, #22635 closed, #26084 closed, #26638 closed, #27751 open
transferred: #25896 → objectstack-ai/hotcrm#2307
destroyed: #19374 (404/410 — --restore=19374), #19471 …, #19478 …, #19538 …, #19540 …
The board's arithmetic was then read again; the count check below is that second reading.
count check ok — 321 open issue(s) archived, board says 259 (open_issues_count 276 minus 17 open pull request(s)).
freshness fresh — this run confirmed the archive against the live board.
EXIT=0
```
A second run straight after: no reconcile, `count check ok`, 34 numbers
in 47 requests (the settle window's re-read), EXIT=0.
</details>
<details><summary>dispatch-gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands` derives 38 families. All
38 were run with exit codes captured before any pipe. Nine first exited
3 (`yaml`/`@changesets/cli` missing in a fresh worktree) and were re-run
after `pnpm install --frozen-lockfile`, exit 0. `--ran` reconcile: `38
derived famil(ies) accounted for — 38 run, 0 NOT-MEASURED`. Changed
lines 501 (+471 / −30), under the 3000 threshold.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (eslint on the changed
file, exit 0; the full run is CI's).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset — `scripts/pm/**`
publishes nothing; `skip-changeset`.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cyJuqtXc5LcmkT6E4D
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 684f2f6 commit 63fc947
1 file changed
Lines changed: 665 additions & 33 deletions
0 commit comments