| name | arch |
| position | start |
| description | Write a SLICE's architecture lens as a grounding doc (architecture.md) — the components the slice threads (each in its layer, with its contract), the stack (tech + versions) behind them, and the vertical build that runs the slice end-to-end — written directly in place on the live model. The START of the NON-FUNCTIONAL realize pipe (arch → quality → run), run on a shaped slice. Every component is selected from the slice's functionalities' systems + the profile surfaces, never invented; the build is one vertical end-to-end. Reads the hub from the spine (functionality grounding + profile) and MAY read the functional lenses, never the measure or run lens. Writes only the slice's architecture lens and any material-choice decision. |
| user-invocable | true |
arch
Write a shaped slice's architecture lens as the grounding doc architecture.md: the
components the slice threads (each in its layer, with its contract), the stack behind them, and
the vertical build that runs the slice end-to-end. /arch reads the slice's hub — its
functionalities' grounding docs plus the profile box (both from the spine) — and MAY read the
already-merged functional lenses (ux/agentic/marketing), never the measure or run lens.
Pipeline position: start. /arch OPENS the non-functional realize pipe (arch → quality → run):
the D2 rule prepends start-change — resolve or create the slice-realize issue, cut the branch
off fresh main, optional worktree, init STM — so /quality and /run run on this already-started
branch. No close sequence is injected here; the non-functional pipe closes at /run. It writes the
persistent product model (the slice's architecture lens) directly, in place on the started
branch — there is no draft copy and no apply/promote step; review is the branch git diff and the
pipe's end PR (at /run).
Write discipline (ADR 026, standards/rules/direct-model-write.md). The LLM authoring skill
writes ONLY the one per-node lens doc (architecture.md) straight to the live model, re-deriving
this slice's lens in place; every shared-file mutation (the material-choice decisions/) is done
by the deterministic keyed persist script, in place, reading the skill's manifest — it writes each
decision skip-if-exists and refuses any path that is not a decision (the node-level containment the
file-level scoped guard cannot see). The skill writes NO shared model file — no _spine.yaml, no
profile, no decisions/, no other lens, no slice record. The model tree is asserted clean once
start-change has cut the branch (F13) and the play commits its own model delta after approval (C13),
so the working-tree diff vs HEAD is exactly this run's delta. Containment is a post-write scoped
guard (scoped_write_guard.py), not a draft.
Compiled From
This play was compiled from the arch ICE (reference/ice.md) by play-editor (#466 Batch C,
Level 3 rollout per ADR 025; #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–C13) and failure
conditions (F1–F13); 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 step order. You delegate the domain work —
authoring the architecture lens grounding doc — to the product-os-keeper agent via a JSON
contract over files on disk, and you run the mechanical checks (readiness/hub resolution, the
shape linter, the content-quality eval, grounding + coverage, KB grounding, the keyed in-place
persist, and the post-write scoped guard) through bundled scripts and an isolated judge. You never
write the lens yourself, you never write the shared decisions/ by any route other than
scripts/persist_arch.py, and you never commit the model delta before the human approves the
single checkpoint (C11).
Forbidden: hand-writing the lens or a decision; writing anything other than this slice's
architecture.md (by the authoring skill) and a decision (by persist_arch.py) — C2; reading or
grounding on the measure or run lens (C7); writing the decisions/ by any route other than
scripts/persist_arch.py; committing the model delta before the Step 6 gate resolves; running the
lens writes against a dirty product-os tree (C13/F13); closing COMPLETED without the stop-condition
verdict reading held (C12/F11).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Author the slice's architecture lens (components + stack + vertical build) in place on the live model, from the hub + KB architecture/technology grounding, and emit the decisions as manifest data | kb-search, author-architecture-lens | Author |
product-os-keeper is the single domain agent this play uses (1 of the ≤5 budget). The utility
work — cutting the branch and resolving the issue — is start-change (injected head), not a domain
agent. The content-quality judge always runs as an isolated, clean-context sub-agent (optionally on
a configured different model) — never the orchestrator's own context.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Resolve grounding-eval.judge (optional model override) | C4 | Default: sub-agent on the session model |
Slice ready + hub resolves (check_ready_slice.py) | C1 | Hard halt (REC1) |
Clean model tree — after start-change (Step 0), git status --porcelain -- <product_base>product-os is empty | C13/F13 | Hard halt (REC13) |
Resolve the pre-flight facts mechanically with the bundled resolver:
python3 scripts/preflight.py --play arch --config .garura/core/config.yaml
Then resolve the slice and its hub from the spine — the readiness gate that every realize lens
shares:
python3 scripts/check_ready_slice.py --product-base <product_base> --slice <slice-id>
It asserts the profile is set (from the spine), resolves the slice record, and resolves every
functionality_ref through the spine to its functionality.md grounding doc — the hub. It emits
the resolved domain, slice_id, slice_file, lens_dir, and functionality_groundings. If the
slice is absent, a functionality does not resolve, or the profile is not firmed, hard halt
(C1/REC1). <domain> and <slice> below are the resolved domain / slice_id — they key the
run's write scope (the guard globs).
The run's working root (<working> below) is {stm_base}_realize/arch/ — the manifest, the
change-shape, the persist record (persist-manifest.json), the captured scoped-guard report
(guard-report.json), and the status/ markers 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/,
never into <working>. The stop condition evaluates against <working>.
Clean-tree assertion (C13/F13, ADR 026). start-change cuts the branch off fresh main, so the
product-os tree is clean by construction; still assert it right after Step 0 — HEAD is only a
correct base for the scoped guard and the change-shape if the tree is clean before the lens writes:
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC13)"; exit 1; }
If dirty, halt and ask for a clean model tree (commit or revert the pending model edits) before
/arch writes the lens.
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}_realize/arch/status/session-stamp-arch.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_realize/arch/status/<slice-id>.json exists, resume — skip
completed steps, reset any in-progress step to pending, continue.
Task DAG
Create ALL tasks immediately after resolving config — before any domain work.
Write-then-review (ADR 026): the FULL model delta — the LLM lens doc AND the keyed persist's
decisions — 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.
[T0] start-change (injected — start, head) blockedBy: []
[T1] Author the lens (doc to live) blockedBy: [T0]
[T2] Validate the live lens blockedBy: [T1]
[T3] Persist (keyed, in place — decisions) blockedBy: [T2]
[T4] Guard the full delta + classify the shape blockedBy: [T3]
[T5] Checkpoint (approval over the full git diff) blockedBy: [T4]
[T6] Commit the model delta blockedBy: [T5]
[T7] Scenario Validation blockedBy: [T6]
[T8] Close blockedBy: [T7]
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: Start (injected — D2 position: start)
Step 0 — start-change · Owner: start-change (sub-play) · Depends on: pre-flight
Run the start-of-pipeline member as a sub-play, dispatched with parent_run_id so it emits only
its own C1 evidence. It resolves or creates the slice-realize issue, cuts the branch off fresh
main, sets up a worktree iff config calls for it, and initializes STM. /quality and /run run on
this branch; /run closes it.
{
"play": "start-change",
"parent_run_id": "<this run id>",
"inputs": { "title": "<realize arch: the slice>" },
"outputs": { "result": "{stm_base}_realize/arch/start/start-change.json" }
}
start-change owns its own evals (issue anchored, branch off latest main, worktree per config, STM
initialized); they are not re-checked here. Immediately after it returns, run the clean-tree
assertion (pre-flight, C13/F13) so HEAD is a correct base for the guard and the change-shape.
SE-13 (F13/C13): the product-os tree was clean once start-change had cut the branch — the
assertion (git status --porcelain -- <product_base>product-os empty) passed before any lens
write; a dirty model tree halts (REC13), so the change-shape and the scoped guard reflect only this
run's delta.
Phase: Author (write the lens to the live model, ADR 026)
Step 1 — Author the lens (doc to live) · Owner: product-os-keeper · Depends on: Step 0
The agent invokes author-architecture-lens to write the slice's architecture.md (components +
stack + vertical build, per the Architecture lens template) from the hub (the functionality
grounding docs + the profile box) and KB architecture/technology grounding. Per ADR 026 the skill
writes the lens doc straight to the live model — in place at the slice's lens path, re-deriving
this slice's lens — and emits the grounding map plus any material-choice decision as structured
data in arch-manifest.yaml. It writes NO shared model file (_spine.yaml, the profile, the
decisions/) — the keyed persist (Step 3) writes the decisions:
{
"task": "author the slice's architecture lens (components/stack/vertical build) in place on the live model from its hub; ground components to functionalities' systems or profile surfaces and the stack to the KB or a decision; emit decisions as manifest data",
"inputs": { "slice_ref": "<domain>/<slice>",
"slice_file": "<product_base>/<slice_file>",
"functionality_groundings": "<from check_ready_slice>",
"profile": "<spine profile>", "product_base": "<product_base>",
"lens_rel": "product-os/<domain>/slices/<slice>/lens/architecture.md",
"manifest_path": "<working>/arch-manifest.yaml",
"proposals_dir": "<working>/proposals/" },
"outputs": { "lens_rel": "product-os/<domain>/slices/<slice>/lens/architecture.md",
"manifest": "<working>/arch-manifest.yaml" }
}
The skill reads the hub read-only (and may read the functional lenses), writes architecture.md
IN PLACE under <product_base>product-os/, and writes arch-manifest.yaml + any KB proposals
under <working> (STM) with the decisions as structured data. It writes NO decision file and NO
shared model file. It returns the contract with the output paths on disk — never inline content.
Step 2 — Validate the live lens · Owner: play · Depends on: Step 1
Run the guards over the LIVE lens this run wrote and the manifest, before the checkpoint — shape
first, then grounding, then content. Under direct-model-write the lens is already written in place,
and the decisions have NOT yet been written for this run (the keyed persist runs after Step 2), so
validate_arch.py reads the decisions from the manifest (data), not off disk.
python3 scripts/lint_grounding.py --doc <product_base>/product-os/<domain>/slices/<slice>/lens/architecture.md
python3 scripts/validate_arch.py --manifest <working>/arch-manifest.yaml --slice-file <product_base>/<slice_file>
python3 scripts/check_kb_grounding.py --manifest <working>/arch-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/proposals
Then run the content-quality eval over the live architecture.md: spawn an isolated,
clean-context sub-agent handed the judge prompt (standards/rules/grounding-eval.md), the doc, and
the Architecture lens per-section guidance, on the model from grounding-eval.judge.model (default
the session model). One lens doc — a single isolated judge, no fan-out. Gate the verdict:
python3 scripts/grounding_gate.py --verdict <verdict.json>
SE-1 (F1/C1): check_ready_slice.py passed at pre-flight — the slice is ready and its hub
resolves; an unready slice halted (REC1).
SE-2 (F2/C3): lint_grounding.py exits 0 — architecture.md conforms to the Architecture
lens template (Intent/Components/Stack/Vertical build), no missing/extra/empty section.
SE-3 (F3/C4): the content-quality eval gate (grounding_gate.py) passes — architecture.md
is self-explaining and clears the stranger test.
SE-4 (F4/C5): validate_arch.py — every component in the manifest grounds to a functionality's
system or a profile surface; the stack grounds to the KB or a decision.
SE-5 (F5/C6): validate_arch.py — every functionality the slice bundles threads through at
least one component (coverage; the vertical is end-to-end).
SE-6 (F6/C7): validate_arch.py — no component grounds on the measure or run lens.
SE-7 (F7/C8): validate_arch.py — the stack names a decision (in the manifest) that resolves.
SE-8 (F8/C10): check_kb_grounding.py exits 0 — the stack and architecture choices trace to
a KB learning or a recorded proposal.
On any GAP, apply the matching recovery (REC2–REC8) and re-run before the checkpoint.
Phase: Persist (write the full delta first, ADR 026 write-then-review)
Step 3 — Persist (keyed, in place — decisions) · Owner: play · Depends on: Step 2
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the
checkpoint. The lens is already on the live model (Step 1). persist_arch.py now writes the
material-choice decisions/ in place, reading the manifest's decision data: each decision is
written skip-if-exists (an accepted decision is never edited in place — /arch supersedes with a
new record), and the script REFUSES any manifest entry whose path is not a decision (this is the
node-level containment the file-level guard cannot provide). No draft, no doc copy, no spine write.
Nothing is COMMITTED yet — the commit (Step 6) happens only after the gate approves; on cancel the
whole delta is reverted (Step 5):
python3 scripts/persist_arch.py --manifest <working>/arch-manifest.yaml \
--product-base <product_base> --out-manifest <working>/persist-manifest.json
A run with no material decision still stamps applied: true (empty written) — the lens doc was
the only delta, written live by the skill at Step 1.
SE-9 (F9/C2/C9): the persist manifest's written list contains no decision that already
existed (those appear only in skipped) and no refused entry (nothing outside the decisions/
was offered) — the skip-if-exists + refuse-non-decision writer makes overwriting an accepted
decision or writing a shared file structurally impossible; the scoped guard (Step 4) confirms no
path outside the slice's write scope changed.
Phase: Guard + Classify (over the full delta)
Step 4 — Guard the full delta + classify the shape · Owner: play · Depends on: Step 3
The run's write scope (the per-play guard policy, ADR 026). The old apply_arch.py was handed
only this slice's draft, so it could touch only the slice's architecture.md (re-derive) and its
decisions/ (skip-if-exists) by construction; under direct-model-write that same scope, KEYED to
the resolved slice, is the scoped_write_guard.py policy:
--allow 'product-os/<domain>/slices/<slice>/lens/architecture.md' # this slice's lens (re-derived in place)
--add-only 'product-os/<domain>/slices/<slice>/decisions/*.yaml' # this slice's decisions (added, never modified)
Keying the globs to <domain>/<slice> preserves the by-construction containment the old
apply_arch.py had — a write to any OTHER slice's lens or decisions is out of scope and fails the
guard.
Guard ONCE over the full delta (C9). After ALL writes (the LLM lens from Step 1 and the keyed
persist's decisions from Step 3), run the scoped guard a single time over the whole delta. 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/<domain>/slices/<slice>/lens/architecture.md' \
--add-only 'product-os/<domain>/slices/<slice>/decisions/*.yaml' \
--out <working>/guard-report.json
If the guard exits non-zero (a path outside the slice's write scope changed, or an accepted
decision was modified), re-run with --restore to revert the offending paths, apply REC9, and
re-persist before the checkpoint.
Classify the full working-tree delta (C11). Classify the model tree's diff vs HEAD — now the
FULL delta (the lens + any decision), per ADR 026 write-then-review (no draft dir):
python3 scripts/classify_change.py --play arch \
--product-base <product_base> --base-ref HEAD --out <working>/shape.json
SE-9 (F9/C2/C9): the scoped-write guard report reads ok: true — the model delta is confined
to the slice's write scope (its lens re-derived, its decisions added); no path outside the scope
changed, and no accepted decision was modified.
Phase: Checkpoint (conditional gate, C11)
Step 5 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 4
This is the single checkpoint (C11) — the agent never skips it on its own judgment. It is a
conditional gate per standards/rules/gate-config.md (#467; /arch is one of the eleven
conditional document plays). Resolve, first match wins: pinned (n/a here) → gates.plays.arch →
the learned policy → gates.classes.standard → gates.default (absent ⇒ on). For the policy
lookup, use the shape key classified in Step 4.
Look the shape key up in the config-resolved policy (gates.conditional.policy, default
.garura/core/gate-policy.yaml): auto-pass iff the shape is in the policy's auto: block AND
not in never_auto: AND Step 2 + Step 4 stand with no blocking finding (a lint_grounding.py gap,
a grounding_gate.py content-eval fail, or a guard violation). 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 6 (commit):
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play arch --issue <issue> \
--shape <shape_key> --predicted auto --human auto_pass --policy-version <policy version> --ts <ts>
Anything else resolves the gate on (an explicit gates.plays.arch: off instead records
gate skipped by config (<resolution path>) as a Checkpoint Decisions row and proceeds). When on,
present the proposed components (with contracts), the stack, and the vertical build inline over
the real model git diff, plus the decision — render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. Approve → continue to
Step 6 (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
model paths and git clean/remove the new ones (byte-clean back to HEAD), then halt — nothing was
committed, and cancel means "revert what was written" (the branch, issue, and STM start-change
created are its own committed side effects and are left as-is):
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 arch --issue <issue> \
--shape <shape_key> --predicted gate --human <approved_clean|approved_edited|rejected> --ts <ts>
<issue> is the slice-realize issue Step 0 resolved. <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. <ts> is the run's own UTC timestamp, derived the same way the
close derives ts (date -u +%Y%m%d-%H%M%S), passed by the orchestrator.
SE-10 (F10/C11): the model delta was written to the live model by Steps 1 + 3 but is COMMITTED
(made durable) only at Step 6 on approval — so no product-model change is COMMITTED before the gate
resolves (a typed approval, a recorded config skip, or a recorded policy auto-pass); on cancel the
whole working-tree delta is reverted before any commit, so nothing is left on the tree.
SE-12 (F12/C11): 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 6 — Commit the model delta · Owner: play · Depends on: Step 5
The gate approved (or auto-passed / was skipped by config). Commit the full model delta on the
branch (C13, ADR 026 step 7) — a lightweight persist step that makes the writes durable and
advances HEAD so the next pipe play (/quality) enters a clean tree; it is NOT the pipe end sequence
(no end PR — that closes at /run). A cancelled checkpoint never reaches this step — its tree was
already restored in Step 5:
git add -- <product_base>product-os
git commit -m "feat(model): architecture lens for <slice> — components, stack, vertical build (#<issue>)"
SE-11 (F11/C12): 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 (C13); a run whose persist or guard did not
land closes HALTED, never COMPLETED (REC11).
Phase: Scenario Validation
Step 7 — Scenario evals · Owner: play · Depends on: Step 6
- SCE-1 (S1 — architect): the lens this run persisted —
architecture.md at its live path —
is a valid Architecture Lens doc clearing the linter + the content eval, and the
spine/slice/profile/other lenses are byte-identical; the stop-condition verdict reads held.
- SCE-2 (S2 — build lead): every functionality the slice bundles maps to ≥1 component.
- SCE-3 (S3 — architect): every component traces to a functionality's system or a profile
surface; the stack to a decision that resolves.
- SCE-4 (S4 — reviewer): no measure or run lens was read or written.
- SCE-5 (S5 — architect, re-run): a re-run re-derives only
architecture.md in place; the
scoped-guard report reads ok; everything else byte-identical; no accepted decision edited in
place.
- SCE-6 (S6 — reviewer): the Step 5 checkpoint showed the components, stack, and vertical build
inline 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 + live-eval
ledger line + diff summary stand in the approval's place.
Phase: Evidence & Close
Step 8 — Close · Owner: play · Depends on: Step 7
Run the Standard Play Close. /arch is a slice-realize play — record evidence per the D1 rule.
SE-11 (F11/C12): the stop-condition verdict is held before the run closes COMPLETED; a close
over an unmet or unevaluable verdict reads HALTED, never COMPLETED (REC11).
# --- 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/arch/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/arch/" ; slug="${slice_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}_realize/arch/status/session-stamp-arch.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means as the close's authoritative input.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_realize/arch/" \
--out "${stm_base}_realize/arch/status/stop-condition-arch.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 "$(yq '.gates.conditional.ledger' .garura/core/config.yaml)" \
--policy "$(yq '.gates.conditional.policy' .garura/core/config.yaml)" \
--streak "$(yq '.gates.conditional.streak' .garura/core/config.yaml)" \
--project "$(yq '.project.name' .garura/core/config.yaml)" || true
/arch opened a slice-realize issue via start-change, so it is project-scoped:
evidence_base="${stm_base}${issue}/evidence/arch/" and slug="#${issue}".
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 run that never applied (checkpoint cancelled, validation failed) is a
HALTED run, not a done one; fix the state per REC11 (re-run the keyed persist, re-capture the
scoped-write guard report, or make the model-delta commit) 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
arch, run_id arch-${ts}, slice slug, started/completed, status per C0, exit_reason; artifacts:
the slice's architecture.md (live path), the manifest, any decision, the persist manifest
(persist-manifest.json), the captured guard-report.json, the model-delta commit sha, the
stop-condition verdict; the content-eval verdict; step + scenario evals SE-1…SE-13 / SCE-1…SCE-6;
checkpoint decision (incl. any gate skipped by config or 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: ## arch Delivered — ${slug}, the Run Summary table, the Pipeline Steps table, the Artifacts Produced table (the
architecture lens + any decision), Next Steps (run /quality on this slice), and a pointer to
$evidence_dest. Always emitted.
# --- end Standard Play Close ---
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first run | architect | SCE-1 |
| S2 — end-to-end | build lead | SCE-2 |
| S3 — grounded | architect | SCE-3 |
| S4 — foundation discipline | reviewer | SCE-4 |
| S5 — re-run | architect | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | the slice is absent, a functionality does not resolve, or the profile is not firmed | halt and route to /shape or /understand before /arch runs | human |
| F2 | architecture.md fails the template/shape or carries out-of-scope content | re-emit to the Architecture lens template (Intent/Components/Stack/Vertical build only) | autonomous |
| F3 | architecture.md fails the content-quality eval | rewrite the failing section to the judge's cited fixes and re-judge until the gate passes | autonomous |
| F4 | an invented/ungrounded component or stack pick | drop it, or re-tie the component to a functionality's system or a profile surface, and the stack to a decision | autonomous |
| F5 | a functionality threads through no component | add the component(s) that build the missing functionality, so the vertical is end-to-end | autonomous |
| F6 | /arch read or depended on the measure or run lens | remove the dependency; /arch derives from the hub and may read only the functional lenses | autonomous |
| F7 | the stack was set with no decision | record the slice-level stack decision (reuse the product one if it exists) | autonomous |
| F8 | an architecture choice with no KB learning and no recorded proposal | search the KB via kb-search and ground the choice, or raise a KB-learning-gap proposal | autonomous |
| F9 | the scoped-write guard report is not ok — a model path outside this slice's architecture.md and its decisions/ changed, or an accepted decision was edited in place | the guard's --restore already reverted the offending paths; re-run the keyed persist writing only this slice's decisions (skip-if-exists) and re-derive only its architecture.md, after a human confirms the restore | human |
| F10 | the model delta was committed before the checkpoint resolved, or a cancelled checkpoint left writes on the working tree | revert the premature commit and the working-tree writes (guard --restore, empty allow set) and re-present the checkpoint; commit only after the gate resolves | human |
| F11 | the run is about to close COMPLETED with the Done means unmet |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target slice, check the status
marker, skip completed steps, reset any in-progress step to pending, and continue. Resuming a run
that already wrote the lens enters a dirty tree; the clean-tree assertion (F13) is scoped to a
FRESH start right after start-change — a resume continues its own in-progress delta.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:ccc8313690cd591e2f551ad6920000c62499c0210201e08ce8258f10eda9296e (of reference/ice.md) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#467 Batch B, #466 Batch C) |
| pipeline_position | start (non-functional pipe head; the non-functional pipe closes at /run) |
| position_exception | model-writing start play — writes the model on the started branch and commits its own model delta (C13); the pipe end PR belongs to /run |
| workflow_structure | A (single checkpoint — class: standard, conditional learned 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 (start-change is the injected head, not a domain agent) |
| skills_used | kb-search, author-architecture-lens |
| scripts | 11 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_arch, check_kb_grounding, persist_arch, scoped_write_guard, classify_change, gate_eval, distill_gate_policy, check_stop_condition, session_stamp) |
| step_evals | 13 (SE-1…SE-13) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 13 (one per failure condition; 9 autonomous / 4 human) |
Recompiled note (#500, direct-model-write / ADR 026): migrated from draft-then-apply to
direct-model-write. The old draft model tree and the apply_arch.py/check_arch.py promotion path
are removed; the authoring skill (author-architecture-lens) writes the one lens doc
(architecture.md) straight to the live model, re-deriving it in place, and emits the decisions as
manifest data; the new keyed persist_arch.py writes the material-choice decisions/ in place
skip-if-exists (never editing an accepted decision, refusing any non-decision path); containment is
the post-write scoped_write_guard.py (the lens --allow, the decisions --add-only, both keyed
to the resolved <domain>/<slice>; its guard-report.json is D3); classify_change.py reads the
working-tree git diff (--product-base/--base-ref HEAD); validate_arch.py reads decisions from
the manifest (pre-persist), not off disk; checkpoint cancel reverts the working tree via the guard
--restore; the play asserts a clean product-os tree once start-change has cut the branch (F13) and
commits its own feat(model) delta after approval (C13). Order is write-then-review (ADR 026
"Order of operations"): the full delta — the LLM lens AND the keyed persist's decisions — is written
to the live model FIRST (Steps 1+3), then guarded ONCE and classified over the full delta (Step 4),
then the gate resolves over the real git diff (Step 5), and only an approved gate COMMITS (Step 6).
Nothing is COMMITTED before approval; cancel reverts the uncommitted writes. 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, 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 the /vision, /grill, /learn
fan-outs of that pattern: /arch is the non-functional-realize fan-out and remains
convergence-unverified until an interactive play-editor run confirms it.