@sun-asterisk/sungen 3.3.0-alpha.1 → 3.3.0-alpha.10

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 (463) hide show
  1. package/dist/capabilities/discover.d.ts +4 -0
  2. package/dist/capabilities/discover.d.ts.map +1 -1
  3. package/dist/capabilities/discover.js +16 -2
  4. package/dist/capabilities/discover.js.map +1 -1
  5. package/dist/cli/case-triage-hint.d.ts +14 -0
  6. package/dist/cli/case-triage-hint.d.ts.map +1 -0
  7. package/dist/cli/case-triage-hint.js +23 -0
  8. package/dist/cli/case-triage-hint.js.map +1 -0
  9. package/dist/cli/commands/audit.js +1 -1
  10. package/dist/cli/commands/audit.js.map +1 -1
  11. package/dist/cli/commands/contract-derive-command.d.ts +4 -2
  12. package/dist/cli/commands/contract-derive-command.d.ts.map +1 -1
  13. package/dist/cli/commands/contract-derive-command.js +19 -5
  14. package/dist/cli/commands/contract-derive-command.js.map +1 -1
  15. package/dist/cli/commands/contract.d.ts +3 -0
  16. package/dist/cli/commands/contract.d.ts.map +1 -1
  17. package/dist/cli/commands/contract.js +40 -7
  18. package/dist/cli/commands/contract.js.map +1 -1
  19. package/dist/cli/commands/delivery.d.ts.map +1 -1
  20. package/dist/cli/commands/delivery.js +59 -18
  21. package/dist/cli/commands/delivery.js.map +1 -1
  22. package/dist/cli/commands/gate.d.ts.map +1 -1
  23. package/dist/cli/commands/gate.js +2 -1
  24. package/dist/cli/commands/gate.js.map +1 -1
  25. package/dist/cli/commands/generate.d.ts.map +1 -1
  26. package/dist/cli/commands/generate.js +14 -2
  27. package/dist/cli/commands/generate.js.map +1 -1
  28. package/dist/cli/commands/journey.d.ts.map +1 -1
  29. package/dist/cli/commands/journey.js +6 -4
  30. package/dist/cli/commands/journey.js.map +1 -1
  31. package/dist/cli/commands/next.d.ts +7 -2
  32. package/dist/cli/commands/next.d.ts.map +1 -1
  33. package/dist/cli/commands/next.js +41 -10
  34. package/dist/cli/commands/next.js.map +1 -1
  35. package/dist/cli/commands/triage.d.ts +2 -1
  36. package/dist/cli/commands/triage.d.ts.map +1 -1
  37. package/dist/cli/commands/triage.js +45 -3
  38. package/dist/cli/commands/triage.js.map +1 -1
  39. package/dist/cli/commands/update.d.ts.map +1 -1
  40. package/dist/cli/commands/update.js +6 -2
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/index.js +5 -1
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/json-error-on-exit.d.ts +34 -0
  45. package/dist/cli/json-error-on-exit.d.ts.map +1 -0
  46. package/dist/cli/json-error-on-exit.js +63 -0
  47. package/dist/cli/json-error-on-exit.js.map +1 -0
  48. package/dist/cli/project-sungen-invocation.d.ts +13 -0
  49. package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
  50. package/dist/cli/project-sungen-invocation.js +95 -0
  51. package/dist/cli/project-sungen-invocation.js.map +1 -0
  52. package/dist/cli/run-command-hints.d.ts +9 -0
  53. package/dist/cli/run-command-hints.d.ts.map +1 -0
  54. package/dist/cli/run-command-hints.js +84 -0
  55. package/dist/cli/run-command-hints.js.map +1 -0
  56. package/dist/cli/waiting-for-you-block.d.ts +18 -0
  57. package/dist/cli/waiting-for-you-block.d.ts.map +1 -0
  58. package/dist/cli/waiting-for-you-block.js +22 -0
  59. package/dist/cli/waiting-for-you-block.js.map +1 -0
  60. package/dist/contract/accept-opaque-steps.d.ts +3 -0
  61. package/dist/contract/accept-opaque-steps.d.ts.map +1 -0
  62. package/dist/contract/accept-opaque-steps.js +114 -0
  63. package/dist/contract/accept-opaque-steps.js.map +1 -0
  64. package/dist/contract/approve-contract.d.ts +8 -1
  65. package/dist/contract/approve-contract.d.ts.map +1 -1
  66. package/dist/contract/approve-contract.js +6 -0
  67. package/dist/contract/approve-contract.js.map +1 -1
  68. package/dist/contract/build-contract-verdict.js +2 -2
  69. package/dist/contract/build-contract-verdict.js.map +1 -1
  70. package/dist/contract/build-trace-conformance.d.ts +5 -1
  71. package/dist/contract/build-trace-conformance.d.ts.map +1 -1
  72. package/dist/contract/build-trace-conformance.js +12 -14
  73. package/dist/contract/build-trace-conformance.js.map +1 -1
  74. package/dist/contract/classify-contract-change.d.ts +3 -2
  75. package/dist/contract/classify-contract-change.d.ts.map +1 -1
  76. package/dist/contract/classify-contract-change.js.map +1 -1
  77. package/dist/contract/classify-trace-conformance.d.ts +18 -3
  78. package/dist/contract/classify-trace-conformance.d.ts.map +1 -1
  79. package/dist/contract/classify-trace-conformance.js +64 -17
  80. package/dist/contract/classify-trace-conformance.js.map +1 -1
  81. package/dist/contract/contract-schema.d.ts +21 -0
  82. package/dist/contract/contract-schema.d.ts.map +1 -1
  83. package/dist/contract/contract-schema.js +25 -3
  84. package/dist/contract/contract-schema.js.map +1 -1
  85. package/dist/contract/contracts-lock-file.d.ts.map +1 -1
  86. package/dist/contract/contracts-lock-file.js +1 -0
  87. package/dist/contract/contracts-lock-file.js.map +1 -1
  88. package/dist/contract/derive-free-feature.d.ts +19 -0
  89. package/dist/contract/derive-free-feature.d.ts.map +1 -1
  90. package/dist/contract/derive-free-feature.js +40 -2
  91. package/dist/contract/derive-free-feature.js.map +1 -1
  92. package/dist/contract/project-contract-blockers.d.ts.map +1 -1
  93. package/dist/contract/project-contract-blockers.js +4 -2
  94. package/dist/contract/project-contract-blockers.js.map +1 -1
  95. package/dist/contract/record-contract-approval.d.ts +16 -1
  96. package/dist/contract/record-contract-approval.d.ts.map +1 -1
  97. package/dist/contract/record-contract-approval.js +29 -1
  98. package/dist/contract/record-contract-approval.js.map +1 -1
  99. package/dist/contract/render-free-gherkin.d.ts +2 -0
  100. package/dist/contract/render-free-gherkin.d.ts.map +1 -1
  101. package/dist/contract/render-free-gherkin.js +6 -0
  102. package/dist/contract/render-free-gherkin.js.map +1 -1
  103. package/dist/contract/resolve-unit-trace-identities.d.ts +12 -0
  104. package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -1
  105. package/dist/contract/resolve-unit-trace-identities.js +33 -49
  106. package/dist/contract/resolve-unit-trace-identities.js.map +1 -1
  107. package/dist/contract/trace/collect-trace-evidence.d.ts +16 -5
  108. package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -1
  109. package/dist/contract/trace/collect-trace-evidence.js +101 -43
  110. package/dist/contract/trace/collect-trace-evidence.js.map +1 -1
  111. package/dist/contract/trace/read-file-once.d.ts +20 -0
  112. package/dist/contract/trace/read-file-once.d.ts.map +1 -0
  113. package/dist/contract/trace/read-file-once.js +135 -0
  114. package/dist/contract/trace/read-file-once.js.map +1 -0
  115. package/dist/contract/trace/scenario-id-claims.d.ts +11 -0
  116. package/dist/contract/trace/scenario-id-claims.d.ts.map +1 -0
  117. package/dist/contract/trace/scenario-id-claims.js +89 -0
  118. package/dist/contract/trace/scenario-id-claims.js.map +1 -0
  119. package/dist/contract/trace/select-final-attempt.d.ts +4 -2
  120. package/dist/contract/trace/select-final-attempt.d.ts.map +1 -1
  121. package/dist/contract/trace/select-final-attempt.js +7 -4
  122. package/dist/contract/trace/select-final-attempt.js.map +1 -1
  123. package/dist/contract/trace/test-step-join.d.ts +3 -3
  124. package/dist/contract/trace/test-step-join.d.ts.map +1 -1
  125. package/dist/contract/trace/test-step-join.js +2 -2
  126. package/dist/contract/trace/test-step-join.js.map +1 -1
  127. package/dist/contract/trace/trace-binding.d.ts +3 -2
  128. package/dist/contract/trace/trace-binding.d.ts.map +1 -1
  129. package/dist/contract/trace/trace-binding.js +6 -3
  130. package/dist/contract/trace/trace-binding.js.map +1 -1
  131. package/dist/contract/trace/trace-file-reader.d.ts +14 -2
  132. package/dist/contract/trace/trace-file-reader.d.ts.map +1 -1
  133. package/dist/contract/trace/trace-file-reader.js +27 -45
  134. package/dist/contract/trace/trace-file-reader.js.map +1 -1
  135. package/dist/contract/trace/trace-freshness.d.ts +27 -0
  136. package/dist/contract/trace/trace-freshness.d.ts.map +1 -0
  137. package/dist/contract/trace/trace-freshness.js +146 -0
  138. package/dist/contract/trace/trace-freshness.js.map +1 -0
  139. package/dist/contract/trace/trace-identity-resolution.d.ts +2 -0
  140. package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -1
  141. package/dist/contract/trace/trace-identity-resolution.js +6 -3
  142. package/dist/contract/trace/trace-identity-resolution.js.map +1 -1
  143. package/dist/contract/trace/trace-run-record.d.ts +8 -3
  144. package/dist/contract/trace/trace-run-record.d.ts.map +1 -1
  145. package/dist/contract/trace/trace-run-record.js +42 -22
  146. package/dist/contract/trace/trace-run-record.js.map +1 -1
  147. package/dist/contract/trace/trace-steps.js +1 -1
  148. package/dist/contract/trace/trace-steps.js.map +1 -1
  149. package/dist/contract/trace/trace-zip-limits.d.ts +5 -1
  150. package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -1
  151. package/dist/contract/trace/trace-zip-limits.js +6 -2
  152. package/dist/contract/trace/trace-zip-limits.js.map +1 -1
  153. package/dist/contract/verify-approval-decisions.d.ts.map +1 -1
  154. package/dist/contract/verify-approval-decisions.js +9 -4
  155. package/dist/contract/verify-approval-decisions.js.map +1 -1
  156. package/dist/decisions/confirmed-decision-commands.d.ts +11 -0
  157. package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -1
  158. package/dist/decisions/confirmed-decision-commands.js +21 -3
  159. package/dist/decisions/confirmed-decision-commands.js.map +1 -1
  160. package/dist/decisions/decision-schema.d.ts +2 -1
  161. package/dist/decisions/decision-schema.d.ts.map +1 -1
  162. package/dist/decisions/decision-schema.js +2 -1
  163. package/dist/decisions/decision-schema.js.map +1 -1
  164. package/dist/decisions/git-head-trust.d.ts +12 -0
  165. package/dist/decisions/git-head-trust.d.ts.map +1 -1
  166. package/dist/decisions/git-head-trust.js +52 -0
  167. package/dist/decisions/git-head-trust.js.map +1 -1
  168. package/dist/decisions/human-confirmation.d.ts +2 -0
  169. package/dist/decisions/human-confirmation.d.ts.map +1 -1
  170. package/dist/decisions/human-confirmation.js +6 -0
  171. package/dist/decisions/human-confirmation.js.map +1 -1
  172. package/dist/decisions/legacy-waiver-migration.d.ts +8 -1
  173. package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -1
  174. package/dist/decisions/legacy-waiver-migration.js +13 -4
  175. package/dist/decisions/legacy-waiver-migration.js.map +1 -1
  176. package/dist/exporters/csv-exporter.d.ts +11 -2
  177. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  178. package/dist/exporters/csv-exporter.js +8 -4
  179. package/dist/exporters/csv-exporter.js.map +1 -1
  180. package/dist/exporters/matrix/export.d.ts +6 -1
  181. package/dist/exporters/matrix/export.d.ts.map +1 -1
  182. package/dist/exporters/matrix/export.js +11 -4
  183. package/dist/exporters/matrix/export.js.map +1 -1
  184. package/dist/exporters/matrix/xlsx-cells.js +1 -1
  185. package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
  186. package/dist/exporters/xlsx-exporter.d.ts +3 -2
  187. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  188. package/dist/exporters/xlsx-exporter.js +4 -4
  189. package/dist/exporters/xlsx-exporter.js.map +1 -1
  190. package/dist/graph/board-human-actions.d.ts +5 -0
  191. package/dist/graph/board-human-actions.d.ts.map +1 -0
  192. package/dist/graph/board-human-actions.js +83 -0
  193. package/dist/graph/board-human-actions.js.map +1 -0
  194. package/dist/graph/evaluate-graph.d.ts +21 -1
  195. package/dist/graph/evaluate-graph.d.ts.map +1 -1
  196. package/dist/graph/evaluate-graph.js +37 -9
  197. package/dist/graph/evaluate-graph.js.map +1 -1
  198. package/dist/graph/human-action.d.ts +21 -0
  199. package/dist/graph/human-action.d.ts.map +1 -0
  200. package/dist/graph/human-action.js +3 -0
  201. package/dist/graph/human-action.js.map +1 -0
  202. package/dist/graph/manual-triage-decisions.d.ts +19 -0
  203. package/dist/graph/manual-triage-decisions.d.ts.map +1 -0
  204. package/dist/graph/manual-triage-decisions.js +53 -0
  205. package/dist/graph/manual-triage-decisions.js.map +1 -0
  206. package/dist/graph/node-decl.d.ts +16 -0
  207. package/dist/graph/node-decl.d.ts.map +1 -1
  208. package/dist/graph/node-decl.js +4 -1
  209. package/dist/graph/node-decl.js.map +1 -1
  210. package/dist/graph/nodes/test-case-contract-nodes.d.ts +3 -1
  211. package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -1
  212. package/dist/graph/nodes/test-case-contract-nodes.js +22 -6
  213. package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -1
  214. package/dist/graph/nodes/test-case-nodes.d.ts +3 -0
  215. package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -1
  216. package/dist/graph/nodes/test-case-nodes.js +33 -4
  217. package/dist/graph/nodes/test-case-nodes.js.map +1 -1
  218. package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -1
  219. package/dist/graph/nodes/unit-audit-nodes.js +1 -0
  220. package/dist/graph/nodes/unit-audit-nodes.js.map +1 -1
  221. package/dist/graph/nodes/unit-contract-node.d.ts +2 -1
  222. package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -1
  223. package/dist/graph/nodes/unit-contract-node.js +16 -1
  224. package/dist/graph/nodes/unit-contract-node.js.map +1 -1
  225. package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -1
  226. package/dist/graph/nodes/unit-design-nodes.js +2 -1
  227. package/dist/graph/nodes/unit-design-nodes.js.map +1 -1
  228. package/dist/graph/test-case-results.d.ts +1 -0
  229. package/dist/graph/test-case-results.d.ts.map +1 -1
  230. package/dist/graph/test-case-results.js +27 -3
  231. package/dist/graph/test-case-results.js.map +1 -1
  232. package/dist/graph/test-case-satisfied.d.ts +5 -4
  233. package/dist/graph/test-case-satisfied.d.ts.map +1 -1
  234. package/dist/graph/test-case-satisfied.js +4 -0
  235. package/dist/graph/test-case-satisfied.js.map +1 -1
  236. package/dist/graph/test-case-state-shape.d.ts +7 -1
  237. package/dist/graph/test-case-state-shape.d.ts.map +1 -1
  238. package/dist/graph/unit-artifact-probes.d.ts.map +1 -1
  239. package/dist/graph/unit-artifact-probes.js +3 -1
  240. package/dist/graph/unit-artifact-probes.js.map +1 -1
  241. package/dist/graph/unit-results-freshness.d.ts +29 -0
  242. package/dist/graph/unit-results-freshness.d.ts.map +1 -0
  243. package/dist/graph/unit-results-freshness.js +213 -0
  244. package/dist/graph/unit-results-freshness.js.map +1 -0
  245. package/dist/harness/audit.d.ts +3 -2
  246. package/dist/harness/audit.d.ts.map +1 -1
  247. package/dist/harness/audit.js +4 -17
  248. package/dist/harness/audit.js.map +1 -1
  249. package/dist/harness/catalog/gate-rules.yaml +3 -3
  250. package/dist/harness/defect-draft.d.ts.map +1 -1
  251. package/dist/harness/defect-draft.js +12 -2
  252. package/dist/harness/defect-draft.js.map +1 -1
  253. package/dist/harness/defects.d.ts +14 -0
  254. package/dist/harness/defects.d.ts.map +1 -1
  255. package/dist/harness/defects.js +14 -10
  256. package/dist/harness/defects.js.map +1 -1
  257. package/dist/harness/flow-check.d.ts +9 -0
  258. package/dist/harness/flow-check.d.ts.map +1 -1
  259. package/dist/harness/flow-check.js +11 -0
  260. package/dist/harness/flow-check.js.map +1 -1
  261. package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -1
  262. package/dist/harness/gates/audit-gate-verdict.js +2 -1
  263. package/dist/harness/gates/audit-gate-verdict.js.map +1 -1
  264. package/dist/harness/gates/audit-input-hash.d.ts +2 -0
  265. package/dist/harness/gates/audit-input-hash.d.ts.map +1 -1
  266. package/dist/harness/gates/audit-input-hash.js +30 -2
  267. package/dist/harness/gates/audit-input-hash.js.map +1 -1
  268. package/dist/harness/gates/audit-report-verification.d.ts +17 -6
  269. package/dist/harness/gates/audit-report-verification.d.ts.map +1 -1
  270. package/dist/harness/gates/audit-report-verification.js +55 -21
  271. package/dist/harness/gates/audit-report-verification.js.map +1 -1
  272. package/dist/harness/gates/delivery-checklist.d.ts +3 -0
  273. package/dist/harness/gates/delivery-checklist.d.ts.map +1 -1
  274. package/dist/harness/gates/delivery-checklist.js +39 -6
  275. package/dist/harness/gates/delivery-checklist.js.map +1 -1
  276. package/dist/harness/gates/delivery-decision-gate.d.ts +4 -0
  277. package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -1
  278. package/dist/harness/gates/delivery-decision-gate.js +26 -1
  279. package/dist/harness/gates/delivery-decision-gate.js.map +1 -1
  280. package/dist/harness/gates/delivery-decision-step.d.ts +4 -1
  281. package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -1
  282. package/dist/harness/gates/delivery-decision-step.js +17 -2
  283. package/dist/harness/gates/delivery-decision-step.js.map +1 -1
  284. package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -1
  285. package/dist/harness/gates/in-run-decision-check.js +40 -12
  286. package/dist/harness/gates/in-run-decision-check.js.map +1 -1
  287. package/dist/harness/gates/migrated-waiver-import-bounds.d.ts +13 -0
  288. package/dist/harness/gates/migrated-waiver-import-bounds.d.ts.map +1 -0
  289. package/dist/harness/gates/migrated-waiver-import-bounds.js +141 -0
  290. package/dist/harness/gates/migrated-waiver-import-bounds.js.map +1 -0
  291. package/dist/harness/gates/migrated-waiver-review.d.ts +14 -4
  292. package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -1
  293. package/dist/harness/gates/migrated-waiver-review.js +78 -80
  294. package/dist/harness/gates/migrated-waiver-review.js.map +1 -1
  295. package/dist/harness/journey.d.ts +40 -0
  296. package/dist/harness/journey.d.ts.map +1 -1
  297. package/dist/harness/journey.js +155 -22
  298. package/dist/harness/journey.js.map +1 -1
  299. package/dist/harness/next-step.d.ts +19 -0
  300. package/dist/harness/next-step.d.ts.map +1 -1
  301. package/dist/harness/next-step.js +21 -0
  302. package/dist/harness/next-step.js.map +1 -1
  303. package/dist/harness/provenance.d.ts +29 -0
  304. package/dist/harness/provenance.d.ts.map +1 -1
  305. package/dist/harness/provenance.js +109 -9
  306. package/dist/harness/provenance.js.map +1 -1
  307. package/dist/harness/triage-manual-classification.d.ts +32 -0
  308. package/dist/harness/triage-manual-classification.d.ts.map +1 -0
  309. package/dist/harness/triage-manual-classification.js +56 -0
  310. package/dist/harness/triage-manual-classification.js.map +1 -0
  311. package/dist/harness/unit-paths.d.ts +8 -0
  312. package/dist/harness/unit-paths.d.ts.map +1 -1
  313. package/dist/harness/unit-paths.js +23 -1
  314. package/dist/harness/unit-paths.js.map +1 -1
  315. package/dist/hooks/bash-read-only-segment.d.ts +9 -0
  316. package/dist/hooks/bash-read-only-segment.d.ts.map +1 -0
  317. package/dist/hooks/bash-read-only-segment.js +168 -0
  318. package/dist/hooks/bash-read-only-segment.js.map +1 -0
  319. package/dist/hooks/bash-write-targets.d.ts +12 -2
  320. package/dist/hooks/bash-write-targets.d.ts.map +1 -1
  321. package/dist/hooks/bash-write-targets.js +122 -29
  322. package/dist/hooks/bash-write-targets.js.map +1 -1
  323. package/dist/hooks/guard-protected-paths.d.ts +4 -0
  324. package/dist/hooks/guard-protected-paths.d.ts.map +1 -1
  325. package/dist/hooks/guard-protected-paths.js +13 -3
  326. package/dist/hooks/guard-protected-paths.js.map +1 -1
  327. package/dist/hooks/guard-rules.d.ts +0 -17
  328. package/dist/hooks/guard-rules.d.ts.map +1 -1
  329. package/dist/hooks/guard-rules.js +79 -10
  330. package/dist/hooks/guard-rules.js.map +1 -1
  331. package/dist/hooks/guard-tty-spoof-rule.d.ts +3 -1
  332. package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -1
  333. package/dist/hooks/guard-tty-spoof-rule.js +3 -2
  334. package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
  335. package/dist/hooks/hand-back-core.d.ts +9 -4
  336. package/dist/hooks/hand-back-core.d.ts.map +1 -1
  337. package/dist/hooks/hand-back-core.js +24 -11
  338. package/dist/hooks/hand-back-core.js.map +1 -1
  339. package/dist/hooks/session-start-unit-state-hook.d.ts +2 -1
  340. package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -1
  341. package/dist/hooks/session-start-unit-state-hook.js +9 -4
  342. package/dist/hooks/session-start-unit-state-hook.js.map +1 -1
  343. package/dist/hooks/stop-next-step-hook.d.ts.map +1 -1
  344. package/dist/hooks/stop-next-step-hook.js +1 -0
  345. package/dist/hooks/stop-next-step-hook.js.map +1 -1
  346. package/dist/orchestrator/assets-drift.d.ts +5 -0
  347. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  348. package/dist/orchestrator/assets-drift.js +12 -3
  349. package/dist/orchestrator/assets-drift.js.map +1 -1
  350. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  351. package/dist/orchestrator/project-initializer.js +1 -0
  352. package/dist/orchestrator/project-initializer.js.map +1 -1
  353. package/dist/orchestrator/templates/ai-src/commands/create-test.md +6 -3
  354. package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
  355. package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
  356. package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  357. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
  358. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
  359. package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
  360. package/package.json +3 -3
  361. package/src/capabilities/discover.ts +15 -2
  362. package/src/cli/case-triage-hint.ts +31 -0
  363. package/src/cli/commands/audit.ts +1 -1
  364. package/src/cli/commands/contract-derive-command.ts +20 -8
  365. package/src/cli/commands/contract.ts +41 -7
  366. package/src/cli/commands/delivery.ts +56 -18
  367. package/src/cli/commands/gate.ts +2 -1
  368. package/src/cli/commands/generate.ts +13 -2
  369. package/src/cli/commands/journey.ts +8 -6
  370. package/src/cli/commands/next.ts +41 -12
  371. package/src/cli/commands/triage.ts +41 -3
  372. package/src/cli/commands/update.ts +6 -2
  373. package/src/cli/index.ts +5 -1
  374. package/src/cli/json-error-on-exit.ts +72 -0
  375. package/src/cli/project-sungen-invocation.ts +59 -0
  376. package/src/cli/run-command-hints.ts +47 -0
  377. package/src/cli/waiting-for-you-block.ts +27 -0
  378. package/src/contract/accept-opaque-steps.ts +114 -0
  379. package/src/contract/approve-contract.ts +10 -3
  380. package/src/contract/build-contract-verdict.ts +2 -2
  381. package/src/contract/build-trace-conformance.ts +11 -8
  382. package/src/contract/classify-contract-change.ts +3 -3
  383. package/src/contract/classify-trace-conformance.ts +76 -22
  384. package/src/contract/contract-schema.ts +31 -2
  385. package/src/contract/contracts-lock-file.ts +1 -0
  386. package/src/contract/derive-free-feature.ts +46 -3
  387. package/src/contract/project-contract-blockers.ts +4 -2
  388. package/src/contract/record-contract-approval.ts +41 -1
  389. package/src/contract/render-free-gherkin.ts +5 -0
  390. package/src/contract/resolve-unit-trace-identities.ts +35 -12
  391. package/src/contract/trace/collect-trace-evidence.ts +105 -36
  392. package/src/contract/trace/read-file-once.ts +95 -0
  393. package/src/contract/trace/scenario-id-claims.ts +60 -0
  394. package/src/contract/trace/select-final-attempt.ts +12 -5
  395. package/src/contract/trace/test-step-join.ts +6 -5
  396. package/src/contract/trace/trace-binding.ts +7 -4
  397. package/src/contract/trace/trace-file-reader.ts +34 -13
  398. package/src/contract/trace/trace-freshness.ts +110 -0
  399. package/src/contract/trace/trace-identity-resolution.ts +7 -4
  400. package/src/contract/trace/trace-run-record.ts +37 -15
  401. package/src/contract/trace/trace-steps.ts +1 -1
  402. package/src/contract/trace/trace-zip-limits.ts +6 -2
  403. package/src/contract/verify-approval-decisions.ts +9 -5
  404. package/src/decisions/confirmed-decision-commands.ts +25 -4
  405. package/src/decisions/decision-schema.ts +2 -1
  406. package/src/decisions/git-head-trust.ts +44 -0
  407. package/src/decisions/human-confirmation.ts +5 -0
  408. package/src/decisions/legacy-waiver-migration.ts +14 -3
  409. package/src/exporters/csv-exporter.ts +12 -4
  410. package/src/exporters/matrix/export.ts +11 -4
  411. package/src/exporters/matrix/xlsx-cells.ts +1 -1
  412. package/src/exporters/xlsx-exporter.ts +5 -4
  413. package/src/graph/board-human-actions.ts +80 -0
  414. package/src/graph/evaluate-graph.ts +45 -10
  415. package/src/graph/human-action.ts +33 -0
  416. package/src/graph/manual-triage-decisions.ts +55 -0
  417. package/src/graph/node-decl.ts +18 -0
  418. package/src/graph/nodes/test-case-contract-nodes.ts +28 -7
  419. package/src/graph/nodes/test-case-nodes.ts +37 -5
  420. package/src/graph/nodes/unit-audit-nodes.ts +2 -1
  421. package/src/graph/nodes/unit-contract-node.ts +20 -2
  422. package/src/graph/nodes/unit-design-nodes.ts +2 -1
  423. package/src/graph/test-case-results.ts +25 -4
  424. package/src/graph/test-case-satisfied.ts +7 -4
  425. package/src/graph/test-case-state-shape.ts +7 -2
  426. package/src/graph/unit-artifact-probes.ts +3 -1
  427. package/src/graph/unit-results-freshness.ts +165 -0
  428. package/src/harness/audit.ts +4 -13
  429. package/src/harness/catalog/gate-rules.yaml +3 -3
  430. package/src/harness/defect-draft.ts +13 -2
  431. package/src/harness/defects.ts +22 -9
  432. package/src/harness/flow-check.ts +11 -0
  433. package/src/harness/gates/audit-gate-verdict.ts +2 -1
  434. package/src/harness/gates/audit-input-hash.ts +27 -3
  435. package/src/harness/gates/audit-report-verification.ts +57 -24
  436. package/src/harness/gates/delivery-checklist.ts +39 -7
  437. package/src/harness/gates/delivery-decision-gate.ts +29 -1
  438. package/src/harness/gates/delivery-decision-step.ts +19 -3
  439. package/src/harness/gates/in-run-decision-check.ts +38 -13
  440. package/src/harness/gates/migrated-waiver-import-bounds.ts +103 -0
  441. package/src/harness/gates/migrated-waiver-review.ts +90 -37
  442. package/src/harness/journey.ts +174 -21
  443. package/src/harness/next-step.ts +34 -1
  444. package/src/harness/provenance.ts +99 -11
  445. package/src/harness/triage-manual-classification.ts +76 -0
  446. package/src/harness/unit-paths.ts +19 -0
  447. package/src/hooks/bash-read-only-segment.ts +130 -0
  448. package/src/hooks/bash-write-targets.ts +109 -23
  449. package/src/hooks/guard-protected-paths.ts +13 -3
  450. package/src/hooks/guard-rules.ts +37 -18
  451. package/src/hooks/guard-tty-spoof-rule.ts +6 -3
  452. package/src/hooks/hand-back-core.ts +24 -12
  453. package/src/hooks/session-start-unit-state-hook.ts +9 -4
  454. package/src/hooks/stop-next-step-hook.ts +1 -0
  455. package/src/orchestrator/assets-drift.ts +12 -5
  456. package/src/orchestrator/project-initializer.ts +1 -0
  457. package/src/orchestrator/templates/ai-src/commands/create-test.md +6 -3
  458. package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
  459. package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
  460. package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  461. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
  462. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
  463. package/src/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The human-only actions the graph does not hold ("Waiting for you", #954) — asked by the journey
3
+ * board and the delivery's decision check, read through THEIR functions (never restated here):
4
+ *
5
+ * waive a waiver the board re-surfaces: stale (the audit changed since) or untrusted (not
6
+ * in git HEAD) — readJourneyHumanAsks, the reconcile `runJourney` commits
7
+ * decide a gate-override that relaxes nothing because no human confirmed it (ineffective) —
8
+ * re-recorded with `journey --decide`, superseding it (loadUnitGateOverrides)
9
+ * signoff every other obligation holds — assertCanSignOff, as `journey --signoff` checks it
10
+ * accept-in-run a decision recorded during the run — evaluateDeliveryDecisions, as `delivery` runs it
11
+ * The sign-off and accept-in-run only once the frontier was not cut (the unit has run and nothing
12
+ * blocks before it). Read-only and advisory: whatever cannot be read adds nothing.
13
+ */
14
+ import { formatCommand } from '../decisions/human-confirmation';
15
+ import { waiveCommand } from '../decisions/legacy-waiver-migration';
16
+ import { evaluateDeliveryDecisions } from '../harness/gates/delivery-decision-gate';
17
+ import { IN_RUN_RULE } from '../harness/gates/in-run-decision-check';
18
+ import { loadUnitGateOverrides, unitScopeOfDir } from '../harness/gates/unit-gate-overrides';
19
+ import { readJourneyHumanAsks, signoffCommand } from '../harness/journey';
20
+ import type { UnitState } from '../harness/next-step';
21
+ import type { HumanAction } from './human-action';
22
+ import { unitResultFiles } from './unit-artifact-probes';
23
+
24
+ function journeyActions(projectRoot: string, s: UnitState, cut: boolean): HumanAction[] {
25
+ const asks = readJourneyHumanAsks(projectRoot, s.unit);
26
+ const out: HumanAction[] = asks.redecide.map((w) => ({
27
+ kind: 'waive',
28
+ command: waiveCommand(s.unit, w.obligation, w.reason),
29
+ reason: w.why === 'stale'
30
+ ? `the waiver on "${w.title}" is stale — the evidence changed since it was given, so it no longer counts; if it still holds, a human gives it again on a terminal (or repair the obligation)`
31
+ : `the waiver on "${w.title}" is not in git HEAD (decision.in-run), so it is not counted — if it is yours, confirm it on a terminal`,
32
+ }));
33
+ if (!cut && asks.canSignOff && asks.signoff.status !== 'given') {
34
+ out.push({
35
+ kind: 'signoff',
36
+ command: signoffCommand(s.unit),
37
+ reason: asks.signoff.status === 'stale'
38
+ ? `the sign-off of ${asks.signoff.at} is stale — the suite changed since; review the queue (\`sungen journey --screen ${s.unit}\`) and sign off again`
39
+ : `every other obligation is satisfied or waived — the human sign-off is the one gate \`sungen gate --phase deliver\` still waits on; review the queue (\`sungen journey --screen ${s.unit}\`, ${asks.report.needsYou.length} item(s)), then sign`,
40
+ });
41
+ }
42
+ return out;
43
+ }
44
+
45
+ function unconfirmedOverrides(projectRoot: string, unitDir: string, s: UnitState): HumanAction[] {
46
+ return loadUnitGateOverrides(projectRoot, unitScopeOfDir(projectRoot, unitDir)).ineffective.map((o) => {
47
+ const { humanAccepted: _ignored, ...payload } = o.entry.payload ?? {};
48
+ return {
49
+ kind: 'decide',
50
+ command: formatCommand(['sungen', 'journey', '--screen', s.unit, '--decide', 'gate-override', '--text', o.entry.text, '--reason', o.entry.reason,
51
+ '--payload', JSON.stringify(payload), '--supersedes', o.decisionId]),
52
+ reason: `the gate-override ${o.decisionId}${o.rule ? ` (${o.rule})` : ''} relaxes nothing: ${o.reason} — if it is meant, a human records it again on a terminal, superseding it`,
53
+ };
54
+ });
55
+ }
56
+
57
+ function inRunActions(projectRoot: string, unitDir: string, s: UnitState): HumanAction[] {
58
+ const files = unitResultFiles(projectRoot, s.unit);
59
+ const refs = (files.length ? files : [null]).map((resultsPath) => ({ label: s.unit, unitName: s.unit, unitDir, resultsPath }));
60
+ const inRun = evaluateDeliveryDecisions(projectRoot, refs).inRun;
61
+ if (!inRun.length) return [];
62
+ const which = inRun.slice(0, 3).map((d) => `${d.kind} ${d.id}`).join(', ');
63
+ return [{
64
+ kind: 'accept-in-run',
65
+ command: formatCommand(['sungen', 'delivery', s.unit, '--accept-in-run']),
66
+ reason: `${inRun.length} decision(s) recorded during the run (${IN_RUN_RULE}): ${which}${inRun.length > 3 ? ', …' : ''} — \`sungen delivery\` refuses them until they are committed through review and the tests re-run, or a human accepts them on a terminal`,
67
+ }];
68
+ }
69
+
70
+ /** The board's and the delivery's asks of a human for this unit. `cut`: the frontier stopped at a blocking node. */
71
+ export function boardHumanActions(projectRoot: string, unitDir: string, s: UnitState, cut: boolean): HumanAction[] {
72
+ const out: HumanAction[] = [];
73
+ const add = (read: () => HumanAction[]): void => {
74
+ try { out.push(...read()); } catch { /* advisory: an unreadable board or decision adds nothing */ }
75
+ };
76
+ add(() => journeyActions(projectRoot, s, cut));
77
+ add(() => unconfirmedOverrides(projectRoot, unitDir, s));
78
+ if (!cut) add(() => inRunActions(projectRoot, unitDir, s));
79
+ return out;
80
+ }
@@ -4,13 +4,19 @@
4
4
  *
5
5
  * open active ∧ ¬satisfied (a test-case node is open when at least one case is)
6
6
  * frontier open ∧ (every `requires` satisfied ∨ ¬blocking)
7
- * cut an open blocking node ends the walk — nothing ranked after it is offered
7
+ * cut an open blocking node ends the walk — nothing ranked after it is offered, except a
8
+ * node's instances that `surfacesPastCut` (a hard contract verdict, #940), which follow
9
+ * the cutting step and never cut themselves
8
10
  *
9
11
  * `--case <id>` (`testCase` option): the frontier is that case's test-case nodes plus the blocking
10
12
  * unit nodes before them; non-blocking unit advice is left to the unit view. A `caseOnly` test-case
11
13
  * node speaks only there (its unit-level decision is in the unit view already).
14
+ *
15
+ * evaluateHumanActions reads the SAME walk for "Waiting for you" (#954): the human-only actions behind
16
+ * the offered steps, and whether the walk was cut.
12
17
  */
13
18
  import type { NextStep, UnitState } from '../harness/next-step';
19
+ import type { HumanAction } from './human-action';
14
20
  import type { NodeDecl, StepCore } from './node-decl';
15
21
  import type { TestCaseState } from './test-case-state-shape';
16
22
 
@@ -35,28 +41,57 @@ function openCases(node: NodeDecl, s: UnitState, tcStates: TestCaseState[] | und
35
41
  return tcStates.filter((tc) => (!only || tc.id === only) && node.active(s, tc) && !node.satisfied(s, tc));
36
42
  }
37
43
 
38
- export function evaluateGraph(
39
- nodes: readonly NodeDecl[], s: UnitState, tcStates?: TestCaseState[], opts: EvaluateOptions = {},
40
- ): NextStep[] {
44
+ /** One offered step and what produced it: the node, and the cases open at it (test-case nodes). */
45
+ interface Offered { node: NodeDecl; step: NextStep; cases: TestCaseState[] }
46
+
47
+ /** The walk both answers share: the offered steps in rank order, and whether an open blocking node cut it. */
48
+ function walkFrontier(nodes: readonly NodeDecl[], s: UnitState, tcStates: TestCaseState[] | undefined, opts: EvaluateOptions): { offered: Offered[]; cut: boolean; cutBy?: NodeDecl } {
41
49
  const ordered = [...nodes].sort((a, b) => a.rank - b.rank);
42
50
  const open = new Set<string>();
43
- const out: NextStep[] = [];
51
+ const offered: Offered[] = [];
52
+ let cut = false;
53
+ let cutBy: NodeDecl | undefined;
44
54
  for (const node of ordered) {
45
55
  if (!node.appliesTo.includes(s.kind) || (node.granularity === 'test-case' && node.caseOnly && !opts.testCase)) continue;
56
+ if (cut && !node.surfacesPastCut) continue;
46
57
  let step: NextStep | null = null;
58
+ let cases: TestCaseState[] = [];
47
59
  if (node.granularity === 'unit') {
48
- if (!node.active(s) || node.satisfied(s)) continue;
60
+ if (!node.active(s) || node.satisfied(s) || (cut && !node.surfacesPastCut!(s))) continue;
49
61
  if (!opts.testCase || node.blocking) step = withMeta(node, node.step(s));
50
62
  } else {
51
- const cases = openCases(node, s, tcStates, opts.testCase);
63
+ cases = openCases(node, s, tcStates, opts.testCase).filter((tc) => !cut || node.surfacesPastCut!(s, tc));
52
64
  if (!cases.length) continue;
53
65
  const single = Boolean(opts.testCase);
54
66
  step = withMeta(node, node.step(s, cases, single), single ? cases[0].id : undefined);
55
67
  }
56
68
  open.add(node.id);
57
69
  const ready = node.requires.every((r) => !open.has(r));
58
- if (step && (ready || !node.blocking)) out.push(step);
59
- if (node.blocking) break;
70
+ if (step && (cut || ready || !node.blocking)) offered.push({ node, step, cases });
71
+ if (node.blocking && !cut) { cut = true; cutBy = node; }
60
72
  }
61
- return out;
73
+ return { offered, cut, ...(cutBy ? { cutBy } : {}) };
74
+ }
75
+
76
+ export function evaluateGraph(
77
+ nodes: readonly NodeDecl[], s: UnitState, tcStates?: TestCaseState[], opts: EvaluateOptions = {},
78
+ ): NextStep[] {
79
+ return walkFrontier(nodes, s, tcStates, opts).offered.map((o) => o.step);
80
+ }
81
+
82
+ /**
83
+ * The HUMAN-ONLY actions of the same frontier ("Waiting for you", #954) — never a second walk with
84
+ * rules of its own: each offered step's node says what a person runs (its `human` hook), and a decide
85
+ * step without one is its own command. `cut`: an open blocking node ended the walk, so only what does
86
+ * not depend on it is offered (a hard verdict that surfaces past the cut), and the board's asks that
87
+ * need the unit past it (sign-off, accept-in-run) wait; `heldBy` is that node's title and `heldByCommand`
88
+ * its offered step's command (#966: "More appear once …" names what to run).
89
+ */
90
+ export function evaluateHumanActions(nodes: readonly NodeDecl[], s: UnitState, tcStates?: TestCaseState[]): { actions: HumanAction[]; cut: boolean; heldBy?: string; heldByCommand?: string } {
91
+ const { offered, cut, cutBy } = walkFrontier(nodes, s, tcStates, {});
92
+ const actions = offered.flatMap(({ node, step, cases }) => (node.human
93
+ ? node.human(s, step, cases)
94
+ : step.kind === 'decide' ? [{ command: step.command, reason: step.because, kind: 'decide' as const }] : []));
95
+ const heldByCommand = cutBy ? offered.find((o) => o.node === cutBy)?.step.command : undefined;
96
+ return { actions, cut, ...(cutBy ? { heldBy: cutBy.title } : {}), ...(heldByCommand ? { heldByCommand } : {}) };
62
97
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A HUMAN-ONLY action of a unit — what `sungen next` lists under "Waiting for you" (#954) and `--json`
3
+ * carries as `humanActions`. An agent presents these and never runs them: they approve, sign, waive,
4
+ * classify or accept something on a person's behalf, and most refuse without a typed confirmation.
5
+ *
6
+ * `command` is canonical (`sungen …`, or a slash command); the text renders it as the project runs
7
+ * sungen (`npx sungen` with a local install). Placeholders a human fills are quoted (`"<name>"`, `"…"`)
8
+ * so a copied command never turns into a shell redirection.
9
+ */
10
+ export type HumanActionKind =
11
+ | 'contract-approve' // approve (or re-approve) the Test Contract — `sungen contract approve`
12
+ | 'accept-opaque' // accept a reviewed opaque trace step — `contract approve --scenario <id> --accept-opaque`
13
+ | 'contract-commit' // commit, or restore, a lock / approval decision through review
14
+ | 'contract-review' // read the contract verdict on a terminal (it could not be built)
15
+ | 'accept-viewpoint' // accept the changed viewpoint baseline — `sungen audit --accept-viewpoint`
16
+ | 'defect-draft' // decide whether a product-defect candidate is a defect — `sungen defect draft`
17
+ | 'rerun-environment' // re-run the reds the environment caused, once a human confirms it is up
18
+ | 'triage-classify' // classify a red by hand as test-defect / environment — `sungen triage --case --as`
19
+ | 'signoff' // the single human gate — `sungen journey --signoff`
20
+ | 'waive' // give a waiver again — `sungen journey --waive`
21
+ | 'decide' // record a decision — `sungen journey --decide`
22
+ | 'accept-in-run'; // deliver despite decisions recorded during the run — `sungen delivery --accept-in-run`
23
+
24
+ export interface HumanAction {
25
+ command: string;
26
+ reason: string;
27
+ kind: HumanActionKind;
28
+ /**
29
+ * `command` is an instruction a person carries out by hand (commit through review, restore from git),
30
+ * not a command line to copy — the block marks it so, and never renders it as one.
31
+ */
32
+ instruction?: true;
33
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Manual triage classifications (#928) — a red `sungen triage` could not classify, or classified
3
+ * wrongly, judged by hand with `sungen triage --screen <u> --case <id> --as <class> --reason "…"`
4
+ * (harness/triage-manual-classification.ts writes them). Each is a `triage-classification` decision
5
+ * in the unit's `decisions/`, read here for the test-case state (test-case-results.ts):
6
+ *
7
+ * payload { case: <id>, class: product-candidate | test-defect | environment, humanConfirmed? }
8
+ * evidence_hash the hash of the red final attempts it judged (TestCaseState.evidence): a re-run
9
+ * makes a new red, and the classification no longer applies to it
10
+ * in force verifies (read-decisions), not superseded, bound to today's red — and, for
11
+ * test-defect / environment, typed by a human on a terminal (`humanConfirmed`, only
12
+ * ever set after the typed confirmation): those classes can hide a product defect,
13
+ * so an entry without it is ignored (fail closed). product-candidate is stricter than
14
+ * any automatic class, so anyone may record it.
15
+ * The latest classification in force wins over the automatic one.
16
+ */
17
+ import { readDecisions } from '../decisions/read-decisions';
18
+ import { unitScopeOfDir } from '../harness/gates/unit-gate-overrides';
19
+
20
+ export const TRIAGE_DECISION_KIND = 'triage-classification' as const;
21
+ export type ManualTriageClass = 'product-candidate' | 'test-defect' | 'environment';
22
+ /** `--as` word → the triage class it records. */
23
+ export const MANUAL_TRIAGE_AS: Readonly<Record<string, ManualTriageClass>> = { product: 'product-candidate', 'test-defect': 'test-defect', environment: 'environment' };
24
+ /** Classes that can hide a product defect: a human types a confirmation on a terminal. */
25
+ export const HUMAN_ONLY_TRIAGE: ReadonlySet<ManualTriageClass> = new Set(['test-defect', 'environment']);
26
+
27
+ export interface ManualClassification {
28
+ decision: string;
29
+ caseId: string;
30
+ class: ManualTriageClass;
31
+ evidence: string;
32
+ reason: string;
33
+ by: string;
34
+ }
35
+
36
+ const isClass = (v: unknown): v is ManualTriageClass => Object.values(MANUAL_TRIAGE_AS).includes(v as ManualTriageClass);
37
+
38
+ /** The unit's classifications that can be in force, oldest first (read-decisions orders by time). */
39
+ export function manualClassifications(projectRoot: string, unitDir: string): ManualClassification[] {
40
+ const scope = unitScopeOfDir(projectRoot, unitDir);
41
+ if (!scope) return [];
42
+ return readDecisions(projectRoot, scope, { kind: TRIAGE_DECISION_KIND }).entries.flatMap((e) => {
43
+ const p = e.payload ?? {};
44
+ if (e.superseded_by || typeof p.case !== 'string' || !isClass(p.class) || typeof e.evidence_hash !== 'string') return [];
45
+ if (HUMAN_ONLY_TRIAGE.has(p.class) && p.humanConfirmed !== true) return [];
46
+ return [{ decision: e.id, caseId: p.case, class: p.class, evidence: e.evidence_hash, reason: e.reason, by: e.source.by }];
47
+ });
48
+ }
49
+
50
+ /** The latest classification of this case bound to this red, if any. */
51
+ export function manualClassificationFor(list: ManualClassification[], caseId: string, evidence: string | undefined): ManualClassification | undefined {
52
+ if (!evidence) return undefined;
53
+ for (let i = list.length - 1; i >= 0; i--) if (list[i].caseId === caseId && list[i].evidence === evidence) return list[i];
54
+ return undefined;
55
+ }
@@ -11,6 +11,7 @@
11
11
  * `sungen next <unit> --case <id>`).
12
12
  */
13
13
  import type { NextStep, UnitState } from '../harness/next-step';
14
+ import type { HumanAction, HumanActionKind } from './human-action';
14
15
  import type { TestCaseState } from './test-case-state-shape';
15
16
 
16
17
  export type NodeActor = 'ai' | 'human' | 'deterministic';
@@ -35,13 +36,28 @@ interface NodeDeclBase {
35
36
  gates: readonly string[];
36
37
  /** Artifacts the step produces, for a reader of the graph. */
37
38
  produces: readonly string[];
39
+ /**
40
+ * The HUMAN-ONLY actions behind an offered step ("Waiting for you", #954): what a person runs, with
41
+ * why. Without it a `decide` step is one action of kind `decide` and any other step is none. `cases`
42
+ * are the cases open at a test-case node (none for a unit node).
43
+ */
44
+ human?(s: UnitState, step: StepCore, cases: TestCaseState[]): HumanAction[];
38
45
  }
39
46
 
47
+ /** The `human` hook of a node whose decide step IS the human action: its command and reason, under `kind`. */
48
+ export const humanStep = (kind: HumanActionKind) => (_s: UnitState, step: StepCore): HumanAction[] =>
49
+ [{ command: step.command, reason: step.because, kind }];
50
+
40
51
  export interface UnitNodeDecl extends NodeDeclBase {
41
52
  granularity: 'unit';
42
53
  active(s: UnitState): boolean;
43
54
  satisfied(s: UnitState): boolean;
44
55
  step(s: UnitState): StepCore;
56
+ /**
57
+ * Offered even when a blocking node ranked before it cut the walk — only while this holds (#940: a
58
+ * hard contract verdict must not hide behind a stale or failing audit). It never cuts by itself.
59
+ */
60
+ surfacesPastCut?(s: UnitState): boolean;
45
61
  }
46
62
 
47
63
  export interface TestCaseNodeDecl extends NodeDeclBase {
@@ -55,6 +71,8 @@ export interface TestCaseNodeDecl extends NodeDeclBase {
55
71
  satisfied(s: UnitState, tc: TestCaseState): boolean;
56
72
  /** One step for `cases` (all open at this node): a unit-level fold, or exactly one case when `single`. */
57
73
  step(s: UnitState, cases: TestCaseState[], single: boolean): StepCore;
74
+ /** The cases offered even past a cut (see UnitNodeDecl.surfacesPastCut). */
75
+ surfacesPastCut?(s: UnitState, tc: TestCaseState): boolean;
58
76
  }
59
77
 
60
78
  export type NodeDecl = UnitNodeDecl | TestCaseNodeDecl;
@@ -11,11 +11,14 @@
11
11
  * tc.conformance its trace is in review: a `low` target identity do contract check --trace
12
12
  * · its final attempt did not pass (inconclusive) do run-test --ids
13
13
  * · a step no code can compare (opaque) decide contract check
14
- * Exempt cases (DOS-11.2) open nothing. A proposed case (never approved) asks nothing of its own: a
14
+ * tc.contract-check (a weaker / retargeted / lost check, or an uncommitted lock entry) and tc.unproven
15
+ * are hard verdicts: they are offered past a stale or failing audit (the cut at unit.audit-stale /
16
+ * unit.gate-fail), #940; tc.contract-approve (changed, not weaker) is review and waits behind it. Exempt cases (DOS-11.2) open nothing. A proposed case (never approved) asks nothing of its own: a
15
17
  * compiled test without a lock is never blocked, and the unit view offers the approval.
16
18
  */
17
- import { ALL_KINDS, StepCore, TestCaseNodeDecl } from '../node-decl';
19
+ import { ALL_KINDS, humanStep, StepCore, TestCaseNodeDecl } from '../node-decl';
18
20
  import type { UnitState } from '../../harness/next-step';
21
+ import type { HumanAction } from '../human-action';
19
22
  import type { TestCaseState } from '../test-case-state-shape';
20
23
  import { exemptionOf } from '../test-case-satisfied';
21
24
  import { idsArg, listed } from './test-case-nodes';
@@ -29,16 +32,31 @@ const withDetail = (cases: TestCaseState[]): string =>
29
32
  const check = (s: UnitState, trace = false): string => `sungen contract check --screen ${s.unit}${trace ? ' --trace' : ''}`;
30
33
 
31
34
  function contractNode(id: string, rank: number, title: string, actor: TestCaseNodeDecl['actor'], gates: string[], caseOnly: boolean,
32
- active: (tc: TestCaseState) => boolean, step: Step): TestCaseNodeDecl {
35
+ active: (tc: TestCaseState) => boolean, step: Step, hard?: (tc: TestCaseState) => boolean, human?: TestCaseNodeDecl['human']): TestCaseNodeDecl {
33
36
  return {
34
37
  id, title, actor, granularity: 'test-case', appliesTo: ALL_KINDS, blocking: false, rank, gates, step,
35
38
  requires: caseOnly ? ['unit.gate-fail'] : ['unit.run'], produces: ['contracts.lock.yaml', 'decisions/<id>.yaml'],
36
39
  ...(caseOnly ? { caseOnly: true as const } : {}),
37
40
  active: (_s, tc) => active(tc),
38
41
  satisfied: (_s, tc) => exemptionOf(tc) !== null,
42
+ ...(hard ? { surfacesPastCut: (_s: UnitState, tc: TestCaseState) => hard(tc) } : {}),
43
+ ...(human ? { human } : {}),
39
44
  };
40
45
  }
41
46
 
47
+ /**
48
+ * An opaque step is cleared by a QA accepting it as it reads now, never by re-approving (#923): one
49
+ * `--accept-opaque` per case whose only hold is the opaque step. A case held by a `low` identity is
50
+ * not acceptable yet (`contract check --trace` resolves it first), so it is not offered (#954).
51
+ */
52
+ function acceptOpaque(s: UnitState, _step: StepCore, cases: TestCaseState[]): HumanAction[] {
53
+ return cases.filter((c) => classOf(c) === 'opaque' && !c.contract?.lowIdentity).map((c) => ({
54
+ kind: 'accept-opaque',
55
+ command: `sungen contract approve --screen ${s.unit} --scenario ${c.id} --accept-opaque`,
56
+ reason: `${c.id} carries a step sungen cannot compare${c.contract?.detail ? ` (${c.contract.detail})` : ''} — a QA reviews it verbatim with its file:line in \`${check(s)}\`, then accepts it as it reads now; until then the contract is in review and \`sungen delivery\` waits`,
57
+ }));
58
+ }
59
+
42
60
  const conformanceStep: Step = (s, cases) => {
43
61
  const low = cases.filter((c) => c.contract?.lowIdentity);
44
62
  if (low.length) {
@@ -54,16 +72,19 @@ const conformanceStep: Step = (s, cases) => {
54
72
  export const TEST_CASE_CONTRACT_NODES: readonly TestCaseNodeDecl[] = [
55
73
  contractNode('tc.contract-approve', 106, 'Re-approve this changed case', 'human', ['contract.hash-changed'], true,
56
74
  (tc) => classOf(tc) === 'hash-changed' && !tc.contract?.uncommitted,
57
- (s, [c]) => ({ kind: 'decide', command: `sungen contract approve --screen ${s.unit} --scenario ${c.id}`, because: `this case changed since QA approved it, not weaker${c.contract?.detail ? ` (${c.contract.detail})` : ''} — a QA re-approves this scenario on a terminal; until then review blocks \`sungen delivery\`. An agent presents it and never approves its own edit` })),
75
+ (s, [c]) => ({ kind: 'decide', command: `sungen contract approve --screen ${s.unit} --scenario ${c.id}`, because: `this case changed since QA approved it, not weaker${c.contract?.detail ? ` (${c.contract.detail})` : ''} — a QA re-approves this scenario on a terminal; until then review blocks \`sungen delivery\`. An agent presents it and never approves its own edit` }),
76
+ undefined, humanStep('contract-approve')),
58
77
  contractNode('tc.contract-check', 107, 'Restore the approved check of this case', 'human',
59
78
  ['contract.oracle-weakened', 'contract.target-changed', 'contract.assertion-lost', 'contract.lock-uncommitted'], true,
60
79
  (tc) => Boolean(tc.contract?.uncommitted) || HARD_LADDER.has(classOf(tc) ?? ''),
61
80
  (s, [c]) => ({ kind: 'blocked', command: check(s), because: c.contract?.uncommitted
62
81
  ? `its lock entry is not what QA approved and committed (contract.lock-uncommitted) — \`sungen next ${s.unit}\` names the repair; \`sungen delivery\` is blocked until then`
63
- : `this case breaks its approved contract (${c.contract?.class}${c.contract?.detail ? `: ${c.contract.detail}` : ''}) — restore the approved check (in the .feature, or in the hand-written test of a free / impl:code case), or a QA approves the change on a terminal (\`sungen contract approve --screen ${s.unit} --scenario ${c.id}\`). \`sungen delivery\` is blocked until then; never weaken an expected value to go green` })),
82
+ : `this case breaks its approved contract (${c.contract?.class}${c.contract?.detail ? `: ${c.contract.detail}` : ''}) — restore the approved check (in the .feature, or in the hand-written test of a free / impl:code case), or a QA approves the change on a terminal (\`sungen contract approve --screen ${s.unit} --scenario ${c.id}\`). \`sungen delivery\` is blocked until then; never weaken an expected value to go green` }),
83
+ (tc) => Boolean(tc.contract?.uncommitted) || HARD_LADDER.has(classOf(tc) ?? '')),
64
84
  contractNode('tc.unproven', 136, 'Prove the approved free cases with a trace', 'ai', ['contract.unproven'], false,
65
85
  (tc) => classOf(tc) === 'unproven',
66
- (s, cases) => ({ kind: 'do', command: `/sungen:run-test ${s.unit}${idsArg(cases)}`, because: `${cases.length} approved free / impl:code test case(s) have no trace that proves them (${withDetail(cases)}) — run them again with trace: 'on' and the sungen run-record reporter (run-test Phase 4 does), then \`${check(s, true)}\`. Until then \`sungen contract check\` fails (contract.unproven) and \`sungen delivery\` is blocked; re-approving or editing the .feature cannot prove them` })),
86
+ (s, cases) => ({ kind: 'do', command: `/sungen:run-test ${s.unit}${idsArg(cases)}`, because: `${cases.length} approved free / impl:code test case(s) have no trace that proves them (${withDetail(cases)}) — run them again with trace: 'on' and the sungen run-record reporter (run-test Phase 4 does), then \`${check(s, true)}\`. Until then \`sungen contract check\` fails (contract.unproven) and \`sungen delivery\` is blocked; re-approving or editing the .feature cannot prove them` }),
87
+ (tc) => classOf(tc) === 'unproven'),
67
88
  contractNode('tc.conformance', 138, 'Settle the traces in review', 'ai', ['contract.opaque-step', 'contract.inconclusive'], false,
68
- (tc) => classOf(tc) === 'opaque' || classOf(tc) === 'inconclusive', conformanceStep),
89
+ (tc) => classOf(tc) === 'opaque' || classOf(tc) === 'inconclusive', conformanceStep, undefined, acceptOpaque),
69
90
  ];
@@ -8,12 +8,16 @@
8
8
  * tc.rerun-stale changed after the recorded run (run manifest) do run-test --change-only
9
9
  * tc.triage red, not classified by a current triage do sungen triage
10
10
  * tc.product-candidate triage found a product-defect candidate decide sungen defect draft
11
+ * tc.rerun-environment a red the environment caused (#928) decide run-test --ids, once a human confirms it is up
12
+ * tc.fix-test a red the test caused (#928) do run-test --ids (auto-fix)
13
+ * The class is `sungen triage`'s, or a manual classification in force (`sungen triage --case --as`).
11
14
  * They sit after unit.run (which cuts a unit that never ran) and before delivery. tc.triage takes
12
15
  * the reds the cases account for; unit.triage keeps the rest (unattributedReds), so a red no case
13
16
  * explains — an @cases row, a renamed scenario, a nested describe title — still gets a triage step.
14
17
  */
15
- import { ALL_KINDS, StepCore, TestCaseNodeDecl } from '../node-decl';
18
+ import { ALL_KINDS, humanStep, StepCore, TestCaseNodeDecl } from '../node-decl';
16
19
  import type { UnitState } from '../../harness/next-step';
20
+ import type { HumanAction } from '../human-action';
17
21
  import type { TestCaseState } from '../test-case-state-shape';
18
22
  import { exemptionOf, TestCaseNeed, triggerOf } from '../test-case-satisfied';
19
23
 
@@ -23,17 +27,33 @@ export const listed = (cases: TestCaseState[]): string => `${cases.slice(0, 4).m
23
27
  export const idsArg = (cases: TestCaseState[]): string =>
24
28
  (cases.length <= MAX_IDS && cases.every((c) => !c.unstableId) ? ` --ids ${cases.map((c) => c.id).join(',')}` : '');
25
29
  const flag = (s: UnitState): string => (s.kind === 'api' ? '--api' : '--screen');
30
+ /** Up to three cases with the triage rule that judged each. */
31
+ const withRule = (cases: TestCaseState[]): string =>
32
+ `${cases.slice(0, 3).map((c) => `${c.id}: ${c.triage?.ruleId}`).join('; ')}${cases.length > 3 ? '; …' : ''}`;
26
33
 
27
34
  function caseNode(id: string, need: TestCaseNeed, rank: number, title: string, actor: TestCaseNodeDecl['actor'],
28
- step: (s: UnitState, cases: TestCaseState[], single: boolean) => StepCore, produces: string[]): TestCaseNodeDecl {
35
+ step: (s: UnitState, cases: TestCaseState[], single: boolean) => StepCore, produces: string[], human?: TestCaseNodeDecl['human']): TestCaseNodeDecl {
29
36
  return {
30
37
  id, title, actor, granularity: 'test-case', appliesTo: ALL_KINDS, requires: ['unit.run'], blocking: false, rank,
31
38
  gates: [], produces, step,
32
39
  active: (_s, tc) => triggerOf(tc) === need,
33
40
  satisfied: (_s, tc) => exemptionOf(tc) !== null,
41
+ ...(human ? { human } : {}),
34
42
  };
35
43
  }
36
44
 
45
+ /**
46
+ * A red triage left unclassified is a human's to classify (#928): test-defect and environment take it
47
+ * off the product-defect path, so they need a person on a terminal. One action per such case (#954).
48
+ */
49
+ function classifyByHand(s: UnitState, _step: StepCore, cases: TestCaseState[]): HumanAction[] {
50
+ return cases.filter((c) => c.triage?.class === 'unclassified').map((c) => ({
51
+ kind: 'triage-classify',
52
+ command: `sungen triage ${flag(s)} ${s.unit} --case ${c.id} --as "<test-defect|environment>" --reason "…"`,
53
+ reason: `${c.id} failed and \`sungen triage\` left it unclassified — read its evidence (trace, error), then classify it: test-defect or environment take it off the product-defect path, so a human types them on a terminal (\`--as product\` anyone may record)`,
54
+ }));
55
+ }
56
+
37
57
  export const TEST_CASE_NODES: readonly TestCaseNodeDecl[] = [
38
58
  caseNode('tc.run', 'run', 131, 'Run the cases that never ran', 'ai', (s, cases) => ({
39
59
  kind: 'do',
@@ -50,14 +70,26 @@ export const TEST_CASE_NODES: readonly TestCaseNodeDecl[] = [
50
70
  return {
51
71
  kind: 'do',
52
72
  command: `sungen triage ${flag(s)} ${s.unit}`,
53
- because: `${cases.length} test case(s) failed in the latest run and are not classified yet${left ? ` (${left} left unclassified by the last triage — read its evidence and classify by hand)` : ''} — classify each (known defect, test defect, environment, product candidate) before exporting or extending: an unexplained red in the deliverable is a finding nobody has judged`,
73
+ because: `${cases.length} test case(s) failed in the latest run and are not classified yet${left ? ` (${left} left unclassified by the last triage — read its evidence and classify by hand: \`sungen triage ${flag(s)} ${s.unit} --case <id> --as product|test-defect|environment --reason "…"\`; test-defect and environment are a human's call, on a terminal)` : ''} — classify each (known defect, test defect, environment, product candidate) before exporting or extending: an unexplained red in the deliverable is a finding nobody has judged`,
54
74
  };
55
- }, ['.sungen/triage/<unit>.json']),
75
+ }, ['.sungen/triage/<unit>.json'], classifyByHand),
56
76
  caseNode('tc.product-candidate', 'product-candidate', 134, 'Decide on the product-defect candidates', 'human', (s, cases, single) => ({
57
77
  kind: 'decide',
58
78
  command: `sungen defect draft ${flag(s)} ${s.unit}${single ? ` --scenario ${cases[0].id}` : ''}`,
59
79
  because: `${cases.length} red test case(s) look like a product defect (${listed(cases)}) — the app answered and the answer differs from the oracle. A QA decides whether it is a defect to record; an agent presents it and does not draft it on its own`,
60
- }), ['qa/defects/DEF-<n>-<slug>.md']),
80
+ }), ['qa/defects/DEF-<n>-<slug>.md'], humanStep('defect-draft')),
81
+ // decide, not do: an agent re-running into an environment that is still down gets the same red and
82
+ // the same step again — a loop with no stop. A human confirms the environment first (#928 review W3).
83
+ caseNode('tc.rerun-environment', 'rerun-environment', 139, 'Re-run the cases the environment failed, once it is up', 'human', (s, cases) => ({
84
+ kind: 'decide',
85
+ command: `/sungen:run-test ${s.unit}${idsArg(cases)}`,
86
+ because: `${cases.length} red test case(s) failed on the environment, not the app (${withRule(cases)}) — the run never reached a usable app, so their results say nothing about it yet. A human confirms the app, base URL, network, auth state or device is up, then they are re-run; an agent presents this and does not re-run on its own — into an environment that is still down, the same red comes back`,
87
+ }), ['specs/generated/<unit>/<unit>-test-result.json'], humanStep('rerun-environment')),
88
+ caseNode('tc.fix-test', 'fix-test', 139.5, 'Fix the tests triaged as test defects', 'ai', (s, cases) => ({
89
+ kind: 'do',
90
+ command: `/sungen:run-test ${s.unit}${idsArg(cases)}`,
91
+ because: `${cases.length} red test case(s) are test defects (${withRule(cases)}) — the test, not the app, failed: fix its selector, test data or step (run-test's auto-fix reads the triage evidence), then re-run them; never weaken an assertion to make one pass`,
92
+ }), ['selectors/<unit>.yaml', 'specs/generated/<unit>/<unit>-test-result.json']),
61
93
  ];
62
94
 
63
95
  /** Red final attempts the test cases account for (a case read without the count stands for one). */
@@ -8,7 +8,7 @@
8
8
  * 9 unit.repair-findings findings with a known repair → repair + re-audit, names ≤ 4 codes
9
9
  * 10 unit.gate-fail gateStatus FAIL → blocked, cuts
10
10
  */
11
- import { ALL_KINDS, UnitNodeDecl } from '../node-decl';
11
+ import { ALL_KINDS, humanStep, UnitNodeDecl } from '../node-decl';
12
12
  import type { UnitState } from '../../harness/next-step';
13
13
 
14
14
  /** The finding codes worth naming in a next step — each maps to a specific repair. */
@@ -66,6 +66,7 @@ export const UNIT_AUDIT_NODES: readonly UnitNodeDecl[] = [
66
66
  command: `sungen audit --screen ${s.unit} --accept-viewpoint`,
67
67
  because: 'test-viewpoint.md changed since the accepted baseline, so the ledger and traceability axes are not evidence until a QA confirms the new declaration. A decision, not a step: an agent presents it and does not run it — accepting the yardstick you just wrote makes both axes read 100% by construction',
68
68
  }),
69
+ human: humanStep('accept-viewpoint'),
69
70
  },
70
71
  {
71
72
  id: 'unit.repair-findings', title: 'Repair the named findings', actor: 'ai', granularity: 'unit',
@@ -3,7 +3,8 @@
3
3
  * verdict `sungen contract check` and the delivery guard rebuild (buildContractVerdict), so the three
4
4
  * never disagree. Always `decide`: an agent presents it and never approves its own edit.
5
5
  *
6
- * unit.contract-approve (rank 105, after the audit gate, before the first run)
6
+ * unit.contract-approve (rank 105, after the audit gate, before the first run; a hard verdict —
7
+ * hard or uncommitted — is offered past a stale or failing audit too, #940)
7
8
  * error the verdict could not be built → check it on a terminal
8
9
  * uncommitted by kind: tampered → re-approve or restore · untracked → commit after review ·
9
10
  * edited → review and commit, or `git checkout --` the lock · unverified → commit the
@@ -15,8 +16,9 @@
15
16
  * A free / impl:code scenario only its TRACE cannot prove — unproven, inconclusive, opaque — is not a
16
17
  * unit decision: the case nodes say what proves it (test-case-contract-nodes.ts, phase 09).
17
18
  */
18
- import { ALL_KINDS, StepCore, UnitNodeDecl, unitKindDir } from '../node-decl';
19
+ import { ALL_KINDS, humanStep, StepCore, UnitNodeDecl, unitKindDir } from '../node-decl';
19
20
  import type { UnitState } from '../../harness/next-step';
21
+ import type { HumanActionKind } from '../human-action';
20
22
 
21
23
  const HUMAN_ONLY = 'A QA decision on a terminal: an agent presents it and never approves its own edit';
22
24
  const approveCmd = (s: UnitState): string => `sungen contract approve --screen ${s.unit}`;
@@ -49,6 +51,10 @@ function contractStep(s: UnitState): StepCore {
49
51
  return { kind: 'decide', command: approveCmd(s), because: `${c.review} approved scenario(s) changed since QA approved them (changed, not weaker) — review blocks \`sungen delivery\` until QA re-approves. ${HUMAN_ONLY}` };
50
52
  }
51
53
 
54
+ /** What the human does for the step contractStep offered: approve, commit / restore through review, or read the verdict. */
55
+ const contractActionKind = (command: string): HumanActionKind =>
56
+ command.startsWith('sungen contract approve') ? 'contract-approve' : command.startsWith('sungen contract check') ? 'contract-review' : 'contract-commit';
57
+
52
58
  const CONTRACT_RULES = ['contract.hash-changed', 'contract.oracle-weakened', 'contract.target-changed', 'contract.assertion-lost', 'contract.lock-uncommitted'];
53
59
  /** The verdict is a pass and it was built: nothing blocks, nothing to repair. */
54
60
  const verdictClean = (s: UnitState): boolean => !s.contract!.error && s.contract!.verdict === 'pass';
@@ -66,6 +72,17 @@ export const UNIT_CONTRACT_NODES: readonly UnitNodeDecl[] = [
66
72
  active: (s) => Boolean(s.contract),
67
73
  satisfied: nothingToDecide,
68
74
  step: contractStep,
75
+ human: (_s, step) => {
76
+ const kind = contractActionKind(step.command);
77
+ // A lock repair is a commit (or `git checkout --`) after review — prose, never a sungen command to copy.
78
+ return [{ command: step.command, reason: step.because, kind, ...(kind === 'contract-commit' ? { instruction: true as const } : {}) }];
79
+ },
80
+ // A hard verdict must not wait behind a re-audit (#940): the audit's PASS says nothing about it —
81
+ // a broken approved check, or a lock that is not what QA approved and committed (contract.lock-
82
+ // uncommitted, hard in `contract check`; the step names that repair first). What stays behind the
83
+ // cut: a changed-not-weaker contract (review — QA re-approves after the design is fixed) and a
84
+ // verdict that could not be built (not a verdict; `contract check` names the error).
85
+ surfacesPastCut: (s) => !s.contract!.error && (s.contract!.uncommitted > 0 || s.contract!.hard > 0),
69
86
  },
70
87
  {
71
88
  id: 'unit.contract-proposed', title: 'Approve the Test Contract', actor: 'human', granularity: 'unit',
@@ -78,5 +95,6 @@ export const UNIT_CONTRACT_NODES: readonly UnitNodeDecl[] = [
78
95
  command: approveCmd(s),
79
96
  because: `${s.contract!.proposed} scenario(s) have no approved contract yet — approving locks the checks QA reviewed, so a later edit that weakens one (a healer's included) fails \`sungen contract check\`. ${HUMAN_ONLY}; proposed scenarios do not block`,
80
97
  }),
98
+ human: humanStep('contract-approve'),
81
99
  },
82
100
  ];
@@ -10,6 +10,7 @@
10
10
  * 6 unit.flow-inventory a flow contract without `flows:` → add it, re-audit
11
11
  */
12
12
  import { ALL_KINDS, UnitNodeDecl, unitKindDir } from '../node-decl';
13
+ import { updateChannelFlag } from '../../cli/project-sungen-invocation';
13
14
 
14
15
  const design = (unit: string): string => `/sungen:create-test ${unit}`;
15
16
 
@@ -34,7 +35,7 @@ export const UNIT_DESIGN_NODES: readonly UnitNodeDecl[] = [
34
35
  satisfied: (s) => !s.assetsDrift,
35
36
  step: (s) => ({
36
37
  kind: 'do',
37
- command: 'sungen update',
38
+ command: `sungen update${updateChannelFlag(s.assetsDrift!.runningVersion)}`, // keep the channel (#939)
38
39
  because: `this project's AI commands/skills were written by sungen ${s.assetsDrift!.recordedVersion} and you are running ${s.assetsDrift!.runningVersion} — upgrading the package does NOT refresh them, so any rule added since ${s.assetsDrift!.recordedVersion} is not in effect for the next run`,
39
40
  }),
40
41
  },