Skip to content

check-half-states: H8's branch-name fallback reads a merged Refs #N (item k) PR on a card-named branch as a full delivery, so it prescribes dropping pm:dispatched on a lawfully re-dispatched remainder #16036

Closed

Description

@os-steve

Found while implementing #16003 (the partial-dispatch release rule and the report-only H49 row). Not addressed there — that card's ruling scoped its diff to the new rule, the new row and two tidiness items, and H8's delivery relation is outside it.

What H8 does today

prDeliversCard (scripts/pm/check-half-states.mjs) reads a PR body for Part of #N or a closing keyword bound to #N; when the body declares NO delivery at all it falls back to the branch name, so a PR on claude/issue-N-slug whose body says only Refs #N is read as delivering card N. That fallback was added for a measured specimen (a merged PR whose body said only Refs #10757, leaving its card dispatched and invisible for ~22h) and it was right for that shape.

Why the partial-dispatch rule changes the reading

Under the rule landing with #16003, a PR that lands ONE item of a card carries Refs #N (item k) and never Fixes, and its landing releases the card back to pm:queue; the remaining items are then re-dispatched from the queue as a fresh claim. The re-dispatched remainder is a card that LAWFULLY carries pm:dispatched again, with a fresh Claim: on the thread — while the merged Refs #N (item k) PR sits in H8's merged window on a branch named for the card.

H8 reads that as "delivering PR merged, card still pm:dispatched — drop pm:dispatched and re-grade the remainder". It reads no thread, so the fresh Claim: newer than the merge — the exact record that stands H49 down — is invisible to it, and the row keeps firing from the re-claim until the remainder's own PR opens (at which point the open half downgrades H8 to its no-action "delivered IN PART" sentence). Followed literally, the remedy de-labels a card a dev is working on and invites a second seat onto it.

Measured in #16003's self-test (adjacency cases, pinned as facts rather than as the desired contract): on a Refs-only merged PR whose head is NOT named for the card H8 is silent and H49 fires; on a head named for the card both fire; after a Claim: newer than the merge H49 stands down while H8 does not.

Options for triage

  1. Make the branch-name fallback decline when the body carries Refs #N for that same N (the body SPOKE about the card, in the one spelling the protocol reserves for a non-delivery) — the smallest change, keeps the Authenticated data requests issue ~20 DB queries in ~17 sequential legs — per-request auth/session/localization/metadata resolution has no cross-request caching, costing ~1.5s/request on remote Postgres #10757 recovery for bodies that declare nothing at all.
  2. Teach H8 to read the card thread (a Claim: newer than the merge stands it down), the way H49 does — a wider change; H8 would then need the coverage pair H49 carries.
  3. Leave H8 as is and treat the double reading as acceptable noise for the window between re-claim and the next PR.

Refs (not addressed here): #16003.

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Sep 5, 2026
  2. os-steve commented on Sep 5, 2026

    @os-steve
    CollaboratorAuthor

    Graded by the skills seat (first touch, body read): domain:skills, tooling, priority:p3, pm:blocked.

    Blocked-by: #16003

    Reading: a real misreading by H8's branch-name fallback — a merged Refs #N (item k) PR on a card-named branch is a partial landing (the release act, which PR #16040 writes down and whose absence H49 now reports), not a full delivery, so H8 must not prescribe dropping pm:dispatched on a lawfully re-dispatched remainder. The fix belongs in H8's predicate (a Refs-only PR is out of H8's full-delivery reading; reuse refsTargets), pinned both directions. Same file as PR #16040, so this waits for it; #16037's three line-number deletions ride along as tidiness.


    Generated by Claude Code

  3. self-assigned this
    on Sep 6, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 6, 2026
  5. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claim: PM loop round 8 — the claim-side readers of check-half-states.mjs, one flight of three same-file cards under the five-gate test: #16036 (H8's branch-name fallback excludes a merged Refs-only PR — reuse refsTargets), #16170 (claimedBranches admits a list-marker prefix before Branch:, and the refusal message names the real cause — a claim that governs nothing versus no claim), #16037 (the three remaining SKILL.md line-number citations deleted, quotes kept). Unblocked when PR #16040 landed.
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-16036-claim-readers
    Worktree: objectstack-issue-16036
    Domain: domain:skills
    File surface: scripts/pm/check-half-states.mjs only; self-test both directions for each change.
    Container & model: S, mode:subagent, model: opus (scripts/pm/** hits no mandated glob; clause ② no).
    Clause-②: no
    Serial: no open PR touches the file (PR #16040 landed 23:50Z). Pure code ⇒ in-seat review, then ready + queue from the seat; skip-changeset. The PR carries Fixes #16036, Fixes #16170, Fixes #16037.


    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 16036,
      "status": "done",
      "branch": "claude/issue-16036-claim-readers",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16202",
      "premise_still_valid": true,
      "summary": "Flight C, all three cards in scripts/pm/check-half-states.mjs alone, no new rows and the family markers untouched. #16170: claimedBranches now admits an optional markdown list marker before the directive (the ruled anchor, verbatim), so the mixed shape measured live on #15511 governs again; the claim MARKER is deliberately NOT widened with it, because when both lines carry a bullet the marker misses too and 'no claim comment' is then the true reading, so only the mixed shape was malignant. #16036: H8's MERGED side reads a new, narrower prFullyDeliversCard that calls H49's own refsOnlyLinksFor, so a merged Refs-only PR on a card-named branch is no longer a full delivery and H8 cannot prescribe dropping pm:dispatched on a lawfully re-dispatched remainder; the shared prDeliversCard relation is untouched, because H8's OPEN side, H31 and claimDelivery all need the wide reading (narrowing it would make a live half invisible, the #10468 harm run backwards). #16037: the three SKILL.md line-number citations deleted, quoted text kept. CORRECTION to #16170's premise, reported rather than acted on: the refusal message the card quotes lives in scripts/pm/check-clause2-carriers.mjs (the absent limb of cardDeclaration), which this flight does not touch by ruling — and that limb ALREADY distinguishes the two readings, separated by CLAIM_COMMENT_MARKER rather than by the branch parse, so on the measured #15511 shape claimRows is non-empty and the thread lands on 'missing' ('the card's claim comment is there and carries no Clause-2 line'), whose sentence is accurate. #16170's anchor premise is exact and was fixed; its part-(b) claim that the message names the wrong cause did not reproduce. See open_questions.",
      "tests": "Self-test: `node scripts/pm/check-half-states.mjs --self-test` exit 0 (captured before any pipe) — 2535 cases BEFORE, 2553 AFTER. Case-name diff: 20 added, 2 removed, both forced by the ruling flipping H8's verdict on one fixture and each with a named successor on the same input ('H49 adjacency: on a head NAMED for the card H8 fires through its branch fallback' -> '... H8 is silent too, since #16036'; 'H49 adjacency: ...and this row fires beside it - two readings, no double silence' -> '...and this row fires there - one reading now, and no silence'). Two names survive on a MOVED subject and are named rather than left to be found: 'H8 branch: a Refs #N-only body delivers via its branch name' now asserts the unchanged prDeliversCard relation, and 'H8 branch: ...and the finding names the delivering PR' now asserts on the body-silent specimen. ABLATIONS, both on a scratch copy outside the worktree (implementation committed first; trap-restored; absolute paths): the copy was pinned byte-identical to HEAD:scripts/pm/check-half-states.mjs (blob 62db8efec42e3d68e9c7e76ec2c880bf0b1524d9) before each leg and restored by BLOB-HASH COMPARISON after it, and each mutation was confirmed ON DISK by removed-text and injected-text occurrence counts plus a blob-hash change BEFORE the run was read. NO REBUILD LEG EXISTS AND THAT IS STATED RATHER THAN SKIPPED: this file is a .mjs script node executes directly, there is no package dist/ between the edit and the run, so the dist-preflight discipline has no surface here; the byte proof is the whole proof. (1) revert the claimedBranches list marker -> blob 62db8ef to ed7c565, removed-text 1 to 0, injected-text 0 to 1 -> self-test exit 1, 6 NAMED failures, all the #16170 cases including the mixed-shape governing one. (2) revert H8's merged side to the wide relation -> blob 62db8ef to 5778d9e, same on-disk proof -> self-test exit 1, 2 NAMED failures, the two #16036 cases. Both restored to 62db8ef, verified equal to the HEAD blob. NOTE: ablation (1) first died on a TypeError naming neither row nor mutation; commit 2 on this branch adds the `?? []` row wrapper H34's own note prescribes, and the numbers above are the re-run. GATES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 33 commands at commit c493f98f22; all 33 run, all exit 0; `--ran` reconciles '33 derived famil(ies) accounted for - 33 run, 0 NOT-MEASURED', exit 0. `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-half-states.mjs` exit 0, 'NOT governed - ordinary queue landing applies'. The 8 artifact-roster families whose roster sits under scripts/ or scripts/pm/ (where 'silent' is evidence in neither direction) were run too: 6 green; check:published-readme-exports and check:dts-closure both exit 3 = PREREQUISITE NOT MET (no package has a dist/ here) = NOT MEASURED, not red - this diff touches no package source and CI builds the closure. LINT: whole-repo `pnpm lint` (eslint . --no-inline-config) through `bash scripts/pm/os-verify-lock.sh -c` slot issue-16036 -> 'VERDICT command-exit 0 - held the lock 73s - waited 1s' (shared-box seconds). CI on head c493f98f22, polled in the foreground until every run completed: 35 check runs, 24 SUCCESS, 11 SKIPPED, 0 FAILURE - Lint & Repo Gates, TypeScript Type Check, Test Core (and all 6 shards), Dogfood Regression Gate, Governed Surface Queue Guard, Live half-state sweep, Check Changeset and Check PR Size all SUCCESS; Build Core and Temporal Conformance SKIPPED by path filter. LIVE SWEEPS, read-only, both boards, before and after, from two scratch trees pinned by git hash-object to the merge-base blob and the HEAD blob: objectstack 299 -> 301 half-states, objectui 173 -> 182, and on BOTH boards the after row set is a strict SUPERSET of the before one, i.e. ZERO rows removed by this diff, which is the only direction the change permits. Every difference is board drift between the paired runs and each one is accounted for: on objectstack, PRs #16054 and #16156 merged between the runs, so their H46 rows (an OPEN-PR population) left and their cards #15983 and #14936 gained H8 rows naming those exact PRs - both from closing-keyword bodies, so the narrowing correctly leaves them firing, which is the ruling's second pin observed LIVE; the other movements (H19, H31, H35, H36, H44) are in families that read neither claimedBranches nor H8's predicate. On objectui, H20 on #7689 and #7693 fired at '~62 min after the claim was posted (threshold 60 min)' - a clock crossing, not a reading change - and their H8 rows arrived with PR #7970's merge in the same window. The reason nothing was silenced is measured, not assumed: H49 reported 0 rows with COMPLETE coverage on both boards (63/63 then 64/64 on objectstack, 13/13 on objectui), so no card on either live board currently carries a merged Refs-only landing inside H8's merged window. The guard is prospective and the self-test is where it is measured. Control-character scan of the diff: `grep -naP` over the file, no hits; pnpm check:nul-bytes green.",
      "mcp_calls": "1 — a single issue_write to file #16217. Every other GitHub read went through the zero-quota single-card payload channel (issue pages) and every other write through repo-scoped REST after a green probe; the dedup enumeration was the REST list endpoint, 680 rows reconciled exactly against open_issues_count 680.",
      "open_questions": [
        {
          "question": "The ruled anchor puts the list marker BEFORE the optional blockquote, so `- > Branch:` reads and `> - Branch:` does not — yet `> - ` is the natural markdown for a bulleted line INSIDE a blockquote, and is arguably what the ruling's pin 'blockquote + bullet ⇒ one branch' names. Implemented exactly as ruled; which order did the ruling mean?",
          "options": [
            "A — keep the ruled anchor as landed. `- > Branch:` satisfies the 'blockquote + bullet' pin, and `> - Branch:` is pinned as a measured residual with a comment naming the ruling as its authority. Nothing further owed.",
            "B — admit both orders: `^\\s*(?:[-*+]\\s+)?>?\\s*(?:[-*+]\\s+)?Branch(?:es)?\\s*:`. One line, one extra pin, and it makes the reader agnostic to how a seat nests the two decorations.",
            "C — move the marker to AFTER the blockquote instead (`>?\\s*(?:[-*+]\\s+)?`), which admits `> - ` and drops `- > `."
          ],
          "recommendation": "A for this PR, because the anchor is quoted verbatim in the 裁决区 and a dev does not widen a ruled regex on its own reading; then B as a one-line follow-up if the seat confirms `> - ` was the intent. On the four axes: real need — the only MEASURED live specimen is the bare bullet (#15511), and no live claim on either board uses either mixed order, so B is speculative today; long-term — B is the shape that stops the file being half-tolerant, which is the card's own warning; AI-error-prevention — B tightens nothing and loosens an INPUT tolerance rather than a platform acceptance surface, so it does not weaken any contract, while the residual pin is what makes today's gap legible instead of silent; startup focus — B is one character class, not a capability, so cost is not the argument against it; the argument against it is authorship, that the anchor was ruled verbatim and is the seat's to move."
        },
        {
          "question": "#16170's part (b) — 'when a claim comment IS recognised but yields no branches, say that, and name the line it could not parse' — did not reproduce as described, and the file this flight may edit carries no such message. What, if anything, is still owed on that half?",
          "options": [
            "A — nothing. cardDeclaration already returns `missing` vs `absent` as two states with two sentences, the split is driven by CLAIM_COMMENT_MARKER (not by the branch parse), and the widening landed here removes the mixed shape that made a claim govern nothing. Close #16170 on the anchor alone.",
            "B — a separate card against check-clause2-carriers.mjs: the `missing` sentence could additionally say when governingClaim returned null (the carrier exists, is a claim, and names no branch this reader recognises) and quote that line — legible today only because the anchor was widened, and still legible for a `Branch: main` or a fullwidth colon.",
            "C — a card against check-half-states.mjs instead: H20/H27 are SILENT (not wrong) on a claim that names no parseable branch, and silence is the failure mode this file otherwise treats as worse than a row."
          ],
          "recommendation": "A now, with B as a small follow-up if the seat wants the diagnosis legible for the residual spellings (`Branch: main`, fullwidth colon, `> - `) that still parse to zero. Real need: the measured harm — a seat and then a whole dispatching round taught a wrong rule — came from the anchor, and the anchor is fixed; the message was accurate on that thread, so B buys clarity for shapes nobody has hit rather than for the one that cost something. Long-term: B is contract-first in the right direction (the producer's line is named at the point of refusal), but it edits a file this flight is forbidden to touch, so it is a card and not a rider. AI-error-prevention: neither option widens an acceptance surface; B makes a refusal name its cause, which is the direction that stops the next seat guessing. Startup focus: C is the widest and the least pulled — H20's silence is a deliberate under-report this file argues for twice, and reopening it needs a ruling, not a dev's judgement."
        }
      ],
      "out_of_scope_findings": [
        "filed as #16217: check-half-states H22's closed-card pass is TRUNCATED at the 12-page quota ceiling on this board — the sweep declares the shortfall and its own remedy on every run, and no card carried it (dedup: complete open enumeration, 680 rows reconciled exactly against open_issues_count 680, with firing controls); unassigned and unlabelled for triage",
        "not filed, reported as an observation only: refsTargets' docblock says 'Neither GitHub nor prDeliversCard reads it as a delivery', which is true of the Refs SPELLING but reads oddly beside the branch-name fallback that does deliver such a PR. Checked and judged defensible as written rather than a defect, so no card was opened; naming it here so the next reader of that paragraph does not have to re-derive the question."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  7. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatpriority:p3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions