Skip to content

[finding] The merge-queue triage comment's operative guidance prose is unpinned — its own gate suite reads only the excerpt and the aggregation #13967

Description

@claude

Found while adding a paragraph to the merge-queue triage guidance (PR #13966, card #13830). Not that card's defect class, so it was left alone and recorded here.

The reading

.github/workflows/merge-queue-triage.yml composes the triage comment it posts on every red merge_group build. Two of the lines in that body array are the operative guidance — the timeout-vs-assertion heuristic the triage checklist turns on, and (as of PR #13966) the named exception beside it.

scripts/check-merge-queue-triage-outcome.mjs is a serious pin suite for this workflow: it extracts the shipped script with a real YAML parser, drives it against real captured vitest logs under doubles, and its --self-test mutates that shipped script 17 ways and requires the battery to go red for each. It is not decoration.

But none of its 73 assertions read the guidance prose. They read:

  • the fenced excerpt lines, via a helper (excerptLines) that is deliberately scoped to code-fence content — its own docblock says why: "the comment's own explanatory prose names both FAIL and AssertionError, so a grep would let the static text satisfy assertions about the EXTRACTION." That scoping is correct for what it protects; the side effect is that no assertion can see the prose at all.
  • the anchor notes, the sighting markers, the anti-no-op announcements, the delivery/summary limbs.

Grepping the gate for the guidance text (判读的关键, 负载/时序, 行为改变) returns nothing.

Why it is worth a card

Delete both guidance lines from the body array and every gate in the derived family stays green — measured on the union for a change to this file: 19 families, all green, and the pin suite's own verdict line reports only assertion and scenario counts over extraction and aggregation.

That is the shape the repo already treats as a defect elsewhere: a surface whose correctness is asserted in prose that nothing reads back. The prose here is not commentary — the triage checklist is the whole point of the workflow (its header calls the comment "the machine-readable signal the PM dispatch loop can key on"), and the heuristic is the line that decides whether an author investigates or re-queues.

Shape of a remedy (not a decision, just the cheap end)

A prose assertion in the pin suite would have to read the whole posted body, not excerptLines — which is exactly the coupling that helper exists to prevent, so it needs its own accessor and a comment saying which question it answers. One assertion per operative claim (the rule; the named exception; the boundary sentence that the re-queue allowance is governed elsewhere) is probably the right granularity: enough that a silent deletion goes red, few enough that a rewording does not.

⚠️ Worth weighing against the opposite failure: a prose pin that quotes too much turns every wording improvement into a two-file edit, and that is how pins rot into copy-paste. The judgment call is which claims are load-bearing, and that is why this is a finding rather than a patch.

Evidence

  • .github/workflows/merge-queue-triage.yml — the guidance lines in the body array.
  • scripts/check-merge-queue-triage-outcome.mjs — excerptLines and its docblock; no assertion over postedBody prose.
  • .github/workflows/lint.yml — the Merge-queue triage outcome contract step, whose own comment enumerates what the suite holds: the excerpt naming the failure reason, and the cross-PR aggregation. Guidance prose is not on that list, so this is a gap in coverage rather than a claim the suite makes and misses.

Generated by Claude Code

Activity

  1. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 8.

    • Branch: claude/issue-13967-triage-guidance-pin
    • Worktree: ../objectstack-13967 (dedicated per-task worktree, off origin/main)
    • Domain: domain:devx per the triage (the gate's SUBJECT is the workflow's own comment correctness, not a governed surface)
    • File surface (≤3 files): scripts/check-merge-queue-triage-outcome.mjs (a new whole-body accessor beside excerptLines, one prose assertion per operative claim, and matching MUTATIONS rows so --self-test proves the new assertions can go red); .github/workflows/lint.yml — only the "Merge-queue triage outcome contract" step comment (:2857-2891 on origin/main fed4fa409), adding the third "what it holds" row; .github/workflows/merge-queue-triage.yml is the SUBJECT being pinned and is expected untouched. ⛔ excerptLines and every existing assertion unchanged; ⛔ no new mode/flag/fixture; ⛔ no whole-paragraph quoting — the PR body must list the exact pinned fragments and why each is load-bearing (the triage makes that section mandatory).
    • Premise re-verified on origin/main fed4fa409: the three operative claims sit in the body array of merge-queue-triage.yml at :734-744 (rule :734-735, named exception :737-741, boundary sentence :742 "后者由别处管"); git grep -n "判读的关键\|负载/时序\|行为改变\|后者由别处管" origin/main -- scripts/check-merge-queue-triage-outcome.mjs → zero hits (the gap the card measured); excerptLines at :497-508 with its docblock, postedBody at :506, MUTATIONS at :1076, selfTest() at :1224. Premise holds.
    • Container & model: subagent lane (S/M), sonnet.
    • Clause ②: not reached — a pin suite gains assertions; no contract, no behaviour change in the workflow.
    • Serial constraints: scripts/check-merge-queue-triage-outcome.mjs was in PR Verdict handshake for 134 scripts/** self-tests that exit 0 on an early return #14479's surface — merged (9acddde94), no hazard. lint.yml is also edited by this seat's PR docs(ci): stop hard-coding hook self-test deps in a comment #14538 (:1343, comment only) and PR Gate the workspace manifest dependency graph against cycles #14524 (:3628), both far from :2857-2891 — no textual overlap; the dev merges origin/main before reporting. Changeset: scripts/** + .github/** only ⇒ no changeset, skip-changeset label.

    Generated by Claude Code

  2. baozhoutao commented on Sep 2, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
      "issue": 13967,
      "status": "done",
      "branch": "claude/issue-13967-triage-guidance-pin",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14550",
      "premise_still_valid": true,
      "summary": "Added guidanceProse(r), a new accessor built on postedBody(r) (deliberately NOT excerptLines -- docblock explains why: excerptLines is scoped to code-fence content on purpose, and the guidance prose lives outside every fence). Added three assertions in scenario E1, one per operative claim pinned to the shortest load-bearing fragment: the rule (FIELD.includes('LOAD_TIMING_TERM') && FIELD.includes('BEHAVIOUR_CHANGE_TERM')), the named exception PR #13966 (FIELD.includes('VALIDITY_PREMISE_TERM') && FIELD.includes('DURATION_TERM')), and the boundary sentence (FIELD.includes('GOVERNED_ELSEWHERE_TERM')). Added three matching MUTATIONS rows M17/M18/M19, each expecting E1 to go red with A1 as the keepGreen control. Fixed lint.yml's step comment: added the third what-it-holds row and corrected the mutation count (measured 20 after this change; it previously said fourteen against a measured pre-change 17 -- both numbers now correct). merge-queue-triage.yml is untouched, as required.",
      "tests": "node scripts/check-merge-queue-triage-outcome.mjs --self-test -> '104 assertions, 20 mutations of the shipped script each driven to red.' node scripts/check-merge-queue-triage-outcome.mjs -> 'OK (76 assertions over 20 scenarios...)' (73+3; 20 scenarios unchanged, confirmed with --list). Counter-measurement: committed the fix, then on a REPO_ROOT-absolute path with a trap 'git checkout HEAD -- PATH' EXIT INT TERM restore, blanked the rule pair and the boundary sentence directly in merge-queue-triage.yml -- battery went RED naming exactly '[E1] the timeout-vs-assertion rule is present in the posted body...' and '[E1] the boundary sentence is present in the posted body...'; the untouched named-exception assertion correctly stayed green. Restore confirmed by git diff HEAD (empty) after. Re-ran self-test + main check after `git fetch origin main && git merge origin/main` (clean merge, unrelated files) -- both green on merged tree at 4e61df652. eslint (touched .mjs only, --format json): 1 file, 0 errors, 0 warnings; repo has one flat eslint.config.mjs with no parserOptions.project anywhere (no type-aware linting), so this narrowing cannot move any untouched file's verdict. Gate union: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-merge-queue-triage-outcome.mjs .github/workflows/lint.yml -> 35 commands, all run, reconciled with --ran (35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN). Per-command: check-aggregator-roster.mjs PASS; check-ci-filter-parity.mjs PASS; check-cross-package-test-inputs.mjs PASS; check-merge-queue-triage-outcome.mjs PASS; check-position-name-fold-loaders.mjs PASS; check-required-contexts.mjs PASS; check-self-test-wired.mjs PASS; check-self-test-workflow-commands.mjs PASS; check-shard-attestation.mjs PASS; check-step-collectors.mjs PASS; check-test-completeness.mjs NOT-MEASURED (exit 3, PREREQUISITE NOT MET -- needs a saved turbo-test log CI produces, unrelated to this diff); check-whole-set-label-write.mjs PASS; docs-audit/check-drift-comment.mjs PASS; pm/bare-root-worklist.mjs --self-test PASS; pm/ci-failure.mjs --self-test PASS; check:agent-test-spelling PASS; check:bash32-floor PASS; check:cli-command-ids PASS; check:cross-package-test-inputs PASS; check:declared-population-live PASS; check:entry-guard PASS; check:node-version PASS; check:parse-guard PASS; check:pm-dispatch-gates PASS (1240 self-test cases; bare invocation runs its OWN --self-test only, by design); check:pnpm-acquisition PASS; check:pnpm-filter-targets PASS; check:ratchet-remedy-authority PASS; check:required-contexts PASS; check:shard-attestation PASS; check:stall-guard-budget PASS; check:stall-guard-headroom PASS; check:type-check-coverage PASS; check:type-check-debt NOT-MEASURED (exit 3, PREREQUISITE NOT MET -- needs the full workspace dist/*.d.ts closure built, unrelated to this diff); check:watch-hint-literal PASS; check:workflow-status-functions PASS. 33/35 PASS, 2/35 NOT-MEASURED (both legitimate prerequisite gaps, neither a red, neither caused by this diff). CI job results not awaited (draft-PR-time report per the standing clause).",
      "mcp_calls": 2,
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  3. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #14550 merged to main as c53d8c2ac (out of the merge queue, 12:4xZ; added_to_merge_queue 11:56:12Z). Session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023), R1 wave 8.

    Close-out probes on origin/main f60ab90ae (verify worktree; git merge-base --is-ancestor c53d8c2ac origin/main → ancestor):

    • git grep -n "guidanceProse" origin/main -- scripts/check-merge-queue-triage-outcome.mjs → :524 (const guidanceProse = (r) => postedBody(r); — the NEW accessor over the WHOLE posted body, with a docblock saying why it deliberately does not go through excerptLines) and :568 (the scenario's const prose = guidanceProse(r);). Three prose assertions follow it, one per claim: the timeout-vs-assertion rule (负载/时序 + 真实的行为改变), the named exception (实验自身的有效性前提 + 时长、一个时间戳、一个耗时计数), and the boundary sentence (后者由别处管) — shortest carrying substrings, ⛔ no whole-paragraph quote.
    • MUTATIONS gained M17 / M18 / M19, each deleting one of the three fragments, each expected to be caught by E1 with A1 as the keepGreen control (so the catch is the new prose assertion, not "every scenario reacts to any change").
    • git grep -n "twenty ways" origin/main -- .github/workflows/lint.yml → :2886; the "What it holds" enumeration in the Merge-queue triage outcome contract step comment now carries row (3) the operative guidance prose … is present in the posted body, one assertion per claim over the WHOLE body, not the excerpt (:2880-2883) and the mutation list names "drop each half of the guidance prose" (:2892). Only that comment span moved in lint.yml (+20/−8 per the merge stat, comment lines only).
    • .github/workflows/merge-queue-triage.yml untouched (merge stat: 2 files, +65 −8); excerptLines and every existing assertion untouched (the accessor is additive).

    Against the triage ruling (5484515284): coverage gap closed as a SUBJECT test of the workflow's own comment (⛔ not a governed-surface pin); assertions read the full posted body, ⛔ not excerptLines; ⛔ no whole-paragraph quote; ⛔ no new mode/flag/fixture; guidance prose itself not reworded. All held. skip-changeset (scripts/** + .github/**). Zero rework.

    pm:dispatched removed with this comment; the card closes via the PR's Fixes #13967.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions