| name | deploy |
| position | none |
| description | Deploy a delivered increment (a validated, merged epic) to a named CLOUD environment the run lens already defined — defaulting to the lowest cloud tier (dev), running exactly what that environment's run.yaml declares, then proving it came up healthy with an independent reachability check and recording the deploy as evidence. Lightweight and on-demand: changes nothing in the product model and stops below production (prod stays with CD from main). The /deploy command in the ProductOS command model. Use when a delivered increment needs to run on a cloud environment /run already defined. |
| user-invocable | true |
deploy
Take a delivered increment — a validated epic already merged to main — and deploy it to a named
cloud environment, defaulting to the lowest cloud tier defined in the slice's run lens (dev).
Run exactly what that environment's run.yaml declares — provider, region, compute, services,
networking, and the deploy command — then prove the increment answers with an independent health
check, and record the deploy as evidence. /deploy is on-demand and lightweight: it changes nothing in
the product model, and it stops below production — production rollout stays with CD from main.
Pipeline position: none (standalone). /deploy acts on an already-delivered increment on main. It
opens no issue, cuts no branch, injects no start-change head and no end sequence — it deploys and
records evidence. It reads the slice's run.yaml and the repository; it never writes the product
model. (#434)
Compiled From
This play was compiled from the deploy ICE (reference/ice.md) by play-editor (#466 Batch B,
Level 3 rollout per ADR 025). Intent defines constraints (C1–C10) and failure conditions (F1–F10);
the expectation defines success scenarios (S1–S8), a Done means (D1–D3, baked to
stop-condition.yaml), and one recovery entry per failure condition. To modify this play, update
reference/ice.md and recompile with play-editor. Do NOT edit this file manually — it is a
compiled artifact.
(#467) deploy Confirm checkpoint pinned per Kapil's ruling — deploy needs HITL. Standing up a live
cloud environment is outward-facing, so the Confirm Deploy checkpoint (Step 1) is now pinned (C10/F10):
it always requires a typed human approval and no config value can turn it off.
Role
You are the orchestrator. You own the workflow and step order. You delegate the domain work —
standing the increment up on the cloud environment and proving it reachable — to the env-operator
agent via a JSON contract over files on disk, and you run the mechanical checks (config + target
resolution, the deploy-record assertions, the model snapshot/diff) through bundled scripts. You never
run the deploy yourself, you never target production, you never write the product model, and you never
call a deploy "done" on the command's exit code — only on the independent health check.
Forbidden: inventing infrastructure the run lens did not declare (C1); targeting production or an
out-of-scope tier (C2); deploying undelivered/unmerged code (C3); writing any product-model file (C4);
reporting success without a green independent health check (C5); printing/logging/committing a secret
(C6); attempting an automatic rollback-and-redeploy on failure (C8); closing COMPLETED without the
stop-condition verdict reading held (C9/F9).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
env-operator | Operate the cloud environment — deploy the increment per the run lens, prove it reachable, capture the deploy record | deploy-to-cloud-env | Deploy |
env-operator is the single domain agent this play uses (1 of the ≤5 budget). It is context-isolated
to operations — it reads the run lens and the repository, never evals/verdicts/sign-off, and never
edits product code or the product model.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (scripts/preflight.py) | — | Hard halt |
Slice resolves and its lens/run.yaml exists | C1 | Hard halt (REC8) |
Resolve the target cloud env (scripts/resolve_deploy_target.py) — default lowest, or named; refuse prod | C2, C8/F8 | Hard halt: production → REC2; no in-scope cloud env → REC8 |
| The increment is delivered — the epic is validated and merged to main | C3 | Hard halt (REC3) |
Resolve the deterministic pre-flight facts with the bundled resolver:
python3 scripts/preflight.py --play deploy --config .garura/core/config.yaml
Then resolve the target cloud environment from the slice's run lens (default lowest-defined cloud tier,
or an explicit --env; production is refused, and a slice with no cloud env halts):
python3 scripts/resolve_deploy_target.py --run-yaml <product_base>/product-os/<domain>/slices/<slice>/lens/run.yaml [--env <name>]
Exit 0 resolves target_env; exit 3 is production-out-of-scope (halt, REC2); exit 4 is no in-scope
cloud environment defined (halt, REC8). Then confirm the increment is delivered — the epic's record
shows status: delivered (validated and merged to main); if not, halt (REC3). /deploy deploys what is
already delivered — it never validates or merges.
The run's working root (<working> below) is {stm_base}_deploy/ — the deploy record
(deploy.json), the captured verify output (deploy-checks.json), the logs, the model snapshots,
and the status/ markers all live under it; the stop condition evaluates against it.
Right after the resolver, record the session identity stamp's start marker (#463 — soft-fail, never
a halt):
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}_deploy/status/session-stamp-deploy.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_deploy/status/<slice-id>-<env>.json exists, resume — skip completed
steps, reset any in-progress step to pending, continue.
Task DAG
Create ALL tasks immediately after pre-flight resolves — before any domain work.
[T1] Confirm Deploy blockedBy: []
[T2] Deploy the increment blockedBy: [T1]
[T3] Verify healthy + surgical blockedBy: [T2]
[T4] Scenario Validation blockedBy: [T3]
[T5] Close blockedBy: [T4]
Mark each task in-progress before its step and completed right after its eval passes.
Workflow
Phase: Confirm
Step 1 — Confirm Deploy (class: one-way-door, pinned) · Owner: play · Depends on: pre-flight
Deploying to a live cloud environment is outward-facing — the increment starts answering on real
infrastructure. This checkpoint is pinned per standards/rules/gate-config.md: it always fires and
always waits for a typed human approval before anything is deployed — no config value, policy, or ledger
can disable it (unpinning is an intent change via play-editor, never a config edit). Present the proposed
deploy — the slice, the resolved target environment and tier, the delivered increment (epic + merge ref),
and what the environment's run.yaml declares will run — and wait for a typed approval. Approve →
continue; cancel → halt with nothing deployed.
Phase: Deploy
Step 2 — Deploy the increment · Owner: env-operator · Depends on: Step 1
First snapshot the product model so Step 3 can prove it untouched (cp -R <product_base>/product-os <working>/model-before). Then the agent invokes deploy-to-cloud-env to deploy the delivered
increment to the resolved cloud environment exactly as its run.yaml declares, read secrets from the
declared manager binding, and prove reachability — writing a deploy record:
{
"task": "deploy the delivered increment to the resolved cloud environment per its run.yaml; prove it reachable; never expose secrets; never touch the product model",
"inputs": { "run_lens": "<product_base>/product-os/<domain>/slices/<slice>/lens/run.yaml",
"environment": "<resolved target env name>",
"repo_root": ".",
"mode": "up" },
"outputs": { "deploy_record": "<working>/deploy.json", "logs": "<working>/deploy.log" }
}
The skill executes only what the environment declares, reads secrets from the declared binding (never
prints them), runs an independent health check, and — on failure — leaves the environment in its prior
state with prior_state_left: true. It never writes the product model.
Phase: Verify
Step 3 — Verify healthy + surgical · Owner: play · Depends on: Step 2
Snapshot the model after (cp -R <product_base>/product-os <working>/model-after) and assert over the
captured deploy record — capture the checker's JSON output; its checks.A1 field is the stop
condition's D3 input:
python3 scripts/check_deploy.py --deploy-record <working>/deploy.json --run-yaml <product_base>/product-os/<domain>/slices/<slice>/lens/run.yaml --target-env <resolved env> --model-before <working>/model-before --model-after <working>/model-after --logs <working>/deploy.log > <working>/deploy-checks.json
SE-1 (F2/C2): resolve_deploy_target.py refused production / an out-of-scope tier at pre-flight
(exit 3) — production is never deployed (REC2).
SE-2 (F8/C8): resolve_deploy_target.py halted when no in-scope cloud environment is defined (exit
4) — /deploy never proceeds without a run-lens cloud env (REC8).
SE-3 (F3/C3): the increment is delivered — the epic is validated and merged to main — confirmed at
pre-flight before any deploy (REC3).
SE-4 (F1/C1): check_deploy.py A1 — the deploy record's environment is the resolved target and it
ran the environment's declared deploy_cmd; nothing undeclared ran (REC1).
SE-5 (F5/C5): check_deploy.py A2 — a succeeded status carries a green independent health
check; a command that exited 0 without answering is failed, not done (REC5).
SE-6 (F6/C6): check_deploy.py A3 — no secret literal in the record or logs; secrets_source names
a secrets-manager binding (REC6).
SE-7 (F4/C4): check_deploy.py A4 — the product model is byte-identical before and after; /deploy
wrote no model file (REC4).
SE-8 (F7/C8): check_deploy.py A5 — a failed deploy left the environment in its prior state
(prior_state_left: true); never a half-deploy, never an auto-rollback-redeploy (REC7).
SE-9 (C7): the deploy record is a complete evidence record — the target environment, the steps that
ran, the reachable address, and the health proof are all captured (Step 5 writes it to the evidence
file per the D1 evidence rule).
SE-10 (F9/C9): the close is stop-condition gated — check_stop_condition.py over the baked
stop-condition.yaml (D1 the deploy record exists; D2 health.status is green; D3 the captured
deploy-checks.json reads checks.A1: true) must read held before any COMPLETED close; a failed
deploy that recorded its failure honestly closes HALTED, never COMPLETED (REC9).
SE-11 (F10/C10): the deploy proceeded only after a typed human approval at the pinned Confirm Deploy
checkpoint (Step 1); no deploy ran without it — the gate is pinned and no config value can disable it
(REC10).
On any GAP, apply the matching recovery before closing.
Phase: Scenario Validation
Step 4 — Scenario evals · Owner: play · Depends on: Step 3
- SCE-1 (S1 — devops engineer, default deploy): with no target named, the resolver picked dev, the
declared deploy ran, health is green with an address, an evidence record exists, the model is
byte-identical, and the stop-condition verdict reads held.
- SCE-2 (S2 — devops engineer, named higher tier): with target
stage, the deploy record's
environment is stage, not dev.
- SCE-3 (S3 — release manager, production refused): targeting production halted before any deploy;
no production deploy record was written.
- SCE-4 (S4 — security reviewer, no secret leak): no secret literal in the record or logs; the
secrets-manager binding is named.
- SCE-5 (S5 — SRE, false green caught): a deploy command that exited 0 without answering was
reported failed, gated on the independent health check.
- SCE-6 (S6 — SRE, failed deploy left clean): a failed deploy left the environment in its prior
state and reported the failure; no rollback-redeploy was attempted.
- SCE-7 (S7 — product owner, no model write): the product model is byte-identical before and after.
- SCE-8 (S8 — devops engineer, no cloud environment): a slice with no cloud environment defined
halted with nothing deployed.
Phase: Evidence & Close
Step 5 — Close · Owner: play · Depends on: Step 4
Run the Standard Play Close. /deploy is a standalone product-scoped play — record evidence per the D1
rule.
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}_deploy/status/session-stamp-deploy.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_deploy/" \
--out "${stm_base}_deploy/status/stop-condition-deploy.yaml"
sc_exit=$?
/deploy is standalone (no issue branch), so it is product-scoped:
evidence_base="${product_base}_evidence/deploy/" and slug="${slice_slug}-${env}".
Step C0 — bind the verdict. sc_exit == 0 (held) permits status: COMPLETED. Anything else
closes HALTED with exit_reason: stop_condition_unmet and the evidence's Stop Condition section
names every unmet clause — a failed deploy that recorded its failure honestly is a HALTED run, not a
done one; fix the state per REC9 (re-run the verify capture, or record the honest failure) and
re-evaluate; the close stays HALTED until the verdict reads held. An unevaluable verdict is never
a pass.
Step C1 — Write evidence file. Gated by the resolved evidence.record flag. When false, skip and
record evidence skipped (record=false). Otherwise fill the evidence-file.md slots (play deploy,
run_id deploy-${ts}, slice+env slug, started/completed, status per C0, exit_reason; artifacts: the
deploy record, deploy-checks.json, the scrubbed logs, the stop-condition verdict; the resolved
target env; step + scenario evals SE-1…SE-11 / SCE-1…SCE-8; the health proof; the checkpoint decision
from the pinned Step 1; the session identity stamp fields from
$session_stamp (#463): session_id, ledger_file, ledger_start_offset, ledger_end_offset (null when
unresolved — never blocks the close); and stop_condition per C0 with the Stop Condition section
filled) and write to $evidence_dest. Do NOT hand-author the body. This persists the deploy evidence
record (C7, verified by SE-9) to the evidence file, or explicitly skips on record=false.
Step C2 — Render delivery report. Also render the Next line: resolve this play in standards/rules/pipeline-next.md and emit **Next:** /<command> — <why>. Or run /next to see all recommended actions. (only /next pointer, or omit, when the mapped command is null), per play-close.md. Fill the delivery-report.md slots: ## deploy Delivered — ${slug}, the Run Summary table (target env, address, health), the Pipeline Steps table, the Artifacts
Produced table (the deploy record + logs), Next Steps (promote to a higher cloud tier by re-running
/deploy --env <tier>; production follows from main via CD), and a pointer to $evidence_dest. Always
emitted.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — default deploy | devops engineer | SCE-1 |
| S2 — named higher tier | devops engineer | SCE-2 |
| S3 — production refused | release manager | SCE-3 |
| S4 — no secret leak | security reviewer | SCE-4 |
| S5 — false green caught | SRE | SCE-5 |
| S6 — failed deploy left clean | SRE | SCE-6 |
| S7 — no model write | product owner | SCE-7 |
| S8 — no cloud environment | devops engineer | SCE-8 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | /deploy deployed to, or ran, something the run lens does not declare | stop and undo the out-of-scope action; deploy only what the target env's run.yaml declares | autonomous |
| F2 | production or an out-of-scope tier was targeted | halt; production rollout is out of scope — it stays with CD from main | human |
| F3 | the increment is not delivered, merged, or validated | halt; /deploy deploys only a validated, merged increment — route to /validate or /launch first | human |
| F4 | a product-model file changed | revert the write; /deploy executes only and never writes the model | autonomous |
| F5 | success was reported but the increment is not healthy | mark the deploy failed; "done" requires the independent health check to pass | autonomous |
| F6 | a secret value appeared in the logs, the record, or the repo | strip it, read secrets only from the declared secrets-manager binding, and re-verify none remain | autonomous |
| F7 | a failed deploy left the environment half-deployed | report the partial state for a human to resolve; /deploy does not auto-rollback | human |
| F8 | no run lens, or no cloud environment is defined | halt; run /run to define the cloud environment before /deploy | human |
| F9 | the run is about to close COMPLETED with the Done means unmet | close HALTED (stop_condition_unmet) with the unmet clauses named; fix the state — re-run the verify capture, or record the honest failure — and re-evaluate; the close stays HALTED until the verdict reads held | autonomous |
| F10 | a deploy would proceed without the pinned human approval | halt with nothing deployed; present the proposed deploy and wait for the typed approval | human |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target slice + environment, check the
status marker, skip completed steps, reset any in-progress step to pending, and continue.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:495ffe2a4d7fb55d8e4b598a24cba7f6586ef9204ed5b3b6f90cc917635cb431 (of reference/ice.md) |
| compiled_by | play-editor (#466 Batch B; #467 deploy pinned) |
| pipeline_position | none (standalone; acts on a delivered increment on main; no start/end injection) |
| model_writes | no (execution only — never writes the product model) |
| workflow_structure | C (chained: confirm → deploy → verify → close; one pinned one-way-door checkpoint per gate-config.md; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (env-operator) |
| utility_agents | 0 |
| skills_used | deploy-to-cloud-env |
| scripts | 5 (preflight, resolve_deploy_target, check_deploy, check_stop_condition, session_stamp) |
| step_evals | 11 (SE-1…SE-11) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 10 (one per failure condition; 5 autonomous / 5 human) |