okstra 0.176.0 → 0.177.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/task-process/final-verification.md +5 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/okstra-provider-exec.py +2 -7
- package/runtime/prompts/coding-preflight/overview.md +2 -1
- package/runtime/prompts/lead/convergence.md +11 -6
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/plan-body-verification.md +4 -2
- package/runtime/prompts/lead/report-writer.md +8 -4
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +11 -9
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +7 -6
- package/runtime/prompts/wizard/prompts.ko.json +2 -2
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +2 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +50 -6
- package/runtime/python/okstra_ctl/dispatch_state.py +28 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +49 -3
- package/runtime/python/okstra_ctl/handoff.py +27 -14
- package/runtime/python/okstra_ctl/release_gate.py +56 -0
- package/runtime/python/okstra_ctl/report_contract.py +1 -0
- package/runtime/python/okstra_ctl/report_finalize.py +54 -0
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +4 -1
- package/runtime/python/okstra_ctl/run.py +31 -16
- package/runtime/python/okstra_ctl/stage_targets.py +73 -1
- package/runtime/python/okstra_ctl/wizard.py +19 -10
- package/runtime/python/okstra_ctl/worker_liveness.py +3 -1
- package/runtime/python/okstra_ctl/wrapper_status.py +15 -0
- package/runtime/schemas/final-report-v2.0.schema.json +55 -18
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -1
- package/runtime/validators/validate-run.py +98 -12
- package/runtime/validators/validate_analysis_report.py +2 -5
|
@@ -51,7 +51,7 @@ sequenceDiagram
|
|
|
51
51
|
P->>Reg: resolve task or stage worktree
|
|
52
52
|
alt whole-task
|
|
53
53
|
P->>Git: merge done stage commits into task worktree
|
|
54
|
-
P->>Git:
|
|
54
|
+
P->>Git: keep stage worktrees (teardown runs after the verdict)
|
|
55
55
|
else single-stage
|
|
56
56
|
P->>Git: reuse selected stage worktree
|
|
57
57
|
end
|
|
@@ -103,7 +103,7 @@ flowchart LR
|
|
|
103
103
|
Deny -->|denied token| Reject[record rejected command<br/>do not execute]
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
The
|
|
106
|
+
The runtime validates the `project.json` `qaCommands` deny-list at the prepare stage for both `implementation` and `final-verification` (`okstra_ctl.run.validate_project_qa_commands`), so a Tier 2 declaration carrying a mutating token stops the run before it starts. Tier 1 comes from the brief or the approved plan and is not covered by that gate — the lead self-checks those commands right before execution.
|
|
107
107
|
|
|
108
108
|
## 5. Verdict and routing
|
|
109
109
|
|
|
@@ -132,6 +132,7 @@ flowchart TD
|
|
|
132
132
|
Snapshot[base/head SHA<br/>status + diff stat] --> Report[final-verification final report]
|
|
133
133
|
Source[Source Implementation Report<br/>path + quoted commit/diff] --> Report
|
|
134
134
|
Coverage[requirement coverage<br/>artifact per requirement] --> Report
|
|
135
|
+
Added[added-surface audit<br/>each addition → requirement] --> Report
|
|
135
136
|
Blockers[acceptance blockers] --> Report
|
|
136
137
|
Risks[residual risks] --> Report
|
|
137
138
|
Commands[read-only command log] --> Report
|
|
@@ -149,6 +150,7 @@ The final report requires at least the following.
|
|
|
149
150
|
- blocker table or `No acceptance blockers found.`
|
|
150
151
|
- residual risk and escalation trigger
|
|
151
152
|
- coverage artifact per requirement
|
|
153
|
+
- added-surface audit — one row per identifier / module / configuration entry the diff added, with its callers and the requirement it serves
|
|
152
154
|
- read-only command log and exit code
|
|
153
155
|
- next safe phase recommendation
|
|
154
156
|
|
|
@@ -165,7 +167,7 @@ flowchart TD
|
|
|
165
167
|
FV -. forbidden .-> Hide[hide verifier dissent]
|
|
166
168
|
```
|
|
167
169
|
|
|
168
|
-
The stage merge
|
|
170
|
+
The stage merge of whole-task mode is a runtime-owned integration step that prepare performs; the matching teardown is a runtime-owned step too, but it runs after the verdict (Phase 7 `teardown-stages`) and only when the verdict clears the work for release. After that, lead verification is read-only. Source edit, follow-up fix, and scope expansion are all forbidden. When a defect is found, it is not fixed within the current run. Cause defects route to `error-analysis`, selected-direction defects route to `implementation-option-selection`, and detailed-plan defects route to `implementation-planning`.
|
|
169
171
|
|
|
170
172
|
## 8. Verified code
|
|
171
173
|
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -120,7 +120,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
|
|
|
120
120
|
|
|
121
121
|
- Read the exact permitted header values from the task bundle schema excerpt. In the current v2 contract, `header.reportOwner` is `"Okstra lead"` and `header.reportAuthor` is `"Report writer worker"`. Set author to `"Okstra lead"` only for `release-handoff` runs (single-lead by design) or a recorded report-writer dispatch failure fallback. A legacy v1 excerpt may retain its historical compatibility values; follow that excerpt rather than inferring ownership from the provider.
|
|
122
122
|
- **Source items (worker:item) preservation.** Every `consensus[].sourceItems`, `differences[].workersPosition[].itemId`, and `evidence.primary[].sourceItems` entry MUST carry the worker:item-id pair (e.g. `claude:F-001`, `codex:1.1`, `antigravity:F-3`, or `lead:mcp-1` for lead-only evidence). The schema enforces this via the `SourceItem` regex; bare worker-name lists no longer parse.
|
|
123
|
-
- **Verdict Card consistency.** `
|
|
123
|
+
- **Verdict Card consistency.** The Card has no `verdictToken` field: the verdict token is authored once, in `finalVerdict.verdictToken`. `verdictCard.direction` MUST byte-match `finalVerdict.direction`; `validators/validate-run.py` diffs it and fails the run on divergence. `verdictCard.nextStep` names the same action as `finalVerdict.nextStep` and `recommendedNextSteps[0].text` but is written as the actionable command the reader runs (e.g. `/okstra-run task-key=… task-type=release-handoff`) where the other two are prose — it is deliberately not a byte copy.
|
|
124
124
|
- **Error-analysis diagnosis and routing.** When `header.taskType` is `error-analysis`, populate the required `errorAnalysis` object. Copy `errorAnalysis.symptomVerbatim` byte-for-byte from the symptom stated in the brief's `Source Material`; do not paraphrase it. Every `causeCandidates[]` row includes the full `supportingEvidence`, `falsifyingEvidenceChecked`, `confidence`, and `disproveWith` fields. When a candidate is a step in a propagation chain rather than a competing explanation — the analysis calls it a downstream step, a second stage, or a consequence of another candidate — set its `downstreamOf` to the ids of the candidates immediately upstream of it; leave the field absent for a candidate that stands on its own. Every id listed MUST be another candidate in the same report, no row may name itself, and the links MUST NOT form a cycle; `validators/validate-run.py::_validate_cause_chain` rejects all three. This is the only place the chain is machine-readable — prose calling a candidate "the second step of the chain" while `downstreamOf` is absent leaves the report's figure claiming the candidates are alternatives. Route `errorAnalysis.routing.nextTaskType=implementation-option-selection` with `direction=begin-option-selection`, or route `errorAnalysis.routing.nextTaskType=error-analysis` with `direction=continue-investigation`; no other pairing is valid. `verdictCard.nextStep`, `finalVerdict.nextStep`, the first `recommendedNextSteps` action and command, and the unique `followUpTasks` row whose `origin` is `phase-continuation` MUST all point to the same `errorAnalysis.routing.nextTaskType` target. The schema enforces only the presence of a `phase-continuation` row; `validators/validate-run.py::_validate_error_analysis_consistency` enforces exact target agreement and uniqueness.
|
|
125
125
|
- **Implementation-option-selection comparison.** When `header.taskType` is `implementation-option-selection`, populate `implementationOptionSelection` from the converged direction-selection findings. Preserve every merged or rejected raw candidate in `candidateAudit`, and put at most three selectable candidates in `rankedOptions`. Each displayed candidate carries its requirement coverage, scope commitments, criterion scores, feasibility votes, safety blockers, unresolved feasibility facts, planning invariants, and exact coverage summary. In each displayed candidate, `expectedChangeAreas` names direction-level change surfaces, never exact file paths or an exact file list. `expectedVerification` names direction-level verification signals, never a stage list or executable test commands. `schemas/final-report-v2.0.schema.json` enforces the displayed-summary constants and the three-option cap; semantic recalculation belongs to `validators/validate-run.py`.
|
|
126
126
|
- **Implementation-planning direction branch.** For `planningContract: selected-direction`, read `selectedDirectionRef` and its snapshot, then preserve their core mechanism, architecture boundaries, and planning invariants in `directionRealization`. Materialize files, interfaces, stages, validation, rollback, and bidirectional original-requirement links without candidate generation, scoring, recommendation, or user candidate selection. Author exactly one `P-Dir-1` whose payload is the complete `directionRealization`; its verifier checks those preserved properties and any hidden direction change. If the direction must change, author `direction-invalidated` and no execution queue. Legacy candidate-comparison reruns retain Option Candidates, trade-offs, Recommended Option, and `P-Opt-*` semantics.
|
|
@@ -34,6 +34,7 @@ from okstra_ctl.domain.provider import ( # noqa: E402
|
|
|
34
34
|
UnknownProviderError,
|
|
35
35
|
)
|
|
36
36
|
from okstra_ctl.domain.role import normalize_role, role_for_duty # noqa: E402
|
|
37
|
+
from okstra_ctl.wrapper_status import log_path_for_prompt # noqa: E402
|
|
37
38
|
from okstra_ctl.domain.worker_exec import ( # noqa: E402
|
|
38
39
|
ExecutionStrategy,
|
|
39
40
|
WorkerExecRequest,
|
|
@@ -164,7 +165,7 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
164
165
|
strategy=strategy,
|
|
165
166
|
request=request,
|
|
166
167
|
presentation=presentation,
|
|
167
|
-
log_path=
|
|
168
|
+
log_path=log_path_for_prompt(prompt_path),
|
|
168
169
|
status_path=Path(f"{prompt_path}.status.json"),
|
|
169
170
|
status_extra=status_extra,
|
|
170
171
|
served_model_normalizer=served_model_normalizer,
|
|
@@ -408,12 +409,6 @@ def _check_command(strategy: ExecutionStrategy, request: WorkerExecRequest) -> N
|
|
|
408
409
|
raise PreflightError(127, f"{binary} CLI is not installed on PATH")
|
|
409
410
|
|
|
410
411
|
|
|
411
|
-
def _log_path(prompt_path: Path) -> Path:
|
|
412
|
-
if prompt_path.name.endswith(".md"):
|
|
413
|
-
return prompt_path.with_name(f"{prompt_path.name[:-3]}.log")
|
|
414
|
-
return Path(f"{prompt_path}.log")
|
|
415
|
-
|
|
416
|
-
|
|
417
412
|
def main(argv: list[str]) -> int:
|
|
418
413
|
try:
|
|
419
414
|
invocation = parse_invocation(argv[1:])
|
|
@@ -51,10 +51,11 @@ If no Stage 1 language rule matches (an unlisted language), stop and ask the use
|
|
|
51
51
|
- [ ] `clean-code.md` principles applied: DRY, KISS, SOLID, YAGNI, meaningful naming (truthful + standalone + one identifier one meaning per file), single-purpose functions, plain-English summary test, 50-line cap, no magic numbers, shallow nesting, comments-explain-why.
|
|
52
52
|
- [ ] **Mutation and state boundaries** (`clean-code.md`): decide on the direct identifier, not a status/flag proxy; capture before-state in one snapshot ahead of the mutating boundary; update only this work's owned fields on an existing row; re-read state before calling a zero-affected-rows write success or failure; put priority-between-inputs in a named domain function; keep error messages to what was observed.
|
|
53
53
|
- [ ] Tests planned: which test(s) cover this change. New behaviour without a test is **incomplete** unless the user has explicitly opted out for this change.
|
|
54
|
+
- [ ] **Existing tests surveyed (run this before planning any new test):** for every surface this change touches, `grep -rln` the candidate test files, list their test names, then READ the bodies of the ones that could overlap. A list of names does not tell you what a test asserts, and behavioural overlap does not collide on names — treat a clean name search as unfinished, not as a clear result. Record per surface: `extend <path:line>` / `new (nothing covers <behaviour>)` / `retire <path:line> (behaviour moved to <where>)`. A new test or test file with no survey line is unfinished work. The survey may change the design — moving the seam so existing tests keep passing beats adding parallel ones. Carry out a `retire` verdict in the same commit, on behavioural grounds only: the behaviour it asserted is gone, or a new test asserts it more strongly through the same seam. "Looks similar" / "touches the same file" is not grounds; without grounds the test stays, because a silent coverage loss costs more than one extra test.
|
|
54
55
|
- [ ] **Testing discipline:** the test does not stub/spy methods on the SUT itself (collaborators are fine), and assertions are on outcomes (return values, state, events, boundary calls) — not on which internal helper was called. Each branch this change adds (`catch`, guard, early return, `else`) has a test that fails when the branch body is deleted; assertions land on the last write to a record, not an intermediate one; each test title names its unit and the single condition it isolates; no effect is claimed under its own mock; shared fixtures keep their ordinary defaults; the scenarios' setup values actually differ; every new test helper/mock is used by a test in this same change; no positional mock-argument access (`rg 'mock\.calls'`).
|
|
55
56
|
- [ ] **Third-party wrapper (when this change wraps a library call):** the wrapper adds behaviour the library does not already provide — read the installed library source before keeping a recovery branch — no comment names a condition the call site does not establish, and a rethrow keeps the original error as `cause`.
|
|
56
57
|
- [ ] **Hexagonal overlay (if loaded):** no business logic inside any port body, adapter methods are I/O only (no post-fetch JS filtering on domain state, no `findValid*`/`findActive*` adapter names hiding rules), all domain objects declared under `domain/`, no changed domain file importing outward (ORM / framework / adapters / services), and a service dependency you add or modify goes through a port rather than a concrete adapter (advisory — record it with the port sketch; blocking when the project declares `architecture.style = hexagonal` in `.okstra/project.json` — fix before write, never record-and-pass).
|
|
57
|
-
- [ ] Existing code searched
|
|
58
|
+
- [ ] **Existing code searched — by capability, not by name:** `grep` the identifier you are about to add AND the behaviour it performs (call sites, domain literals, error strings, the shape of the data it returns). A capability that already exists under a different name is the duplication this check exists to catch, and an identifier grep alone never finds it — name-level uniqueness is not evidence of absence. Record per new unit: the search terms used, the nearest existing implementation found, and the verdict `extend <path:line>` / `new (nothing covers <behaviour>)`. No survey line, no new unit.
|
|
58
59
|
- [ ] Project conventions checked: `.editorconfig`, `CONTRIBUTING.md`, formatter config (`.prettierrc`, `rustfmt.toml`, `ktlint`, `google-java-format`, etc.). **Project rules override this resource pack on conflict.**
|
|
59
60
|
|
|
60
61
|
## Completion sweep (before declaring a multi-file change done)
|
|
@@ -257,9 +257,11 @@ Run `okstra agent-prompt verify --run-manifest <path> --metadata
|
|
|
257
257
|
<metadataPath> --json` immediately before dispatch. A failed verification is a
|
|
258
258
|
pre-dispatch contract failure. For `runner=native-session`, pass only the
|
|
259
259
|
returned `hostModelValue` to the host model argument. For
|
|
260
|
-
`runner=cli-wrapper`,
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
`runner=cli-wrapper`, follow the planned execution surface after
|
|
261
|
+
`core-pre-dispatch` verification: invoke `okstra team dispatch` when
|
|
262
|
+
`terminalBackend` is `cmux-pane`, otherwise invoke `okstra worker-dispatch`
|
|
263
|
+
and let it consume the returned `modelExecutionValue`; never pass that
|
|
264
|
+
value as a native-host model token. Before a host-native call, run `okstra agent-prompt record-dispatch`
|
|
263
265
|
with the project root, run manifest, metadata path, and
|
|
264
266
|
`--enforcement-mode host-native-spec-link-gate`. After its Result Path exists,
|
|
265
267
|
run `okstra agent-prompt link-result` with the same run manifest,
|
|
@@ -607,7 +609,8 @@ materialize` with `--audience scope-critic`, `--assignment-ref critic/scope`,
|
|
|
607
609
|
the critic worker ID, and `--dispatch-kind critic`. Verify the returned
|
|
608
610
|
`metadataPath` before dispatch and use its `promptPath` without modification.
|
|
609
611
|
For `runner=native-session`, use only `hostModelValue`; for
|
|
610
|
-
`runner=cli-wrapper`, use `okstra
|
|
612
|
+
`runner=cli-wrapper`, use `okstra team dispatch` when `terminalBackend` is
|
|
613
|
+
`cmux-pane`, otherwise `okstra worker-dispatch`, which consumes
|
|
611
614
|
`modelExecutionValue`. Record host-native linkage with
|
|
612
615
|
`enforcementMode=host-native-spec-link-gate` and the metadata path. If the
|
|
613
616
|
persisted assignment or either model value required by its runner is absent,
|
|
@@ -727,8 +730,10 @@ accepted — surface candidate acceptance BLOCKERS the verifiers may have missed
|
|
|
727
730
|
- regressions or broken error paths,
|
|
728
731
|
- scope / contract violations.
|
|
729
732
|
For each, emit a candidate blocker with a one-line statement, evidence (file:line /
|
|
730
|
-
log / test output), and a severity (critical / major
|
|
731
|
-
|
|
733
|
+
log / test output), and a severity (critical / major). A finding that would not stop the
|
|
734
|
+
release is not a blocker — say so, and it is recorded as a conditional-acceptance
|
|
735
|
+
condition instead. Do NOT restate an existing Acceptance Blocker. If you find none, say
|
|
736
|
+
so explicitly.
|
|
732
737
|
```
|
|
733
738
|
|
|
734
739
|
### Verification — confirm-or-downgrade (BLOCKING)
|
|
@@ -173,7 +173,7 @@ The table below documents those prep-time seed values **for reference only** —
|
|
|
173
173
|
| Codex worker | gpt-5.6-sol | codex-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
|
|
174
174
|
| Antigravity worker | gemini-3.1-pro | antigravity-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
|
|
175
175
|
|
|
176
|
-
Each analysis assignment follows its recorded `runner`. `runner=native-session` uses the host's native subagent primitive after `host-native-spec-link-gate
|
|
176
|
+
Each analysis assignment follows its recorded `runner`. `runner=native-session` uses the host's native subagent primitive after `host-native-spec-link-gate`. `runner=cli-wrapper` follows the planned execution surface after `core-pre-dispatch` verification: `okstra team dispatch` when `terminalBackend` is `cmux-pane`, otherwise the deterministic `okstra worker-dispatch` process boundary. No LLM transport wrapper sits in front of a provider CLI.
|
|
177
177
|
|
|
178
178
|
### Implementation phase: Executor binding
|
|
179
179
|
|
|
@@ -182,7 +182,7 @@ For `--task-type implementation` runs, the task bundle additionally pins one of
|
|
|
182
182
|
- `instruction-set/analysis-profile.md` — top "Executor binding" block (provider, display name, model, runner, and dispatch mode)
|
|
183
183
|
- `runs/implementation/manifests/run-manifest-*.json` — `teamContract.executor` object (the same binding plus `appliesTo: "implementation"`)
|
|
184
184
|
|
|
185
|
-
Lead MUST dispatch Edit/Write-bearing work only through that executor binding: use the host primitive with `hostModelValue` for `runner=native-session`, or `okstra worker-dispatch` with `modelExecutionValue`
|
|
185
|
+
Lead MUST dispatch Edit/Write-bearing work only through that executor binding: use the host primitive with `hostModelValue` for `runner=native-session`. For `runner=cli-wrapper`, use `okstra team dispatch` when `terminalBackend` is `cmux-pane`, or `okstra worker-dispatch` with `modelExecutionValue` otherwise. The other providers in the roster still run as read-only verifiers in the same run; the executor's own provider does not, because its worker ID materializes as the executor on every dispatch — so the diff is reviewed context-isolated by the remaining verifiers. Session isolation is the primary self-review safeguard — a verifier reusing the executor's model variant is acceptable in a distinct session. A different model variant (e.g. executor=opus / Claude verifier=sonnet) is recommended but not mandatory.
|
|
186
186
|
|
|
187
187
|
Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). For CLI-backed executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`), not through the lead runtime's `write_artifact` operation.
|
|
188
188
|
|
|
@@ -216,8 +216,10 @@ the host primitive, run `okstra agent-prompt record-dispatch` with the project
|
|
|
216
216
|
root, run manifest, metadata path, and `--enforcement-mode
|
|
217
217
|
host-native-spec-link-gate`, then run `okstra agent-prompt link-result` with
|
|
218
218
|
`--dispatch-id <invocationId>:attempt-1` and the result path before parsing it;
|
|
219
|
-
CLI-wrapper calls
|
|
220
|
-
`
|
|
219
|
+
CLI-wrapper calls follow the planned execution surface after
|
|
220
|
+
`core-pre-dispatch` verification: `okstra team dispatch` when
|
|
221
|
+
`terminalBackend` is `cmux-pane`, otherwise `okstra worker-dispatch`, and
|
|
222
|
+
consume only `modelExecutionValue`. A missing or invalid invocation contract blocks the
|
|
221
223
|
round before any host or provider process starts.
|
|
222
224
|
|
|
223
225
|
1. Lead runs `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, places the persisted `items[]` verbatim in every verifier prompt with the compact `subject` and lossless `payload`, then runs `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. Dispatch only after that exact-match validation succeeds.
|
|
@@ -34,7 +34,7 @@ Emit `frontmatter.approved` as `false` and `frontmatter.implementationOption` as
|
|
|
34
34
|
2. Write a call-specific task-instructions file containing the anchor headers and audience-specific reading list.
|
|
35
35
|
3. Run `okstra agent-prompt materialize --audience report-writer --assignment-ref initial/report-writer --worker-id report-writer --dispatch-kind report-writer ...`, then run `okstra agent-prompt verify` against the returned `metadataPath`. Use the returned `promptPath` without appending role prose. A correction redispatch repeats this step with a fresh invocation ID and the same audience and assignment reference.
|
|
36
36
|
4. Emit the Phase 6 checkpoint.
|
|
37
|
-
5. For `runner=native-session`, first run `okstra agent-prompt record-dispatch` with the project root, run manifest, metadata path, and `--enforcement-mode host-native-spec-link-gate`, then call the host primitive with only the returned `hostModelValue`. After its result exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and the result path before accepting it. For `runner=cli-wrapper`, call `okstra worker-dispatch --workers report-writer
|
|
37
|
+
5. For `runner=native-session`, first run `okstra agent-prompt record-dispatch` with the project root, run manifest, metadata path, and `--enforcement-mode host-native-spec-link-gate`, then call the host primitive with only the returned `hostModelValue`. After its result exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and the result path before accepting it. For `runner=cli-wrapper`, call `okstra team dispatch` when `terminalBackend` is `cmux-pane`, or `okstra worker-dispatch --workers report-writer` otherwise, which consumes `modelExecutionValue` and verifies the metadata before starting the provider process. Never combine this Phase 6 call with analysis workers.
|
|
38
38
|
6. Call `await_workers([handle])` and verify the data.json Result Path, rendered Markdown sibling, and worker-result pointer at Worker Result Path. Verify the separate heartbeat audit sidecar before accepting the run. **Enforced:** both dispatch adapters keep the three completion paths in `WorkerJob.completion_paths`, and `validators/validate_session_conformance.py` validates the audit sidecar.
|
|
39
39
|
|
|
40
40
|
The complete assignment supplies both runner-specific model values and the prompt header in item 9 below. A native host uses `hostModelValue`; a deterministic provider process uses `modelExecutionValue`; the recorded `**Model:**` header remains the canonical assignment label. Missing or unsupported model resolution is a pre-dispatch contract failure; the common contract does not choose a runtime fallback.
|
|
@@ -108,8 +108,9 @@ use the returned `promptPath` unchanged. A native-session call uses only
|
|
|
108
108
|
manifest, metadata path, and `--enforcement-mode
|
|
109
109
|
host-native-spec-link-gate`, then run `okstra agent-prompt link-result` with
|
|
110
110
|
`--dispatch-id <invocationId>:attempt-1` and the translation result before
|
|
111
|
-
accepting it. A CLI-wrapper call uses `okstra
|
|
112
|
-
`
|
|
111
|
+
accepting it. A CLI-wrapper call uses `okstra team dispatch` when `terminalBackend` is
|
|
112
|
+
`cmux-pane`, or `okstra worker-dispatch` and its `modelExecutionValue`
|
|
113
|
+
otherwise. The host-native record links the accepted result to a
|
|
113
114
|
verified call specification but does not assert that Okstra observed the host's
|
|
114
115
|
actual prompt delivery.
|
|
115
116
|
|
|
@@ -124,7 +125,7 @@ okstra report-finalize \
|
|
|
124
125
|
--report <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md
|
|
125
126
|
```
|
|
126
127
|
|
|
127
|
-
Do NOT run the
|
|
128
|
+
Do NOT run the seven steps below by hand. Hand-running them is the recurring root cause of reports shipping with stale activity, `--` token cells, a missing html sibling, Section 3 missing follow-up entries, or Section 4 rows never spawning — the order is load-bearing and a skipped step surfaces only later, as a validator `contract-violated`. Every step is idempotent, so after fixing a reported failure just re-run the same command.
|
|
128
129
|
|
|
129
130
|
The steps it executes, in this contractual order, and the contract each one carries:
|
|
130
131
|
|
|
@@ -160,6 +161,8 @@ The steps it executes, in this contractual order, and the contract each one carr
|
|
|
160
161
|
The status file is written after routing and follow-up persistence completes.
|
|
161
162
|
6. **`validate-run` — validate the finished run.** Checks the completed artifact set, including exact canonical-event-to-`agentActivity[]` conformance and the report-views contract that catches a missing or stale html sibling. A failure here names the specific contract; fix it and re-run `okstra report-finalize`.
|
|
162
163
|
|
|
164
|
+
7. **`teardown-stages` — reclaim the stage worktrees.** In-process, and a no-op for every run except a whole-task `final-verification` whose verdict clears the work for release (`accepted`, or `conditional-accept` with no condition blocking release). Whole-task entry merges the done stages but deliberately leaves their worktrees and registry stage-keys in place, because a `blocked` verdict routes straight back to rework on those trees. This step is where they are reclaimed once the verdict says the work is moving on. A stage worktree with uncommitted changes is preserved; stage branches are never deleted.
|
|
165
|
+
|
|
163
166
|
After `okstra report-finalize` reports `"ok": true`, **execute the run-scoped cleanup gate.** Call `shutdown_workers` only after that success, all persistence work, and explicit user approval under [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle". If the user keeps resources, leave the selected adapter's resources intact and surface its manual cleanup guidance.
|
|
164
167
|
|
|
165
168
|
## Schema-v2 report data responsibilities
|
|
@@ -355,6 +358,7 @@ Every field MUST anchor its claim with at least one evidence reference — a `pa
|
|
|
355
358
|
1. **Clarification Items** — single unified `C-*` table; column schema (4 columns with the short fields stacked in one record-meta cell), ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (SSOT). The deprecated `5.5.9 Open Questions` / `1.1 Additional Material Request` / `1.2 User Confirmation Questions` sub-sections are removed; the validator fails reports that reintroduce them.
|
|
356
359
|
- **Open `Blocks=approval` rows carry `origin` and `userConfirmation`** (same SSOT). Lead's dispatch prompt MUST state, per intended blocker, which `origin` applies and what Lead did about it — the writer cannot observe either. When Lead instructed the writer to raise an item rather than decide it, that row's `origin` is `lead-directed` no matter how the workers subsequently voted on it: an instruction returning as a consensus is not a finding. Before writing such an instruction, run the confirmation sequence in [okstra-lead-contract](./okstra-lead-contract.md) "User confirmation before an approval blocker" — asking first is usually cheaper than the row.
|
|
357
360
|
2. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
|
|
361
|
+
- **Final-verification added-surface audit.** When `header.taskType` is `final-verification`, populate `finalVerification.addedSurfaceAudit` from the workers' enumeration of what the diff added — one row per identifier / module / configuration entry, each with the callers found across the repository and its disposition. Do not summarise the rows away: this table is the only machine-readable evidence for the over-delivery axis, and requirement coverage answers the opposite question. An `over-delivery` row's `note` MUST cite the `AB-NNN` (no caller) or `CA-NNN` (called, but no requirement) row it became, and that row MUST exist in this report. **Enforced:** `schemas/final-report-v2.0.schema.json` requires the array and rejects an unknown `disposition`; `validators/validate-run.py::_validate_added_surface_audit` enforces the citation and refuses a `traced` row that names no requirement.
|
|
358
362
|
- **Error-analysis diagnosis and routing.** When `header.taskType` is `error-analysis`, populate the required `errorAnalysis` object. Copy `errorAnalysis.symptomVerbatim` byte-for-byte from the symptom stated in the brief's `Source Material`; do not paraphrase it. Every `causeCandidates[]` row includes the full `supportingEvidence`, `falsifyingEvidenceChecked`, `confidence`, and `disproveWith` fields. When a candidate is a step in a propagation chain rather than a competing explanation — the analysis calls it a downstream step, a second stage, or a consequence of another candidate — set its `downstreamOf` to the ids of the candidates immediately upstream of it; leave the field absent for a candidate that stands on its own. Every id listed MUST be another candidate in the same report, no row may name itself, and the links MUST NOT form a cycle; `validators/validate-run.py::_validate_cause_chain` rejects all three. This is the only place the chain is machine-readable — prose calling a candidate "the second step of the chain" while `downstreamOf` is absent leaves the report's figure claiming the candidates are alternatives. Route `errorAnalysis.routing.nextTaskType=implementation-option-selection` with `direction=begin-option-selection`, or route `errorAnalysis.routing.nextTaskType=error-analysis` with `direction=continue-investigation`; no other pairing is valid. `verdictCard.nextStep`, `finalVerdict.nextStep`, the first `recommendedNextSteps` action and command, and the unique `followUpTasks` row whose `origin` is `phase-continuation` MUST all point to the same `errorAnalysis.routing.nextTaskType` target. The schema enforces only the presence of a `phase-continuation` row; `validators/validate-run.py::_validate_error_analysis_consistency` enforces exact target agreement and uniqueness.
|
|
359
363
|
- **Implementation-option-selection comparison.** When `header.taskType` is `implementation-option-selection`, populate `implementationOptionSelection` from the converged direction-selection findings. Preserve every merged or rejected raw candidate in `candidateAudit`, and put at most three selectable candidates in `rankedOptions`. Each displayed candidate carries its requirement coverage, scope commitments, criterion scores, feasibility votes, safety blockers, unresolved feasibility facts, planning invariants, and exact coverage summary. In each displayed candidate, `expectedChangeAreas` names direction-level change surfaces, never exact file paths or an exact file list. `expectedVerification` names direction-level verification signals, never a stage list or executable test commands. `schemas/final-report-v2.0.schema.json` enforces the displayed-summary constants and the three-option cap; semantic recalculation belongs to `validators/validate-run.py`.
|
|
360
364
|
- **Routing.** `implementationOptionSelection.routing` is a required **string enum** — not an object — with exactly three values: `implementation-planning`, `pending-direction-selection`, `blocked`. It is the only field in this report that records where the task goes next, and Phase 7 projects `workflow.nextRecommendedPhase` from it (`scripts/okstra_ctl/next_phase.py`): `implementation-planning` becomes a `ready` pointer naming that phase, while `pending-direction-selection` and `blocked` become `pending` and `blocked` pointers carrying no phase. Only the first proposes a next run.
|
|
@@ -90,7 +90,7 @@ profile document.
|
|
|
90
90
|
- When a response is carried in, reconcile every prior `clarificationItems[]` row against new evidence and update its status to `resolved` or `obsolete` before issuing the next verdict. Schema-v1 compatibility Markdown may additionally render its conditional Section 0; schema-v2 AI Markdown records decisions under `## Clarification and User Decisions`.
|
|
91
91
|
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). **Enforced:** `validators/validate-run.py` `_validate_supersession_ledger` requires an entry per answered clarification; whether the claim is *true* is what the §5.5.9 adversarial round tests.
|
|
92
92
|
- Verdict Card data consistency (shared; schema-v1 Markdown keeps the legacy visible card):
|
|
93
|
-
- `
|
|
93
|
+
- The Card carries no verdict token — the token lives once, in `finalVerdict.verdictToken`, and every gate reads it there. `verdictCard.direction` MUST byte-match `finalVerdict.direction`; next-step routing must agree with `recommendedNextSteps[0]`. The AI handoff and human summary are derived from the data fields without repeating both visible sections. **Enforced:** `validators/validate-run.py` `_validate_verdict_card_fields`.
|
|
94
94
|
- Cross-worker traceability (shared — applies to every analysis worker output and to the lead's `## 6.` / `## 2.` tables in the final-report):
|
|
95
95
|
- **Worker-side item IDs (free-form but unique within the worker).** Every row item in sections 1–5 (and any optional section 6) of an analysis worker's output MUST carry an item ID that is unique within that one worker's result file. The ID convention is the worker's choice — `F-001` / `F-002` per the suggested schema, `1.1` / `1.2` / `1.3` as Codex tends to use, or any other shape — but it MUST appear as the leading column of the row (for table-form items) or as a `[<ID>]` prefix (for bullet/numbered items). Workers that emit findings without IDs make cross-worker reconciliation impossible.
|
|
96
96
|
- **Lead-side ID assignment + source preservation.** When the lead (or `report-writer-worker`) synthesises consensus, difference, or primary-evidence rows from worker outputs, the lead assigns a fresh `C-NNN` / `D-NNN` / `E-NNN` row ID. Each `sourceItems` field MUST list every contributing worker:item pair (e.g. `claude:F-001`, `codex:1.1`, `grok:F-3`, `kimi:2.4`) so an agent can trace the synthesised row to the worker result. Bare worker names are rejected. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.SourceItem` pins each entry to `^[a-z][a-z-]*:[A-Za-z0-9._-]+$`, and `ConsensusRow` / `PrimaryEvidenceRow` require non-empty `sourceItems`.
|
|
@@ -215,7 +215,7 @@ A mocked unit test cannot observe the SQL a query builder actually emits — `co
|
|
|
215
215
|
- **Requirement when fired.** The verifier MUST reproduce a real-DB execution: run the `db-test` tier (Tier 1 = plan `validation` db step; else Tier 2 = `project.json.qaCommands.db-test`) against a **local / replica** datastore (same engine + schema — never shared / staging / prod, consistent with the verifier forbidden-actions list) and record its exact command + exit code. A mock, an in-memory shim that does not parse real SQL, or static reasoning does NOT satisfy this.
|
|
216
216
|
- **No `db-test` command available → blocking, not a passive skip.** If neither tier declares a `db-test` command, the verifier records the blocking finding `db-test not configured — DB change unverified (mock-only)` and sets the verdict to `FAIL`; it MUST NOT emit only the passive `qa-command not configured` note and pass. Recommended fix: declare a `db-test` command in `project.json.qaCommands` or the plan's validation set.
|
|
217
217
|
- **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion `static-analysis only …, unverified (not executed)` (never `verified`), records it as a blocking finding, and sets `FAIL`. Never downplay the real run as "too heavy / static proof suffices".
|
|
218
|
-
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push. **Enforced:** `validators/validate-run.py` `_validate_verifier_fail_blocks_verdict` fails a report whose `
|
|
218
|
+
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push. **Enforced:** `validators/validate-run.py` `_validate_verifier_fail_blocks_verdict` fails a report whose `finalVerdict.verdictToken` is `accepted` / `conditional-accept` while any `implementation.verifierResults[]` row records `verdict: FAIL` — a rejection dropped during synthesis is exactly how rejected work reached `release-handoff`.
|
|
219
219
|
|
|
220
220
|
## All-verifier-failure policy
|
|
221
221
|
|
|
@@ -9,7 +9,7 @@ roles:
|
|
|
9
9
|
duty: acceptance-verifier
|
|
10
10
|
- role: critic
|
|
11
11
|
min: 0
|
|
12
|
-
recommended:
|
|
12
|
+
recommended: 1
|
|
13
13
|
max: 1
|
|
14
14
|
duty: acceptance-critic
|
|
15
15
|
- role: report-writer
|
|
@@ -19,7 +19,7 @@ roles:
|
|
|
19
19
|
duty: report-writer
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- Purpose:
|
|
22
|
+
- Purpose: judge the delivered implementation on three axes before final acceptance — does it cover every requirement (under-delivery), does it carry work no requirement asked for (over-delivery), and does it actually do what it claims (defects, and tests that verify nothing). Whether the run followed okstra's own procedure is not one of the axes: the runtime and `validators/validate-run.py` own that, and a finding about it is not an acceptance judgement
|
|
23
23
|
- Required workers:
|
|
24
24
|
- claude
|
|
25
25
|
- codex
|
|
@@ -32,6 +32,7 @@ roles:
|
|
|
32
32
|
- Primary focus areas (each maps to a deliverable section below):
|
|
33
33
|
- Acceptance-gating — a failure here pushes the verdict toward `blocked` / `conditional-accept`:
|
|
34
34
|
- requirement & acceptance coverage — every must-pass point in the brief's `## Expected Behavior` / `## Preserved Behavior` / `## Expected Outcome` (and the approved plan's requirements) is covered with a cited artifact or raised as an Acceptance Blocker; no silent omissions
|
|
35
|
+
- over-delivery — every surface the merged diff **adds** is traced back to a requirement. Enumerate them: each identifier, module, and configuration entry the diff introduces, searched for its callers across the whole repository (a declaration, its own test, or a commented-out line is not a caller). Record one `addedSurfaceAudit` row per surface with its disposition. `traced` names the brief requirement it serves. `exempt` names one of the legitimate exits and cites it — the approved plan reserves it for a named later stage, or something outside project code calls it (framework entrypoint, implemented interface method, migration hook, published-package API). `over-delivery` is everything else, and it is graded by callers: **no caller anywhere** is an Acceptance Blocker (delete it, or fold an added parameter back into its single call site), while **called but serving no requirement** is a Conditional Acceptance Condition with `blocksReleaseHandoff: false` — the judgement there rests on reading intent, so it travels to the PR body instead of stopping the release. The baseline is the brief, not the approved plan: a surface the plan authorised but no requirement asked for is still over-delivery, and this is the last gate that can see it. **Enforced:** `validators/validate-run.py` `_validate_added_surface_audit` refuses a `traced` row naming no requirement, and an `over-delivery` row whose note does not cite the `AB-NNN` / `CA-NNN` row it became.
|
|
35
36
|
- delivered artifacts match recorded expected values in `reference-expectations` (config files, deployment manifests, other recorded expected states); when reference-expectations are absent, record it as missing information rather than assuming a match
|
|
36
37
|
- test & validation suite pass status — independently re-run the read-only two-tier command set (Tier 1 = brief/approved-plan `validation`, Tier 2 = `project.json` `qaCommands`) and confirm each passes on the verified head, citing exact command + exit code
|
|
37
38
|
- test correctness — delivered tests actually assert the intended behaviour: no gutted/weakened assertions, no tautological or always-passing tests, no tests exercising only mocks; new behaviour has matching coverage. For an `external-interface` / `transformation-mapping` surface specifically, treat a test whose only oracle is a self-authored synthetic fixture (no captured-real-sample provenance) as NOT establishing external correctness — it shows only that the parser agrees with its author's assumed shape, never that the shape matches reality; record that surface's external correctness as a user-owned external advisory gap (a Residual Risk carrying the exact "capture a real sample and confirm" next step, per the Coverage check in the self-review pass below), never as covered — the same non-blocking treatment the External QA advisory policy gives a live external check
|
|
@@ -48,13 +49,13 @@ roles:
|
|
|
48
49
|
- Pre-verification entry gate (resolved & enforced by `okstra render-bundle` prep — the lead does NOT recompute it):
|
|
49
50
|
- the verification target (scope / worktree / base / stages / source reports / diff stat) is injected as the `VERIFICATION_TARGET` block. The lead MUST treat it as authoritative and MUST NOT re-pick a target from the brief.
|
|
50
51
|
- **whole-task scope** (`--stage auto`, default): prep has already verified every Stage Map stage is `status:done` in `consumers.jsonl`, every done stage's `head_commit` is an ancestor of the task worktree HEAD (all stage branches merged), and the worktree is clean outside `.okstra/`. If any check failed the run never started (PrepareError); a started whole-task run is therefore a fully-merged, clean target.
|
|
51
|
-
- **whole-task is a mutating phase, not a read-only one.** On entry, whole-task mode auto-merges (with `--no-ff`) the done stages not yet merged into the task branch to create an integration commit
|
|
52
|
+
- **whole-task is a mutating phase, not a read-only one.** On entry, whole-task mode auto-merges (with `--no-ff`) the done stages not yet merged into the task branch to create an integration commit. The stage worktrees are NOT removed on entry: they are reclaimed after the verdict, by the Phase 7 `teardown-stages` step, and only when the verdict clears the work for release (`accepted`, or `conditional-accept` with no condition blocking release). A `blocked` verdict therefore leaves every stage worktree in place, so the rework it routes to can start immediately. The stage branches are kept as the reviewable stack (the target of `okstra handoff local-checkout --stage <N>`). If a merge conflict occurs it reports the conflicting files and aborts (the user resolves them manually and retries). A stage worktree with uncommitted changes remaining is preserved. Therefore the "fully-merged, clean target" the entry gate above refers to is the state after this auto-integration step completes, and whole-task final-verification must be treated as a mutating phase that creates the integration commit.
|
|
52
53
|
- **single-stage scope** (`--stage N`): prep verified stage N is `status:done` and its isolated stage worktree exists and is clean. Other stages' state is irrelevant. A single-stage run is a partial verification: it MUST NOT recommend plain `release-handoff`, but MAY recommend `release-handoff(stage-group)` when the verdict is `accepted` — the stage becomes PR-eligible for a stage-group handoff.
|
|
53
54
|
- the lead still captures `git status --short` from the injected worktree to confirm the analysis ran against the delivered work-tree state; an unexpected divergence (dirty tree outside `.okstra/`, missing worktree) is a `tool-failure`, not a silent proceed.
|
|
54
55
|
- Worker verification procedure:
|
|
55
|
-
- **Target confirmation:**
|
|
56
|
+
- **Target confirmation:** analyse the injected target and nothing else. Read `verification-target.md` for the stage/report mapping and the complete diff stat. Prepare fixed that target and `validators/validate-run.py` `_validate_verification_target_match` re-checks the report against its digest, so the procedure to follow here is simply: if the worktree you can see does not match the injected target, record a `tool-failure` — never reselect a target.
|
|
56
57
|
- **Evidence:** attach file:line, exact command + exit code, log excerpt, or MCP SELECT evidence to every finding. Mark a requirement as covered only when the cited artifact demonstrates it.
|
|
57
|
-
- **Tier 1 and Tier 2 read-only validation:** Tier 1 is the originating brief/approved plan `validation` set; Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` `qaCommands`. Do not auto-detect commands from package manifests. A missing tier is `qa-command not configured: <category>`. Before execution, reject commands containing source/lockfile mutation tokens such as `--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, or `npm install` without `ci`; record the exact denied token.
|
|
58
|
+
- **Tier 1 and Tier 2 read-only validation:** Tier 1 is the originating brief/approved plan `validation` set; Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` `qaCommands`. Do not auto-detect commands from package manifests. A missing tier is `qa-command not configured: <category>`. Before execution, reject commands containing source/lockfile mutation tokens such as `--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, or `npm install` without `ci`; record the exact denied token. Tier 2 is already screened — prepare refuses to start the run when `project.json` declares such a token (`okstra_ctl.run.validate_project_qa_commands`), so this check is the one that catches a Tier 1 command the brief or plan named.
|
|
58
59
|
- **External QA outcome policy:** continue to attempt every in-scope Tier 3
|
|
59
60
|
command. For an entry requiring `db`, `http`, or `external`, record non-PASS
|
|
60
61
|
as a Tier 3 `advisory` command, add a user-owned Residual Risk and exact
|
|
@@ -76,14 +77,15 @@ roles:
|
|
|
76
77
|
- **Source-mutation prohibition:** verification may write only assigned okstra run artifacts. Do not edit source, schema, deployment, lockfile, or configuration files; route detected defects to a later phase.
|
|
77
78
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
78
79
|
- **Source Implementation Report(s)** (**Enforced:** `validators/validate-run.py` `_validate_verification_target_match` compares `verificationScope`, `worktreePath`, `implementationBaseRef`, `capturedHeadSha`, and the `stageReports` stage set against the digest-verified `instruction-set/verification-target.md`; a snapshot whose digest no longer checks out is ignored rather than trusted. `verificationScope` in particular gates both stage-group eligibility and release-handoff routing, so it is not the report's to restate): the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base/head refs, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). Every analyser prompt carries the same compact target identity (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification head ref:** / **Verification target path:** / **Verification target digest:**`) and reads the sidecar on demand for the complete diff stat. A worker that cannot confirm its analysis ran against that worktree's delivered diff MUST record a `tool-failure`.
|
|
79
|
-
- **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `
|
|
80
|
-
- **
|
|
80
|
+
- **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). `blocksReleaseHandoff` is a gate, not a note: `false` means this condition alone would not stop the release, and a `conditional-accept` whose conditions are all `false` may route to `release-handoff` (the conditions travel into the PR body as unresolved items). Declare `true` for anything that must be settled before release. The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is release-ready by `okstra_ctl.release_gate.release_handoff_allowed`. **Any Acceptance Blocker therefore forces the verdict off `accepted` (to `conditional-accept` or `blocked`); the gates below cite this rule instead of restating the arithmetic.**
|
|
81
|
+
- **Added-surface audit** (`finalVerification.addedSurfaceAudit`): the reverse of requirement coverage — one row per surface the merged diff added (`id` `AS-NNN`, `surface` as `path:line` + the added name, `callers`, `requirement`, `disposition`, `note`). Requirement coverage proves every requirement reached the diff; this table proves every addition answers a requirement. An empty array is a claim that the diff added no identifier, module, or configuration entry, not permission to skip the enumeration. Single-stage scope audits its own stage's diff; whole-task audits the merged diff, which is the only place a surface added by one stage and orphaned by another is visible.
|
|
82
|
+
- **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase: `error-analysis` for a cause problem, `implementation-option-selection` for a direction problem, or `implementation-planning` for a detailed-plan problem. Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
|
|
81
83
|
- **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
|
|
82
84
|
- **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
|
|
83
85
|
- **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `advisory` (external Tier 3 did not PASS; carries observed/expected results and remains user-owned) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token — skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable` or `advisory` according to the external QA policy — never silently dropped and never reported as `executed` with an invented exit code. Mutating-command prohibition is the shared read-only boundary (see Non-goals); it is not restated per row.
|
|
84
86
|
- **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `advisory` / `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
|
|
85
|
-
- **Routing recommendation**: `finalVerification.routingRecommendation` is an **object** with exactly two fields — `target`, one value of the enum below, and `rationale`, the sentence tying that choice to the verdict and the blocker list. Free routing prose is not the field; a target named only in the prose does not route the task, because Phase 7 projects `workflow.nextRecommendedPhase` from `target` alone. The seven allowed targets are `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, and `done`. Both `release-handoff` forms are allowed ONLY when the
|
|
86
|
-
- **Verified-row recording** (single-stage scope only): when the
|
|
87
|
+
- **Routing recommendation**: `finalVerification.routingRecommendation` is an **object** with exactly two fields — `target`, one value of the enum below, and `rationale`, the sentence tying that choice to the verdict and the blocker list. Free routing prose is not the field; a target named only in the prose does not route the task, because Phase 7 projects `workflow.nextRecommendedPhase` from `target` alone. The seven allowed targets are `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, and `done`. Both `release-handoff` forms are allowed ONLY when the verdict is release-ready — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false`. Plain `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a release-ready `single-stage` run routes to `release-handoff(stage-group)` (or `implementation` / `done`) instead. `done` ends the lifecycle here. Enforcement: `schemas/final-report-v2.0.schema.json` rejects a `target` outside the enum, a missing `rationale`, and a string in place of the object; `validators/validate-run.py` rejects a missing `target`, a verdict that is not release-ready routed to either `release-handoff` form (naming the condition ids that block it), and a `single-stage` report whose routing cites plain `release-handoff`.
|
|
88
|
+
- **Verified-row recording** (single-stage scope only): when the verdict is release-ready, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json against the same release-gate rule, so a blocked, condition-blocked, or whole-task report is rejected at the tool layer, and the row records the token the report actually carries. **Enforced:** `validators/validate-run.py` `_validate_verified_row_recorded` requires a `verified` row in `runs/implementation-planning/consumers.jsonl` for every release-ready stage — the helper validated its own inputs but nothing checked it had ever run, leaving reports that said `accepted` while the registry said unverified, so the stage was never offered for a stage-group PR.
|
|
87
89
|
- Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
|
|
88
90
|
- populate `## 1. Clarification Items` only when a blocker hinges on information only the user can supply (deployment intent, intended target environment, business-rule interpretation); use `Blocks=next-phase` for items that gate continuing to release-handoff
|
|
89
91
|
- Self-review pass before finalising the report (the Okstra lead runs this; do not delegate it):
|
|
@@ -74,7 +74,7 @@ roles:
|
|
|
74
74
|
- v1 legacy branch: when validating or rerendering an existing schema-v1 report, preserve its `## 5.9 Improvement Candidates` table and legacy Markdown contract; do not rewrite that historical data into v2 implicitly.
|
|
75
75
|
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 11-column schema from `validators/validate_improvement_report.py` (Cand ID `I-NNN`, Lens from whitelist, Title, Scope ⊆ scan-scope, Severity, Effort, Consensus, Source workers `<worker>:<id>` from {claude, codex, antigravity}, Recommended next-phase ∈ {requirements-discovery, implementation-option-selection, error-analysis}, Expected behavior after, Evidence as path:line list). `Expected behavior after` states, in one observable sentence, what becomes different once the candidate is applied — it is the seed of the downstream brief's `EB-NNN` / `EO-NNN`. A candidate you cannot write this cell for is a preference, not a finding: drop it rather than filling the cell with a restatement of the title.
|
|
76
76
|
- `Consensus` cells in `## 5.9 Improvement Candidates` use the table enum exactly: `full`, `partial`, `contested`, `worker-unique`. Map convergence's `full-consensus` / `partial-consensus` labels to `full` / `partial` before writing the table.
|
|
77
|
-
- Verdict Token — **branch-specific, and the two branches do not share a vocabulary.** On the current v2 branch use the shared analysis enum: `analysis-complete` when every resolved lens was examined, `analysis-partial` when one could not be, `blocked` when the scan itself could not run. `schemas/final-report-v2.0.schema.json` admits only those three for `
|
|
77
|
+
- Verdict Token — **branch-specific, and the two branches do not share a vocabulary.** On the current v2 branch use the shared analysis enum: `analysis-complete` when every resolved lens was examined, `analysis-partial` when one could not be, `blocked` when the scan itself could not run. `schemas/final-report-v2.0.schema.json` admits only those three for `finalVerdict.verdictToken` — the report's single verdict-token home — so a v2 report carrying `candidates-ready` fails Phase 7. **Finding no candidates is not a verdict**: it is an empty `candidates[]` plus a `lensCoverage[]` row per lens with `status: no-candidate` and its evidence-backed rationale — the verdict stays `analysis-complete`. `candidates-ready` / `no-candidates` belong to the v1 legacy `## 7. Final Verdict` Markdown alone, where `validators/validate_improvement_report.py` enforces them. Both branches: Direction `routing`; Next Step "ask the user to select K candidates (see the ## 5.9 table)".
|
|
78
78
|
- `## 3. Recommended Next Steps` first entry summarises per-candidate routing and proposes new task-key names of the form `<task-group>/imp-<Cand-ID>`
|
|
79
79
|
- author the shared schema-v2 report fields plus `improvementDiscovery.candidates[]`, `improvementDiscovery.lensCoverage[]`, `improvementDiscovery.selectionLimit`, and `improvementDiscovery.userNarrative` in data.json. `candidates[]` carries the same 11 logical fields described above; `lensCoverage[]` records either candidate IDs or an evidence-backed no-candidate rationale for every resolved lens. `schemas/final-report-v2.0.schema.json` and `validators/validate_improvement_report.py` enforce this contract. The renderers independently derive AI handoff Markdown and human HTML; never author a free-form report.
|
|
80
80
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
@@ -19,9 +19,10 @@ roles: []
|
|
|
19
19
|
- The shared "MCP read-only" rule still applies if the brief lists MCP servers, though most release-handoff runs do not use MCP.
|
|
20
20
|
- Pre-handoff entry gate (mandatory — refuse to start if any item fails):
|
|
21
21
|
- the run's input document (`release-handoff-input.md`, generated by prepare in place of a task brief — briefs belong to entry phases only) carries a `## Source Verification Report` section with `Mode`, `Stages`, and one table row per cited `final-verification` final-report. The run context exposes the same selection as `HANDOFF_MODE` (`whole-task` | `stage-group`) and `HANDOFF_STAGES` (csv, empty for whole-task).
|
|
22
|
-
- **whole-task mode** (`HANDOFF_MODE=whole-task`): the lead opens the cited report and confirms its `Verdict Token`
|
|
23
|
-
- **stage-group mode** (`HANDOFF_MODE=stage-group`): the lead opens each cited single-stage report and confirms every
|
|
24
|
-
- if the verdict is `conditional-accept
|
|
22
|
+
- **whole-task mode** (`HANDOFF_MODE=whole-task`): the lead opens the cited report and confirms its `verificationScope` is `whole-task` and its verdict is release-ready — `Verdict Token` exactly `accepted`, or exactly `conditional-accept` with every **Conditional Acceptance Condition** row declaring `blocksReleaseHandoff: false`. The rule lives in `okstra_ctl.release_gate.release_handoff_allowed`; the lead reads the report against it and does not invent a third case.
|
|
23
|
+
- **stage-group mode** (`HANDOFF_MODE=stage-group`): the lead opens each cited single-stage report and confirms every verdict is release-ready by the same rule as whole-task mode. Eligibility was already enforced at prepare time (`okstra_ctl.handoff.compute_eligibility`) and is re-enforced by `okstra handoff assemble` — the lead never hand-computes it.
|
|
24
|
+
- if the verdict is `blocked`, a `conditional-accept` carrying any condition that blocks release, or any other token (including ambiguous phrasing like "looks good"), the run MUST end immediately with status `blocked` and a routing recommendation back to `error-analysis` or `implementation-planning`. Do NOT prompt the user; Do NOT run any git command.
|
|
25
|
+
- when the cited verdict is `conditional-accept`, the lead MUST carry every **Conditional Acceptance Condition** row into the PR body under a heading naming them as unresolved — id, condition, and the evidence the reviewer would need. These conditions are the whole reason a non-`accepted` verdict was allowed through; a PR body that drops them turns a recorded condition into a silent one.
|
|
25
26
|
- the lead MUST capture `git status --short` and confirm the working tree is clean. Dirty state aborts the run; release-handoff packages the commits produced by `implementation`, it does not stage or commit changes.
|
|
26
27
|
- the lead MUST capture `git rev-parse --abbrev-ref HEAD` and record it as the **feature branch**. If the current branch is itself `main`, `master`, `prod`, `preprod`, `staging`, or `dev`, the run MUST end immediately — release-handoff never operates on a base branch.
|
|
27
28
|
- the lead MUST confirm `git log --oneline <base>..HEAD` contains at least one implementation commit. If it is empty, the run MUST end with status `blocked` and route back to `implementation`.
|
|
@@ -73,7 +74,7 @@ roles: []
|
|
|
73
74
|
- Forbidden actions (any occurrence → terminal status `contract-violated`):
|
|
74
75
|
{{PHASE_FORBIDDEN_ACTIONS}}
|
|
75
76
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
76
|
-
- **Source Verification Report**: relative path of the originating `final-verification` final-report file plus the literal quoted `Verdict Token` row
|
|
77
|
+
- **Source Verification Report**: relative path of the originating `final-verification` final-report file plus the literal quoted `Verdict Token` row, and — when that token is `conditional-accept` — every Conditional Acceptance Condition row quoted with its `blocksReleaseHandoff` value.
|
|
77
78
|
- **Feature Branch & Working-Tree State**: branch name from `git rev-parse --abbrev-ref HEAD`, output of `git status --short` at run start.
|
|
78
79
|
- **User Selections**: a block recording each prompt and the user's verbatim answer.
|
|
79
80
|
- Q1 action: `local checkout` | `push + PR` | `skip`.
|
|
@@ -100,14 +101,14 @@ roles: []
|
|
|
100
101
|
- **Stage Group** (stage-group mode only): selected stages, each stage's single-stage verification report path + quoted `Verdict Token` row, collector branch name, merge commit SHAs from assemble, and the dependency-closure verdict (from the assemble output / error).
|
|
101
102
|
- **Routing recommendation**: explicit `done` token, since release-handoff is the terminal lifecycle phase. If the run ended in `skip` or `cancel`, the recommendation MUST also state whether re-entry into release-handoff is appropriate.
|
|
102
103
|
- Self-review pass before finalising the report (the Okstra lead runs this):
|
|
103
|
-
1. **Entry-gate audit** — section 2 cites the originating final-verification report path and the literal `Verdict Token` row with value
|
|
104
|
+
1. **Entry-gate audit** — section 2 cites the originating final-verification report path and the literal `Verdict Token` row with a release-ready value. If either is missing, or a `conditional-accept` run's PR body omits the conditions, the run is invalid and MUST be re-routed to `final-verification`.
|
|
104
105
|
2. **User-selection traceability** — every executed mutating command maps to a user selection captured in the report. Any mutating command without a corresponding user answer is a contract violation.
|
|
105
106
|
3. **Forbidden-action audit** — scan the run's session transcripts (`git`, `gh` invocations) for every entry in the Forbidden actions list above. Any occurrence means the run has crossed into unsafe territory and MUST be flagged as `contract-violated`.
|
|
106
107
|
4. **Push-target audit** — for every `git push` recorded, confirm the refspec resolves to the feature branch, not the base branch.
|
|
107
108
|
5. **Idempotency check** — if a PR with the same head already existed at run start, confirm the report records `PR reused` rather than a fresh `gh pr create` invocation.
|
|
108
109
|
6. **Merge-conflict probe audit** — for any `push + PR` run, confirm the report's `Merge Conflict Probe` section is present and either records `Clean` or records `Conflicts detected` with the user's verbatim choice. A missing or unparseable probe entry on a `push + PR` run is a contract violation.
|
|
109
110
|
- Non-goals:
|
|
110
|
-
- re-litigating the final-verification verdict — release-handoff trusts the cited
|
|
111
|
+
- re-litigating the final-verification verdict — release-handoff trusts the cited release-ready verdict and does not reopen acceptance checks. Carrying a conditional-accept's conditions into the PR body is transcription, not verification: the lead does not check whether a condition has been satisfied.
|
|
111
112
|
- creating, amending, squashing, or rewriting commits. Commit production belongs to `implementation`.
|
|
112
113
|
- opening additional PRs, releases, or deployments beyond the single PR the user chose to create.
|
|
113
114
|
- merging the PR. Merging is a separate, manual step performed by the user (or by repo automation) after release-handoff ends; the lead MUST NOT call `gh pr merge`.
|
|
@@ -497,11 +497,11 @@
|
|
|
497
497
|
}
|
|
498
498
|
},
|
|
499
499
|
"role_add": {
|
|
500
|
-
"label": "선택 역할 {role} 을(를) 이번 run 에 추가할까요? (최대 {maximum}
|
|
500
|
+
"label": "선택 역할 {role} 을(를) 이번 run 에 추가할까요? (최대 {maximum}개)",
|
|
501
501
|
"echo_template": "role-add: {value}",
|
|
502
502
|
"options": {
|
|
503
503
|
"skip": "추가 안 함{default_suffix}",
|
|
504
|
-
"add": "{count}개 추가",
|
|
504
|
+
"add": "{count}개 추가{default_suffix}",
|
|
505
505
|
"default_suffix": " (기본)"
|
|
506
506
|
}
|
|
507
507
|
},
|
|
@@ -43,7 +43,7 @@ from .assignment_resolver import AssignmentContext, resolve_dispatch_assignment
|
|
|
43
43
|
from .path_hints import hydrate_active_run_context
|
|
44
44
|
from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
|
|
45
45
|
from .worker_artifact_paths import audit_sidecar_rel
|
|
46
|
-
from .wrapper_status import status_path_for_prompt
|
|
46
|
+
from .wrapper_status import log_path_for_prompt, status_path_for_prompt
|
|
47
47
|
from .worker_prompt_policy import resolve_prompt_plan_for_manifest
|
|
48
48
|
from .dispatch_state import (
|
|
49
49
|
BACKEND_CLI_WRAPPER,
|
|
@@ -477,7 +477,7 @@ def _dynamic_verifier_artifact_paths(
|
|
|
477
477
|
audit_source_path,
|
|
478
478
|
project_root / audit_sidecar_rel(audit_rel),
|
|
479
479
|
status_path_for_prompt(prompt_path),
|
|
480
|
-
|
|
480
|
+
log_path_for_prompt(prompt_path),
|
|
481
481
|
}
|
|
482
482
|
error_logs = active_context.get("errorLogs")
|
|
483
483
|
sidecars = error_logs.get("sidecarsByWorkerId") if isinstance(error_logs, Mapping) else None
|