okstra 0.202.0 → 0.205.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/README.md +7 -6
- package/dist/cli-registry.mjs +7 -7
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +50 -124
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +41 -8
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/install-assets.mjs +3 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/runtime-manifest.mjs +2 -1
- package/dist/lib/runtime-manifest.mjs.map +1 -1
- package/dist/lib/types.d.mts +2 -1
- package/docs/architecture/storage-model.md +14 -11
- package/docs/architecture.md +26 -20
- package/docs/cli.md +15 -12
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +3 -9
- package/docs/project-structure-overview.md +39 -11
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/final-verification.md +3 -1
- package/docs/task-process/implementation-option-selection.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +36 -39
- package/package.json +1 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/common.json +28 -0
- package/runtime/agents/operations/code-review.json +6 -0
- package/runtime/agents/operations/report-translation.json +6 -0
- package/runtime/agents/operations/schedule-verification.json +6 -0
- package/runtime/agents/roles/analyser.json +18 -0
- package/runtime/agents/roles/critic.json +18 -0
- package/runtime/agents/roles/designer.json +18 -0
- package/runtime/agents/roles/implementer.json +20 -0
- package/runtime/agents/roles/leader.json +20 -0
- package/runtime/agents/roles/planner.json +18 -0
- package/runtime/agents/roles/report-writer.json +19 -0
- package/runtime/agents/roles/translator.json +19 -0
- package/runtime/agents/roles/verifier.json +18 -0
- package/runtime/bin/lib/okstra/usage.sh +5 -5
- package/runtime/prompts/duties/acceptance-critic.json +32 -0
- package/runtime/prompts/duties/acceptance-verifier.json +32 -0
- package/runtime/prompts/duties/analysis-worker.json +32 -0
- package/runtime/prompts/duties/code-reviewer.json +32 -0
- package/runtime/prompts/duties/diagnosis-worker.json +32 -0
- package/runtime/prompts/duties/direction-selection-worker.json +32 -0
- package/runtime/prompts/duties/discovery-worker.json +32 -0
- package/runtime/prompts/duties/implementation-executor.json +32 -0
- package/runtime/prompts/duties/implementation-verifier.json +32 -0
- package/runtime/prompts/duties/lead.json +32 -0
- package/runtime/prompts/duties/planning-worker.json +36 -0
- package/runtime/prompts/duties/report-writer.json +32 -0
- package/runtime/prompts/duties/reverification-worker.json +32 -0
- package/runtime/prompts/duties/schedule-verifier.json +32 -0
- package/runtime/prompts/duties/scope-critic.json +32 -0
- package/runtime/prompts/duties/technical-verification-worker.json +32 -0
- package/runtime/prompts/duties/translator.json +32 -0
- package/runtime/prompts/launch.template.md +2 -1
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/convergence.md +4 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +115 -6
- package/runtime/prompts/lead/plan-body-verification.md +6 -6
- package/runtime/prompts/lead/report-writer.md +3 -3
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +4 -1
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
- package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
- package/runtime/prompts/profiles/error-analysis.json +39 -0
- package/runtime/prompts/profiles/error-analysis.md +0 -25
- package/runtime/prompts/profiles/feature-analysis.json +31 -0
- package/runtime/prompts/profiles/feature-analysis.md +0 -20
- package/runtime/prompts/profiles/final-verification.json +30 -0
- package/runtime/prompts/profiles/final-verification.md +4 -23
- package/runtime/prompts/profiles/forbidden-actions.json +4 -3
- package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
- package/runtime/prompts/profiles/implementation-planning.json +40 -0
- package/runtime/prompts/profiles/implementation-planning.md +4 -29
- package/runtime/prompts/profiles/implementation.json +30 -0
- package/runtime/prompts/profiles/implementation.md +1 -20
- package/runtime/prompts/profiles/improvement-discovery.json +31 -0
- package/runtime/prompts/profiles/improvement-discovery.md +0 -20
- package/runtime/prompts/profiles/project-analysis.json +31 -0
- package/runtime/prompts/profiles/project-analysis.md +0 -20
- package/runtime/prompts/profiles/release-handoff.json +5 -0
- package/runtime/prompts/profiles/release-handoff.md +74 -74
- package/runtime/prompts/profiles/requirements-discovery.json +39 -0
- package/runtime/prompts/profiles/requirements-discovery.md +0 -25
- package/runtime/prompts/profiles/technical-verification.json +39 -0
- package/runtime/prompts/profiles/technical-verification.md +0 -25
- package/runtime/prompts/wizard/prompts.ko.json +14 -18
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
- package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
- package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
- package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
- package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
- package/runtime/python/okstra_ctl/cmux.py +69 -20
- package/runtime/python/okstra_ctl/code_review_target.py +16 -8
- package/runtime/python/okstra_ctl/conformance.py +43 -0
- package/runtime/python/okstra_ctl/consumers.py +23 -8
- package/runtime/python/okstra_ctl/container.py +31 -8
- package/runtime/python/okstra_ctl/context_cost.py +11 -15
- package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +38 -0
- package/runtime/python/okstra_ctl/convergence_provenance.py +7 -1
- package/runtime/python/okstra_ctl/design_prep.py +34 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
- package/runtime/python/okstra_ctl/domain/host.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
- package/runtime/python/okstra_ctl/error_report.py +4 -3
- package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
- package/runtime/python/okstra_ctl/handoff.py +384 -286
- package/runtime/python/okstra_ctl/handoff_verification.py +25 -6
- package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
- package/runtime/python/okstra_ctl/lead_progress.py +1 -1
- package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -4
- package/runtime/python/okstra_ctl/manager_launch.py +1 -1
- package/runtime/python/okstra_ctl/manager_paths.py +14 -3
- package/runtime/python/okstra_ctl/manager_store.py +210 -3
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/manager_view.py +2 -1
- package/runtime/python/okstra_ctl/model_discovery.py +30 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
- package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
- package/runtime/python/okstra_ctl/models.py +1 -1
- package/runtime/python/okstra_ctl/next_phase.py +16 -6
- package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
- package/runtime/python/okstra_ctl/option_comparison.py +168 -0
- package/runtime/python/okstra_ctl/path_hints.py +9 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/profile_show.py +42 -1
- package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
- package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
- package/runtime/python/okstra_ctl/render.py +79 -0
- package/runtime/python/okstra_ctl/report_contract.py +1 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
- package/runtime/python/okstra_ctl/report_translation.py +2 -1
- package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
- package/runtime/python/okstra_ctl/role_requirements.py +142 -129
- package/runtime/python/okstra_ctl/rollup.py +3 -1
- package/runtime/python/okstra_ctl/run.py +76 -29
- package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
- package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
- package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
- package/runtime/python/okstra_ctl/stage_map.py +16 -2
- package/runtime/python/okstra_ctl/stage_targets.py +209 -43
- package/runtime/python/okstra_ctl/team.py +22 -13
- package/runtime/python/okstra_ctl/time_report.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +3 -1
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
- package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
- package/runtime/python/okstra_ctl/wizard/state.py +3 -5
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +12 -21
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
- package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
- package/runtime/python/okstra_token_usage/pricing.py +6 -4
- package/runtime/schemas/agent-common-v1.schema.json +34 -0
- package/runtime/schemas/agent-duty-v1.schema.json +38 -0
- package/runtime/schemas/agent-operation-v1.schema.json +11 -0
- package/runtime/schemas/agent-profile-v1.schema.json +46 -0
- package/runtime/schemas/agent-role-v1.schema.json +29 -0
- package/runtime/schemas/final-report-v2.0.schema.json +118 -97
- package/runtime/schemas/final-report-v3.0.schema.json +118 -97
- package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
- package/runtime/skills/okstra-chat/SKILL.md +2 -2
- package/runtime/skills/okstra-code-review/SKILL.md +23 -9
- package/runtime/skills/okstra-container-build/SKILL.md +10 -10
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
- package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
- package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
- package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +4 -3
- package/runtime/skills/okstra-inspect/facets/time.md +11 -10
- package/runtime/skills/okstra-manager/SKILL.md +18 -2
- package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
- package/runtime/skills/okstra-rollup/SKILL.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +31 -12
- package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
- package/runtime/skills/okstra-setup/SKILL.md +12 -10
- package/runtime/skills/okstra-setup/references/project-config.md +7 -6
- package/runtime/skills/okstra-usage/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +1 -1
- package/runtime/templates/manager/view.template.html +1 -0
- package/runtime/templates/report-writer-prompt-preamble.md +8 -0
- package/runtime/templates/reports/brief.template.md +14 -4
- package/runtime/templates/reports/html/i18n/en.json +5 -4
- package/runtime/templates/reports/html/i18n/ko.json +5 -4
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +6 -4
- package/runtime/templates/translator-prompt-preamble.md +36 -0
- package/runtime/validators/checks/validate-assets-01.py +7 -8
- package/runtime/validators/validate-brief.py +70 -0
- package/runtime/validators/validate-implementation-plan-stages.py +2 -1
- package/runtime/validators/validate-run.py +72 -15
- package/runtime/validators/validate-schedule.py +9 -0
- package/docs/for-ai/README.md +0 -68
- package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
- package/docs/for-ai/skills/okstra-chat.md +0 -34
- package/docs/for-ai/skills/okstra-code-review.md +0 -57
- package/docs/for-ai/skills/okstra-container-build.md +0 -129
- package/docs/for-ai/skills/okstra-inspect.md +0 -262
- package/docs/for-ai/skills/okstra-manager.md +0 -86
- package/docs/for-ai/skills/okstra-memory.md +0 -126
- package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
- package/docs/for-ai/skills/okstra-rollup.md +0 -114
- package/docs/for-ai/skills/okstra-run.md +0 -250
- package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
- package/docs/for-ai/skills/okstra-setup.md +0 -167
- package/docs/for-ai/skills/okstra-usage.md +0 -29
- package/docs/for-ai/skills/okstra-user-response.md +0 -72
- package/runtime/agents/workers/claude-worker.md +0 -128
- package/runtime/agents/workers/report-writer-worker.md +0 -37
- package/runtime/agents/workers/translator-worker.md +0 -63
- package/runtime/prompts/duties/acceptance-critic.md +0 -44
- package/runtime/prompts/duties/acceptance-verifier.md +0 -44
- package/runtime/prompts/duties/analysis-worker.md +0 -44
- package/runtime/prompts/duties/code-reviewer.md +0 -44
- package/runtime/prompts/duties/common.md +0 -39
- package/runtime/prompts/duties/diagnosis-worker.md +0 -44
- package/runtime/prompts/duties/direction-selection-worker.md +0 -44
- package/runtime/prompts/duties/discovery-worker.md +0 -44
- package/runtime/prompts/duties/implementation-executor.md +0 -44
- package/runtime/prompts/duties/implementation-verifier.md +0 -44
- package/runtime/prompts/duties/lead.md +0 -44
- package/runtime/prompts/duties/planning-worker.md +0 -52
- package/runtime/prompts/duties/report-writer.md +0 -44
- package/runtime/prompts/duties/reverification-worker.md +0 -44
- package/runtime/prompts/duties/schedule-verifier.md +0 -44
- package/runtime/prompts/duties/scope-critic.md +0 -44
- package/runtime/prompts/duties/technical-verification-worker.md +0 -44
- package/runtime/prompts/duties/translator.md +0 -44
- package/runtime/python/okstra_ctl/pane_title.py +0 -154
|
@@ -405,7 +405,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
405
405
|
- `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE`. On a blocking kind (`b` / `c` / `e`, and kind `a` on `P-Var-*`) this is scored `majority-disagree` and **blocks approval**. Advisory-only `DISAGREE(d)` and `P-Rb-*` stay recorded dissent and do not block. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs.)
|
|
406
406
|
- `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes; rollback-ordering `DISAGREE(d)` votes are advisory and excluded from the tally), OR any blocking-kind dissent with ≥2 participating votes (a minority `DISAGREE` is not outvoted), OR an unresolved single-vote-blocking kind fires: one reproduced `DISAGREE(a)` on any item other than a `P-Var-*` one, or one reproduced `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks approval**. A valid critic correction is scored before these blocking rules, whether or not the analysers split evenly.
|
|
407
407
|
- `needs-reverify` — one of two shapes the round could not settle.
|
|
408
|
-
- **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote
|
|
408
|
+
- **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote ...`, then `okstra plan-items prompt ...`). The prompt carries the analyser split and no other plan items. Read the answer with `okstra plan-items collect-verdicts --items <the --tie-vote plan-items artifact> --result critic-worker=<path> --output <envelope>` — `--items` takes that artifact, whose `dispatchQueue` is the tie items, so pointing the next step at the raw result is refused against this round's full queue. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --state <plan-body-verification.json> --round <N> --append --items <the --tie-vote plan-items artifact> --result critic-worker=<path>` — `--items` persists the exact partial `dispatchQueue` used by verdict validation and `complete-round`. Earlier verdicts and completed-round history outside that queue remain unchanged. If a previous version saved the critic votes but left the full queue in state, the ordinary `okstra plan-items complete-round --state <state> --run-manifest <manifest> --round <N>` automatically reads this run's canonical prepared queue. It uses that queue when all votes recorded for this round are included, preserving a wider recorded batch when a later prepared queue excludes its votes. An explicit `--items <prepared artifact>` remains available for selecting an artifact. Enforced by `plan_items_cli._round_inputs` and `tests/run/test_plan_items.py` completion coverage. Do not fabricate new-round votes for already agreed items. Without it the result is checked against the whole persisted round queue and refused for every item the critic was never given (measured 2026-09-10: a 7-item tie round refused against 44 items), and the only way through was `--verdicts`, which the CLI's own help calls a historical envelope. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`, including an earlier `DISAGREE(a)` or `DISAGREE(f)` on `P-Req-*`. This decision corrects the disputed judgement before single-vote blocking is evaluated; it does not delete the original dissent. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` fails an in-scope item that carries an even split on a blocking kind and has neither a `critic-worker` vote nor a `blocks: approval` clarification row, `_classify_plan_item_gate` scores the tie shape and fails a settled classification the votes do not support, and `okstra_ctl.plan_items.next_dispatch` returns kind `critic-tie` for exactly these items, so the tie round is the queue the CLI hands you rather than one you assemble. **With no critic on the roster the split is a user decision, not another round.** `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>` answers `user-decision` (not `critic-tie`) when the run's `invocationAssignments` carries no `critic/*` entry, and its `itemIds` are the tie items. For each of them do what step 8 does for a surviving `majority-disagree`: `okstra approval-decision open` with `approvalContext.classification` set to `correctness-critical` when `_is_correctness_critical` is true, or `noncritical-dissent` otherwise, plus the matching `## 1. Clarification Items` row at `Blocks=approval`. Dispatch no further verification for those items. The `Blocks=approval` row is what withholds approval until the user disposes, exactly as for any other approval row; the gate retains unresolved single-vote blockers as `majority-disagree` and folds other `needs-reverify` items into `passed-with-dissent`, so the round closes on the gate it actually scored. A tie left with neither a critic vote nor a decision row surfaces as recorded dissent plus an `advisories[]` entry, not a round-blocking failure. **Enforced:** `okstra_ctl.plan_items.critic_is_rostered` reads the roster and `next_dispatch` returns the kind; `validators/validate-run.py` `_validate_unresolved_tie_was_reverified` reads a `blocks: approval` row linked to the item as the settlement and emits its advisory only when neither settlement is recorded.
|
|
409
409
|
- **A lone dissent nobody cross-verified** — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not, and on a `P-Var-*` item none fires at all — its kind `a` never blocks on one vote and takes a majority like `b` / `e`. This does **not** block approval — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` returns `needs-reverify` for this shape and `_recompute_plan_body_gate` folds it into `passed-with-dissent`.
|
|
410
410
|
- `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
|
|
411
411
|
5. Gate result resolution:
|
|
@@ -429,7 +429,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
429
429
|
**Record the cause, not just the outcome.** The gate value names the outcome; `planBodyVerification.gateBlockedBy` (array) names every input that blocked it — `majority-disagree`, `coverage-gap`, `non-result`. Two independent inputs can block: a `majority-disagree` plan item, and a Requirement Coverage `gap` / `blocked C-NNN` row (`prompts/profiles/implementation-planning.md` §"Requirement Coverage"). A coverage-only block still renders as `blocked-by-disagreement` because that is the only blocking non-abort value, so **without `gateBlockedBy` the report asserts a worker disagreement that never happened** and the reader hunts for a dissent that does not exist. Leave the array empty for a passing gate. **Enforced:** `validators/validate-run.py` `_validate_gate_blocked_by` fails a passing gate that has a blocking coverage row — the coverage rule was prose-only before. The declared array itself is not compared against a recomputed one: `okstra plan-verify` returns `gate.blockedBy` from the same computation that produced the gate value, so recording what it returns is what makes the array right.
|
|
430
430
|
|
|
431
431
|
**A coverage row citing this run's own `C-NNN` is not an independent blocker.** When a coverage row's `blocked C-NNN` points at a clarification that step 8 below promoted from a `majority-disagree` item in *this same run*, that blocker is already counted once as the plan item. Counting it again as a coverage gap makes the run block on a clarification it just authored, and the row carries into the next run as a fresh blocker — the Requirement Coverage ↔ Clarification cycle. Such rows are excluded from `coverage-gap`. **Enforced:** `validators/validate-run.py` `_independent_coverage_blockers`.
|
|
432
|
-
6. `okstra plan-items complete-round --run-manifest <current-run-manifest.json>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `validators/validate-run.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `validators/validate-run.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
|
|
432
|
+
6. `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `validators/validate-run.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `validators/validate-run.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
|
|
433
433
|
|
|
434
434
|
**Check each verifier's verdict distribution before the next round.** After `apply-verdicts` and before opening another worker batch, run `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>`. Python owns that decision. Do not invent a full-roster round from a `needs-reverify` label, from every-item `UNVERIFIABLE`, or from `okstra plan-verify` warnings. `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
|
|
435
435
|
|
|
@@ -446,7 +446,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
446
446
|
|
|
447
447
|
The environment exception in §"Planning-time environment gap" covers **running build and test commands only** — whether a referenced path exists, whether a command is declared in `package.json`, and whether the plan is internally consistent are all checkable without it, and a blanket "capability constraints prevent workspace resolution" is not a valid answer to any of them.
|
|
448
448
|
|
|
449
|
-
**How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
|
|
449
|
+
**How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --project-root <dir> --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
|
|
450
450
|
|
|
451
451
|
Then run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python appends one immutable round history entry, records each verified item's votes, derives the current projection from the actual assigned roster, and stamps `completedAt` after the preceding verification command succeeds. The file accumulates across rounds; it is never truncated to the latest one. Report assembly later projects the completed nested `planBodyVerification` into the final record.
|
|
452
452
|
7. **Self-fix loop (one rewrite, targeting planner-fixable defects).** After the initial verification, lead may run one report-writer rewrite when a `majority-disagree` item has a majority of `DISAGREE` verdicts at `fixability == planner-fixable`. Re-verify changed items once, preserving verdicts on unchanged content. Then stop automatic self-fix regardless of outcome and follow step 8. The fixed order is initial verification → one planner self-fix → targeted re-verification → lead decision or immediate user confirmation. A second automatic self-fix is rejected by `plan_items_cli._record_self_fixes`; `_validate_self_fix_grouping` and session activity validation detect multiple recorded rewrites. No rewrite is needed when no item qualifies.
|
|
@@ -464,7 +464,7 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
464
464
|
- `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
|
|
465
465
|
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `validators/validate-run.py` `_detect_self_fix_recurrence` warns on that shape and names this stop reason.
|
|
466
466
|
- `max-rounds-reached` — the single automatic rewrite has been used. Exit to step 8. Count distinct `selfFixGroups[].round` values; `selfFixRoundsApplied` is the last verification round number, not the rewrite count. Extra verification batches before the rewrite do not increase the self-fix budget.
|
|
467
|
-
- `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run cannot emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced at the only writer:** `okstra plan-items complete-round --self-fix-stop-reason` accepts `all-resolved` / `no-progress` / `max-rounds-reached` and nothing else (`scripts/okstra_ctl/plan_items_cli.py:261`), and it is what sets `selfFixStopReason`, so the value has no way into a new report. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
|
|
467
|
+
- `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run cannot emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced at the only writer:** `okstra plan-items complete-round ... --self-fix-stop-reason` accepts `all-resolved` / `no-progress` / `max-rounds-reached` and nothing else (`scripts/okstra_ctl/plan_items_cli.py:261`), and it is what sets `selfFixStopReason`, so the value has no way into a new report. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
|
|
468
468
|
- `not-attempted` — the loop never ran because no item qualified.
|
|
469
469
|
The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
|
|
470
470
|
- a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced (promotion path):** `validators/validate-run.py` `_validate_self_fix_before_clarification` demands an exhausted self-fix budget only of `planner-fixable` majorities, so a `needs-user-input` majority is promotable with no self-fix round, and `_validate_plan_body_clarification_matching` fails it when it reaches no `blocks: approval` row. The classification *value* is authoring guidance per step 8: `scripts/okstra_ctl/approval_decisions.py` checks it against the classification enum and its allowed dispositions only — no validator recomputes it from the votes' `fixability`.
|
|
@@ -673,7 +673,7 @@ posture in §"Adversarial plan-body posture" still applies, and this round does
|
|
|
673
673
|
not revisit the requirements themselves.
|
|
674
674
|
|
|
675
675
|
Omitting either line fails `okstra team dispatch --dispatch-kind
|
|
676
|
-
plan-verify-r<N
|
|
676
|
+
plan-verify-r<N> ...` before any process starts, reported as `<task-type>
|
|
677
677
|
prompt contract: <worker>: exactly one Primary analysis packet path is required
|
|
678
678
|
(found 0)`. Fix the instructions file and re-materialize with
|
|
679
679
|
`--replace-undispatched` rather than editing the published prompt.
|
|
@@ -852,7 +852,7 @@ What the generated round 2+ prompt has that round 1 does not:
|
|
|
852
852
|
|
|
853
853
|
An item with no recorded vote carrying a round number gets no block, and an envelope with nothing to carry keeps the round-1 shape — `priorRounds` is absent and no preamble is prepended, so a first round is unaffected by this section.
|
|
854
854
|
|
|
855
|
-
**Enforced:** `okstra plan-items validate-prepared --state <same state>` re-derives the carry and exits 2 when the prepared envelope's `priorRounds` does not match, alongside the `items` / `dispatchQueue` comparison it already made. A prepared queue that dropped the dissent cannot pass the step-1 validation the dispatch is gated on.
|
|
855
|
+
**Enforced:** `okstra plan-items validate-prepared ... --state <same state>` re-derives the carry and exits 2 when the prepared envelope's `priorRounds` does not match, alongside the `items` / `dispatchQueue` comparison it already made. A prepared queue that dropped the dissent cannot pass the step-1 validation the dispatch is gated on.
|
|
856
856
|
|
|
857
857
|
The two spellings are different anchors for different artifacts: `**Prior round dissent**` is the block `prompt` puts in the prompt, `**Prior dissent**` is the line the worker puts in its result. `scripts/okstra_ctl/verdict_blocks.py` parses the result line into the verdict block when it is present and leaves it empty when it is not, so an omitted answer line is still silent — the prompt is what is now guaranteed, not the response.
|
|
858
858
|
|
|
@@ -24,7 +24,7 @@ Report assembly reads the role-owned inputs, validates them, derives links and s
|
|
|
24
24
|
|
|
25
25
|
An active clarification exists only in `activeClarifications[]`. A decision carried from a previous run exists only in `carriedDecisions[]`; do not recreate it as an active question.
|
|
26
26
|
|
|
27
|
-
Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). The carry-in record is the `--clarification-response` file; for a new plan it is the option-selection record `--selected-direction` names, and for an implementation run the approved plan `--approved-plan` names (`render._carry_in_source`) — the same pointer assembly writes to `clarificationCarryIn.sourceFile`, so the page links those ids to the prior run's page. A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <
|
|
27
|
+
Prepare seeds `carriedDecisions[]` when it creates the ledger: every clarification the run's carry-in record answered or resolved, plus every row that record's user-responses sidecars answered, arrives carried (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`). The carry-in record is the `--clarification-response` file; for a new plan it is the option-selection record `--selected-direction` names, and for an implementation run the approved plan `--approved-plan` names (`render._carry_in_source`) — the same pointer assembly writes to `clarificationCarryIn.sourceFile`, so the page links those ids to the prior run's page. A carried plan row's `requirementCoverage[].decisionRefs` may still name a `C-NNN` the carry-in record does not answer — a decision from an older run. Carry it before assembly: `okstra approval-decision carry --ledger <approvalDecisionsPath> --from-responses <launch prompt "Clarification Response Carried In" → Source path> --clarification-id C-NNN` — repeat `--clarification-id` to take several in one call. That bundle is the source of truth for an earlier run's answer: it is task-level and cumulative, so no prior run seq has to be located, and each response section names the report that posed the question, which is where the row's `statement`, `expectedForm`, and options come from. A carried row lands as `answered`, not `resolved` — it was resolved in another run, and `resolution.checkRefs` names *this* run's activity rows. Carrying an answer also obliges a `supersessionLedger` entry for it.
|
|
28
28
|
|
|
29
29
|
Each decision option has `role`, `answer`, `rationale`, `disposition`, `reach`, optional `scopeEffects`, `addedWork`, and `directionChange`. `reach` is exactly one of `in-repo` or `cross-repo`. `scopeEffects` may contain `new-schema` and `deferrable`. A `correctness-critical` option cannot use `select` or `accept-risk`; a `noncritical-dissent` option cannot use `select`.
|
|
30
30
|
|
|
@@ -58,7 +58,7 @@ This is enforced by
|
|
|
58
58
|
`report_synthesis_packet.build_report_synthesis_packet()`, and
|
|
59
59
|
`report_assembly.assemble_report()`.
|
|
60
60
|
|
|
61
|
-
Materialize the duty prompt with `okstra agent-prompt materialize --audience report-writer
|
|
61
|
+
Materialize the duty prompt with `okstra agent-prompt materialize --audience report-writer ...`. `--assignment-ref` is `initial/report-writer` — the run manifest declares the report writer under the `initial` phase, so `report-writer/report-writer` is refused. `--result` is the narrative path the run manifest's `reportNarrativePath` names (report contract 3.0; the `expectedReportRecordPath` data.json under 2.0) and `--audit-source` is the roster's worker result (`team-state` `workers[].resultPath` for `report-writer`). The materializer refuses any other value: report assembly reads the narrative from the manifest path and nowhere else, and `okstra team await` records the roster row completed only when the roster's file exists. A corrective dispatch (fix, self-fix, verdict, citations, supersession) reuses both paths and changes only the prompt path and the invocation ID. **Enforced:** `_validate_report_writer_paths` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The materializer also writes the report synthesis packet next to the narrative and puts `- Report synthesis packet: <path>` as the first line of the prompt's `## Inputs` section — opening the section when the instruction has none, or inserting under the instruction's own `## Inputs` heading when it has one. The instruction therefore does not enumerate raw source paths; a missing or unreadable packet source is refused before dispatch with its owner (`report synthesis packet contract defects`). **Enforced:** `_report_writer_input_lines` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`. The prompt starts with these anchors in order:
|
|
62
62
|
|
|
63
63
|
1. `**Project Root:**`
|
|
64
64
|
2. `**Prompt History Path:**`
|
|
@@ -147,7 +147,7 @@ When the result carries `recovery.mode: same-run`, continue the authorized work
|
|
|
147
147
|
|
|
148
148
|
### The translation sidecar: the `translate` step
|
|
149
149
|
|
|
150
|
-
`report-finalize` dispatches the translator itself. Its `translate` step runs directly before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then
|
|
150
|
+
`report-finalize` dispatches the translator itself. Its `translate` step runs directly before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then dispatches the worker and waits for it: on a `terminalBackend: cmux-pane` run into a cmux pane like every other worker (the same code as `okstra team dispatch` followed by `okstra team await`; run-end `okstra team teardown` closes that pane), otherwise through the CLI-wrapper dispatcher. Either path records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
|
|
151
151
|
|
|
152
152
|
The step succeeds only when the sidecar exists afterwards; a translator that exits 0 without publishing it fails the step. A failed `translate` does not stop the sequence: `render-views` still writes the view from the English body, `validate-run` records the missing sidecar as an advisory, and the result's resume hint starts at `--only translate --only render-views …` — run that once the cause (a host approval gate, an unavailable provider) is cleared rather than closing the run on an English view.
|
|
153
153
|
|
|
@@ -5,7 +5,7 @@ Edit here once; every profile picks the change up at next render. Do NOT
|
|
|
5
5
|
add phase-specific rules to this file — phase rules stay in the per-
|
|
6
6
|
profile document.
|
|
7
7
|
-->
|
|
8
|
-
- Team contract (shared): roster roles, model-assignment rules, dispatch invariants, and required-worker attempt rules are canonical in the team contract (`prompts/lead/team-contract.md`). Two consequences every phase honours: the host-native Okstra lead is synthesis-only (in `implementation`, distinct from the `Executor` and verifiers), and unnamed generic parallel workers never replace or extend the per-profile `Required workers:` roster. Prep-time model recommendations come from the catalog defaults in `okstra_ctl.models` (for example, `Codex worker` → `gpt-
|
|
8
|
+
- Team contract (shared): roster roles, model-assignment rules, dispatch invariants, and required-worker attempt rules are canonical in the team contract (`prompts/lead/team-contract.md`). Two consequences every phase honours: the host-native Okstra lead is synthesis-only (in `implementation`, distinct from the `Executor` and verifiers), and unnamed generic parallel workers never replace or extend the per-profile `Required workers:` roster. Prep-time model recommendations come from the catalog defaults in `okstra_ctl.models` (for example, `Codex worker` → `gpt-6-sol`); at dispatch time the task-manifest's materialized assignment is the only source — there is no dispatch-time fallback.
|
|
9
9
|
- Worker interaction model (shared — read before inferring behaviour from the roster):
|
|
10
10
|
- the per-profile `Required workers:` block is a **roster**, not a behaviour contract. Each role's interaction mode changes across operating phases of the same run.
|
|
11
11
|
- **Phase 4 / 5 (independent analysis)**: every analyser in the resolved provider assignment roster produces findings independently and has no access to another worker's output. `report-writer` does not analyse.
|
|
@@ -86,7 +86,7 @@ profile document.
|
|
|
86
86
|
- **One decision per row.** A `decision` row asks one question. When a single option bundles two independent decisions, split the row. The tell is usually the option's `reach`: an option that is `cross-repo` only because one bundled clause crosses a repository boundary contains two decisions of different cost. The §5.5.9 adversarial round judges this semantic rule.
|
|
87
87
|
- each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
|
|
88
88
|
- write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (yes/no, one of the options, number/date, file path, short description, etc.); supply concrete option choices when applicable.
|
|
89
|
-
- **Record coordinates only.** A clarification `statement`, `expectedForm`, or `options[]` answer/rationale may cite a report-record row id (`RB-002`, `C-014`) or a `path:line`. Do not cite a section number (`§4.7`, `§1`). That number exists only on one full reading copy. **Guideline — the cost is a worse question, not a failed run.** `scripts/okstra_ctl/user_response.py` `resolve_refs_from_record` resolves a row id against the report record and returns `null` for a `§` number, so the `okstra user-response` picker shows the user a bare `§4.7` with no context snippet next to the question it is supposed to explain. Nothing rejects the row.
|
|
89
|
+
- **Record coordinates only.** A clarification `statement`, `expectedForm`, or `options[]` answer/rationale may cite a report-record row id (`RB-002`, `C-014`) or a `path:line`. Do not cite a section number (`§4.7`, `§1`). That number exists only on one full reading copy. **Guideline — the cost is a worse question, not a failed run.** `scripts/okstra_ctl/user_response.py` `resolve_refs_from_record` resolves a row id against the report record and returns `null` for a `§` number, so the `okstra user-response` picker shows the user a bare `§4.7` with no context snippet next to the question it is supposed to explain. Nothing rejects the row. A row id the picker *can* resolve still reaches the user as an id: the `statement` says in one clause what the cited row requires before the question leans on it — see the lifecycle core contract "Asking the user" rule 4.
|
|
90
90
|
- **Schema-v2 authors do not use the string grammar below.** A v2 `Kind=decision` row carries its choices in `options[]` (see the Clarification recommendation fragment for the field list); the renderer and the `okstra user-response` picker both build their selectable options from that array, so a choice that exists only in prose is a choice the user cannot pick. The rest of this bullet governs schema-v1 tables and analysis-worker result tables, which have only string cells.
|
|
91
91
|
- if a schema-v1 table or an analysis-worker result table requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(pick 1 of A / B)` or `(pick N of …)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
92
92
|
- For schema v2, data.json is canonical and the HTML exports answers to a user-response sidecar; the source report is never edited. `--resume-clarification` carries those answers into the next run. The lower-level `--clarification-response <path>` remains available for scripted runs.
|
|
@@ -23,7 +23,7 @@ This is where the preflight's conventions get enforced against the code you actu
|
|
|
23
23
|
|
|
24
24
|
Do not scan holistically and stop when it "looks fine". Work the matrix exhaustively — the failure mode this gate exists to prevent is a real defect surviving because you eyeballed the diff instead of enumerating it.
|
|
25
25
|
|
|
26
|
-
**Enforced:** `scripts/okstra_ctl/initial_prompt_materialization.py` `_required_resource_path_candidates` names this body for the `implementation-executor` audience, so it is appended to every executor prompt rather than left to a file reference the CLI executor cannot open
|
|
26
|
+
**Enforced:** `scripts/okstra_ctl/initial_prompt_materialization.py` `_required_resource_path_candidates` names this body for the `implementation-executor` audience, so it is appended to every executor prompt rather than left to a file reference the CLI executor cannot open. The audience selects the body, not the runner, so an in-process `native-session` worker receives the same text through the same path.
|
|
27
27
|
|
|
28
28
|
## Method — enumerate the diff, then walk every (file × applicable-rule) cell
|
|
29
29
|
|
|
@@ -33,6 +33,9 @@ reaches it. Enforcement: `_required_resource_path_candidates` names the three
|
|
|
33
33
|
bodies for the `implementation-executor` audience, so composing the prompt is
|
|
34
34
|
what puts them there — there is no separate dispatch-time heading check, and a
|
|
35
35
|
missing heading means the composer did not run, not that a worker dropped it.
|
|
36
|
+
Both publishers compose it: the dispatch-time composer for a prompt that does not
|
|
37
|
+
exist yet, and `okstra agent-prompt materialize` for a prompt the lead publishes
|
|
38
|
+
first (`required_resource_block`), because dispatch reuses an existing prompt as is.
|
|
36
39
|
The `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` /
|
|
37
40
|
`<SENTINEL_PREFIX>_POSTWRITE_GATE_MISSING` sentinels were the CLI-wrapper
|
|
38
41
|
template's check; that template is gone.
|
|
@@ -46,7 +49,7 @@ template's check; that template is gone.
|
|
|
46
49
|
- **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `static-analysis only …, unverified (not executed)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
|
|
47
50
|
- **External-source adapters — structure AND fixture both derive from a captured real sample; a self-authored fixture is NOT reality evidence:** when this run's diff builds or changes an `external-interface` or `transformation-mapping` surface (an HTTP / network client, or a parser / mapper of a third-party payload — HTML / JSON / XML / CSV originating outside this repo), the adapter's structural assumptions (selectors, field paths, expected response shape) AND the static fixture / golden that tests them MUST BOTH derive from a **captured real sample** of that payload — the capture cited in the stage's `external-interface` / `transformation-mapping` design-prep item, or one captured this run and recorded with its `source` + capture time. The captured sample is a static fixture (no live socket), so a parser test against it stays in source like any unit test — the Real-IO isolation rule below governs *live* calls, not the captured bytes. Do NOT hand-invent the shape and then hand-write a fixture that agrees with it: the passing test then only proves the code matches your assumption, never that the assumption matches reality (self-confirming oracle — the observed failure was a parser whose selectors existed in its synthetic fixture and in zero real pages: hundreds of green units over a fiction, and the whole structure built on the wrong shape). When no real sample is reachable (no network this run, or the brief supplied none), do NOT synthesize a stand-in and present its green tests as correctness: mark the adapter's shape `reality-unverified (no captured sample)` in `Validation evidence`, keep any placeholder fixture explicitly labelled an assumption (never validation evidence), and surface an explicit **user-owned** item in the routing recommendation to confirm against real data. Unlike the DB gate above this does NOT itself block acceptance — live external verification stays a user-owned item per `final-verification`'s External QA advisory policy — but a synthetic external fixture presented as reality-verified is exactly the mock-only external evidence the `final-verification` test-correctness pass is meant to reject.
|
|
48
51
|
- **Real-IO test isolation (BLOCKING).** A test that exercises a **real** datastore, HTTP endpoint, external service, message queue, or filesystem — a live DB connection / DSN, a real `fetch` / `axios` / `http` request, an actual S3 / queue client, anything the project's normal CI test suite cannot run because that backend is absent — MUST be written under the task's qa scripts directory `<task_root>/qa/scripts/` (`<TASK_QA_PATH>/scripts`; the `qa/` root itself holds only data sidecars — the Tier 3 conformance manifest and `result-*.json`). It MUST NOT be written into the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*`, or anywhere the project's lint/test globs collect. Two reasons: (a) the project's CI / normal suite has no real DB or network, so a real-IO test placed in source silently breaks the pipeline; (b) it is an okstra verification artifact, and the artifact-home rule confines okstra outputs to `.okstra/`. **The dividing line is the IO, not the intent:** a unit test that stubs/spies only *injected collaborators* (mock — no real socket, no real DB handle) is a TDD red-green artifact and stays in source; the moment a test opens a real connection or makes a real network call it belongs in qa. A stage's real-IO requirement check is a Tier 3 conformance script under `<task_root>/qa/scripts/` (declared via the implementation-planning conformance entry) — never smuggle real IO into a `*.spec.*` in source to make it run "as a unit test". The `db-test` real-execution gate above is satisfied by the conformance/db-test path against the replica, NOT by adding a live-DB `*.spec.*` to the project suite. **Author qa specs with the project's own test framework — never hand-roll `describe`/`it`/`expect`.** When the project ships a test runner as a devDependency (jest / vitest / pytest …), the qa spec uses it, invoked with the project config plus a discovery override pointing at the qa scripts dir (jest: `npx jest --config <project jest config> --roots <task_root>/qa/scripts --runInBand <spec-name>`) — the project config keeps module aliases resolving while the default sweep never collects the file; never widen the project's own test config to include qa paths. For TypeScript qa specs also write `<task_root>/qa/scripts/tsconfig.json` (`extends` the project tsconfig, adds the runner's `types` entry, `"include": ["**/*.ts"]`) so editors resolve path aliases and test globals — it is a qa artifact like the rest (untracked). **These qa artifacts stay untracked — never commit them.** `.okstra/**` is gitignored (the artifact-home rule); conformance scripts and their results are *executed* and recorded in the carry sidecar / verifier result, never written into git history. A committed `.okstra/qa` file is a stage-branch defect that leaks okstra internals into the eventual PR (see the `git add` rules below).
|
|
49
|
-
- **Stage conformance script (BLOCKING when the approved plan declared `Conformance tests:`).** Planning only declared the path and `requires`. This run MUST write the script to that path under `<task_root>/qa/scripts/` and add the matching `<task_root>/qa/conformance-manifest.json` entry: `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (the set the plan declared), `passContract`, `exemption: null`, `waiver: null`. Do not skip this when the plan declared tests. If the plan declared `Conformance exemption:`, do not invent a script — with one exception: when this stage's diff touches a db/io/http/external surface the exemption promised it would not (the verifier's diff-surface cross-check names the surface), write the script and the manifest entry for this stage exactly as for a declared stage, with `requires` covering those surfaces. The approved plan is not rewritten; `validate-run.py` `_declared_conformance_errors` accepts an entry for a stage the plan exempted and still rejects one for a stage the plan does not have. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test. `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at a tree without this stage's changes. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/`. Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory; `validators/validate-run.py` `_validate_conformance` fails the run if the inherited declaration has no script file.
|
|
52
|
+
- **Stage conformance script (BLOCKING when the approved plan declared `Conformance tests:`).** Planning only declared the path and `requires`. This run MUST write the script to that path under `<task_root>/qa/scripts/` and add the matching `<task_root>/qa/conformance-manifest.json` entry: `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (the set the plan declared, widened by any db/io/http/external surface this stage's diff touches that the plan left out — never narrowed; `validate-run.py` `_declared_conformance_errors` accepts a superset and rejects a subset), `passContract`, `exemption: null`, `waiver: null`. Do not skip this when the plan declared tests. If the plan declared `Conformance exemption:`, do not invent a script — with one exception: when this stage's diff touches a db/io/http/external surface the exemption promised it would not (the verifier's diff-surface cross-check names the surface), write the script and the manifest entry for this stage exactly as for a declared stage, with `requires` covering those surfaces. The approved plan is not rewritten; `validate-run.py` `_declared_conformance_errors` accepts an entry for a stage the plan exempted and still rejects one for a stage the plan does not have. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. The verifier runs `runCommand` from the **worktree cwd**, and that cwd is the tree under test. `runCommand` MUST NOT repoint it: a leading `cd <checkout> &&` sends the script at a tree without this stage's changes. Absolute paths are fine and usually necessary — the script and its `tsconfig` live under `<task_root>/qa/scripts/`, i.e. under `.okstra/`, and a worktree does not carry `.okstra/`. Point at those by absolute path; leave the cwd alone. **Enforced:** `scripts/okstra_ctl/conformance.py` `_check_entry` rejects a `runCommand` whose first word in any `&&` / `;` segment changes directory; `validators/validate-run.py` `_validate_conformance` fails the run if the inherited declaration has no script file.
|
|
50
53
|
- read the approved plan at this prompt's `**Approved plan:**` anchor end-to-end and parse the `## 5.5 Stage Map`. Read this prompt's `**Stage for this implementation run:**` anchor: the single stage number this run owns. The runtime already selected and reserved this stage (one run = one stage) — do NOT recompute the start stage from `consumers.jsonl`. Both anchors are generated headers; when either is missing, stop and report `contract-violated` rather than inferring the value.
|
|
51
54
|
- load every `runs/<plan-key>/carry/stage-<i>.json` for `i ∈ depends-on(this stage)` and inject them into the executor's working context as "runtime carry-in". For a `depends-on (none)` stage, no sidecar load — task-brief only.
|
|
52
55
|
- this stage's `depends-on` are all already `status:done`. Its file list, step order, Stage Validation commands, Stage Exit Contract, and rollback path are the authoritative scope.
|
|
@@ -27,4 +27,4 @@ fails, fix it or surface the violation — do not claim done on a failing item.
|
|
|
27
27
|
Close with a `Self-check coverage:` line naming the files you verified the
|
|
28
28
|
per-file items against, so the Coverage footer above and this gate reconcile.
|
|
29
29
|
|
|
30
|
-
**Enforced:** `scripts/okstra_ctl/initial_prompt_materialization.py` `_required_resource_path_candidates` names this body for the `implementation-executor` audience, so it is appended to every executor prompt rather than left to a file reference the CLI executor cannot open
|
|
30
|
+
**Enforced:** `scripts/okstra_ctl/initial_prompt_materialization.py` `_required_resource_path_candidates` names this body for the `implementation-executor` audience, so it is appended to every executor prompt rather than left to a file reference the CLI executor cannot open. The audience selects the body, not the runner, so an in-process `native-session` worker receives the same text through the same path.
|
|
@@ -9,7 +9,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
|
|
|
9
9
|
|
|
10
10
|
- Every verdict comes from a fresh session with no shared context, never from the session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
|
|
11
11
|
- Session isolation is the primary self-review safeguard: each verifier is a separate invocation with its own context window. Reusing the executor's model is acceptable. The model comes from the run's stored assignment.
|
|
12
|
-
- Verifiers read from the SAME working tree path the Executor used so they observe the exact diff the Executor produced. Source files, lockfiles, Git state, and shared links remain read-only. Declared verification commands may create their normal worktree-local build/cache outputs and install dependencies
|
|
12
|
+
- Verifiers read from the SAME working tree path the Executor used so they observe the exact diff the Executor produced. Source files, lockfiles, Git state, and shared links remain read-only. Declared verification commands may create their normal worktree-local build/cache outputs. **Do not re-run a dependency install** (`npm ci`, `yarn install --frozen-lockfile`, `pnpm install --frozen-lockfile`, and the like — typically the plan's `phase: pre` dependency-precondition row): the executor already installed into this worktree, and the other verifiers of this stage run at the same time against the same `node_modules`, so concurrent installs break each other and report exit codes that say nothing about the code (measured 2026-09-22, jobs dev-10860 stage 1: verifiers dispatched within 4 seconds, `EEXIST` on a workspace symlink in 2 of 3 reruns). Record such a row in `independentValidationRerun` as `not re-run — dependency install precondition, executor exit <code>`; it is outside the Discrepancy rule below. If the installed dependencies are actually missing or broken, the checks that need them fail, and that failure is what you report. This is a guideline: nothing parses the command log for installs. This is the bounded exception to the preceding write restriction; it does not permit source repairs, moving shared links, or redirecting build outputs outside the worktree. Run-owned logs remain in the run artifact directories.
|
|
13
13
|
|
|
14
14
|
**Enforced:** `_validate_verifier_command_log_is_read_only` in `validators/validate-run.py` scans every `verifierResults[].readOnlyCommandLog` for source-mutating commands (`sed -i`, `git checkout --`/`restore`/`reset --hard`/`stash`/`clean`/`apply`, `patch -p`, `rm -rf`, `truncate`). Read-only forms (`git stash list`, `git clean --dry-run`, `git apply --check`) pass.
|
|
15
15
|
|
|
@@ -150,7 +150,7 @@ The runtime AND the verifier MUST reject any `cmd` containing tokens that imply
|
|
|
150
150
|
|
|
151
151
|
### Discrepancy rule
|
|
152
152
|
|
|
153
|
-
Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. The Okstra lead has no synthesis-time override for that FAIL. It MUST NOT prefer the executor's evidence over a verifier's reproduced result, with or without a cited reason: `_validate_verifier_fail_blocks_verdict` fails any report that publishes a passing `finalVerdict.verdictToken` while a `verifierResults[]` row records `FAIL`, and it reads only that row — no rationale field reaches it. A divergence the lead believes is not the code's (a flaky test, a documented environment delta) is resolved where the verdict is written, not after it: it is carried into the next fix run, where the verifier re-checks the finding and cites it `resolved`. Recording the reason in the report without changing the verdict row is what the routing recommendation and the user-owned follow-up are for.
|
|
153
|
+
Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. A dependency-install row is not re-run (see the worktree rule above), so it never yields a discrepancy. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. The Okstra lead has no synthesis-time override for that FAIL. It MUST NOT prefer the executor's evidence over a verifier's reproduced result, with or without a cited reason: `_validate_verifier_fail_blocks_verdict` fails any report that publishes a passing `finalVerdict.verdictToken` while a `verifierResults[]` row records `FAIL`, and it reads only that row — no rationale field reaches it. A divergence the lead believes is not the code's (a flaky test, a documented environment delta) is resolved where the verdict is written, not after it: it is carried into the next fix run, where the verifier re-checks the finding and cites it `resolved`. Recording the reason in the report without changing the verdict row is what the routing recommendation and the user-owned follow-up are for.
|
|
154
154
|
|
|
155
155
|
**When the re-run matched, leave `discrepancy` empty and omit the `Discrepancy` line.** The field records a divergence, so an empty field *is* the record of "no divergence" — the schema makes it optional for exactly that. Do not write `None`, `n/a`, or a sentence explaining that nothing diverged: the check reads any non-empty text as a recorded divergence, so a verifier that states its clean result in prose is failed for the result it is reporting.
|
|
156
156
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "change-impact-analysis",
|
|
4
|
+
"roles": [
|
|
5
|
+
{
|
|
6
|
+
"mode": "static",
|
|
7
|
+
"roleId": "analyser",
|
|
8
|
+
"dutyId": "analysis-worker",
|
|
9
|
+
"min": 2,
|
|
10
|
+
"recommended": 3,
|
|
11
|
+
"max": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"mode": "static",
|
|
15
|
+
"roleId": "report-writer",
|
|
16
|
+
"dutyId": "report-writer",
|
|
17
|
+
"min": 1,
|
|
18
|
+
"recommended": 1,
|
|
19
|
+
"max": 1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"mode": "dynamic",
|
|
23
|
+
"roleId": "verifier",
|
|
24
|
+
"dutyId": "reverification-worker",
|
|
25
|
+
"sourceRoleIds": [
|
|
26
|
+
"analyser"
|
|
27
|
+
],
|
|
28
|
+
"activation": "per-selected-source"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -1,25 +1,5 @@
|
|
|
1
1
|
# Change Impact Analysis Profile
|
|
2
2
|
|
|
3
|
-
```yaml
|
|
4
|
-
roles:
|
|
5
|
-
- role: analyser
|
|
6
|
-
min: 2
|
|
7
|
-
recommended: 3
|
|
8
|
-
max: 5
|
|
9
|
-
duty: analysis-worker
|
|
10
|
-
- role: report-writer
|
|
11
|
-
min: 1
|
|
12
|
-
recommended: 1
|
|
13
|
-
max: 1
|
|
14
|
-
duty: report-writer
|
|
15
|
-
- role: verifier
|
|
16
|
-
min: 0
|
|
17
|
-
recommended: 0
|
|
18
|
-
max: 0
|
|
19
|
-
duty: reverification-worker
|
|
20
|
-
dynamic: true
|
|
21
|
-
```
|
|
22
|
-
|
|
23
3
|
- Purpose: assess the read-only impact of a proposed change, including preserved behavior, affected dependencies, and constraints that a later planning phase must resolve
|
|
24
4
|
- Required workers:
|
|
25
5
|
- claude
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "error-analysis",
|
|
4
|
+
"roles": [
|
|
5
|
+
{
|
|
6
|
+
"mode": "static",
|
|
7
|
+
"roleId": "analyser",
|
|
8
|
+
"dutyId": "diagnosis-worker",
|
|
9
|
+
"min": 2,
|
|
10
|
+
"recommended": 3,
|
|
11
|
+
"max": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"mode": "static",
|
|
15
|
+
"roleId": "critic",
|
|
16
|
+
"dutyId": "scope-critic",
|
|
17
|
+
"min": 0,
|
|
18
|
+
"recommended": 1,
|
|
19
|
+
"max": 1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"mode": "static",
|
|
23
|
+
"roleId": "report-writer",
|
|
24
|
+
"dutyId": "report-writer",
|
|
25
|
+
"min": 1,
|
|
26
|
+
"recommended": 1,
|
|
27
|
+
"max": 1
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"mode": "dynamic",
|
|
31
|
+
"roleId": "verifier",
|
|
32
|
+
"dutyId": "reverification-worker",
|
|
33
|
+
"sourceRoleIds": [
|
|
34
|
+
"analyser"
|
|
35
|
+
],
|
|
36
|
+
"activation": "per-selected-source"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -1,30 +1,5 @@
|
|
|
1
1
|
# Error Analysis Profile
|
|
2
2
|
|
|
3
|
-
```yaml
|
|
4
|
-
roles:
|
|
5
|
-
- role: analyser
|
|
6
|
-
min: 2
|
|
7
|
-
recommended: 3
|
|
8
|
-
max: 5
|
|
9
|
-
duty: diagnosis-worker
|
|
10
|
-
- role: critic
|
|
11
|
-
min: 0
|
|
12
|
-
recommended: 1
|
|
13
|
-
max: 1
|
|
14
|
-
duty: scope-critic
|
|
15
|
-
- role: report-writer
|
|
16
|
-
min: 1
|
|
17
|
-
recommended: 1
|
|
18
|
-
max: 1
|
|
19
|
-
duty: report-writer
|
|
20
|
-
- role: verifier
|
|
21
|
-
min: 0
|
|
22
|
-
recommended: 0
|
|
23
|
-
max: 0
|
|
24
|
-
duty: reverification-worker
|
|
25
|
-
dynamic: true
|
|
26
|
-
```
|
|
27
|
-
|
|
28
3
|
- Purpose: analyse reported errors or incidents and identify likely causes, missing evidence, and validation paths
|
|
29
4
|
- Required workers:
|
|
30
5
|
- claude
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "feature-analysis",
|
|
4
|
+
"roles": [
|
|
5
|
+
{
|
|
6
|
+
"mode": "static",
|
|
7
|
+
"roleId": "analyser",
|
|
8
|
+
"dutyId": "analysis-worker",
|
|
9
|
+
"min": 2,
|
|
10
|
+
"recommended": 3,
|
|
11
|
+
"max": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"mode": "static",
|
|
15
|
+
"roleId": "report-writer",
|
|
16
|
+
"dutyId": "report-writer",
|
|
17
|
+
"min": 1,
|
|
18
|
+
"recommended": 1,
|
|
19
|
+
"max": 1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"mode": "dynamic",
|
|
23
|
+
"roleId": "verifier",
|
|
24
|
+
"dutyId": "reverification-worker",
|
|
25
|
+
"sourceRoleIds": [
|
|
26
|
+
"analyser"
|
|
27
|
+
],
|
|
28
|
+
"activation": "per-selected-source"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -1,25 +1,5 @@
|
|
|
1
1
|
# Feature Analysis Profile
|
|
2
2
|
|
|
3
|
-
```yaml
|
|
4
|
-
roles:
|
|
5
|
-
- role: analyser
|
|
6
|
-
min: 2
|
|
7
|
-
recommended: 3
|
|
8
|
-
max: 5
|
|
9
|
-
duty: analysis-worker
|
|
10
|
-
- role: report-writer
|
|
11
|
-
min: 1
|
|
12
|
-
recommended: 1
|
|
13
|
-
max: 1
|
|
14
|
-
duty: report-writer
|
|
15
|
-
- role: verifier
|
|
16
|
-
min: 0
|
|
17
|
-
recommended: 0
|
|
18
|
-
max: 0
|
|
19
|
-
duty: reverification-worker
|
|
20
|
-
dynamic: true
|
|
21
|
-
```
|
|
22
|
-
|
|
23
3
|
- Purpose: analyse a confirmed feature target's behavior, rules, state changes, external calls, and test coverage scope without designing or changing an implementation
|
|
24
4
|
- Required workers:
|
|
25
5
|
- claude
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "final-verification",
|
|
4
|
+
"roles": [
|
|
5
|
+
{
|
|
6
|
+
"mode": "static",
|
|
7
|
+
"roleId": "verifier",
|
|
8
|
+
"dutyId": "acceptance-verifier",
|
|
9
|
+
"min": 2,
|
|
10
|
+
"recommended": 2,
|
|
11
|
+
"max": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"mode": "static",
|
|
15
|
+
"roleId": "critic",
|
|
16
|
+
"dutyId": "acceptance-critic",
|
|
17
|
+
"min": 0,
|
|
18
|
+
"recommended": 1,
|
|
19
|
+
"max": 1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"mode": "static",
|
|
23
|
+
"roleId": "report-writer",
|
|
24
|
+
"dutyId": "report-writer",
|
|
25
|
+
"min": 1,
|
|
26
|
+
"recommended": 1,
|
|
27
|
+
"max": 1
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
# Final Verification Profile
|
|
2
2
|
|
|
3
|
-
```yaml
|
|
4
|
-
roles:
|
|
5
|
-
- role: verifier
|
|
6
|
-
min: 2
|
|
7
|
-
recommended: 2
|
|
8
|
-
max: 5
|
|
9
|
-
duty: acceptance-verifier
|
|
10
|
-
- role: critic
|
|
11
|
-
min: 0
|
|
12
|
-
recommended: 1
|
|
13
|
-
max: 1
|
|
14
|
-
duty: acceptance-critic
|
|
15
|
-
- role: report-writer
|
|
16
|
-
min: 1
|
|
17
|
-
recommended: 1
|
|
18
|
-
max: 1
|
|
19
|
-
duty: report-writer
|
|
20
|
-
```
|
|
21
|
-
|
|
22
3
|
- 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
4
|
- Required workers:
|
|
24
5
|
- claude
|
|
@@ -49,8 +30,8 @@ roles:
|
|
|
49
30
|
- Pre-verification entry gate (resolved & enforced by `okstra render-bundle` prep — the lead does NOT recompute it):
|
|
50
31
|
- 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.
|
|
51
32
|
- **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.
|
|
52
|
-
- **whole-task
|
|
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
|
|
33
|
+
- **whole-task mutates only when it has to.** When one stage branch already contains every other done stage's commit — the usual shape of a linear plan, since each stage branches from its predecessor's done commit — that branch IS the whole task, and verification runs in that stage's worktree with no merge at all (`stage_targets.containing_stage`). Only when the stage graph has several tips does entry auto-merge (with `--no-ff`) the done stages into the task branch to create an integration commit; that case is the mutating one. 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.
|
|
34
|
+
- **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 of one stage, and that is exactly the unit release-handoff ships: an `accepted` verdict here makes the stage PR-eligible, so `release-handoff` is the routing target. It says nothing about any other stage.
|
|
54
35
|
- 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.
|
|
55
36
|
- Worker verification procedure:
|
|
56
37
|
- **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.
|
|
@@ -84,8 +65,8 @@ roles:
|
|
|
84
65
|
- **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.
|
|
85
66
|
- **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.
|
|
86
67
|
- **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."
|
|
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
|
|
88
|
-
- **Verified-row recording** (
|
|
68
|
+
- **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 eight allowed targets are `release-handoff`, `release-handoff(stage-group)`, `final-verification`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, and `done`. `final-verification` re-runs this phase on the same head and is for exactly one situation: every remaining blocker is an environment or configuration fault whose cause this report already names — a `qaCommands` entry pointing at a path that no longer exists, a missing credential, a stale fixture — so nothing in the code, the plan, or the selected direction is being re-decided. Name the repair in the `rationale`. When any blocker needs a code, plan, or direction change, route to the phase that owns that change instead; routing a defect you have not diagnosed back into this phase re-runs the verification that already failed. Both `release-handoff` forms are allowed ONLY when the verdict is release-ready — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false`. Either verification scope may route there: release-handoff opens one PR per stage, so a release-ready `single-stage` run is the evidence for that stage's PR, and `release-handoff(stage-group)` is only a scope qualifier that projects onto the same phase. `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` and a verdict that is not release-ready routed to either `release-handoff` form (naming the condition ids that block it).
|
|
69
|
+
- **Verified-row recording** (both scopes): 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 data.json path> --data-json <final-report data.json path>` and quote the command + exit code in the report. Pass the record path to both: the Markdown reading copy is rendered on request and does not exist in a finished run (ADR-0014), and the helper normalizes either path to the record anyway. A `whole-task` report clears every stage in its own `stageReports`, so run it **once per those stages** — each run writes that stage's row from this one report. Without those rows the stage is never offered a pull request, and release-handoff opens one PR per stage. The helper checks the latest verification manifest, task/stage identity, report pointer, prepared target, and recorded implementation commit. It records the captured commit and original verdict, including conditional acceptance conditions. A missing target or mismatched commit requires re-verification. Recording happens before final validation; eligibility is granted only after that verification passes validation. **Enforced:** `okstra_ctl.handoff_verification` validates the evidence, and `validators/validate-run.py` `_validate_verified_row_recorded` requires a `verified` row matching this report, captured commit, and verdict.
|
|
89
70
|
- Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
|
|
90
71
|
- 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
|
|
91
72
|
- Self-review pass before finalising the report (the Okstra lead runs this; do not delegate it):
|
|
@@ -75,13 +75,14 @@
|
|
|
75
75
|
],
|
|
76
76
|
"release-handoff": [
|
|
77
77
|
"entering this phase when the cited final-verification `Verdict Token` is `conditional-accept` or `blocked`, or when no final-verification report is cited",
|
|
78
|
-
"local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` run by the lead. The single exception is the merge commits `okstra handoff
|
|
78
|
+
"local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` / `git rebase --onto` / `git cherry-pick` / `git commit --amend` run by the lead. The single exception is the merge commits `okstra handoff pr-plan` itself creates on a `merge-base` branch — the lead never merges by hand. Rewriting a stage branch breaks the PR stack that sits on it.",
|
|
79
79
|
"any git push variant that rewrites remote history, regardless of intent or whether the user said \"force it\": `git push --force`, `git push --force-with-lease`, `git push -f`, `git push +<refspec>`, or any other history-rewriting invocation",
|
|
80
|
-
"pushing directly to a base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the
|
|
80
|
+
"pushing directly to a release base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the release base in this run. The only permitted push targets are the branches `okstra handoff pr-plan` listed for this run (each stage `head_branch`, and any `merge-base` branch).",
|
|
81
81
|
"bypassing repo safeguards: `--no-verify` / `-n` on `git push`, bypassing GPG signing, disabling safeguards via equivalent flags, or any hook bypass.",
|
|
82
82
|
"release-publishing commands: `gh release create`, `gh release edit`, `npm publish`, `cargo publish`, `pip publish`, `twine upload`, `docker push`, `terraform apply`, `kubectl apply` against any non-local cluster.",
|
|
83
83
|
"source-code edits, refactors, or any modification to files outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`). The diff being shipped MUST be exactly what the prior `implementation` run produced; release-handoff packages it, it does not re-author it.",
|
|
84
|
-
"executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `local checkout`; pushing when the user picked `skip`; switching the
|
|
84
|
+
"executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `local checkout`; pushing when the user picked `skip`; switching the release base branch silently after the user already chose one; opening a PR for a stage outside `HANDOFF_STAGES`.",
|
|
85
|
+
"squash-merging, or instructing anyone to squash-merge, a stage PR — and `gh pr merge` in any form. A squash replaces the commits the next stage's PR base points at, so the stack breaks; the PR body states merge-commit-or-rebase and the lead never merges.",
|
|
85
86
|
"retrying a failed git / gh command with weaker safety flags. If `git push` fails with non-fast-forward, the lead MUST stop, explain the failure to the user, and ask for instructions — it MUST NOT add `--force`.",
|
|
86
87
|
"worker dispatch of any kind, or any other parallel sub-agent fan-out. This phase runs entirely under the Okstra lead.",
|
|
87
88
|
"silently treating an unrecognised user reply as one of the menu options. If the user's answer does not match a presented choice, re-ask the question verbatim."
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "implementation-option-selection",
|
|
4
|
+
"roles": [
|
|
5
|
+
{
|
|
6
|
+
"mode": "static",
|
|
7
|
+
"roleId": "designer",
|
|
8
|
+
"dutyId": "direction-selection-worker",
|
|
9
|
+
"min": 3,
|
|
10
|
+
"recommended": 3,
|
|
11
|
+
"max": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"mode": "static",
|
|
15
|
+
"roleId": "report-writer",
|
|
16
|
+
"dutyId": "report-writer",
|
|
17
|
+
"min": 1,
|
|
18
|
+
"recommended": 1,
|
|
19
|
+
"max": 1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"mode": "dynamic",
|
|
23
|
+
"roleId": "verifier",
|
|
24
|
+
"dutyId": "reverification-worker",
|
|
25
|
+
"sourceRoleIds": [
|
|
26
|
+
"designer"
|
|
27
|
+
],
|
|
28
|
+
"activation": "per-selected-source"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|