@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
@@ -0,0 +1,67 @@
1
+ {
2
+ "$comment": "The studio kit's app template, arugula-salad/studio 84c8b21 template/, which is chud's template at jhgaylor/chud 43afcf1 with the chud CLI steps taken out of its prose and delivery/package.json. The fixture for chant workspace upgrade's chud-lexicon-exit migration (INTENTIUS/chant#2737). Data only: nothing here is imported or run by chant's tests, and delivery/ names @intentius/chud-runtime, which chant never installs.",
3
+ "source": "https://github.com/arugula-salad/studio/tree/84c8b21/template",
4
+ "files": {
5
+ ".github/workflows/ci.yml": "name: ci\n\non:\n push:\n branches:\n - main\n pull_request: {}\n\npermissions:\n contents: read\n\nconcurrency:\n group: ci-${{ github.ref }}\n cancel-in-progress: '${{ github.event_name == ''pull_request'' }}'\n\njobs:\n ci:\n runs-on: ubuntu-latest\n timeout-minutes: 20\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n - name: Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: '22.x'\n cache: npm\n cache-dependency-path: delivery/package-lock.json\n - name: Read access to @intentius/chud-runtime\n env:\n CHUD_RUNTIME_TOKEN: '${{ secrets.CHUD_RUNTIME_TOKEN }}'\n CHUD_RUNTIME_DEPLOY_KEY: '${{ secrets.CHUD_RUNTIME_DEPLOY_KEY }}'\n run: |\n if [ -n \"$CHUD_RUNTIME_TOKEN\" ]; then\n git config --global url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf ssh://git@github.com/\n git config --global --add url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf git@github.com:\n elif [ -n \"$CHUD_RUNTIME_DEPLOY_KEY\" ]; then\n mkdir -p ~/.ssh && chmod 700 ~/.ssh\n printf '%s\\n' \"$CHUD_RUNTIME_DEPLOY_KEY\" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519\n echo \"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\" >> ~/.ssh/known_hosts\n else\n echo \"neither CHUD_RUNTIME_TOKEN nor CHUD_RUNTIME_DEPLOY_KEY is set: a private @intentius/chud-runtime will not install\"\n fi\n - name: Install\n run: npm ci\n working-directory: delivery\n - name: Workflow YAML is built from ci/ci.ts\n run: npm run ci:check\n working-directory: delivery\n - name: chant workspace check\n run: npx chant workspace check\n working-directory: delivery\n - name: chant build\n run: npx chant build -o \"$RUNNER_TEMP/chant-build.out\"\n working-directory: delivery\n - name: chant lint\n run: npx chant lint\n working-directory: delivery\n - name: App tests and approved contract checks\n run: npm run check\n working-directory: delivery\n",
6
+ ".gitignore": "node_modules/\n.chud/\n.data/\n.hud/\n# chant build output (the Ops as op.json)\ndist/\n",
7
+ "CLAUDE.md": "# {{chant:name}}: notes for agents\n\nThis repo is a chant workspace made from the studio kit's template\n(`chant init --from <studio>/template`, then the kit's `box/setup-app.mjs`).\nIt holds everything about {{chant:name}}: the app, the contracts it must\nmeet, the decisions made about it, the agents that build it, and the Ops that\nship it. `chant.workspace.json` declares its members, and chant finds\neverything through them.\n\nYou are probably running as hud's agent in the app's page: the app's own\nserver (its `dev` script, `node --watch`) behind hud's proxy. Your working\ndirectory is this one (the workspace root), and your job is to build the app\nin `app/`. See `app/CLAUDE.md`. Decisions and work items are chant records:\nwrite them with `chant workspace records`, and hud shows them.\n\n## Where things are\n\n| Member | Directory | What it holds |\n|---|---|---|\n| app | `app/` | The product. Edited live while its `dev` script runs. |\n| delivery | `delivery/` | The chant project: `chant.config.ts` (this repo's settings as `buildParams`: ports and how to run the app), `ops/` (the release, rollback, upgrade and dispatch Ops), `deploy/` (the app as a chant component and the sites it ships to), `agents/team.ts` (the agents that build the app, as fountain specs), `decisions/` (the decision points and the ship gate's Cedar policy), `ci/ci.ts` (this repo's CI), `.chant/` (the write-scope policy and the contract-sizing lint rules), and `node_modules/`. |\n| design | `design/` | chud's records, written by the design app: `contracts/` (and `contracts/checks/`), `drivers/`, `evidence/`, `sessions/`, `context/`, `proposals/`. Also the design app's overrides and the screen specs in `screens/`. |\n| design-client | `design-client/` | A placeholder for a vendored design client. Nothing here yet. |\n\n`work/` at the root holds the units of work: chant work items\n(`W-NNN-<slug>.md`, `work.kind.mjs` and `work.schema.json` beside them), one\nper agent run or piece of manual work, with chud's fields under `x-chud`.\nchud writes them through `chant workspace records new|amend`; list them with\n`chant workspace records --kind work/work.kind.mjs`.\n\n`decisions/` at the root holds the decision records: chant decision records\n(`decision.kind.mjs` and `decision.schema.json` beside them define the\nformat), the template's `ref-001`, `ref-002` and `chud-001`, and the\n`chud-NNN` records the design app writes. Each cites the issue in\n`chud.decisions.issue` (`delivery/chant.config.ts`) as its source, or\n`chud-001`'s when none is set. List the\ncurrent ones with\n`chant workspace records --kind decisions/decision.kind.mjs --current`.\n\nA decision pins the files it rests on by path and hash: an `evidence` entry\n`{ title, path, sha256, as_of }`, `path` from the workspace root and inside a\nmember (such as `design/screens/home.json`), and `sha256` the hash of the\nfile's bytes, which `chant workspace records pin <path>` prints. What it\ngoverns goes in `constrains` as `member:<name>` or `path:<path>`. `chant\nworkspace records` reports each pin as `pinned`, `drifted` (the file changed\nsince), `missing` or `stale` (a superseding decision pins the file at the\nsame hash, and the file has not changed since). A drifted pin means the file\nmoved away from what was decided: say so, and do not re-pin it yourself. The\ndesign app pins a file by name (`POST /api/decisions` with `{ title, path }`\nin `evidence` hashes it) and re-pins with `PUT /api/decisions/<id>`.\n\nEvidence (`design/evidence/<sha256>.json`) and design sessions\n(`design/sessions/S-NNNN.json`) are chant record kinds too, declared on the\ndesign member in `chant.workspace.json` (`design/evidence/evidence.kind.mjs`,\n`design/sessions/session.kind.mjs`). Read them with\n`chant workspace records --json`, which reads every declared kind. An\nevidence record's id is the sha256 of its bytes; a file whose name is not\nthat hash carries `asset-drift` and is not evidence. A closed session is\nsealed, and an edit to it is `session-seal-mismatch`. Only the design app and\nthe Ops' Check phases write these files, and each write is read back through\nchant before it is committed. Never write or edit one by hand, and never\nrename one.\n\n- Records keep their core fields in YAML front matter (or JSON), validated\n against the schemas in\n `delivery/node_modules/@intentius/chud-runtime/schemas/`. Extra fields must\n start with `x-`.\n- `delivery/node_modules/@intentius/chud-runtime` is the machinery (the design\n app, the Op steps). It is a versioned dependency: never edit it. Upgrade it\n with the upgrade Op (`npm run upgrade` in `delivery/`), which stops at a\n gate for a person.\n- `.chud/`: local state (hud data, staged releases, the local site). Never\n edit it, and never commit it.\n\nEvery path chud records (a unit's scope, a protected path, a record's `file`)\nis relative to this directory, the git top-level.\n\n## Rules\n\n- Contracts and decisions are written by the design app. Read the ones that\n touch your work and do not undo them. Never edit `design/contracts/`,\n `decisions/`, `work/`, `design/evidence/`, `design/drivers/`,\n `design/context/`, `design/sessions/`, `delivery/decisions/`,\n `delivery/.chant/`, `delivery/chant.config.ts` or `chant.workspace.json`\n (or the paths `chud.protectedPaths` in `delivery/chant.config.ts` adds)\n from the app's page. This is enforced: an agent run's changes there are thrown\n away, and a release refuses to plan while a commit that changes them lacks\n the design app's `Chud-Author: design-app` trailer.\n- Never commit, tag or push. Committing, planning and shipping happen in the\n design app, through the release Op and its gate.\n\n## Why a line is the way it is\n\nThe design app's commits for a unit of work carry trailers that tie the\ncommit to that unit: `Chud-Unit: W-NNN`, `Chud-Contract: C-NNN` (or\n`Chud-Driver: D-NNN` for a split) and one `Chud-Evidence: <sha256>` per\nevidence record the unit cites, beside `Chud-Author: design-app`.\n`decisions/decision.kind.mjs` exports `commitJoins`, which joins a commit\nto its unit from the work item whose `x-chud.result.commit` is the commit\n(chant 0.86's `list` on the hook's context), and from those trailers for the\nunit's other commits, so\n\n```sh\nchant workspace graph --intent app/server.js --json\n```\n\nlists the commits that touched the file (a `path:start-end` region narrows\nit to lines), each joined to the unit that made it (`work/W-NNN-<slug>.md`),\nthe contract it served and the evidence it cited, with the decisions whose\n`constrains` cover the file and what they pin, and the work items whose\nwindow a commit fell in (`worked`). Read it before changing code\na decision governs. The template pins chant 0.86, which has all of this.\nchant marks a commit inside a decision's window `decided-by-window`, and the\nkind adds `plugin:chud:contract-moved` for a unit commit whose contract's\ncriteria differ from those the first unit under that decision served: the\ncontract moved with no decision. Record a decision before you change what a\ncontract asks for.\n",
8
+ "README.md": "# {{chant:name}}\n\nA chant workspace made from the studio kit's template\n([arugula-salad/studio](https://github.com/arugula-salad/studio) `template/`),\nwhich is chant's reference workspace with an app, a delivery project and\nrecords laid onto it. It started as chud's template (jhgaylor/chud 43afcf1)\nand moved into the kit when chud began to retire (arugula-salad/studio#37).\nThe app is built with hud in the page, and releases ship through chant Ops.\n\n```sh\nchant init --from <studio checkout>/template --param name=\"{{chant:name}}\" [--param issue=owner/repo#1] <dir>\nnode <studio checkout>/box/setup-app.mjs <dir> # free ports, npm install in delivery/, git init, the first commit\ncd <dir>/app && npm run dev # the app, restarted on every change (node --watch)\n```\n\nA studio box does both steps in `box/provision-template.sh` and runs the\napp's `dev` script behind hud's proxy. No chud command runs.\n\n[`chant.workspace.json`](chant.workspace.json) declares the members, and\n`chant workspace ls` lists them. The release Op finds everything through them.\n\n| Member | Directory | Kind | What it is |\n|---|---|---|---|\n| app | [`app/`](app) | `other` | the product: a Node HTTP server with SQLite migrations and its own tests |\n| delivery | [`delivery/`](delivery) | `chant` | the chant project, which still runs on chud's runtime package (`@intentius/chud-runtime`) and the chud lexicon until they are split out of chud (INTENTIUS/chant#2713): the release, rollback, upgrade and dispatch Ops, the sites releases ship to, the agents that build the app, the decision points, the write-scope policy and this repo's CI |\n| design-client | [`design-client/`](design-client) | `other`, role `design-app` | a placeholder for a vendored design client (chant's reference workspace, #2524 D18) |\n| design | [`design/`](design) | `other` | the design data: chud's records (contracts, drivers, evidence, sessions, context, proposals), the screen specs, and the design app's overrides |\n\nThe root also holds [`decisions/`](decisions), the decision records. They\nare chant decision records, the format of chant's `docs/design/decisions/`\n(the kind file and schema are beside them): `ref-001` and `ref-002` from\nchant's reference workspace, `chud-001`, and the `chud-NNN` records the design\napp writes as the team decides. Each cites the issue this app's design is\ntracked in (`chud.decisions.issue` in `delivery/chant.config.ts`, the\ntemplate's optional `issue` parameter) as its source, or without one the\nsource of `chud-001`:\n\n```sh\nchant workspace records --kind decisions/decision.kind.mjs --current\n```\n\n[`work/`](work) holds the units of work: every agent run and piece of\nmanual work is a chant work item (`W-NNN-<slug>.md`, chant's work kind with\nchud's fields under `x-chud`), written by chud through `chant workspace\nrecords new` and `records amend`. Both kinds are declared in\n`chant.workspace.json` `records`:\n\n```sh\nchant workspace records --kind work/work.kind.mjs\n```\n\n`.chant/workspace.lock.json`, written by `chant init --from`, records the\ntemplate's lineage: where it came from, at which commit, and the parameters\nit was made with.\n\n## Everyday commands\n\nIn `delivery/`:\n\n```sh\nnpm run release # chant run release --on chud: check, plan, stop at the ship gate\nchant approve release ship --plan <digest>\nnpm run release # ship it\nnpm run rollback\nnpm run upgrade # move to a newer @intentius/chud-runtime, through its gate\nnpm run check # the app's tests and every approved contract's check (CI runs this)\nnpm run lint # chant lint agents, with the contract-sizing rules\n```\n\nAt the root:\n\n```sh\nchant workspace check\nchant workspace build\nchant workspace lint\n```\n\n`CLAUDE.md` is the map for agents, and `design/CLAUDE.md` has the rules for\nthe records.\n",
9
+ "app/CLAUDE.md": "# The app: notes for agents\n\nYou are hud's agent inside the running app (its `dev` script, behind hud's proxy). The person is using\nthe page while you edit this directory. Edits to `server.js` and the files it\nimports restart the server by themselves (`node --watch`); edits to `public/`\nshow on reload (use hud's reload tool).\n\n## Shape\n\n- `server.js`: a plain Node HTTP server. `PORT`, `APP_DATA` (the data dir)\n and `APP_REVISION` come from the environment.\n- `db.js`: SQLite via `node:sqlite`, in `APP_DATA/app.db`.\n- `migrations/NNNN_name.sql`: schema changes. Add a new numbered file; never\n edit one that exists (each fires once per site, guarded by a chant effect\n receipt). The box applies new ones (`npm run migrate`) each time the app starts.\n- `public/`: static files; `index.html` is the page.\n- `test/`: `npm test` (node:test). Add or update a test for each behaviour you\n change, and run `npm test` before you finish.\n\n## The contract with the release Op\n\n- `GET /health` answers `{ \"status\": \"healthy\", \"revision\": APP_REVISION }`.\n Releases are verified by it.\n- `node migrate.js <file>` applies one migration; `node migrate.js` applies\n all pending ones.\n- No new npm dependencies unless the person asks; if you add them, put them\n in `app/package.json`.\n\n## Contracts\n\n`../design/contracts/` holds what the app must do: each approved contract's check\n(`../design/contracts/checks/C-NNN.test.js`) runs against every release, and a\nfailing check blocks it. When asked to build a contract, read it and its\ncheck, and make the check pass.\n",
10
+ "app/db.js": "import { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\n\n/** The app's data lives in APP_DATA (a directory that outlives releases). */\nexport function dataDir() {\n const dir = process.env.APP_DATA ?? join(import.meta.dirname, '.data');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function open() {\n const db = new DatabaseSync(join(dataDir(), 'app.db'));\n db.exec('PRAGMA journal_mode = WAL');\n return db;\n}\n",
11
+ "app/migrate.js": "// Apply migrations. With a file name, applies exactly that one (a release\n// runs each migration once per site, guarded by a chant effect receipt).\n// Without one, applies every migration not yet recorded in this database,\n// which is what the box does against the dev data each time the app starts.\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { open } from './db.js';\n\nconst dir = join(import.meta.dirname, 'migrations');\nconst db = open();\ndb.exec('CREATE TABLE IF NOT EXISTS schema_migrations (file TEXT PRIMARY KEY, applied_at TEXT NOT NULL)');\nconst done = new Set(db.prepare('SELECT file FROM schema_migrations').all().map((r) => r.file));\nconst wanted = process.argv[2] ? [process.argv[2]] : readdirSync(dir).filter((f) => f.endsWith('.sql')).sort();\n\nfor (const file of wanted) {\n if (done.has(file)) {\n console.log(`skip ${file} (already applied)`);\n continue;\n }\n db.exec('BEGIN');\n try {\n db.exec(readFileSync(join(dir, file), 'utf8'));\n db.prepare('INSERT INTO schema_migrations (file, applied_at) VALUES (?, ?)').run(file, new Date().toISOString());\n db.exec('COMMIT');\n console.log(`applied ${file}`);\n } catch (error) {\n db.exec('ROLLBACK');\n console.error(`failed ${file}: ${error.message}`);\n process.exit(1);\n }\n}\n",
12
+ "app/migrations/0001_init.sql": "CREATE TABLE notes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n body TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n",
13
+ "app/package-lock.json": "{\n \"name\": \"app\",\n \"lockfileVersion\": 3,\n \"requires\": true,\n \"packages\": {\n \"\": {\n \"name\": \"app\",\n \"engines\": {\n \"node\": \">=22.13\"\n }\n }\n }\n}\n",
14
+ "app/package.json": "{\n \"name\": \"app\",\n \"private\": true,\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.13\"\n },\n \"scripts\": {\n \"dev\": \"node --watch --disable-warning=ExperimentalWarning server.js\",\n \"start\": \"node --disable-warning=ExperimentalWarning server.js\",\n \"migrate\": \"node --disable-warning=ExperimentalWarning migrate.js\",\n \"test\": \"node --disable-warning=ExperimentalWarning --test \\\"test/**/*.test.js\\\"\"\n }\n}\n",
15
+ "app/public/index.html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>{{chant:name}}</title>\n<style>\n body { font: 16px/1.5 system-ui, sans-serif; max-width: 40rem; margin: 3rem auto; padding: 0 1rem; color: #222; }\n form { display: flex; gap: .5rem; } input { flex: 1; padding: .5rem; } button { padding: .5rem 1rem; }\n li { padding: .25rem 0; border-bottom: 1px solid #eee; list-style: none; } ul { padding: 0; }\n .hint { color: #777; font-size: .9rem; }\n</style>\n</head>\n<body>\n<h1>{{chant:name}}</h1>\n<p class=\"hint\">A starter app. Press Ctrl+` to open hud and ask for what you want this to be.</p>\n<form id=\"new\"><input name=\"body\" placeholder=\"Write a note\" autocomplete=\"off\"><button>Add</button></form>\n<ul id=\"notes\"></ul>\n<script>\n const list = document.getElementById('notes');\n async function load() {\n const notes = await (await fetch('/api/notes')).json();\n list.replaceChildren(...notes.map((n) => Object.assign(document.createElement('li'), { textContent: n.body })));\n }\n document.getElementById('new').addEventListener('submit', async (e) => {\n e.preventDefault();\n const input = e.target.body;\n await fetch('/api/notes', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ body: input.value }) });\n input.value = '';\n load();\n });\n load();\n</script>\n</body>\n</html>\n",
16
+ "app/server.js": "import { createServer } from 'node:http';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { open } from './db.js';\n\nconst port = Number(process.env.PORT ?? 3000);\nconst revision = process.env.APP_REVISION ?? 'dev';\nconst db = open();\n\nconst json = (res, status, body) => {\n res.writeHead(status, { 'content-type': 'application/json' });\n res.end(JSON.stringify(body));\n};\n\nasync function body(req) {\n const chunks = [];\n for await (const c of req) chunks.push(c);\n return chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {};\n}\n\nconst routes = {\n 'GET /health': (req, res) => {\n let database = 'healthy';\n try { db.prepare('SELECT 1 FROM notes LIMIT 1').all(); } catch (error) { database = error.message; }\n json(res, database === 'healthy' ? 200 : 503, { status: database === 'healthy' ? 'healthy' : 'unhealthy', revision, services: { web: 'healthy', database } });\n },\n 'GET /api/notes': (req, res) => json(res, 200, db.prepare('SELECT * FROM notes ORDER BY id DESC').all()),\n 'POST /api/notes': async (req, res) => {\n const { body: text } = await body(req);\n if (!text || !String(text).trim()) return json(res, 400, { error: 'body is required' });\n const row = db.prepare('INSERT INTO notes (body) VALUES (?) RETURNING *').get(String(text).trim());\n json(res, 201, row);\n },\n};\n\ncreateServer(async (req, res) => {\n const path = new URL(req.url, 'http://x').pathname;\n try {\n const route = routes[`${req.method} ${path}`];\n if (route) return await route(req, res);\n if (req.method === 'GET' && path === '/') {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n return res.end(await readFile(join(import.meta.dirname, 'public', 'index.html')));\n }\n json(res, 404, { error: 'not found' });\n } catch (error) {\n console.error(error);\n json(res, 500, { error: error.message });\n }\n}).listen(port, '127.0.0.1', () => console.log(`app listening on http://127.0.0.1:${port} (revision ${revision})`));\n",
17
+ "app/test/notes.test.js": "import { test, before, after } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { spawn, execFileSync } from 'node:child_process';\nimport { mkdtempSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nconst root = join(import.meta.dirname, '..');\nconst data = mkdtempSync(join(tmpdir(), 'app-test-'));\nconst port = 20000 + Math.floor(Math.random() * 20000);\nconst env = { ...process.env, APP_DATA: data, PORT: String(port), NODE_NO_WARNINGS: '1' };\nconst url = `http://127.0.0.1:${port}`;\nlet server;\n\nbefore(async () => {\n execFileSync(process.execPath, ['migrate.js'], { cwd: root, env });\n server = spawn(process.execPath, ['server.js'], { cwd: root, env, stdio: 'ignore' });\n for (let i = 0; i < 50; i++) {\n try { if ((await fetch(`${url}/health`)).ok) return; } catch {}\n await new Promise((r) => setTimeout(r, 100));\n }\n throw new Error('server did not start');\n});\n\nafter(() => {\n server?.kill();\n rmSync(data, { recursive: true, force: true });\n});\n\ntest('health reports healthy', async () => {\n const health = await (await fetch(`${url}/health`)).json();\n assert.equal(health.status, 'healthy');\n});\n\ntest('a note can be added and listed', async () => {\n const created = await fetch(`${url}/api/notes`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ body: 'hello' }) });\n assert.equal(created.status, 201);\n const notes = await (await fetch(`${url}/api/notes`)).json();\n assert.equal(notes[0].body, 'hello');\n});\n",
18
+ "chant.template.json": "{\n \"$comment\": \"Template parameters for chant init --from (INTENTIUS/chant#2627). Each listed file carries {{chant:<name>}} where the value goes. The studio kit's template (arugula-salad/studio#37), carried over from chud's at jhgaylor/chud 43afcf1. Make a repo with: chant init --from <studio checkout>/template --param name=\\\"My app\\\" [--param issue=owner/repo#1] <dir>, then node <studio checkout>/box/setup-app.mjs <dir>.\",\n \"parameters\": {\n \"name\": {\n \"type\": \"string\",\n \"default\": \"Untitled app\",\n \"pattern\": \"^[A-Za-z0-9][A-Za-z0-9 ._-]*$\",\n \"description\": \"The app's display name: its page's title and heading, the design app's, the agents' prompts, the first contract and decision, and the screen spec's. Its slug names the Fly app and the fountain Environment and Vault (delivery/app-name.ts)\"\n },\n \"issue\": {\n \"type\": \"string\",\n \"default\": \"\",\n \"pattern\": \"^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+)?$\",\n \"description\": \"Optional: the issue this app's design decisions are tracked in, owner/repo#n. Decision records the design app writes cite it as their source (chant's decision format requires one) and link it as evidence; delivery/chant.config.ts keeps it as chud.decisions.issue. Without one, they cite the source of chud-001, the repo's founding decision, and say so under x-chud\"\n }\n },\n \"files\": [\n \"CLAUDE.md\",\n \"README.md\",\n \"app/public/index.html\",\n \"decisions/chud-001-build-with-chud.md\",\n \"delivery/agents/team.ts\",\n \"delivery/app-name.ts\",\n \"delivery/chant.config.ts\",\n \"design/CLAUDE.md\",\n \"design/contracts/C-001-notes.md\",\n \"design/screens/home.json\",\n \"design/screens/home.svg\"\n ]\n}\n",
19
+ "chant.workspace.json": "{\n \"name\": \"chud-app\",\n \"schema\": 1,\n \"minReader\": \"0.86.0\",\n \"members\": [\n {\n \"name\": \"app\",\n \"dir\": \"app\",\n \"kind\": \"other\",\n \"because\": \"a Node HTTP server with its own tests and SQLite migrations; the box runs its dev script behind hud's proxy, and the release Op checks and ships it; no app kind exists yet (INTENTIUS/chant#2535)\"\n },\n {\n \"name\": \"delivery\",\n \"dir\": \"delivery\",\n \"kind\": \"chant\"\n },\n {\n \"name\": \"design-client\",\n \"dir\": \"design-client\",\n \"kind\": \"other\",\n \"roles\": [\"design-app\"],\n \"because\": \"a placeholder until a hud client package is published to vendor; the kind and upstream come with it\"\n },\n {\n \"name\": \"design\",\n \"dir\": \"design\",\n \"kind\": \"other\",\n \"because\": \"the design data member: the development model's records (contracts, evidence, sessions) and the screen specs\",\n \"records\": [{ \"kind\": \"evidence/evidence.kind.mjs\" }, { \"kind\": \"sessions/session.kind.mjs\" }]\n }\n ],\n \"records\": [{ \"kind\": \"decisions/decision.kind.mjs\" }, { \"kind\": \"work/work.kind.mjs\" }],\n \"pins\": []\n}\n",
20
+ "decisions/chud-001-build-with-chud.md": "---\nschema: 1\nid: \"chud-001\"\ntitle: \"Build {{chant:name}} with chud\"\nstate: \"decided\"\narea: \"process\"\nsource:\n issue: \"jhgaylor/chud#77\"\n row: \"Build {{chant:name}} with chud\"\n revision: null\nquestion: \"How is {{chant:name}} built, checked and shipped?\"\noptions:\n - id: \"a\"\n label: \"an app repo with ad-hoc scripts\"\n how: \"The app and whatever scripts build, test and deploy it, kept by hand.\"\n tradeoff: \"Nothing to learn first. Nothing records what the app must do, what was decided, or what shipped under which approval.\"\n - id: \"b\"\n label: \"chud\"\n how: \"hud in the app to build it (`chud dev`), a design app for contracts and decisions (`chud design`), and chant Ops to ship it, on a chant workspace made from chud's template.\"\n tradeoff: \"Contracts, decisions, units and evidence live in the repo next to the code, and nothing ships without an approval bound to exactly what was checked. The process has its own tools to learn.\"\nchoice:\n option: \"b\"\n reason: \"Every decision and contract lives in the repo next to the code it constrains, and nothing ships without an approval bound to exactly what was checked.\"\nrejected:\n - option: \"a\"\n why: \"It keeps no record of what the app must do or of what was approved to ship.\"\nsupersedes: []\nevidence:\n - title: \"jhgaylor/chud#77, the template this repo was made from\"\n url: \"https://github.com/jhgaylor/chud/pull/77\"\n - title: \"chud's README: what a chud repo holds and how it ships\"\n url: \"https://github.com/jhgaylor/chud#readme\"\ndecided_by: \"chud template\"\ndecided_on: \"2026-09-24\"\nreviews: []\nconstrains:\n - \"member:app\"\n - \"member:delivery\"\n - \"member:design\"\nx-chud:\n kind: \"process\"\n decided_at: \"2026-09-24T00:00:00Z\"\n---\n\n# Build {{chant:name}} with chud\n",
21
+ "decisions/decision.kind.mjs": "// The decision record kind (#2555).\n//\n// chant workspace records --kind decisions/decision.kind.mjs --current --json\n//\n// recordKind is a copy of docs/design/decisions/decision.kind.mjs in the chant\n// repo, with decision.schema.json beside it, so a workspace made from this one\n// reads its decisions without the chant repo. chant's\n// test/reference-workspace.test.ts fails when this kind's data or the schema\n// drifts from chant's. commitJoins below is chud's own.\n//\n// recordKind is chant main's as of INTENTIUS/chant#2676 and #2675: `reviews`\n// names the verdicts field, which `chant workspace records review` appends to.\n// chant 0.85.0 and earlier refuse a kind with `reviews` (kind-invalid), so this\n// kind needs chant 0.86.0 or later.\n//\n// decision.schema.json is chant-v0.90.0's, with the source fields #2708 added\n// (via, client, harness, model, session, turns, transcript). The kind does not\n// yet declare chant-v0.90.0's `source: { field: \"source\" }`, which is how\n// `chant serve mcp` knows to stamp a new record's source with via \"mcp\" and\n// the client (INTENTIUS/chant#2707): chant before 0.90.0 refuses a kind with\n// `source` (kind-invalid), and delivery/ installs chant ^0.86.0, which chud's\n// design app writes records with. The studio smoke adds it in the repo it\n// makes for its MCP round trip (docs/smoke-claims.md, section 28).\nimport { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join, relative, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const recordKind = {\n name: \"decision\",\n location: { dir: \".\", match: \"^[a-z][a-z0-9]{0,15}-[0-9]{3,}-.+\\\\.md$\" },\n format: \"markdown-front-matter\",\n schema: { id: \"urn:intentius:chant:decision:1\", path: \"decision.schema.json\" },\n idField: \"id\",\n stateField: \"state\",\n states: [\"proposed\", \"decided\", \"ratified\", \"superseded\", \"withdrawn\"],\n // Sealed once reached (#2555).\n closedStates: [\"ratified\", \"superseded\"],\n supersedes: { field: \"supersedes\", key: \"decision\" },\n // A supersedes link takes effect under an equal or stricter approval rule\n // (#2524 D4): from a record ranked above 0 and at least as high as the one it\n // names. A decided record supersedes a decided or proposed one, a ratified\n // record supersedes any, and a proposed or withdrawn record none.\n approval: { proposed: 0, withdrawn: 0, decided: 1, ratified: 2, superseded: 2 },\n // Evidence entries with a path pin a workspace file by the hash of its bytes (#2549).\n pins: { field: \"evidence\" },\n // member:<name> and path:<path> entries are the record's links in workspace graph (#2549).\n constrains: { field: \"constrains\" },\n // Verdicts, and the field naming the decider, for each record's digest and\n // quorum (#2671, #2672).\n reviews: { field: \"reviews\", decider: \"decided_by\" },\n};\n\n// How `chant workspace graph --intent <path> --kind decisions/decision.kind.mjs`\n// joins a commit to the unit of work that made it, in the function form of\n// chant's commitJoins (chant #2651, #2656). chant calls it once for each commit\n// that touched the region, as commitJoins(commit, context): commit is { sha,\n// subject, body, author: { name, email }, date, trailers: { key: [values] } }\n// and context is { read(path), list(dir), at }, where read gives a file's text\n// and list a directory's entries in the tree the graph reads, from the\n// workspace root (list is chant #2663, in 0.86.0).\n//\n// A unit is a chant work item, work/W-NNN-<slug>.md, with chud's fields under\n// x-chud (jhgaylor/chud#78). The join comes from the records first: the work\n// item whose x-chud.result.commit is the commit is the unit that made it, its\n// x-chud.contract (or driver) the contract, and its x-chud.evidence the\n// evidence it cites, each evidence record (design/evidence/<hash>.json) adding\n// its fields. That needs context.list, to find the work items. A commit no work\n// item names (a unit's open and close commits, its kept not_done snapshot, a\n// repo on the old layout, a chant without list) is joined by its trailers as\n// the data form joined it (jhgaylor/chud#84): Chud-Unit, Chud-Contract\n// (Chud-Driver for a split) and one Chud-Evidence per cited evidence record\n// beside Chud-Author. A W-NNN unit's fields come from its work item, a U-NNNN\n// unit's from design/units/<id>.json; a contract is markdown, so it is joined\n// by id alone. Chud-Author is the authorship trailer. The unit trailers stay on\n// unit commits for one release, and can go once every commit a unit makes is\n// named by its record.\n//\n// It also reports one finding, contract-moved, in chud's namespace:\n// plugin:chud:contract-moved. chant namespaces a kind's findings by the kind's\n// name, which is \"decision\" here, unless the kind file names them with\n// commitJoinsName (chant #2663, after 0.85.0), which this one does. chant\n// 0.85.0 does not read commitJoinsName and accepts only\n// plugin:decision:contract-moved, so the hook reports the code the running\n// chant accepts (findingCode below). The rule: a unit\n// commit whose contract is constrained by a decision in force at that commit\n// (the decision is in the tree, and no decision in the tree supersedes it),\n// where an earlier commit of another unit on the same contract lies in that\n// decision's window (after the commit that added the decision), and the\n// contract's acceptance criteria at this commit differ from the criteria at\n// that other unit's last commit in the window. The first unit to serve the\n// contract after a decision sets what the decision was carried out on; a later\n// unit that serves different criteria under the same decision moved the\n// contract with no decision of its own. The history comes from git, run in the\n// workspace, since chant hands the hook the commit and the tree read but not\n// the history.\n//\n// chant 0.83 reads only recordKind. chant 0.84 runs the joins and drops the\n// finding; chant 0.85 carries it as a finding node, as\n// plugin:decision:contract-moved; a chant with #2663 carries it as\n// plugin:chud:contract-moved.\n\nconst TRAILER = { unit: 'Chud-Unit', contract: 'Chud-Contract', driver: 'Chud-Driver', evidence: 'Chud-Evidence', author: 'Chud-Author' };\nconst RECORD = { unit: 'design/units/{id}.json', evidence: 'design/evidence/{id}.json' };\nconst CONTRACTS = 'design/contracts';\n/** Work items: the directory and file names of chant's work kind (work/work.kind.mjs). */\nconst WORK = { dir: 'work', match: /^W-[0-9]{3,}-.+\\.md$/, id: /^W-[0-9]{3,}$/ };\n\n/** The namespace of this kind's findings, plugin:chud:<code>, for a chant that reads it (chant #2663). */\nexport const commitJoinsName = 'chud';\nexport const CONTRACT_MOVED = `plugin:${commitJoinsName}:contract-moved`;\n/** The same finding as chant 0.85.0 names it: by the record kind's name, since it ignores commitJoinsName. */\nexport const CONTRACT_MOVED_BY_KIND = `plugin:${recordKind.name}:contract-moved`;\n\n/**\n * The code chant accepts for a finding, which depends on whether it reads\n * commitJoinsName. chant #2663 added that export and the context's list(dir)\n * together, so a context with list is a chant that names the findings after\n * commitJoinsName; without it (0.85.0) they are named after the record kind,\n * and a code in another namespace fails the join (intent-plugin-failed).\n */\nexport function findingCode(context, code) {\n return `plugin:${typeof context?.list === 'function' ? commitJoinsName : recordKind.name}:${code}`;\n}\n\n/** Every value of trailer `key`, compared without case as git does. */\nfunction trailerValues(trailers, key) {\n const want = key.toLowerCase();\n return Object.entries(trailers ?? {})\n .filter(([k]) => k.toLowerCase() === want)\n .flatMap(([, values]) => values.map((v) => String(v).trim()).filter(Boolean));\n}\n\n/** A JSON record's fields, from the tree the graph reads, without its id; {} when it is not there. */\nfunction recordFields(context, template, id) {\n const path = template.replace('{id}', id);\n const text = context.read(path);\n if (text === undefined) return {};\n let value;\n try {\n value = JSON.parse(text);\n } catch {\n throw new Error(`${path} is not JSON`);\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return {};\n const { id: _id, ...rest } = value;\n return rest;\n}\n\n/**\n * chant's front matter YAML, as `chant workspace records new|amend` writes it\n * (records-write.ts toYaml): block mappings and sequences indented by two,\n * every scalar in JSON (strings double-quoted), `[]` and `{}` for empty ones.\n * A plain scalar another writer left is kept as its text. Null when there is\n * no front matter.\n */\nexport function frontMatter(text) {\n const m = String(text).match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/);\n if (!m) return null;\n const lines = m[1].split(/\\r?\\n/).filter((l) => l.trim() !== '' && !/^\\s*#/.test(l));\n let i = 0;\n const indent = (l) => l.match(/^ */)[0].length;\n const scalar = (s) => {\n const v = s.trim();\n if (v === '[]') return [];\n if (v === '{}') return {};\n try {\n return JSON.parse(v);\n } catch {\n return v.length >= 2 && v.startsWith(\"'\") && v.endsWith(\"'\") ? v.slice(1, -1).replace(/''/g, \"'\") : v;\n }\n };\n const KEY = /^(\"(?:[^\"\\\\]|\\\\.)*\"|[^\\s\"'#:-][^:]*?)\\s*:(?:\\s+(.*))?$/;\n const key = (s) => (s.startsWith('\"') ? JSON.parse(s) : s);\n const isItem = (l, at) => indent(l) === at && /^-(\\s|$)/.test(l.slice(at));\n function block(at) {\n if (i >= lines.length || indent(lines[i]) !== at) return null;\n return isItem(lines[i], at) ? seq(at) : map(at);\n }\n function map(at) {\n const out = {};\n while (i < lines.length && indent(lines[i]) === at && !isItem(lines[i], at)) {\n const kv = lines[i].slice(at).match(KEY);\n i++;\n if (!kv) continue;\n if (kv[2] !== undefined && kv[2].trim() !== '') out[key(kv[1])] = scalar(kv[2]);\n else out[key(kv[1])] = block(at + 2) ?? (i < lines.length && isItem(lines[i], at) ? seq(at) : null);\n }\n return out;\n }\n function seq(at) {\n const out = [];\n while (i < lines.length && isItem(lines[i], at)) {\n const rest = lines[i].slice(at + 1).trim();\n if (rest === '') {\n i++;\n out.push(block(at + 2));\n } else if (KEY.test(rest) && !/^[\"'\\[{]/.test(rest)) {\n // A mapping that starts on the dash line: read it as if the dash were indentation.\n lines[i] = `${' '.repeat(at + 2)}${rest}`;\n out.push(map(at + 2));\n } else {\n i++;\n out.push(scalar(rest));\n }\n }\n return out;\n }\n const value = map(0);\n return value && typeof value === 'object' ? value : null;\n}\n\n/** Each work item's front matter in the tree the graph reads that is one of chud's units (it has x-chud), by path. Needs context.list. */\nconst workCache = new WeakMap();\nfunction workUnits(context) {\n if (typeof context?.list !== 'function') return [];\n // chant builds a context for each commit, with the same read function for one graph: the scan is done once per graph.\n const cacheKey = typeof context.read === 'function' ? context.read : null;\n if (cacheKey && workCache.has(cacheKey)) return workCache.get(cacheKey);\n const found = [];\n for (const entry of context.list(WORK.dir) ?? []) {\n const name = entry.split('/').at(-1);\n if (!WORK.match.test(name)) continue;\n const text = context.read(entry.includes('/') ? entry : `${WORK.dir}/${entry}`);\n const data = text === undefined ? null : frontMatter(text);\n if (data && typeof data.id === 'string' && data['x-chud'] && typeof data['x-chud'] === 'object') found.push({ path: entry, data });\n }\n if (cacheKey) workCache.set(cacheKey, found);\n return found;\n}\n\n/** A work item's fields as the joined unit: chud's x-chud, with chant's state and title. */\nconst workFields = (w) => ({ ...w.data['x-chud'], state: w.data.state, title: w.data.title, record: w.path });\n\nexport function commitJoins(commit, context) {\n const t = commit.trailers ?? {};\n const works = workUnits(context);\n // The records first: the work item whose x-chud.result.commit is this commit made it.\n const named = works.find((w) => w.data['x-chud'].result?.commit === commit.sha);\n let unitId;\n let contractId;\n let evidence;\n let unit;\n let serves;\n if (named) {\n const x = named.data['x-chud'];\n unitId = named.data.id;\n contractId = x.contract?.id ?? x.driver?.id;\n serves = x.contract ? 'contract' : x.driver ? 'driver' : null;\n evidence = [...new Set(Array.isArray(x.evidence) ? x.evidence.map(String) : [])];\n unit = { ...workFields(named), id: unitId };\n } else {\n [unitId] = trailerValues(t, TRAILER.unit);\n serves = trailerValues(t, TRAILER.contract).length ? 'contract' : trailerValues(t, TRAILER.driver).length ? 'driver' : null;\n [contractId] = serves === 'contract' ? trailerValues(t, TRAILER.contract) : trailerValues(t, TRAILER.driver);\n evidence = [...new Set(trailerValues(t, TRAILER.evidence))];\n if (unitId) {\n const w = WORK.id.test(unitId) ? works.find((x) => x.data.id === unitId) : null;\n unit = { ...(w ? workFields(w) : WORK.id.test(unitId) ? {} : recordFields(context, RECORD.unit, unitId)), id: unitId };\n }\n }\n const out = {};\n if (unit) out.unit = unit;\n if (contractId) out.contract = { id: contractId };\n if (evidence.length) out.evidence = evidence.map((hash) => ({ ...recordFields(context, RECORD.evidence, hash), id: hash }));\n if (trailerValues(t, TRAILER.author).length) out.authorship = [TRAILER.author];\n if (unitId && serves === 'contract') {\n const found = contractMoved(commit.sha, unitId, contractId, findingCode(context, 'contract-moved'));\n if (found) out.findings = [found];\n }\n return out;\n}\n\n// ---------------------------------------------------------------- the finding\n\n/** The workspace root: the directory above this file's when it holds chant.workspace.json, else the nearest one above the cwd. */\nfunction workspaceRoot() {\n const above = dirname(dirname(fileURLToPath(import.meta.url)));\n if (existsSync(join(above, 'chant.workspace.json'))) return above;\n for (let dir = process.cwd(); ; dir = dirname(dir)) {\n if (existsSync(join(dir, 'chant.workspace.json'))) return dir;\n if (dirname(dir) === dir) return null;\n }\n}\n\n/** The decisions directory, from the workspace root: this file's own when it is in the workspace, else decisions/. */\nfunction decisionsDir(root) {\n const rel = relative(root, dirname(fileURLToPath(import.meta.url)));\n return rel && !rel.startsWith('..') ? rel.split(sep).join('/') : 'decisions';\n}\n\nconst unquote = (s) => {\n const v = String(s ?? '').trim();\n if (v.startsWith('\"') && v.endsWith('\"') && v.length >= 2) {\n try {\n return JSON.parse(v);\n } catch {\n return v.slice(1, -1);\n }\n }\n if (v.startsWith(\"'\") && v.endsWith(\"'\") && v.length >= 2) return v.slice(1, -1).replace(/''/g, \"'\");\n return v;\n};\nconst flowList = (s) => (/^\\[.*\\]$/.test(s.trim()) ? s.trim().slice(1, -1).split(',').map(unquote).filter(Boolean) : []);\n\n/**\n * The fields of a decision's front matter this hook reads: id, state,\n * supersedes (ids), constrains (chant's) and x-chud.constrains (chud's own,\n * where contract ids go). The records are YAML as the design app writes them\n * (block lists, one scalar per line); anything else is skipped.\n */\nexport function decisionFields(text) {\n const m = String(text).match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!m) return null;\n const out = { id: null, state: null, supersedes: [], constrains: [] };\n let top = null;\n let sub = null;\n for (const line of m[1].split(/\\r?\\n/)) {\n const key = line.match(/^([A-Za-z0-9_][A-Za-z0-9_-]*):(?:\\s+(.*))?$/);\n if (key) {\n [top, sub] = [key[1], null];\n const value = key[2] ?? '';\n if (top === 'id') out.id = unquote(value);\n if (top === 'state') out.state = unquote(value);\n if (top === 'constrains') out.constrains.push(...flowList(value));\n continue;\n }\n const nested = line.match(/^ {2}([A-Za-z0-9_][A-Za-z0-9_-]*):(?:\\s+(.*))?$/);\n if (nested) {\n sub = nested[1];\n if (top === 'x-chud' && sub === 'constrains') out.constrains.push(...flowList(nested[2] ?? ''));\n continue;\n }\n const item = line.match(/^\\s*-\\s+(.*)$/);\n if (!item) continue;\n if (top === 'constrains' || (top === 'x-chud' && sub === 'constrains')) out.constrains.push(unquote(item[1]));\n else if (top === 'supersedes') {\n const d = item[1].match(/^decision:\\s*(.*)$/);\n if (d) out.supersedes.push(unquote(d[1]));\n }\n }\n return out.id ? out : null;\n}\n\n/** A contract's acceptance criteria, as `[id] text` lines, and the id it supersedes. */\nexport function contractFields(text) {\n const m = String(text).match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n const supersedes = m?.[1].match(/^supersedes:\\s*(.+)$/m)?.[1];\n return {\n supersedes: supersedes && unquote(supersedes) !== 'null' ? unquote(supersedes) : null,\n criteria: [...String(text).matchAll(/^- \\[(\\w+)\\] (.+)$/gm)].map((x) => ({ id: x[1], text: x[2].trim() })),\n };\n}\n\n/**\n * Whether the unit commit `sha` moved its contract inside a decision's window\n * (see the top of this file). Returns the finding, with `code` (the one the\n * running chant accepts, findingCode), or null. Never throws: when git can't\n * answer (no workspace, no repository, a shallow history), there is no\n * finding and the joins stand.\n */\nexport function contractMoved(sha, unitId, contractId, code = CONTRACT_MOVED) {\n try {\n const root = workspaceRoot();\n if (!root) return null;\n const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 });\n const show = (rev, path) => {\n try {\n return git('show', `${rev}:./${path}`);\n } catch {\n return undefined;\n }\n };\n const contractFile = (rev, id) => git('ls-tree', '--name-only', rev, '--', `${CONTRACTS}/`).split('\\n').find((p) => {\n const name = p.split('/').at(-1);\n return name === `${id}.md` || (name.startsWith(`${id}-`) && name.endsWith('.md'));\n });\n const contractAt = (rev, id) => {\n const file = contractFile(rev, id);\n const text = file && show(rev, file);\n return text === undefined ? null : contractFields(text);\n };\n\n // The contract and the versions it replaces, as the tree at the commit has them.\n const here = contractAt(sha, contractId);\n if (!here) return null;\n const chain = [contractId];\n for (let c = here; c?.supersedes && !chain.includes(c.supersedes) && chain.length < 20; c = contractAt(sha, c.supersedes)) chain.push(c.supersedes);\n\n // The decisions in force at the commit that constrain the contract.\n const dir = decisionsDir(root);\n const match = new RegExp(recordKind.location.match);\n const decisions = git('ls-tree', '--name-only', sha, '--', `${dir}/`).split('\\n').filter((p) => match.test(p.split('/').at(-1)))\n .map((file) => ({ file, ...decisionFields(show(sha, file) ?? '') }))\n .filter((d) => d.id);\n const superseded = new Set(decisions.flatMap((d) => d.supersedes));\n const governing = decisions.filter((d) => d.state !== 'withdrawn' && !superseded.has(d.id) && d.constrains.some((c) => chain.includes(c)));\n\n const criteriaKey = (c) => c.criteria.map((k) => `[${k.id}] ${k.text}`).join('\\n');\n for (const d of governing) {\n const added = git('log', '-1', '--diff-filter=A', '--format=%H', sha, '--', `./${d.file}`).trim();\n if (!added) continue;\n // The unit commits in the window before this one, oldest first.\n const window = git('log', '--reverse', '--format=%H%x09%(trailers:key=Chud-Unit,valueonly,separator=%x2C)%x09%(trailers:key=Chud-Contract,valueonly,separator=%x2C)', `${added}..${sha}`)\n .split('\\n').filter(Boolean).map((l) => l.split('\\t')).map(([commit, unit, contract]) => ({ commit, unit: unit?.trim(), contract: contract?.trim() }))\n .filter((c) => c.commit !== sha && c.unit && c.unit !== unitId && chain.includes(c.contract));\n if (!window.length) continue;\n const first = window[0];\n const last = window.filter((c) => c.unit === first.unit).at(-1);\n const before = contractAt(last.commit, last.contract);\n if (!before || criteriaKey(before) === criteriaKey(here)) continue;\n const ids = [...new Set([...before.criteria, ...here.criteria].map((k) => k.id))];\n const changed = ids.filter((id) => before.criteria.find((k) => k.id === id)?.text !== here.criteria.find((k) => k.id === id)?.text);\n return {\n code,\n message: `${unitId}'s commit ${sha.slice(0, 8)} serves ${contractId} with criteria that differ from those ${first.unit} served inside ${d.id}'s window (${changed.join(', ')} changed), and no decision since ${d.id} records the change`,\n refs: [unitId, contractId, d.id, first.unit, last.commit],\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n",
22
+ "decisions/decision.schema.json": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"urn:intentius:chant:decision:1\",\n \"title\": \"Decision file front matter\",\n \"description\": \"The front matter of one decision file in docs/design/decisions/. The format is described in README.md beside this file. Values stay within what YAML and JSON share, so a file can be sealed later (#2524 D4).\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"patternProperties\": {\n \"^x-\": {}\n },\n \"required\": [\n \"schema\",\n \"id\",\n \"title\",\n \"state\",\n \"area\",\n \"source\",\n \"question\",\n \"options\",\n \"choice\",\n \"rejected\",\n \"supersedes\",\n \"evidence\",\n \"decided_by\",\n \"decided_on\",\n \"reviews\",\n \"constrains\"\n ],\n \"properties\": {\n \"schema\": {\n \"description\": \"Format version of this file. Bumped only by a change to this schema.\",\n \"const\": 1\n },\n \"id\": {\n \"description\": \"Stable id: a lowercase prefix per epic, a dash and three or more digits. Never reused, never renumbered.\",\n \"type\": \"string\",\n \"pattern\": \"^[a-z][a-z0-9]{0,15}-[0-9]{3,}$\"\n },\n \"title\": {\n \"description\": \"Short topic, as the source table names it, without revision markers.\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"state\": {\n \"description\": \"Lifecycle state, from #2555.\",\n \"enum\": [\"proposed\", \"decided\", \"ratified\", \"superseded\", \"withdrawn\"]\n },\n \"area\": {\n \"description\": \"The design section the decision belongs to, such as \\\"D5\\\". Groups the review queue. Null only in an importer draft.\",\n \"type\": [\"string\", \"null\"]\n },\n \"source\": {\n \"description\": \"Where the decision was first recorded: a row of an issue's decisions table, the workspace itself when no issue holds the decision (#2654), or a harness session when neither does (#2708). Each form may also say where the proposal came from: the fields via, client, harness, model, session, turns and transcript, which the kind opts in to with source: { field: \\\"source\\\" }.\",\n \"oneOf\": [{ \"$ref\": \"#/definitions/sourceIssue\" }, { \"$ref\": \"#/definitions/sourceWorkspace\" }, { \"$ref\": \"#/definitions/sourceHarness\" }]\n },\n \"question\": {\n \"description\": \"One sentence. Null only in an importer draft.\",\n \"type\": [\"string\", \"null\"]\n },\n \"options\": {\n \"description\": \"Every option considered, the chosen one included.\",\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": { \"$ref\": \"#/definitions/option\" }\n },\n \"choice\": {\n \"description\": \"The chosen option and why. Null in state proposed or withdrawn.\",\n \"oneOf\": [\n { \"type\": \"null\" },\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"option\", \"reason\"],\n \"properties\": {\n \"option\": { \"$ref\": \"#/definitions/optionId\" },\n \"reason\": { \"type\": [\"string\", \"null\"] }\n }\n }\n ]\n },\n \"rejected\": {\n \"description\": \"The options not chosen, kept on the record.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"option\", \"why\"],\n \"properties\": {\n \"option\": { \"$ref\": \"#/definitions/optionId\" },\n \"why\": { \"type\": [\"string\", \"null\"] }\n }\n }\n },\n \"supersedes\": {\n \"description\": \"Earlier choices this decision replaced: another decision file, or an option chosen in an earlier revision of the same design before decision files existed.\",\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"decision\"],\n \"properties\": {\n \"decision\": { \"$ref\": \"#/definitions/decisionId\" }\n }\n },\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"revision\", \"option\"],\n \"properties\": {\n \"revision\": { \"type\": \"string\", \"pattern\": \"^v[0-9]+$\" },\n \"option\": { \"$ref\": \"#/definitions/optionId\" }\n }\n }\n ]\n }\n },\n \"evidence\": {\n \"description\": \"The research, audits, design sections and workspace files behind the decision. A public link carries a url; a file in the workspace carries a path and the hash of its bytes (#2549). May be empty when the decision cites nothing; chant workspace records then warns decision-no-evidence (#2654).\",\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [{ \"$ref\": \"#/definitions/evidenceLink\" }, { \"$ref\": \"#/definitions/evidencePin\" }]\n }\n },\n \"decided_by\": {\n \"description\": \"Who chose. A forge login. Null in state proposed or withdrawn.\",\n \"type\": [\"string\", \"null\"]\n },\n \"decided_on\": {\n \"description\": \"When the choice was made, as YYYY-MM-DD. Quoted, so YAML reads it as a string.\",\n \"type\": [\"string\", \"null\"],\n \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\"\n },\n \"reviews\": {\n \"description\": \"Each reviewer's verdict. Empty until a review happens.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"reviewer\", \"verdict\", \"on\"],\n \"properties\": {\n \"reviewer\": { \"type\": \"string\", \"minLength\": 1 },\n \"verdict\": { \"enum\": [\"agree\", \"dissent\", \"abstain\"] },\n \"note\": { \"type\": [\"string\", \"null\"] },\n \"on\": { \"type\": \"string\", \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\" },\n \"digest\": {\n \"description\": \"The digest of the record text the verdict judged: the lowercase hex SHA-256 of the file with LF line endings and without its reviews block (#2672). A verdict whose digest is not the record's digest now stops counting toward the quorum. A verdict without one still counts, with the warning review-undigested.\",\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n },\n \"session\": {\n \"description\": \"The id of the review session the verdict was given in, as the session names it (#2650 C10). chant workspace records review --session writes it (#2670).\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"seal\": {\n \"description\": \"The reviewer's ssh signature over the verdict (#2687): the bytes <id>\\\\n<digest>\\\\n<verdict>\\\\n<reviewer>\\\\n<on>, in the ssh-keygen namespace chant-review. chant workspace records review --sign writes it. Once a signers file is active at base, a verdict counts toward the quorum only when its seal verifies for its reviewer against that file. A seal needs a digest.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"signer\", \"key\", \"signature\"],\n \"properties\": {\n \"signer\": { \"description\": \"Who signed: the reviewer, as the signers file names them.\", \"type\": \"string\", \"minLength\": 1 },\n \"key\": { \"description\": \"The signing key's fingerprint. Reported, never trusted: the signature is the proof.\", \"type\": \"string\", \"pattern\": \"^SHA256:[A-Za-z0-9+/]+=*$\" },\n \"signature\": { \"description\": \"The armored ssh signature, as ssh-keygen -Y sign prints it.\", \"type\": \"string\", \"pattern\": \"^-----BEGIN SSH SIGNATURE-----\\\\n\" }\n }\n },\n \"proposes\": {\n \"description\": \"With a dissent, the id of a new proposed decision the reviewer opened.\",\n \"$ref\": \"#/definitions/decisionId\"\n },\n \"addressed_by\": {\n \"description\": \"With a dissent, where the decider or the group answered the concern: a decision id, an issue or pull request (owner/repo#n) or an https link. An answer addresses a concern without necessarily accommodating it (RFC 7282, section 3).\",\n \"anyOf\": [\n { \"$ref\": \"#/definitions/decisionId\" },\n { \"$ref\": \"#/definitions/issueRef\" },\n { \"type\": \"string\", \"pattern\": \"^https://\" }\n ]\n },\n \"withdrawn_on\": {\n \"description\": \"With a dissent, the date its author withdrew the concern. Only the reviewer who raised a concern withdraws it.\",\n \"type\": \"string\",\n \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\"\n }\n },\n \"dependencies\": { \"seal\": [\"digest\"] },\n \"if\": {\n \"required\": [\"verdict\"],\n \"properties\": { \"verdict\": { \"const\": \"dissent\" } }\n },\n \"then\": {\n \"description\": \"A dissent needs a reason: the dissent by {reviewer} has no note.\",\n \"required\": [\"note\"],\n \"properties\": { \"note\": { \"type\": \"string\", \"pattern\": \"\\\\S\" } }\n },\n \"else\": {\n \"description\": \"Only a dissent is a concern: the {verdict} by {reviewer} cannot carry addressed_by or withdrawn_on.\",\n \"properties\": { \"addressed_by\": false, \"withdrawn_on\": false }\n }\n }\n },\n \"seal\": {\n \"description\": \"The author's ssh signature over the record (#2688): the bytes <id>\\\\n<digest>\\\\n<decided_by>\\\\n<state>, in the ssh-keygen namespace chant-record, where digest is the record's digest, which leaves this field and reviews out. chant workspace records new --sign and amend --sign write it, and an amendment without --sign removes it. Optional: under a signers file at base, a record with decided_by and no seal that verifies is read with the warning record-unattested.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"signer\", \"key\", \"signature\"],\n \"properties\": {\n \"signer\": { \"description\": \"Who signed: decided_by, as the signers file names them.\", \"type\": \"string\", \"minLength\": 1 },\n \"key\": { \"description\": \"The signing key's fingerprint. Reported, never trusted: the signature is the proof.\", \"type\": \"string\", \"pattern\": \"^SHA256:[A-Za-z0-9+/]+=*$\" },\n \"signature\": { \"description\": \"The armored ssh signature, as ssh-keygen -Y sign prints it.\", \"type\": \"string\", \"pattern\": \"^-----BEGIN SSH SIGNATURE-----\\\\n\" }\n }\n },\n \"constrains\": {\n \"description\": \"What this decision governs: issues (owner/repo#n), other decisions (their ids), workspace members (member:<name>) or files and directories in the workspace (path:<path>, the grammar of an evidence path, #2549). At least one entry: a decision that governs nothing is refused.\",\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"string\",\n \"pattern\": \"^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+|[a-z][a-z0-9]{0,15}-[0-9]{3,}|member:[a-z0-9][a-z0-9-]{0,39}|path:(?!/)(?!(?:[^/]*/)*\\\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\\\\\\\u0000-\\\\u001f]*[^/\\\\\\\\\\\\u0000-\\\\u001f])$\"\n }\n }\n },\n \"allOf\": [\n {\n \"if\": {\n \"properties\": { \"state\": { \"enum\": [\"decided\", \"ratified\", \"superseded\"] } }\n },\n \"then\": {\n \"properties\": {\n \"area\": { \"type\": \"string\" },\n \"question\": { \"type\": \"string\", \"minLength\": 1 },\n \"choice\": {\n \"type\": \"object\",\n \"properties\": { \"reason\": { \"type\": \"string\", \"minLength\": 1 } }\n },\n \"rejected\": {\n \"items\": { \"properties\": { \"why\": { \"type\": \"string\", \"minLength\": 1 } } }\n },\n \"options\": {\n \"items\": {\n \"properties\": {\n \"how\": { \"type\": \"string\", \"minLength\": 1 },\n \"tradeoff\": { \"type\": \"string\", \"minLength\": 1 }\n }\n }\n },\n \"decided_by\": { \"type\": \"string\" },\n \"decided_on\": { \"type\": \"string\" }\n }\n }\n },\n {\n \"if\": {\n \"properties\": { \"state\": { \"enum\": [\"proposed\", \"withdrawn\"] } }\n },\n \"then\": {\n \"properties\": { \"choice\": { \"type\": \"null\" } }\n }\n }\n ],\n \"definitions\": {\n \"workspacePath\": {\n \"description\": \"A path from the workspace root, with / separators: no leading /, no . or .. segment, no empty segment, no backslash and no trailing /.\",\n \"type\": \"string\",\n \"pattern\": \"^(?!/)(?!(?:[^/]*/)*\\\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\\\\\\\u0000-\\\\u001f]*[^/\\\\\\\\\\\\u0000-\\\\u001f]$\"\n },\n \"evidenceLink\": {\n \"description\": \"A public link.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"title\", \"url\"],\n \"properties\": {\n \"title\": { \"type\": \"string\", \"minLength\": 1 },\n \"url\": { \"type\": \"string\", \"pattern\": \"^https://\" },\n \"as_of\": {\n \"description\": \"When the linked text was read, as an ISO 8601 timestamp. Lets a reader flag evidence that changed afterwards.\",\n \"type\": [\"string\", \"null\"]\n },\n \"sha256\": {\n \"description\": \"Hex SHA-256 of the linked content, once evidence is pinned by hash (#2555). Null until then.\",\n \"type\": [\"string\", \"null\"],\n \"pattern\": \"^[0-9a-f]{64}$\"\n }\n }\n },\n \"evidencePin\": {\n \"description\": \"A file in the workspace, pinned by the hash of its bytes (#2524 D18, #2549). chant workspace records reports a file that changed or went missing since.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"title\", \"path\", \"sha256\"],\n \"properties\": {\n \"title\": { \"type\": \"string\", \"minLength\": 1 },\n \"path\": {\n \"description\": \"The file, from the workspace root. It sits inside a member, such as design/screens/home.json.\",\n \"$ref\": \"#/definitions/workspacePath\"\n },\n \"sha256\": {\n \"description\": \"Hex SHA-256 of the file's bytes when the decision was written. chant workspace records pin <path> prints it.\",\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n },\n \"as_of\": {\n \"description\": \"When the file was read, as an ISO 8601 timestamp.\",\n \"type\": [\"string\", \"null\"]\n }\n }\n },\n \"sourceIssue\": {\n \"description\": \"A row of the decisions table in an issue.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"issue\", \"row\"],\n \"properties\": {\n \"issue\": { \"$ref\": \"#/definitions/issueRef\" },\n \"row\": {\n \"description\": \"The row's topic cell, verbatim, including any revision marker.\",\n \"type\": \"string\"\n },\n \"revision\": {\n \"description\": \"The design revision that last changed this row, such as \\\"v8\\\". Null when the row carries no marker.\",\n \"type\": [\"string\", \"null\"]\n },\n \"session\": { \"$ref\": \"#/definitions/sourceSession\" },\n \"via\": { \"$ref\": \"#/definitions/sourceVia\" },\n \"client\": { \"$ref\": \"#/definitions/sourceClient\" },\n \"harness\": { \"$ref\": \"#/definitions/sourceHarnessId\" },\n \"model\": { \"$ref\": \"#/definitions/sourceModel\" },\n \"turns\": { \"$ref\": \"#/definitions/sourceTurns\" },\n \"transcript\": { \"$ref\": \"#/definitions/sourceTranscript\" }\n }\n },\n \"sourceWorkspace\": {\n \"description\": \"A decision made in the workspace, such as in a product's own design flow, with no issue behind it (#2654).\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"kind\", \"member\"],\n \"properties\": {\n \"kind\": { \"const\": \"workspace\" },\n \"member\": {\n \"description\": \"The workspace member the decision was made in, by its name in the declaration.\",\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]{0,39}$\"\n },\n \"session\": {\n \"description\": \"The id of the session the decision was made in, as the member names it, or the harness's session with the chant session record it was held in. Null or absent when there was none.\",\n \"$ref\": \"#/definitions/sourceSession\"\n },\n \"issue\": {\n \"description\": \"An issue the decision relates to, when there is one.\",\n \"$ref\": \"#/definitions/issueRef\"\n },\n \"via\": { \"$ref\": \"#/definitions/sourceVia\" },\n \"client\": { \"$ref\": \"#/definitions/sourceClient\" },\n \"harness\": { \"$ref\": \"#/definitions/sourceHarnessId\" },\n \"model\": { \"$ref\": \"#/definitions/sourceModel\" },\n \"turns\": { \"$ref\": \"#/definitions/sourceTurns\" },\n \"transcript\": { \"$ref\": \"#/definitions/sourceTranscript\" }\n }\n },\n \"sourceHarness\": {\n \"description\": \"A decision proposed in a harness session, with no issue row or workspace member behind it (#2708). It says how it arrived, in via, and may say the rest of where the proposal came from.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"via\"],\n \"properties\": {\n \"issue\": {\n \"description\": \"An issue the decision relates to, when there is one.\",\n \"$ref\": \"#/definitions/issueRef\"\n },\n \"session\": { \"$ref\": \"#/definitions/sourceSession\" },\n \"via\": { \"$ref\": \"#/definitions/sourceVia\" },\n \"client\": { \"$ref\": \"#/definitions/sourceClient\" },\n \"harness\": { \"$ref\": \"#/definitions/sourceHarnessId\" },\n \"model\": { \"$ref\": \"#/definitions/sourceModel\" },\n \"turns\": { \"$ref\": \"#/definitions/sourceTurns\" },\n \"transcript\": { \"$ref\": \"#/definitions/sourceTranscript\" }\n }\n },\n \"sourceVia\": {\n \"description\": \"How the record reached the workspace: cli, a person or script at chant workspace records new; mcp, a harness through chant serve mcp, which fills this and client itself; harvest, a transcript read after the fact. A harvested decision is written proposed, and a person decides it. Data about the proposal, not trust.\",\n \"enum\": [\"cli\", \"mcp\", \"harvest\"]\n },\n \"sourceClient\": {\n \"description\": \"What wrote the record: the MCP client's clientInfo, or the CLI.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"name\"],\n \"properties\": {\n \"name\": { \"type\": \"string\", \"minLength\": 1 },\n \"version\": { \"type\": \"string\", \"minLength\": 1 },\n \"title\": { \"type\": \"string\", \"minLength\": 1 }\n }\n },\n \"sourceHarnessId\": {\n \"description\": \"The harness the decision was proposed in, such as claude-code, codex, gemini-cli, opencode, fountain or hud.\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"sourceModel\": {\n \"description\": \"The model id the harness reports.\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"sourceSession\": {\n \"description\": \"The session the decision was made in: its id as a string, or an object with the harness's session or conversation id and the chant session record it was held in (#2697). Null when there was none.\",\n \"oneOf\": [\n { \"type\": \"string\", \"minLength\": 1 },\n { \"type\": \"null\" },\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"minProperties\": 1,\n \"properties\": {\n \"id\": { \"description\": \"The harness's session or conversation id.\", \"type\": \"string\", \"minLength\": 1 },\n \"record\": { \"description\": \"The id of the chant session record the decision was held in.\", \"type\": \"string\", \"minLength\": 1 }\n }\n }\n ]\n },\n \"sourceTurns\": {\n \"description\": \"The turns of the session the decision was made in, from and to, counted as the harness counts them. to is not before from.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"from\", \"to\"],\n \"properties\": {\n \"from\": { \"type\": \"integer\", \"minimum\": 0 },\n \"to\": { \"type\": \"integer\", \"minimum\": 0 }\n }\n },\n \"sourceTranscript\": {\n \"description\": \"The session's transcript, pinned by a path or URI and the lowercase hex SHA-256 of its bytes, never copied into the record. A reader checks the transcript it holds is the one meant; chant workspace records warns source-transcript-drift when the file can be read here and hashes to something else. A path is absolute, starts ~/ from the home directory, or starts at the workspace root; of URIs, only file: ones are read.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"sha256\"],\n \"properties\": {\n \"path\": { \"type\": \"string\", \"minLength\": 1 },\n \"uri\": { \"type\": \"string\", \"minLength\": 1 },\n \"sha256\": { \"type\": \"string\", \"pattern\": \"^[0-9a-f]{64}$\" }\n },\n \"oneOf\": [{ \"required\": [\"path\"] }, { \"required\": [\"uri\"] }]\n },\n \"issueRef\": {\n \"type\": \"string\",\n \"pattern\": \"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+$\"\n },\n \"decisionId\": {\n \"type\": \"string\",\n \"pattern\": \"^[a-z][a-z0-9]{0,15}-[0-9]{3,}$\"\n },\n \"optionId\": {\n \"type\": \"string\",\n \"pattern\": \"^[a-z]$\"\n },\n \"option\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\"id\", \"label\", \"how\", \"tradeoff\"],\n \"properties\": {\n \"id\": { \"$ref\": \"#/definitions/optionId\" },\n \"label\": {\n \"description\": \"The option as the source table words it, without revision markers.\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"how\": {\n \"description\": \"How the option works.\",\n \"type\": [\"string\", \"null\"]\n },\n \"tradeoff\": {\n \"description\": \"What choosing it costs or gains.\",\n \"type\": [\"string\", \"null\"]\n },\n \"chosen_in\": {\n \"description\": \"The earlier design revision that had chosen this option, when a later revision replaced it.\",\n \"type\": \"string\",\n \"pattern\": \"^v[0-9]+$\"\n }\n }\n }\n }\n}\n",
23
+ "decisions/ref-001-how-the-app-is-deployed.md": "---\nschema: 1\nid: \"ref-001\"\ntitle: \"How the app is deployed\"\nstate: \"decided\"\narea: \"delivery\"\nsource:\n issue: \"INTENTIUS/chant#2543\"\n row: \"How the app is deployed\"\n revision: null\nquestion: \"What builds the app's image and runs it?\"\noptions:\n - id: \"a\"\n label: \"a chant project with the docker lexicon\"\n how: \"The delivery member is a chant project. It declares the app as a Docker Compose service built from the app's own Dockerfile, and `chant build` writes the compose file.\"\n tradeoff: \"The deployment is typed, linted and built by chant, so it exercises a real `chant` member. The app and its deployment are two members, joined only by a build-context path until member links land.\"\n - id: \"b\"\n label: \"a hand-written compose file in the app\"\n how: \"The app directory holds its own `docker-compose.yml`, written and kept by hand.\"\n tradeoff: \"One member fewer and nothing to build. Nothing checks the file, and the workspace would have no `chant` member to test.\"\n - id: \"c\"\n label: \"a hosted platform lexicon\"\n how: \"The delivery member deploys the app to a hosted platform through a lexicon such as fly or render.\"\n tradeoff: \"Closer to production. CI would need an account or an emulator for a check that only has to prove the member builds.\"\nchoice:\n option: \"a\"\n reason: \"The reference workspace needs a `chant` member that CI can build and lint offline, and Compose needs no account or emulator. Keeping the Dockerfile in the app lets the app build on its own, whatever deploys it.\"\nrejected:\n - option: \"b\"\n why: \"It leaves the workspace without a `chant` member, and nothing would check the file.\"\n - option: \"c\"\n why: \"CI would need a platform account or emulator for a fixture whose job is to build and lint.\"\nsupersedes: []\nevidence:\n - title: \"INTENTIUS/chant#2543, the reference spec-and-skeleton workspace as a walking skeleton\"\n url: \"https://github.com/INTENTIUS/chant/issues/2543\"\n as_of: \"2026-09-24T01:50:08Z\"\n - title: \"INTENTIUS/chant#2524, D19. Delivery: CI, releases, environments\"\n url: \"https://github.com/INTENTIUS/chant/issues/2524#d19-delivery-ci-releases-environments\"\n as_of: \"2026-09-24T01:50:08Z\"\ndecided_by: \"lex00\"\ndecided_on: \"2026-09-23\"\nreviews: []\nconstrains:\n - \"member:app\"\n - \"member:delivery\"\n---\n\n# How the app is deployed\n",
24
+ "decisions/ref-002-where-the-screen-design-lives.md": "---\nschema: 1\nid: \"ref-002\"\ntitle: \"Where the screen design lives\"\nstate: \"decided\"\narea: \"design\"\nsource:\n issue: \"INTENTIUS/chant#2543\"\n row: \"Where the screen design lives\"\n revision: null\nquestion: \"Which member holds the spec and wireframe for the app's home screen?\"\noptions:\n - id: \"a\"\n label: \"the design data member\"\n how: \"`design/screens/` holds the screen spec and its wireframe. The app implements the spec, and the design client edits it.\"\n tradeoff: \"Upgrading the design client never touches the design, and records can pin the files once #2549 lands. The app reads its spec from another member.\"\n - id: \"b\"\n label: \"inside the app\"\n how: \"The app keeps its screen spec next to its code.\"\n tradeoff: \"The spec sits beside what implements it. The design client would edit files inside the product's own member.\"\n - id: \"c\"\n label: \"inside the design client\"\n how: \"The client stores the designs it edits in its own directory.\"\n tradeoff: \"No extra member. The client's lineage scope, which an upgrade writes into, would hold user work.\"\nchoice:\n option: \"a\"\n reason: \"This follows ws-024: artifacts live in a `design` data member so an upgrade of the client, which writes only inside the client's scope, cannot overwrite them.\"\nrejected:\n - option: \"b\"\n why: \"The design client would have to write into the product's member to edit a screen.\"\n - option: \"c\"\n why: \"An upgrade of the client writes into the directory that would hold the designs, which ws-024 rules out.\"\nsupersedes: []\nevidence:\n - title: \"INTENTIUS/chant#2524, D18. Design apps\"\n url: \"https://github.com/INTENTIUS/chant/issues/2524#d18-design-apps\"\n as_of: \"2026-09-24T01:50:08Z\"\n - title: \"ws-024, Design app\"\n url: \"https://github.com/INTENTIUS/chant/blob/main/docs/design/decisions/ws-024-design-app.md\"\n as_of: \"2026-09-24T01:50:08Z\"\n - title: \"The home screen spec\"\n path: \"design/screens/home.json\"\n sha256: \"074e55f524703fe65ecba4cf0e2cd3200e21f969a1f789618e22ff9537dd99e0\"\n as_of: \"2026-09-24T12:00:00Z\"\ndecided_by: \"lex00\"\ndecided_on: \"2026-09-23\"\nreviews: []\nconstrains:\n - \"member:app\"\n - \"member:design\"\n - \"member:design-client\"\n---\n\n# Where the screen design lives\n",
25
+ "delivery/.chant/policies/write-scope.ts": "/**\n * CHUD-WRITE-SCOPE: only the design app writes the reference the work is\n * judged against.\n *\n * The design member's contracts/ (and their checks), units/, evidence/,\n * drivers/, context/ and sessions/, the workspace's decisions/ (the decision\n * records), this project's decisions/,\n * .chant/ and chant.config.ts, where this guard is declared, and\n * chant.workspace.json, plus any path added under `chud.protectedPaths` in\n * chant.config.ts (root-relative), are written by the design app, and each\n * commit it makes carries the trailer `Chud-Author: design-app`. A commit\n * that changes one of them without the trailer was made some other way: by\n * hand, or by an agent that committed on its own. This check names each such\n * commit and the protected paths it changes.\n *\n * A person can let such a change through in two signed ways, both decision\n * records in the workspace's decisions/ that the design app writes:\n * - \"Commit all\" on the Ship tab, acknowledging the protected paths it\n * includes. The commit's trailer names the person\n * (`Chud-Author: design-app; human=<name>`) and a `protected-commit`\n * record lists the paths. Reported as a warning.\n * - A `scope-override` record approving commits by sha, for commits made\n * outside the design app. Reported as a warning naming the record.\n *\n * It is a policy (a PostSynthCheck under `lint.policies`) rather than a lint\n * rule, because it is about the repo's history, not a source file. The release\n * plan step builds the commit it would ship (so this file and chant.config.ts\n * are as that commit has them, not as the working tree does), reads the\n * commits since the serving release and the override records, and leaves them\n * in .chud/scope-facts.json for this check, with where the workspace's\n * members are (paths in git are relative to the workspace root, above this\n * project). The check runs no git and reads only that file, so it also runs\n * under `chant build --sandbox`. A plain `chant build` or `npm run agents` has\n * no facts file, and finds nothing.\n */\nimport type { PostSynthCheck, PostSynthDiagnostic } from \"@intentius/chant/lint/post-synth\";\nimport { AUTHOR_KEY, DESIGN_APP, POLICY_ID, judge, protectedPaths, readFacts } from \"@intentius/chud-runtime/scope\";\nimport config from \"../../chant.config.ts\";\n\nexport const writeScope: PostSynthCheck = {\n id: POLICY_ID,\n description: `Commits that change a protected path carry ${AUTHOR_KEY}: ${DESIGN_APP}`,\n check(): PostSynthDiagnostic[] {\n // chant runs policies in the project directory, sandboxed or not.\n const facts = readFacts(process.cwd());\n if (!facts) return [];\n const paths = protectedPaths(config.chud?.protectedPaths, facts.layout);\n const { refused, overridden, acknowledged } = judge(facts.commits, paths, facts.overrides, facts.layout);\n const finding = (c: { commit: string }, severity: \"error\" | \"warning\", message: string): PostSynthDiagnostic => ({\n checkId: POLICY_ID, severity, entity: c.commit.slice(0, 12), message,\n });\n return [\n ...refused.map((c) => finding(c, \"error\", `commit \"${c.subject}\" by ${c.author} changes ${c.paths.join(\", \")} without the ${AUTHOR_KEY}: ${DESIGN_APP} trailer. Protected paths are written only by the design app.`)),\n ...overridden.map((c) => finding(c, \"warning\", `commit \"${c.subject}\" by ${c.author} changes ${c.paths.join(\", \")} without the ${AUTHOR_KEY}: ${DESIGN_APP} trailer; passed by override ${c.override.id}, signed by ${c.override.by}.`)),\n ...acknowledged.map((c) => finding(c, \"warning\", `commit \"${c.subject}\" changes ${c.paths.join(\", \")} by hand, acknowledged by ${c.human} on \"Commit all\".`)),\n ];\n },\n};\n",
26
+ "delivery/.chant/rules/contract-sizing.ts": "/**\n * CHUD001, CHUD002, CHUD003: contracts are sized for the builder tier they ask for.\n *\n * chant lint reads TypeScript, and contracts are markdown. So these rules are\n * anchored on the file that declares the builders (agents/team.ts: the Agents\n * with metadata \"chud/role\": \"build\", one per \"chud/tier\"). When chant lints\n * that file, the rules read every contract in the design member's contracts/\n * (../design/contracts from this project) through the runtime's\n * record parser (front matter validated against the contract schema), measure\n * it, and report against the contract's own file and line. A contract that\n * fails its schema is left to the schema: the design app and the release plan\n * report it.\n *\n * The measuring and the thresholds live in @intentius/chud-runtime/sizing, so\n * the design app warns with exactly what `chant lint` reports. Thresholds are\n * the rules' options in chant.config.ts (`lint.rules.CHUD001`, `CHUD002`).\n *\n * CHUD001 a contract holds more (criteria, files named, words) than its tier allows\n * CHUD002 a contract asks for tier large but fits tier small\n * CHUD003 a contract asks for a tier no builder Agent is declared for (when\n * the builders are declared per tier at all)\n */\nimport { dirname, join, resolve } from \"node:path\";\nimport * as ts from \"typescript\";\nimport type { LintContext, LintDiagnostic, LintRule } from \"@intentius/chant\";\nimport { findRoot } from \"@intentius/chud-runtime/layout\";\nimport { contractsIn, lineOf, RULES, sizeFindings, wasteFindings } from \"@intentius/chud-runtime/sizing\";\n\ntype Contract = ReturnType<typeof contractsIn>[number];\n\n/** The builder tiers this file declares, or null when it declares no builder. */\nfunction declaredTiers(sourceFile: ts.SourceFile): Set<string> | null {\n let builds = false;\n const tiers = new Set<string>();\n const visit = (node: ts.Node): void => {\n if (ts.isObjectLiteralExpression(node)) {\n const values = new Map<string, string>();\n for (const prop of node.properties) {\n if (!ts.isPropertyAssignment(prop)) continue;\n const key = ts.isStringLiteral(prop.name) || ts.isIdentifier(prop.name) ? prop.name.text : undefined;\n if (key && ts.isStringLiteral(prop.initializer)) values.set(key, prop.initializer.text);\n }\n if (values.get(\"chud/role\") === \"build\") {\n builds = true;\n const tier = values.get(\"chud/tier\");\n if (tier) tiers.add(tier);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return builds ? tiers : null;\n}\n\n/**\n * The workspace root: the nearest directory above the file with a\n * chant.workspace.json. The contracts are in its design member, and their\n * `file` is relative to it.\n */\nfunction repoRoot(file: string): string | null {\n try {\n return findRoot(dirname(resolve(file)));\n } catch {\n return null;\n }\n}\n\n/** The contracts to check when `context` is the file that declares the builders. */\nfunction contractsFor(context: LintContext): { root: string; tiers: Set<string>; contracts: Contract[] } | null {\n const tiers = declaredTiers(context.sourceFile);\n if (!tiers) return null;\n const root = repoRoot(context.filePath);\n if (!root) return null;\n return { root, tiers, contracts: contractsIn(root) };\n}\n\nfunction diagnostic(root: string, c: Contract, ruleId: string, severity: LintDiagnostic[\"severity\"], message: string, key: string): LintDiagnostic {\n return { file: join(root, c.file), line: lineOf(c.text, key), column: 1, ruleId, severity, message };\n}\n\nexport const contractSizeRule: LintRule = {\n id: RULES.size,\n severity: \"warning\",\n category: \"performance\",\n description: \"A contract must fit the builder tier it asks for (criteria, files named, words)\",\n check(context, options) {\n const found = contractsFor(context);\n if (!found) return [];\n return found.contracts.flatMap((c) =>\n sizeFindings(c, options).map((f) => diagnostic(found.root, c, RULES.size, this.severity, f.message, c.meta.tier ? \"tier\" : \"id\")),\n );\n },\n};\n\nexport const modelWasteRule: LintRule = {\n id: RULES.waste,\n severity: \"warning\",\n category: \"performance\",\n description: \"A contract that fits the small tier should not ask for the large one\",\n check(context, options) {\n const found = contractsFor(context);\n if (!found) return [];\n return found.contracts.flatMap((c) =>\n wasteFindings(c, options).map((f) => diagnostic(found.root, c, RULES.waste, this.severity, f.message, \"tier\")),\n );\n },\n};\n\nexport const tierAgentRule: LintRule = {\n id: RULES.agent,\n severity: \"error\",\n category: \"correctness\",\n description: \"Every tier a contract asks for has a builder Agent declared with that chud/tier\",\n check(context) {\n const found = contractsFor(context);\n // A repo whose one builder names no tier runs it for every contract, as the design app does.\n if (!found || found.tiers.size === 0) return [];\n return found.contracts\n .filter((c) => !found.tiers.has(c.tier))\n .map((c) => diagnostic(found.root, c, RULES.agent, this.severity,\n `${c.id} asks for tier ${c.tier}, and no builder Agent declares \"chud/tier\": \"${c.tier}\" (declared: ${[...found.tiers].join(\", \") || \"none\"}).`, c.meta.tier ? \"tier\" : \"id\"));\n },\n};\n",
27
+ "delivery/.npmrc": "install-links=true\n",
28
+ "delivery/agents/team.ts": "/**\n * The agents that build {{chant:name}}, declared with chant's fountain lexicon.\n *\n * `npm run agents` builds these into fountain manifests\n * (.chud/agents.fountain.yaml); `fountain apply -f` puts them on a Fountain\n * account. Locally, the design app runs them as Claude Code with the model,\n * description and system prompt declared here. The dispatcher (ops/dispatch.op.ts,\n * `chud factory run`) runs the builders the same way, or on Fountain.\n */\nimport { Agent, Environment, Schedule, Teammate, Vault } from \"@intentius/chant-lexicon-fountain\";\nimport { appSlug } from \"../app-name.ts\";\n\nexport const benchPackages = { apt: [\"git\"] };\n\nexport const benchNetwork = { allowed_hosts: [\"registry.npmjs.org\"] };\n\n/**\n * git answers an HTTPS host's password prompt with GIT_TOKEN, the secret the\n * git Vault below puts in the sandbox, read when git asks (not when this runs).\n */\nexport const benchSetup =\n \"git config --global credential.helper '!f() { test \\\"$1\\\" = get && echo username=x-access-token && echo \\\"password=$GIT_TOKEN\\\"; }; f'\";\n\nexport const bench = new Environment({\n name: `${appSlug}-bench`,\n packages: benchPackages,\n networking_type: \"limited\",\n networking_config: benchNetwork,\n setup_script: benchSetup,\n});\n\nexport const gitVaultMeta = { \"chud/role\": \"git\", \"chud/secret-keys\": \"GIT_TOKEN\" };\n\n/**\n * The credential the builders and the dispatcher reach the git host with: a\n * token for the repo, which a sandbox the Vault is attached to gets as\n * GIT_TOKEN, to clone the unit's commit and push its work (ops/dispatch.op.ts).\n * Only the secret's name is here. Its value is the reference\n * `${CHUD_GIT_TOKEN}`, which `fountain apply` fills in from the environment it\n * runs in (or `--var CHUD_GIT_TOKEN=...`), so the built manifest holds no\n * secret. A secret-manager reference (`op://...`, `bws://...`,\n * `infisical://...`) works the same way. The builders and the dispatcher admit\n * only this Vault (allowed_vault_ids). The dispatcher attaches it to each\n * build by this name (CHUD_FOUNTAIN_VAULT overrides it), and the two\n * Teammates below run with it.\n */\nexport const gitVaultSecrets = [{ key: \"GIT_TOKEN\", value: \"${CHUD_GIT_TOKEN}\" }];\n\nexport const gitVault = new Vault({\n name: `${appSlug}-git`,\n description: \"The token the builders and the dispatcher clone and push the repo with.\",\n secrets: gitVaultSecrets,\n metadata: gitVaultMeta,\n});\n\nexport const formalizerMeta = { \"chud/role\": \"formalize\" };\n\nexport const formalizer = new Agent({\n name: \"formalizer\",\n runtime: \"claude\",\n model: \"anthropic/claude-sonnet-5\",\n environment: bench,\n description: \"Turns a contract's acceptance criteria into an executable check that fails until the app meets them.\",\n system:\n \"You write acceptance checks for {{chant:name}}. A check is a node:test file that talks to the running app over HTTP at process.env.APP_URL. Test behaviour a user would see, one test per criterion, named after it. Never edit the app.\",\n metadata: formalizerMeta,\n});\n\nexport const splitterMeta = { \"chud/role\": \"split\" };\n\n/**\n * Splits a driver into draft contracts, each with its story, criteria and\n * depends_on. The design app runs it from a driver, and throws away anything\n * it writes other than new draft contracts. People approve the split as a\n * whole before any of it is built.\n */\nexport const splitter = new Agent({\n name: \"splitter\",\n runtime: \"claude\",\n model: \"anthropic/claude-sonnet-5\",\n environment: bench,\n description: \"Splits a driver into small draft contracts with stories, criteria and dependencies, for people to approve as a whole.\",\n system:\n \"You split work on {{chant:name}} into contracts. Analyse the driver's work first: the distinct pieces, what each needs and produces, and which depend on which. Then write one draft contract per piece in design/contracts/, each small enough for a haiku-class builder, with criteria a user can observe over HTTP, and depends_on naming only what must be done first. Write nothing but those new draft contract files.\",\n metadata: splitterMeta,\n});\n\n/**\n * The builders, one per tier. A contract names its tier (`tier: small |\n * medium | large` in its front matter, small when it names none), and the\n * design app runs the builder whose `chud/tier` matches. Each tier's model is\n * declared here and nowhere else. Small is haiku-class: contracts are sized so\n * a small builder can do them, and the lint rules in .chant/rules/ (run by\n * `chant lint`) warn when one is too big for its tier, or when a large tier is\n * spent on a contract a small one could do.\n */\nconst builderSystem =\n \"You build {{chant:name}} in app/. Read CLAUDE.md and app/CLAUDE.md first. Make the contract's check pass without editing design/contracts/, decisions/, design/units/, design/evidence/, design/drivers/, design/context/, design/sessions/, delivery/decisions/, delivery/.chant/, delivery/chant.config.ts or chant.workspace.json: chud throws away changes there and fails the run. Run the app's tests before you finish.\";\n\nexport const builderSmallMeta = { \"chud/role\": \"build\", \"chud/tier\": \"small\" };\n\nexport const builderSmall = new Agent({\n name: \"builder-small\",\n runtime: \"claude\",\n model: \"anthropic/claude-haiku-4-5\",\n environment: bench,\n allowed_vault_ids: [gitVault],\n description: \"Builds the app until an approved small contract's check passes.\",\n system: builderSystem,\n metadata: builderSmallMeta,\n});\n\nexport const builderMediumMeta = { \"chud/role\": \"build\", \"chud/tier\": \"medium\" };\n\nexport const builderMedium = new Agent({\n name: \"builder-medium\",\n runtime: \"claude\",\n model: \"anthropic/claude-sonnet-5\",\n environment: bench,\n allowed_vault_ids: [gitVault],\n description: \"Builds the app until an approved medium contract's check passes.\",\n system: builderSystem,\n metadata: builderMediumMeta,\n});\n\nexport const builderLargeMeta = { \"chud/role\": \"build\", \"chud/tier\": \"large\" };\n\nexport const builderLarge = new Agent({\n name: \"builder-large\",\n runtime: \"claude\",\n model: \"anthropic/claude-opus-5\",\n environment: bench,\n allowed_vault_ids: [gitVault],\n description: \"Builds the app until an approved large contract's check passes.\",\n system: builderSystem,\n metadata: builderLargeMeta,\n});\n\nexport const nightShift = new Teammate({\n agent: builderMedium,\n name: \"night-shift\",\n vault: gitVault,\n});\n\nexport const nightlyMeta = { \"chud/risk\": \"low\" };\n\nexport const nightly = new Schedule({\n name: \"nightly-hygiene\",\n teammate: nightShift,\n cron: \"0 3 * * *\",\n prompt: \"Run the app's tests and every approved contract check. Fix anything failing. If nothing needs changing, change nothing.\",\n metadata: nightlyMeta,\n});\n\nexport const dispatcherMeta = { \"chud/role\": \"dispatch\" };\n\nexport const dispatcherPermissions = { default: \"auto_allow\" };\n\n/**\n * The factory's seat. The dispatcher is an Op (ops/dispatch.op.ts), not a\n * model: it claims the next ready contract with a lease, runs the builder\n * declared above for that contract's tier, runs its check and records the\n * unit. So its Agent speaks the Agent Client Protocol over `chant acp`, which\n * reads each prompt as a chant command line, the way the fountain lexicon's\n * Steward does: a persistent sandbox (its checkout survives a turn) and no\n * permission prompts (nobody is at the keyboard; chant's own gates still need\n * a person). Its Teammate gives it one thread, so two schedule fires never\n * run two dispatchers on one checkout, and the Schedule is what triggers it.\n */\nexport const dispatcher = new Agent({\n name: \"dispatcher\",\n runtime: \"acp\",\n runtime_command: \"chant acp\",\n sandbox_mode: \"persistent\",\n permission_policy: dispatcherPermissions,\n environment: bench,\n allowed_vault_ids: [gitVault],\n description: \"Runs the factory: claims ready contracts with leases, builds each with its declared builder, checks it and records the unit.\",\n metadata: dispatcherMeta,\n});\n\nexport const factory = new Teammate({\n agent: dispatcher,\n name: \"factory\",\n vault: gitVault,\n});\n\nexport const factoryDispatch = new Schedule({\n name: \"factory-dispatch\",\n teammate: factory,\n cron: \"*/15 * * * *\",\n // The line a person would type: `chant acp` runs it (the fountain lexicon's runPrompt(\"dispatch\")).\n prompt: \"chant run dispatch\",\n enabled: true,\n one_off: false,\n});\n",
29
+ "delivery/app-name.ts": "/**\n * The app's name, as `chant init --from jhgaylor/chud@<ref>#template --param\n * name=\"...\"` gave it, and the slug made from it for names that allow only\n * lowercase letters, digits and dashes: the Fly app (deploy/fly.ts) and the\n * fountain Environment and Vault (agents/team.ts). Fly app names are global,\n * so change `appSlug` before the first release to a real Fly org if it is\n * taken there.\n */\nexport const appName = \"{{chant:name}}\";\n\nexport const appSlug = appName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\") || \"app\";\n",
30
+ "delivery/chant.config.ts": "import \"@intentius/chant-lexicon-fountain\";\nimport type { ChantConfig } from \"@intentius/chant\";\n\n// chant's config accepts keys of its own beyond ChantConfig (its schema is\n// passthrough); `chud` holds this repo's settings that are not scalars.\n// Intersected rather than cast, so every other field stays checked.\ntype Config = ChantConfig & {\n chud?: {\n /**\n * Paths only the design app writes, added to chud's defaults (the design\n * member's contracts/, decisions/, units/, evidence/, drivers/, context/\n * and sessions/, this project's decisions/, .chant/ and this file, which\n * declare the guard, and chant.workspace.json). Relative to the workspace\n * root, as git names them: \"design/screens/\", not \"screens/\". A release\n * plan refuses a commit that changes one of them without the\n * `Chud-Author: design-app` trailer, and an agent run's change to one is\n * thrown away.\n */\n protectedPaths?: string[];\n /**\n * Where releases ship, by environment: `{ kind: \"local\" }` is this\n * machine, `{ kind: \"fly\" }` the Fly app deploy/fly.ts declares. The\n * release and rollback Ops are the same for every site; `--env <name>`\n * (or CHUD_ENV) picks one, the first by default. A fly site's\n * `lifecycle` is the git remote its approvals, effect receipts and ledger\n * records are pushed to (default \"origin\"), and its `url` where its\n * health endpoint answers, when it has one.\n */\n sites?: Record<string, { kind: \"local\" } | { kind: \"fly\"; lifecycle?: string; url?: string }>;\n /**\n * Decision records are chant's decision record kind, in the workspace's\n * decisions/ (`chant workspace records --kind decisions/decision.kind.mjs`).\n * That format names the issue each record came from: the design app cites\n * `issue` (owner/repo#n) as the source of every record it writes, and\n * links it as evidence.\n */\n decisions?: { issue: string };\n };\n};\n\n// The workspace's chant project (the `delivery` member of chant.workspace.json;\n// the app is the `app` member, and chud's records are the `design` member).\n// The fountain lexicon for the agents\n// in agents/, the Ops in ops/ (found by their *.op.ts names), the chud\n// lexicon for the sites releases ship to, the fly lexicon for the remote\n// one (deploy/fly.ts), and the cedar lexicon for the ship gate's approval\n// policy (decisions/ship-skip.cedar.ts), so `chant build` serializes and checks\n// it like the rest. The github lexicon is for this repo's CI (ci/ci.ts), which\n// `npm run ci:build` writes to .github/workflows/ci.yml; adding it leaves the\n// build of everything else as it was. The app is a chant component\n// (deploy/app.component.ts) composed of the chud lexicon's capabilities;\n// `sourceDir` is where chant finds it and the site it deploys to, so\n// `chant list --components` and `chant components status --live` read deploy/.\n//\n// `lint.rules` configures the contract-sizing rules in .chant/rules/, which\n// `chant lint` runs (see there). The thresholds are how much a contract may\n// hold for each builder tier: acceptance criteria, files it names, and words.\n// Small is what a haiku-class builder can do (chant-old's defaults); large has\n// no limit. The design app reads the same options, so it warns alike.\n//\n// buildParams are this repo's settings: its name, its ports, and how to run,\n// migrate and test the app. chud (and @intentius/chud-runtime) reads them from\n// here. A param with `env` can be overridden by that environment variable.\n// Where the app is comes from chant.workspace.json (the `app` member), not\n// from here; the commands run in it.\nconst sizing = {\n small: { criteria: 10, files: 5, words: 150 },\n medium: { criteria: 20, files: 10, words: 300 },\n};\n\nexport default {\n lexicons: [\"fountain\", \"chud\", \"fly\", \"cedar\", \"github\"],\n sourceDir: \"deploy\",\n lint: {\n rules: {\n CHUD001: [\"warning\", sizing],\n CHUD002: [\"warning\", sizing],\n },\n // CHUD-WRITE-SCOPE: the release plan runs it (through `chant build`) over\n // the commits it would ship. See the file.\n policies: [\".chant/policies/write-scope.ts\"],\n },\n // Where releases ship: prod, the local site under .chud/site, and fly, a Fly\n // app (deploy/fly.ts). `npm run release` ships to prod; `npm run release --\n // --env fly` to fly, with FLY_API_TOKEN set (FLY_FLAPS_BASE_URL points it at\n // mudflaps, the Machines API emulator, instead of Fly).\n environments: [\"prod\", \"fly\"],\n buildParams: {\n name: { type: \"string\", default: \"{{chant:name}}\", description: \"The app's name, as the pages show it\" },\n devPort: { type: \"number\", default: 3000, env: \"CHUD_DEV_PORT\", description: \"chud dev: the app, with hud\" },\n designPort: { type: \"number\", default: 3100, env: \"CHUD_DESIGN_PORT\", description: \"chud design: the design app, with hud\" },\n prodPort: { type: \"number\", default: 3200, env: \"CHUD_PROD_PORT\", description: \"The prod site the release Op ships to\" },\n appDev: { type: \"string\", default: \"node --watch --disable-warning=ExperimentalWarning server.js\", description: \"Runs the app under chud dev\" },\n appStart: { type: \"string\", default: \"node --disable-warning=ExperimentalWarning server.js\", description: \"Runs a release on the site\" },\n appMigrate: { type: \"string\", default: \"node --disable-warning=ExperimentalWarning migrate.js\", description: \"Applies migrations (one file when given its name)\" },\n appMigrations: { type: \"string\", default: \"migrations\", description: \"The migrations folder, inside the app member\" },\n appTest: { type: \"string\", default: \"npm test --silent\", description: \"The app's own tests, run before every release\" },\n appHealth: { type: \"string\", default: \"/health\", description: \"The path a release is verified by\" },\n vulnDb: { type: \"string\", default: \"\", env: \"CHUD_VULN_DB\", description: \"A pinned advisory database (JSON) the release scans the SBOM against, offline; empty: grype or trivy\" },\n },\n // The release's supply chain (deploy/app.component.ts): each release builds the app at HEAD as\n // one archive (.chud/builds/<commit>/app.tar), makes its SBOM, scans it, and stops before the\n // ship gate, listing the findings, when the SBOM violates vulnPolicy. The plan digest covers the\n // SBOM's hash, so an approval binds to exactly what was scanned. After the gate the archive is\n // signed, and the signature is verified before the site runs anything in it.\n //\n // The SBOM comes from the app's package-lock.json (chant's hermetic lockfile generator).\n sbom: { format: \"spdx\" },\n // Signed with this machine's key: a PEM private key, its public half beside it as .pub. The path\n // is relative to the workspace root; a key under its .chud/ (never committed) is made on the first release. For keyless signing in CI, set\n // `keyless: true` with `oidcIssuer` and `identity` (who may have signed); that needs cosign.\n signing: { keyless: false, key: \".chud/keys/release.key\" },\n // chant's vuln-gate: block high and critical findings that have a fix, and anything in CISA's\n // KEV catalog; warn on medium. The findings come from the advisory database `vulnDb` names\n // (buildParams, below) when it names one, else from grype or trivy, which must then be\n // installed: a release is never waved through unscanned.\n vulnPolicy: { failSeverity: \"high\", fixableOnly: true, warnSeverity: \"medium\", failOnKev: true },\n chud: {\n protectedPaths: [],\n decisions: { issue: \"{{chant:issue}}\" },\n sites: {\n prod: { kind: \"local\" },\n fly: { kind: \"fly\", lifecycle: \"origin\" },\n },\n },\n} satisfies Config;\n",
31
+ "delivery/ci/ci.ts": "// This repo's CI, declared with chant's github lexicon. `npm run ci:build` (in\n// delivery/) serializes it to .github/workflows/ci.yml at the workspace root;\n// commit both. The workflow's \"Workflow YAML is built from ci/ci.ts\" step\n// rebuilds it and fails when the committed YAML differs.\n//\n// On every push to main and every pull request it installs the delivery\n// project, checks the workspace declaration (`chant workspace check`), runs\n// `chant build` and `chant lint` over the project, then `npm run check`: the\n// app's own tests and every approved contract's check, run by\n// @intentius/chud-runtime the way a release's Check phase runs them (records\n// valid, approved contracts unchanged since approval), without recording\n// evidence. Every step but the checkout runs in delivery/.\n//\n// @intentius/chud-runtime is installed from a private GitHub repo (its git tag,\n// package.json), which the workflow's own GITHUB_TOKEN cannot read. Give CI\n// read access with one of these repository secrets:\n//\n// - CHUD_RUNTIME_TOKEN: a fine-grained personal access token with read access\n// to the contents of the repo the runtime is tagged in;\n// - CHUD_RUNTIME_DEPLOY_KEY: the private half of a read-only deploy key added\n// to that repo.\n//\n// With neither set, `npm ci` installs only what it can reach without them.\nimport { Workflow, Job, Step, Checkout, SetupNode, secrets } from \"@intentius/chant-lexicon-github\";\n\nexport const workflow = new Workflow({\n name: \"ci\",\n on: {\n push: { branches: [\"main\"] },\n pull_request: {},\n },\n permissions: { contents: \"read\" },\n // A new push to a pull request cancels the run it supersedes. Runs on main all finish.\n concurrency: {\n group: \"ci-${{ github.ref }}\",\n \"cancel-in-progress\": \"${{ github.event_name == 'pull_request' }}\",\n },\n});\n\n// npm installs a github: dependency over ssh (the lockfile records\n// git+ssh://git@github.com/...). With the token, git fetches those over https\n// with it instead; with the deploy key, over ssh with the key. GitHub's\n// published ed25519 host key is pinned rather than trusted on first use.\nconst runtimeAccess = [\n 'if [ -n \"$CHUD_RUNTIME_TOKEN\" ]; then',\n ' git config --global url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf ssh://git@github.com/',\n ' git config --global --add url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf git@github.com:',\n 'elif [ -n \"$CHUD_RUNTIME_DEPLOY_KEY\" ]; then',\n ' mkdir -p ~/.ssh && chmod 700 ~/.ssh',\n ' printf \\'%s\\\\n\\' \"$CHUD_RUNTIME_DEPLOY_KEY\" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519',\n ' echo \"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\" >> ~/.ssh/known_hosts',\n 'else',\n ' echo \"neither CHUD_RUNTIME_TOKEN nor CHUD_RUNTIME_DEPLOY_KEY is set: a private @intentius/chud-runtime will not install\"',\n 'fi',\n].join(\"\\n\");\n\n/** The delivery member, where chant, its lexicons and the runtime are installed. */\nconst project = \"delivery\";\n\nexport const ci = new Job({\n \"runs-on\": \"ubuntu-latest\",\n timeoutMinutes: 20,\n steps: [\n Checkout({}).step,\n SetupNode({ nodeVersion: \"22.x\", cache: \"npm\", cacheFilePath: `${project}/package-lock.json` }).step,\n new Step({\n name: \"Read access to @intentius/chud-runtime\",\n env: {\n CHUD_RUNTIME_TOKEN: secrets(\"CHUD_RUNTIME_TOKEN\"),\n CHUD_RUNTIME_DEPLOY_KEY: secrets(\"CHUD_RUNTIME_DEPLOY_KEY\"),\n },\n run: runtimeAccess,\n }),\n new Step({ name: \"Install\", run: \"npm ci\", \"working-directory\": project }),\n new Step({ name: \"Workflow YAML is built from ci/ci.ts\", run: \"npm run ci:check\", \"working-directory\": project }),\n // The members chant.workspace.json declares are on disk and of the kinds it says.\n new Step({ name: \"chant workspace check\", run: \"npx chant workspace check\", \"working-directory\": project }),\n // Writes the Ops under dist/ (ignored); the rest of the output goes to the runner's temp directory.\n new Step({ name: \"chant build\", run: 'npx chant build -o \"$RUNNER_TEMP/chant-build.out\"', \"working-directory\": project }),\n new Step({ name: \"chant lint\", run: \"npx chant lint\", \"working-directory\": project }),\n new Step({ name: \"App tests and approved contract checks\", run: \"npm run check\", \"working-directory\": project }),\n ],\n});\n",
32
+ "delivery/decisions/points.yaml": "# yaml-language-server: $schema=../node_modules/@intentius/chud-runtime/schemas/decision-points.schema.json\n#\n# Decision points: the questions this repo's process asks again and again,\n# declared as data rather than written into the Ops.\n#\n# Each point is a typed question (boolean, choice or score) with its\n# candidates, and an ordered chain of deciders:\n#\n# table the first row whose `when` holds answers. `when` names inputs and\n# a value each must equal, or one comparison (eq, ne, lt, lte, gt,\n# gte, in). `{}` matches everything.\n# model a model decider (backend `systemone` is any Jev-compatible server\n# at $CHUD_SYSTEMONE_URL; `stub` is for tests). An answer below its\n# threshold is \"not observed\", and the next decider is asked.\n# quorum people, always last. Their answer is recorded when they give it.\n#\n# Every answer, and every escalation, is a decision record on the chud/decisions\n# branch (points/<point>/NNNN-*.md): the decider, the inputs and their hash, the\n# answer, and the probabilities and threshold when a model answered.\n#\n# The question shape is INTENTIUS/chant#2491's (a boolean is its noul). Only\n# the design app writes this directory (see the write scope in the README).\npoints:\n slice-tier:\n title: Which builder tier builds this contract\n question:\n type: choice\n instructions: >-\n Pick the smallest builder tier that can build this contract. The state is\n how much the contract holds and whether that fits each tier's sizing limits\n (lint.rules in chant.config.ts).\n criteria:\n small: A haiku-class builder. The contract fits the small limits.\n medium: A mid-size builder. The contract fits the medium limits.\n large: The largest builder. The contract is bigger than the medium limits.\n inputs:\n criteria: acceptance criteria in the contract\n files: distinct paths the contract names\n words: words in the body outside headings\n fits_small: whether it is within the small tier's limits\n fits_medium: whether it is within the medium tier's limits\n deciders:\n - kind: table\n rows:\n - when: { fits_small: true }\n answer: small\n - when: { fits_medium: true }\n answer: medium\n - kind: model\n backend: systemone\n model: bosun-v3.1-1.7b\n threshold: 0.8\n - kind: quorum\n count: 1\n\n ship-skip:\n title: May this release skip the human gate\n question:\n type: boolean\n instructions: >-\n May this release pass the ship gate without a person approving it? The\n state is what the release plan would change.\n criteria:\n \"true\": An agent may pass the gate for this release (only in enforce mode).\n \"false\": A person approves the release at the gate.\n inputs:\n first_release: nothing has shipped yet\n new_migrations: migrations that would fire\n files_changed: files changed since the serving release\n app_changed: whether any file under the app directory changed\n contracts_changed: whether any file under contracts/ changed\n units: units of work the release ships\n deciders:\n - kind: table\n rows:\n - when: {}\n answer: false\n note: Never, until someone adds a row above this one that says when.\n # The ship gate's quorum (ops/release.op.ts reads it from here).\n - kind: quorum\n count: 1\n",
33
+ "delivery/decisions/ship-skip.cedar.ts": "/**\n * \"May this release skip the human gate\", as the ship gate's Cedar policy\n * (chant's cedar lexicon, gate approval policy from INTENTIUS/chant#2508).\n *\n * `chant approve release ship` evaluates it for every approval, as a\n * `PassGate` request: the principal is `Chant::Human::\"<approver>\"` or, with\n * `--agent` (or over MCP/ACP), `Chant::Agent::\"<name>\"`. Its context is the\n * plan digest and `shipSkip`, the answer to the ship-skip decision point in\n * decisions/points.yaml as the release plan recorded it: `no by table`,\n * `yes by table`, `yes by model`, `escalated to quorum`, ...\n *\n * The rule: an agent may pass the gate only when the decision table said yes.\n * A model's yes never does (a decision may route or report, never authorize,\n * chant#2491), and nothing else does either: the floor forbids every agent\n * otherwise, and Cedar denies what nothing permits. The table says no to\n * every release until someone adds a row, so by default a person always\n * approves.\n *\n * ops/release.op.ts runs this in `log-only` mode: each decision is recorded on\n * the approval in the gate ledger (chant/lifecycle, `_gates/release.jsonl`)\n * and in the release's history entry, and only people pass the gate. Moving\n * it to `enforce` is a separate, deliberate change.\n *\n * The entity names are strings from chant's gate model. This repo declares no\n * Cedar schema, so the typed scopes are cast (the cedar lexicon's generated\n * types know only the schema they were generated from).\n */\nimport { DenyByDefaultSet, Policy, gatePolicy, GATE_AGENT_TYPE, PASS_GATE_ACTION } from \"@intentius/chant-lexicon-cedar\";\n\nconst agent = GATE_AGENT_TYPE as never;\nconst passGate = PASS_GATE_ACTION as never;\nconst tableSaysYes = 'context has shipSkip && context.shipSkip == \"yes by table\"';\n\nexport const agentSkipsOnTableYes = new Policy({\n effect: \"permit\",\n principal: { is: agent },\n action: { eq: passGate },\n when: [tableSaysYes],\n annotations: { id: \"agent-skips-when-the-table-says-yes\" },\n});\n\nconst floored = DenyByDefaultSet({\n policies: [agentSkipsOnTableYes],\n principal: agent,\n actions: passGate,\n when: [\"true\"],\n unless: [tableSaysYes],\n annotations: { id: \"agents-never-skip-otherwise\" },\n});\n\nexport const shipSkipPolicy = gatePolicy(\"ship-skip\", floored.all);\n",
34
+ "delivery/deploy/app.component.ts": "/**\n * The app, as a chant component. Its deploy is a composition of typed\n * capabilities: the chud lexicon's release and site steps, and chant's own\n * vuln-gate and wait-endpoint. `chant list --components` and `chant describe\n * app --components` show it. Its live names are the sites (deploy/site.ts's\n * localSite, and deploy/fly.ts's flySite), which `chant components status\n * <env> --live` observes for it, each in its own environment. It deploys the\n * ChudLocalSite composite site.ts declares, and says so in `composites`, so\n * `chant workspace graph --composites` offers it for that instance.\n *\n * The release Op (ops/release.op.ts) runs the supply-chain phases (`build`:\n * Build, SBOM, Scan) after its Check and before its Plan, so a release whose\n * SBOM violates `vulnPolicy` stops before the gate with the findings listed,\n * and the plan digest covers the SBOM's hash. The rest runs after the ship\n * gate: Upload unpacks the approved archive, Sign signs it per `signing`,\n * and Verify artifact checks the signature (and that the release holds\n * exactly the signed files) before any migration or start runs its code.\n * `sbom`, `signing` and `vulnPolicy` are chant.config.ts's, resolved by\n * chant's own resolvers.\n *\n * The deploy is the same for every site: the steps are the chud lexicon's,\n * and each does what the site the release ships to (`--env`, CHUD_ENV) needs.\n * On a remote site the release is signed and verified here before any of it\n * goes to the site. Only Verify differs: chant's wait-endpoint waits on the\n * site's health endpoint when it has one (the local site always; a fly site\n * with a `url`).\n *\n * Upload refuses a plan nobody approved at the release Op's ship gate, so\n * running this component on its own (`chant run --components app`) cannot ship\n * around the gate. Record appends each release to chant's component ledger.\n */\nimport { phase, type Component, type Phase } from \"@intentius/chant/components/component\";\nimport { vulnGate, waitEndpoint } from \"@intentius/chant/components/builders\";\nimport { resolveSbomFormat, resolveSigningDefaults, resolveVulnPolicy } from \"@intentius/chant/config\";\nimport {\n releaseBuildStep, releaseSbomStep, releaseScanStep, releaseSignStep, releaseVerifyStep,\n siteMigrateStep, siteRecordStep, siteStartStep, siteUploadStep, siteVerifyStep,\n} from \"@intentius/chud-runtime/lexicon\";\nimport { config, migrationFiles } from \"@intentius/chud-runtime/release\";\nimport project from \"../chant.config.ts\";\n\nconst url = config.site.kind === \"local\" ? `http://127.0.0.1:${config.ports.prod}` : config.site.url;\nconst verify = url ? [waitEndpoint({ url: `${url}${config.app.health}`, intervalMs: 250, timeoutMs: 30_000 }), siteVerifyStep()] : [siteVerifyStep()];\n// One phase per migration, each fired once per site by the release Op's effect receipts.\nconst migrate = migrationFiles().map(({ file, sha }) =>\n phase(`Migrate ${file}`, [siteMigrateStep({ file, sha, noRollback: \"migrations are forward-only: the data stays where it is\" })]),\n);\n\nconst signing = resolveSigningDefaults(project);\n\n/** Before the gate: the app at HEAD as one archive, its SBOM, and the SBOM judged by vulnPolicy. */\nexport const build: Phase[] = [\n phase(\"Build\", [releaseBuildStep()]),\n phase(\"SBOM\", [releaseSbomStep({ digest: \"@Build.digest\", format: resolveSbomFormat(project) })]),\n phase(\"Scan\", [\n releaseScanStep({ sbom: \"@SBOM.sbom\" }),\n // chant's gate: throws, listing the blocking findings, when the SBOM violates the policy.\n vulnGate({ sbom: \"@SBOM.sbom\", findings: \"@Scan.findings\", digest: \"@Build.digest\", policy: resolveVulnPolicy(project) } as never),\n ]),\n];\n\nexport const app: Component = {\n name: \"app\",\n archetype: \"service\",\n composites: [\"ChudLocalSite\"],\n liveNames: [\"localSite\", \"flySite\"],\n dependsOn: [],\n build: { kind: \"release-build\", context: config.app.dir, into: \"archive\", sbom: { format: resolveSbomFormat(project) } },\n deploy: [\n ...build,\n phase(\"Upload\", [siteUploadStep({ approval: { op: \"release\", gate: \"ship\" } })]),\n phase(\"Sign\", [releaseSignStep({ signing })]),\n phase(\"Verify artifact\", [releaseVerifyStep({ signing })]),\n ...migrate,\n phase(\"Start\", [siteStartStep()]),\n phase(\"Verify\", verify),\n // The ledger record is under the site's environment.\n phase(\"Record\", [siteRecordStep()]),\n ],\n};\n",
35
+ "delivery/deploy/fly-machine.ts": "/**\n * The Machine that serves the fly site's release (fly.ts declares the rest\n * of the app): a Node image, the app's data Volume mounted at /data, and the\n * app on port 8080 behind Fly's proxy. A release adds its files, its start\n * command and its commit (@intentius/chud-runtime's fly-site.mjs).\n */\nimport { Machine, MachineConfig, MachineGuest, MachineMount, MachinePort, MachineService } from \"@intentius/chant-lexicon-fly\";\n\nexport const guest = new MachineGuest({ cpu_kind: \"shared\", cpus: 1, memory_mb: 256 });\nexport const dataMount = new MachineMount({ volume: \"data\", path: \"/data\" });\nexport const https = new MachinePort({ port: 443, handlers: [\"tls\", \"http\"] });\nexport const http = new MachinePort({ port: 80, handlers: [\"http\"] });\nexport const web = new MachineService({ protocol: \"tcp\", internal_port: 8080, ports: [https, http] });\n\nexport const server = new Machine({\n name: \"web\",\n region: \"iad\",\n config: new MachineConfig({\n image: \"node:22-slim\",\n guest,\n mounts: [dataMount],\n services: [web],\n env: { PORT: \"8080\", APP_DATA: \"/data\" },\n }),\n});\n",
36
+ "delivery/deploy/fly.ts": "/**\n * The remote site, `fly` in chant.config.ts's chud.sites: a Fly app declared\n * with chant's fly lexicon. `chant build deploy --lexicon fly` serializes it\n * to Machines API requests, and a release to fly (`npm run release -- --env\n * fly`) applies them with the lexicon's applier, flyApply: the App, the Volume\n * the app's data lives on, a shared IPv4, the Secret, and the Machine in\n * fly-machine.ts, which the release puts its app directory, start command and\n * commit on (@intentius/chud-runtime's fly-site.mjs).\n *\n * The same declarations apply to a real Fly org (FLY_API_TOKEN, and FLY_ORG\n * for the org) or to mudflaps, the Machines API emulator, when\n * FLY_FLAPS_BASE_URL points at it. Fly app names are global: the name is the\n * app's slug (../app-name.ts); change it before the first release to a real\n * org if it is taken.\n *\n * No secret value is in the repo. APP_SECRET's value comes from the\n * environment at release time (CHUD_FLY_APP_SECRET); without it, the release\n * checks the app already has it (`fly secrets set APP_SECRET=...`) and\n * refuses otherwise.\n */\nimport { App, Fly, IPAddress, Secret, Volume } from \"@intentius/chant-lexicon-fly\";\nimport { FlySite } from \"@intentius/chud-runtime/lexicon\";\nimport { appSlug } from \"../app-name.ts\";\n\nconst name = appSlug;\nconst region = \"iad\";\n\nexport const flyApp = new App({ name, org_slug: Fly.OrgSlug });\n\n/** The app's data (APP_DATA): it outlives releases, as .chud/site/data does locally. */\nexport const data = new Volume({ name: \"data\", region, size_gb: 1 });\n\nexport const publicIp = new IPAddress({ type: \"shared_v4\" });\n\nexport const appSecret = new Secret({ name: \"APP_SECRET\", value: process.env.CHUD_FLY_APP_SECRET || undefined });\n\n/** The site, as `chant components status fly --live` observes it: the Machine (fly-machine.ts), and the release its metadata names. */\nexport const flySite = new FlySite({ env: \"fly\", app: name, machine: \"web\" });\n",
37
+ "delivery/deploy/site.ts": "/**\n * The local site releases ship to: this machine, under .chud/site, serving on\n * prodPort (the `local` entry of chant.config.ts's chud.sites; the remote one\n * is in fly.ts). Declared with the chud lexicon's ChudLocalSite composite, so\n * chant can observe it and name it: `chant components status --live` reads\n * its LocalSite (`localSite`) through the lexicon's describeResources and\n * reconciles it with the component ledger, and `chant workspace graph\n * --composites` lists the instance `local` with the app component\n * (app.component.ts), which names ChudLocalSite in its `composites`.\n */\nimport { ChudLocalSite } from \"@intentius/chud-runtime/lexicon\";\nimport { config } from \"@intentius/chud-runtime/release\";\n\nconst env = Object.values(config.sites).find((s) => s.kind === \"local\")?.env ?? \"prod\";\n\nexport const local = ChudLocalSite({ env, port: config.ports.prod });\n",
38
+ "delivery/ops/chant-lexicon-chud/index.ts": "// The chud lexicon (the local site, and the capabilities the release is\n// composed of) comes from @intentius/chud-runtime, the version package.json pins.\nexport * from \"@intentius/chud-runtime/lexicon\";\n",
39
+ "delivery/ops/chant-lexicon-chud/op/activities.ts": "// The chud lexicon's Op activities, from @intentius/chud-runtime.\nexport * from \"@intentius/chud-runtime/lexicon/activities\";\n",
40
+ "delivery/ops/chant-lexicon-chud/op/activity-contracts.ts": "// The chud lexicon's activity contracts, from @intentius/chud-runtime: what\n// `chant build` and `chant lint` check the Ops' step-output references against.\nexport * from \"@intentius/chud-runtime/lexicon/activity-contracts\";\n",
41
+ "delivery/ops/chant-lexicon-chud/package.json": "{\n \"name\": \"@intentius/chant-lexicon-chud\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"description\": \"Thin entry: the chud lexicon from @intentius/chud-runtime, under the name chant looks lexicons up by.\",\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./index.ts\",\n \"./op/activities\": \"./op/activities.ts\",\n \"./op/activity-contracts\": \"./op/activity-contracts.ts\"\n }\n}\n",
42
+ "delivery/ops/dispatch.op.ts": "/**\n * Dispatch: one turn of the factory. Claim the next ready contract with a\n * lease, build it with the builder Agent declared for its tier, run its\n * check, and record the unit and its evidence. Everything it writes is\n * appended: a unit that opens and closes once, content-addressed evidence,\n * and lease records on the chud/leases branch.\n *\n * Claim (close out leases that ran out, then take the first ready contract\n * nobody holds; nothing claimed and the steps after it do nothing) \u2192 Run\n * (open the build unit and run the builder: Claude Code here, or the\n * Agent on Fountain with CHUD_RUNNER=fountain) \u2192 Check (the contract's check\n * on the working tree, committed as evidence) \u2192 Record (close the unit, done\n * only when the agent finished and the check passed, and release the lease).\n * If a step fails, Abandon closes the unit not_done and releases the lease.\n *\n * `chud factory run` runs this until nothing is ready. On Fountain, the\n * `factory` Teammate's Schedule in agents/team.ts posts `chant run dispatch`\n * to its thread. Every step is a capability of the chud lexicon, run\n * through its `chudCapability` activity.\n */\nimport { Op, phase } from \"@intentius/chant/op\";\nimport {\n capabilityStep,\n dispatchAbandonStep,\n dispatchCheckStep,\n dispatchClaimStep,\n dispatchRecordStep,\n dispatchRunStep,\n} from \"@intentius/chud-runtime/lexicon\";\n\nconst claim = capabilityStep(dispatchClaimStep(), { id: \"claim\" });\nconst run = capabilityStep(dispatchRunStep({ lease: claim.out.lease }), { id: \"run\" });\nconst check = capabilityStep(dispatchCheckStep({ lease: claim.out.lease, unit: run.out.unit, ok: run.out.ok }), { id: \"check\" });\nconst record = capabilityStep(dispatchRecordStep({\n lease: claim.out.lease,\n claimed: claim.out.contract,\n unit: run.out.unit,\n ok: run.out.ok,\n note: run.out.note,\n passes: check.out.ok,\n evidence: check.out.evidence,\n}), { id: \"record\" });\n\nexport default Op({\n name: \"dispatch\",\n overview: \"Claim the next ready contract with a lease, build it with its declared builder, check it, and record the unit.\",\n phases: [\n phase(\"Claim\", [claim]),\n phase(\"Run\", [run]),\n phase(\"Check\", [check]),\n phase(\"Record\", [record]),\n ],\n onFailure: [phase(\"Abandon\", [capabilityStep(dispatchAbandonStep())])],\n});\n",
43
+ "delivery/ops/release.op.ts": "/**\n * Release: ship what is committed on HEAD to the site.\n *\n * Check (stage HEAD, run the app's tests and every approved contract's\n * check against it, and commit an evidence record per check) \u2192 the app\n * component's build phases (deploy/app.component.ts): the app as one\n * archive, its SBOM, and the scan chant's vuln-gate judges by vulnPolicy,\n * which stops here, listing the findings, when the SBOM violates it \u2192 Plan\n * (work out the digest, which covers the evidence hashes, the artifact and\n * its SBOM's hash, and the ship-skip decision point's answer; it refuses\n * unless every approved contract has passing evidence) \u2192 the `ship` gate,\n * bound to the plan's digest \u2192 the rest of the component's deploy: upload\n * (which checks the evidence, that the plan passed the gate, and that the\n * archive and SBOM are the planned ones) \u2192 sign \u2192 verify the signature \u2192\n * one effect per migration, each witnessed by a receipt so it fires once per\n * site \u2192 start \u2192 verify \u2192 record, which appends the release to chant's\n * component ledger.\n *\n * Every step is a typed capability of the chud lexicon (or chant's own, like\n * wait-endpoint), run through its `chudCapability` activity.\n *\n * The first run stops at the gate. `chant approve release ship --plan <digest>`\n * (or Approve in the design app) records the approval on the chant/lifecycle\n * branch, and the next run walks through. If anything changed since, the\n * fresh plan's digest differs and the run refuses.\n *\n * The gate's approval block (chant's gate approval policy): its quorum is the\n * one that ends the ship-skip point in decisions/points.yaml, and every\n * approval is also put to the Cedar policy in decisions/ship-skip.cedar.ts,\n * with the plan's ship-skip answer as context. The policy runs `log-only`:\n * its decision is recorded on each approval, and only people pass the gate.\n */\nimport { Op, phase, gate } from \"@intentius/chant/op\";\nimport { capabilityStep, releaseCheckStep, releaseDecisionStep, releasePhases, releasePlanStep, siteRestoreStep } from \"@intentius/chud-runtime/lexicon\";\nimport { config as chud, migrationReceipt, shipQuorum } from \"@intentius/chud-runtime/release\";\nimport { shipSkipPolicy } from \"../decisions/ship-skip.cedar.ts\";\nimport { app } from \"../deploy/app.component.ts\";\n\nexport { migrationReceipt };\n\nconst plan = capabilityStep(releasePlanStep(), { id: \"plan\" });\nconst shipSkip = capabilityStep(releaseDecisionStep({ name: \"ship-skip\" }), { id: \"shipSkip\" });\n// The component's deploy, split at the gate: the build, SBOM and scan run before the plan, the rest after\n// the gate. Named consts, since chant's EVL004 only lets an Op spread a const.\nconst build = releasePhases(app, { until: \"Upload\" });\nconst ship = releasePhases(app, {\n from: \"Upload\",\n effects: (s) => (s.kind === \"site-migrate\" ? migrationReceipt(s.file as string, s.sha as string) : undefined),\n});\n\nexport default Op({\n name: \"release\",\n overview: `Ship HEAD of ${chud.name} to the site, gated on its plan.`,\n phases: [\n phase(\"Check\", [capabilityStep(releaseCheckStep())]),\n ...build,\n phase(\"Plan\", [plan, shipSkip]),\n phase(\"Gate\", [\n gate(\"ship\", {\n plan: plan.out.digest,\n description: `Ship ${chud.name}`,\n approval: {\n quorum: shipQuorum(),\n policy: shipSkipPolicy,\n mode: \"log-only\",\n context: { shipSkip: shipSkip.out.said },\n },\n }),\n ]),\n ...ship,\n ],\n onFailure: [phase(\"Restore the previous release\", [capabilityStep(siteRestoreStep())])],\n});\n",
44
+ "delivery/ops/rollback.op.ts": "/**\n * Rollback: put the release before the one serving back on the site. Gated\n * like a release; migrations are not undone (the data stays where it is).\n * Rolling back appends the release it serves again to chant's component\n * ledger, under the plan digest it first shipped with. The steps are typed\n * capabilities of the chud lexicon.\n */\nimport { Op, phase, gate } from \"@intentius/chant/op\";\nimport { capabilityStep, rollbackPlanStep, siteRollbackStep } from \"@intentius/chud-runtime/lexicon\";\nimport { app } from \"../deploy/app.component.ts\";\n\nconst plan = capabilityStep(rollbackPlanStep(), { id: \"plan\" });\n\nexport default Op({\n name: \"rollback\",\n overview: \"Serve the previous release again.\",\n phases: [\n phase(\"Plan\", [plan]),\n phase(\"Gate\", [gate(\"rollback\", { plan: plan.out.digest, description: \"Roll back to the previous release\" })]),\n phase(\"Roll back\", [capabilityStep(siteRollbackStep(), { component: app.name })]),\n ],\n});\n",
45
+ "delivery/ops/site.mjs": "// The release and rollback Ops' steps: `node ops/site.mjs <step>`. The steps\n// themselves come from @intentius/chud-runtime, the version package-lock.json pins.\n// CHUD_ROOT is the workspace root above this project (chant.workspace.json).\nimport { findRoot } from '@intentius/chud-runtime/layout';\n\nprocess.env.CHUD_ROOT = findRoot(import.meta.dirname);\nawait import('@intentius/chud-runtime/site');\n",
46
+ "delivery/ops/upgrade.mjs": "// The upgrade Op's steps: `node ops/upgrade.mjs <step>`. The steps themselves\n// come from @intentius/chud-runtime, the version package-lock.json pins.\n// CHUD_ROOT is the workspace root above this project (chant.workspace.json).\nimport { findRoot } from '@intentius/chud-runtime/layout';\n\nprocess.env.CHUD_ROOT = findRoot(import.meta.dirname);\nawait import('@intentius/chud-runtime/upgrade');\n",
47
+ "delivery/ops/upgrade.op.ts": "/**\n * Upgrade: move this repo to another @intentius/chud-runtime.\n *\n * Plan (find the target, install it into the working tree, bring this repo's\n * own files up to it, and work out the digest, which covers every file that\n * changed), then Check (the app's tests and every approved contract, on the\n * new runtime), then the `bump` gate, bound to the plan's digest, then Commit.\n *\n * The target is CHUD_UPGRADE_TO (a version, or any npm spec such as\n * `file:../chud/packages/runtime`), or else the newest `chud-runtime-v*` tag\n * on the repo the current spec points at.\n *\n * npm run upgrade # stops at the gate\n * chant approve upgrade bump --plan <digest> # a person approves\n * npm run upgrade # commits the bump\n *\n * With no git remote the bump is committed on the current branch. With one,\n * it is committed on a `chud-runtime-<version>` branch, pushed, and opened as\n * a pull request (with `gh`, for a GitHub remote) for a person to merge. If\n * any step fails, package.json, package-lock.json and every file the plan\n * brought up go back to HEAD's.\n */\nimport { Op, phase, shell, gate } from \"@intentius/chant/op\";\n\nconst plan = shell(\"node ops/upgrade.mjs plan\", { id: \"plan\" });\n\nexport default Op({\n name: \"upgrade\",\n overview: \"Bump @intentius/chud-runtime, run the tests and approved contract checks, and commit it after the gate.\",\n phases: [\n phase(\"Plan\", [plan]),\n phase(\"Check\", [shell(\"node ops/upgrade.mjs check\")]),\n phase(\"Gate\", [gate(\"bump\", { plan: plan.out.stdout, description: \"Commit the @intentius/chud-runtime upgrade\" })]),\n phase(\"Commit\", [shell(\"node ops/upgrade.mjs commit\")]),\n ],\n onFailure: [phase(\"Restore the dependency\", [shell(\"node ops/upgrade.mjs restore\")])],\n});\n",
48
+ "delivery/package.json": "{\n \"name\": \"delivery\",\n \"description\": \"The chant project that delivers the app: its Ops, its sites, its agents and its CI.\",\n \"private\": true,\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.13\"\n },\n \"scripts\": {\n \"release\": \"chant run release --on chud\",\n \"rollback\": \"chant run rollback --on chud\",\n \"upgrade\": \"chant run upgrade --on chud\",\n \"dispatch\": \"chant run dispatch --on chud\",\n \"approve\": \"chant approve\",\n \"agents\": \"chant build agents --lexicon fountain -o ../.chud/agents.fountain.yaml\",\n \"lint\": \"chant lint agents\",\n \"check\": \"node ops/site.mjs ci\",\n \"ci:build\": \"chant build ci --lexicon github -o ../.github/workflows/ci.yml\",\n \"ci:check\": \"npm run ci:build && git diff --exit-code ../.github/workflows/ci.yml\"\n },\n \"dependencies\": {\n \"@intentius/chant\": \"^0.86.0\",\n \"@intentius/chant-lexicon-cedar\": \"^0.86.0\",\n \"@intentius/chant-lexicon-chud\": \"file:ops/chant-lexicon-chud\",\n \"@intentius/chant-lexicon-fly\": \"^0.86.0\",\n \"@intentius/chant-lexicon-fountain\": \"^0.86.0\",\n \"@intentius/chant-lexicon-github\": \"^0.86.0\",\n \"@intentius/chud-runtime\": \"github:jhgaylor/chud#semver:^0.9.0\",\n \"tsx\": \"^4.23.15\"\n }\n}\n",
49
+ "design-client/README.md": "# design-client\n\nA placeholder for the workspace's design client (#2524 D18): a hud client with the `design-app` role, which edits the artifacts in `../design`.\n\nNothing is here yet, because no hud client package is published to vendor. When one is, this directory will hold that client, brought in by `chant init --from` or `chant vendor`, and the lineage lock (`.chant/workspace.lock.json`, #2540) will record its upstream as a scope of its own: the template id, the commit it came from, its parameters and which of its files are owned, generated or seed. An upgrade of the client then writes only inside this directory and never touches `../design`.\n\nThe client has a lineage of its own, separate from this workspace's. The app in `../app` does not embed it, so the app declares no `depends-on` link to it (#2539).\n",
50
+ "design/CLAUDE.md": "# The design member: notes for agents\n\nThis directory is the workspace's `design` member: chud's records, and the\ndesign app's overrides. The design app writes the records; an agent run that\nchanges them (anywhere but its own unit's scope) has the change thrown away.\n\n| Directory | What it holds |\n|---|---|\n| `contracts/` | Contracts (`C-NNN-slug.md`) and their checks (`checks/C-NNN.test.js`). See `contracts/README.md`. |\n| `drivers/` | Drivers (`D-NNN-slug.md`, or `D-NNN/design.html`), their group checks and closure records. See `drivers/README.md`. |\n| `evidence/` | Check results (`<sha256>.json`), named for their bytes: a chant record kind (`evidence.kind.mjs`, `evidence.schema.json`). |\n| `sessions/` | Design sessions (`S-NNNN.json`), sealed when closed: a chant record kind (`session.kind.mjs`, `session.schema.json`). |\n| `schemas/` | `defs.schema.json`, the definitions the evidence and session schemas refer to. |\n| `context/` | The context bundle each agent run was given (`<sha256>.json`). |\n| `proposals/` | Notes agents propose for later runs. Not authoritative. |\n| `screens/` | Screen specs and wireframes for the app. |\n| `public/`, `routes.js` | The design app's overrides, when the team has any (below). |\n\nReference assets in contracts (`references`) name driver documents relative\nto this directory: `drivers/D-001/design.html#section`.\n\nDecision records are not here: they are chant decision records in the\nworkspace's `../decisions/` (`chud-NNN-slug.md`, the format\n`../decisions/decision.schema.json` defines), which\n`chant workspace records --kind decisions/decision.kind.mjs` lists. The\ndesign app writes them; never edited, a changed decision is a new record\nwhose `supersedes` names the old one. A record pins the design files it rests\non by path and hash (`evidence: [{ title, path: design/screens/home.json,\nsha256 }]`, the hash from `chant workspace records pin <path>`), so an edit to\na pinned screen spec shows as `drifted` in `chant workspace records` and in\nthe app's decisions view until a decision is revisited and the pin updated.\n\n## If you are hud's agent in the design app\n\nYou are inside the design app (`chud design`), and your working directory is\nthis one. The team uses the app to write contracts, record decisions and ship\nreleases of {{chant:name}}. Your job is to change this app when the team\nasks: new views, different workflows, extra checks, whatever helps them build\n{{chant:name}}. You do not build the app itself here; that happens in `chud\ndev` (see `../app/CLAUDE.md`).\n\n### Where the app lives\n\nThe design app ships in the `@intentius/chud-runtime` package, the version\n`../delivery/package-lock.json` pins, at\n`../delivery/node_modules/@intentius/chud-runtime/design/`. `chud design` runs\nits `server.js` directly. Read it there, but never edit it: an `npm install`\nor an upgrade replaces it. This directory holds only this repo's overrides,\nand they win:\n\n- `public/<file>`: served instead of the package's `design/public/<file>`.\n To change the page, copy the file you need (`app.js`, `style.css`,\n `index.html`) from the package into `public/` and edit the copy. New files\n here are served too.\n- `routes.js`: extra API routes, matched before the package's. A route with\n the same method and path as one of the package's replaces it. Its default\n export gets `ctx` (`config`, `root`, `jobs`, `repo`, `decisions`,\n `contracts`, `ops`, `agents`, `job`, `fail`: the package's own modules) and\n returns a list of `[method, pattern, handler(params, body, query)]`:\n\n ```js\n export default (ctx) => [\n ['GET', /^\\/api\\/hello$/, async () => ({ hello: ctx.config.name })],\n ];\n ```\n\nAn override you copy from the package stops receiving that file's fixes when\nthe package is upgraded. Override as little as you can, and prefer adding a\nroute or a file to replacing one.\n\n### Shape of the package's app\n\n- `design/server.js`: a plain Node HTTP server. `routes` is the whole API;\n each route is `[method, pattern, handler(params, body, query)]`. Long work\n runs as a job (`lib/jobs.js`) whose output streams to the page over\n `/api/events`.\n- `design/lib/repo.js`: git: what is pending, and commits of the app's own writes.\n- `design/lib/contracts.js`: contracts (`contracts/` here), checks, approvals.\n- `design/lib/decisions.js`: decision records (`../decisions/`, chant's\n decision record kind; `src/decisions.mjs` reads and writes the format).\n- `design/lib/ops.js`: runs the chant Ops in `../delivery/ops/` (`chant run\n <op> --on chud --json`, in `../delivery/`), reads the gate ledger on the\n `chant/lifecycle` branch, and approves gates with `chant approve`.\n- `design/lib/agents.js`: builds `../delivery/agents/` (fountain specs) with\n `chant build` and runs an Agent locally as headless Claude Code.\n- `src/units.mjs`: units of work, chant work items in `../work/`\n (`src/work.mjs`): open (`chant workspace records new`), close once as done\n or not_done (`records amend`), list. The unit board reads them through\n `chant workspace records --json` (`boardUnits`). Every agent run goes\n through `unitJob` in `server.js`. chant proposes a unit's id, and it is\n taken on the `chud/leases` branch (`src/leases.mjs`), so clones sharing\n `CHUD_LEASE_REMOTE` and worktrees never reuse one.\n- `src/layout.mjs`: where the members are, from `../chant.workspace.json`.\n- `src/project.mjs`: shared with the Ops: settings (the `buildParams` in\n `../delivery/chant.config.ts`), running the app, running checks.\n- `design/public/`: the page. `app.js` is a plain ES module (no build step)\n with one render function per tab; `style.css` has the colour tokens (light\n and dark).\n\nEdits to `routes.js` here restart the server by themselves; edits to\n`public/` show on reload (use hud's reload tool).\n\n### Rules\n\n- Keep the guarantees: nothing ships except through the release Op and its\n gate; approvals go through `chant approve`; contracts are locked by sha on\n approval; decision records are never edited, only superseded. If the team\n asks for something that would break one of these, say so and offer a way\n that keeps it.\n- Every formalizer, builder or splitter run is a unit (a work item,\n `../work/W-NNN-<slug>.md`), and a closed unit is never edited; a fix is a\n new unit with `corrects`. Write units only through `src/units.mjs`, which\n writes through chant.\n- The design app commits only its own writes (contracts, checks, decision\n records, units and their work), path by path. \"Commit all\" on the Ship tab\n is the one place it commits the app's pending changes, because the person\n asked it to. When those include protected paths it refuses until the\n person acknowledges them; it then writes a `protected-commit` decision\n record naming the paths and the person, and the trailer names them\n (`design-app; human=<name>`). Do not add a route that commits hand edits\n to protected paths without that acknowledgement.\n- Every commit the design app makes carries the trailer\n `Chud-Author: design-app` (`repo.commitPaths` and `repo.commitAll` add\n it). Keep it on any commit a route of yours makes: a release refuses to\n plan while a commit that changes a protected path (`design/contracts/`,\n `decisions/`, `work/`, `design/evidence/`, `design/drivers/`,\n `design/context/`, `design/sessions/`, `delivery/decisions/`,\n `delivery/.chant/`, `delivery/chant.config.ts`, `chant.workspace.json`,\n plus `chud.protectedPaths` in `../delivery/chant.config.ts`) lacks it.\n- No npm dependencies beyond `../delivery/package.json`.\n- Never edit `../delivery/node_modules/`. A fix that belongs in the package\n goes to chud, not into this repo's copy.\n- Never edit `../.chud/`, and never run the Ops yourself from the chat; the\n person runs them from the page.\n",
51
+ "design/README.md": "# design\n\nThe workspace's `design` member: the design data it owns, apart from the\ndesign client in `../design-client`, so upgrading a client never writes over\nit. Here that is chud's records, which the design app (`chud design`) writes,\nand the screen specs.\n\n| Path | What it is |\n|---|---|\n| `contracts/` | acceptance criteria as contracts, and their checks |\n| `drivers/` | groups of contracts with one intent, their group checks and closure records |\n| `evidence/` | check results, each named for the sha256 of its bytes; `evidence.kind.mjs` makes them a chant record kind |\n| `sessions/` | design sessions; `session.kind.mjs` makes them a chant record kind |\n| `schemas/` | `defs.schema.json`, which the evidence and session schemas refer to |\n| `context/` | the context bundle each agent run was given |\n| `proposals/` | notes agents propose; not authoritative |\n| `screens/home.json`, `screens/home.svg` | the home screen's spec and wireframe, from chant's reference workspace. `ref-002` pins `home.json` by hash, so an edit to it shows as drifted in `chant workspace records` |\n| `public/`, `routes.js` | the design app's overrides, when there are any (see `CLAUDE.md`) |\n\nThe protected ones (everything above but `proposals/`, `screens/` and the\noverrides) are written only by the design app, and each commit it makes says\nso with a `Chud-Author: design-app` trailer. `CLAUDE.md` has the rules.\n",
52
+ "design/contracts/C-001-notes.md": "---\nid: C-001\ntitle: \"Notes can be written and read back\"\nstatus: draft\n---\n# C-001 \u00b7 Notes can be written and read back\n\n## Story\n\nAs someone using {{chant:name}}, I want to write a short note and see it listed, so that I know it was kept.\n\n## Acceptance criteria\n\n- [a1] Given the app is running, when I post a note with a body, then it answers 201 with the saved note.\n- [a2] Given I posted a note, when I list notes, then the newest note is first.\n- [a3] Given a note with an empty body, when I post it, then it is refused with 400.\n",
53
+ "design/contracts/README.md": "# Contracts\n\nA contract is an acceptance criterion made formal: a story, its criteria\n(`- [a1] Given \u2026, when \u2026, then \u2026`), and a check in `checks/<id>.test.js`\nthat exercises the running app over HTTP (`APP_URL`).\n\n- **draft**: being written. The design app can have an agent write the check.\n- **approved**: locked. The approval records the sha of the contract and its\n check (`approved_sha`), and from then on neither file changes. Every\n release runs the approved checks against the staged build, and refuses to\n plan if an approved contract or its check was edited.\n- **retired**: no longer enforced.\n\nChanging an approved contract makes a new version: editing it in the design\napp writes a new draft with the next free id (`C-004`, say) and\n`supersedes: C-001`, plus a copy of the check at `checks/C-004.test.js`.\n`C-001` stays as it is and stays in force until `C-004` is approved. The\napproval's decision record supersedes `C-001`'s, and units still open\nagainst `C-001` close as not done, with `superseded_by: C-004`. Releases run\nonly the latest approved version in each chain. Retiring an approved\ncontract works the same way: a new version with `status: retired` that\nsupersedes it. Retiring a draft just abandons it.\n\nThe design app (`chud design`) writes these files. Approvals and retirements\nalso leave a decision record in the workspace's `decisions/` (a chant\ndecision record).\n\nA contract may name what it `depends_on`: the contracts whose work must be\ndone first. It is ready to build once it is approved, each of those is met\n(a done unit with passing evidence), and no unit is open against it. The\ndesign app's Drivers tab shows the ready queue.\n\nA driver's splitter Agent proposes drafts with `split: {driver, unit}` in\ntheir front matter. Such a draft is approved only together with the rest of\nits split, from the driver. Approving the split locks every draft, records\none decision, and adds the drafts to the driver's members.\n\nA criterion can cite a section of a driver's HTML design document as its\nreference asset: `references: [{criterion: a2, asset:\ndrivers/D-001/design.html#notes-page}]`. The check then also verifies the\nroutes, elements and text the section declares against the running app, and\nthe evidence records each one. The section is part of what approval locks.\nAdd `check: none` to only show it.\n\nA contract may name a `tier`: `small` (the default when it names none),\n`medium` or `large`. The design app builds it with the builder Agent that\n`delivery/agents/team.ts` declares for that tier, each with its own model; small is\nhaiku-class. Size contracts so a small builder can do them. `chant lint`\n(`npm run lint`) warns when a contract holds more criteria, named files or\nwords than its tier allows (CHUD001), and when a large tier is spent on a\ncontract that fits the small one (CHUD002). The thresholds are in\n`delivery/chant.config.ts`, and the rules are in `delivery/.chant/rules/`.\n\nThe front matter (`id`, `title`, `status`, `tier`, `supersedes`, `depends_on`, `split`, `references`, and the `approved_*` fields) is\nchecked against the contract schema that ships in `@intentius/chud-runtime`\n(`schemas/contract.schema.json`), on every read and every write. Fields the\nschema does not know are refused unless their name starts with `x-`. A\nrelease will not plan while any contract or decision record fails its schema;\nthe plan names each file and field.\n",
54
+ "design/contracts/checks/C-001.test.js": "// Check for C-001 (contracts/C-001-notes.md). Runs against APP_URL.\nimport { test } from 'node:test';\nimport assert from 'node:assert/strict';\n\nconst url = process.env.APP_URL ?? 'http://127.0.0.1:3000';\nconst post = (body) => fetch(`${url}/api/notes`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ body }) });\n\ntest('a1: posting a note answers 201 with the saved note', async () => {\n const res = await post('first');\n assert.equal(res.status, 201);\n assert.equal((await res.json()).body, 'first');\n});\n\ntest('a2: the newest note is listed first', async () => {\n await post('older');\n await post('newer');\n const notes = await (await fetch(`${url}/api/notes`)).json();\n assert.equal(notes[0].body, 'newer');\n});\n\ntest('a3: an empty note is refused with 400', async () => {\n assert.equal((await post(' ')).status, 400);\n});\n",
55
+ "design/drivers/README.md": "# Drivers\n\nA driver groups contracts that serve one intent. `D-NNN-slug.md` holds, in\nits front matter, the title, the `intent` (what the members add up to), the\n`members` (contract ids), the group-level `criteria` (`g1`, `g2`, ...; each\nmay list the members it depends on under `contracts`), the `components` it\ntouches, and its `status` (`open` or `closed`). The body holds the design\nnotes. The front matter is checked against the driver schema that ships in\n`@intentius/chud-runtime` (`schemas/driver.schema.json`).\n\n- A contract belongs to at most one driver. Every version of a contract\n counts as the same contract, and a member that was superseded counts\n through its latest approved version, so the driver keeps listing `C-001`\n after `C-004` replaces it.\n- A member is met when that version is approved, a done unit (build or\n manual) cites passing evidence for it as approved, and its newest check\n at that sha does not fail.\n- A group criterion passes when its tests in `checks/D-NNN.test.js` (a\n node:test file run against a fresh app at `APP_URL`, tests named\n `g1: ...`) pass, or, when it has none, when the members it lists are met.\n One with neither keeps the driver open.\n\nA driver closes exactly once, by itself: when a unit closes done or a\ncontract is approved and every member of its driver is met, the design app\nruns the group check against a fresh app from HEAD. If it passes, the\ndriver's status becomes `closed` and `closures/D-NNN.json` records the\nunit and evidence that met each member, the group check's result, and the\ncommit. From then on neither file changes, and a release will not plan if\none does.\n\nTo split a driver, run the splitter Agent from the Drivers tab. It proposes\ndraft contracts, each with its story, criteria and `depends_on`, and it can\nwrite nothing else: the design app throws away any other change. Give each\ndraft its check, then approve the split as a whole. That locks the drafts,\nwrites one `split-approved` decision record, and adds them to `members`.\n\nA driver can instead be an HTML design document, `D-NNN/design.html`. Its\nfields are JSON in `<script type=\"application/json\" id=\"core\">`, checked\nagainst the same schema, and the rest is mockups and diagrams. The design app\nshows it sandboxed, so nothing in it runs. A contract claim can cite one of\nits sections (`references: [{criterion: a2, asset:\ndrivers/D-001/design.html#section-id}]`). The contract's check then verifies\nthe routes (`data-page`, `data-route`), elements (`data-expect`, a CSS\nselector) and text (`data-expect-text`) that the section declares against the\nrunning app. A driver has one core: keep either `D-NNN-slug.md` or\n`D-NNN/design.html`, not both.\n\nThe design app (`chud design`, Drivers tab) writes everything here.\n",
56
+ "design/evidence/evidence.kind.mjs": "// chud's evidence record kind (jhgaylor/chud#78), data only: no imports, no\n// code. chant reads it from 0.86.0 on (INTENTIUS/chant#2684, ws-053):\n//\n// chant workspace records --json\n//\n// Each record is one run of one contract's check, a JSON file named for the\n// sha256 of its bytes. The id is that hash (idFrom), the same string a pin of\n// the file holds. A file whose name is not the hash of its bytes stays valid\n// in chant's read and carries the warning asset-drift; chud refuses it as\n// evidence by reading that warning. Evidence has no lifecycle and supersedes\n// nothing.\n//\n// evidence.schema.json is @intentius/chud-runtime's schemas/evidence.schema.json\n// byte for byte, and ../schemas/defs.schema.json is its defs.schema.json;\n// chud's tests fail when they drift. chud writes these files itself (chant's\n// write verbs refuse JSON kinds) and reads each write back through chant.\nexport const recordKind = {\n name: \"evidence\",\n location: { dir: \".\", match: \"^[0-9a-f]{64}\\\\.json$\" },\n format: \"json\",\n schema: {\n id: \"https://schemas.intentius.dev/chud-runtime/v0/evidence.schema.json\",\n path: \"evidence.schema.json\",\n refs: [{ id: \"https://schemas.intentius.dev/chud-runtime/v0/defs.schema.json\", path: \"../schemas/defs.schema.json\" }],\n },\n idFrom: \"sha256\",\n};\n",
57
+ "design/evidence/evidence.schema.json": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://schemas.intentius.dev/chud-runtime/v0/evidence.schema.json\",\n \"title\": \"Evidence\",\n \"description\": \"evidence/<sha256>.json: the result of one run of one contract's check, named by the sha256 of the file's bytes. Written only by runners: the design app's check job, and the release and upgrade Ops' Check phases (#5).\",\n \"type\": \"object\",\n \"required\": [\n \"contract\",\n \"check_sha\",\n \"commit\",\n \"tree\",\n \"runner\",\n \"ok\",\n \"criteria\",\n \"output\",\n \"at\"\n ],\n \"properties\": {\n \"contract\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"sha\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/contractId\"\n },\n \"sha\": {\n \"$ref\": \"defs.schema.json#/definitions/sha256\"\n }\n },\n \"description\": \"The contract checked, and its sha (title, body and check) when it ran.\"\n },\n \"check_sha\": {\n \"$ref\": \"defs.schema.json#/definitions/sha256\",\n \"description\": \"sha256 of the check file that ran.\"\n },\n \"commit\": {\n \"description\": \"The commit tested, or null when the working tree was tested and differed from HEAD in the app or the check.\",\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[0-9a-f]{40}$\"\n },\n \"tree\": {\n \"$ref\": \"defs.schema.json#/definitions/gitId\",\n \"description\": \"The git tree id of the app directory as tested. With contract.sha it names what the evidence is about: a release accepts evidence for the tree its commit holds.\"\n },\n \"runner\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"design-check\",\n \"release-check\",\n \"upgrade-check\"\n ]\n },\n \"name\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"version\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n }\n },\n \"description\": \"Who ran the check: the design app (design-check), the release Op's Check phase (release-check) or the upgrade Op's Check phase (upgrade-check), with the chud-runtime version.\"\n },\n \"ok\": {\n \"type\": \"boolean\",\n \"description\": \"Whether the check passed: it exited 0, ran at least one test, and left no criterion unchecked.\"\n },\n \"tests\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"How many tests ran (not skipped). A check that ran none is not evidence.\"\n },\n \"criteria\": {\n \"type\": \"object\",\n \"propertyNames\": {\n \"pattern\": \"^\\\\w+$\"\n },\n \"additionalProperties\": {\n \"enum\": [\n \"pass\",\n \"fail\",\n \"unchecked\"\n ]\n },\n \"description\": \"Result per acceptance criterion, keyed by its id (a1, a2, ...).\"\n },\n \"output\": {\n \"type\": \"string\",\n \"maxLength\": 6000,\n \"description\": \"An excerpt of the check's output (its tail), capped at 6000 characters.\"\n },\n \"at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"structural\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"criterion\",\n \"asset\",\n \"ok\",\n \"expectations\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"criterion\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\w+$\"\n },\n \"asset\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"problem\": {\n \"type\": \"string\",\n \"maxLength\": 1000,\n \"description\": \"Why nothing was checked: the section is missing, or declares nothing.\"\n },\n \"expectations\": {\n \"type\": \"array\",\n \"maxItems\": 200,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"target\",\n \"ok\",\n \"detail\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"route\",\n \"element\",\n \"text\"\n ]\n },\n \"target\": {\n \"type\": \"string\",\n \"description\": \"The route, the CSS selector, or the text.\"\n },\n \"page\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"description\": \"The page an element or text was looked for on.\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"detail\": {\n \"type\": \"string\",\n \"maxLength\": 500\n }\n }\n }\n }\n }\n },\n \"description\": \"Structural checks run for the contract's reference assets (design-doc.mjs): per referenced section, each route, element and text it declares, and whether the running app has it. Their results count toward the criteria.\"\n }\n },\n \"patternProperties\": {\n \"^x-\": true\n },\n \"additionalProperties\": false\n}\n",
58
+ "design/proposals/README.md": "# Proposals\n\nNotes agents (or people) propose for later runs, one markdown file each. A\nnote may start with front matter:\n\n```yaml\n---\ntitle: The notes list is paged by 20\nconstrains: [C-001, app/server.js]\n---\n```\n\nA proposal is not authoritative. No agent run is given it until a person\npromotes it in the design app (`POST /api/proposals/<name>.md/promote`),\nwhich writes it into a decision record in the workspace's `decisions/` and removes it\nfrom here in the same commit.\n",
59
+ "design/schemas/defs.schema.json": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://schemas.intentius.dev/chud-runtime/v0/defs.schema.json\",\n \"title\": \"Shared definitions for chud records\",\n \"definitions\": {\n \"datetime\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}(\\\\.\\\\d+)?(Z|[+-]\\\\d{2}:\\\\d{2})$\",\n \"description\": \"An ISO 8601 date-time, e.g. 2026-09-23T10:00:00.000Z.\"\n },\n \"sha256\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\",\n \"description\": \"A sha256, lowercase hex.\"\n },\n \"gitId\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{40}$\",\n \"description\": \"A full git commit or tree id.\"\n },\n \"text\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"contractId\": {\n \"type\": \"string\",\n \"pattern\": \"^C-\\\\d{3,}$\"\n },\n \"decisionId\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d{4,}$\"\n },\n \"unitId\": {\n \"type\": \"string\",\n \"pattern\": \"^(U-\\\\d{4,}|W-\\\\d{3,})$\",\n \"description\": \"A unit: W-NNN, the chant work item it is (runtime 0.8.0 on), or U-NNNN, a units/U-NNNN.json file on the old layout.\"\n },\n \"driverId\": {\n \"type\": \"string\",\n \"pattern\": \"^D-\\\\d{3,}$\"\n },\n \"criterionId\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\w+$\"\n },\n \"sessionId\": {\n \"type\": \"string\",\n \"pattern\": \"^S-\\\\d{4,}$\"\n },\n \"quorum\": {\n \"type\": \"object\",\n \"required\": [\n \"count\"\n ],\n \"description\": \"How many distinct people must approve, and which roles count. The shape of chant's gate approval quorum (GateQuorum, INTENTIUS/chant#2508), and tallied by chant's own tallyGateApprovals: only people count (never an agent), once each, and only with one of `roles` when it names any.\",\n \"properties\": {\n \"count\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"roles\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"uniqueItems\": true,\n \"items\": {\n \"$ref\": \"#/definitions/text\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"recordDecisionId\": {\n \"type\": \"string\",\n \"pattern\": \"^[a-z][a-z0-9]{0,15}-[0-9]{3,}$\",\n \"description\": \"A decision record's id in the workspace's decisions/ (chant's decision record kind): a lowercase prefix, a dash and three or more digits, such as chud-003.\"\n }\n }\n}\n",
60
+ "design/screens/home.json": "{\n \"$comment\": \"Screen spec for the app's home page. The app member implements it in app/src/server.mjs. Decision ref-002 pins this file by hash, so an edit shows as drift in chant workspace records until the decision is revisited and re-pinned.\",\n \"screen\": \"home\",\n \"route\": \"/\",\n \"title\": \"{{chant:name}}\",\n \"regions\": [\n { \"id\": \"header\", \"element\": \"header\", \"contains\": [{ \"element\": \"h1\", \"text\": \"{{chant:name}}\" }] },\n { \"id\": \"status\", \"element\": \"main\", \"contains\": [{ \"element\": \"p\", \"id\": \"status\", \"text\": \"Running.\" }] }\n ],\n \"wireframe\": \"home.svg\"\n}\n",
61
+ "design/screens/home.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"320\" height=\"200\" viewBox=\"0 0 320 200\" role=\"img\" aria-labelledby=\"t\">\n <title id=\"t\">Home screen wireframe</title>\n <rect x=\"0.5\" y=\"0.5\" width=\"319\" height=\"199\" fill=\"#fff\" stroke=\"#333\"/>\n <rect id=\"header\" x=\"12\" y=\"12\" width=\"296\" height=\"40\" fill=\"#eee\" stroke=\"#333\"/>\n <text x=\"24\" y=\"38\" font-family=\"sans-serif\" font-size=\"16\">{{chant:name}}</text>\n <rect id=\"status\" x=\"12\" y=\"64\" width=\"296\" height=\"124\" fill=\"none\" stroke=\"#333\" stroke-dasharray=\"4 3\"/>\n <text x=\"24\" y=\"90\" font-family=\"sans-serif\" font-size=\"12\">Running.</text>\n</svg>\n",
62
+ "design/sessions/session.kind.mjs": "// chud's design-session record kind (jhgaylor/chud#78), data only: no\n// imports, no code. chant reads it from 0.86.0 on (INTENTIUS/chant#2684,\n// #2673):\n//\n// chant workspace records --json\n// chant workspace records --kind design/sessions/session.kind.mjs --since <open> --at <close> --json\n//\n// A session is where people review contracts, drivers, splits and release\n// gates together, with a facilitator that prepares and never gives a verdict.\n// It is open, then closed, and a closed session never changes: `seal` is\n// chant's seal for a JSON session kind, the lowercase hex sha256 of the file\n// with LF line endings and without its top-level `seal` member (and the comma\n// before it), which chant checks on every read (session-seal-mismatch).\n//\n// `verdicts` are the verdicts the session produced, in chant's shape\n// { record, principal, verdict, digest? } (jhgaylor/chud#95): one per entry of\n// chud's own `approvals` ({ kind, id, digest, by, roles, at }, what the quorum\n// is tallied over), in the same order and written with it. `principal` is the\n// approver and `verdict` is agree. `record` is the decision the approval\n// produced, once there is one (a contract or a split approved at its quorum),\n// with `digest`, the decision's digest; chant checks it against the subjects.\n// An approval that produced no decision (a driver's sign-off, a gate, one\n// short of its quorum) names its contract, driver, split or gate only under\n// `x-chud`. Decisions made in a session name it in their own records.\n//\n// session.schema.json is @intentius/chud-runtime's schemas/session.schema.json\n// byte for byte, and ../schemas/defs.schema.json is its defs.schema.json;\n// chud's tests fail when they drift. chud writes these files itself (chant's\n// write verbs refuse JSON kinds) and reads each write back through chant.\nexport const recordKind = {\n name: \"session\",\n location: { dir: \".\", match: \"^S-[0-9]+\\\\.json$\" },\n format: \"json\",\n schema: {\n id: \"https://schemas.intentius.dev/chud-runtime/v0/session.schema.json\",\n path: \"session.schema.json\",\n refs: [{ id: \"https://schemas.intentius.dev/chud-runtime/v0/defs.schema.json\", path: \"../schemas/defs.schema.json\" }],\n },\n idField: \"id\",\n stateField: \"status\",\n states: [\"open\", \"closed\"],\n closedStates: [\"closed\"],\n session: {\n verdicts: \"verdicts\",\n seal: \"seal\",\n subjects: { kind: \"../../decisions/decision.kind.mjs\" },\n },\n};\n",
63
+ "design/sessions/session.schema.json": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://schemas.intentius.dev/chud-runtime/v0/session.schema.json\",\n \"title\": \"Design session\",\n \"description\": \"sessions/S-NNNN.json: a facilitated review, written by the design app. It records who took part, what was reviewed (a contract, a driver, a driver's split or a release gate, with the evidence hashes and any screenshot or recording), what a facilitating agent prepared, and what the people decided: approvals, decisions, comments, and follow-up contracts drafted. The facilitator prepares; only participants' actions write approvals, decisions, comments, follow-ups or the closing. Once closed it never changes: `seal` is chant's seal for a JSON session kind, the sha256 of the file's text without its top-level `seal` member, and chant workspace records checks it on every read (session-seal-mismatch). In a repo whose chant.workspace.json does not declare the session kind, it is the sha256 of the record without `seal` as canonical JSON, and chud checks it.\",\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"status\",\n \"opened_by\",\n \"opened_at\",\n \"participants\",\n \"reviewed\",\n \"approvals\",\n \"decisions\",\n \"comments\",\n \"follow_ups\"\n ],\n \"properties\": {\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/sessionId\"\n },\n \"title\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"status\": {\n \"enum\": [\n \"open\",\n \"closed\"\n ]\n },\n \"opened_by\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"opened_at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"facilitator\": {\n \"type\": \"object\",\n \"required\": [\n \"name\"\n ],\n \"properties\": {\n \"name\": {\n \"$ref\": \"defs.schema.json#/definitions/text\",\n \"description\": \"The agent that lays out the evidence. It is never a participant, and nothing it does is a verdict.\"\n }\n },\n \"additionalProperties\": false\n },\n \"participants\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"roles\",\n \"joined_at\"\n ],\n \"properties\": {\n \"name\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"roles\": {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"items\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"description\": \"The roles the person claims, as `chant approve --role` records them. A quorum that names roles counts only people holding one.\"\n },\n \"joined_at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"reviewed\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"$ref\": \"#/definitions/subject\"\n }\n },\n \"prepared\": {\n \"type\": \"object\",\n \"required\": [\n \"by\",\n \"at\",\n \"items\"\n ],\n \"description\": \"What the facilitator laid out: for each reviewed subject, the two sides to compare, the checks that pass and fail, and the differences. Never a verdict.\",\n \"properties\": {\n \"by\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"notes\": {\n \"type\": \"string\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\",\n \"passing\",\n \"failing\",\n \"differences\"\n ],\n \"properties\": {\n \"kind\": {\n \"$ref\": \"#/definitions/kind\"\n },\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"side_by_side\": {\n \"type\": \"object\",\n \"required\": [\n \"left\",\n \"right\"\n ],\n \"properties\": {\n \"left\": {\n \"$ref\": \"#/definitions/side\"\n },\n \"right\": {\n \"$ref\": \"#/definitions/side\"\n }\n },\n \"additionalProperties\": false\n },\n \"passing\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"failing\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"differences\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"sections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"The design-document sections under review, each as `<ref>: <its title>`.\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"approvals\": {\n \"type\": \"array\",\n \"description\": \"chud's own record of each approval: one participant approving one subject as it was (`digest`: a contract's sha, a driver file's sha256, a split's digest over both, a release plan's digest). Quorums are tallied over these by chant's tallyGateApprovals. chant reads them as `verdicts`.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\",\n \"digest\",\n \"by\",\n \"roles\",\n \"at\"\n ],\n \"properties\": {\n \"kind\": {\n \"$ref\": \"#/definitions/kind\"\n },\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"digest\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"by\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"roles\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n }\n },\n \"at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"note\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"verdicts\": {\n \"type\": \"array\",\n \"description\": \"The approvals as chant reads a session's verdicts (the session kind's `session.verdicts`), one per approval and in the same order, kept in step by the writer that appends an approval: `principal` is the approval's `by` and `verdict` is agree. `record` is the decision the approval produced, once there is one (a contract's or a split's approval once its quorum is met), with `digest`, that decision's chant digest, when it is a chant record; an approval that produced none (a driver's sign-off, a gate, one still short of its quorum) has no `record`. `x-chud` is the subject as the approval names it. Sessions written before this list existed have none, and chant reads no verdicts from them.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"principal\",\n \"verdict\",\n \"x-chud\"\n ],\n \"properties\": {\n \"record\": {\n \"anyOf\": [\n { \"$ref\": \"defs.schema.json#/definitions/recordDecisionId\" },\n { \"$ref\": \"defs.schema.json#/definitions/decisionId\" }\n ]\n },\n \"principal\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"verdict\": {\n \"enum\": [\n \"agree\",\n \"dissent\",\n \"abstain\"\n ]\n },\n \"digest\": {\n \"$ref\": \"defs.schema.json#/definitions/sha256\",\n \"description\": \"The decision's digest by chant's digest rule when the approval produced it, the one `chant workspace records --json` prints for it.\"\n },\n \"x-chud\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\",\n \"digest\"\n ],\n \"description\": \"The subject approved, as the matching entry of `approvals` names it.\",\n \"properties\": {\n \"kind\": {\n \"$ref\": \"#/definitions/kind\"\n },\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"digest\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n },\n \"decisions\": {\n \"type\": \"array\",\n \"description\": \"Decision records made in the session. Each names this session in its own `session` field.\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"title\",\n \"where\"\n ],\n \"properties\": {\n \"id\": {\n \"anyOf\": [\n { \"$ref\": \"defs.schema.json#/definitions/recordDecisionId\" },\n { \"$ref\": \"defs.schema.json#/definitions/decisionId\" }\n ]\n },\n \"title\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"where\": {\n \"enum\": [\n \"decisions\",\n \"docs/decisions\",\n \"chud/decisions\"\n ],\n \"description\": \"decisions: a chant decision record in the workspace's decisions/; chud/decisions: a decision point's answer on that branch; docs/decisions: a record of a repo from before the workspace template.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"comments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"by\",\n \"at\",\n \"text\"\n ],\n \"properties\": {\n \"by\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"text\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"follow_ups\": {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"items\": {\n \"$ref\": \"defs.schema.json#/definitions/contractId\"\n },\n \"description\": \"Contracts drafted in the session.\"\n },\n \"closed_by\": {\n \"$ref\": \"defs.schema.json#/definitions/text\"\n },\n \"closed_at\": {\n \"$ref\": \"defs.schema.json#/definitions/datetime\"\n },\n \"seal\": {\n \"$ref\": \"defs.schema.json#/definitions/sha256\"\n }\n },\n \"definitions\": {\n \"kind\": {\n \"enum\": [\n \"contract\",\n \"driver\",\n \"split\",\n \"gate\"\n ]\n },\n \"subject\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"id\",\n \"evidence\",\n \"media\"\n ],\n \"properties\": {\n \"kind\": {\n \"$ref\": \"#/definitions/kind\"\n },\n \"id\": {\n \"$ref\": \"defs.schema.json#/definitions/text\",\n \"description\": \"C-NNN, D-NNN, a driver's split as D-NNN/U-NNNN, or <op>/<gate> (release/ship).\"\n },\n \"digest\": {\n \"$ref\": \"defs.schema.json#/definitions/text\",\n \"description\": \"What it was when it was put up for review: a contract's sha, a driver file's sha256, a plan digest.\"\n },\n \"evidence\": {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"items\": {\n \"$ref\": \"defs.schema.json#/definitions/sha256\"\n },\n \"description\": \"evidence/<sha256>.json records reviewed.\"\n },\n \"media\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"ref\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"screenshot\",\n \"recording\"\n ]\n },\n \"ref\": {\n \"$ref\": \"defs.schema.json#/definitions/text\",\n \"description\": \"A repo path or a URL.\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"sections\": {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"items\": {\n \"type\": \"string\",\n \"pattern\": \"^drivers/D-\\\\d+/design\\\\.html#[A-Za-z][\\\\w:.-]*$\"\n },\n \"description\": \"Sections of a driver's HTML design document (drivers/D-NNN/design.html#section-id) under review with the subject: the ones named when it was put up, and a contract's own references. Each must exist.\"\n }\n },\n \"additionalProperties\": false\n },\n \"side\": {\n \"type\": \"object\",\n \"required\": [\n \"label\",\n \"lines\"\n ],\n \"properties\": {\n \"label\": {\n \"type\": \"string\"\n },\n \"lines\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"patternProperties\": {\n \"^x-\": true\n },\n \"additionalProperties\": false,\n \"if\": {\n \"properties\": {\n \"status\": {\n \"const\": \"closed\"\n }\n },\n \"required\": [\n \"status\"\n ]\n },\n \"then\": {\n \"required\": [\n \"closed_by\",\n \"closed_at\",\n \"seal\"\n ]\n }\n}\n",
64
+ "work/work.kind.mjs": "// The work item record kind (#2683), data only: no imports, no code.\n//\n// chant workspace records --kind work/work.kind.mjs --json\n// chant workspace graph --intent <region> --kind decisions/decision.kind.mjs --kind work/work.kind.mjs\n//\n// A work item is a record in the workspace, with its dependencies written\n// inside it, that people and agents share as one queue with no server. Most\n// come from a gap the intent graph reports, named in `source.finding`.\n// Records are Markdown with front matter: an item keeps its id while its\n// state changes, so it is not content-addressed. ws-053 (#2664) lets a kind\n// read `format: \"json\"`, but `records new` and `records amend` write Markdown\n// only, so work items stay Markdown until the write commands write JSON.\nexport const recordKind = {\n name: \"work\",\n location: { dir: \".\", match: \"^W-[0-9]{3,}-.+\\\\.md$\" },\n format: \"markdown-front-matter\",\n schema: { id: \"urn:intentius:chant:work:1\", path: \"work.schema.json\" },\n idField: \"id\",\n stateField: \"state\",\n states: [\"open\", \"in-progress\", \"done\", \"dropped\"],\n closedStates: [\"done\", \"dropped\"],\n // Takes effect from a done or dropped item, the closed states.\n supersedes: { field: \"supersedes\", key: \"work\" },\n // Evidence is the proof of done: links, or workspace files pinned by hash.\n pins: { field: \"evidence\" },\n // The same grammar as a decision's: member:, path:, issues and decision ids.\n constrains: { field: \"constrains\" },\n // needs and implements, the decisions they name, and when an item is ready.\n work: {\n needs: \"needs\",\n implements: \"implements\",\n decisions: \"../decisions/decision.kind.mjs\",\n open: \"open\",\n done: \"done\",\n closedOn: \"closed_on\",\n },\n};\n",
65
+ "work/work.schema.json": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"urn:intentius:chant:work:1\",\n \"title\": \"Work item front matter\",\n \"description\": \"The front matter of one work item in work/ (#2683): a record in the workspace, with its dependencies inside it, that people and agents share as one queue. The Markdown body after the front matter is the description. Values stay within what YAML and JSON share.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"patternProperties\": {\n \"^x-\": {}\n },\n \"required\": [\n \"schema\",\n \"id\",\n \"title\",\n \"state\",\n \"implements\",\n \"needs\",\n \"constrains\",\n \"evidence\",\n \"opened_on\",\n \"source\",\n \"supersedes\"\n ],\n \"properties\": {\n \"schema\": {\n \"description\": \"Format version of this file. Bumped only by a change to this schema.\",\n \"const\": 1\n },\n \"id\": {\n \"description\": \"Stable id: W, a dash and three or more digits. Never reused, never renumbered.\",\n \"$ref\": \"#/definitions/workId\"\n },\n \"title\": {\n \"description\": \"What the work is, in a few words.\",\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"state\": {\n \"description\": \"open: nobody has started it. in-progress: someone has. done: finished, with its proof in evidence. dropped: not going to happen. done and dropped are closed.\",\n \"enum\": [\n \"open\",\n \"in-progress\",\n \"done\",\n \"dropped\"\n ]\n },\n \"implements\": {\n \"description\": \"The decisions this work carries out, by id. May be empty. chant workspace records warns work-implements-undecided when one is still proposed.\",\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/decisionId\"\n },\n \"uniqueItems\": true\n },\n \"needs\": {\n \"description\": \"The work items that must be done before this one is ready, by id. May be empty.\",\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/workId\"\n },\n \"uniqueItems\": true\n },\n \"constrains\": {\n \"description\": \"What this work touches, in the grammar a decision's constrains uses: issues (owner/repo#n), decision ids, workspace members (member:<name>) or workspace paths (path:<path>). At least one entry.\",\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"string\",\n \"pattern\": \"^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+|[a-z][a-z0-9]{0,15}-[0-9]{3,}|member:[a-z0-9][a-z0-9-]{0,39}|path:(?!/)(?!(?:[^/]*/)*\\\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\\\\\\\u0000-\\\\u001f]*[^/\\\\\\\\\\\\u0000-\\\\u001f])$\"\n }\n },\n \"evidence\": {\n \"description\": \"The proof of done: public links, or workspace files pinned by the hash of their bytes (chant workspace records pin <path>). May be empty while the work is open; a done item with none gets the warning work-done-unpinned.\",\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/evidenceLink\"\n },\n {\n \"$ref\": \"#/definitions/evidencePin\"\n }\n ]\n }\n },\n \"owner\": {\n \"description\": \"Who has taken the work: a principal, such as a forge login or an agent's name. Null or absent while nobody has.\",\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"minLength\": 1\n },\n \"opened_on\": {\n \"description\": \"When the item was written, as YYYY-MM-DD. Quoted, so YAML reads it as a string.\",\n \"type\": \"string\",\n \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\"\n },\n \"closed_on\": {\n \"description\": \"When the item became done or dropped, as YYYY-MM-DD. chant workspace records warns work-closed-without-date when a closed item has none.\",\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\"\n },\n \"source\": {\n \"description\": \"Where the work came from: a gap the intent graph reported, an issue, or the workspace itself.\",\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/sourceGap\"\n },\n {\n \"$ref\": \"#/definitions/sourceIssue\"\n },\n {\n \"$ref\": \"#/definitions/sourceWorkspace\"\n }\n ]\n },\n \"supersedes\": {\n \"description\": \"Earlier work items this one replaces. Takes effect once this item is done or dropped.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"work\"\n ],\n \"properties\": {\n \"work\": {\n \"$ref\": \"#/definitions/workId\"\n }\n }\n }\n },\n \"x-chud\": {\n \"description\": \"chud's dispatch fields for a work item that is one of its units (jhgaylor/chud#78): what the unit serves, who ran it and how, where its work went, and why it did not finish. chud's runtime writes it through chant workspace records new and amend; the unit board and chud status read it back from chant workspace records --json.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"patternProperties\": {\n \"^x-\": {}\n },\n \"required\": [\n \"role\",\n \"agent\",\n \"scope\",\n \"context_hash\",\n \"base_commit\",\n \"result\",\n \"evidence\",\n \"opened_at\"\n ],\n \"properties\": {\n \"role\": {\n \"enum\": [\n \"formalize\",\n \"build\",\n \"manual\",\n \"split\"\n ],\n \"description\": \"What kind of work: a formalizer run (writes the contract's check), a builder run (changes the app), manual work recorded from the design app, or a splitter run (proposes draft contracts for a driver).\"\n },\n \"contract\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"id\",\n \"sha\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^C-[0-9]{3,}$\"\n },\n \"sha\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n }\n },\n \"description\": \"The contract the unit serves, and the sha256 of its file when the unit opened. Every unit but a split has one.\"\n },\n \"driver\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"id\",\n \"sha\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^D-[0-9]{3,}$\"\n },\n \"sha\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n }\n },\n \"description\": \"split: the driver the unit splits, and the sha256 of its file when the unit opened.\"\n },\n \"agent\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"name\",\n \"model\",\n \"runtime\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"The agent, or the person for manual work.\"\n },\n \"model\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"runtime\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n }\n },\n \"scope\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"uniqueItems\": true,\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"description\": \"The paths the unit's work covers, from the workspace root. Done work under them is committed; not_done work is kept at refs/chud/units/<id> and removed from the working tree. The item's constrains names them as path: entries.\"\n },\n \"by\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Who started the unit.\"\n },\n \"context_hash\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[0-9a-f]{64}$\",\n \"description\": \"sha256 of the context bundle the agent was given (design/context/<hash>.json). null for manual work.\"\n },\n \"base_commit\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{40}$\",\n \"description\": \"HEAD when the unit opened.\"\n },\n \"lease\": {\n \"type\": [\n \"object\",\n \"null\"\n ],\n \"additionalProperties\": false,\n \"required\": [\n \"id\",\n \"worker\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"worker\": {\n \"type\": \"string\",\n \"minLength\": 1\n }\n },\n \"description\": \"The dispatch lease the unit runs under (the chud/leases branch): its claim id and the worker holding it. Absent for work the design app started.\"\n },\n \"slice\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"tier\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"description\": \"The slice a dispatcher or the design app built: the tier it ran at.\"\n },\n \"result\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"commit\",\n \"ref\"\n ],\n \"properties\": {\n \"commit\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[0-9a-f]{40}$\"\n },\n \"ref\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"description\": \"Where the work is. done: the commit holding it (null when the unit changed nothing). not_done: the snapshot commit and refs/chud/units/<id>. Both null while open. The intent graph joins a commit to the unit through this field.\"\n },\n \"evidence\": {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"items\": {\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n },\n \"description\": \"The evidence records (design/evidence/<sha256>.json) the unit cites, by hash. The item's evidence pins the same files by path and sha256.\"\n },\n \"not_done\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"reason\": {\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[a-z][a-z0-9-]*$\",\n \"maxLength\": 60\n }\n },\n \"description\": \"Set when the unit closed not done (the item is dropped). reason: one word a program can read, lease-lost or lease-expired, or null.\"\n },\n \"corrects\": {\n \"type\": \"string\",\n \"pattern\": \"^(U-[0-9]{4,}|W-[0-9]{3,})$\",\n \"description\": \"The earlier unit this one fixes.\"\n },\n \"from\": {\n \"type\": \"string\",\n \"pattern\": \"^(U-[0-9]{4,}|W-[0-9]{3,})$\",\n \"description\": \"The not_done unit whose kept work this unit started from.\"\n },\n \"superseded_by\": {\n \"type\": \"string\",\n \"pattern\": \"^C-[0-9]{3,}$\",\n \"description\": \"Set when the unit closed not done because its contract was superseded: the version that replaced it.\"\n },\n \"note\": {\n \"type\": \"string\",\n \"description\": \"What the unit is for, or why it closed as it did.\"\n },\n \"opened_at\": {\n \"type\": \"string\",\n \"description\": \"When the unit opened, as an ISO 8601 date-time.\"\n },\n \"closed_at\": {\n \"type\": \"string\",\n \"description\": \"When the unit closed, as an ISO 8601 date-time.\"\n }\n }\n }\n },\n \"definitions\": {\n \"workId\": {\n \"type\": \"string\",\n \"pattern\": \"^W-[0-9]{3,}$\"\n },\n \"decisionId\": {\n \"type\": \"string\",\n \"pattern\": \"^[a-z][a-z0-9]{0,15}-[0-9]{3,}$\"\n },\n \"issueRef\": {\n \"type\": \"string\",\n \"pattern\": \"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+$\"\n },\n \"workspacePath\": {\n \"description\": \"A path from the workspace root, with / separators: no leading /, no . or .. segment, no empty segment, no backslash and no trailing /.\",\n \"type\": \"string\",\n \"pattern\": \"^(?!/)(?!(?:[^/]*/)*\\\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\\\\\\\u0000-\\\\u001f]*[^/\\\\\\\\\\\\u0000-\\\\u001f]$\"\n },\n \"evidenceLink\": {\n \"description\": \"A public link.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"title\",\n \"url\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"url\": {\n \"type\": \"string\",\n \"pattern\": \"^https://\"\n },\n \"as_of\": {\n \"description\": \"When the linked text was read, as an ISO 8601 timestamp. Lets a reader flag evidence that changed afterwards.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"sha256\": {\n \"description\": \"Hex SHA-256 of the linked content, once evidence is pinned by hash (#2555). Null until then.\",\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"pattern\": \"^[0-9a-f]{64}$\"\n }\n }\n },\n \"evidencePin\": {\n \"description\": \"A file in the workspace, pinned by the hash of its bytes. chant workspace records reports a file that changed or went missing since.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"title\",\n \"path\",\n \"sha256\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"path\": {\n \"description\": \"The file, from the workspace root. It sits inside a member, such as design/screens/home.json.\",\n \"$ref\": \"#/definitions/workspacePath\"\n },\n \"sha256\": {\n \"description\": \"Hex SHA-256 of the file's bytes when the work was done. chant workspace records pin <path> prints it.\",\n \"type\": \"string\",\n \"pattern\": \"^[0-9a-f]{64}$\"\n },\n \"as_of\": {\n \"description\": \"When the file was read, as an ISO 8601 timestamp.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n }\n },\n \"sourceGap\": {\n \"description\": \"The gap the work closes, as chant workspace graph --intent reported it (#2683). The honest way to finish the work is to make the finding stop firing on the region; graph --intent warns work-done-gap-open on a done item whose finding still fires.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"finding\",\n \"region\"\n ],\n \"properties\": {\n \"finding\": {\n \"description\": \"The finding's code: one of chant's intent finding codes, or a plugin's plugin:<name>:<code>.\",\n \"anyOf\": [\n {\n \"enum\": [\n \"intent-commit-undecided\",\n \"intent-commit-bare\",\n \"intent-pin-drifted\",\n \"intent-pin-missing\",\n \"intent-artifact-unpinned\",\n \"intent-decision-superseded-live\",\n \"intent-decision-provisional\",\n \"intent-constraint-coarse\",\n \"intent-constraint-lost\",\n \"intent-evidence-unpinned\",\n \"intent-trailer-unverified\",\n \"intent-region-unconstrained\",\n \"intent-decision-unimplemented\",\n \"intent-work-blocked\",\n \"intent-work-open-decided-code\"\n ]\n },\n {\n \"type\": \"string\",\n \"pattern\": \"^plugin:[^:\\\\s]+:[a-z0-9]+(-[a-z0-9]+)*$\"\n }\n ]\n },\n \"region\": {\n \"description\": \"The region the finding fired on, as graph --intent takes it: a workspace path, path:line or path:start-end, or . for the whole workspace.\",\n \"type\": \"string\",\n \"pattern\": \"^(\\\\.|(?!/)(?!(?:[^/]*/)*\\\\.{1,2}(?:/|:|$))(?!.*//)[^\\\\\\\\\\\\u0000-\\\\u001f:]*[^/\\\\\\\\\\\\u0000-\\\\u001f:](:[1-9][0-9]*(-[1-9][0-9]*)?)?)$\"\n },\n \"decision\": {\n \"description\": \"The decision the finding concerned, when it concerned one.\",\n \"$ref\": \"#/definitions/decisionId\"\n },\n \"artifact\": {\n \"description\": \"The artifact the finding concerned, from the workspace root, when it concerned one.\",\n \"$ref\": \"#/definitions/workspacePath\"\n }\n }\n },\n \"sourceIssue\": {\n \"description\": \"An issue that asked for the work.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"issue\"\n ],\n \"properties\": {\n \"issue\": {\n \"$ref\": \"#/definitions/issueRef\"\n },\n \"row\": {\n \"description\": \"A row of the issue's table the work comes from, verbatim, when there is one.\",\n \"type\": \"string\"\n }\n }\n },\n \"sourceWorkspace\": {\n \"description\": \"Work that came up in the workspace, such as in a member's own session, with no issue behind it.\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"kind\",\n \"member\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"workspace\"\n },\n \"member\": {\n \"description\": \"The workspace member the work came up in, by its name in the declaration.\",\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]{0,39}$\"\n },\n \"session\": {\n \"description\": \"The id of the session the work came up in, as the member names it. Null or absent when there was none.\",\n \"type\": [\n \"string\",\n \"null\"\n ],\n \"minLength\": 1\n },\n \"issue\": {\n \"description\": \"An issue the work relates to, when there is one.\",\n \"$ref\": \"#/definitions/issueRef\"\n }\n }\n }\n }\n}\n"
66
+ }
67
+ }