Skip to content

test(pm): scope each window's summary assertions to its own clause - #13654

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-13629-scoped-summary-assertions
Aug 31, 2026
Merged

test(pm): scope each window's summary assertions to its own clause#13654
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-13629-scoped-summary-assertions

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #13629

summaryLine returns ONE sentence carrying every pass's disclosure, so a summaryLine(...).includes(phrase) case asks "did ANY window say this" while its name promises "did H8 say this". That was exact while H8 was the only speaker, and became a check that cannot fail once four more clauses joined the line.

This is test scaffolding only. The rendered summary line does not change — proof below.

What landed

  • SUMMARY_CLAUSE_ANCHORS — the 17 windows that speak on the line, in render order, each with the lead-in phrase that opens its clause.
  • summaryClause(summary, key) — cuts one window's clause out of a rendered line. A clause runs from its own anchor to whichever other anchor comes next, so an absent neighbour widens rather than truncates.
  • saidBy(key, line) in selfTest — every summary-disclosure case now asserts against the extraction.

Two failure modes, deliberately asymmetric and documented at the function: an unknown key throws (author error, and the loud abort names the typo), while a missing anchor returns '' (the ablation path, which must never throw — a throw inside a t() argument aborts the whole suite, and the per-window ablation this card asks for would be unreadable).

Every summary case, adjudicated

No silent skips. 96 cases call summaryLine in selfTest; 94 were scoped, 2 were kept whole-line on purpose.

已改 (scoped) — 94 cases

window cases window cases
h19Blockers 18 h22Read 5
h20Dispatch 10 h14Fallback 4
h35GatePatrol 9 h9Restart 3
head 7 h23Window 3
h8Window 6 h23Read 3
h22Window 6 h17Holds 3
openListings 5 h36SharedFiles 2
h37Folds 5
h32Seats 5

本就 scoped — 13 cases. The timeBound(...) / exBound(...) cases call describeWindowBound directly. Their subject IS a single clause, with no summary line involved, so there is no neighbouring text that could answer them. Left as they are.

明确不在范围 — 2 cases. summary: the report-only contract still ends the sentence and …after H20 assert .endsWith('not a gate verdict.') on the whole line. That is a claim about the assembled sentence, not about a window; scoping it to the reportOnly clause would only weaken it. Both are marked where they sit.

One case's whole-line reach was replaced rather than dropped: the per-window …never to undefined cases now speak only for their own clause, so a new summaryLine: no clause ANYWHERE prints the string undefined case keeps watch over the gaps between them.

Acceptance: one ablation per window, 17 of 17

Predictions were fixed in the harness at 2026-08-31T04:55:09Z on commit 8813cf1c7, before any leg ran (PREDICTIONS.json). Each leg deletes that window's clause from the summary construction (its source chunks are replaced with ''), then:

  • mutation proven on disk by re-reading the file and counting the exact source marker inside summaryLine's return body — 1 -> 0 on every leg — plus an independent render-level check that the window's anchor is gone from the output;
  • restore proven by git checkout HEAD -- <abs path> (never a bare checkout, which restores from the index and would leave the mutation in place), then git diff HEAD empty and git hash-object equal to the HEAD blob. Final blob 24e366cd5c506cb5e2439125179e21bc9089d84c on every leg.
result every leg
suite exits non-zero 17 / 17
the window's own positive cases go RED 17 / 17
the window's presence control goes RED 17 / 17
another window's case goes red 0 / 17

reportOnly is the one window with no scoped cases of its own — by design, since its two cases are the whole-line endsWith pair above. Ablating it still reddens its presence control and both endsWith cases.

A window's negative cases stay green under its own ablation: an empty clause satisfies "does NOT say X" vacuously. That is inherent to a negative assertion, not a gap in the scoping, and it is exactly why each window also carries a presence control that goes red when it stops speaking.

What this actually fixed — measured against the base

The same 17-leg ablation was run against the base revision (7483afdd9, unmutated: ✓ check-half-states self-test: 1749 cases pass.). Four pre-existing cases had their own window's clause deleted outright and stayed GREEN, satisfied by a neighbour:

case needle windows that speak it
summaryLine: absent H16 counts degrade to 0, never to undefined read on 0 of 0 8head, h17Holds, h14Fallback, h9Restart, h20Dispatch, h32Seats, h36SharedFiles, h37Folds
H22 summary: a completed window says the boundary was reached boundary reached h22Window, h23Window
summary: …and says the unit is DISTINCT targets, not per-card edges distinct h19Blockers, h20Dispatch
H37 summary: …stating the miss-only direction never invent one h36SharedFiles, h37Folds

All four are red under their own ablation now.

The card's headline example (H8's horizon reached) is not in that list, and the honest reading is that the naming convention #13628 introduced is currently holding: no neighbour says "horizon reached" today, so deleting H8's clause already reddened its cases at base. The convention works — it just is not what keeps working when the next author reaches for the obvious word. Both directions of that are now pinned structurally, against a line deliberately doctored to make a neighbour speak H8's words:

  • #13629 loud direction: a neighbour saying "horizon reached" DOES reach the whole line → true, and …but H8's truncated clause still does not claim it → false.
  • #13629 silent direction: the whole line still says it, from the neighbour → true (H8's own clause deleted), and …and the scoped case refuses to be satisfied by that → false.

The rendered line does not change

summaryLine was dumped at 7483afdd9 and at this branch's HEAD over 7 count shapes × 3 finding counts:

RENDER DIFF: byte-identical across all 21 renderings
d77684b401a3ee782adc5690835093ec  render-base.txt
d77684b401a3ee782adc5690835093ec  render-head.txt

The extractor was also validated against a real production rendering, not only synthetic shapes — the live read-only patrol line below partitions exactly, 3293 chars of clauses over a 3293-char line, all 17 anchors unique and clause-opening.

Verification

--self-test: 1749 → 1783 cases pass. Scoping replaced; the extractor's own controls (partition on two shapes, anchor uniqueness, declared-vs-rendered order, 17 presence controls, the ablation contract, the doctored-neighbour pins) added.

✓ check-half-states self-test: 1783 cases pass.
✓ dispatch-gates self-test: 1017 cases pass.

Gate families derived from the ACTUAL diff via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — 17 by path + 2 by change KIND (this edits a gate script). All 19 run; each gate's own verdict line:

gate verdict
check:pm-half-states ✓ check-half-states self-test: 1783 cases pass.
check:pm-dispatch-gates ✓ dispatch-gates self-test: 1017 cases pass.
bare-root-worklist --self-test OK self-test: 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale
check:entry-guard ✓ 187 scripts/ file(s) — every entry guard goes through invoked-as.mjs
check:partof-closing-keyword ✓ check-partof-closing-keyword self-test: 28 cases pass.
check:watch-hint-literal ✓ 33 declaration(s) across 4 rostered name(s)
check:pnpm-filter-targets ✓ 140/179 --filter occurrence(s) across 32 file(s) resolve
check:cli-command-ids ✓ 304 command-id literal(s) across 109 file(s) … all resolve
check-ci-filter-parity OK: all 127 declared cross-package glob(s) (92 unique) are covered
check-closing-keyword-parity OK (3 parsers agree on all 9 keywords and both measured separators)
check-shard-attestation ✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).
check-cross-package-test-inputs OK: 24 package(s) read outside themselves, all declared
check:agent-test-spelling, check:bash32-floor, check:parse-guard exit 0
check-nul-bytes OK (scanned 7549 text file(s) … no raw ASCII control bytes)

node scripts/check-test-completeness.mjs is NOT MEASURED locally, by the gate's own account: it grades a saved turbo run test log, no local log exists to hand it, and it prints that this branch is unreachable in CI. Not read as a pass or a fail.

ESLint, repo-wide rather than narrowed — pnpm exec eslint . --no-inline-config --format json: 5555 files selected by eslint's own config, 0 with problems, exit 0, and the changed file is in that selected population.

Live read-only fire at this commit (node scripts/pm/check-half-states.mjs, exit 0, run with env -u GITHUB_TOKEN -u GH_TOKEN because the container's value is the literal placeholder proxy-injected):

check-half-states: swept 232 open pm-/p0-labeled issue(s), 358 open issue(s) in the unscoped pass (H13–H15, H18), 17 open PR(s) (merge state read on 4 of 4 H16 candidate(s)) and 966 recently-merged PR(s) in objectstack-ai/objectstack — 230 half-state(s) found. H8's merged window is a TIME cap of 8 day(s), read in 10 page(s) (horizon reached: …). …

Out of scope, filed

#13650summaryLine's H19 clause has no sentence-terminating period unless a cross-repo probe fired, so on an ordinary healthy pass it runs straight into H20's clause. Visible in the live line above. Filed unassigned, for triage; not touched here, since this card's default is that the rendered line does not change.

Notes for review

Generated by Claude Code


Generated by Claude Code

`summaryLine` returns one sentence carrying every pass's disclosure, so a
`summaryLine(...).includes(phrase)` case asked "did ANY window say this"
while its name promised "did H8 say this". Exact while H8 was the only
speaker (#13499); a check that cannot fail once #13606 put four more
clauses beside it.

Adds `summaryClause(summary, key)` + `SUMMARY_CLAUSE_ANCHORS`, and routes
all 94 summary-disclosure cases through a `saidBy(key, line)` helper so a
case can only be answered by the window it names. Two whole-line cases are
kept deliberately (the sentence-terminal `endsWith` contract, and one
`undefined` sweep) and marked where they sit.

No change to the rendered summary line — test scaffolding only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review August 31, 2026 05:15
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit a9b184a Aug 31, 2026
35 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-13629-scoped-summary-assertions branch August 31, 2026 05:48
zhuangjianguo pushed a commit that referenced this pull request Aug 31, 2026
`summaryLine`'s H19 clause carried its terminating `. ` inside the
cross-repo branch alone, so the only rendering that ended the sentence
was the one where an unrelated optional probe had fired. On an ordinary
healthy pass -- no shortfall and no probe -- the clause stopped at
`card(s)` and a lone space joined it straight onto H20's lead-in, which
the live 2026-08-31 patrol rendered as
`...card(s) Dispatch liveness (H20 + H27): ...`.

Terminate the clause unconditionally and let the two optional branches
contribute only their own text. The cross-repo branch already ended in a
period of its own, so its leading space and the join's trailing space
move into the terminator rather than adding a second one: across every
rendered shape the whole line differs from base by exactly one inserted
`.`, inside H19's terminator region and nowhere else.

Pin it with the #13654 scoped-assertion scaffolding: `saidBy('h19Blockers',
...)` cases assert the clause ends `. ` on the healthy, shortfall, probe
and both-branches shapes, plus that the probe shape does not render
`resolves them.. `. The neighbouring whole-line `endsWith('not a gate
verdict.')` cases could not see this -- they pin where the SENTENCE ends,
not where this clause does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…3677)

`summaryLine`'s H19 clause carried its terminating `. ` inside the
cross-repo branch alone, so the only rendering that ended the sentence
was the one where an unrelated optional probe had fired. On an ordinary
healthy pass -- no shortfall and no probe -- the clause stopped at
`card(s)` and a lone space joined it straight onto H20's lead-in, which
the live 2026-08-31 patrol rendered as
`...card(s) Dispatch liveness (H20 + H27): ...`.

Terminate the clause unconditionally and let the two optional branches
contribute only their own text. The cross-repo branch already ended in a
period of its own, so its leading space and the join's trailing space
move into the terminator rather than adding a second one: across every
rendered shape the whole line differs from base by exactly one inserted
`.`, inside H19's terminator region and nowhere else.

Pin it with the objectstack-ai#13654 scoped-assertion scaffolding: `saidBy('h19Blockers',
...)` cases assert the clause ends `. ` on the healthy, shortfall, probe
and both-branches shapes, plus that the probe shape does not render
`resolves them.. `. The neighbouring whole-line `endsWith('not a gate
verdict.')` cases could not see this -- they pin where the SENTENCE ends,
not where this clause does.


Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-half-states' per-window summary assertions match the WHOLE summary line, so a neighbouring window's clause can satisfy them

2 participants