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 verd...
Scanned 9/10/2026
Install to Claude Code
npx -y skills add kapilvirenahuja/garura --skill validate --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Validate?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kapilvirenahuja-validate)More formats (shields.io, HTML) on the badges page.
---
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.
```bash
# --- 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/validate/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/validate/" ; 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}${issue}/status/session-stamp-validate.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Single-pass play: the Done means is proven here, once.
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=$? # 0 held · 1 unmet · 2 error
```
**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.
```bash
# --- end Standard Play Close ---
```
## 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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!