Deployment plan execution
Run item cases (QA written on one item, or added by the flow without a plan) with the item and its pinned stage:
yoke qa plan run --item PREFIX-N --stage STAGE --project P
A post-deploy plan attached to release members runs once per release as one plan group:
yoke qa plan run --deployment-run-id RUN --stage item-qa --plan PLAN --project P
See item recovery, target binding and yoke qa plan run --help.
Admission copies executable item cases, retaining others on the item. Attached
plans supply cases without copies; stage targets supply target_env for corrections.
QA continuity: item authority, release takeover, cross-build observations and exact-execution recovery.
Plan groups
When item-qa dispatches, each attached plan (a live attachment or project
default, unretired, for the run's environment) that a member still owes gets
one plan group: memberless run rows, one per case and required baseline, at any
number of attaching items. Its target records
deployment.shared_plan = {plan_id, source_revision, group_id}; group_id is
set once and never changes. The deployed candidate must contain the plan's
source revision, and cases are filtered to the target. The run pins each plan
at its first group, so plan edits during a release wait for the next release.
A run holds at most one open group per plan.
The driver runs groups outside dispatch; review never stops other groups.
Manual/review waits also notify owing item holders (else project steering),
naming cases and recovery; closure withdraws them. Driver notices fall back
to starter, then steering. Plan-only holders park. Recipes retain --env.
Plan commands have no member: DEPLOYMENT_MEMBER_REF refuses as
qa_plan_member_reference_unsupported. Read YOKE_QA_CANDIDATE_TREE (JSON with
root, head_sha) or detached HEAD. yoke watch pytest -- <paths> runs locally
in that candidate. Post-deploy command-ci refuses as
deployment_ci_case_unsupported; use a Command case against the candidate.
--plan PLAN at item-qa runs that plan's open group for the whole release;
no command selects one member's plan cases, so attach the plan instead. With no
open group it refuses plan_group_not_open, listing the plan's closed groups.
A group closes when the existing settlement rules accept it: every case passes
(a corrected case counts through its replacement) and any required stage
acceptance is recorded. Closing records covered_item_ids once: the run
members attaching the plan at that moment. A closed group never changes or
moves. An item is done only when each attached post-deploy plan has a closed
group covering it, in its target-qualified completion run (including a
supplemental staging run that recorded the delivered revision) or a run that
run superseded. An item or attachment that arrives after a group closed is not
covered: it gets a new group with its own group_id. A verification pass never
counts for post-deploy.
A failing case keeps the group open; a retry reruns only failed cases. One notice to the same recipient names the report and the group's items and releases no member; that session decides who fixes what.
Recording a case verdict while its stage is executing settles that stage in
the same write; a group case settles its group. When the stage cannot settle,
the write refuses as qa_verdict_settlement_refused with the cause and whether
the verdict persisted. Record an unpersisted verdict again after repairing the
cause. For a persisted verdict, let the runner adopt it with
yoke --env CONTROL-PLANE watch deploy -- RUN-ID.
For a correction, name its plan and failed requirement. A group case uses the group form above with the same flag:
yoke qa plan run --item PREFIX-N --stage STAGE \
--plan CORRECTED_PLAN --project P --replaces CASE_KEY=FAILED_REQUIREMENT_ID
Only the correction plan's rows run; other cases remain owed. A passing independent verdict supersedes the failed requirement.
Takeover moves an open group, its results and any running execution to the
successor unchanged; a successor member attaching the plan joins it. Closed
groups stay in the run where they passed. Removing a member or withdrawing its
attachment keeps the group for the members still attaching. When none is left,
yoke deployment-runs remove-item retracts the open group and stops its
execution; passed rows are never retracted. A retracted group is never reused:
a later attacher gets a new group.
Machine partitions
When a deployment member's cases require several Test Machines, the scoped command executes one immutable roster per machine group, each with its own lease in that host's FIFO and the same run, stage and frozen target. Sessions walk separate plans concurrently, one live execution per session and target. Begin skips held groups and aborts abandoned holders. Plan order holds across machine groups. A waiting host or independent review stops the command. Re-enter the same scoped command while review is pending to reprint that bundle without recapturing. Once a verdict batch is recorded, the same command reprints passing verdicts and does not capture those settled requirements; a new capture of a passed requirement uses --replaces. A failed or undetermined verdict stays open: rerunning begins a fresh execution that captures it again. After review settles, rerun to continue any remaining hosts. Completed scoped cases with passing evidence are retained. The member is accepted only when every required case passes. Omit --test-machine for this multi-machine form: a single host pin cannot satisfy conflicting constraints.
For one case that needs several hosts simultaneously, declare its driving host
as method_config.test_machine and every required host in
method_config.test_machines; names must be registered, unique, and include
the driving host. Run this case through yoke qa plan run; direct case
execution refuses it. The
runner acquires every host through its FIFO in sorted name order, releases
partial acquisitions on contention, and retains the full set through case
execution and mission review. Completion or abort releases the set; completed captures remain reviewable. Independent cases with different driving hosts or declared sets use separate
execution rosters. An inherit chain uses its head driver and the union of
its hosts as one execution group. Admission reserves all chains' hosts for
that driver before the first capture and retains them through completion,
review or abort. Contention releases partial acquisitions and queues the
chain; a retry never reacquires lost custody. Definition-time validation
refuses inherited_driver_mismatch for a changed driver: use the predecessor's
method_config.test_machine, or start a new baseline / as_is chain.
An exploratory mission walks after captures and may inherit only at a chain's
tail; no case may inherit after it. Item and deployment scopes isolate the
whole chain so no later capture disturbs its state. Standalone mixed plans
refuse inherited_mission_requires_isolated_plan: put the chain in its own
plan or use item/deployment attachment. Declare inherit to preserve
preparation; as_is opens a separate chain.
Shared host turns and starting state
An occupied or unhealthy Test Machine is a wait, never a FAIL. If your host
is busy, let the plan runner queue you, then park; you are woken when it is
your turn. When a host its case needs is leased, or its newest health fact
is a failed verification, plan acquisition keeps its cursor open and its
session joins the host's waiting line (qa_test_machine_waits), the same
FIFO line a refused manual yoke claims coordination-claim acquire --key QA_HOST:<machine>
joins. The runner prints Queued for QA host <machine>, position N of M with
the park command; write no host hold of your own. A session keeps its place
from its first wait and across retries; a rerun of an aborted walk
that held the host takes its place. Every release of the host — including
yoke claims work release --claim-id N — reserves it for the oldest live
waiter and sends that session the exact resume command; stopped owners use the
same harness-neutral wake route as other control-plane notices. An unhealthy
host is never reserved: it stays free for the operator's
yoke test-machine verify or yoke test-machine golden-capture, and that
operation's successful release wakes the first waiter. A plan case whose host
SSH cannot reach (ssh_unavailable: connect timeout, refusal, no route or a
rejected key) records that failed verification itself and queues the same way
instead of recording an error verdict. Leases start and stop cloud hosts;
start others (or repair SSH), then run yoke test-machine verify. Reset and
diagnosis receipts do not change health. Ending the session, settling the
execution, or releasing an unused reservation cancels the wait and passes a
reserved turn to the next waiter.
Removing a release member ends its QA execution and releases its hosts when every mission already recorded its walk-end restore. Otherwise the removed mission retains custody for its original owner's walk-end teardown. A deliberate host release by the holder, a covering steering seat, or claim id cancels the live execution and releases its entire host set in the same transaction; it records the release reason and never counts as a QA pass. Stop the runner and recover the host's starting state before using manual release.
Before it parks, a queued plan runs every remaining host-free automatic case in
its roster — Command and Command (CI) — through the ordinary case runner and
records each result against its requirement. A failing case is recorded and
the plan stays queued; a case whose runner raises stays unrecorded and runs in
order on resume. The park message names the cases it ran. On the host, the
cursor steps over cases already recorded, so resume never reruns them. Machine
cases, inherit chains, baselines, and agent- or human-judged cases keep their
roster order.
If a walker discovers a second required host is occupied, return
WALK_STATUS: HOST_WAIT and its registered name plus holder evidence. Submit
through the current bundle's yoke qa plan review-submit ... --stdin:
{"host_wait":{"test_machine":"linux-example","rationale":"holder evidence"}}
Submit this object alone. It records no verdict or human review request,
retains the old capture and bundle as history, and queues a new capture against
the identical immutable target. Host-ready and continuation commands omit
--test-machine, so every platform in a multi-host roster can resume. The scheduler
keeps simultaneous cases on their declared driver with the full host set. For
single-host cases it returns each previously used platform to its recorded host; only a new platform
selects a fresh eligible host. Correction-ready commands also omit the pin; scheduling retains each corrected
case’s failed attempt host through its requirement replacement history.
Use a verdict only after the required host was available and the walk ran.
Exploratory mission method_config can declare an apt package fixture:
{
"executor":"informed_subagent",
"test_machine":"linux-example",
"host_starting_state":{"os_packages":{
"absent":["python3-venv","python3.12-venv"],"present":[]
}}
}
A Command case whose command calls yoke test-machine must declare
method_config.test_machine; without it the case would run outside the host's
queue and meet test_machine_leased, so authoring refuses it naming that field.
method_config.test_machine selects that registered host even when this direct
requirement has only the generic test-machine capability. Deployment
requirements retain the pin; omitting --test-machine does not permit another
host, and a conflicting run pin is refused before a lease is acquired.
Before a plan's first golden reset on each Test Machine, Yoke compares the
harness CLI versions recorded beside that golden with each vendor's latest
release. A golden that is behind is refreshed once under the plan's own host
lease: reset, install the newer CLIs as the test user, capture a new golden,
then begin the case again, so every later reset restores current CLIs. A
failed refresh stops the plan with a golden_refresh_* reason and its repair.
Old snapshots left after a verified refresh capture only warn
(golden_snapshot_retention_failed, with the host's error and the cleanup).
A declared host baseline restores the golden home. Before new case work, an
unproven mission cleanup also requires a reset: its reset_owed machine receipt
names the failed cleanup's baseline, or fresh-host when none was declared.
The next holder resets first, including an agent mission, as_is case or
case without a baseline. Failed recovery records machine_needs_repair and
the exact yoke test-machine reset command; the steering report shows that
machine and command until a later successful reset receipt clears the debt.
A failed ordinary case restore or current holder's mission restore artifact
also records this debt before custody ends. A terminal case missing its
restore receipt records debt too; an intermediate successful case keeps
its chain state, and an intentional as_is no-restore receipt remains proof
that its declared cleanup finished. A cleanup finish always releases its
lease, even without restore proof, and
records restore evidence before committing the release. A deliberate manual
QA_HOST release also records reset debt for every released execution host,
using its case baseline or fresh-host, while preserving cancellation and
the recorded release reason. Recovery contracts
require a runner supporting host-control-reset-recovery-v1; an older runner
refuses before host work and must be upgraded before retrying.
A mission declared as_is otherwise keeps the home it finds and, absent
host_starting_state, leaves
packages and their journal untouched. A named baseline or explicit fixture
undoes only the preceding mission's journal-attributed package delta, applies the declaration and
records proof before walking. Linux and WSL fixtures use apt; a declaration on another OS
refuses with the supported host named. Package changes made through
yoke qa mission host-command are journaled, including transitive installs and
failed commands. The owner-only journal lives beside the golden archive,
outside the reset home. No arbitrary package cleanup command is accepted.
Independent host changes outside the journal's changed and declared packages,
including unattended security upgrades, are preserved and become the new base
inventory after restore. Missing attribution refuses before package mutation.
Restoring missing or changed packages requires the recorded versions to remain
available; a failed restore names apt access and journal reconciliation and
blocks execution. Continuations preserve the existing walk and journal.
Stop host work safely
yoke qa plan abort --execution-id EXECUTION_ID \
--reason 'Stop this host case'
qa_checkin_busy pauses setup or cases as blocked_on_precondition; keep custody
and resume the same execution after contention clears. Other setup errors fail.
Owners can abort ended runs; settled walks report state. A steering abort returns
stop_request; the owner stops at its next case, heartbeat or
walk-end. Without a seat,
qa_execution_stop_requires_steering refuses.
An in-flight host abort first records cancellation. Until the interrupted
case's cleanup proof is recorded, qa_abort_restore_pending retains custody.
Keep the owner connected: heartbeat replies carry cancellation; the runner stops
before its next step, cleans up and submits starting_state_restore evidence. A case with a declared
baseline must prove restored: true for that baseline. An as_is case or a
chain without a declared baseline records that there is no starting state to
restore; that cleanup receipt permits release with restored: false.
Other roster cases do not impose their baselines on the interrupted case.
Blocking resets finish before the runner acts on cancellation. Retry abort
only after cleanup proof is recorded; success confirms the lease is released.
Failed cleanup, failed restore or a lost runner retains custody. Stop the old runner; the lease holder or a project steering seat can reset retained machines to the interrupted case's baseline:
yoke test-machine reset --project P --test-machine NAME --baseline BASELINE
Then retry the same scoped abort command. Only a successful reset receipt for the same machine and lease, matching that baseline and recorded after the abort request, proves recovery. Reset retains custody; abort performs normal capture, review and lease settlement. A stale or failed reset cannot release custody. Without a declared baseline, recover the runner's no-restore cleanup receipt. Preserve the execution, capture and restore diagnostics. A liveness heartbeat does not mark the next case in flight. Host-case admission identifies its cursor; only a subsequent check-in for that admitted cursor marks host work in flight. Admission alone does not require restoration. After the last case, heartbeats record no check-in. Both walking and review re-enter the bundle, never a paused case.
After an abort, rerun the same scoped command. The fresh execution leaves out
each case whose newest run passed with its evidence on this same target,
including passes the aborted execution recorded, and starts at the first case
still owed; a multi-host member moves on to the next host. A passed case
stays while a later case of its chain is still owed, so the chain runs again
from its opener. A case whose review lands after the roster froze is recorded
retained when it and every later case of its chain now pass, instead of
running or blocking its followers. A pass on
another target still requires a fresh run. Cases a stopped execution's pending
review holds stay out. With nothing owed the run refuses qa_plan_nothing_owed,
listing the stage's blockers and next commands; once every case passed, stage
acceptance settles on the newest stopped execution. A live execution keeps its
own roster: abort it first to drop cases passed elsewhere.
Recover communication through exact execution recovery, preserving saved receipts and host state.