Files
poimen/tasks/T10.6-plan-draft-and-suspended-approval.md
T

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)

  • Suspended is 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-draft is itself an ordinary poimen workflow: a Model step producing a WorkflowDef draft, 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

  1. Define the built-in plan-draft workflow: one Model step (problem statement → draft WorkflowDef), one validation step (registry check via T3.9).
  2. On draft ready, transition the plan-draft run to Suspended, releasing its lease (T7.3).
  3. /v1/plans/{id}/approve (T10.4 route): load the (possibly edited) draft, run it through the exact loader door (a)'s WorkflowFormat/builder uses, canonicalize (T3.1), resume — matching T1.3's resume-from-Suspended semantics.
  4. /v1/plans/{id}/reject: cancel from Suspended (legal transition per T1.3's cancel-from-any-non-terminal rule).
  5. Test: submit a problem statement, assert Suspended, edit the draft, approve, assert the resulting WorkflowVersion hash matches hand-authoring the same edited YAML directly.

Acceptance

  • A submitted problem statement produces a run parked in Suspended with no held executor slot — lease released, per T7.3's own test shape.
  • Approving a draft (edited or not) produces a WorkflowVersion byte-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 testtests/it_plan_draft_approval.rs:

  1. POST /v1/problems with a free-text statement; assert the run reaches Suspended.
  2. Assert the lease is released and no heartbeat is emitted while Suspended — reuse T7.3's assertion (its step 8).
  3. Fetch the draft; edit one step; POST /v1/plans/{id}/approve with the edited draft.
  4. Assert the resulting WorkflowVersion hash equals hashing the same edited content loaded directly via WorkflowFormat/builder — front-door equivalence, extends T3.10.
  5. POST /v1/plans/{id2}/reject on a second draft; assert it reaches Cancelled, not stuck in Suspended.
  6. Draft referencing an unregistered verifier id; assert plan-draft's validation step fails before ever reaching Suspended — 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 Suspended must also support "no."

Traps

  • Having /approve construct a WorkflowDef directly 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 Suspended is 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