okstra 0.167.0 → 0.169.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/docs/architecture/storage-model.md +57 -1
- package/docs/architecture.md +70 -2
- package/docs/cli.md +8 -4
- package/docs/for-ai/skills/okstra-code-review.md +3 -2
- package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
- package/docs/pr-template-usage.md +10 -6
- package/docs/project-structure-overview.md +14 -11
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -5
- package/runtime/agents/workers/report-writer-worker.md +9 -4
- package/runtime/agents/workers/translator-worker.md +6 -4
- package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
- package/runtime/prompts/coding-preflight/clean-code.md +13 -0
- package/runtime/prompts/duties/acceptance-critic.md +24 -0
- package/runtime/prompts/duties/acceptance-verifier.md +24 -0
- package/runtime/prompts/duties/analysis-worker.md +24 -0
- package/runtime/prompts/duties/code-reviewer.md +24 -0
- package/runtime/prompts/duties/common.md +35 -0
- package/runtime/prompts/duties/implementation-executor.md +24 -0
- package/runtime/prompts/duties/implementation-verifier.md +24 -0
- package/runtime/prompts/duties/lead.md +24 -0
- package/runtime/prompts/duties/report-writer.md +24 -0
- package/runtime/prompts/duties/reverification-worker.md +24 -0
- package/runtime/prompts/duties/schedule-verifier.md +24 -0
- package/runtime/prompts/duties/scope-critic.md +24 -0
- package/runtime/prompts/duties/translator.md +24 -0
- package/runtime/prompts/lead/convergence.md +51 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
- package/runtime/prompts/lead/plan-body-verification.md +16 -1
- package/runtime/prompts/lead/report-writer.md +20 -5
- package/runtime/prompts/lead/team-contract.md +13 -13
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
- package/runtime/prompts/profiles/implementation.md +4 -2
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
- package/runtime/python/okstra_ctl/context_cost.py +46 -5
- package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
- package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
- package/runtime/python/okstra_ctl/doctor.py +150 -16
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
- package/runtime/python/okstra_ctl/path_hints.py +26 -0
- package/runtime/python/okstra_ctl/paths.py +20 -0
- package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
- package/runtime/python/okstra_ctl/ports/host.py +3 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
- package/runtime/python/okstra_ctl/pr_template.py +3 -6
- package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
- package/runtime/python/okstra_ctl/render.py +217 -12
- package/runtime/python/okstra_ctl/report_finalize.py +44 -0
- package/runtime/python/okstra_ctl/run.py +368 -51
- package/runtime/python/okstra_ctl/session.py +16 -12
- package/runtime/python/okstra_ctl/team.py +11 -11
- package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
- package/runtime/skills/okstra-code-review/SKILL.md +22 -3
- package/runtime/skills/okstra-run/SKILL.md +16 -1
- package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
- package/runtime/templates/implementation-worker-preamble.md +0 -10
- package/runtime/templates/report-writer-prompt-preamble.md +0 -9
- package/runtime/templates/reports/settings.template.json +0 -11
- package/runtime/templates/worker-prompt-preamble.md +0 -10
- package/runtime/validators/lib/fixtures.sh +93 -0
- package/runtime/validators/lib/validate-assets.sh +0 -8
- package/runtime/validators/validate-run.py +182 -0
- package/src/cli-registry.mjs +14 -0
- package/src/commands/execute/agent-prompt.mjs +25 -0
- package/src/commands/execute/codex-dispatch.mjs +6 -63
- package/src/commands/execute/worker-dispatch.mjs +76 -0
- package/src/commands/lifecycle/doctor.mjs +18 -3
- package/src/commands/lifecycle/install.mjs +33 -15
- package/src/commands/lifecycle/uninstall.mjs +4 -3
- package/src/lib/install-assets.mjs +9 -0
- package/runtime/agents/workers/antigravity-worker.md +0 -259
- package/runtime/agents/workers/codex-worker.md +0 -259
- package/runtime/agents/workers/grok-worker.md +0 -259
- package/runtime/agents/workers/kimi-worker.md +0 -259
- package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
- package/runtime/templates/operating-standard.md +0 -22
- package/src/lib/worker-agent-render.mjs +0 -50
- /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
|
@@ -21,6 +21,7 @@ import hashlib
|
|
|
21
21
|
import json
|
|
22
22
|
import re
|
|
23
23
|
import sys
|
|
24
|
+
from collections.abc import Mapping
|
|
24
25
|
from pathlib import Path
|
|
25
26
|
|
|
26
27
|
from okstra_project.dirs import TASK_MANIFEST_FILENAME, OKSTRA_DIR_NAME, project_json_path
|
|
@@ -354,6 +355,58 @@ def _lead_assignment(ctx: dict) -> dict:
|
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
|
|
358
|
+
def _invocation_assignments(ctx: dict) -> dict:
|
|
359
|
+
raw = ctx.get("INVOCATION_ASSIGNMENTS_JSON", "")
|
|
360
|
+
try:
|
|
361
|
+
parsed = json.loads(raw) if raw else {}
|
|
362
|
+
except json.JSONDecodeError as exc:
|
|
363
|
+
raise ValueError("invalid invocation assignments JSON") from exc
|
|
364
|
+
if not isinstance(parsed, dict):
|
|
365
|
+
raise ValueError("invocation assignments must be an object")
|
|
366
|
+
return parsed
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _agent_contract(ctx: dict) -> dict:
|
|
370
|
+
raw = ctx.get("AGENT_CONTRACT_JSON", "")
|
|
371
|
+
try:
|
|
372
|
+
parsed = json.loads(raw) if raw else {}
|
|
373
|
+
except json.JSONDecodeError as exc:
|
|
374
|
+
raise ValueError("invalid agent contract JSON") from exc
|
|
375
|
+
if not isinstance(parsed, dict):
|
|
376
|
+
raise ValueError("agent contract must be an object")
|
|
377
|
+
return parsed
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _executor_contract(ctx: dict) -> dict:
|
|
381
|
+
runner = ctx.get("EXECUTOR_RUNNER", "")
|
|
382
|
+
dispatch_mode = ctx.get("EXECUTOR_DISPATCH_MODE", "")
|
|
383
|
+
host_model_value = ctx.get("EXECUTOR_HOST_MODEL_VALUE") or None
|
|
384
|
+
expected_mode = {
|
|
385
|
+
"native-session": "host-native",
|
|
386
|
+
"cli-wrapper": "worker-dispatch",
|
|
387
|
+
}.get(runner)
|
|
388
|
+
if expected_mode is None or dispatch_mode != expected_mode:
|
|
389
|
+
raise ValueError(
|
|
390
|
+
"executor runner and dispatch mode must be native-session/host-native "
|
|
391
|
+
"or cli-wrapper/worker-dispatch"
|
|
392
|
+
)
|
|
393
|
+
if runner == "native-session" and not host_model_value:
|
|
394
|
+
raise ValueError("native-session executor requires host model value")
|
|
395
|
+
if runner == "cli-wrapper" and host_model_value is not None:
|
|
396
|
+
raise ValueError("cli-wrapper executor must not define host model value")
|
|
397
|
+
return {
|
|
398
|
+
"workerId": ctx.get("EXECUTOR_WORKER_ID", ""),
|
|
399
|
+
"provider": ctx.get("EXECUTOR_PROVIDER", ""),
|
|
400
|
+
"displayName": ctx.get("EXECUTOR_DISPLAY_NAME", ""),
|
|
401
|
+
"model": ctx.get("EXECUTOR_MODEL_DISPLAY", ""),
|
|
402
|
+
"modelExecutionValue": ctx.get("EXECUTOR_MODEL_EXECUTION_VALUE", ""),
|
|
403
|
+
"hostModelValue": host_model_value,
|
|
404
|
+
"runner": runner,
|
|
405
|
+
"dispatchMode": dispatch_mode,
|
|
406
|
+
"appliesTo": "implementation",
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
357
410
|
def _worker_assignments(ctx: dict) -> list[dict]:
|
|
358
411
|
raw = ctx.get("WORKER_ASSIGNMENTS_JSON", "")
|
|
359
412
|
if raw:
|
|
@@ -648,6 +701,7 @@ def render_active_run_context(active_context_path: str, ctx: dict) -> None:
|
|
|
648
701
|
"task": _active_task(ctx),
|
|
649
702
|
"workflow": _active_workflow(ctx),
|
|
650
703
|
"run": _active_run(ctx),
|
|
704
|
+
"agentContract": _agent_contract(ctx),
|
|
651
705
|
"instructionSet": _active_instruction_set(ctx),
|
|
652
706
|
"workers": _active_workers(ctx),
|
|
653
707
|
"errorLogs": _active_error_logs(ctx),
|
|
@@ -690,6 +744,8 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
690
744
|
"leadRuntime": _lead_runtime(ctx),
|
|
691
745
|
"leadAssignment": _lead_assignment(ctx),
|
|
692
746
|
"workerAssignments": _worker_assignments(ctx),
|
|
747
|
+
"agentContract": _agent_contract(ctx),
|
|
748
|
+
"invocationAssignments": _invocation_assignments(ctx),
|
|
693
749
|
"leadAdapter": _lead_adapter(ctx, worker_dispatch_plan),
|
|
694
750
|
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
695
751
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
@@ -1341,10 +1397,8 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1341
1397
|
"referenceExpectationsPath": ctx.get(
|
|
1342
1398
|
"REFERENCE_EXPECTATIONS_RELATIVE_PATH", ""
|
|
1343
1399
|
),
|
|
1344
|
-
"leadExecutionPromptPath": ctx.get("
|
|
1345
|
-
|
|
1346
|
-
"claudeExecutionPromptPath": ctx.get("INSTRUCTION_SET_RELATIVE_PATH", "")
|
|
1347
|
-
+ "/claude-execution-prompt.md",
|
|
1400
|
+
"leadExecutionPromptPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1401
|
+
"claudeExecutionPromptPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1348
1402
|
"leadPromptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1349
1403
|
"workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1350
1404
|
"workerPromptPathByWorkerId": worker_prompt_paths,
|
|
@@ -1567,6 +1621,8 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1567
1621
|
"runtimeResolution": _runtime_resolution(ctx),
|
|
1568
1622
|
"leadAssignment": _lead_assignment(ctx),
|
|
1569
1623
|
"workerAssignments": _worker_assignments(ctx),
|
|
1624
|
+
"agentContract": _agent_contract(ctx),
|
|
1625
|
+
"invocationAssignments": _invocation_assignments(ctx),
|
|
1570
1626
|
"leadAdapter": _lead_adapter(ctx, worker_dispatch_plan),
|
|
1571
1627
|
"workCategory": task_manifest.get(
|
|
1572
1628
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
@@ -1595,6 +1651,33 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1595
1651
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
1596
1652
|
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
1597
1653
|
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1654
|
+
"leadInstructionsPath": ctx.get("LEAD_INSTRUCTIONS_RELATIVE_PATH", ""),
|
|
1655
|
+
"leadExecutionPromptPath": ctx.get(
|
|
1656
|
+
"RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""
|
|
1657
|
+
),
|
|
1658
|
+
"leadPromptMetadataPath": ctx.get(
|
|
1659
|
+
"LEAD_PROMPT_METADATA_RELATIVE_PATH", ""
|
|
1660
|
+
),
|
|
1661
|
+
"resources": {
|
|
1662
|
+
"leadInstructionsPath": ctx.get(
|
|
1663
|
+
"LEAD_INSTRUCTIONS_RELATIVE_PATH", ""
|
|
1664
|
+
),
|
|
1665
|
+
"leadExecutionPromptPath": ctx.get(
|
|
1666
|
+
"RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""
|
|
1667
|
+
),
|
|
1668
|
+
"leadPromptMetadataPath": ctx.get(
|
|
1669
|
+
"LEAD_PROMPT_METADATA_RELATIVE_PATH", ""
|
|
1670
|
+
),
|
|
1671
|
+
"leadPromptSnapshotPath": ctx.get(
|
|
1672
|
+
"RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""
|
|
1673
|
+
),
|
|
1674
|
+
"claudeExecutionPromptPath": ctx.get(
|
|
1675
|
+
"RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""
|
|
1676
|
+
),
|
|
1677
|
+
},
|
|
1678
|
+
"invocationReservationRootPath": ctx.get(
|
|
1679
|
+
"INVOCATION_RESERVATION_ROOT_RELATIVE_PATH", ""
|
|
1680
|
+
),
|
|
1598
1681
|
"workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1599
1682
|
"workerPromptPathByWorkerId": worker_prompt_paths,
|
|
1600
1683
|
"expectedReportPath": ctx.get("FINAL_REPORT_RELATIVE_PATH", ""),
|
|
@@ -1664,14 +1747,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1664
1747
|
"disallowLeadSoloAnalysisAsWorkerResult": True,
|
|
1665
1748
|
"disallowGenericParallelOnlyExecution": True,
|
|
1666
1749
|
"preferredCompletedWorkerResults": len(reviewers),
|
|
1667
|
-
"executor":
|
|
1668
|
-
"provider": ctx.get("EXECUTOR_PROVIDER", ""),
|
|
1669
|
-
"displayName": ctx.get("EXECUTOR_DISPLAY_NAME", ""),
|
|
1670
|
-
"workerAgent": ctx.get("EXECUTOR_WORKER_AGENT", ""),
|
|
1671
|
-
"model": ctx.get("EXECUTOR_MODEL_DISPLAY", ""),
|
|
1672
|
-
"modelExecutionValue": ctx.get("EXECUTOR_MODEL_EXECUTION_VALUE", ""),
|
|
1673
|
-
"appliesTo": "implementation",
|
|
1674
|
-
},
|
|
1750
|
+
"executor": _executor_contract(ctx),
|
|
1675
1751
|
},
|
|
1676
1752
|
"validation": {
|
|
1677
1753
|
"required": True,
|
|
@@ -1698,9 +1774,138 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1698
1774
|
)
|
|
1699
1775
|
if _lead_runtime(ctx) == "claude-code":
|
|
1700
1776
|
payload["claudeSessionId"] = ctx.get("CLAUDE_SESSION_ID", "")
|
|
1777
|
+
_guard_invocation_manifest_rewrite(existing_run_manifest, payload)
|
|
1701
1778
|
_write_json(Path(run_manifest_path), payload)
|
|
1702
1779
|
|
|
1703
1780
|
|
|
1781
|
+
_MODEL_ASSIGNMENT_KEYS = {
|
|
1782
|
+
"provider",
|
|
1783
|
+
"model",
|
|
1784
|
+
"modelExecutionValue",
|
|
1785
|
+
"runner",
|
|
1786
|
+
"hostRuntime",
|
|
1787
|
+
"hostModelValue",
|
|
1788
|
+
}
|
|
1789
|
+
_AGENT_CONTRACT_KEYS = {
|
|
1790
|
+
"schemaVersion",
|
|
1791
|
+
"dutyRootPath",
|
|
1792
|
+
"catalogDigest",
|
|
1793
|
+
"invocationReservationRootPath",
|
|
1794
|
+
"allowedAudiences",
|
|
1795
|
+
"authorizedPaths",
|
|
1796
|
+
}
|
|
1797
|
+
_IMMUTABLE_INVOCATION_KEYS = (
|
|
1798
|
+
"agentContract",
|
|
1799
|
+
"invocationAssignments",
|
|
1800
|
+
"leadAssignment",
|
|
1801
|
+
"workerAssignments",
|
|
1802
|
+
"runDirectoryPath",
|
|
1803
|
+
"runManifestPath",
|
|
1804
|
+
"leadInstructionsPath",
|
|
1805
|
+
"leadExecutionPromptPath",
|
|
1806
|
+
"promptSnapshotPath",
|
|
1807
|
+
"leadPromptMetadataPath",
|
|
1808
|
+
"resources",
|
|
1809
|
+
"invocationReservationRootPath",
|
|
1810
|
+
"workerPromptsDirectoryPath",
|
|
1811
|
+
"workerPromptPathByWorkerId",
|
|
1812
|
+
"workerResultsDirectoryPath",
|
|
1813
|
+
"expectedReportPath",
|
|
1814
|
+
"reportTemplatePath",
|
|
1815
|
+
)
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
def _guard_invocation_manifest_rewrite(
|
|
1819
|
+
existing: Mapping[str, object],
|
|
1820
|
+
proposed: Mapping[str, object],
|
|
1821
|
+
) -> None:
|
|
1822
|
+
existing_has_contract = bool(existing.get("agentContract"))
|
|
1823
|
+
proposed_has_contract = bool(proposed.get("agentContract"))
|
|
1824
|
+
if existing_has_contract:
|
|
1825
|
+
_validate_invocation_manifest(existing)
|
|
1826
|
+
if proposed_has_contract:
|
|
1827
|
+
_validate_invocation_manifest(proposed)
|
|
1828
|
+
elif existing_has_contract:
|
|
1829
|
+
raise ValueError("incomplete invocation manifest: proposed contract missing")
|
|
1830
|
+
if not existing_has_contract:
|
|
1831
|
+
return
|
|
1832
|
+
drift = [
|
|
1833
|
+
key
|
|
1834
|
+
for key in _IMMUTABLE_INVOCATION_KEYS
|
|
1835
|
+
if existing.get(key) != proposed.get(key)
|
|
1836
|
+
]
|
|
1837
|
+
if drift:
|
|
1838
|
+
raise ValueError(
|
|
1839
|
+
"immutable invocation manifest fields changed: " + ", ".join(drift)
|
|
1840
|
+
)
|
|
1841
|
+
|
|
1842
|
+
|
|
1843
|
+
def _validate_invocation_manifest(payload: Mapping[str, object]) -> None:
|
|
1844
|
+
contract = payload.get("agentContract")
|
|
1845
|
+
assignments = payload.get("invocationAssignments")
|
|
1846
|
+
if (
|
|
1847
|
+
not isinstance(contract, Mapping)
|
|
1848
|
+
or set(contract) != _AGENT_CONTRACT_KEYS
|
|
1849
|
+
or contract.get("schemaVersion") != 1
|
|
1850
|
+
or not isinstance(assignments, Mapping)
|
|
1851
|
+
or not assignments
|
|
1852
|
+
):
|
|
1853
|
+
raise ValueError("incomplete invocation manifest")
|
|
1854
|
+
if any(
|
|
1855
|
+
not isinstance(item, Mapping) or set(item) != _MODEL_ASSIGNMENT_KEYS
|
|
1856
|
+
for item in assignments.values()
|
|
1857
|
+
):
|
|
1858
|
+
raise ValueError("incomplete invocation manifest assignments")
|
|
1859
|
+
_validate_compatibility_projections(payload, assignments)
|
|
1860
|
+
for key in (
|
|
1861
|
+
item
|
|
1862
|
+
for item in _IMMUTABLE_INVOCATION_KEYS[4:]
|
|
1863
|
+
if item != "workerPromptPathByWorkerId"
|
|
1864
|
+
):
|
|
1865
|
+
if not payload.get(key):
|
|
1866
|
+
raise ValueError(f"incomplete invocation manifest path: {key}")
|
|
1867
|
+
workers = payload.get("workerAssignments")
|
|
1868
|
+
worker_prompt_paths = payload.get("workerPromptPathByWorkerId")
|
|
1869
|
+
expected_worker_ids = {
|
|
1870
|
+
str(worker.get("workerId") or "")
|
|
1871
|
+
for worker in workers
|
|
1872
|
+
if isinstance(worker, Mapping)
|
|
1873
|
+
}
|
|
1874
|
+
if (
|
|
1875
|
+
not isinstance(worker_prompt_paths, Mapping)
|
|
1876
|
+
or set(worker_prompt_paths) != expected_worker_ids
|
|
1877
|
+
or any(
|
|
1878
|
+
not isinstance(path, str) or not path
|
|
1879
|
+
for path in worker_prompt_paths.values()
|
|
1880
|
+
)
|
|
1881
|
+
):
|
|
1882
|
+
raise ValueError(
|
|
1883
|
+
"incomplete invocation manifest path: workerPromptPathByWorkerId"
|
|
1884
|
+
)
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
def _validate_compatibility_projections(
|
|
1888
|
+
payload: Mapping[str, object],
|
|
1889
|
+
assignments: Mapping[str, object],
|
|
1890
|
+
) -> None:
|
|
1891
|
+
lead = payload.get("leadAssignment")
|
|
1892
|
+
workers = payload.get("workerAssignments")
|
|
1893
|
+
if not isinstance(lead, Mapping) or not isinstance(workers, list):
|
|
1894
|
+
raise ValueError("incomplete invocation manifest compatibility projection")
|
|
1895
|
+
if _model_projection(lead) != assignments.get("lead"):
|
|
1896
|
+
raise ValueError("immutable invocation manifest lead projection drift")
|
|
1897
|
+
for worker in workers:
|
|
1898
|
+
if not isinstance(worker, Mapping):
|
|
1899
|
+
raise ValueError("incomplete invocation manifest worker projection")
|
|
1900
|
+
reference = f"initial/{worker.get('workerId', '')}"
|
|
1901
|
+
if _model_projection(worker) != assignments.get(reference):
|
|
1902
|
+
raise ValueError("immutable invocation manifest worker projection drift")
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
def _model_projection(value: Mapping[str, object]) -> dict[str, object]:
|
|
1906
|
+
return {key: value.get(key) for key in _MODEL_ASSIGNMENT_KEYS}
|
|
1907
|
+
|
|
1908
|
+
|
|
1704
1909
|
def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
1705
1910
|
task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
|
|
1706
1911
|
task_manifest = {}
|
|
@@ -27,6 +27,7 @@ from typing import Any, Callable, Mapping, Sequence
|
|
|
27
27
|
|
|
28
28
|
from .final_report_paths import final_report_data_path, final_report_markdown_path
|
|
29
29
|
from .paths import task_dir, task_manifest_file
|
|
30
|
+
from .dispatch_state import DispatchError, link_agent_dispatch_result
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
STEP_CHECK_SOURCE = "check-source"
|
|
@@ -371,6 +372,15 @@ def run_finalize(
|
|
|
371
372
|
for name, command in commands:
|
|
372
373
|
if before_step is not None:
|
|
373
374
|
before_step(name)
|
|
375
|
+
if name == STEP_VALIDATE_RUN:
|
|
376
|
+
try:
|
|
377
|
+
_link_lead_result_for_validation(ctx)
|
|
378
|
+
except (DispatchError, OSError, json.JSONDecodeError) as exc:
|
|
379
|
+
return {
|
|
380
|
+
"ok": False,
|
|
381
|
+
"reason": f"lead result linkage failed: {exc}",
|
|
382
|
+
"steps": steps,
|
|
383
|
+
}
|
|
374
384
|
result = subprocess.run(
|
|
375
385
|
command,
|
|
376
386
|
cwd=ctx.project_root,
|
|
@@ -387,6 +397,40 @@ def run_finalize(
|
|
|
387
397
|
return {"ok": True, "reason": "", "steps": steps}
|
|
388
398
|
|
|
389
399
|
|
|
400
|
+
def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
|
|
401
|
+
"""Link the lead-authored synthesis artifact before Phase 7 validation."""
|
|
402
|
+
manifest = _load_manifest(ctx.manifest_path)
|
|
403
|
+
contract = manifest.get("agentContract")
|
|
404
|
+
if not isinstance(contract, Mapping) or contract.get("schemaVersion") != 1:
|
|
405
|
+
return
|
|
406
|
+
team_state = _load_manifest(ctx.team_state_path)
|
|
407
|
+
lead_dispatches = [
|
|
408
|
+
row for row in (team_state.get("agentDispatches") or [])
|
|
409
|
+
if isinstance(row, Mapping)
|
|
410
|
+
and row.get("audience") == "lead"
|
|
411
|
+
and row.get("workerId") == "lead"
|
|
412
|
+
]
|
|
413
|
+
if len(lead_dispatches) != 1:
|
|
414
|
+
return
|
|
415
|
+
convergence_value = string_value(manifest.get("convergenceStatePath"))
|
|
416
|
+
convergence_path = (
|
|
417
|
+
resolve_project_path(ctx.project_root, convergence_value)
|
|
418
|
+
if convergence_value else None
|
|
419
|
+
)
|
|
420
|
+
if convergence_path is not None and convergence_path.is_file():
|
|
421
|
+
result_path = convergence_path
|
|
422
|
+
elif ctx.task_type == "release-handoff" and ctx.data_path.is_file():
|
|
423
|
+
result_path = ctx.data_path
|
|
424
|
+
else:
|
|
425
|
+
return
|
|
426
|
+
link_agent_dispatch_result(
|
|
427
|
+
project_root=ctx.project_root,
|
|
428
|
+
run_manifest_path=ctx.manifest_path,
|
|
429
|
+
dispatch_id=str(lead_dispatches[0]["dispatchId"]),
|
|
430
|
+
result_path=result_path,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
390
434
|
def _load_manifest(path: Path) -> Mapping[str, Any]:
|
|
391
435
|
try:
|
|
392
436
|
payload = json.loads(path.read_text(encoding="utf-8"))
|