Skip to content

feat(pm): H17 — render the on-hold trigger-file index on the patrol anchor - #10041

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-10034-onhold-trigger-index
Aug 19, 2026
Merged

feat(pm): H17 — render the on-hold trigger-file index on the patrol anchor#10041
os-warren merged 3 commits into
mainfrom
claude/issue-10034-onhold-trigger-index

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#10034

Adds H17 — the on-hold trigger-file index to scripts/pm/check-half-states.mjs: a report-only section on the patrol anchor listing, for every open pm:on-hold card, the repo-relative files its hold comment(s) or body name as opportunistic-restart triggers.

Why the patrol side, not dispatch-gates.mjs

The card's direction ① as-written (teach the dispatch gate to grep hold comments) is rejected for transport reasons, per the PM's direction comment. dispatch-gates.mjs runs in a seat container, whose live GitHub read is 403 — measured again on this branch:

$ node scripts/pm/check-half-states.mjs --probe ; echo "EXIT=$?"
check-half-states: PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential
EXIT=3

Building the intersection there would re-create the disease one layer down: a second mechanism that cannot execute. The patrol runs on a runner where the transport prerequisite is met, so it gathers and renders; the dispatching seat reads the result during the "read the anchor first" step it already performs. Zero new seat-side dependency.

The census that decided the branch (mandatory step 1)

Nine cards' hold comments/bodies were read live before any code was written. Seven carry a trigger-file clause, and all seven name their paths as backticked repo-relative paths that exist in the tree — so a high-precision deterministic extraction is possible, and this PR takes branch 2, not branch 3.

CardClause shapePaths
#8331Named restart conditions: ①… — anchor and path on one line1
#8883**Restart condition (trigger files):**1
#86563. **opportunistic:** …1
#8984anchor sentence, then an immediate bullet list3
#8897**Trigger file: …** inside a numbered item1
#9139**Trigger files** (opportunistic-restart clause) — in the card body, which has zero comments2
#8662**Opportunistic trigger files**, blank line, then a bullet list2
#9276, #9707no trigger clause (both are Restart-when card-closure holds)0

No card uses a canonical machine-readable prefix. So the extractor reads the prose shapes that exist and the proposed canonical Restart-touch: channel (zero live matches today by design — the mechanism precedes the convention, so no second change is owed the day a hold is written with it). That is branch 3's deliverable folded in for free, since its mechanism is a strict subset of branch 2's.

Extraction — deterministic, and it drops what it cannot verify

Two closed stages, no fuzzy parsing and no LLM in the loop:

  1. Anchor. A line qualifies only via a closed term set (trigger file, opportunistic, restart condition) or a canonical Restart-touch: line. ⛔ rider is deliberately excluded — it is the word the audit and release comments use, and admitting it would render dead triggers as live ones (measured on lint.yml's "Build the ledgered packages' dependencies" step duplicates the closure refresh once #8330 lands #8331's release comment, which contains a tracked path and nominates nothing).
  2. Validate. Every candidate token is checked against git ls-files. A token that is not a tracked file is dropped, never guessed at. The measured decoys this removes are real: Field ([spec] 13 of 49 declared field types have no Field.* builder, so authors silently drop to the literal { type: … } form #8656), FIXTURE_CAPTURED_NEGATED (finding: check-where-matcher-conformance cannot grade an INVERTED survivor filter — the control probe drops it, so a delete double's combinator blindness stays ungraded #8662), and path.mjs:1679 line-suffixed citations.

The index therefore under-reports and never invents. That error direction is deliberate: a missing row costs a seat the intersection it would not have had anyway, while a wrong row sends it to intersect against a file nobody nominated — the original defect wearing a new mask.

Report-only, no labels, no thresholds, and it can never produce a finding: a hold naming trigger files is a hold in good standing. The only bound is a render budget (H17_INDEX_ROW_CAP), the same class of constant as MARKDOWN_BODY_BUDGET.

End-to-end on the real census data

Driving the shipped exported functions over the seven cards' verbatim text against the real 6,360-file oracle:

rows (6 of 7 cards)
#8331 => .github/workflows/lint.yml
#8662 => scripts/check-where-matcher-conformance.mjs, scripts/where-matcher-conformance.baseline.json
#8883 => packages/rest/src/rest-server.ts
#8897 => scripts/check-durability-degradation-log-level.mjs
#8984 => content/docs/automation/hook-bodies.mdx, content/docs/kernel/index.mdx, content/docs/ui/setup-app.mdx
#9139 => packages/lint/src/data-model-rules.ts, packages/lint/src/validate-security-posture.test.ts

11 paths, zero decoys, and #9276 correctly contributes nothing.

Reverse verification — directions predicted in writing, then driven

No rebuild leg exists to declare: node executes this .mjs source directly, so there is no dist/ layer an ablation could measure the wrong side of.

AblationPredictedObserved
drop opportunistic from the anchor setexactly 1 red (#8656); #8662/#9139 survive on redundant anchors1 red, #8656
remove the git ls-files validationthe decoy set turns red6 red — both decoys, the path:line citation, the untracked Restart-touch: value
remove fenced-block stripping1 redgreen — the test was vacuous; see below
remove the index budget reservationthe render-budget assertion turns red1 red

⭐ The fence ablation is the one worth reading. It came back green, which falsified my own test rather than the code: the fixture used #8656's verbatim path:line citations, and the :234 suffix already fails the tracked-file check on its own — so the case was measuring the oracle while vouching for a fence parser it never exercised. A bare path in a fence does not exercise it either (nothing unbackticked is ever harvested). The shape that actually needs the strip is the one these threads are full of: a comment quoting a prior comment wholesale, anchor term and backticks included. Rewritten to that shape, the ablation turns red with the exact wrong-inheritance value (packages/spec/src/data/field.zod.ts attributed to the quoting card). The fixture and the reasoning are recorded in the test.

That pass also surfaced a real under-report — a path on a hard-wrapped continuation line is not harvested — recorded as a stated boundary rather than fixed, since widening the scan would re-admit the prose it bounds away.

Gates

All at 08664f256, exit codes captured by redirecting to a file and reading $? before any pipe.

  • node scripts/pm/dispatch-gates.mjs (no args, real diff) — re-derived at the final commit; 1 path, 6 families, added nothing beyond the dispatched list.
  • pnpm check:pm-half-states✓ check-half-states self-test: 414 cases pass.
  • pnpm check:partof-closing-keyword✓ check-partof-closing-keyword self-test: 28 cases pass.
  • pnpm check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • node scripts/check-cross-package-test-inputs.mjs — same verdict line, exit 0.
  • pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6355 text file(s) ... no raw ASCII control bytes).
  • node scripts/check-partof-closing-keyword.mjs — exit 2, NOT WIRED. That is the CI-only entry point, which requires PR_BODY/PR_NUMBER from its workflow and says of itself that it is "a wiring or usage failure, NOT a verdict". This body was separately run through it with PR_BODY set, and it passed.
  • node scripts/pm/check-half-states.mjs (live sweep) — cannot run in this container by construction (exit 3, transport prerequisite). It is the gate whose unavailability this PR is about.

Self-test: 352 before → 414 after (+62), all H17: the seven measured clause shapes, both negative-census cards, the decoy drops, the anchor-set exclusion of rider, the fence quote, the canonical channel including its case-sensitivity, the continuation bounds, the gathering policy, row assembly, and both renderers in all three of the section's states (rows / read-and-empty / oracle-unreadable).

No changeset: this publishes nothing (one PM tooling script). skip-changeset applied and read back.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] Armed rider clauses on pm:on-hold cards are 0-for-19: no dispatching seat runs the pre-dispatch trigger-file intersection

2 participants

@os-warren@claude