fix(pm): H9's issue-ref class requires the reference at the declaration position - #17816
Merged
claude[bot] merged 1 commit intoSep 12, 2026
Merged
Conversation
…on position `classifyRestartWhen`'s `issue-ref` step tested `/#\d+\b/` — an issue reference the value CARRIES anywhere — so a `Restart-when:` value that merely mentions a card mid-sentence read as a machine-fireable exit. On the 2026-09-11 census of the 104 open `pm:on-hold` cards that cleared four holds whose value actually names a tracked repo path (a misfiled `Restart-touch:` line), because each mentions an unrelated issue number in passing. The step now tests the DECLARATION POSITION: the value must START with `#N` or `owner/repo#N`, after the same leading decoration `partOfDeclarationRe` tolerates. That is H9's own advertised spelling (`Restart-when: #N rules on X`) read literally, so it costs no ruling and the class ORDER is untouched. A mid-sentence mention falls through to `command` / `tracked-path` / `prose` and fires the row its real shape earns. The `prose` row's remedy follows: a value that mentions a card without declaring one is now prescribed the declaration beside the generic "mark it `manual` or name the event", because an author who wrote a number down was not missing an event — they wrote it where nothing reads. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
os-sales
marked this pull request as ready for review
September 12, 2026 08:23
This was referenced Sep 12, 2026
claude
Bot
deleted the
claude/issue-17605-h9-issue-ref-declaration-position
branch
September 12, 2026 09:09
This was referenced Sep 12, 2026
docs(pm): rest-channel.md rows 1–2 name the ccr REST routes for the draft flip and auto-merge
#17823
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
classifyRestartWhen'sissue-refstep tested for an issue reference the value CARRIES anywhere (/#\d+\b/), so aRestart-when:value that merely mentions a card mid-sentence read as a machine-fireable exit and cleared H9. On the 2026-09-11 census of the 104 openpm:on-holdcards that let four holds whose value actually names a tracked repo path — a misfiledRestart-touch:line, exactly the class the parent card built a row for — read clean, each on an unrelated issue number written in passing. The step now tests the DECLARATION POSITION: the trimmed value must START with#Norowner/repo#N, after the same leading decoration this file's existingpartOfDeclarationReidiom tolerates. That is H9's own advertised spelling (Restart-when: #N rules on X) read literally, so it costs no ruling, and the class ORDER andFIREABLE_RESTART_WHEN_CLASSESare untouched. A mid-sentence mention now falls through tocommand/tracked-path/proseand fires the row its real shape earns; theproserow's remedy gains a "declare it" clause when the value carries a reference, because an author who wrote a number down was not missing an event — they wrote it where nothing reads.Fixes #17605
Acceptance notes
Premises — all five re-verified on
origin/main@2070a1d8, all TRUEclassifyRestartWhenreadsif (/#\d+\b/.test(v)) return 'issue-ref';— the reference is accepted anywhere in the value.manualor name the event, and the self-test pin asserts it. That pin is untouched and still green (H9: …and the row names the missing event).partOfDeclarationRe()sits besidepartOfRe()— the file's own declaration-position idiom, mirrored here rather than reinvented.pm:on-hold, each naming a tracked path with the reference mid-sentence; Give a lowered hook body a declared constant surface — every shared constant is currently hand-copied into each handler and pinned by a bespoke drift test (8 rows in the reference app) #13718 and [P2] engine ADR: durable pause inside structured regions (unlock topology-level parallel approvals / waits / subflows) #3267 start with theirs; The by-id write pre-image gate resolves the row under the caller's own read scope, so an app-authored widener is still dead onprivateeven once checkAuthoredRowWrite admits it #7401 open. ⛔ No card was touched.premise_still_valid: true.What landed (A–E)
A —
issue-refnarrowed to the declaration position. The predicate is a named pair mirroring the file's own:restartWhenIssueDeclarationRe()(the class test) besiderestartWhenIssueRefRe()(the anywhere form, kept as a LABEL for the prose row). The decoration prefix is copied frompartOfDeclarationRe; the qualifier is this file's one reference grammar (referenceRe— a fullowner/repo, never a bare single segment, because/is what separates a cross-repo reference from a path segment); the NUMBER test is left byte-for-byte as the wide form spelled it. ⛔ The six-step order is unchanged andFIREABLE_RESTART_WHEN_CLASSESis still['closed-ref', 'issue-ref', 'command'](pinned twice).B — the prose row's remedy now branches on whether a
prosevalue carries a reference. With one, the row says the value MENTIONS an issue reference mid-sentence but DECLARES none, and prescribes:Without one, the existing [finding] H9 and H4 miss a
Restart-when:/Blocked-by:line wrapped in backticks — #9591 is a live false positive, and the prescribed remedy for it is to close a maintainer-commissioned card #10102 sentence stands byte-identical — which is what keeps the old pin green.C — the H9 header's "measured price" paragraph rewritten: the price is no longer paid, the position test is what retired it, and the sentence saying why ranking
tracked-pathaboveissue-refwas NOT the instrument (the order is ruled) is kept. The reserved unreachable-closed …#Nparagraph above it is untouched.D — 23 new self-test cases, both directions, named by specimen card number as the file's idiom does.
E — step 3 of
classifyRestartWhen's docblock now says DECLARES, not CARRIES.The specimens are verbatim
Each of the six values below was copied from the live card's
Restart-when:line on 2026-09-12 and pinned as a literal string, the same discipline the parent card's three specimens use: #8753, #8607, #8589, #6009 (→tracked-pathunder an oracle naming the one path each carries; →proseunder the default oracle, both directions pinned), #13718 and #3267 (→ stillissue-ref). The existing#13651 rules on the count an app cannot reachpin is stillissue-ref, andobjectstack-ai/cloud#861 …still is too.Reverse verification (ablation)
Fix committed first (
7599059), then the one classifier line mutated back to the wide/#\d+\b/form in place, proven on disk (grep -con both the injected and the deleted text: narrow 1→0, wide 0→1;git hash-objectmoved from6fe3ffetod30ac02), the self-test re-run, then restored under atrap … EXIT INT TERMpinned to an absolute repo root and verified byte-identical (git hash-objectback to6fe3ffe,git status --porcelainempty).issue-ref) stay GREEN under the mutation, which is what they are for: they assert the narrowing does not reach a declaration.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 41 families from the merge-base changeset. All 41 ran, all exit 0. Reconciled with--ran:41 derived, 41 run, 0 NOT-MEASURED, 0 UNRUN(a DERIVED zero — every line carried its exit code). Plusnode scripts/pm/check-half-states.mjs --self-test→ exit 0, 3707 cases pass.pnpm lint(eslint . --no-inline-config) run in full rather than narrowed: exit 0 at759905928, 6644 files.Readings corrected, and things the PM should know
--self-test, not--test. The dispatch word named--test. That spelling is not a flag this script recognises, so it falls through to a live patrol sweep — which is what it did here, at real API cost, before the mistake was caught. The file's own usage line (its header) is the authority. Worth fixing in whatever produced the dispatch word.packages/objectql/src/registry.ts, No gate requires a breaking-behavior changeset to carry the breaking annotation at all — the ADR-0087 disposition gate only fires once one already does #8607 →scripts/check-adr-0087-registration.mjs, The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589 →packages/runtime/src/domains/mcp.ts, drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009 →packages/drivers/driver-sql/src/sql-driver.ts— each a correcttracked-pathrow naming the exact path it misfiled. Give a lowered hook body a declared constant surface — every shared constant is currently hand-copied into each handler and pinned by a bespoke drift test (8 rows in the reference app) #13718 and [P2] engine ADR: durable pause inside structured regions (unlock topology-level parallel approvals / waits / subflows) #3267 produced no H9 row at all. 73 H9 rows total.privateeven once checkAuthoredRowWrite admits it #7401, which the card and the triage note both read as out of reach. The by-id write pre-image gate resolves the row under the caller's own read scope, so an app-authored widener is still dead onprivateeven once checkAuthoredRowWrite admits it #7401's value mentions#5493mid-sentence, so under the wide form it readissue-refand cleared H9; under the declaration test it lands inproseand fires the NEW mention row. That is a correct row — a card whose own words say it has "no closing event any scan can detect" should not read clean — but it is a state change on a card the dispatch put out of scope, so it is reported and not acted on. ⛔ No test was written against The by-id write pre-image gate resolves the row under the caller's own read scope, so an app-authored widener is still dead onprivateeven once checkAuthoredRowWrite admits it #7401 and its card was not touched. Nine live cards gain this row in total: tech-debt/tracking: 让「特权内部写入必须显式声明 isSystem」成为可检查契约(先立约束 → 增量迁移 → 收敛强制) #3166, The by-id write pre-image gate resolves the row under the caller's own read scope, so an app-authored widener is still dead onprivateeven once checkAuthoredRowWrite admits it #7401, Decide: shrink-only delta-ratchet for check-durability-degradation-log-level — measured at 0 net decreases/month over 3,195 commits #9882, FlipreadLegacyHeadersfrom warn-and-accept to a loud dated rejection — step 3 of the #9930 retirement #10164, 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, [finding] A contract-review PASS leaves ZERO trace on the PR itself — verdict on the card, label cleared, no review object — so a legitimate un-park is indistinguishable from an intruder from the PR side #13417, [finding] the deliberatereference_totolerance in plugin-security has no measured stored-metadata population — the prerequisite for ever narrowing it #13542, Shouldos buildfail by default on the accidental hook-body-lowering class? — deferred until the new lint rule has produced a real population number #13838, [finding] The flow language has no aggregation / group-by node — the grouping half of the digest shape, deferred by ruling until a second pull #14500.scripts/pm/**is PM-loop tooling; nothing in it is shipped by any package'sfiles[], so nothing publishes.skip-changeset.Authored by the os-dev seat in session
session_01MCLBsUgfykL74aU716rzVK(https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK), dispatched for #17605.Generated by Claude Code