Skip to content

fix(pm): H9's issue-ref class requires the reference at the declaration position - #17816

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-17605-h9-issue-ref-declaration-position
Sep 12, 2026
Merged

fix(pm): H9's issue-ref class requires the reference at the declaration position#17816
claude[bot] merged 1 commit into
mainfrom
claude/issue-17605-h9-issue-ref-declaration-position

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

classifyRestartWhen's issue-ref step tested for an issue reference the value CARRIES anywhere (/#\d+\b/), so a Restart-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 open pm:on-hold cards that let four holds whose value actually names a tracked repo path — a misfiled Restart-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 #N or owner/repo#N, after the same leading decoration this file's existing partOfDeclarationRe idiom 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 and FIREABLE_RESTART_WHEN_CLASSES are untouched. A mid-sentence mention now falls through to command / tracked-path / prose and fires the row its real shape earns; the prose row'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 TRUE

  1. classifyRestartWhen reads if (/#\d+\b/.test(v)) return 'issue-ref'; — the reference is accepted anywhere in the value.
  2. ✅ The H9 header note carries the "measured price" paragraph naming finding: the tenant-scope index is declared on FEDERATED objects, where the wall composes no predicate and no DDL is ever issued — the #8608 disagreement's mirror image #8753, 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, The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589, drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009.
  3. ✅ The prose row's remedy sentence ends with the words Mark it manual or 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).
  4. partOfDeclarationRe() sits beside partOfRe() — the file's own declaration-position idiom, mirrored here rather than reinvented.
  5. ✅ Live board, read-only: finding: the tenant-scope index is declared on FEDERATED objects, where the wall composes no predicate and no DDL is ever issued — the #8608 disagreement's mirror image #8753 / 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 / The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589 / drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009 open 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 on private even once checkAuthoredRowWrite admits it #7401 open. ⛔ No card was touched.

premise_still_valid: true.

What landed (A–E)

  • Aissue-ref narrowed to the declaration position. The predicate is a named pair mirroring the file's own: restartWhenIssueDeclarationRe() (the class test) beside restartWhenIssueRefRe() (the anywhere form, kept as a LABEL for the prose row). The decoration prefix is copied from partOfDeclarationRe; the qualifier is this file's one reference grammar (referenceRe — a full owner/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 and FIREABLE_RESTART_WHEN_CLASSES is still ['closed-ref', 'issue-ref', 'command'] (pinned twice).

  • B — the prose row's remedy now branches on whether a prose value carries a reference. With one, the row says the value MENTIONS an issue reference mid-sentence but DECLARES none, and prescribes:

    If the mentioned card IS the exit, declare it: `Restart-when: #N rules on X`
    (the reference first). Otherwise mark it `manual` or name the event
    

    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-path above issue-ref was NOT the instrument (the order is ruled) is kept. The reserved unreachable-closed …#N paragraph 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-path under an oracle naming the one path each carries; → prose under the default oracle, both directions pinned), #13718 and #3267 (→ still issue-ref). The existing #13651 rules on the count an app cannot reach pin is still issue-ref, and objectstack-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 -c on both the injected and the deleted text: narrow 1→0, wide 0→1; git hash-object moved from 6fe3ffe to d30ac02), the self-test re-run, then restored under a trap … EXIT INT TERM pinned to an absolute repo root and verified byte-identical (git hash-object back to 6fe3ffe, git status --porcelain empty).

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 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). Plus node 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 at 759905928, 6644 files.

Readings corrected, and things the PM should know

  1. The self-test flag is --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.
  2. That accidental live run is, however, the best evidence this change works. On the live board it produced: finding: the tenant-scope index is declared on FEDERATED objects, where the wall composes no predicate and no DDL is ever issued — the #8608 disagreement's mirror image #8753packages/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 #8607scripts/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 #8589packages/runtime/src/domains/mcp.ts, drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009packages/drivers/driver-sql/src/sql-driver.ts — each a correct tracked-path row 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.
  3. ⚠️ The narrowing DOES 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 on private even 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 on private even once checkAuthoredRowWrite admits it #7401's value mentions #5493 mid-sentence, so under the wide form it read issue-ref and cleared H9; under the declaration test it lands in prose and 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 on private even 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 on private even 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, Flip readLegacyHeaders from 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 deliberate reference_to tolerance in plugin-security has no measured stored-metadata population — the prerequisite for ever narrowing it #13542, Should os build fail 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.
  4. No changeset. scripts/pm/** is PM-loop tooling; nothing in it is shipped by any package's files[], 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

…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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 12, 2026
@os-sales
os-sales marked this pull request as ready for review September 12, 2026 08:23
@claude
claude Bot added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit f830fa2 Sep 12, 2026
53 checks passed
@claude
claude Bot deleted the claude/issue-17605-h9-issue-ref-declaration-position branch September 12, 2026 09:09
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

1 participant