okstra 0.130.4 → 0.131.1

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 (365) hide show
  1. package/README.md +0 -1
  2. package/docs/architecture.md +2 -2
  3. package/docs/cli.md +1 -1
  4. package/docs/for-ai/README.md +0 -2
  5. package/docs/for-ai/skills/okstra-run.md +13 -2
  6. package/docs/for-ai/skills/okstra-setup.md +4 -1
  7. package/docs/project-structure-overview.md +5 -4
  8. package/docs/task-process/implementation-planning.md +13 -0
  9. package/docs/task-process/implementation.md +17 -2
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/workers/report-writer-worker.md +7 -0
  13. package/runtime/bin/lib/okstra/usage.sh +4 -4
  14. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  15. package/runtime/prompts/lead/plan-body-verification.md +26 -5
  16. package/runtime/prompts/lead/report-writer.md +6 -2
  17. package/runtime/prompts/profiles/_common-contract.md +2 -1
  18. package/runtime/prompts/profiles/_implementation-deliverable.md +6 -1
  19. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  20. package/runtime/prompts/profiles/_implementation-verifier.md +33 -10
  21. package/runtime/prompts/profiles/final-verification.md +21 -6
  22. package/runtime/prompts/profiles/implementation-planning.md +16 -4
  23. package/runtime/python/okstra_ctl/codex_dispatch.py +31 -0
  24. package/runtime/python/okstra_ctl/conformance.py +49 -11
  25. package/runtime/python/okstra_ctl/report_finalize.py +52 -1
  26. package/runtime/python/okstra_ctl/run.py +15 -3
  27. package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
  28. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
  29. package/runtime/python/okstra_vendor/__init__.py +0 -44
  30. package/runtime/schemas/final-report-v1.0.schema.json +99 -5
  31. package/runtime/skills/okstra-run/SKILL.md +11 -4
  32. package/runtime/skills/okstra-setup/SKILL.md +3 -2
  33. package/runtime/skills/okstra-setup/references/project-config.md +16 -7
  34. package/runtime/templates/reports/final-report.template.md +15 -9
  35. package/runtime/templates/reports/i18n/en.json +1 -0
  36. package/runtime/templates/reports/i18n/ko.json +1 -0
  37. package/runtime/validators/lib/fixtures.sh +6 -0
  38. package/runtime/validators/validate-run.py +2019 -96
  39. package/src/cli-registry.mjs +0 -7
  40. package/src/commands/lifecycle/doctor.mjs +0 -9
  41. package/src/lib/skill-catalog.mjs +0 -1
  42. package/docs/for-ai/skills/okstra-graphify.md +0 -64
  43. package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
  44. package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
  45. package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
  46. package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
  47. package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
  48. package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
  49. package/runtime/python/okstra_vendor/graphify/build.py +0 -107
  50. package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
  51. package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
  52. package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
  53. package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
  54. package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
  55. package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
  56. package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
  57. package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
  58. package/runtime/python/okstra_vendor/graphify/report.py +0 -175
  59. package/runtime/python/okstra_vendor/graphify/security.py +0 -203
  60. package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
  61. package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
  62. package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
  63. package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
  64. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
  65. package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
  66. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
  67. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
  68. package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
  69. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
  70. package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
  71. package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
  72. package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
  73. package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
  74. package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
  75. package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
  76. package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
  77. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
  78. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
  79. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
  80. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
  81. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
  82. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
  83. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
  84. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
  85. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
  86. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
  87. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
  88. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
  89. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
  90. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
  91. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
  92. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
  93. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
  94. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
  95. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
  96. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
  97. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
  98. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
  99. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
  100. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
  101. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
  114. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
  116. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
  117. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
  118. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
  139. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
  140. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
  141. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
  142. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
  143. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
  144. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
  145. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
  146. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
  147. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
  148. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
  149. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
  150. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
  151. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
  152. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
  153. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
  154. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
  155. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
  156. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
  157. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
  158. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
  159. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
  160. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
  161. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
  162. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
  163. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
  164. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
  165. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
  166. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
  167. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
  168. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
  169. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
  170. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
  171. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
  172. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
  173. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
  174. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
  175. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
  176. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
  177. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
  178. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
  179. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
  180. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
  181. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
  182. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
  183. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
  184. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
  185. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
  186. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
  187. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
  188. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
  189. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
  190. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
  191. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
  192. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
  193. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
  194. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
  195. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
  196. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
  197. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
  198. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
  199. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
  200. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
  201. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
  202. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
  203. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
  204. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
  205. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
  206. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
  207. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
  208. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
  209. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
  210. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
  211. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
  212. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
  213. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
  214. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
  215. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
  216. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
  217. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
  218. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
  219. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
  220. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
  221. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
  222. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
  223. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
  224. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
  225. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
  226. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
  227. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
  228. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
  229. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
  230. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
  231. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
  232. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
  233. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
  234. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
  235. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
  236. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
  237. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
  238. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
  239. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
  240. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
  241. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
  242. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
  243. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
  244. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
  245. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
  246. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
  247. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
  248. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
  249. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
  250. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
  251. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
  252. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
  253. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
  254. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
  255. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
  256. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
  257. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
  258. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
  259. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
  260. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
  261. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
  262. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
  263. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
  264. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
  265. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
  266. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
  267. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
  268. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
  269. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
  270. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
  271. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
  272. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
  273. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
  274. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
  275. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
  276. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
  277. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
  278. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
  279. package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
  280. package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
  281. package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
  282. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
  283. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
  284. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
  285. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
  286. package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
  287. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
  288. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
  289. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
  290. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
  291. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
  292. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
  293. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
  294. package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
  295. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
  296. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  297. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
  298. package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
  299. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
  300. package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
  301. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
  302. package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
  303. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
  304. package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
  305. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
  306. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
  307. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
  308. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
  309. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
  310. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
  311. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
  312. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
  313. package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
  314. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
  315. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
  316. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
  317. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
  318. package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
  319. package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
  320. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
  321. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
  322. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
  323. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
  324. package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
  325. package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
  326. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
  327. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
  328. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
  329. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
  330. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
  331. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
  332. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
  333. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
  334. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
  335. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
  336. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
  337. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
  338. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
  339. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
  340. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
  341. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
  342. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
  343. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
  344. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
  345. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
  346. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
  347. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
  348. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
  349. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
  350. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
  351. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
  352. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
  353. package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
  354. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
  355. package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
  356. package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
  357. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
  358. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
  359. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
  360. package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
  361. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
  362. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
  363. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
  364. package/runtime/skills/okstra-graphify/SKILL.md +0 -169
  365. package/src/commands/graphify.mjs +0 -32
@@ -42,14 +42,35 @@ Verifier obtains the QA command set from exactly two declared sources, in order
42
42
 
43
43
  Tier 1 commands run verbatim first. Then every Tier 2 entry runs once. Then the Tier 3 stage conformance script (below) runs once. Each command runs in the worktree cwd, and is recorded in the worker result with its exact command line, exit code, and the tail of stdout/stderr. Substituting or paraphrasing a Tier 1 command is forbidden (see Verifier-specific forbidden actions below).
44
44
 
45
- ### Tier 3 — stage conformance scripts (requirement-conformance verification)
46
-
47
- Tier 3 proves the stage actually *meets the upper-level requirement* it was scoped to, by running a declared conformance script against the running state — Tiers 1·2 only prove the diff *builds and passes*. This is a real gate: its result sidecar is the input the `validate-run.py` Tier 3 gate reads, so a missing or non-PASS result BLOCKS acceptance.
45
+ ### Tier 3 — stage conformance scripts
46
+
47
+ Tier 3 still attempts the declared `runCommand` against the permitted test
48
+ environment. Classify the manifest entry by `requires`: any `db`, `http`, or
49
+ `external` capability makes the entry external-advisory; an empty list or
50
+ `io`-only entry stays blocking.
51
+
52
+ **External QA outcome policy.** Record PASS normally. For FAIL, MISSING, no
53
+ result, startup failure, credential/network/service absence, write the honest
54
+ sidecar and command evidence with status `ADVISORY`. That result alone
55
+ MUST NOT change the overall verifier verdict from PASS to CONCERNS/FAIL, trigger a fix
56
+ cycle, or block the stage. Return the exact command, prerequisites, observed
57
+ result, and expected result to the report writer as a user-owned follow-up.
58
+ Enforcement: `scripts/okstra_ctl/conformance.py` (`decide_conformance_gate`)
59
+ performs the core advisory classification/reduction;
60
+ `validators/validate-run.py` (`_validate_conformance`) routes that status to
61
+ warnings instead of failures; and
62
+ `tests/contract/test_okstra_ctl_conformance.py` (`test_external_non_pass_is_advisory`),
63
+ `tests/contract/test_validate_run_conformance.py` (`test_external_non_pass_returns_warning_not_failure`),
64
+ and `tests/contract/test_final_report_contracts.py` (`test_external_qa_advisory_renders_without_downgrading_accepted_verdict`)
65
+ lock the core, gate, and accepted-report behavior respectively.
66
+
67
+ An `io`-only non-PASS remains BLOCKING. Manifest/schema/source-mutation defects
68
+ also remain contract violations.
48
69
 
49
70
  - **Source.** The conformance manifest is `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token). This run's stage conformance entry is the manifest `entries[]` item whose `stageKey` equals this run's stageKey — `<task-id>-stage-<N>`, where `<N>` is the injected Stage number. Find that one entry; ignore the others (other stages are run by their own implementation runs or by final-verification).
50
- - **Exemption / waiver → do NOT run.** If the entry carries an `exemption` (or a user `waiver`), the verifier does NOT execute the script. It records the fact and the reason (`exemption.reason` / `waiver.reason` + `waiver.acknowledgedBy`) in the Read-only command log AND writes the result sidecar reflecting the skip. An `exemption` passes the gate outright; a `waiver` passes but is conditional (conformance left unverified by explicit user acknowledgement). No script runs in either case.
71
+ - **Exemption / waiver → do NOT run.** If the entry carries an `exemption` (or a user `waiver`), the verifier does NOT execute the script. It records the fact and the reason (`exemption.reason` / `waiver.reason` + `waiver.acknowledgedBy`) in the Read-only command log AND writes the result sidecar reflecting the skip. An `exemption` passes outright. An external-advisory waiver is reported as `ADVISORY` with `conditional=false`; only an `io`-only blocking waiver is conditional. An empty `requires` list cannot be waived; it is declaration/contract trouble and remains BLOCKING. No script runs in either permitted waiver case.
51
72
  - **Otherwise run `runCommand` in the worktree cwd.** Execute the entry's `runCommand` verbatim from the worktree cwd. Inject env from `<PROJECT_ROOT>/.okstra/project.json`'s `qaEnv` (replica DB DSN / app base URL / env file — declared in Phase 4e). This is a **replica / test environment only** path — never run it against shared / staging / prod, identical to the DB real-execution gate principle above.
52
- - **Interpret the standard interface.** Parse the process exit code together with stdout: the `QA-RESULT: PASS|FAIL` marker line (if several appear, the last one wins) and the per-requirement `REQ <id>: PASS|FAIL: <reason>` lines. If no `QA-RESULT` marker is emitted, the overall result is `MISSING` which the gate treats as BLOCKING (the script broke the contract).
73
+ - **Interpret the standard interface.** Parse the process exit code together with stdout: the `QA-RESULT: PASS|FAIL` marker line (if several appear, the last one wins) and the per-requirement `REQ <id>: PASS|FAIL: <reason>` lines. If no `QA-RESULT` marker is emitted, the overall result is `MISSING`; classify it according to the entry's blocking or external-advisory capability policy above.
53
74
  - **Write the result sidecar (BLOCKING deliverable).** Write `<task_root>/qa/result-<stageKey>.json` as:
54
75
  ```json
55
76
  {
@@ -59,13 +80,13 @@ Tier 3 proves the stage actually *meets the upper-level requirement* it was scop
59
80
  "requirements": { "<id>": { "status": "PASS", "reason": "<from REQ line>" } }
60
81
  }
61
82
  ```
62
- `overall` is exactly one of `PASS` / `FAIL` / `MISSING`. This file is the input to the `validate-run.py` Tier 3 gate if it is absent the gate reports the stage as "never ran" and BLOCKS, so writing it is mandatory whenever the script runs (and on the exemption/waiver skip path, recording the skip outcome).
63
- - **Read-only command log.** Record the `runCommand` exact line + its exit code in the Read-only command log. Unlike Tiers 1·2, a conformance script MAY mutate the **replica datastore** (exercising integrated state is its whole purpose) — but only the `qaEnv` replica target, never a shared/staging/prod store. The `runCommand` itself is still subject to the same source/lockfile mutation deny-list as Tier 2 (`--fix`, `npm install` without `ci`, etc.); a denied token aborts with `contract-violated`.
83
+ `overall` is exactly one of `PASS` / `FAIL` / `MISSING`. Writing the honest sidecar is mandatory whenever the script runs and on the exemption/waiver skip path. A missing `io`-only sidecar blocks; a missing external-advisory sidecar is reported as `ADVISORY` rather than accepted as hidden evidence.
84
+ - **Read-only command log.** Record the `runCommand` exact line + its exit code in the Read-only command log. Tier 3 external non-PASS evidence MUST remain visible with status `ADVISORY`. Unlike Tiers 1·2, a conformance script MAY mutate the **replica datastore** (exercising integrated state is its whole purpose) — but only the `qaEnv` replica target, never a shared/staging/prod store. The `runCommand` itself is still subject to the same source/lockfile mutation deny-list as Tier 2 (`--fix`, `npm install` without `ci`, etc.); a denied token aborts with `contract-violated`.
64
85
  - **No manifest / no entry for this stage.** If the manifest file is absent, or it has no entry whose `stageKey` matches this run's stageKey, the verifier records `conformance: no manifest entry for <stageKey>` and proceeds (forcing the *declaration* of conformance entries is the job of planning Step 11 + the `validate-run.py` diff-surface cross-check, not the verifier).
65
86
 
66
87
  ### Missing-tier handling
67
88
 
68
- If a tier is empty or absent, verifier records the single line `qa-command not configured: <category>` per missing category (`lint` / `format` / `typecheck` / `test`; and `db-test` **only when the diff touches DB/IO/SQL**, where a missing `db-test` is escalated to a blocking finding per the DB real-execution gate below) in the worker result and proceeds — silent omission is a contract violation. Verifier MUST NOT auto-detect or invent a command in this case; the user/operator must declare it in `project.json.qaCommands` or in the plan.
89
+ If a tier is empty or absent, verifier records the single line `qa-command not configured: <category>` per missing category (`lint` / `format` / `typecheck` / `test`; and `db-test` **only when the diff touches DB/IO/SQL**, where a missing `db-test` is escalated to a blocking finding per the DB real-execution gate below) in the worker result and proceeds — silent omission is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_missing_qa_categories_recorded` for the four unconditional categories; `db-test` is left to the DB gate below because its requirement depends on whether the diff touches DB/IO/SQL. Without the note, "the category passed" and "the category never ran" read identically in the report. Verifier MUST NOT auto-detect or invent a command in this case; the user/operator must declare it in `project.json.qaCommands` or in the plan.
69
90
 
70
91
  ### `cmd` field deny-list (Tier 2 validation)
71
92
 
@@ -73,7 +94,7 @@ The runtime AND the verifier MUST reject any `cmd` containing tokens that imply
73
94
 
74
95
  ### Discrepancy rule
75
96
 
76
- If the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. `Claude lead` MUST NOT silently prefer the executor's evidence over a verifier's reproduced result during synthesis; if it overrides, it MUST cite a concrete reproduction-time reason (flaky-test commit-cited, environment delta documented) — handwaving is not allowed.
97
+ Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. `Claude lead` MUST NOT silently prefer the executor's evidence over a verifier's reproduced result during synthesis; if it overrides, it MUST cite a concrete reproduction-time reason (flaky-test commit-cited, environment delta documented) — handwaving is not allowed.
77
98
 
78
99
  ### Read-only command log (per verifier)
79
100
 
@@ -132,11 +153,13 @@ carried findings is a contract violation the lead records via
132
153
 
133
154
  A mocked unit test cannot observe the SQL a query builder actually emits — `count({ col: 'FontFamily.fontFamily' })` passes a mocked suite yet throws `Unknown column` on a real database. For this class of change a green mock-only suite is therefore NOT evidence; only a run against a real (or faithful-replica) datastore is. This gate is the verifier's enforcement of that rule.
134
155
 
156
+ **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
157
+
135
158
  - **Trigger.** Fires when `git diff <base>...HEAD` touches DB/IO/SQL: ORM / query-builder code (sequelize / typeorm / prisma / knex / raw SQL), `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string.
136
159
  - **Requirement when fired.** The verifier MUST reproduce a real-DB execution: run the `db-test` tier (Tier 1 = plan `validation` db step; else Tier 2 = `project.json.qaCommands.db-test`) against a **local / replica** datastore (same engine + schema — never shared / staging / prod, consistent with the verifier forbidden-actions list) and record its exact command + exit code. A mock, an in-memory shim that does not parse real SQL, or static reasoning does NOT satisfy this.
137
160
  - **No `db-test` command available → blocking, not a passive skip.** If neither tier declares a `db-test` command, the verifier records the blocking finding `db-test not configured — DB change unverified (mock-only)` and sets the verdict to `FAIL`; it MUST NOT emit only the passive `qa-command not configured` note and pass. Recommended fix: declare a `db-test` command in `project.json.qaCommands` or the plan's validation set.
138
161
  - **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion `static-analysis only …, unverified (not executed)` (never `verified`), records it as a blocking finding, and sets `FAIL`. Never downplay the real run as "too heavy / static proof suffices".
139
- - **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push.
162
+ - **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push. **Enforced:** `validators/validate-run.py` `_validate_verifier_fail_blocks_verdict` fails a report whose `verdictCard.verdictToken` is `accepted` / `conditional-accept` while any `implementation.verifierResults[]` row records `verdict: FAIL` — a rejection dropped during synthesis is exactly how rejected work reached `release-handoff`.
140
163
 
141
164
  ## All-verifier-failure policy
142
165
 
@@ -15,6 +15,7 @@
15
15
  - test & validation suite pass status — independently re-run the read-only two-tier command set (Tier 1 = brief/approved-plan `validation`, Tier 2 = `project.json` `qaCommands`) and confirm each passes on the verified head, citing exact command + exit code
16
16
  - test correctness — delivered tests actually assert the intended behaviour: no gutted/weakened assertions, no tautological or always-passing tests, no tests exercising only mocks; new behaviour has matching coverage
17
17
  - DB / IO / SQL real-execution evidence — trigger: the diff touches DB/IO/SQL (ORM / query-builder, `*.repository.*`, model / `migrations/**` / `*.sql`, or changed query strings). Then Validation Evidence MUST cite a real (or faithful-replica) DB execution — the `db-test` command + exit code — not a mock-only suite. Rationale: a mock-only suite cannot observe the SQL actually emitted (observed failure class: `prompts/profiles/_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). A DB-touching change whose only evidence is mocked, or for which no `db-test` ran, is an **Acceptance Blocker** (`major`+; per the Verdict vocabulary below, any blocker moves the verdict off `accepted`). This gate stops an unverified DB change from reaching `release-handoff` and being pushed.
18
+ - **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
18
19
  - no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
19
20
  - scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
20
21
  - project review-rule packs (when present) — search `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`; read the matching `SKILL.md` + referenced `references/*.md` and apply their rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none found`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
@@ -33,27 +34,41 @@
33
34
  - **Target confirmation:** verify the inline worktree, scope, base/head refs, target path, and digest against `verification-target.md` before analysis. Use the sidecar's stage/report mapping and complete diff stat; a missing sidecar, digest mismatch, dirty worktree outside `.okstra/`, or wrong head is a `tool-failure`, never a silent target reselection.
34
35
  - **Evidence:** attach file:line, exact command + exit code, log excerpt, or MCP SELECT evidence to every finding. Mark a requirement as covered only when the cited artifact demonstrates it.
35
36
  - **Tier 1 and Tier 2 read-only validation:** Tier 1 is the originating brief/approved plan `validation` set; Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` `qaCommands`. Do not auto-detect commands from package manifests. A missing tier is `qa-command not configured: <category>`. Before execution, reject commands containing source/lockfile mutation tokens such as `--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, or `npm install` without `ci`; record the exact denied token.
36
- - **Tier 3 stage conformance:** read `<task_root>/qa/conformance-manifest.json`. In whole-task scope, run every non-exempt `entries[].runCommand` against the merged worktree; in single-stage scope, run only the matching `stageKey`. Use `qaEnv` only with a replica/test datastore, refresh `result-<stageKey>.json`, and interpret the exit code plus the last `QA-RESULT: PASS|FAIL` marker. Missing/non-PASS evidence is an acceptance-blocker recommendation. An exemption or user waiver is not executed; record its reason, and surface a waiver as a `conditional-accept` recommendation.
37
+ - **External QA outcome policy:** continue to attempt every in-scope Tier 3
38
+ command. For an entry requiring `db`, `http`, or `external`, record non-PASS
39
+ as a Tier 3 `advisory` command, add a user-owned Residual Risk and exact
40
+ Recommended Next Step, and include it in Could-Not-Verify. It
41
+ MUST NOT create an Acceptance Blocker, conditional acceptance condition, blocked verdict, or
42
+ non-release routing by itself. `io`-only non-PASS and malformed/undeclared
43
+ conformance contracts remain acceptance-gating.
44
+ Enforcement: `scripts/okstra_ctl/conformance.py` (`decide_conformance_gate`)
45
+ performs the core advisory classification/reduction;
46
+ `validators/validate-run.py` (`_validate_conformance`) routes that status to
47
+ warnings instead of failures; and
48
+ `tests/contract/test_okstra_ctl_conformance.py` (`test_external_non_pass_is_advisory`),
49
+ `tests/contract/test_validate_run_conformance.py` (`test_external_non_pass_returns_warning_not_failure`),
50
+ and `tests/contract/test_final_report_contracts.py` (`test_external_qa_advisory_renders_without_downgrading_accepted_verdict`)
51
+ lock the core, gate, and accepted-report behavior respectively.
37
52
  - **Manual user test:** read only the source implementation report's `implementation.manualUserTest`. Execute reproducible steps and record `pass`, `fail`, or `blocked` with observed evidence. Human-only or environment-unavailable steps remain `blocked` with the exact reason. Reaffirm an `applicable=false` exemption; do not execute planning `designPreparation` or manual-test PREP items directly.
38
53
  - **Design-preparation carry-in:** read only source implementation report `missingInformation` rows whose `source` starts with `design-prep:`. Recommend `ifStillOpen: block` as an acceptance blocker and `ifStillOpen: follow-up` as residual risk. This phase does not read planning PREP sidecars directly or mutate planning snapshots.
39
54
  - **Could-not-verify honesty:** use `not-configured`, `env-unavailable`, `rejected`, `gap`, or `blocked` as appropriate. Never convert unavailable evidence into an executed/pass claim.
40
55
  - **Source-mutation prohibition:** verification may write only assigned okstra run artifacts. Do not edit source, schema, deployment, lockfile, or configuration files; route detected defects to a later phase.
41
56
  - Required deliverable shape (final report, in addition to the standard sections):
42
- - **Source Implementation Report(s)**: the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base/head refs, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). Every analyser prompt carries the same compact target identity (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification head ref:** / **Verification target path:** / **Verification target digest:**`) and reads the sidecar on demand for the complete diff stat. A worker that cannot confirm its analysis ran against that worktree's delivered diff MUST record a `tool-failure`.
57
+ - **Source Implementation Report(s)** (**Enforced:** `validators/validate-run.py` `_validate_verification_target_match` compares `verificationScope`, `worktreePath`, `implementationBaseRef`, `capturedHeadSha`, and the `stageReports` stage set against the digest-verified `instruction-set/verification-target.md`; a snapshot whose digest no longer checks out is ignored rather than trusted. `verificationScope` in particular gates both stage-group eligibility and release-handoff routing, so it is not the report's to restate): the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base/head refs, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). Every analyser prompt carries the same compact target identity (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification head ref:** / **Verification target path:** / **Verification target digest:**`) and reads the sidecar on demand for the complete diff stat. A worker that cannot confirm its analysis ran against that worktree's delivered diff MUST record a `tool-failure`.
43
58
  - **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`. **Any Acceptance Blocker therefore forces the verdict off `accepted` (to `conditional-accept` or `blocked`); the gates below cite this rule instead of restating the arithmetic.**
44
59
  - **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
45
60
  - **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
46
61
  - **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
47
- - **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token — skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable` with the reason — never silently dropped and never reported as `executed` with an invented exit code. Mutating-command prohibition is the shared read-only boundary (see Non-goals); it is not restated per row.
48
- - **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
62
+ - **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `advisory` (external Tier 3 did not PASS; carries observed/expected results and remains user-owned) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token — skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable` or `advisory` according to the external QA policy — never silently dropped and never reported as `executed` with an invented exit code. Mutating-command prohibition is the shared read-only boundary (see Non-goals); it is not restated per row.
63
+ - **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `advisory` / `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
49
64
  - **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`. `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a `single-stage` accepted run routes to `release-handoff(stage-group)` (or `implementation` / `done`); plain `release-handoff` remains whole-task-only. Enforcement: `validators/validate-run.py` rejects a `single-stage` report whose routing cites plain `release-handoff`.
50
- - **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer.
65
+ - **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer. **Enforced:** `validators/validate-run.py` `_validate_verified_row_recorded` requires a `verified` row in `runs/implementation-planning/consumers.jsonl` for every accepted stage — the helper validated its own inputs but nothing checked it had ever run, leaving reports that said `accepted` while the registry said unverified, so the stage was never offered for a stage-group PR.
51
66
  - Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
52
67
  - populate `## 1. Clarification Items` only when a blocker hinges on information only the user can supply (deployment intent, intended target environment, business-rule interpretation); use `Blocks=next-phase` for items that gate continuing to release-handoff
53
68
  - Self-review pass before finalising the report (`Claude lead` runs this; do not delegate to a generic subagent):
54
69
  1. **Verdict precision** — section 7 (`Final Verdict`) includes `Verdict Token` with one of the three allowed verdict tokens; `conditional-accept` lists every condition as an actionable item.
55
70
  2. **Blocker traceability** — every blocker cites a concrete artifact (file:line, log excerpt, test exit code, MCP SELECT). Blockers without evidence are demoted to residual risk or removed.
56
- 3. **Coverage check** — every requirement in the originating plan/task brief is either marked covered (with artifact) or listed as a blocker. No silent omissions.
71
+ 3. **Coverage check** — every requirement in the originating plan/task brief is either marked covered (with artifact) or listed as a blocker. A user-owned external advisory gap is the sole outcome exception: keep it as a Residual Risk with the exact rerun Recommended Next Step instead of promoting it to a blocker. No silent omissions.
57
72
  4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
58
73
  5. **No source-mutation audit** — scan the run's session transcripts for Edit / Write or state-mutating Bash commands that touch paths OUTSIDE `<PROJECT_ROOT>/.okstra/**` and outside the assigned run-artifact paths. Writes to worker prompts, audit sidecars, team-state, the final-report `data.json`, and rendered reports under the run directory are allowed okstra artifacts. Any source/schema/deployment mutation means the run has crossed into implementation and MUST be re-routed; do NOT silently strip the evidence.
59
74
  - Cross-verification mode:
@@ -69,7 +69,11 @@
69
69
  - writing artifacts anywhere except `<PROJECT_ROOT>/.okstra/` — the run's `reports/` directory is the canonical location for this phase
70
70
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
71
71
  {{INCLUDE:_clarification-recommendation.md}}
72
- - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above.
72
+ - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above. **Enforced:** `validators/validate-run.py` `_validate_clarification_evidence_note` requires the note to exist and to be either a concrete `<path>:<line>` or the `none — <reason>` form. The check proves the note exists, not that the lookup happened — a false `none` is caught by review, but it is now an attributable claim rather than a silent omission.
73
+ - **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a *rejected* option blocked a plan with the same weight as a business decision. A row earns `Blocks=approval` only when, **absent an answer, the `implementation` run would produce wrong or unsafe code**. Apply the test to the row's consequence, not its topic:
74
+ - admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
75
+ - NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything inside an option the plan does not recommend, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
76
+ - A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
73
77
  - Section heading contract (BLOCKING — validator scans for these literal English substrings):
74
78
  - The final report MUST include section headings containing each of the following exact strings — this list mirrors `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.) Three further headings are enforced elsewhere, not by that scan: `Stage Exit Contract` and `Stage Validation` are per-stage subsections checked by `validators/validate-implementation-plan-stages.py`, and `Implementation Design Preparation` is enforced and rendered by `schemas/final-report-v1.0.schema.json` plus `templates/reports/final-report.template.md` from its required data block.
75
79
  - Korean translations are allowed in parentheses (e.g. `### Recommended Option (Korean gloss)`), but the English keyword must be present verbatim in the heading line.
@@ -105,7 +109,15 @@
105
109
  - **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
106
110
  - `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. When the verification body is a test spec, author it with the project's own test framework (devDependency) invoked via a discovery override at `<task_root>/qa/scripts/` (jest: `--config <project config> --roots <task_root>/qa/scripts`) — never hand-roll `describe`/`expect` and never widen the project's own test config; for TypeScript specs also write `<task_root>/qa/scripts/tsconfig.json` extending the project tsconfig with the runner's `types` entry so editors resolve the file.
107
111
  - `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
108
- The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result gate (each script's `QA-RESULT`) is enforced by the verifier Tier3 + validate-run.
112
+ - **External QA outcome guideline:** after satisfying the mandatory script,
113
+ manifest, and S11 declarations above, an entry whose `requires` contains
114
+ `db`, `http`, or `external` should document the exact `runCommand`,
115
+ prerequisites, requirement IDs, and expected `QA-RESULT`. Okstra may start the
116
+ environment and run it automatically, but `FAIL`, missing evidence, or an
117
+ unavailable environment is a user-owned follow-up, never a plan approval or
118
+ later run blocker. `requires=[]` and `requires=[io]` remain blocking.
119
+ Remote IO should also declare `external`.
120
+ The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result policy is evaluated by `conformance.py` and `validate-run.py`.
109
121
  - `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
110
122
  - `### Stage Validation` — pre / mid / post exact commands or observable outcomes for this stage only.
111
123
  - **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal.
@@ -133,7 +145,7 @@
133
145
  - **The reach of this gate — do not over-trust it.** What is mechanically enforced is the *form* of each source, that a `brief:` heading literally exists, that a `derived:` chain terminates without cycling, and that no stage is uncited. What is **not** enforced is whether the cited source genuinely demands the requirement. Every brief carries the same generic headings (`## Acceptance Criteria`, `## Context`, …), so attaching invented work to a real heading still parses clean. The gate's value is that it forces every item to name an origin and makes fabrication explicit and auditable — judging whether that origin actually demands the item remains a reviewer / `DISAGREE(f)` responsibility, and passing this gate is never evidence that the scope is justified.
134
146
  - **Stage citation format:** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These all read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; ranges (`Stages 1-3`, `Stages 1 to 3`, `Stages 1 through 3`); and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the recommended option, step 4` cites nothing. **Enforced:** `validators/validate-run.py` `_validate_stage_has_requirement` fails the plan when any Stage Map stage is cited by no coverage row.
135
147
  - Because that reader only sees prose, it cannot tell a citation from a mention: `Stage 1 (superseded by Stage 2)` still counts Stage 1 as cited, and one row citing `Stages 1-64` rubber-stamps every stage in the map. Cite the stages a requirement is actually satisfied by — not stages merely mentioned, and never a blanket range standing in for the work of checking.
136
- - **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. For `covered`, `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, `blocked C-NNN`, or `documented-deviation`. A deviation records the concrete alternative in `coveredBy`, non-empty unique `decisionRefs` (`C-NNN` clarification IDs and/or `D-NNNN` decision-draft numbers), and `approvalDisposition: accepted|blocked C-NNN`. `accepted` is valid only when a referenced clarification is user-confirmed (`answered|resolved` with non-empty `userInput`); `blocked C-NNN` is valid only when that same-report clarification is `open` and `Blocks=approval`. **Enforced:** schema `$defs.ImplementationRequirementCoverageRow` plus `validators/validate-run.py` `_validate_requirement_deviations`; the exact `P-Req-*` queue still comes from `scripts/okstra_ctl/plan_items.py`. If any row is `gap`, plain `blocked C-NNN`, or a deviation whose approval disposition is blocked, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
148
+ - **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. For `covered`, `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, `blocked C-NNN`, or `documented-deviation`. A deviation records the concrete alternative in `coveredBy`, non-empty unique `decisionRefs` (`C-NNN` clarification IDs and/or `D-NNNN` decision-draft numbers), and `approvalDisposition: accepted|blocked C-NNN`. `accepted` is valid only when a referenced clarification is user-confirmed (`answered|resolved` with non-empty `userInput`); `blocked C-NNN` is valid only when that same-report clarification is `open` and `Blocks=approval`. **Enforced:** schema `$defs.ImplementationRequirementCoverageRow` plus `validators/validate-run.py` `_validate_requirement_deviations`; the exact `P-Req-*` queue still comes from `scripts/okstra_ctl/plan_items.py`. If any row is `gap`, plain `blocked C-NNN`, or a deviation whose approval disposition is blocked, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`, and record `coverage-gap` in `planBodyVerification.gateBlockedBy` so the blocking value does not silently read as a worker disagreement. **Exception — no double counting:** a row whose `blocked C-NNN` cites a clarification that *this same run's* §5.5.9 round promoted from a `majority-disagree` item is not an independent blocker; that blocker is already counted as the plan item, and re-counting it makes the run block on a clarification it just authored and carries the row into the next run as a fresh blocker. **Enforced:** `validators/validate-run.py` `_validate_gate_blocked_by` (fails a passing gate with a blocking coverage row) and `_independent_coverage_blockers` (the same-run exclusion).
137
149
  - **Review-rule compliance plan:** when a project-local review rule pack is found, each Option Candidate MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
138
150
  - the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
139
151
  - the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
@@ -147,7 +159,7 @@
147
159
  ```
148
160
 
149
161
  An `AGREE` note records the counterexample considered and its exclusion reason. If the judgement needs unavailable external material, record `verification-error`, not `DISAGREE`. **Enforced:** `validators/validate-run.py` `_validate_plan_item_extraction_completeness` compares the exact deterministic set, independently rejecting missing, unexpected, and duplicate plan-item IDs, including `P-Prep-*`.
150
- - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through one report-writer self-fix pass before being promoted to a user clarification (`prompts/lead/plan-body-verification.md` "Self-fix round"). Only items not resolved by self-fix, or that are majority-`needs-user-input`, become `Blocks=approval` clarifications. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails a planner-fixable majority item promoted without a self-fix as `contract-violated`.
162
+ - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through report-writer self-fix rounds (`prompts/lead/plan-body-verification.md` "Self-fix round"). A `planner-fixable` item that survives the budget is **not** promoted to the user — it becomes a Working Assumption in `## 5. Missing Information and Risks` and folds into `passed-with-dissent`, because a defect the planner could have fixed is not a user decision. Only majority-`needs-user-input` items, and correctness-critical defects (`DISAGREE` kinds `a` / `d`, or `f` on `P-Req-*`) regardless of fixability, become `Blocks=approval` clarifications. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails a planner-fixable majority item promoted without a self-fix as `contract-violated`.
151
163
  - **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
152
164
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
153
165
  2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
@@ -830,9 +830,40 @@ def _implementation_executor_tail(
830
830
  selfcheck_path = profiles / "_implementation-self-check.md"
831
831
  if selfcheck_path.is_file():
832
832
  parts.append(selfcheck_path.read_text(encoding="utf-8"))
833
+ parts.extend(_clarification_response_section(manifest, active_context, project_root))
833
834
  return "\n\n".join(parts)
834
835
 
835
836
 
837
+ def _clarification_response_section(
838
+ manifest: Mapping[str, Any],
839
+ active_context: Mapping[str, Any],
840
+ project_root: Path,
841
+ ) -> list[str]:
842
+ """The user's answers, inlined for a CLI executor.
843
+
844
+ A CLI worker runs in its own process and receives only the prompt text, so
845
+ a path reference never reaches it. Passing the clarification response as a
846
+ path meant the executor implemented the pre-answer plan while the report
847
+ described the answers as carried in — the user answered and nothing
848
+ downstream acted on it.
849
+ """
850
+ relative = _instruction_path(manifest, active_context, "clarificationResponsePath")
851
+ if not relative:
852
+ return []
853
+ path = _resolve_project_path(project_root, relative)
854
+ if not path.is_file():
855
+ return []
856
+ body = path.read_text(encoding="utf-8").strip()
857
+ if not body:
858
+ return []
859
+ return [
860
+ "# Clarification answers carried in (authoritative)\n\n"
861
+ "The user answered these before this run. Where an answer conflicts "
862
+ "with the approved plan text, the answer wins — implement the answer "
863
+ f"and say so in your result.\n\nSource: `{relative}`\n\n{body}"
864
+ ]
865
+
866
+
836
867
  def _resolve_report_language(
837
868
  project_root: Path,
838
869
  manifest: Mapping[str, Any],
@@ -16,6 +16,23 @@ from dataclasses import dataclass
16
16
 
17
17
  # diff 가 건드린 표면과 대조할 capability 태그 화이트리스트.
18
18
  CAPABILITY_WHITELIST: tuple[str, ...] = ("db", "io", "http", "external")
19
+ EXTERNAL_ADVISORY_CAPABILITIES: frozenset[str] = frozenset(
20
+ {"db", "http", "external"}
21
+ )
22
+
23
+
24
+ def is_advisory_conformance_entry(entry: object) -> bool:
25
+ """Return whether one entry depends on user-owned external QA."""
26
+ if not isinstance(entry, dict):
27
+ return False
28
+ requires = entry.get("requires")
29
+ if not isinstance(requires, list):
30
+ return False
31
+ return bool(
32
+ EXTERNAL_ADVISORY_CAPABILITIES.intersection(
33
+ capability for capability in requires if isinstance(capability, str)
34
+ )
35
+ )
19
36
 
20
37
 
21
38
  def _check_nonempty_str(value: object, path: str, errors: list[str]) -> bool:
@@ -138,9 +155,9 @@ def parse_qa_result(stdout: str) -> QaResult:
138
155
  @dataclass
139
156
  class ConformanceVerdict:
140
157
  stage_key: str
141
- status: str # "PASS" | "BLOCKING" | "WAIVED" | "EXEMPT"
142
- ok: bool # 진행 허용 여부 (PASS/WAIVED/EXEMPT True)
143
- conditional: bool # WAIVED 때만 True conformance 미검증(사용자 확인)
158
+ status: str # "PASS" | "ADVISORY" | "BLOCKING" | "WAIVED" | "EXEMPT"
159
+ ok: bool # True when the gate permits progress.
160
+ conditional: bool # True only for a user-confirmed blocking-entry waiver.
144
161
  message: str
145
162
 
146
163
 
@@ -152,21 +169,24 @@ def _waiver_covers_entry(waiver: dict, entry: dict) -> bool:
152
169
  요구하는 모든 capability(`requires`)가 scope 안에 들어올 때만 entry 가 완전히
153
170
  가려진다. scope 밖 capability 를 entry 가 건드리면 waiver 가 덮지 못하므로
154
171
  결과로 정상 게이트해야 한다(예: `db` 만 waive 한 run 에서 `http` 는 계속 게이트)."""
172
+ requires = entry.get("requires")
173
+ required = {c for c in requires if isinstance(c, str)} if isinstance(requires, list) else set()
174
+ if not required:
175
+ return False
155
176
  scope = waiver.get("scope")
156
177
  if not isinstance(scope, list) or not scope:
157
178
  return True
158
- requires = entry.get("requires")
159
- required = {c for c in requires if isinstance(c, str)} if isinstance(requires, list) else set()
160
179
  return required.issubset({c for c in scope if isinstance(c, str)})
161
180
 
162
181
 
163
182
  def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
164
- """단일 stage entry + 실행 결과(`QaResult | None`)로 게이트 판정.
183
+ """Determine the gate verdict from one stage entry and its result.
165
184
 
166
- 우선순위: exemption (entry 덮는) waiver 결과 평가. 미실행/MISSING/FAIL
167
- BLOCKING. 면제·waiver 형태 검증은 `validate_conformance_manifest`
168
- 이미 보장한다. capability-scoped waiver scope entry.requires 를 전부
169
- 덮을 때만 적용되고, 그렇지 않으면 결과로 정상 게이트한다(`_waiver_covers_entry`).
185
+ Precedence is exemption, advisory waiver, blocking waiver, then result
186
+ evaluation. A PASS result passes. For unexecuted, missing, or failed results,
187
+ external-capability entries are ADVISORY with user-owned follow-up; all other
188
+ entries are BLOCKING. Manifest validation already guarantees valid exemption
189
+ and waiver shapes.
170
190
  """
171
191
  key = entry.get("stageKey", "<unknown>")
172
192
  exemption = entry.get("exemption")
@@ -175,8 +195,15 @@ def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
175
195
  key, "EXEMPT", True, False,
176
196
  f"conformance exempted: {exemption.get('reason', '')}",
177
197
  )
198
+ advisory = is_advisory_conformance_entry(entry)
178
199
  waiver = entry.get("waiver")
179
- if waiver and _waiver_covers_entry(waiver, entry):
200
+ if advisory and waiver and _waiver_covers_entry(waiver, entry):
201
+ return ConformanceVerdict(
202
+ key, "ADVISORY", True, False,
203
+ f"external conformance not run by user waiver: "
204
+ f"{waiver.get('reason', '')}",
205
+ )
206
+ if not advisory and waiver and _waiver_covers_entry(waiver, entry):
180
207
  return ConformanceVerdict(
181
208
  key, "WAIVED", True, True,
182
209
  f"conformance waived by {waiver.get('acknowledgedBy', '?')}: "
@@ -185,6 +212,17 @@ def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
185
212
  overall = getattr(result, "overall", None) # None when result is None → "never ran"
186
213
  if overall == "PASS":
187
214
  return ConformanceVerdict(key, "PASS", True, False, "conformance PASS")
215
+ if advisory:
216
+ if overall is None:
217
+ reason = "external conformance did not run"
218
+ elif overall == "MISSING":
219
+ reason = "external conformance emitted no QA-RESULT marker"
220
+ else:
221
+ reason = f"external conformance {overall}"
222
+ return ConformanceVerdict(
223
+ key, "ADVISORY", True, False,
224
+ f"{reason} (user-owned follow-up; non-blocking)",
225
+ )
188
226
  if overall is None:
189
227
  return ConformanceVerdict(
190
228
  key, "BLOCKING", False, False,
@@ -292,12 +292,18 @@ def run_finalize(
292
292
  ctx: FinalizeContext,
293
293
  *,
294
294
  before_step: Callable[[str], None] | None = None,
295
+ only: Sequence[str] | None = None,
295
296
  ) -> dict[str, Any]:
296
297
  """Run the Phase 7 steps in order, stopping at the first non-zero exit.
297
298
 
298
299
  ``before_step`` fires immediately before each step is spawned, letting an
299
300
  adapter settle state the step will read (the Codex adapter marks the report
300
301
  writer `completed` before `validate-run` inspects team-state).
302
+
303
+ ``only`` restricts the run to the named steps, preserving contractual
304
+ order. The last step (`validate-run`) is the one that usually fails, and
305
+ re-running the whole sequence to retry it repeats three idempotent steps
306
+ at full token and wall-clock cost.
301
307
  """
302
308
  steps: list[dict[str, Any]] = []
303
309
  try:
@@ -305,6 +311,17 @@ def run_finalize(
305
311
  except FinalizeError as exc:
306
312
  return {"ok": False, "reason": str(exc), "steps": steps}
307
313
 
314
+ if only:
315
+ selected = set(only)
316
+ unknown = sorted(selected - set(STEP_ORDER))
317
+ if unknown:
318
+ return {
319
+ "ok": False,
320
+ "reason": f"unknown finalize step(s): {', '.join(unknown)}",
321
+ "steps": steps,
322
+ }
323
+ commands = [(name, cmd) for name, cmd in commands if name in selected]
324
+
308
325
  for name, command in commands:
309
326
  if before_step is not None:
310
327
  before_step(name)
@@ -374,9 +391,34 @@ def _parser() -> argparse.ArgumentParser:
374
391
  default="",
375
392
  help="defaults to the run manifest's teamStatePath",
376
393
  )
394
+ parser.add_argument(
395
+ "--only",
396
+ action="append",
397
+ default=[],
398
+ choices=list(STEP_ORDER),
399
+ help=(
400
+ "run only these steps (repeatable, contractual order preserved). "
401
+ "Use `--only validate-run` to retry the step that usually fails "
402
+ "without repeating the three idempotent ones before it."
403
+ ),
404
+ )
377
405
  return parser
378
406
 
379
407
 
408
+ def _step_summary_lines(result: Mapping[str, Any]) -> list[str]:
409
+ """One human-readable line per step, so the outcome is legible without
410
+ parsing the JSON payload."""
411
+ lines = []
412
+ for step in result.get("steps") or []:
413
+ code = step.get("exitCode")
414
+ mark = "ok " if code == 0 else "FAIL"
415
+ lines.append(f" [{mark}] {step.get('name')} (exit {code})")
416
+ for name in STEP_ORDER:
417
+ if not any(s.get("name") == name for s in (result.get("steps") or [])):
418
+ lines.append(f" [skip] {name}")
419
+ return lines
420
+
421
+
380
422
  def main(argv: Sequence[str] | None = None) -> int:
381
423
  args = _parser().parse_args(argv)
382
424
  try:
@@ -384,10 +426,19 @@ def main(argv: Sequence[str] | None = None) -> int:
384
426
  except FinalizeError as exc:
385
427
  print(f"error: {exc}", file=sys.stderr)
386
428
  return 2
387
- result = run_finalize(ctx)
429
+ result = run_finalize(ctx, only=args.only or None)
388
430
  print(json.dumps(result, indent=2, ensure_ascii=False))
431
+ print("finalize steps:", file=sys.stderr)
432
+ for line in _step_summary_lines(result):
433
+ print(line, file=sys.stderr)
389
434
  if not result["ok"]:
390
435
  print(f"error: {result['reason']}", file=sys.stderr)
436
+ if result.get("steps"):
437
+ print(
438
+ "retry just the failing step with "
439
+ f"`--only {result['steps'][-1].get('name')}`",
440
+ file=sys.stderr,
441
+ )
391
442
  return 1
392
443
  return 0
393
444
 
@@ -1788,6 +1788,11 @@ def _persist_run_inputs(
1788
1788
  brief_relative: str,
1789
1789
  ) -> None:
1790
1790
  """이 run 의 입력 스냅샷(run-inputs-*.json)을 run-manifests 디렉터리에 기록."""
1791
+ approved_plan_path = (
1792
+ str(Path(inp.approved_plan_path).resolve())
1793
+ if inp.approved_plan_path
1794
+ else ""
1795
+ )
1791
1796
  run_inputs_path = write_run_inputs(
1792
1797
  project_root=Path(inp.project_root),
1793
1798
  run_manifests_dir=Path(ctx["RUN_MANIFESTS_DIR"]),
@@ -1805,7 +1810,7 @@ def _persist_run_inputs(
1805
1810
  "reportWriterModel": models.rw.display,
1806
1811
  "executor": models.executor_provider,
1807
1812
  "relatedTasks": inp.related_tasks_raw,
1808
- "approvedPlanPath": inp.approved_plan_path,
1813
+ "approvedPlanPath": approved_plan_path,
1809
1814
  "clarificationResponsePath": inp.clarification_response_path,
1810
1815
  "renderOnly": inp.render_only,
1811
1816
  },
@@ -2363,8 +2368,15 @@ def main(argv: list[str]) -> int:
2363
2368
  p.add_argument("--critic", default="")
2364
2369
  p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
2365
2370
  p.add_argument("--approved-plan", default="", dest="approved_plan_path")
2366
- p.add_argument("--qa-waiver", default="", dest="qa_waiver",
2367
- help='Stage conformance 우회: "<stageKey>:<reason>" (사용자 확인형, 매니페스트 entry.waiver 기록)')
2371
+ p.add_argument(
2372
+ "--qa-waiver",
2373
+ default="",
2374
+ dest="qa_waiver",
2375
+ help=(
2376
+ 'User-recorded stage conformance waiver: "<stageKey>:<reason>". '
2377
+ "Normally used only for blocking local io entries; external QA is advisory."
2378
+ ),
2379
+ )
2368
2380
  p.add_argument(
2369
2381
  "--stage", default="auto", dest="stage",
2370
2382
  help=(
@@ -6,7 +6,11 @@ from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  from typing import Any, Mapping, Sequence
8
8
 
9
- from .worker_prompt_policy import PromptPlan, resolve_prompt_plan_for_manifest
9
+ from .worker_prompt_policy import (
10
+ ERRORS_PATH_HEADERS,
11
+ PromptPlan,
12
+ resolve_prompt_plan_for_manifest,
13
+ )
10
14
 
11
15
 
12
16
  MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
@@ -35,8 +39,7 @@ _NON_BODY_PREFIXES = (
35
39
  "**Audit sidecar path:**",
36
40
  "Assigned worker prompt history path:",
37
41
  "**Worker Preamble Path:**",
38
- "**Errors log path:**",
39
- "**Errors sidecar path:**",
42
+ *ERRORS_PATH_HEADERS,
40
43
  "**Read scope:**",
41
44
  "**Worktree:**",
42
45
  "**Verification scope:**",
@@ -231,8 +234,15 @@ def _validate_prompt_for_plan(
231
234
  ) -> list[str]:
232
235
  errors: list[str] = []
233
236
  _require_non_empty_header(text, _WORKER_ERROR_CONTRACT_HEADER, errors)
237
+ # The compact final-verification contract below returns early, so the plan's
238
+ # own required headers must be checked before that branch or they never are.
239
+ for prefix in plan.required_headers:
240
+ _require_non_empty_header(text, prefix, errors)
234
241
  if manifest.get("taskType") == "final-verification" and plan.audience == "analysis":
235
- return [*errors, *validate_final_verification_initial_prompt(text)]
242
+ # Compact target identity re-checks headers the loop above already
243
+ # required, so drop the repeats and report each violation once.
244
+ compact = validate_final_verification_initial_prompt(text)
245
+ return errors + [error for error in compact if error not in errors]
236
246
  if not plan.allow_coding_preflight:
237
247
  _reject_literal(
238
248
  text,
@@ -240,8 +250,6 @@ def _validate_prompt_for_plan(
240
250
  "Coding preflight pack is forbidden for this prompt audience",
241
251
  errors,
242
252
  )
243
- for prefix in plan.required_headers:
244
- _require_non_empty_header(text, prefix, errors)
245
253
  if plan.audience == "analysis":
246
254
  packet_count = len(_PRIMARY_PACKET_RE.findall(text))
247
255
  if packet_count != 1: