okstra 0.142.0 → 0.143.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/docs/project-structure-overview.md +2 -0
- package/docs/task-process/error-analysis.md +9 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +2 -1
- package/runtime/prompts/lead/context-loader.md +2 -2
- package/runtime/prompts/lead/convergence.md +5 -2
- package/runtime/prompts/lead/report-writer.md +4 -3
- package/runtime/prompts/profiles/_common-contract.md +3 -1
- package/runtime/prompts/profiles/error-analysis.md +5 -1
- package/runtime/python/okstra_ctl/analysis_packet.py +28 -3
- package/runtime/python/okstra_ctl/brief_frontmatter.py +56 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +66 -15
- package/runtime/python/okstra_ctl/run.py +81 -33
- package/runtime/python/okstra_ctl/schema_excerpt.py +5 -3
- package/runtime/python/okstra_ctl/wizard.py +2 -44
- package/runtime/schemas/final-report-v1.0.schema.json +99 -0
- package/runtime/templates/reports/final-report.template.md +26 -0
- package/runtime/templates/reports/i18n/en.json +20 -2
- package/runtime/templates/reports/i18n/ko.json +20 -2
- package/runtime/validators/validate-brief.py +5 -1
- package/runtime/validators/validate-run.py +287 -36
|
@@ -8,9 +8,12 @@ import importlib.util
|
|
|
8
8
|
import json
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
|
+
import shlex
|
|
11
12
|
import sys
|
|
13
|
+
from collections.abc import Mapping
|
|
12
14
|
from datetime import datetime, timezone
|
|
13
15
|
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
14
17
|
|
|
15
18
|
# Make the okstra packages importable however this validator is reached:
|
|
16
19
|
# ``scripts/`` next to the repo checkout, ``python/`` next to the installed
|
|
@@ -188,10 +191,29 @@ def advance_next_phase(
|
|
|
188
191
|
return default_next_phase(current_phase)
|
|
189
192
|
|
|
190
193
|
|
|
194
|
+
def _error_analysis_next_phase(data: Mapping[str, Any]) -> str | None:
|
|
195
|
+
if not isinstance(data, Mapping):
|
|
196
|
+
return None
|
|
197
|
+
header = data.get("header")
|
|
198
|
+
if not isinstance(header, Mapping) or header.get("taskType") != "error-analysis":
|
|
199
|
+
return None
|
|
200
|
+
error_analysis = data.get("errorAnalysis")
|
|
201
|
+
if not isinstance(error_analysis, Mapping):
|
|
202
|
+
return None
|
|
203
|
+
routing = error_analysis.get("routing")
|
|
204
|
+
if not isinstance(routing, Mapping):
|
|
205
|
+
return None
|
|
206
|
+
target = routing.get("nextTaskType")
|
|
207
|
+
if target in {"error-analysis", "implementation-planning"}:
|
|
208
|
+
return str(target)
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
191
212
|
def update_workflow_metadata(
|
|
192
213
|
run_manifest: dict,
|
|
193
214
|
task_manifest: dict,
|
|
194
215
|
validation_status: str,
|
|
216
|
+
report_data: Mapping[str, Any] | None = None,
|
|
195
217
|
) -> None:
|
|
196
218
|
workflow = task_manifest.get("workflow", {})
|
|
197
219
|
if not isinstance(workflow, dict):
|
|
@@ -221,7 +243,14 @@ def update_workflow_metadata(
|
|
|
221
243
|
# Validation just passed → actively advance to the next phase in
|
|
222
244
|
# the sequence rather than preserving a stale value that may equal
|
|
223
245
|
# current_phase (which would cause the lifecycle pointer to stall).
|
|
224
|
-
|
|
246
|
+
report_next_phase = (
|
|
247
|
+
_error_analysis_next_phase(report_data or {})
|
|
248
|
+
if current_phase == "error-analysis"
|
|
249
|
+
else None
|
|
250
|
+
)
|
|
251
|
+
next_recommended_phase = report_next_phase or advance_next_phase(
|
|
252
|
+
current_phase, phase_sequence
|
|
253
|
+
)
|
|
225
254
|
else:
|
|
226
255
|
current_phase_state = "blocked"
|
|
227
256
|
if current_phase:
|
|
@@ -322,6 +351,7 @@ def update_validation_metadata(
|
|
|
322
351
|
task_manifest: dict,
|
|
323
352
|
validation_status: str,
|
|
324
353
|
failures: list[str],
|
|
354
|
+
report_data: Mapping[str, Any] | None = None,
|
|
325
355
|
) -> None:
|
|
326
356
|
checked_at = utc_now()
|
|
327
357
|
|
|
@@ -350,7 +380,12 @@ def update_validation_metadata(
|
|
|
350
380
|
task_manifest["currentStatus"] = (
|
|
351
381
|
"completed" if validation_status == "passed" else "contract-violated"
|
|
352
382
|
)
|
|
353
|
-
update_workflow_metadata(
|
|
383
|
+
update_workflow_metadata(
|
|
384
|
+
run_manifest,
|
|
385
|
+
task_manifest,
|
|
386
|
+
validation_status,
|
|
387
|
+
report_data=report_data,
|
|
388
|
+
)
|
|
354
389
|
|
|
355
390
|
|
|
356
391
|
def extract_contract(
|
|
@@ -1645,7 +1680,10 @@ def _validate_conformance(
|
|
|
1645
1680
|
|
|
1646
1681
|
|
|
1647
1682
|
def _check_incremental_audit_block(
|
|
1648
|
-
report_path: Path,
|
|
1683
|
+
report_path: Path,
|
|
1684
|
+
content: str,
|
|
1685
|
+
failures: list[str],
|
|
1686
|
+
report_data: Mapping[str, Any] | None = None,
|
|
1649
1687
|
) -> None:
|
|
1650
1688
|
"""Enforce the Section 0 incremental audit block.
|
|
1651
1689
|
|
|
@@ -1657,7 +1695,11 @@ def _check_incremental_audit_block(
|
|
|
1657
1695
|
decision are exempt, so this never conflicts with the empty-Section-0
|
|
1658
1696
|
stub rule (that fires only when NO carry-in was provided at all).
|
|
1659
1697
|
"""
|
|
1660
|
-
data =
|
|
1698
|
+
data = (
|
|
1699
|
+
report_data
|
|
1700
|
+
if report_data is not None
|
|
1701
|
+
else _load_final_report_data(report_path)
|
|
1702
|
+
)
|
|
1661
1703
|
planning = data.get("implementationPlanning")
|
|
1662
1704
|
if not isinstance(planning, dict):
|
|
1663
1705
|
return
|
|
@@ -1689,6 +1731,7 @@ def validate_report(
|
|
|
1689
1731
|
*,
|
|
1690
1732
|
allow_unavailable_token_usage: bool = False,
|
|
1691
1733
|
unavailable_ok_labels: frozenset[str] = frozenset(),
|
|
1734
|
+
report_data: Mapping[str, Any] | None = None,
|
|
1692
1735
|
) -> None:
|
|
1693
1736
|
if not report_path.exists():
|
|
1694
1737
|
failures.append(f"final report is missing: {report_path}")
|
|
@@ -1779,7 +1822,12 @@ def validate_report(
|
|
|
1779
1822
|
|
|
1780
1823
|
# Incremental audit block — an `incremental`-mode re-run must expose its
|
|
1781
1824
|
# scope decision in Section 0 so the narrowed re-verification is auditable.
|
|
1782
|
-
_check_incremental_audit_block(
|
|
1825
|
+
_check_incremental_audit_block(
|
|
1826
|
+
report_path,
|
|
1827
|
+
content,
|
|
1828
|
+
failures,
|
|
1829
|
+
report_data=report_data,
|
|
1830
|
+
)
|
|
1783
1831
|
|
|
1784
1832
|
# Deprecated section headings — pre-1.0 hard removal.
|
|
1785
1833
|
for pattern, remedy in _DEPRECATED_FINAL_REPORT_PATTERNS:
|
|
@@ -2240,6 +2288,213 @@ def _validate_verdict_card_fields(data: dict, failures: list[str]) -> None:
|
|
|
2240
2288
|
)
|
|
2241
2289
|
|
|
2242
2290
|
|
|
2291
|
+
def _route_target_matches(value: Any, target: str, *, command: bool) -> bool:
|
|
2292
|
+
if not isinstance(value, str):
|
|
2293
|
+
return False
|
|
2294
|
+
if not command:
|
|
2295
|
+
pattern = rf"(?<![\w-]){re.escape(target)}(?![\w-])"
|
|
2296
|
+
return re.search(pattern, value) is not None
|
|
2297
|
+
try:
|
|
2298
|
+
tokens = shlex.split(value)
|
|
2299
|
+
except ValueError:
|
|
2300
|
+
return False
|
|
2301
|
+
task_type_values: list[str] = []
|
|
2302
|
+
for index, token in enumerate(tokens):
|
|
2303
|
+
if token in {"task-type", "--task-type"}:
|
|
2304
|
+
task_type_values.append(
|
|
2305
|
+
tokens[index + 1] if index + 1 < len(tokens) else ""
|
|
2306
|
+
)
|
|
2307
|
+
for prefix in ("task-type=", "--task-type="):
|
|
2308
|
+
if token.startswith(prefix):
|
|
2309
|
+
task_type_values.append(token[len(prefix):])
|
|
2310
|
+
return bool(task_type_values) and all(
|
|
2311
|
+
task_type_value == target for task_type_value in task_type_values
|
|
2312
|
+
)
|
|
2313
|
+
|
|
2314
|
+
|
|
2315
|
+
def _validate_error_analysis_consistency(
|
|
2316
|
+
data: Mapping[str, Any], failures: list[str]
|
|
2317
|
+
) -> None:
|
|
2318
|
+
error_analysis_value = data.get("errorAnalysis")
|
|
2319
|
+
error_analysis = (
|
|
2320
|
+
error_analysis_value if isinstance(error_analysis_value, Mapping) else {}
|
|
2321
|
+
)
|
|
2322
|
+
reproduction_value = error_analysis.get("reproduction")
|
|
2323
|
+
reproduction = (
|
|
2324
|
+
reproduction_value if isinstance(reproduction_value, Mapping) else {}
|
|
2325
|
+
)
|
|
2326
|
+
reproduction_status = reproduction.get("status")
|
|
2327
|
+
blocked_reason = reproduction.get("blockedReason")
|
|
2328
|
+
if reproduction_status == "blocked-before-repro":
|
|
2329
|
+
if not isinstance(blocked_reason, str) or not blocked_reason.strip():
|
|
2330
|
+
failures.append(
|
|
2331
|
+
"final-report data.json: blocked-before-repro requires a non-empty "
|
|
2332
|
+
"errorAnalysis.reproduction.blockedReason."
|
|
2333
|
+
)
|
|
2334
|
+
elif blocked_reason != "":
|
|
2335
|
+
failures.append(
|
|
2336
|
+
"final-report data.json: errorAnalysis.reproduction.blockedReason "
|
|
2337
|
+
"must be exactly empty unless status is blocked-before-repro."
|
|
2338
|
+
)
|
|
2339
|
+
|
|
2340
|
+
candidates_value = error_analysis.get("causeCandidates")
|
|
2341
|
+
candidates = candidates_value if isinstance(candidates_value, list) else []
|
|
2342
|
+
candidate_ids: list[str] = []
|
|
2343
|
+
for candidate in candidates:
|
|
2344
|
+
if not isinstance(candidate, Mapping):
|
|
2345
|
+
continue
|
|
2346
|
+
candidate_id = candidate.get("id")
|
|
2347
|
+
if isinstance(candidate_id, str):
|
|
2348
|
+
candidate_ids.append(candidate_id)
|
|
2349
|
+
duplicate_ids = sorted(
|
|
2350
|
+
candidate_id
|
|
2351
|
+
for candidate_id in set(candidate_ids)
|
|
2352
|
+
if candidate_ids.count(candidate_id) > 1
|
|
2353
|
+
)
|
|
2354
|
+
if duplicate_ids:
|
|
2355
|
+
failures.append(
|
|
2356
|
+
"final-report data.json: duplicate cause candidate id(s): "
|
|
2357
|
+
+ ", ".join(duplicate_ids)
|
|
2358
|
+
+ "."
|
|
2359
|
+
)
|
|
2360
|
+
|
|
2361
|
+
routing_value = error_analysis.get("routing")
|
|
2362
|
+
routing = routing_value if isinstance(routing_value, Mapping) else {}
|
|
2363
|
+
target = routing.get("nextTaskType")
|
|
2364
|
+
leading_cause_id = routing.get("leadingCauseId")
|
|
2365
|
+
candidate_id_set = set(candidate_ids)
|
|
2366
|
+
if target == "implementation-planning":
|
|
2367
|
+
if not candidates:
|
|
2368
|
+
failures.append(
|
|
2369
|
+
"final-report data.json: implementation-planning routing requires "
|
|
2370
|
+
"at least one cause candidate."
|
|
2371
|
+
)
|
|
2372
|
+
if (
|
|
2373
|
+
not isinstance(leading_cause_id, str)
|
|
2374
|
+
or leading_cause_id not in candidate_id_set
|
|
2375
|
+
):
|
|
2376
|
+
failures.append(
|
|
2377
|
+
"final-report data.json: implementation-planning routing "
|
|
2378
|
+
"leadingCauseId must reference a cause candidate."
|
|
2379
|
+
)
|
|
2380
|
+
elif target == "error-analysis" and (
|
|
2381
|
+
not isinstance(leading_cause_id, str)
|
|
2382
|
+
or (leading_cause_id != "" and leading_cause_id not in candidate_id_set)
|
|
2383
|
+
):
|
|
2384
|
+
failures.append(
|
|
2385
|
+
"final-report data.json: error-analysis routing leadingCauseId must be "
|
|
2386
|
+
"empty or reference a cause candidate."
|
|
2387
|
+
)
|
|
2388
|
+
|
|
2389
|
+
expected_direction = {
|
|
2390
|
+
"implementation-planning": "begin-planning",
|
|
2391
|
+
"error-analysis": "continue-investigation",
|
|
2392
|
+
}.get(target)
|
|
2393
|
+
verdict_card_value = data.get("verdictCard")
|
|
2394
|
+
verdict_card = (
|
|
2395
|
+
verdict_card_value if isinstance(verdict_card_value, Mapping) else {}
|
|
2396
|
+
)
|
|
2397
|
+
final_verdict_value = data.get("finalVerdict")
|
|
2398
|
+
final_verdict = (
|
|
2399
|
+
final_verdict_value if isinstance(final_verdict_value, Mapping) else {}
|
|
2400
|
+
)
|
|
2401
|
+
if expected_direction:
|
|
2402
|
+
for field_name, verdict in (
|
|
2403
|
+
("verdictCard", verdict_card),
|
|
2404
|
+
("finalVerdict", final_verdict),
|
|
2405
|
+
):
|
|
2406
|
+
if verdict.get("direction") != expected_direction:
|
|
2407
|
+
failures.append(
|
|
2408
|
+
f"final-report data.json: {field_name}.direction must be "
|
|
2409
|
+
f"`{expected_direction}` for `{target}` routing."
|
|
2410
|
+
)
|
|
2411
|
+
|
|
2412
|
+
follow_up_tasks_value = data.get("followUpTasks")
|
|
2413
|
+
follow_up_tasks = (
|
|
2414
|
+
follow_up_tasks_value if isinstance(follow_up_tasks_value, list) else []
|
|
2415
|
+
)
|
|
2416
|
+
continuations = [
|
|
2417
|
+
row
|
|
2418
|
+
for row in follow_up_tasks
|
|
2419
|
+
if isinstance(row, Mapping) and row.get("origin") == "phase-continuation"
|
|
2420
|
+
]
|
|
2421
|
+
if len(continuations) != 1:
|
|
2422
|
+
failures.append(
|
|
2423
|
+
"final-report data.json: followUpTasks must contain exactly one "
|
|
2424
|
+
"phase-continuation row."
|
|
2425
|
+
)
|
|
2426
|
+
else:
|
|
2427
|
+
continuation = continuations[0]
|
|
2428
|
+
frontmatter_value = data.get("frontmatter")
|
|
2429
|
+
frontmatter = (
|
|
2430
|
+
frontmatter_value if isinstance(frontmatter_value, Mapping) else {}
|
|
2431
|
+
)
|
|
2432
|
+
task_id = frontmatter.get("taskId")
|
|
2433
|
+
if continuation.get("suggestedTaskType") != target:
|
|
2434
|
+
failures.append(
|
|
2435
|
+
"final-report data.json: phase-continuation suggestedTaskType "
|
|
2436
|
+
"must match the routing target."
|
|
2437
|
+
)
|
|
2438
|
+
if continuation.get("newTaskId") != task_id:
|
|
2439
|
+
failures.append(
|
|
2440
|
+
"final-report data.json: phase-continuation newTaskId must match "
|
|
2441
|
+
"frontmatter.taskId."
|
|
2442
|
+
)
|
|
2443
|
+
if continuation.get("priority") != "P0":
|
|
2444
|
+
failures.append(
|
|
2445
|
+
"final-report data.json: phase-continuation priority must be P0."
|
|
2446
|
+
)
|
|
2447
|
+
if continuation.get("autoSpawn") != "no":
|
|
2448
|
+
failures.append(
|
|
2449
|
+
"final-report data.json: phase-continuation autoSpawn must be no."
|
|
2450
|
+
)
|
|
2451
|
+
|
|
2452
|
+
if isinstance(target, str) and target in {
|
|
2453
|
+
"error-analysis",
|
|
2454
|
+
"implementation-planning",
|
|
2455
|
+
}:
|
|
2456
|
+
for field_name, value in (
|
|
2457
|
+
("verdictCard.nextStep", verdict_card.get("nextStep")),
|
|
2458
|
+
("finalVerdict.nextStep", final_verdict.get("nextStep")),
|
|
2459
|
+
):
|
|
2460
|
+
if not _route_target_matches(value, target, command=False):
|
|
2461
|
+
failures.append(
|
|
2462
|
+
f"final-report data.json: {field_name} must contain routing "
|
|
2463
|
+
f"target `{target}`."
|
|
2464
|
+
)
|
|
2465
|
+
|
|
2466
|
+
next_steps_value = data.get("recommendedNextSteps")
|
|
2467
|
+
next_steps = next_steps_value if isinstance(next_steps_value, list) else []
|
|
2468
|
+
first_step = (
|
|
2469
|
+
next_steps[0]
|
|
2470
|
+
if next_steps and isinstance(next_steps[0], Mapping)
|
|
2471
|
+
else {}
|
|
2472
|
+
)
|
|
2473
|
+
step_text = first_step.get("text")
|
|
2474
|
+
if not _route_target_matches(step_text, target, command=False):
|
|
2475
|
+
failures.append(
|
|
2476
|
+
"final-report data.json: recommendedNextSteps[0].text must contain "
|
|
2477
|
+
f"routing target `{target}`."
|
|
2478
|
+
)
|
|
2479
|
+
commands_value = first_step.get("commands")
|
|
2480
|
+
commands = commands_value if isinstance(commands_value, list) else []
|
|
2481
|
+
if not commands:
|
|
2482
|
+
failures.append(
|
|
2483
|
+
"final-report data.json: recommendedNextSteps[0].commands must "
|
|
2484
|
+
"contain at least one command."
|
|
2485
|
+
)
|
|
2486
|
+
for index, command_value in enumerate(commands):
|
|
2487
|
+
command = command_value if isinstance(command_value, Mapping) else {}
|
|
2488
|
+
for command_field in ("claudeCode", "terminal"):
|
|
2489
|
+
value = command.get(command_field)
|
|
2490
|
+
if not _route_target_matches(value, target, command=True):
|
|
2491
|
+
failures.append(
|
|
2492
|
+
"final-report data.json: recommendedNextSteps[0].commands"
|
|
2493
|
+
f"[{index}].{command_field} must contain routing target "
|
|
2494
|
+
f"`{target}`."
|
|
2495
|
+
)
|
|
2496
|
+
|
|
2497
|
+
|
|
2243
2498
|
def _load_final_report_data(report_path: Path) -> dict:
|
|
2244
2499
|
"""Best-effort parse of the final-report data.json sibling. Returns {} when
|
|
2245
2500
|
absent or unparseable — those conditions are already surfaced as failures by
|
|
@@ -2258,7 +2513,7 @@ def validate_final_report_data(
|
|
|
2258
2513
|
failures: list[str],
|
|
2259
2514
|
*,
|
|
2260
2515
|
report_contracts: set[str] | None = None,
|
|
2261
|
-
) -> None:
|
|
2516
|
+
) -> Mapping[str, Any] | None:
|
|
2262
2517
|
"""Validate the final-report data.json against the v1.0 schema.
|
|
2263
2518
|
|
|
2264
2519
|
The data.json is the source-of-truth that the renderer reads to
|
|
@@ -2271,6 +2526,8 @@ def validate_final_report_data(
|
|
|
2271
2526
|
Missing data.json is reported as a single failure rather than a
|
|
2272
2527
|
cascade of substring failures — that points the writer at the right
|
|
2273
2528
|
fix (write the data.json) instead of futilely editing the markdown.
|
|
2529
|
+
The returned mapping is the exact loaded snapshot consumed by later
|
|
2530
|
+
finalization checks and workflow persistence.
|
|
2274
2531
|
"""
|
|
2275
2532
|
if schema_validate is None or load_schema is None:
|
|
2276
2533
|
# Module-load fallback path; should never fire in a real install.
|
|
@@ -2315,7 +2572,9 @@ def validate_final_report_data(
|
|
|
2315
2572
|
_validate_verifier_fail_blocks_verdict(data, failures)
|
|
2316
2573
|
if task_type == "implementation":
|
|
2317
2574
|
_validate_stage_carry_sidecar_exists(data, report_path, failures)
|
|
2318
|
-
if task_type == "
|
|
2575
|
+
if task_type == "error-analysis":
|
|
2576
|
+
_validate_error_analysis_consistency(data, failures)
|
|
2577
|
+
elif task_type == "final-verification":
|
|
2319
2578
|
_validate_final_verification_consistency(data, failures)
|
|
2320
2579
|
_validate_verified_row_recorded(data, report_path, failures)
|
|
2321
2580
|
elif task_type == "implementation-planning":
|
|
@@ -2364,6 +2623,8 @@ def validate_final_report_data(
|
|
|
2364
2623
|
for warning in warnings:
|
|
2365
2624
|
print(f"validate-run: warning: {warning}", file=sys.stderr)
|
|
2366
2625
|
|
|
2626
|
+
return data
|
|
2627
|
+
|
|
2367
2628
|
|
|
2368
2629
|
# path:line (foo.service.ts:268), an in-report ID (C-001 / E-006 / P-004), a
|
|
2369
2630
|
# namespaced audit ref (claude:F-005), or a §section reference (§5.4) — any one
|
|
@@ -3727,20 +3988,6 @@ def _validate_missing_qa_categories_recorded(
|
|
|
3727
3988
|
)
|
|
3728
3989
|
|
|
3729
3990
|
|
|
3730
|
-
def _load_report_data(report_path: Path) -> dict | None:
|
|
3731
|
-
"""The report's data.json, or ``None`` when it is missing or malformed.
|
|
3732
|
-
Both cases are already reported by `validate_final_report_data`; callers
|
|
3733
|
-
here only need to skip rather than re-report."""
|
|
3734
|
-
data_path = _data_path_for(report_path)
|
|
3735
|
-
if not data_path.is_file():
|
|
3736
|
-
return None
|
|
3737
|
-
try:
|
|
3738
|
-
loaded = json.loads(data_path.read_text(encoding="utf-8"))
|
|
3739
|
-
except (OSError, json.JSONDecodeError):
|
|
3740
|
-
return None
|
|
3741
|
-
return loaded if isinstance(loaded, dict) else None
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
3991
|
def _validate_verification_target_match(
|
|
3745
3992
|
data: dict,
|
|
3746
3993
|
run_manifest: dict,
|
|
@@ -6340,20 +6587,20 @@ def main() -> int:
|
|
|
6340
6587
|
report_contracts = _normalize_report_contracts(
|
|
6341
6588
|
run_manifest.get("reportContracts")
|
|
6342
6589
|
)
|
|
6343
|
-
validate_final_report_data(
|
|
6590
|
+
report_data = validate_final_report_data(
|
|
6344
6591
|
report_path,
|
|
6345
6592
|
failures,
|
|
6346
6593
|
report_contracts=report_contracts,
|
|
6347
6594
|
)
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
)
|
|
6595
|
+
validation_data = report_data if isinstance(report_data, Mapping) else {}
|
|
6596
|
+
_validate_fix_cycle(run_manifest, validation_data, failures)
|
|
6351
6597
|
validate_report(
|
|
6352
6598
|
report_path,
|
|
6353
6599
|
contract["required_agent_status_entries"],
|
|
6354
6600
|
failures,
|
|
6355
6601
|
allow_unavailable_token_usage=_session_accounting(team_state) == "artifact-only",
|
|
6356
6602
|
unavailable_ok_labels=_unavailable_usage_worker_labels(team_state),
|
|
6603
|
+
report_data=validation_data,
|
|
6357
6604
|
)
|
|
6358
6605
|
validate_team_state_usage(team_state, failures)
|
|
6359
6606
|
|
|
@@ -6401,11 +6648,12 @@ def main() -> int:
|
|
|
6401
6648
|
if task_type in _END_STATE_PHASES:
|
|
6402
6649
|
if task_type == "implementation-planning":
|
|
6403
6650
|
_validate_planning_conformance_declared(report_path, failures)
|
|
6404
|
-
|
|
6405
|
-
_validate_end_state_coverage(report_data, brief_path, failures)
|
|
6651
|
+
_validate_end_state_coverage(validation_data, brief_path, failures)
|
|
6406
6652
|
if task_type == "implementation-planning":
|
|
6407
|
-
_validate_requirement_provenance(
|
|
6408
|
-
|
|
6653
|
+
_validate_requirement_provenance(
|
|
6654
|
+
validation_data, brief_path, failures
|
|
6655
|
+
)
|
|
6656
|
+
_validate_stage_has_requirement(validation_data, failures)
|
|
6409
6657
|
if task_type == "improvement-discovery":
|
|
6410
6658
|
run_dir = report_path.parent.parent
|
|
6411
6659
|
_validate_improvement_discovery(report_path, run_dir, brief_path, failures)
|
|
@@ -6422,22 +6670,20 @@ def main() -> int:
|
|
|
6422
6670
|
validate_report_views(report_path, failures)
|
|
6423
6671
|
if task_type == "implementation":
|
|
6424
6672
|
_validate_missing_qa_categories_recorded(
|
|
6425
|
-
|
|
6673
|
+
validation_data, project_root, failures
|
|
6426
6674
|
)
|
|
6427
6675
|
if task_type == "implementation-planning":
|
|
6428
|
-
_validate_plan_body_state_file(
|
|
6429
|
-
_load_report_data(report_path) or {}, report_path, failures
|
|
6430
|
-
)
|
|
6676
|
+
_validate_plan_body_state_file(validation_data, report_path, failures)
|
|
6431
6677
|
if task_type == "final-verification":
|
|
6432
6678
|
_validate_verification_target_match(
|
|
6433
|
-
|
|
6679
|
+
validation_data,
|
|
6434
6680
|
run_manifest,
|
|
6435
6681
|
project_root,
|
|
6436
6682
|
failures,
|
|
6437
6683
|
)
|
|
6438
6684
|
# Phase-agnostic: any task-type can be launched with a carry-in.
|
|
6439
6685
|
_validate_clarification_carry_in_recorded(
|
|
6440
|
-
|
|
6686
|
+
validation_data,
|
|
6441
6687
|
report_path,
|
|
6442
6688
|
run_manifest_path,
|
|
6443
6689
|
project_root,
|
|
@@ -6446,7 +6692,12 @@ def main() -> int:
|
|
|
6446
6692
|
|
|
6447
6693
|
validation_status = "passed" if not failures else "failed"
|
|
6448
6694
|
update_validation_metadata(
|
|
6449
|
-
team_state,
|
|
6695
|
+
team_state,
|
|
6696
|
+
run_manifest,
|
|
6697
|
+
task_manifest,
|
|
6698
|
+
validation_status,
|
|
6699
|
+
failures,
|
|
6700
|
+
report_data=validation_data,
|
|
6450
6701
|
)
|
|
6451
6702
|
|
|
6452
6703
|
write_json(team_state_path, team_state)
|