Context
PR #16156 exposed a deterministic local/hosted validation split.
The author ran:
npm run agent-preflight -- --no-fix --change-class restoration \
--pr-title "fix(dashboard): retain topology across tear-out projection (#16153)" \
--pr-body /private/tmp/pr16153-body.md
Local result:
agent-preflight: PR body contains the required template anchors.
agent-preflight: all requested gates passed.
The ready PR then failed hosted lint-pr-body immediately. Its stack legitimately contained two #15955 witness commits beneath the #16153 fix, while the body declared only Resolves #16153. Hosted lint enumerated the PR commits and required the inherited ticket to be declared. Adding Related: #15955 was the correct repair.
Live source confirms the parity gap:
.github/workflows/agent-pr-body-lint.yml parses Resolves / Refs / Related, enumerates PR commits, and rejects any ticket-suffixed commit whose ticket is undeclared.
buildScripts/util/agent-preflight.mjs --pr-body validates the body anchors locally but does not inspect the branch commit set.
Memory Core, Knowledge Base, live latest-open, and GitHub keyword sweeps found no ticket owning this specific stacked-PR preflight parity.
The Problem
The command presented as the local PR gate can return “all requested gates passed” for a PR body that hosted lint is guaranteed to reject seconds later.
This creates a needless create → CI red → read remote log → edit body cycle at exactly the point where the local preflight is supposed to prevent it. The hosted diagnostic is correct; the missing substrate is an equivalent pre-creation check.
The Architectural Reality
- Legitimate stacked PRs are allowed. The rule is not “one ticket across every commit”; it is “every ticket claimed by a commit must be declared by the PR body.”
- Before PR creation there is no pull-request number to query, but the local Git commit graph already contains the necessary commit subjects.
- The comparison boundary must be the PR branch's commits relative to its intended base/merge-base, not an arbitrary commit count.
- Ticket declarations and ticket-suffixed commit parsing must remain semantically aligned with hosted
agent-pr-body-lint.yml.
- The check must work before network mutation; requiring an already-created PR would preserve the current friction.
The Fix
- Extend the local
agent-preflight --pr-body path with an intended-base input (or an equivalent deterministic base resolution).
- Enumerate the branch commits that the PR will carry and parse their trailing
(#N) ticket claims.
- Parse
Resolves, Refs, and Related ticket declarations from the candidate PR body using hosted-equivalent semantics.
- Fail locally when any ticket-suffixed commit is undeclared, listing the offending SHA, ticket, and subject.
- Add unit coverage for a legitimate declared stack, an undeclared inherited ticket, multiple commits for one declared ticket, and a commit without a ticket suffix.
- Document the stacked-body invocation in the pull-request workflow.
Decision Record impact
none — this closes parity inside the existing PR-body gate.
Acceptance Criteria
Out of Scope
- Automatically editing the PR body.
- Automatically rebasing or flattening legitimate stacks.
- Replacing the ticket gate for commit subjects.
- Changing which closing keywords Neo permits.
Avoided Traps
- Do not reject every multi-ticket stack; declared related tickets are valid.
- Do not use
origin/dev..HEAD without accounting for the intended merge-base.
- Do not require
gh pr view or an existing remote PR.
- Do not copy the hosted parser without a parity guard that can drift silently.
- Do not report a generic body-template failure when the defect is an undeclared inherited ticket.
Evidence
- Local preflight false-green: PR #16156 creation attempt, 2026-07-30.
- Hosted failure: workflow run
30528853945, job 90826121319.
- Hosted diagnosis: foreign commits
046d3571cd and 3088764ced, both claiming #15955 while the original body declared only #16153.
Origin Session ID: 019fac4d-7844-7422-9486-7f73ccf308f5
Retrieval Hint: agent-preflight stacked PR foreign commit declared ticket lint parity
Context
PR #16156 exposed a deterministic local/hosted validation split.
The author ran:
Local result:
The ready PR then failed hosted
lint-pr-bodyimmediately. Its stack legitimately contained two #15955 witness commits beneath the #16153 fix, while the body declared onlyResolves #16153. Hosted lint enumerated the PR commits and required the inherited ticket to be declared. AddingRelated: #15955was the correct repair.Live source confirms the parity gap:
.github/workflows/agent-pr-body-lint.ymlparsesResolves/Refs/Related, enumerates PR commits, and rejects any ticket-suffixed commit whose ticket is undeclared.buildScripts/util/agent-preflight.mjs --pr-bodyvalidates the body anchors locally but does not inspect the branch commit set.Memory Core, Knowledge Base, live latest-open, and GitHub keyword sweeps found no ticket owning this specific stacked-PR preflight parity.
The Problem
The command presented as the local PR gate can return “all requested gates passed” for a PR body that hosted lint is guaranteed to reject seconds later.
This creates a needless create → CI red → read remote log → edit body cycle at exactly the point where the local preflight is supposed to prevent it. The hosted diagnostic is correct; the missing substrate is an equivalent pre-creation check.
The Architectural Reality
agent-pr-body-lint.yml.The Fix
agent-preflight --pr-bodypath with an intended-base input (or an equivalent deterministic base resolution).(#N)ticket claims.Resolves,Refs, andRelatedticket declarations from the candidate PR body using hosted-equivalent semantics.Decision Record impact
none — this closes parity inside the existing PR-body gate.
Acceptance Criteria
gh pr create.Related: #15955makes the same stack pass locally.(#N)stay outside this check, matching hosted ownership.Out of Scope
Avoided Traps
origin/dev..HEADwithout accounting for the intended merge-base.gh pr viewor an existing remote PR.Evidence
30528853945, job90826121319.046d3571cdand3088764ced, both claiming #15955 while the original body declared only #16153.Origin Session ID:
019fac4d-7844-7422-9486-7f73ccf308f5Retrieval Hint:
agent-preflight stacked PR foreign commit declared ticket lint parity