On 2026-07-26, three maintainers independently called PRs merge-eligible after reading approval, CI, reviewer seats, and head SHA while omitting mergeStateStatus. The PRs were actually BLOCKED because their old heads did not emit the newly-required integration-parity context. GitHub's native rules prevented a bad merge; the false lifecycle claims still reached peers and the operator.
The existing pure ai/scripts/lifecycle/validateMergeReady.mjs already fails closed on missing checks, merge state, and reviewer requests, but the Discussion's source audit found no production caller. Each maintainer still chose the field set manually.
Creation Gate 0:
Live latest-open sweep: checked the latest 20 created-open issues at 2026-07-26T21:05:55.677Z; no equivalent found
A2A in-flight claim sweep: checked the 30 most recent all-state messages at 2026-07-26T21:05:55.677Z; no competing claim found
Semantic KB ticket sweep was attempted on 2026-07-26 but Chroma was unavailable; exact local archive searches for source-bound merge-readiness observations and validateMergeReady tooling found no equivalent ticket.
npm run --silent ai:structure-map -- --files --loc confirms the owning siblings: ai/services/github-workflow/PullRequestService.mjs for PR-source behavior and ai/mcp/server/github-workflow/{toolService.mjs,openapi.yaml} for the MCP boundary. No new service or class file is prescribed.
The Problem
validateMergeReady() is only as safe as the bundle passed to it. Today, callers assemble that bundle from ad-hoc GitHub reads. That makes omission the default failure mode: an unfetched field can disappear before it reaches the fail-closed validator.
The incident exposed a second ambiguity. Counting emitted green checks is not the same as deriving the effective required set. The live source is GET /repos/{owner}/{repo}/rules/branches/{base}; the obvious branches/{base}/protection route returns 404 in this repository. An unreadable required set must never be rendered as an empty set.
The solution must close caller-selected field omission without creating a second merge predicate, a Memory Core dependency, a signing-key authority, or a derived required Check Run. A receipt is an observation at time T, not a validity token for a later human merge.
The Architectural Reality
ai/scripts/lifecycle/validateMergeReady.mjs is the sole strict-claim predicate. Its JSDoc explicitly preserves human-only merge authority.
ai/services/github-workflow/PullRequestService.mjs already owns PR source reads and guarded review-state preflights.
ai/mcp/server/github-workflow/toolService.mjs already routes get_conversation to the PR service. Its ordinary conversation read remains unguarded; only the opt-in merge-readiness projection gets an equivalent explicit identity wrapper before a positive observation can be returned.
ai/graph/assertExpectedIdentity.mjs already binds expected AgentIdentity, authenticated GitHub login, and Memory Core request identity. This grammar must be reused.
/rules/branches/{base} returned the same required context (integration-parity) for 10/10 resident PATs during Discussion convergence. 403/404/omitted/malformed responses remain fail-closed, including future App/cloud credentials.
GitHub Workflow is a resident-local stdio MCP server. The cloud Compose profile has no github-workflow container, so cloud-mode residents have no B′ issuer.
Existing lifecycle skill payloads mention validateMergeReady but do not route canonical merge-eligible claims through a source-owned operation.
GitHub's existing native ruleset/review/check surfaces remain the merge-time authority. This ticket does not change branch protection or create a new Check Run.
The Fix
Extend the existing get_conversation operation with projection: merge-readiness; add no MCP operation:
Add a projection-local identity wrapper that reuses assertExpectedIdentity() before the existing operation can return a positive merge-readiness observation; ordinary conversation reads stay unchanged.
In the existing PR service, derive the complete live bundle from the PR coordinate: repository, number, open/merged state, base/head, reviewDecision, reviewer requests, mergeStateStatus, emitted contexts, and the effective required set.
Bind one coherent observation: capture base/head, derive rules and checks, then refetch/compare the source coordinate before issuance. A head/base/state change during the read returns source-changed-during-read, never a mixed snapshot.
Compare the effective required set and emitted contexts in both directions. Preserve discriminated states: absent-required, pending, failing, skipped, not-applicable, and required-set-unreadable.
Feed the derived bundle into the existing validateMergeReady() and return a versioned immutable observation. Do not fork the readiness grammar.
Update the existing merge-eligibility decision atoms in the post-review-pickup, pull-request, and pr-review payloads: canonical [merge-eligible] claims cite a positive B′ marker; free-form status without one is visibly uncertified. Keep tool mechanics in the MCP description, not the skill payloads.
State the availability boundary: a cloud-mode resident has no issuer and must not emit a certified [merge-eligible] claim. If it reports status, the marker is [merge-readiness-uncertified][issuer-unavailable:cloud-mode].
Caller selects the opt-in projection and supplies only the PR number; service derives all readiness inputs; the default conversation projection is unchanged
Any unreadable/incoherent source yields diagnostics and no positive observation
Existing openapi.yaml operation description; lifecycle payloads cite behavior, not parameters
Existing payload sections only; no new skill/router
Skill reference-integrity + exact-text tests where available
Resident/cloud availability
Live deployment topology
Resident-local certification only
Cloud status carries issuer-unavailable:cloud-mode and stays uncertified
MCP description + lifecycle payload decision atom
Config/topology unit fixture or static contract test
Decision Record impact
none — B′ is a bounded source-local tool using existing GitHub Workflow, identity, and merge-readiness authorities. It does not create a new cross-service authority or merge policy.
Decision Record
Optional: Discussion #16026 is the decision authority for B′. Any expansion into Memory Core writes, signed receipts/key custody, or a GitHub-native merge gate must return to the Discussion before implementation.
Discussion Criteria Mapping
Discussion [RESOLVED_TO_AC] criterion
Ticket AC
Source-owned completeness
AC1, AC3
Exact-head and blocker semantics
AC3, AC4, AC5
Predicate authority
AC6
Identity
AC2, AC8
Freshness
AC3, AC7
Service boundary
AC13
Bypass / revalidation trigger
AC11, AC12
Resident/cloud availability
AC11
Acceptance Criteria
AC1 — Source-owned projection, zero tool growth: existing get_conversation accepts projection: merge-readiness with only the PR coordinate; readiness fields cannot be caller-supplied, no MCP operation is added, and the default conversation shape remains unchanged.
AC2 — Identity before issuance: the existing identity predicate runs for this identity-bearing read/issuance path; login/AgentIdentity/Memory Core drift returns no positive observation.
AC3 — Coherent exact-head snapshot: the result binds repository, base, head, open/merged state, and observedAt; any source change across the multi-call read returns source-changed-during-read.
AC4 — Required-set authority: effective contexts come from /rules/branches/{base}. 403/404/omitted/malformed is required-set-unreadable, never an empty set.
AC5 — Discriminated contexts: compare required and emitted contexts in both directions and expose absent-required, pending, failing, skipped, and not-applicable without flattening them to one boolean.
AC6 — Single predicate: the derived bundle is evaluated by the existing validateMergeReady(); no duplicate readiness grammar is introduced.
AC7 — Observation semantics: the versioned positive payload contains repo, pr, base, head, observedAt, bound principals, required-set digest/details, verdict, and blockers, and says observed merge-ready at T rather than asserting current validity.
AC8 — Negative semantics: identity, source-read, coherence, or validator failure yields explicit diagnostics and bounded tool-call audit provenance but no positive/copyable observation marker.
AC9 — Mutation-discriminating tests: focused service/tool tests prove the positive case and independently fail for omitted inputs, unreadable-vs-empty rules, missing required context, each context state, identity drift, and source movement during the read.
AC10 — Contextual Completeness: every new/modified public class or method carries precise JSDoc and @summary tags.
AC11 — Consumer + availability contract: existing lifecycle skill payloads require the positive B′ marker for canonical [merge-eligible]; cloud-mode status is explicitly uncertified with issuer-unavailable:cloud-mode. Tool mechanics remain solely in the MCP description.
AC12 — Two revalidation triggers: (a) a false merge-eligible claim without a B′ marker reopens the actuator question (A/E-family); (b) a false claim carrying/forging a marker reopens signed/atomic enforcement (H/D-family).
AC13 — Boundary preservation: no Memory Core read/write, receipt store, signing key/registry, Check Run, branch-ruleset mutation, webhook listener, or publish transaction is introduced.
AC14 — Skill-load audit: the PR body records /turn-memory-pre-flight placement and load effect. Changes stay in existing payload sections, with minimal or negative always-loaded delta; lint-skill-manifest.mjs --base origin/dev and reference-integrity checks pass.
Out of Scope
A new MCP operation or tool-catalog entry; this behavior is an opt-in projection on existing get_conversation.
Preventing arbitrary free-form prose or direct add_message calls.
Signed receipts, key custody, rotation/revocation, or a verifier registry.
GitHub Workflow → Memory Core coupling or cloud deployment of GitHub Workflow.
A merge-readiness Check Run, ruleset/branch-protection changes, or any new merge authority.
A consumed/invalidated receipt state machine or post-observation validity promise.
Avoided Traps
Tool-catalog proliferation: a dedicated certification operation would add another MCP tool for behavior that belongs to the existing single-PR read boundary.
Caller-supplied completeness: validating a caller-composed object preserves the omission defect.
Unreadable means empty: a 403/404 must never certify zero requirements.
Derived Check Run: making the aggregate check required is circular with mergeStateStatus/checksGreen, event-stale on same-head review changes, and changes merge policy.
Signed receipt by precedent label: Ed25519 transport does not solve key authority, revocation, or freshness.
Cross-service deposit: a local shared store fails cloud parity; a network store creates a new authority.
Skill mechanics duplication: payloads state when certification is required and how uncertified claims are marked; OpenAPI owns parameters and result mechanics.
Graduated from Discussion #16026. Cross-family graduation approval and non-author Step 2.5: Grace's B′ signal.
Context
On 2026-07-26, three maintainers independently called PRs merge-eligible after reading approval, CI, reviewer seats, and head SHA while omitting
mergeStateStatus. The PRs were actuallyBLOCKEDbecause their old heads did not emit the newly-requiredintegration-paritycontext. GitHub's native rules prevented a bad merge; the false lifecycle claims still reached peers and the operator.The existing pure
ai/scripts/lifecycle/validateMergeReady.mjsalready fails closed on missing checks, merge state, and reviewer requests, but the Discussion's source audit found no production caller. Each maintainer still chose the field set manually.Creation Gate 0:
checked the latest 20 created-open issues at 2026-07-26T21:05:55.677Z; no equivalent foundchecked the 30 most recent all-state messages at 2026-07-26T21:05:55.677Z; no competing claim foundvalidateMergeReadytooling found no equivalent ticket.npm run --silent ai:structure-map -- --files --locconfirms the owning siblings:ai/services/github-workflow/PullRequestService.mjsfor PR-source behavior andai/mcp/server/github-workflow/{toolService.mjs,openapi.yaml}for the MCP boundary. No new service or class file is prescribed.The Problem
validateMergeReady()is only as safe as the bundle passed to it. Today, callers assemble that bundle from ad-hoc GitHub reads. That makes omission the default failure mode: an unfetched field can disappear before it reaches the fail-closed validator.The incident exposed a second ambiguity. Counting emitted green checks is not the same as deriving the effective required set. The live source is
GET /repos/{owner}/{repo}/rules/branches/{base}; the obviousbranches/{base}/protectionroute returns 404 in this repository. An unreadable required set must never be rendered as an empty set.The solution must close caller-selected field omission without creating a second merge predicate, a Memory Core dependency, a signing-key authority, or a derived required Check Run. A receipt is an observation at time
T, not a validity token for a later human merge.The Architectural Reality
ai/scripts/lifecycle/validateMergeReady.mjsis the sole strict-claim predicate. Its JSDoc explicitly preserves human-only merge authority.ai/services/github-workflow/PullRequestService.mjsalready owns PR source reads and guarded review-state preflights.ai/mcp/server/github-workflow/toolService.mjsalready routesget_conversationto the PR service. Its ordinary conversation read remains unguarded; only the opt-in merge-readiness projection gets an equivalent explicit identity wrapper before a positive observation can be returned.ai/graph/assertExpectedIdentity.mjsalready binds expected AgentIdentity, authenticated GitHub login, and Memory Core request identity. This grammar must be reused./rules/branches/{base}returned the same required context (integration-parity) for 10/10 resident PATs during Discussion convergence. 403/404/omitted/malformed responses remain fail-closed, including future App/cloud credentials.github-workflowcontainer, so cloud-mode residents have no B′ issuer.validateMergeReadybut do not route canonical merge-eligible claims through a source-owned operation.The Fix
Extend the existing
get_conversationoperation withprojection: merge-readiness; add no MCP operation:assertExpectedIdentity()before the existing operation can return a positive merge-readiness observation; ordinary conversation reads stay unchanged.reviewDecision, reviewer requests,mergeStateStatus, emitted contexts, and the effective required set.source-changed-during-read, never a mixed snapshot.absent-required,pending,failing,skipped,not-applicable, andrequired-set-unreadable.validateMergeReady()and return a versioned immutable observation. Do not fork the readiness grammar.post-review-pickup,pull-request, andpr-reviewpayloads: canonical[merge-eligible]claims cite a positive B′ marker; free-form status without one is visibly uncertified. Keep tool mechanics in the MCP description, not the skill payloads.[merge-eligible]claim. If it reports status, the marker is[merge-readiness-uncertified][issuer-unavailable:cloud-mode].Contract Ledger
get_conversation({pr_number, projection: merge-readiness})existing MCP operationopenapi.yamloperation description; lifecycle payloads cite behavior, not parametersassertExpectedIdentity()get_conversationrouter policy/rules/branches/{base}required-set-unreadablevalidateMergeReady()+ source snapshotrepo/pr/base/head/observedAt/principals/requiredSet/verdict/blockers[merge-eligible]decision atompost-review-pickuplifecycle authorityissuer-unavailable:cloud-modeand stays uncertifiedDecision Record impact
none— B′ is a bounded source-local tool using existing GitHub Workflow, identity, and merge-readiness authorities. It does not create a new cross-service authority or merge policy.Decision Record
Optional: Discussion #16026 is the decision authority for B′. Any expansion into Memory Core writes, signed receipts/key custody, or a GitHub-native merge gate must return to the Discussion before implementation.
Discussion Criteria Mapping
[RESOLVED_TO_AC]criterionAcceptance Criteria
get_conversationacceptsprojection: merge-readinesswith only the PR coordinate; readiness fields cannot be caller-supplied, no MCP operation is added, and the default conversation shape remains unchanged.observedAt; any source change across the multi-call read returnssource-changed-during-read./rules/branches/{base}. 403/404/omitted/malformed isrequired-set-unreadable, never an empty set.absent-required,pending,failing,skipped, andnot-applicablewithout flattening them to one boolean.validateMergeReady(); no duplicate readiness grammar is introduced.repo,pr,base,head,observedAt, bound principals, required-set digest/details, verdict, and blockers, and saysobserved merge-ready at Trather than asserting current validity.@summarytags.[merge-eligible]; cloud-mode status is explicitly uncertified withissuer-unavailable:cloud-mode. Tool mechanics remain solely in the MCP description./turn-memory-pre-flightplacement and load effect. Changes stay in existing payload sections, with minimal or negative always-loaded delta;lint-skill-manifest.mjs --base origin/devand reference-integrity checks pass.Out of Scope
get_conversation.add_messagecalls.merge-readinessCheck Run, ruleset/branch-protection changes, or any new merge authority.Avoided Traps
mergeStateStatus/checksGreen, event-stale on same-head review changes, and changes merge policy.Related
Related: #13587 Related: #14534 Related: #15090 Related: #15919 Related: #15983 Related: #16021 Related: #16022
Source Discussion: #16026 Graduation approval: DC_kwDODSospM4BD3T_
Retrieval Hint:
D#16026 B′ source-owned merge-readiness observation required-set-unreadable identity-bearing issuanceRetrieval Hint:validateMergeReady mergeStateStatus reviewRequests integration-parity omission incident 2026-07-26