@intentius/chant 0.91.0 → 0.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/lint.d.ts.map +1 -1
  3. package/dist/cli/handlers/graph.d.ts.map +1 -1
  4. package/dist/cli/handlers/operator.d.ts +6 -0
  5. package/dist/cli/handlers/operator.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts.map +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  9. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  10. package/dist/cli/plugins.d.ts.map +1 -1
  11. package/dist/cli/registry.d.ts +12 -2
  12. package/dist/cli/registry.d.ts.map +1 -1
  13. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  14. package/dist/config.d.ts +22 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/lexicon-module.d.ts +22 -0
  17. package/dist/lexicon-module.d.ts.map +1 -1
  18. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  19. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  20. package/dist/lifecycle/run-ledger.d.ts +2 -0
  21. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  22. package/dist/lifecycle/symptoms.d.ts +51 -0
  23. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  24. package/dist/lint/rules/index.d.ts +4 -2
  25. package/dist/lint/rules/index.d.ts.map +1 -1
  26. package/dist/lint/rules/op/index.d.ts +14 -3
  27. package/dist/lint/rules/op/index.d.ts.map +1 -1
  28. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  29. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  31. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  33. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  35. package/dist/op/activities/activity-contracts.d.ts +201 -0
  36. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  37. package/dist/op/activities/apply.d.ts.map +1 -1
  38. package/dist/op/activities/change-coverage.d.ts +44 -0
  39. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  40. package/dist/op/activities/converge.d.ts +24 -1
  41. package/dist/op/activities/converge.d.ts.map +1 -1
  42. package/dist/op/activities/decide.d.ts +107 -0
  43. package/dist/op/activities/decide.d.ts.map +1 -0
  44. package/dist/op/activities/index.d.ts +10 -0
  45. package/dist/op/activities/index.d.ts.map +1 -1
  46. package/dist/op/activities/shell.d.ts +32 -0
  47. package/dist/op/activities/shell.d.ts.map +1 -1
  48. package/dist/op/activities/source-release.d.ts +86 -0
  49. package/dist/op/activities/source-release.d.ts.map +1 -0
  50. package/dist/op/activities/source-rollback.d.ts +100 -0
  51. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  52. package/dist/op/activities/work-evidence.d.ts +42 -0
  53. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  54. package/dist/op/activity-contract.d.ts +1 -0
  55. package/dist/op/activity-contract.d.ts.map +1 -1
  56. package/dist/op/activity-profiles.d.ts +16 -0
  57. package/dist/op/activity-profiles.d.ts.map +1 -1
  58. package/dist/op/builders.d.ts +84 -2
  59. package/dist/op/builders.d.ts.map +1 -1
  60. package/dist/op/composites/converge-op.d.ts +31 -4
  61. package/dist/op/composites/converge-op.d.ts.map +1 -1
  62. package/dist/op/decide-backend.d.ts +116 -0
  63. package/dist/op/decide-backend.d.ts.map +1 -0
  64. package/dist/op/decide-config.d.ts +120 -0
  65. package/dist/op/decide-config.d.ts.map +1 -0
  66. package/dist/op/decide-read-inputs.d.ts +40 -0
  67. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  68. package/dist/op/gate-wait.d.ts +43 -0
  69. package/dist/op/gate-wait.d.ts.map +1 -0
  70. package/dist/op/gate.d.ts +5 -2
  71. package/dist/op/gate.d.ts.map +1 -1
  72. package/dist/op/index.d.ts +11 -4
  73. package/dist/op/index.d.ts.map +1 -1
  74. package/dist/op/local-executor.d.ts +29 -4
  75. package/dist/op/local-executor.d.ts.map +1 -1
  76. package/dist/op/local-output.d.ts +2 -0
  77. package/dist/op/local-output.d.ts.map +1 -1
  78. package/dist/op/op-ir.d.ts +2 -0
  79. package/dist/op/op-ir.d.ts.map +1 -1
  80. package/dist/op/op-verb-class.d.ts.map +1 -1
  81. package/dist/op/operator.d.ts +97 -1
  82. package/dist/op/operator.d.ts.map +1 -1
  83. package/dist/op/runtime.d.ts +37 -5
  84. package/dist/op/runtime.d.ts.map +1 -1
  85. package/dist/op/runtimes/local.d.ts.map +1 -1
  86. package/dist/op/source-archive.d.ts +63 -0
  87. package/dist/op/source-archive.d.ts.map +1 -0
  88. package/dist/op/steward-points.d.ts +136 -0
  89. package/dist/op/steward-points.d.ts.map +1 -0
  90. package/dist/op/steward-turn.d.ts +53 -0
  91. package/dist/op/steward-turn.d.ts.map +1 -0
  92. package/dist/op/steward.d.ts +22 -1
  93. package/dist/op/steward.d.ts.map +1 -1
  94. package/dist/op/types.d.ts +7 -0
  95. package/dist/op/types.d.ts.map +1 -1
  96. package/dist/op/work-lease-run.d.ts +1 -1
  97. package/dist/op/work-lease-run.d.ts.map +1 -1
  98. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  100. package/dist/workspace/answers-ledger.d.ts +76 -0
  101. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  102. package/dist/workspace/changes-cli.d.ts +16 -0
  103. package/dist/workspace/changes-cli.d.ts.map +1 -0
  104. package/dist/workspace/changes.d.ts +200 -0
  105. package/dist/workspace/changes.d.ts.map +1 -0
  106. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  107. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  108. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  118. package/dist/workspace/chant-migrations.d.ts +99 -0
  119. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  120. package/dist/workspace/checks/boxes.d.ts +34 -1
  121. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  122. package/dist/workspace/checks/diagrams.d.ts +31 -0
  123. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  124. package/dist/workspace/checks/records.d.ts +7 -0
  125. package/dist/workspace/checks/records.d.ts.map +1 -1
  126. package/dist/workspace/checks.d.ts +3 -0
  127. package/dist/workspace/checks.d.ts.map +1 -1
  128. package/dist/workspace/compose-graph.d.ts +2 -0
  129. package/dist/workspace/compose-graph.d.ts.map +1 -1
  130. package/dist/workspace/conformance/index.d.ts.map +1 -1
  131. package/dist/workspace/decide.d.ts +49 -4
  132. package/dist/workspace/decide.d.ts.map +1 -1
  133. package/dist/workspace/declaration.d.ts +52 -0
  134. package/dist/workspace/declaration.d.ts.map +1 -1
  135. package/dist/workspace/declaration.schema.json +114 -1
  136. package/dist/workspace/declared-kinds.d.ts +20 -0
  137. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  138. package/dist/workspace/generated-files.d.ts +7 -0
  139. package/dist/workspace/generated-files.d.ts.map +1 -1
  140. package/dist/workspace/intent.d.ts.map +1 -1
  141. package/dist/workspace/lineage-check.d.ts +3 -0
  142. package/dist/workspace/lineage-check.d.ts.map +1 -1
  143. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  144. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  145. package/dist/workspace/ls.d.ts +39 -1
  146. package/dist/workspace/ls.d.ts.map +1 -1
  147. package/dist/workspace/point-answer.schema.json +95 -0
  148. package/dist/workspace/points-cli.d.ts.map +1 -1
  149. package/dist/workspace/points.d.ts +1 -1
  150. package/dist/workspace/reason-codes.d.ts +14 -2
  151. package/dist/workspace/reason-codes.d.ts.map +1 -1
  152. package/dist/workspace/records-cli.d.ts +11 -1
  153. package/dist/workspace/records-cli.d.ts.map +1 -1
  154. package/dist/workspace/records-close.d.ts +1 -1
  155. package/dist/workspace/records-close.d.ts.map +1 -1
  156. package/dist/workspace/records-write.d.ts +11 -5
  157. package/dist/workspace/records-write.d.ts.map +1 -1
  158. package/dist/workspace/records.d.ts +34 -4
  159. package/dist/workspace/records.d.ts.map +1 -1
  160. package/dist/workspace/status-gates.d.ts +7 -2
  161. package/dist/workspace/status-gates.d.ts.map +1 -1
  162. package/dist/workspace/status-stewards.d.ts +59 -1
  163. package/dist/workspace/status-stewards.d.ts.map +1 -1
  164. package/dist/workspace/status.d.ts +16 -0
  165. package/dist/workspace/status.d.ts.map +1 -1
  166. package/dist/workspace/work-cli.d.ts +113 -2
  167. package/dist/workspace/work-cli.d.ts.map +1 -1
  168. package/dist/workspace/work-evidence.d.ts +74 -0
  169. package/dist/workspace/work-evidence.d.ts.map +1 -0
  170. package/dist/workspace/work.d.ts +43 -1
  171. package/dist/workspace/work.d.ts.map +1 -1
  172. package/package.json +1 -1
  173. package/src/cli/commands/audit.test.ts +9 -1
  174. package/src/cli/commands/build.ts +3 -2
  175. package/src/cli/commands/lint.ts +7 -1
  176. package/src/cli/handlers/graph.test.ts +20 -0
  177. package/src/cli/handlers/graph.ts +16 -2
  178. package/src/cli/handlers/operator.ts +34 -2
  179. package/src/cli/handlers/run.test.ts +154 -2
  180. package/src/cli/handlers/run.ts +121 -2
  181. package/src/cli/main.test.ts +10 -0
  182. package/src/cli/main.ts +19 -0
  183. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  184. package/src/cli/mcp/workspace-tools.ts +62 -4
  185. package/src/cli/plugins.ts +11 -3
  186. package/src/cli/registry.ts +12 -2
  187. package/src/codegen/pinned-upgrade.ts +2 -1
  188. package/src/codegen/release-wiring.test.ts +49 -2
  189. package/src/components/capability-plugin-loader.ts +3 -3
  190. package/src/components/cli-support.ts +2 -2
  191. package/src/config.ts +11 -0
  192. package/src/lexicon-module.ts +56 -1
  193. package/src/lexicon-package.test.ts +96 -0
  194. package/src/lifecycle/converge-ledger.ts +20 -0
  195. package/src/lifecycle/run-ledger.ts +6 -0
  196. package/src/lifecycle/symptoms.ts +95 -0
  197. package/src/lint/rules/index.ts +6 -2
  198. package/src/lint/rules/op/index.ts +17 -4
  199. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  200. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  201. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  202. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  203. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  204. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  205. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  206. package/src/op/activities/activity-contracts.ts +190 -1
  207. package/src/op/activities/apply.ts +7 -6
  208. package/src/op/activities/change-coverage.ts +60 -0
  209. package/src/op/activities/converge.test.ts +55 -1
  210. package/src/op/activities/converge.ts +208 -29
  211. package/src/op/activities/decide.live.test.ts +78 -0
  212. package/src/op/activities/decide.test.ts +300 -0
  213. package/src/op/activities/decide.ts +267 -0
  214. package/src/op/activities/index.ts +21 -0
  215. package/src/op/activities/shell.test.ts +91 -1
  216. package/src/op/activities/shell.ts +91 -2
  217. package/src/op/activities/source-release.test.ts +118 -0
  218. package/src/op/activities/source-release.ts +168 -0
  219. package/src/op/activities/source-rollback.test.ts +131 -0
  220. package/src/op/activities/source-rollback.ts +239 -0
  221. package/src/op/activities/work-evidence.ts +62 -0
  222. package/src/op/activity-contract-registry.test.ts +2 -0
  223. package/src/op/activity-contract-registry.ts +2 -2
  224. package/src/op/activity-contract.test.ts +12 -0
  225. package/src/op/activity-contract.ts +6 -1
  226. package/src/op/activity-profiles.ts +29 -0
  227. package/src/op/activity-registry.test.ts +1 -1
  228. package/src/op/activity-registry.ts +2 -2
  229. package/src/op/builders.ts +119 -5
  230. package/src/op/command-gate.test.ts +192 -0
  231. package/src/op/composites/converge-op.test.ts +27 -1
  232. package/src/op/composites/converge-op.ts +69 -15
  233. package/src/op/decide-backend.test.ts +44 -0
  234. package/src/op/decide-backend.ts +212 -0
  235. package/src/op/decide-config.test.ts +52 -0
  236. package/src/op/decide-config.ts +80 -0
  237. package/src/op/decide-read-inputs.ts +117 -0
  238. package/src/op/gate-approval.ts +2 -2
  239. package/src/op/gate-wait.ts +57 -0
  240. package/src/op/gate.ts +8 -3
  241. package/src/op/generate-pipeline.ts +2 -2
  242. package/src/op/index.ts +18 -3
  243. package/src/op/local-executor.ts +151 -15
  244. package/src/op/local-output.test.ts +33 -0
  245. package/src/op/local-output.ts +20 -2
  246. package/src/op/op-ir.test.ts +10 -0
  247. package/src/op/op-ir.ts +4 -0
  248. package/src/op/op-verb-class.ts +1 -0
  249. package/src/op/operator.ts +290 -11
  250. package/src/op/runtime.ts +31 -8
  251. package/src/op/runtimes/local.ts +3 -1
  252. package/src/op/source-archive.ts +161 -0
  253. package/src/op/steward-points.test.ts +313 -0
  254. package/src/op/steward-points.ts +194 -0
  255. package/src/op/steward-turn.ts +79 -0
  256. package/src/op/steward.test.ts +87 -3
  257. package/src/op/steward.ts +25 -1
  258. package/src/op/types.ts +7 -0
  259. package/src/op/work-lease-run.test.ts +20 -1
  260. package/src/op/work-lease-run.ts +2 -2
  261. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  262. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  263. package/src/workspace/answers-ledger.ts +118 -0
  264. package/src/workspace/changes-cli.ts +56 -0
  265. package/src/workspace/changes.schema.json +472 -0
  266. package/src/workspace/changes.test.ts +315 -0
  267. package/src/workspace/changes.ts +512 -0
  268. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  269. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  270. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  271. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  272. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  273. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  274. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  275. package/src/workspace/chant-migrations.ts +164 -0
  276. package/src/workspace/check-contract.test.ts +4 -2
  277. package/src/workspace/check.schema.json +15 -5
  278. package/src/workspace/checks/boxes.test.ts +80 -1
  279. package/src/workspace/checks/boxes.ts +136 -1
  280. package/src/workspace/checks/diagrams.test.ts +134 -0
  281. package/src/workspace/checks/diagrams.ts +108 -0
  282. package/src/workspace/checks/records.ts +47 -0
  283. package/src/workspace/checks.ts +8 -1
  284. package/src/workspace/compose-graph.ts +2 -0
  285. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  286. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  287. package/src/workspace/conformance/index.ts +5 -0
  288. package/src/workspace/decide.test.ts +1 -1
  289. package/src/workspace/decide.ts +156 -23
  290. package/src/workspace/declaration.schema.json +114 -1
  291. package/src/workspace/declaration.test.ts +58 -0
  292. package/src/workspace/declaration.ts +104 -0
  293. package/src/workspace/declared-kinds.test.ts +5 -5
  294. package/src/workspace/declared-kinds.ts +46 -1
  295. package/src/workspace/generated-files.ts +14 -0
  296. package/src/workspace/graph-cli.ts +1 -1
  297. package/src/workspace/graph.schema.json +9 -1
  298. package/src/workspace/intent.schema.json +8 -2
  299. package/src/workspace/intent.ts +3 -10
  300. package/src/workspace/lineage-check.ts +16 -1
  301. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  302. package/src/workspace/lineage-upgrade.test.ts +70 -0
  303. package/src/workspace/lineage-upgrade.ts +81 -10
  304. package/src/workspace/ls-contract.test.ts +57 -0
  305. package/src/workspace/ls.schema.json +46 -1
  306. package/src/workspace/ls.ts +62 -3
  307. package/src/workspace/points-cli.ts +14 -3
  308. package/src/workspace/points-write.schema.json +11 -1
  309. package/src/workspace/points.schema.json +33 -0
  310. package/src/workspace/points.test.ts +1 -1
  311. package/src/workspace/points.ts +1 -1
  312. package/src/workspace/read-contract.test.ts +1 -1
  313. package/src/workspace/reason-codes.test.ts +11 -1
  314. package/src/workspace/reason-codes.ts +18 -2
  315. package/src/workspace/records-amend.schema.json +2 -0
  316. package/src/workspace/records-cli.ts +13 -3
  317. package/src/workspace/records-close.schema.json +2 -0
  318. package/src/workspace/records-new.schema.json +2 -0
  319. package/src/workspace/records-review.schema.json +2 -0
  320. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  321. package/src/workspace/records-sessions-write.test.ts +2 -34
  322. package/src/workspace/records-write.e2e.test.ts +108 -0
  323. package/src/workspace/records-write.test.ts +32 -41
  324. package/src/workspace/records-write.ts +33 -15
  325. package/src/workspace/records.schema.json +36 -2
  326. package/src/workspace/records.test.ts +48 -3
  327. package/src/workspace/records.ts +109 -3
  328. package/src/workspace/status-contract.test.ts +13 -8
  329. package/src/workspace/status-gates.ts +9 -4
  330. package/src/workspace/status-stewards.ts +108 -4
  331. package/src/workspace/status.schema.json +111 -7
  332. package/src/workspace/status.ts +36 -1
  333. package/src/workspace/work-acceptance.test.ts +274 -0
  334. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  335. package/src/workspace/work-cli.ts +193 -10
  336. package/src/workspace/work-evidence.schema.json +212 -0
  337. package/src/workspace/work-evidence.ts +169 -0
  338. package/src/workspace/work-history.schema.json +349 -0
  339. package/src/workspace/work-history.test.ts +156 -0
  340. package/src/workspace/work.test.ts +3 -2
  341. package/src/workspace/work.ts +100 -1
  342. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  343. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  344. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -63,6 +63,13 @@
63
63
  "$ref": "#/$defs/host"
64
64
  }
65
65
  },
66
+ "diagrams": {
67
+ "type": "array",
68
+ "description": "The workspace's own diagram artifacts (#2764): the diagrams that belong to no one member, such as a diagrams directory at the root. A member names its own in its diagrams. chant workspace ls lists every declared diagram, and chant workspace check reports one whose render is stale for its source when the entry records a sourceHash. Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
69
+ "items": {
70
+ "$ref": "#/$defs/diagram"
71
+ }
72
+ },
66
73
  "checks": {
67
74
  "type": "object",
68
75
  "description": "Severity for the declaration checks that allow it, keyed by WSP id (#2535, ws-028): error, warning, info or off. chant workspace check refuses an id it does not know, and one whose severity is fixed.",
@@ -72,6 +79,28 @@
72
79
  "additionalProperties": {
73
80
  "$ref": "#/$defs/severity"
74
81
  }
82
+ },
83
+ "changes": {
84
+ "type": "object",
85
+ "additionalProperties": false,
86
+ "description": "The forward coverage check, chant workspace check --changes <base>..<head> (#2773): each path a diff changes is mapped to the current records whose constrains cover it. severity says what a finding does: off reports none, warn (the default) reports them and passes, fail reports them and fails. ignore lists globs over files whose changes need no record, such as lockfiles and generated output. Added in schema 1 by chant 0.92.0, so a declaration that uses it sets minReader to 0.92.0 or newer.",
87
+ "properties": {
88
+ "severity": {
89
+ "enum": [
90
+ "off",
91
+ "warn",
92
+ "fail"
93
+ ],
94
+ "description": "What a change-uncovered or change-out-of-scope finding does. warn when absent."
95
+ },
96
+ "ignore": {
97
+ "type": "array",
98
+ "description": "Globs over file paths from the workspace root: * and ? within one segment, ** across segments. A changed path one matches is listed as ignored and never reported.",
99
+ "items": {
100
+ "$ref": "#/$defs/fileGlob"
101
+ }
102
+ }
103
+ }
75
104
  }
76
105
  },
77
106
  "patternProperties": {
@@ -276,6 +305,13 @@
276
305
  "$ref": "#/$defs/recordKind"
277
306
  }
278
307
  },
308
+ "diagrams": {
309
+ "type": "array",
310
+ "description": "The diagram artifacts this member holds (#2764). chant workspace ls lists every declared diagram, and chant workspace check reports one whose render is stale for its source when the entry records a sourceHash. Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
311
+ "items": {
312
+ "$ref": "#/$defs/diagram"
313
+ }
314
+ },
279
315
  "box": {
280
316
  "$ref": "#/$defs/box"
281
317
  },
@@ -366,7 +402,7 @@
366
402
  },
367
403
  "box": {
368
404
  "type": "object",
369
- "description": "The member is a box, or holds a box's declarations (#2726). A box holds no credential: each capability it needs outside itself is reached through a broker, a runtime such as a lobby or a door, which holds the credential and enforces the scope. chant workspace check fails when a file in the member's directory carries a literal secret (WSP121) and when a capability names no broker (WSP122). chant workspace status --json lists the capabilities, so a broker can read the scopes. Its host, slot, ports, state and cookies state the box's isolation (#2727): chant derives each value from the box's identity, its host, the member's name and its slot, and prints them in chant workspace status --json, so a runtime reads them instead of choosing its own. chant workspace check fails when two boxes on a host resolve to the same value (WSP123) and when a box hard-codes a machine path (WSP124). Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
405
+ "description": "The member is a box, or holds a box's declarations (#2726). A box holds no credential: each capability it needs outside itself is reached through a broker, a runtime such as a lobby or a door, which holds the credential and enforces the scope. chant workspace check fails when a file in the member's directory carries a literal secret (WSP121) and when a capability names no broker (WSP122). chant workspace status --json lists the capabilities, so a broker can read the scopes. Its host, slot, ports, state and cookies state the box's isolation (#2727): chant derives each value from the box's identity, its host, the member's name and its slot, and prints them in chant workspace status --json, so a runtime reads them instead of choosing its own. chant workspace check fails when two boxes on a host resolve to the same value (WSP123) and when a box hard-codes a machine path (WSP124). A member that builds the fountain lexicon's Box declares the callback token fountain gives its persistent sandbox as the capability fountain-callback, brokered by fountain, with scope owner, or chant workspace check fails (WSP125, #2780). Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
370
406
  "properties": {
371
407
  "capabilities": {
372
408
  "type": "array",
@@ -493,6 +529,78 @@
493
529
  },
494
530
  "additionalProperties": false
495
531
  },
532
+ "diagram": {
533
+ "type": "object",
534
+ "description": "A diagram artifact the workspace declares (#2764): a rendered image with a source, pinned to the renderer that made it. chant never runs the renderer.",
535
+ "required": [
536
+ "name",
537
+ "title",
538
+ "render",
539
+ "renderer"
540
+ ],
541
+ "properties": {
542
+ "name": {
543
+ "$ref": "#/$defs/name",
544
+ "description": "Unique across the declaration."
545
+ },
546
+ "title": {
547
+ "type": "string",
548
+ "minLength": 1,
549
+ "description": "Shown above the diagram."
550
+ },
551
+ "source": {
552
+ "oneOf": [
553
+ { "$ref": "#/$defs/path" },
554
+ { "type": "null" }
555
+ ],
556
+ "description": "The source file, from the workspace root, with / separators. Null for an SVG with no source. Default null."
557
+ },
558
+ "render": {
559
+ "$ref": "#/$defs/path",
560
+ "description": "The rendered image, from the workspace root, with / separators."
561
+ },
562
+ "renderer": {
563
+ "$ref": "#/$defs/diagramRenderer"
564
+ },
565
+ "sourceHash": {
566
+ "type": ["string", "null"],
567
+ "pattern": "^[0-9a-f]{64}$",
568
+ "description": "sha256 hex of the source's bytes when render was last produced from it. chant workspace check compares this against the source in the tree read and reports drift (WSP133) on a mismatch; without it, or without a source, the render is never checked for drift. Default null."
569
+ }
570
+ },
571
+ "patternProperties": {
572
+ "^x-": true
573
+ },
574
+ "additionalProperties": false
575
+ },
576
+ "diagramRenderer": {
577
+ "type": "object",
578
+ "description": "The renderer a diagram's render was made with. chant records the pin; it never installs or runs the renderer (ws-052).",
579
+ "required": [
580
+ "tool",
581
+ "version"
582
+ ],
583
+ "properties": {
584
+ "tool": {
585
+ "enum": ["d2", "mermaid", "graphviz"]
586
+ },
587
+ "version": {
588
+ "$ref": "#/$defs/version",
589
+ "description": "The exact release the render was made with, such as 0.9.0."
590
+ },
591
+ "args": {
592
+ "type": "array",
593
+ "description": "Passed before the input and output paths, in order, such as [\"--layout=elk\", \"--theme=0\", \"--pad=40\", \"--omit-version\"]. Empty when absent.",
594
+ "items": {
595
+ "type": "string"
596
+ }
597
+ }
598
+ },
599
+ "patternProperties": {
600
+ "^x-": true
601
+ },
602
+ "additionalProperties": false
603
+ },
496
604
  "generatedFile": {
497
605
  "type": "object",
498
606
  "description": "One generated file of a member (#2524 D14, #2541).",
@@ -641,6 +749,11 @@
641
749
  "type": "integer",
642
750
  "minimum": 1,
643
751
  "maximum": 65535
752
+ },
753
+ "fileGlob": {
754
+ "type": "string",
755
+ "description": "A glob over file paths, relative to the workspace root: * and ? within one segment, ** across segments, [...] classes and {a,b} alternatives. No leading /, no . or .. segments.",
756
+ "pattern": "^(?!\\.\\.?(/|$))[^/\\\\]+(/(?!\\.\\.?(/|$))[^/\\\\]+)*$"
644
757
  }
645
758
  }
646
759
  }
@@ -6,6 +6,7 @@ import { afterAll, describe, expect, test } from "vitest";
6
6
  import {
7
7
  compareVersions,
8
8
  DECLARATION_SCHEMA_ID,
9
+ declaredDiagrams,
9
10
  ownerOf,
10
11
  parseDeclaration,
11
12
  readDeclaration,
@@ -345,3 +346,60 @@ describe("generated files (#2541)", () => {
345
346
  expect(failure(base([{ name: "g", kind: "examples", glob: "examples/*", generated: [] }])).message).toContain('unknown field "generated"');
346
347
  });
347
348
  });
349
+
350
+ describe("diagram artifacts (#2764)", () => {
351
+ const renderer = { tool: "d2", version: "0.9.0", args: ["--layout=elk", "--theme=0", "--pad=40", "--omit-version"] };
352
+ const diagram = (extra: Record<string, unknown> = {}) => ({
353
+ name: "architecture",
354
+ title: "Studio architecture",
355
+ source: "docs/diagrams/architecture.d2",
356
+ render: "docs/diagrams/architecture.svg",
357
+ renderer,
358
+ ...extra,
359
+ });
360
+ const member = (diagrams: unknown[], extra: Record<string, unknown> = {}) => ({ name: "docs", dir: "docs", kind: "other", because: "the docs site", diagrams, ...extra });
361
+
362
+ test("a member's diagram is read with the workspace-relative source and render, and a null source is the default", () => {
363
+ const d = parse(base([member([diagram()])]));
364
+ expect(d.members[0].diagrams).toEqual([
365
+ { name: "architecture", title: "Studio architecture", source: "docs/diagrams/architecture.d2", render: "docs/diagrams/architecture.svg", renderer, sourceHash: null, member: "docs", pointer: "/members/0/diagrams/0" },
366
+ ]);
367
+ const noSource = parse(base([member([diagram({ source: undefined })])]));
368
+ expect(noSource.members[0].diagrams[0].source).toBeNull();
369
+ });
370
+
371
+ test("the workspace's own diagrams are in the top-level diagrams, with member null", () => {
372
+ const d = parse(base([{ name: "x", dir: "x", kind: "chant" }], { diagrams: [diagram({ name: "boundary" })] }));
373
+ expect(d.diagrams).toEqual([{ ...diagram({ name: "boundary" }), sourceHash: null, member: null, pointer: "/diagrams/0" }]);
374
+ expect(declaredDiagrams(d)).toEqual(d.diagrams);
375
+ });
376
+
377
+ test("declaredDiagrams lists the workspace's own first, then each member's, in file order", () => {
378
+ const d = parse(base([member([diagram()]), { name: "app", dir: "app", kind: "chant" }], { diagrams: [diagram({ name: "boundary", render: "docs/diagrams/boundary.svg" })] }));
379
+ expect(declaredDiagrams(d).map((x) => x.name)).toEqual(["boundary", "architecture"]);
380
+ });
381
+
382
+ test("a diagram name is unique across the declaration, workspace and members together", () => {
383
+ const clash = failure(base([member([diagram()])], { diagrams: [diagram()] }));
384
+ expect(clash.code).toBe("declaration-invalid");
385
+ expect(clash.message).toContain('the diagram name "architecture" is already used by the diagram at /diagrams/0');
386
+ });
387
+
388
+ test("renderer.tool is a closed list, and title, render and renderer are required", () => {
389
+ expect(failure(base([member([diagram({ renderer: { tool: "mscgen", version: "1.0.0" } })])])).code).toBe("declaration-invalid");
390
+ expect(failure(base([member([{ name: "a", source: null, render: "a.svg", renderer }])])).message).toContain('missing required field "title"');
391
+ expect(failure(base([member([{ name: "a", title: "A", source: null, renderer }])])).message).toContain('missing required field "render"');
392
+ expect(failure(base([member([{ name: "a", title: "A", source: null, render: "a.svg" }])])).message).toContain('missing required field "renderer"');
393
+ });
394
+
395
+ test("a sourceHash is a 64-character lowercase hex string, or absent", () => {
396
+ const hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
397
+ expect(parse(base([member([diagram({ sourceHash: hash })])])).members[0].diagrams[0].sourceHash).toBe(hash);
398
+ expect(failure(base([member([diagram({ sourceHash: "not-a-hash" })])])).code).toBe("declaration-invalid");
399
+ });
400
+
401
+ test("source and render stay inside the workspace, like every other path", () => {
402
+ expect(failure(base([member([diagram({ source: "/etc/passwd" })])])).code).toBe("declaration-invalid");
403
+ expect(failure(base([member([diagram({ render: "../out.svg" })])])).code).toBe("declaration-invalid");
404
+ });
405
+ });
@@ -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`.
@@ -208,6 +253,8 @@ export interface Member {
208
253
  links: LinkDeclaration[];
209
254
  /** The record kinds this member declares, in file order (#2680). */
210
255
  records: RecordKindDeclaration[];
256
+ /** The diagram artifacts this member declares, in file order (#2764). */
257
+ diagrams: DiagramDeclaration[];
211
258
  /** The member's box block, or null when it declares none (#2726). */
212
259
  box: BoxDeclaration | null;
213
260
  upstream: string | null;
@@ -252,8 +299,12 @@ export interface Declaration {
252
299
  quorum: number | null;
253
300
  /** The workspace's own record kinds, from the top-level `records`, in file order (#2680). A member's are on the member. */
254
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[];
255
304
  /** The hosts boxes run on, in file order (#2727). */
256
305
  hosts: Host[];
306
+ /** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
307
+ changes: ChangesPolicy | null;
257
308
  /** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
258
309
  file: string;
259
310
  }
@@ -466,6 +517,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
466
517
  pointer: `${pointer}/links/${j}`,
467
518
  }));
468
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`);
469
521
  const box = boxOf(e.box, `${pointer}/box`);
470
522
  return {
471
523
  type: "member",
@@ -477,6 +529,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
477
529
  outputs: e.outputs === undefined ? null : [...(e.outputs as string[])],
478
530
  links,
479
531
  records,
532
+ diagrams,
480
533
  box,
481
534
  upstream: (e.upstream as string | undefined) ?? null,
482
535
  because: (e.because as string | undefined) ?? null,
@@ -572,6 +625,17 @@ export function parseDeclaration(text: string, file: string, reader: string = re
572
625
  }
573
626
  }
574
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
+
575
639
  const hosts = hostsOf(obj, members, at);
576
640
 
577
641
  const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
@@ -592,11 +656,19 @@ export function parseDeclaration(text: string, file: string, reader: string = re
592
656
  checks: { ...((obj.checks as Record<string, CheckSeverity> | undefined) ?? {}) },
593
657
  quorum: typeof obj.quorum === "number" ? obj.quorum : null,
594
658
  records: ownRecords,
659
+ diagrams: ownDiagrams,
595
660
  hosts,
661
+ changes: changesOf(obj.changes as Record<string, unknown> | undefined),
596
662
  file,
597
663
  };
598
664
  }
599
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
+
600
672
  /** The `records` list at `pointer`, already validated, with each kind file's path from the workspace root. */
601
673
  function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer: string): RecordKindDeclaration[] {
602
674
  return ((raw as { kind: string; name?: string }[] | undefined) ?? []).map((r, i) => ({
@@ -608,6 +680,29 @@ function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer
608
680
  }));
609
681
  }
610
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
+
611
706
  /** The `box` block at `pointer`, already validated, or null when there is none (#2726, #2727). */
612
707
  function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
613
708
  if (raw === undefined) return null;
@@ -687,6 +782,15 @@ export function declaredRecordKinds(declaration: Declaration): RecordKindDeclara
687
782
  return [...declaration.records, ...declaration.members.flatMap((m) => m.records)];
688
783
  }
689
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
+
690
794
  /** Whether `path` is `dir` or sits under it. Both are tree-relative; `"."` is the root. */
691
795
  export function isInside(path: string, dir: string): boolean {
692
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([[], []]);
@@ -36,6 +36,25 @@ export interface DeclaredKind {
36
36
  * other kind, and when the kind wasn't loaded.
37
37
  */
38
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;
39
58
  }
40
59
 
41
60
  /** The kind file of `declared` on disk, under the workspace root `rootOnDisk`. */
@@ -53,7 +72,7 @@ export async function loadDeclaredKinds(
53
72
  declaration: Declaration,
54
73
  tree: WorkspaceTree,
55
74
  rootOnDisk: string,
56
- options: { load?: boolean } = {},
75
+ options: { load?: boolean; acceptance?: boolean; at?: string } = {},
57
76
  ): Promise<DeclaredKind[]> {
58
77
  const declared = declaredRecordKinds(declaration);
59
78
  if (declared.length === 0) return [];
@@ -73,6 +92,10 @@ export async function loadDeclaredKinds(
73
92
  const loaded = await records.loadRecordKind(file);
74
93
  const entry: DeclaredKind = { declared: d, file, kind: loaded.kind.name, reason: null };
75
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
+ }
76
99
  out.push(entry);
77
100
  } catch (err) {
78
101
  if (!(err instanceof records.RecordReadError)) throw err;
@@ -83,6 +106,28 @@ export async function loadDeclaredKinds(
83
106
  return out;
84
107
  }
85
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
+
86
131
  /** An answer kind's points file (ws-058), read from `tree` and validated as `points ask` reads it. */
87
132
  async function checkPoints(abs: string, rootOnDisk: string, tree: WorkspaceTree): Promise<NonNullable<DeclaredKind["points"]>> {
88
133
  const file = relative(rootOnDisk, abs).split(sep).join("/");
@@ -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": {
@@ -941,6 +945,8 @@
941
945
  "work-done-unpinned",
942
946
  "work-closed-without-date",
943
947
  "work-done-gap-open",
948
+ "work-acceptance-unmet",
949
+ "work-acceptance-self-verified",
944
950
  "answer-points-unreadable",
945
951
  "answer-point-unknown",
946
952
  "answer-point-changed"
@@ -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 ?? "" });