okstra 0.214.1 → 0.216.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 +2 -1
- package/dist/cli-registry.d.mts +14 -0
- package/dist/cli-registry.mjs +12 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/chat/chat.mjs +2 -56
- package/dist/commands/chat/chat.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +5 -2
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/wizard.mjs +6 -0
- package/dist/commands/execute/wizard.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +57 -6
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/file-lock.d.mts +1 -0
- package/dist/lib/file-lock.mjs +60 -0
- package/dist/lib/file-lock.mjs.map +1 -0
- package/dist/lib/python-command.mjs +11 -1
- package/dist/lib/python-command.mjs.map +1 -1
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/dist/lib/types.d.mts +1 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +36 -14
- package/docs/project-structure-overview.md +6 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/cli.sh +9 -2
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +8 -2
- package/runtime/bin/lib/okstra/usage.sh +3 -1
- package/runtime/bin/okstra.sh +7 -1
- package/runtime/prompts/duties/discussion-worker.json +32 -0
- package/runtime/prompts/lead/convergence.md +58 -136
- package/runtime/prompts/lead/okstra-lead-contract.md +45 -12
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +14 -1
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +4 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +4 -11
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +2 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +4 -4
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +2 -2
- package/runtime/python/okstra_ctl/analysis_packet.py +3 -0
- package/runtime/python/okstra_ctl/consumers.py +8 -2
- package/runtime/python/okstra_ctl/convergence.py +143 -29
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +57 -0
- package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +106 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +140 -95
- package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +3 -27
- package/runtime/python/okstra_ctl/convergence_store.py +11 -4
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -1
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/manager_cli.py +27 -1
- package/runtime/python/okstra_ctl/manager_launch.py +44 -4
- package/runtime/python/okstra_ctl/manager_sync.py +14 -1
- package/runtime/python/okstra_ctl/manager_view.py +53 -1
- package/runtime/python/okstra_ctl/next_phase.py +1 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +2 -1
- package/runtime/python/okstra_ctl/phases/discussion/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/discussion/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/discussion/cli.py +512 -0
- package/runtime/python/okstra_ctl/phases/discussion/entry.py +116 -0
- package/runtime/python/okstra_ctl/phases/discussion/followup.py +876 -0
- package/runtime/python/okstra_ctl/phases/discussion/memory.py +294 -0
- package/runtime/python/okstra_ctl/phases/discussion/overview.py +85 -0
- package/runtime/python/okstra_ctl/phases/discussion/profile.json +31 -0
- package/runtime/python/okstra_ctl/phases/discussion/profile.md +48 -0
- package/runtime/python/okstra_ctl/phases/discussion/report.py +73 -0
- package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.html +15 -0
- package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.md +6 -0
- package/runtime/python/okstra_ctl/phases/discussion/spec.md +197 -0
- package/runtime/python/okstra_ctl/phases/discussion/store.py +1143 -0
- package/runtime/python/okstra_ctl/phases/discussion/validation.py +111 -0
- package/runtime/python/okstra_ctl/phases/discussion/wizard.py +90 -0
- package/runtime/python/okstra_ctl/phases/discussion/workflow.py +667 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/host-rules.md +4 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +1 -1
- package/runtime/python/okstra_ctl/plan_run_root.py +8 -0
- package/runtime/python/okstra_ctl/render.py +7 -20
- package/runtime/python/okstra_ctl/render_final_report.py +2 -1
- package/runtime/python/okstra_ctl/report_assembly.py +22 -0
- package/runtime/python/okstra_ctl/report_contract.py +2 -0
- package/runtime/python/okstra_ctl/report_narrative.py +13 -4
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +10 -0
- package/runtime/python/okstra_ctl/run.py +40 -7
- package/runtime/python/okstra_ctl/stage_close.py +46 -11
- package/runtime/python/okstra_ctl/stage_reopen.py +151 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +11 -35
- package/runtime/python/okstra_ctl/wizard/cli.py +8 -0
- package/runtime/python/okstra_ctl/wizard/confirmation.py +4 -1
- package/runtime/python/okstra_ctl/wizard/engine.py +8 -0
- package/runtime/python/okstra_ctl/wizard/ids.py +12 -0
- package/runtime/python/okstra_ctl/wizard/registry.py +35 -3
- package/runtime/python/okstra_ctl/wizard/render.py +4 -1
- package/runtime/python/okstra_ctl/wizard/sources.py +2 -2
- package/runtime/python/okstra_ctl/wizard/state.py +11 -1
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +4 -4
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +24 -6
- package/runtime/python/okstra_token_usage/pricing.py +2 -0
- package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
- package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
- package/runtime/schemas/final-report-v3.0.schema.json +90 -1
- package/runtime/skills/_fragments/wizard-bash-invocation-rule.md +1 -0
- package/runtime/skills/_fragments/wizard-confirm-step.md +7 -0
- package/runtime/skills/_fragments/wizard-lead-takeover.md +8 -0
- package/runtime/skills/_fragments/wizard-preflight.md +75 -0
- package/runtime/skills/_fragments/wizard-prompt-loop.md +22 -0
- package/runtime/skills/_fragments/wizard-relay-protocol.md +40 -0
- package/runtime/skills/_fragments/wizard-render-bundle.md +74 -0
- package/runtime/skills/okstra-discussion/SKILL.md +347 -0
- package/runtime/skills/okstra-pr-gen/SKILL.md +7 -0
- package/runtime/skills/okstra-run/SKILL.md +22 -2
- package/runtime/templates/manager/view.template.html +3 -0
- package/runtime/templates/reports/html/i18n/en.json +5 -0
- package/runtime/templates/reports/html/i18n/ko.json +5 -0
- package/runtime/validators/validate-brief.py +3 -2
- package/runtime/validators/validate-run.py +4 -6
package/runtime/BUILD.json
CHANGED
|
@@ -24,7 +24,10 @@ collect_required_arguments() {
|
|
|
24
24
|
prompt_for_required_argument TASK_GROUP "Task Group"
|
|
25
25
|
prompt_for_required_argument TASK_ID "Task ID"
|
|
26
26
|
prompt_for_required_argument TASK_TYPE "Task Type"
|
|
27
|
-
if [[ "$TASK_TYPE"
|
|
27
|
+
if [[ "$TASK_TYPE" == "discussion" ]]; then
|
|
28
|
+
# discussion 은 brief 대신 시작 입력을 받는다 — prepare 가 입력 문서를 만든다.
|
|
29
|
+
prompt_for_required_argument DISCUSSION_START "Discussion start (link, short description, or this task's key)"
|
|
30
|
+
elif [[ "$TASK_TYPE" != "release-handoff" ]]; then
|
|
28
31
|
# release-handoff 는 brief 가 없다 — prepare 가 검증 보고서 인용 input 을 생성.
|
|
29
32
|
prompt_for_required_argument BRIEF_PATH "Task Brief Path"
|
|
30
33
|
fi
|
|
@@ -175,6 +178,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
175
178
|
BRIEF_PATH="$(require_option_value --task-brief "${2-}")"
|
|
176
179
|
shift 2
|
|
177
180
|
;;
|
|
181
|
+
--discussion-start)
|
|
182
|
+
DISCUSSION_START="$(require_option_value --discussion-start "${2-}")"
|
|
183
|
+
shift 2
|
|
184
|
+
;;
|
|
178
185
|
--directive)
|
|
179
186
|
DIRECTIVE="$(require_option_value --directive "${2-}")"
|
|
180
187
|
shift 2
|
|
@@ -249,7 +256,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
249
256
|
printf ' hint: did you mean --task-id?\n' >&2
|
|
250
257
|
;;
|
|
251
258
|
esac
|
|
252
|
-
printf ' valid options: --render-only --resume-clarification --yes --role-count --role-model --lead-provider --lead-model --claude-model --codex-model --antigravity-model --worker-model --report-writer-provider --report-writer-model --lead-runtime --executor --critic --related-tasks --work-category --task-type --project-id --project-root --task-group --task-id --task-brief --directive --base-ref --fix-cycle --clarification-response --selected-direction --task-key --approved-plan --approve --implementation-option --stage --stages --qa-waiver --no-plan-verification -h|--help\n' >&2
|
|
259
|
+
printf ' valid options: --render-only --resume-clarification --yes --role-count --role-model --lead-provider --lead-model --claude-model --codex-model --antigravity-model --worker-model --report-writer-provider --report-writer-model --lead-runtime --executor --critic --related-tasks --work-category --task-type --project-id --project-root --task-group --task-id --task-brief --discussion-start --directive --base-ref --fix-cycle --clarification-response --selected-direction --task-key --approved-plan --approve --implementation-option --stage --stages --qa-waiver --no-plan-verification -h|--help\n' >&2
|
|
253
260
|
usage
|
|
254
261
|
exit 1
|
|
255
262
|
;;
|
|
@@ -145,7 +145,9 @@ autofill_from_manifest() {
|
|
|
145
145
|
fi
|
|
146
146
|
# release-handoff 는 brief 입력이 없다 (prepare 가 검증 보고서 인용 input 을
|
|
147
147
|
# 생성) — task-type autofill 이후에 판정해야 하므로 순서가 위와 바뀌면 안 된다.
|
|
148
|
-
|
|
148
|
+
# discussion 의 매니페스트 brief 는 prepare 가 만든 입력 문서라 다시 넘기지 않는다.
|
|
149
|
+
if [[ -z "$BRIEF_PATH" && -n "$manifest_brief" && "$TASK_TYPE" != "release-handoff" \
|
|
150
|
+
&& "$TASK_TYPE" != "discussion" ]]; then
|
|
149
151
|
BRIEF_PATH="$manifest_brief"
|
|
150
152
|
printf 'autofill: brief-path from task-manifest.json: %s\n' "$BRIEF_PATH" >&2
|
|
151
153
|
fi
|
|
@@ -159,7 +161,11 @@ missing_required_arguments_summary() {
|
|
|
159
161
|
[[ -z "$TASK_ID" ]] && missing+=("<task-id>")
|
|
160
162
|
[[ -z "$TASK_TYPE" ]] && missing+=("<task-type>")
|
|
161
163
|
# release-handoff 는 brief 입력이 없다 — prepare 가 input 문서를 생성한다.
|
|
162
|
-
[[
|
|
164
|
+
if [[ "$TASK_TYPE" == "discussion" ]]; then
|
|
165
|
+
[[ -z "$DISCUSSION_START" ]] && missing+=("<discussion-start>")
|
|
166
|
+
elif [[ -z "$BRIEF_PATH" && "$TASK_TYPE" != "release-handoff" ]]; then
|
|
167
|
+
missing+=("<brief-path>")
|
|
168
|
+
fi
|
|
163
169
|
|
|
164
170
|
if (( ${#missing[@]} == 0 )); then
|
|
165
171
|
printf '%s' ""
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
usage() {
|
|
4
4
|
cat >&2 <<USAGE_EOF
|
|
5
5
|
usage:
|
|
6
|
-
$DISPLAY_COMMAND_NAME [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--role-count <role>=<N>] [--role-model <role>=<provider/model>] [--lead-provider <provider>] [--lead-model <model>] [--worker-model provider=model,...] [--report-writer-provider <provider>] [--report-writer-model <model>] [--lead-runtime <host-id-or-alias>] [--executor claude|codex|antigravity] [--critic off|claude|codex|antigravity|grok|kimi] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
|
|
6
|
+
$DISPLAY_COMMAND_NAME [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--role-count <role>=<N>] [--role-model <role>=<provider/model>] [--lead-provider <provider>] [--lead-model <model>] [--worker-model provider=model,...] [--report-writer-provider <provider>] [--report-writer-model <model>] [--lead-runtime <host-id-or-alias>] [--executor claude|codex|antigravity] [--critic off|claude|codex|antigravity|grok|kimi] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> (--task-brief <brief-path> | --discussion-start <start-input>) [--directive <directive>] [--fix-cycle <yes|no>]
|
|
7
7
|
|
|
8
8
|
summary:
|
|
9
9
|
$DISPLAY_TOOL_NAME prepares a task-keyed instruction bundle. The standalone launcher defaults to an interactive Claude session; in-host skills keep the current registered host session as the native lead.
|
|
@@ -23,6 +23,8 @@ required arguments:
|
|
|
23
23
|
--task-id Stable task identifier inside the task group. Example: login-error-analysis
|
|
24
24
|
--task-type Task type. Also selects prompts/profiles/<task-type>.md
|
|
25
25
|
--task-brief Task brief file path. Relative paths are resolved against the target project root.
|
|
26
|
+
--discussion-start Replaces --task-brief for --task-type discussion: a ticket or project link, a short
|
|
27
|
+
description, or this task's key to continue its discussion in a new research cycle.
|
|
26
28
|
|
|
27
29
|
optional arguments:
|
|
28
30
|
--project-root Absolute path to the target project root. Resolution order when omitted:
|
package/runtime/bin/okstra.sh
CHANGED
|
@@ -185,6 +185,7 @@ okstra execution summary:
|
|
|
185
185
|
task group: ${TASK_GROUP}
|
|
186
186
|
task id: ${TASK_ID}
|
|
187
187
|
task brief: ${BRIEF_PATH}
|
|
188
|
+
discussion start: ${DISCUSSION_START:-None}
|
|
188
189
|
directive: ${DIRECTIVE:-None}
|
|
189
190
|
clarification response: ${CLARIFICATION_RESPONSE_PATH:-None}
|
|
190
191
|
selected direction: ${SELECTED_DIRECTION_PATH:-None}
|
|
@@ -216,8 +217,13 @@ PY_ARGS=(
|
|
|
216
217
|
--task-group "$TASK_GROUP"
|
|
217
218
|
--task-id "$TASK_ID"
|
|
218
219
|
--task-type "$TASK_TYPE"
|
|
219
|
-
--task-brief "$BRIEF_PATH"
|
|
220
220
|
)
|
|
221
|
+
# discussion 은 brief 를 받지 않는다(prepare 가 --task-brief 를 거부한다) — 시작 입력이 그 자리다.
|
|
222
|
+
if [[ "$TASK_TYPE" == "discussion" ]]; then
|
|
223
|
+
PY_ARGS+=(--discussion-start "$DISCUSSION_START")
|
|
224
|
+
else
|
|
225
|
+
PY_ARGS+=(--task-brief "$BRIEF_PATH")
|
|
226
|
+
fi
|
|
221
227
|
[[ -n "${DIRECTIVE-}" ]] && PY_ARGS+=(--directive "$DIRECTIVE")
|
|
222
228
|
[[ -n "${FIX_CYCLE-}" ]] && PY_ARGS+=(--fix-cycle "$FIX_CYCLE")
|
|
223
229
|
for role_count in "${ROLE_COUNTS[@]-}"; do
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"id": "discussion-worker",
|
|
4
|
+
"roleId": "analyser",
|
|
5
|
+
"responsibilities": [
|
|
6
|
+
"Research the discussion subject and hand the user and the other participants the evidence, assumptions and alternative directions the decision depends on, without making the decision that belongs to the user."
|
|
7
|
+
],
|
|
8
|
+
"requiredConduct": [
|
|
9
|
+
"Address the subject and the question the facilitator confirmed with the user; read the shared discussion record, including every user answer, before researching; give each claim its evidence location; state each alternative's requirements, exclusions and risks; and name the questions only the user can answer."
|
|
10
|
+
],
|
|
11
|
+
"decisionPrinciples": [
|
|
12
|
+
"Separate factual claims about the code or external sources, which evidence can settle, from intent, scope, priority and acceptable risk, which only the user can settle. Resolve by inspection what the evidence answers and raise the rest as a user question. Prefer a falsifiable claim over a broad impression."
|
|
13
|
+
],
|
|
14
|
+
"authorityAndBoundaries": [
|
|
15
|
+
"Research and argue; do not decide. Do not edit sources, write implementation plans or file-change specifications, or start follow-up tasks. A user decision recorded in the discussion record stands until the user changes it."
|
|
16
|
+
],
|
|
17
|
+
"evidenceStandards": [
|
|
18
|
+
"Support each factual claim with evidence that bears on it directly and cite the inspected location. State when evidence is indirect, incomplete or contradicted, and what the research could not reach."
|
|
19
|
+
],
|
|
20
|
+
"collaborationContract": [
|
|
21
|
+
"Reason independently and keep a minority position whose evidence is stronger. When another participant's claim is refuted or confirmed, say by which evidence. Leave cross-participant synthesis and the questions put to the user to the facilitator."
|
|
22
|
+
],
|
|
23
|
+
"completionCriteria": [
|
|
24
|
+
"The confirmed question has an explicit answer or a stated reason why it has none; every factual claim carries evidence; every alternative carries its requirements, exclusions and risks; and every open user question is listed."
|
|
25
|
+
],
|
|
26
|
+
"prohibitions": [
|
|
27
|
+
"Do not present an assumption as a fact, infer the user's intent from a fact check, repeat an earlier claim as new without new evidence, or drop a contested position to reach consensus."
|
|
28
|
+
],
|
|
29
|
+
"blockedStateReporting": [
|
|
30
|
+
"Name the evidence or input that was unavailable, what was attempted, and which part of the question therefore has no researched answer."
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -49,9 +49,8 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
|
|
|
49
49
|
| Setting | Default | Description |
|
|
50
50
|
|------|--------|------|
|
|
51
51
|
| `enabled` | `true` | If `false`, skip the convergence loop and use the existing consensus/divergence method |
|
|
52
|
-
| `maxRounds` |
|
|
53
|
-
| `
|
|
54
|
-
| `adversarial` | phase-aware: `true` for `requirements-discovery` / `error-analysis` / `implementation-option-selection` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`, `false` otherwise | When `true`, Phase 5.5 runs in **adversarial mode** (see §"Adversarial Verification Mode"): verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is forced to `"full-reanalysis"` scoped to the finding's cited evidence. Resolved by `scripts/okstra_ctl/render.py` `_build_convergence_block` and recorded in `config.adversarial` of the convergence state artifact. |
|
|
52
|
+
| `maxRounds` | `2` (range 1–3) | Maximum number of re-verification rounds. A second round runs only for a finding whose first round produced no usable vote or a tie the critic decides (§"Adversarial classification"); with `1`, a tie is `contested`. Lead resolves the effective value when the manifest omits the key and records it in `config.effectiveMaxRounds` of the convergence state artifact. |
|
|
53
|
+
| `adversarial` | `true` | Always `true`: every phase runs Phase 5.5 in **adversarial mode** (see §"Adversarial Verification Mode") — verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is `"full-reanalysis"` scoped to the finding's cited evidence. `okstra convergence prepare-groups` writes both values regardless of the task manifest, and `seed` refuses a grouping with `adversarial: false` (**Enforced:** `_parse_config` in `scripts/okstra_ctl/convergence_engine.py`). |
|
|
55
54
|
|
|
56
55
|
**Auto-disable rule (BLOCKING).** Convergence requires ≥2 analyser workers to produce a meaningful consensus tally. When the run's selected roster (`resultContract.workerRoles`) holds fewer than 2 analyser workers — the user selected one analyser, or `release-handoff` (zero analyser workers, lead-only) — the lead MUST treat `convergence.enabled` as `false` for that run regardless of manifest configuration, skip Phase 5.5, and record `finalState: "converged"` with `totalRounds: 0`, `round2SkippedReason: "auto-disabled"`, an empty `roundHistory`, and an explanatory note in `config` (e.g. `"autoDisabled": "fewer-than-two-analysers"`). An `implementation-planning` run with one planner still runs the plan-body verification round; that planner's single vote settles each item (`plan-body-verification` (the absolute path in **Okstra Runtime Resources**), "A one-analyser roster"). **Enforced:** `scripts/okstra_ctl/convergence_engine.py` `seed_working_state` stops with `auto-disabled` below two analysis workers, and `validators/validate-run.py` `single_verifier_advisories` records the missing cross-model check as an advisory.
|
|
57
56
|
|
|
@@ -81,7 +80,7 @@ Nothing about the census stops a run. A missing cell, a missing result, a single
|
|
|
81
80
|
|
|
82
81
|
## Convergence Algorithm
|
|
83
82
|
|
|
84
|
-
**Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the
|
|
83
|
+
**Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the counted votes for that finding: the verifiers' non-error votes plus the origin worker's implicit SURVIVES (`verification-error` votes are excluded). A tie is NOT a majority: it goes to the critic when one is rostered and is `contested` otherwise (§"Adversarial classification"). The plan-body verification round (`plan-body-verification` (the absolute path in **Okstra Runtime Resources**)) keeps its own rule, which counts only verifier votes.
|
|
85
84
|
|
|
86
85
|
**Enforced:** the engine owns the classifier and replays it — `scripts/okstra_ctl/convergence_engine.py` `validate_final_state` recomputes each finding's expected final classification from its recorded votes and rejects the state when the persisted value differs, and `finalize` runs that same check before it writes, so a tie scored as a consensus never reaches the artifact. `okstra convergence validate` is the same call on demand. Nothing re-derives the majority rule outside the engine; a second implementation would only drift from it.
|
|
87
86
|
|
|
@@ -129,7 +128,7 @@ Follow this protocol exactly:
|
|
|
129
128
|
1. Run `okstra convergence seed --groups <groups> --run-manifest <current-run-manifest> --work-state <work> --final-state <final> --migration-dir <state/migrations>` for a v2 worker roster. `--run-manifest` must be the exact current manifest named by the groups document's `runManifestPath`; a previous run from the same task is not interchangeable. `seed` refuses a grouping that cites a worker whose initial attempt the mutation audit discarded (`contract-failed-unattributed`, `mutation-present-unresolved`) — the result file is still on disk, but the ledger says it was never accepted. Re-dispatch that worker as a new invocation and cite the new result, or leave it out of the grouping. **Enforced:** `discarded_worker_errors` in `scripts/okstra_ctl/convergence_provenance.py`, called by `_seed`. Omit the flag for a legacy v1 roster. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
|
|
130
129
|
2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state. Its stdout JSON carries `round` and `inputQueueSize`; on a `dispatch` plan the round's `PROGRESS: phase-5.5-convergence round=<N> queue=<count>` line copies those two values — never count the queue by hand. **Enforced:** `_plan_round` in `scripts/okstra_ctl/convergence.py`, `tests/run/test_convergence_cli.py`.
|
|
131
130
|
3. Every plan carries `dispatchable`: `true` on an `action: "dispatch"` plan, `false` on an `action: "finalize"` plan. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
|
|
132
|
-
4. Run `okstra convergence collect-results --plan <round-plan> --
|
|
131
|
+
4. Run `okstra convergence collect-results --plan <round-plan> --result <worker>=<path>… --run-manifest <current-run-manifest> --output convergence-round-<N>-results-<task-type>-<seq>.json`. Each planned worker's terminal outcome and `durationMs` come from the manifest's attempt ledger, not from the lead: an attempt still `started` refuses the collect — run `okstra team await` first so the attempt closes — and a worker whose attempt did not close `ok` cannot contribute a vote even if its result file exists. **Enforced:** `_dispatch_rows_from_manifest` in `scripts/okstra_ctl/convergence.py`. Then run `okstra convergence apply-round --work-state <work> --plan <round-plan> --results <round-results>`.
|
|
133
132
|
5. Repeat `plan-round` and `apply-round` until the plan action is `finalize`. A `finalize` plan is **never dispatched**: it carries `dispatchable: false`, an empty `dispatches[]`, and a `note` naming the next command. Its `round` is only the `<N>` in `convergence-round-<N>-plan-<task-type>-<seq>.json` — not a round to run — so do not open reverify workers for it. **Enforced:** `okstra convergence apply-round` refuses a non-dispatch plan and returns the gate's closing reason with the remedy (`scripts/okstra_ctl/convergence_engine.py` `apply_round_results`).
|
|
134
133
|
6. When the coverage critic is enabled, convert its verification batch to the canonical `dispatches[]` / `gaps[]` result shape and run `okstra convergence apply-critic-gaps --work-state <work> --results <critic-results>` exactly once. The reducer, not the lead, merges verified gaps.
|
|
135
134
|
7. Run `okstra convergence finalize --work-state <work> --output <final>`.
|
|
@@ -175,17 +174,15 @@ The executable source is `scripts/okstra_ctl/convergence_engine.py`; `okstra con
|
|
|
175
174
|
|
|
176
175
|
## Verification Mode
|
|
177
176
|
|
|
178
|
-
|
|
177
|
+
Finding convergence always uses scoped full re-analysis: the verifier re-opens the evidence each finding cites (§"Adversarial Verification Mode" → Scoped full-reanalysis).
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
### Lightweight (plan-body verification only)
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
Use each finding as a guide but reanalyze the original code/data yourself. High accuracy at 2–3× the cost and time.
|
|
181
|
+
Decide solely on the findings and evidence other reviewers present; do not reanalyze the original code or data. Only the plan-body verification round uses this mode.
|
|
185
182
|
|
|
186
183
|
## Adversarial Verification Mode
|
|
187
184
|
|
|
188
|
-
|
|
185
|
+
Every phase runs in this mode. In adversarial mode the verifier's job inverts: instead of confirming a peer's finding, the verifier **tries to break it**, and the burden of proof sits on the claim — a finding survives only if refutation attempts fail.
|
|
189
186
|
|
|
190
187
|
### Read-only analysis task contract
|
|
191
188
|
|
|
@@ -219,33 +216,32 @@ Each `disagree` vote records a new field `disagreeBasis`:
|
|
|
219
216
|
|
|
220
217
|
A `disagree` with `disagreeBasis == null` is a contract violation in adversarial mode — every refutation must state which of the two grounds it rests on. **Enforced:** `_validate_adversarial_disagree_carries_a_basis` in `validators/validate-run.py` fails such a vote when `config.adversarial` is set; non-adversarial rounds are out of scope because their verdict semantics differ. Bare "I disagree" without re-inspection is not allowed. If capability, credential, network, or service state prevents that inspection, the verdict is `UNVERIFIABLE`, persisted as `verification-error`; verifier failure is never converted to `DISAGREE` or `burden-not-met`. A live/external claim without `evidenceArtifacts` remains schema-valid, but a verifier that needs the missing artifact and cannot independently access the source MUST answer `UNVERIFIABLE` with a non-empty explanation.
|
|
221
218
|
|
|
222
|
-
### Adversarial classification
|
|
219
|
+
### Adversarial classification
|
|
223
220
|
|
|
224
|
-
`verification-error` votes are excluded from
|
|
221
|
+
`verification-error` votes are excluded from the tally. The origin worker does not vote, but it holds its own finding, so the tally counts it as one SURVIVES. For each finding `F` in the queue at a round:
|
|
225
222
|
|
|
226
223
|
```text
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
IF
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
ELIF all_others_disagree:
|
|
235
|
-
resolve F as "worker-unique" # only the discoverer still holds it
|
|
236
|
-
ELIF len(hard_refutes) >= 1:
|
|
237
|
-
# an evidence-backed refute exists and the roster is split → the claim is disputed
|
|
224
|
+
votes = non-error votes of this round, plus the previous round's votes when F was tied there
|
|
225
|
+
disagrees = [v for v in votes if v.verdict == "disagree"]
|
|
226
|
+
survives = 1 + (len(votes) - len(disagrees)) # the 1 is the origin worker
|
|
227
|
+
|
|
228
|
+
IF votes is empty:
|
|
229
|
+
carry F forward; at the LAST executed round classify it "unverified"
|
|
230
|
+
ELIF any disagree has disagreeBasis == "counter-evidence":
|
|
238
231
|
resolve F as "contested" IN THIS ROUND; F leaves the queue
|
|
239
|
-
ELIF
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
resolve F as "
|
|
232
|
+
ELIF survives > len(disagrees):
|
|
233
|
+
resolve F as "full-consensus" when there is no disagree and SURVIVES-WITH-CAVEAT
|
|
234
|
+
votes are not a majority of votes; otherwise "partial-consensus"
|
|
235
|
+
ELIF len(disagrees) > survives:
|
|
236
|
+
resolve F as "worker-unique"
|
|
237
|
+
ELSE (tie):
|
|
238
|
+
critic rostered → carry F forward; the next round dispatches F to the critic only
|
|
239
|
+
no critic rostered → resolve F as "contested" IN THIS ROUND
|
|
244
240
|
```
|
|
245
241
|
|
|
246
|
-
|
|
242
|
+
The tie round's votes stay in the tally when the critic votes, so the critic's vote breaks the tie. Two analysers produce a tie whenever the one verifier refutes; three analysers produce a tie only when one verifier refutes and the other's vote is unusable. A tie whose critic round never ran (round limit) or returned no usable vote is `contested` at finalize. **Enforced:** `classify_finding_round` and `_finding_outcome` in `scripts/okstra_ctl/convergence_engine.py`; `plan_next_round` routes a tied finding to the worker named by `config.tieBreaker`, which `seed` sets to the grouping's single `critic`-audience worker or `null`.
|
|
247
243
|
|
|
248
|
-
Design intent: one `counter-evidence` refute denies a claim consensus
|
|
244
|
+
Design intent: one `counter-evidence` refute denies a claim consensus however many others SURVIVE. Because that refute is permanent, re-dispatching the finding buys nothing, so the refute settles the finding where it lands, and the finding is reported under `## 6.2 Differences`. A `burden-not-met` doubt is weighed by the majority. A tie is not sent back to the same analysers — they have no new information — but to the critic, a verifier outside the analyser panel. A caveat is weighed by majority: with zero disagrees, SURVIVES-WITH-CAVEAT lands partial-consensus only when caveats are a **majority** of the votes; the caveat is recorded in the dissent log either way.
|
|
249
245
|
|
|
250
246
|
## Re-verification Dispatch
|
|
251
247
|
|
|
@@ -266,7 +262,14 @@ materialize` with `--audience
|
|
|
266
262
|
reverification-worker`, `--assignment-ref reverify/<workerId>`,
|
|
267
263
|
`--source-role-execution-ref
|
|
268
264
|
<sourceRoleExecutionRef>`, the exact `--worker-id`, `--dispatch-kind
|
|
269
|
-
reverify-r<N>`, and the authorized prompt/result/audit paths.
|
|
265
|
+
reverify-r<N>`, and the authorized prompt/result/audit paths. A row whose
|
|
266
|
+
`worker` is `critic-worker` is a tie round: it takes the run's critic assignment
|
|
267
|
+
(`--assignment-ref critic/scope`, or `critic/acceptance` on `final-verification`)
|
|
268
|
+
instead of `reverify/<workerId>`, everything else unchanged, and its result is
|
|
269
|
+
named `critic-worker-reverify-r<N>-<task-type>-<seq>.md`. **Enforced:**
|
|
270
|
+
`okstra_ctl.worker_prompt_policy.is_critic_verification` admits that pair, and
|
|
271
|
+
`convergence_store._validate_dynamic_verifier_request` admits a critic source
|
|
272
|
+
only for a plan-body or finding re-verification round. Do not derive the
|
|
270
273
|
source role execution from `workerId`, provider, model, or execution-label
|
|
271
274
|
text. A legacy v1 run omits `--source-role-execution-ref`; the materializer
|
|
272
275
|
keeps its legacy read-only identity resolution for that schema version. The
|
|
@@ -374,12 +377,9 @@ Reverify prompts MUST NOT inject the Phase 2 `[Required reading]` clause:
|
|
|
374
377
|
|
|
375
378
|
**Enforced:** `_validate_reverify_prompt_suppresses_required_reading` in `validators/validate-run.py` fails any `*-reverify-r*.md` prompt containing the clause.
|
|
376
379
|
|
|
377
|
-
|
|
380
|
+
A reverify does not require the original `analysis-packet.md`, `analysis-profile.md`, `task-brief.md`, or instruction set. Its complete input is the receiving worker's current engine-planned `findingIds` batch, the evidence those findings cite, and the origin item and audit sidecar the rendered prompt names. Including the clause forces workers to re-read the entire instruction-set per round per worker (3 workers × 2 rounds × 5+ files in the worst case) for no quality gain.
|
|
378
381
|
|
|
379
|
-
|
|
380
|
-
- **Full-reanalysis mode**: workers DO need to re-read source materials, but only the analysis-worker file list (no `final-report-template.md`). If lead chooses to inject a reading clause here, it MUST mirror the audience-scoped enumeration in [okstra-lead-contract](./okstra-lead-contract.md) Phase 2 (no template).
|
|
381
|
-
|
|
382
|
-
This is the single largest avoidable cost in `requirements-discovery`, `error-analysis`, `implementation-option-selection`, and `implementation-planning` runs. Treat as mandatory.
|
|
382
|
+
This is the single largest avoidable cost in convergence. Treat as mandatory.
|
|
383
383
|
|
|
384
384
|
## Conditional reference reading
|
|
385
385
|
|
|
@@ -390,61 +390,17 @@ avoiding unused examples; queue and dispatch validators still enforce execution.
|
|
|
390
390
|
|
|
391
391
|
| Current operation | Additional section to read |
|
|
392
392
|
|---|---|
|
|
393
|
-
| Diagnosing a rejected verifier prompt |
|
|
393
|
+
| Diagnosing a rejected verifier prompt | Adversarial Re-verification Prompt |
|
|
394
394
|
| `config.critic.enabled` is true | Coverage critic pass, including its shared dispatch procedure |
|
|
395
395
|
| An enabled critic uses acceptance mode for `final-verification` | Acceptance critic pass as well as the shared Coverage critic dispatch procedure |
|
|
396
396
|
|
|
397
397
|
Read the matching sections before their dispatch. A disabled critic needs neither
|
|
398
|
-
critic section. Generated prompts
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
### Lightweight Re-verification Prompt
|
|
402
|
-
|
|
403
|
-
Rendered by `okstra convergence reverify-prompt` when `config.adversarial` is false; the block below is the reference shape, and the rendered body additionally carries each finding's `**Origin item**` and `**Origin audit sidecar**` lines.
|
|
404
|
-
|
|
405
|
-
```
|
|
406
|
-
## Instructions
|
|
407
|
-
|
|
408
|
-
**Rendered by:** okstra convergence reverify-prompt
|
|
409
|
-
|
|
410
|
-
Perform re-verification for <task-key> (round <N>).
|
|
411
|
-
|
|
412
|
-
Review the following findings discovered by other workers.
|
|
413
|
-
For EACH finding, respond with exactly one verdict:
|
|
414
|
-
|
|
415
|
-
- **AGREE**: The finding is valid based on the evidence presented
|
|
416
|
-
- **DISAGREE**: The finding is incorrect or unsupported (explain briefly why)
|
|
417
|
-
- **SUPPLEMENT**: The finding is valid AND you have additional supporting evidence or context
|
|
418
|
-
- **UNVERIFIABLE**: Capability, credential, network, or service state prevents you
|
|
419
|
-
from checking this finding. Explain the unavailable capability; do not substitute DISAGREE.
|
|
420
|
-
|
|
421
|
-
Do NOT re-analyze the original source materials. Judge based on the evidence provided.
|
|
422
|
-
|
|
423
|
-
## Findings to verify
|
|
424
|
-
|
|
425
|
-
### F-001: <one-line summary>
|
|
426
|
-
**Origin**: <worker role>
|
|
427
|
-
**Evidence**: <file paths, line numbers, reasoning from origin worker>
|
|
428
|
-
|
|
429
|
-
### F-002: <one-line summary>
|
|
430
|
-
**Origin**: <worker role>
|
|
431
|
-
**Evidence**: <...>
|
|
432
|
-
|
|
433
|
-
## Response format
|
|
434
|
-
|
|
435
|
-
For each finding, respond as:
|
|
436
|
-
|
|
437
|
-
### F-001
|
|
438
|
-
**Verdict**: AGREE | DISAGREE | SUPPLEMENT | UNVERIFIABLE
|
|
439
|
-
**Explanation** (required for every verdict, AGREE included): <2-3 sentences>
|
|
440
|
-
|
|
441
|
-
### F-002
|
|
442
|
-
**Verdict**: ...
|
|
443
|
-
```
|
|
398
|
+
critic section. Generated prompts carry the adversarial instructions even when
|
|
399
|
+
the lead does not read the example text.
|
|
444
400
|
|
|
445
401
|
### Adversarial Re-verification Prompt
|
|
446
402
|
|
|
447
|
-
|
|
403
|
+
The reverify prompt for every phase. Rendered by `okstra convergence reverify-prompt`; the block below is the reference shape, and the rendered body additionally carries each finding's `**Origin item**` and `**Origin audit sidecar**` lines. The required anchor headers (§"Required reverify-prompt anchor headers") are identical. The `[Required reading]` clause is suppressed; only the cited-evidence paths of the items under attack are injected (see §"Adversarial Verification Mode" → Scoped full-reanalysis).
|
|
448
404
|
|
|
449
405
|
```
|
|
450
406
|
## Instructions
|
|
@@ -504,50 +460,6 @@ When persisting votes, map SURVIVES→`agree`, SURVIVES-WITH-CAVEAT→`supplemen
|
|
|
504
460
|
|
|
505
461
|
UNVERIFIABLE is **not** `verification-error`. A verifier that opened the evidence and could not check it participated in the round; a `verification-error` is a verifier that failed to answer. The classifier counts only non-error votes, so folding the two shrinks the participating roster without saying so — a round where one analyser answers UNVERIFIABLE throughout would read as a two-way cross-check while reporting three voters. **Enforced:** `okstra convergence collect-results` applies this mapping (`okstra_ctl.verdict_blocks.ADVERSARIAL_VERDICTS`); do not transcribe votes by hand.
|
|
506
462
|
|
|
507
|
-
### Full Re-analysis Re-verification Prompt
|
|
508
|
-
|
|
509
|
-
```
|
|
510
|
-
## Instructions
|
|
511
|
-
|
|
512
|
-
**Rendered by:** okstra convergence reverify-prompt
|
|
513
|
-
|
|
514
|
-
Perform deep re-verification for <task-key> (round <N>).
|
|
515
|
-
|
|
516
|
-
Independently verify the following findings by examining the original materials.
|
|
517
|
-
Use each finding as a starting point, NOT as a confirmed conclusion.
|
|
518
|
-
If capability, credential, network, or service state prevents access to evidence
|
|
519
|
-
needed for a finding, you MUST answer UNVERIFIABLE with a non-empty explanation.
|
|
520
|
-
You MUST NOT answer DISAGREE merely because access or reproduction failed;
|
|
521
|
-
UNVERIFIABLE is persisted as `verification-error` and excluded from consensus.
|
|
522
|
-
|
|
523
|
-
**Enforced:** `scripts/okstra_ctl/convergence_engine.py` `_VERDICTS` accepts the verdict as `verification-error`, and `classify_collaborative_round` drops those votes before counting consensus.
|
|
524
|
-
|
|
525
|
-
## Task bundle paths
|
|
526
|
-
- Instruction set: <instruction-set path>
|
|
527
|
-
- Task brief: <task-brief path>
|
|
528
|
-
- Analysis material: <analysis-material path>
|
|
529
|
-
- Reference expectations: <reference-expectations path>
|
|
530
|
-
|
|
531
|
-
## Findings to verify
|
|
532
|
-
|
|
533
|
-
### F-001: <one-line summary>
|
|
534
|
-
**Origin**: <worker role>
|
|
535
|
-
**Original evidence**: <file paths, line numbers>
|
|
536
|
-
|
|
537
|
-
### F-002: <one-line summary>
|
|
538
|
-
**Origin**: <worker role>
|
|
539
|
-
**Original evidence**: <...>
|
|
540
|
-
|
|
541
|
-
## Response format
|
|
542
|
-
|
|
543
|
-
For each finding:
|
|
544
|
-
|
|
545
|
-
### F-001
|
|
546
|
-
**Verdict**: AGREE | DISAGREE | SUPPLEMENT | UNVERIFIABLE
|
|
547
|
-
**Your evidence**: <your independent evidence trail with file paths and line numbers>
|
|
548
|
-
**Explanation**: <detailed analysis>
|
|
549
|
-
```
|
|
550
|
-
|
|
551
463
|
## Convergence State Artifact
|
|
552
464
|
|
|
553
465
|
Save it to `runs/<task-type>/state/convergence-<task-type>-<seq>.json`.
|
|
@@ -558,10 +470,10 @@ Save it to `runs/<task-type>/state/convergence-<task-type>-<seq>.json`.
|
|
|
558
470
|
"taskKey": "<task-key>",
|
|
559
471
|
"config": {
|
|
560
472
|
"enabled": true,
|
|
561
|
-
"adversarial":
|
|
473
|
+
"adversarial": true,
|
|
562
474
|
"maxRounds": 2,
|
|
563
475
|
"effectiveMaxRounds": 2,
|
|
564
|
-
"verificationMode": "
|
|
476
|
+
"verificationMode": "full-reanalysis"
|
|
565
477
|
},
|
|
566
478
|
"findings": [
|
|
567
479
|
{
|
|
@@ -616,9 +528,9 @@ Save it to `runs/<task-type>/state/convergence-<task-type>-<seq>.json`.
|
|
|
616
528
|
|
|
617
529
|
Schema rules:
|
|
618
530
|
|
|
619
|
-
- `schemaVersion`: literal string `"1.4"` for all new runs
|
|
531
|
+
- `schemaVersion`: literal string `"1.4"` for all new runs. Historical readers accept `"1.0"` / `"1.1"` / `"1.2"` / `"1.3"` unchanged and never rewrite those artifacts during validation. v1.3 added the strict coverage-critic ledger and the rejection of unknown top-level fields; v1.4 adds the `unverified` classification and its `finalClassificationCounts.unverified` key. Work-state remains v1.0.
|
|
620
532
|
- `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis` / `implementation-option-selection` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
|
|
621
|
-
- `config.effectiveMaxRounds`: the integer the lead actually used after resolving the
|
|
533
|
+
- `config.effectiveMaxRounds`: the integer the lead actually used after resolving the default (`2`). It may be lower than `config.maxRounds` — a phase-aware default that resolves below the manifest ceiling is the normal case — but never higher. **Enforced:** `scripts/okstra_ctl/convergence_engine.py` `_parse_config` raises `ConvergenceContractError` on `effectiveMaxRounds > maxRounds`, so a state carrying that pair cannot be loaded, and `plan_next_round` finalizes with reason `max-rounds-reached` once the executed rounds reach the effective budget rather than dispatching another one. `totalRounds` is therefore bounded by construction, not by a later re-count.
|
|
622
534
|
- `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). It is empty when the phase does not require ticket tagging; `"unknown"` is not a ticket key and must not be synthesized.
|
|
623
535
|
- `findings[].rounds[].votes.<worker>.verdict`: enum, one of `agree | disagree | supplement | verification-error`. Lower-case tokens; map upper-case AGREE/DISAGREE/SUPPLEMENT verdicts emitted by workers to their lower-case form and map the input alias `unverifiable` to persisted `verification-error`. The latter represents either a terminal non-result dispatch or a completed dispatch that could not verify a particular finding (§"Worker failure handling in reverify"). Every vote has a non-empty `explanation`.
|
|
624
536
|
- `findings[].rounds[].votes.<worker>.disagreeBasis`: enum `counter-evidence | burden-not-met | null`. Non-null only when `verdict == "disagree"` AND `config.adversarial == true`; `null` (or absent, treated as null) otherwise. See §"Adversarial Verification Mode".
|
|
@@ -635,7 +547,7 @@ Schema rules:
|
|
|
635
547
|
|
|
636
548
|
## Coverage critic pass
|
|
637
549
|
|
|
638
|
-
Runs when `convergence.critic.enabled == true`. Critic is opt-in on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification` (role `min` 0, `recommended`/`max` 1; the user chooses whether to add the slot and picks the model). A run without a critic slot renders `enabled: false` and skips this pass. For `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **scope in both directions** — findings that are missing (coverage) and work the findings propose that no requirement asked for (over-scope) — distinct from convergence, which targets **agreement quality** among the findings already raised. The pass keeps its `coverage` mode id and `gaps` vocabulary for both halves; the two are told apart by each candidate's `category`, so no schema or reducer distinguishes them. In `implementation-planning` the same critic slot also settles plan-body analyser 1-1 splits as `critic-worker
|
|
550
|
+
Runs when `convergence.critic.enabled == true`. Critic is opt-in on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification` (role `min` 0, `recommended`/`max` 1; the user chooses whether to add the slot and picks the model). A run without a critic slot renders `enabled: false` and skips this pass. For `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **scope in both directions** — findings that are missing (coverage) and work the findings propose that no requirement asked for (over-scope) — distinct from convergence, which targets **agreement quality** among the findings already raised. The pass keeps its `coverage` mode id and `gaps` vocabulary for both halves; the two are told apart by each candidate's `category`, so no schema or reducer distinguishes them. In `implementation-planning` the same critic slot also settles plan-body analyser 1-1 splits as `critic-worker`, and in every phase it breaks finding ties as `critic-worker` (§"Adversarial classification").
|
|
639
551
|
|
|
640
552
|
### When
|
|
641
553
|
|
|
@@ -741,14 +653,15 @@ The asymmetry is deliberate and runs the opposite way from the coverage half: a
|
|
|
741
653
|
|
|
742
654
|
The `final-verification` phase uses the same fresh one-shot `redispatch_worker` pattern and the same dispatch timing as §"Coverage critic pass" §"When" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; critic is opt-in — the pass is skipped when the run resolved no critic; same model-unresolved skip rule) — the delivered work the critic inspects is likewise fixed before the reverify round starts. Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
743
655
|
|
|
744
|
-
Before that call, write the
|
|
656
|
+
Before that call, write the output of `okstra convergence critic-prompt
|
|
657
|
+
--run-manifest <run-manifest>` verbatim as the task instructions and run `okstra
|
|
745
658
|
agent-prompt materialize` with `--audience acceptance-critic`,
|
|
746
659
|
`--assignment-ref critic/acceptance`, `--worker-id acceptance` (the run-input
|
|
747
660
|
Worker Roster line), and `--dispatch-kind critic`. **The final-verification 96-nonblank-line prompt-body
|
|
748
661
|
cap applies to this dispatch too** — `resolve_prompt_plan` exempts a critic from
|
|
749
662
|
the analysis equality group, not from the cap. The anchors and the duty contract
|
|
750
663
|
account for roughly 51 of those lines, so the instructions you write have a
|
|
751
|
-
budget of about 45 nonblank lines; the
|
|
664
|
+
budget of about 45 nonblank lines; the rendered body omits the Round 0 list to fit. Adding
|
|
752
665
|
your own restatement of the run's findings on top of it is what overruns the cap
|
|
753
666
|
(measured 117 on 2026-09-10, dev-10642-15 final-verification 001). Verify the returned `metadataPath`, dispatch only the
|
|
754
667
|
returned `promptPath`, and select `hostModelValue` for a native host or
|
|
@@ -758,6 +671,8 @@ delivery was observed.
|
|
|
758
671
|
|
|
759
672
|
### Prompt
|
|
760
673
|
|
|
674
|
+
The mandate `critic-prompt` renders for `final-verification` (it adds the delivery-mode header, the analysis packet line, the analyser result lines and the `### [AC-NNN]` candidate format):
|
|
675
|
+
|
|
761
676
|
```
|
|
762
677
|
Challenge acceptance for <task-key>. The delivered work is about
|
|
763
678
|
to be judged for acceptance. Your ONLY job is to find reasons it should NOT be
|
|
@@ -776,6 +691,13 @@ so explicitly.
|
|
|
776
691
|
### Verification — confirm-or-downgrade (BLOCKING)
|
|
777
692
|
|
|
778
693
|
Each candidate blocker is verified by the Phase 4 analysers — all of them, on the same roster rule as §"critic gaps" above; sharing the critic's provider name does not disqualify an analyser. Do NOT use the adversarial finding classifier's "uncertain → reject" rule here.
|
|
694
|
+
|
|
695
|
+
1. Write the candidates as `{schemaVersion, taskKey, mode: "acceptance-devils-advocate", provider, modelExecutionValue, candidates[]}` with one `{candidateId, statement, severity, originEvidence}` per candidate, `candidateId` being the id in the critic's candidate heading (`### [AC-001]` or a plain `### AC-001 — …` from a critic dispatched before the rendered format).
|
|
696
|
+
2. For each analyser, render `okstra convergence critic-verify-prompt --run-manifest <run-manifest> --gaps <that batch> --worker <worker>` and materialize it as a critic-gap verification row (`--dispatch-kind critic-verify`, `--assignment-ref reverify/<worker>`).
|
|
697
|
+
3. Run `okstra convergence collect-acceptance-votes --run-manifest <run-manifest> --candidates <that batch> --result <worker>=<path>… --output <acceptance-batch>`; it marks a candidate `confirmed` when any analyser answers SURVIVES or SURVIVES-WITH-CAVEAT and `downgraded` otherwise.
|
|
698
|
+
4. Pass that output to `apply-acceptance-critic` (§State).
|
|
699
|
+
|
|
700
|
+
**Enforced:** `okstra_ctl.convergence_critic_verify_prompt.acceptance_verify_prompt_body` renders the instructions under the `critic-verify` signature that `validate_reverify_prompt` requires, and `convergence._collect_acceptance_votes` derives each verdict from the parsed votes.
|
|
779
701
|
- Do NOT run `apply-critic-gaps` for this mode. That reducer implements coverage merge/drop semantics and rejects `acceptance-devils-advocate` input.
|
|
780
702
|
- **Confirmed** (an analyser reproduces it or cites supporting evidence) → promote to a `## 5.8 Acceptance Blockers` row (keep severity + recommended follow-up phase).
|
|
781
703
|
- **Not confirmed** (cannot reproduce, or evidence is weak) → **downgrade to a Residual Risk row — never drop it.** Record the escalation trigger so the user can re-judge a high-severity-but-unconfirmed candidate.
|