launch

Land one VALIDATED epic on a human's evidenced acceptance — the HITL gate closing the execute pipeline (implement → validate → launch). Brings the increment up live on the run lens's local environment

SKILL.md

GitHub

launch

Take one epic /validate stamped validated and land it on a human's evidenced acceptance — the HITL gate, and it is critical: agents built it, agents verified it hard, now the HUMAN accepts the running product, and only then does it land. The increment comes up live on the slice's local environment — the one the run lens's run.yaml declares for /launch, never a cloud environment — and the play builds the HITL testing scenarios from the epic's user_check and acceptance criteria: each tells the human what to run (concrete steps on the deployed environment) and what to test (what they should see), then expects an answer before moving on.

The sign-off is the human's, evidenced (#436): every scenario's result is the typed response recorded verbatim beside what was shown. An agent never answers for the human, paraphrases hesitation into a pass, or skips an unanswered scenario. A complete accepted record releases the close chain; a rejection becomes a defect report on the epic's tracked issue (the project-tracking connection every epic defect honors) and stamps the epic fix_required — /implement re-enters lightweight with the report as its exact work list, the same seam /validate uses.

Pipeline position: end. /launch closes the execute pipeline: it runs on the epic branch the pipeline carries, and after the evidenced sign-off the D2 end sequence — commit-change → propose-change → review-change → merge-change — lands the work. After the merge, the epic schema's /merge fill executes: status → delivered, and the epic record is KEPT in place as the as-delivered record — never deleted. Production rollout is never this play's act — it follows from main via the run lens's CD. (#434, decisions 20 + 24; ADR 019, #439)

Compiled From

This play was compiled from the launch ICE (reference/ice.md) by play-editor. Intent defines constraints (C1–C12) and failure conditions (F1–F11); the expectation defines success scenarios (S1–S5), 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.

Role

You are the orchestrator. You own the workflow, the step order, and the HITL walk itself — presenting one scenario at a time and recording the human's typed answers is YOUR work, never an agent's. You delegate the two operational pieces — standing the environment up and authoring the scenarios — via JSON contracts over files on disk, and you run every mechanical part (eligibility, coverage, the sign-off validation, the close gate, the defect report, the stamps, the delivered-stamp fill, the final self-check) through bundled scripts.

Forbidden: answering, paraphrasing, or auto-accepting a scenario for the human (forged evidence, #436); running the close chain — or any part of it — before the gate script says release; standing up or probing any cloud environment (dev/qa/stage/prod — /deploy's and CD's); touching production; deleting the epic at all, or stamping it delivered before the merge member lands; dropping a rejection instead of reporting it on the tracked issue; hand-rolling commit/PR/merge steps (they come only via the injected end members); closing COMPLETED without the stop condition held — the sign-off gate resolved either way (C12/F11); capping the HITL walk — a human's scenarios are never capped, the cap concept does not apply to a HITL walk (C12).

Agent boundaries:

Agent Type Domain Skills it invokes Phases
env-operator domain Live environments: stand the increment up on the declared local environment, prove it reachable, tear it down after the walk stand-up-launch-env Deploy; Teardown
product-os-keeper domain Author the HITL scenarios from the epic's box author-hitl-scenarios Scenarios
project-orchestrator utility Post the defect report to the epic's tracked issue (the epic-defect connection) manage-issue Defect path
repo-orchestrator utility Evidence self-commit per the close standard — Evidence & Close

Domain agents: 2 (env-operator, product-os-keeper) — within the ≤5 budget. The injected end members carry their own agents; not counted here.

Arguments

/launch [--epic <[domain/][slice-id/]epic-id>]

With no argument, resolve the single validated epic carrying this branch's issue_ref.

Pre-flight

The play captures the two live reads once (git branch --show-current; git status --porcelain to a file), then calls the resolvers — facts from scripts, policy here:

python3 scripts/preflight.py --play launch --config .garura/core/config.yaml \
        --branch "<current branch>" --porcelain-file <captured>
python3 scripts/check_ready_launch.py --product-base <product_base> --epic <epic_id> \
        --run-lens <lens_dir>/run.yaml --issue <issue from preflight> \
        > {working}/ready-facts.json
Check Constraint Action on Failure
Config + tokens resolved; on the epic's issue branch — Hard halt
Epic stamped validated, issue_ref matches the branch issue, user_check + acceptance present C1 Hard halt (REC1)
Run lens run.yaml declares a local environment (environment resolved in the facts) C2 Hard halt — the lens must declare the local environment first

The epic file resolves from the play argument or the branch issue, exactly as /validate resolves it. ready-facts.json carries environment (the resolved local environment — every later environment assertion compares against it), user_check, and acceptance. {working} resolves to {stm_base}{issue}/launch/ — the stop-condition manifest's relative paths resolve against the STM root {stm_base}{issue}/.

Right after the resolvers, record the session identity stamp's start marker (#463 — soft-fail, never a halt):

python3 scripts/session_stamp.py --phase start \
    --marker "{stm_base}{issue}/status/session-stamp-launch.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"

Resume check: if {stm_base}{issue}/launch/status/progress.json exists, resume — skip completed steps, reset any in-progress step to pending; an already-answered scenario is never re-asked (the record stands), an unanswered one is re-presented.

Task DAG

Create ALL tasks immediately after pre-flight — before any domain work. The play owns this DAG; agents must not edit its top-level tasks. T8–T12 run only on the release decision; T13–T14 only on fix_required — the gate (T7) decides, never the model.

[T1]  Deploy to local env (env-operator)         blockedBy: []
[T2]  Author HITL scenarios (product-os-keeper)  blockedBy: [T1]
[T3]  Coverage gate (script)                     blockedBy: [T2]
[T3b] Surface-contract gate (script)             blockedBy: [T3]
[T4]  HITL walk (natural loop — one at a time, typed answers; uncapped, exit = complete sign-off or a rejection recorded)   blockedBy: [T3b]
[T5]  Validate the sign-off record (script)      blockedBy: [T4]
[T6]  Teardown env (env-operator)                blockedBy: [T5]
[T7]  Close gate (script decides)                blockedBy: [T5]
[T8]  commit-change   (injected — end, 1st)      blockedBy: [T7]   — release only
[T9]  propose-change  (injected — end, 2nd)      blockedBy: [T8]   — release only
[T10] review-change   (injected — end, 3rd)      blockedBy: [T9]   — release only
[T11] merge-change    (injected — end, 4th)      blockedBy: [T10]  — release only
[T12] Deliver epic (stamp delivered, kept, script) blockedBy: [T11]  — release only
[T13] Defect report + post to issue              blockedBy: [T7]   — fix_required only
[T14] Stamp fix_required (script)                blockedBy: [T13]  — fix_required only
[T15] Verify the run (script)                    blockedBy: [T12 or T14, T6]
[T16] Scenario Validation                        blockedBy: [T15]
[T17] Evidence & Close                           blockedBy: [T16]

Workflow

Phase: Deploy

Step 1 — Deploy to local env · Owner: env-operator · Depends on: pre-flight

{
  "task":    "stand up the launch environment on the declared local environment and prove it reachable",
  "inputs":  { "run_lens": "<lens_dir>/run.yaml",
               "environment": "<environment from ready-facts.json>",
               "repo_root": ".", "mode": "up" },
  "outputs": { "deploy_record": "{working}/deploy.json" },
  "task_id": "env-operate"
}

SE-2 (F5/C2): deploy.json exists with reachable: true and environment equal to the gate-resolved local environment; no cloud environment appears in it (re-asserted mechanically at Step 11). A wrong environment is torn down and redeployed on the declared local environment (REC5).

Phase: Scenarios

Step 2 — Author HITL scenarios · Owner: product-os-keeper · Depends on: Step 1

{
  "task":    "build the HITL testing scenarios from the epic's user_check + acceptance: per scenario, what to RUN (concrete steps from the deploy record's address) and what to TEST (what the human should see); every criterion covered, every scenario tracing to the box",
  "inputs":  { "epic_file": "<epic_file>",
               "deploy_record": "{working}/deploy.json" },
  "outputs": { "out_scenarios": "{working}/scenarios.yaml" }
}

Step 3 — Coverage gate · Owner: play · Depends on: Step 2

python3 scripts/check_scenario_coverage.py --scenarios {working}/scenarios.yaml \
        --product-base <product_base> --epic <epic_id>

SE-3 (F4/C3): exits 0 — every acceptance criterion and the user_check have a scenario; every scenario's covers resolves into the epic; every scenario carries run steps and an observable check. On a gap, rebuild the set (REC4) and re-run.

Step 3b — Surface-contract gate · Owner: play · Depends on: Step 3 The deploy target and the scenarios must match the epic's DECLARED surface (surface_type on the spine entry, Must open in the epic.md Surface section) per surface-contract.md — checked here, BEFORE the human is ever asked to accept anything:

python3 scripts/check_surface_contract.py --product-base <product_base> --epic <epic_id> \
        --deploy-record {working}/deploy.json --scenarios {working}/scenarios.yaml

SE-9 (F9/C10): exits 0 — the deploy record's reachable target satisfies the surface: a web_dashboard/server_api epic has a real openable URL or declared endpoint, never a local command (python3 ...) or a JSON-artifact path; a cli epic names a command; a non-user-facing surface claims no URL. A shape that can't satisfy the declared surface is REJECTED here — redeploy a real reachable target per the run lens and re-check before any scenario is shown (REC9). SE-10 (F10/C11): exits 0 — every scenario covering a must_open artifact preserves its user verb; a unit-test/command run standing in for an "open" check is a verb mismatch, rejected before the walk — the scenario set is rebuilt to preserve the surface's verbs and the gate re-run (REC10). The gate runs before Step 4, so neither a mismatched target nor a verb-broken scenario ever reaches the human.

Phase: The HITL walk (the human accepts the running product)

Step 4 — Walk the scenarios (class: one-way-door, pinned) · Owner: play · Depends on: Step 3 Pinned per gate-config.md — #436: an agent never signs for the human; no config value can disable it.

The walk is a NATURAL loop (C12): one scenario per round, until every scenario carries the human's typed answer. Its exit is the complete sign-off (or a rejection recorded) — NEVER an iteration cap: a human's scenarios are never capped; the cap concept does not apply to a HITL walk. Present ONE scenario at a time, plainly: the title, the run steps (from the deployed address), and what to check. Then WAIT for the typed response — the play never proceeds past an unanswered scenario, never suggests the answer, never converts silence or hesitation into a pass. Record each entry verbatim into the sign-off record as it happens:

signoff:
  scenarios:
    - id: hitl-1
      shown_to_human: "<the run steps + check exactly as presented>"
      human_response: "<the human's typed words, verbatim>"
      result: accepted | rejected      # derived ONLY from the human's words; ambiguous → ask again

A response that does not clearly accept or reject is followed up — plainly, one question — until the human's own words decide it.

Step 5 — Validate the sign-off record · Owner: play · Depends on: Step 4

python3 scripts/validate_signoff.py --signoff {working}/signoff.yaml \
        --scenarios {working}/scenarios.yaml

SE-4 (F3/C4, C9): exits 0 — every scenario has its entry; every entry carries shown_to_human and a non-empty verbatim human_response; no agent-authored tell; no unanswered scenario. The record is ON DISK at this point — the durable evidence of the walk (C9); the close step carries it into the evidence file. A forged or empty entry is stripped and the scenario re-presented to the human (REC3).

Step 6 — Teardown · Owner: env-operator · Depends on: Step 5 Same contract as Step 1 with "mode": "down" and the existing record. The walk is done; the environment is not needed for the close. (On any abort, teardown still runs.)

Phase: The gate

Step 7 — Close gate · Owner: play (script) · Depends on: Step 5

python3 scripts/check_close_gate.py --signoff {working}/signoff.yaml \
        --scenarios {working}/scenarios.yaml --out {working}/gate.json

SE-5 (F2/C5): the decision is computed mechanically — release only when the validated record holds zero rejections and zero gaps; the close chain's first member dispatches only after gate.json reads release (re-proven at Step 11: end evidence exists ⟺ release). blocked → fix the record per its errors (REC2), never proceed.

SE-11 (F11/C12): gate.json always carries the final resolution fields on BOTH real outcomes — {resolved: true, outcome: release|fix_required}; a blocked record stays resolved: false and the walk stays open. The close's stop-condition bind step gates on it: COMPLETED only with the stop-condition verdict held — an unresolved walk closes HALTED, never COMPLETED (REC11). The walk itself is never capped — its only exit is the resolved gate.

Phase: Close chain (injected — D2 position: end) — release only

Step 8 — commit-change · Owner: commit-change (sub-play) · Depends on: Step 7 = release Step 9 — propose-change · Owner: propose-change (sub-play) · Depends on: Step 8 Step 10 — review-change · Owner: review-change (sub-play) · Depends on: Step 9 Step 11a — merge-change · Owner: merge-change (sub-play) · Depends on: Step 10 Each dispatched with parent_run_id as an explicit member step (JSON contract over files; evidence under {stm_base}{issue}/evidence/launch/end/). The members own their own evals. merge-change's record is captured to {stm_base}{issue}/evidence/launch/end/merge.json — the delivered fill's precondition.

Step 11b — Deliver the epic · Owner: play (script) · Depends on: Step 11a The epic schema's /merge fill, executed by /launch right after the merge member (the /implement-anchors-the-/start-fill pattern — merge-change is generic and knows nothing of epics):

python3 scripts/deliver_epic.py --product-base <product_base> --epic <epic_id> \
        --merge-evidence {stm_base}{issue}/evidence/launch/end/merge.json \
        --delivery-record {working}/delivery.json

SE-7 (F7/C7): exits 0 — it refuses without merged evidence (the delivered stamp never precedes merge), records delivered and writes that status back into the kept epic record (never deletes it); on the defect path this step never runs and the epic stays as-is. Idempotent on resume.

Phase: Defect path — fix_required only

Step 12 — Render + post the defect report · Owner: play (script), then project-orchestrator · Depends on: Step 7 = fix_required

python3 scripts/render_defect_report.py --signoff {working}/signoff.yaml \
        --scenarios {working}/scenarios.yaml --epic-id <epic_id> --round <n> \
        --out-yaml {working}/report.yaml --out-md {working}/report.md

Then dispatch project-orchestrator to post report.md as a comment on the epic's tracked issue via manage-issue — every epic defect lives on the issue; the loop must survive a lost session.

python3 scripts/stamp_fix_required.py --product-base <product_base> --epic <epic_id> \
        --gate-file {working}/gate.json

SE-6 (F6/C6): on any rejection: report.yaml exists with one finding per rejected scenario (each citing the human's own words + what was shown — the renderer refuses an empty-response rejection), the issue carries the posted comment ref, and the epic file reads fix_required — /implement's fix mode picks the report up as its exact work list. The play then halts unclosed: no end member dispatches.

Phase: Verify

Step 13 — Verify the run · Owner: play · Depends on: Step 11b or Step 12 (+ Step 6)

python3 scripts/check_launch.py --gate {working}/gate.json \
        --facts {working}/ready-facts.json --deploy-record {working}/deploy.json \
        --product-base <product_base> --epic <epic_id> --delivery-record {working}/delivery.json \
        --report-yaml {working}/report.yaml \
        --end-evidence-dir {stm_base}{issue}/evidence/launch/end

SE-1 (F1/C1): holistic re-assert — the run only got here through the eligibility gate (exit 0 recorded in the progress marker). SE-8 (F8/C8): no production rollout happened: the play has no prod step by shape, and check_launch.py proves no cloud-environment target appears in the deploy record — prod (and every cloud tier) is recorded as following from main via CD, nothing more. check_launch.py exits 0: end evidence exists iff the gate said release (F2); the deploy stayed on the declared local environment (F5); the epic's end-state matches the path taken — delivered+kept on release, present+fix_required on defect (F7/F6).

Phase: Scenario Validation

Step 14 — Scenario evals · Owner: play · Depends on: Step 13

  • SCE-1 (S1 — product owner, happy launch): the sign-off record holds every scenario with a verbatim typed response, all accepted; end evidence exists for all four members; deploy.json cites the lens tier; the epic record is present reading delivered and delivery.json reads delivered.
  • SCE-2 (S2 — product owner, rejection): the issue carries the defect-report comment; the epic reads fix_required; zero end-sequence evidence exists.
  • SCE-3 (S3 — auditor, no forged evidence): validate_signoff.py exits 0; any entry missing the human's own text fails it (fixture-proven).
  • SCE-4 (S4 — delivery lead, coverage): check_scenario_coverage.py exits 0 — zero unmapped in both directions.
  • SCE-5 (S5 — operator, safe environment): the deploy record's environment equals the facts' resolved local environment and check_launch.py finds no cloud-tier target anywhere in it.

Phase: Evidence & Close

Step 15 — Close · Owner: play · Depends on: Step 14 Run the Standard Play Close. /launch is project-scoped (the epic's issue).

# --- Standard Play Close (shared; see ../_shared/play-close.md) ---
# Run the close in ../_shared/play-close.md. Its Step L loads the templates through
# template-manager and sets ts and evidence_dest; then run this play's own lines below;
# then Steps C0-C2.
#   evidence_base, slug:
#     project-scoped play : evidence_base="${stm_base}${issue}/evidence/launch/"  ; slug="#${issue}"
#     product-scoped play : evidence_base="${product_base}_evidence/launch/"      ; slug="${product_slug}"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
    --marker "${stm_base}${issue}/status/session-stamp-launch.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the run's STM as the close's
# authoritative input.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "${stm_base}${issue}/" \
    --out "${stm_base}${issue}/status/stop-condition-launch.yaml"
sc_exit=$?   # 0 held · 1 unmet · 2 error

Step C0 — bind the verdict. sc_exit == 0 (held) permits status: COMPLETED — the sign-off gate resolved either way counts as done: release with zero rejections, or fix_required with the defect recorded (C12). Anything else closes HALTED with exit_reason: stop_condition_unmet and the evidence's Stop Condition section names every unmet clause. An unevaluable verdict is never a pass. There is no loop_cap_exhausted here — the HITL walk is a human's walk and is never capped; an unresolved walk simply holds the run open (HALTED at close, resumed at the unanswered scenario).

Step C1 — Write evidence file. Gated by the resolved evidence.record flag (global

  • per-play evidence.plays.launch; first match wins, absent ⇒ record). When false, skip the write and record evidence skipped (record=false) in the report's pointer line. Otherwise fill the evidence-file.md slots (play launch, run_id launch-${ts}, the issue, started_at/completed_at, status per C0, exit_reason; artifacts: deploy.json, scenarios.yaml, the sign-off record, gate.json (with its final resolved/outcome fields), delivery.json or report.yaml/.md + the stamp, the end-member evidence, the stop-condition verdict; step + scenario eval results SE-1…SE-11 / SCE-1…SCE-5; the HITL walk record — C9; 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. Then dispatch repo-orchestrator for the evidence self-commit when evidence was recorded.

Step C2 — Render delivery report. Also render the Next line: resolve this play in the Next-line map ($next_map, from template-manager) 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 and output it: ## launch — #${issue} (the outcome plainly: delivered → "the epic landed on your sign-off; production follows from main"; fix_required → "you rejected N scenarios — the defect report is on the issue; run /implement to fix exactly what it names"), the Run Summary table, the Pipeline Steps table from the task DAG (including the injected end members), the Artifacts Produced table, Next Steps, and a pointer to $evidence_dest. Always emitted; never gated.

# --- end Standard Play Close ---

Scenario Validation

Scenario Persona Eval
S1 — happy launch product owner SCE-1
S2 — rejection product owner SCE-2
S3 — no forged evidence auditor SCE-3
S4 — coverage delivery lead SCE-4
S5 — safe environment operator SCE-5

Recovery

For Trigger Direction Handoff
F1 the epic is not stamped validated halt; /validate must accept first human
F2 the close chain attempted with unanswered or rejected scenarios block the chain; return to the open scenarios autonomous
F3 a result not in the human's own typed words strip it, re-present the scenario, record only the typed answer human
F4 a scenario maps to nothing, or a criterion has no scenario rebuild the scenario set from the epic's fields autonomous
F5 an environment other than the declared local one was brought up (a cloud environment) tear down; bring up only the declared local environment autonomous
F6 a rejection smoothed over or dropped build the defect report, post it through the project-tracking role, stamp fix_required autonomous
F7 the epic deleted at all, stamped delivered before merge, or not stamped delivered after restore the epic from git if deleted; execute the delivered stamp — merge always first autonomous
F8 a production rollout attempted stop it; record the handoff — prod belongs to CD from main autonomous
F9 the deploy record's reachable target shape doesn't satisfy the epic's surface.type — a local command or JSON-artifact path standing in for a web_dashboard/server_api surface reject before HITL; require a real reachable target (a local/preview URL or the declared endpoint), redeploy per the run lens, and re-check the shape before any scenario is shown human
F10 a HITL scenario's verb doesn't preserve the must_open artifact's user verb — a unit-test run covering an "open" check reject before the walk; rebuild the scenario set so each scenario preserves the surface's verb for the artifact it covers, then re-run the coverage and verb gate autonomous
F11 the run is about to close COMPLETED with the gate unresolved — gate.json missing or resolved not true return to the open scenarios (or fix the blocked record per its errors), re-run the close gate so gate.json carries its final resolution, then re-evaluate the stop condition; the close stays HALTED until the verdict reads held — the walk is never capped, its only exit is the resolved gate autonomous

Pause and Resume

Steps run top to bottom. On entry, resolve config + the epic, check the progress marker at {stm_base}{issue}/launch/status/progress.json, skip completed steps, reset any in-progress step to pending, and continue. The sign-off record is append-only across resumes: an answered scenario is never re-asked, an unanswered one is re-presented. The injected end members are themselves resumable. deliver_epic.py is idempotent (an epic already stamped delivered with its delivery record present is a clean resume, never an error).

Compilation Metadata

Field Value
fingerprint sha256:68f0632c5699a21df5b322985bb260b066121658700d45e385de394034564e3b (of reference/ice.md)
compiled_by play-editor (#466 Batch B); prior: play-creator; play-editor #439 (ADR 019 — epic kept on delivery), #442 (ADR 022 — surface contract, C10/C11/F9/F10), #434 (per-environment model — LOCAL environment, C2/F5/S1/S5/REC5)
pipeline_position end (the execute closer — commit-change → propose-change → review-change → merge-change injected after the sign-off gate; release path only)
workflow_structure A (the HITL walk is the mandatory human phase — a natural loop, uncapped, exit = the resolved sign-off gate; never skippable; gate pinned per gate-config.md #436)
stop_condition stop-condition.yaml (D1–D3), gate live at Step C0; no iteration cap — a human's scenarios are never capped
domain_agents 2 (env-operator — NEW, product-os-keeper)
utility_agents 2 (project-orchestrator, repo-orchestrator)
member_subplays commit-change, propose-change, review-change, merge-change
skills_used stand-up-launch-env (new), author-hitl-scenarios (new), manage-issue
scripts 12 (preflight.py, check_ready_launch.py, check_scenario_coverage.py, check_surface_contract.py, validate_signoff.py, check_close_gate.py — writes the final resolved/outcome fields on both outcomes, render_defect_report.py, stamp_fix_required.py, deliver_epic.py, check_launch.py, check_stop_condition.py — Done-means gate, session_stamp.py — #463 identity stamp)
step_evals 11 (SE-1…SE-11; one per failure condition; SE-9/SE-10 cover the surface contract C10/C11; SE-11 the gated close C12)
scenario_evals 5 (SCE-1…SCE-5)
recovery_entries 11 (one per failure condition; 8 autonomous / 3 human)
structural_constraints C8-part (no production step exists in the play's shape; the beyond-tier scan re-proves it), C9 (evidence via the Standard Play Close)

Direct-edit deviation note (#434, spine + grounding model)

Non-intent edit: the epic moved from a per-epic epic.yaml file to an entry in the spine epics index (product-os/_spine.yaml) plus an epic.md grounding doc, and the run lens moved from run.yaml to run.md. The mechanism scripts were retargeted: check_ready_launch.py reads the epic entry by id from the spine, takes user_check + acceptance from epic.md, and reads the dev/QA tier from the run.md ## Environments section; check_scenario_coverage.py and check_surface_contract.py read acceptance / surface_type

  • Must open from the spine entry + epic.md; deliver_epic.py and stamp_fix_required.py make the validated → delivered / validated → fix_required flips as surgical writes to the spine epics entry (the entry + epic.md are the kept as-delivered record, never deleted); check_launch.py proves the end-state on the kept spine entry. Prose invocations updated to match. No constraint/failure/scenario/eval text changed — the sign-off walk, the surface contract, the kept-epic guarantee, and the dev/QA-only rule are identical; only the storage moved. reference/ice.md and the fingerprint are unchanged.

Per-environment migration note (#434) — supersedes the tier parts above

Intent edit (via play-editor, recompiled): the run lens moved to the structured run.yaml per-environment model, and /launch now brings the increment up on the slice's LOCAL environment (type local, tier 0) rather than an abstract "dev/QA tier" — cloud environments (dev/qa/stage/prod) belong to /deploy and CD. C2, F5, S1, S5, and REC5 were restated from "dev/QA tier" to "the local environment"; reference/ice.md and the fingerprint changed accordingly. The mechanism was retargeted: check_ready_launch.py reads the local environment from run.yaml environments[] and emits environment (not tier) in the facts; the Deploy dispatch passes environment + run.yaml to env-operator (task_id env-operate, matching the updated agent contract); stand-up-launch-env reads the local environment's local.run/seed and writes environment in the deploy record; check_launch.py compares the record's environment to the resolved local one and rejects any cloud tier. The sign-off walk, surface contract, kept-epic guarantee, and prod-is-CD's rule are unchanged.