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
|
@@ -3,10 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import json
|
|
6
|
-
import os
|
|
7
6
|
import re
|
|
8
7
|
import sys
|
|
9
|
-
import tempfile
|
|
10
8
|
from collections.abc import Mapping, Sequence
|
|
11
9
|
from datetime import datetime, timezone
|
|
12
10
|
from pathlib import Path
|
|
@@ -22,6 +20,11 @@ from okstra_ctl.worker_audit_ledger import (
|
|
|
22
20
|
parse_evidence_commands,
|
|
23
21
|
read_evidence_commands,
|
|
24
22
|
)
|
|
23
|
+
from okstra_ctl.json_boundary import (
|
|
24
|
+
JsonBoundaryError,
|
|
25
|
+
load_owned_object,
|
|
26
|
+
mutate_owned_object_atomic,
|
|
27
|
+
)
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
ACTIVITY_KINDS = frozenset({
|
|
@@ -59,12 +62,9 @@ class ActivityProjectionError(ValueError):
|
|
|
59
62
|
|
|
60
63
|
def _read_json_object(path: Path) -> dict[str, Any]:
|
|
61
64
|
try:
|
|
62
|
-
|
|
63
|
-
except
|
|
64
|
-
raise ActivityProjectionError(
|
|
65
|
-
if not isinstance(payload, dict):
|
|
66
|
-
raise ActivityProjectionError(f"JSON value must be an object: {path}")
|
|
67
|
-
return payload
|
|
65
|
+
return load_owned_object(path, artifact="agent activity artifact")
|
|
66
|
+
except JsonBoundaryError as exc:
|
|
67
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def _resolve_project_path(project_root: Path, value: str | Path) -> Path:
|
|
@@ -88,6 +88,50 @@ def _manifest_run_seq(manifest: Mapping[str, Any]) -> str:
|
|
|
88
88
|
return _require_string(sequences, "manifests")
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _validate_activity_ownership(
|
|
92
|
+
project_root: Path,
|
|
93
|
+
manifest: Mapping[str, Any],
|
|
94
|
+
details: Mapping[str, Any],
|
|
95
|
+
) -> None:
|
|
96
|
+
executions = manifest.get("roleExecutions")
|
|
97
|
+
assignments = manifest.get("workerAssignments")
|
|
98
|
+
if isinstance(assignments, list):
|
|
99
|
+
assigned = {
|
|
100
|
+
row["workerId"] for row in assignments
|
|
101
|
+
if isinstance(row, Mapping) and isinstance(row.get("workerId"), str)
|
|
102
|
+
}
|
|
103
|
+
elif isinstance(executions, list):
|
|
104
|
+
assigned = {
|
|
105
|
+
f"{row['provider']}-worker"
|
|
106
|
+
for row in executions
|
|
107
|
+
if isinstance(row, Mapping) and isinstance(row.get("provider"), str)
|
|
108
|
+
}
|
|
109
|
+
else:
|
|
110
|
+
assigned = set()
|
|
111
|
+
if isinstance(assignments, list) or isinstance(executions, list):
|
|
112
|
+
assigned.update({"okstra-lead", f"{manifest.get('leadRuntime', '')}-lead"})
|
|
113
|
+
agent = _require_string(details, "agent")
|
|
114
|
+
if agent not in assigned:
|
|
115
|
+
raise ActivityProjectionError(
|
|
116
|
+
f"agent `{agent}` is not assigned to this run"
|
|
117
|
+
)
|
|
118
|
+
state_value = manifest.get("planBodyVerificationPath")
|
|
119
|
+
if not isinstance(state_value, str) or not details.get("planItemIds"):
|
|
120
|
+
return
|
|
121
|
+
state = _read_json_object(_resolve_project_path(project_root, state_value))
|
|
122
|
+
verification = state.get("planBodyVerification")
|
|
123
|
+
items = verification.get("planItems") if isinstance(verification, Mapping) else []
|
|
124
|
+
known = {
|
|
125
|
+
row.get("id") for row in items
|
|
126
|
+
if isinstance(row, Mapping) and isinstance(row.get("id"), str)
|
|
127
|
+
}
|
|
128
|
+
unknown = sorted(set(details["planItemIds"]) - known)
|
|
129
|
+
if unknown:
|
|
130
|
+
raise ActivityProjectionError(
|
|
131
|
+
"plan item(s) not in the current plan state: " + ", ".join(unknown)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
91
135
|
def _utc_now() -> str:
|
|
92
136
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
93
137
|
|
|
@@ -116,6 +160,14 @@ def _activity_row(event: LeadEvent) -> dict[str, Any]:
|
|
|
116
160
|
from okstra_ctl.execution_identity import stored_identity
|
|
117
161
|
|
|
118
162
|
row = {key: details[key] for key in ACTIVITY_FIELDS}
|
|
163
|
+
if "clarificationRefs" in details:
|
|
164
|
+
refs = details["clarificationRefs"]
|
|
165
|
+
if not isinstance(refs, list) or any(
|
|
166
|
+
not isinstance(ref, str) or re.fullmatch(r"C-\d{3,}", ref) is None
|
|
167
|
+
for ref in refs
|
|
168
|
+
):
|
|
169
|
+
raise ActivityProjectionError("invalid clarificationRefs")
|
|
170
|
+
row["clarificationRefs"] = list(refs)
|
|
119
171
|
row.update(stored_identity(details))
|
|
120
172
|
return row
|
|
121
173
|
|
|
@@ -131,27 +183,6 @@ def _validate_activity_order(rows: Sequence[Mapping[str, Any]]) -> None:
|
|
|
131
183
|
)
|
|
132
184
|
|
|
133
185
|
|
|
134
|
-
def _atomic_write_json(path: Path, payload: Mapping[str, Any]) -> None:
|
|
135
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
-
temporary_path: Path | None = None
|
|
137
|
-
try:
|
|
138
|
-
with tempfile.NamedTemporaryFile(
|
|
139
|
-
mode="w",
|
|
140
|
-
encoding="utf-8",
|
|
141
|
-
dir=path.parent,
|
|
142
|
-
prefix=f".{path.name}.",
|
|
143
|
-
suffix=".tmp",
|
|
144
|
-
delete=False,
|
|
145
|
-
) as handle:
|
|
146
|
-
temporary_path = Path(handle.name)
|
|
147
|
-
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
148
|
-
handle.write("\n")
|
|
149
|
-
os.replace(temporary_path, path)
|
|
150
|
-
finally:
|
|
151
|
-
if temporary_path is not None and temporary_path.exists():
|
|
152
|
-
temporary_path.unlink()
|
|
153
|
-
|
|
154
|
-
|
|
155
186
|
def record_activity(
|
|
156
187
|
project_root: Path,
|
|
157
188
|
run_manifest_path: Path,
|
|
@@ -164,6 +195,7 @@ def record_activity(
|
|
|
164
195
|
raise ActivityProjectionError(
|
|
165
196
|
"run manifest does not declare activityContractVersion 1"
|
|
166
197
|
)
|
|
198
|
+
_validate_activity_ownership(project_root, manifest, details)
|
|
167
199
|
event = LeadEvent(
|
|
168
200
|
event_type="activity",
|
|
169
201
|
lead_runtime=_require_string(manifest, "leadRuntime"),
|
|
@@ -176,15 +208,17 @@ def record_activity(
|
|
|
176
208
|
events_path = _resolve_project_path(
|
|
177
209
|
project_root, _require_string(manifest, "leadEventsPath")
|
|
178
210
|
)
|
|
179
|
-
|
|
211
|
+
try:
|
|
212
|
+
return append_activity_event(events_path, event)
|
|
213
|
+
except ValueError as exc:
|
|
214
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
180
215
|
|
|
181
216
|
|
|
182
|
-
def
|
|
217
|
+
def agent_activity_rows(
|
|
183
218
|
project_root: Path,
|
|
184
219
|
run_manifest_path: Path,
|
|
185
|
-
data_path: Path,
|
|
186
220
|
) -> tuple[dict[str, Any], ...]:
|
|
187
|
-
"""
|
|
221
|
+
"""현재 실행의 정본 활동을 파일 변경 없이 반환한다."""
|
|
188
222
|
manifest = _read_json_object(run_manifest_path)
|
|
189
223
|
if manifest.get("activityContractVersion") != 1:
|
|
190
224
|
return ()
|
|
@@ -204,9 +238,27 @@ def project_agent_activity(
|
|
|
204
238
|
)
|
|
205
239
|
rows = tuple(_activity_row(event) for event in events)
|
|
206
240
|
_validate_activity_order(rows)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
241
|
+
return rows
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def project_agent_activity(
|
|
245
|
+
project_root: Path,
|
|
246
|
+
run_manifest_path: Path,
|
|
247
|
+
data_path: Path,
|
|
248
|
+
) -> tuple[dict[str, Any], ...]:
|
|
249
|
+
"""2.0 호환 경로에서 ``agentActivity``만 교체한다."""
|
|
250
|
+
rows = agent_activity_rows(project_root, run_manifest_path)
|
|
251
|
+
manifest = _read_json_object(run_manifest_path)
|
|
252
|
+
if manifest.get("activityContractVersion") != 1:
|
|
253
|
+
return ()
|
|
254
|
+
try:
|
|
255
|
+
mutate_owned_object_atomic(
|
|
256
|
+
data_path,
|
|
257
|
+
lambda data: {**data, "agentActivity": list(rows)},
|
|
258
|
+
artifact="final report record",
|
|
259
|
+
)
|
|
260
|
+
except JsonBoundaryError as exc:
|
|
261
|
+
raise ActivityProjectionError(str(exc)) from exc
|
|
210
262
|
return rows
|
|
211
263
|
|
|
212
264
|
|
|
@@ -222,6 +274,9 @@ def _parse_command_records(raw_records: Sequence[str]) -> tuple[EvidenceCommand,
|
|
|
222
274
|
|
|
223
275
|
def _activity_commands(args: argparse.Namespace) -> tuple[EvidenceCommand, ...]:
|
|
224
276
|
commands = list(_parse_command_records(args.command_record))
|
|
277
|
+
typed = _typed_command(args)
|
|
278
|
+
if typed is not None:
|
|
279
|
+
commands.append(typed)
|
|
225
280
|
if args.audit_sidecar is not None:
|
|
226
281
|
audit_commands, failures = read_evidence_commands(args.audit_sidecar)
|
|
227
282
|
if failures:
|
|
@@ -230,6 +285,45 @@ def _activity_commands(args: argparse.Namespace) -> tuple[EvidenceCommand, ...]:
|
|
|
230
285
|
return tuple(commands)
|
|
231
286
|
|
|
232
287
|
|
|
288
|
+
def _typed_command(args: argparse.Namespace) -> EvidenceCommand | None:
|
|
289
|
+
values = (
|
|
290
|
+
args.evidence_command,
|
|
291
|
+
args.command_cwd,
|
|
292
|
+
args.command_exit_code,
|
|
293
|
+
args.command_output_file,
|
|
294
|
+
)
|
|
295
|
+
if not any(value is not None for value in values):
|
|
296
|
+
return None
|
|
297
|
+
if any(value is None for value in values):
|
|
298
|
+
raise ActivityProjectionError(
|
|
299
|
+
"--command requires --command-cwd, --command-exit-code, and "
|
|
300
|
+
"--command-output-file"
|
|
301
|
+
)
|
|
302
|
+
try:
|
|
303
|
+
output_summary = args.command_output_file.read_text(encoding="utf-8")
|
|
304
|
+
except (OSError, UnicodeError) as exc:
|
|
305
|
+
raise ActivityProjectionError(
|
|
306
|
+
f"cannot read command output {args.command_output_file}: {exc}"
|
|
307
|
+
) from exc
|
|
308
|
+
return EvidenceCommand(
|
|
309
|
+
command=args.evidence_command,
|
|
310
|
+
cwd=args.command_cwd,
|
|
311
|
+
exit_code=args.command_exit_code,
|
|
312
|
+
output_summary=output_summary,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _activity_summary(args: argparse.Namespace) -> str:
|
|
317
|
+
if args.summary is not None:
|
|
318
|
+
return args.summary
|
|
319
|
+
try:
|
|
320
|
+
return args.summary_file.read_text(encoding="utf-8").strip()
|
|
321
|
+
except (OSError, UnicodeError) as exc:
|
|
322
|
+
raise ActivityProjectionError(
|
|
323
|
+
f"cannot read activity summary {args.summary_file}: {exc}"
|
|
324
|
+
) from exc
|
|
325
|
+
|
|
326
|
+
|
|
233
327
|
def _conversation_activity_line(details: Mapping[str, Any]) -> str:
|
|
234
328
|
plan_items = ",".join(details["planItemIds"]) or "<none>"
|
|
235
329
|
result_path = str(details["resultPath"] or "<none>")
|
|
@@ -246,13 +340,15 @@ def _append(args: argparse.Namespace) -> int:
|
|
|
246
340
|
details = {
|
|
247
341
|
"kind": args.kind,
|
|
248
342
|
"agent": args.agent,
|
|
249
|
-
"summary": args
|
|
343
|
+
"summary": _activity_summary(args),
|
|
250
344
|
"planItemIds": args.plan_item_id,
|
|
251
345
|
"resultPath": args.result_path,
|
|
252
346
|
"commands": [command.to_record() for command in commands],
|
|
253
347
|
"evidenceRefs": args.evidence_ref,
|
|
254
348
|
"outcome": args.outcome,
|
|
255
349
|
}
|
|
350
|
+
if args.request_ref is not None:
|
|
351
|
+
details["activityRequestRef"] = args.request_ref
|
|
256
352
|
event = record_activity(args.project_root, args.run_manifest, details)
|
|
257
353
|
payload = dict(event.details)
|
|
258
354
|
payload["ok"] = True
|
|
@@ -281,15 +377,22 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
281
377
|
"--kind", choices=sorted(ACTIVITY_KINDS), required=True
|
|
282
378
|
)
|
|
283
379
|
append_parser.add_argument("--agent", required=True)
|
|
284
|
-
append_parser.
|
|
380
|
+
summary = append_parser.add_mutually_exclusive_group(required=True)
|
|
381
|
+
summary.add_argument("--summary")
|
|
382
|
+
summary.add_argument("--summary-file", type=Path)
|
|
285
383
|
append_parser.add_argument(
|
|
286
384
|
"--outcome", choices=sorted(ACTIVITY_OUTCOMES), required=True
|
|
287
385
|
)
|
|
288
386
|
append_parser.add_argument("--plan-item-id", action="append", default=[])
|
|
289
387
|
append_parser.add_argument("--evidence-ref", action="append", default=[])
|
|
290
388
|
append_parser.add_argument("--command-record", action="append", default=[])
|
|
389
|
+
append_parser.add_argument("--command", dest="evidence_command")
|
|
390
|
+
append_parser.add_argument("--command-cwd")
|
|
391
|
+
append_parser.add_argument("--command-exit-code", type=int)
|
|
392
|
+
append_parser.add_argument("--command-output-file", type=Path)
|
|
291
393
|
append_parser.add_argument("--result-path", default="")
|
|
292
394
|
append_parser.add_argument("--audit-sidecar", type=Path)
|
|
395
|
+
append_parser.add_argument("--request-ref")
|
|
293
396
|
project_parser = subparsers.add_parser("project")
|
|
294
397
|
project_parser.add_argument("--project-root", type=Path, required=True)
|
|
295
398
|
project_parser.add_argument("--run-manifest", type=Path, required=True)
|
|
@@ -15,6 +15,12 @@ import tempfile
|
|
|
15
15
|
from typing import Iterator, Literal, Mapping, get_args
|
|
16
16
|
|
|
17
17
|
from .domain.role import RoleCatalogError, role_for_duty
|
|
18
|
+
from .json_boundary import (
|
|
19
|
+
JsonBoundaryError,
|
|
20
|
+
external_invocation_json_source,
|
|
21
|
+
load_external_json,
|
|
22
|
+
load_owned_object,
|
|
23
|
+
)
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
AgentAudience = Literal[
|
|
@@ -935,8 +941,15 @@ def _verified_result_envelope(
|
|
|
935
941
|
) -> tuple[bytes, str]:
|
|
936
942
|
try:
|
|
937
943
|
body = result_path.read_bytes()
|
|
938
|
-
envelope =
|
|
939
|
-
|
|
944
|
+
envelope = load_external_json(
|
|
945
|
+
external_invocation_json_source(
|
|
946
|
+
result_path,
|
|
947
|
+
project_root=project_root,
|
|
948
|
+
purpose=purpose,
|
|
949
|
+
),
|
|
950
|
+
artifact="standalone worker result",
|
|
951
|
+
)
|
|
952
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError, JsonBoundaryError) as exc:
|
|
940
953
|
raise AgentInvocationError(
|
|
941
954
|
f"standalone result envelope is missing or invalid: {result_path}"
|
|
942
955
|
) from exc
|
|
@@ -1494,8 +1507,8 @@ def _metadata_difference(
|
|
|
1494
1507
|
so they are reported by name too.
|
|
1495
1508
|
"""
|
|
1496
1509
|
try:
|
|
1497
|
-
existing =
|
|
1498
|
-
except
|
|
1510
|
+
existing = load_owned_object(metadata, artifact="agent invocation metadata")
|
|
1511
|
+
except JsonBoundaryError as exc:
|
|
1499
1512
|
return f"existing metadata is unreadable: {exc}"
|
|
1500
1513
|
composed = materialized.metadata
|
|
1501
1514
|
if not isinstance(existing, dict):
|
|
@@ -2240,8 +2253,8 @@ def _load_json_object(path: Path, label: str) -> dict[str, object]:
|
|
|
2240
2253
|
|
|
2241
2254
|
def _read_json_if_object(path: Path) -> dict[str, object] | None:
|
|
2242
2255
|
try:
|
|
2243
|
-
value =
|
|
2244
|
-
except
|
|
2256
|
+
value = load_owned_object(path, artifact="agent invocation metadata")
|
|
2257
|
+
except JsonBoundaryError:
|
|
2245
2258
|
return None
|
|
2246
2259
|
return value if isinstance(value, dict) else None
|
|
2247
2260
|
|
|
@@ -34,6 +34,8 @@ from .agent_invocation import (
|
|
|
34
34
|
verify_agent_invocation,
|
|
35
35
|
v2_role_assignment_authority_errors,
|
|
36
36
|
)
|
|
37
|
+
from .fixed_text import line
|
|
38
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
37
39
|
from .convergence_store import (
|
|
38
40
|
DYNAMIC_VERIFIER_SOURCE_ROLES,
|
|
39
41
|
reserve_dynamic_verifier,
|
|
@@ -48,7 +50,11 @@ from .wrapper_status import (
|
|
|
48
50
|
prompt_derived_paths,
|
|
49
51
|
status_path_for_prompt,
|
|
50
52
|
)
|
|
51
|
-
from .
|
|
53
|
+
from .report_inputs import report_narrative_path, uses_report_contract_v3
|
|
54
|
+
from .worker_prompt_policy import (
|
|
55
|
+
CRITIC_DUTY_BY_ASSIGNMENT_SEGMENT,
|
|
56
|
+
resolve_prompt_plan_for_manifest,
|
|
57
|
+
)
|
|
52
58
|
from .dispatch_state import (
|
|
53
59
|
BACKEND_CLI_WRAPPER,
|
|
54
60
|
BACKEND_CMUX_PANE,
|
|
@@ -70,6 +76,13 @@ class AgentPromptCliError(RuntimeError):
|
|
|
70
76
|
def _parser() -> argparse.ArgumentParser:
|
|
71
77
|
parser = argparse.ArgumentParser(prog="okstra agent-prompt")
|
|
72
78
|
commands = parser.add_subparsers(dest="command", required=True)
|
|
79
|
+
_add_materialize_parser(commands)
|
|
80
|
+
_add_completion_parsers(commands)
|
|
81
|
+
_add_dispatch_parsers(commands)
|
|
82
|
+
return parser
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _add_materialize_parser(commands: argparse._SubParsersAction) -> None:
|
|
73
86
|
materialize = commands.add_parser("materialize")
|
|
74
87
|
_common_paths(materialize)
|
|
75
88
|
materialize.add_argument("--invocation-id", required=True)
|
|
@@ -110,7 +123,10 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
110
123
|
verify.add_argument("--run-manifest")
|
|
111
124
|
verify.add_argument("--metadata", required=True)
|
|
112
125
|
verify.add_argument("--json", action="store_true")
|
|
126
|
+
verify.add_argument("--text", action="store_true")
|
|
113
127
|
|
|
128
|
+
|
|
129
|
+
def _add_completion_parsers(commands: argparse._SubParsersAction) -> None:
|
|
114
130
|
materialize_result = commands.add_parser("materialize-result")
|
|
115
131
|
_standalone_completion_args(materialize_result)
|
|
116
132
|
materialize_result.add_argument("--returned-body-file", required=True)
|
|
@@ -124,6 +140,8 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
124
140
|
verify_completion.add_argument("--completion", required=True)
|
|
125
141
|
verify_completion.add_argument("--json", action="store_true")
|
|
126
142
|
|
|
143
|
+
|
|
144
|
+
def _add_dispatch_parsers(commands: argparse._SubParsersAction) -> None:
|
|
127
145
|
record_dispatch = commands.add_parser("record-dispatch")
|
|
128
146
|
_common_paths(record_dispatch)
|
|
129
147
|
record_dispatch.add_argument("--run-manifest", required=True)
|
|
@@ -153,7 +171,6 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
153
171
|
link_result.add_argument("--dispatch-id", required=True)
|
|
154
172
|
link_result.add_argument("--result", required=True)
|
|
155
173
|
link_result.add_argument("--json", action="store_true")
|
|
156
|
-
return parser
|
|
157
174
|
|
|
158
175
|
|
|
159
176
|
def _common_paths(parser: argparse.ArgumentParser) -> None:
|
|
@@ -179,6 +196,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
179
196
|
_emit(
|
|
180
197
|
{"ok": True, "metadataPath": str(Path(args.metadata).resolve())},
|
|
181
198
|
args.json,
|
|
199
|
+
args.text,
|
|
182
200
|
)
|
|
183
201
|
return 0
|
|
184
202
|
if args.command == "materialize-result":
|
|
@@ -314,16 +332,32 @@ def _materialize_run(
|
|
|
314
332
|
)
|
|
315
333
|
if args.audience == "report-writer" and not args.audit_source:
|
|
316
334
|
# The report writer is the one audience whose result path is not its own
|
|
317
|
-
# worker result: it writes the report
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
322
|
-
#
|
|
335
|
+
# worker result: it writes the report body, while the audit sidecar is
|
|
336
|
+
# derived from its `.md`. With both collapsed into one value the prompt
|
|
337
|
+
# loses its `**Worker Result Path:**` anchor and the writer puts the
|
|
338
|
+
# report where the audit file belongs — silently, because every header
|
|
339
|
+
# is still present and well-formed. The roster path derives both from
|
|
340
|
+
# the manifest; a dynamic call has to name them.
|
|
341
|
+
#
|
|
342
|
+
# Which artifact `--result` names depends on the report contract, and
|
|
343
|
+
# `dispatch_state.dispatch_result_path` is what decides it. Naming only
|
|
344
|
+
# the v2 answer here sent v3 runs to the data.json, so the narrative
|
|
345
|
+
# `report-finalize` assembles from was never written and the phase
|
|
346
|
+
# failed later, at a place that does not point back here.
|
|
347
|
+
if uses_report_contract_v3(manifest):
|
|
348
|
+
expected = report_narrative_path(project_root, manifest)
|
|
349
|
+
raise AgentPromptCliError(
|
|
350
|
+
"report-writer materialization requires --audit-source: under "
|
|
351
|
+
f"report contract 3.0 --result is the narrative ({expected}), "
|
|
352
|
+
"which `report-finalize` assembles the report data.json from, "
|
|
353
|
+
"and --audit-source the worker-result .md the audit sidecar is "
|
|
354
|
+
"derived from"
|
|
355
|
+
)
|
|
323
356
|
raise AgentPromptCliError(
|
|
324
|
-
"report-writer materialization requires --audit-source:
|
|
325
|
-
"
|
|
326
|
-
"audit sidecar is
|
|
357
|
+
"report-writer materialization requires --audit-source: under "
|
|
358
|
+
"report contract 2.0 --result is the report data.json, and "
|
|
359
|
+
"--audit-source the worker-result .md the audit sidecar is "
|
|
360
|
+
"derived from"
|
|
327
361
|
)
|
|
328
362
|
audit_source_path = (
|
|
329
363
|
_authorized_path(
|
|
@@ -630,17 +664,15 @@ def _validate_run_identity(
|
|
|
630
664
|
raise AgentPromptCliError("translator assignment requires worker ID 'translator'")
|
|
631
665
|
elif assignment_ref.startswith("critic/"):
|
|
632
666
|
scope = assignment_ref.split("/", 1)[1]
|
|
633
|
-
expected =
|
|
634
|
-
scope,
|
|
635
|
-
"",
|
|
636
|
-
)
|
|
667
|
+
expected = CRITIC_DUTY_BY_ASSIGNMENT_SEGMENT.get(scope, "")
|
|
637
668
|
if not expected or dispatch_kind != "critic":
|
|
638
669
|
raise AgentPromptCliError("critic assignment identity is invalid")
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
670
|
+
# provider 대조는 여기 두지 않는다. critic 의 `worker_id` 는 배정 참조의
|
|
671
|
+
# 마지막 마디(`scope` / `acceptance`)이고 provider 이름이 아니다
|
|
672
|
+
# (`render.py` 의 로스터 행이 `assignment_ref.rsplit("/", 1)[-1]` 로 만든다).
|
|
673
|
+
# 종전에 있던 `provider != worker_id` 검사는 두 어휘를 비교해서 늘 참이 되는
|
|
674
|
+
# 형태였고, 설정을 run manifest 에서 찾다 못 찾아 조용히 건너뛰었기에
|
|
675
|
+
# 드러나지 않았다. 정본에서 읽게 고치면 모든 critic 디스패치가 거부된다.
|
|
644
676
|
else:
|
|
645
677
|
try:
|
|
646
678
|
expected = resolve_prompt_plan_for_manifest(
|
|
@@ -1039,8 +1071,8 @@ def _relative(project_root: Path, path: Path) -> str:
|
|
|
1039
1071
|
|
|
1040
1072
|
def _read_json_object(path: Path, label: str) -> dict[str, Any]:
|
|
1041
1073
|
try:
|
|
1042
|
-
value =
|
|
1043
|
-
except
|
|
1074
|
+
value = load_owned_object(path, artifact="agent prompt metadata")
|
|
1075
|
+
except JsonBoundaryError as exc:
|
|
1044
1076
|
raise AgentPromptCliError(f"{label} is invalid: {path}") from exc
|
|
1045
1077
|
if not isinstance(value, dict):
|
|
1046
1078
|
raise AgentPromptCliError(f"{label} must be a JSON object: {path}")
|
|
@@ -1098,12 +1130,27 @@ def _prepared_payload(
|
|
|
1098
1130
|
}
|
|
1099
1131
|
|
|
1100
1132
|
|
|
1101
|
-
def _emit(
|
|
1133
|
+
def _emit(
|
|
1134
|
+
payload: Mapping[str, object], as_json: bool, as_text: bool = False
|
|
1135
|
+
) -> None:
|
|
1102
1136
|
if as_json:
|
|
1103
1137
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
1138
|
+
elif as_text:
|
|
1139
|
+
print(render_agent_prompt_text(payload), end="")
|
|
1104
1140
|
else:
|
|
1105
1141
|
print(payload.get("promptPath") or payload.get("metadataPath") or "ok")
|
|
1106
1142
|
|
|
1107
1143
|
|
|
1144
|
+
def render_agent_prompt_text(payload: Mapping[str, object]) -> str:
|
|
1145
|
+
"""agent prompt 검증 결과의 승인된 경로만 투영한다."""
|
|
1146
|
+
rows = ["Okstra agent prompt\n"]
|
|
1147
|
+
rows.append(line("Status", "ready" if payload.get("ok", True) else "error"))
|
|
1148
|
+
for label, key in (("Prompt path", "promptPath"), ("Metadata path", "metadataPath"),
|
|
1149
|
+
("Assignment ID", "assignmentId"), ("Worker role", "workerRole")):
|
|
1150
|
+
if key in payload:
|
|
1151
|
+
rows.append(line(label, payload.get(key)))
|
|
1152
|
+
return "".join(rows)
|
|
1153
|
+
|
|
1154
|
+
|
|
1108
1155
|
if __name__ == "__main__":
|
|
1109
1156
|
raise SystemExit(main())
|
|
@@ -11,6 +11,7 @@ from typing import Mapping, Sequence
|
|
|
11
11
|
|
|
12
12
|
from .final_report_paths import final_report_data_path
|
|
13
13
|
from .paths import task_manifest_file
|
|
14
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
14
15
|
from .user_response import (
|
|
15
16
|
UserResponseError,
|
|
16
17
|
load_authoritative_analysis_review,
|
|
@@ -196,8 +197,8 @@ def _task_key_from_manifest(task_root: Path, okstra_root: Path) -> str:
|
|
|
196
197
|
task_manifest_file(task_root), okstra_root, "task manifest path"
|
|
197
198
|
)
|
|
198
199
|
try:
|
|
199
|
-
manifest =
|
|
200
|
-
except
|
|
200
|
+
manifest = load_owned_object(manifest_path, artifact="task manifest")
|
|
201
|
+
except JsonBoundaryError as exc:
|
|
201
202
|
raise AnalysisInputError(f"cannot read task manifest: {manifest_path}") from exc
|
|
202
203
|
if not isinstance(manifest, dict):
|
|
203
204
|
raise AnalysisInputError("task manifest must contain an object")
|
|
@@ -219,8 +220,8 @@ def load_analysis_report_candidate(
|
|
|
219
220
|
raise AnalysisInputError("report filename is not a final report")
|
|
220
221
|
data_path = _resolved_within(final_report_data_path(report), okstra_root, "data.json path")
|
|
221
222
|
try:
|
|
222
|
-
data =
|
|
223
|
-
except
|
|
223
|
+
data = load_owned_object(data_path, artifact="analysis report record")
|
|
224
|
+
except JsonBoundaryError as exc:
|
|
224
225
|
raise AnalysisInputError(f"cannot read data.json: {data_path}") from exc
|
|
225
226
|
if not isinstance(data, dict):
|
|
226
227
|
raise AnalysisInputError("data.json must contain an object")
|