Add accepted-plan decomposition exact-once guards and UI state (#6831)

## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies, so
planning approvals and child-issue fan-out are part of the core
control-plane loop.
> - Accepted plans are supposed to be a safe bridge from planning into
execution, especially when agents wake from review decisions and reuse
isolated workspaces.
> - The duplicate-subtask incident showed that an accepted plan revision
could be interpreted more than once across overlapping runs, which broke
the single-source-of-truth model for issue decomposition.
> - Fixing that required tightening the backend contract first:
accepted-plan decomposition needs an exact-once fingerprint, durable
claim state, and retry-safe child creation.
> - Once that backend behavior existed, the board still needed
visibility into what happened, so the issue detail view needed a
dedicated decomposition section instead of forcing operators to
reconstruct child creation from raw activity.
> - This pull request adds the exact-once decomposition primitive,
hardens wake routing and regressions around the incident, and surfaces
decomposition state in the UI so future incidents are both prevented and
easier to inspect.

## What Changed

- Added accepted-plan decomposition semantics to
`doc/execution-semantics.md`, including the exact-once fingerprint,
durable claim/result expectations, and retry/resume behavior.
- Added persistent accepted-plan decomposition claims in the backend,
including schema, shared types/validators, service logic, and issue
routes for creating and listing decomposition state.
- Hardened heartbeat routing so an accepted-plan continuation stays
scoped to the relevant planning issue instead of opportunistically
re-decomposing another accepted issue on the same assignee.
- Added regression coverage for the original failure modes: concurrent
same-parent retries, cross-issue accepted-plan isolation, and partial
child recreation under the same fingerprint.
- Added the `Plan decomposition` issue-detail section plus supporting
API/query-key/activity formatting updates so operators can see revision
status, owner, child counts, and the linked child issues directly in the
UI.
- Included the small follow-up UI fix so the decomposition section still
renders when the issue work mode is no longer `planning`.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t
"lists persisted decompositions with child issue summaries"`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t
"accepted plan decomposition"
server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
server/src/__tests__/heartbeat-context-summary.test.ts`
- Manual UI path: create a planning issue without an isolated execution
workspace, add a `plan` document, accept the `request_confirmation`, let
Paperclip create child issues, then reopen the parent issue detail page
and confirm the `Plan decomposition` section shows the accepted
revision, status, idempotent-claim badge, and child links.
- Separate follow-up bug noted during manual UI validation: accepting a
plan on an issue whose run never records `workspace_finalize` is tracked
in `PAPA-445` and is not part of this PR’s fix scope.

## Risks

- This adds a new migration and a large Drizzle snapshot update;
reviewers should confirm the schema shape and generated metadata match
the intended decomposition table.
- The exact-once claim changes sit on the accepted-plan fan-out path, so
regressions there could block legitimate child creation or mis-handle
retries if the claim state machine is wrong.
- The new UI only appears when decomposition records exist; reviewers
should use the manual verification path above rather than expecting
existing issues on a stale local instance to show the section
automatically.
- `PAPA-445` remains an open follow-up for the `workspace_finalize`
accept gate when a planning handoff never records finalize; that bug can
interfere with reproducing the UI flow on isolated workspaces but does
not change the correctness of the exact-once decomposition feature
itself.

> Checked `ROADMAP.md`: this PR is a bug fix / control-plane hardening
change for accepted-plan decomposition, not a new uncoordinated roadmap
feature.

## Model Used

- OpenAI Codex via Paperclip `codex_local` (GPT-5-based coding agent;
exact backend model ID/context window not exposed in the run context),
with repository tool use, shell execution, and code-editing
capabilities.

<img width="806" height="1069" alt="Screenshot 2026-05-27 at 11 05
48 PM"
src="https://github.com/user-attachments/assets/5b00b670-96cd-4470-b0a3-581743bcae28"
/>


## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin Foley 2026-05-28 23:30:18 -07:00 committed by GitHub
parent 9eac727cf1
commit d9f91576a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 22308 additions and 16 deletions

View file

@ -1,7 +1,7 @@
# Execution Semantics
Status: Current implementation guide
Date: 2026-04-26
Date: 2026-05-23
Audience: Product and engineering
This document explains how Paperclip interprets issue assignment, issue status, execution runs, wakeups, parent/sub-issue structure, and blocker relationships.
@ -152,7 +152,73 @@ Blocked issues should stay idle while blockers remain unresolved. Paperclip shou
If a parent is truly waiting on a child, model that with blockers. Do not rely on the parent/child relationship alone.
## 7. Non-Terminal Issue Liveness Contract
## 7. Accepted-Plan Decomposition
An accepted plan confirmation is permission to decompose one specific accepted plan revision into child issues.
This complements the existing accepted-plan continuation rule: once a plan is accepted, the source issue may create child implementation issues, but it must not start implementation work on the source issue itself during that continuation.
Paperclip must treat accepted-plan decomposition as an exact-once control-plane primitive, not as a free-floating wake that any later run may interpret again.
### Exact-once fingerprint
The canonical decomposition fingerprint is:
- `(sourceIssueId, acceptedPlanRevisionId)`
Where:
- `sourceIssueId` is the issue whose `plan` document revision was accepted
- `acceptedPlanRevisionId` is the accepted `plan` document revision
This is the product contract because the accepted revision is the thing being authorized for decomposition. Re-accepting, re-waking, or re-reading the same accepted revision must not authorize a second child tree. A later accepted revision on the same source issue is a new fingerprint and may produce a different decomposition result.
An implementation may also store the accepted interaction id, acceptance run id, or other evidence, but those values must collapse onto the same uniqueness guarantee. They must not allow a second decomposition claim for the same `(sourceIssueId, acceptedPlanRevisionId)` pair.
### Durable claim and durable result
Before creating child issues, the first decomposition attempt must create or reuse a durable record for the fingerprint.
That durable record must be able to answer, without reconstructing the thread from comments or transcripts:
- whether decomposition for the fingerprint is `in_flight` or `completed`
- which run or owner currently holds the in-flight claim
- which child issues, if any, have already been created under that fingerprint
- which final child issue ids belong to the completed result
Paperclip does not need to mandate a specific storage shape in this document. The record may live in a dedicated table, source-issue execution state, interaction metadata, or another durable product surface. What matters is the contract:
- the claim is durable before fan-out starts
- partial progress is durable while fan-out is underway
- the completed child result set is durable after fan-out finishes
If a run creates some children and then dies, retries must continue from the same fingerprint and reuse the already-recorded partial result. They must not restart decomposition as if nothing happened.
### Parent live path while decomposition is in flight
While decomposition for an accepted fingerprint is incomplete, the source issue must expose an explicit live path for that same fingerprint.
The accepted interaction by itself is only evidence that the plan was approved. It is not a sufficient live path once decomposition begins. The source issue must make it clear what moves the fingerprint forward next, such as:
- the active decomposition run
- a queued continuation wake for the same assignee
- a monitor or explicit recovery action tied to the same decomposition claim
- a blocked state that names the real blocker for finishing that claimed decomposition
If the live run disappears, Paperclip must repair, resume, or visibly block the existing claim. It must not leave the source issue in a state where a second run can interpret the same acceptance as fresh permission to create sibling issues again.
### Concurrent and repeat attempts
Every later run that encounters the same accepted-plan fingerprint must consult the durable claim/result before creating children.
- If no claim exists, the run may atomically create the claim and become the decomposition owner.
- If a claim exists and is `in_flight`, the later run must reuse that claim. It may resume the same decomposition if it is the valid continuation owner, or it may exit after observing that another run already owns the work.
- If a claim exists and is `completed`, the later run must reuse the recorded child result and must not create new sibling issues.
- If the prior attempt ended after partial child creation, the retry must continue under the same fingerprint and preserve the already-created child ids.
Concurrent accepted-plan runs are therefore idempotent relative to the fingerprint. Creating multiple child trees for the same `(sourceIssueId, acceptedPlanRevisionId)` pair is a product bug.
## 8. Non-Terminal Issue Liveness Contract
For agent-owned, non-terminal issues, Paperclip should never leave work in a state where nobody is responsible for the next move and nothing will wake or surface it.
@ -292,13 +358,13 @@ A blocker chain is covered only when its unresolved leaf is live or explicitly w
A `blocked` issue is stalled when the unresolved blocker leaf has no active run, queued wake, typed participant, pending interaction or approval, user owner, external owner/action, or recovery action. In that case the parent should show the first stalled leaf instead of presenting the dependency as calmly covered.
## 8. Crash and Restart Recovery
## 9. Crash and Restart Recovery
Paperclip now treats crash/restart recovery as a stranded-assigned-work problem, not just a stranded-run problem.
There are two distinct failure modes.
### 8.1 Stranded assigned `todo`
### 9.1 Stranded assigned `todo`
Example:
@ -314,7 +380,7 @@ Recovery rule:
This is a dispatch recovery, not a continuation recovery.
### 8.2 Stranded assigned `in_progress`
### 9.2 Stranded assigned `in_progress`
Example:
@ -330,13 +396,13 @@ Recovery rule:
This is an active-work continuity recovery.
### 8.3 Recovery model-profile lane
### 9.3 Recovery model-profile lane
Cheap model profiles are only for status-only operational recovery overhead. Paperclip may request `modelProfile: "cheap"` for bounded recovery-owner work that updates task liveness, clears bad status, records a disposition, or asks for human/manager intervention. Those wakes must carry guard context such as `allowDeliverableWork: false`, `allowDocumentUpdates: false`, and `resumeRequiresNormalModel: true`.
Automatic retries that can continue source work must use the original/normal model lane. This includes failed source-work retries, process-loss retries, transient/scheduled retries, max-turn continuations, source-assignee continuations, assigned-todo dispatch recovery, and any run that can update repo files, issue documents, plans, work products, or attachments. When a cheap status-only recovery determines that actual work remains, it must hand back to a normal-model worker run before source work or persistent deliverable updates resume. Cheap recovery hints must be scrubbed from copied retry, resume, child, and downstream source-work contexts.
## 9. Startup and Periodic Reconciliation
## 10. Startup and Periodic Reconciliation
Startup recovery and periodic recovery are different from normal wakeup delivery.
@ -350,7 +416,7 @@ On startup and on the periodic recovery loop, Paperclip now does five things in
The stranded-work pass closes the gap where issue state survives a crash but the wake/run path does not. The silent-run scan covers the separate case where a live process exists but has stopped producing observable output. The productivity-review pass is later and separate; it reviews unusual progression patterns on assigned source issues, not stale run handles after a source issue already has a valid disposition.
## 10. Silent Active-Run Watchdog
## 11. Silent Active-Run Watchdog
An active run can still be unhealthy even when its process is `running`. Paperclip treats prolonged output silence as a watchdog signal, not as proof that the run is failed.
@ -402,7 +468,7 @@ This is distinct from productivity review. Productivity review asks whether an a
Detached process cleanup is operational hygiene, not source issue liveness. Cleanup should be best-effort and auditable. If cleanup fails but the source issue is already terminal with same-run durable evidence, Paperclip should preserve the cleanup failure on the run/watchdog audit trail and route only the cleanup concern to bounded recovery when a real owner/action remains.
## 11. Auto-Recover vs Explicit Recovery vs Human Escalation
## 12. Auto-Recover vs Explicit Recovery vs Human Escalation
Paperclip uses three different recovery outcomes, depending on how much it can safely infer.
@ -446,7 +512,7 @@ Examples:
In these cases Paperclip should leave a visible issue/comment trail instead of silently retrying.
## 12. What This Does Not Mean
## 13. What This Does Not Mean
These semantics do not change V1 into an auto-reassignment system.
@ -463,7 +529,7 @@ The recovery model is intentionally conservative:
- open an explicit recovery action when the system can identify a bounded recovery owner/action
- escalate visibly when the system cannot safely keep going
## 13. Practical Interpretation
## 14. Practical Interpretation
For a board operator, the intended meaning is: