okstra 0.179.2 → 0.183.2
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 +1 -1
- package/dist/cli-registry.mjs +30 -2
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/incremental-carry.mjs +9 -8
- package/dist/commands/execute/incremental-carry.mjs.map +1 -1
- package/dist/commands/execute/plan-verify.mjs +3 -1
- package/dist/commands/execute/plan-verify.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.d.mts +4 -2
- package/dist/commands/execute/render-bundle.mjs +46 -5
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +11 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/inspect/model-io.d.mts +1 -0
- package/dist/commands/inspect/model-io.mjs +25 -0
- package/dist/commands/inspect/model-io.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.mjs +29 -8
- package/dist/commands/inspect/stage-map.mjs.map +1 -1
- package/dist/commands/inspect/task-list.mjs +52 -6
- package/dist/commands/inspect/task-list.mjs.map +1 -1
- package/dist/commands/inspect/user-response.mjs +14 -4
- package/dist/commands/inspect/user-response.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.d.mts +1 -0
- package/dist/commands/lifecycle/check-project.mjs +69 -50
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/contract-check.d.mts +1 -0
- package/dist/commands/lifecycle/contract-check.mjs +18 -0
- package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.mjs +154 -51
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/pr/pr.d.mts +1 -0
- package/dist/commands/pr/pr.mjs +19 -1
- package/dist/commands/pr/pr.mjs.map +1 -1
- package/dist/commands/report/agent-activity.mjs +2 -2
- package/dist/commands/report/approval-decision.d.mts +1 -0
- package/dist/commands/report/approval-decision.mjs +21 -0
- package/dist/commands/report/approval-decision.mjs.map +1 -0
- package/dist/commands/report/design-snapshot.d.mts +1 -0
- package/dist/commands/report/design-snapshot.mjs +19 -0
- package/dist/commands/report/design-snapshot.mjs.map +1 -0
- package/dist/commands/report/translate.mjs +3 -0
- package/dist/commands/report/translate.mjs.map +1 -1
- package/dist/lib/host-registry-client.mjs +13 -9
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +21 -10
- package/docs/cli.md +36 -20
- package/docs/container.md +6 -4
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
- package/docs/for-ai/skills/okstra-code-review.md +4 -4
- package/docs/for-ai/skills/okstra-container-build.md +20 -17
- package/docs/for-ai/skills/okstra-inspect.md +20 -23
- package/docs/for-ai/skills/okstra-manager.md +19 -18
- package/docs/for-ai/skills/okstra-memory.md +2 -2
- package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
- package/docs/for-ai/skills/okstra-rollup.md +14 -13
- package/docs/for-ai/skills/okstra-run.md +7 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
- package/docs/for-ai/skills/okstra-setup.md +7 -7
- package/docs/for-ai/skills/okstra-usage.md +5 -4
- package/docs/for-ai/skills/okstra-user-response.md +50 -32
- package/docs/project-structure-overview.md +43 -31
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +7 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/report-writer-worker.md +15 -164
- package/runtime/agents/workers/translator-worker.md +5 -13
- package/runtime/bin/okstra-error-log.py +51 -11
- package/runtime/bin/okstra-report-translate.py +210 -23
- package/runtime/prompts/host-orchestration/implementation.md +1 -1
- package/runtime/prompts/launch.template.md +9 -12
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/context-loader.md +41 -141
- package/runtime/prompts/lead/convergence.md +10 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +40 -49
- package/runtime/prompts/lead/plan-body-verification.md +217 -23
- package/runtime/prompts/lead/report-writer.md +81 -421
- package/runtime/prompts/lead/team-contract.md +9 -54
- package/runtime/prompts/profiles/_clarification-recommendation.md +5 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/feature-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +20 -12
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/project-analysis.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +0 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +8 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
- package/runtime/python/okstra_ctl/agent_activity.py +141 -38
- package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +70 -23
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
- package/runtime/python/okstra_ctl/analysis_packet.py +124 -3
- package/runtime/python/okstra_ctl/approval_decisions.py +328 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
- package/runtime/python/okstra_ctl/backfill.py +13 -10
- package/runtime/python/okstra_ctl/batch.py +2 -4
- package/runtime/python/okstra_ctl/build_tools.py +6 -3
- package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
- package/runtime/python/okstra_ctl/clarification_items.py +27 -13
- package/runtime/python/okstra_ctl/cmux.py +130 -52
- package/runtime/python/okstra_ctl/code_review_target.py +34 -8
- package/runtime/python/okstra_ctl/consumers.py +5 -4
- package/runtime/python/okstra_ctl/container.py +103 -8
- package/runtime/python/okstra_ctl/context_cost.py +2 -1
- package/runtime/python/okstra_ctl/contract_graph.py +497 -0
- package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
- package/runtime/python/okstra_ctl/convergence.py +338 -17
- package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
- package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
- package/runtime/python/okstra_ctl/convergence_store.py +55 -34
- package/runtime/python/okstra_ctl/design_prep.py +7 -4
- package/runtime/python/okstra_ctl/design_snapshot.py +134 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +97 -69
- package/runtime/python/okstra_ctl/dispatch_state.py +163 -63
- package/runtime/python/okstra_ctl/doctor.py +6 -3
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
- package/runtime/python/okstra_ctl/error_log_write.py +35 -30
- package/runtime/python/okstra_ctl/error_report.py +26 -1
- package/runtime/python/okstra_ctl/error_zip.py +27 -5
- package/runtime/python/okstra_ctl/execution_identity.py +3 -2
- package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -2
- package/runtime/python/okstra_ctl/final_report_schema.py +26 -17
- package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
- package/runtime/python/okstra_ctl/fixed_text.py +39 -0
- package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
- package/runtime/python/okstra_ctl/handoff.py +5 -4
- package/runtime/python/okstra_ctl/i18n.py +4 -2
- package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
- package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
- package/runtime/python/okstra_ctl/incremental_carry.py +130 -17
- package/runtime/python/okstra_ctl/incremental_scope.py +12 -4
- package/runtime/python/okstra_ctl/index.py +8 -11
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
- package/runtime/python/okstra_ctl/invocation.py +3 -6
- package/runtime/python/okstra_ctl/json_boundary.py +366 -0
- package/runtime/python/okstra_ctl/json_registry.py +10 -12
- package/runtime/python/okstra_ctl/jsonl.py +19 -2
- package/runtime/python/okstra_ctl/lead_events.py +33 -1
- package/runtime/python/okstra_ctl/listing.py +3 -3
- package/runtime/python/okstra_ctl/log_report.py +24 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -7
- package/runtime/python/okstra_ctl/manager_store.py +12 -10
- package/runtime/python/okstra_ctl/material.py +5 -1
- package/runtime/python/okstra_ctl/migrate.py +29 -25
- package/runtime/python/okstra_ctl/model_cli.py +3 -15
- package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
- package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
- package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
- package/runtime/python/okstra_ctl/path_hints.py +12 -0
- package/runtime/python/okstra_ctl/paths.py +21 -0
- package/runtime/python/okstra_ctl/plan_items.py +115 -4
- package/runtime/python/okstra_ctl/plan_items_cli.py +626 -34
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +2 -1
- package/runtime/python/okstra_ctl/pr_template.py +3 -2
- package/runtime/python/okstra_ctl/project_meta.py +5 -7
- package/runtime/python/okstra_ctl/recap.py +5 -4
- package/runtime/python/okstra_ctl/reconcile.py +21 -27
- package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
- package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
- package/runtime/python/okstra_ctl/render.py +74 -16
- package/runtime/python/okstra_ctl/render_final_report.py +10 -8
- package/runtime/python/okstra_ctl/report_assembly.py +509 -0
- package/runtime/python/okstra_ctl/report_contract.py +2 -1
- package/runtime/python/okstra_ctl/report_finalize.py +67 -32
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_inputs.py +72 -0
- package/runtime/python/okstra_ctl/report_language.py +3 -2
- package/runtime/python/okstra_ctl/report_markdown.py +81 -8
- package/runtime/python/okstra_ctl/report_narrative.py +351 -0
- package/runtime/python/okstra_ctl/report_projections.py +265 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
- package/runtime/python/okstra_ctl/report_views.py +3 -2
- package/runtime/python/okstra_ctl/rollup.py +65 -4
- package/runtime/python/okstra_ctl/run.py +184 -65
- package/runtime/python/okstra_ctl/run_audit.py +3 -2
- package/runtime/python/okstra_ctl/run_context.py +6 -9
- package/runtime/python/okstra_ctl/run_index_row.py +2 -8
- package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
- package/runtime/python/okstra_ctl/schema_excerpt.py +15 -8
- package/runtime/python/okstra_ctl/session_transcript.py +27 -1
- package/runtime/python/okstra_ctl/set_work_status.py +64 -38
- package/runtime/python/okstra_ctl/stage_fix_carry.py +8 -6
- package/runtime/python/okstra_ctl/stage_ledger.py +132 -18
- package/runtime/python/okstra_ctl/stage_map.py +96 -28
- package/runtime/python/okstra_ctl/stage_targets.py +3 -4
- package/runtime/python/okstra_ctl/team.py +3 -2
- package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
- package/runtime/python/okstra_ctl/time_report.py +51 -4
- package/runtime/python/okstra_ctl/usage_identity.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +58 -4
- package/runtime/python/okstra_ctl/user_response.py +1431 -66
- package/runtime/python/okstra_ctl/wizard.py +11 -44
- package/runtime/python/okstra_ctl/work_categories.py +3 -2
- package/runtime/python/okstra_ctl/worker_dispatch.py +5 -2
- package/runtime/python/okstra_ctl/worker_prompt_body.py +49 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +31 -3
- package/runtime/python/okstra_ctl/worker_runner.py +14 -12
- package/runtime/python/okstra_ctl/workflow.py +2 -1
- package/runtime/python/okstra_ctl/worktree.py +3 -2
- package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
- package/runtime/python/okstra_ctl/write_policy.py +4 -2
- package/runtime/python/okstra_token_usage/antigravity.py +39 -12
- package/runtime/python/okstra_token_usage/collect.py +90 -38
- package/runtime/python/okstra_token_usage/grok.py +127 -0
- package/runtime/schemas/final-report-v2.0.schema.json +9 -0
- package/runtime/schemas/final-report-v3.0.schema.json +10219 -0
- package/runtime/schemas/report-narrative-v3.0.schema.json +30 -0
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
- package/runtime/skills/okstra-code-review/SKILL.md +21 -11
- package/runtime/skills/okstra-container-build/SKILL.md +18 -18
- package/runtime/skills/okstra-inspect/SKILL.md +12 -11
- package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
- package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
- package/runtime/skills/okstra-inspect/facets/history.md +9 -14
- package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
- package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
- package/runtime/skills/okstra-inspect/facets/report.md +6 -10
- package/runtime/skills/okstra-inspect/facets/status.md +9 -8
- package/runtime/skills/okstra-inspect/facets/time.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +16 -14
- package/runtime/skills/okstra-memory/SKILL.md +3 -3
- package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
- package/runtime/skills/okstra-rollup/SKILL.md +6 -16
- package/runtime/skills/okstra-run/SKILL.md +7 -7
- package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
- package/runtime/skills/okstra-setup/SKILL.md +21 -13
- package/runtime/skills/okstra-setup/references/project-config.md +2 -2
- package/runtime/skills/okstra-usage/SKILL.md +10 -10
- package/runtime/skills/okstra-user-response/SKILL.md +78 -107
- package/runtime/templates/report-writer-prompt-preamble.md +29 -19
- package/runtime/templates/reports/html/macros/forms.html +6 -4
- package/runtime/templates/reports/schedule.template.md +4 -4
- package/runtime/templates/worker-error-contract.md +17 -29
- package/runtime/validators/validate-run.py +636 -72
- package/runtime/validators/validate_session_conformance.py +43 -7
|
@@ -18,8 +18,12 @@ from __future__ import annotations
|
|
|
18
18
|
import json
|
|
19
19
|
import re
|
|
20
20
|
from pathlib import Path
|
|
21
|
+
from typing import Mapping
|
|
21
22
|
|
|
22
23
|
from okstra_ctl.convergence_engine import grouped_input_digest
|
|
24
|
+
from okstra_ctl.json_boundary import (
|
|
25
|
+
load_owned_object,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
GROUPS_BASENAME_RE = re.compile(
|
|
25
29
|
r"^convergence-groups-(?P<suffix>[a-z][a-z-]*?-\d{3})\.json$"
|
|
@@ -84,7 +88,7 @@ def groups_digest_ok(state_dir: Path, suffix: str, document: dict) -> bool:
|
|
|
84
88
|
"""
|
|
85
89
|
work_path = state_dir / f"convergence-work-{suffix}.json"
|
|
86
90
|
try:
|
|
87
|
-
work =
|
|
91
|
+
work = load_owned_object(work_path, artifact="convergence work state")
|
|
88
92
|
except (OSError, ValueError):
|
|
89
93
|
return True
|
|
90
94
|
recorded = work.get("groupsDigest") if isinstance(work, dict) else None
|
|
@@ -96,10 +100,18 @@ def groups_digest_ok(state_dir: Path, suffix: str, document: dict) -> bool:
|
|
|
96
100
|
def read_canonical_worker_result(
|
|
97
101
|
worker_results_dir: Path, worker: str, suffix: str
|
|
98
102
|
) -> str | None:
|
|
99
|
-
"""Return the `<worker
|
|
100
|
-
resolvable file directly inside ``worker-results/`` (missing, unreadable,
|
|
101
|
-
or a worker slug that is not a single path component).
|
|
102
|
-
|
|
103
|
+
"""Return the `<worker>-worker-<suffix>.md` text, or ``None`` when it is not
|
|
104
|
+
a resolvable file directly inside ``worker-results/`` (missing, unreadable,
|
|
105
|
+
or a worker slug that is not a single path component).
|
|
106
|
+
|
|
107
|
+
The literal `-worker` marker comes from the writer,
|
|
108
|
+
`worker_artifacts.artifacts_from_context`, which names results
|
|
109
|
+
`f"{worker_id}-worker{result_suffix}.md"` from a bare worker id. Dropping it
|
|
110
|
+
here made every lookup miss once the roster moved to bare ids, and a missed
|
|
111
|
+
lookup is silent by design (see `provenance_errors`), so the guard stopped
|
|
112
|
+
firing without any signal.
|
|
113
|
+
"""
|
|
114
|
+
candidate = worker_results_dir / f"{worker}-worker-{suffix}.md"
|
|
103
115
|
try:
|
|
104
116
|
if candidate.resolve().parent != worker_results_dir.resolve():
|
|
105
117
|
return None
|
|
@@ -141,13 +153,46 @@ def provenance_errors(
|
|
|
141
153
|
errors.append(
|
|
142
154
|
f"convergence groups `{groups_label}` group {finding_id} "
|
|
143
155
|
f"cites source item `{worker}:{item_id}`, but that ID does not "
|
|
144
|
-
f"occur in the worker's result file `{worker}-{suffix}.md` — a "
|
|
156
|
+
f"occur in the worker's result file `{worker}-worker-{suffix}.md` — a "
|
|
145
157
|
"grouping may not invent a provenance link to an item the "
|
|
146
158
|
"worker never reported."
|
|
147
159
|
)
|
|
148
160
|
return errors
|
|
149
161
|
|
|
150
162
|
|
|
163
|
+
def worker_result_suffix(run_dir: Path, document: object) -> str | None:
|
|
164
|
+
"""`<task-type>-<seq>` naming the worker results a grouping cites.
|
|
165
|
+
|
|
166
|
+
A grouping's own filename carries the **state** sequence, but worker results
|
|
167
|
+
are named with the **workerResults** sequence — separate counters, both
|
|
168
|
+
written by `render.render_run_manifest`. They happened to agree on early
|
|
169
|
+
runs, which is why reusing the groups suffix went unnoticed.
|
|
170
|
+
|
|
171
|
+
The link is the grouping's `runManifestPath`, required by
|
|
172
|
+
`convergence-groups-v2.0.schema.json`. Only its basename is used: the
|
|
173
|
+
grouping and its run manifest live in the same run directory, so this does
|
|
174
|
+
not need a project root.
|
|
175
|
+
"""
|
|
176
|
+
if not isinstance(document, Mapping):
|
|
177
|
+
return None
|
|
178
|
+
recorded = document.get("runManifestPath")
|
|
179
|
+
if not _nonempty_string(recorded):
|
|
180
|
+
return None
|
|
181
|
+
manifest_path = Path(run_dir) / "manifests" / Path(str(recorded)).name
|
|
182
|
+
try:
|
|
183
|
+
manifest = load_owned_object(manifest_path, artifact="run manifest")
|
|
184
|
+
except (OSError, ValueError):
|
|
185
|
+
return None
|
|
186
|
+
if not isinstance(manifest, Mapping):
|
|
187
|
+
return None
|
|
188
|
+
task_type = manifest.get("taskType")
|
|
189
|
+
sequences = manifest.get("runSequencesByCategory")
|
|
190
|
+
seq = sequences.get("workerResults") if isinstance(sequences, Mapping) else None
|
|
191
|
+
if not _nonempty_string(task_type) or not _nonempty_string(seq):
|
|
192
|
+
return None
|
|
193
|
+
return f"{task_type}-{seq}"
|
|
194
|
+
|
|
195
|
+
|
|
151
196
|
def run_dir_provenance_errors(run_dir: Path) -> list[str]:
|
|
152
197
|
"""Provenance errors across every grouping persisted under ``run_dir``.
|
|
153
198
|
|
|
@@ -167,18 +212,23 @@ def run_dir_provenance_errors(run_dir: Path) -> list[str]:
|
|
|
167
212
|
continue
|
|
168
213
|
suffix = match.group("suffix")
|
|
169
214
|
try:
|
|
170
|
-
document =
|
|
215
|
+
document = load_owned_object(
|
|
216
|
+
groups_path, artifact="convergence groups input"
|
|
217
|
+
)
|
|
171
218
|
except (OSError, ValueError):
|
|
172
219
|
continue
|
|
173
220
|
if not isinstance(document, dict):
|
|
174
221
|
continue
|
|
175
222
|
if not groups_digest_ok(state_dir, suffix, document):
|
|
176
223
|
continue
|
|
224
|
+
result_suffix = worker_result_suffix(Path(run_dir), document)
|
|
225
|
+
if result_suffix is None:
|
|
226
|
+
continue
|
|
177
227
|
errors.extend(
|
|
178
228
|
provenance_errors(
|
|
179
229
|
document,
|
|
180
230
|
worker_results_dir=worker_results_dir,
|
|
181
|
-
suffix=
|
|
231
|
+
suffix=result_suffix,
|
|
182
232
|
groups_label=groups_path.name,
|
|
183
233
|
)
|
|
184
234
|
)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"""Validated JSON loading and atomic persistence for convergence artifacts."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
4
|
from pathlib import Path
|
|
7
|
-
import tempfile
|
|
8
5
|
from typing import Any, Mapping
|
|
9
6
|
|
|
10
7
|
from .convergence_engine import ConvergenceContractError
|
|
@@ -14,6 +11,8 @@ from .execution_manifest import (
|
|
|
14
11
|
read_execution_manifest,
|
|
15
12
|
reserve_derived_role_invocation,
|
|
16
13
|
)
|
|
14
|
+
from .final_report_schema import load_named_schema
|
|
15
|
+
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
17
16
|
from .worker_request import verifier_extra_dirs
|
|
18
17
|
from .write_policy import build_invocation_write_contract
|
|
19
18
|
|
|
@@ -25,44 +24,66 @@ DYNAMIC_VERIFIER_SOURCE_ROLES = frozenset({
|
|
|
25
24
|
"verifier",
|
|
26
25
|
})
|
|
27
26
|
|
|
27
|
+
_CRITIC_BATCH_SCHEMA = {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"required": [
|
|
30
|
+
"schemaVersion", "taskKey", "mode", "provider",
|
|
31
|
+
"modelExecutionValue", "dispatches", "gaps",
|
|
32
|
+
],
|
|
33
|
+
"additionalProperties": False,
|
|
34
|
+
"properties": {
|
|
35
|
+
"schemaVersion": {"const": "1.0"},
|
|
36
|
+
"taskKey": {"type": "string", "pattern": "\\S"},
|
|
37
|
+
"mode": {"const": "coverage"},
|
|
38
|
+
"provider": {"type": "string", "pattern": "\\S"},
|
|
39
|
+
"modelExecutionValue": {"type": "string", "pattern": "\\S"},
|
|
40
|
+
"dispatches": {"type": "array", "items": {"type": "object"}},
|
|
41
|
+
"gaps": {"type": "array", "items": {"type": "object"}},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_owned_json_object(path: Path) -> dict[str, Any]:
|
|
47
|
+
try:
|
|
48
|
+
return load_owned_object(path, artifact="convergence artifact")
|
|
49
|
+
except JsonBoundaryError as exc:
|
|
50
|
+
raise ConvergenceContractError(str(exc)) from exc
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _load_convergence_result(
|
|
54
|
+
path: Path, *, schema: Mapping[str, Any]
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
try:
|
|
57
|
+
return load_owned_object(
|
|
58
|
+
path, artifact="convergence result", schema=schema
|
|
59
|
+
)
|
|
60
|
+
except JsonBoundaryError as exc:
|
|
61
|
+
raise ConvergenceContractError(str(exc)) from exc
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_convergence_round_results(path: Path) -> dict[str, Any]:
|
|
65
|
+
return _load_convergence_result(
|
|
66
|
+
path,
|
|
67
|
+
schema=load_named_schema("convergence-round-results-v1.0.schema.json"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_convergence_critic_batch(path: Path) -> dict[str, Any]:
|
|
72
|
+
return _load_convergence_result(path, schema=_CRITIC_BATCH_SCHEMA)
|
|
73
|
+
|
|
28
74
|
|
|
29
75
|
def load_json_object(path: Path) -> dict[str, Any]:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
raise ConvergenceContractError(f"JSON input must be an object: {path}")
|
|
33
|
-
return value
|
|
76
|
+
"""Compatibility alias for convergence artifacts owned by okstra."""
|
|
77
|
+
return load_owned_json_object(path)
|
|
34
78
|
|
|
35
79
|
|
|
36
80
|
def write_json_atomic(path: Path, payload: Mapping[str, Any]) -> None:
|
|
37
81
|
if not isinstance(payload, Mapping):
|
|
38
82
|
raise ConvergenceContractError("JSON output payload must be an object")
|
|
39
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
-
temp_path: Path | None = None
|
|
41
83
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
dir=path.parent,
|
|
46
|
-
prefix=f".{path.name}.",
|
|
47
|
-
suffix=".tmp",
|
|
48
|
-
delete=False,
|
|
49
|
-
) as handle:
|
|
50
|
-
temp_path = Path(handle.name)
|
|
51
|
-
json.dump(
|
|
52
|
-
payload,
|
|
53
|
-
handle,
|
|
54
|
-
ensure_ascii=False,
|
|
55
|
-
sort_keys=True,
|
|
56
|
-
indent=2,
|
|
57
|
-
)
|
|
58
|
-
handle.write("\n")
|
|
59
|
-
handle.flush()
|
|
60
|
-
os.fsync(handle.fileno())
|
|
61
|
-
os.replace(temp_path, path)
|
|
62
|
-
temp_path = None
|
|
63
|
-
finally:
|
|
64
|
-
if temp_path is not None:
|
|
65
|
-
temp_path.unlink(missing_ok=True)
|
|
84
|
+
write_owned_object_atomic(path, payload, artifact="convergence artifact")
|
|
85
|
+
except JsonBoundaryError as exc:
|
|
86
|
+
raise ConvergenceContractError(str(exc)) from exc
|
|
66
87
|
|
|
67
88
|
|
|
68
89
|
def write_final_state_atomic(
|
|
@@ -111,7 +132,7 @@ def reserve_dynamic_verifier(
|
|
|
111
132
|
source_role_execution_ref,
|
|
112
133
|
duty_id,
|
|
113
134
|
)
|
|
114
|
-
authority =
|
|
135
|
+
authority = load_owned_json_object(manifest_path)
|
|
115
136
|
if authority.get("taskKey") not in (None, task_key):
|
|
116
137
|
raise ExecutionManifestError("dynamic verifier taskKey does not match manifest")
|
|
117
138
|
project_root = _manifest_project_root(authority, manifest_path)
|
|
@@ -21,6 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
from typing import Any, Iterator
|
|
22
22
|
|
|
23
23
|
from .final_report_schema import load_schema, validate as validate_schema
|
|
24
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
24
25
|
from okstra_project import ResolverError, resolve_project_root
|
|
25
26
|
|
|
26
27
|
|
|
@@ -166,8 +167,10 @@ def assessment_fingerprint(item: Mapping[str, Any]) -> str:
|
|
|
166
167
|
|
|
167
168
|
def _load_plan_data(data_path: Path) -> dict[str, Any]:
|
|
168
169
|
try:
|
|
169
|
-
data =
|
|
170
|
-
except
|
|
170
|
+
data = load_owned_object(data_path, artifact="planning report record")
|
|
171
|
+
except JsonBoundaryError as exc:
|
|
172
|
+
if exc.reason == "top-level JSON value must be an object":
|
|
173
|
+
raise DesignPrepError(f"planning data must be an object: {data_path}") from exc
|
|
171
174
|
raise DesignPrepError(f"cannot read planning data {data_path}: {exc}") from exc
|
|
172
175
|
if not isinstance(data, dict):
|
|
173
176
|
raise DesignPrepError(f"planning data must be an object: {data_path}")
|
|
@@ -1045,8 +1048,8 @@ def _read_carry(path: Path, stage: int, warnings: list[str]) -> Mapping[str, Any
|
|
|
1045
1048
|
if not path.is_file():
|
|
1046
1049
|
return None
|
|
1047
1050
|
try:
|
|
1048
|
-
payload =
|
|
1049
|
-
except
|
|
1051
|
+
payload = load_owned_object(path, artifact="design preparation carry")
|
|
1052
|
+
except JsonBoundaryError as exc:
|
|
1050
1053
|
warnings.append(f"malformed carry stage {stage}; ignored: {exc}")
|
|
1051
1054
|
return None
|
|
1052
1055
|
if not isinstance(payload, Mapping):
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""보고서 서사에서 설계 표면 탐지기 소유 스냅샷을 생성한다."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Mapping
|
|
9
|
+
|
|
10
|
+
from .convergence_store import write_json_atomic
|
|
11
|
+
from .design_surfaces import DesignSurfaceError, detect_design_surfaces
|
|
12
|
+
from .final_report_schema import load_schema_version
|
|
13
|
+
from .report_narrative import NarrativeContractError, parse_narrative
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _evidence(trigger: Any) -> list[dict[str, Any]]:
|
|
17
|
+
return [
|
|
18
|
+
{"step": row.step, "field": row.field, "match": row.match}
|
|
19
|
+
for row in trigger.evidence
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_design_snapshot(planning: Mapping[str, Any]) -> dict[str, Any]:
|
|
24
|
+
"""계획 본문에서 재현 가능한 설계 표면과 보수적 준비 항목을 만든다."""
|
|
25
|
+
triggers = detect_design_surfaces(planning)
|
|
26
|
+
by_stage: dict[int, list[dict[str, Any]]] = {}
|
|
27
|
+
items = []
|
|
28
|
+
for index, trigger in enumerate(triggers, start=1):
|
|
29
|
+
evidence = _evidence(trigger)
|
|
30
|
+
prep_id = f"PREP-{index:03d}"
|
|
31
|
+
evidence_refs = [
|
|
32
|
+
f"Stage {trigger.stage} step {row['step'] or 'n/a'} "
|
|
33
|
+
f"{row['field']}={row['match']}"
|
|
34
|
+
for row in evidence
|
|
35
|
+
]
|
|
36
|
+
by_stage.setdefault(trigger.stage, []).append({
|
|
37
|
+
"kind": trigger.kind,
|
|
38
|
+
"triggerEvidence": evidence,
|
|
39
|
+
"disposition": "prep-item",
|
|
40
|
+
"prepItemId": prep_id,
|
|
41
|
+
})
|
|
42
|
+
items.append({
|
|
43
|
+
"id": prep_id,
|
|
44
|
+
"kind": trigger.kind,
|
|
45
|
+
"title": f"Stage {trigger.stage} {trigger.kind} contract",
|
|
46
|
+
"stageRefs": [trigger.stage],
|
|
47
|
+
"status": "provisional",
|
|
48
|
+
"need": (
|
|
49
|
+
f"Stage {trigger.stage} touches a {trigger.kind} surface, but the "
|
|
50
|
+
"detector evidence does not define its implementation contract."
|
|
51
|
+
),
|
|
52
|
+
"knownFacts": [
|
|
53
|
+
{"statement": "The plan triggered this design surface.", "evidence": ref}
|
|
54
|
+
for ref in evidence_refs
|
|
55
|
+
],
|
|
56
|
+
"openQuestions": [
|
|
57
|
+
f"What exact {trigger.kind} contract must Stage {trigger.stage} implement?"
|
|
58
|
+
],
|
|
59
|
+
"aiProposal": {
|
|
60
|
+
"summary": "Preserve existing behaviour until the contract is confirmed.",
|
|
61
|
+
"details": [
|
|
62
|
+
"Record the concrete contract before an irreversible implementation step."
|
|
63
|
+
],
|
|
64
|
+
"assumptions": [
|
|
65
|
+
"Detector evidence proves surface presence, not the missing contract details."
|
|
66
|
+
],
|
|
67
|
+
"evidence": evidence_refs,
|
|
68
|
+
"confidence": "low",
|
|
69
|
+
},
|
|
70
|
+
"humanConfirmation": {
|
|
71
|
+
"required": True,
|
|
72
|
+
"reason": "The plan does not contain enough evidence to infer the contract.",
|
|
73
|
+
"suggestedAction": "confirm-or-edit",
|
|
74
|
+
},
|
|
75
|
+
"workingAssumption": "Preserve existing behaviour and avoid irreversible changes.",
|
|
76
|
+
"guardrails": [
|
|
77
|
+
"Do not present a synthetic or inferred contract as externally verified."
|
|
78
|
+
],
|
|
79
|
+
"reviewAt": {"phase": "implementation", "stage": trigger.stage},
|
|
80
|
+
"ifStillOpen": "block",
|
|
81
|
+
"requestPath": (
|
|
82
|
+
f"design-prep-requests/design-prep-request-{trigger.stage}-{prep_id}.md"
|
|
83
|
+
),
|
|
84
|
+
"replanTriggerFields": ["aiProposal", "workingAssumption"],
|
|
85
|
+
})
|
|
86
|
+
preparation = {
|
|
87
|
+
"mode": "assessed" if triggers else "no-design-inputs",
|
|
88
|
+
"reason": (
|
|
89
|
+
f"Detector found {len(triggers)} design surface(s)."
|
|
90
|
+
if triggers else "Detector found no design surfaces in the plan."
|
|
91
|
+
),
|
|
92
|
+
"items": items,
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
"schemaVersion": "1.0",
|
|
96
|
+
"owner": "design-surface-detector",
|
|
97
|
+
"designPreparation": preparation,
|
|
98
|
+
"stageCoverage": [
|
|
99
|
+
{"stage": stage, "rows": rows}
|
|
100
|
+
for stage, rows in sorted(by_stage.items())
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def write_snapshot(narrative_path: Path, output_path: Path) -> dict[str, Any]:
|
|
106
|
+
narrative = parse_narrative(
|
|
107
|
+
narrative_path.read_text(encoding="utf-8"), load_schema_version("3.0")
|
|
108
|
+
)
|
|
109
|
+
planning = narrative.get("implementationPlanning")
|
|
110
|
+
if not isinstance(planning, Mapping):
|
|
111
|
+
raise DesignSurfaceError("implementationPlanning is missing from narrative")
|
|
112
|
+
snapshot = build_design_snapshot(planning)
|
|
113
|
+
write_json_atomic(output_path, snapshot)
|
|
114
|
+
return snapshot
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main(argv: list[str] | None = None) -> int:
|
|
118
|
+
parser = argparse.ArgumentParser(prog="okstra design-snapshot")
|
|
119
|
+
parser.add_argument("--narrative", type=Path, required=True)
|
|
120
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
121
|
+
args = parser.parse_args(argv)
|
|
122
|
+
try:
|
|
123
|
+
snapshot = write_snapshot(args.narrative, args.output)
|
|
124
|
+
except (OSError, UnicodeError, DesignSurfaceError, NarrativeContractError) as exc:
|
|
125
|
+
print(f"design-snapshot: {exc}", file=sys.stderr)
|
|
126
|
+
return 2
|
|
127
|
+
print(json.dumps({"ok": True, "surfaces": sum(
|
|
128
|
+
len(row["rows"]) for row in snapshot["stageCoverage"]
|
|
129
|
+
)}, ensure_ascii=False))
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
raise SystemExit(main())
|