@intentius/chant 0.91.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 (344) 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/graph.d.ts.map +1 -1
  4. package/dist/cli/handlers/operator.d.ts +6 -0
  5. package/dist/cli/handlers/operator.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts.map +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  9. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  10. package/dist/cli/plugins.d.ts.map +1 -1
  11. package/dist/cli/registry.d.ts +12 -2
  12. package/dist/cli/registry.d.ts.map +1 -1
  13. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  14. package/dist/config.d.ts +22 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/lexicon-module.d.ts +22 -0
  17. package/dist/lexicon-module.d.ts.map +1 -1
  18. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  19. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  20. package/dist/lifecycle/run-ledger.d.ts +2 -0
  21. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  22. package/dist/lifecycle/symptoms.d.ts +51 -0
  23. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  24. package/dist/lint/rules/index.d.ts +4 -2
  25. package/dist/lint/rules/index.d.ts.map +1 -1
  26. package/dist/lint/rules/op/index.d.ts +14 -3
  27. package/dist/lint/rules/op/index.d.ts.map +1 -1
  28. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  29. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  31. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  33. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  35. package/dist/op/activities/activity-contracts.d.ts +201 -0
  36. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  37. package/dist/op/activities/apply.d.ts.map +1 -1
  38. package/dist/op/activities/change-coverage.d.ts +44 -0
  39. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  40. package/dist/op/activities/converge.d.ts +24 -1
  41. package/dist/op/activities/converge.d.ts.map +1 -1
  42. package/dist/op/activities/decide.d.ts +107 -0
  43. package/dist/op/activities/decide.d.ts.map +1 -0
  44. package/dist/op/activities/index.d.ts +10 -0
  45. package/dist/op/activities/index.d.ts.map +1 -1
  46. package/dist/op/activities/shell.d.ts +32 -0
  47. package/dist/op/activities/shell.d.ts.map +1 -1
  48. package/dist/op/activities/source-release.d.ts +86 -0
  49. package/dist/op/activities/source-release.d.ts.map +1 -0
  50. package/dist/op/activities/source-rollback.d.ts +100 -0
  51. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  52. package/dist/op/activities/work-evidence.d.ts +42 -0
  53. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  54. package/dist/op/activity-contract.d.ts +1 -0
  55. package/dist/op/activity-contract.d.ts.map +1 -1
  56. package/dist/op/activity-profiles.d.ts +16 -0
  57. package/dist/op/activity-profiles.d.ts.map +1 -1
  58. package/dist/op/builders.d.ts +84 -2
  59. package/dist/op/builders.d.ts.map +1 -1
  60. package/dist/op/composites/converge-op.d.ts +31 -4
  61. package/dist/op/composites/converge-op.d.ts.map +1 -1
  62. package/dist/op/decide-backend.d.ts +116 -0
  63. package/dist/op/decide-backend.d.ts.map +1 -0
  64. package/dist/op/decide-config.d.ts +120 -0
  65. package/dist/op/decide-config.d.ts.map +1 -0
  66. package/dist/op/decide-read-inputs.d.ts +40 -0
  67. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  68. package/dist/op/gate-wait.d.ts +43 -0
  69. package/dist/op/gate-wait.d.ts.map +1 -0
  70. package/dist/op/gate.d.ts +5 -2
  71. package/dist/op/gate.d.ts.map +1 -1
  72. package/dist/op/index.d.ts +11 -4
  73. package/dist/op/index.d.ts.map +1 -1
  74. package/dist/op/local-executor.d.ts +29 -4
  75. package/dist/op/local-executor.d.ts.map +1 -1
  76. package/dist/op/local-output.d.ts +2 -0
  77. package/dist/op/local-output.d.ts.map +1 -1
  78. package/dist/op/op-ir.d.ts +2 -0
  79. package/dist/op/op-ir.d.ts.map +1 -1
  80. package/dist/op/op-verb-class.d.ts.map +1 -1
  81. package/dist/op/operator.d.ts +97 -1
  82. package/dist/op/operator.d.ts.map +1 -1
  83. package/dist/op/runtime.d.ts +37 -5
  84. package/dist/op/runtime.d.ts.map +1 -1
  85. package/dist/op/runtimes/local.d.ts.map +1 -1
  86. package/dist/op/source-archive.d.ts +63 -0
  87. package/dist/op/source-archive.d.ts.map +1 -0
  88. package/dist/op/steward-points.d.ts +136 -0
  89. package/dist/op/steward-points.d.ts.map +1 -0
  90. package/dist/op/steward-turn.d.ts +53 -0
  91. package/dist/op/steward-turn.d.ts.map +1 -0
  92. package/dist/op/steward.d.ts +22 -1
  93. package/dist/op/steward.d.ts.map +1 -1
  94. package/dist/op/types.d.ts +7 -0
  95. package/dist/op/types.d.ts.map +1 -1
  96. package/dist/op/work-lease-run.d.ts +1 -1
  97. package/dist/op/work-lease-run.d.ts.map +1 -1
  98. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  100. package/dist/workspace/answers-ledger.d.ts +76 -0
  101. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  102. package/dist/workspace/changes-cli.d.ts +16 -0
  103. package/dist/workspace/changes-cli.d.ts.map +1 -0
  104. package/dist/workspace/changes.d.ts +200 -0
  105. package/dist/workspace/changes.d.ts.map +1 -0
  106. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  107. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  108. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  118. package/dist/workspace/chant-migrations.d.ts +99 -0
  119. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  120. package/dist/workspace/checks/boxes.d.ts +34 -1
  121. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  122. package/dist/workspace/checks/diagrams.d.ts +31 -0
  123. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  124. package/dist/workspace/checks/records.d.ts +7 -0
  125. package/dist/workspace/checks/records.d.ts.map +1 -1
  126. package/dist/workspace/checks.d.ts +3 -0
  127. package/dist/workspace/checks.d.ts.map +1 -1
  128. package/dist/workspace/compose-graph.d.ts +2 -0
  129. package/dist/workspace/compose-graph.d.ts.map +1 -1
  130. package/dist/workspace/conformance/index.d.ts.map +1 -1
  131. package/dist/workspace/decide.d.ts +49 -4
  132. package/dist/workspace/decide.d.ts.map +1 -1
  133. package/dist/workspace/declaration.d.ts +52 -0
  134. package/dist/workspace/declaration.d.ts.map +1 -1
  135. package/dist/workspace/declaration.schema.json +114 -1
  136. package/dist/workspace/declared-kinds.d.ts +20 -0
  137. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  138. package/dist/workspace/generated-files.d.ts +7 -0
  139. package/dist/workspace/generated-files.d.ts.map +1 -1
  140. package/dist/workspace/intent.d.ts.map +1 -1
  141. package/dist/workspace/lineage-check.d.ts +3 -0
  142. package/dist/workspace/lineage-check.d.ts.map +1 -1
  143. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  144. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  145. package/dist/workspace/ls.d.ts +39 -1
  146. package/dist/workspace/ls.d.ts.map +1 -1
  147. package/dist/workspace/point-answer.schema.json +95 -0
  148. package/dist/workspace/points-cli.d.ts.map +1 -1
  149. package/dist/workspace/points.d.ts +1 -1
  150. package/dist/workspace/reason-codes.d.ts +14 -2
  151. package/dist/workspace/reason-codes.d.ts.map +1 -1
  152. package/dist/workspace/records-cli.d.ts +11 -1
  153. package/dist/workspace/records-cli.d.ts.map +1 -1
  154. package/dist/workspace/records-close.d.ts +1 -1
  155. package/dist/workspace/records-close.d.ts.map +1 -1
  156. package/dist/workspace/records-write.d.ts +11 -5
  157. package/dist/workspace/records-write.d.ts.map +1 -1
  158. package/dist/workspace/records.d.ts +34 -4
  159. package/dist/workspace/records.d.ts.map +1 -1
  160. package/dist/workspace/status-gates.d.ts +7 -2
  161. package/dist/workspace/status-gates.d.ts.map +1 -1
  162. package/dist/workspace/status-stewards.d.ts +59 -1
  163. package/dist/workspace/status-stewards.d.ts.map +1 -1
  164. package/dist/workspace/status.d.ts +16 -0
  165. package/dist/workspace/status.d.ts.map +1 -1
  166. package/dist/workspace/work-cli.d.ts +113 -2
  167. package/dist/workspace/work-cli.d.ts.map +1 -1
  168. package/dist/workspace/work-evidence.d.ts +74 -0
  169. package/dist/workspace/work-evidence.d.ts.map +1 -0
  170. package/dist/workspace/work.d.ts +43 -1
  171. package/dist/workspace/work.d.ts.map +1 -1
  172. package/package.json +1 -1
  173. package/src/cli/commands/audit.test.ts +9 -1
  174. package/src/cli/commands/build.ts +3 -2
  175. package/src/cli/commands/lint.ts +7 -1
  176. package/src/cli/handlers/graph.test.ts +20 -0
  177. package/src/cli/handlers/graph.ts +16 -2
  178. package/src/cli/handlers/operator.ts +34 -2
  179. package/src/cli/handlers/run.test.ts +154 -2
  180. package/src/cli/handlers/run.ts +121 -2
  181. package/src/cli/main.test.ts +10 -0
  182. package/src/cli/main.ts +19 -0
  183. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  184. package/src/cli/mcp/workspace-tools.ts +62 -4
  185. package/src/cli/plugins.ts +11 -3
  186. package/src/cli/registry.ts +12 -2
  187. package/src/codegen/pinned-upgrade.ts +2 -1
  188. package/src/codegen/release-wiring.test.ts +49 -2
  189. package/src/components/capability-plugin-loader.ts +3 -3
  190. package/src/components/cli-support.ts +2 -2
  191. package/src/config.ts +11 -0
  192. package/src/lexicon-module.ts +56 -1
  193. package/src/lexicon-package.test.ts +96 -0
  194. package/src/lifecycle/converge-ledger.ts +20 -0
  195. package/src/lifecycle/run-ledger.ts +6 -0
  196. package/src/lifecycle/symptoms.ts +95 -0
  197. package/src/lint/rules/index.ts +6 -2
  198. package/src/lint/rules/op/index.ts +17 -4
  199. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  200. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  201. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  202. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  203. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  204. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  205. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  206. package/src/op/activities/activity-contracts.ts +190 -1
  207. package/src/op/activities/apply.ts +7 -6
  208. package/src/op/activities/change-coverage.ts +60 -0
  209. package/src/op/activities/converge.test.ts +55 -1
  210. package/src/op/activities/converge.ts +208 -29
  211. package/src/op/activities/decide.live.test.ts +78 -0
  212. package/src/op/activities/decide.test.ts +300 -0
  213. package/src/op/activities/decide.ts +267 -0
  214. package/src/op/activities/index.ts +21 -0
  215. package/src/op/activities/shell.test.ts +91 -1
  216. package/src/op/activities/shell.ts +91 -2
  217. package/src/op/activities/source-release.test.ts +118 -0
  218. package/src/op/activities/source-release.ts +168 -0
  219. package/src/op/activities/source-rollback.test.ts +131 -0
  220. package/src/op/activities/source-rollback.ts +239 -0
  221. package/src/op/activities/work-evidence.ts +62 -0
  222. package/src/op/activity-contract-registry.test.ts +2 -0
  223. package/src/op/activity-contract-registry.ts +2 -2
  224. package/src/op/activity-contract.test.ts +12 -0
  225. package/src/op/activity-contract.ts +6 -1
  226. package/src/op/activity-profiles.ts +29 -0
  227. package/src/op/activity-registry.test.ts +1 -1
  228. package/src/op/activity-registry.ts +2 -2
  229. package/src/op/builders.ts +119 -5
  230. package/src/op/command-gate.test.ts +192 -0
  231. package/src/op/composites/converge-op.test.ts +27 -1
  232. package/src/op/composites/converge-op.ts +69 -15
  233. package/src/op/decide-backend.test.ts +44 -0
  234. package/src/op/decide-backend.ts +212 -0
  235. package/src/op/decide-config.test.ts +52 -0
  236. package/src/op/decide-config.ts +80 -0
  237. package/src/op/decide-read-inputs.ts +117 -0
  238. package/src/op/gate-approval.ts +2 -2
  239. package/src/op/gate-wait.ts +57 -0
  240. package/src/op/gate.ts +8 -3
  241. package/src/op/generate-pipeline.ts +2 -2
  242. package/src/op/index.ts +18 -3
  243. package/src/op/local-executor.ts +151 -15
  244. package/src/op/local-output.test.ts +33 -0
  245. package/src/op/local-output.ts +20 -2
  246. package/src/op/op-ir.test.ts +10 -0
  247. package/src/op/op-ir.ts +4 -0
  248. package/src/op/op-verb-class.ts +1 -0
  249. package/src/op/operator.ts +290 -11
  250. package/src/op/runtime.ts +31 -8
  251. package/src/op/runtimes/local.ts +3 -1
  252. package/src/op/source-archive.ts +161 -0
  253. package/src/op/steward-points.test.ts +313 -0
  254. package/src/op/steward-points.ts +194 -0
  255. package/src/op/steward-turn.ts +79 -0
  256. package/src/op/steward.test.ts +87 -3
  257. package/src/op/steward.ts +25 -1
  258. package/src/op/types.ts +7 -0
  259. package/src/op/work-lease-run.test.ts +20 -1
  260. package/src/op/work-lease-run.ts +2 -2
  261. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  262. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  263. package/src/workspace/answers-ledger.ts +118 -0
  264. package/src/workspace/changes-cli.ts +56 -0
  265. package/src/workspace/changes.schema.json +472 -0
  266. package/src/workspace/changes.test.ts +315 -0
  267. package/src/workspace/changes.ts +512 -0
  268. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  269. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  270. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  271. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  272. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  273. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  274. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  275. package/src/workspace/chant-migrations.ts +164 -0
  276. package/src/workspace/check-contract.test.ts +4 -2
  277. package/src/workspace/check.schema.json +15 -5
  278. package/src/workspace/checks/boxes.test.ts +80 -1
  279. package/src/workspace/checks/boxes.ts +136 -1
  280. package/src/workspace/checks/diagrams.test.ts +134 -0
  281. package/src/workspace/checks/diagrams.ts +108 -0
  282. package/src/workspace/checks/records.ts +47 -0
  283. package/src/workspace/checks.ts +8 -1
  284. package/src/workspace/compose-graph.ts +2 -0
  285. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  286. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  287. package/src/workspace/conformance/index.ts +5 -0
  288. package/src/workspace/decide.test.ts +1 -1
  289. package/src/workspace/decide.ts +156 -23
  290. package/src/workspace/declaration.schema.json +114 -1
  291. package/src/workspace/declaration.test.ts +58 -0
  292. package/src/workspace/declaration.ts +104 -0
  293. package/src/workspace/declared-kinds.test.ts +5 -5
  294. package/src/workspace/declared-kinds.ts +46 -1
  295. package/src/workspace/generated-files.ts +14 -0
  296. package/src/workspace/graph-cli.ts +1 -1
  297. package/src/workspace/graph.schema.json +9 -1
  298. package/src/workspace/intent.schema.json +8 -2
  299. package/src/workspace/intent.ts +3 -10
  300. package/src/workspace/lineage-check.ts +16 -1
  301. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  302. package/src/workspace/lineage-upgrade.test.ts +70 -0
  303. package/src/workspace/lineage-upgrade.ts +81 -10
  304. package/src/workspace/ls-contract.test.ts +57 -0
  305. package/src/workspace/ls.schema.json +46 -1
  306. package/src/workspace/ls.ts +62 -3
  307. package/src/workspace/points-cli.ts +14 -3
  308. package/src/workspace/points-write.schema.json +11 -1
  309. package/src/workspace/points.schema.json +33 -0
  310. package/src/workspace/points.test.ts +1 -1
  311. package/src/workspace/points.ts +1 -1
  312. package/src/workspace/read-contract.test.ts +1 -1
  313. package/src/workspace/reason-codes.test.ts +11 -1
  314. package/src/workspace/reason-codes.ts +18 -2
  315. package/src/workspace/records-amend.schema.json +2 -0
  316. package/src/workspace/records-cli.ts +13 -3
  317. package/src/workspace/records-close.schema.json +2 -0
  318. package/src/workspace/records-new.schema.json +2 -0
  319. package/src/workspace/records-review.schema.json +2 -0
  320. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  321. package/src/workspace/records-sessions-write.test.ts +2 -34
  322. package/src/workspace/records-write.e2e.test.ts +108 -0
  323. package/src/workspace/records-write.test.ts +32 -41
  324. package/src/workspace/records-write.ts +33 -15
  325. package/src/workspace/records.schema.json +36 -2
  326. package/src/workspace/records.test.ts +48 -3
  327. package/src/workspace/records.ts +109 -3
  328. package/src/workspace/status-contract.test.ts +13 -8
  329. package/src/workspace/status-gates.ts +9 -4
  330. package/src/workspace/status-stewards.ts +108 -4
  331. package/src/workspace/status.schema.json +111 -7
  332. package/src/workspace/status.ts +36 -1
  333. package/src/workspace/work-acceptance.test.ts +274 -0
  334. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  335. package/src/workspace/work-cli.ts +193 -10
  336. package/src/workspace/work-evidence.schema.json +212 -0
  337. package/src/workspace/work-evidence.ts +169 -0
  338. package/src/workspace/work-history.schema.json +349 -0
  339. package/src/workspace/work-history.test.ts +156 -0
  340. package/src/workspace/work.test.ts +3 -2
  341. package/src/workspace/work.ts +100 -1
  342. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  343. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  344. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://intentius.io/chant/schemas/workspace/points-write/v1/points-write.schema.json",
4
4
  "title": "chant workspace points ask and answer output",
5
- "description": "What `chant workspace points ask` and `points answer` print (ws-058, #2739): the answer record asked for or written, as `points --json` lists a question. ask returns the record that is there when the same point, declaration and inputs were answered or proposed before (reused), and otherwise writes one: answered from a table, proposed from a model at or above its threshold, escalated to people below it. answer records people's answer to an open question once the point's quorum is met. Nothing is committed, and nothing is written on a refusal or with --dry-run. The error codes are a closed list, each in the one closed list of `reason-codes.ts`. Written at contract version 1 of the write documents.",
5
+ "description": "What `chant workspace points ask` and `points answer` print (ws-058, #2739): the answer record asked for or written, as `points --json` lists a question. ask returns the record that is there when the same point, declaration and inputs were answered or proposed before (reused), and otherwise writes one: answered from a table, proposed from a model at or above its threshold, escalated to people below it. answer records people's answer to an open question once the point's quorum is met. Nothing is committed to the working branch, and nothing is written on a refusal or with --dry-run. In a steward's turn a new record goes on the lifecycle ledger instead of the working tree (#2786), as a commit on chant/lifecycle, and a record already held there is rewritten there. The error codes are a closed list, each in the one closed list of `reason-codes.ts`. Written at contract version 1 of the write documents.",
6
6
  "oneOf": [
7
7
  {
8
8
  "$ref": "#/$defs/result"
@@ -372,6 +372,13 @@
372
372
  "type": "string"
373
373
  }
374
374
  },
375
+ "ledger": {
376
+ "type": [
377
+ "string",
378
+ "null"
379
+ ],
380
+ "description": "Where the record is held when it is on the lifecycle ledger rather than in the working tree (#2786): chant/lifecycle:<path>, which git show reads. A question asked in a steward's turn is held there, since a steward never writes the checkout; people's answer to it is written there too. null for a record in the working tree. path is where the record reads as being either way."
381
+ },
375
382
  "askedOn": {
376
383
  "type": [
377
384
  "string",
@@ -465,6 +472,7 @@
465
472
  "point-decider-failed",
466
473
  "answer-not-candidate",
467
474
  "quorum-not-met",
475
+ "answer-in-steward-turn",
468
476
  "record-not-found",
469
477
  "record-closed",
470
478
  "record-id-taken",
@@ -473,6 +481,8 @@
473
481
  "record-id-duplicate",
474
482
  "record-supersedes-unknown",
475
483
  "record-supersedes-conflict",
484
+ "record-remediates-unknown",
485
+ "record-remediates-not-closed",
476
486
  "session-seal-mismatch",
477
487
  "session-verdict-unknown-record"
478
488
  ]
@@ -583,6 +583,39 @@
583
583
  "type": "string"
584
584
  }
585
585
  },
586
+ "askedBy": {
587
+ "description": "The steward whose turn asked the question (#2749), and the run it was in, or null. The steward waits on the question and never answers it: a person does.",
588
+ "oneOf": [
589
+ {
590
+ "type": "null"
591
+ },
592
+ {
593
+ "type": "object",
594
+ "required": [
595
+ "steward",
596
+ "run"
597
+ ],
598
+ "properties": {
599
+ "steward": {
600
+ "type": "string"
601
+ },
602
+ "run": {
603
+ "type": [
604
+ "string",
605
+ "null"
606
+ ]
607
+ }
608
+ }
609
+ }
610
+ ]
611
+ },
612
+ "ledger": {
613
+ "type": [
614
+ "string",
615
+ "null"
616
+ ],
617
+ "description": "Where the record is held when it is on the lifecycle ledger rather than in the working tree (#2786): chant/lifecycle:<path>, which git show reads. A question asked in a steward's turn is held there, since a steward never writes the checkout; people's answer to it is written there too. null for a record in the working tree. path is where the record reads as being either way."
618
+ },
586
619
  "askedOn": {
587
620
  "type": [
588
621
  "string",
@@ -142,7 +142,7 @@ describe("the decision points schema (#2738)", () => {
142
142
 
143
143
  test("the reference workspace's points validate", () => {
144
144
  const points = parsePoints(readFileSync(join(REPO, "reference-workspace", "decisions", "points.json"), "utf-8"), "decisions/points.json");
145
- expect(Object.keys(points)).toEqual(["slice-tier", "ship-skip"]);
145
+ expect(Object.keys(points)).toEqual(["slice-tier", "ship-skip", "finding-triage", "needs-a-decision"]);
146
146
  });
147
147
 
148
148
  test("a point whose chain does not end in a quorum is refused", () => {
@@ -48,7 +48,7 @@ export const POINT_INPUT_OUTPUTS = {
48
48
  record: { schema: "records", def: "record", description: "a record of any kind, as records --json lists it" },
49
49
  decision: { schema: "records", def: "record", description: "a decision record, as records --json lists it" },
50
50
  "work-item": { schema: "records", def: "record", description: "a work item record, as records --json lists it (#2683)" },
51
- finding: { schema: "intent", def: "finding", description: "a finding of graph --intent" },
51
+ finding: { schema: "intent", def: "finding", description: "a finding of graph --intent, or of check --changes (#2794)" },
52
52
  region: { schema: "intent", def: "region", description: "the region an intent graph covers" },
53
53
  commit: { schema: "intent", def: "commit", description: "a commit in an intent graph's window" },
54
54
  member: { schema: "ls", def: "member", description: "a member, as ls lists it" },
@@ -192,7 +192,7 @@ describe("every schema against the reference workspace (#2543)", () => {
192
192
  const doc = await workspacePoints({ cwd: FIXTURE, at });
193
193
  expectValid(doc);
194
194
  if ("error" in doc) throw new Error(doc.error.message);
195
- expect(doc.points.map((p) => p.name)).toEqual(["slice-tier", "ship-skip"]);
195
+ expect(doc.points.map((p) => p.name)).toEqual(["slice-tier", "ship-skip", "finding-triage", "needs-a-decision"]);
196
196
  }
197
197
  const open = await workspacePoints({ cwd: FIXTURE, open: true });
198
198
  expectValid(open);
@@ -9,6 +9,7 @@ import { readdirSync, readFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { describe, expect, test } from "vitest";
11
11
  import { REPO } from "./__fixtures__/contract-repo";
12
+ import { CHANGES_ERROR_CODES, CHANGES_FINDING_CODES } from "./changes";
12
13
  import { MEMBER_RUN_REASON_CODES } from "./compose-graph";
13
14
  import { COMPOSITES_ENVIRONMENT_REASON_CODES, COMPOSITES_ERROR_CODES, COMPOSITES_REASON_CODES, COMPOSITES_RUNTIME_REASON_CODES } from "./composites";
14
15
  import { WORKSPACE_ERROR_CODES } from "./declaration";
@@ -26,7 +27,10 @@ import { RECORDS_SINCE_ERROR_CODES, RECORDS_SINCE_REASON_CODES } from "./records
26
27
  import { STATUS_ERROR_CODES, STATUS_GATE_REASON_CODES, STATUS_REASON_CODES, STATUS_STEWARD_REASON_CODES } from "./status";
27
28
  import { WORK_WARNING_CODES } from "./work";
28
29
  import { BOX_FINDING_CODES } from "./checks/boxes";
29
- import { WORK_ERROR_CODES, WORK_LEASE_REFUSALS } from "./work-cli";
30
+ import { DIAGRAM_FINDING_CODES } from "./checks/diagrams";
31
+ import { WORK_ERROR_CODES, WORK_HISTORY_ERROR_CODES, WORK_LEASE_REFUSALS } from "./work-cli";
32
+ import { WORK_EVIDENCE_ERROR_CODES } from "./work-evidence";
33
+ import { RECORD_FINDING_CODES } from "./checks/records";
30
34
  import { ANSWER_WARNING_CODES } from "./points";
31
35
  import { POINTS_ERROR_CODES, POINTS_SOURCE_REASON_CODES } from "./points-cli";
32
36
  import { POINTS_WRITE_ERROR_CODES } from "./decide";
@@ -42,6 +46,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
42
46
  CHECK_CODES,
43
47
  CHECK_ERROR_CODES,
44
48
  BOX_FINDING_CODES,
49
+ DIAGRAM_FINDING_CODES,
45
50
  STATUS_REASON_CODES,
46
51
  STATUS_ERROR_CODES,
47
52
  STATUS_GATE_REASON_CODES,
@@ -62,12 +67,17 @@ const PER_COMMAND: Record<string, readonly string[]> = {
62
67
  INTENT_ERROR_CODES,
63
68
  INTENT_FINDING_CODES,
64
69
  INTENT_REASON_CODES,
70
+ CHANGES_FINDING_CODES,
71
+ CHANGES_ERROR_CODES,
65
72
  COMPOSITES_ERROR_CODES,
66
73
  COMPOSITES_REASON_CODES,
67
74
  COMPOSITES_RUNTIME_REASON_CODES,
68
75
  COMPOSITES_ENVIRONMENT_REASON_CODES,
69
76
  WORK_ERROR_CODES,
77
+ WORK_HISTORY_ERROR_CODES,
70
78
  WORK_LEASE_REFUSALS,
79
+ WORK_EVIDENCE_ERROR_CODES,
80
+ RECORD_FINDING_CODES,
71
81
  ANSWER_WARNING_CODES,
72
82
  POINTS_ERROR_CODES,
73
83
  POINTS_SOURCE_REASON_CODES,
@@ -60,13 +60,15 @@ export const REASONS = {
60
60
  // A member's stewards status can't fully list (status, #2731).
61
61
  "stewards-unreadable": "An *.op.ts file could not be imported, so a steward it declares may be missing.",
62
62
  "stewards-conflict": "A steward was dropped: its name, or an Op it lists, belongs to another steward.",
63
- "steward-runs-unreadable": "Reading an Op's run ledger failed, so its last run is null.",
63
+ "steward-runs-unreadable": "Reading an Op's run ledger, or a ConvergeOp's converge ledger, failed, so its last run or last tick is null.",
64
64
  // A record that isn't valid (records).
65
65
  "record-unparseable": "No front matter, a YAML error or a value outside the JSON subset of YAML, or for a JSON kind a file that is not one object or repeats a member name.",
66
66
  "record-schema-invalid": "The record's front matter, or its JSON object, does not match the kind's schema.",
67
67
  "record-id-duplicate": "Another record earlier in path order has the same id.",
68
68
  "record-supersedes-unknown": "A supersedes link names an id no record has.",
69
69
  "record-supersedes-conflict": "A second closed record supersedes a record another one already superseded.",
70
+ "record-remediates-unknown": "A remediates link names an id no record has.",
71
+ "record-remediates-not-closed": "A remediates link names a record that isn't closed; a record still open is amended instead.",
70
72
  // A review session that isn't valid (records, #2673).
71
73
  "session-seal-mismatch": "A closed session's seal is not the digest of its text: the session changed after it closed.",
72
74
  "session-verdict-unknown-record": "A session's verdict names a record that none of the session kind's subject records has.",
@@ -86,6 +88,8 @@ export const REASONS = {
86
88
  "work-done-unpinned": "A work record is done and its evidence list is empty: nothing shows the work was done.",
87
89
  "work-closed-without-date": "A work record is done or dropped and has no closing date.",
88
90
  "work-done-gap-open": "A work record is done, and the finding it came from still fires on its region. graph --intent raises it, and records does by walking that region.",
91
+ "work-acceptance-unmet": "A work record is done, and one of its acceptance criteria has no passing evidence of the verification it expects. check fails on it (WSP117).",
92
+ "work-acceptance-self-verified": "A passing manual verdict on a work record's criterion names the record's implementer, so it does not count: a manual verdict comes from someone else.",
89
93
  // An answer to a decision point that is valid but warned about (records, points, ws-058, #2739).
90
94
  "answer-points-unreadable": "The points file the answer kind names can't be read, or is not valid.",
91
95
  "answer-point-unknown": "The answer's point is not declared in the points file the answer kind names.",
@@ -136,7 +140,8 @@ export const REASONS = {
136
140
  "point-inputs-invalid": "The inputs given to an ask are not a JSON object of the point's declared inputs.",
137
141
  "point-decider-failed": "A model decider that fails closed (unreachable: fail) could not answer, so the ask wrote nothing.",
138
142
  "answer-not-candidate": "The people's answer is not one of the question's candidates.",
139
- "quorum-not-met": "Too few of the people who answered count toward the point's quorum: distinct, not holding the agent role, and holding one of its roles when it names any.",
143
+ "quorum-not-met": "Too few of the people who answered count toward the point's quorum: distinct, not holding the agent role, not the steward that asked, and holding one of its roles when it names any.",
144
+ "answer-in-steward-turn": "The answer was given during a steward's turn, or by a process it started: a steward never answers a decision point, and a person answers it through hud or at a shell.",
140
145
  // records --since that fails (#2673).
141
146
  "since-rev-unknown": "--since names no commit, or a session with no opening revision and no commit that added it.",
142
147
  "since-session-unknown": "--since has the shape of a session id and names no commit, and no session the kind or the declaration reads has that id.",
@@ -164,6 +169,9 @@ export const REASONS = {
164
169
  "intent-decision-unimplemented": "A decided decision constrains the region, no work item that is not dropped implements it, and no commit falls in its window.",
165
170
  "intent-work-blocked": "A work item constraining the region has commits in its window while a work item it needs is not done.",
166
171
  "intent-work-open-decided-code": "Commits in the region are a decision's own work while the work item implementing that decision is still open.",
172
+ // The forward coverage check (check --changes, #2773): findings, one per changed path.
173
+ "change-uncovered": "A path the diff changes is covered by no current decided record and no open work item, by path or by its member.",
174
+ "change-out-of-scope": "A record in hand for the change, such as the work item it is for or a decision that item implements, lists a path the diff changes in its out_of_scope.",
167
175
  // Composite instances joined to components (graph --composites, #2662): why the list is empty or has no component.
168
176
  "composites-no-chant-member": "No member of kind chant was read, so nothing declares a composite instance or a component.",
169
177
  "composites-none-declared": "The members read declare no composite instance.",
@@ -175,14 +183,22 @@ export const REASONS = {
175
183
  "environments-none-declared": "The member's chant.config.ts declares no environments, so only local and the environments in its ledger are listed.",
176
184
  "environments-ledger-undeclared": "The member's ledger has releases in an environment its config's environments don't cover, so chant run --env would refuse it and it is not listed.",
177
185
  "environments-ledger-unreadable": "The chant/lifecycle branch exists and the member's ledger environments could not be listed.",
186
+ // A declared diagram's source or render (check, #2764). Each is also a WSP finding.
187
+ "diagram-source-missing": "A declared diagram's source file does not exist in the tree read.",
188
+ "diagram-render-missing": "A declared diagram's render file does not exist in the tree read.",
189
+ "diagram-render-drift": "A declared diagram records a sourceHash, and the source's bytes now hash to something else: the render is stale for its source.",
178
190
  // A box that holds a credential (check, #2726). Each is also a WSP finding.
179
191
  "box-credential-declared": "A file in a box member's directory carries a literal secret: a credential's shape, or a literal where a credential goes. A variable or secret-manager reference is not one.",
180
192
  "box-capability-unbrokered": "A capability in a member's box block names no broker, so the box would hold its credential.",
193
+ // The credential fountain itself hands a persistent box (check, #2780). Also a WSP finding.
194
+ "box-fountain-callback-undeclared": "A box member builds a fountain Box, whose persistent sandbox fountain gives a callback token scoped to its owner, and the member's box block does not declare the fountain-callback capability brokered by fountain with scope owner.",
181
195
  // The work lease (chant workspace work claim|renew|release, #2732): why the command could not run.
182
196
  "work-kind-missing": "No work kind to find the item in: --kind names a kind with no work block, or the declaration names no work kind.",
183
197
  "work-kind-ambiguous": "More than one declared work kind has a record with the id, so --kind must name one.",
184
198
  "work-item-unknown": "No work record has the id, so there is nothing to lease.",
185
199
  "work-item-closed": "A claim on a work item in a closed state, such as done or dropped: there is no work left to claim.",
200
+ // Evidence attached to a criterion under the run's work lease (#2772): why it was refused. Nothing is written.
201
+ "work-criterion-unknown": "The work record lists no acceptance criterion with the id the evidence names, or its kind has no acceptance criteria.",
186
202
  // The work lease: why a claim, renew or release was refused (exit 2).
187
203
  "lease-held": "Someone holds a live lease on the work item: another worker, or, for a claim, the same one.",
188
204
  "lease-not-held": "Nobody holds a live lease on the work item: it expired, was released or was never claimed.",
@@ -180,6 +180,8 @@
180
180
  "record-id-duplicate",
181
181
  "record-supersedes-unknown",
182
182
  "record-supersedes-conflict",
183
+ "record-remediates-unknown",
184
+ "record-remediates-not-closed",
183
185
  "session-seal-mismatch",
184
186
  "session-verdict-unknown-record"
185
187
  ]
@@ -24,7 +24,7 @@ import { formatError } from "../cli/format";
24
24
  import type { CommandContext } from "../cli/registry";
25
25
  import { findWorkspaceRoot } from "../project-root";
26
26
  import { fileDigest, isWorkspacePath } from "./record-assets";
27
- import { gitRevisionSource, gitRoot, resolveRevision, workingTreeSource } from "./record-source";
27
+ import { gitRevisionSource, gitRoot, resolveRevision, workingTreeSource, type RecordSource } from "./record-source";
28
28
  import { declaredRecordKinds, readDeclaration, WorkspaceReadError, type RecordKindDeclaration } from "./declaration";
29
29
  import { declaredKindFile } from "./declared-kinds";
30
30
  import { locateWorkspace } from "./which-chant";
@@ -249,7 +249,15 @@ function gitHistory(top: string, rev: string, prefix: string): RecordHistory {
249
249
  * with each pin checked in the same tree. Throws a {@link RecordReadError}.
250
250
  * `chant workspace graph` and `check` read records through this too.
251
251
  */
252
- export async function readRecordsFor(query: Omit<RecordsQuery, "base">): Promise<RecordsRead> {
252
+ export async function readRecordsFor(
253
+ query: Omit<RecordsQuery, "base"> & {
254
+ /**
255
+ * Lay more records over a working-tree read, such as the answer records a
256
+ * steward keeps on the lifecycle ledger (#2786). Not called with `at`.
257
+ */
258
+ overlay?: (source: RecordSource, kind: { loaded: LoadedRecordKind; root: string }) => Promise<RecordSource>;
259
+ },
260
+ ): Promise<RecordsRead> {
253
261
  // git reports its top through symlinks resolved (/var is /private/var on
254
262
  // macOS), so the directory has to be too, or record paths leave the repository.
255
263
  const cwd = realpathOr(query.cwd);
@@ -266,6 +274,7 @@ export async function readRecordsFor(query: Omit<RecordsQuery, "base">): Promise
266
274
  source = gitRevisionSource(top, at);
267
275
  assets = gitTree(top, at, workspaceRoot === "." ? "" : workspaceRoot);
268
276
  }
277
+ if (at === null && query.overlay) source = await query.overlay(source, { loaded, root });
269
278
  const history = top ? gitHistory(top, at ?? "HEAD", workspaceRoot) : undefined;
270
279
  // A session kind's verdicts name records of another kind, read from the same tree (#2673).
271
280
  let subjects: { records: RecordEntry[]; reviews: string } | undefined;
@@ -602,9 +611,10 @@ function formatRecords(records: RecordView[], summary: { total: number; valid: n
602
611
  const title = typeof r.data?.title === "string" ? r.data.title : r.path;
603
612
  const flag = r.valid ? "" : " INVALID";
604
613
  const superseded = r.supersededBy ? ` superseded by ${r.supersededBy}` : "";
614
+ const remediated = r.remediatedBy.length > 0 ? ` remediated by ${r.remediatedBy.join(", ")}` : "";
605
615
  const attested = r.provenance.level === "attested" ? ` attested by ${r.provenance.principal}` : "";
606
616
  const sealed = r.attested === true ? ` sealed by ${(r.data?.[RECORD_SEAL_FIELD] as { signer: string }).signer}` : "";
607
- lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${attested}${sealed}${flag}`);
617
+ lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${remediated}${attested}${sealed}${flag}`);
608
618
  if (r.ready !== undefined) {
609
619
  const blocked = (r.blockedBy ?? []).map((b) => `${b.id} (${b.state ?? "unknown"})`).join(", ");
610
620
  const implemented = (r.implements ?? []).map((d) => `${d.id} (${d.state ?? "unknown"})`).join(", ");
@@ -176,6 +176,8 @@
176
176
  "record-id-duplicate",
177
177
  "record-supersedes-unknown",
178
178
  "record-supersedes-conflict",
179
+ "record-remediates-unknown",
180
+ "record-remediates-not-closed",
179
181
  "session-seal-mismatch",
180
182
  "session-verdict-unknown-record"
181
183
  ]
@@ -169,6 +169,8 @@
169
169
  "record-id-duplicate",
170
170
  "record-supersedes-unknown",
171
171
  "record-supersedes-conflict",
172
+ "record-remediates-unknown",
173
+ "record-remediates-not-closed",
172
174
  "session-seal-mismatch",
173
175
  "session-verdict-unknown-record"
174
176
  ]
@@ -264,6 +264,8 @@
264
264
  "record-id-duplicate",
265
265
  "record-supersedes-unknown",
266
266
  "record-supersedes-conflict",
267
+ "record-remediates-unknown",
268
+ "record-remediates-not-closed",
267
269
  "session-seal-mismatch",
268
270
  "session-verdict-unknown-record"
269
271
  ]
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Sessions from a UI (#2693), through the command line: `records close` with
3
+ * the declared session kind, and `records --since <session id>` without
4
+ * --kind. Four CLI spawns put it near 13s on CI, close to the unit shards'
5
+ * per-test budget, so it runs in the test-e2e job (#2817). The rest of #2693
6
+ * is in records-sessions-write.test.ts.
7
+ */
8
+
9
+ import { spawnSync } from "node:child_process";
10
+ import { join } from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { afterAll, describe, expect, test } from "vitest";
13
+ import { cleanScratch, commitAll, contract, git, REPO } from "./__fixtures__/contract-repo";
14
+ import { declareSessions, newSessionFields, sessionsRepo, SESSIONS_KIND } from "./__fixtures__/sessions";
15
+ import closeSchema from "./records-close.schema.json";
16
+ import { type RecordsSinceDocument } from "./records-since";
17
+ import sinceSchema from "./records-since.schema.json";
18
+ import { newRecord } from "./records-write";
19
+ import newSchema from "./records-new.schema.json";
20
+
21
+ afterAll(cleanScratch);
22
+
23
+ const close = contract(closeSchema);
24
+ const since = contract(sinceSchema);
25
+ const created = contract(newSchema);
26
+
27
+ /** The reference fixture with a declaration, committed. */
28
+ function declared(): string {
29
+ const root = sessionsRepo();
30
+ declareSessions(root);
31
+ commitAll(root, "declare");
32
+ return root;
33
+ }
34
+
35
+ /** Open S-0002 through records new, and commit it. */
36
+ async function opened(root: string): Promise<{ path: string; head: string }> {
37
+ const head = git(root, "rev-parse", "HEAD");
38
+ const doc = await newRecord({ kind: SESSIONS_KIND, fields: newSessionFields(), cwd: root });
39
+ created.expectValid(doc);
40
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
41
+ expect(doc.id).toBe("S-0002");
42
+ commitAll(root, "open S-0002");
43
+ return { path: doc.path, head };
44
+ }
45
+
46
+ describe("records --since <session id> (#2693)", () => {
47
+ test(
48
+ "through the CLI: close with the declared session kind, and --since <session id> without --kind",
49
+ async () => {
50
+ const root = declared();
51
+ await opened(root);
52
+ const run = (...args: string[]) =>
53
+ spawnSync(process.execPath, ["--import", pathToFileURL(join(REPO, "node_modules/tsx/dist/loader.mjs")).href, join(REPO, "packages/core/src/cli/main.ts"), "workspace", "records", ...args], {
54
+ cwd: root,
55
+ encoding: "utf-8",
56
+ env: { ...process.env, NO_COLOR: "1" },
57
+ timeout: 60_000,
58
+ });
59
+ const closed = run("close", "S-0002");
60
+ expect(closed.status, closed.stderr).toBe(0);
61
+ close.expectValid(JSON.parse(closed.stdout));
62
+ const again = run("close", "S-0002");
63
+ expect(again.status).toBe(1);
64
+ expect(JSON.parse(again.stdout).error.code).toBe("record-closed");
65
+ commitAll(root, "close");
66
+ const set = run("--since", "S-0002", "--json");
67
+ expect(set.status, set.stderr).toBe(0);
68
+ const kinds = JSON.parse(set.stdout).kinds as RecordsSinceDocument[];
69
+ for (const k of kinds) since.expectValid(k);
70
+ expect(kinds.map((k) => ("session" in k ? k.session?.id : null))).toEqual(["S-0002", "S-0002"]);
71
+ const text = run("--kind", SESSIONS_KIND, "--since", "S-0002");
72
+ expect(text.stdout).toContain("session S-0002 (closed)");
73
+ expect(text.stdout).toContain("S-0002 new, closed");
74
+ },
75
+ 120_000,
76
+ );
77
+ });
@@ -5,13 +5,12 @@
5
5
  * is not open and appends the verdict to the session too, and
6
6
  * `records --since <session id>` compares the session's own revisions. The
7
7
  * fixture is a copy of the reference workspace's decisions and design
8
- * member with a declaration naming both kinds.
8
+ * member with a declaration naming both kinds. The CLI run of close and
9
+ * --since is in records-sessions-write.e2e.test.ts (#2817).
9
10
  */
10
11
 
11
- import { spawnSync } from "node:child_process";
12
12
  import { cpSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
- import { pathToFileURL } from "node:url";
15
14
  import { afterAll, describe, expect, test } from "vitest";
16
15
  import { cleanScratch, commitAll, contract, git, REPO, scratchDir } from "./__fixtures__/contract-repo";
17
16
  import { declareSessions, DECISIONS_KIND, newSessionFields, sessionsRepo, SESSIONS_KIND } from "./__fixtures__/sessions";
@@ -240,35 +239,4 @@ describe("records --since <session id> (#2693)", () => {
240
239
  git(root, "tag", "v-1");
241
240
  expect((await sinceDoc({ kind: DECISIONS_KIND, since: "v-1", cwd: root })).session).toBeUndefined();
242
241
  });
243
-
244
- test(
245
- "through the CLI: close with the declared session kind, and --since <session id> without --kind",
246
- async () => {
247
- const root = declared();
248
- await opened(root);
249
- const run = (...args: string[]) =>
250
- spawnSync(process.execPath, ["--import", pathToFileURL(join(REPO, "node_modules/tsx/dist/loader.mjs")).href, join(REPO, "packages/core/src/cli/main.ts"), "workspace", "records", ...args], {
251
- cwd: root,
252
- encoding: "utf-8",
253
- env: { ...process.env, NO_COLOR: "1" },
254
- timeout: 60_000,
255
- });
256
- const closed = run("close", "S-0002");
257
- expect(closed.status, closed.stderr).toBe(0);
258
- close.expectValid(JSON.parse(closed.stdout));
259
- const again = run("close", "S-0002");
260
- expect(again.status).toBe(1);
261
- expect(JSON.parse(again.stdout).error.code).toBe("record-closed");
262
- commitAll(root, "close");
263
- const set = run("--since", "S-0002", "--json");
264
- expect(set.status, set.stderr).toBe(0);
265
- const kinds = JSON.parse(set.stdout).kinds as RecordsSinceDocument[];
266
- for (const k of kinds) since.expectValid(k);
267
- expect(kinds.map((k) => ("session" in k ? k.session?.id : null))).toEqual(["S-0002", "S-0002"]);
268
- const text = run("--kind", SESSIONS_KIND, "--since", "S-0002");
269
- expect(text.stdout).toContain("session S-0002 (closed)");
270
- expect(text.stdout).toContain("S-0002 new, closed");
271
- },
272
- 120_000,
273
- );
274
242
  });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `chant workspace records new|amend|review` (#2670) through the command
3
+ * line: standard input, exit codes and the refusal document, from the CLI a
4
+ * user runs. Each test spawns the CLI several times, which put them near 20s
5
+ * on CI, over the unit shards' per-test budget, so they run in the test-e2e
6
+ * job (#2817). The commands' own behaviour is covered in records-write.test.ts.
7
+ */
8
+
9
+ import { execFileSync } from "node:child_process";
10
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
15
+ import { parseFrontMatter } from "./records";
16
+ import { renderRecord } from "./records-write";
17
+
18
+ const REPO = join(import.meta.dirname, "..", "..", "..", "..");
19
+ const DECISIONS = join(REPO, "docs", "design", "decisions");
20
+ const KIND = "decisions/decision.kind.mjs";
21
+
22
+ type Data = Record<string, unknown>;
23
+
24
+ const SAMPLE = (() => {
25
+ const fm = parseFrontMatter(readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8"));
26
+ if (!fm.ok) throw new Error(fm.message);
27
+ return fm.value;
28
+ })();
29
+
30
+ /** A decided decision like ws-003, with `over` laid on top. */
31
+ function decision(over: Data = {}): Data {
32
+ return { ...structuredClone(SAMPLE), ...over };
33
+ }
34
+
35
+ /** A proposed decision: no choice, nobody decided. */
36
+ function proposal(over: Data = {}): Data {
37
+ return decision({ state: "proposed", choice: null, decided_by: null, decided_on: null, ...over });
38
+ }
39
+
40
+ let dir: string;
41
+ beforeEach(() => {
42
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-records-write-")));
43
+ mkdirSync(join(dir, "decisions"));
44
+ cpSync(join(DECISIONS, "decision.kind.mjs"), join(dir, "decisions", "decision.kind.mjs"));
45
+ cpSync(join(DECISIONS, "decision.schema.json"), join(dir, "decisions", "decision.schema.json"));
46
+ put("ws-001-one.md", decision({ id: "ws-001", title: "One" }));
47
+ put("ws-002-two.md", proposal({ id: "ws-002", title: "Two" }));
48
+ put("ws-003-three.md", decision({ id: "ws-003", title: "Three", state: "ratified" }));
49
+ });
50
+ afterEach(() => rmSync(dir, { recursive: true, force: true }));
51
+
52
+ function put(name: string, data: Data): void {
53
+ writeFileSync(join(dir, "decisions", name), renderRecord(data, `\n# ${String(data.title)}\n`));
54
+ }
55
+
56
+ describe("the command line", () => {
57
+ const cli = (args: string[], input?: string) => {
58
+ const loader = pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href;
59
+ try {
60
+ const out = execFileSync(process.execPath, ["--import", loader, join(REPO, "packages", "core", "src", "cli", "main.ts"), "workspace", "records", ...args], {
61
+ cwd: dir,
62
+ input,
63
+ encoding: "utf-8",
64
+ env: { ...process.env, TSX_DISABLE_CACHE: "1" },
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ });
67
+ return { status: 0, doc: JSON.parse(out) };
68
+ } catch (err) {
69
+ const e = err as { status: number; stdout: string };
70
+ return { status: e.status, doc: JSON.parse(e.stdout) };
71
+ }
72
+ };
73
+
74
+ test(
75
+ "new reads fields from standard input, review appends, and a refusal exits 1 with its code",
76
+ () => {
77
+ const { id: _id, ...rest } = decision({ title: "From stdin" });
78
+ const made = cli(["new", KIND, "--from", "-"], JSON.stringify(rest));
79
+ expect(made).toMatchObject({ status: 0, doc: { id: "ws-004", path: "decisions/ws-004-from-stdin.md" } });
80
+ expect(existsSync(join(dir, "decisions", "ws-004-from-stdin.md"))).toBe(true);
81
+ const reviewed = cli(["review", "ws-004", "--kind", KIND, "--verdict", "agree", "--by", "alice"]);
82
+ expect(reviewed).toMatchObject({ status: 0, doc: { review: { reviewer: "alice", verdict: "agree" } } });
83
+ const refused = cli(["review", "ws-004", "--kind", KIND, "--verdict", "dissent", "--by", "bob"]);
84
+ expect(refused).toMatchObject({ status: 1, doc: { error: { code: "review-note-required" } } });
85
+ writeFileSync(join(dir, "patch.json"), JSON.stringify({ title: "Changed" }));
86
+ expect(cli(["amend", "ws-004", "--kind", KIND, "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "amend-supersede-instead" } } });
87
+ expect(cli(["amend", "ws-004", "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "write-usage-invalid" } } });
88
+ },
89
+ 120_000,
90
+ );
91
+
92
+ test(
93
+ "new --by writes the proposer, not the decider, and amend --by is refused rather than dropped (#2756)",
94
+ () => {
95
+ const { id: _id, ...rest } = decision({ title: "Kept by a session", state: "proposed", choice: null, decided_by: null, decided_on: null });
96
+ const made = cli(["new", KIND, "--from", "-", "--by", "hud-session"], JSON.stringify(rest));
97
+ expect(made).toMatchObject({ status: 0, doc: { id: "ws-004" } });
98
+ const fm = parseFrontMatter(readFileSync(join(dir, "decisions", "ws-004-kept-by-a-session.md"), "utf-8"));
99
+ if (!fm.ok) throw new Error(fm.message);
100
+ expect(fm.value).toMatchObject({ proposed_by: "hud-session", decided_by: null });
101
+
102
+ writeFileSync(join(dir, "patch.json"), JSON.stringify({ title: "Changed" }));
103
+ const refused = cli(["amend", "ws-004", "--kind", KIND, "--set", "patch.json", "--by", "alice"]);
104
+ expect(refused).toMatchObject({ status: 1, doc: { error: { code: "write-usage-invalid", message: expect.stringContaining("--by is not taken by amend") } } });
105
+ },
106
+ 120_000,
107
+ );
108
+ });