Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Rank the half-state anchor trim by row family, and make every family legible by claude[bot] · Pull Request #14071 · objectstack-ai/objectstack · GitHub
Skip to content

Rank the half-state anchor trim by row family, and make every family legible - #14071

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility
Sep 1, 2026
Merged

Rank the half-state anchor trim by row family, and make every family legible#14071
os-sam merged 3 commits into
mainfrom
claude/issue-13947-patrol-family-visibility

Conversation

@claude

@claudeclaudeBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes#13947

Verified at HEAD 0e42227db (one file: scripts/pm/check-half-states.mjs, +574 / −6).

The defect, in triage's words

「族内按重要性裁,族外按运气裁」 — this file already carries a whole rank of per-family ceilings (BLOCKING_DEPENDENT_LIST_CAP, H19_TARGET_LIST_CAP, H20_BRANCH_LIST_CAP, H36_SAMPLE_PATHS, H37_MEMBER_LIST_CAP, H17_INDEX_ROW_CAP, H40_ROW_CAP), and every one of them answers which of this family's rows matter most. The body trim answered a different question — which rows happened to be laid out first — and dropped 157 of 231 findings on the measured run.

Premise re-checked on origin/main before writing a line, and it holds. The flat positional trim is still the live behaviour (renderMarkdown, the for loop over rows breaking on MARKDOWN_BODY_BUDGET). #13964's H40 arrived as a reserved section precisely to route around this, and its own header says so — that reservation and its unconditional summary clause are untouched here, and the #13947 cases it left behind still pass verbatim.

What lands

1. A row-family registry and an explicit priority tableHALF_STATE_FAMILY_BANDS (the five bands, each carrying the question it answers) and HALF_STATE_FAMILY_BAND (code to band), sitting together above the renderer. renderMarkdown's sort gains one key between the existing bands and the card number:

loud (P0-SUSPECT) -> UNJUDGED (#11218) -> family band (new) -> card number

The two existing reservations are untouched by design: an UNJUDGED row is a gap in what was read, and #11218's reservation must survive this change rather than be re-litigated by it. Within one family the issue-number order is preserved, so the list is still stable run to run and diffable in the anchor's edit history.

bandfamilieswhat the band answers
gateH31H35the row's subject is a GATE that may have been stripped or split — its absence reads as a green light
unregistered(none today)no band was declared for this code — severity unknown, so protected and flagged rather than trimmed
stallH4H12H16H19H20H26H27H28H32H33H36H38a card or PR whose forward motion is STOPPED or misrouted, and which nothing else will move
stateH1H2H3H7H8H9H10H13H18H21H23H24H25H29H30H34H37contradictory or half-written label/claim state on a live card — readable and repairable from the board
inventoryH5H6H11H14H15H22a census, a cache or residue — an inventory of a population rather than an alarm about one card

⚠️The band assignment is a semantic judgment this file did not previously hold. It is written as one greppable table beside the codes it ranks — not scattered through the predicates — precisely so the seat can argue with it; moving a code is a one-line edit plus a self-test case. The three anchors: gate is H31's own header (「闸门被剥不是红灯是放行」 — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence, and H35 reads the event behind that state); inventory is triage's own examples of the class gate rows outrank (H14 / H22 / H5); stall is the class where no later sweep frees the card.

2. An unconditional per-family ledgerfamilyLedger + renderFamilyLedger, a first-class section rendered below the findings and reserved out of the budget exactly as the H17 index, the H39 census and the H40 section are. Ordering alone cannot fix this card: it moves which rows are lost, never whether their loss is readable. The load-bearing half is the closing sentence, which resolves the H31 acceptance test in words:

  • a family with rows is in the table, with computed and rendered, whether or not the trim left any of it in the body;
  • a family that computed nothing is named in the Computed 0 row(s) this sweep line;
  • so zero rendered H31 rows is never ambiguous between the two readings.

An unregistered code is flagged in the table, called out beside it, and sorted just below the gate band — an unknown severity is protected from the trim rather than sacrificed to it (#4690's direction), and the flag makes that protection temporary by construction.

3. The trim keeps announcing itself. This is a legibility fix, not a silent-truncation fix. The omission notice, its wording, its run-log pointer and the three self-tests protecting it are unchanged; new cases assert the notice still fires on the severity-ordered body and beside the ledger.

4. A registry-coverage gate that cannot go stale.familyRegistryCoverage reads this file's own source for the codes the sweep actually pushes and compares them with the registry — the AGENTS.md source-scan discipline (a detector with no dependencies cannot itself fail to resolve). A family added without a band fails --self-test, rather than inheriting one silently.

Body-size arithmetic

The ledger's declared ceiling is FAMILY_LEDGER_WORST_CASE_BYTES = 6000:

componentworst case
heading + intro prose~1.1 KB, fixed
table rowsFAMILY_LEDGER_ROW_CAP (64) × ~48 B ≈ 3.1 KB
trim calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~26 B + prose
unregistered calloutFAMILY_LEDGER_CALLOUT_CAP (12) × ~8 B + prose
computed-0 lineregistry size (37) × ~8 B + prose

Built and measured, not claimed: 2,285 B with all 37 registered families carrying 999 rows each; 3,754 B with enough unregistered codes on top to reach the row cap. Both are pinned by self-test cases that construct the worst case. 6,000 is under an eighth of MARKDOWN_BODY_BUDGET (60,000), and because the ledger is reserved out of that budget the 5,536-byte headroom up to ISSUE_BODY_LIMIT (65,536) is untouched by it.

⚠️ The ceiling is what a reviewer can check by hand; it is not the guarantee. familyLedgerReservation computes the run-in-hand upper bound — rendered = 0 (longest callout) plus the exact digit slack, since 0 ≤ rendered ≤ computed bounds the column's width — and renderMarkdown subtracts it before laying out a single finding row. A self-test drives everyshown value from 0 to the row count and asserts the render fits the bound; on a 58-row fixture the bound is 4 bytes above the observed worst case.

Before / after on the live board

Measured by running the real sweep from this container (node scripts/pm/check-half-states.mjs, exit 0, 2026-09-01), then rendering that identical 247-finding corpus through origin/main's renderer and this branch's:

origin/mainthis branch
findings computed247247
rows rendered7347
rows omitted (announced)174200
families with rows in the body1712
families a reader can account for17 of 2222 of 22
gate-band rows rendered (H35, computed 2)0 of 22 of 2
body bytes59,39459,611

On origin/main five computed families — H1 (1), H13 (2), H23 (1), H35 (2), H36 (5) — rendered zero rows and were, from the page, indistinguishable from families that found nothing. H35 is a gate family: a gate label removed with no matching review-chain evidence, twice, invisible. On this branch every gate and stall family renders in full (H35 2/2, H4 5/5, H19 13/13, H20 2/2, H26 12/12, H28 1/1, H36 5/5, H38 2/2) and all 22 computed families carry an exact computed/rendered pair.

The honest trade, stated rather than buried: fewer rows fit (47 vs 73, because the surviving high-band rows are longer and the ledger costs ~2.5 KB), so the announced omission count rises. That is the card's own framing — the defect was never "too few rows", it was 「说了丢多少、没说丢的是谁」. What changes is that no finding is now lost unaccountably, and the rows that do survive are the ones whose absence reads as a green light.

Sequencing

Comment 5482734531's constraint is met: sections and families are first-class in the renderer. renderFamilyLedger is a section renderer of the same shape as renderDanglingReferences / renderTriggerIndex / renderClosedResidueCensus, composed into the same reserved block, so the clause-② live-sweep wiring can take its own section without inheriting the flat trim. That wiring is not in this PR (#13922 and #13944 are not addressed here).

Verification

Self-test: 1,951 cases before, 2,017 after (+66), node scripts/pm/check-half-states.mjs --self-test exit 0. Every pre-existing case passes unchanged.

Ablation — three legs, each proving the new cases can fail. Mutation confirmed on disk by occurrence count before each run; restore proved by blob hash equal to the HEAD blob and an empty git diff HEAD, with an absolute-path trap on EXIT/INT/TERM:

legmutationoccurrencesresult
Adelete the family-rank sort key from renderMarkdown2 → 14 cases red
Bdelete the ledger reservation from the budget accounting1 → 06 cases red (3 of them pre-existing budget cases)
Cdrop H31 from the registry1 → 06 cases red

Leg A also caught a real vacuity in a case I had written. Asserting that the gate row's indexOf is less than the inventory row's passes when the row was trimmed away entirely, because indexOf answers minus one and minus one is less than everything — so the case stayed green on exactly the regression it existed to catch. Repaired in 0e42227db by requiring the index to be positive as well, with the reason recorded beside it.

Gate union — derived at HEAD with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 path-derived + 2 convention-triggered, because this diff edits a gate script). Exit codes captured by redirect before any pipe:

check:agent-test-spelling 0 check:pnpm-filter-targets 0
check:bash32-floor 0 check:watch-hint-literal 0
check:cli-command-ids 0 check-ci-filter-parity.mjs 0
check:cross-package-test-inputs 0 check-closing-keyword-parity.mjs 0
check:entry-guard 0 check-cross-package-test-inputs.mjs 0
check:parse-guard 0 check-shard-attestation.mjs 0
check:partof-closing-keyword 0 bare-root-worklist.mjs --self-test 0
check:pm-half-states 0 check:pm-dispatch-gates 0
check-nul-bytes.mjs 0 check:pm-clause2-carriers 0
check:stall-guard-headroom 0 check-half-states.mjs (live sweep) 0

Two gates in that family are NOT MEASURED locally, by their own printed verdicts, not by my reading:

  • node scripts/check-partof-closing-keyword.mjs exits 2 with NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict. Its measurable half, pnpm check:partof-closing-keyword (28 cases), is green.
  • node scripts/check-test-completeness.mjs exits 3 with the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix.

Repo-wide ESLint ran in full — no narrowing to declare.pnpm lint (eslint . --no-inline-config) exit 0, 70 s under the shared verify lock.

check:pm-clause2-carriers (64 cases) and check:stall-guard-headroom (32 assertions) are run explicitly because they import from the edited file and CI schedules them on every PR.

Not in scope

  • No changeset: scripts-only diff, publishes nothing from any package — skip-changeset applied additively and read back.
  • No workflow edit. .github/workflows/half-state-patrol.yml is untouched; the renderer change did not require it.
  • No per-family floor (guaranteeing every computed family at least one rendered row). It would strengthen legibility further, but it is a third mechanism the ruling did not ask for, and the ledger already resolves the acceptance test. Recorded here for the seat rather than smuggled in.

Generated by Claude Code

@os-samClaude

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 5488076261. Preconditions verified at arm time on head 0e42227db: all 33 checks completed with zero failures (Lint & Repo Gates, TypeScript Type Check, and the real Live half-state sweep 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 merge fires on GitHub's side; the card's label hygiene follows the MERGED event.


Generated by Claude Code

@os-sam
os-sam added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit afbf271Sep 1, 2026
35 checks passed
@os-sam
os-sam deleted the claude/issue-13947-patrol-family-visibility branch September 1, 2026 03:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude