| name | roadmap |
| position | end |
| description | Plan the product's vertical slices into a build sequence — order the slices /shape produced across all domains, resolve their dependencies, and estimate each one's effort, writing only the plan onto each slice, directly in place on the live model. The planning play in the ProductOS command model, after /shape. Reads the slices to judge order and effort but writes only the plan (order, effort, dependencies). Closes the strategy pipeline through the end PR. Opens no delivery issue. |
| user-invocable | true |
roadmap
Take the vertical slices /shape produced and turn the pile into a plan. /roadmap reads
every slice across every shaped domain and lays out the build sequence: the order to
build them, the dependencies between them (within and across domains), and an effort
estimate for each. It writes that plan onto each slice's spine index entry — order,
effort, resolved depends_on, and the status flip to planned — directly in place on
the live model, and touches nothing else: the slice records (composition) and every other
part of the model stay byte-identical. One run plans the whole set, because order only
means something by comparison. Convention: a lower order number means sooner.
Pipeline position: end. /roadmap CLOSES the strategy pipeline: after the ordering is persisted and verified, the D2 rule injects the close sequence commit-change → propose-change → review-change → merge-change, so the strategy change is committed, raised, reviewed, and merged. No start-change head — /vision opened the pipeline and every earlier strategy play (/understand, /shape) committed its own model delta on this branch, so /roadmap enters on an already-started branch with a clean model tree. It runs after /shape, since slices must exist before they can be planned. (#437)
Write discipline (ADR 026, standards/rules/direct-model-write.md). /roadmap writes no
per-node grounding doc at all — its LLM authoring skill (author-roadmap) writes NO model
file; it only drafts plan data (effort, resolved dependencies, a value preference) to STM,
which compute_plan.py turns into the coherent plan.json. Every mutation of the one
shared file /roadmap touches — the spine _spine.yaml slices index — is done by the
deterministic keyed persist script (persist_roadmap.py), in place, keyed to each planned
slice id: it writes ONLY the four plan fields (order/effort/depends_on/status) on
that slice's entry and refuses a plan that names a slice absent from the live spine (the
node-level containment the file-level scoped guard cannot see inside the shared spine).
Because the LLM never writes any model file, containment is a post-write scoped guard
(scoped_write_guard.py), not a draft. The model tree is asserted clean at entry (F11) and
the play commits its own feat(model) delta after the checkpoint (C10), before the injected
close sequence — so the working-tree diff vs HEAD is exactly this run's delta.
Compiled From
This play was compiled from the roadmap ICE (reference/ice.md) by play-editor
(#466 Batch C; #467 Batch B — the checkpoint upgraded to a conditional learned gate,
see standards/rules/gate-config.md; #500 — migrated to direct-model-write per ADR 026
and standards/rules/direct-model-write.md). Intent defines constraints (C1–C10) and
failure conditions (F1–F11); the expectation defines success scenarios (S1–S6), 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 and the step order. You delegate the
judgment — effort, resolving dependencies, and a value preference — to the
product-os-keeper agent via a JSON contract over files on disk, and you run every
mechanical part (reading the slices, the ordering math, the keyed in-place persist, the
post-write scoped guard, and the change-shape classifier) through bundled scripts. You
never write slice YAML by hand, you never assign the order numbers yourself, and you never
COMMIT the model delta before the single checkpoint (C8) resolves — a typed approval, a
recorded config skip, or a recorded policy auto-pass.
Forbidden: hand-writing slice/plan YAML; assigning order numbers in prose instead of
via scripts/compute_plan.py; writing the shared spine by any route other than
scripts/persist_roadmap.py; writing anything other than the plan fields
(order/effort/depends_on/status) on a slice's spine entry; editing a slice's composition
(its record); persisting an incoherent plan (a compute_plan.py non-zero exit is a hard
stop before the persist); committing the model delta before the Step 6 gate resolves;
running against a dirty product-os tree (C10/F11); touching the grounding docs, the node
tree, the profile, the lenses, decisions, or the _deferred bucket (C2).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Read /shape's slices and judge the plan inputs — estimate effort per slice, resolve dependency_notes (+ shared functionalities + their spine depends_on) into concrete depends_on, propose a value order; draft to STM only | author-roadmap | Draft |
product-os-keeper is the single domain agent this play uses (1 of the ≤5 budget).
The utility work — committing, raising, reviewing, and merging the change — is the injected
end sequence (commit-change → propose-change → review-change → merge-change), not a domain
agent. /roadmap does not ground in the KB: planning is a judgment over the slices the
model already holds, not a placement question.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Clean model tree — git status --porcelain -- <product_base>product-os is empty | C10/F11 | Hard halt (REC11) |
| The product model holds ≥1 slice to plan | C1 | Graceful exit |
Resolve config mechanically with the bundled resolver; /roadmap has no branch or issue of
its own — the branch carries /vision's strategy-pipeline issue:
python3 scripts/preflight.py --play roadmap --config .garura/core/config.yaml
python3 scripts/snapshot_model.py --product-base <product_base> --probe
preflight.py returns config facts (product_base, stm_base, evidence_record).
snapshot_model.py --probe reports slice_count without writing anything. If
slice_count is 0, exit gracefully: there is nothing to plan — run /shape first to
create slices. The probe is write-free, so re-entering pre-flight on a resume is safe.
Clean-tree assertion (C10/F11, ADR 026). The earlier strategy plays (/vision,
/understand, /shape) each committed their own model delta on this branch, so the product-os
tree is clean when /roadmap enters; still assert it before any write — HEAD is only a
correct base for the scoped guard and the change-shape if the tree is clean:
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC11)"; exit 1; }
If dirty, halt and ask for a clean model tree (commit or revert the pending model edits)
before /roadmap plans.
<working> is the run's STM working root, {stm_base}_shaping/roadmap/ — the plan draft,
the computed plan, the persist record (persist-manifest.json), the captured scoped-guard
report (guard-report.json), and the change-shape all live under it. These are STM,
non-model artifacts (ADR 008/017) — the model itself is written IN PLACE under
<product_base>product-os/. The baked stop condition's relative paths resolve against
<working>.
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}_shaping/roadmap/status/session-stamp-roadmap.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_shaping/roadmap/status.json exists, resume — skip
completed steps, reset any in-progress step to pending, continue from the first incomplete.
Task DAG
Create ALL tasks immediately after resolving config — before any domain work.
The play owns this DAG; the agent must not edit its top-level tasks.
Write-then-review (ADR 026): the FULL model delta — the keyed persist's spine plan-field
writes — is written to the live model BEFORE the checkpoint, so the guard, the change-shape,
and the human all see the real delta. Nothing is COMMITTED before the gate resolves; cancel
reverts the uncommitted writes.
[T1] Read the slices blockedBy: []
[T2] Draft plan (to STM) blockedBy: [T1]
[T3] Compute plan (coherence gate) blockedBy: [T2]
[T4] Persist (keyed, in place — spine plan fields) blockedBy: [T3]
[T5] Guard the full delta + classify the shape blockedBy: [T4]
[T6] Checkpoint (approval over the full git diff) blockedBy: [T5]
[T7] Commit the model delta blockedBy: [T6]
[TE1] commit-change (injected — end #1) blockedBy: [T7]
[TE2] propose-change (injected — end #2) blockedBy: [TE1]
[TE3] review-change (injected — end #3) blockedBy: [TE2]
[TE4] merge-change (injected — end #4) blockedBy: [TE3]
[T8] Scenario Validation blockedBy: [TE4]
[T9] Close blockedBy: [T8]
Mark each task in-progress before its step and completed right after its eval passes.
No runtime reordering. On resume, skip completed and reset in-progress to pending.
Workflow
Phase: Read
Step 1 — Read the slices · Owner: play · Depends on: pre-flight
Read the current spine slices across every shaped domain (id, domain_ref, bundled
functionalities, dependency_notes, current plan fields) as the authoritative list of what
to plan. Under direct-model-write this is a READ of current state (HEAD is clean), not a
before/after picture — the scoped guard (Step 5), not a file census, proves
non-destructiveness:
python3 scripts/snapshot_model.py --product-base <product_base> --out <working>/snapshot.json
SE-1 (F11/C10): the product-os tree was clean at entry — the pre-flight assertion
(git status --porcelain -- <product_base>product-os empty) passed before any write, so
HEAD is a correct base for the scoped guard and the change-shape; a dirty model tree halted
(REC11). (Step 1 otherwise only reads state; the plan is exercised by Step 3 and proven by
Step 5's scoped guard.)
Phase: Draft
Step 2 — Draft plan (to STM) · Owner: product-os-keeper · Depends on: Step 1
The agent invokes author-roadmap to estimate each slice's effort, resolve its
dependencies into concrete depends_on slice ids, and propose a value order — drafting
plan-draft.yaml to STM. Per ADR 026 the skill writes NO model file: plan-draft.yaml is
the manifest data the keyed persist (via compute_plan.py) later applies to the live spine:
{
"task": "for every slice in the snapshot, estimate effort, resolve depends_on (from dependency_notes + shared functionalities + their spine depends_on), and propose a value order; draft to STM only — write no model file",
"inputs": { "snapshot_path": "<working>/snapshot.json",
"product_base": "<product_base>" },
"outputs": { "plan_draft": "<working>/plan-draft.yaml" }
}
The skill reads the slices + their functionalities' grounding read-only and writes only
the STM draft. It assigns no order numbers and runs no topological sort — those are Step 3's
deterministic job. It writes no _spine.yaml, no grounding doc, no model file at all. It
returns the contract with the output path on disk — never inline content.
Phase: Compute (coherence is a HARD gate before persist, C4)
Step 3 — Compute plan · Owner: play · Depends on: Step 2
Turn the draft into a coherent, dependency-correct global plan:
python3 scripts/compute_plan.py --snapshot <working>/snapshot.json \
--plan-draft <working>/plan-draft.yaml --out <working>/plan.json
compute_plan.py topologically orders all slices so dependencies precede dependents
(value order breaks ties), assigns the distinct integer order 1..N across every domain,
carries each slice's effort, and detects cycles. It writes the machine coherence field
into the plan record: plan.json carries orders_coherent: true|false.
Coherence is a HARD gate before the persist (C4): compute_plan.py MUST exit 0 (no
cycle, no missing effort, a coherent 1..N) before Step 4 runs. On a non-zero exit (a cycle
or a slice missing from the draft) apply REC4/REC5 and re-run — an incoherent plan is NEVER
persisted. This non-zero exit is also /roadmap's blocking finding for the Step 6 gate
(no auto-pass while it stands).
SE-3 (F3/C3): within the computed plan, no slice precedes one it depends_on.
SE-4 (F4/C4): every slice in the plan has an integer order and a non-empty effort; the
orders are distinct and form 1..N; compute_plan.py exited 0 (orders_coherent: true)
before the persist ran.
SE-5 (F5/C5): the plan spans every shaped domain's slices and no cycle was persisted;
on a non-zero exit (cycle or a slice missing from the draft) REC4/REC5 applied and re-ran
before Step 4.
Phase: Persist (write the full delta first, ADR 026 write-then-review)
Step 4 — Persist (keyed, in place — spine plan fields) · Owner: play · Depends on: Step 3
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the
checkpoint, so the guard, the change-shape, and the human all see the real delta.
persist_roadmap.py writes the SHARED spine in place, keyed to each planned slice id: for
each slice in the coherent plan it sets ONLY the four plan fields
(order/effort/depends_on/status: planned) on that slice's spine entry, preserving
every other field and collection, and REFUSES a plan that names a slice absent from the live
spine. No draft tree, no doc copy, no before/after census. Nothing is COMMITTED yet — the
commit (Step 7) happens only after the gate approves; on cancel the whole delta is reverted
(Step 6):
python3 scripts/persist_roadmap.py --plan <working>/plan.json \
--product-base <product_base> --out-manifest <working>/persist-manifest.json
persist_roadmap.py writes the machine persist record: persist-manifest.json carries
applied: true alongside the written/changed lists — this field, not prose, is what the
close's stop-condition gate (D2) reads.
SE-6 (F1/F2/C1/C2, F7/C7): the persist wrote only the four plan fields on the named
spine slice entries — no non-slice entry received a plan, no slice record (composition),
grounding doc, or other collection was touched, and each written entry conforms to the spine
schema (integer order, status: planned, non-empty effort, resolved depends_on) — because
the keyed persist writes exactly the fields of an already-coherent plan and refuses any slice
id absent from the spine, by construction.
Phase: Guard + Classify (over the full delta)
Step 5 — Guard the full delta + classify the shape · Owner: play · Depends on: Step 4
The run's write scope (the per-play guard policy, ADR 026). The old apply_roadmap.py
encoded /roadmap's write scope by construction — it only ever wrote _spine.yaml, and only
the slices' plan fields. Under direct-model-write that same file-level scope is the
scoped_write_guard.py policy: the spine is the ONLY model file /roadmap may change, so its
scope is a single --allow:
--allow 'product-os/_spine.yaml' # the shared spine (the keyed persist modifies its slice plan fields in place)
Every other product-model file is out of scope. Node-level containment inside the shared
spine — only the planned slices' plan fields — is kept by the keyed persist (Step 4), not
the guard.
Guard ONCE over the full delta (C6). After ALL writes (the keyed persist's spine
plan-field writes from Step 4), run the scoped guard a single time over the whole delta
against HEAD. Capture its report — its ok field is the stop condition's D3 input:
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--allow 'product-os/_spine.yaml' \
--out <working>/guard-report.json
If the guard exits non-zero (a non-spine model file changed, or a non-plan field inside the
spine), re-run with --restore to revert the offending paths, apply REC6, and re-persist
before the checkpoint.
Classify the full working-tree delta (C8). Classify the model tree's diff vs HEAD — now
the FULL delta (the spine plan-field changes), per ADR 026 write-then-review (no draft dir):
python3 scripts/classify_change.py --play roadmap \
--product-base <product_base> --base-ref HEAD --out <working>/shape.json
SE-7 (F6/C6): the scoped-write guard report reads ok: true — the model delta is
confined to the run's write scope (only _spine.yaml changed); no other product-model file
changed, and the run is non-destructive.
Phase: Checkpoint (conditional gate, C8)
Step 6 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 5
This is the single checkpoint (C8). It is a conditional gate (#467) per
standards/rules/gate-config.md — /roadmap is one of the eleven conditional document
plays. Resolve it first match wins: pinned (n/a here) → gates.plays.roadmap → the
learned policy → gates.classes.standard → gates.default (absent ⇒ on). For the policy
lookup, use the shape key classified in Step 5.
Look the shape key up in the config-resolved policy (gates.conditional.policy):
auto-pass iff the shape is in the policy's auto: block AND not in never_auto:
AND Steps 3 + 5 stand with no blocking finding (a compute_plan.py non-zero exit or a
cycle anomaly in plan.json, or a guard violation — /roadmap's lint-equivalent). On
auto-pass, do NOT wait: record gate auto-passed by learned policy (shape: <shape-key>, policy v<version>) as a Checkpoint Decisions row, include the working-tree diff summary in
the run record, append the crossing's live-eval ledger line, and proceed to Step 7 (commit):
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play roadmap \
--issue <strategy issue> --shape <shape-key> --predicted auto --human auto_pass \
--policy-version <policy version> --ts <run ts>
Anything else resolves the gate on (an explicit gates.plays.roadmap: off instead
records gate skipped by config (<resolution path>) as a Checkpoint Decisions row and
proceeds on the computed plan). When on, present the plan inline over the real model git
diff — the slices in order, each with its effort and its resolved dependencies, plus any
cycle anomalies for the human to break — render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. Approve →
continue to Step 7 (commit). Cancel → revert the working tree (ADR 026 step 6): the full
delta is already on disk, so run the guard with --restore and an EMPTY allow set to
git restore the modified spine back to HEAD, then halt — nothing was committed, and cancel
means "revert what was written":
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--restore --out <working>/guard-report.json # empty --allow ⇒ every model path reverted
Then append the crossing's live-eval ledger line with the human's real action:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play roadmap \
--issue <strategy issue> --shape <shape-key> --predicted gate \
--human <approved_clean|approved_edited|rejected> --ts <run ts>
<strategy issue> is the strategy-pipeline issue the run's branch carries (opened by
/vision's start-change). <gates.conditional.ledger> / <gates.conditional.policy>
resolve from config gates.conditional (defaults .garura/core/gate-evals.jsonl /
.garura/core/gate-policy.yaml); <policy version> is the policy file's version:
field. <run ts> is the run's own UTC timestamp, derived the same way the close
derives ts (date -u), passed by the orchestrator.
SE-8 (F8/C8): the model delta was written to the live spine by Step 4 but is COMMITTED
(made durable) only at Step 7 on approval — so no product-model change is COMMITTED before
this gate resolves (a typed approval, a recorded config skip, or a recorded policy
auto-pass); on cancel the whole working-tree delta is reverted to HEAD before any commit, so
nothing is left on the tree.
SE-10 (F10): every crossing of this gate appended exactly one live-eval ledger line
(shape, predicted gate|auto, the human's real action or auto_pass), and an
auto-pass fired only for a shape the policy lists in auto: (and not in never_auto:)
with no blocking finding standing.
Phase: Commit (make the delta durable, ADR 026 step 7)
Step 7 — Commit the model delta · Owner: play · Depends on: Step 6
The gate approved (or auto-passed / was skipped by config). Commit the full model delta on
the branch (C10, ADR 026 step 7) — a lightweight persist step that makes the writes durable
and advances HEAD, run BEFORE the injected close sequence so the subsequent commit-change
handles only what remains uncommitted (STM evidence, ADRs), not the model delta this play
already committed. A cancelled checkpoint never reaches this step — its tree was already
restored in Step 6:
git add -- <product_base>product-os
git commit -m "feat(model): plan slices — order, effort, dependencies (#<strategy issue>)"
SE-9 (F9/C9): the close is stop-condition gated — check_stop_condition.py over the
baked stop-condition.yaml (D1 the persist record persist-manifest.json exists; D2 it
stamps applied: true; D3 the captured guard-report.json reads ok: true) must read
held before any COMPLETED close, and the model delta is committed (C10); a run whose
keyed persist or scoped guard did not land closes HALTED, never COMPLETED (REC9).
Phase: End sequence (injected — D2 position: end)
The persisted ordering is a durable model change; the standard end sequence closes the
strategy pipeline (#437). Each member runs as a sub-play dispatched with parent_run_id
(emits only its own C1 evidence; this play's close absorbs it). Each member resolves its
own context from the branch and config; this play passes no hand-rolled git/PR/merge
logic. The feat(model) model delta was already committed at Step 7, so commit-change
handles only what remains uncommitted (STM evidence, ADRs).
Step E1 — commit-change · Owner: commit-change (sub-play) · Depends on: Step 7 —
commit the remaining strategy-pipeline changes grouped by concern; no push.
Step E2 — propose-change · Owner: propose-change (sub-play) · Depends on: Step E1 —
self-review, push the branch, open the PR.
Step E3 — review-change · Owner: review-change (sub-play) · Depends on: Step E2 —
diff-scoped review, approve/reject verdict. A reject stops the sequence before merge.
Step E4 — merge-change · Owner: merge-change (sub-play) · Depends on: Step E3
(approve verdict) — merge the PR, switch to main and pull, delete the feature branch.
{
"play": "<commit-change | propose-change | review-change | merge-change>",
"parent_run_id": "<this run id>",
"inputs": {},
"outputs": { "result": "{stm_base}_roadmap/end/<member>.json" }
}
Phase: Scenario Validation
Step 8 — Scenario evals · Owner: play · Depends on: the end sequence
- SCE-1 (S1 — planner, first plan): every non-deferred slice's spine entry has an
integer order and a non-empty effort; orders are distinct 1..N; the scoped-guard report
reads
ok: true (only _spine.yaml changed, only the plan fields); the stop-condition
verdict reads held.
- SCE-2 (S2 — architect, dependencies): for every resolved
A depends_on B,
order(B) < order(A); any cycle is in the checkpoint anomaly list and no order persisted
for it (plan.json anomalies + the persisted spine).
- SCE-3 (S3 — delivery lead, effort): every planned slice carries an effort estimate on
its persisted spine entry.
- SCE-4 (S4 — product manager, cross-domain): the orders form a single 1..N spanning
slices from every shaped domain, not a per-domain sequence.
- SCE-5 (S5 — product owner, re-run non-destructive): on an unchanged model the persist
manifest's
written list is empty and the guard report reads ok; on a changed model only
the plan fields differ on any slice and the guard confirms no other model file changed.
- SCE-6 (S6 — reviewer, the checkpoint): the Step 6 checkpoint showed the ordered slices
with effort and dependencies and any anomalies, over the real model git diff, and no
product-model change was COMMITTED before that gate resolved — on cancel the working tree
returns byte-clean to HEAD — or, on the auto-pass path (a policy-listed shape), the gate
resolved with no wait and the recorded auto-pass, the appended ledger line, and the diff
summary stand in the approval's place.
Phase: Evidence & Close
Step 9 — Close · Owner: play · Depends on: Step 8
Run the Standard Play Close. /roadmap is a product-scoped play (no issue of its own) —
use the product-scoped evidence base and slug. Evidence recording is play-only and
config-gated per the D1 evidence rule (standards/rules/evidence-recording.md).
SE-9 (F9/C9): the close is stop-condition gated — check_stop_condition.py over the
baked stop-condition.yaml (D1 the persist record exists; D2 it stamps applied: true;
D3 the captured guard report reads ok: true) reads held before the run closes
COMPLETED, and the model delta is committed (C10); anything else closes HALTED with the
unmet clauses named.
# --- Standard Play Close (canonical; see standards/rules/play-close.md) ---
# Path tokens resolved at pre-flight (resolve here if not already):
# ltm_project_target = yq '.ltm.project-target' .garura/core/config.yaml
# evidence_base, slug:
# project-scoped play : evidence_base="${stm_base}${issue}/evidence/roadmap/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/roadmap/" ; slug="${product_slug}"
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 identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}_shaping/roadmap/status/session-stamp-roadmap.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 working root as the
# close's authoritative input.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_shaping/roadmap/" \
--out "${stm_base}_shaping/roadmap/status/stop-condition-roadmap.yaml"
sc_exit=$? # 0 held · 1 unmet · 2 error
# Conditional-gate policy refresh (#467) — soft: a distill failure never blocks the close
python3 scripts/distill_gate_policy.py --ledger "<gates.conditional.ledger>" --policy "<gates.conditional.policy>" --streak <gates.conditional.streak> --project "<project name from config>" || true
/roadmap is product-scoped: evidence_base="${product_base}_evidence/roadmap/" and
slug="${product_slug}" (the product, since one run plans the whole set).
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 — fix the state per REC9 (re-run
persist_roadmap.py over the coherent plan, re-capture the scoped-guard report) 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 (global
- per-play
evidence.plays.roadmap; 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 roadmap, run_id roadmap-${ts},
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
plan draft, the computed plan, the persist manifest (persist-manifest.json), the captured
guard-report.json, the model-delta commit sha, the slices planned, the stop-condition
verdict; step and scenario eval results SE-3…SE-10 / SCE-1…SCE-6; checkpoint decision from
Step 6 including the order and any anomalies (incl. any gate skipped by config /
gate auto-passed by learned policy row) plus the gate ledger line(s) appended this run; 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.
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 and output the
report: ## roadmap Delivered — ${product_slug}, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (the ordered slices with
effort + dependencies, any anomalies, the model-delta commit), Next Steps (run /grill on the
top-ordered slice's functionalities to cut epics), and a pointer to $evidence_dest. Always
emitted; never gated.
# --- end Standard Play Close ---
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first plan | planner | SCE-1 |
| S2 — dependencies | architect | SCE-2 |
| S3 — effort | delivery lead | SCE-3 |
| S4 — cross-domain | product manager | SCE-4 |
| S5 — re-run non-destructive | product owner | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | a plan was written onto a non-slice artifact | the keyed persist refuses a non-slice by construction; revert and re-run the persist over the coherent plan only | autonomous |
| F2 | a write touched a slice's composition (record) or another part of the model | the guard's --restore reverted the out-of-scope write; re-run the keyed persist writing only order/effort/depends_on/status on the spine slices, after a human confirms the restore | human |
| F3 | a slice is ordered ahead of one it depends_on | re-run compute_plan.py so dependencies precede dependents before persisting | autonomous |
| F4 | an incoherent plan (a planned slice lacks order or effort, or the orders aren't a coherent 1..N) | recompute a complete, coherent plan before the keyed persist; the persist is gated on compute_plan.py exit 0 | autonomous |
| F5 | a dependency cycle, or a domain's slices left unplanned | surface the cycle at the checkpoint for a human to break, and include every shaped domain's slices in the single plan | human |
| F6 | the scoped-write guard reports an out-of-scope path (a non-spine model file, or a non-plan field inside the spine) | the guard's --restore already reverted the offending paths; re-run the keyed persist writing only the plan fields on the spine slices, after a human confirms the restore | human |
| F7 | a written slice spine entry fails the spine schema | re-derive a coherent plan and re-run the keyed persist so the entry conforms before the play completes | autonomous |
| F8 | the model delta was committed before the checkpoint gate resolved, or a cancelled checkpoint left writes on the working tree | revert the premature commit and the working-tree writes (scoped_write_guard.py --restore, empty allow set) and re-present the checkpoint; commit only after the gate resolves | human |
| F9 | the run is about to close COMPLETED with the Done means unmet (a missing persist record, applied not true, or the guard report absent or not ok) | produce the missing artifact — re-run persist_roadmap.py over the coherent plan so the persist record carries the machine field, re-capture the guard report — then re-evaluate the stop condition; the close stays HALTED until the verdict reads held |
Pause and Resume
Steps run top to bottom. On entry, resolve config, run the write-free pre-flight probe,
check the status marker ({stm_base}_shaping/roadmap/status.json), skip completed steps,
reset any in-progress step to pending, and continue. The pre-flight clean-tree assertion
(F11) is scoped to a FRESH start — a resume that already wrote the spine plan fields
continues its own in-progress delta. A fresh start with no marker runs everything.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:11d120b0c33fd8faaf290bcfe75bbf6cbce0d6b46e45ca380a05d00c5e5a2132 (of reference/ice.md) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#467 Batch B); play-editor (#466 Batch C) |
| pipeline_position | end (commit-change → propose-change → review-change → merge-change tail; opened by /vision) |
| position_exception | model-writing end play — writes the model in place on the branch and commits its own feat(model) delta (C10) BEFORE the injected close sequence; commit-change then handles only the STM remainder |
| workflow_structure | A (single checkpoint, class standard, conditional gate per gate-config.md #467; direct-model-write WRITE-THEN-REVIEW per ADR 026 — persist + guard + classify before the gate, commit after; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 (the end sequence is injected sub-plays, not domain agents) |
| skills_used | author-roadmap |
| scripts | 9 (preflight.py, snapshot_model.py — reads current slices, compute_plan.py — writes the machine orders_coherent field and hard-gates the persist, persist_roadmap.py — keyed in-place persist writing the machine applied field, scoped_write_guard.py — post-write containment, classify_change.py — #467 shape classifier (git mode), gate_eval.py — #467 live-eval ledger, distill_gate_policy.py — #467 policy learner, check_stop_condition.py — Done-means gate, session_stamp.py — #463 identity stamp) |
| step_evals | 10 (SE-1, SE-3…SE-10; SE-2 folded into SE-6) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 11 (one per failure condition; 6 autonomous / 5 human) |
Recompiled note (#500, direct-model-write / ADR 026): migrated from draft-then-apply to
direct-model-write. The old apply_roadmap.py promotion step and the validate_roadmap.py
before/after verify are removed; snapshot_model.py was slimmed to a current-slices read
(its file census / spine_before before-picture are gone — the scoped guard now proves
non-destructiveness). The authoring skill (author-roadmap) writes NO model file — it drafts
plan data to STM, which compute_plan.py turns into the coherent plan.json; the new keyed
persist_roadmap.py writes the spine slices' plan fields in place, keyed by slice id, and
refuses a plan naming a slice absent from the live spine. Containment is the post-write
scoped_write_guard.py (--allow 'product-os/_spine.yaml' — the single file /roadmap
touches; its guard-report.json is D3); classify_change.py reads the working-tree git diff
(--product-base/--base-ref HEAD); checkpoint cancel reverts the working tree via the guard
--restore. Order is write-then-review (ADR 026 "Order of operations"): the full delta is
written to the live spine FIRST (Step 4), then guarded ONCE and classified over the full delta
(Step 5), then the gate resolves over the real git diff (Step 6), and only an approved (or
auto-passed / config-skipped) gate COMMITS (Step 7). Nothing is COMMITTED before the gate;
cancel reverts the uncommitted writes. The play asserts a clean product-os tree at entry (F11)
and commits its own feat(model) delta after the checkpoint (C10), BEFORE the injected close
sequence — which then handles only what remains uncommitted (STM evidence, ADRs). The Done
means moved from the plan-draft/plan/orders_coherent triple to the canonical persist + guard
triple (D1 persist record exists; D2 applied: true; D3 guard ok: true); plan coherence is
now a HARD pre-persist gate (compute_plan.py exit 0) and the Step 6 gate's blocking finding,
not a Done clause. The strategy-chain close (the injected commit-change → propose-change → review-change → merge-change end sequence, opened by /vision) is preserved unchanged. See
standards/rules/direct-model-write.md.
Recompiled note (#467 Batch B): checkpoint upgraded to a conditional learned gate;
see gate-config.md.
Direct-edit deviation note (#500) — INTENT CHANGE, HAND-COMPILED, CONVERGENCE UNVERIFIED:
This SKILL was updated to the direct-model-write write-then-review shape (ADR 026) by a
hand-compile from reference/ice.md, NOT by a /play-editor run. This is an intent
change (it alters the write path, the containment guarantee, the checkpoint cancel semantics,
the Done means, and the step order), so the sanctioned path is recompile-via-/play-editor;
play-editor is interactive-only (fully gated, human-checkpoint) and cannot run headless in this
environment, so the compiled output was produced by hand to match what play-editor would emit
from the current reference/ice.md (fingerprint above). The compiled_by line names
play-editor for provenance intent, but no play-editor run actually occurred and convergence is
UNVERIFIED. An interactive /play-editor convergence run against reference/ice.md is
REQUIRED — confirming the emitted SKILL matches this hand-compiled body and refreshing the
fingerprint. This mirrors the same caveat on /understand's #498 migration (the ratified
reference implementation) and its /vision, /grill, /learn fan-out: roadmap is the last strategy
play in that fan-out and remains convergence-unverified until an interactive play-editor run
confirms it.