Skip to content

porch: terminal state (protocol complete / verify gates) systemically never reaches main — 106/179 projects stranded mid-phase, 140 phantom pending gates #1229

Description

@mohidmakhdoomi

Summary

Porch's final state commits — chore(porch): <id> protocol complete, and for verify-phase protocols every verify-phase transition and the verify-approval gate record — systemically fail to reach main. They are committed and pushed to the builder branch after the PR merge, so the merge never carries them, and afx cleanup then deletes the branch (local and remote), leaving main's codev/projects/<id>/status.yaml permanently frozen mid-phase.

This is not occasional. 106 of 179 projects on main (59%) have status.yaml stranded in a non-terminal phase, the overwhelming majority of them long-merged. porch pending run from a main checkout currently reports 140 "pending" gates, including pr gates for PRs merged in April.

Observed workarounds today (both manual, both partial):

  • The human tells the architect "before merging the PR, ensure the builder pushes the chore(porch): protocol complete commit" — fixes the race for BUGFIX/AIR/PIR, does nothing for SPIR/ASPIR (see below).
  • Hand-made bookkeeping PRs that restore stranded state byte-for-byte from the dead branch — e.g. PR chore(porch): 1210 verify-approval gate-approved (restore stranded state) #1228 restoring project 1210's verify-approval approval.

How state flows (and where it breaks)

writeStateAndCommit (packages/codev/src/commands/porch/state.ts:184-210) commits status.yaml and pushes to the current builder branch on every state-machine event (Spec 653 §B.3: "zero gaps"). Spec 653 assumed: "When the branch merges, status.yaml lands on main naturally." That holds for every mid-protocol transition — and fails precisely at the end of the lifecycle, because the terminal transitions happen after the merge. Two distinct strata:

Stratum 1 — merge-ordering race (BUGFIX, AIR, PIR, MAINTAIN)

Porch's designed ordering is actually safe: when the builder polls porch next after pr gate approval, porch commits+pushes protocol complete first and only then emits the merge task in the same invocation (packages/codev/src/commands/porch/next.ts:344-351 → recursion → merge task at next.ts:262-281). If the builder follows that ordering, the complete-state commit is on the branch head before gh pr merge and rides the merge into main. That's how the 73 projects that did reach terminal state got there.

The race: the merge is usually triggered out-of-band. Porch's own gate-approval notification says "Gate pr approved — please run porch next to advance" (porch/notify.ts:37-39), but the architect role doc instructs afx send <id> "PR approved, please merge". Builders receiving that message merge immediately and only afterwards poll porch — so protocol complete lands on the branch seconds after the merge and is stranded. Sometimes the builder never polls again at all and the commit is never created.

Evidence:

Stratum 2 — structurally post-merge verify phase (SPIR, ASPIR)

For protocols with a verify phase, the merge is Step 1 of the verify phase (next.ts:810-826: "Verify phase: merge PR first, then verify"). Everything after — verify phase-transition, verify build-complete, verify-approval gate-requested, verify-approval gate-approved, protocol complete — is post-merge by design. No ordering discipline can fix this: main structurally cannot receive SPIR/ASPIR terminal state through the PR.

Evidence — ASPIR 1210 (PR #1223, merged 2026-07-22T14:00:18Z), all on origin/builder/aspir-1210, none ancestors of main:

commit subject when (UTC)
ee3adc92 verify phase-transition 14:00:47 (29s post-merge)
bfb910d7 verify-approval gate-requested 14:02:27
75ebb7ac verify build-complete (auto) 21:53:18
fc088c4e verify-approval gate-approved 21:53:20
41a4e5b2 protocol complete 21:53:22

Main's snapshot: phase: review, verify-approval: pending — a gate the human in fact approved. PR #1228 is the hand-made restore for exactly this.

This gap is already acknowledged in code as deferred work — packages/codev/src/commands/porch/index.ts:778-782: "The 'verified' state is committed to the builder branch, which may not be merged back to main. The closed GitHub Issue serves as the canonical 'done' signal on main. State alignment (making status.yaml on main authoritative) is tracked as future work per spec 653." This issue is that future work, now with data.

Stratum 3 — cleanup destroys the only surviving copy

afx cleanup deletes the local branch (agent-farm/commands/cleanup.ts:322) and the remote branch (cleanup.ts:167). After cleanup, the stranded terminal-state commits are unreachable and a #1228-style byte-for-byte restore becomes impossible. Main's stale snapshot is all that remains.

Quantified impact (audit of main, 2026-07-22)

179 codev/projects/*/status.yaml on main; 106 not in verified/complete (a handful are genuinely in-flight; the overwhelming majority correspond to merged PRs — spot-verified: 1210, 1220, bugfix-1224, 671, 761, 787, 987, …):

protocol stranded mid-phase reached terminal state
spir 20 1
aspir 3 0
air 12 1
bugfix 38 27
pir 33 41

The SPIR/ASPIR column is the structural stratum (near-total loss); BUGFIX/PIR/AIR is the race stratum (roughly a coin flip).

Consequences:

  • porch pending on main reports 140 phantom gates (some duplicated), oldest from 2026-04-15 — the human-facing gate queue is mostly noise.
  • Any consumer of main's status.yaml (dashboard Work view, Needs Attention–style derivations, future tooling per Spec 653's "status.yaml is the project's history") sees merged, verified work as blocked at a gate.
  • The archaeological record is wrong for 59% of projects: approvals the human actually granted (e.g. 1210's verify) are recorded nowhere reachable.

Relationship to existing issues

This issue documents the main-side consequence — the terminal state never becoming visible where every consumer reads it — and quantifies it. #801/#802 describe where the commits go wrong; this one is about main never being told the protocol finished.

Possible directions (not prescriptive)

  1. Fix the race (stratum 1) cheaply: change the architect flow/role docs so the post-approval message is "run porch next to continue" (matching porch's own notify text) instead of "please merge"; optionally have the merge task itself assert that the protocol complete commit is on the PR head before merging.
  2. Give post-merge state a designed path to main (stratum 2): e.g. porch approve verify-approval / terminal porch done, when the PR is already merged, writes status.yaml to the main checkout and commits it there (a small bookkeeping commit, analogous to what PR chore(porch): 1210 verify-approval gate-approved (restore stranded state) #1228 did by hand); or afx cleanup ports final state to main before deleting the branch; or a porch reconcile that sweeps merged projects and syncs terminal state.
  3. Or declare main's status.yaml non-authoritative post-merge and make consumers honor that: porch pending (and any dashboard derivation) cross-checks the PR/issue state and suppresses gates for merged work — aligning with the existing "closed GitHub Issue is the canonical done signal" stance in index.ts:778-782.

Whichever direction: the acceptance bar is that porch pending on a fresh main checkout reports no phantom gates for merged work, and a completed project's terminal state is recorded somewhere durable and reachable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions