okstra 0.180.0 → 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.
Files changed (223) hide show
  1. package/dist/cli-registry.mjs +16 -2
  2. package/dist/cli-registry.mjs.map +1 -1
  3. package/dist/commands/execute/render-bundle.d.mts +4 -2
  4. package/dist/commands/execute/render-bundle.mjs +46 -5
  5. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  6. package/dist/commands/execute/run.mjs +11 -3
  7. package/dist/commands/execute/run.mjs.map +1 -1
  8. package/dist/commands/inspect/model-io.d.mts +1 -0
  9. package/dist/commands/inspect/model-io.mjs +25 -0
  10. package/dist/commands/inspect/model-io.mjs.map +1 -0
  11. package/dist/commands/inspect/stage-map.mjs +29 -8
  12. package/dist/commands/inspect/stage-map.mjs.map +1 -1
  13. package/dist/commands/inspect/task-list.mjs +52 -6
  14. package/dist/commands/inspect/task-list.mjs.map +1 -1
  15. package/dist/commands/inspect/user-response.mjs +14 -4
  16. package/dist/commands/inspect/user-response.mjs.map +1 -1
  17. package/dist/commands/lifecycle/check-project.d.mts +1 -0
  18. package/dist/commands/lifecycle/check-project.mjs +69 -50
  19. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  20. package/dist/commands/lifecycle/contract-check.d.mts +1 -0
  21. package/dist/commands/lifecycle/contract-check.mjs +18 -0
  22. package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
  23. package/dist/commands/lifecycle/preflight.mjs +154 -51
  24. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  25. package/dist/commands/pr/pr.d.mts +1 -0
  26. package/dist/commands/pr/pr.mjs +19 -1
  27. package/dist/commands/pr/pr.mjs.map +1 -1
  28. package/dist/commands/report/agent-activity.mjs +2 -2
  29. package/dist/commands/report/translate.mjs +3 -0
  30. package/dist/commands/report/translate.mjs.map +1 -1
  31. package/dist/lib/host-registry-client.mjs +13 -9
  32. package/dist/lib/host-registry-client.mjs.map +1 -1
  33. package/docs/architecture.md +11 -0
  34. package/docs/cli.md +26 -13
  35. package/docs/container.md +6 -4
  36. package/docs/contributor-change-matrix.md +1 -1
  37. package/docs/for-ai/README.md +2 -2
  38. package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
  39. package/docs/for-ai/skills/okstra-code-review.md +4 -4
  40. package/docs/for-ai/skills/okstra-container-build.md +20 -17
  41. package/docs/for-ai/skills/okstra-inspect.md +20 -23
  42. package/docs/for-ai/skills/okstra-manager.md +19 -18
  43. package/docs/for-ai/skills/okstra-memory.md +2 -2
  44. package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
  45. package/docs/for-ai/skills/okstra-rollup.md +14 -13
  46. package/docs/for-ai/skills/okstra-run.md +7 -3
  47. package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
  48. package/docs/for-ai/skills/okstra-setup.md +7 -7
  49. package/docs/for-ai/skills/okstra-usage.md +5 -4
  50. package/docs/for-ai/skills/okstra-user-response.md +50 -32
  51. package/docs/project-structure-overview.md +29 -26
  52. package/docs/task-process/implementation.md +7 -4
  53. package/package.json +1 -1
  54. package/runtime/BUILD.json +2 -2
  55. package/runtime/agents/workers/claude-worker.md +4 -4
  56. package/runtime/agents/workers/report-writer-worker.md +3 -3
  57. package/runtime/agents/workers/translator-worker.md +5 -13
  58. package/runtime/bin/okstra-error-log.py +51 -11
  59. package/runtime/bin/okstra-report-translate.py +210 -23
  60. package/runtime/prompts/host-orchestration/implementation.md +1 -1
  61. package/runtime/prompts/launch.template.md +3 -7
  62. package/runtime/prompts/lead/context-loader.md +41 -141
  63. package/runtime/prompts/lead/convergence.md +8 -6
  64. package/runtime/prompts/lead/okstra-lead-contract.md +25 -35
  65. package/runtime/prompts/lead/plan-body-verification.md +187 -14
  66. package/runtime/prompts/lead/report-writer.md +20 -1
  67. package/runtime/prompts/lead/team-contract.md +8 -53
  68. package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
  69. package/runtime/prompts/profiles/_common-contract.md +1 -1
  70. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  71. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  72. package/runtime/prompts/profiles/final-verification.md +1 -1
  73. package/runtime/prompts/profiles/implementation-planning.md +8 -2
  74. package/runtime/prompts/profiles/release-handoff.md +0 -1
  75. package/runtime/prompts/wizard/prompts.ko.json +0 -1
  76. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
  77. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
  78. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +6 -1
  79. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  80. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
  81. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  82. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
  83. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
  84. package/runtime/python/okstra_ctl/agent_activity.py +118 -35
  85. package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
  86. package/runtime/python/okstra_ctl/agent_prompt_cli.py +65 -18
  87. package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
  88. package/runtime/python/okstra_ctl/analysis_packet.py +81 -1
  89. package/runtime/python/okstra_ctl/approval_decisions.py +3 -2
  90. package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
  91. package/runtime/python/okstra_ctl/backfill.py +13 -10
  92. package/runtime/python/okstra_ctl/batch.py +2 -4
  93. package/runtime/python/okstra_ctl/build_tools.py +6 -3
  94. package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
  95. package/runtime/python/okstra_ctl/clarification_items.py +27 -13
  96. package/runtime/python/okstra_ctl/cmux.py +130 -52
  97. package/runtime/python/okstra_ctl/code_review_target.py +34 -8
  98. package/runtime/python/okstra_ctl/consumers.py +5 -4
  99. package/runtime/python/okstra_ctl/container.py +103 -8
  100. package/runtime/python/okstra_ctl/context_cost.py +2 -1
  101. package/runtime/python/okstra_ctl/contract_graph.py +497 -0
  102. package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
  103. package/runtime/python/okstra_ctl/convergence.py +338 -17
  104. package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
  105. package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
  106. package/runtime/python/okstra_ctl/convergence_store.py +55 -34
  107. package/runtime/python/okstra_ctl/design_prep.py +7 -4
  108. package/runtime/python/okstra_ctl/dispatch_core.py +35 -65
  109. package/runtime/python/okstra_ctl/dispatch_state.py +134 -59
  110. package/runtime/python/okstra_ctl/doctor.py +6 -3
  111. package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
  112. package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
  113. package/runtime/python/okstra_ctl/error_log_write.py +35 -30
  114. package/runtime/python/okstra_ctl/error_report.py +26 -1
  115. package/runtime/python/okstra_ctl/error_zip.py +27 -5
  116. package/runtime/python/okstra_ctl/execution_identity.py +3 -2
  117. package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
  118. package/runtime/python/okstra_ctl/final_report_schema.py +2 -2
  119. package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
  120. package/runtime/python/okstra_ctl/fixed_text.py +39 -0
  121. package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
  122. package/runtime/python/okstra_ctl/handoff.py +5 -4
  123. package/runtime/python/okstra_ctl/i18n.py +4 -2
  124. package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
  125. package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
  126. package/runtime/python/okstra_ctl/incremental_carry.py +2 -1
  127. package/runtime/python/okstra_ctl/incremental_scope.py +8 -3
  128. package/runtime/python/okstra_ctl/index.py +8 -11
  129. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
  130. package/runtime/python/okstra_ctl/invocation.py +3 -6
  131. package/runtime/python/okstra_ctl/json_boundary.py +366 -0
  132. package/runtime/python/okstra_ctl/json_registry.py +10 -12
  133. package/runtime/python/okstra_ctl/jsonl.py +19 -2
  134. package/runtime/python/okstra_ctl/lead_events.py +33 -1
  135. package/runtime/python/okstra_ctl/listing.py +3 -3
  136. package/runtime/python/okstra_ctl/log_report.py +24 -2
  137. package/runtime/python/okstra_ctl/manager_cli.py +92 -7
  138. package/runtime/python/okstra_ctl/manager_store.py +12 -10
  139. package/runtime/python/okstra_ctl/material.py +5 -1
  140. package/runtime/python/okstra_ctl/migrate.py +29 -25
  141. package/runtime/python/okstra_ctl/model_cli.py +3 -15
  142. package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
  143. package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
  144. package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
  145. package/runtime/python/okstra_ctl/paths.py +9 -0
  146. package/runtime/python/okstra_ctl/plan_items.py +115 -4
  147. package/runtime/python/okstra_ctl/plan_items_cli.py +513 -18
  148. package/runtime/python/okstra_ctl/pr_template.py +3 -2
  149. package/runtime/python/okstra_ctl/project_meta.py +5 -7
  150. package/runtime/python/okstra_ctl/recap.py +5 -4
  151. package/runtime/python/okstra_ctl/reconcile.py +21 -27
  152. package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
  153. package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
  154. package/runtime/python/okstra_ctl/render.py +26 -15
  155. package/runtime/python/okstra_ctl/render_final_report.py +3 -2
  156. package/runtime/python/okstra_ctl/report_assembly.py +172 -17
  157. package/runtime/python/okstra_ctl/report_finalize.py +7 -10
  158. package/runtime/python/okstra_ctl/report_html/render.py +3 -2
  159. package/runtime/python/okstra_ctl/report_language.py +3 -2
  160. package/runtime/python/okstra_ctl/report_markdown.py +13 -1
  161. package/runtime/python/okstra_ctl/report_narrative.py +40 -8
  162. package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
  163. package/runtime/python/okstra_ctl/report_views.py +3 -2
  164. package/runtime/python/okstra_ctl/rollup.py +65 -4
  165. package/runtime/python/okstra_ctl/run.py +159 -56
  166. package/runtime/python/okstra_ctl/run_audit.py +3 -2
  167. package/runtime/python/okstra_ctl/run_context.py +6 -9
  168. package/runtime/python/okstra_ctl/run_index_row.py +2 -8
  169. package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
  170. package/runtime/python/okstra_ctl/schema_excerpt.py +4 -2
  171. package/runtime/python/okstra_ctl/session_transcript.py +27 -1
  172. package/runtime/python/okstra_ctl/set_work_status.py +64 -38
  173. package/runtime/python/okstra_ctl/stage_fix_carry.py +4 -2
  174. package/runtime/python/okstra_ctl/stage_map.py +26 -6
  175. package/runtime/python/okstra_ctl/stage_targets.py +3 -4
  176. package/runtime/python/okstra_ctl/team.py +2 -1
  177. package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
  178. package/runtime/python/okstra_ctl/time_report.py +51 -4
  179. package/runtime/python/okstra_ctl/usage_identity.py +2 -1
  180. package/runtime/python/okstra_ctl/usage_report.py +58 -4
  181. package/runtime/python/okstra_ctl/user_response.py +1431 -66
  182. package/runtime/python/okstra_ctl/wizard.py +11 -44
  183. package/runtime/python/okstra_ctl/work_categories.py +3 -2
  184. package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -7
  185. package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
  186. package/runtime/python/okstra_ctl/worker_runner.py +14 -12
  187. package/runtime/python/okstra_ctl/workflow.py +2 -1
  188. package/runtime/python/okstra_ctl/worktree.py +3 -2
  189. package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
  190. package/runtime/python/okstra_ctl/write_policy.py +4 -2
  191. package/runtime/python/okstra_token_usage/antigravity.py +39 -12
  192. package/runtime/python/okstra_token_usage/collect.py +90 -38
  193. package/runtime/python/okstra_token_usage/grok.py +127 -0
  194. package/runtime/schemas/final-report-v2.0.schema.json +9 -0
  195. package/runtime/schemas/final-report-v3.0.schema.json +9 -0
  196. package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
  197. package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
  198. package/runtime/skills/okstra-code-review/SKILL.md +21 -11
  199. package/runtime/skills/okstra-container-build/SKILL.md +18 -18
  200. package/runtime/skills/okstra-inspect/SKILL.md +12 -11
  201. package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
  202. package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
  203. package/runtime/skills/okstra-inspect/facets/history.md +9 -14
  204. package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
  205. package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
  206. package/runtime/skills/okstra-inspect/facets/report.md +6 -10
  207. package/runtime/skills/okstra-inspect/facets/status.md +9 -8
  208. package/runtime/skills/okstra-inspect/facets/time.md +3 -3
  209. package/runtime/skills/okstra-manager/SKILL.md +16 -14
  210. package/runtime/skills/okstra-memory/SKILL.md +3 -3
  211. package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
  212. package/runtime/skills/okstra-rollup/SKILL.md +6 -16
  213. package/runtime/skills/okstra-run/SKILL.md +7 -7
  214. package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
  215. package/runtime/skills/okstra-setup/SKILL.md +21 -13
  216. package/runtime/skills/okstra-setup/references/project-config.md +2 -2
  217. package/runtime/skills/okstra-usage/SKILL.md +10 -10
  218. package/runtime/skills/okstra-user-response/SKILL.md +78 -107
  219. package/runtime/templates/report-writer-prompt-preamble.md +17 -1
  220. package/runtime/templates/reports/schedule.template.md +4 -4
  221. package/runtime/templates/worker-error-contract.md +17 -29
  222. package/runtime/validators/validate-run.py +379 -63
  223. package/runtime/validators/validate_session_conformance.py +43 -7
@@ -23,6 +23,7 @@ This script is the canonical single-reference-point. The Node CLI
23
23
  from __future__ import annotations
24
24
 
25
25
  import argparse
26
+ import hashlib
26
27
  import json
27
28
  import os
28
29
  import sys
@@ -57,18 +58,77 @@ from okstra_ctl.final_report_paths import ( # noqa: E402
57
58
  translation_sidecar_path,
58
59
  translation_source_path,
59
60
  )
61
+ from okstra_ctl.json_boundary import ( # noqa: E402
62
+ JsonBoundaryError,
63
+ load_owned_object,
64
+ load_owned_object_snapshot,
65
+ write_owned_object_atomic,
66
+ )
67
+ from okstra_ctl.fixed_text import line, scalar # noqa: E402
68
+ from okstra_ctl.convergence import ( # noqa: E402
69
+ ConvergenceContractError,
70
+ RunArtifactAuthority,
71
+ canonical_run_report_artifact,
72
+ validated_run_authority,
73
+ )
60
74
 
61
75
 
62
76
  def _load(path: Path) -> dict:
63
77
  try:
64
- payload = json.loads(path.read_text(encoding="utf-8"))
65
- except OSError as exc:
66
- raise SystemExit(f"error: cannot read {path}: {exc}") from exc
67
- except json.JSONDecodeError as exc:
68
- raise SystemExit(f"error: {path} is not valid JSON: {exc}") from exc
69
- if not isinstance(payload, dict):
70
- raise SystemExit(f"error: {path} must hold a JSON object")
71
- return payload
78
+ return load_owned_object(path, artifact="report translation artifact")
79
+ except JsonBoundaryError as exc:
80
+ raise SystemExit(f"error: {exc}") from exc
81
+
82
+
83
+ def _translation_authority(
84
+ manifest_path: Path,
85
+ ) -> tuple[RunArtifactAuthority, Path]:
86
+ try:
87
+ authority = validated_run_authority(manifest_path)
88
+ report = canonical_run_report_artifact(authority)
89
+ except (ConvergenceContractError, JsonBoundaryError) as exc:
90
+ raise SystemExit(f"error: {exc}") from exc
91
+ return authority, report
92
+
93
+
94
+ def _report_snapshot(path: Path):
95
+ try:
96
+ return load_owned_object_snapshot(
97
+ path, artifact="report translation source"
98
+ )
99
+ except JsonBoundaryError as exc:
100
+ raise SystemExit(f"error: {exc}") from exc
101
+
102
+
103
+ def _source_payload(
104
+ authority: RunArtifactAuthority, data_path: Path, raw_bytes: bytes, data: dict
105
+ ) -> dict:
106
+ return {
107
+ "taskKey": authority.task_key,
108
+ "runManifestPath": authority.manifest_ref,
109
+ "sourceData": data_path.name,
110
+ "sourceDataPath": data_path.relative_to(authority.project_root).as_posix(),
111
+ "sourceDataSha256": hashlib.sha256(raw_bytes).hexdigest(),
112
+ "lang": str((data.get("meta") or {}).get("reportLanguage") or ""),
113
+ "strings": extract(data),
114
+ }
115
+
116
+
117
+ def _validate_source_payload(
118
+ source: dict, expected: dict, supplied_digest: str
119
+ ) -> None:
120
+ for key in (
121
+ "taskKey", "runManifestPath", "sourceData", "sourceDataPath",
122
+ "sourceDataSha256", "lang", "strings",
123
+ ):
124
+ if source.get(key) != expected.get(key):
125
+ raise SystemExit(
126
+ "error: report changed after translation source publication"
127
+ )
128
+ if supplied_digest != expected["sourceDataSha256"]:
129
+ raise SystemExit(
130
+ "error: report changed after translation source publication"
131
+ )
72
132
 
73
133
 
74
134
  def _english_source_failure(data_path: Path, data: dict) -> str | None:
@@ -99,18 +159,10 @@ def cmd_extract(args: argparse.Namespace) -> int:
99
159
  strings = extract(data)
100
160
  out_path = translation_source_path(data_path)
101
161
  lang = str((data.get("meta") or {}).get("reportLanguage") or "")
102
- out_path.write_text(
103
- json.dumps(
104
- {
105
- "lang": lang,
106
- "sourceData": data_path.name,
107
- "strings": strings,
108
- },
109
- ensure_ascii=False,
110
- indent=2,
111
- )
112
- + "\n",
113
- encoding="utf-8",
162
+ write_owned_object_atomic(
163
+ out_path,
164
+ {"lang": lang, "sourceData": data_path.name, "strings": strings},
165
+ artifact="translation source",
114
166
  )
115
167
  print(
116
168
  json.dumps(
@@ -128,8 +180,130 @@ def cmd_extract(args: argparse.Namespace) -> int:
128
180
  return 0
129
181
 
130
182
 
183
+ def cmd_source(args: argparse.Namespace) -> int:
184
+ authority, data_path = _translation_authority(Path(args.run_manifest))
185
+ snapshot = _report_snapshot(data_path)
186
+ data = snapshot.value
187
+ failure = _english_source_failure(data_path, data)
188
+ if failure is not None:
189
+ sys.stderr.write(failure)
190
+ return 1
191
+ payload = _source_payload(authority, data_path, snapshot.raw_bytes, data)
192
+ out_path = translation_source_path(data_path)
193
+ if out_path.is_symlink():
194
+ raise SystemExit("error: translation source path is a symlink")
195
+ write_owned_object_atomic(
196
+ out_path, payload, artifact="translation source"
197
+ )
198
+ strings = payload["strings"]
199
+ print("# Translation work list")
200
+ print(line("Task key", authority.task_key), end="")
201
+ print(line("Report", payload["sourceDataPath"]), end="")
202
+ print(line("Run manifest", authority.manifest_ref), end="")
203
+ print(line("Source digest", payload["sourceDataSha256"]), end="")
204
+ print(line("String count", len(strings)), end="")
205
+ for index, source in enumerate(strings.values(), 1):
206
+ print(f"\n## T-{index:03d}\n{scalar(source)}")
207
+ return 0
208
+
209
+
210
+ def _translation_blocks(path: Path) -> list[str]:
211
+ text = path.read_text(encoding="utf-8")
212
+ blocks: list[str] = []
213
+ current: list[str] | None = None
214
+ for row in text.splitlines():
215
+ if row.startswith("## T-"):
216
+ if current is not None:
217
+ blocks.append("\n".join(current).strip())
218
+ expected = f"## T-{len(blocks) + 1:03d}"
219
+ if row != expected:
220
+ raise SystemExit(f"error: expected translation heading {expected}")
221
+ current = []
222
+ elif current is not None:
223
+ current.append(row)
224
+ if current is not None:
225
+ blocks.append("\n".join(current).strip())
226
+ return blocks
227
+
228
+
229
+ def cmd_write(args: argparse.Namespace) -> int:
230
+ authority, data_path = _translation_authority(Path(args.run_manifest))
231
+ snapshot = _report_snapshot(data_path)
232
+ data = snapshot.value
233
+ expected = _source_payload(
234
+ authority, data_path, snapshot.raw_bytes, data
235
+ )
236
+ source_path = translation_source_path(data_path)
237
+ if source_path.is_symlink():
238
+ raise SystemExit("error: translation source path is a symlink")
239
+ source = _load(source_path)
240
+ _validate_source_payload(source, expected, args.source_digest)
241
+ sources = expected["strings"]
242
+ translated = _translation_blocks(Path(args.translations))
243
+ if len(translated) != len(sources) or any(not value for value in translated):
244
+ raise SystemExit("error: translation blocks must match every T-NNN item")
245
+ lang = expected["lang"]
246
+ sidecar = translation_sidecar_path(data_path, lang)
247
+ if sidecar.is_symlink():
248
+ raise SystemExit("error: translation sidecar path is a symlink")
249
+ strings = dict(zip(sources, translated))
250
+ _, report = overlay(data, strings)
251
+ if report.unresolved or report.applied != len(strings):
252
+ raise SystemExit("error: translations do not validate against report source")
253
+ write_owned_object_atomic(
254
+ sidecar,
255
+ {
256
+ "taskKey": authority.task_key,
257
+ "runManifestPath": authority.manifest_ref,
258
+ "lang": lang,
259
+ "sourceData": data_path.name,
260
+ "sourceDataPath": expected["sourceDataPath"],
261
+ "sourceDataSha256": expected["sourceDataSha256"],
262
+ "strings": strings,
263
+ },
264
+ artifact="report translation sidecar",
265
+ )
266
+ print("Translation sidecar\n" + line("Status", "ready"), end="")
267
+ return 0
268
+
269
+
270
+ def cmd_check_data(args: argparse.Namespace) -> int:
271
+ authority, data_path = _translation_authority(Path(args.run_manifest))
272
+ snapshot = _report_snapshot(data_path)
273
+ expected = _source_payload(
274
+ authority, data_path, snapshot.raw_bytes, snapshot.value
275
+ )
276
+ sidecar_path = translation_sidecar_path(data_path, expected["lang"])
277
+ if sidecar_path.is_symlink():
278
+ raise SystemExit("error: translation sidecar path is a symlink")
279
+ sidecar = _load(sidecar_path)
280
+ _validate_authority_sidecar(sidecar, expected)
281
+ payload, code = _check_sidecar(sidecar_path)
282
+ print("Translation check\n" + line("Status", "ready" if payload["ok"] else "error")
283
+ + line("Applied strings", payload["applied"])
284
+ + line("Unresolved strings", len(payload["unresolved"])), end="")
285
+ return code
286
+
287
+
288
+ def _validate_authority_sidecar(sidecar: dict, expected: dict) -> None:
289
+ for key in (
290
+ "taskKey", "runManifestPath", "lang", "sourceData", "sourceDataPath",
291
+ "sourceDataSha256",
292
+ ):
293
+ if sidecar.get(key) != expected.get(key):
294
+ raise SystemExit(
295
+ "error: translation sidecar does not match run authority"
296
+ )
297
+
298
+
131
299
  def cmd_check(args: argparse.Namespace) -> int:
132
300
  sidecar_file = Path(args.sidecar).resolve()
301
+ payload, code = _check_sidecar(sidecar_file)
302
+ print(json.dumps(payload, ensure_ascii=False))
303
+ return code
304
+
305
+
306
+ def _check_sidecar(sidecar_file: Path) -> tuple[dict, int]:
133
307
  sidecar = _load(sidecar_file)
134
308
  strings = sidecar.get("strings")
135
309
  if not isinstance(strings, dict):
@@ -150,7 +324,6 @@ def cmd_check(args: argparse.Namespace) -> int:
150
324
  "untranslated": list(report.untranslated),
151
325
  "unresolved": list(report.unresolved),
152
326
  }
153
- print(json.dumps(payload, ensure_ascii=False))
154
327
  if report.unresolved:
155
328
  # A pointer that resolves nowhere means the sidecar was written against
156
329
  # a different report. Rendering it would silently drop those strings.
@@ -158,8 +331,8 @@ def cmd_check(args: argparse.Namespace) -> int:
158
331
  f"error: {len(report.unresolved)} pointer(s) do not resolve in "
159
332
  f"{data_path.name}\n"
160
333
  )
161
- return 1
162
- return 0
334
+ return payload, 1
335
+ return payload, 0
163
336
 
164
337
 
165
338
  def cmd_check_source(args: argparse.Namespace) -> int:
@@ -191,6 +364,20 @@ def _parser() -> argparse.ArgumentParser:
191
364
  extract_cmd.add_argument("data", help="path to final-report-<type>-<seq>.data.json")
192
365
  extract_cmd.set_defaults(func=cmd_extract)
193
366
 
367
+ source_view = sub.add_parser("source", help="render a fixed translation work list")
368
+ source_view.add_argument("--run-manifest", required=True)
369
+ source_view.set_defaults(func=cmd_source)
370
+
371
+ write_cmd = sub.add_parser("write", help="publish translations from T-NNN blocks")
372
+ write_cmd.add_argument("--run-manifest", required=True)
373
+ write_cmd.add_argument("--source-digest", required=True)
374
+ write_cmd.add_argument("--translations", required=True)
375
+ write_cmd.set_defaults(func=cmd_write)
376
+
377
+ check_data = sub.add_parser("check-data", help="verify the derived sidecar")
378
+ check_data.add_argument("--run-manifest", required=True)
379
+ check_data.set_defaults(func=cmd_check_data)
380
+
194
381
  check_cmd = sub.add_parser("check", help="verify a filled sidecar against its report")
195
382
  check_cmd.add_argument("sidecar", help="path to final-report-<type>-<seq>.i18n.<lang>.json")
196
383
  check_cmd.set_defaults(func=cmd_check)
@@ -41,7 +41,7 @@ If `render-bundle` stdout carries an `okstra concurrent-run stages: <stages>` la
41
41
 
42
42
  If `render-bundle` fails with a `PrepareError` containing `Recorded stage SHAs no longer match the git history`, the git history changed outside okstra (rebase / squash / review-feedback amend / branch deletion). Never fix the registry/consumers by hand; recover in this order:
43
43
 
44
- 1. Run the `okstra git-reconcile … --check --json` command printed in the error message verbatim to get the stale report. (Items whose content-identity is proven by patch-id were already auto-reconciled by prepare, so only confirm items remain here.)
44
+ 1. Run the `okstra git-reconcile … --check --text` command printed in the error message verbatim to get the stale report. (Items whose content-identity is proven by patch-id were already auto-reconciled by prepare, so only confirm items remain here.)
45
45
  2. For each confirm item, present a 3-option picker to the user:
46
46
  - **Re-record to the `stage-<N>` branch's current tip (recommended)** — when an intended change such as review feedback lives on that branch.
47
47
  - **Enter a different ref directly** — the user names a commit/branch/tag.
@@ -49,6 +49,7 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
49
49
  ## Run Paths
50
50
 
51
51
  - Team state: `{{TEAM_STATE_RELATIVE_PATH}}`
52
+ - Plan-body verification state: `{{PLAN_BODY_VERIFICATION_STATE_RELATIVE_PATH}}`
52
53
  - Final report: `{{FINAL_REPORT_RECORD_RELATIVE_PATH}}`
53
54
  - Final status: `{{FINAL_STATUS_RELATIVE_PATH}}`
54
55
  - Validator: `{{RUN_VALIDATOR_RELATIVE_PATH}}`
@@ -57,12 +58,7 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
57
58
 
58
59
  - Run-level errors log (absolute): `{{RUN_ERRORS_LOG_PATH}}`
59
60
  - Run-level errors log (relative): `{{RUN_ERRORS_LOG_RELATIVE_PATH}}`
60
- - Worker error sidecars (absolute):
61
- - Claude worker: `{{CLAUDE_WORKER_ERRORS_SIDECAR_PATH}}`
62
- - Codex worker: `{{CODEX_WORKER_ERRORS_SIDECAR_PATH}}`
63
- - Antigravity worker: `{{ANTIGRAVITY_WORKER_ERRORS_SIDECAR_PATH}}`
64
- - Report writer worker: `{{REPORT_WRITER_WORKER_ERRORS_SIDECAR_PATH}}`
65
- - The paths above are the values; the wiring duties — injecting the `**Errors log path:**` / `**Errors sidecar path:**` header pair into every dispatch prompt, and dumping each terminated worker's sidecar via `okstra error-log append-from-worker` — are defined once in the lifecycle core contract (`{{OKSTRA_LEAD_CONTRACT_PATH}}` "Errors log path wiring (BLOCKING)"). Follow that section; do not re-derive the rules from this block.
61
+ - The lead injects `**Errors log path:**` into every dispatch prompt. Workers use the typed `okstra error-log append-observed` command in the shared worker error contract; runtime-owned sidecar compatibility is not a worker instruction.
66
62
 
67
63
  ## Executor Worktree
68
64
 
@@ -114,7 +110,7 @@ The **default is full re-verification**. Only narrow this re-run to the impacted
114
110
  - Pass ONLY stage numbers that appear in that Stage Map when you do use `--impacted`. Never invent or guess a stage number — a number absent from the graph must never enter `--impacted` (it would leak into `reverify_stages`).
115
111
  2. **Resolve the two base SHAs (safety condition C1 — code-unchanged, decided by the CLI, not by you).**
116
112
  - Current base SHA: `{{EXECUTOR_WORKTREE_BASE_REF}}` (this run's resolved worktree base commit).
117
- - Prior base SHA: read the prior run's active-run-context at `runs/implementation-planning/state/active-run-context-implementation-planning-<prev-seq>.json`, field `executorWorktree.baseRef` (a resolved commit SHA). The run-manifest does NOT carry this field use the active-run-context. If that file or field cannot be located, treat the run as **full** and skip the rest of this procedure.
113
+ - Prior base SHA: run `okstra model-io active-context-input --project-root <project-root> --run-manifest <run-manifest>` and use its fixed `Executor base ref` field. Python resolves the prior active context only when it belongs to this project and run. If the command fails or the field is empty, treat the run as **full** and skip the rest of this procedure.
118
114
  3. **Call the CLI** (it is pure — same inputs always yield the same decision):
119
115
  ```
120
116
  okstra incremental-scope \
@@ -2,154 +2,54 @@
2
2
 
3
3
  ## When to Use
4
4
 
5
- - When starting okstra lead Phase 1 (Task-bundle intake)
6
- - When the user needs to know the okstra task bundle path
7
- - When you need to derive all artifact paths based on `task-manifest.json`
8
-
9
- ## Step 1: Resolve the Task Bundle Path
10
-
11
- (Resolve which task-root path to use; Step 2 opens `task-manifest.json` at that path.)
12
-
13
- ### Default Location Rules
14
-
15
- - AI documentation root: `.project-docs/`
16
- - Project-level latest-task pointer: `.okstra/discovery/latest-task.json`
17
- - Project-level task catalog: `.okstra/discovery/task-catalog.json`
18
- - okstra task root: `.okstra/tasks/`
19
- - Task path pattern: `.okstra/tasks/<task-group>/<task-id>/`
20
-
21
- ### Task Identification
22
- 1. If the user specifies the `task-manifest.json` path or the task root path, that path is used.
23
- 2. If the user specifies only the task key, the expected task root is calculated by converting the `task-group` and `task-id` to lowercase and applying the slug rule (`[^a-z0-9]+` → `-`), and the corresponding `task-manifest.json` is opened.
24
- 3. If the user attempts to find a task based on `task-group` + `task-id` or `task-id`, `.okstra/discovery/task-catalog.json` is read to find candidates.
25
- 4. If multiple candidates are found based on `task-id` alone, the situation is ambiguous, so `task-group` or the full `taskKey` is required.
26
- 5. If the user has not provided an explicit task key/path, first read `.okstra/discovery/latest-task.json` using the current-task convenience pointer.
27
- 6. If the latest-task pointer is missing or corrupted but the task catalog exists, list candidates from the catalog. Do not use the legacy `CLAUDE.md`, project guide, or task scan fallback.
28
- 7. If **neither** `latest-task.json` **nor** `task-catalog.json` exists, ABORT Phase 1 with `OKSTRA_CONTEXT_NOT_INITIALIZED`. Suggest the user run `/okstra-setup` and `/okstra-brief-gen` to bootstrap the project. Do NOT crawl `.okstra/tasks/` directly — discovery pointers are the only supported entry path.
29
-
30
- ## Step 2: Open and Parse task-manifest.json
31
-
32
- `task-manifest.json` (found at the task-root resolved in Step 1) is the canonical metadata source. Extract the following fields:
33
-
34
- | Field | Description |
35
- |------|------|
36
- | `taskKey` | `<project-id>:<task-group>:<task-id>` |
37
- | `projectId` | Project ID |
38
- | `taskGroup` | Task group |
39
- | `taskId` | Task ID |
40
- | `taskType` | Analysis type (requirements-discovery, error-analysis, implementation-option-selection, implementation-planning, implementation, final-verification, release-handoff, plus the sidetrack improvement-discovery) |
41
- | `workCategory` | bugfix / feature / improvement / refactor / ops / unknown |
42
- | `recommendedWorkers` | List of selected workers |
43
- | `currentStatus` | Current task status |
44
- | `workflow.phaseSequence` | Ordered lifecycle phases for the task |
45
- | `workflow.currentPhase` | Current lifecycle phase |
46
- | `workflow.currentPhaseState` | Current lifecycle phase state |
47
- | `workflow.phaseStates` | Phase-by-phase lifecycle state map |
48
- | `workflow.lastCompletedPhase` | Last completed lifecycle phase |
49
- | `workflow.nextRecommendedPhase` | Next-Phase Pointer — an object carrying the target phase, whether it can be started, and why. A projection of this report's Phase Routing; not authored here. Written per the Artifact Persistence Checklist in [report-writer](./report-writer.md); do not re-derive its rules here. |
50
- | `workflow.awaitingApproval` | Approval wait marker |
51
- | `workflow.lastSafeCheckpoint` | Safe resume checkpoint metadata |
52
- | `instructionSetPath` | Path to the `instruction-set/` **directory** containing `analysis-packet.md`, `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, `final-report-template.md`, and — only for task types whose host orchestration carries gates — `host-orchestration-rules.md` (see Step 4). Not a single-file path. |
53
- | `referenceExpectationsPath` | config/deployment expectation artifact path |
54
- | `latestRunPath` | latest run path |
55
- | `latestRunStatus` | latest run status |
56
- | `latestRunPromptsPath` | latest run prompt directory path |
57
- | `latestReportRecordPath` | latest report path |
58
- | `latestResumeCommandPath` | resume helper path |
59
- | `historyTimelinePath` | timeline path |
60
- | `resultContract` | team contract and expected artifact metadata |
61
- | `resultContract.requiredWorkerRoles[*].promptPath` | worker prompt history path by role |
62
- | `convergence` | convergence loop settings (`enabled`, `maxRounds`, `verificationMode`). See [convergence](./convergence.md) for the authoritative defaults — do not re-document the `maxRounds` value here. |
63
-
64
- ## Step 3: Directory Structure Rules
65
-
66
- After identifying the task root in `task-manifest.json`, derive all paths according to the following rules:
67
-
68
- ```
69
- <task-root>/
70
- ├── task-manifest.json (canonical metadata)
71
- ├── task-index.md (human-readable summary, non-canonical)
72
- ├── instruction-set/
73
- │ ├── analysis-profile.md (analysis guide by task type)
74
- │ ├── analysis-packet.md (primary compact input for analysis workers)
75
- │ ├── analysis-material.md (analysis materials)
76
- │ ├── reference-expectations.md (config/deployment expected values)
77
- │ ├── task-brief.md (task brief)
78
- │ ├── final-report-template.md (Final Report Template)
79
- │ └── host-orchestration-rules.md (host gate rules; present only for task types whose host orchestration carries gates)
80
- ├── runs/
81
- │ └── <task-type>/ (Run scope isolated per task-type, e.g. error-analysis)
82
- │ ├── manifests/ (run-manifest-<task-type>-<seq>.json)
83
- │ ├── state/ (team-state-<task-type>-<seq>.json, convergence-<task-type>-<seq>.json)
84
- │ ├── prompts/ (run prompt path and worker prompt history paths recorded in the manifest)
85
- │ ├── reports/ (final-report-<task-type>-<seq>.data.json + .html;
86
- │ the .md reading copy is rendered on demand)
87
- │ ├── status/ (final-<task-type>-<seq>.status)
88
- │ ├── sessions/ (runtime-specific resume artifacts when the selected adapter supports them)
89
- │ ├── logs/ (errors-<task-type>-<seq>.jsonl, optional)
90
- │ └── worker-results/
91
- │ ├── claude-worker-<task-type>-<seq>.md
92
- │ ├── codex-worker-<task-type>-<seq>.md
93
- │ ├── antigravity-worker-<task-type>-<seq>.md
94
- │ └── report-writer-worker-<task-type>-<seq>.md
95
- └── history/
96
- └── timeline.json
97
- ```
98
-
99
- ### File Naming Conventions
100
-
101
- - Run-level artifact files use the `-<task-type>-<seq>` suffix, where `<seq>` is a 3-digit zero-padded sequence number (`001`, `002`, …) scanned **per-category directory** (`manifests/`, `prompts/`, `reports/`, `status/`, `state/`, `sessions/`, `worker-results/`). Re-runs of the same task-type never overwrite each other's artifacts.
102
- - Because each category directory has its own counter, the `<seq>` for `manifests/` may differ from the `<seq>` for `reports/` if a previous run only wrote some categories. The shared run identifier across categories is the manifest `runDateTimeSegment` field (still ISO timestamp), not a filename infix.
103
- - Worker result files use the same `-<task-type>-<seq>` filename suffix as other run-level artifacts (counter scoped to `worker-results/`).
104
- - Worker prompt history files use role + suffix names such as `claude-worker-prompt-<task-type>-<seq>.md`.
105
- - A single `runs/<task-type>/` directory is reused for every run of that task type; the per-run separation comes entirely from the per-category sequence-number infix inside each typed subdirectory.
106
-
107
- ### Finding the Latest Run
108
-
109
- 1. If a `latest-task` pointer exists, prioritize the path to the latest run, latest run manifest, or latest team-state recorded there.
110
- 2. If entering via an explicit task key/path, locate the current run based on the latest run metadata in `task-manifest.json`.
111
- 3. Only if necessary, directly check `manifests/`, `state/`, `prompts/`, `reports/`, and `sessions/` within the relevant run directory.
112
-
113
- ## Step 4: Instruction Set Reading Order
114
-
115
- After verifying `task-manifest.json`, read only the compact intake files needed for the current action. Do not bulk-read the whole instruction-set directory.
116
-
117
- 1. `instruction-set/analysis-profile.md` (analysis guide by task type)
118
- 2. `instruction-set/analysis-packet.md` (primary compact input for analysis workers)
119
- 3. `runs/<task-type>/state/active-run-context-<task-type>-<seq>.json` if present (compact current-run path/worker snapshot)
120
- 4. `instruction-set/host-orchestration-rules.md` when the launch prompt's Host Orchestration Rules path is set. Read it here even though no gate has fired yet — the gates it governs fire around `render-bundle`, so waiting for one means reading it too late, and this is the copy that survives compaction. **Enforced:** Phase 7 conformance fails an `implementation` run whose evidence source records no read of it.
121
-
122
- Read source files lazily:
123
-
124
- - `instruction-set/task-brief.md` only for reporter-confirmation checks, source verification, or report-writer synthesis.
125
- - `instruction-set/analysis-material.md` only when packet content is insufficient or a source citation needs verification.
126
- - `instruction-set/reference-expectations.md` for report-writer synthesis or when packet expectation extract is insufficient.
127
- - `instruction-set/final-report-template.md` only for report-writer authoring.
128
- - `instruction-set/host-orchestration-rules.md` — already read at Step 4; re-read it whenever you are about to act on a host gate — the conformance waiver offer, the concurrent-run branch, stale-SHA recovery, the chaining queue — and again after any compaction. Absent file means this task type carries no host gates.
5
+ - When starting okstra lead Phase 1 task-bundle intake.
6
+ - When the user needs the task bundle or current run paths.
7
+
8
+ ## JSON Boundary
9
+
10
+ Do not open, parse, or infer Okstra-owned task, run, discovery, or active-context JSON. This contract uses the fixed text views below before the lead contract is loaded, so it cannot bypass that boundary.
11
+
12
+ - `okstra model-io project-context --project-root <project-root> --task-ref <task-ref>` provides project identity and the pointer for one explicit task reference. `task-ref` accepts a bare task ID, full task key, or that task's manifest path.
13
+ - `okstra model-io run-input --run-manifest <run-manifest-path>` provides the current run identity, worker roster, model assignments, and permitted artifact paths.
14
+
15
+ ## Step 1: Resolve the Task and Run Paths
16
+
17
+ 1. Determine `<project-root>` from the user request or the current project.
18
+ 2. If the user supplies a task ID, full task key, or task-manifest path, run `okstra model-io project-context --project-root <project-root> --task-ref <task-ref>`. Otherwise run `okstra model-io project-context --project-root <project-root>`.
19
+ 3. If the supplied bare task ID is ambiguous, require the full task key or task-manifest path.
20
+ 4. Use the `Task key`, `Task manifest`, and `Latest run manifest` labels from the same Project Context result.
21
+ 5. Use `Latest run manifest` as `<run-manifest-path>`. If it is `-`, request an explicit run-manifest path from the launch context or stop with `OKSTRA_CONTEXT_NOT_INITIALIZED`. Do not scan task directories or reconstruct a run path.
22
+ 6. Run `okstra model-io run-input --run-manifest <run-manifest-path>`.
23
+
24
+ ## Step 2: Read the Compact Intake Files
25
+
26
+ Use the `Instruction set` and `Reference expectations` labels in Run Input as paths for Markdown resources. Read only the resources needed for the current action.
27
+
28
+ 1. `<instruction-set>/analysis-profile.md` for the task-type rules and required worker block.
29
+ 2. `<instruction-set>/analysis-packet.md` for the Phase 1 compact input.
30
+ 3. `<instruction-set>/host-orchestration-rules.md` when the launch prompt supplies that path.
31
+
32
+ Read `task-brief.md`, `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` only at their phase-specific need. The report writer owns the final-report template.
129
33
 
130
34
  ### Brief Reporter-Confirmation Precondition (BLOCKING)
131
35
 
132
- After reading `task-brief.md`, extract the frontmatter `reporter-confirmations` field (`complete | partial | pending | skipped`) when present and apply the shared handling matrix in `prompts/profiles/_common-contract.md` "Brief handoff contract" → "Reporter confirmation precondition" that block is the single authority on per-value semantics; do not re-derive them here.
36
+ After reading `task-brief.md`, extract the frontmatter `reporter-confirmations` field when present and apply the shared handling matrix in `prompts/profiles/_common-contract.md` "Brief handoff contract" → "Reporter confirmation precondition". That block is the authority for the value semantics.
133
37
 
134
- Loader-level defensive flow control only: on `pending`, emit `REPORTER_CONFIRMATION_PENDING` and STOP do not invoke `team-contract` or any analyser, and do not write a final report. Regenerate the brief with `okstra-brief-gen` Step 6.5 and prepare a fresh run. A missing field is a legacy brief, not `pending`, and proceeds to Step 5. Current-format missing or invalid values are rejected during preparation, before a fresh run can reach this loader. Every other value proceeds to Step 5 with the matrix's flags carried forward for the phase profile.
38
+ On `pending`, emit `REPORTER_CONFIRMATION_PENDING` and stop. Do not invoke `team-contract` or an analyser, and do not write a final report. Regenerate the brief with `okstra-brief-gen` Step 6.5 and prepare a fresh run. A missing field is a legacy brief and proceeds with the matrix's carried flags. Current-format invalid values are rejected during preparation.
135
39
 
136
- ## Step 5: Read Run Manifest and Team State
40
+ ## Step 3: Use Run Input as the Run-State View
137
41
 
138
- 1. Identify the active run by reading `runDateTimeSegment` from the latest `runs/<task-type>/manifests/run-manifest-*.json` (mtime order). That segment is the shared run identifier across all category subdirectories (`state/`, `prompts/`, `reports/`, `status/`, `sessions/`, `worker-results/`).
139
- 2. Resolve sibling artifacts for this run by matching the same `runDateTimeSegment`. Do NOT re-scan `<seq>` counters per category — they may diverge if an earlier run only wrote some categories.
140
- 3. Current team state: the team-state file whose `runDateTimeSegment` matches the active run manifest.
141
- 4. Extract the worker prompt directory path and per-worker prompt history paths from the current run manifest and team-state.
142
- 5. If an existing run report is available, use it solely as historical context.
42
+ Run Input is the only Phase 1 source for task identity, work category, workflow state, selected workers, model assignments, worker prompt paths, result paths, validator path, resume command, and configuration references.
43
+
44
+ Use the `Worker Roster` section for dispatch and model assignment. Use `Artifact Paths` for prompt, result, report, status, validator, and resume locations. Use `Configuration References` for the task-level Markdown resources. Do not derive sibling paths from file names or sequence counters.
45
+
46
+ Use the `Current phase` labels as the workflow view. Rules for `nextRecommendedPhase` are maintained by [report-writer](./report-writer.md); do not re-derive phase routing here.
143
47
 
144
48
  ## Output
145
49
 
146
50
  Information produced after completing this contract:
147
- - task key, task type, work category, workflow lifecycle snapshot, analysis profile
148
- - List of selected workers and model assignments by role
149
- - Absolute/relative paths of all artifacts (including latest-task pointer, task catalog, prompt directory, per-worker prompt history files, resume command, and timeline)
150
- - Reference list of config files/deployment manifests and task-level expected values
151
- - Current run status and presence of existing worker results
152
- - Current run prompt history contract for attempted workers
153
- - Candidate `teamName` for the manifest/team-state audit identity: `okstra-<task-key>` (with task-key slugified per Step 1's slug rule); implementation stage runs append `-s<N>`. The selected runtime adapter owns whether an adapter-specific audit label is exposed in the launch prompt's "Runtime Adapter Dispatch Gate (BLOCKING)" setup facts; this common contract does not guarantee that label for every runtime.
154
- - Current lead session/checkpoint identity, when supplied by the selected adapter
155
- - Resume artifact path from `task-manifest.json.artifacts.resumeCommandPath`; never reconstruct a runtime-specific filename
51
+
52
+ - Project ID, task key, task type, work category, and workflow snapshot.
53
+ - Selected workers and assigned models.
54
+ - Prompt, result, report, status, validator, resume, and configuration-reference paths.
55
+ - The compact Markdown intake paths required for Phase 1.
@@ -84,7 +84,9 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
84
84
  - Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
85
85
  - Only one worker confirms a finding → one single-source group.
86
86
  4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging). Semantic matching, ticket-set equality, and evidence interpretation remain lead judgments; the engine does not perform fuzzy matching or decide whether evidence is credible.
87
- 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and the version-selected convergence-groups schema enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. In a v2 run, set top-level `schemaVersion: "2.0"`, `executionIdentityVersion: 2`, and `runManifestPath` to the current run manifest's exact canonical project-relative path. Write the groups artifact under that same resolved run directory's `state/` directory. Every v2 worker row also carries the paired `participantRef` and `sourceRoleExecutionRef` from that run manifest's canonical role state. Set `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field; a static source row has null in the latter field. Copy `participantRef` from that same selected row. Never derive those references from the worker name, provider, model, or execution label. A legacy v1 document keeps `schemaVersion: "1.0"` and omits `executionIdentityVersion`, `runManifestPath`, and both worker reference fields. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` and selects an `analyser`, `designer`, `planner`, or `verifier` source role. Only the report author uses `report-writer`, paired with a `report-writer` source role. There is no `implementation-verifier` audience here; map an implementation verifier to `analysis`. **Your own review findings use `audience: "lead"`.** The phases that ask you to review the deliverable yourself produce findings that belong in this state — it is what the report author reads — and declaring yourself an analysis worker to get them in is forbidden. A `lead` row is a source, never a vote: `originWorker`, `discoveredBy` and `sourceItems` accept it, and the consensus count ignores it, so a finding only you saw stays queued for verification instead of resolving itself.
87
+ 5. Author the fixed grouping Markdown accepted by `okstra convergence prepare-groups --run-manifest <run-manifest> --input <grouping.md>`, then run that command. Python owns the artifact identifier, target path, schema version, task identity, run-manifest reference, and every participant reference. Each Markdown group records ticket IDs, origin worker and evidence, discovering workers, source worker item IDs, and optional captured evidence. An analysis sidetrack with no ticket uses an empty `Tickets:` value, never a placeholder. Use the ordered functional roster: finding workers have the `analysis` audience, the report author has `report-writer`, and the lead uses `lead`. A lead source never votes. Never infer live evidence or functional scope from wording, provider, model, or execution label.
88
+
89
+ The command sets each worker's paired `participantRef` and `sourceRoleExecutionRef` from the run manifest's canonical role state. It sets `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field.
88
90
  6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
89
91
  - Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
90
92
  - Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
@@ -254,7 +256,7 @@ row names the invocation, replacement is rejected even for v1 and the prompt is
254
256
  history. Do not delete prompt, metadata, or reservation files by hand.
255
257
 
256
258
  Run `okstra agent-prompt verify --run-manifest <path> --metadata
257
- <metadataPath> --json` immediately before dispatch. A failed verification is a
259
+ <metadataPath> --text` immediately before dispatch. A failed verification is a
258
260
  pre-dispatch contract failure. For `runner=native-session`, pass only the
259
261
  returned `hostModelValue` to the host model argument. For
260
262
  `runner=cli-wrapper`, follow the planned execution surface after
@@ -298,9 +300,9 @@ Assigned worker prompt history path: <Project Root>/<Prompt History Path>
298
300
 
299
301
  Before dispatch, materialize `**Audit sidecar path:**` by passing the exact reverify `**Result Path:**` through `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()` and resolving that project-relative result against `**Project Root:**`. Write the resulting absolute path into the header. The lead MUST NOT construct the audit filename from a role, task type, round, or sequence independently.
300
302
 
301
- The two errors paths carry the same absolute values the lead forwarded in the initial Phase 4 dispatch for that role (source: the launch prompt's `## Run Logs (error-log wiring)` section). Omitting either one makes `worker-dispatch` reject the CLI invocation before it starts the provider process — the path-delivery contract in [team-contract](./team-contract.md) "Error reporting" is not relaxed for reverify.
303
+ The two error-path anchors carry the same absolute values the lead forwarded in the initial Phase 4 dispatch for that role (source: the launch prompt's `## Run Logs (error-log wiring)` section). Workers use the errors log path with the typed error-log command from [team-contract](./team-contract.md) "Error reporting". The errors sidecar path only reserves the runtime-owned write-artifact path used by dispatch validation; no model-authored error JSON file is part of reverify.
302
304
 
303
- Relative to the Phase 4 anchor set rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()`, a reverify prompt drops two anchors whose targets lightweight mode never reads: `**Worker Preamble Path:**` and `**Coding preflight pack:**`.
305
+ Relative to the Phase 4 anchor set rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()`, a reverify prompt drops two anchors whose targets lightweight mode never reads: `**Worker Preamble Path:**` and `**Worker Error Contract Path:**`.
304
306
 
305
307
  **Where the composer's sections go.** `okstra agent-prompt materialize` (§"Invocation materialization gate") writes the dispatched body itself, as: these anchors, then the model-assignment block it appends (`**Provider:**`, `**Model:**`, `**Model execution value:**`, `**Runner:**`, `**Host runtime:**`, and `**Host model value:**` for a native host), then `## Duty Contract`, then `## Task Instructions` followed verbatim by the task-instructions file the lead wrote. So the lead authors only the last part, and every rule below about ordering — the phase boundary before the instruction headings, the `**Model:** <role>, <modelExecutionValue>` line — is about the lead's own file, not about the composed document. The composer's `**Model:** <modelExecutionValue>` anchor is a different line with a different shape; do not try to reshape it, and do not count it among the 8.
306
308
 
@@ -687,7 +689,7 @@ say so explicitly for that half; silence on one half is an incomplete result.
687
689
  ```
688
690
 
689
691
  ### Gap verification (1 adversarial reverify round)
690
- Each critic gap enters the verification queue as a finding with `originWorker = "<provider>-critic"` and `source = "critic"`. The lead runs ONE adversarial reverify round (§"Adversarial Verification Mode" classifier) with the Phase 4 analysers as voters, **excluding every analyser whose provider is the critic's provider** the exclusion matches on the provider name (`codex`, `codex-worker`), not on the critic's worker id, so picking a critic provider that is already in the analyser roster removes that analyser from the vote and shrinks the quorum by one. `okstra apply-critic-gaps` refuses a vote from an excluded worker (`critic voter must be a non-critic analyser`), so dispatching one spends a worker whose verdict cannot be counted. Only gaps classified `full-consensus` / `partial-consensus` merge into the final report findings; `contested` / `worker-unique` gaps are treated as hallucinations and dropped (recorded in the convergence state, not promoted).
692
+ Each critic gap enters the verification queue as a finding with `originWorker = "<provider>-critic"` and `source = "critic"`. The lead runs ONE adversarial reverify round (§"Adversarial Verification Mode" classifier) with **every Phase 4 analyser as a voter**. Choosing a critic provider that is already in the analyser roster costs nothing: the critic is a different role contract, a different duty and a different session, so an analyser is not disqualified by sharing its provider name (ADR-0017 — provider and model are not role identity, and the same model assigned to two roles gets two independent workers). The critic cannot judge its own gaps because it is not an analyser: the voter roster is `workers[]` filtered to `audience == "analysis"`, and a critic is not even representable there (the allowed values are `analysis` / `lead` / `report-writer`). `okstra apply-critic-gaps` refuses a vote from anyone outside that roster (`critic voter must be a non-critic analyser`). Only gaps classified `full-consensus` / `partial-consensus` merge into the final report findings; `contested` / `worker-unique` gaps are treated as hallucinations and dropped (recorded in the convergence state, not promoted).
691
693
 
692
694
  **A gap that received no verdict is NOT a rejected gap (BLOCKING).** Dropping applies only to gaps the voters actually judged. A gap can also end the round *unjudged* — the verification dispatch returned a terminal non-result (`timeout`, `error`, no result file), the returned result covered only some of the gaps, or no non-critic analyser was available to vote at all. Nobody inspected those, so classifying them as hallucinations is a fabricated verdict. Each one MUST be recorded as a `## 5. Missing Information and Risks` row (`missingInformation`, `source: "critic-unverified"`) whose `risk` names the gap and the reason verification did not complete, and counted in `config.critic.gapsUnverified`. They are **not** promoted to findings (unverified) and **not** raised as `clarification` items — an unverified gap needs an analyser to verify it on the next run, not a decision from the user. Silently losing them is a contract violation: the batch that times out is exactly the batch of gaps too expensive to check, so the highest-risk items are the ones that vanish.
693
695
 
@@ -738,7 +740,7 @@ so explicitly.
738
740
 
739
741
  ### Verification — confirm-or-downgrade (BLOCKING)
740
742
 
741
- Each candidate blocker is verified by the Phase 4 analysers, excluding every analyser whose provider is the critic's provider (same rule as §"critic gaps" above). Do NOT use the adversarial finding classifier's "uncertain → reject" rule here.
743
+ Each candidate blocker is verified by the Phase 4 analysers all of them, on the same roster rule as §"critic gaps" above; sharing the critic's provider name does not disqualify an analyser. Do NOT use the adversarial finding classifier's "uncertain → reject" rule here.
742
744
  - Do NOT run `apply-critic-gaps` for this mode. That reducer implements coverage merge/drop semantics and rejects `acceptance-devils-advocate` input.
743
745
  - **Confirmed** (an analyser reproduces it or cites supporting evidence) → promote to a `## 5.8 Acceptance Blockers` row (keep severity + recommended follow-up phase).
744
746
  - **Not confirmed** (cannot reproduce, or evidence is weak) → **downgrade to a Residual Risk row — never drop it.** Record the escalation trigger so the user can re-judge a high-severity-but-unconfirmed candidate.