| name | validate |
| position | none |
| description | Independently verify one built EPIC agent-side — the deep gate of the execute pipeline (implement → validate → launch). Re-runs everything mechanically through a per-tool runner family (java, .net, node, frontend, sql, lint, sonar): the epic's tests, blast-radius-scoped regression, code-level security and quality scans — then compares results against the slice's quality-lens gates AND the product profile's benchmarks (green is the entry, the floor is the bar). Ends in a binary stamped verdict: `validated` (the precondition /launch requires) or `fix_required` (blocks /launch; the fix report names each failure by check and location so /implement fixes exactly that, nothing more). Implement ↔ validate is expected to loop — scope narrows per round, 3 rejections halt to a human. Finds, never fixes; every finding mechanically cited. The /validate command in the ProductOS command model. Use when an implemented epic awaits validation. |
| user-invocable | true |
validate
Take one epic /implement built to "specs passing, awaiting validation" and verify it
independently, agent-side — the deep gate of the execute pipeline. Agents are good at
fixing things; this play's whole value is finding what is not working with total
clarity. Implement ↔ validate is expected to run a few rounds; the loop is affordable
because every finding is mechanically cited (the failing check, the rule that fired,
where) and the re-verification scope narrows per round
(architecture/regression-by-blast-radius).
Green is the entry, never the verdict (technology/validation-floor-profile-benchmarks):
captured results are compared against the slice's quality-lens gates and the product
profile's benchmarks — all tests passing with coverage below the profile's floor is a
reject. An unmeasured bar is not a passed bar.
The mechanics run through a per-tool runner family (the platform-adapter pattern):
run_checks.py orchestrates; runners/runner_{java,dotnet,node,frontend,sql,lint,sonar}.py
each ensure their tool, run it, and emit one normalized result record. The play may
compile, run, or deploy the product only to test it — deploying for human acceptance
is /launch's. Security is code-level only: static analysis, lint, dependency scans —
never penetration-style probing.
The verdict is binary and stamped on the epic — the one durable model write:
validated unblocks /launch; fix_required blocks it and re-admits /implement with the
fix report as its exact work list (lightweight re-entry: fix what's named, no re-plan).
Pipeline position: none. /validate is the MIDDLE play of the execute pipeline
(implement → validate → launch): it expects to run on the epic branch /implement already
started (one issue per epic), injects no head and no close, stops after the verdict is
stamped and reported, and leaves the branch as-is — /launch carries the close chain after
human sign-off. (#434, decisions 20 + 24)
Compiled From
This play was compiled from the validate ICE (reference/ice.md) by play-editor
(#466 Batch B, Level 3 rollout per ADR 025; #467 Batch D turned the Step 4 plan-review
gate off and elevated its machine precondition). Intent defines constraints (C1–C15) and
failure conditions (F1–F15); the expectation defines success scenarios (S1–S5), a Done
means (D1–D4, baked to stop-condition.yaml), and one recovery entry per failure
condition. This play is SINGLE-PASS per run: no internal loop, no iteration cap — the
implement ↔ validate fix loop lives ACROSS runs; the Done means is proven once, at the
gated close.
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 two
pieces of judgment — planning the checks (which tools for which stacks, KB-grounded) and
judging the captured results into cited findings — to the quality-auditor agent via JSON
contracts over files on disk, and you run every mechanical part (eligibility + round
resolution, scope resolution, check execution, gates + benchmarks, the verdict, the stamp,
the report, the final self-check) through bundled scripts. You never edit product code,
never run a check inline that a runner owns, never hand-stamp a verdict, and never write
any model file except through scripts/stamp_epic.py.
Forbidden: editing or "quick-fixing" product code (validate finds, /implement fixes);
hand-authoring a verdict or a finding without its captured citation; running tools outside
the runner family; trusting implement's self-reported results instead of re-running;
probing live systems for security; full-suite regression when a scope file bounds it;
touching the epic record beyond the surgical stamp; starting a fourth round past the cap;
closing COMPLETED without the stop-condition verdict held (C14/F14 — a verdict stamped
either way counts as done, an unstamped run does not).
Agent boundaries:
| Agent | Domain | Skills it invokes | Phases |
|---|
quality-auditor | Plan the checks (stack detection + KB-grounded tooling) and judge the captured results into mechanically cited findings | plan-validation-checks, judge-validation-results | Plan, Judge |
project-orchestrator | Post the rendered report to the epic's tracked issue | manage-issue | Report |
repo-orchestrator | Evidence self-commit per the close standard | commit-change (evidence scope) | Close |
quality-auditor is the single domain agent (1 of the ≤5 budget); the other two are
utility. The judge skill carries no Bash — it cannot run mechanics (C4).
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + tokens (.garura/core/config.yaml) | — | Hard halt |
| On the epic's issue branch (issue resolved from branch) | C10-position | Hard halt |
Epic eligible: in_delivery + issue_ref; implement's done verdict holds (pieces done, gates pass, steelman PASS) | C1 | Hard halt (REC1) |
| Loop cap not reached (rejections < 3) | C10 | Hard halt → escalation record (REC10) |
python3 scripts/preflight.py --play validate --config .garura/core/config.yaml \
--branch "$(git branch --show-current)" --porcelain-file <captured>
python3 scripts/check_ready_validate.py --product-base <product_base> --epic <epic_id> \
--plan {stm_base}{issue}/plan.yaml \
--gates {stm_base}{issue}/gates-results.yaml \
--verdict {stm_base}{issue}/verdict.yaml \
--status-dir {stm_base}{issue}/validate/status --max-rounds 3
preflight.py returns config facts (stm_base, product_base, evidence_record, the
issue from the branch). The epic resolves by id to its entry in the spine epics index
({product_base}/product-os/_spine.yaml); its grounding lives in the epic.md the entry
points to. check_ready_validate.py is the
eligibility gate AND round resolver: it emits round (prior rejections + 1) and
prior_report (the last fix report, the round-N scope source). At the cap it sets
escalate: true — present the round history to the human and stop; never start the round
(C10/F10). Also capture the two live reads to files now: git status --porcelain > {working}/porcelain-before.txt (SE-12 compares after the run).
{working} is the issue's STM context dir ({stm_base}{issue}/context/) — the baked
stop-condition manifest reads the run's artifacts there, relative to the STM root.
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}{issue}/status/session-stamp-validate.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}{issue}/validate/status/round-<n>-progress.json exists,
resume — skip completed steps, reset any in-progress step to pending.
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.
[T1] Resolve blast scope blockedBy: []
[T2] Plan checks (KB-grounded) blockedBy: [T1]
[T3] Grounding check (KB) blockedBy: [T2]
[T4] Checkpoint (skippable) blockedBy: [T3]
[T5] Run checks (runner family) blockedBy: [T4]
[T6] Gates + benchmarks blockedBy: [T5]
[T7] Judge findings blockedBy: [T6]
[T8] Compute verdict blockedBy: [T7]
[T9] Stamp epic blockedBy: [T8]
[T10] Render + post report blockedBy: [T9]
[T11] Verify the run blockedBy: [T10]
[T12] Scenario Validation blockedBy: [T11]
[T13] Close blockedBy: [T12]
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: Scope
Step 1 — Resolve blast scope · Owner: play · Depends on: pre-flight
python3 scripts/resolve_blast_scope.py \
--reports-dir {stm_base}{issue}/piece-reports \
--round <round> [--prior-report <prior_report>] \
--out {working}/scope.json
Round 1 scopes from implement's piece reports (files_modified — the build's recorded
claims); round N+1 from the FIX's piece reports plus what the prior report's findings
named. The script can only read claims or fail loud — it cannot invent scope.
SE-2 (F8/C6): resolve_blast_scope.py exits 0; scope.json.source is
implement-claims (round 1) or fix-claims+prior-report (later rounds); its files are
non-empty and every entry traces to a piece report or the prior report.
Phase: Plan
Step 2 — Plan checks (KB-grounded) · Owner: quality-auditor · Depends on: Step 1
The agent gathers the epic, the slice's quality + architecture lenses, the profile, the
scope, and the repo, and invokes plan-validation-checks:
{
"task": "author the check manifest for this epic: detect the stack(s) (arch lens + repo manifests), resolve each to KB-grounded tooling (build, tests, scoped regression per scope.json, lint/static analysis, dependency audit, the scanners the bars demand), map every quality-lens gate (quality.md "## Gates" table) and measure-lens metric (measure.md "## Metrics" table) to a concrete check; code-level security only; round > 1 plans only what the prior report and narrowed scope require; uncovered tool choices become KB-learning-gap proposals, never silent picks. ALSO map the epic's declared surface.type (surface-contract.md) to its ONE required runnable check and add it to the manifest tagged `surface_check: <surface.type>` — a real browser check that opens each must_open artifact (web_dashboard), an HTTP/API call to the declared endpoint (server_api), a run of human_run_target (cli), or the library/service's own tests (library/service_read_model). The plan is INCOMPLETE for a required (user-facing) surface unless it carries that surface_check entry — never omit it for lack of a browser/API probe; the absence of a probe fails the surface, it does not waive it.",
"inputs": { "epic_file": "<epic_file>",
"quality_lens": "<lens_dir>/quality.md",
"arch_lens": "<lens_dir>/architecture.md",
"measure_lens": "<lens_dir>/measure.md",
"profile_path": "<product_base>/product-os/_spine.yaml",
"scope_file": "{working}/scope.json",
"repo_root": "<repo_root>",
"round": "<round>",
"kb_search": "<skills>/kb-search/scripts/kb_search.py",
"kb_root": "<knowledge_dir>" },
"outputs": { "out_dir": "{working}/plan/",
"manifest": "{working}/plan/checks.yaml",
"choices": "{working}/plan/check-choices.yaml",
"proposals": "{working}/plan/proposals/" }
}
SE-4 (F9/C5): every checks.yaml entry's runner is one of the bundled runner
classes (java, dotnet, node, frontend, sql, lint, sonar) and no command targets a remote
system — the plan is code-level/local by construction; a probe-class check cannot execute
because no runner exists for it.
SE-14 (F13/C13): the plan is complete against the epic's surface — checks.yaml
carries exactly one entry tagged surface_check: <epic.surface.type> for the declared
type (for a user-facing type web_dashboard/server_api/cli that entry is the required
runnable check: a browser open of each must_open, an HTTP call to the endpoint, or a run
of human_run_target; for library/service_read_model it is the own-tests entry). A
required surface with no surface_check entry is an INCOMPLETE plan — the check is not
waived for lack of a browser/API probe; loop back to Step 2 to add it before running.
Step 3 — Grounding check (KB) · Owner: play · Depends on: Step 2
python3 scripts/check_kb_grounding.py --manifest {working}/plan/check-choices.yaml \
--proposals-dir {working}/plan/proposals --kb-root <knowledge_dir>
SE-3 (F12/C12): exits 0 — every tool pick and scoping decision carries a KB learning
id that resolves or a proposal file that exists; none is invented. On GAP, apply REC12.
Phase: Checkpoint (skippable)
Step 4 — Review the plan · Owner: play · Depends on: Step 3 · Checkpoint (class: standard)
Resolve the checkpoint's gate FIRST, per standards/rules/gate-config.md (first match
wins: gates.plays.validate → gates.classes.standard → gates.default; absent ⇒ on).
Under #467 Batch D gates.plays.validate is off — the human plan-review is redundant
because the manifest is fully DERIVED (Step 2 detects the stacks, Step 3 grounds every
tool choice in the KB, and any ungroundable pick already halted as a KB-learning-gap at
Step 3, C12/F12). The gate governs ONLY the human review; that grounding wall is the
machine's and is never gated (C15).
- off — do NOT wait. The precondition that replaces the human review is the machine
wall:
check-choices.yaml grounded clean at Step 3 — every check traces to a
stack-detection result plus a KB-grounded tool choice, no ungrounded pick. Record
gate skipped by config (gates.plays.validate) as a Checkpoint Decisions row in the
evidence and proceed. If any tool choice is ungrounded and was NOT recorded as a
KB-learning-gap, do NOT proceed — apply REC15: halt, run the KB search, record the gap,
re-derive, and proceed only when the manifest is fully grounded (this halt is the
machine wall, never gated).
- on — present the check plan in plain language: the stacks detected, the checks per
bar, the regression scope and its source, any KB gaps or lens-vs-repo mismatches from
notes. Skip rule: when check-choices.yaml grounded clean with zero proposals and
the plan's notes are empty, skip — post the summary and continue. Otherwise wait for a
typed response.
SE-16 (F15/C15): every entry in checks.yaml traces to a stack-detection result plus
a KB basis (its check-choices.yaml learning id resolves) OR to a recorded
KB-learning-gap proposal under {working}/plan/proposals/ — the off-path precondition
holds; a proceed on an ungrounded, unrecorded choice is F15 (REC15). The Step 4 gate
resolution (fired, skipped, or its config-skip row) is recorded in the evidence either
way — never silent.
Phase: Execute
Step 5 — Run checks (runner family) · Owner: play · Depends on: Step 4
python3 scripts/run_checks.py --manifest {working}/plan/checks.yaml \
--results-dir {working}/results [--only <prior-findings' check ids, round > 1>]
The orchestrator dispatches each check to its runner as a concurrent read-only fan-out
(standards/rules/concurrent-fanout.md): the checks run in a bounded worker pool and JOIN
before the summary is built. The three safety conditions hold — each check only READS the
code (SE-12 proves the tracked-file set is byte-unchanged), each writes only its own
<check_id>.json, and no check consumes another's output; results are collected in
manifest order, so summary.json is identical to a serial run. Runners ensure their tool
(install when the manifest says how), run it, and emit normalized records. A tool that
cannot run is status: error — recorded, never skipped.
SE-5 (F5/C4, C7): every result in {working}/results/ was emitted by a runner (each
carries raw_log_path; summary.json counts them); no check result was authored by an
agent or inline prose; everything that ran, ran to TEST — the manifest carries no
acceptance-deploy entry (deploying for human acceptance is /launch's, C7). SE-12 (F4/C2): re-capture git status --porcelain > {working}/porcelain-after.txt; the tracked-file set matches porcelain-before.txt —
validate edited no product code (build outputs are untracked; any tracked diff is REC4).
Step 6 — Gates + benchmarks · Owner: play · Depends on: Step 5
python3 scripts/check_gates.py --quality-lens <lens_dir>/quality.md \
--measure-lens <lens_dir>/measure.md \
--results-dir {working}/results --out {working}/gates-map.json \
--product-base <product_base> --epic <epic_id>
SE-11 (F3-partial/C11): every quality-lens gate (read from quality.md's "## Gates"
table, by dimension) maps to a captured result and every measure-lens metric (read from
measure.md's "## Metrics" table) is captured and, where its Target cell yields a
comparator + number, at/above its floor (or at/below its ceiling) — or it is a FINDING in
gates-map.json with citation + location (gate-unmeasured, benchmark-below-floor). The
floor is the bar; exit 1 with findings is a valid (failing) outcome, not an error.
SE-14 (F13/C13) — verdict half: check_gates.py (with --product-base --epic) requires a
captured result tagged surface_check: <epic.surface_type> with status pass; a required
surface the run did not measure, or a captured surface result whose type does not match the
declared surface.type, is a finding (surface-unmeasured / surface-mismatch /
surface-failed) in gates-map.json with citation + location — so the verdict step
(Step 8) computes fix_required, never validated, on an unmeasured required surface. A
browser/API/CLI promise cannot pass on code-level checks alone.
Phase: Judge
Step 7 — Judge findings · Owner: quality-auditor · Depends on: Step 6
The agent reads ONLY the captured artifacts and invokes judge-validation-results:
{
"task": "compose the findings from the captured results: extract the exact failing assertion/rule/file:line from each failed check's raw log, deduplicate root causes across tools, flag gamed-looking patterns (collapsed test counts, fresh suppressions) — every finding cites captured output and a location; no speculation, nothing run, nothing fixed",
"inputs": { "results_dir": "{working}/results",
"gates_map": "{working}/gates-map.json",
"scope_file": "{working}/scope.json" },
"outputs": { "out_findings": "{working}/findings.yaml" }
}
SE-6 (F2/C3): every entry in findings.yaml carries a citation quoting captured
output and a location (file:line or the raw log path) — enforced again mechanically at
Step 8, which refuses uncited findings.
Phase: Verdict
Step 8 — Compute verdict · Owner: play · Depends on: Step 7
python3 scripts/compute_verdict.py --summary {working}/results/summary.json \
--gates-map {working}/gates-map.json --findings {working}/findings.yaml \
--round <round> --epic-id <epic_id> --out {working}/verdict.json
SE-7 (F3/C8): the verdict is computed mechanically — validated only when every
check passed AND zero gate/benchmark findings AND zero judge findings; the script refuses
uncited findings (ok=false) rather than weakening the verdict. Record the round:
copy verdict.json → {stm_base}{issue}/validate/status/verdict-round-<round>.json
(with report set to the report path once rendered) — the next round's gate reads these.
Step 9 — Stamp epic · Owner: play · Depends on: Step 8
Snapshot first, then the one durable model write:
cp <product_base>/product-os/_spine.yaml {working}/spine-before.yaml
python3 scripts/stamp_epic.py --product-base <product_base> --epic <epic_id> \
--verdict-file {working}/verdict.json --record {working}/verdict.json
On a successful stamp, --record marks the verdict artifact stamped: true in place —
the value the stop-condition gate reads at close (#464, Done means D4; a verdict stamped
either way counts as done, an unstamped run does not — C14). Then re-copy
verdict.json → {stm_base}{issue}/validate/status/verdict-round-<round>.json so the
durable round record carries the stamp.
On a validated verdict the stamp also sets surface_verified: true — the surface-parity
gate (C13) passed, so the epic records that its required surface was measured and matched
(surface-contract.md); /next reads this to distinguish a surface that shipped from surface
debt. On fix_required, surface_verified is left untouched.
SE-8 (F6/C8): stamp_epic.py exits 0 and the epic's status now equals the verdict —
fix_required actually blocks /launch and re-admits /implement; validated actually
unblocks it. SE-9 (F11/C8): Step 11 proves against the snapshot that only status,
metadata.version, and (on a pass) surface_verified: true changed.
Phase: Report
Step 10 — Render + post report · Owner: play, then project-orchestrator · Depends on: Step 9
python3 scripts/render_fix_report.py --verdict {working}/verdict.json --round <round> \
--out-yaml {working}/report.yaml --out-md {working}/report.md
Then dispatch project-orchestrator to post report.md to the epic's tracked issue via
manage-issue (comment) — the report is implement's work list and must survive a lost
session, like implement's published plan. Update the round record's report field with
the report.yaml path.
SE-10 (F7/C9): render_fix_report.py exits 0 (it refuses uncited entries); on
fix_required every rendered entry names the check id, the citation, and the location;
the issue carries the posted comment.
Phase: Verify
Step 11 — Verify the run · Owner: play · Depends on: Step 10
python3 scripts/check_validate.py --verdict {working}/verdict.json \
--summary {working}/results/summary.json --gates-map {working}/gates-map.json \
--spine-before {working}/spine-before.yaml --product-base <product_base> --epic <epic_id> \
--report-yaml {working}/report.yaml --report-md {working}/report.md
SE-1 (F1/C1): holistic re-assert — the run only got here through the eligibility
gate (pre-flight exit 0 recorded in the round progress record). SE-13 (F10/C10): the
round number in verdict.json is ≤ 3 and, when this round was a third rejection, the
close presents the escalation record (all three rounds' findings + fixes) instead of
inviting a fourth. check_validate.py exits 0: the verdict matches the recomputed
evidence (F3), the stamp matches the verdict (F6), the epic write was surgical (F11),
and a failed run carries a complete, cited report (F7).
Phase: Scenario Validation
Step 12 — Scenario evals · Owner: play · Depends on: Step 11
- SCE-1 (S1 — delivery lead): on a clean run, the epic file's status is
validated,
verdict.json cites every captured check (counts match summary.json), and its
findings list is empty.
- SCE-2 (S2 — implementer): on a failing run, the epic status is
fix_required,
every report.yaml finding carries check id + location, and on the re-run round the
stamp flips to validated with total rounds ≤ 3.
- SCE-3 (S3 — security auditor): scan results exist under
{working}/results/ with
rule ids in their findings' citations; no check ran outside the runner family; the
grounding check exits 0 with zero ungrounded tool choices.
- SCE-4 (S4 — product owner): the regression checks' commands trace to
scope.json,
whose source is recorded claims; each regression failure in the findings carries its
check id.
- SCE-5 (S5 — human): at three rejections the next invocation's eligibility gate
exits with
escalate: true, no round-4 artifacts exist, and the escalation record
lists each round's findings and fixes.
Phase: Evidence & Close
Step 13 — Close · Owner: play · Depends on: Step 12
Run the Standard Play Close. /validate is project-scoped (the epic's issue). The close
is GATED (the stop-condition step below, #464): the verdict decides COMPLETED vs HALTED.
SE-15 (F14/C14): the stop-condition verdict read held before COMPLETED was stamped —
D1–D4 all held; a run whose checks ran but whose verdict never stamped closes HALTED
(stop_condition_unmet), never COMPLETED. A stamped fix_required counts as done — the
gate binds the RUN's completeness, not the epic's quality.
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}${issue}/status/session-stamp-validate.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}${issue}/" \
--out "${stm_base}${issue}/status/stop-condition-validate.yaml"
sc_exit=$?
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. An unevaluable verdict is never a pass.
The bind is about the RUN, not the epic: a fix_required verdict, stamped, still closes
COMPLETED — an unstamped run does not (C14).
Step C1 — Write evidence file. Gated by the resolved evidence.record flag (global +
per-play evidence.plays.validate; 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 validate, run_id validate-${ts},
the issue, started_at/completed_at, status per C0, exit_reason; artifacts produced:
scope.json, checks.yaml + check-choices.yaml, results/ + summary.json, gates-map.json,
findings.yaml, verdict.json, the round record, report.yaml/.md, the stamp diff, the
stop-condition verdict; step and scenario eval results SE-1…SE-15 / SCE-1…SCE-5; the
Step 4 checkpoint decision or skip (or its config-skip row); 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 (the WORK is never committed by this
play — the branch is /launch's to close).
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: ## validate Round <round> — #${issue} (the verdict, plainly: validated → "/launch
is next"; fix_required → "the fix report is on the issue — run /implement to fix exactly
what it names"; escalation → the three-round history), the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table, Next Steps, and a pointer to
$evidence_dest. Always emitted; never gated.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — happy path | delivery lead | SCE-1 |
| S2 — the fix loop | implementer | SCE-2 |
| S3 — security pass | security auditor | SCE-3 |
| S4 — nothing shipped broke | product owner | SCE-4 |
| S5 — escalation | human | SCE-5 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | implement's done verdict missing or incomplete at pre-flight | halt; the epic must finish /implement first | human |
| F2 | a finding has no mechanical citation | rebuild the finding from captured results; re-run its check if needed; drop what cannot be cited | autonomous |
| F3 | the verdict contradicts captured evidence | recompute the verdict mechanically from the result files — never hand-stamp | autonomous |
| F4 | validate edited product code | revert the edit; record it as a finding routed to implement | autonomous |
| F5 | an agent ran mechanics or judged without captured results | re-dispatch with the script doing the work; the agent re-judges over its output | autonomous |
| F6 | a failed run carries no fix_required stamp | apply the stamp via the surgical writer before close | autonomous |
| F7 | a fix report too vague to act on | regenerate entries with check id + location from captured results | autonomous |
| F8 | blast scope not drawn from recorded change claims | rebuild the scope from the recorded claims; re-run regression on it | autonomous |
| F9 | probing beyond code-level | discard those results; re-run the security pass code-level only | autonomous |
| F10 | the cap reached without escalation | stop the loop; produce the escalation record with round history | human |
| F11 | the epic record touched beyond the stamp | restore the record and re-apply only the surgical stamp | autonomous |
| F12 | an ungrounded scan choice with no gap recorded | run the KB search; if still uncovered, record the gap proposal, then proceed | autonomous |
| F13 | the required surface check for the epic's surface.type was not run, or the captured surface result does not match the declared type | stamp fix_required naming the unmeasured/mismatched surface (the surface.type and the must_open/human_run_target it owed), and re-admit /implement to restore it so the next round can measure it | autonomous |
| F14 | the close would stamp COMPLETED without the stop-condition verdict held | evaluate the stop condition, surface the unmet clauses, and close HALTED (stop_condition_unmet) until they are fixed — an unevaluable verdict is never a pass | autonomous |
| F15 | with the plan-review gate off, the manifest reached the check-execution step carrying an ungrounded tool choice that was neither KB-matched nor recorded as a KB-learning-gap | halt before the checks run; run the KB search for the uncovered choice, record the KB-learning-gap proposal, re-derive the manifest, and proceed only when every check is fully grounded | autonomous |
Pause and Resume
Steps run top to bottom. On entry, resolve config, capture the live reads, run the
eligibility + round gate, check the round progress marker, skip completed steps, reset any
in-progress step to pending, and continue. The epic snapshot is captured at Step 9 (the
gated stamp step) and preserved on resume, so the surgical comparison always diffs against
true pre-stamp state. A fresh start with no marker runs everything and creates the marker
at Step 1. Each round is its own progress marker; a resumed round never re-counts itself.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:01bbfe25700849b61f01cc3d0ee6390e2578db62c704560ea22831e01cea3ac8 (of reference/ice.md) |
| compiled_by | play-editor (#466 Batch B; #467 Batch D — Step 4 gate off + C15/F15/REC15/SE-16) |
| pipeline_position | none |
| position_exception | middle of the execute pipeline (implement → validate → launch) — runs on the epic branch /implement started; the close chain belongs to /launch after human sign-off (#434, decisions 20 + 24) |
| structural_constraints | C2-part (finder/fixer split — no code-edit step exists), C4 (judge skill carries no Bash; mechanics only via runners), C7 (no acceptance-deploy step — run-to-test only), C9 (remediation only via the posted report; no builder dispatch), C10-position (execute middle — no head, no close) |
| workflow_structure | A (Step 4 checkpoint gate off under #467 Batch D — the human plan-review is replaced by the KB-grounding wall, C15; still skippable when on and the plan grounds clean); single-pass — no loop, no cap; gated close |
| stop_condition | stop-condition.yaml (D1–D4), gate live at Step C0 |
| domain_agents | 1 (quality-auditor) |
| utility_agents | 2 (project-orchestrator, repo-orchestrator) |
| skills_used | plan-validation-checks, judge-validation-results, kb-search, manage-issue |
| standards_consumed | standards/rules/gate-config.md (Step 4 switch); standards/rules/play-close.md; standards/rules/pipeline-next.md |
| scripts | 13 + 8 runners (preflight.py, check_ready_validate.py, resolve_blast_scope.py, run_checks.py, check_gates.py, check_kb_grounding.py, compute_verdict.py, stamp_epic.py, render_fix_report.py, check_validate.py, session_stamp.py — session identity stamp, check_stop_condition.py — Done-means gate, runners/runner_common.py + 7 runner*.py) |
| step_evals | 16 (SE-1…SE-16; every failure condition covered; SE-14 covers the surface contract F13/C13 at the plan and verdict halves; SE-15 covers the gated close F14/C14; SE-16 covers the gate-off manifest-grounding precondition F15/C15) |
| scenario_evals | 5 (SCE-1…SCE-5) |
| recovery_entries | 15 (one per failure condition; 13 autonomous / 2 human) |
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. The mechanism
scripts were retargeted: check_ready_validate.py reads the epic entry by id from the spine;
check_gates.py reads the epic's surface_type from the spine entry (was epic.yaml
surface.type); stamp_epic.py makes the in_delivery → validated/fix_required flip (plus
surface_verified on a pass) as a surgical write to the spine epics entry (--product-base --epic); the surgical-write proof now diffs the epic entry between a pre-stamp spine snapshot
(--spine-before) and the live spine (check_validate.py). Prose invocations updated to
match. No constraint/failure/scenario/eval text changed — the verdict logic, the surface gate,
and the surgical-write guarantee are identical; only the storage moved. reference/ice.md and
the fingerprint are unchanged.
Direct-edit deviation note (#434, validate-readers-spine-migration): check_gates.py's
gate + benchmark DEFINITIONS now read the lens GROUNDING DOCS, not YAML: quality gates parse
quality.md's "## Gates" table (gate id = dimension slug; captured check_id matches the slug),
and benchmarks parse measure.md's "## Metrics" table (each metric is presence-required, and
where its Target cell yields a comparator + number it is also compared as a floor/ceiling). The
old standalone profile.yaml benchmark source is retired — the structured floor/ceiling numbers
it carried now live as prose targets in the measure lens, so a tolerant comparator parse stands
in (prose-only targets are presence-checked). CLI: --quality-lens now takes quality.md,
--profile is replaced by --measure-lens <measure.md>; the plan-validation-checks contract
points at the .md lenses + the spine. The gate LOGIC (every declared bar maps to a captured
result; an unmeasured bar is not a passed bar) is identical — only the definition source moved
from YAML to the linted, eval-gated grounding docs. reference/ice.md and the fingerprint are
unchanged.
Direct-edit deviation note (#468 Stage 5, concurrent fan-out): run_checks.py now runs
its per-tool check runners as a concurrent read-only fan-out — a bounded worker pool that
joins before the summary is built (standards/rules/concurrent-fanout.md) — instead of a
serial loop. The three safety conditions hold (each check reads only the code, writes only
its own <check_id>.json, and depends on no other), and results are collected in manifest
order, so summary.json is byte-identical to a serial run; a --max-parallel 1 flag forces
the old serial behavior. Execution-timing change only: no constraint/failure/scenario/eval
touched, the finder-not-fixer guarantee (SE-12) is unchanged, and reference/ice.md and the
fingerprint stand. A duplicate-check_id guard was added because concurrent runners must not
race on the same result file.