Skip to content

fix(pm): fold H22 closed-card pm:* residue into the H39 census as a count - #14101

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14072-h22-census-fold
Sep 1, 2026
Merged

fix(pm): fold H22 closed-card pm:* residue into the H39 census as a count#14101
os-sam merged 1 commit into
mainfrom
claude/issue-14072-h22-census-fold

Conversation

@claude

@claude claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #14072

One rendered patrol body was telling a reader two incompatible things about one population: the H39 census clause said closed-card pm:* residue is archive owed no cleanup, while H22 filed a finding row per member of that same population — 94 of the sweep's 247 rows on the filing card's measurement, 88 of 227 on this container's own run today. This PR folds H22 into the census as a COUNT, which is the card's first form and triage's third-tier adjudication.

Premise step — the mandatory one, and it HOLDS

The adjudication made the fold conditional on a falsifiable premise: that 批 #13 was ruled on the REASON (readers scope pm:* queries to open cards) rather than on the then-enumerated batch of stale archives, and that it reserved no fresh-residue window. I read the ruling text on the decision card that produced the census clause, #13605, plus its two census neighbours #13526 and #13901, before writing any code.

Maintainer ruling, 2026-08-31, 第 6 场总监席决裁批 #13, verbatim and untranslated:

13605 已关卡 为什么要清理。普查时不应该只看open的卡片吗,其他同意

The recorded operative reasoning on the same thread, also verbatim:

裁定:⛔ 不做批量清扫(选项 A/C 的 ~3,000/555 次标签写全部否决)。已关卡上的 pm:* 残留由读者侧消化:凡普查、巡检、候选查询、座位互斥读,一律强制 state:open 过滤——过滤到位,残留即无害

四维归位:裁定把 ② 轴的读法纠正了——「标签即状态机」的语义只须对open 集合成立,closed 卡是档案不是状态

Verdict: the premise HOLDS, on three independent readings of that text:

  1. It is reason-based, not enumeration-scoped. 「普查时不应该只看open的卡片吗」 is a question about census METHOD, and the recorded 裁定 generalises it to a class of readers — 「凡普查、巡检、候选查询、座位互斥读,一律强制 state:open 过滤」. 「巡检」 is the patrol itself, and 「一律」 admits no exception.
  2. The category claim carries no time term. 「closed 卡是档案不是状态」 is stated about closed cards as a class. A card closed two hours ago is a closed card.
  3. Nothing reserves a fresh window. The ruling's item ③ keeps close-time hygiene as a convention at zero incremental cost, and in the same breath refuses the write H22's row prescribed: 「⛔ 不为存量补写」. Every H22 row asked a seat to strip labels from an already-closed card, which is that write. Item ③ preserves a HABIT, not a finding family.

The earlier round on #13605 left this open deliberately — its ACCEPT recorded the H22 question for a veto window and said a one-line follow-up on its own card was the right route. This is that card.

H22's own counter-argument is answered, not deleted

H22's section header carried an argument that predates the ruling and was undefeated on its own terms. It is kept WORD FOR WORD in the file, and the ruling's effect on it is now stated in the same header rather than left for a reader to infer:

the counterargument — that labels on a closed card are historical metadata — does not dispose of it: pm:dispatched is not descriptive, it is a claim of in-flight-ness … If that is a defect at 09:00 while the card is open and not a defect at 09:01 once it closes, the rule is about the board's tidiness rather than about the duty

The header now continues:

the answer the counter-argument asked for, stated plainly rather than left for a reader to infer: YES — the ruling's effect is precisely that this is about the board's TIDINESS rather than about the duty, AND that tidiness is NOT OWED. The argument was right that the two cases differ only in tidiness; it assumed tidiness was the thing worth a row, and the ruling says it is not. That is a defeat on the merits, not a change of subject, so the argument stays here with its defeat attached instead of being deleted — the next author is owed the whole exchange, not the winning half.

It also records why the 3-day window could not survive as a row boundary: the ruling's reason reaches a two-hour-old residue exactly as it reaches an August one, so the window was cutting one archive into a part that got ninety-four rows and a part that got a number.

⛔ Not claimed

The H22 predicate was never wrong. All 94 rows were correctly computed, and the classifier's SELECTION is unchanged — closed gate, dated floor, PM_RESIDUE_LABELS all decide exactly the same cards, and the original selection cases are kept in the suite, re-expressed against the labels. What changed is the DISPOSITION of a positive verdict.

What the diff does

  • h22ClosedCardPmResidue returns the residue LABELS instead of a finding sentence. The imperative it used to return prescribed "Strip the pm:* state label(s)" — the write 批 [WIP] Add Chinese version of the documentation #13 refused by name — so it could not be left in the file as prose nobody renders. A self-test case pins that no strip imperative can survive inside the classifier.
  • New h22FreshResidueTally counts carriers and the per-label breakdown over the closed cards the sweep ALREADY holds. Zero extra requests. It carries read beside cards so a clean pass and an unread pass stay different facts (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690).
  • renderClosedResidueCensus takes the tally and renders it as one clause of the SAME sentence. Still one line, still informational, still no glyph and no imperative. It renders even when the census leg failed, because the two readings come from two passes and dropping one that WAS taken is check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 inside the section that exists to refuse it.
  • H22 leaves the row-family registry entirely — the composition question the renderer that landed tonight poses. It joins H17/H39/H40 as a reserved-section leg with no band, because a band is the trim's answer to "how long does this family's ROW survive" and a family that emits no row has no such question. familyRegistryCoverage pins it from both sides: missing catches a restored push with no band, extra catches a restored band with no push. That is a stronger guarantee than the inventory band it gave up, where it was first in line to be trimmed.
  • The summary line's H22 clause states the disposition, so a reader seeing an 8-page closed pass and no rows is not left guessing which fact that is.
  • The closed-issue read STAYS. It feeds H37's fold drift and H40's corpus, and it is what the fold counts.

Live before/after, real board data, this container

Two full markdown sweeps, node scripts/pm/check-half-states.mjs --format=markdown, both exit 0 — BEFORE on origin/main at dda969cd7 in a comparison worktree, AFTER on this branch at 649d18410.

BEFORE AFTER
half-states computed 230 134
rows rendered in the body 36 35
rows omitted by the trim 194 99
families with rows omitted 16 15
body length (characters) 59,682 58,349
H22 ledger row inventory / 90 computed / 0 rendered absent

⚠️ The "what newly fits" effect did NOT materialise, and the reason is the card's own prediction. The dispatch expected ~94 rows leaving the flat budget to let other rows in. It does not, because the severity ordering that landed with the family-visibility renderer already sorts inventory last: H22's 90 computed rows rendered 0 before this change, so removing them frees nothing the trim was giving to anyone else. The ±1 rendered-row difference above is run-to-run board drift (H2 fell 35 to 31 and H8 5 to 3 between the two sweeps), not an effect of the fold. What the fold actually removes is 96 computed rows — 41.7% of this run's corpus — one 90-row ledger entry that rendered zero, one family from the trim callout, and the contradiction the card filed.

The census line, before and after, rendered live:

BEFORE:

Closed-card pm:* residue (H39 census, informational): pm:dispatched 2075, pm:queue 850, pm:blocking 29, pm:blocked 20, pm:on-hold 13, pm:awaiting-maintainer 1, oldest closed 2026-08-02; 555 carrying both pm:queue and pm:dispatched. Archive, not state — no cleanup is owed and none is planned (ruled 2026-08-31, 批 #13); readers scope pm:* queries to open cards.

AFTER:

Closed-card pm:* residue (H39 census, informational): pm:dispatched 2075, pm:queue 850, pm:blocking 29, pm:blocked 20, pm:on-hold 13, pm:awaiting-maintainer 1, oldest closed 2026-08-02; 555 carrying both pm:queue and pm:dispatched. H22's 3-day closure window, read separately, holds 88 of 399 closed card(s) still carrying residue (pm:dispatched 79, pm:queue 5, pm:blocked 2, pm:blocking 2). Archive, not state — no cleanup is owed and none is planned (ruled 2026-08-31, 批 #13); readers scope pm:* queries to open cards, and that reason reaches a residue two hours old exactly as it reaches one from August, which is why the window above is counted here rather than filed as rows (#14072).

The 88 cards that used to be 88 rows are the same 88 cards, now a number in the sentence that explains why they are not rows.

Self-test

node scripts/pm/check-half-states.mjs --self-test — BEFORE 2017 cases pass exit 0 (measured on origin/main at dda969cd7 in a comparison worktree); AFTER 2062 cases pass exit 0. +45 cases.

Ablation — three legs, each proven on disk, each restore blob-proven

The implementation was COMMITTED first, so every restore had a real commit to return to. Each leg proved the mutation landed by counting the injected spelling against the original one (never a bare --stat, never the editor's exit code), restored with git checkout HEAD -- ABSOLUTE_PATH (never a bare git checkout -- path, which reads the index a checkout-from-ref has already written), and proved the restore by blob-hash equality against the HEAD blob plus an empty git diff HEAD — never by an exit code. The harness carried a trap on EXIT/INT/TERM. No rebuild leg applies: this is a standalone .mjs run directly by node and resolved by path, with no dist/ and no package build between the source and the self-test.

leg mutation on-disk proof result
A re-file H22 as a finding row in the sweep injected marker x2, original x1 2 of 2062 red, exit 1 — every emitted family carries a band names H22; the sweep pushes no H22 row any more flips true
B drop the fold clause from the census render injected x1, original x2 4 of 2062 red, exit 1 — the fresh-edge count vanishes from the sentence, from the failed-census path, from the absent-census path and from the body
C put H22 back in the band registry injected x1, original x1 5 of 2062 red, exit 1 — no band names a family the sweep never emits names H22, the SECTION-family pin flips, and the ledger's folded-family pin flips

Control before and after the run: blob d01cf419f930cd0f863f5bcd299fe75f03d5cbef equals the HEAD blob, git diff HEAD empty, self-test 2062 cases pass exit 0.

Gates

Derived from the real change set after the last commit — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, exit 0, at 649d18410 (the script reads the merge-base change set itself; no hand-built path list). 19 derived. Every exit code captured by redirecting to a file BEFORE reading it, never through a pipe.

17 exit 0: check-ci-filter-parity · check-closing-keyword-parity · check-cross-package-test-inputs (both spellings) · check-shard-attestation · bare-root-worklist --self-test · check:pm-half-states (2062 cases pass) · check:pm-dispatch-gates (1090 cases pass) · check:nul-bytes (75 self-test assertions, then 7,702 text files, no raw control bytes) · check:agent-test-spelling · check:bash32-floor · check:cli-command-ids · check:entry-guard · check:parse-guard · check:partof-closing-keyword --self-test (28 cases) · check:pnpm-filter-targets · check:watch-hint-literal.

2 NOT MEASURED, each self-classifying, neither a red: node scripts/check-partof-closing-keyword.mjs exits 2 NOT WIRED with no PR body in the environment — its pnpm spelling is the --self-test above and exits 0, and this body was checked against the predicate separately; node scripts/check-test-completeness.mjs exits 3 and its own text instructs the local family runner to record NOT MEASURED.

Plus, beyond the derived family: pnpm lint — repo-wide eslint . --no-inline-config, NO narrowing — exit 0 in 65s under the shared verify lock, and a direct control-byte grep over the edited file, empty. The live sweep itself ran three times from this container, exit 0 each time.

The edited file is a gate script, so its own suite is owed beyond the derived family: it has no *.test.ts anywhere in the tree (checked by git grep for its filename) — its suite IS the --self-test above, run at every stage.

Scope

One file, scripts/pm/check-half-states.mjs. No workflow change was required. Scripts-only, publishing nothing from any package, so skip-changeset is applied and no changeset is added.

Generated by Claude Code


Generated by Claude Code

…ount

Ruled 2026-08-31 (批 #13): closed-card pm:* residue is archive, not state,
made harmless reader-side by open-scoped queries. The rendered patrol body
was saying both things at once — the census clause said no cleanup is owed
while H22 filed a row per member of that same population (94 of a 247-row
sweep, 38%).

H22 keeps its predicate, window and floor; what changes is the disposition
of a positive verdict: it becomes a count in the reserved census section
instead of a finding row. It therefore leaves HALF_STATE_FAMILY_BAND, the
same as the other reserved-section legs (H17/H39/H40), and the coverage
scan pins that in both directions.

The predicate was never wrong and nothing here claims otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2

os-sam commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Provenance — flipped ready and armed auto-merge by the dispatching seat (skills-lane PM, session session_01Msg17tAHJ3jVTYFgHydCm2), citing the in-seat review PASS of record: the ACCEPT on this PR's card, comment 5489270459. Preconditions verified at arm time on head 649d18410: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep over the folded renderer among them — every check, not the required subset); non-governed pure-code surface (scripts/pm/** only), so the in-seat landing path applies. Armed ≠ landed; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit b8c4762 Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-14072-h22-census-fold branch September 1, 2026 05:53
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

2 participants