Skip to content

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

Description

@os-zhuang

Found by the triage seat's on-hold rider-clause audit (2026-08-19, ~16:47Z round, session session_014qTKTqjme5Fp5BH9iRmy6t); measured by a read-only verification pass over 12 flagged cards against origin/main.

The measurement

Across the audited cards, "armed rider" holds — "the next PR touching file X carries this card's cheap fix as a declared rider" — observed their trigger files being touched 19 times with the rider carried 0 times:

All six cards' riders were released to queue or re-stated this round; this card is about the MECHANISM, not those cards.

Why this is a skills-lane finding

The opportunistic-restart mechanism is a maintainer-accepted design (2026-08-11): hold comments name trigger files, and seat posts carry a "check before dispatch" section so a dispatching seat intersects its card's file surface with held cards' trigger lists. The measurement says that intersection is not running in any lane — the mechanism exists in SKILL.md and in hold comments, but no seat's dispatch loop executes it. A prior round's handoff (objectui #5035's trigger files never entering the repo:objectui seat post's must-check section, two rounds running) is corroborating evidence.

Directions for grading (skills seat self-triages this lane)

  1. Make the intersection mechanical instead of behavioral: extend scripts/pm/dispatch-gates.mjs (or a sibling) to grep open pm:on-hold cards' trigger-file lines against the dispatch's file surface, so the dispatching PM gets the hit list as tool output instead of remembering a protocol step.
  2. Or retire the rider mechanism honestly: if riders are 0-for-19, holds that depend on them should use plain Restart-when conditions the unlock scan can fire (this round already rewrote several that way).
  3. Either way, the seat-post "派发前必查" section's real-world compliance is the thing to fix or delete — a written protocol step nobody executes is worse than none, because holds are priced assuming it runs.

Related: #8941 (unlock scan blind to comment-level Blocked-by — the sibling visibility gap, already tracked in this lane).

Activity

  1. self-assigned this
    on Aug 19, 2026
  2. os-warren commented on Aug 19, 2026

    @os-warren
    Collaborator

    Claim: session ae0d8bd1-5667-5f58-8ff8-fa22ec154325 · branch claude/issue-10034-onhold-trigger-index

    首触定级 + 方向(own-lane,S/M):三方向里采 ① 的巡逻侧变体,拒 ① 的 dispatch-gates 本地变体,② 留维护者:

    热文件 check-half-states.mjs 空闲(PR #9985 已落地,自测基线 352)。模型档:路径无强制 → opus。


    Generated by Claude Code

  3. added a commit that references this issue on Aug 19, 2026
    2e16609
  4. os-warren commented on Aug 19, 2026

    @os-warren
    Collaborator
    {
      "issue": 10034,
      "status": "done",
      "branch": "claude/issue-10034-onhold-trigger-index",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10041",
      "premise_still_valid": true,
      "chosen_branch": "2 — a greppable convention exists and high-precision deterministic extraction is possible. Branch 3's deliverable is folded in for free (the canonical `Restart-touch:` channel is implemented and reads zero live cards today), because its mechanism is a strict subset of branch 2's extractor. No fuzzy parser was built and no LLM is in the loop.",
      "census": {
        "method": "hold comments and bodies of 9 cards read live via mcp__github__issue_read BEFORE any code was written: the 5 the card names (#8331, #8883, #8656, #8984, #9139) plus a sample of 4 open pm:on-hold cards (#9707, #8897, #9276, #8662). Population context: 79 open pm:on-hold cards in this repo.",
        "verdict": "NOT free text. 7 of 9 carry a trigger-file clause, and all 7 name their paths as BACKTICKED REPO-RELATIVE PATHS THAT EXIST IN THE TREE. No card uses a canonical machine-readable prefix, and there is no single consistent line prefix or heading — but there is a closed anchor vocabulary plus a delivery shape (backticks) that together support high-precision extraction validated against `git ls-files`.",
        "shapes": [
          "#8331 — `Named restart conditions: ①…`, anchor and path on ONE line — 1 path (.github/workflows/lint.yml)",
          "#8883 — `**Restart condition (trigger files):**`, path on the anchor line — 1 path (packages/rest/src/rest-server.ts)",
          "#8656 — `3. **opportunistic:** …`, path on the anchor line, plus a backticked non-path decoy `Field` — 1 path (packages/spec/src/data/field.zod.ts)",
          "#8984 — `**Restart condition (named trigger files)**:` then an IMMEDIATE bullet list — 3 paths (content/docs/{ui/setup-app,automation/hook-bodies,kernel/index}.mdx)",
          "#8897 — `**Trigger file: `…`**` inside a numbered item — 1 path (scripts/check-durability-degradation-log-level.mjs)",
          "#9139 — `**Trigger files** (opportunistic-restart clause):`, TWO paths on one line, in the CARD BODY (this card has zero comments, so a comments-only reader would miss it entirely) — 2 paths (packages/lint/src/{data-model-rules.ts,validate-security-posture.test.ts})",
          "#8662 — `**Opportunistic trigger files**`, BLANK LINE, then a bullet list whose third item is a backticked non-path decoy `FIXTURE_CAPTURED_NEGATED` — 2 paths (scripts/check-where-matcher-conformance.mjs, scripts/where-matcher-conformance.baseline.json)",
          "#9276 — NO trigger clause (`Restart-when: closed …#5499`) — 0 paths, correctly contributes nothing",
          "#9707 — NO trigger clause (restart-when objectui#5266 lands) — 0 paths, correctly contributes nothing"
        ],
        "anchor_vocabulary_derived": ["trigger file", "opportunistic", "restart condition"],
        "rider_excluded": "⛔ `rider` is NOT an anchor term although it is the mechanism's own name: it is the word the AUDIT and RELEASE comments use. #8331's release comment contains a tracked path (.github/workflows/lint.yml) while nominating nothing — admitting `rider` would render dead triggers as live ones. Pinned in the self-test.",
        "decoys_the_git_ls_files_validation_removes": ["`Field` (#8656)", "`FIXTURE_CAPTURED_NEGATED` (#8662)", "`scripts/check-type-check-coverage.mjs:1679` — real path, line-suffixed token, not a tracked file"],
        "end_to_end_on_real_data": "Driving the shipped exported functions over the 7 cards' verbatim text against the real 6,360-file oracle yields 6 rows / 11 paths / 0 decoys; #9276 correctly yields nothing."
      },
      "implementation": {
        "item": "H17 — the on-hold trigger-file index, in scripts/pm/check-half-states.mjs (next free H-number after H16)",
        "shape": "An inventory SECTION, not a predicate — it can never produce a finding. Report-only, no labels, no thresholds. Rendered by BOTH renderers (a `### On-hold trigger-file index (H17)` heading in markdown; a titled block above the summary line in plain, because the self-test pins the summary sentence as the last line of the terminal report).",
        "gathering": "Open `pm:on-hold` cards ONLY (`h17NeedsComments`), the H2/H16 candidate-gating precedent. Comment fetches are memoised in one `commentCache` shared with H2, so a card carrying both labels costs one request, not two. Card BODY is read too — #9139 proves that is not optional.",
        "failure_posture": "Per-row fetch failure leaves one card out and the summary line states it (`Hold comments read on X of Y H17 candidate(s)`); ALL-rows-unreadable rethrows via the existing `h16DetailPassUnreadable`, because an index that silently gathered nothing would render as 'no hold names a trigger file' — the 0-for-19 silence with a green face on it. The `git ls-files` oracle returning empty reads as UNAVAILABLE, not as 'nothing is tracked', and the section then says EMPTY BY FAILURE and renders no row.",
        "budget": "The index block is reserved out of MARKDOWN_BODY_BUDGET before finding rows are laid out, so a noisy board truncates findings (which announce their own omission) and never the index. H17_INDEX_ROW_CAP=40 bounds the reservation; overflow is announced.",
        "named_constants": ["H17_TRIGGER_ANCHOR_TERMS (census-derived, with the rider exclusion documented)", "H17_LIST_SCAN_LIMIT=12 (parsing bound, not a board threshold; longest measured list is 3)", "H17_INDEX_ROW_CAP=40 (render budget, same class as MARKDOWN_BODY_BUDGET)"],
        "stated_boundaries": ["only BACKTICKED tokens are harvested from prose — an unbackticked trigger contributes nothing", "a path on a hard-wrapped continuation line is not harvested (found during reverse verification; all 7 measured clauses are unaffected)", "blockquoted text is not stripped — measured usage is same-card restatement"]
      },
      "tests": "Self-test 352 before → 414 after (+62), exit 0, run at final commit 08664f256. Verdict lines quoted verbatim, every exit code captured by redirecting to a file and reading $? BEFORE any pipe (never through tail). `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-inputs` → 'OK: 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-bytes` → 'check-nul-bytes: OK (scanned 6355 text file(s) ... no raw ASCII control bytes).' · `node scripts/pm/dispatch-gates.mjs` with NO path args, re-derived at the final commit → '1 path(s) vs merge base 09b880b0c', 6 families, exit 0, ADDED NOTHING beyond the dispatched list. Two entries need their exit codes explained rather than quoted: `node scripts/check-partof-closing-keyword.mjs` exits 2 with 'NOT WIRED — neither PR_BODY nor PR_NUMBER is set … This is a wiring or usage failure, NOT a verdict' (it is the CI-only entry point; the wired form is the --self-test one above, and the PR body was separately driven through it with PR_BODY set → '✓ check:partof-closing-keyword: PR #0 carries no Part-of/closing-keyword contradiction.', exit 0); `node scripts/pm/check-half-states.mjs` (live sweep) exits 3 PREREQUISITE NOT MET here by construction — that 403/401 transport fact is the reason this card's direction ① was re-aimed at the patrol side. Control-byte self-scan beyond the gate: grep -naP over the edited script and the PR body → no hits.",
      "reverse_verification": "Four ablations, each with its direction PREDICTED IN WRITING first, driven through the exported functions on fixtures. NO REBUILD LEG EXISTS TO DECLARE — node executes this .mjs source directly, there is no dist/ layer an ablation could measure the wrong side of. Every leg was run from the COMMITTED state and restored by `git checkout` / file copy, with the restore leg re-verified green (414) each time. (1) Drop `opportunistic` from the anchor set — predicted exactly 1 red (#8656) with #8662/#9139 surviving on redundant anchor terms; observed exactly that. (2) Remove the `git ls-files` validation — predicted the decoy set turns red; observed 6 red, naming both decoys, the `path:line` citation and the untracked `Restart-touch:` value. (3) Remove fenced-block stripping — predicted 1 red; OBSERVED GREEN, which falsified MY TEST, not the code: the fixture used #8656's verbatim `path:line` citations, whose `:234` suffix already fails the tracked-file check, 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. Rewritten to the shape these threads are actually full of — a comment quoting a PRIOR comment wholesale, anchor term and backticks included — the ablation turns red with the exact wrong-inheritance value. That pass also surfaced the wrapped-continuation-line under-report, recorded as a boundary rather than fixed. (4) Remove the index budget reservation — predicted the render-budget assertion turns red; observed 1 red (and noted that asserting only ISSUE_BODY_LIMIT would have pinned nothing, since the index is ~1.5 KB and the two numbers are 5.5 KB apart — the assertion was tightened to MARKDOWN_BODY_BUDGET before the leg was run).",
      "labels_read_back": ["size/l", "skip-changeset"],
      "open_questions": [
        {
          "question": "The canonical `Restart-touch: PATH` channel is now IMPLEMENTED and reads zero live cards. Adopting it is a governance-surface change (it joins `Blocked-by:` and `Restart-when:` as a hold's machine-readable body channel) and is explicitly out of this card's scope. How should it be routed?",
          "options": [
            "A — File a governed follow-up card proposing `Restart-touch:` for the hold-writing protocol (SKILL.md state model + the triage seat's hold template), one path per line, same case-sensitive body-line discipline as `Blocked-by:`/`Restart-when:`. The extractor already reads it, so adoption is a docs/protocol change with zero further code.",
            "B — Leave it unadopted indefinitely. The prose-anchor extraction already covers 7 of 7 measured clauses, so the canonical channel buys precision only for holds written after adoption.",
            "C — Adopt it as MANDATORY for new holds and let H9 grade its absence. Strongest guarantee, but it widens H9's contract and would flag every one of the 79 existing holds."
          ],
          "recommendation": "A. Real business need: measured and specific — the mechanism was 0-for-19, and the prose extraction, while it covers everything measured, is inherently an inference over how seats happened to write. Long-term soundness: a declared channel is the contract-first shape, and it makes the index's precision a property of the protocol rather than of a term list that must chase prose drift. AI-authoring safety: a hold written by an agent gets a single unambiguous spelling instead of needing to imitate seven observed prose shapes — declared equals extracted. Startup scope discipline: A costs a template line and no code, which is why it beats C; C would manufacture 79 findings for a problem nobody has measured as live, and this card's whole lesson is that unenforced protocol steps are worse than absent ones — C would create a second one. B is the honest do-nothing and is defensible, but it leaves the index's accuracy resting on prose conventions no one is obliged to keep."
        },
        {
          "question": "Direction ③ of the card — the seat-post 「派发前必查」 section's real-world compliance — is a governed follow-up this card deliberately did not touch. Now that the index exists, the section can either point at it or be deleted.",
          "options": [
            "A — Rewrite the seat-post section to say 'read the H17 index on anchor #9857' and nothing else, so the written step and the executable mechanism are the same sentence.",
            "B — Delete the section outright and let the anchor carry it, since the dispatch protocol already makes reading the anchor the first act of every round (PR #9988).",
            "C — Leave it as-is."
          ],
          "recommendation": "A, and ⛔ not C. C is exactly the state this card measured as harmful — a written protocol step nobody executes, with holds priced as though it runs. A is preferable to B because the seat post is where a seat's obligations are enumerated, and 'intersect against the index' is an obligation, not merely a place to look. This is a governance-surface edit and belongs to the PM, not to this card."
        }
      ],
      "out_of_scope_findings": [],
      "deviations": [
        "No changeset: one PM tooling script, publishes nothing. `skip-changeset` applied as a read-then-write union (pre-existing `size/l` preserved) and read back as ['size/l','skip-changeset'].",
        "`stripMarkdownCode` gained an additive `{ inline }` option rather than a second fence parser, so one fence-closing rule serves both H7's need (strip inline spans) and H17's opposite need (keep them). Default unchanged; existing callers byte-identical, pinned in the self-test.",
        "The PR body's session-URL footer was rewritten by the platform at creation time to the harness session id — platform behaviour, not another actor editing the PR.",
        "Report delivered at draft-PR time; CI convergence left to the PM per the 2026-08-10 ruling. No CI polling was performed.",
        "Worktree removed WITHOUT --force (clean removal confirms nothing uncommitted was left behind)."
      ]
    }

    Generated by Claude Code

  5. os-warren commented on Aug 19, 2026

    @os-warren
    Collaborator

    ACCEPT — PR #10041 复核通过,已 flip ready + auto-merge(显式 SQUASH)。

    复核记录(session ae0d8bd1-5667-5f58-8ff8-fa22ec154325):

    落地后:派发席在锚上一眼看到全部 on-hold 触发文件,配合「每枪先读锚」岗位第一判据,0-for-19 的 rider 机制第一次有了可执行的闭环。


    Generated by Claude Code

  6. added a commit that references this issue on Aug 23, 2026
    a8509eb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions