@intentius/chant 0.91.0 → 0.94.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 (361) 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-plugins.d.ts +6 -6
  9. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  11. package/dist/cli/plugins.d.ts.map +1 -1
  12. package/dist/cli/registry.d.ts +12 -2
  13. package/dist/cli/registry.d.ts.map +1 -1
  14. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  15. package/dist/config.d.ts +22 -0
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/lexicon-module.d.ts +22 -0
  18. package/dist/lexicon-module.d.ts.map +1 -1
  19. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  20. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  21. package/dist/lifecycle/run-ledger.d.ts +2 -0
  22. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  23. package/dist/lifecycle/symptoms.d.ts +51 -0
  24. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  25. package/dist/lint/rules/index.d.ts +4 -2
  26. package/dist/lint/rules/index.d.ts.map +1 -1
  27. package/dist/lint/rules/op/index.d.ts +14 -3
  28. package/dist/lint/rules/op/index.d.ts.map +1 -1
  29. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  31. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  33. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  35. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  36. package/dist/op/activities/activity-contracts.d.ts +201 -0
  37. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  38. package/dist/op/activities/apply.d.ts.map +1 -1
  39. package/dist/op/activities/change-coverage.d.ts +44 -0
  40. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  41. package/dist/op/activities/converge.d.ts +24 -1
  42. package/dist/op/activities/converge.d.ts.map +1 -1
  43. package/dist/op/activities/decide.d.ts +107 -0
  44. package/dist/op/activities/decide.d.ts.map +1 -0
  45. package/dist/op/activities/index.d.ts +10 -0
  46. package/dist/op/activities/index.d.ts.map +1 -1
  47. package/dist/op/activities/shell.d.ts +32 -0
  48. package/dist/op/activities/shell.d.ts.map +1 -1
  49. package/dist/op/activities/source-release.d.ts +86 -0
  50. package/dist/op/activities/source-release.d.ts.map +1 -0
  51. package/dist/op/activities/source-rollback.d.ts +100 -0
  52. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  53. package/dist/op/activities/work-evidence.d.ts +42 -0
  54. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  55. package/dist/op/activity-contract.d.ts +1 -0
  56. package/dist/op/activity-contract.d.ts.map +1 -1
  57. package/dist/op/activity-profiles.d.ts +16 -0
  58. package/dist/op/activity-profiles.d.ts.map +1 -1
  59. package/dist/op/builders.d.ts +84 -2
  60. package/dist/op/builders.d.ts.map +1 -1
  61. package/dist/op/composites/converge-op.d.ts +31 -4
  62. package/dist/op/composites/converge-op.d.ts.map +1 -1
  63. package/dist/op/decide-backend.d.ts +116 -0
  64. package/dist/op/decide-backend.d.ts.map +1 -0
  65. package/dist/op/decide-config.d.ts +120 -0
  66. package/dist/op/decide-config.d.ts.map +1 -0
  67. package/dist/op/decide-read-inputs.d.ts +40 -0
  68. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  69. package/dist/op/gate-wait.d.ts +43 -0
  70. package/dist/op/gate-wait.d.ts.map +1 -0
  71. package/dist/op/gate.d.ts +5 -2
  72. package/dist/op/gate.d.ts.map +1 -1
  73. package/dist/op/index.d.ts +11 -4
  74. package/dist/op/index.d.ts.map +1 -1
  75. package/dist/op/local-executor.d.ts +29 -4
  76. package/dist/op/local-executor.d.ts.map +1 -1
  77. package/dist/op/local-output.d.ts +2 -0
  78. package/dist/op/local-output.d.ts.map +1 -1
  79. package/dist/op/op-ir.d.ts +2 -0
  80. package/dist/op/op-ir.d.ts.map +1 -1
  81. package/dist/op/op-verb-class.d.ts.map +1 -1
  82. package/dist/op/operator.d.ts +97 -1
  83. package/dist/op/operator.d.ts.map +1 -1
  84. package/dist/op/runtime.d.ts +37 -5
  85. package/dist/op/runtime.d.ts.map +1 -1
  86. package/dist/op/runtimes/local.d.ts.map +1 -1
  87. package/dist/op/source-archive.d.ts +63 -0
  88. package/dist/op/source-archive.d.ts.map +1 -0
  89. package/dist/op/steward-points.d.ts +136 -0
  90. package/dist/op/steward-points.d.ts.map +1 -0
  91. package/dist/op/steward-turn.d.ts +53 -0
  92. package/dist/op/steward-turn.d.ts.map +1 -0
  93. package/dist/op/steward.d.ts +22 -1
  94. package/dist/op/steward.d.ts.map +1 -1
  95. package/dist/op/types.d.ts +7 -0
  96. package/dist/op/types.d.ts.map +1 -1
  97. package/dist/op/work-lease-run.d.ts +1 -1
  98. package/dist/op/work-lease-run.d.ts.map +1 -1
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  100. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  101. package/dist/workspace/answers-ledger.d.ts +76 -0
  102. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  103. package/dist/workspace/box-intent.d.ts +85 -0
  104. package/dist/workspace/box-intent.d.ts.map +1 -0
  105. package/dist/workspace/changes-cli.d.ts +16 -0
  106. package/dist/workspace/changes-cli.d.ts.map +1 -0
  107. package/dist/workspace/changes.d.ts +200 -0
  108. package/dist/workspace/changes.d.ts.map +1 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  118. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  119. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  120. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  121. package/dist/workspace/chant-migrations.d.ts +104 -0
  122. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  123. package/dist/workspace/checks/boxes.d.ts +47 -1
  124. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  125. package/dist/workspace/checks/diagrams.d.ts +31 -0
  126. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  127. package/dist/workspace/checks/records.d.ts +7 -0
  128. package/dist/workspace/checks/records.d.ts.map +1 -1
  129. package/dist/workspace/checks.d.ts +7 -0
  130. package/dist/workspace/checks.d.ts.map +1 -1
  131. package/dist/workspace/compose-graph.d.ts +2 -0
  132. package/dist/workspace/compose-graph.d.ts.map +1 -1
  133. package/dist/workspace/conformance/index.d.ts.map +1 -1
  134. package/dist/workspace/decide.d.ts +49 -4
  135. package/dist/workspace/decide.d.ts.map +1 -1
  136. package/dist/workspace/decision-points.schema.json +3 -3
  137. package/dist/workspace/declaration.d.ts +58 -0
  138. package/dist/workspace/declaration.d.ts.map +1 -1
  139. package/dist/workspace/declaration.schema.json +119 -1
  140. package/dist/workspace/declared-kinds.d.ts +20 -0
  141. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  142. package/dist/workspace/generated-files.d.ts +7 -0
  143. package/dist/workspace/generated-files.d.ts.map +1 -1
  144. package/dist/workspace/intent-joins.d.ts +5 -5
  145. package/dist/workspace/intent.d.ts.map +1 -1
  146. package/dist/workspace/lineage-check.d.ts +3 -0
  147. package/dist/workspace/lineage-check.d.ts.map +1 -1
  148. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  149. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  150. package/dist/workspace/ls.d.ts +39 -1
  151. package/dist/workspace/ls.d.ts.map +1 -1
  152. package/dist/workspace/point-answer.schema.json +95 -0
  153. package/dist/workspace/points-cli.d.ts +2 -0
  154. package/dist/workspace/points-cli.d.ts.map +1 -1
  155. package/dist/workspace/points.d.ts +4 -6
  156. package/dist/workspace/points.d.ts.map +1 -1
  157. package/dist/workspace/reason-codes.d.ts +16 -2
  158. package/dist/workspace/reason-codes.d.ts.map +1 -1
  159. package/dist/workspace/records-cli.d.ts +11 -1
  160. package/dist/workspace/records-cli.d.ts.map +1 -1
  161. package/dist/workspace/records-close.d.ts +1 -1
  162. package/dist/workspace/records-close.d.ts.map +1 -1
  163. package/dist/workspace/records-write.d.ts +11 -5
  164. package/dist/workspace/records-write.d.ts.map +1 -1
  165. package/dist/workspace/records.d.ts +34 -4
  166. package/dist/workspace/records.d.ts.map +1 -1
  167. package/dist/workspace/status-gates.d.ts +7 -2
  168. package/dist/workspace/status-gates.d.ts.map +1 -1
  169. package/dist/workspace/status-stewards.d.ts +65 -1
  170. package/dist/workspace/status-stewards.d.ts.map +1 -1
  171. package/dist/workspace/status.d.ts +24 -0
  172. package/dist/workspace/status.d.ts.map +1 -1
  173. package/dist/workspace/work-cli.d.ts +113 -2
  174. package/dist/workspace/work-cli.d.ts.map +1 -1
  175. package/dist/workspace/work-evidence.d.ts +74 -0
  176. package/dist/workspace/work-evidence.d.ts.map +1 -0
  177. package/dist/workspace/work.d.ts +43 -1
  178. package/dist/workspace/work.d.ts.map +1 -1
  179. package/package.json +1 -1
  180. package/src/cli/commands/audit.test.ts +9 -1
  181. package/src/cli/commands/build.ts +3 -2
  182. package/src/cli/commands/lint.ts +7 -1
  183. package/src/cli/handlers/graph.test.ts +20 -0
  184. package/src/cli/handlers/graph.ts +16 -2
  185. package/src/cli/handlers/operator.ts +34 -2
  186. package/src/cli/handlers/run.test.ts +154 -2
  187. package/src/cli/handlers/run.ts +121 -2
  188. package/src/cli/main.test.ts +10 -0
  189. package/src/cli/main.ts +23 -3
  190. package/src/cli/mcp/workspace-plugins.ts +6 -6
  191. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  192. package/src/cli/mcp/workspace-tools.ts +62 -4
  193. package/src/cli/plugins.ts +11 -3
  194. package/src/cli/registry.ts +12 -2
  195. package/src/cli/serve-mcp-workspace.test.ts +6 -6
  196. package/src/codegen/pinned-upgrade.ts +2 -1
  197. package/src/codegen/release-wiring.test.ts +49 -2
  198. package/src/components/capability-plugin-loader.ts +3 -3
  199. package/src/components/cli-support.ts +2 -2
  200. package/src/config.ts +11 -0
  201. package/src/lexicon-module.ts +56 -1
  202. package/src/lexicon-package.test.ts +96 -0
  203. package/src/lifecycle/converge-ledger.ts +20 -0
  204. package/src/lifecycle/run-ledger.ts +6 -0
  205. package/src/lifecycle/symptoms.ts +95 -0
  206. package/src/lint/rules/index.ts +6 -2
  207. package/src/lint/rules/op/index.ts +17 -4
  208. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  209. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  210. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  211. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  212. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  213. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  214. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  215. package/src/op/activities/activity-contracts.ts +190 -1
  216. package/src/op/activities/apply.ts +7 -6
  217. package/src/op/activities/change-coverage.ts +60 -0
  218. package/src/op/activities/converge.test.ts +55 -1
  219. package/src/op/activities/converge.ts +208 -29
  220. package/src/op/activities/decide.live.test.ts +78 -0
  221. package/src/op/activities/decide.test.ts +308 -0
  222. package/src/op/activities/decide.ts +267 -0
  223. package/src/op/activities/index.ts +21 -0
  224. package/src/op/activities/shell.test.ts +91 -1
  225. package/src/op/activities/shell.ts +91 -2
  226. package/src/op/activities/source-release.test.ts +118 -0
  227. package/src/op/activities/source-release.ts +168 -0
  228. package/src/op/activities/source-rollback.test.ts +131 -0
  229. package/src/op/activities/source-rollback.ts +239 -0
  230. package/src/op/activities/work-evidence.ts +62 -0
  231. package/src/op/activity-contract-registry.test.ts +2 -0
  232. package/src/op/activity-contract-registry.ts +2 -2
  233. package/src/op/activity-contract.test.ts +12 -0
  234. package/src/op/activity-contract.ts +6 -1
  235. package/src/op/activity-profiles.ts +29 -0
  236. package/src/op/activity-registry.test.ts +1 -1
  237. package/src/op/activity-registry.ts +2 -2
  238. package/src/op/builders.ts +119 -5
  239. package/src/op/command-gate.test.ts +192 -0
  240. package/src/op/composites/converge-op.test.ts +27 -1
  241. package/src/op/composites/converge-op.ts +69 -15
  242. package/src/op/decide-backend.test.ts +44 -0
  243. package/src/op/decide-backend.ts +212 -0
  244. package/src/op/decide-config.test.ts +52 -0
  245. package/src/op/decide-config.ts +80 -0
  246. package/src/op/decide-read-inputs.ts +117 -0
  247. package/src/op/gate-approval.ts +2 -2
  248. package/src/op/gate-wait.ts +57 -0
  249. package/src/op/gate.ts +8 -3
  250. package/src/op/generate-pipeline.ts +2 -2
  251. package/src/op/index.ts +18 -3
  252. package/src/op/local-executor.ts +151 -15
  253. package/src/op/local-output.test.ts +33 -0
  254. package/src/op/local-output.ts +20 -2
  255. package/src/op/op-ir.test.ts +10 -0
  256. package/src/op/op-ir.ts +4 -0
  257. package/src/op/op-verb-class.ts +1 -0
  258. package/src/op/operator.ts +290 -11
  259. package/src/op/runtime.ts +31 -8
  260. package/src/op/runtimes/local.ts +3 -1
  261. package/src/op/source-archive.ts +161 -0
  262. package/src/op/steward-points.test.ts +364 -0
  263. package/src/op/steward-points.ts +194 -0
  264. package/src/op/steward-turn.ts +79 -0
  265. package/src/op/steward.test.ts +87 -3
  266. package/src/op/steward.ts +25 -1
  267. package/src/op/types.ts +7 -0
  268. package/src/op/work-lease-run.test.ts +20 -1
  269. package/src/op/work-lease-run.ts +2 -2
  270. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  271. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  272. package/src/workspace/answers-ledger.ts +118 -0
  273. package/src/workspace/box-intent.test.ts +205 -0
  274. package/src/workspace/box-intent.ts +159 -0
  275. package/src/workspace/changes-cli.ts +56 -0
  276. package/src/workspace/changes.schema.json +472 -0
  277. package/src/workspace/changes.test.ts +315 -0
  278. package/src/workspace/changes.ts +512 -0
  279. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  280. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  281. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  282. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  283. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  284. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  285. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  286. package/src/workspace/chant-migrations.ts +169 -0
  287. package/src/workspace/check-contract.test.ts +4 -2
  288. package/src/workspace/check.schema.json +19 -5
  289. package/src/workspace/checks/boxes.test.ts +82 -2
  290. package/src/workspace/checks/boxes.ts +202 -1
  291. package/src/workspace/checks/diagrams.test.ts +134 -0
  292. package/src/workspace/checks/diagrams.ts +108 -0
  293. package/src/workspace/checks/records.ts +47 -0
  294. package/src/workspace/checks.ts +20 -2
  295. package/src/workspace/compose-graph.ts +2 -0
  296. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  297. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  298. package/src/workspace/conformance/index.ts +5 -0
  299. package/src/workspace/decide.test.ts +1 -1
  300. package/src/workspace/decide.ts +156 -23
  301. package/src/workspace/decision-points.schema.json +3 -3
  302. package/src/workspace/declaration.schema.json +119 -1
  303. package/src/workspace/declaration.test.ts +58 -0
  304. package/src/workspace/declaration.ts +112 -0
  305. package/src/workspace/declared-kinds.test.ts +31 -5
  306. package/src/workspace/declared-kinds.ts +46 -1
  307. package/src/workspace/generated-files.ts +14 -0
  308. package/src/workspace/graph-cli.ts +1 -1
  309. package/src/workspace/graph.schema.json +9 -1
  310. package/src/workspace/intent-joins.test.ts +5 -5
  311. package/src/workspace/intent-joins.ts +5 -5
  312. package/src/workspace/intent.schema.json +8 -2
  313. package/src/workspace/intent.ts +3 -10
  314. package/src/workspace/lineage-check.ts +16 -1
  315. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  316. package/src/workspace/lineage-upgrade.test.ts +70 -0
  317. package/src/workspace/lineage-upgrade.ts +81 -10
  318. package/src/workspace/ls-contract.test.ts +57 -0
  319. package/src/workspace/ls.schema.json +46 -1
  320. package/src/workspace/ls.ts +62 -3
  321. package/src/workspace/points-cli.ts +17 -3
  322. package/src/workspace/points-write.schema.json +11 -1
  323. package/src/workspace/points.schema.json +51 -0
  324. package/src/workspace/points.test.ts +16 -15
  325. package/src/workspace/points.ts +5 -17
  326. package/src/workspace/read-contract.test.ts +9 -1
  327. package/src/workspace/reason-codes.test.ts +18 -8
  328. package/src/workspace/reason-codes.ts +21 -2
  329. package/src/workspace/records-amend.schema.json +2 -0
  330. package/src/workspace/records-cli.ts +27 -4
  331. package/src/workspace/records-close.schema.json +2 -0
  332. package/src/workspace/records-formats.test.ts +15 -15
  333. package/src/workspace/records-new.schema.json +2 -0
  334. package/src/workspace/records-review.schema.json +2 -0
  335. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  336. package/src/workspace/records-sessions-write.test.ts +2 -34
  337. package/src/workspace/records-since.test.ts +8 -7
  338. package/src/workspace/records-write.e2e.test.ts +108 -0
  339. package/src/workspace/records-write.test.ts +32 -41
  340. package/src/workspace/records-write.ts +33 -15
  341. package/src/workspace/records.schema.json +36 -2
  342. package/src/workspace/records.test.ts +49 -4
  343. package/src/workspace/records.ts +109 -3
  344. package/src/workspace/status-contract.test.ts +14 -8
  345. package/src/workspace/status-gates.ts +9 -4
  346. package/src/workspace/status-stewards.ts +155 -5
  347. package/src/workspace/status.schema.json +129 -8
  348. package/src/workspace/status.ts +46 -1
  349. package/src/workspace/work-acceptance.test.ts +274 -0
  350. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  351. package/src/workspace/work-cli.ts +193 -10
  352. package/src/workspace/work-evidence.schema.json +212 -0
  353. package/src/workspace/work-evidence.ts +169 -0
  354. package/src/workspace/work-history.schema.json +349 -0
  355. package/src/workspace/work-history.test.ts +156 -0
  356. package/src/workspace/{work-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
  357. package/src/workspace/work.test.ts +20 -2
  358. package/src/workspace/work.ts +100 -1
  359. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  360. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  361. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -24,10 +24,11 @@ import {
24
24
  } from "./points";
25
25
 
26
26
  /**
27
- * chud's points, as template/delivery/decisions/points.yaml declares them at
28
- * jhgaylor/chud@43afcf1, as JSON.
27
+ * Points shaped like an externally authored template's decisions file
28
+ * (ported from chud, jhgaylor/chud@43afcf1's
29
+ * template/delivery/decisions/points.yaml), as JSON.
29
30
  */
30
- const CHUD = {
31
+ const IMPORTED = {
31
32
  points: {
32
33
  "slice-tier": {
33
34
  title: "Which builder tier builds this contract",
@@ -63,7 +64,7 @@ const CHUD = {
63
64
  "ship-skip": {
64
65
  title: "May this release skip the human gate",
65
66
  question: {
66
- type: "boolean",
67
+ type: "noul",
67
68
  instructions: "May this release pass the ship gate without a person approving it? The state is what the release plan would change.",
68
69
  criteria: {
69
70
  true: "An agent may pass the gate for this release (only in enforce mode).",
@@ -86,9 +87,9 @@ const CHUD = {
86
87
  },
87
88
  };
88
89
 
89
- /** chud's points with each input named as a read-contract output: the slice tier reads a work item, the ship gate a release. */
90
- function renamed(): typeof CHUD {
91
- const copy = JSON.parse(JSON.stringify(CHUD)) as typeof CHUD;
90
+ /** The imported points with each input named as a read-contract output: the slice tier reads a work item, the ship gate a release. */
91
+ function renamed(): typeof IMPORTED {
92
+ const copy = JSON.parse(JSON.stringify(IMPORTED)) as typeof IMPORTED;
92
93
  const prefix: Record<string, string> = { "slice-tier": "work-item", "ship-skip": "release" };
93
94
  for (const [name, point] of Object.entries(copy.points) as [string, { inputs: Record<string, string>; deciders: { rows?: { when: Record<string, unknown> }[] }[] }][]) {
94
95
  const to = (k: string) => `${prefix[name]}.${k}`;
@@ -123,14 +124,14 @@ describe("the decision points schema (#2738)", () => {
123
124
  }
124
125
  });
125
126
 
126
- test("chud's slice-tier and ship-skip validate unchanged, apart from the input names", () => {
127
- // As chud declares them, only the input names are refused: each names no read-contract output.
128
- const found = problems(CHUD);
127
+ test("the imported slice-tier and ship-skip validate unchanged, apart from the input names", () => {
128
+ // As the template declares them, only the input names are refused: each names no read-contract output.
129
+ const found = problems(IMPORTED);
129
130
  expect(found.length).toBeGreaterThan(0);
130
131
  for (const p of found) expect(p.message).toMatch(/is not a read-contract output|is not one of this point's inputs/);
131
132
  expect(found.filter((p) => p.message.includes("read-contract output")).map((p) => p.field)).toEqual([
132
- ...Object.keys(CHUD.points["slice-tier"].inputs).map((k) => `points.slice-tier.inputs.${k}`),
133
- ...Object.keys(CHUD.points["ship-skip"].inputs).map((k) => `points.ship-skip.inputs.${k}`),
133
+ ...Object.keys(IMPORTED.points["slice-tier"].inputs).map((k) => `points.slice-tier.inputs.${k}`),
134
+ ...Object.keys(IMPORTED.points["ship-skip"].inputs).map((k) => `points.ship-skip.inputs.${k}`),
134
135
  ]);
135
136
  // With the inputs renamed, nothing else changes and both validate.
136
137
  const points = parsePoints(text(renamed()), "points.json");
@@ -142,7 +143,7 @@ describe("the decision points schema (#2738)", () => {
142
143
 
143
144
  test("the reference workspace's points validate", () => {
144
145
  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"]);
146
+ expect(Object.keys(points)).toEqual(["slice-tier", "ship-skip", "finding-triage", "needs-a-decision"]);
146
147
  });
147
148
 
148
149
  test("a point whose chain does not end in a quorum is refused", () => {
@@ -156,7 +157,7 @@ describe("the decision points schema (#2738)", () => {
156
157
 
157
158
  test("a point with an unknown input is refused, and so is a row testing an undeclared one", () => {
158
159
  const v = renamed() as unknown as { points: Record<string, { inputs: Record<string, string>; deciders: { rows?: { when: Record<string, unknown> }[] }[] }> };
159
- v.points["slice-tier"].inputs["contract.size"] = "chud's contract, which the read contract has no output for";
160
+ v.points["slice-tier"].inputs["contract.size"] = "a contract, which the read contract has no output for";
160
161
  expect(problems(v)).toEqual([
161
162
  {
162
163
  field: "points.slice-tier.inputs.contract.size",
@@ -168,7 +169,7 @@ describe("the decision points schema (#2738)", () => {
168
169
  expect(problems(w)).toEqual([{ field: "points.slice-tier.deciders.0.rows.0.when.work-item.tier", message: expect.stringContaining("is not one of this point's inputs") }]);
169
170
  });
170
171
 
171
- test("the schema and the code refuse the rest of what chud refused, and an alias model id", () => {
172
+ test("the schema and the code refuse the rest of what an imported points file can get wrong, and an alias model id", () => {
172
173
  const cases: [(p: Record<string, unknown>) => void, RegExp][] = [
173
174
  [(p) => ((p.deciders as Record<string, unknown>[])[1].model = "jev-latest"), /is an alias/],
174
175
  [(p) => ((p.deciders as Record<string, unknown>[])[1].count = 2), /is only for a quorum decider/],
@@ -20,7 +20,7 @@
20
20
  * runtime's decider or a test's stub supplies. Without one, a model decider is
21
21
  * not asked and the chain moves on.
22
22
  *
23
- * Taken from chud's `packages/runtime/src/decide.mjs` at 43afcf1: the chain,
23
+ * Ported from chud's `packages/runtime/src/decide.mjs` at 43afcf1: the chain,
24
24
  * the observation rule, the point version and the inputs hash. The records
25
25
  * are chant records: `decide.ts` writes them, and {@link applyAnswers} warns
26
26
  * about them on read.
@@ -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" },
@@ -71,7 +71,6 @@ export function inputOutput(name: string): string {
71
71
  export type QuestionType = "noul" | "choice" | "score";
72
72
 
73
73
  export interface Question {
74
- /** `boolean` in a points file is read as `noul`. */
75
74
  type: QuestionType;
76
75
  instructions: string;
77
76
  criteria: Record<string, string> | string[];
@@ -220,20 +219,9 @@ export function schemaProblems(data: unknown): PointProblem[] {
220
219
  return out;
221
220
  }
222
221
 
223
- /** `boolean` read as `noul`. The declaration is otherwise kept as written. */
224
- function normalise(points: Record<string, Point>): Record<string, Point> {
225
- const out: Record<string, Point> = {};
226
- for (const [name, p] of Object.entries(points)) {
227
- const type = (p.question.type as string) === "boolean" ? "noul" : p.question.type;
228
- out[name] = { ...p, question: { ...p.question, type } };
229
- }
230
- return out;
231
- }
232
-
233
222
  /**
234
- * Parse and validate a points file's text. Returns its points, with
235
- * `boolean` read as `noul`. Throws a {@link PointsError} naming the file and
236
- * each field.
223
+ * Parse and validate a points file's text. Returns its points. Throws a
224
+ * {@link PointsError} naming the file and each field.
237
225
  */
238
226
  export function parsePoints(text: string, file: string): Record<string, Point> {
239
227
  let data: unknown;
@@ -244,7 +232,7 @@ export function parsePoints(text: string, file: string): Record<string, Point> {
244
232
  }
245
233
  const problems = schemaProblems(data);
246
234
  if (problems.length > 0) throw new PointsError(file, problems);
247
- const points = normalise((data as { points: Record<string, Point> }).points);
235
+ const points = (data as { points: Record<string, Point> }).points;
248
236
  const more = pointProblems(points);
249
237
  if (more.length > 0) throw new PointsError(file, more);
250
238
  return points;
@@ -192,7 +192,15 @@ 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
+ const sliceTier = doc.points.find((p) => p.name === "slice-tier")!;
197
+ expect(sliceTier.criteria).toEqual({
198
+ small: "A haiku-class builder. The work item fits the small limits.",
199
+ medium: "A mid-size builder. The work item fits the medium limits.",
200
+ large: "The largest builder. The work item is bigger than the medium limits.",
201
+ });
202
+ const shipSkip = doc.points.find((p) => p.name === "ship-skip")!;
203
+ expect(shipSkip.criteria).toEqual({ true: "An agent may pass the gate for this release.", false: "A person approves the release at the gate." });
196
204
  }
197
205
  const open = await workspacePoints({ cwd: FIXTURE, open: true });
198
206
  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,
@@ -147,11 +157,11 @@ describe("the closed list of reason codes", () => {
147
157
  });
148
158
 
149
159
  test("a plugin's finding codes are in its own namespace, outside the list, and the intent schema accepts them (#2656)", () => {
150
- expect(isPluginCode("plugin:chud:contract-criteria-changed")).toBe(true);
151
- expect(isPluginCode("plugin:chud:contract-criteria-changed", "chud")).toBe(true);
152
- expect(isPluginCode("plugin:chud:contract-criteria-changed", "units")).toBe(false);
153
- for (const bad of ["plugin:chud", "plugin::x", "plugin:chud:Upper", "plugin:chud:a:b", "intent-commit-bare", 7]) expect(isPluginCode(bad), String(bad)).toBe(false);
154
- expect(isReasonCode("plugin:chud:contract-criteria-changed")).toBe(false);
160
+ expect(isPluginCode("plugin:acme:contract-criteria-changed")).toBe(true);
161
+ expect(isPluginCode("plugin:acme:contract-criteria-changed", "acme")).toBe(true);
162
+ expect(isPluginCode("plugin:acme:contract-criteria-changed", "units")).toBe(false);
163
+ for (const bad of ["plugin:acme", "plugin::x", "plugin:acme:Upper", "plugin:acme:a:b", "intent-commit-bare", 7]) expect(isPluginCode(bad), String(bad)).toBe(false);
164
+ expect(isReasonCode("plugin:acme:contract-criteria-changed")).toBe(false);
155
165
  const { validate } = contract(intentSchema);
156
166
  const finding = (code: string) => ({ id: `finding:${code}:1`, kind: "finding", code, message: "m", concerns: [] });
157
167
  const doc = (code: string) => ({
@@ -168,9 +178,9 @@ describe("the closed list of reason codes", () => {
168
178
  reasons: [],
169
179
  summary: { commits: 0, decisions: 0, artifacts: 0, findings: 1 },
170
180
  });
171
- expect(validate(doc("plugin:chud:contract-criteria-changed"))).toBe(true);
181
+ expect(validate(doc("plugin:acme:contract-criteria-changed"))).toBe(true);
172
182
  expect(validate(doc("intent-commit-bare"))).toBe(true);
173
- expect(validate(doc("plugin:chud:Nope"))).toBe(false);
183
+ expect(validate(doc("plugin:acme:Nope"))).toBe(false);
174
184
  expect(validate(doc("made-up-code"))).toBe(false);
175
185
  });
176
186
 
@@ -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,25 @@ 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.",
195
+ // A box's intent, the decision record its box block names (check, #2850). Each is also a WSP finding.
196
+ "box-intent-unknown": "A box block names an intent, and no record of a declared kind named decision has that id.",
197
+ "box-intent-unconstrained": "The decision record a box names as its intent constrains no member or path of this workspace: no member: entry for a declared member and no path: entry at, above or inside one's directory.",
181
198
  // The work lease (chant workspace work claim|renew|release, #2732): why the command could not run.
182
199
  "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
200
  "work-kind-ambiguous": "More than one declared work kind has a record with the id, so --kind must name one.",
184
201
  "work-item-unknown": "No work record has the id, so there is nothing to lease.",
185
202
  "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.",
203
+ // Evidence attached to a criterion under the run's work lease (#2772): why it was refused. Nothing is written.
204
+ "work-criterion-unknown": "The work record lists no acceptance criterion with the id the evidence names, or its kind has no acceptance criteria.",
186
205
  // The work lease: why a claim, renew or release was refused (exit 2).
187
206
  "lease-held": "Someone holds a live lease on the work item: another worker, or, for a claim, the same one.",
188
207
  "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;
@@ -501,6 +510,14 @@ export async function runWorkspaceRecords(ctx: CommandContext): Promise<number>
501
510
  * names, or, when it names none or there is no declaration, the error it has
502
511
  * always been. A kind whose read fails is listed with its error, the others
503
512
  * are still read, and the exit code is 1.
513
+ *
514
+ * Locating the declaration itself can fail before any kind is known. For
515
+ * `not-a-git-repository` and `revision-unknown`, the codes a single kind's
516
+ * read can also fail with (#2860), `--json` prints the same
517
+ * `{ $schema, contract, error: { code, message } }` document that read
518
+ * failure would, so a reader of `--json` never sees a silent exit 1. A
519
+ * declaration-level code the schema doesn't carry (`declaration-invalid` and
520
+ * the rest of `WorkspaceErrorCode`) still prints text on stderr only.
504
521
  */
505
522
  async function runDeclaredRecords(args: CommandContext["args"]): Promise<number> {
506
523
  if (args.require !== undefined && args.require !== "attested") {
@@ -512,7 +529,12 @@ async function runDeclaredRecords(args: CommandContext["args"]): Promise<number>
512
529
  declared = declaredKindFiles(process.cwd(), args.at);
513
530
  } catch (err) {
514
531
  if (!(err instanceof WorkspaceReadError)) throw err;
515
- console.error(formatError({ message: `${err.code}: ${err.describe()}; without --kind, the declaration names the record kinds`, hint: USAGE }));
532
+ const message = `${err.describe()}; without --kind, the declaration names the record kinds`;
533
+ if (args.json && (err.code === "not-a-git-repository" || err.code === "revision-unknown")) {
534
+ console.log(JSON.stringify({ $schema: RECORDS_OUTPUT_SCHEMA_ID, contract: RECORDS_CONTRACT_VERSION, error: { code: err.code, message } }, null, 2));
535
+ } else {
536
+ console.error(formatError({ message: `${err.code}: ${message}`, hint: USAGE }));
537
+ }
516
538
  return 1;
517
539
  }
518
540
  if (declared.length === 0) {
@@ -602,9 +624,10 @@ function formatRecords(records: RecordView[], summary: { total: number; valid: n
602
624
  const title = typeof r.data?.title === "string" ? r.data.title : r.path;
603
625
  const flag = r.valid ? "" : " INVALID";
604
626
  const superseded = r.supersededBy ? ` superseded by ${r.supersededBy}` : "";
627
+ const remediated = r.remediatedBy.length > 0 ? ` remediated by ${r.remediatedBy.join(", ")}` : "";
605
628
  const attested = r.provenance.level === "attested" ? ` attested by ${r.provenance.principal}` : "";
606
629
  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}`);
630
+ lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${remediated}${attested}${sealed}${flag}`);
608
631
  if (r.ready !== undefined) {
609
632
  const blocked = (r.blockedBy ?? []).map((b) => `${b.id} (${b.state ?? "unknown"})`).join(", ");
610
633
  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
  ]
@@ -3,11 +3,11 @@
3
3
  * kinds without a lifecycle, a supersedes field of bare ids, schema files a
4
4
  * schema `$ref`s, and content-addressed ids.
5
5
  *
6
- * The fixtures are shaped like chud's records (jhgaylor/chud#78: units,
7
- * evidence named by the hash of its bytes, sessions, driver closures and
8
- * Markdown contracts, with draft-07 schemas that `$ref` a shared
9
- * `defs.schema.json`), trimmed to the fields that matter here. Each one reads
10
- * through a kind file alone: no reader code.
6
+ * The fixtures are shaped like a development-model plugin's records (ported
7
+ * from chud, jhgaylor/chud#78: units, evidence named by the hash of its
8
+ * bytes, sessions, driver closures and Markdown contracts, with draft-07
9
+ * schemas that `$ref` a shared `defs.schema.json`), trimmed to the fields
10
+ * that matter here. Each one reads through a kind file alone: no reader code.
11
11
  */
12
12
 
13
13
  import { execFileSync } from "node:child_process";
@@ -29,7 +29,7 @@ const validateOutput = new Ajv2020({ strict: true, allErrors: true }).compile(sc
29
29
  let root: string;
30
30
  beforeEach(() => {
31
31
  root = realpathSync(mkdtempSync(join(tmpdir(), "chant-record-formats-")));
32
- writeChudModel();
32
+ writeAcmeModel();
33
33
  });
34
34
  afterEach(() => rmSync(root, { recursive: true, force: true }));
35
35
 
@@ -72,9 +72,9 @@ async function failure(kind: string): Promise<{ code: string; message: string }>
72
72
  const codes = (r: { reasons: Array<{ code: string }> }) => r.reasons.map((x) => x.code);
73
73
  const warned = (r: { warnings: Array<{ code: string }> }) => r.warnings.map((x) => x.code);
74
74
 
75
- // ── chud's model, as kind files ──────────────────────────────────────────────
75
+ // ── acme's model, as kind files ──────────────────────────────────────────────
76
76
 
77
- const BASE = "https://schemas.example.test/chud-runtime/v0";
77
+ const BASE = "https://schemas.example.test/acme-runtime/v0";
78
78
  const ref = (name: string) => ({ $ref: `defs.schema.json#/definitions/${name}` });
79
79
 
80
80
  const DEFS = {
@@ -106,7 +106,7 @@ const UNIT_SCHEMA = {
106
106
  scope: { type: "array", minItems: 1, items: ref("text") },
107
107
  base_commit: ref("gitId"),
108
108
  result: { type: "object", required: ["commit", "ref"], properties: { commit: { type: ["string", "null"] }, ref: { type: ["string", "null"] } } },
109
- // A bare hash, as chud writes today, or a pin chant checks.
109
+ // A bare hash, as acme writes today, or a pin chant checks.
110
110
  evidence: {
111
111
  type: "array",
112
112
  items: { anyOf: [ref("sha256"), { type: "object", required: ["path", "sha256"], properties: { path: ref("text"), sha256: ref("sha256") } }] },
@@ -178,7 +178,7 @@ const CONTRACT_SCHEMA = {
178
178
  },
179
179
  };
180
180
 
181
- /** A kind file: data only, the way chud's model package would ship it. */
181
+ /** A kind file: data only, the way acme's model package would ship it. */
182
182
  function kindFile(kind: Record<string, unknown>): string {
183
183
  return `export const recordKind = ${JSON.stringify(kind, null, 2)};\n`;
184
184
  }
@@ -235,7 +235,7 @@ const KINDS: Record<string, Record<string, unknown>> = {
235
235
  },
236
236
  };
237
237
 
238
- function writeChudModel(): void {
238
+ function writeAcmeModel(): void {
239
239
  write("design/schemas/defs.schema.json", json(DEFS));
240
240
  write("design/schemas/unit.schema.json", json(UNIT_SCHEMA));
241
241
  write("design/schemas/evidence.schema.json", json(EVIDENCE_SCHEMA));
@@ -304,7 +304,7 @@ test("the output schema lists exactly the formats a kind file may name", () => {
304
304
  });
305
305
 
306
306
  describe("format json", () => {
307
- test("chud's units, sessions and driver closures read through kind files alone", async () => {
307
+ test("acme's units, sessions and driver closures read through kind files alone", async () => {
308
308
  write("design/units/U-0001.json", json(unit("U-0001", { outcome: "done", closed_at: "2026-09-24T11:00:00.000Z" })));
309
309
  write("design/units/U-0002.json", json(unit("U-0002")));
310
310
  write("design/sessions/S-0001.json", json(SESSION));
@@ -407,7 +407,7 @@ describe("the write commands through a declared kind (#2680)", () => {
407
407
  test.each(["unit", "evidence"])("refuse the declared %s kind with write-usage-invalid", async (kind) => {
408
408
  write(
409
409
  "chant.workspace.json",
410
- json({ name: "chud", schema: 1, members: [{ name: "design", dir: "design", kind: "other", because: "chud's records" }], records: [{ kind: `design/${kind}.kind.mjs` }] }),
410
+ json({ name: "acme", schema: 1, members: [{ name: "design", dir: "design", kind: "other", because: "acme's records" }], records: [{ kind: `design/${kind}.kind.mjs` }] }),
411
411
  );
412
412
  write("design/units/U-0001.json", json(unit("U-0001")));
413
413
  write("fields.json", "{}\n");
@@ -510,7 +510,7 @@ describe("kinds without a lifecycle", () => {
510
510
  const contract = (id: string, status: string, supersedes?: string | string[]) =>
511
511
  `---\nid: "${id}"\nstatus: "${status}"\n${supersedes === undefined ? "" : `supersedes: ${JSON.stringify(supersedes)}\n`}---\n\n# ${id}\n`;
512
512
 
513
- test("supersedes as one id, as chud's contracts write it, derives supersededBy under approval ranks", async () => {
513
+ test("supersedes as one id, as acme's contracts write it, derives supersededBy under approval ranks", async () => {
514
514
  write("design/contracts/C-001-first.md", contract("C-001", "approved"));
515
515
  write("design/contracts/C-002-second.md", contract("C-002", "approved", "C-001"));
516
516
  write("design/contracts/C-003-draft.md", contract("C-003", "draft", "C-002"));
@@ -588,7 +588,7 @@ describe("schema.refs", () => {
588
588
  // ── 4. Content-addressed ids ─────────────────────────────────────────────────
589
589
 
590
590
  describe("idFrom sha256", () => {
591
- test("chud's evidence reads with the hash of its bytes as its id, in the tree and under --at", async () => {
591
+ test("acme's evidence reads with the hash of its bytes as its id, in the tree and under --at", async () => {
592
592
  const h = writeEvidence();
593
593
  const bytes = readFileSync(join(root, "design", "evidence", `${h}.json`));
594
594
  expect(createHash("sha256").update(bytes).digest("hex")).toBe(h);
@@ -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
+ });