okstra 0.186.4 → 0.186.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/architecture.md +1 -1
- package/docs/cli.md +3 -3
- package/docs/for-ai/skills/okstra-user-response.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-render-report-views.py +6 -5
- package/runtime/prompts/launch.template.md +14 -0
- package/runtime/prompts/lead/convergence.md +2 -2
- package/runtime/prompts/lead/okstra-lead-contract.md +4 -14
- package/runtime/prompts/lead/plan-body-verification.md +4 -3
- package/runtime/prompts/lead/report-writer.md +3 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +2 -2
- package/runtime/prompts/wizard/prompts.ko.json +2 -4
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -5
- package/runtime/python/okstra_ctl/agent_activity.py +6 -0
- package/runtime/python/okstra_ctl/clarification_items.py +67 -11
- package/runtime/python/okstra_ctl/next_phase.py +6 -3
- package/runtime/python/okstra_ctl/plan_items.py +32 -6
- package/runtime/python/okstra_ctl/plan_items_cli.py +57 -3
- package/runtime/python/okstra_ctl/render_final_report.py +3 -1
- package/runtime/python/okstra_ctl/report_assembly.py +65 -3
- package/runtime/python/okstra_ctl/report_html/run_usage.py +5 -1
- package/runtime/python/okstra_ctl/report_projections.py +45 -4
- package/runtime/python/okstra_ctl/run.py +21 -6
- package/runtime/python/okstra_ctl/usage_cells.py +15 -0
- package/runtime/python/okstra_ctl/user_response.py +72 -6
- package/runtime/python/okstra_ctl/wizard.py +1 -5
- package/runtime/python/okstra_token_usage/codex.py +32 -3
- package/runtime/python/okstra_token_usage/collect.py +148 -15
- package/runtime/python/okstra_token_usage/grok.py +24 -5
- package/runtime/python/okstra_token_usage/report.py +12 -2
- package/runtime/skills/okstra-user-response/SKILL.md +4 -2
- package/runtime/templates/reports/html/assets/base.css +3 -9
- package/runtime/templates/reports/html/assets/base.js +0 -21
- package/runtime/templates/reports/html/base.template.html +1 -4
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +9 -28
- package/runtime/validators/lib/runners.sh +5 -1
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +240 -102
- package/runtime/validators/validate_session_conformance.py +71 -18
package/docs/architecture.md
CHANGED
|
@@ -867,7 +867,7 @@ The manifest-provided `lead-events-*.jsonl` file is the canonical record for str
|
|
|
867
867
|
|
|
868
868
|
The shared `okstra report-finalize` entrypoint reads canonical activity before translation source checking. Under contract v3 its in-process `project-activity` step assembles the report, filtering events by run identity and validating activity ID order before `agentActivity[]` is published. A schema-invalid composed record is still published so `validate-run` can scan it; the step itself fails. A historical v2 manifest retains the in-place projection path. A failed Phase 7 step does not skip later checks: `validate-run` still runs, and only `teardown-stages` is skipped. For a non-English report, the lead runs `token-usage`, `project-activity`, and `check-source` before translator dispatch, then resumes at `render-views` after the translation sidecar exists. Conformance compares the resulting `agentActivity[]` IDs, order, and core fields with the canonical events for every lead host.
|
|
869
869
|
|
|
870
|
-
Approval blockers use `open`, `answered`, `resolved`, and `obsolete`. `open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block approval or the next phase; the DISAGREE votes stay on the plan item as evidence. A response sidecar with a proceeding disposition unblocks the same way. `request-revision` and `reject` still withhold the next phase.
|
|
870
|
+
Approval blockers use `open`, `answered`, `resolved`, and `obsolete`. `open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block approval or the next phase; the DISAGREE votes stay on the plan item as evidence. A response sidecar with a proceeding disposition unblocks the same way. `request-revision` and `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`).
|
|
871
871
|
|
|
872
872
|
An approval row classifies its cause as `user-decision`, `noncritical-dissent`, or `correctness-critical`. `accept-risk` is available to all three: it ends the gate and keeps the dissent on the record for later stages. `correctness-critical` can still take `request-revision` when the user wants the plan corrected and re-verified.
|
|
873
873
|
|
package/docs/cli.md
CHANGED
|
@@ -64,7 +64,7 @@ The Node CLI requires Node.js 22 or newer. Its TypeScript sources are compiled f
|
|
|
64
64
|
Base command for initial entry with full arguments:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
scripts/okstra.sh [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--role-count <role>=<N>] [--role-model <role>=<modelRef>] [--lead-runtime <host-id-or-alias>] [--lead-provider <provider>] [--lead-model <model>] [--worker-model provider=model,...] [--report-writer-provider <provider>] [--report-writer-model <model>] [--executor claude|codex|antigravity|grok|kimi] [--critic
|
|
67
|
+
scripts/okstra.sh [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--role-count <role>=<N>] [--role-model <role>=<modelRef>] [--lead-runtime <host-id-or-alias>] [--lead-provider <provider>] [--lead-model <model>] [--worker-model provider=model,...] [--report-writer-provider <provider>] [--report-writer-model <model>] [--executor claude|codex|antigravity|grok|kimi] [--critic claude|codex|antigravity|grok|kimi] [--related-tasks taskA,taskB] [--work-category bugfix|feature|refactor|ops|improvement|unknown] [--base-ref <branch|tag|sha>] [--clarification-response <previous-final-report>] [--selected-direction <selection-final-report.md>] [--approved-plan <plan-path>] [--approve] --project-id <project-id> --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Analysis input ownership is narrower than the base shell command. The `/okstra-run` wizard collects `--analysis-target` and `--evidence-inputs` values and passes them internally to `node bin/okstra render-bundle`. `scripts/okstra.sh` does not accept either flag. Because `feature-analysis` requires a target, start that task type with the in-host skill; the two option sections below document the internal Node render inputs, not standalone shell options.
|
|
@@ -583,7 +583,7 @@ scripts/okstra.sh --task-type implementation \
|
|
|
583
583
|
|
|
584
584
|
### `--critic`
|
|
585
585
|
|
|
586
|
-
Selects the provider for the
|
|
586
|
+
Selects the provider for the required critic slot on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification`. The value is `claude`, `codex`, `antigravity`, `grok`, or `kimi`. `--critic off` is rejected. Prefer `--role-model critic=<provider>/<model>` so the user picks the model. The critic also settles plan-body 1-1 splits in `implementation-planning` (`critic-worker` on `--tie-vote` items).
|
|
587
587
|
|
|
588
588
|
- Critic dispatch runs concurrently with the first convergence reverify round in Phase 5.5, with critic input fixed to the integrated Round 0 result. One gap/blocker verification round runs in Phase 5.6 after convergence finishes. It detects coverage gaps in discovery, error-analysis, and implementation-planning, and acts as an acceptance devil's advocate in final-verification. Follow the "Coverage critic pass" and "Acceptance critic pass" sections of `prompts/lead/convergence.md` for the detailed contract.
|
|
589
589
|
- It shares the same value space as the critic-selection step in the in-session `okstra-run` wizard. `_resolve_model_bindings` in `prepare_task_bundle` validates the value; anything else is rejected immediately with `PrepareError`.
|
|
@@ -820,7 +820,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
820
820
|
| `okstra plan-items seed --narrative <report-narrative.md> --state <plan-body-verification.json>` | Create the convergence-owned `planBodyVerification.planItems[]` rows every verdict lands in, from the same deterministic extraction `extract` uses. The historical v2 form is `--data <data.json>`. Idempotent by id: an existing row keeps its verdicts and carried fields. Reports `seeded` / `existing` counts. |
|
|
821
821
|
| `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md>… --round <N>` | Read each worker's Markdown verdict directly, validate every current `P-*` id, reject duplicate worker submissions, and overwrite that round's stored verdicts. The historical `--verdicts <file>` form remains automation compatibility only. |
|
|
822
822
|
| `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--self-fix-note <item-id>=<markdown-file>]… [--self-fix-group <cause-file>=<item-id>[,<item-id>...]]… [--self-fix-stop-reason <all-resolved\|no-progress\|max-rounds-reached>]` | After `plan-verify` succeeds, atomically derive and record the round's per-item votes, gate result, participant counts from the actual assigned roster, immutable completion time, convergence history, and optional self-fix notes/groups read from Markdown files. Models do not write the state JSON. Stdout also carries `nextDispatch`. |
|
|
823
|
-
| `okstra plan-items next-dispatch --state <plan-body-verification.json> [--run-manifest <path>]` | After `apply-verdicts`, decide whether this round opens a worker batch. `kind: none` — missing-dependency `UNVERIFIABLE` only, no new batch. `kind: worker-correction` — re-prompt only those workers; peers stay idle. `kind:
|
|
823
|
+
| `okstra plan-items next-dispatch --state <plan-body-verification.json> [--run-manifest <path>]` | After `apply-verdicts`, decide whether this round opens a worker batch. `kind: none` — missing-dependency `UNVERIFIABLE` only, no new batch. `kind: worker-correction` — re-prompt only those workers; peers stay idle. `kind: critic-tie` — unsettled analyser 1-1, `critic-worker` on those item ids only. A missing path is never environment-unverifiable. |
|
|
824
824
|
| `okstra plan-items correction-prompt --state <plan-body-verification.json> --run-manifest <path> --worker <id>` | Emit the planning-time environment-gap paragraph, then the assigned queue. The environment exception is first. Exits 2 unless `next-dispatch` named that worker as a blanket-UNVERIFIABLE correction target. |
|
|
825
825
|
| `okstra error-log append-observed --out <errors.jsonl> --task-key <key> --phase <phase> --agent <assigned-worker-id> --agent-role worker --model <model> --error-type tool-failure --command-file <markdown-file> --command-kind <kind> --message-file <markdown-file> [--cause <cause> --evidence-file <kind=file>]…` | Worker-facing typed error recording surface. Python validates and serializes the JSONL record; a worker supplies scalar identity fields plus Markdown files for free-form command, message, and probe content, never a JSON sidecar or JSON argument. `sandbox-denied` requires both `targetProbe` and `controlProbe` evidence files. |
|
|
826
826
|
| `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
|
|
@@ -27,7 +27,7 @@ The legacy `list` and `show` JSON commands remain for automation compatibility.
|
|
|
27
27
|
## Flow
|
|
28
28
|
|
|
29
29
|
1. Run `okstra preflight --runtime <host-runtime>` for the current harness. On `Okstra preflight: failed`, show `Reason` and `Recovery`, then stop. On `Okstra preflight: ready`, carry `Project root`, `Project ID`, `Runtime`, and `Relay contract`, then run `okstra paths --field home`.
|
|
30
|
-
2. Read the relay `Wizard interaction relay` JSON. When `native-single` is available and the option count fits `nativeLimits`, call `interactions.native-single.function` (`AskUserQuestion` / `ask_user_question` / `request_user_input` from that field). Do not print a numbered list in chat while the native tool is available. Otherwise render a numbered Markdown list. Do not substitute one host function name for another. Pass only the
|
|
30
|
+
2. Read the relay `Wizard interaction relay` JSON. When `native-single` is available and the option count fits `nativeLimits`, call `interactions.native-single.function` (`AskUserQuestion` / `ask_user_question` / `request_user_input` from that field). Do not print a numbered list in chat while the native tool is available. Otherwise render a numbered Markdown list. Do not substitute one host function name for another. Copy the view's `Picker:` `- Label:` / `Description:` pairs into that function in that order. Do not rebuild labels from the `Options:` dump. `--option-number` is the 1-based `Option N:` index, which is the same order as `Picker:`. The HTML report's `<select>` uses the same `option.answer` values. Pass only the `list-view` `Picker:` rows, the report `Picker:` rows, or the two confirmation labels. Do not append `Enter directly`. Claude Other, Grok `z`, and Codex's free-form row are `Enters an answer`; on a numbered list, so is a next message that is not a listed label or its 1-based number. Do not ask a second question for the custom value.
|
|
31
31
|
3. Select a task from `list-view` through that host picker. A host free-text row or unmatched next message is the report path or task key.
|
|
32
32
|
4. Read only `show-view --report <reportPath> --project-root <projectRoot>` for report facts. The view also prints `Why asked`, `Linked plan items`, and `Cited artifacts`.
|
|
33
33
|
5. Read every cited `path:line` under the project root and every linked plan-item definition before asking. Do not search beyond that list. Investigation explains; it never changes `options[]`.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -8,10 +8,10 @@ Usage:
|
|
|
8
8
|
[--seq <NNN>]
|
|
9
9
|
[--source-report <relative-path>]
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
HTML template and always produces an HTML sibling. Markdown
|
|
13
|
-
data sibling first; schema-v1 and quick reports keep the
|
|
14
|
-
Markdown renderer.
|
|
11
|
+
Structured ``.data.json`` input (schema 2.0 or 3.0) is rendered directly into
|
|
12
|
+
the dedicated task HTML template and always produces an HTML sibling. Markdown
|
|
13
|
+
input resolves its data sibling first; schema-v1 and quick reports keep the
|
|
14
|
+
legacy conditional Markdown renderer.
|
|
15
15
|
|
|
16
16
|
Output (idempotent — overwrites):
|
|
17
17
|
- <stem>.html — single-file self-contained HTML view
|
|
@@ -48,6 +48,7 @@ if (SCRIPTS_DIR / "okstra_ctl" / "report_views.py").is_file():
|
|
|
48
48
|
elif HOME_LIB.is_dir() and str(HOME_LIB) not in sys.path:
|
|
49
49
|
sys.path.insert(0, str(HOME_LIB))
|
|
50
50
|
|
|
51
|
+
from okstra_ctl.clarification_items import STRUCTURED_REPORT_VERSIONS # noqa: E402
|
|
51
52
|
from okstra_ctl.report_views import infer_run_meta, render_html_view # noqa: E402
|
|
52
53
|
from okstra_ctl.final_report_paths import ( # noqa: E402
|
|
53
54
|
final_report_data_path,
|
|
@@ -184,7 +185,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
184
185
|
data_path, markdown_path = _report_pair(report_path)
|
|
185
186
|
data = _load_data_if_present(data_path)
|
|
186
187
|
|
|
187
|
-
if data is not None and data.get("schemaVersion")
|
|
188
|
+
if data is not None and data.get("schemaVersion") in STRUCTURED_REPORT_VERSIONS:
|
|
188
189
|
from okstra_ctl.report_html.render import render_v2_html_view
|
|
189
190
|
|
|
190
191
|
html_path = render_v2_html_view(
|
|
@@ -25,6 +25,20 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
|
|
|
25
25
|
- Phase advancement requires a new okstra invocation, launched with an explicit `--task-type` after this run's final report is written and approved. The target of that run comes from the pointer this run's report authors into `workflow.nextRecommendedPhase`, and only when that pointer's `status` says it can be started. The lead must not write source code, run builds/migrations/deployments, or otherwise produce artifacts of a different phase from inside this run.
|
|
26
26
|
- See `Lifecycle Phase Boundaries` in the lifecycle core contract (`{{OKSTRA_LEAD_CONTRACT_PATH}}`) for the canonical rules and the phase-transition checklist.
|
|
27
27
|
|
|
28
|
+
## User closeout (BLOCKING)
|
|
29
|
+
|
|
30
|
+
After Phase 7 persistence, the last user-facing message of this run is the next command. A status dump is not a close. A prohibition (`do not start implementation`) is not a next action. This applies to every task type.
|
|
31
|
+
|
|
32
|
+
Close with one command the user can run now. The first matching row wins:
|
|
33
|
+
|
|
34
|
+
- Open `blocks: approval` rows → `/okstra-user-response` (name the `C-NNN` ids). An `accept-risk` / `select` / `answer` already recorded is not an open blocker.
|
|
35
|
+
- `workflow.awaitingApproval` is true → `/okstra-run` → `implementation` or `--approve`. Do not propose another planning run.
|
|
36
|
+
- Phase 7 `validate-run` failed → one line naming the blocking cause, then `/okstra-run` to re-run this phase, or `/okstra-inspect recap`.
|
|
37
|
+
- Pointer `status: ready` → `/okstra-run` for that `phase`.
|
|
38
|
+
- Otherwise → `/okstra-inspect status` for this task.
|
|
39
|
+
|
|
40
|
+
Do not end the turn after the validator result. Task-qualified report paths and the same table live in the lifecycle core contract (`{{OKSTRA_LEAD_CONTRACT_PATH}}` "After persistence").
|
|
41
|
+
|
|
28
42
|
{{TEAM_CREATION_GATE}}
|
|
29
43
|
|
|
30
44
|
## Project Root
|
|
@@ -596,7 +596,7 @@ Schema rules:
|
|
|
596
596
|
|
|
597
597
|
## Coverage critic pass
|
|
598
598
|
|
|
599
|
-
Runs
|
|
599
|
+
Runs when `convergence.critic.enabled == true`. Critic is required on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification` (role `min`/`max` 1; the user picks the model). `--critic off` is rejected. 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`.
|
|
600
600
|
|
|
601
601
|
### When
|
|
602
602
|
|
|
@@ -710,7 +710,7 @@ The asymmetry is deliberate and runs the opposite way from the coverage half: a
|
|
|
710
710
|
|
|
711
711
|
## Acceptance critic pass (final-verification)
|
|
712
712
|
|
|
713
|
-
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;
|
|
713
|
+
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 required; 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).
|
|
714
714
|
|
|
715
715
|
Before that call, write the acceptance-only task instructions and run `okstra
|
|
716
716
|
agent-prompt materialize` with `--audience acceptance-critic`,
|
|
@@ -149,7 +149,7 @@ The approval state transitions are fixed:
|
|
|
149
149
|
|
|
150
150
|
Do not move `answered` back to `open` because a check failed. The user's choice stands. Record the failed check on the row; later stages still see the DISAGREE votes.
|
|
151
151
|
|
|
152
|
-
`open` blocks until the user judges. `answered` with `select` / `accept-risk` / `answer`, `resolved`, and `obsolete` do not block approval or the next phase. `request-revision` and `reject` still withhold the next phase. `accept-risk` does not require re-verification `AGREE`. The worker votes stay on the plan item so a later stage can still see the dissent. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_user_accepted_plan_item_ids`.
|
|
152
|
+
`open` blocks until the user judges. `answered` with `select` / `accept-risk` / `answer`, `resolved`, and `obsolete` do not block approval or the next phase. `request-revision` and `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`). `accept-risk` does not require re-verification `AGREE`. The worker votes stay on the plan item so a later stage can still see the dissent. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_user_accepted_plan_item_ids`.
|
|
153
153
|
|
|
154
154
|
When a terminal row preserves a pre-correction dissent classification, keep superseded votes in `state/plan-body-verification-implementation-planning-<seq>.json`. Activities that implement or check the decision record the exact `C-NNN` in `clarificationRefs` and the affected `P-*` identifiers in `planItemIds`. Report assembly verifies that every resolution `checkRefs` value names an existing activity and derives each plan item's `clarificationRefs`; the lead never copies those references into `approvalContext`. A corrected coverage-only blocker keeps its `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`. An `obsolete` row is invalid while its disagreement or coverage blocker remains active in the current plan. **Enforced:** `scripts/okstra_ctl/report_assembly.py` `_clarification_row` / `_attach_plan_backlinks` and `validators/validate-run.py` `_validate_v3_approval_context`.
|
|
155
155
|
|
|
@@ -414,19 +414,7 @@ Order of operations:
|
|
|
414
414
|
|
|
415
415
|
Keep the assigned worker prompt history paths stable in `team-state`, `run-manifest`, and `task-manifest`. Do not rewrite prompt artifacts to `/tmp` or omit prompt metadata for attempted workers.
|
|
416
416
|
|
|
417
|
-
After persistence, the run
|
|
418
|
-
|
|
419
|
-
```bash
|
|
420
|
-
# Counts per agent
|
|
421
|
-
jq -s 'group_by(.agent) | map({agent: .[0].agent, count: length})' <runDir>/logs/errors-<task-type>-<seq>.jsonl
|
|
422
|
-
|
|
423
|
-
# Counts per errorType
|
|
424
|
-
jq -s 'group_by(.errorType) | map({type: .[0].errorType, count: length})' <runDir>/logs/errors-<task-type>-<seq>.jsonl
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
The errors log is informational. Its presence/absence does not affect the final verdict. Do not block report writing on it.
|
|
428
|
-
|
|
429
|
-
After persistence, reply briefly in the resolved Report Language. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first. Then: completion status, the task-qualified human report path, the report record path, validator result, any remaining blocker. **Close with the user's next action** — one command they can run now. A prohibition (`do not start implementation`) is not a next action. A status dump is not a close.
|
|
417
|
+
After persistence, reply briefly in the resolved Report Language. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first. Then: completion status, the task-qualified human report path, the report record path, validator result, any remaining blocker. **Close with the user's next action** — one command they can run now. A prohibition (`do not start implementation`) is not a next action. A status dump is not a close. This closeout is also in the launch prompt (`prompts/launch.template.md` "User closeout (BLOCKING)") so it is not lazy-read.
|
|
430
418
|
|
|
431
419
|
Pick the next action from this table; the first matching row wins:
|
|
432
420
|
|
|
@@ -440,6 +428,8 @@ When the host native picker is available and two of those rows could apply, ask
|
|
|
440
428
|
|
|
441
429
|
**Every run-artifact path in this reply MUST be task-qualified** — report the human report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.html` rooted at the task bundle, NOT the bare `runs/<task-type>/reports/...` form (byte-for-byte identical across every task of the same task-type, so it cannot identify the task). Under that, cite the report record (`.data.json`) and one line to render the full reading copy: `okstra render-final-report <task-qualified data.json>`. The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.
|
|
442
430
|
|
|
431
|
+
The run-level error log lives at `<runDir>/logs/errors-<task-type>-<seq>.jsonl`. It is informational. Its presence or absence does not affect the final verdict. Do not block report writing on it.
|
|
432
|
+
|
|
443
433
|
## Run-scoped worker-resource lifecycle
|
|
444
434
|
|
|
445
435
|
- At run start, call the selected adapter's setup required to distinguish lead-owned resources from worker-owned resources.
|
|
@@ -364,7 +364,7 @@ round before any host or provider process starts.
|
|
|
364
364
|
- `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.)
|
|
365
365
|
- `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 any 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**.
|
|
366
366
|
- `needs-reverify` — one of two shapes the round could not settle.
|
|
367
|
-
- **An even split on a blocking kind.** The majority test is strict, so a panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) reaches neither `full-consensus` nor `majority-disagree`. Until this shape existed it folded into `has-dissent` and the gate passed
|
|
367
|
+
- **An even split on a blocking kind.** The majority test is strict, so a panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) reaches neither `full-consensus` nor `majority-disagree`. Until this shape existed it folded into `has-dissent` and the gate passed. 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. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --append`. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` / `_validate_tie_received_extra_vote` / `_validate_unresolved_tie_was_reverified`, `okstra_ctl.plan_items.next_dispatch` kind `critic-tie`.
|
|
368
368
|
- **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`.
|
|
369
369
|
- `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
|
|
370
370
|
5. Gate result resolution:
|
|
@@ -394,7 +394,8 @@ round before any host or provider process starts.
|
|
|
394
394
|
|---|---|
|
|
395
395
|
| `none` | Do not add a worker batch. A round whose only failures are missing-dependency command runs — `UNVERIFIABLE` on a declared `npm` / `pytest` / equivalent, §"Planning-time environment gap" — is this shape. |
|
|
396
396
|
| `worker-correction` | Re-dispatch **only** those workers. Peers are not re-run. The queue does not become a new round. Place the output of `okstra plan-items correction-prompt --worker <id> --run-manifest … --state …` first in that worker's prompt — the environment-exception paragraph is first. A byte-identical re-dispatch reproduces the same failure; a corrected one recovered 37 substantive verdicts from a worker whose first attempt answered `UNVERIFIABLE` to all 80 items. |
|
|
397
|
-
| `queue-reverify` | An unsettled tie on a blocking kind.
|
|
397
|
+
| `queue-reverify` | An unsettled tie on a blocking kind. Legacy kind; current scoring emits `critic-tie` instead. |
|
|
398
|
+
| `critic-tie` | An unsettled analyser tie. Dispatch `critic-worker` on those `itemIds` only. The critic's verdict settles the split. Do not re-run the original two. |
|
|
398
399
|
|
|
399
400
|
A referenced **path** that does not exist is still `DISAGREE(b)` / a fact probe, never environment-unverifiable. **Enforced:** `okstra_ctl.plan_items.next_dispatch` / `correction_prompt_text`.
|
|
400
401
|
|
|
@@ -439,7 +440,7 @@ round before any host or provider process starts.
|
|
|
439
440
|
- `answered → resolved` only after the selected disposition is applied and its checks pass
|
|
440
441
|
- `answered → open` when application or checking fails
|
|
441
442
|
- `open → obsolete` only when a plan change removes the question
|
|
442
|
-
`open` blocks until the user judges. `answered` with a proceeding disposition (`accept-risk` / `select` / `answer`) does not block. `request-revision` / `reject` still withhold the next phase. A user-directed correction does not consume the automatic self-fix limit, and a failed check does not restart the automatic loop or reopen the row.
|
|
443
|
+
`open` blocks until the user judges. `answered` with a proceeding disposition (`accept-risk` / `select` / `answer`) does not block. `request-revision` / `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`). A user-directed correction does not consume the automatic self-fix limit, and a failed check does not restart the automatic loop or reopen the row.
|
|
443
444
|
- A terminal row preserves its original dissent classification only from the convergence-owned state history. Every `user-decision-required` / `user-decision-evaluated` activity cites the row's `C-NNN` in `clarificationRefs` and affected plan items in `planItemIds`. A resolved decision names only existing `A-NNN` checks. Report assembly validates those links and derives the report backtraces; it does not accept copied IDs from the approval ledger. When an independent coverage-only blocker is corrected, keep the `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`. `obsolete` is valid only after current evidence shows that the question or blocker disappeared.
|
|
444
445
|
9. Approval lives in the report record `frontmatter.approved` field — there is no in-body marker line. The user may set it to `true` (via `--approve` or the in-session wizard) when remaining `Blocks=approval` rows are user-proceeded (`accept-risk` / `select` / `answer`) even if the recorded `gateResult` is still `blocked-by-disagreement`. `aborted-non-result` still withholds approval. **Enforced:** run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan` / `_blocking_gate_survives_user_decision`) and `validators/validate-run.py` `_validate_plan_body_gate_recompute`.
|
|
445
446
|
|
|
@@ -104,6 +104,8 @@ Do not run the seven steps below manually. Invoke `okstra report-finalize`; cont
|
|
|
104
104
|
6. **`validate-run`** — validate the record, views, run manifest, and team state.
|
|
105
105
|
7. **`teardown-stages`** — remove eligible stage worktrees after successful validation.
|
|
106
106
|
|
|
107
|
+
After `report-finalize` returns, the lead — not the report writer — closes the run with the launch prompt's User closeout: one command the user can run now.
|
|
108
|
+
|
|
107
109
|
### Before `report-finalize`: the translation sidecar
|
|
108
110
|
|
|
109
111
|
Never dispatch the translator before report assembly and `check-source`. For a non-English human report, first run `report-finalize --only token-usage --only project-activity --only check-source`; the extraction command refuses to build a work list from a non-English source. Then dispatch the translator worker with `okstra agent-prompt materialize --audience translator`, `okstra agent-prompt record-dispatch`, `okstra worker-dispatch --audience translator`, and `okstra agent-prompt link-result`. Resume with `report-finalize --only render-views --only spawn-followups --only validate-run --only teardown-stages`; do not assemble the record a second time.
|
|
@@ -112,7 +114,7 @@ Never dispatch the translator before report assembly and `check-source`. For a n
|
|
|
112
114
|
|
|
113
115
|
`workflow.nextRecommendedPhase` has `phase`, `status`, and `rationale`. The status vocabulary is `ready`, `pending`, `blocked`, and `terminal`.
|
|
114
116
|
|
|
115
|
-
`phase` is non-empty only for an authored `ready` pointer. `prepare` may lower `ready` to `pending` while retaining `phase` in `scripts/okstra_ctl/render.py::_derive_next_recommended_phase`; readers must use `status` for launchability. When `finalVerification.routingRecommendation.target` is `release-handoff(stage-group)`, write `phase` as `release-handoff`. When `implementationPlanning.outcome` is `plan-ready` and a `Blocks=approval` row still blocks progress (`open`, or `request-revision` / `reject`), or the plan-body gate is `aborted-non-result`, write `status` `blocked` (empty `phase`). `blocked-by-disagreement` with every approval row user-proceeded (`accept-risk` / `select` / `answer`) is approvable — write `ready` at `implementation`. The user still has to approve it separately.
|
|
117
|
+
`phase` is non-empty only for an authored `ready` pointer. `prepare` may lower `ready` to `pending` while retaining `phase` in `scripts/okstra_ctl/render.py::_derive_next_recommended_phase`; readers must use `status` for launchability. When `finalVerification.routingRecommendation.target` is `release-handoff(stage-group)`, write `phase` as `release-handoff`. When `implementationPlanning.outcome` is `plan-ready` and a `Blocks=approval` row still blocks progress (`open`, or an unincorporated `request-revision` / `reject`), or the plan-body gate is `aborted-non-result`, write `status` `blocked` (empty `phase`). `blocked-by-disagreement` with every approval row user-proceeded (`accept-risk` / `select` / `answer`) or incorporated in `supersessionLedger` is approvable — write `ready` at `implementation`. The user still has to approve it separately.
|
|
116
118
|
|
|
117
119
|
## Compatibility
|
|
118
120
|
|
|
@@ -14,4 +14,4 @@ mode:" when the include directive (placed at column 0) is resolved in-place.
|
|
|
14
14
|
Do NOT write the literal include directive token in this file's body — the
|
|
15
15
|
resolver matches it anywhere and would recurse on this file itself.
|
|
16
16
|
-->
|
|
17
|
-
- **Coverage critic (
|
|
17
|
+
- **Coverage critic (required, one slot)**: critic `min`/`recommended`/`max` are 1. The user picks the model at launch (`--role-model critic=<provider>/<model>` or the wizard role-model step). `--critic off` is rejected. A reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface **both** findings nobody covered and work the findings propose that no requirement asked for (`category: "unrequested-scope"`); its candidates are judged only after a 1-round adversarial reverify that follows convergence. The two halves are disposed of differently — a contested coverage gap is dropped as a hallucination, while a contested over-scope candidate is recorded as a `## 5. Missing Information and Risks` row instead of vanishing. In `implementation-planning`, the same critic slot also settles plan-body 1-1 splits (`critic-worker` on `--tie-vote` items only). See `prompts/lead/convergence.md` "Coverage critic pass" and `prompts/lead/plan-body-verification.md` even-split rule.
|
|
@@ -8,7 +8,7 @@ roles:
|
|
|
8
8
|
max: 5
|
|
9
9
|
duty: acceptance-verifier
|
|
10
10
|
- role: critic
|
|
11
|
-
min:
|
|
11
|
+
min: 1
|
|
12
12
|
recommended: 1
|
|
13
13
|
max: 1
|
|
14
14
|
duty: acceptance-critic
|
|
@@ -95,7 +95,7 @@ roles:
|
|
|
95
95
|
4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
|
|
96
96
|
5. **No source-mutation audit** — scan the run's session transcripts for Edit / Write or state-mutating Bash commands that touch paths OUTSIDE `<PROJECT_ROOT>/.okstra/**` and outside the assigned run-artifact paths. Writes to worker prompts, audit sidecars, team-state, the final-report `data.json`, and rendered reports under the run directory are allowed okstra artifacts. Any source/schema/deployment mutation means the run has crossed into implementation and MUST be re-routed; do NOT silently strip the evidence.
|
|
97
97
|
- Cross-verification mode:
|
|
98
|
-
- **Acceptance critic (
|
|
98
|
+
- **Acceptance critic (required, one slot)**: the user picks the critic model at launch. A reused-worker **acceptance devil's-advocate** pass is dispatched concurrently with the first convergence reverify round to surface candidate acceptance blockers the verifiers may have missed; candidates are verified only after convergence completes. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row; unconfirmed → a `Residual Risk` row (never dropped). See `prompts/lead/convergence.md` "Acceptance critic pass (final-verification)".
|
|
99
99
|
- Non-goals:
|
|
100
100
|
- proposing unrelated refactors beyond the delivered scope
|
|
101
101
|
- **source code edits, follow-up bug fixes, or scope expansion** — this run renders a verdict only; defects detected here become inputs to a new `error-analysis`, `implementation-option-selection`, or `implementation-planning` run according to whether the cause, direction, or detailed plan is invalid
|
|
@@ -8,8 +8,8 @@ roles:
|
|
|
8
8
|
max: 5
|
|
9
9
|
duty: planning-worker
|
|
10
10
|
- role: critic
|
|
11
|
-
min:
|
|
12
|
-
recommended:
|
|
11
|
+
min: 1
|
|
12
|
+
recommended: 1
|
|
13
13
|
max: 1
|
|
14
14
|
duty: acceptance-critic
|
|
15
15
|
- role: report-writer
|
|
@@ -234,7 +234,7 @@ roles:
|
|
|
234
234
|
- `open → answered` when the raw user response is recorded
|
|
235
235
|
- `answered → resolved` after the selected disposition is applied, when that work completed
|
|
236
236
|
- `open → obsolete` only when a plan change removes the question
|
|
237
|
-
`open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block. Do not move `answered` back to `open` because a check failed. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_validate_approval_context`, run-prep `scripts/okstra_ctl/run.py` `_validate_approved_plan`.
|
|
237
|
+
`open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block. `request-revision` / `reject` still block unless this report's `supersessionLedger` already incorporated that id. Do not move `answered` back to `open` because a check failed. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_validate_approval_context`, run-prep `scripts/okstra_ctl/run.py` `_validate_approved_plan`.
|
|
238
238
|
- **Terminal approval evidence.** A resolved correctness-critical decision names a later successful evaluation through `resolutionInput.checkRefs`. Each referenced activity carries the same `C-NNN` in `clarificationRefs[]`, the affected `planItemIds[]`, zero-exit commands, and the plan-body state result. Report assembly rejects a missing activity or reverse link before publication. **Enforced:** `scripts/okstra_ctl/report_assembly.py::_clarification_row` and `_attach_plan_backlinks`.
|
|
239
239
|
- **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the chosen realization introduces against the three criteria:
|
|
240
240
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
@@ -461,11 +461,9 @@
|
|
|
461
461
|
}
|
|
462
462
|
},
|
|
463
463
|
"critic_pick": {
|
|
464
|
-
"label": "
|
|
464
|
+
"label": "critic 모델을 고르세요 (매 런 1명. 동수 계획 항목을 이 역할이 가릅니다)",
|
|
465
465
|
"echo_template": "critic: {value}",
|
|
466
|
-
"options": {
|
|
467
|
-
"off": "사용 안 함 (기본·추천)"
|
|
468
|
-
},
|
|
466
|
+
"options": {},
|
|
469
467
|
"labels": {
|
|
470
468
|
"provider_recommended": "{provider} critic (추천)",
|
|
471
469
|
"provider": "{provider} critic"
|
|
@@ -5,7 +5,7 @@ import shutil
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from okstra_ctl.adapters.accounting import
|
|
8
|
+
from okstra_ctl.adapters.accounting import CliArtifactUsageAccountingPort
|
|
9
9
|
from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
10
10
|
INTERACTION_FUNCTIONS,
|
|
11
11
|
PENDING_HOST_PORT,
|
|
@@ -41,10 +41,7 @@ def create_adapter(
|
|
|
41
41
|
interaction_port=None,
|
|
42
42
|
lead_session_port=PENDING_HOST_PORT,
|
|
43
43
|
worker_dispatch_port=PENDING_HOST_PORT,
|
|
44
|
-
usage_accounting_port=
|
|
45
|
-
"Grok host usage accounting is unavailable because no session "
|
|
46
|
-
"transcript or CLI usage artifact contract is registered."
|
|
47
|
-
),
|
|
44
|
+
usage_accounting_port=CliArtifactUsageAccountingPort(),
|
|
48
45
|
provider_registry: ProviderRegistry | None = None,
|
|
49
46
|
host_model_port=None,
|
|
50
47
|
) -> CapabilityHostAdapter:
|
|
@@ -347,6 +347,12 @@ def _append(args: argparse.Namespace) -> int:
|
|
|
347
347
|
"evidenceRefs": args.evidence_ref,
|
|
348
348
|
"outcome": args.outcome,
|
|
349
349
|
}
|
|
350
|
+
clarification_refs = [
|
|
351
|
+
ref for ref in args.evidence_ref
|
|
352
|
+
if isinstance(ref, str) and re.fullmatch(r"C-\d{3,}", ref)
|
|
353
|
+
]
|
|
354
|
+
if clarification_refs:
|
|
355
|
+
details["clarificationRefs"] = clarification_refs
|
|
350
356
|
if args.request_ref is not None:
|
|
351
357
|
details["activityRequestRef"] = args.request_ref
|
|
352
358
|
event = record_activity(args.project_root, args.run_manifest, details)
|
|
@@ -5,6 +5,9 @@ a single data point. Each row carries a ``Blocks`` value out of
|
|
|
5
5
|
``{approval, next-phase, none}``. Rows with ``Blocks=approval`` are the
|
|
6
6
|
approval gate: they MUST resolve before the user flips the frontmatter
|
|
7
7
|
``approved`` field to ``true`` and starts the next ``implementation`` run.
|
|
8
|
+
A ``request-revision`` / ``reject`` answer still blocks the report that has
|
|
9
|
+
not yet incorporated it. The report that recorded the same id in
|
|
10
|
+
``supersessionLedger`` has already absorbed that return and does not block.
|
|
8
11
|
|
|
9
12
|
The two schemas store those rows in different places, and that is why the
|
|
10
13
|
read functions take a report **path**, not its text:
|
|
@@ -400,6 +403,10 @@ ANSWER_DISPOSITIONS = frozenset({
|
|
|
400
403
|
PROCEEDING_DISPOSITIONS = frozenset({"answer", "select", "accept-risk"})
|
|
401
404
|
# 사용자가 이 계획으로 진행하지 않겠다고 고른 처분.
|
|
402
405
|
RETURN_DISPOSITIONS = frozenset({"request-revision", "reject"})
|
|
406
|
+
# 이 런이 그 답을 본문에 반영했다고 원장이 적은 처분.
|
|
407
|
+
INCORPORATED_LEDGER_DISPOSITIONS = frozenset(
|
|
408
|
+
{"superseded", "no-dependent-statement"}
|
|
409
|
+
)
|
|
403
410
|
|
|
404
411
|
|
|
405
412
|
def clarification_disposition(row: Mapping[str, object]) -> str:
|
|
@@ -425,13 +432,46 @@ def clarification_disposition(row: Mapping[str, object]) -> str:
|
|
|
425
432
|
return ""
|
|
426
433
|
|
|
427
434
|
|
|
428
|
-
def
|
|
435
|
+
def incorporated_clarification_ids(
|
|
436
|
+
report_data: Mapping[str, object] | None,
|
|
437
|
+
) -> frozenset[str]:
|
|
438
|
+
"""이 런이 답을 본문에 반영했다고 원장에 적은 C-id.
|
|
439
|
+
|
|
440
|
+
`superseded` 와 `no-dependent-statement` 만 센다. 원장에 없는
|
|
441
|
+
`request-revision` / `reject` 는 아직 다음 계획을 막는 되돌림이다.
|
|
442
|
+
"""
|
|
443
|
+
if not isinstance(report_data, Mapping):
|
|
444
|
+
return frozenset()
|
|
445
|
+
planning = report_data.get("implementationPlanning")
|
|
446
|
+
if not isinstance(planning, Mapping):
|
|
447
|
+
return frozenset()
|
|
448
|
+
ledger = planning.get("supersessionLedger")
|
|
449
|
+
if not isinstance(ledger, list):
|
|
450
|
+
return frozenset()
|
|
451
|
+
ids: set[str] = set()
|
|
452
|
+
for entry in ledger:
|
|
453
|
+
if not isinstance(entry, Mapping):
|
|
454
|
+
continue
|
|
455
|
+
row_id = str(entry.get("clarificationId") or "").strip()
|
|
456
|
+
disposition = str(entry.get("disposition") or "").strip().lower()
|
|
457
|
+
if row_id and disposition in INCORPORATED_LEDGER_DISPOSITIONS:
|
|
458
|
+
ids.add(row_id)
|
|
459
|
+
return frozenset(ids)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def row_blocks_progress(
|
|
463
|
+
status: str,
|
|
464
|
+
disposition: str = "",
|
|
465
|
+
*,
|
|
466
|
+
incorporated: bool = False,
|
|
467
|
+
) -> bool:
|
|
429
468
|
"""이 행이 승인·다음 단계 진입을 막는가.
|
|
430
469
|
|
|
431
470
|
진행 처분(`accept-risk` / `select` / `answer`)은 고치지 않은 DISAGREE 를
|
|
432
471
|
행과 투표에 남긴 채로 게이트만 내린다. `request-revision` / `reject` 는
|
|
433
|
-
사용자가 진행을 거절한 것이므로 막는다.
|
|
434
|
-
|
|
472
|
+
사용자가 진행을 거절한 것이므로 막는다. 다만 이 보고서 원장이 그 답을
|
|
473
|
+
이미 반영했으면(`incorporated`) 같은 되돌림이 다음 계획 런을 강제하지
|
|
474
|
+
않는다. 처분이 없는 `answered` 도 판단 기록이므로 막지 않는다.
|
|
435
475
|
"""
|
|
436
476
|
normalized_status = status.strip().lower()
|
|
437
477
|
normalized_disposition = disposition.strip().lower()
|
|
@@ -440,17 +480,23 @@ def row_blocks_progress(status: str, disposition: str = "") -> bool:
|
|
|
440
480
|
if normalized_disposition in PROCEEDING_DISPOSITIONS:
|
|
441
481
|
return False
|
|
442
482
|
if normalized_disposition in RETURN_DISPOSITIONS:
|
|
443
|
-
return
|
|
483
|
+
return not incorporated
|
|
444
484
|
return normalized_status not in {"answered", "resolved"}
|
|
445
485
|
|
|
446
486
|
|
|
447
487
|
def progress_blocking_ids(
|
|
448
488
|
rows: object,
|
|
449
489
|
blocking_values: frozenset[str] = APPROVAL_BLOCKS,
|
|
490
|
+
*,
|
|
491
|
+
report_data: Mapping[str, object] | None = None,
|
|
450
492
|
) -> list[str]:
|
|
451
|
-
"""게이트를 아직 막는 행 id. 사용자 진행 처분이 있는 행은 빠진다.
|
|
493
|
+
"""게이트를 아직 막는 행 id. 사용자 진행 처분이 있는 행은 빠진다.
|
|
494
|
+
|
|
495
|
+
``report_data`` 가 있으면 원장에 반영된 되돌림 행도 빠진다.
|
|
496
|
+
"""
|
|
452
497
|
if not isinstance(rows, list):
|
|
453
498
|
return []
|
|
499
|
+
incorporated = incorporated_clarification_ids(report_data)
|
|
454
500
|
ids: list[str] = []
|
|
455
501
|
for row in rows:
|
|
456
502
|
if not isinstance(row, Mapping):
|
|
@@ -460,7 +506,11 @@ def progress_blocking_ids(
|
|
|
460
506
|
row_id = row.get("id")
|
|
461
507
|
if (
|
|
462
508
|
blocks in blocking_values
|
|
463
|
-
and row_blocks_progress(
|
|
509
|
+
and row_blocks_progress(
|
|
510
|
+
status,
|
|
511
|
+
clarification_disposition(row),
|
|
512
|
+
incorporated=isinstance(row_id, str) and row_id in incorporated,
|
|
513
|
+
)
|
|
464
514
|
and isinstance(row_id, str)
|
|
465
515
|
and row_id
|
|
466
516
|
):
|
|
@@ -487,8 +537,9 @@ def scan_approval_gate(report_path: Path) -> ClarificationScan:
|
|
|
487
537
|
|
|
488
538
|
A recorded user proceeding disposition (`accept-risk` / `select` /
|
|
489
539
|
`answer`), including one that lives only in the sidecar, does not block.
|
|
490
|
-
``request-revision`` / ``reject`` still block
|
|
491
|
-
|
|
540
|
+
``request-revision`` / ``reject`` still block unless this report's
|
|
541
|
+
``supersessionLedger`` already incorporated that id. The scan refuses to
|
|
542
|
+
guess whenever the schema drifted.
|
|
492
543
|
"""
|
|
493
544
|
return scan_clarification_blockers(
|
|
494
545
|
report_path,
|
|
@@ -516,8 +567,10 @@ def scan_clarification_blockers(
|
|
|
516
567
|
"""Shared fail-closed clarification walk for both gates above — schema-v2
|
|
517
568
|
reads its rows from the data sibling and schema-v1 from the §1 table.
|
|
518
569
|
``honor_sidecar_answers`` hides rows the sidecar already answered.
|
|
519
|
-
``sidecar_unblocks_proceeding_only`` keeps
|
|
520
|
-
as
|
|
570
|
+
``sidecar_unblocks_proceeding_only`` keeps an unincorporated
|
|
571
|
+
``request-revision`` / ``reject`` as a blocker so a return choice cannot
|
|
572
|
+
start the next phase. A ledger entry for that id means this report already
|
|
573
|
+
absorbed the return.
|
|
521
574
|
"""
|
|
522
575
|
v2_scan = _scan_v2_blockers(report_path, blocking_values)
|
|
523
576
|
scan = (
|
|
@@ -634,11 +687,14 @@ def _scan_v2_blockers(
|
|
|
634
687
|
f"schema-v2 data.json has {unparsed} `clarificationItems` row(s) "
|
|
635
688
|
"missing id/blocks/status"
|
|
636
689
|
))
|
|
690
|
+
incorporated = incorporated_clarification_ids(data)
|
|
637
691
|
blockers = [
|
|
638
692
|
row["item"] for row in rows
|
|
639
693
|
if row["item"].blocks in blocking_values
|
|
640
694
|
and row_blocks_progress(
|
|
641
|
-
row["item"].status,
|
|
695
|
+
row["item"].status,
|
|
696
|
+
str(row.get("disposition") or ""),
|
|
697
|
+
incorporated=row["item"].row_id in incorporated,
|
|
642
698
|
)
|
|
643
699
|
]
|
|
644
700
|
return ClarificationScan(blockers, None)
|
|
@@ -223,7 +223,9 @@ def _from_option_selection(report_data: Mapping[str, Any]) -> dict[str, str]:
|
|
|
223
223
|
|
|
224
224
|
def _unresolved_approval_ids(report_data: Mapping[str, Any]) -> list[str]:
|
|
225
225
|
return progress_blocking_ids(
|
|
226
|
-
report_data.get("clarificationItems"),
|
|
226
|
+
report_data.get("clarificationItems"),
|
|
227
|
+
APPROVAL_BLOCKS,
|
|
228
|
+
report_data=report_data,
|
|
227
229
|
)
|
|
228
230
|
|
|
229
231
|
|
|
@@ -234,8 +236,9 @@ def _planning_approval_block_reason(
|
|
|
234
236
|
|
|
235
237
|
자문 게이트(`passed-with-dissent`)와 재현 실패 `has-dissent` 는 여기 안
|
|
236
238
|
들어온다. 차단은 `aborted-non-result` 와, 사용자가 아직 진행 처분을
|
|
237
|
-
고르지 않은 `Blocks=approval` 행이다.
|
|
238
|
-
행들이 전부 `accept-risk` / `select` /
|
|
239
|
+
고르지 않았고 이 런 원장에도 반영되지 않은 `Blocks=approval` 행이다.
|
|
240
|
+
`blocked-by-disagreement` 는 그 행들이 전부 `accept-risk` / `select` /
|
|
241
|
+
`answer` 이거나 원장이 되돌림 답을 반영했으면 증거가 된 뒤라
|
|
239
242
|
포인터를 막지 않는다.
|
|
240
243
|
"""
|
|
241
244
|
ids = _unresolved_approval_ids(report_data)
|