Skip to content

fix(pm): board-snapshot reconciles a disagreeing census and settles its cursor, so the patrol is green again - #27769

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-ux77w6
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-ux77w6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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

Verification

Live run of this head against a scratch copy of board-archive (read-only against GitHub, wrote only the copy)
  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.

dispatch-gates

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.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (eslint on the changed file, exit 0; the full run is CI's).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset — scripts/pm/** publishes nothing; skip-changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cyJuqtXc5LcmkT6E4D

…ts cursor, so the patrol is green again

Every scheduled snapshot since 2026-10-26T15:11Z failed its count check
with a SURPLUS of 1 to 3. Measured against the API: the archive held three
open issues the board does not. #25896 was transferred to
objectstack-ai/hotcrm#2307, and the listing never returns a transferred
number. #26084 and #26638 were closed seconds before the run's cursor: the
listing served their pre-close rows while serving later rows fresh, and the
cursor taken from the newest row skipped them for good. The same lag left
four closed pull requests archived open.

- A live walk's cursor is stored no later than LISTING_SETTLE_MS (15 min)
  before its last listing read, so the next run re-reads the window the
  listing may not have caught up on.
- A census that disagrees with the board is reconciled before it is
  reported: the open listing is compared with the archive's open records,
  each disagreeing number is read on its own (redirects not followed), a
  live record is archived again, a 301 to another repository is written
  onto the record as transferred_to and counted apart, and a 404/410 is
  left as archived so it still reds the run. The board's arithmetic is then
  read again and the census retaken.

Self-test: a new battery reproduces today's red (stale close + transfer),
the settle window alone, and a destroyed number that stays red; each fix
ablated turns its cases red.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyJuqtXc5LcmkT6E4D
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 29, 2026
…cile, and reconciles every verdict

Seat review r1 of the reconcile found paths where a real drift could still
read green. Each is now pinned in the self-test and ablated:

- An issue whose own read answers 404/410 and that no ledger answers for
  keeps the run red whatever the counts say (withDestroyedIssues; verdict
  `destroyed` when the arithmetic agrees).
- The reconcile runs on an agreeing census too, since errors of opposite
  sign cancel in the counts and never in the records.
- A reconcile cut short by its budget reports the census from before it
  against the board read before it, keeps what it read, and says CUT SHORT.
  It runs under a reserve (BOARD_READ_RESERVE) so the second board read fits.
- A gone-ledger number is subtracted only while the census counts it open,
  so a ledgered transfer cannot absorb a real surplus.
- The fake fetch follows redirects unless told manual, and a 200 that names
  another number or repository is unreadable, never archived.
- A 5xx on one number's read is that number's verdict, not the run's.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyJuqtXc5LcmkT6E4D
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat review r1 addressed at head 1150f2c. Each item is pinned in --self-test (now 200 cases, the new battery 31) and ablated:

r1 item fix ablation
1 destroyed issue cancelled by a new card withDestroyedIssues: an unledgered issue whose own read answers 404/410 keeps the run red; verdict destroyed when the counts agree 2 cases red
2 cut-short reconcile vs first board a cut-short reconcile reports the FIRST census against the FIRST board read; the reconcile runs under BOARD_READ_RESERVE (5) so the second read fits repaired census vs first board: 1 case red
3 cut-short drops the record the outcome (re-read / transferred / destroyed) is kept and the summary says CUT SHORT 3 cases red
4 gone subtracted twice only gone numbers the census counted open are subtracted 1 case red
5 fake fetch ignores redirect the stub follows redirects unless manual, the destination serves its own card; a 200 naming another number or repo is unreadable dropping redirect: 'manual': 4 cases red
6 5xx unreachable reply mode returns 5xx as the number's verdict the self-test throws (red)
7 (optional, done) every verdict is reconciled, an agreeing one too 3 cases red

Live, against a fresh copy of board-archive: run 1 first census SURPLUS 3 → reconciled → ok, exit 0. Run 2: first census agreed; 5 destroyed pull requests named, issue verdict ok, exit 0. dispatch-gates --ran: 38 derived, 38 run, 0 NOT-MEASURED.

Note on acceptance: Fixes #27726 closes the card on merge, before the acceptance run. The seat reads the first scheduled Fleet Patrols snapshot after merge.


Generated by Claude Code

…ly, and names the census a cut-short run reports

Seat review r2: the census's open-number set held open pull requests too,
so a ledgered deleted pull request could cancel part of a real issue
surplus. The ledger is now subtracted only for numbers counted as open
issues (censusArchive openIssueNumbers), pinned and ablated. On a cut-short
reconcile the archive line now says it is the census after the repairs,
while the count check is the census from before them.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyJuqtXc5LcmkT6E4D
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

维护者速读

席位意见:ACCEPT。席位复核三轮(第一轮 6 条、第二轮 1 条,均已改正并带自测钉子与消融),CI 全绿。

席位核对

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 00:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 30, 2026 00:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 63fc947 Oct 30, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ux77w6 branch October 30, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant