Skip to content

Commit f830fa2

Browse files
claude[bot]claude
andauthored
fix(pm): H9's issue-ref class requires the reference at the declaration position (#17816)
`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 #8753, #8607, #8589, #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: #8753 / #8607 / #8589 / #6009 open `pm:on-hold`, each naming a tracked path with the reference mid-sentence; #13718 and #3267 start with theirs; #7401 open. ⛔ No card was touched. `premise_still_valid: true`. ### What landed (A–E) - **A** — `issue-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 #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). - Direction predicted and observed: **turns red**. 16 of 3707 cases fail under the wide form, and they are exactly the position-sensitive ones — the four census values (both oracle states), the row assertions for #8753, the whole new prose-clause group, and the single-segment-qualifier pin. - The two over-reach pins (#13718, #3267 stay `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/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: #8753 → `packages/objectql/src/registry.ts`, #8607 → `scripts/check-adr-0087-registration.mjs`, #8589 → `packages/runtime/src/domains/mcp.ts`, #6009 → `packages/drivers/driver-sql/src/sql-driver.ts` — each a correct `tracked-path` row naming the exact path it misfiled. #13718 and #3267 produced no H9 row at all. 73 H9 rows total. 3. **⚠️ The narrowing DOES reach #7401, which the card and the triage note both read as out of reach.** #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 #7401 and its card was not touched. Nine live cards gain this row in total: #3166, #7401, #9882, #10164, #10757, #13417, #13542, #13838, #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](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2b08a72 commit f830fa2

1 file changed

Lines changed: 164 additions & 15 deletions

File tree

‎scripts/pm/check-half-states.mjs‎

Lines changed: 164 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2412,14 +2412,30 @@ export function h8MergedPrStillDispatched(issue, mergedPrs, openPrs) {
24122412
// classifies on its SPELLING alone, whatever #N's reachability; the paragraph
24132413
// above says why that ruling is not this card's to make.
24142414
//
2415-
// The ruling spelling stays fireable: a value carrying an issue reference
2416-
// (`#N` or `owner/repo#N`) is `issue-ref`, tested BEFORE the two unfireable
2417-
// classes, so `Restart-when: #N rules on X` reads exactly as it did. That
2418-
// order has a measured price — on the 2026-09-11 census of the 104 open holds,
2419-
// four values name a tracked repo path and read clean on an unrelated issue
2420-
// mention (#8753, #8607, #8589, #6009). Recorded here, not worked around: the
2421-
// alternative is to rank `tracked-path` above `issue-ref`, which would make
2422-
// the advertised ruling spelling fire, and that is the question above.
2415+
// The ruling spelling stays fireable, and it is read at the DECLARATION
2416+
// POSITION (#17605): a value that STARTS with an issue reference (`#N` or
2417+
// `owner/repo#N`, after the leading decoration `partOfDeclarationRe`
2418+
// tolerates) is `issue-ref`, tested BEFORE the two unfireable classes, so
2419+
// `Restart-when: #N rules on X` reads exactly as this header advertises it. A
2420+
// reference the value merely CARRIES mid-sentence is a mention, not an exit,
2421+
// and falls through to `command` / `tracked-path` / `prose`.
2422+
//
2423+
// That position test is what RETIRED the price this note used to record. On
2424+
// the 2026-09-11 census of the 104 open holds, four values name a tracked repo
2425+
// path and read clean on an unrelated mid-sentence issue mention (#8753,
2426+
// #8607, #8589, #6009); under the declaration test all four classify
2427+
// `tracked-path` and get the rewrite row the class exists for. ⛔ The CLASS
2428+
// ORDER was NOT the instrument and is unchanged: ranking `tracked-path` above
2429+
// `issue-ref` would make the advertised ruling spelling fire, and that is the
2430+
// ruled question above. Narrowing the POSITION costs no ruling because it
2431+
// preserves that spelling literally — and it is the shape this file already
2432+
// carries for `Part of #N` (`partOfDeclarationRe`), not a new one.
2433+
//
2434+
// The `prose` row moves with it: a value that mentions a card without
2435+
// declaring one is prescribed the DECLARATION (「declare it: `Restart-when: #N
2436+
// rules on X`」) beside the generic 「mark it `manual` or name the event」,
2437+
// because an author who wrote a number down was not missing an event — they
2438+
// wrote it where nothing reads.
24232439
//
24242440
// ## H9's path test is WIDER than H17's, deliberately
24252441
//
@@ -2500,6 +2516,59 @@ export function restartWhenTrackedPaths(value, isTracked = () => false) {
25002516
return out;
25012517
}
25022518

2519+
/**
2520+
* An issue reference a `Restart-when:` value CARRIES anywhere.
2521+
*
2522+
* The anywhere-form, kept as a LABEL and never as the class test: it is what
2523+
* lets the `prose` row say 「this value mentions a card」 and prescribe the
2524+
* declaration, instead of reporting a missing event to an author who plainly
2525+
* had one in mind. Same pair as `partOfRe` / `partOfDeclarationRe`, with the
2526+
* roles reversed — there the wide form classifies and the narrow one labels;
2527+
* here the narrow one classifies and the wide one labels.
2528+
*/
2529+
function restartWhenIssueRefRe() {
2530+
return /#\d+\b/;
2531+
}
2532+
2533+
/**
2534+
* The same reference, restricted to the DECLARATION POSITION — the value
2535+
* STARTS with `#N` or `owner/repo#N`, after the leading decoration
2536+
* `partOfDeclarationRe` tolerates (blockquote markers, a list bullet, a
2537+
* markdown emphasis run).
2538+
*
2539+
* ## Why this is a class TEST here, where the `Part of` one is only a label
2540+
*
2541+
* `partOfDeclarationRe`'s docblock refuses to narrow its relation, and on its
2542+
* own corpus that refusal is right: narrowing there EMPTIES a set and drops a
2543+
* row through to a fallback channel, so a real declaration written second on
2544+
* a line is a finding LOST. Nothing is lost here. `classifyRestartWhen` is
2545+
* total — every value lands in one of six classes — so a value that fails
2546+
* this test is not dropped, it falls through to `command` / `tracked-path` /
2547+
* `prose` and fires a row carrying the remedy for the shape it actually has.
2548+
* The direction of the error is therefore opposite: the wide form here loses
2549+
* findings (a mention CLEARS the card), the narrow one gains them.
2550+
*
2551+
* ## The measured reason (#17605)
2552+
*
2553+
* On the 2026-09-11 census of the 104 open `pm:on-hold` cards, the wide form
2554+
* read four tracked-path holds clean on an unrelated issue number mentioned
2555+
* mid-sentence — #8753, #8607, #8589 and #6009, every one of them a misfiled
2556+
* `Restart-touch:` line, exactly the class #17377 built a row for. The
2557+
* declaration position is H9's own advertised spelling (`Restart-when: #N
2558+
* rules on X`) read literally, so the narrowing costs no ruling: #13718
2559+
* (`#13651 closes …`) and #3267 (`objectstack-ai/cloud#861 … is scheduled`)
2560+
* both start with their reference and stay `issue-ref`.
2561+
*
2562+
* The qualifier is this file's one reference grammar (`referenceRe`) — a full
2563+
* `owner/repo`, never a bare single segment, because `/` is what separates a
2564+
* cross-repo reference from a path segment. The NUMBER test is left exactly as
2565+
* the wide form spelled it (`#\d+\b`): this card narrows the POSITION and
2566+
* nothing else.
2567+
*/
2568+
function restartWhenIssueDeclarationRe() {
2569+
return /^[ \t]*(?:>[ \t]*)*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+)?[*_]{0,3}(?:[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*)?#\d+\b/;
2570+
}
2571+
25032572
/**
25042573
* Which of the six shapes is this `Restart-when:` value? (#17377)
25052574
*
@@ -2510,9 +2579,12 @@ export function restartWhenTrackedPaths(value, isTracked = () => false) {
25102579
* the one-word spelling can never be rescued by anything
25112580
* that follows it
25122581
* 2. `closed-ref` the unlock sweep's own literal form
2513-
* 3. `issue-ref` any issue reference the value CARRIES — the ruling
2514-
* spelling H9's header admitted (`#N rules on X`), which
2515-
* is why it is tested before the two unfireable classes
2582+
* 3. `issue-ref` an issue reference the value DECLARES — one it opens
2583+
* with, which is the ruling spelling H9's header admitted
2584+
* (`#N rules on X`) read literally, and why it is tested
2585+
* before the two unfireable classes. A reference merely
2586+
* CARRIED mid-sentence is a mention, not an exit, and
2587+
* falls through to 4/5/6 (#17605)
25162588
* 4. `command` a backticked opening span, or one of the measured bare
25172589
* heads — a one-line executable predicate
25182590
* 5. `tracked-path` some token is a tracked repo file: a misfiled
@@ -2531,7 +2603,7 @@ export function classifyRestartWhen(value, isTracked = () => false) {
25312603
if (!v) return 'prose';
25322604
if (/^manual\b/i.test(v)) return 'manual';
25332605
if (/^closed\b[^\n]*?#\d+\b/i.test(v)) return 'closed-ref';
2534-
if (/#\d+\b/.test(v)) return 'issue-ref';
2606+
if (restartWhenIssueDeclarationRe().test(v)) return 'issue-ref';
25352607
if (/^`[^`\n]+`/.test(v)) return 'command';
25362608
if (RESTART_WHEN_COMMAND_HEADS.includes(v.split(/\s+/)[0].toLowerCase())) return 'command';
25372609
if (restartWhenTrackedPaths(v, isTracked).length > 0) return 'tracked-path';
@@ -2640,9 +2712,15 @@ export function h9OnHoldNoRestartWhen(issue, commentBodies, isTracked = () => fa
26402712
'`closed <owner/repo>#N`. Rewrite the line as `Restart-touch: <path>` (one path per line) ' +
26412713
'and give `Restart-when:` a real exit or `manual`'
26422714
: classes.includes('prose')
2643-
? 'its `Restart-when:` is prose naming no issue, no tracked path and no runnable ' +
2644-
'command — a `manual` in disguise; nothing schedules the actor it waits on. Mark it ' +
2645-
'`manual` or name the event'
2715+
? values.some((v, i) => classes[i] === 'prose' && restartWhenIssueRefRe().test(v))
2716+
? 'its `Restart-when:` is prose that MENTIONS an issue reference mid-sentence but ' +
2717+
'DECLARES none — a mention is not an exit; `issue-ref` is read at the declaration ' +
2718+
'position, so nothing schedules the actor this value waits on. If the mentioned ' +
2719+
'card IS the exit, declare it: `Restart-when: #N rules on X` (the reference ' +
2720+
'first). Otherwise mark it `manual` or name the event'
2721+
: 'its `Restart-when:` is prose naming no issue, no tracked path and no runnable ' +
2722+
'command — a `manual` in disguise; nothing schedules the actor it waits on. Mark it ' +
2723+
'`manual` or name the event'
26462724
: values.length > 0
26472725
? 'its only `Restart-when:` is `manual`, which no mechanism can fire'
26482726
: commentsRead
@@ -20598,6 +20676,77 @@ async function selfTest() {
2059820676
t('gate: the ruling spelling still buys nothing', needsRestartWhenComments(hold('Restart-when: #13651 rules on it'), h9Tracked), false);
2059920677
t('gate: the reserved `closed …#N` class still buys nothing', needsRestartWhenComments(hold(`Restart-when: ${v5499}`), h9Tracked), false);
2060020678

20679+
// -- H9: `issue-ref` is the DECLARATION position (#17605) -------------------
20680+
// The wide form (`/#\d+\b/`) read a MENTION as an exit, so four live holds
20681+
// whose value names a tracked repo path cleared H9 on an unrelated issue
20682+
// number written mid-sentence. Every value below is VERBATIM from the live
20683+
// card on 2026-09-12, pinned as a literal string for the reason the #17377
20684+
// specimens are: a later reader sees what the position test was measured
20685+
// against, not a paraphrase of it. Both directions, on the same four.
20686+
const v8753 =
20687+
'any PR makes provisionTenantScopeIndex in packages/objectql/src/registry.ts read ' +
20688+
'injected-column provenance (#7865 convergence map resumes), or a gate begins pinning ' +
20689+
'/meta/object/:name index accuracy';
20690+
const v8607 =
20691+
'the ADR-0087 changeset-gate family is reworked (scripts/check-adr-0087-registration.mjs ' +
20692+
'and siblings, e.g. out of #8299), or a second breaking-behavior changeset lands unmarked';
20693+
const v8589 =
20694+
'buildMcpBridge in packages/runtime/src/domains/mcp.ts reaches the engine other than ' +
20695+
'through callData, a third McpDataBridge implementation appears, or the #7823 tripwire is ' +
20696+
'narrowed or moved off the protocol ingress path';
20697+
const v6009 =
20698+
'any PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes in ' +
20699+
'packages/drivers/driver-sql/src/sql-driver.ts (price the backfill-SET-side guard, NOT the ' +
20700+
'cloud#1005-rejected shared-read heuristic), or unresolvedEpochTextRows counts non-zero on ' +
20701+
'real data, or a bare-numeric-TEXT Field.datetime site is reported';
20702+
const v13718 = '#13651 closes (ask 1 lands and reports the count of constants an app cannot reach)';
20703+
const v3267 =
20704+
'objectstack-ai/cloud#861 (Phase 3 EE governance) is scheduled, or a real customer / ' +
20705+
'example scenario needs durable suspension inside a parallel branch (parallel approval, ' +
20706+
'wait signal, or a subflow containing a pause)';
20707+
// The census oracle: the one tracked path each of the four names, and
20708+
// nothing else — the fixture states the tracked set rather than inheriting
20709+
// whatever this checkout happens to hold (`h9Tracked`'s reason).
20710+
const censusTracked = (p) =>
20711+
['packages/objectql/src/registry.ts', 'scripts/check-adr-0087-registration.mjs',
20712+
'packages/runtime/src/domains/mcp.ts',
20713+
'packages/drivers/driver-sql/src/sql-driver.ts'].includes(p);
20714+
20715+
t('H9 class: #8753 — a mid-sentence mention is not an exit', classifyRestartWhen(v8753, censusTracked), 'tracked-path');
20716+
t('H9 class: #8607 — same, the gate family it names is tracked', classifyRestartWhen(v8607, censusTracked), 'tracked-path');
20717+
t('H9 class: #8589 — same, and the mention sits after the path', classifyRestartWhen(v8589, censusTracked), 'tracked-path');
20718+
t('H9 class: #6009 — same, on a cross-repo `cloud#N` mention', classifyRestartWhen(v6009, censusTracked), 'tracked-path');
20719+
// The other direction: with no oracle the same four collapse into `prose` —
20720+
// same verdict, the less specific row — which is what the wide form's
20721+
// false-clean was hiding in BOTH oracle states.
20722+
t('H9 class: #8753 with no oracle is prose, never fireable', classifyRestartWhen(v8753), 'prose');
20723+
t('H9 class: #8607 with no oracle is prose, never fireable', classifyRestartWhen(v8607), 'prose');
20724+
t('H9 class: #8589 with no oracle is prose, never fireable', classifyRestartWhen(v8589), 'prose');
20725+
t('H9 class: #6009 with no oracle is prose, never fireable', classifyRestartWhen(v6009), 'prose');
20726+
// ⛔ And the narrowing does NOT reach a value that DECLARES its reference —
20727+
// the advertised spelling, read literally, in both its forms.
20728+
t('H9 class: #13718 declares its reference first -> still `issue-ref`', classifyRestartWhen(v13718, censusTracked), 'issue-ref');
20729+
t('H9 class: #3267 declares a cross-repo one -> still `issue-ref`', classifyRestartWhen(v3267, censusTracked), 'issue-ref');
20730+
t('H9 class: …and the decoration a declaration may carry is the `Part of` one', classifyRestartWhen('> **#13651** rules on it'), 'issue-ref');
20731+
t('H9 class: a bare single-segment qualifier is a path segment, not a repo', classifyRestartWhen('cloud#861 is scheduled'), 'prose');
20732+
// The four are H9 ROWS now, each prescribing the rewrite its class names.
20733+
t('H9: #8753 is a finding, not a legal hold', typeof h9OnHoldNoRestartWhen(hold(`Restart-when: ${v8753}`), undefined, censusTracked), 'string');
20734+
t('H9: …and the row names the tracked path it misfiled', h9row(hold(`Restart-when: ${v8753}`), undefined, censusTracked).includes('`packages/objectql/src/registry.ts`'), true);
20735+
t('H9: …and prescribes the `Restart-touch:` rewrite', h9row(hold(`Restart-when: ${v8753}`), undefined, censusTracked).includes('Rewrite the line as `Restart-touch: <path>`'), true);
20736+
t('H9: #13718 stays clean — a declaration is an exit', h9OnHoldNoRestartWhen(hold(`Restart-when: ${v13718}`), undefined, censusTracked), null);
20737+
t('H9: #3267 stays clean too', h9OnHoldNoRestartWhen(hold(`Restart-when: ${v3267}`), undefined, censusTracked), null);
20738+
// The prose row's remedy follows the value: a MENTION gets the declaration
20739+
// prescribed, a value with no reference at all keeps the #10102 sentence.
20740+
const mentionProse = 'the design session rules the question below, with #5493 context on the table';
20741+
t('H9: a prose value that MENTIONS a card is prose', classifyRestartWhen(mentionProse, censusTracked), 'prose');
20742+
t('H9: …and its row prescribes the DECLARATION', h9row(hold(`Restart-when: ${mentionProse}`), undefined, censusTracked).includes('declare it: `Restart-when: #N rules on X`'), true);
20743+
t('H9: …saying in as many words that a mention is not an exit', h9row(hold(`Restart-when: ${mentionProse}`), undefined, censusTracked).includes('MENTIONS an issue reference mid-sentence'), true);
20744+
t('H9: …while still naming the event as the other branch', h9row(hold(`Restart-when: ${mentionProse}`), undefined, censusTracked).includes('mark it `manual` or name the event'), true);
20745+
t('H9: a reference-free prose value does NOT gain the clause', h9row(h3739, undefined, h9Tracked).includes('declare it: `Restart-when: #N rules on X`'), false);
20746+
t('H9: …and keeps the #10102 sentence verbatim', h9row(h3739, undefined, h9Tracked).includes('Mark it `manual` or name the event'), true);
20747+
// ⛔ The class ORDER is untouched — B was never the instrument (#17605).
20748+
t('H9 class: the fireable set is STILL exactly the three', FIREABLE_RESTART_WHEN_CLASSES.join('|'), 'closed-ref|issue-ref|command');
20749+
2060120750
// -- H10: stale unclaimed p0 (routing-gap backstop) -------------------------
2060220751
const NOW = Date.parse('2026-08-16T12:00:00Z');
2060320752
const hoursAgo = (h) => new Date(NOW - h * 3_600_000).toISOString();

0 commit comments

Comments
 (0)