@intentius/chant 0.90.0 → 0.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/lint.d.ts.map +1 -1
  3. package/dist/cli/handlers/components.d.ts +8 -0
  4. package/dist/cli/handlers/components.d.ts.map +1 -1
  5. package/dist/cli/handlers/graph.d.ts.map +1 -1
  6. package/dist/cli/handlers/operator.d.ts +20 -0
  7. package/dist/cli/handlers/operator.d.ts.map +1 -1
  8. package/dist/cli/handlers/run.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts +7 -4
  11. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  12. package/dist/cli/plugins.d.ts.map +1 -1
  13. package/dist/cli/registry.d.ts +42 -1
  14. package/dist/cli/registry.d.ts.map +1 -1
  15. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  16. package/dist/components/verbs/vuln-scan.d.ts +72 -0
  17. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  18. package/dist/config.d.ts +22 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/lexicon-module.d.ts +22 -0
  21. package/dist/lexicon-module.d.ts.map +1 -1
  22. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  23. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  24. package/dist/lifecycle/git.d.ts +40 -5
  25. package/dist/lifecycle/git.d.ts.map +1 -1
  26. package/dist/lifecycle/lease.d.ts +72 -16
  27. package/dist/lifecycle/lease.d.ts.map +1 -1
  28. package/dist/lifecycle/member-ledger.d.ts +3 -2
  29. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  30. package/dist/lifecycle/plan-ledger.d.ts +114 -0
  31. package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
  32. package/dist/lifecycle/run-ledger.d.ts +2 -0
  33. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  34. package/dist/lifecycle/symptoms.d.ts +51 -0
  35. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  36. package/dist/lifecycle/work-lease.d.ts +140 -0
  37. package/dist/lifecycle/work-lease.d.ts.map +1 -0
  38. package/dist/lint/rules/index.d.ts +4 -2
  39. package/dist/lint/rules/index.d.ts.map +1 -1
  40. package/dist/lint/rules/op/index.d.ts +14 -3
  41. package/dist/lint/rules/op/index.d.ts.map +1 -1
  42. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  43. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  44. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  45. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  46. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  47. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  48. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  49. package/dist/op/activities/activity-contracts.d.ts +203 -2
  50. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  51. package/dist/op/activities/apply.d.ts.map +1 -1
  52. package/dist/op/activities/change-coverage.d.ts +44 -0
  53. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  54. package/dist/op/activities/converge.d.ts +24 -1
  55. package/dist/op/activities/converge.d.ts.map +1 -1
  56. package/dist/op/activities/decide.d.ts +107 -0
  57. package/dist/op/activities/decide.d.ts.map +1 -0
  58. package/dist/op/activities/index.d.ts +10 -0
  59. package/dist/op/activities/index.d.ts.map +1 -1
  60. package/dist/op/activities/shell.d.ts +32 -0
  61. package/dist/op/activities/shell.d.ts.map +1 -1
  62. package/dist/op/activities/source-release.d.ts +86 -0
  63. package/dist/op/activities/source-release.d.ts.map +1 -0
  64. package/dist/op/activities/source-rollback.d.ts +100 -0
  65. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  66. package/dist/op/activities/work-evidence.d.ts +42 -0
  67. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  68. package/dist/op/activity-contract.d.ts +1 -0
  69. package/dist/op/activity-contract.d.ts.map +1 -1
  70. package/dist/op/activity-profiles.d.ts +16 -0
  71. package/dist/op/activity-profiles.d.ts.map +1 -1
  72. package/dist/op/builders.d.ts +84 -2
  73. package/dist/op/builders.d.ts.map +1 -1
  74. package/dist/op/composites/converge-op.d.ts +31 -4
  75. package/dist/op/composites/converge-op.d.ts.map +1 -1
  76. package/dist/op/decide-backend.d.ts +116 -0
  77. package/dist/op/decide-backend.d.ts.map +1 -0
  78. package/dist/op/decide-config.d.ts +120 -0
  79. package/dist/op/decide-config.d.ts.map +1 -0
  80. package/dist/op/decide-read-inputs.d.ts +40 -0
  81. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  82. package/dist/op/discover.d.ts +25 -0
  83. package/dist/op/discover.d.ts.map +1 -1
  84. package/dist/op/gate-wait.d.ts +43 -0
  85. package/dist/op/gate-wait.d.ts.map +1 -0
  86. package/dist/op/gate.d.ts +5 -2
  87. package/dist/op/gate.d.ts.map +1 -1
  88. package/dist/op/index.d.ts +18 -5
  89. package/dist/op/index.d.ts.map +1 -1
  90. package/dist/op/lifecycle-receipt-store.d.ts +34 -0
  91. package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
  92. package/dist/op/local-executor.d.ts +48 -4
  93. package/dist/op/local-executor.d.ts.map +1 -1
  94. package/dist/op/local-output.d.ts +2 -0
  95. package/dist/op/local-output.d.ts.map +1 -1
  96. package/dist/op/op-ir.d.ts +12 -1
  97. package/dist/op/op-ir.d.ts.map +1 -1
  98. package/dist/op/op-verb-class.d.ts.map +1 -1
  99. package/dist/op/operator.d.ts +126 -1
  100. package/dist/op/operator.d.ts.map +1 -1
  101. package/dist/op/runtime.d.ts +46 -5
  102. package/dist/op/runtime.d.ts.map +1 -1
  103. package/dist/op/runtimes/local.d.ts.map +1 -1
  104. package/dist/op/source-archive.d.ts +63 -0
  105. package/dist/op/source-archive.d.ts.map +1 -0
  106. package/dist/op/step-output-ref.d.ts +2 -2
  107. package/dist/op/step-output-ref.d.ts.map +1 -1
  108. package/dist/op/steward-points.d.ts +136 -0
  109. package/dist/op/steward-points.d.ts.map +1 -0
  110. package/dist/op/steward-turn.d.ts +53 -0
  111. package/dist/op/steward-turn.d.ts.map +1 -0
  112. package/dist/op/steward.d.ts +161 -0
  113. package/dist/op/steward.d.ts.map +1 -0
  114. package/dist/op/types.d.ts +58 -0
  115. package/dist/op/types.d.ts.map +1 -1
  116. package/dist/op/work-lease-decl.d.ts +18 -0
  117. package/dist/op/work-lease-decl.d.ts.map +1 -0
  118. package/dist/op/work-lease-run.d.ts +173 -0
  119. package/dist/op/work-lease-run.d.ts.map +1 -0
  120. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  121. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  122. package/dist/workspace/answers-ledger.d.ts +76 -0
  123. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  124. package/dist/workspace/box-isolation.d.ts +99 -0
  125. package/dist/workspace/box-isolation.d.ts.map +1 -0
  126. package/dist/workspace/changes-cli.d.ts +16 -0
  127. package/dist/workspace/changes-cli.d.ts.map +1 -0
  128. package/dist/workspace/changes.d.ts +200 -0
  129. package/dist/workspace/changes.d.ts.map +1 -0
  130. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  131. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  132. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  133. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  134. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  135. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  136. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  137. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  138. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  139. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  140. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  141. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  142. package/dist/workspace/chant-migrations.d.ts +99 -0
  143. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  144. package/dist/workspace/checks/box-isolation.d.ts +18 -0
  145. package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
  146. package/dist/workspace/checks/boxes.d.ts +104 -0
  147. package/dist/workspace/checks/boxes.d.ts.map +1 -0
  148. package/dist/workspace/checks/diagrams.d.ts +31 -0
  149. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  150. package/dist/workspace/checks/records.d.ts +8 -0
  151. package/dist/workspace/checks/records.d.ts.map +1 -1
  152. package/dist/workspace/checks.d.ts +13 -1
  153. package/dist/workspace/checks.d.ts.map +1 -1
  154. package/dist/workspace/compose-graph.d.ts +2 -0
  155. package/dist/workspace/compose-graph.d.ts.map +1 -1
  156. package/dist/workspace/conformance/index.d.ts.map +1 -1
  157. package/dist/workspace/decide.d.ts +229 -0
  158. package/dist/workspace/decide.d.ts.map +1 -0
  159. package/dist/workspace/decision-points.schema.json +137 -0
  160. package/dist/workspace/declaration.d.ts +103 -0
  161. package/dist/workspace/declaration.d.ts.map +1 -1
  162. package/dist/workspace/declaration.schema.json +285 -0
  163. package/dist/workspace/declared-kinds.d.ts +32 -0
  164. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  165. package/dist/workspace/generated-files.d.ts +7 -0
  166. package/dist/workspace/generated-files.d.ts.map +1 -1
  167. package/dist/workspace/intent.d.ts.map +1 -1
  168. package/dist/workspace/lineage-check.d.ts +3 -0
  169. package/dist/workspace/lineage-check.d.ts.map +1 -1
  170. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  171. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  172. package/dist/workspace/ls.d.ts +39 -1
  173. package/dist/workspace/ls.d.ts.map +1 -1
  174. package/dist/workspace/point-answer.schema.json +95 -0
  175. package/dist/workspace/points-cli.d.ts +113 -0
  176. package/dist/workspace/points-cli.d.ts.map +1 -0
  177. package/dist/workspace/points.d.ts +320 -0
  178. package/dist/workspace/points.d.ts.map +1 -0
  179. package/dist/workspace/reason-codes.d.ts +38 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/record-assets.d.ts.map +1 -1
  182. package/dist/workspace/records-cli.d.ts +23 -1
  183. package/dist/workspace/records-cli.d.ts.map +1 -1
  184. package/dist/workspace/records-close.d.ts +1 -1
  185. package/dist/workspace/records-close.d.ts.map +1 -1
  186. package/dist/workspace/records-write.d.ts +11 -5
  187. package/dist/workspace/records-write.d.ts.map +1 -1
  188. package/dist/workspace/records.d.ts +42 -6
  189. package/dist/workspace/records.d.ts.map +1 -1
  190. package/dist/workspace/status-gates.d.ts +7 -2
  191. package/dist/workspace/status-gates.d.ts.map +1 -1
  192. package/dist/workspace/status-stewards.d.ts +179 -0
  193. package/dist/workspace/status-stewards.d.ts.map +1 -0
  194. package/dist/workspace/status.d.ts +68 -1
  195. package/dist/workspace/status.d.ts.map +1 -1
  196. package/dist/workspace/work-cli.d.ts +189 -0
  197. package/dist/workspace/work-cli.d.ts.map +1 -0
  198. package/dist/workspace/work-evidence.d.ts +74 -0
  199. package/dist/workspace/work-evidence.d.ts.map +1 -0
  200. package/dist/workspace/work.d.ts +43 -1
  201. package/dist/workspace/work.d.ts.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/commands/audit.test.ts +9 -1
  204. package/src/cli/commands/build.ts +3 -2
  205. package/src/cli/commands/lint.ts +7 -1
  206. package/src/cli/handlers/components.test.ts +93 -0
  207. package/src/cli/handlers/components.ts +44 -3
  208. package/src/cli/handlers/graph.test.ts +20 -0
  209. package/src/cli/handlers/graph.ts +16 -2
  210. package/src/cli/handlers/operator.ts +141 -4
  211. package/src/cli/handlers/run.test.ts +173 -2
  212. package/src/cli/handlers/run.ts +174 -3
  213. package/src/cli/main.test.ts +50 -0
  214. package/src/cli/main.ts +97 -2
  215. package/src/cli/mcp/workspace-tools.test.ts +48 -19
  216. package/src/cli/mcp/workspace-tools.ts +110 -8
  217. package/src/cli/plugins.ts +11 -3
  218. package/src/cli/registry.ts +42 -1
  219. package/src/codegen/pinned-upgrade.ts +2 -1
  220. package/src/codegen/release-wiring.test.ts +49 -2
  221. package/src/components/capability-plugin-loader.ts +3 -3
  222. package/src/components/cli-support.ts +2 -2
  223. package/src/components/verbs/vuln-scan.test.ts +124 -1
  224. package/src/components/verbs/vuln-scan.ts +142 -1
  225. package/src/config.ts +11 -0
  226. package/src/lexicon-module.ts +56 -1
  227. package/src/lexicon-package.test.ts +96 -0
  228. package/src/lifecycle/converge-ledger.ts +20 -0
  229. package/src/lifecycle/git.ts +65 -15
  230. package/src/lifecycle/lease.test.ts +22 -0
  231. package/src/lifecycle/lease.ts +133 -29
  232. package/src/lifecycle/member-ledger.ts +3 -2
  233. package/src/lifecycle/plan-ledger.test.ts +148 -0
  234. package/src/lifecycle/plan-ledger.ts +158 -0
  235. package/src/lifecycle/run-ledger.ts +6 -0
  236. package/src/lifecycle/symptoms.ts +95 -0
  237. package/src/lifecycle/work-lease.test.ts +236 -0
  238. package/src/lifecycle/work-lease.ts +426 -0
  239. package/src/lint/rules/index.ts +6 -2
  240. package/src/lint/rules/op/index.ts +17 -4
  241. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  242. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  243. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  244. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  245. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  246. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  247. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  248. package/src/op/activities/activity-contracts.ts +190 -1
  249. package/src/op/activities/apply.ts +7 -6
  250. package/src/op/activities/change-coverage.ts +60 -0
  251. package/src/op/activities/converge.test.ts +55 -1
  252. package/src/op/activities/converge.ts +208 -29
  253. package/src/op/activities/decide.live.test.ts +78 -0
  254. package/src/op/activities/decide.test.ts +300 -0
  255. package/src/op/activities/decide.ts +267 -0
  256. package/src/op/activities/index.ts +21 -0
  257. package/src/op/activities/shell.test.ts +91 -1
  258. package/src/op/activities/shell.ts +91 -2
  259. package/src/op/activities/source-release.test.ts +118 -0
  260. package/src/op/activities/source-release.ts +168 -0
  261. package/src/op/activities/source-rollback.test.ts +131 -0
  262. package/src/op/activities/source-rollback.ts +239 -0
  263. package/src/op/activities/work-evidence.ts +62 -0
  264. package/src/op/activity-contract-registry.test.ts +2 -0
  265. package/src/op/activity-contract-registry.ts +2 -2
  266. package/src/op/activity-contract.test.ts +12 -0
  267. package/src/op/activity-contract.ts +6 -1
  268. package/src/op/activity-profiles.ts +29 -0
  269. package/src/op/activity-registry.test.ts +1 -1
  270. package/src/op/activity-registry.ts +2 -2
  271. package/src/op/builders.ts +124 -5
  272. package/src/op/command-gate.test.ts +192 -0
  273. package/src/op/composites/converge-op.test.ts +27 -1
  274. package/src/op/composites/converge-op.ts +69 -15
  275. package/src/op/decide-backend.test.ts +44 -0
  276. package/src/op/decide-backend.ts +212 -0
  277. package/src/op/decide-config.test.ts +52 -0
  278. package/src/op/decide-config.ts +80 -0
  279. package/src/op/decide-read-inputs.ts +117 -0
  280. package/src/op/discover.ts +71 -0
  281. package/src/op/gate-approval.ts +2 -2
  282. package/src/op/gate-wait.ts +57 -0
  283. package/src/op/gate.ts +8 -3
  284. package/src/op/generate-pipeline.ts +2 -2
  285. package/src/op/index.ts +34 -6
  286. package/src/op/lifecycle-receipt-store.test.ts +60 -0
  287. package/src/op/lifecycle-receipt-store.ts +61 -0
  288. package/src/op/local-executor.ts +367 -33
  289. package/src/op/local-output.test.ts +33 -0
  290. package/src/op/local-output.ts +33 -2
  291. package/src/op/op-ir.test.ts +10 -0
  292. package/src/op/op-ir.ts +18 -0
  293. package/src/op/op-verb-class.ts +1 -0
  294. package/src/op/operator.ts +360 -10
  295. package/src/op/runtime.ts +37 -8
  296. package/src/op/runtimes/local.ts +6 -1
  297. package/src/op/source-archive.ts +161 -0
  298. package/src/op/step-output-ref.ts +6 -2
  299. package/src/op/steward-points.test.ts +313 -0
  300. package/src/op/steward-points.ts +194 -0
  301. package/src/op/steward-turn.ts +79 -0
  302. package/src/op/steward.test.ts +296 -0
  303. package/src/op/steward.ts +277 -0
  304. package/src/op/types.ts +60 -0
  305. package/src/op/work-lease-decl.ts +80 -0
  306. package/src/op/work-lease-run.test.ts +345 -0
  307. package/src/op/work-lease-run.ts +395 -0
  308. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  309. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  310. package/src/workspace/answers-ledger.ts +118 -0
  311. package/src/workspace/box-isolation.test.ts +261 -0
  312. package/src/workspace/box-isolation.ts +205 -0
  313. package/src/workspace/changes-cli.ts +56 -0
  314. package/src/workspace/changes.schema.json +472 -0
  315. package/src/workspace/changes.test.ts +315 -0
  316. package/src/workspace/changes.ts +512 -0
  317. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  318. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  319. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  320. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  321. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  322. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  323. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  324. package/src/workspace/chant-migrations.ts +164 -0
  325. package/src/workspace/check-contract.test.ts +5 -1
  326. package/src/workspace/check.schema.json +25 -7
  327. package/src/workspace/checks/box-isolation.ts +68 -0
  328. package/src/workspace/checks/boxes.test.ts +276 -0
  329. package/src/workspace/checks/boxes.ts +442 -0
  330. package/src/workspace/checks/diagrams.test.ts +134 -0
  331. package/src/workspace/checks/diagrams.ts +108 -0
  332. package/src/workspace/checks/records.ts +72 -0
  333. package/src/workspace/checks.test.ts +7 -0
  334. package/src/workspace/checks.ts +27 -3
  335. package/src/workspace/compose-graph.ts +2 -0
  336. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  337. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  338. package/src/workspace/conformance/index.ts +5 -0
  339. package/src/workspace/decide.test.ts +224 -0
  340. package/src/workspace/decide.ts +709 -0
  341. package/src/workspace/decision-points.schema.json +137 -0
  342. package/src/workspace/declaration.schema.json +285 -0
  343. package/src/workspace/declaration.test.ts +58 -0
  344. package/src/workspace/declaration.ts +241 -0
  345. package/src/workspace/declared-kinds.test.ts +5 -5
  346. package/src/workspace/declared-kinds.ts +71 -3
  347. package/src/workspace/generated-files.ts +14 -0
  348. package/src/workspace/graph-cli.ts +1 -1
  349. package/src/workspace/graph.schema.json +9 -1
  350. package/src/workspace/intent.schema.json +12 -3
  351. package/src/workspace/intent.ts +3 -10
  352. package/src/workspace/lineage-check.ts +16 -1
  353. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  354. package/src/workspace/lineage-upgrade.test.ts +70 -0
  355. package/src/workspace/lineage-upgrade.ts +81 -10
  356. package/src/workspace/ls-contract.test.ts +57 -0
  357. package/src/workspace/ls.schema.json +46 -1
  358. package/src/workspace/ls.ts +62 -3
  359. package/src/workspace/point-answer.schema.json +95 -0
  360. package/src/workspace/points-cli.ts +284 -0
  361. package/src/workspace/points-write.schema.json +499 -0
  362. package/src/workspace/points.schema.json +743 -0
  363. package/src/workspace/points.test.ts +264 -0
  364. package/src/workspace/points.ts +564 -0
  365. package/src/workspace/read-contract.test.ts +18 -1
  366. package/src/workspace/reason-codes.test.ts +24 -1
  367. package/src/workspace/reason-codes.ts +49 -0
  368. package/src/workspace/record-assets.test.ts +3 -2
  369. package/src/workspace/record-assets.ts +4 -1
  370. package/src/workspace/records-amend.schema.json +2 -0
  371. package/src/workspace/records-cli.ts +28 -5
  372. package/src/workspace/records-close.schema.json +2 -0
  373. package/src/workspace/records-contract.test.ts +3 -2
  374. package/src/workspace/records-new.schema.json +2 -0
  375. package/src/workspace/records-review.schema.json +2 -0
  376. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  377. package/src/workspace/records-sessions-write.test.ts +2 -34
  378. package/src/workspace/records-write.e2e.test.ts +108 -0
  379. package/src/workspace/records-write.test.ts +32 -41
  380. package/src/workspace/records-write.ts +33 -15
  381. package/src/workspace/records.schema.json +53 -2
  382. package/src/workspace/records.test.ts +48 -3
  383. package/src/workspace/records.ts +142 -6
  384. package/src/workspace/status-contract.test.ts +189 -6
  385. package/src/workspace/status-gates.ts +9 -4
  386. package/src/workspace/status-stewards.ts +329 -0
  387. package/src/workspace/status.schema.json +335 -5
  388. package/src/workspace/status.ts +142 -6
  389. package/src/workspace/work-acceptance.test.ts +274 -0
  390. package/src/workspace/work-cli.e2e.test.ts +180 -0
  391. package/src/workspace/work-cli.ts +429 -0
  392. package/src/workspace/work-evidence.schema.json +212 -0
  393. package/src/workspace/work-evidence.ts +169 -0
  394. package/src/workspace/work-history.schema.json +349 -0
  395. package/src/workspace/work-history.test.ts +156 -0
  396. package/src/workspace/work-lease.schema.json +233 -0
  397. package/src/workspace/work-readiness-chud.test.ts +145 -0
  398. package/src/workspace/work.test.ts +3 -2
  399. package/src/workspace/work.ts +100 -1
  400. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  401. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  402. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -14,11 +14,21 @@
14
14
  * | WSP113 | a pinned file that a superseding record pins at the old hash: the artifact did not follow the decision |
15
15
  * | WSP114 | the records of `--kind` can't be read (fixed) |
16
16
  * | WSP115 | a record kind the declaration names is missing or does not load as one (fixed, #2680) |
17
+ * | WSP116 | an answer kind's decision points file is missing or not valid (fixed, ws-058, #2738) |
18
+ * | WSP117 | a done work item has an acceptance criterion no passing evidence meets (#2772) |
19
+ *
20
+ * WSP117 reads the records of every declared work kind with acceptance
21
+ * criteria, in the working tree, and the records of `--kind`. It carries the
22
+ * reason code `work-acceptance-unmet`.
17
23
  */
18
24
 
19
25
  import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
26
+ import type { ReasonCode } from "../reason-codes";
20
27
  import type { ReadErrorCode, RecordEntry } from "../records";
21
28
 
29
+ /** The read contract's codes the record checks carry as `code`: WSP117's (#2772). */
30
+ export const RECORD_FINDING_CODES = ["work-acceptance-unmet"] as const satisfies readonly ReasonCode[];
31
+
22
32
  /** The records `check --kind` read, or why they could not be read. */
23
33
  export type RecordFacts =
24
34
  | { kind: string; records: readonly (RecordEntry & { file: string })[] }
@@ -99,4 +109,66 @@ export const RECORD_CHECKS: readonly WorkspaceCheck[] = [
99
109
  }));
100
110
  },
101
111
  },
112
+ {
113
+ id: "WSP116",
114
+ name: "decision-points-invalid",
115
+ description:
116
+ "Every answer kind the declaration names has a decision points file that matches decision-points.schema.json: each input names a read-contract output, each table row tests declared inputs and answers a candidate, each model id is pinned, and the chain ends in its one quorum (ws-058).",
117
+ severity: "error",
118
+ configurable: false,
119
+ check(ctx) {
120
+ const out: WorkspaceDiagnostic[] = [];
121
+ for (const k of ctx.facts?.declaredKinds ?? []) {
122
+ if (!k.points) continue;
123
+ const where = k.declared.member === null ? "the workspace" : `member ${k.declared.member}`;
124
+ const finding = (message: string) => ({
125
+ checkId: this.id,
126
+ severity: this.severity,
127
+ message,
128
+ ...(k.declared.member !== null ? { entity: k.declared.member } : {}),
129
+ pointer: `${k.declared.pointer}/kind`,
130
+ });
131
+ for (const p of k.points.problems) out.push(finding(`${where} declares the answer kind ${k.declared.kind}, whose points file ${k.points.file} ${p.field ? `has ${p.field}, which ${p.message}` : p.message}`));
132
+ }
133
+ return out;
134
+ },
135
+ },
136
+ {
137
+ id: "WSP117",
138
+ name: "work-acceptance-unmet",
139
+ description:
140
+ "Every done work item meets its acceptance criteria: each has evidence that names it, passed, and has the verification the criterion expects, and a manual verdict is from someone other than the implementer (#2772).",
141
+ severity: "error",
142
+ configurable: true,
143
+ check(ctx) {
144
+ const out: WorkspaceDiagnostic[] = [];
145
+ const seen = new Set<string>();
146
+ const facts = ctx.facts?.records;
147
+ if (facts && !("error" in facts)) {
148
+ for (const r of facts.records) {
149
+ if (r.supersededBy !== null) continue;
150
+ const w = r.warnings.find((x) => x.code === "work-acceptance-unmet");
151
+ if (!w || seen.has(r.file)) continue;
152
+ seen.add(r.file);
153
+ out.push({ checkId: this.id, severity: this.severity, message: w.message, pointer: "", file: r.file, code: "work-acceptance-unmet" });
154
+ }
155
+ }
156
+ for (const k of ctx.facts?.declaredKinds ?? []) {
157
+ for (const a of k.acceptance ?? []) {
158
+ if (a.unmet === null || seen.has(a.file)) continue;
159
+ seen.add(a.file);
160
+ out.push({
161
+ checkId: this.id,
162
+ severity: this.severity,
163
+ message: a.unmet,
164
+ ...(k.declared.member !== null ? { entity: k.declared.member } : {}),
165
+ pointer: "",
166
+ file: a.file,
167
+ code: "work-acceptance-unmet",
168
+ });
169
+ }
170
+ }
171
+ return out;
172
+ },
173
+ },
102
174
  ];
@@ -60,6 +60,13 @@ describe("the WSP catalog", () => {
60
60
  "records-unreadable",
61
61
  // A declared record kind that doesn't load breaks every reader's default (#2680).
62
62
  "record-kind-unloadable",
63
+ // An answer kind's points file that isn't valid can't be asked (ws-058).
64
+ "decision-points-invalid",
65
+ // A credential in a box's files is in git (#2726).
66
+ "box-credential-declared",
67
+ // Two boxes sharing a value, or a box that only holds on one machine, is never the fix (#2727).
68
+ "box-isolation-collision",
69
+ "box-isolation-literal",
63
70
  ]);
64
71
  });
65
72
 
@@ -28,6 +28,12 @@
28
28
  * | WSP101 to WSP106 | generated files |
29
29
  * | WSP111 to WSP114 | records read with `--kind` (#2549) |
30
30
  * | WSP115 | the record kinds the declaration names (#2680) |
31
+ * | WSP116 | the decision points file of each declared answer kind (ws-058) |
32
+ * | WSP117 | a done work item's acceptance criteria all met (#2772) |
33
+ * | WSP121, WSP122 | boxes: no literal credential, every capability brokered (#2726) |
34
+ * | WSP123, WSP124 | box isolation: no shared port, state path or cookie, no literal machine path (#2727) |
35
+ * | WSP125 | boxes: a fountain Box declares the callback token fountain gives its sandbox (#2780) |
36
+ * | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
31
37
  */
32
38
 
33
39
  import { realpathSync } from "node:fs";
@@ -45,6 +51,8 @@ import { gatherGeneratedFacts, GENERATED_CHECKS, type GeneratedFileFacts } from
45
51
  import { gatherLedgerFacts, LEDGER_CHECKS, type MemberLedgerFacts } from "./checks/ledgers";
46
52
  import { gatherPipelineFacts, PIPELINE_CHECKS, type MemberPipelineFacts } from "./checks/pipelines";
47
53
  import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
54
+ import { BOX_CHECKS } from "./checks/boxes";
55
+ import { DIAGRAM_CHECKS } from "./checks/diagrams";
48
56
  import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
49
57
 
50
58
  /**
@@ -87,6 +95,12 @@ export interface WorkspaceDiagnostic extends PostSynthDiagnostic {
87
95
  pointer: string;
88
96
  /** An absolute path, for a finding about a file other than the declaration, such as a record (#2549). It is reported at its first line. */
89
97
  file?: string;
98
+ /** A path in the tree checked, relative to the workspace root, for a finding about a file there (#2726). It is reported at `line` and `column`. */
99
+ treeFile?: string;
100
+ line?: number;
101
+ column?: number;
102
+ /** The read contract's code for the finding, when it has one (#2726). */
103
+ code?: ReasonCode;
90
104
  }
91
105
 
92
106
  export interface WorkspaceCheck {
@@ -104,7 +118,7 @@ export interface WorkspaceCheck {
104
118
  /** A diagnostic ready for lint's reporters, with the entry it is about. */
105
119
  export interface WorkspaceFinding extends LintDiagnostic {
106
120
  entity?: string;
107
- /** The read contract's reason code, for a WSP001 finding: why the declaration can't be read (#2536). */
121
+ /** The read contract's reason code: for a WSP001 finding, why the declaration can't be read (#2536); for a box check, its finding code (#2726). */
108
122
  code?: ReasonCode;
109
123
  }
110
124
 
@@ -307,6 +321,10 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
307
321
  ...GENERATED_CHECKS,
308
322
  // Records read with --kind (#2549).
309
323
  ...RECORD_CHECKS,
324
+ // Boxes and their brokered capabilities (#2726).
325
+ ...BOX_CHECKS,
326
+ // Diagram artifacts (#2764).
327
+ ...DIAGRAM_CHECKS,
310
328
  ];
311
329
 
312
330
  const BY_ID = new Map(WORKSPACE_CHECKS.map((c) => [c.id, c]));
@@ -449,7 +467,8 @@ export async function runDeclarationChecks(
449
467
  const { registry, problems } = loadKindRegistry(declaration.pins, root);
450
468
  const gathered = options.gather === false || options.tree ? {} : await gatherWorkspaceFacts(root, declaration, options);
451
469
  // The declared record kinds load from the working tree; under --at only whether each exists at the revision is checked (#2680).
452
- const declaredKinds = options.gather === false ? undefined : await loadDeclaredKinds(declaration, tree, root, { load: !options.tree });
470
+ // A declared work kind's acceptance criteria are counted in the working tree too (#2772).
471
+ const declaredKinds = options.gather === false ? undefined : await loadDeclaredKinds(declaration, tree, root, { load: !options.tree, acceptance: !options.tree });
453
472
  const facts: WorkspaceFacts = { ...gathered, ...(options.records ? { records: options.records } : {}), ...(declaredKinds ? { declaredKinds } : {}) };
454
473
  const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
455
474
  const findings = runWorkspaceChecks(ctx);
@@ -459,11 +478,16 @@ export async function runDeclarationChecks(
459
478
  const locate = (pointer: string): TextLocation => (parsed.ok ? parsed.locate(pointer) : { line: 1, column: 1 });
460
479
  const file = display(declaration.file);
461
480
  const toFinding = (d: WorkspaceDiagnostic): WorkspaceFinding => ({
462
- ...(d.file !== undefined ? { file: display(relative(root, d.file).split(sep).join("/")), line: 1, column: 1 } : { file, ...locate(d.pointer) }),
481
+ ...(d.treeFile !== undefined
482
+ ? { file: display(d.treeFile), line: d.line ?? 1, column: d.column ?? 1 }
483
+ : d.file !== undefined
484
+ ? { file: display(relative(root, d.file).split(sep).join("/")), line: 1, column: 1 }
485
+ : { file, ...locate(d.pointer) }),
463
486
  ruleId: d.checkId,
464
487
  severity: d.severity,
465
488
  message: d.message,
466
489
  ...(d.entity !== undefined ? { entity: d.entity } : {}),
490
+ ...(d.code !== undefined ? { code: d.code } : {}),
467
491
  });
468
492
  const order = (a: LintDiagnostic, b: LintDiagnostic) => a.line - b.line || a.column - b.column || (a.ruleId < b.ruleId ? -1 : a.ruleId > b.ruleId ? 1 : 0);
469
493
  const diagnostics = active.map(toFinding).sort(order);
@@ -113,6 +113,8 @@ export interface GraphRecord {
113
113
  state: string | null;
114
114
  valid: boolean;
115
115
  supersededBy: string | null;
116
+ /** The ids of records whose remediates link names this one (#2774). */
117
+ remediatedBy: string[];
116
118
  }
117
119
 
118
120
  /** Read one member's `chant graph --format ir` output, upgrading an unversioned (v1) IR in place. */
@@ -17,6 +17,9 @@ export const recordKind = {
17
17
  // Sealed once reached (#2555).
18
18
  closedStates: ["ratified", "superseded"],
19
19
  supersedes: { field: "supersedes", key: "decision" },
20
+ // A remediates link names a closed decision this one fixes the consequence
21
+ // of, without replacing it: the target's state never changes (#2774).
22
+ remediates: { field: "remediates", key: "decision" },
20
23
  // A supersedes link takes effect under an equal or stricter approval rule
21
24
  // (#2524 D4): from a record ranked above 0 and at least as high as the one it
22
25
  // names. A decided record supersedes a decided or proposed one, a ratified
@@ -26,9 +29,16 @@ export const recordKind = {
26
29
  pins: { field: "evidence" },
27
30
  // member:<name> and path:<path> entries are the record's links in workspace graph (#2549).
28
31
  constrains: { field: "constrains" },
32
+ // Workspace paths a change carrying the decision out must not touch
33
+ // (#2773): chant workspace check --changes reports change-out-of-scope.
34
+ outOfScope: { field: "out_of_scope" },
29
35
  // Verdicts, and the field naming the decider, for each record's digest and
30
36
  // quorum (#2671, #2672).
31
37
  reviews: { field: "reviews", decider: "decided_by" },
38
+ // records new --by and the MCP records-new tool's by name a proposal's
39
+ // proposer here, apart from decided_by, which stays null until the
40
+ // decision is decided (#2756).
41
+ proposedBy: { field: "proposed_by" },
32
42
  // source is where a decision came from, and says where its proposal came
33
43
  // from too: via, client, harness, model, session, turns and a transcript
34
44
  // pinned by hash (#2708).
@@ -116,6 +116,19 @@
116
116
  ]
117
117
  }
118
118
  },
119
+ "remediates": {
120
+ "description": "Closed decisions this one fixes the consequence of, without replacing them: each names the decision by id and may say why (#2774). The named decision's status never changes because of a remediates link; a decision still open is amended instead, never remediated. Optional: most decisions remediate nothing.",
121
+ "type": "array",
122
+ "items": {
123
+ "type": "object",
124
+ "additionalProperties": false,
125
+ "required": ["decision"],
126
+ "properties": {
127
+ "decision": { "$ref": "#/definitions/decisionId" },
128
+ "reason": { "type": ["string", "null"] }
129
+ }
130
+ }
131
+ },
119
132
  "evidence": {
120
133
  "description": "The research, audits, design sections and workspace files behind the decision. A public link carries a url; a file in the workspace carries a path and the hash of its bytes (#2549). May be empty when the decision cites nothing; chant workspace records then warns decision-no-evidence (#2654).",
121
134
  "type": "array",
@@ -127,6 +140,10 @@
127
140
  "description": "Who chose. A forge login. Null in state proposed or withdrawn.",
128
141
  "type": ["string", "null"]
129
142
  },
143
+ "proposed_by": {
144
+ "description": "Who proposed this decision: the name chant workspace records new --by or the MCP records-new tool's by wrote, for a decision that opened proposed (#2756). Null or absent when nobody named a proposer, or the decision opened straight into a later state. Never cleared by a later decide, ratify or supersede.",
145
+ "type": ["string", "null"]
146
+ },
130
147
  "decided_on": {
131
148
  "description": "When the choice was made, as YYYY-MM-DD. Quoted, so YAML reads it as a string.",
132
149
  "type": ["string", "null"],
@@ -218,6 +235,15 @@
218
235
  "type": "string",
219
236
  "pattern": "^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+|[a-z][a-z0-9]{0,15}-[0-9]{3,}|member:[a-z0-9][a-z0-9-]{0,39}|path:(?!/)(?!(?:[^/]*/)*\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\u0000-\\u001f]*[^/\\\\\\u0000-\\u001f])$"
220
237
  }
238
+ },
239
+ "out_of_scope": {
240
+ "description": "Workspace paths this decision puts out of scope (#2773): files or directories, from the workspace root, that a change carrying out the decision must not touch. chant workspace check --changes reports a change to one as change-out-of-scope while the decision is in hand, and the decision does not cover it even where its constrains would. Optional.",
241
+ "type": "array",
242
+ "items": {
243
+ "type": "string",
244
+ "pattern": "^(?!/)(?!(?:[^/]*/)*\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\u0000-\\u001f]*[^/\\\\\\u0000-\\u001f]$"
245
+ },
246
+ "uniqueItems": true
221
247
  }
222
248
  },
223
249
  "allOf": [
@@ -346,6 +346,9 @@ export function mcpToolCall(argv: readonly string[]): { name: string; arguments:
346
346
  else if (a === "--since") args.since = value();
347
347
  else if (a === "--compare-to") args.compareTo = value();
348
348
  else if (a === "--intent") args.intent = value();
349
+ else if (a === "--changes") args.range = value();
350
+ else if (a === "--work") args.work = value();
351
+ else if (a === "--severity") args.severity = value();
349
352
  else if (a === "--current") args.current = true;
350
353
  else if (a === "--composites") args.composites = true;
351
354
  else if (a.startsWith("-")) return undefined;
@@ -358,6 +361,8 @@ export function mcpToolCall(argv: readonly string[]): { name: string; arguments:
358
361
  return positional.length === 1 && kinds.length === 0 ? { name: "workspace-status", arguments: { ...args, env: positional[0] } } : undefined;
359
362
  case "records":
360
363
  return positional.length === 0 && kinds.length <= 1 ? { name: "workspace-records", arguments: { ...args, ...(kinds.length ? { kind: kinds[0] } : {}) } } : undefined;
364
+ case "check":
365
+ return positional.length === 0 && args.range !== undefined ? { name: "workspace-changes", arguments: { ...args, ...(kinds.length ? { kind: kinds } : {}) } } : undefined;
361
366
  case "graph":
362
367
  return positional.length === 0 ? { name: "workspace-graph", arguments: { ...args, ...(kinds.length ? { kind: kinds } : {}) } } : undefined;
363
368
  default:
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Answers to decision points as records (ws-058, #2739), with a stub model
3
+ * decider, on a workspace in a throwaway git repository: `points ask` and
4
+ * `points answer` as `decide.ts` runs them, `points --json` as
5
+ * `points-cli.ts` prints it, and WSP116 in `check`.
6
+ */
7
+
8
+ import { cpSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
11
+ import { runDeclarationChecks } from "./checks";
12
+ import { cleanScratch, commitAll, contract, REPO, repo } from "./__fixtures__/contract-repo";
13
+ import { answerPoint, askPoint, type PointsWriteDocument } from "./decide";
14
+ import pointAnswerSchema from "./point-answer.schema.json";
15
+ import { workspacePoints } from "./points-cli";
16
+ import pointsSchema from "./points.schema.json";
17
+ import pointsWriteSchema from "./points-write.schema.json";
18
+ import type { ModelAsk, WireAnswer } from "./points";
19
+ import { queryRecords } from "./records-cli";
20
+
21
+ const REF = join(REPO, "reference-workspace");
22
+ const POINTS = JSON.parse(readFileSync(join(REF, "decisions", "points.json"), "utf-8")) as { points: Record<string, unknown> };
23
+
24
+ const points = {
25
+ points: {
26
+ ...POINTS.points,
27
+ "needs-decision": {
28
+ title: "Does this change need a decision",
29
+ question: { type: "noul", instructions: "Does the change make a choice nobody has recorded?", criteria: { true: "It needs a decision.", false: "It does not." } },
30
+ inputs: { commit: "the commit, as graph --intent lists it" },
31
+ deciders: [
32
+ { kind: "model", backend: "systemone", model: "jev-1.13.0", threshold: 0.8 },
33
+ { kind: "quorum", count: 2, roles: ["lead"] },
34
+ ],
35
+ },
36
+ },
37
+ };
38
+
39
+ const write = contract(pointsWriteSchema);
40
+ const read = contract(pointsSchema);
41
+
42
+ let root: string;
43
+ const on = "2026-09-25";
44
+
45
+ /** A stub model decider answering every point with `answer`, at the pinned model. */
46
+ const stub =
47
+ (answer: WireAnswer): ModelAsk =>
48
+ async (req) => ({ model: req.model, answer });
49
+
50
+ const big = { "work-item.criteria": 9, "work-item.fits_small": false, "work-item.fits_medium": false };
51
+ const confident: WireAnswer = { type: "choice", choice: "large", probabilities: { small: 0.01, medium: 0.07, large: 0.92 }, confidence: 0.88 };
52
+ const unsure: WireAnswer = { type: "choice", choice: "medium", probabilities: { small: 0.2, medium: 0.45, large: 0.35 }, confidence: 0.175 };
53
+
54
+ function ok(doc: PointsWriteDocument): Extract<PointsWriteDocument, { question: unknown }> {
55
+ write.expectValid(doc);
56
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
57
+ return doc;
58
+ }
59
+
60
+ function refused(doc: PointsWriteDocument): string {
61
+ write.expectValid(doc);
62
+ if (!("error" in doc)) throw new Error("expected a refusal");
63
+ return doc.error.code;
64
+ }
65
+
66
+ const answers = () => readdirSync(join(root, "answers")).filter((f) => f.endsWith(".md")).sort();
67
+ const fm = (path: string) => readFileSync(join(root, path), "utf-8");
68
+
69
+ beforeAll(() => {
70
+ root = repo(
71
+ {
72
+ "chant.workspace.json": JSON.stringify(
73
+ { name: "studio", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "a plain Node server" }], records: [{ kind: "answers/answer.kind.mjs" }] },
74
+ null,
75
+ 2,
76
+ ),
77
+ "app/server.mjs": "export const port = 8080;\n",
78
+ "decisions/points.json": JSON.stringify(points, null, 2),
79
+ ".chant/trust.json": JSON.stringify({ schema: 1, roles: { agent: ["bot"], lead: ["alice", "bob", "bot"] } }),
80
+ },
81
+ false,
82
+ );
83
+ cpSync(join(REF, "answers", "answer.kind.mjs"), join(root, "answers", "answer.kind.mjs"), { recursive: true });
84
+ cpSync(join(REF, "answers", "answer.schema.json"), join(root, "answers", "answer.schema.json"));
85
+ commitAll(root, "c0");
86
+ });
87
+
88
+ afterAll(cleanScratch);
89
+
90
+ describe("points ask (#2739)", () => {
91
+ test("a table's answer is answered at once", async () => {
92
+ const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { "work-item.fits_small": true }, subject: "W-001", on }));
93
+ expect(doc.question).toMatchObject({ state: "answered", open: false, answer: "small", decider: { kind: "table", row: 0 }, subject: "W-001", answeredOn: on, model: null });
94
+ expect(doc.written).toBe(true);
95
+ });
96
+
97
+ test("a model answer at or above its threshold is written as proposed, with its values and source", async () => {
98
+ const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, subject: "W-002", ask: stub(confident), on }));
99
+ expect(doc.question).toMatchObject({
100
+ state: "proposed",
101
+ open: true,
102
+ answer: "large",
103
+ decider: { kind: "model", backend: "systemone", model: "bosun-v3.1-1.7b" },
104
+ probabilities: { small: 0.01, medium: 0.07, large: 0.92 },
105
+ confidence: 0.88,
106
+ threshold: 0.8,
107
+ model: { answer: "large", confidence: 0.88, threshold: 0.8, observed: true },
108
+ escalations: [{ kind: "table", reason: "no row matches these inputs" }],
109
+ });
110
+ expect(doc.id).toBe(`slice-tier-${doc.question.inputsHash.slice(0, 12)}`);
111
+ expect(fm(doc.path)).toContain('source:\n via: "cli"\n model: "bosun-v3.1-1.7b"');
112
+ const records = await queryRecords({ kind: "answers/answer.kind.mjs", cwd: root });
113
+ if ("error" in records) throw new Error(records.error.message);
114
+ expect(records.records.find((r) => r.id === doc.id)).toMatchObject({ valid: true, state: "proposed", warnings: [] });
115
+ });
116
+
117
+ test("asking again with the same inputs returns the existing record", async () => {
118
+ const before = answers();
119
+ const first = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, subject: "W-002", ask: stub(unsure), on }));
120
+ // Key order does not matter: the inputs hash over canonical JSON.
121
+ const again = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: Object.fromEntries(Object.entries(big).reverse()), ask: stub(unsure) }));
122
+ for (const d of [first, again]) expect(d).toMatchObject({ reused: true, written: false, question: { state: "proposed", answer: "large" } });
123
+ expect(answers()).toEqual(before);
124
+ });
125
+
126
+ test("below its threshold the question is escalated and listed as open for a quorum, with the model's answer", async () => {
127
+ const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 }, subject: "W-003", ask: stub(unsure), on }));
128
+ expect(doc.question).toMatchObject({ state: "escalated", open: true, answer: null, decider: { kind: "quorum", count: 1 } });
129
+ expect(fm(doc.path)).not.toMatch(/^answer:/m);
130
+ const listed = await workspacePoints({ cwd: root, open: true });
131
+ read.expectValid(listed);
132
+ if ("error" in listed) throw new Error(listed.error.message);
133
+ const q = listed.questions.find((x) => x.id === doc.id)!;
134
+ expect(q).toMatchObject({ state: "escalated", open: true, current: true, model: { answer: "medium", confidence: 0.175, threshold: 0.8, model: "bosun-v3.1-1.7b", observed: false } });
135
+ expect(q.escalations.map((e) => e.reason)).toEqual(["no row matches these inputs", "not observed: confidence 0.175 is below the threshold 0.8"]);
136
+ expect(listed.questions.every((x) => x.open)).toBe(true);
137
+ expect(listed.points.map((p) => p.name)).toEqual(["slice-tier", "ship-skip", "finding-triage", "needs-a-decision", "needs-decision"]);
138
+ expect(listed.points[0].inputs[0]).toEqual({ name: "work-item.criteria", output: "work-item", description: "acceptance criteria in the work item" });
139
+
140
+ // A later ask still escalating keeps the standing record; one a model answers rewrites it, as proposed.
141
+ const still = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 } }));
142
+ expect(still).toMatchObject({ reused: true, question: { state: "escalated" } });
143
+ const now = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 }, ask: stub(confident), on }));
144
+ expect(now).toMatchObject({ id: doc.id, path: doc.path, reused: false, question: { state: "proposed", subject: "W-003", answer: "large" } });
145
+ });
146
+
147
+ test("with --dry-run nothing is written, and the text is returned", async () => {
148
+ const before = answers();
149
+ const doc = ok(await askPoint({ cwd: root, point: "ship-skip", inputs: { "release.units": 3 }, dryRun: true, on }));
150
+ expect(doc).toMatchObject({ written: false, dryRun: true, question: { state: "answered", answer: false } });
151
+ expect((doc as { text?: string }).text).toContain("answer: false");
152
+ expect(answers()).toEqual(before);
153
+ });
154
+
155
+ test("refusals: an unknown point, undeclared inputs, a closed-failing backend", async () => {
156
+ expect(refused(await askPoint({ cwd: root, point: "nope", inputs: {} }))).toBe("point-unknown");
157
+ expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: { "contract.size": 3 } }))).toBe("point-inputs-invalid");
158
+ expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: [] }))).toBe("point-inputs-invalid");
159
+ });
160
+ });
161
+
162
+ describe("points answer (#2739)", () => {
163
+ test("a person confirms a model's proposal, which keeps the model as its decider", async () => {
164
+ const asked = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, ask: stub(confident) }));
165
+ const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "large", by: ["alice"], on }));
166
+ expect(doc.question).toMatchObject({ state: "answered", open: false, answer: "large", decider: { kind: "model", model: "bosun-v3.1-1.7b" }, confidence: 0.88, answeredBy: ["alice"], answeredOn: on });
167
+ expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "small", by: ["alice"] }))).toBe("record-closed");
168
+ // Asking again returns the confirmed answer.
169
+ expect(ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, ask: stub(unsure) }))).toMatchObject({ reused: true, question: { state: "answered" } });
170
+ });
171
+
172
+ test("people answering otherwise is the quorum's answer, and the proposal moves into the escalations", async () => {
173
+ const asked = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 4 }, ask: stub(confident), on }));
174
+ expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "tiny", by: ["alice"] }))).toBe("answer-not-candidate");
175
+ const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "medium", by: ["bob"], on }));
176
+ expect(doc.question).toMatchObject({ state: "answered", answer: "medium", decider: { kind: "quorum", count: 1, by: ["bob"] }, probabilities: null, confidence: null });
177
+ expect(doc.question.escalations.at(-1)).toMatchObject({ kind: "model", answer: "large", confidence: 0.88, reason: "proposed large, and people answered medium" });
178
+ });
179
+
180
+ test("the quorum counts distinct people holding its roles, never an agent", async () => {
181
+ const asked = ok(await askPoint({ cwd: root, point: "needs-decision", inputs: { commit: { sha: "abc" } }, ask: stub({ type: "noul", noul: 0.55 }), on }));
182
+ expect(asked.question).toMatchObject({ state: "escalated", model: { answer: true, confidence: 0.55, observed: false } });
183
+ expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "yes", by: ["alice", "Alice ", "bot", "carol"] }))).toBe("quorum-not-met");
184
+ const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "yes", by: ["alice", "bob"], on }));
185
+ expect(doc.question).toMatchObject({ state: "answered", answer: true, decider: { kind: "quorum", count: 2, roles: ["lead"], by: ["alice", "bob"] } });
186
+ });
187
+
188
+ test("an unknown id is refused", async () => {
189
+ expect(refused(await answerPoint({ cwd: root, id: "slice-tier-000000000000", answer: "small", by: ["alice"] }))).toBe("record-not-found");
190
+ });
191
+
192
+ test("every record written follows point-answer.schema.json, and the reference workspace carries a copy of it", () => {
193
+ expect(JSON.parse(readFileSync(join(REF, "answers", "answer.schema.json"), "utf-8"))).toEqual(pointAnswerSchema);
194
+ expect(answers().length).toBeGreaterThan(3);
195
+ });
196
+ });
197
+
198
+ describe("the points file in check and on read (#2738)", () => {
199
+ test("a changed point warns on its answers, and an invalid points file is WSP116 and a points-invalid source", async () => {
200
+ const file = join(root, "decisions", "points.json");
201
+ const changed = JSON.parse(readFileSync(file, "utf-8"));
202
+ changed.points["slice-tier"].title = "Which builder builds this work item";
203
+ writeFileSync(file, JSON.stringify(changed, null, 2));
204
+ const doc = await workspacePoints({ cwd: root });
205
+ read.expectValid(doc);
206
+ if ("error" in doc) throw new Error(doc.error.message);
207
+ const slice = doc.questions.filter((q) => q.point === "slice-tier");
208
+ expect(slice.every((q) => q.current === false && q.warnings.some((w) => w.code === "answer-point-changed"))).toBe(true);
209
+ expect((await runDeclarationChecks(root)).diagnostics.filter((d) => d.ruleId === "WSP116")).toEqual([]);
210
+
211
+ changed.points["slice-tier"].deciders.pop();
212
+ writeFileSync(file, JSON.stringify(changed, null, 2));
213
+ const report = await runDeclarationChecks(root);
214
+ expect(report.diagnostics.filter((d) => d.ruleId === "WSP116").map((d) => d.message)).toEqual([
215
+ "the workspace declares the answer kind answers/answer.kind.mjs, whose points file decisions/points.json has points.slice-tier.deciders, which must end in a quorum: when no table row or model answers, people do",
216
+ ]);
217
+ expect(report.ok).toBe(false);
218
+ const broken = await workspacePoints({ cwd: root });
219
+ read.expectValid(broken);
220
+ if ("error" in broken) throw new Error(broken.error.message);
221
+ expect(broken.sources[0].reason?.code).toBe("points-invalid");
222
+ expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: big }))).toBe("points-invalid");
223
+ });
224
+ });