okstra 0.207.1 → 0.209.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 +3 -2
- package/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +1 -1
- package/dist/commands/lifecycle/doctor.mjs +1 -1
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/docs/architecture/storage-model.md +14 -0
- package/docs/architecture.md +30 -9
- package/docs/cli.md +26 -22
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/project-structure-overview.md +15 -8
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/explain-flow.json +6 -0
- package/runtime/bin/lib/okstra/cli.sh +1 -5
- package/runtime/bin/lib/okstra/globals.sh +0 -2
- package/runtime/bin/lib/okstra/usage.sh +5 -3
- package/runtime/bin/okstra.sh +0 -2
- package/runtime/prompts/duties/business-flow-investigator.json +14 -0
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +22 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/lead/team-contract.md +12 -17
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +0 -91
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
- package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
- package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
- package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
- package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
- package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
- package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
- package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
- package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
- package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
- package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
- package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
- package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
- package/runtime/python/okstra_ctl/cmux.py +21 -16
- package/runtime/python/okstra_ctl/convergence.py +173 -2
- package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
- package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
- package/runtime/python/okstra_ctl/coverage_census.py +603 -0
- package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
- package/runtime/python/okstra_ctl/direct_work.py +1 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
- package/runtime/python/okstra_ctl/doctor.py +12 -6
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/group_context.py +5 -4
- package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
- package/runtime/python/okstra_ctl/manager_split.py +4 -1
- package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
- package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
- package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +14 -8
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
- package/runtime/python/okstra_ctl/plan_items.py +1 -1
- package/runtime/python/okstra_ctl/render.py +10 -43
- package/runtime/python/okstra_ctl/render_final_report.py +3 -0
- package/runtime/python/okstra_ctl/report_assembly.py +15 -1
- package/runtime/python/okstra_ctl/report_finalize.py +50 -0
- package/runtime/python/okstra_ctl/report_html/render.py +3 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
- package/runtime/python/okstra_ctl/run.py +78 -409
- package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
- package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
- package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
- package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
- package/runtime/python/okstra_ctl/wizard/render.py +2 -2
- package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
- package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
- package/runtime/python/okstra_ctl/wizard/state.py +36 -145
- package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
- package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
- package/runtime/schemas/business-flow-v1.schema.json +847 -0
- package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
- package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
- package/runtime/skills/okstra-inspect/facets/history.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +2 -2
- package/runtime/templates/reports/business-flow.template.md +106 -0
- package/runtime/templates/reports/html/base.template.html +14 -1
- package/runtime/templates/reports/html/business-flow.template.html +31 -0
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/templates/worker-prompt-preamble.md +11 -2
- package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
- package/runtime/validators/validate-run.py +70 -21
- package/runtime/validators/validate_analysis_report.py +21 -21
- package/runtime/python/okstra_ctl/workers.py +0 -133
|
@@ -89,6 +89,13 @@
|
|
|
89
89
|
"type": "array",
|
|
90
90
|
"minItems": 1,
|
|
91
91
|
"items": { "$ref": "#/$defs/SourceItem" }
|
|
92
|
+
},
|
|
93
|
+
"cellRefs": {
|
|
94
|
+
"description": "Coverage-census cells this finding answers (grouping Markdown `Cells:`). Optional: a finding outside the census has none.",
|
|
95
|
+
"type": "array",
|
|
96
|
+
"minItems": 1,
|
|
97
|
+
"uniqueItems": true,
|
|
98
|
+
"items": { "type": "string", "pattern": "^C-\\S+$" }
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
},
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: okstra-explain-flow
|
|
3
|
+
description: Explain a product business flow across related projects, including inbound and outbound dependencies, business rules, states, errors, before/after changes, risks and side effects. Query or retry shared business explanations independently of okstra-run.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Explain a Business Flow
|
|
7
|
+
|
|
8
|
+
Use the user's business name or question as the investigation input. If the project is ambiguous, resolve the intended project before investigating. A task, brief, prior analysis or implementation is not required.
|
|
9
|
+
|
|
10
|
+
Run the shared runtime from the project:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
okstra explain-flow --question "<the user's business question>" --report-language <reader language> --host-runtime <current host runtime>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Use `claude-code`, `codex`, or the configured external host identifier. The runtime owns operation/model resolution, auditable invocation preparation, provider execution, source snapshots, validated result publication, shared knowledge and reports. Do not dispatch an unaudited replacement investigator or rerun an entire task when only an explanation failed.
|
|
17
|
+
|
|
18
|
+
For relevant stored knowledge:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
okstra explain-flow knowledge --query "<business question>"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For a failed or partial explanation:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
okstra explain-flow rerun --execution <execution-id>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If the original task checkout has been removed, add `--source-root <available checkout>` to the retry. Preserve the original execution and its baseline.
|
|
31
|
+
|
|
32
|
+
For current status:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
okstra explain-flow status --execution <execution-id>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read the returned human report. Explain the business findings, verified scope and gaps in the user's language. Link the returned report paths; do not assemble a guessed path. Distinguish static source inspection from actual execution evidence, expected changes, historical source versions and unresolved conflicts. Never report missing access as no impact.
|
|
39
|
+
|
|
40
|
+
The investigation reads source, configuration, test bodies and explicitly supplied existing results. Its policy forbids executing tests, starting services, deploying, migrating or editing investigated projects. The report distinguishes that policy and the before/after source audit from an enforced write boundary. A report and a shared fact use the same accepted evidence. New runs retain prior reports and source baselines.
|
|
41
|
+
|
|
42
|
+
Enforcement: `business_flow.engine` validates the result schema, source evidence and source stability before contribution; `agent.invocation` verifies standalone completion; `business_flow.store` preserves claim provenance and source versions. These behaviors are covered by `tests/run/test_business_flow.py` and existing invocation/JSON-boundary contract tests.
|
|
@@ -59,10 +59,9 @@ Builds a fresh run — new run-seq, new manifest, new report — using parameter
|
|
|
59
59
|
- `taskType` → `--task-type`
|
|
60
60
|
- `taskBriefPath` → `--task-brief`
|
|
61
61
|
3. Optional arguments (include only when present in source):
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
-
|
|
65
|
-
- for `taskType: implementation`: `teamContract.executor.provider` → `--executor <claude|codex|antigravity>` when different from `claude`.
|
|
62
|
+
- each `Role count` line → one `--role-count <role>=<N>` (only roles whose count can change appear)
|
|
63
|
+
- each `Role model` line → one `--role-model <role>=<provider>/<model>`, in the printed order (pass the values verbatim; never rebuild them from a roster)
|
|
64
|
+
- `Related tasks` → `--related-tasks`
|
|
66
65
|
4. **`taskType: implementation` only — resolve `--base-ref`:** do not inspect the worktree registry. Omit `--base-ref` to reuse an existing registration. If the launch reports that no worktree is registered and a base is required, ask the user before retrying.
|
|
67
66
|
5. Display the assembled command:
|
|
68
67
|
```bash
|
|
@@ -72,7 +71,8 @@ Builds a fresh run — new run-seq, new manifest, new report — using parameter
|
|
|
72
71
|
--task-id <task-id> \
|
|
73
72
|
--task-type <task-type> \
|
|
74
73
|
--task-brief <brief-path> \
|
|
75
|
-
--
|
|
74
|
+
--role-count <role>=<N> \
|
|
75
|
+
--role-model <role>=<provider>/<model>
|
|
76
76
|
```
|
|
77
77
|
6. Once the user confirms, execute it.
|
|
78
78
|
|
|
@@ -205,7 +205,7 @@ That is the entire interactive flow. The wizard handles:
|
|
|
205
205
|
- base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
|
|
206
206
|
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number). Implementer slots use role-count / role-model like every other role (`executor` is only a compatibility alias for `implementer`). When an approved plan is selected and a `## PLAN DECISION` sidecar carrying `Status: approved`, exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
|
|
207
207
|
- `release-handoff`-only sub-flow: after the approved plan auto-resolves, a `handoff_stage_pick` multi-select — choose the eligible stages to open a PR for, one PR per stage; the result goes out as render-args' `stages` key (csv; empty takes every eligible stage),
|
|
208
|
-
- launch selection after identity/worktree steps: one screen per static role, in profile order. A role that can run several instances (`max > 1`) is a checkbox step `role-models:<role>` (`multi: true`) — the number of models checked is the number of instances, there is no separate count question; the label states the profile range and recommended count, every executable candidate is listed on that one screen (defaults first, the recommended set flagged), and when the list exceeds the host's native checkbox limit the runtime either splits it into several checkbox questions on one `pick_group` screen (interaction plan `native-group`; the question steps are `role-models:<role>#1`, `#2`, … and the answer is one JSON object keyed by them, each value a CSV) when the host's native question group holds every option, or returns the interaction plan `numbered-multi` — render the whole list, never a shortlist or pages. An optional role (`min = 0`, e.g. critic) carries a `추가 안 함` row. A fixed single role (`min = max = 1`, e.g. report-writer) is a single pick `role-model:<role>:1`, and a role capped at one model (`max = 1`, e.g. critic) is a single pick on its `role-models:<role>` step; both split the same way when they exceed the native option limit — the tabs are checkbox questions, the answer is the same keyed JSON object, and the wizard rejects a screen whose tabs together name more than one value. current-session lead is this session and is listed on the confirmation summary, not as a wizard step. The wizard does not fork on defaults-vs-customize, does not show a provider roster multi-pick, and does not offer a separate implementer-provider pick. Dynamic verifiers are not chosen at launch.
|
|
208
|
+
- launch selection after identity/worktree steps: one screen per static role, in profile order. A role that can run several instances (`max > 1`) is a checkbox step `role-models:<role>` (`multi: true`) — the number of models checked is the number of instances, there is no separate count question; the label states the profile range and recommended count, every executable candidate is listed on that one screen (defaults first, the recommended set flagged), and when the list exceeds the host's native checkbox limit the runtime either splits it into several checkbox questions on one `pick_group` screen (interaction plan `native-group`; the question steps are `role-models:<role>#1`, `#2`, … and the answer is one JSON object keyed by them, each value a CSV) when the host's native question group holds every option, or returns the interaction plan `numbered-multi` — render the whole list, never a shortlist or pages. An optional role (`min = 0`, e.g. critic) carries a `추가 안 함` row. A fixed single role (`min = max = 1`, e.g. report-writer) is a single pick `role-model:<role>:1`, and a role capped at one model (`max = 1`, e.g. critic) is a single pick on its `role-models:<role>` step; both split the same way when they exceed the native option limit — the tabs are checkbox questions, the answer is the same keyed JSON object, and the wizard rejects a screen whose tabs together name more than one value. current-session lead is this session and is listed on the confirmation summary, not as a wizard step. The wizard does not fork on defaults-vs-customize, does not show a provider roster multi-pick, and does not offer a separate implementer-provider pick. Dynamic verifiers are not chosen at launch. Repeated `--role-count` / `--role-model` tokens on `renderArgv` are intentional,
|
|
209
209
|
- **resume-clarification (in-session equivalent)** — there is no separate mode or flag matching the shell's `okstra.sh --resume-clarification`; two steps of the standard flow carry out its substance. (1) `reuse_previous` (yes/no to reuse the previous run's settings — in `requirements-discovery` / `error-analysis` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`, only when prior run-inputs exist): YES prefills role-count·role-model·directive·related-tasks at once (and, for the analysis types, the target and evidence inputs). (2) `clarification_pick`: a `revision-requested` analysis report for the selected analysis type is recommended first; otherwise the **task-type's own** previous `final-report` is auto-recommended as the carry-in input (for `technical-verification`, the `implementation-option-selection` report), falling back to the newest by mtime across all phases when absent — except for `implementation-planning` and `technical-verification`, which get no cross-phase fallback. The approved plan is never recommended here. The same run's `user-responses/` sidecar (answers the user filled in) is attached alongside. The chosen path is passed to prepare as `--clarification-response` — the user makes the sidecar via the report's `Export user response`, places it in `runs/<task-type>/user-responses/`, and re-runs the same phase,
|
|
210
210
|
- **re-verification scope (`reverify_scope_pick`, `implementation-planning` clarification re-runs only)** — asked right before `confirm` when the re-run is narrowable **or** an answered `C-NNN` traces to no stage. When every answered id traces to a stage: 3 options — `auto` (recommended — leave it to the lead's `okstra incremental-scope` decision) / `full` (re-verify every stage) / Enter directly (a stage-number CSV, validated against the prior report's Stage Map). When an id is unlinked, `auto` is omitted and the user names stages or picks `full`; that unlinked id does not freeze the run at full. The answer goes out as `--reverify-scope` and reaches the lead prompt as the `REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` tokens; it shapes that CLI's inputs rather than replacing the decision. The confirmation block's `reverify-scope` line names unlinked ids as needing stage numbers, not as a forced full re-run,
|
|
211
211
|
- `release-handoff` PR template override + persist scope,
|
|
@@ -271,7 +271,7 @@ Before invoking it, follow the active host relay's execution-permission guidance
|
|
|
271
271
|
|
|
272
272
|
Analysis sidetracks therefore forward wizard-owned tokens such as `--analysis-target "<value>"` and `--evidence-inputs "<value>"` when they are present. These are examples of the verbatim token rule, not a separate hard-coded argument list.
|
|
273
273
|
|
|
274
|
-
Step 3's empty-answer and escaping rules apply verbatim: every flag in `renderArgv` whose following value is the empty string MUST still be passed explicitly (e.g. `--
|
|
274
|
+
Step 3's empty-answer and escaping rules apply verbatim: every flag in `renderArgv` whose following value is the empty string MUST still be passed explicitly (e.g. `--related-tasks ""`, `--directive ""`) — the wizard's intent is always "flag present with empty value", even where prepare's default happens to be the same empty value.
|
|
275
275
|
|
|
276
276
|
`renderArgv` already contains exactly one `--lead-runtime <host-runtime>` pair. Do not add or replace it. Do not enumerate a fixed provider list in this skill because the wizard and role model pool own the ordered tokens.
|
|
277
277
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# {{ flow.title }}
|
|
2
|
+
|
|
3
|
+
{{ flow.summary }}
|
|
4
|
+
|
|
5
|
+
- Mode: `{{ execution.request.mode }}`
|
|
6
|
+
- Status: `{{ execution.status }}`
|
|
7
|
+
- Execution: `{{ execution.id }}`
|
|
8
|
+
{% if execution.request.baseline %}
|
|
9
|
+
- Before-state explanation: `{{ execution.request.baseline }}`
|
|
10
|
+
{% endif %}
|
|
11
|
+
|
|
12
|
+
## Business terms
|
|
13
|
+
{% for row in flow.terms %}
|
|
14
|
+
- **{{ row.term }}**: {{ row.meaning }}
|
|
15
|
+
{% endfor %}
|
|
16
|
+
|
|
17
|
+
## Business steps
|
|
18
|
+
{% for step in flow.steps %}
|
|
19
|
+
### {{ loop.index }}. {{ step.title }}
|
|
20
|
+
|
|
21
|
+
{{ step.purpose }}
|
|
22
|
+
|
|
23
|
+
- Projects: {{ step.projectIds | join(', ') }}
|
|
24
|
+
{% for field in ['inputs', 'outputs', 'rules', 'states', 'failures', 'retries', 'recovery', 'risks', 'sideEffects'] %}
|
|
25
|
+
|
|
26
|
+
#### {{ field }}
|
|
27
|
+
{% for value in step[field] %}
|
|
28
|
+
- {{ value }}
|
|
29
|
+
{% endfor %}
|
|
30
|
+
{% endfor %}
|
|
31
|
+
{% if step.before %}
|
|
32
|
+
|
|
33
|
+
Before: {{ step.before }}
|
|
34
|
+
{% endif %}
|
|
35
|
+
{% if step.after %}
|
|
36
|
+
|
|
37
|
+
After: {{ step.after }}
|
|
38
|
+
{% endif %}
|
|
39
|
+
|
|
40
|
+
Claim keys: {{ step.factKeys | join(', ') }}
|
|
41
|
+
{% endfor %}
|
|
42
|
+
|
|
43
|
+
## Related projects
|
|
44
|
+
|
|
45
|
+
| From | To | Relationship | Business data | Claim keys |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
{% for row in flow.relationships %}
|
|
48
|
+
| {{ row.from | cell }} | {{ row.to | cell }} | {{ row.kind | cell }} | {{ row.data | cell }} | {{ row.factKeys | join(', ') | cell }} |
|
|
49
|
+
{% endfor %}
|
|
50
|
+
|
|
51
|
+
## Business facts and evidence
|
|
52
|
+
{% for fact in flow.facts %}
|
|
53
|
+
### {{ fact.key }}
|
|
54
|
+
|
|
55
|
+
{{ fact.value }}
|
|
56
|
+
|
|
57
|
+
Evidence level: `{{ fact.level }}`
|
|
58
|
+
{% for evidence in fact.evidence %}
|
|
59
|
+
|
|
60
|
+
- `{{ evidence.projectId }}:{{ evidence.path }}:{{ evidence.line }}-{{ evidence.endLine }}`
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
{{ evidence.excerpt }}
|
|
64
|
+
```
|
|
65
|
+
{% endfor %}
|
|
66
|
+
{% for receipt in fact.get('executionEvidence', []) %}
|
|
67
|
+
- Existing execution record: `{{ receipt }}`
|
|
68
|
+
{% endfor %}
|
|
69
|
+
{% endfor %}
|
|
70
|
+
|
|
71
|
+
## Source comparison
|
|
72
|
+
{% for row in execution.changes %}
|
|
73
|
+
- {{ row.projectId }}: {% if row.baselineAvailable %}baseline available{% else %}no comparison baseline{% endif %}
|
|
74
|
+
{% for path in row.paths %}
|
|
75
|
+
- `{{ path }}`
|
|
76
|
+
{% endfor %}
|
|
77
|
+
{% endfor %}
|
|
78
|
+
|
|
79
|
+
## Shared knowledge versions and conflicts
|
|
80
|
+
{% for claim in claims %}
|
|
81
|
+
- `{{ claim.key }}`: {{ claim.value }} ({{ claim.level }}; {{ claim.status }})
|
|
82
|
+
- Claim: `{{ claim.id }}`; producers: {{ claim.producers }}
|
|
83
|
+
{% for projectId, state in claim.sourceStates.items() %}
|
|
84
|
+
- Source: `{{ projectId }}` at `{{ state.head }}`; fingerprint: `{{ state.digest }}`
|
|
85
|
+
{% endfor %}
|
|
86
|
+
{% endfor %}
|
|
87
|
+
|
|
88
|
+
## Investigation limits
|
|
89
|
+
|
|
90
|
+
Policy: `{{ execution.readOnlyAudit.policy }}`; enforced boundary: `{{ execution.readOnlyAudit.boundary }}`; audit coverage: `{{ execution.readOnlyAudit.coverage }}`.
|
|
91
|
+
{% for surface in execution.readOnlyAudit.unobserved %}
|
|
92
|
+
- Unobserved: {{ surface }}
|
|
93
|
+
{% endfor %}
|
|
94
|
+
{% if execution.get('reconciliation') %}
|
|
95
|
+
- Reinvestigation: `{{ execution.reconciliation.executionId }}` ({{ execution.reconciliation.status }})
|
|
96
|
+
{% endif %}
|
|
97
|
+
|
|
98
|
+
## Unverified segments
|
|
99
|
+
{% for gap in flow.gaps %}
|
|
100
|
+
- {{ gap }}
|
|
101
|
+
{% endfor %}
|
|
102
|
+
|
|
103
|
+
## Unresolved business questions
|
|
104
|
+
{% for question in flow.questions %}
|
|
105
|
+
- {{ question }}
|
|
106
|
+
{% endfor %}
|
|
@@ -44,6 +44,17 @@
|
|
|
44
44
|
</header>
|
|
45
45
|
<main id="main-content" data-report-role="human-main">
|
|
46
46
|
{% block human_content %}{% endblock %}
|
|
47
|
+
{% if businessFlow | default([]) %}
|
|
48
|
+
<section data-report-section="business-flow">
|
|
49
|
+
<h2>Business Flow Explanations</h2>
|
|
50
|
+
{% for row in businessFlow %}
|
|
51
|
+
<p>{{ row.mode }}: {{ row.status }}. {{ row.summary }}</p>
|
|
52
|
+
<ul>{% for kind, path in row.reports.items() %}<li><a href="{{ path }}">{{ kind }}</a></li>{% endfor %}</ul>
|
|
53
|
+
{% if row.error %}<p>Explanation error: {{ row.error }}</p>{% endif %}
|
|
54
|
+
{% if row.id %}<p>Retry: <code>okstra explain-flow rerun --execution {{ row.id }}</code></p>{% endif %}
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</section>
|
|
57
|
+
{% endif %}
|
|
47
58
|
{% if endStates %}
|
|
48
59
|
<section data-report-section="brief-end-states">
|
|
49
60
|
<h2>{{ t('base.brief-end-states') }}</h2>
|
|
@@ -54,9 +65,11 @@
|
|
|
54
65
|
</table>
|
|
55
66
|
</section>
|
|
56
67
|
{% endif %}
|
|
57
|
-
{%
|
|
68
|
+
{% set no_cross_model_check = taskType != "release-handoff" and (crossVerification.get("roundHistory") or {}).get("round2SkippedReason") == "single-analyser-only" %}
|
|
69
|
+
{% if crossVerification.get("consensus") or crossVerification.get("differences") or no_cross_model_check %}
|
|
58
70
|
<section data-report-section="cross-check">
|
|
59
71
|
<h2>{{ t('base.cross-check') }}</h2>
|
|
72
|
+
{% if no_cross_model_check %}<p class="section-lede">{{ t('base.no-cross-model-check') }}</p>{% endif %}
|
|
60
73
|
{% if crossVerification.get("consensus") %}
|
|
61
74
|
<h3>{{ t('base.agreed-across-workers') }}</h3>
|
|
62
75
|
<div class="summary-grid">{% for row in crossVerification.consensus %}<article class="summary-card" id="id-xv-{{ row.id }}"><p class="eyebrow">{{ row.id | inline_code }}</p><h3>{{ row.statement | inline_code }}</h3><p class="evidence-refs">{{ t('macros.layout.evidence') }} {{ row.evidence | inline_code }}</p></article>{% endfor %}</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="{{ execution.request.report_language }}">
|
|
3
|
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{{ flow.title }}</title><style>{{ css | safe }}</style></head>
|
|
4
|
+
<body><main>
|
|
5
|
+
<h1>{{ flow.title }}</h1><p>{{ flow.summary }}</p>
|
|
6
|
+
<p>Mode: <code>{{ execution.request.mode }}</code> · Status: <code>{{ execution.status }}</code></p>
|
|
7
|
+
{% if execution.request.baseline %}<p>Before-state explanation: <code>{{ execution.request.baseline }}</code></p>{% endif %}
|
|
8
|
+
<h2>Business terms</h2><dl>{% for row in flow.terms %}<dt>{{ row.term }}</dt><dd>{{ row.meaning }}</dd>{% endfor %}</dl>
|
|
9
|
+
<h2>Business steps</h2>
|
|
10
|
+
{% for step in flow.steps %}
|
|
11
|
+
<section id="step-{{ loop.index }}"><h3>{{ loop.index }}. {{ step.title }}</h3><p>{{ step.purpose }}</p><p>Projects: {{ step.projectIds | join(', ') }}</p>
|
|
12
|
+
{% for field in ['inputs', 'outputs', 'rules', 'states', 'failures', 'retries', 'recovery', 'risks', 'sideEffects'] %}
|
|
13
|
+
<h4>{{ field }}</h4><ul>{% for value in step[field] %}<li>{{ value }}</li>{% endfor %}</ul>
|
|
14
|
+
{% endfor %}
|
|
15
|
+
{% if step.before %}<p>Before: {{ step.before }}</p>{% endif %}{% if step.after %}<p>After: {{ step.after }}</p>{% endif %}
|
|
16
|
+
<p>Claim keys: {{ step.factKeys | join(', ') }}</p></section>
|
|
17
|
+
{% endfor %}
|
|
18
|
+
<h2>Related projects</h2><table><thead><tr><th>From</th><th>To</th><th>Relationship</th><th>Business data</th><th>Claim keys</th></tr></thead><tbody>
|
|
19
|
+
{% for row in flow.relationships %}<tr><td>{{ row.from }}</td><td>{{ row.to }}</td><td>{{ row.kind }}</td><td>{{ row.data }}</td><td>{{ row.factKeys | join(', ') }}</td></tr>{% endfor %}
|
|
20
|
+
</tbody></table>
|
|
21
|
+
<h2>Business facts and evidence</h2>
|
|
22
|
+
{% for fact in flow.facts %}<section><h3>{{ fact.key }}</h3><p>{{ fact.value }}</p><p>Evidence level: <code>{{ fact.level }}</code></p>
|
|
23
|
+
{% for evidence in fact.evidence %}<p><code>{{ evidence.projectId }}:{{ evidence.path }}:{{ evidence.line }}-{{ evidence.endLine }}</code></p><pre><code>{{ evidence.excerpt }}</code></pre>{% endfor %}
|
|
24
|
+
{% for receipt in fact.get('executionEvidence', []) %}<p>Existing execution record: <code>{{ receipt }}</code></p>{% endfor %}</section>{% endfor %}
|
|
25
|
+
<h2>Source comparison</h2><ul>{% for row in execution.changes %}<li>{{ row.projectId }}: {% if row.baselineAvailable %}baseline available{% else %}no comparison baseline{% endif %}<ul>{% for path in row.paths %}<li><code>{{ path }}</code></li>{% endfor %}</ul></li>{% endfor %}</ul>
|
|
26
|
+
<h2>Unverified segments</h2><ul>{% for gap in flow.gaps %}<li>{{ gap }}</li>{% endfor %}</ul>
|
|
27
|
+
<h2>Shared knowledge versions and conflicts</h2><ul>{% for claim in claims %}<li>{{ claim.key }}: {{ claim.value }} ({{ claim.level }}; {{ claim.status }})<ul><li>Claim: <code>{{ claim.id }}</code>; producers: {{ claim.producers }}</li>{% for projectId, state in claim.sourceStates.items() %}<li>Source: {{ projectId }} at <code>{{ state.head }}</code>; fingerprint: <code>{{ state.digest }}</code></li>{% endfor %}</ul></li>{% endfor %}</ul>
|
|
28
|
+
<h2>Investigation limits</h2><p>Policy: {{ execution.readOnlyAudit.policy }}; enforced boundary: {{ execution.readOnlyAudit.boundary }}; audit coverage: {{ execution.readOnlyAudit.coverage }}.</p><ul>{% for surface in execution.readOnlyAudit.unobserved %}<li>Unobserved: {{ surface }}</li>{% endfor %}</ul>
|
|
29
|
+
{% if execution.get('reconciliation') %}<p>Reinvestigation: <code>{{ execution.reconciliation.executionId }}</code> ({{ execution.reconciliation.status }})</p>{% endif %}
|
|
30
|
+
<h2>Unresolved business questions</h2><ul>{% for question in flow.questions %}<li>{{ question }}</li>{% endfor %}</ul>
|
|
31
|
+
</main></body></html>
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"elapsed": "Elapsed",
|
|
173
173
|
"evidence-ledger": "Evidence ledger",
|
|
174
174
|
"cross-check": "Worker agreement and dissent",
|
|
175
|
+
"no-cross-model-check": "No cross-model check ran: fewer than two analysis workers were selected, so no other model verified these findings.",
|
|
175
176
|
"agreed-across-workers": "Agreed across workers",
|
|
176
177
|
"workers-disagreed": "Workers disagreed",
|
|
177
178
|
"source": "Source",
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"elapsed": "소요 시간",
|
|
173
173
|
"evidence-ledger": "근거 대장",
|
|
174
174
|
"cross-check": "작업자 합의와 이견",
|
|
175
|
+
"no-cross-model-check": "교차 모델 검증을 하지 않았습니다. 분석 작업자를 두 명 미만으로 골라 다른 모델이 이 결과를 검증하지 않았습니다.",
|
|
175
176
|
"agreed-across-workers": "작업자 간 합의",
|
|
176
177
|
"workers-disagreed": "작업자 간 이견",
|
|
177
178
|
"source": "출처",
|
|
@@ -40,9 +40,18 @@ Every analysis result starts with YAML frontmatter containing the task identity,
|
|
|
40
40
|
3. Safe or Reasonable Areas
|
|
41
41
|
4. Uncertain Points
|
|
42
42
|
5. Recommended Next Actions
|
|
43
|
-
6.
|
|
43
|
+
6. Coverage Verdicts (when the packet carries `## Coverage Census`)
|
|
44
|
+
7. Specialization Lens (optional, additive only)
|
|
44
45
|
|
|
45
|
-
Every item has a worker-local ID and file:line evidence where code evidence exists. Sections 1–5 are the common core: feasibility, requirement interpretation, hidden assumptions, alternatives, and execution risk. Section
|
|
46
|
+
Every item has a worker-local ID and file:line evidence where code evidence exists. Sections 1–5 are the common core: feasibility, requirement interpretation, hidden assumptions, alternatives, and execution risk. Section 7 is the only legal home for specialization and is not consensus input.
|
|
47
|
+
|
|
48
|
+
Section 6 judges every cell of the packet's `## Coverage Census`, one line per cell, using the cell id verbatim:
|
|
49
|
+
|
|
50
|
+
- `- <cell-id>: clean — <path:line evidence>`
|
|
51
|
+
- `- <cell-id>: finding <worker-local ID>` — the ID of an item in sections 1–5
|
|
52
|
+
- `- <cell-id>: n/a — <reason>`
|
|
53
|
+
|
|
54
|
+
A cell with no line, a `clean` without a `path:line` citation, an `n/a` without a reason, or a `finding` naming an ID your result does not contain counts as unjudged. The lead sends unjudged cells back to you once, in a `census-gapfill` dispatch; whatever stays unjudged after it is reported as a coverage warning. That dispatch narrows this contract: write only Findings (the new items its verdicts point to, or `- none`) and Coverage Verdicts for the cells it lists. When the packet has no `## Coverage Census`, omit section 6.
|
|
46
55
|
|
|
47
56
|
## Return message to the lead
|
|
48
57
|
|
|
@@ -22,30 +22,30 @@ def validate_prompt_contract(
|
|
|
22
22
|
worker_ids: list[str],
|
|
23
23
|
prompt_dir: str,
|
|
24
24
|
prompt_map: object,
|
|
25
|
-
|
|
25
|
+
worker_role_rows: object,
|
|
26
26
|
) -> None:
|
|
27
27
|
if not prompt_dir:
|
|
28
28
|
errors.append(f"{prefix} is missing worker prompt directory metadata")
|
|
29
29
|
if not isinstance(prompt_map, dict):
|
|
30
30
|
errors.append(f"{prefix} worker prompt map is missing or invalid")
|
|
31
31
|
prompt_map = {}
|
|
32
|
-
if not isinstance(
|
|
33
|
-
errors.append(f"{prefix}
|
|
34
|
-
|
|
32
|
+
if not isinstance(worker_role_rows, list):
|
|
33
|
+
errors.append(f"{prefix} workerRoles is missing or invalid")
|
|
34
|
+
worker_role_rows = []
|
|
35
35
|
|
|
36
36
|
expected_dir_prefix = prompt_dir.rstrip("/") + "/" if prompt_dir else ""
|
|
37
37
|
for worker_id in worker_ids:
|
|
38
38
|
if worker_id not in prompt_map:
|
|
39
39
|
errors.append(f"{prefix} worker prompt map is missing selected worker: {worker_id}")
|
|
40
40
|
|
|
41
|
-
for worker in
|
|
41
|
+
for worker in worker_role_rows:
|
|
42
42
|
if not isinstance(worker, dict):
|
|
43
|
-
errors.append(f"{prefix}
|
|
43
|
+
errors.append(f"{prefix} workerRoles contains a non-object entry")
|
|
44
44
|
continue
|
|
45
45
|
worker_id = str(worker.get("workerId", "")).strip()
|
|
46
46
|
prompt_relative = str(worker.get("promptPath", "")).strip()
|
|
47
47
|
if not worker_id:
|
|
48
|
-
errors.append(f"{prefix}
|
|
48
|
+
errors.append(f"{prefix} workerRoles contains an entry without workerId")
|
|
49
49
|
continue
|
|
50
50
|
if not prompt_relative:
|
|
51
51
|
errors.append(
|
|
@@ -54,7 +54,7 @@ def validate_prompt_contract(
|
|
|
54
54
|
continue
|
|
55
55
|
if prompt_map.get(worker_id) != prompt_relative:
|
|
56
56
|
errors.append(
|
|
57
|
-
f"{prefix} worker prompt map does not match
|
|
57
|
+
f"{prefix} worker prompt map does not match workerRoles for {worker_id}"
|
|
58
58
|
)
|
|
59
59
|
if expected_dir_prefix and not prompt_relative.startswith(expected_dir_prefix):
|
|
60
60
|
errors.append(
|
|
@@ -85,7 +85,7 @@ else:
|
|
|
85
85
|
selected_workers,
|
|
86
86
|
str(task_artifacts.get("workerPromptsDirectoryPath", "")).strip(),
|
|
87
87
|
task_artifacts.get("workerPromptPathByWorkerId"),
|
|
88
|
-
task_manifest.get("resultContract", {}).get("
|
|
88
|
+
task_manifest.get("resultContract", {}).get("workerRoles"),
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
if (
|
|
@@ -167,7 +167,7 @@ else:
|
|
|
167
167
|
selected_workers,
|
|
168
168
|
run_prompt_dir,
|
|
169
169
|
run_manifest.get("workerPromptPathByWorkerId"),
|
|
170
|
-
run_manifest.get("teamContract", {}).get("
|
|
170
|
+
run_manifest.get("teamContract", {}).get("workerRoles"),
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
team_state_relative_path = str(
|
|
@@ -55,6 +55,7 @@ from okstra_ctl.phases.implementation_planning.guidance import (
|
|
|
55
55
|
_validate_rerun_guidance as _validate_rerun_guidance,
|
|
56
56
|
_validate_approval_guidance as _validate_approval_guidance,
|
|
57
57
|
)
|
|
58
|
+
from okstra_ctl.coverage_census import census_advisories, read_census_state
|
|
58
59
|
from okstra_ctl.plan_approval import plan_is_approved as _report_already_approved
|
|
59
60
|
from okstra_ctl.report_validation_identity import (
|
|
60
61
|
_report_run_seq as _report_run_seq,
|
|
@@ -1248,18 +1249,18 @@ def extract_contract(
|
|
|
1248
1249
|
if not isinstance(task_contract, dict):
|
|
1249
1250
|
task_contract = {}
|
|
1250
1251
|
|
|
1251
|
-
|
|
1252
|
-
if not isinstance(
|
|
1253
|
-
|
|
1254
|
-
if not isinstance(
|
|
1255
|
-
|
|
1256
|
-
failures.append("
|
|
1252
|
+
worker_role_rows = run_contract.get("workerRoles")
|
|
1253
|
+
if not isinstance(worker_role_rows, list):
|
|
1254
|
+
worker_role_rows = task_contract.get("workerRoles")
|
|
1255
|
+
if not isinstance(worker_role_rows, list):
|
|
1256
|
+
worker_role_rows = []
|
|
1257
|
+
failures.append("workerRoles is missing from run/task manifest")
|
|
1257
1258
|
|
|
1258
|
-
|
|
1259
|
-
if not isinstance(
|
|
1260
|
-
|
|
1261
|
-
if not isinstance(
|
|
1262
|
-
|
|
1259
|
+
critic_role_rows = run_contract.get("criticRoles")
|
|
1260
|
+
if not isinstance(critic_role_rows, list):
|
|
1261
|
+
critic_role_rows = task_contract.get("criticRoles")
|
|
1262
|
+
if not isinstance(critic_role_rows, list):
|
|
1263
|
+
critic_role_rows = []
|
|
1263
1264
|
|
|
1264
1265
|
lead_role = (
|
|
1265
1266
|
run_contract.get("leadRole")
|
|
@@ -1273,7 +1274,7 @@ def extract_contract(
|
|
|
1273
1274
|
if not isinstance(required_agent_status_entries, list):
|
|
1274
1275
|
required_agent_status_entries = [lead_role] + [
|
|
1275
1276
|
item.get("role", "")
|
|
1276
|
-
for item in
|
|
1277
|
+
for item in worker_role_rows
|
|
1277
1278
|
if isinstance(item, dict) and item.get("role")
|
|
1278
1279
|
]
|
|
1279
1280
|
|
|
@@ -1290,8 +1291,8 @@ def extract_contract(
|
|
|
1290
1291
|
or task_contract.get("leadModelExecutionValue")
|
|
1291
1292
|
or ""
|
|
1292
1293
|
),
|
|
1293
|
-
"
|
|
1294
|
-
"
|
|
1294
|
+
"worker_role_rows": worker_role_rows,
|
|
1295
|
+
"critic_role_rows": critic_role_rows,
|
|
1295
1296
|
"required_agent_status_entries": [
|
|
1296
1297
|
item
|
|
1297
1298
|
for item in required_agent_status_entries
|
|
@@ -1460,7 +1461,7 @@ def _validate_initial_analysis_prompts(
|
|
|
1460
1461
|
return
|
|
1461
1462
|
run_manifest = data.get("runManifest") or {}
|
|
1462
1463
|
team_contract = run_manifest.get("teamContract") or {}
|
|
1463
|
-
selected_workers = team_contract.get("
|
|
1464
|
+
selected_workers = team_contract.get("workerRoles") or []
|
|
1464
1465
|
selected_worker_ids = [
|
|
1465
1466
|
str(worker.get("workerId") or "").strip()
|
|
1466
1467
|
for worker in selected_workers
|
|
@@ -1598,6 +1599,30 @@ def _validate_cmux_workers_were_dispatched_by_okstra(
|
|
|
1598
1599
|
)
|
|
1599
1600
|
|
|
1600
1601
|
|
|
1602
|
+
_CROSS_VERIFICATION_ROLES = frozenset({"analyser", "designer", "planner", "verifier"})
|
|
1603
|
+
|
|
1604
|
+
|
|
1605
|
+
def single_verifier_advisories(run_manifest: dict) -> list[str]:
|
|
1606
|
+
"""사용자가 한 명만 고른 교차 검증 역할은 다른 모델의 검증을 받지 않는다.
|
|
1607
|
+
|
|
1608
|
+
사용자 선택이므로 run 을 막지 않고, 사람이 보도록 advisory 로만 남긴다.
|
|
1609
|
+
재검증 칸(`sourceRoleExecutionRef` 가 있는 동적 역할)은 세지 않는다.
|
|
1610
|
+
"""
|
|
1611
|
+
counts: dict[str, int] = {}
|
|
1612
|
+
for row in run_manifest.get("executionRoles") or []:
|
|
1613
|
+
if not isinstance(row, dict) or row.get("sourceRoleExecutionRef"):
|
|
1614
|
+
continue
|
|
1615
|
+
role = row.get("role")
|
|
1616
|
+
if role in _CROSS_VERIFICATION_ROLES:
|
|
1617
|
+
counts[role] = counts.get(role, 0) + 1
|
|
1618
|
+
return [
|
|
1619
|
+
f"no cross-model check: only one {role} was selected, so no other "
|
|
1620
|
+
"model verified its results"
|
|
1621
|
+
for role, count in sorted(counts.items())
|
|
1622
|
+
if count == 1
|
|
1623
|
+
]
|
|
1624
|
+
|
|
1625
|
+
|
|
1601
1626
|
def validate_team_state(
|
|
1602
1627
|
team_state: dict,
|
|
1603
1628
|
project_root: Path,
|
|
@@ -1702,24 +1727,24 @@ def validate_team_state(
|
|
|
1702
1727
|
|
|
1703
1728
|
expected_workers: dict[str, dict] = {}
|
|
1704
1729
|
# 선택은 배정 전의 선택이다. 배정된 비평 역할도 결과 또는 생략 사유가
|
|
1705
|
-
# 있어야 하므로
|
|
1730
|
+
# 있어야 하므로 다른 선택 역할과 같은 상태 검사를 거친다.
|
|
1706
1731
|
for worker in [
|
|
1707
|
-
*contract["
|
|
1708
|
-
*contract.get("
|
|
1732
|
+
*contract["worker_role_rows"],
|
|
1733
|
+
*contract.get("critic_role_rows", []),
|
|
1709
1734
|
]:
|
|
1710
1735
|
if not isinstance(worker, dict):
|
|
1711
|
-
failures.append("
|
|
1736
|
+
failures.append("workerRoles contains a non-object entry")
|
|
1712
1737
|
continue
|
|
1713
1738
|
role = str(worker.get("role", "")).strip()
|
|
1714
1739
|
if not role:
|
|
1715
|
-
failures.append("
|
|
1740
|
+
failures.append("workerRoles contains an entry without role")
|
|
1716
1741
|
continue
|
|
1717
1742
|
expected_workers[role] = worker
|
|
1718
1743
|
|
|
1719
1744
|
for role, expected in expected_workers.items():
|
|
1720
1745
|
worker = by_role.get(role)
|
|
1721
1746
|
if worker is None:
|
|
1722
|
-
failures.append(f"missing
|
|
1747
|
+
failures.append(f"missing selected worker role: {role}")
|
|
1723
1748
|
continue
|
|
1724
1749
|
|
|
1725
1750
|
expected_worker_id = expected.get("workerId")
|
|
@@ -3120,6 +3145,7 @@ def validate_final_report_data(
|
|
|
3120
3145
|
print(f"validate-run: warning: {warning}", file=sys.stderr)
|
|
3121
3146
|
# Phase-agnostic: the coverage critic runs in every finding-producing phase.
|
|
3122
3147
|
_validate_unverified_critic_gaps_recorded(data, failures)
|
|
3148
|
+
_validate_census_coverage(data, manifest, project_root, failures)
|
|
3123
3149
|
# Called here rather than from a task-type branch: four profiles raise
|
|
3124
3150
|
# clarification rows, and the gate scopes itself by task type internally.
|
|
3125
3151
|
_validate_clarification_options(data, failures)
|
|
@@ -4078,6 +4104,28 @@ def _normalize_report_contracts(raw_contracts: object) -> set[str]:
|
|
|
4078
4104
|
}
|
|
4079
4105
|
|
|
4080
4106
|
|
|
4107
|
+
def _validate_census_coverage(
|
|
4108
|
+
data: Mapping[str, Any],
|
|
4109
|
+
manifest: Mapping[str, Any],
|
|
4110
|
+
project_root: Path | None,
|
|
4111
|
+
failures: list[str],
|
|
4112
|
+
) -> None:
|
|
4113
|
+
"""Coverage-census leftovers, reported and never blocking.
|
|
4114
|
+
|
|
4115
|
+
The user's rule for the census is that no cell state stops a run, so these
|
|
4116
|
+
strings carry no `blocking_checks` fragment and `partition` files them as
|
|
4117
|
+
`validate-run: advisory`.
|
|
4118
|
+
"""
|
|
4119
|
+
if project_root is None:
|
|
4120
|
+
return
|
|
4121
|
+
state = read_census_state(project_root, manifest)
|
|
4122
|
+
if state is None:
|
|
4123
|
+
return
|
|
4124
|
+
failures.extend(
|
|
4125
|
+
census_advisories(state, data.get("missingInformation") or [])
|
|
4126
|
+
)
|
|
4127
|
+
|
|
4128
|
+
|
|
4081
4129
|
CRITIC_UNVERIFIED_SOURCE = "critic-unverified"
|
|
4082
4130
|
|
|
4083
4131
|
|
|
@@ -5853,6 +5901,7 @@ def main() -> int:
|
|
|
5853
5901
|
failures.extend(autofix_messages)
|
|
5854
5902
|
_validate_execution_identity_v2(run_manifest, failures)
|
|
5855
5903
|
contract = extract_contract(run_manifest, task_manifest, failures)
|
|
5904
|
+
advisories.extend(single_verifier_advisories(run_manifest))
|
|
5856
5905
|
concurrent_run_authorized = bool(
|
|
5857
5906
|
(run_manifest.get("concurrentRun") or {}).get("detected")
|
|
5858
5907
|
)
|