Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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" + '
H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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('^' + ".*" + ' H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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('^' + ".*" + ' H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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" + ' H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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('^' + ".*" + ' H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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('^' + ".*" + ' H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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); } })(); })(); H40: dangling-reference patrol over the live board's `#` references (report-only) by os-sam · Pull Request #13964 · objectstack-ai/objectstack · GitHub
Skip to content

H40: dangling-reference patrol over the live board's # references (report-only) - #13964

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol
Aug 31, 2026
Merged

H40: dangling-reference patrol over the live board's # references (report-only)#13964
os-sam merged 1 commit into
mainfrom
claude/issue-13634-dangling-reference-patrol

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#13634 — A-half only, per triage's narrowing (comment 5477064578). The
ruling-durability half was split out and is neither answered nor waited on here.

Adds H40, a report-only row family in scripts/pm/check-half-states.mjs: it
sweeps the live board for # references into this repo, resolves each DISTINCT
number once, and reports the ones that fail to resolve together with the cards
that point at them.

The four hard requirements, and where each one lives

① Sweep bodies and comments; resolve each distinct number once; report failures
with their referrers.
referencedNumbers extracts, buildReferenceIndex maps
each distinct number to every referrer that names it, h40ResolutionPlan decides
what gets asked, and the report names each dangling number with its referrers
(capped at H40_REFERRER_LIST_CAP). One resolution per number, cached across the
whole sweep — numbers H19 already resolved are reused rather than re-fetched.

② closed is not unreachable.h40Verdict has four states, not two:

observationverdict
HTTP 200, open or closedresolvable — silent, forever
HTTP 404 and only 404unresolvable — the finding
403 / 429 / 5xx / transportunjudged — reported as unjudged, never as a vanished card
budget exhaustednot attempted — counted and announced, never read as clean

Measured control, 2026-08-31, one session and one token: #13398#13399#13178
#12924#13456 all answered HTTP 404, while #13324#13571 (closed) and #13592 (a
closed PR) resolved normally alongside four open cards. Both verdicts present on
the same instrument, so the failures are facts about those numbers rather than
about the channel. Widening unresolvable past 404 is the one change that would
turn this into an accusation generator, and the self-test pins it in both
directions.

③ Report-only. No reference is rewritten, no card is closed, no label is
written, and ⛔ no cause is asserted for any unresolvable number — the row hands a
human two facts and stops. The finding text says so, and so does the summary line.

④ Cost control. The referrer corpus costs zero extra requests: it is the
open cards and PRs this sweep already holds plus the comment threads already in
its cache. Resolutions are bounded by H40_RESOLUTION_BUDGET (400), spent
newest-first, with the remainder published as NOT ATTEMPTED and the floor it
reached stated by number.

The budget is a wall-clock bound derived from two measurements taken here, not a
guess: ~366 ms per resolution, and a baseline sweep of 228 s against
half-state-patrol.yml's timeout-minutes: 15. The live run below came in at
360 s — about 40% of that cap.

Two cheaper shapes were measured and declined, and the header records both so
nobody re-derives them. A bulk state=all&sort=created&direction=asc listing
maps 100 consecutive numbers per request, and cannot work: the issues listing
refuses deep pagination — page 99 answers 200 and page 100 answers HTTP 422
,
so an ascending pass stops at #9,900 while this repo is past #13,900, and the
descending half that would cover the rest reorders under every new issue (a row
shifting across a page boundary mid-pass would be published as a dangling
reference — a false accusation about a live card). A batched GraphQL query was
declined structurally: this file's whole transport story is REST-shaped, so a
second transport would run outside the prerequisite classifier in the one script
whose contract is that an unread input never reads as a clean one.

The renderer-trimming interaction (#13947), and the choice made

That card measured the anchor body dropping 157 of 231 rows by position in one
flat list, so a new family added as ordinary finding rows can be computed and
then silently omitted. Chosen: a reserved section, plus an unconditional
summary clause
— not a priority position and not a deferral.

Reasoning, in the file's own conventions: a row family whose entire subject is a
defect that no instrument reports must not itself be droppable. The reservation
is the mechanism renderTriggerIndex, the H39 census and the rate premise
already use — the block's length is charged to the budget before finding rows
are laid out, so the trim can only ever fall on rows that announce their own
omission. A priority position was rejected because it is probabilistic (the loud
and UNJUDGED bands already compete there), and marking confirmed findings as
UNJUDGED to buy trim protection would corrupt a band whose meaning the file
depends on. The section keeps its own H40_ROW_CAP so the reservation stays
bounded, and the summary clause renders above the fold on every run — which
matters because H40's findings deliberately do not enter the finding COUNT.

Three self-test cases pin this directly: a 900-row flood that really does trigger
the trim, the H40 section still rendered inside it, and the body still under
MARKDOWN_BODY_BUDGET.

One deliberate divergence from the neighbouring extractors

Fenced code blocks are stripped; inline code spans are kept. Every other
extractor here blanks spans because GitHub's closing-keyword parser does not fire
inside one, so a backticked keyword is genuinely inert. That reason does not
transfer: a reference is a pointer a reader follows, and a backticked
Blocked-by: #9612 is the natural markdown for a line meant to be grepped. A
seat that opens a backticked number and gets a resolution error has hit exactly
the defect this row exists to find. Fences still go, for H17's reason — a number
inside one is a citation, not a pointer.

Evidence

Self-test: 1826 → 1922 cases, all green (pnpm check:pm-half-states).

Ablation — three legs, each mutated, proven on disk (anchor uniqueness, an
injected/removed text count, and a git hash-object differing from HEAD) and
restored back to the byte-identical HEAD blob with git diff HEAD empty. No
build step sits between this source file and its suite — the suite runs the file
directly — so there is no rebuild leg to state, and the mutation proof stands in
its place. Control on the unmutated tree: green, 1922 pass.

legred cases
h40Verdict treats any failure as unresolvable9 — the 403/429/5xx/no-status UNJUDGED cases, the report's dangling set, and both render assertions
the section leaves the reserved block3 — all three #13947 cases
the reserved block's length stops being charged to the budget2 — the body-fits case here and H17's existing one

Live run at this head, 360 s: 45 numbers referred to by the live board do
not resolve
, 0 unjudged, over 3,725 distinct references read off 421 open
cards/PRs and 293 already-cached comment threads; 1,019 answered free from
listings in hand, 400 attempted (355 answered), 2,306 not attempted at the budget
(reached down to #11410). The known instances appear with named referrers —
#13456 via #13440#13536#13634#13766, #13398 via #6021#12981#13634#13766
alongside 41 that nothing had reported. Four of the new ones were re-resolved by
hand (#13462#13412#13165#12924, all 404) and one referrer claim was verified
against the referring body (#13417 does name #13412 and #13410), so the row is
not reporting an extractor artefact.

Read coupling:pnpm check:pm-clause2-carriers green (64 cases) — every
export it imports is untouched. check-governed-merges and ci-failure
self-tests green too.

Gates, derived on the final head 35c684c7 via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (19
commands: 17 by path, 2 by change kind — this edits a gate script). All green,
plus pnpm lint over the whole repo (78 s, exit 0) and check:nul-bytes. Four
of the 19 are NOT MEASURED locally rather than red, each by its own printed
verdict: check-test-completeness says so in as many words (it grades a saved
turbo log that no local run produces) and check-partof-closing-keyword needs
PR context it only has in CI. The other two were PREREQUISITE NOT MET before
pnpm install and green after it.

Scope

scripts/pm/check-half-states.mjs only — no other file, no workflow change. The
scheduled patrol picks the row up on its next fire with no wiring. Code-only, so
skip-changeset: nothing is published from any package.

#13947 remains open and is not addressed here; the reservation above is how this
row avoids its hazard, not a fix for it. #13766 is out of scope: it carries the
B-half question and is ruled separately.


Generated by Claude Code

…erences
Sweeps the open board's bodies and already-cached comment threads for `#`
references into this repo, resolves each DISTINCT number once, and reports the
ones that fail to resolve with their referrers. Report-only.
⛔ closed != unreachable: only HTTP 404 is read as unresolvable; a closed card
resolves normally and is never a finding, and any other failure is UNJUDGED
rather than a vanished card.
Renders as a RESERVED section plus an unconditional summary clause, so the
anchor body's row trim cannot silently drop it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
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