okstra 0.200.0 → 0.201.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.
Files changed (96) hide show
  1. package/README.md +4 -2
  2. package/dist/cli-registry.mjs +6 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/docs/cli.md +14 -3
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/agents/workers/report-writer-worker.md +7 -3
  8. package/runtime/bin/okstra-spawn-followups.py +2 -2
  9. package/runtime/prompts/duties/technical-verification-worker.md +44 -0
  10. package/runtime/prompts/launch.template.md +7 -1
  11. package/runtime/prompts/lead/okstra-lead-contract.md +7 -2
  12. package/runtime/prompts/lead/plan-body-verification.md +3 -1
  13. package/runtime/prompts/lead/report-writer.md +11 -5
  14. package/runtime/prompts/lead/team-contract.md +6 -0
  15. package/runtime/prompts/profiles/_implementation-verifier.md +7 -1
  16. package/runtime/prompts/profiles/final-verification.md +5 -0
  17. package/runtime/prompts/profiles/forbidden-actions.json +6 -0
  18. package/runtime/prompts/profiles/implementation-option-selection.md +7 -1
  19. package/runtime/prompts/profiles/implementation-planning.md +1 -0
  20. package/runtime/prompts/profiles/technical-verification.md +53 -0
  21. package/runtime/prompts/wizard/prompts.ko.json +2 -1
  22. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -4
  23. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +2 -0
  24. package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +36 -5
  25. package/runtime/python/okstra_ctl/agent/invocation.py +14 -6
  26. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +4 -3
  27. package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +83 -22
  28. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +44 -2
  29. package/runtime/python/okstra_ctl/conformance.py +2 -20
  30. package/runtime/python/okstra_ctl/dispatch_core.py +25 -5
  31. package/runtime/python/okstra_ctl/dispatch_state.py +2 -0
  32. package/runtime/python/okstra_ctl/domain/provider.py +0 -1
  33. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  34. package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -1
  35. package/runtime/python/okstra_ctl/implementation_direction.py +64 -7
  36. package/runtime/python/okstra_ctl/implementation_options.py +58 -45
  37. package/runtime/python/okstra_ctl/model_pool.py +2 -5
  38. package/runtime/python/okstra_ctl/next_phase.py +3 -0
  39. package/runtime/python/okstra_ctl/plan_items.py +15 -0
  40. package/runtime/python/okstra_ctl/plan_items_cli.py +9 -3
  41. package/runtime/python/okstra_ctl/qa_commands.py +30 -0
  42. package/runtime/python/okstra_ctl/registry/provider_registry.py +11 -8
  43. package/runtime/python/okstra_ctl/render.py +3 -0
  44. package/runtime/python/okstra_ctl/render_final_report.py +1 -0
  45. package/runtime/python/okstra_ctl/report_assembly.py +8 -2
  46. package/runtime/python/okstra_ctl/report_contract.py +3 -0
  47. package/runtime/python/okstra_ctl/report_corrections.py +209 -93
  48. package/runtime/python/okstra_ctl/report_finalize.py +25 -8
  49. package/runtime/python/okstra_ctl/report_html/router.py +2 -0
  50. package/runtime/python/okstra_ctl/report_html/view_models/technical_verification.py +21 -0
  51. package/runtime/python/okstra_ctl/report_projections.py +4 -3
  52. package/runtime/python/okstra_ctl/report_synthesis_packet.py +181 -47
  53. package/runtime/python/okstra_ctl/run.py +82 -0
  54. package/runtime/python/okstra_ctl/team.py +4 -1
  55. package/runtime/python/okstra_ctl/technical_verification.py +195 -0
  56. package/runtime/python/okstra_ctl/usage_identity.py +54 -0
  57. package/runtime/python/okstra_ctl/usage_report.py +22 -8
  58. package/runtime/python/okstra_ctl/verification_target.py +74 -0
  59. package/runtime/python/okstra_ctl/wizard/__init__.py +1 -1
  60. package/runtime/python/okstra_ctl/wizard/cli.py +2 -1
  61. package/runtime/python/okstra_ctl/wizard/confirmation.py +38 -2
  62. package/runtime/python/okstra_ctl/wizard/engine.py +3 -0
  63. package/runtime/python/okstra_ctl/wizard/ids.py +1 -0
  64. package/runtime/python/okstra_ctl/wizard/outcome.py +63 -0
  65. package/runtime/python/okstra_ctl/wizard/picker_navigation.py +2 -2
  66. package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
  67. package/runtime/python/okstra_ctl/wizard/render.py +8 -55
  68. package/runtime/python/okstra_ctl/wizard/roles.py +11 -7
  69. package/runtime/python/okstra_ctl/wizard/sources.py +28 -2
  70. package/runtime/python/okstra_ctl/wizard/state.py +13 -6
  71. package/runtime/python/okstra_ctl/wizard/steps_plan.py +8 -0
  72. package/runtime/python/okstra_ctl/worker_liveness.py +52 -39
  73. package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
  74. package/runtime/python/okstra_ctl/workflow.py +8 -0
  75. package/runtime/python/okstra_ctl/write_policy.py +23 -0
  76. package/runtime/python/okstra_token_usage/blocks.py +50 -1
  77. package/runtime/python/okstra_token_usage/claude.py +42 -21
  78. package/runtime/python/okstra_token_usage/codex.py +17 -0
  79. package/runtime/python/okstra_token_usage/collect.py +299 -162
  80. package/runtime/python/okstra_token_usage/cursor.py +2 -3
  81. package/runtime/python/okstra_token_usage/report.py +35 -30
  82. package/runtime/python/okstra_token_usage/task_totals.py +3 -12
  83. package/runtime/schemas/final-report-v2.0.schema.json +298 -7
  84. package/runtime/schemas/final-report-v3.0.schema.json +298 -7
  85. package/runtime/schemas/report-narrative-v3.0.schema.json +1 -0
  86. package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +1 -1
  87. package/runtime/schemas/report-writer-corrections-v1.0.schema.json +30 -3
  88. package/runtime/skills/okstra-run/SKILL.md +10 -2
  89. package/runtime/skills/okstra-setup/SKILL.md +42 -7
  90. package/runtime/templates/report-writer-prompt-preamble.md +7 -3
  91. package/runtime/templates/reports/html/i18n/en.json +11 -0
  92. package/runtime/templates/reports/html/i18n/ko.json +11 -0
  93. package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +7 -3
  94. package/runtime/templates/reports/html/tasks/technical-verification.template.html +35 -0
  95. package/runtime/templates/reports/md/tasks/technical-verification.template.md +5 -0
  96. package/runtime/validators/validate-run.py +9 -4
@@ -21,6 +21,7 @@ from okstra_ctl.design_prep import ( # noqa: E402
21
21
  from okstra_ctl.usage_cells import duration_ms_from_bounds # noqa: E402
22
22
 
23
23
  from .task_totals import task_cumulative_usage # noqa: E402
24
+ from .blocks import accounting_workers # noqa: E402
24
25
 
25
26
 
26
27
  class SubstituteRefusedError(RuntimeError):
@@ -155,7 +156,7 @@ def _cache_reads(summary: dict, team_state: dict) -> dict[str, int]:
155
156
  lead = _cache_read_tokens(team_state.get("leadUsage"))
156
157
  worker = sum(
157
158
  _cache_read_tokens(entry.get("usage"))
158
- for entry in team_state.get("workers") or []
159
+ for entry in accounting_workers(team_state)
159
160
  if isinstance(entry, dict)
160
161
  )
161
162
  worker += _cache_read_tokens(summary.get("unattributedWorkerUsage"))
@@ -171,7 +172,7 @@ def _cache_read_tokens(usage: object) -> int:
171
172
 
172
173
  def _worker_detail_rows(team_state: dict) -> list[dict]:
173
174
  rows = []
174
- for worker in team_state.get("workers") or []:
175
+ for worker in accounting_workers(team_state):
175
176
  if isinstance(worker, dict):
176
177
  rows.append(
177
178
  _worker_detail_row(_worker_detail_label(worker), worker.get("usage") or {})
@@ -212,6 +213,37 @@ def populate_data_token_cells(data_path: Path, team_state: dict) -> int:
212
213
  data = json.loads(data_path.read_text(encoding="utf-8"))
213
214
  changes = 0
214
215
 
216
+ changes += _populate_summary_token_cells(data, summary, team_state, data_path)
217
+
218
+ # Execution Status by Agent — per-row token / cost / duration.
219
+ # `collect.py` writes lead usage to `team_state["leadUsage"]` (flat
220
+ # usage_block), while worker usage lives at `worker["usage"]`. Wrap
221
+ # the lead block so `_populate_execution_row` can read both shapes
222
+ # via the same `source["usage"]` path.
223
+ lead_source = {"usage": team_state.get("leadUsage") or {}}
224
+ workers = accounting_workers(team_state)
225
+ rows = data.get("executionStatus") or []
226
+ used_workers: set[int] = set()
227
+ for row in rows:
228
+ role = row.get("role", "")
229
+ if "lead" in role.lower():
230
+ _populate_execution_row(row, lead_source)
231
+ changes += 1
232
+ continue
233
+ idx = _match_worker_index(row, workers, used_workers)
234
+ if idx is not None:
235
+ used_workers.add(idx)
236
+ _populate_execution_row(row, workers[idx])
237
+ changes += 1
238
+
239
+ data_path.write_text(
240
+ json.dumps(data, indent=2, ensure_ascii=False) + "\n",
241
+ encoding="utf-8",
242
+ )
243
+ return changes
244
+
245
+
246
+ def _populate_summary_token_cells(data: dict, summary: dict, team_state: dict, data_path: Path) -> int:
215
247
  # Token Usage Summary table — four rows.
216
248
  cost = summary.get("estimatedCostUsd") or {}
217
249
  lead_cost = cost.get("lead") or 0
@@ -245,34 +277,7 @@ def populate_data_token_cells(data_path: Path, team_state: dict) -> int:
245
277
  cumulative = task_cumulative_usage(data_path)
246
278
  if cumulative is not None:
247
279
  token_usage["taskCumulative"] = cumulative
248
- changes += 4
249
-
250
- # Execution Status by Agent — per-row token / cost / duration.
251
- # `collect.py` writes lead usage to `team_state["leadUsage"]` (flat
252
- # usage_block), while worker usage lives at `worker["usage"]`. Wrap
253
- # the lead block so `_populate_execution_row` can read both shapes
254
- # via the same `source["usage"]` path.
255
- lead_source = {"usage": team_state.get("leadUsage") or {}}
256
- workers = team_state.get("workers") or []
257
- rows = data.get("executionStatus") or []
258
- used_workers: set[int] = set()
259
- for row in rows:
260
- role = row.get("role", "")
261
- if "lead" in role.lower():
262
- _populate_execution_row(row, lead_source)
263
- changes += 1
264
- continue
265
- idx = _match_worker_index(row, workers, used_workers)
266
- if idx is not None:
267
- used_workers.add(idx)
268
- _populate_execution_row(row, workers[idx])
269
- changes += 1
270
-
271
- data_path.write_text(
272
- json.dumps(data, indent=2, ensure_ascii=False) + "\n",
273
- encoding="utf-8",
274
- )
275
- return changes
280
+ return 4
276
281
 
277
282
 
278
283
  def populate_token_cells(
@@ -16,6 +16,8 @@ from __future__ import annotations
16
16
  import json
17
17
  from pathlib import Path
18
18
 
19
+ from .blocks import normalize_usage_block, usage_blocks
20
+
19
21
  _TEAM_STATE_GLOB = "runs/*/state/team-state-*.json"
20
22
  _SUMMED_KEYS = (
21
23
  ("totalTokens", "totalTokens"),
@@ -40,17 +42,6 @@ def _task_root(data_path: Path) -> Path | None:
40
42
  return parents[3]
41
43
 
42
44
 
43
- def _usage_blocks(team_state: dict) -> list[dict]:
44
- blocks = [team_state.get("leadUsage") or {}]
45
- for worker in team_state.get("workers") or []:
46
- if isinstance(worker, dict):
47
- blocks.append(worker.get("usage") or {})
48
- unattributed = (team_state.get("usageSummary") or {}).get("unattributedWorkerUsage")
49
- if isinstance(unattributed, dict):
50
- blocks.append(unattributed)
51
- return [block for block in blocks if isinstance(block, dict)]
52
-
53
-
54
45
  def _number(value: object) -> int | float:
55
46
  if isinstance(value, bool) or not isinstance(value, (int, float)):
56
47
  return 0
@@ -76,7 +67,7 @@ def task_cumulative_usage(data_path: Path) -> dict | None:
76
67
  continue
77
68
  if not isinstance(team_state, dict):
78
69
  continue
79
- blocks = _usage_blocks(team_state)
70
+ blocks = [normalize_usage_block(block) for block in usage_blocks(team_state)]
80
71
  measured = sum(_number(block.get("totalTokens")) for block in blocks)
81
72
  if measured <= 0:
82
73
  continue
@@ -25,6 +25,7 @@
25
25
  ],
26
26
  "additionalProperties": false,
27
27
  "properties": {
28
+ "technicalVerification": {"$ref": "#/$defs/TechnicalVerification"},
28
29
  "schemaVersion": {
29
30
  "const": "2.0",
30
31
  "description": "Schema version. Renderer refuses to process unknown versions."
@@ -1664,6 +1665,86 @@
1664
1665
  }
1665
1666
  },
1666
1667
  "allOf": [
1668
+ {
1669
+ "if": {
1670
+ "properties": {
1671
+ "header": {
1672
+ "properties": {
1673
+ "taskType": {
1674
+ "const": "technical-verification"
1675
+ }
1676
+ }
1677
+ }
1678
+ },
1679
+ "required": [
1680
+ "header"
1681
+ ]
1682
+ },
1683
+ "then": {
1684
+ "required": [
1685
+ "technicalVerification"
1686
+ ],
1687
+ "properties": {
1688
+ "frontmatter": {
1689
+ "properties": {
1690
+ "approved": {
1691
+ "const": false
1692
+ }
1693
+ }
1694
+ },
1695
+ "implementationOptionSelection": false,
1696
+ "implementationPlanning": false,
1697
+ "implementation": false,
1698
+ "finalVerification": false,
1699
+ "releaseHandoff": false,
1700
+ "errorAnalysis": false,
1701
+ "requirementsDiscovery": false,
1702
+ "improvementDiscovery": false,
1703
+ "analysisCommon": false,
1704
+ "projectAnalysis": false,
1705
+ "featureAnalysis": false,
1706
+ "changeImpactAnalysis": false,
1707
+ "followUpTasks": {
1708
+ "contains": {
1709
+ "properties": {
1710
+ "origin": {
1711
+ "const": "phase-continuation"
1712
+ }
1713
+ },
1714
+ "required": [
1715
+ "origin"
1716
+ ]
1717
+ },
1718
+ "minContains": 1,
1719
+ "maxContains": 1,
1720
+ "items": {
1721
+ "if": {
1722
+ "properties": {
1723
+ "origin": {
1724
+ "const": "phase-continuation"
1725
+ }
1726
+ },
1727
+ "required": [
1728
+ "origin"
1729
+ ]
1730
+ },
1731
+ "then": {
1732
+ "properties": {
1733
+ "suggestedTaskType": {
1734
+ "const": "implementation-option-selection"
1735
+ }
1736
+ }
1737
+ }
1738
+ }
1739
+ }
1740
+ }
1741
+ },
1742
+ "else": {
1743
+ "properties": {
1744
+ "technicalVerification": false
1745
+ }
1746
+ }
1747
+ },
1667
1748
  {
1668
1749
  "description": "Frontmatter approval input matches the implementation-planning contract.",
1669
1750
  "if": {
@@ -2013,6 +2094,7 @@
2013
2094
  "properties": {
2014
2095
  "taskType": {
2015
2096
  "enum": [
2097
+ "technical-verification",
2016
2098
  "requirements-discovery",
2017
2099
  "project-analysis",
2018
2100
  "feature-analysis",
@@ -2192,6 +2274,7 @@
2192
2274
  }
2193
2275
  ],
2194
2276
  "oneOf": [
2277
+ {"required": ["technicalVerification"]},
2195
2278
  {
2196
2279
  "required": [
2197
2280
  "requirementsDiscovery"
@@ -3073,7 +3156,8 @@
3073
3156
  "enum": [
3074
3157
  "pending-direction-selection",
3075
3158
  "implementation-planning",
3076
- "blocked"
3159
+ "blocked",
3160
+ "technical-verification"
3077
3161
  ]
3078
3162
  },
3079
3163
  "userNarrative": {
@@ -3635,6 +3719,17 @@
3635
3719
  ],
3636
3720
  "additionalProperties": false,
3637
3721
  "properties": {
3722
+ "resolutionKind": {
3723
+ "type": "string",
3724
+ "enum": ["user-decision", "technical-verification"],
3725
+ "description": "Optional for historical reads; blocked report publication requires explicit classification."
3726
+ },
3727
+ "clarificationRefs": {
3728
+ "type": "array",
3729
+ "uniqueItems": true,
3730
+ "items": {"type": "string", "pattern": "^C-[0-9]{3}$"},
3731
+ "description": "User decisions require nonempty references to actual clarification records, including answered records."
3732
+ },
3638
3733
  "fact": {
3639
3734
  "type": "string",
3640
3735
  "minLength": 1
@@ -3950,7 +4045,7 @@
3950
4045
  },
3951
4046
  "stageRefs": {
3952
4047
  "type": "array",
3953
- "minItems": 1,
4048
+ "minItems": 0,
3954
4049
  "uniqueItems": true,
3955
4050
  "items": {
3956
4051
  "type": "integer",
@@ -3959,7 +4054,7 @@
3959
4054
  },
3960
4055
  "stepRefs": {
3961
4056
  "type": "array",
3962
- "minItems": 1,
4057
+ "minItems": 0,
3963
4058
  "uniqueItems": true,
3964
4059
  "items": {
3965
4060
  "type": "string",
@@ -3968,7 +4063,7 @@
3968
4063
  },
3969
4064
  "validationRefs": {
3970
4065
  "type": "array",
3971
- "minItems": 1,
4066
+ "minItems": 0,
3972
4067
  "uniqueItems": true,
3973
4068
  "items": {
3974
4069
  "type": "string",
@@ -3977,7 +4072,7 @@
3977
4072
  },
3978
4073
  "fileRefs": {
3979
4074
  "type": "array",
3980
- "minItems": 1,
4075
+ "minItems": 0,
3981
4076
  "uniqueItems": true,
3982
4077
  "items": {
3983
4078
  "type": "string",
@@ -3987,10 +4082,59 @@
3987
4082
  "status": {
3988
4083
  "enum": [
3989
4084
  "covered",
3990
- "not-covered"
4085
+ "not-covered",
4086
+ "externally-tracked"
3991
4087
  ]
4088
+ },
4089
+ "crossProjectDependencyRefs": {
4090
+ "type": "array",
4091
+ "uniqueItems": true,
4092
+ "items": {
4093
+ "type": "string",
4094
+ "pattern": "^XP-[0-9]{3,}$"
4095
+ }
3992
4096
  }
3993
- }
4097
+ },
4098
+ "oneOf": [
4099
+ {
4100
+ "properties": {
4101
+ "status": {
4102
+ "enum": [
4103
+ "covered",
4104
+ "not-covered"
4105
+ ]
4106
+ },
4107
+ "stageRefs": {
4108
+ "minItems": 1
4109
+ },
4110
+ "stepRefs": {
4111
+ "minItems": 1
4112
+ },
4113
+ "validationRefs": {
4114
+ "minItems": 1
4115
+ },
4116
+ "fileRefs": {
4117
+ "minItems": 1
4118
+ },
4119
+ "crossProjectDependencyRefs": {
4120
+ "maxItems": 0
4121
+ }
4122
+ }
4123
+ },
4124
+ {
4125
+ "required": [
4126
+ "crossProjectDependencyRefs"
4127
+ ],
4128
+ "properties": {
4129
+ "status": {
4130
+ "const": "externally-tracked"
4131
+ },
4132
+ "crossProjectDependencyRefs": {
4133
+ "minItems": 1
4134
+ }
4135
+ }
4136
+ }
4137
+ ]
3994
4138
  },
3995
4139
  "SelectedDirectionCoverageSummary": {
3996
4140
  "type": "object",
@@ -4056,8 +4200,155 @@
4056
4200
  }
4057
4201
  }
4058
4202
  },
4203
+ "TechnicalVerification": {
4204
+ "type": "object",
4205
+ "additionalProperties": false,
4206
+ "required": [
4207
+ "sourceReport",
4208
+ "sourceDataSha256",
4209
+ "scope",
4210
+ "checks",
4211
+ "routing"
4212
+ ],
4213
+ "properties": {
4214
+ "sourceReport": {
4215
+ "type": "string",
4216
+ "minLength": 1
4217
+ },
4218
+ "sourceDataSha256": {
4219
+ "type": "string",
4220
+ "pattern": "^[0-9a-f]{64}$"
4221
+ },
4222
+ "scope": {
4223
+ "const": "technical-evidence-only"
4224
+ },
4225
+ "checks": {
4226
+ "type": "array",
4227
+ "minItems": 1,
4228
+ "items": {
4229
+ "type": "object",
4230
+ "additionalProperties": false,
4231
+ "required": [
4232
+ "id",
4233
+ "candidateId",
4234
+ "factIndex",
4235
+ "fact",
4236
+ "hypothesis",
4237
+ "procedure",
4238
+ "confirmingSignal",
4239
+ "rejectingSignal",
4240
+ "status",
4241
+ "commands",
4242
+ "observation",
4243
+ "limitations"
4244
+ ],
4245
+ "properties": {
4246
+ "id": {
4247
+ "type": "string",
4248
+ "pattern": "^TV-[0-9]{3}$"
4249
+ },
4250
+ "candidateId": {
4251
+ "type": "string",
4252
+ "pattern": "^IO-[0-9]{3}$"
4253
+ },
4254
+ "factIndex": {
4255
+ "type": "integer",
4256
+ "minimum": 0
4257
+ },
4258
+ "fact": {
4259
+ "type": "string",
4260
+ "minLength": 1
4261
+ },
4262
+ "hypothesis": {
4263
+ "type": "string",
4264
+ "minLength": 1
4265
+ },
4266
+ "procedure": {
4267
+ "type": "string",
4268
+ "minLength": 1
4269
+ },
4270
+ "confirmingSignal": {
4271
+ "type": "string",
4272
+ "minLength": 1
4273
+ },
4274
+ "rejectingSignal": {
4275
+ "type": "string",
4276
+ "minLength": 1
4277
+ },
4278
+ "status": {
4279
+ "enum": [
4280
+ "supported",
4281
+ "refuted",
4282
+ "inconclusive",
4283
+ "not-run"
4284
+ ]
4285
+ },
4286
+ "commands": {
4287
+ "type": "array",
4288
+ "items": {
4289
+ "type": "object",
4290
+ "additionalProperties": false,
4291
+ "required": [
4292
+ "command",
4293
+ "cwd",
4294
+ "exitCode",
4295
+ "logPath"
4296
+ ],
4297
+ "properties": {
4298
+ "command": {
4299
+ "type": "string",
4300
+ "minLength": 1
4301
+ },
4302
+ "cwd": {
4303
+ "type": "string",
4304
+ "minLength": 1
4305
+ },
4306
+ "exitCode": {
4307
+ "type": "integer"
4308
+ },
4309
+ "logPath": {
4310
+ "type": "string",
4311
+ "minLength": 1
4312
+ }
4313
+ }
4314
+ }
4315
+ },
4316
+ "observation": {
4317
+ "type": "string",
4318
+ "minLength": 1
4319
+ },
4320
+ "limitations": {
4321
+ "type": "array",
4322
+ "items": {
4323
+ "type": "string",
4324
+ "minLength": 1
4325
+ }
4326
+ }
4327
+ }
4328
+ }
4329
+ },
4330
+ "routing": {
4331
+ "type": "object",
4332
+ "additionalProperties": false,
4333
+ "required": [
4334
+ "nextTaskType",
4335
+ "rationale"
4336
+ ],
4337
+ "properties": {
4338
+ "nextTaskType": {
4339
+ "const": "implementation-option-selection"
4340
+ },
4341
+ "rationale": {
4342
+ "type": "string",
4343
+ "minLength": 1
4344
+ }
4345
+ }
4346
+ }
4347
+ }
4348
+ },
4059
4349
  "TaskType": {
4060
4350
  "enum": [
4351
+ "technical-verification",
4061
4352
  "requirements-discovery",
4062
4353
  "improvement-discovery",
4063
4354
  "error-analysis",