@intentius/chant 0.90.0 → 0.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/lint.d.ts.map +1 -1
  3. package/dist/cli/handlers/components.d.ts +8 -0
  4. package/dist/cli/handlers/components.d.ts.map +1 -1
  5. package/dist/cli/handlers/graph.d.ts.map +1 -1
  6. package/dist/cli/handlers/operator.d.ts +20 -0
  7. package/dist/cli/handlers/operator.d.ts.map +1 -1
  8. package/dist/cli/handlers/run.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts +7 -4
  11. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  12. package/dist/cli/plugins.d.ts.map +1 -1
  13. package/dist/cli/registry.d.ts +42 -1
  14. package/dist/cli/registry.d.ts.map +1 -1
  15. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  16. package/dist/components/verbs/vuln-scan.d.ts +72 -0
  17. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  18. package/dist/config.d.ts +22 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/lexicon-module.d.ts +22 -0
  21. package/dist/lexicon-module.d.ts.map +1 -1
  22. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  23. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  24. package/dist/lifecycle/git.d.ts +40 -5
  25. package/dist/lifecycle/git.d.ts.map +1 -1
  26. package/dist/lifecycle/lease.d.ts +72 -16
  27. package/dist/lifecycle/lease.d.ts.map +1 -1
  28. package/dist/lifecycle/member-ledger.d.ts +3 -2
  29. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  30. package/dist/lifecycle/plan-ledger.d.ts +114 -0
  31. package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
  32. package/dist/lifecycle/run-ledger.d.ts +2 -0
  33. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  34. package/dist/lifecycle/symptoms.d.ts +51 -0
  35. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  36. package/dist/lifecycle/work-lease.d.ts +140 -0
  37. package/dist/lifecycle/work-lease.d.ts.map +1 -0
  38. package/dist/lint/rules/index.d.ts +4 -2
  39. package/dist/lint/rules/index.d.ts.map +1 -1
  40. package/dist/lint/rules/op/index.d.ts +14 -3
  41. package/dist/lint/rules/op/index.d.ts.map +1 -1
  42. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  43. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  44. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  45. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  46. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  47. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  48. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  49. package/dist/op/activities/activity-contracts.d.ts +203 -2
  50. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  51. package/dist/op/activities/apply.d.ts.map +1 -1
  52. package/dist/op/activities/change-coverage.d.ts +44 -0
  53. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  54. package/dist/op/activities/converge.d.ts +24 -1
  55. package/dist/op/activities/converge.d.ts.map +1 -1
  56. package/dist/op/activities/decide.d.ts +107 -0
  57. package/dist/op/activities/decide.d.ts.map +1 -0
  58. package/dist/op/activities/index.d.ts +10 -0
  59. package/dist/op/activities/index.d.ts.map +1 -1
  60. package/dist/op/activities/shell.d.ts +32 -0
  61. package/dist/op/activities/shell.d.ts.map +1 -1
  62. package/dist/op/activities/source-release.d.ts +86 -0
  63. package/dist/op/activities/source-release.d.ts.map +1 -0
  64. package/dist/op/activities/source-rollback.d.ts +100 -0
  65. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  66. package/dist/op/activities/work-evidence.d.ts +42 -0
  67. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  68. package/dist/op/activity-contract.d.ts +1 -0
  69. package/dist/op/activity-contract.d.ts.map +1 -1
  70. package/dist/op/activity-profiles.d.ts +16 -0
  71. package/dist/op/activity-profiles.d.ts.map +1 -1
  72. package/dist/op/builders.d.ts +84 -2
  73. package/dist/op/builders.d.ts.map +1 -1
  74. package/dist/op/composites/converge-op.d.ts +31 -4
  75. package/dist/op/composites/converge-op.d.ts.map +1 -1
  76. package/dist/op/decide-backend.d.ts +116 -0
  77. package/dist/op/decide-backend.d.ts.map +1 -0
  78. package/dist/op/decide-config.d.ts +120 -0
  79. package/dist/op/decide-config.d.ts.map +1 -0
  80. package/dist/op/decide-read-inputs.d.ts +40 -0
  81. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  82. package/dist/op/discover.d.ts +25 -0
  83. package/dist/op/discover.d.ts.map +1 -1
  84. package/dist/op/gate-wait.d.ts +43 -0
  85. package/dist/op/gate-wait.d.ts.map +1 -0
  86. package/dist/op/gate.d.ts +5 -2
  87. package/dist/op/gate.d.ts.map +1 -1
  88. package/dist/op/index.d.ts +18 -5
  89. package/dist/op/index.d.ts.map +1 -1
  90. package/dist/op/lifecycle-receipt-store.d.ts +34 -0
  91. package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
  92. package/dist/op/local-executor.d.ts +48 -4
  93. package/dist/op/local-executor.d.ts.map +1 -1
  94. package/dist/op/local-output.d.ts +2 -0
  95. package/dist/op/local-output.d.ts.map +1 -1
  96. package/dist/op/op-ir.d.ts +12 -1
  97. package/dist/op/op-ir.d.ts.map +1 -1
  98. package/dist/op/op-verb-class.d.ts.map +1 -1
  99. package/dist/op/operator.d.ts +126 -1
  100. package/dist/op/operator.d.ts.map +1 -1
  101. package/dist/op/runtime.d.ts +46 -5
  102. package/dist/op/runtime.d.ts.map +1 -1
  103. package/dist/op/runtimes/local.d.ts.map +1 -1
  104. package/dist/op/source-archive.d.ts +63 -0
  105. package/dist/op/source-archive.d.ts.map +1 -0
  106. package/dist/op/step-output-ref.d.ts +2 -2
  107. package/dist/op/step-output-ref.d.ts.map +1 -1
  108. package/dist/op/steward-points.d.ts +136 -0
  109. package/dist/op/steward-points.d.ts.map +1 -0
  110. package/dist/op/steward-turn.d.ts +53 -0
  111. package/dist/op/steward-turn.d.ts.map +1 -0
  112. package/dist/op/steward.d.ts +161 -0
  113. package/dist/op/steward.d.ts.map +1 -0
  114. package/dist/op/types.d.ts +58 -0
  115. package/dist/op/types.d.ts.map +1 -1
  116. package/dist/op/work-lease-decl.d.ts +18 -0
  117. package/dist/op/work-lease-decl.d.ts.map +1 -0
  118. package/dist/op/work-lease-run.d.ts +173 -0
  119. package/dist/op/work-lease-run.d.ts.map +1 -0
  120. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  121. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  122. package/dist/workspace/answers-ledger.d.ts +76 -0
  123. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  124. package/dist/workspace/box-isolation.d.ts +99 -0
  125. package/dist/workspace/box-isolation.d.ts.map +1 -0
  126. package/dist/workspace/changes-cli.d.ts +16 -0
  127. package/dist/workspace/changes-cli.d.ts.map +1 -0
  128. package/dist/workspace/changes.d.ts +200 -0
  129. package/dist/workspace/changes.d.ts.map +1 -0
  130. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  131. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  132. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  133. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  134. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  135. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  136. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  137. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  138. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  139. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  140. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  141. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  142. package/dist/workspace/chant-migrations.d.ts +99 -0
  143. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  144. package/dist/workspace/checks/box-isolation.d.ts +18 -0
  145. package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
  146. package/dist/workspace/checks/boxes.d.ts +104 -0
  147. package/dist/workspace/checks/boxes.d.ts.map +1 -0
  148. package/dist/workspace/checks/diagrams.d.ts +31 -0
  149. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  150. package/dist/workspace/checks/records.d.ts +8 -0
  151. package/dist/workspace/checks/records.d.ts.map +1 -1
  152. package/dist/workspace/checks.d.ts +13 -1
  153. package/dist/workspace/checks.d.ts.map +1 -1
  154. package/dist/workspace/compose-graph.d.ts +2 -0
  155. package/dist/workspace/compose-graph.d.ts.map +1 -1
  156. package/dist/workspace/conformance/index.d.ts.map +1 -1
  157. package/dist/workspace/decide.d.ts +229 -0
  158. package/dist/workspace/decide.d.ts.map +1 -0
  159. package/dist/workspace/decision-points.schema.json +137 -0
  160. package/dist/workspace/declaration.d.ts +103 -0
  161. package/dist/workspace/declaration.d.ts.map +1 -1
  162. package/dist/workspace/declaration.schema.json +285 -0
  163. package/dist/workspace/declared-kinds.d.ts +32 -0
  164. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  165. package/dist/workspace/generated-files.d.ts +7 -0
  166. package/dist/workspace/generated-files.d.ts.map +1 -1
  167. package/dist/workspace/intent.d.ts.map +1 -1
  168. package/dist/workspace/lineage-check.d.ts +3 -0
  169. package/dist/workspace/lineage-check.d.ts.map +1 -1
  170. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  171. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  172. package/dist/workspace/ls.d.ts +39 -1
  173. package/dist/workspace/ls.d.ts.map +1 -1
  174. package/dist/workspace/point-answer.schema.json +95 -0
  175. package/dist/workspace/points-cli.d.ts +113 -0
  176. package/dist/workspace/points-cli.d.ts.map +1 -0
  177. package/dist/workspace/points.d.ts +320 -0
  178. package/dist/workspace/points.d.ts.map +1 -0
  179. package/dist/workspace/reason-codes.d.ts +38 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/record-assets.d.ts.map +1 -1
  182. package/dist/workspace/records-cli.d.ts +23 -1
  183. package/dist/workspace/records-cli.d.ts.map +1 -1
  184. package/dist/workspace/records-close.d.ts +1 -1
  185. package/dist/workspace/records-close.d.ts.map +1 -1
  186. package/dist/workspace/records-write.d.ts +11 -5
  187. package/dist/workspace/records-write.d.ts.map +1 -1
  188. package/dist/workspace/records.d.ts +42 -6
  189. package/dist/workspace/records.d.ts.map +1 -1
  190. package/dist/workspace/status-gates.d.ts +7 -2
  191. package/dist/workspace/status-gates.d.ts.map +1 -1
  192. package/dist/workspace/status-stewards.d.ts +179 -0
  193. package/dist/workspace/status-stewards.d.ts.map +1 -0
  194. package/dist/workspace/status.d.ts +68 -1
  195. package/dist/workspace/status.d.ts.map +1 -1
  196. package/dist/workspace/work-cli.d.ts +189 -0
  197. package/dist/workspace/work-cli.d.ts.map +1 -0
  198. package/dist/workspace/work-evidence.d.ts +74 -0
  199. package/dist/workspace/work-evidence.d.ts.map +1 -0
  200. package/dist/workspace/work.d.ts +43 -1
  201. package/dist/workspace/work.d.ts.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/commands/audit.test.ts +9 -1
  204. package/src/cli/commands/build.ts +3 -2
  205. package/src/cli/commands/lint.ts +7 -1
  206. package/src/cli/handlers/components.test.ts +93 -0
  207. package/src/cli/handlers/components.ts +44 -3
  208. package/src/cli/handlers/graph.test.ts +20 -0
  209. package/src/cli/handlers/graph.ts +16 -2
  210. package/src/cli/handlers/operator.ts +141 -4
  211. package/src/cli/handlers/run.test.ts +173 -2
  212. package/src/cli/handlers/run.ts +174 -3
  213. package/src/cli/main.test.ts +50 -0
  214. package/src/cli/main.ts +97 -2
  215. package/src/cli/mcp/workspace-tools.test.ts +48 -19
  216. package/src/cli/mcp/workspace-tools.ts +110 -8
  217. package/src/cli/plugins.ts +11 -3
  218. package/src/cli/registry.ts +42 -1
  219. package/src/codegen/pinned-upgrade.ts +2 -1
  220. package/src/codegen/release-wiring.test.ts +49 -2
  221. package/src/components/capability-plugin-loader.ts +3 -3
  222. package/src/components/cli-support.ts +2 -2
  223. package/src/components/verbs/vuln-scan.test.ts +124 -1
  224. package/src/components/verbs/vuln-scan.ts +142 -1
  225. package/src/config.ts +11 -0
  226. package/src/lexicon-module.ts +56 -1
  227. package/src/lexicon-package.test.ts +96 -0
  228. package/src/lifecycle/converge-ledger.ts +20 -0
  229. package/src/lifecycle/git.ts +65 -15
  230. package/src/lifecycle/lease.test.ts +22 -0
  231. package/src/lifecycle/lease.ts +133 -29
  232. package/src/lifecycle/member-ledger.ts +3 -2
  233. package/src/lifecycle/plan-ledger.test.ts +148 -0
  234. package/src/lifecycle/plan-ledger.ts +158 -0
  235. package/src/lifecycle/run-ledger.ts +6 -0
  236. package/src/lifecycle/symptoms.ts +95 -0
  237. package/src/lifecycle/work-lease.test.ts +236 -0
  238. package/src/lifecycle/work-lease.ts +426 -0
  239. package/src/lint/rules/index.ts +6 -2
  240. package/src/lint/rules/op/index.ts +17 -4
  241. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  242. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  243. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  244. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  245. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  246. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  247. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  248. package/src/op/activities/activity-contracts.ts +190 -1
  249. package/src/op/activities/apply.ts +7 -6
  250. package/src/op/activities/change-coverage.ts +60 -0
  251. package/src/op/activities/converge.test.ts +55 -1
  252. package/src/op/activities/converge.ts +208 -29
  253. package/src/op/activities/decide.live.test.ts +78 -0
  254. package/src/op/activities/decide.test.ts +300 -0
  255. package/src/op/activities/decide.ts +267 -0
  256. package/src/op/activities/index.ts +21 -0
  257. package/src/op/activities/shell.test.ts +91 -1
  258. package/src/op/activities/shell.ts +91 -2
  259. package/src/op/activities/source-release.test.ts +118 -0
  260. package/src/op/activities/source-release.ts +168 -0
  261. package/src/op/activities/source-rollback.test.ts +131 -0
  262. package/src/op/activities/source-rollback.ts +239 -0
  263. package/src/op/activities/work-evidence.ts +62 -0
  264. package/src/op/activity-contract-registry.test.ts +2 -0
  265. package/src/op/activity-contract-registry.ts +2 -2
  266. package/src/op/activity-contract.test.ts +12 -0
  267. package/src/op/activity-contract.ts +6 -1
  268. package/src/op/activity-profiles.ts +29 -0
  269. package/src/op/activity-registry.test.ts +1 -1
  270. package/src/op/activity-registry.ts +2 -2
  271. package/src/op/builders.ts +124 -5
  272. package/src/op/command-gate.test.ts +192 -0
  273. package/src/op/composites/converge-op.test.ts +27 -1
  274. package/src/op/composites/converge-op.ts +69 -15
  275. package/src/op/decide-backend.test.ts +44 -0
  276. package/src/op/decide-backend.ts +212 -0
  277. package/src/op/decide-config.test.ts +52 -0
  278. package/src/op/decide-config.ts +80 -0
  279. package/src/op/decide-read-inputs.ts +117 -0
  280. package/src/op/discover.ts +71 -0
  281. package/src/op/gate-approval.ts +2 -2
  282. package/src/op/gate-wait.ts +57 -0
  283. package/src/op/gate.ts +8 -3
  284. package/src/op/generate-pipeline.ts +2 -2
  285. package/src/op/index.ts +34 -6
  286. package/src/op/lifecycle-receipt-store.test.ts +60 -0
  287. package/src/op/lifecycle-receipt-store.ts +61 -0
  288. package/src/op/local-executor.ts +367 -33
  289. package/src/op/local-output.test.ts +33 -0
  290. package/src/op/local-output.ts +33 -2
  291. package/src/op/op-ir.test.ts +10 -0
  292. package/src/op/op-ir.ts +18 -0
  293. package/src/op/op-verb-class.ts +1 -0
  294. package/src/op/operator.ts +360 -10
  295. package/src/op/runtime.ts +37 -8
  296. package/src/op/runtimes/local.ts +6 -1
  297. package/src/op/source-archive.ts +161 -0
  298. package/src/op/step-output-ref.ts +6 -2
  299. package/src/op/steward-points.test.ts +313 -0
  300. package/src/op/steward-points.ts +194 -0
  301. package/src/op/steward-turn.ts +79 -0
  302. package/src/op/steward.test.ts +296 -0
  303. package/src/op/steward.ts +277 -0
  304. package/src/op/types.ts +60 -0
  305. package/src/op/work-lease-decl.ts +80 -0
  306. package/src/op/work-lease-run.test.ts +345 -0
  307. package/src/op/work-lease-run.ts +395 -0
  308. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  309. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  310. package/src/workspace/answers-ledger.ts +118 -0
  311. package/src/workspace/box-isolation.test.ts +261 -0
  312. package/src/workspace/box-isolation.ts +205 -0
  313. package/src/workspace/changes-cli.ts +56 -0
  314. package/src/workspace/changes.schema.json +472 -0
  315. package/src/workspace/changes.test.ts +315 -0
  316. package/src/workspace/changes.ts +512 -0
  317. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  318. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  319. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  320. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  321. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  322. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  323. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  324. package/src/workspace/chant-migrations.ts +164 -0
  325. package/src/workspace/check-contract.test.ts +5 -1
  326. package/src/workspace/check.schema.json +25 -7
  327. package/src/workspace/checks/box-isolation.ts +68 -0
  328. package/src/workspace/checks/boxes.test.ts +276 -0
  329. package/src/workspace/checks/boxes.ts +442 -0
  330. package/src/workspace/checks/diagrams.test.ts +134 -0
  331. package/src/workspace/checks/diagrams.ts +108 -0
  332. package/src/workspace/checks/records.ts +72 -0
  333. package/src/workspace/checks.test.ts +7 -0
  334. package/src/workspace/checks.ts +27 -3
  335. package/src/workspace/compose-graph.ts +2 -0
  336. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  337. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  338. package/src/workspace/conformance/index.ts +5 -0
  339. package/src/workspace/decide.test.ts +224 -0
  340. package/src/workspace/decide.ts +709 -0
  341. package/src/workspace/decision-points.schema.json +137 -0
  342. package/src/workspace/declaration.schema.json +285 -0
  343. package/src/workspace/declaration.test.ts +58 -0
  344. package/src/workspace/declaration.ts +241 -0
  345. package/src/workspace/declared-kinds.test.ts +5 -5
  346. package/src/workspace/declared-kinds.ts +71 -3
  347. package/src/workspace/generated-files.ts +14 -0
  348. package/src/workspace/graph-cli.ts +1 -1
  349. package/src/workspace/graph.schema.json +9 -1
  350. package/src/workspace/intent.schema.json +12 -3
  351. package/src/workspace/intent.ts +3 -10
  352. package/src/workspace/lineage-check.ts +16 -1
  353. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  354. package/src/workspace/lineage-upgrade.test.ts +70 -0
  355. package/src/workspace/lineage-upgrade.ts +81 -10
  356. package/src/workspace/ls-contract.test.ts +57 -0
  357. package/src/workspace/ls.schema.json +46 -1
  358. package/src/workspace/ls.ts +62 -3
  359. package/src/workspace/point-answer.schema.json +95 -0
  360. package/src/workspace/points-cli.ts +284 -0
  361. package/src/workspace/points-write.schema.json +499 -0
  362. package/src/workspace/points.schema.json +743 -0
  363. package/src/workspace/points.test.ts +264 -0
  364. package/src/workspace/points.ts +564 -0
  365. package/src/workspace/read-contract.test.ts +18 -1
  366. package/src/workspace/reason-codes.test.ts +24 -1
  367. package/src/workspace/reason-codes.ts +49 -0
  368. package/src/workspace/record-assets.test.ts +3 -2
  369. package/src/workspace/record-assets.ts +4 -1
  370. package/src/workspace/records-amend.schema.json +2 -0
  371. package/src/workspace/records-cli.ts +28 -5
  372. package/src/workspace/records-close.schema.json +2 -0
  373. package/src/workspace/records-contract.test.ts +3 -2
  374. package/src/workspace/records-new.schema.json +2 -0
  375. package/src/workspace/records-review.schema.json +2 -0
  376. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  377. package/src/workspace/records-sessions-write.test.ts +2 -34
  378. package/src/workspace/records-write.e2e.test.ts +108 -0
  379. package/src/workspace/records-write.test.ts +32 -41
  380. package/src/workspace/records-write.ts +33 -15
  381. package/src/workspace/records.schema.json +53 -2
  382. package/src/workspace/records.test.ts +48 -3
  383. package/src/workspace/records.ts +142 -6
  384. package/src/workspace/status-contract.test.ts +189 -6
  385. package/src/workspace/status-gates.ts +9 -4
  386. package/src/workspace/status-stewards.ts +329 -0
  387. package/src/workspace/status.schema.json +335 -5
  388. package/src/workspace/status.ts +142 -6
  389. package/src/workspace/work-acceptance.test.ts +274 -0
  390. package/src/workspace/work-cli.e2e.test.ts +180 -0
  391. package/src/workspace/work-cli.ts +429 -0
  392. package/src/workspace/work-evidence.schema.json +212 -0
  393. package/src/workspace/work-evidence.ts +169 -0
  394. package/src/workspace/work-history.schema.json +349 -0
  395. package/src/workspace/work-history.test.ts +156 -0
  396. package/src/workspace/work-lease.schema.json +233 -0
  397. package/src/workspace/work-readiness-chud.test.ts +145 -0
  398. package/src/workspace/work.test.ts +3 -2
  399. package/src/workspace/work.ts +100 -1
  400. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  401. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  402. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -96,6 +96,17 @@ export interface Suppression {
96
96
  /** What `checks` may set a declaration check's severity to. */
97
97
  export type CheckSeverity = "error" | "warning" | "info" | "off";
98
98
 
99
+ /** How `chant workspace check --changes` treats its findings (#2773): not at all, as warnings, or as failures. */
100
+ export type ChangeSeverity = "off" | "warn" | "fail";
101
+
102
+ /** The declaration's `changes` block (#2773): the severity of the forward coverage check, and the paths it never reports. */
103
+ export interface ChangesPolicy {
104
+ /** Absent from the declaration, `warn`. */
105
+ severity: ChangeSeverity;
106
+ /** Globs over files, relative to the workspace root, whose changes need no record: lockfiles, generated output. */
107
+ ignore: string[];
108
+ }
109
+
99
110
  export interface MemberRole {
100
111
  name: string;
101
112
  /** Relative to the member's directory, or null for the whole member. */
@@ -128,6 +139,40 @@ export interface LinkDeclaration {
128
139
  pointer: string;
129
140
  }
130
141
 
142
+ /** A renderer a diagram is pinned to (#2764): a closed list of tools chant does not run. */
143
+ export const DIAGRAM_TOOLS = ["d2", "mermaid", "graphviz"] as const;
144
+ export type DiagramTool = (typeof DIAGRAM_TOOLS)[number];
145
+
146
+ /** The renderer a diagram's render was made with (#2764). chant never runs it; it is recorded so a reader can. */
147
+ export interface DiagramRenderer {
148
+ tool: DiagramTool;
149
+ /** The exact release the render was made with, such as "0.9.0". */
150
+ version: string;
151
+ /** Passed before the input and output paths, in order. */
152
+ args: string[];
153
+ }
154
+
155
+ /**
156
+ * A diagram artifact the declaration names (#2764): the workspace's own, in
157
+ * the top-level `diagrams`, or a member's, in that member's `diagrams`. A
158
+ * name is given once across the declaration, like a record kind's.
159
+ */
160
+ export interface DiagramDeclaration {
161
+ name: string;
162
+ title: string;
163
+ /** From the workspace root, with / separators. Null for an SVG with no source. */
164
+ source: string | null;
165
+ /** From the workspace root, with / separators. */
166
+ render: string;
167
+ renderer: DiagramRenderer;
168
+ /** sha256 hex of the source's bytes when the render was last produced, for `chant workspace check`'s drift finding. Null when not recorded, or when source is null: the render is then never checked for drift. */
169
+ sourceHash: string | null;
170
+ /** The member that declares it, or null for the workspace's own. */
171
+ member: string | null;
172
+ /** The entry's JSON Pointer in the file, for messages. */
173
+ pointer: string;
174
+ }
175
+
131
176
  /**
132
177
  * A record kind the declaration names (#2680): the workspace's own, in the
133
178
  * top-level `records`, or a member's, in that member's `records`.
@@ -145,6 +190,54 @@ export interface RecordKindDeclaration {
145
190
  pointer: string;
146
191
  }
147
192
 
193
+ /**
194
+ * A capability a box reaches through a broker (#2726): inference, Fountain,
195
+ * a third-party API. The broker is runtime (a lobby, a door, a studio) and
196
+ * holds the credential; the declaration names it and the scope it enforces.
197
+ */
198
+ export interface BoxCapability {
199
+ name: string;
200
+ /** What brokers it, such as `lobby`, or null when the entry names no broker (WSP122). */
201
+ broker: string | null;
202
+ /** What of the box's own the broker lets it reach, such as `agent`, `vault`, `conversations`, `sandboxes`. */
203
+ scope: string[];
204
+ /** The entry's JSON Pointer in the file, for messages. */
205
+ pointer: string;
206
+ }
207
+
208
+ /** A member that is a box, or a box's declarations (#2726). */
209
+ export interface BoxDeclaration {
210
+ capabilities: BoxCapability[];
211
+ /** The host and slot of the box's isolation, or null when the block declares none (#2727). The values are derived in `boxes.ts`. */
212
+ isolation: BoxIsolationDeclaration | null;
213
+ /** The block's JSON Pointer in the file, for messages. */
214
+ pointer: string;
215
+ }
216
+
217
+ /** What a box declares about its isolation (#2727): its identity on a host, and the names of what it needs kept apart. */
218
+ export interface BoxIsolationDeclaration {
219
+ host: string;
220
+ slot: number;
221
+ /** Port name to offset in the box's block, in file order. */
222
+ ports: Record<string, number>;
223
+ /** State name to a path relative to the box's state directory, in file order. */
224
+ state: Record<string, string>;
225
+ cookies: string[];
226
+ }
227
+
228
+ /** A host boxes run on (#2727): one machine under one hostname. */
229
+ export interface Host {
230
+ name: string;
231
+ ports: { from: number; to: number; perBox: number };
232
+ /** As written, or null for the default ({@link DEFAULT_STATE_ROOT}). */
233
+ stateRoot: string | null;
234
+ /** The entry's JSON Pointer in the file, for messages. */
235
+ pointer: string;
236
+ }
237
+
238
+ /** Where boxes keep state when their host names no stateRoot (#2727). */
239
+ export const DEFAULT_STATE_ROOT = "${XDG_STATE_HOME}/chant/boxes";
240
+
148
241
  export interface Member {
149
242
  type: "member";
150
243
  name: string;
@@ -160,6 +253,10 @@ export interface Member {
160
253
  links: LinkDeclaration[];
161
254
  /** The record kinds this member declares, in file order (#2680). */
162
255
  records: RecordKindDeclaration[];
256
+ /** The diagram artifacts this member declares, in file order (#2764). */
257
+ diagrams: DiagramDeclaration[];
258
+ /** The member's box block, or null when it declares none (#2726). */
259
+ box: BoxDeclaration | null;
163
260
  upstream: string | null;
164
261
  because: string | null;
165
262
  suppress: Suppression[];
@@ -202,6 +299,12 @@ export interface Declaration {
202
299
  quorum: number | null;
203
300
  /** The workspace's own record kinds, from the top-level `records`, in file order (#2680). A member's are on the member. */
204
301
  records: RecordKindDeclaration[];
302
+ /** The workspace's own diagram artifacts, from the top-level `diagrams`, in file order (#2764). A member's are on the member. */
303
+ diagrams: DiagramDeclaration[];
304
+ /** The hosts boxes run on, in file order (#2727). */
305
+ hosts: Host[];
306
+ /** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
307
+ changes: ChangesPolicy | null;
205
308
  /** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
206
309
  file: string;
207
310
  }
@@ -414,6 +517,8 @@ export function parseDeclaration(text: string, file: string, reader: string = re
414
517
  pointer: `${pointer}/links/${j}`,
415
518
  }));
416
519
  const records = recordKindsOf(e.records, e.dir as string, e.name as string, `${pointer}/records`);
520
+ const diagrams = diagramsOf(e.diagrams, e.name as string, `${pointer}/diagrams`);
521
+ const box = boxOf(e.box, `${pointer}/box`);
417
522
  return {
418
523
  type: "member",
419
524
  name: e.name as string,
@@ -424,6 +529,8 @@ export function parseDeclaration(text: string, file: string, reader: string = re
424
529
  outputs: e.outputs === undefined ? null : [...(e.outputs as string[])],
425
530
  links,
426
531
  records,
532
+ diagrams,
533
+ box,
427
534
  upstream: (e.upstream as string | undefined) ?? null,
428
535
  because: (e.because as string | undefined) ?? null,
429
536
  suppress,
@@ -508,6 +615,29 @@ export function parseDeclaration(text: string, file: string, reader: string = re
508
615
  byName.set(r.name, r);
509
616
  }
510
617
 
618
+ // A box names each capability once (#2726): a broker reads its scope by name.
619
+ for (const m of members) {
620
+ const seen = new Map<string, BoxCapability>();
621
+ for (const c of m.box?.capabilities ?? []) {
622
+ const first = seen.get(c.name);
623
+ if (first) throw new WorkspaceReadError("declaration-invalid", `member ${m.name}'s box lists the capability ${c.name} twice; the first is at ${first.pointer}`, at(`${c.pointer}/name`));
624
+ seen.set(c.name, c);
625
+ }
626
+ }
627
+
628
+ // A diagram name is given once across the declaration (#2764): a reader keys diagrams by name.
629
+ const ownDiagrams = diagramsOf(obj.diagrams, null, "/diagrams");
630
+ const byDiagramName = new Map<string, DiagramDeclaration>();
631
+ for (const d of [...ownDiagrams, ...members.flatMap((m) => m.diagrams)]) {
632
+ const first = byDiagramName.get(d.name);
633
+ if (first) {
634
+ throw new WorkspaceReadError("declaration-invalid", `the diagram name ${JSON.stringify(d.name)} is already used by the diagram at ${first.pointer}`, at(`${d.pointer}/name`));
635
+ }
636
+ byDiagramName.set(d.name, d);
637
+ }
638
+
639
+ const hosts = hostsOf(obj, members, at);
640
+
511
641
  const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
512
642
  package: p.package ?? null,
513
643
  version: p.version ?? null,
@@ -526,10 +656,19 @@ export function parseDeclaration(text: string, file: string, reader: string = re
526
656
  checks: { ...((obj.checks as Record<string, CheckSeverity> | undefined) ?? {}) },
527
657
  quorum: typeof obj.quorum === "number" ? obj.quorum : null,
528
658
  records: ownRecords,
659
+ diagrams: ownDiagrams,
660
+ hosts,
661
+ changes: changesOf(obj.changes as Record<string, unknown> | undefined),
529
662
  file,
530
663
  };
531
664
  }
532
665
 
666
+ /** The `changes` block, already validated, with its defaults (#2773). */
667
+ function changesOf(block: Record<string, unknown> | undefined): ChangesPolicy | null {
668
+ if (block === undefined) return null;
669
+ return { severity: (block.severity as ChangeSeverity | undefined) ?? "warn", ignore: [...((block.ignore as string[] | undefined) ?? [])] };
670
+ }
671
+
533
672
  /** The `records` list at `pointer`, already validated, with each kind file's path from the workspace root. */
534
673
  function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer: string): RecordKindDeclaration[] {
535
674
  return ((raw as { kind: string; name?: string }[] | undefined) ?? []).map((r, i) => ({
@@ -541,6 +680,99 @@ function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer
541
680
  }));
542
681
  }
543
682
 
683
+ /**
684
+ * The `diagrams` list at `pointer`, already validated. `source` and `render`
685
+ * are workspace-root-relative as written: unlike a generated file or a
686
+ * record kind, a diagram's files need not sit inside the declaring member's
687
+ * directory, so there is no member-relative form to resolve (#2764).
688
+ */
689
+ function diagramsOf(raw: unknown, member: string | null, pointer: string): DiagramDeclaration[] {
690
+ return (
691
+ (raw as
692
+ | { name: string; title: string; source?: string | null; render: string; renderer: { tool: DiagramTool; version: string; args?: string[] }; sourceHash?: string | null }[]
693
+ | undefined) ?? []
694
+ ).map((d, i) => ({
695
+ name: d.name,
696
+ title: d.title,
697
+ source: d.source ?? null,
698
+ render: d.render,
699
+ renderer: { tool: d.renderer.tool, version: d.renderer.version, args: [...(d.renderer.args ?? [])] },
700
+ sourceHash: d.sourceHash ?? null,
701
+ member,
702
+ pointer: `${pointer}/${i}`,
703
+ }));
704
+ }
705
+
706
+ /** The `box` block at `pointer`, already validated, or null when there is none (#2726, #2727). */
707
+ function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
708
+ if (raw === undefined) return null;
709
+ const b = raw as {
710
+ capabilities?: { name: string; broker?: string; scope?: string[] }[];
711
+ host?: string;
712
+ slot?: number;
713
+ ports?: Record<string, number>;
714
+ state?: Record<string, string>;
715
+ cookies?: string[];
716
+ };
717
+ return {
718
+ capabilities: (b.capabilities ?? []).map((c, i) => ({ name: c.name, broker: c.broker ?? null, scope: [...(c.scope ?? [])], pointer: `${pointer}/capabilities/${i}` })),
719
+ // The schema requires host and slot together, and host for ports, state and cookies.
720
+ isolation:
721
+ b.host === undefined
722
+ ? null
723
+ : { host: b.host, slot: b.slot!, ports: { ...(b.ports ?? {}) }, state: { ...(b.state ?? {}) }, cookies: [...(b.cookies ?? [])] },
724
+ pointer,
725
+ };
726
+ }
727
+
728
+ /**
729
+ * The hosts, already validated, with the rules the schema can't say (#2727):
730
+ * host names are unique, a box's host is declared, its slot's block fits the
731
+ * host's range and its offsets fit the block. What two boxes resolve to is
732
+ * `chant workspace check`'s question (WSP123), not a read error.
733
+ */
734
+ function hostsOf(obj: Record<string, unknown>, members: Member[], at: (pointer: string, key?: boolean) => ErrorLocation): Host[] {
735
+ const hosts = ((obj.hosts as Record<string, unknown>[] | undefined) ?? []).map((h, i): Host => ({
736
+ name: h.name as string,
737
+ ports: { ...(h.ports as Host["ports"]) },
738
+ stateRoot: (h.stateRoot as string | undefined) ?? null,
739
+ pointer: `/hosts/${i}`,
740
+ }));
741
+ const invalid = (message: string, pointer: string) => new WorkspaceReadError("declaration-invalid", message, at(pointer));
742
+ const byName = new Map<string, Host>();
743
+ for (const h of hosts) {
744
+ const first = byName.get(h.name);
745
+ if (first) throw invalid(`the host name ${JSON.stringify(h.name)} is already used by the host at ${first.pointer}`, `${h.pointer}/name`);
746
+ byName.set(h.name, h);
747
+ if (h.ports.from > h.ports.to) throw invalid(`host ${h.name}'s port range starts at ${h.ports.from}, after its end ${h.ports.to}`, `${h.pointer}/ports`);
748
+ }
749
+ for (const m of members) {
750
+ const iso = m.box?.isolation;
751
+ if (!iso) continue;
752
+ const box = m.box!.pointer;
753
+ const host = byName.get(iso.host);
754
+ if (!host) {
755
+ const known = hosts.map((h) => h.name).join(", ") || "none are declared";
756
+ throw invalid(`member ${m.name}'s box names the host ${JSON.stringify(iso.host)}, which hosts does not declare; declared hosts: ${known}`, `${box}/host`);
757
+ }
758
+ const { from, to, perBox } = host.ports;
759
+ const last = from + (iso.slot + 1) * perBox - 1;
760
+ if (last > to) {
761
+ const slots = Math.floor((to - from + 1) / perBox);
762
+ throw invalid(
763
+ `member ${m.name}'s box has slot ${iso.slot} on host ${host.name}, which needs ports ${from + iso.slot * perBox} to ${last}, past the end of the host's range (${to}); the range holds ${slots} slot${slots === 1 ? "" : "s"}`,
764
+ `${box}/slot`,
765
+ );
766
+ }
767
+ for (const [port, offset] of Object.entries(iso.ports)) {
768
+ if (offset >= perBox) {
769
+ throw invalid(`member ${m.name}'s box gives port ${port} offset ${offset}, and host ${host.name} gives each box ${perBox} port${perBox === 1 ? "" : "s"} (offsets 0 to ${perBox - 1})`, `${box}/ports/${pointerToken(port)}`);
770
+ }
771
+ }
772
+ }
773
+ return hosts;
774
+ }
775
+
544
776
  /**
545
777
  * Every record kind the declaration names (#2680), in the order readers use
546
778
  * them: the workspace's own first, then each member's, members in file order
@@ -550,6 +782,15 @@ export function declaredRecordKinds(declaration: Declaration): RecordKindDeclara
550
782
  return [...declaration.records, ...declaration.members.flatMap((m) => m.records)];
551
783
  }
552
784
 
785
+ /**
786
+ * Every diagram artifact the declaration names (#2764), in the order readers
787
+ * use them: the workspace's own first, then each member's, members in file
788
+ * order and each list in its own order.
789
+ */
790
+ export function declaredDiagrams(declaration: Declaration): DiagramDeclaration[] {
791
+ return [...declaration.diagrams, ...declaration.members.flatMap((m) => m.diagrams)];
792
+ }
793
+
553
794
  /** Whether `path` is `dir` or sits under it. Both are tree-relative; `"."` is the root. */
554
795
  export function isInside(path: string, dir: string): boolean {
555
796
  if (dir === ".") return true;
@@ -111,20 +111,20 @@ describe("workspace ls lists the declared kinds (#2680)", () => {
111
111
  ]);
112
112
  const doc = result(await listWorkspaceWithKinds({ cwd: root }));
113
113
  expect(doc.contract).toBe(1);
114
- expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null }]);
114
+ expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null, acceptance: null }]);
115
115
  const design = doc.members.find((m) => m.name === "design")!;
116
116
  expect(design.readable).toBe(true);
117
117
  expect(design.records).toEqual([
118
- { name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null },
119
- { name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" } },
120
- { name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" } },
118
+ { name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null, acceptance: null },
119
+ { name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" }, acceptance: null },
120
+ { name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" }, acceptance: null },
121
121
  ]);
122
122
  expect(doc.members.find((m) => m.name === "app")!.records).toEqual([]);
123
123
  });
124
124
 
125
125
  test("listWorkspace lists them unloaded, and a workspace that names none lists empty arrays", async () => {
126
126
  const doc = result(listWorkspace({ cwd: workspace() }));
127
- expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null }]);
127
+ expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null, acceptance: null }]);
128
128
  const none = result(await listWorkspaceWithKinds({ cwd: repo({ "chant.workspace.json": decl(members()), "app/x": "", "design/x": "" }) }));
129
129
  expect(none.workspace.records).toEqual([]);
130
130
  expect(none.members.map((m) => m.records)).toEqual([[], []]);
@@ -13,7 +13,7 @@
13
13
  * kind file.
14
14
  */
15
15
 
16
- import { join } from "node:path";
16
+ import { dirname, join, relative, resolve, sep } from "node:path";
17
17
  import { declaredRecordKinds, type Declaration, type RecordKindDeclaration } from "./declaration";
18
18
  import type { ReasonCode } from "./reason-codes";
19
19
  import type { WorkspaceTree } from "./tree";
@@ -30,6 +30,31 @@ export interface DeclaredKind {
30
30
  kind: string | null;
31
31
  /** Why it can't be loaded, or null. */
32
32
  reason: { code: DeclaredKindReasonCode; message: string } | null;
33
+ /**
34
+ * For an answer kind (ws-058, #2738): its points file from the workspace
35
+ * root, and what is wrong with it, empty when nothing is. Absent for any
36
+ * other kind, and when the kind wasn't loaded.
37
+ */
38
+ points?: { file: string; problems: { field: string | null; message: string }[] };
39
+ /**
40
+ * For a work kind with acceptance criteria (#2772), read with
41
+ * `acceptance: true`: each current record that lists criteria, with how
42
+ * many are met. Absent for any other kind, when the kind wasn't loaded,
43
+ * and when its records can't be read (`records` reports why).
44
+ */
45
+ acceptance?: DeclaredAcceptance[];
46
+ }
47
+
48
+ /** One work record's acceptance criteria, counted, as `ls`, `status` and `check` read them (#2772). */
49
+ export interface DeclaredAcceptance {
50
+ item: string;
51
+ state: string | null;
52
+ met: number;
53
+ total: number;
54
+ /** The record file on disk, absolute. */
55
+ file: string;
56
+ /** The record's `work-acceptance-unmet` warning, when it is done with a criterion unmet; null otherwise. */
57
+ unmet: string | null;
33
58
  }
34
59
 
35
60
  /** The kind file of `declared` on disk, under the workspace root `rootOnDisk`. */
@@ -47,7 +72,7 @@ export async function loadDeclaredKinds(
47
72
  declaration: Declaration,
48
73
  tree: WorkspaceTree,
49
74
  rootOnDisk: string,
50
- options: { load?: boolean } = {},
75
+ options: { load?: boolean; acceptance?: boolean; at?: string } = {},
51
76
  ): Promise<DeclaredKind[]> {
52
77
  const declared = declaredRecordKinds(declaration);
53
78
  if (declared.length === 0) return [];
@@ -65,7 +90,13 @@ export async function loadDeclaredKinds(
65
90
  }
66
91
  try {
67
92
  const loaded = await records.loadRecordKind(file);
68
- out.push({ declared: d, file, kind: loaded.kind.name, reason: null });
93
+ const entry: DeclaredKind = { declared: d, file, kind: loaded.kind.name, reason: null };
94
+ if (loaded.kind.answers) entry.points = await checkPoints(resolve(dirname(file), loaded.kind.answers.points), rootOnDisk, tree);
95
+ if (loaded.kind.work?.acceptance && options.acceptance) {
96
+ const counted = await countAcceptance(file, rootOnDisk, options.at);
97
+ if (counted) entry.acceptance = counted;
98
+ }
99
+ out.push(entry);
69
100
  } catch (err) {
70
101
  if (!(err instanceof records.RecordReadError)) throw err;
71
102
  // loadRecordKind names the file as it was given, here an absolute path: name it from the workspace root instead.
@@ -74,3 +105,40 @@ export async function loadDeclaredKinds(
74
105
  }
75
106
  return out;
76
107
  }
108
+
109
+ /** The acceptance counts of a work kind's current records, or undefined when they can't be read. */
110
+ async function countAcceptance(file: string, rootOnDisk: string, at: string | undefined): Promise<DeclaredAcceptance[] | undefined> {
111
+ const { readRecordsFor } = await import("./records-cli");
112
+ const { RecordReadError } = await import("./records");
113
+ try {
114
+ const read = await readRecordsFor({ kind: file, cwd: rootOnDisk, current: true, ...(at !== undefined ? { at } : {}) });
115
+ return read.result.records
116
+ .filter((r): r is typeof r & { id: string; acceptance: NonNullable<typeof r.acceptance> } => r.id !== null && r.acceptance != null)
117
+ .map((r) => ({
118
+ item: r.id,
119
+ state: r.state,
120
+ met: r.acceptance.met,
121
+ total: r.acceptance.total,
122
+ file: join(read.root, ...r.path.split("/")),
123
+ unmet: r.warnings.find((w) => w.code === "work-acceptance-unmet")?.message ?? null,
124
+ }));
125
+ } catch (err) {
126
+ if (err instanceof RecordReadError) return undefined;
127
+ throw err;
128
+ }
129
+ }
130
+
131
+ /** An answer kind's points file (ws-058), read from `tree` and validated as `points ask` reads it. */
132
+ async function checkPoints(abs: string, rootOnDisk: string, tree: WorkspaceTree): Promise<NonNullable<DeclaredKind["points"]>> {
133
+ const file = relative(rootOnDisk, abs).split(sep).join("/");
134
+ if (file.startsWith("../")) return { file, problems: [{ field: null, message: "is outside the workspace" }] };
135
+ if (tree.stat(file) !== "file") return { file, problems: [{ field: null, message: `does not exist${tree.label}` }] };
136
+ const { parsePoints, PointsError } = await import("./points");
137
+ try {
138
+ parsePoints(tree.read(file), file);
139
+ return { file, problems: [] };
140
+ } catch (err) {
141
+ if (err instanceof PointsError) return { file, problems: err.problems };
142
+ throw err;
143
+ }
144
+ }
@@ -22,6 +22,7 @@
22
22
  import { relative, resolve, sep } from "node:path";
23
23
  import { findWorkspaceRoot } from "../project-root";
24
24
  import { isInside, readDeclaration, type Declaration } from "./declaration";
25
+ import { memberHolding } from "./record-assets";
25
26
  import { workingTree } from "./tree";
26
27
 
27
28
  /** What `chant update` rewrites, registered as implicit generated entries. */
@@ -79,6 +80,19 @@ export function declaredFilesUnder(declaration: Declaration, dir: string): Map<s
79
80
  return out;
80
81
  }
81
82
 
83
+ /**
84
+ * Whether a workspace path (from the workspace root) is a generated file:
85
+ * declared in its member's `generated` entries, or one core registers. The
86
+ * `generated` of `graph --intent`'s region and file nodes and of `check
87
+ * --changes`'s paths.
88
+ */
89
+ export function isGeneratedPath(declaration: Declaration, path: string): boolean {
90
+ const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
91
+ const dir = m?.dir ?? ".";
92
+ const rel = dir === "." ? path : path.slice(dir.length + 1);
93
+ return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
94
+ }
95
+
82
96
  /**
83
97
  * The declared generated files for the absolute directory `absDir`, from the
84
98
  * nearest `chant.workspace.json` above it. Empty when there is none, which is
@@ -220,7 +220,7 @@ export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
220
220
  try {
221
221
  const read = await readRecordsFor({ kind: query.kind, cwd: query.cwd, at: query.at });
222
222
  const name = read.loaded.kind.name;
223
- graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy }));
223
+ graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy, remediatedBy: r.remediatedBy }));
224
224
  graph.links.push(...recordLinkRows(name, read.result.records, read.loaded.kind.constrains?.field, located.tree, declaration.members));
225
225
  } catch (err) {
226
226
  if (!(err instanceof RecordReadError)) throw err;
@@ -752,7 +752,8 @@
752
752
  "path",
753
753
  "state",
754
754
  "valid",
755
- "supersededBy"
755
+ "supersededBy",
756
+ "remediatedBy"
756
757
  ],
757
758
  "properties": {
758
759
  "kind": {
@@ -784,6 +785,13 @@
784
785
  "string",
785
786
  "null"
786
787
  ]
788
+ },
789
+ "remediatedBy": {
790
+ "description": "Added in contract 1 by #2774. The ids of records whose remediates link names this one.",
791
+ "type": "array",
792
+ "items": {
793
+ "type": "string"
794
+ }
787
795
  }
788
796
  }
789
797
  }
@@ -600,7 +600,9 @@
600
600
  "record-schema-invalid",
601
601
  "record-id-duplicate",
602
602
  "record-supersedes-unknown",
603
- "record-supersedes-conflict"
603
+ "record-supersedes-conflict",
604
+ "record-remediates-unknown",
605
+ "record-remediates-not-closed"
604
606
  ]
605
607
  },
606
608
  "message": {
@@ -794,7 +796,9 @@
794
796
  "record-schema-invalid",
795
797
  "record-id-duplicate",
796
798
  "record-supersedes-unknown",
797
- "record-supersedes-conflict"
799
+ "record-supersedes-conflict",
800
+ "record-remediates-unknown",
801
+ "record-remediates-not-closed"
798
802
  ]
799
803
  },
800
804
  "message": {
@@ -940,7 +944,12 @@
940
944
  "work-implements-undecided",
941
945
  "work-done-unpinned",
942
946
  "work-closed-without-date",
943
- "work-done-gap-open"
947
+ "work-done-gap-open",
948
+ "work-acceptance-unmet",
949
+ "work-acceptance-self-verified",
950
+ "answer-points-unreadable",
951
+ "answer-point-unknown",
952
+ "answer-point-changed"
944
953
  ]
945
954
  },
946
955
  "message": {
@@ -38,7 +38,7 @@ import { realpathSync } from "node:fs";
38
38
  import { basename, dirname, relative, resolve, sep } from "node:path";
39
39
  import { declaredRecordKinds, readDeclaration, readerVersion, resolveGroups, WORKSPACE_ERROR_CODES, WorkspaceReadError, type Declaration } from "./declaration";
40
40
  import { declaredKindFile } from "./declared-kinds";
41
- import { classifyFile, declaredFilesUnder } from "./generated-files";
41
+ import { isGeneratedPath } from "./generated-files";
42
42
  import { entityDecisions, hasTrailer, readCommitJoins, runCommitJoins, type CommitJoins, type IntentCommit, type JoinedEntity, type PluginFinding } from "./intent-joins";
43
43
  import { loadKindRegistry } from "./kinds";
44
44
  import { resolveLinks, type LinkTableRow } from "./links";
@@ -541,13 +541,6 @@ function filesUnder(tree: WorkspaceTree, dir: string): string[] {
541
541
  return out;
542
542
  }
543
543
 
544
- function isGenerated(declaration: Declaration, path: string): boolean {
545
- const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
546
- const dir = m?.dir ?? ".";
547
- const rel = dir === "." ? path : path.slice(dir.length + 1);
548
- return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
549
- }
550
-
551
544
  function stringOr(v: unknown): string | null {
552
545
  return typeof v === "string" ? v : null;
553
546
  }
@@ -707,9 +700,9 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
707
700
  // The region, its files and its member.
708
701
  const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
709
702
  const rid = regionId(region.path, region.lines);
710
- add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGenerated(declaration, region.path) : null, node: region.node });
703
+ add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGeneratedPath(declaration, region.path) : null, node: region.node });
711
704
  const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
712
- for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGenerated(declaration, f) });
705
+ for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
713
706
  const memberNode = (name: string) => {
714
707
  const m = declaration.members.find((x) => x.name === name);
715
708
  return add<MemberNode>({ id: `member:${name}`, kind: "member", name, dir: m?.dir ?? "", memberKind: m?.kind ?? "" });
@@ -16,6 +16,9 @@
16
16
  *
17
17
  * `chant workspace upgrade` runs the same checks in its staging worktree
18
18
  * before it reaches its gate.
19
+ *
20
+ * `--changes <base>..<head>` runs the forward coverage check instead
21
+ * (`changes-cli.ts`, #2773), which prints a document of its own.
19
22
  */
20
23
 
21
24
  import { realpathSync } from "node:fs";
@@ -84,7 +87,8 @@ export function findingKey(f: CheckFinding): string {
84
87
  return `${f.code}\0${f.scope ?? ""}\0${f.path ?? ""}`;
85
88
  }
86
89
 
87
- const USAGE = "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>]";
90
+ const USAGE =
91
+ "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>]\n chant workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]";
88
92
  const FORMATS = ["stylish", "json", "sarif"] as const;
89
93
 
90
94
  /** A lock finding as a lint diagnostic, for `--format json` and `--format sarif`. */
@@ -223,6 +227,17 @@ export async function runWorkspaceCheck(ctx: CommandContext): Promise<number> {
223
227
  console.error(formatError({ message: `chant workspace check takes no argument (got ${ctx.args.extraPositional})`, hint: USAGE }));
224
228
  return 1;
225
229
  }
230
+ // The forward coverage check (#2773) is its own document.
231
+ if (ctx.args.changes !== undefined) {
232
+ if (ctx.args.at !== undefined) {
233
+ console.error(formatError({ message: "--changes reads the records at the range's head, so it takes no --at", hint: USAGE }));
234
+ return 1;
235
+ }
236
+ const { handToRootChant } = await import("./which-chant");
237
+ const handed = await handToRootChant(root, undefined);
238
+ if (handed !== undefined) return handed;
239
+ return (await import("./changes-cli")).runWorkspaceChanges(ctx, root);
240
+ }
226
241
  const format = (ctx.args.format || "stylish") as (typeof FORMATS)[number];
227
242
  if (!FORMATS.includes(format)) {
228
243
  console.error(formatError({ message: `--format ${format} is not a check format; use stylish, json or sarif`, hint: USAGE }));
@@ -71,7 +71,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
71
71
  }),
72
72
  );
73
73
  }
74
- if (!staged.changed) {
74
+ if (!staged.changed && staged.checksOk) {
75
75
  if (!opts.json) console.error(formatSuccess(`scope "${staged.scope}" is already at ${staged.to ?? "its source"}; nothing to upgrade`));
76
76
  return report("up-to-date", 0);
77
77
  }