4.3 KiB
4.3 KiB
T10.6 — plan-draft workflow + Suspended-approval flow
| Field | Value |
|---|---|
| Phase | P10 — Orchestration |
| Size | M — 1 to 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
Goal
Front door (b): a free-text problem statement produces a draft
WorkflowDef, parked in Suspended for human review, resumed through the
identical loader front door (a) uses.
Facts (inlined — no spec read needed)
Suspendedis an exact fit, verified against source: "a run awaiting human approval or a webhook must not hold an executor slot" (T1.3), lease released outright with no heartbeat while parked (T7.3).plan-draftis itself an ordinary poimen workflow: aModelstep producing aWorkflowDefdraft, no new kernel machinery.- The draft is validated against T3.9's capability registry before a human ever sees it — an unresolved verifier/judge/model id in the draft fails here, at draft time, not after approval.
- Approval feeds the (possibly human-edited) draft through the same
WorkflowFormat/builder + T3.1 canonicalization as door (a) — this is what keeps both doors on one IR without new proof machinery.
Steps
- Define the built-in
plan-draftworkflow: oneModelstep (problem statement → draftWorkflowDef), one validation step (registry check via T3.9). - On draft ready, transition the
plan-draftrun toSuspended, releasing its lease (T7.3). /v1/plans/{id}/approve(T10.4 route): load the (possibly edited) draft, run it through the exact loader door (a)'sWorkflowFormat/builder uses, canonicalize (T3.1), resume — matching T1.3's resume-from-Suspendedsemantics./v1/plans/{id}/reject: cancel fromSuspended(legal transition per T1.3's cancel-from-any-non-terminal rule).- Test: submit a problem statement, assert
Suspended, edit the draft, approve, assert the resultingWorkflowVersionhash matches hand-authoring the same edited YAML directly.
Acceptance
- A submitted problem statement produces a run parked in
Suspendedwith no held executor slot — lease released, per T7.3's own test shape. - Approving a draft (edited or not) produces a
WorkflowVersionbyte-identical to loading the same content by hand through door (a).
Verify
Harness: stub model for the plan-draft step, embedded redb, T7.3's
lease-release assertion pattern reused.
Integration test — tests/it_plan_draft_approval.rs:
POST /v1/problemswith a free-text statement; assert the run reachesSuspended.- Assert the lease is released and no heartbeat is emitted while
Suspended— reuse T7.3's assertion (its step 8). - Fetch the draft; edit one step;
POST /v1/plans/{id}/approvewith the edited draft. - Assert the resulting
WorkflowVersionhash equals hashing the same edited content loaded directly viaWorkflowFormat/builder — front-door equivalence, extends T3.10. POST /v1/plans/{id2}/rejecton a second draft; assert it reachesCancelled, not stuck inSuspended.- Draft referencing an unregistered verifier id; assert
plan-draft's validation step fails before ever reachingSuspended— the human never sees an unloadable plan.
Command: cargo test -p orchestration plan_draft_approval
False pass:
- Skipping step 4's hash comparison and only checking "run resumed" — a subtly different canonicalization from door (a) would still resume, just diverge from what T3.1's guarantee promises.
- Not testing reject — an approval-only test suite misses that
Suspendedmust also support "no."
Traps
- Having
/approveconstruct aWorkflowDefdirectly in the HTTP handler instead of routing through the same loader as door (a) — this is exactly the two-IR-drift bug T3.10 exists to prevent, reintroduced one layer up. - Skipping the registry validation step "since a human will catch it." The
whole point of validating before
Suspendedis that load errors are cheap pre-human, expensive post-human.
Background (not required to do this task): ../INDEX.md (customization contract ~line 19-58) · T1.3-run-executor.md · T7.3-leases-and-fencing.md · T3.10-front-door-equivalence.md