LearnNewsExamplesServices
Frontmatter
id16906
titlePost-Merge Validation deferred onto the close target has no live home
stateClosed
labels
bugaimodel-experienceagent-os
assigneesneo-opus-vega
createdAtAug 10, 2026, 9:24 PM
updatedAtAug 12, 2026, 8:38 AM
githubUrlhttps://github.com/neomjs/neo/issues/16906
authorneo-opus-vega
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 12, 2026, 8:38 AM

Post-Merge Validation deferred onto the close target has no live home

Closed Backlog/active-chunk-15 bugaimodel-experienceagent-os
neo-opus-vega
neo-opus-vega commented on Aug 10, 2026, 9:24 PM

Context

Operator-directed sweep of the last 10 merged PRs, 2026-08-10T19:12–19:20Z, asking whether each Approve+Follow-Up disposition's follow-up ticket actually exists.

Three of five A+FU PRs are clean: #16900#16904 (open), #16901#15874 (open), #16889#16897 (open). Four other merged PRs defer named, unchecked work that has no live home once the merge lands.

Body folded 2026-08-10T19:40Z. The first version proposed a same-ticket comparison and claimed it caught all four specimens. @neo-gpt-emmy replayed the actual PR bodies and falsified that: it catches 2 of 4, and my own AC-3 and AC-4 were mutually contradictory. My independent replay confirms her result and surfaces a further hole in the fork she recommended. The prescription below is rebuilt; the correction trail is in the thread.

Live latest-open sweep: checked latest 20 open issues at 2026-08-10T19:19:33Z; A2A claim sweep over the latest 30 messages at 19:20Z. No equivalent found, no competing claim.

The Problem

A reviewer approves conditionally, the author records the residual as unchecked work under ## Post-Merge Validation, and the ticket that would own it closes with the merge. An A+FU is an approval conditioned on the residual being carried; when its home closes, the condition is discharged by nobody while the reviewer's record still shows it as owned.

Four measured instances, all merged today. The discriminating column is not "which ticket" — it is whether the section names a durable home at all:

PR Resolves live obligations in PMV tickets cited in PMV durable home?
#16887 #16882 2 unchecked none no
#16894 #16893 2 unchecked #16893 (the close target) + #16887 (a PR, not an issue) no
#16899 #16895 2 unchecked none no
#16884 #16830 6 unchecked #16830 (the close target) no

Every close target listed is now CLOSED. #16882 and #16895 carry zero comments after close; #16893's only comment predates its close by 11 minutes and states AC3/AC4 are NOT_YET_MEASURED; #16830 has nothing after 11:53Z. Three of the four closed within one second of the merge — the deferral and its own invalidation are the same event.

#16887 and #16894 defer the same two container observations — one lane, deferred twice, filed never. So the daemon exit-code contract merged with its container boundary explicitly unproven, and the only artifact saying so is closed.

This is not four mistakes. learn/agentos/process/evidence-ladder.md line 62 documents the residual declaration as:

Evidence: L<X> (…) → L<Y> required (…). Residual: AC<N> [#<close-target>].

The template prescribes naming the close target. Every author here followed the documented format. That makes this a substrate defect, and it means a guard alone would put the linter in contradiction with the doc it enforces.

The Architectural Reality

Correction to the first version of this body. I wrote that the linter "knows the close target and knows the section." It does not. validatePrBody() (buildScripts/util/agent-preflight.mjs:216) is whole-body substring matching:

  • VISIBLE_PR_BODY_ANCHORS (line 17) is checked with body.includes(anchor) — it proves the heading string appears somewhere, not that a section exists or what it contains;
  • RESOLVES_PATTERN (line 29) and DECLARED_TICKET_PATTERN (line 32) match against the whole body, not a section.

So there is no section extractor today, and one is a prerequisite of any comparison. That is a real cost this ticket owns rather than hand-waves. .github/workflows/agent-pr-body-lint.yml already invokes the validator, so no new workflow or CI minute is needed.

The Fix

Two parts, and neither works alone.

1. A declared durable-home field

Add a ## Post-Merge Validation section extractor, then: if the section contains a live obligation, it must declare a durable home.

  • Live obligation — an unchecked - [ ] item, or a NOT_YET_MEASURED / Residual: marker.
  • Durable home — an explicit Residual-Owner: #N line where N is not the Resolves target.
  • A section with no live obligation (None deferred., N/A, only checked items) requires nothing.

Why a declared field rather than inferring from prose refs — #16894 is the proof. Its PMV cites the close target and #16887. A rule of "cite at least one different ticket" is satisfied by that incidental reference and passes the specimen; a rule of "cite no target reference" breaks the legitimate cross-reference to the predecessor PR. Worse, #16887 is a PR number, and nothing lexical distinguishes a PR ref from an issue ref. A declared field sidesteps all of it: unambiguous, no PR/issue disambiguation, no network call, and it fails all four specimens because none of them carries it.

2. Amend the evidence-ladder template

learn/agentos/process/evidence-ladder.md line 62 must stop prescribing [#<close-target>] and prescribe the durable home instead. Shipping the guard without this makes the linter reject the documented format.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
validatePrBody() in buildScripts/util/agent-preflight.mjs:216 this ticket gains a ## Post-Merge Validation section extractor; a live obligation without Residual-Owner: #N (≠ Resolves target) fails sections with no live obligation unchanged; --pr-draft unchanged the guard's failure message spec: each of the four bodies reds; a no-obligation section stays green
VISIBLE_PR_BODY_ANCHORS (line 17) existing unchanged — heading presence stays a separate, weaker check n/a n/a existing anchor specs stay green
learn/agentos/process/evidence-ladder.md line 62 this ticket residual template names a durable home, not the close target n/a the line itself the four PRs each followed the current template
.github/workflows/agent-pr-body-lint.yml existing workflow unchanged — already invokes the validator n/a n/a no new workflow needed

Decision Record impact

none. Amends a process doc, not an ADR.

Acceptance Criteria

  • A ## Post-Merge Validation section extractor exists and is unit-tested against a body with multiple ## sections, the section last, and the section absent.
  • A live obligation (unchecked - [ ], NOT_YET_MEASURED, or Residual:) with no Residual-Owner: line fails, naming the orphaned obligation.
  • A live obligation whose Residual-Owner: equals the Resolves target fails.
  • A live obligation with Residual-Owner: #N, NResolves target, passes.
  • Residual-Owner points at ALREADY-EXISTING ownership; it is never a licence to mint a ticket. Raised by @neo-gpt at intake and load-bearing under the operator's A+FU-off / resolve-2-before-open directive: a guard that demands a durable owner is read as a guard that demands a new ticket, which makes it the bloat generator wearing a guard's clothes. The failure message must therefore prescribe, in order: finish the obligation before merge, or name existing durable ownership, or drop the obligation — and must NOT say "file a follow-up." Verified by a spec asserting the message text contains no filing instruction.
  • Non-vacuity: a section with no live obligation and no ticket reference passes. Without this arm a guard rejecting every PMV section goes green on all the arms above.
  • Regression corpus: replayed bodies of #16887, #16894, #16899, #16884 each fail. #16894 is the discriminating case — a rule satisfied by its incidental #16887 reference is the wrong rule. #16907 passes unchanged because it has no live obligation. The unchanged #16900 and #16901 bodies fail because they do not yet carry the explicit field; each passes after declaring its already-existing owner (Residual-Owner: #16904 / Residual-Owner: #15874).
  • learn/agentos/process/evidence-ladder.md line 62 no longer prescribes [#<close-target>].
  • --pr-draft behaviour unchanged.

Out of Scope

  • Reading this guard as a reason to file anything. Explicitly: Residual-Owner names ownership that already exists. If no such owner exists, the correct outcomes are finish it now or let it go — under the operator's directive (A+FU off; resolve 2 before opening) filing is the last resort, not the default. @neo-gpt named this at intake and it is the constraint most likely to be lost by a future implementer reading only the Fix section.

  • Ownership of this lane by @neo-gpt. He accepted intake, then operator policy put him at reviews-only with no git tools and no ticket work. The lane is released, unclaimed, and his intake constraint is carried above rather than carried by him.

  • Re-homing the four residuals. Their authors' and reviewers' calls, broadcast separately. #16882/#16893's AC3/AC4 needs a coordinated destructive window and a live leaf; #16895 and #16830 are dischargeable by a body edit on the closed ticket. This ticket delivers the guard and the template amendment only.

  • Whether any of the four A+FU dispositions was correct. Each approval stands; only the residual's destination was wrong.

  • The PR review-body linter (agent-pr-review-body-lint.yml). The reviewer names the residual; the author's body routes it, and routing is the check. A plausible successor, deliberately not bundled.

  • Retroactively reopening #16882, #16893, #16895, or #16830.

  • Resolving ticket refs through the GitHub API to test open/closed state. The declared field makes it unnecessary, and a lint that needs the network is a lint that fails offline.

Avoided Traps

  • Inferring the durable home from prose refs. Falsified by #16894 above: an incidental predecessor-PR reference satisfies "names a different ticket," and no lexical rule separates a PR number from an issue number.
  • The same-target comparison alone (this ticket's first prescription). Catches 2 of 4; the two silent shapes cite nothing at all, so it exempts exactly the worst cases.
  • Two ACs that cannot both hold. The first version required "each of the four bodies fails" and "a body with no ticket reference passes" — #16887 and #16899 satisfy the second and are counterexamples to the first. Recorded because the arms each read correct in isolation.
  • Another prose rule. The discipline is already in two skill files. A third sentence net-increases loaded bytes and changes nothing.
  • Shipping the guard without the doc amendment. The template prescribes the shape the guard would reject.
  • Reopening the closed tickets as the fix. A reopened close target evaporates again on the next merge — treats the instance, leaves the class.

Related

Instances: #16887 / #16882, #16894 / #16893, #16899 / #16895, #16884 / #16830. Durable-owner source examples (the guard requires their owner to be explicit in PMV): #16900#16904, #16901#15874, #16889#16897.

Origin Session ID: 5629adeb-c743-45f8-9fb2-c2ea738a1a35

Retrieval Hint: "Post-Merge Validation deferred onto the close target has no live home"; query_raw_memories on "A+FU follow-up ticket missing after merge"; buildScripts/util/agent-preflight.mjs PMV section extractor + Residual-Owner field.

tobiu unassigned from @neo-gpt on Aug 10, 2026, 10:29 PM
tobiu referenced in commit 5ba7f88 - "feat(build): deferred work must name an owner that survives the merge (#16906) (#16940) on Aug 12, 2026, 8:38 AM
tobiu closed this issue on Aug 12, 2026, 8:38 AM