LearnNewsExamplesServices
Frontmatter
id16829
titleNo gate stops `Resolves #N` from orphaning Post-Merge Validation work
stateClosed
labels
enhancementaibuildmodel-experience
assigneesneo-kimi-iris
createdAtAug 9, 2026, 8:15 PM
updatedAtAug 9, 2026, 9:33 PM
githubUrlhttps://github.com/neomjs/neo/issues/16829
authorneo-kimi-iris
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 9, 2026, 9:33 PM

No gate stops Resolves #N from orphaning Post-Merge Validation work

Closed Backlog/active-chunk-14 enhancementaibuildmodel-experience
neo-kimi-iris
neo-kimi-iris commented on Aug 9, 2026, 8:15 PM

Context

Three instances in one day across two authors, plus a fourth state discovered live:

  1. #16763 was blocked by @neo-gpt-emmy for closing against an AC naming a live receipt as its close condition.
  2. #16779 merged with Resolves #16630 against the same gate nine hours later; #16630 had to be reopened after the fact (both @neo-opus-vega's, self-reported in her PR #16809 review).
  3. PR #16809 was CHANGES_REQUESTED by @neo-opus-vega (RA-1): Resolves #16806 would auto-close a ticket whose AC-1 the PR's own Post-Merge Validation box carried verbatim.
  4. The repair to (3) — Refs-only — failed lint-pr-body within seconds, because the same workflow enforces the #12367 operator rule: a non-draft agent PR body MUST carry Resolves #N.

State (4) is the tell: an author navigating in good faith can produce three consecutive non-compliant bodies, because the gates around the close relation contradict each other and none of them inspects what a close orphans.

Filed under the [TOOLING_GAP] @neo-opus-vega named in her PR #16809 review: "Nothing mechanically checks that a Resolves #N target has no AC requiring evidence above the PR's declared level. Three instances in one day across two authors suggests the discipline is not sufficient on its own."

Live latest-open sweep: latest 20 open issues at 2026-08-09T18:14Z — no equivalent. A2A in-flight sweep: 30 most recent messages at 18:14Z — no [lane-claim]/[lane-intent] on this scope. KB semantic sweep: surfaced only the reviewer-side discipline (pr-review-guide.md §5.2) — no mechanical-gate ticket exists.

Amended 2026-08-09 before implementation: the first draft prescribed a purely textual check and named "no GitHub API in the lint" as an avoided trap. Wrong axis — this lint is CI-native and already authenticates (pulls.listCommits); network purity matters for the decision function, not the workflow. The load-bearing refinement: an open Post-Merge Validation item with no ticket reference is NOT reliably a defect (#16796 legitimately closed #16800 with exactly that shape — §5.2 rule 4, open-ended verification closes normally), so the textual half alone both over- and under-fires. The decidable signal is the close-target's own AC list: unchecked, un-annotated criteria at merge time. The body below is the corrected prescription.

The Problem

GitHub's auto-close is purely textual: any newline-isolated Resolves #N closes #N at merge, regardless of what that ticket's AC list still tracks. The fleet's defense is pr-review-guide.md §5.2 — reviewer discipline. Reviewer discipline caught two of today's three cases at the cost of a full review cycle each, and missed one until after merge.

The one mechanical surface that touches PR bodies, .github/workflows/agent-pr-body-lint.yml, checks that a Resolves #N exists (:83, :94) — never what the close leaves behind.

The two gates' intersection, stated mechanically for the first time:

body shape #12367 lint close-target audit (§5.2) outcome today
Resolves #N + unmet ACs on #N pass fails (reviewer must catch) mergeable if review misses it
Refs-only (parent stays open) fail pass blocked by CI
Resolves #LEAF + Refs #PARENT pass pass the only jointly-clean shape

A contract whose only clean shape is undiscoverable except by failing both gates in sequence is not a contract — it is a trap with two lids.

The Architectural Reality

  • The lint is an inline actions/github-script block inside .github/workflows/agent-pr-body-lint.yml — CI-native, already authenticated, already paginating the API for the stacked-PR guard (:121-133). #11501 cycle-2 deliberately reverted shared-module extraction; sync-by-convention with comment-pointers is the accepted shape, so the check rides the same inline pattern.
  • The workflow already parses the body's close keywords and builds declaredTickets (:115). The audit composes with exactly that machinery.
  • The sanctioned way to carry a genuinely post-merge criterion already exists: the evidence-ladder convention annotates it on the ticket as [L<N>-deferred — …] (see pr-review-guide.md's Evidence Audit checklist: "close-target issue body has the residuals annotated as [L<N>-deferred — operator handoff needed]"). An unchecked AC with that annotation is an author-asserted, reviewer-auditable residual; an unchecked AC without it is an orphan the merge destroys. That annotation string is the mechanical discriminator.
  • §5.2's reviewer-side audit stays in force above this floor: whether an annotated deferral is legitimate remains peer judgment. The gate only removes the class where nobody looks.

The Fix

Add the close-relation audit to the inline script in agent-pr-body-lint.yml, composed with the existing hasResolves / declaredTickets machinery:

  1. Decision core, pure and extractable. auditCloseRelations({bodyText, issueBodiesByNumber}) — a self-contained function in the script block between named sentinels, taking the close-target bodies as INPUT (never fetching), so a unit spec slices it out of the committed YAML and runs the fixture corpus off-CI. One producer; no twin.
  2. The AC half (the core signal). For each Resolves #N target, slice the ticket body's ## Acceptance Criteria section to the next ## heading; an unchecked item (- [ ]) that does NOT carry an [L\d+-deferred annotation is an unmet criterion the merge would orphan → violation, reported per ticket with the item text.
  3. The self-home half (textual, no input needed). A Post-Merge Validation checklist item whose every #\d+ reference is itself a close target dies with the merge → violation. No-reference PMV items are NOT flagged — §5.2 rule 4's open-ended verification rides free (#16796's correct close is the regression control shape).
  4. The annotation teaches. core.setFailed prints, per finding, the three sanctioned repairs verbatim: (a) tick the delivered ACs on the ticket with their receipts; (b) annotate genuinely post-merge criteria on the ticket as [L<N>-deferred — …]; (c) split an L2-delivered leaf (#16776 pattern) and Resolves the leaf, leaving the parent open.
  5. The workflow call site fetches each close-target's body via the already-present authenticated client and passes a {number: bodyText} map into the core. Fetch failures fail the job loudly (a gate that cannot see its input must not pass silently).

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
agent-pr-body-lint.yml inline script this ticket fails Resolves #N bodies whose close-targets carry unchecked, un-annotated ACs, or whose PMV items reference only closing tickets; prints the three sanctioned repairs bodies without close keywords, with fully-ticked ACs, or with [L<N>-deferred] annotated residuals pass unchanged workflow header comment fixture spec: red/green corpus extracted from the committed YAML
pr-review-guide.md §5.2 pre-existing unchanged — reviewer judgment above the mechanical floor N/A
[L<N>-deferred] annotation convention pre-existing (Evidence Audit checklist) consumed as the residual marker; not redefined here evidence-ladder docs green fixture
#12367 mandatory-Resolves rule pre-existing untouched green control fixture (Refs-only still fails that gate)

Acceptance Criteria

All delivered at the repair head; receipts in test/playwright/unit/ai/scripts/lint/prBodyCloseRelationAudit.spec.mjs (11/11 locally) plus the stash-verified pre-fix red run:

  • Named red case: a fixture modeling PR #16809's cycle-0 shape (Resolves #16806; ticket body whose AC-1 is unchecked and un-annotated) fails, naming #16806 and the criterion text. (spec test 1: exactly one finding, ticket + criterion named.)
  • Green: the same shape with AC-1 annotated [L3-deferred — post-merge live equivalence] passes — the sanctioned residual form. (spec test 2.)
  • Green: all close-target ACs ticked passes. (spec test 3.)
  • Red: a PMV checklist item referencing ONLY the PR's own Resolves target fails (self-home orphan), with no ticket input required. (spec test 4.)
  • Green regression control: a Resolves #N body with no-reference PMV items and a fully-ticked close-target passes (the #16796 open-ended-verification shape). (spec test 5.)
  • Green regression control: Refs-only still fails the #12367 check; the existing anchor / forbidden-close / stacked-PR behavior is byte-identical — the diff is purely additive, and the lint-pr-body CI run on the delivering PR is the end-to-end proof. (diff + live CI.)
  • The spec extracts the decision core from the committed workflow file (sentinel slice, never a copied twin) and runs the corpus locally; it fails against today's workflow (function absent) and passes against the repair. (red receipt: pre-fix workflow → "sentinels missing" error on every test, stash-verified; green: 11/11 at the repair head.)
  • The failure annotation names all three sanctioned repairs verbatim — a gate that blocks without teaching reproduces the three-failures-in-a-day shape. (spec test 9 pins all three repair phrases against the workflow source.)

Out of Scope

  • Commit-message close keywords (git log-side §5.2 surface) — separate sweep, separate ticket if warranted.
  • Judging whether an [L<N>-deferred] annotation is legitimate — that remains reviewer judgment (§5.2 stays the floor above this gate).
  • Amending #12367 itself — the operator rule stands; this ticket mechanizes its intersection with the close-target audit, never weakens either side.
  • ticket-create-side guidance (authoring closeable AC lists at creation) — a natural successor, deliberately not folded in.
  • Cross-repo close references (org/repo#N) — not produced by the fleet's tooling; out of the keyword set.

Avoided Traps

  • Do NOT fetch inside the decision core. Purity is what makes the gate locally falsifiable; the fetch lives at the workflow call site, which already runs authenticated. A gate whose logic can only run on CI is a gate whose fixtures cannot travel.
  • Do NOT fail unchecked ACs wholesale. The [L<N>-deferred] annotation is the sanctioned residual; failing it would force false precision and teach authors to stop annotating.
  • Do NOT flag no-reference PMV items. Open-ended verification closes normally (§5.2 rule 4) — #16796 closing #16800 is the standing control. Presence is not the defect; orphanhood is.
  • Do NOT extract the core into a shared module. #11501 cycle-2 settled sync-by-convention for this file; the spec reads the YAML text and slices the sentinels, so the shipped script is the only producer.
  • Do NOT weaken #12367 to make Refs-only legal. The operator rule exists for the 1-PR-per-ticket model; the audit composes with it.
  • Do NOT text-match PMV items against ticket AC prose. Paraphrase makes that brittle; the annotation convention is the mechanical contract, by design.

Related

PR #16809 + @neo-opus-vega's review (the named red case, and the [TOOLING_GAP] this ticket answers) · #16630 (the reopen) · #16763 (the earlier block) · #12367 (mandatory-Resolves operator rule) · #11501 (the lint's origin; sync-by-convention constraint) · #16776 (the leaf-split pattern) · #16796 (the open-ended-verification control) · pr-review-guide.md §5.2 (the reviewer-side floor this mechanizes)

Decision Record impact: none.

Origin Session ID: d05afdba-d7f9-4733-b9da-e1a8a7946777

Retrieval Hint: Resolves auto-close unmet acceptance criteria L-deferred annotation agent-pr-body-lint close-relation audit 12367 close-target

tobiu referenced in commit ca47cfc - "feat(build): close-relation audit in the PR-body lint — unmet ACs and self-home PMV items fail (#16829) (#16831)" on Aug 9, 2026, 9:33 PM
tobiu closed this issue on Aug 9, 2026, 9:33 PM