@intentius/chant 0.102.0 → 0.104.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 (464) hide show
  1. package/dist/audit/core.d.ts +2 -0
  2. package/dist/audit/core.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/change-set.d.ts +232 -0
  5. package/dist/change-set.d.ts.map +1 -0
  6. package/dist/cli/commands/audit.d.ts +7 -0
  7. package/dist/cli/commands/audit.d.ts.map +1 -1
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/handlers/build.d.ts.map +1 -1
  10. package/dist/cli/handlers/change-set.d.ts +11 -0
  11. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  12. package/dist/cli/handlers/fan-out.d.ts +33 -0
  13. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  14. package/dist/cli/handlers/operator.d.ts +7 -0
  15. package/dist/cli/handlers/operator.d.ts.map +1 -1
  16. package/dist/cli/handlers/pr.d.ts +31 -0
  17. package/dist/cli/handlers/pr.d.ts.map +1 -0
  18. package/dist/cli/handlers/run.d.ts.map +1 -1
  19. package/dist/cli/main.d.ts.map +1 -1
  20. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  21. package/dist/cli/mcp/workspace-tools.d.ts +3 -2
  22. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  23. package/dist/cli/registry.d.ts +63 -1
  24. package/dist/cli/registry.d.ts.map +1 -1
  25. package/dist/components/auto-release.d.ts +16 -0
  26. package/dist/components/auto-release.d.ts.map +1 -1
  27. package/dist/components/capability.d.ts +53 -0
  28. package/dist/components/capability.d.ts.map +1 -1
  29. package/dist/components/changed-files.d.ts +31 -0
  30. package/dist/components/changed-files.d.ts.map +1 -0
  31. package/dist/components/cli-support.d.ts +9 -1
  32. package/dist/components/cli-support.d.ts.map +1 -1
  33. package/dist/components/component.d.ts +24 -0
  34. package/dist/components/component.d.ts.map +1 -1
  35. package/dist/components/deploy-units.d.ts.map +1 -1
  36. package/dist/components/driver.d.ts +13 -0
  37. package/dist/components/driver.d.ts.map +1 -1
  38. package/dist/components/fan-out-output.d.ts +6 -0
  39. package/dist/components/fan-out-output.d.ts.map +1 -1
  40. package/dist/components/fan-out-record.d.ts +41 -0
  41. package/dist/components/fan-out-record.d.ts.map +1 -0
  42. package/dist/components/fan-out-run.d.ts +65 -0
  43. package/dist/components/fan-out-run.d.ts.map +1 -1
  44. package/dist/components/fan-out-support.d.ts +2 -0
  45. package/dist/components/fan-out-support.d.ts.map +1 -1
  46. package/dist/components/fan-out.d.ts +6 -0
  47. package/dist/components/fan-out.d.ts.map +1 -1
  48. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  49. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  50. package/dist/components/index.d.ts +1 -1
  51. package/dist/components/index.d.ts.map +1 -1
  52. package/dist/components/layers.d.ts +25 -0
  53. package/dist/components/layers.d.ts.map +1 -0
  54. package/dist/components/pr-pipeline.d.ts +61 -0
  55. package/dist/components/pr-pipeline.d.ts.map +1 -0
  56. package/dist/components/pr-run.d.ts +119 -0
  57. package/dist/components/pr-run.d.ts.map +1 -0
  58. package/dist/components/promote.d.ts.map +1 -1
  59. package/dist/components/verbs/shell.d.ts.map +1 -1
  60. package/dist/components/wave-plan.d.ts +49 -0
  61. package/dist/components/wave-plan.d.ts.map +1 -0
  62. package/dist/gated-waves.d.ts +147 -0
  63. package/dist/gated-waves.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/lexicon.d.ts +48 -0
  67. package/dist/lexicon.d.ts.map +1 -1
  68. package/dist/lifecycle/gate-ledger.d.ts +7 -0
  69. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  70. package/dist/lifecycle/git.d.ts +46 -1
  71. package/dist/lifecycle/git.d.ts.map +1 -1
  72. package/dist/lint/post-synth.d.ts +18 -0
  73. package/dist/lint/post-synth.d.ts.map +1 -1
  74. package/dist/op/activities/activity-contracts.d.ts +126 -0
  75. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  76. package/dist/op/activities/change-set.d.ts +62 -0
  77. package/dist/op/activities/change-set.d.ts.map +1 -0
  78. package/dist/op/activities/decide.d.ts +2 -0
  79. package/dist/op/activities/decide.d.ts.map +1 -1
  80. package/dist/op/activities/factory.d.ts +137 -0
  81. package/dist/op/activities/factory.d.ts.map +1 -0
  82. package/dist/op/activities/index.d.ts +4 -0
  83. package/dist/op/activities/index.d.ts.map +1 -1
  84. package/dist/op/activity-contract.d.ts.map +1 -1
  85. package/dist/op/builders.d.ts +11 -0
  86. package/dist/op/builders.d.ts.map +1 -1
  87. package/dist/op/decide-backend.d.ts +5 -1
  88. package/dist/op/decide-backend.d.ts.map +1 -1
  89. package/dist/op/decide-read-inputs.d.ts +4 -2
  90. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  91. package/dist/op/factory.d.ts +83 -0
  92. package/dist/op/factory.d.ts.map +1 -0
  93. package/dist/op/index.d.ts +4 -1
  94. package/dist/op/index.d.ts.map +1 -1
  95. package/dist/op/op-ir.d.ts +2 -0
  96. package/dist/op/op-ir.d.ts.map +1 -1
  97. package/dist/op/op-verb-class.d.ts.map +1 -1
  98. package/dist/op/types.d.ts +8 -0
  99. package/dist/op/types.d.ts.map +1 -1
  100. package/dist/op/work-lease-run.d.ts.map +1 -1
  101. package/dist/plan-summary.d.ts +207 -0
  102. package/dist/plan-summary.d.ts.map +1 -0
  103. package/dist/pr-forge.d.ts +105 -0
  104. package/dist/pr-forge.d.ts.map +1 -0
  105. package/dist/pr-loop.d.ts +178 -0
  106. package/dist/pr-loop.d.ts.map +1 -0
  107. package/dist/telemetry-attribution.d.ts +31 -0
  108. package/dist/telemetry-attribution.d.ts.map +1 -1
  109. package/dist/terraform/hcl2json.d.ts +45 -0
  110. package/dist/terraform/hcl2json.d.ts.map +1 -0
  111. package/dist/terraform/parse.d.ts +4 -40
  112. package/dist/terraform/parse.d.ts.map +1 -1
  113. package/dist/workspace/box-cli.d.ts +12 -0
  114. package/dist/workspace/box-cli.d.ts.map +1 -0
  115. package/dist/workspace/box-factory.d.ts +27 -3
  116. package/dist/workspace/box-factory.d.ts.map +1 -1
  117. package/dist/workspace/box-listing.d.ts +85 -0
  118. package/dist/workspace/box-listing.d.ts.map +1 -0
  119. package/dist/workspace/box-publish.d.ts +147 -0
  120. package/dist/workspace/box-publish.d.ts.map +1 -0
  121. package/dist/workspace/broker-protocol.d.ts +190 -0
  122. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  123. package/dist/workspace/checks.d.ts.map +1 -1
  124. package/dist/workspace/composites.d.ts +2 -2
  125. package/dist/workspace/composites.d.ts.map +1 -1
  126. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  127. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  128. package/dist/workspace/conformance/broker.d.ts +202 -0
  129. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  130. package/dist/workspace/conformance/index.d.ts +2 -1
  131. package/dist/workspace/conformance/index.d.ts.map +1 -1
  132. package/dist/workspace/conformance/vitest.d.ts +9 -2
  133. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  134. package/dist/workspace/conformance/writer.d.ts +108 -14
  135. package/dist/workspace/conformance/writer.d.ts.map +1 -1
  136. package/dist/workspace/decide.d.ts +126 -12
  137. package/dist/workspace/decide.d.ts.map +1 -1
  138. package/dist/workspace/decision-points.schema.json +11 -4
  139. package/dist/workspace/declaration.d.ts +65 -0
  140. package/dist/workspace/declaration.d.ts.map +1 -1
  141. package/dist/workspace/declaration.schema.json +153 -4
  142. package/dist/workspace/environments.d.ts +36 -10
  143. package/dist/workspace/environments.d.ts.map +1 -1
  144. package/dist/workspace/factory-rules.d.ts +180 -0
  145. package/dist/workspace/factory-rules.d.ts.map +1 -0
  146. package/dist/workspace/init.d.ts +21 -0
  147. package/dist/workspace/init.d.ts.map +1 -1
  148. package/dist/workspace/intent-cli.d.ts +1 -1
  149. package/dist/workspace/intent-cli.d.ts.map +1 -1
  150. package/dist/workspace/intent-record.d.ts +28 -1
  151. package/dist/workspace/intent-record.d.ts.map +1 -1
  152. package/dist/workspace/intent.d.ts +102 -5
  153. package/dist/workspace/intent.d.ts.map +1 -1
  154. package/dist/workspace/json-edit.d.ts +24 -0
  155. package/dist/workspace/json-edit.d.ts.map +1 -0
  156. package/dist/workspace/jsonc.d.ts +11 -0
  157. package/dist/workspace/jsonc.d.ts.map +1 -1
  158. package/dist/workspace/kinds.d.ts +48 -0
  159. package/dist/workspace/kinds.d.ts.map +1 -1
  160. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  161. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  162. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  163. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  164. package/dist/workspace/point-answer.schema.json +42 -2
  165. package/dist/workspace/points-cli.d.ts +10 -3
  166. package/dist/workspace/points-cli.d.ts.map +1 -1
  167. package/dist/workspace/points.d.ts +66 -5
  168. package/dist/workspace/points.d.ts.map +1 -1
  169. package/dist/workspace/reason-codes.d.ts +33 -4
  170. package/dist/workspace/reason-codes.d.ts.map +1 -1
  171. package/dist/workspace/record-sessions.d.ts +5 -7
  172. package/dist/workspace/record-sessions.d.ts.map +1 -1
  173. package/dist/workspace/records-cli.d.ts +7 -0
  174. package/dist/workspace/records-cli.d.ts.map +1 -1
  175. package/dist/workspace/records-close.d.ts +7 -3
  176. package/dist/workspace/records-close.d.ts.map +1 -1
  177. package/dist/workspace/records-write.d.ts +66 -6
  178. package/dist/workspace/records-write.d.ts.map +1 -1
  179. package/dist/workspace/records.d.ts +28 -9
  180. package/dist/workspace/records.d.ts.map +1 -1
  181. package/dist/workspace/runs-cli.d.ts +138 -6
  182. package/dist/workspace/runs-cli.d.ts.map +1 -1
  183. package/dist/workspace/runs.d.ts +132 -11
  184. package/dist/workspace/runs.d.ts.map +1 -1
  185. package/dist/workspace/session-kinds.d.ts +1 -1
  186. package/dist/workspace/session-kinds.d.ts.map +1 -1
  187. package/dist/workspace/squash.d.ts +80 -0
  188. package/dist/workspace/squash.d.ts.map +1 -0
  189. package/dist/workspace/status-gates.d.ts +2 -0
  190. package/dist/workspace/status-gates.d.ts.map +1 -1
  191. package/dist/workspace/status.d.ts +54 -0
  192. package/dist/workspace/status.d.ts.map +1 -1
  193. package/dist/workspace/symbols.d.ts +16 -5
  194. package/dist/workspace/symbols.d.ts.map +1 -1
  195. package/dist/workspace/trust/run-commits.d.ts +46 -0
  196. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  197. package/dist/workspace/trust/run-statement.d.ts +128 -0
  198. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  199. package/dist/workspace/trust/seal.d.ts +6 -1
  200. package/dist/workspace/trust/seal.d.ts.map +1 -1
  201. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  202. package/dist/workspace/trust/verify.d.ts +16 -2
  203. package/dist/workspace/trust/verify.d.ts.map +1 -1
  204. package/dist/workspace/wip-cli.d.ts +21 -0
  205. package/dist/workspace/wip-cli.d.ts.map +1 -0
  206. package/dist/workspace/wip.d.ts +268 -0
  207. package/dist/workspace/wip.d.ts.map +1 -0
  208. package/dist/workspace/work-cli.d.ts.map +1 -1
  209. package/dist/workspace/work-evidence.d.ts +1 -1
  210. package/dist/workspace/work-evidence.d.ts.map +1 -1
  211. package/dist/workspace/work-size.d.ts +55 -0
  212. package/dist/workspace/work-size.d.ts.map +1 -0
  213. package/dist/workspace/workspace-kinds.schema.json +122 -0
  214. package/dist/workspace/write-lock-cli.d.ts +60 -0
  215. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  216. package/dist/workspace/write-lock.d.ts +152 -0
  217. package/dist/workspace/write-lock.d.ts.map +1 -0
  218. package/dist/workspace/write-scope.d.ts +6 -2
  219. package/dist/workspace/write-scope.d.ts.map +1 -1
  220. package/package.json +6 -1
  221. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  222. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  223. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  224. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  225. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  226. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  227. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  228. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  229. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  230. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  231. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  232. package/src/audit/core.test.ts +15 -0
  233. package/src/audit/core.ts +8 -2
  234. package/src/audit/discover.test.ts +11 -0
  235. package/src/audit/discover.ts +7 -2
  236. package/src/change-set-bundle.test.ts +57 -0
  237. package/src/change-set.test.ts +215 -0
  238. package/src/change-set.ts +445 -0
  239. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  240. package/src/cli/commands/audit.ts +29 -2
  241. package/src/cli/commands/build.test.ts +19 -0
  242. package/src/cli/commands/build.ts +3 -2
  243. package/src/cli/handlers/build.ts +13 -1
  244. package/src/cli/handlers/change-set.test.ts +56 -0
  245. package/src/cli/handlers/change-set.ts +44 -0
  246. package/src/cli/handlers/fan-out.test.ts +221 -2
  247. package/src/cli/handlers/fan-out.ts +165 -75
  248. package/src/cli/handlers/graph.ts +3 -0
  249. package/src/cli/handlers/operator.test.ts +21 -0
  250. package/src/cli/handlers/operator.ts +34 -3
  251. package/src/cli/handlers/pr.ts +373 -0
  252. package/src/cli/handlers/run.ts +1 -0
  253. package/src/cli/main.test.ts +20 -0
  254. package/src/cli/main.ts +185 -23
  255. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  256. package/src/cli/mcp/op-tools.ts +24 -2
  257. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  258. package/src/cli/mcp/workspace-tools.ts +34 -6
  259. package/src/cli/registry.ts +63 -1
  260. package/src/components/auto-release.ts +1 -1
  261. package/src/components/capability.ts +55 -0
  262. package/src/components/changed-files.ts +66 -0
  263. package/src/components/cli-support.ts +28 -5
  264. package/src/components/component.schema.json +17 -0
  265. package/src/components/component.ts +25 -0
  266. package/src/components/deploy-units.ts +3 -0
  267. package/src/components/driver.test.ts +52 -0
  268. package/src/components/driver.ts +31 -13
  269. package/src/components/fan-out-output.ts +42 -1
  270. package/src/components/fan-out-record.ts +69 -0
  271. package/src/components/fan-out-run.test.ts +17 -0
  272. package/src/components/fan-out-run.ts +304 -1
  273. package/src/components/fan-out-support.ts +3 -0
  274. package/src/components/fan-out-waves.test.ts +375 -0
  275. package/src/components/fan-out.ts +18 -17
  276. package/src/components/gated-wave-pipeline.ts +82 -0
  277. package/src/components/index.ts +1 -0
  278. package/src/components/layers.ts +30 -0
  279. package/src/components/pr-pipeline.ts +91 -0
  280. package/src/components/pr-run.test.ts +205 -0
  281. package/src/components/pr-run.ts +280 -0
  282. package/src/components/promote.ts +5 -0
  283. package/src/components/verbs/shell.test.ts +11 -0
  284. package/src/components/verbs/shell.ts +7 -3
  285. package/src/components/wave-plan.ts +124 -0
  286. package/src/discovery/sandbox/bundle.ts +1 -1
  287. package/src/gated-waves-bundle.test.ts +62 -0
  288. package/src/gated-waves.test.ts +118 -0
  289. package/src/gated-waves.ts +240 -0
  290. package/src/index.ts +1 -0
  291. package/src/lexicon.ts +51 -0
  292. package/src/lifecycle/gate-ledger.ts +7 -0
  293. package/src/lifecycle/git.ts +102 -16
  294. package/src/lint/post-synth.test.ts +19 -0
  295. package/src/lint/post-synth.ts +19 -1
  296. package/src/meta/declared-imports.test.ts +1 -1
  297. package/src/op/activities/activity-contracts.ts +84 -0
  298. package/src/op/activities/change-set.ts +114 -0
  299. package/src/op/activities/decide.ts +3 -0
  300. package/src/op/activities/factory.ts +675 -0
  301. package/src/op/activities/index.ts +6 -0
  302. package/src/op/activity-contract.test.ts +21 -1
  303. package/src/op/activity-contract.ts +19 -9
  304. package/src/op/builders.ts +23 -0
  305. package/src/op/decide-backend.ts +5 -1
  306. package/src/op/decide-read-inputs.ts +33 -5
  307. package/src/op/factory.e2e.test.ts +232 -0
  308. package/src/op/factory.ts +118 -0
  309. package/src/op/index.ts +9 -1
  310. package/src/op/op-ir.test.ts +9 -0
  311. package/src/op/op-ir.ts +4 -0
  312. package/src/op/op-verb-class.ts +3 -0
  313. package/src/op/types.ts +8 -0
  314. package/src/op/work-lease-run.ts +3 -0
  315. package/src/plan-summary.test.ts +263 -0
  316. package/src/plan-summary.ts +845 -0
  317. package/src/pr-forge.test.ts +237 -0
  318. package/src/pr-forge.ts +332 -0
  319. package/src/pr-loop-bundle.test.ts +46 -0
  320. package/src/pr-loop.test.ts +171 -0
  321. package/src/pr-loop.ts +324 -0
  322. package/src/telemetry-attribution.test.ts +23 -1
  323. package/src/telemetry-attribution.ts +51 -0
  324. package/src/terraform/hcl2json.test.ts +21 -0
  325. package/src/terraform/hcl2json.ts +81 -0
  326. package/src/terraform/parse.ts +11 -74
  327. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  328. package/src/workspace/app-kind.test.ts +196 -0
  329. package/src/workspace/box-cli.ts +113 -0
  330. package/src/workspace/box-factory.test.ts +4 -1
  331. package/src/workspace/box-factory.ts +37 -4
  332. package/src/workspace/box-listing-write.schema.json +204 -0
  333. package/src/workspace/box-listing.test.ts +234 -0
  334. package/src/workspace/box-listing.ts +324 -0
  335. package/src/workspace/box-publish.schema.json +498 -0
  336. package/src/workspace/box-publish.test.ts +204 -0
  337. package/src/workspace/box-publish.ts +420 -0
  338. package/src/workspace/box-ship.test.ts +96 -0
  339. package/src/workspace/broker-protocol.schema.json +186 -0
  340. package/src/workspace/broker-protocol.test.ts +122 -0
  341. package/src/workspace/broker-protocol.ts +317 -0
  342. package/src/workspace/builder-tiers.test.ts +146 -0
  343. package/src/workspace/change-set.schema.json +460 -0
  344. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  345. package/src/workspace/checks/boxes.test.ts +3 -0
  346. package/src/workspace/checks.ts +10 -3
  347. package/src/workspace/component-environments.test.ts +105 -0
  348. package/src/workspace/composites.schema.json +23 -4
  349. package/src/workspace/composites.test.ts +8 -8
  350. package/src/workspace/composites.ts +13 -2
  351. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  352. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  353. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
  354. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
  355. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  356. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  357. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
  358. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  359. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
  361. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
  362. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
  363. package/src/workspace/conformance/broker.test.ts +122 -0
  364. package/src/workspace/conformance/broker.ts +792 -0
  365. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  366. package/src/workspace/conformance/index.mjs +22 -1
  367. package/src/workspace/conformance/index.ts +5 -1
  368. package/src/workspace/conformance/vitest.ts +33 -1
  369. package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
  370. package/src/workspace/conformance/writer.test.ts +32 -2
  371. package/src/workspace/conformance/writer.ts +339 -28
  372. package/src/workspace/decide.test.ts +208 -4
  373. package/src/workspace/decide.ts +427 -73
  374. package/src/workspace/decision-points.schema.json +11 -4
  375. package/src/workspace/declaration.schema.json +153 -4
  376. package/src/workspace/declaration.ts +141 -0
  377. package/src/workspace/environments.ts +66 -14
  378. package/src/workspace/factory-rules.test.ts +62 -0
  379. package/src/workspace/factory-rules.ts +218 -0
  380. package/src/workspace/graph-cli.ts +2 -2
  381. package/src/workspace/graph.schema.json +67 -0
  382. package/src/workspace/identity.test.ts +23 -0
  383. package/src/workspace/init.ts +82 -2
  384. package/src/workspace/intent-cli.ts +26 -9
  385. package/src/workspace/intent-patch-id.test.ts +162 -0
  386. package/src/workspace/intent-record.schema.json +130 -3
  387. package/src/workspace/intent-record.ts +74 -28
  388. package/src/workspace/intent-reviews.test.ts +76 -0
  389. package/src/workspace/intent-squash.test.ts +213 -0
  390. package/src/workspace/intent-why.test.ts +5 -5
  391. package/src/workspace/intent.schema.json +234 -4
  392. package/src/workspace/intent.ts +260 -66
  393. package/src/workspace/json-edit.test.ts +121 -0
  394. package/src/workspace/json-edit.ts +142 -0
  395. package/src/workspace/jsonc.ts +20 -0
  396. package/src/workspace/kinds.ts +157 -2
  397. package/src/workspace/lexicon-symbols.test.ts +148 -0
  398. package/src/workspace/lexicon-symbols.ts +125 -0
  399. package/src/workspace/lineage-upgrade.test.ts +63 -0
  400. package/src/workspace/lineage-upgrade.ts +146 -5
  401. package/src/workspace/plan-summary.schema.json +155 -0
  402. package/src/workspace/point-answer.schema.json +42 -2
  403. package/src/workspace/points-cli.ts +42 -16
  404. package/src/workspace/points-write.schema.json +162 -4
  405. package/src/workspace/points.schema.json +157 -2
  406. package/src/workspace/points.test.ts +13 -1
  407. package/src/workspace/points.ts +118 -12
  408. package/src/workspace/pr-report.schema.json +95 -0
  409. package/src/workspace/profiles.test.ts +159 -0
  410. package/src/workspace/read-contract.test.ts +2 -2
  411. package/src/workspace/reason-codes.test.ts +13 -1
  412. package/src/workspace/reason-codes.ts +38 -4
  413. package/src/workspace/record-sessions.ts +9 -8
  414. package/src/workspace/records-amend.schema.json +78 -0
  415. package/src/workspace/records-cli.ts +20 -6
  416. package/src/workspace/records-close.schema.json +78 -0
  417. package/src/workspace/records-close.ts +31 -7
  418. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  419. package/src/workspace/records-concurrent.test.ts +145 -0
  420. package/src/workspace/records-formats.test.ts +46 -10
  421. package/src/workspace/records-new.schema.json +7 -0
  422. package/src/workspace/records-review.schema.json +78 -0
  423. package/src/workspace/records-sessions-write.test.ts +69 -0
  424. package/src/workspace/records-sessions.test.ts +22 -0
  425. package/src/workspace/records-write.ts +192 -25
  426. package/src/workspace/records.schema.json +16 -0
  427. package/src/workspace/records.ts +50 -9
  428. package/src/workspace/reference-kinds/app/package.json +10 -0
  429. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  430. package/src/workspace/run-statement.schema.json +622 -0
  431. package/src/workspace/runs-cli.ts +343 -14
  432. package/src/workspace/runs-statement.test.ts +239 -0
  433. package/src/workspace/runs-write.schema.json +416 -5
  434. package/src/workspace/runs.schema.json +451 -4
  435. package/src/workspace/runs.test.ts +39 -0
  436. package/src/workspace/runs.ts +296 -35
  437. package/src/workspace/session-kinds.ts +3 -3
  438. package/src/workspace/squash.ts +198 -0
  439. package/src/workspace/status-contract.test.ts +5 -2
  440. package/src/workspace/status-gates.ts +3 -1
  441. package/src/workspace/status.schema.json +126 -3
  442. package/src/workspace/status.ts +90 -1
  443. package/src/workspace/symbols.ts +20 -9
  444. package/src/workspace/trust/run-commits.ts +115 -0
  445. package/src/workspace/trust/run-statement.ts +226 -0
  446. package/src/workspace/trust/seal.ts +8 -2
  447. package/src/workspace/trust/verify-cli.ts +6 -5
  448. package/src/workspace/trust/verify.ts +39 -6
  449. package/src/workspace/wip-cli.ts +97 -0
  450. package/src/workspace/wip-write.schema.json +504 -0
  451. package/src/workspace/wip.schema.json +334 -0
  452. package/src/workspace/wip.test.ts +294 -0
  453. package/src/workspace/wip.ts +753 -0
  454. package/src/workspace/work-cli.ts +3 -0
  455. package/src/workspace/work-evidence.schema.json +3 -0
  456. package/src/workspace/work-size.test.ts +77 -0
  457. package/src/workspace/work-size.ts +93 -0
  458. package/src/workspace/workspace-kinds.schema.json +122 -0
  459. package/src/workspace/write-lock-cli.ts +131 -0
  460. package/src/workspace/write-lock.schema.json +172 -0
  461. package/src/workspace/write-lock.test.ts +189 -0
  462. package/src/workspace/write-lock.ts +474 -0
  463. package/src/workspace/write-scope.test.ts +16 -1
  464. package/src/workspace/write-scope.ts +33 -11
@@ -10,9 +10,16 @@
10
10
  * - `runs start --from <file|->`, `runs end <id> --from <file|->` and `runs
11
11
  * record --from <file|->` append a run's start, its end, or both, to
12
12
  * `_agent-runs/<id>.jsonl` on `chant/lifecycle` (`runs-write.schema.json`).
13
+ * - `runs sign <id> --key <runner key.pem> | --envelope <file|->` appends a
14
+ * statement over an ended run, signed by a runner or steward key the trust
15
+ * policy at base lists, or signed elsewhere and checked here (#3192).
16
+ * `runs statement <id> --signer <principal>` prints the statement for a
17
+ * signer that holds its key elsewhere, and `runs verify [<id>] [--require
18
+ * signed]` judges every stored statement (`run-statement.schema.json`).
13
19
  *
14
20
  * chant records runs and never starts one: whatever ran the agent writes the
15
- * record. Reads never fetch.
21
+ * record. Reads never fetch, except a pull request ref `--follow-squash`
22
+ * needs (#3035).
16
23
  */
17
24
 
18
25
  import { readFileSync } from "node:fs";
@@ -25,9 +32,12 @@ import type { ReasonCode } from "./reason-codes";
25
32
  import { loadRecordKind, RecordReadError } from "./records";
26
33
  import { queryRecords } from "./records-cli";
27
34
  import {
35
+ appendRunStatement,
28
36
  commitsByRunTrailer,
29
37
  endRun,
30
38
  foldRun,
39
+ joinPatchIdCommits,
40
+ joinSquashCommits,
31
41
  joinTrailerCommits,
32
42
  LEDGER_BRANCH,
33
43
  readRuns,
@@ -43,6 +53,10 @@ import { locateWorkspace } from "./which-chant";
43
53
  import { idList } from "./work";
44
54
  import { AGENT_ENV } from "./write-scope";
45
55
  import { execFileSync } from "node:child_process";
56
+ import { IN_TOTO_PAYLOAD_TYPE, loadRunnerKey, type DsseEnvelope } from "./trust/dsse";
57
+ import { policyAtBase, resolveBase, type BaseSource } from "./trust/provenance";
58
+ import type { TrustPolicy } from "./trust/policy";
59
+ import { attestRun, buildRunStatement, runStatementPayload, signRunStatement, verifyRunStatement, type RunAttestation, type RunStatement } from "./trust/run-statement";
46
60
 
47
61
  /** The version of the `runs` documents this chant writes: read contract 1. */
48
62
  export const RUNS_CONTRACT_VERSION = 1;
@@ -50,14 +64,20 @@ export const RUNS_CONTRACT_VERSION = 1;
50
64
  /** `$id` of the JSON Schema for `runs --json`, shipped beside this file. */
51
65
  export const RUNS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/runs/v1/runs.schema.json";
52
66
 
53
- /** `$id` of the JSON Schema for what `runs start|end|record` print. */
67
+ /** `$id` of the JSON Schema for what `runs start|end|record|sign` print. */
54
68
  export const RUNS_WRITE_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/runs-write/v1/runs-write.schema.json";
55
69
 
70
+ /** `$id` of the JSON Schema for what `runs statement` and `runs verify` print (#3192). */
71
+ export const RUN_STATEMENT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/run-statement/v1/run-statement.schema.json";
72
+
56
73
  const USAGE = [
57
- "chant workspace runs [--unit <id>] [--decision <id>] [--by <principal>] [--since <rev>] [--json]",
74
+ "chant workspace runs [--unit <id>] [--decision <id>] [--by <principal>] [--since <rev>] [--follow-squash] [--json]",
58
75
  "chant workspace runs start --from <file|->",
59
76
  "chant workspace runs end <run id> [--from <file|->]",
60
77
  "chant workspace runs record --from <file|->",
78
+ "chant workspace runs sign <run id> (--key <runner key.pem> | --envelope <file|->) [--base <rev>]",
79
+ "chant workspace runs statement <run id> --signer <principal>",
80
+ "chant workspace runs verify [<run id>] [--require signed] [--base <rev>] [--json]",
61
81
  ].join("\n");
62
82
 
63
83
  /** Why `runs` read nothing. Closed. */
@@ -65,13 +85,36 @@ export const RUNS_ERROR_CODES = [...WORKSPACE_ERROR_CODES] as const satisfies re
65
85
  export type RunsErrorCode = (typeof RUNS_ERROR_CODES)[number];
66
86
 
67
87
  /** Why part of the read is missing. The read still succeeds. Closed. */
68
- export const RUNS_REASON_CODES = ["runs-no-ledger", "runs-ledger-malformed", "kind-unreadable"] as const satisfies readonly ReasonCode[];
88
+ export const RUNS_REASON_CODES = ["runs-no-ledger", "runs-ledger-malformed", "kind-unreadable", "squash-unfollowed"] as const satisfies readonly ReasonCode[];
69
89
  export type RunsReasonCode = (typeof RUNS_REASON_CODES)[number];
70
90
 
71
91
  /** Why a run write was refused or could not run. Closed. */
72
- export const RUNS_WRITE_ERROR_CODES = [...WORKSPACE_ERROR_CODES, "write-usage-invalid", "write-input-invalid", "run-exists", "run-unknown", "run-ended"] as const satisfies readonly ReasonCode[];
92
+ export const RUNS_WRITE_ERROR_CODES = [
93
+ ...WORKSPACE_ERROR_CODES,
94
+ "write-usage-invalid",
95
+ "write-input-invalid",
96
+ "run-exists",
97
+ "run-unknown",
98
+ "run-ended",
99
+ // runs sign (#3192)
100
+ "run-not-ended",
101
+ "trust-policy-unreadable",
102
+ "runner-key-invalid",
103
+ "runner-key-is-signer",
104
+ "runner-key-unlisted",
105
+ "envelope-unreadable",
106
+ "envelope-invalid",
107
+ "envelope-untrusted",
108
+ "run-statement-invalid",
109
+ "run-statement-signer-mismatch",
110
+ "run-statement-mismatch",
111
+ ] as const satisfies readonly ReasonCode[];
73
112
  export type RunsWriteErrorCode = (typeof RUNS_WRITE_ERROR_CODES)[number];
74
113
 
114
+ /** Why `runs statement` or `runs verify` printed nothing to judge. Closed. */
115
+ export const RUN_STATEMENT_ERROR_CODES = [...WORKSPACE_ERROR_CODES, "write-usage-invalid", "run-unknown", "run-not-ended"] as const satisfies readonly ReasonCode[];
116
+ export type RunStatementErrorCode = (typeof RUN_STATEMENT_ERROR_CODES)[number];
117
+
75
118
  // ── Totals ───────────────────────────────────────────────────────────────────
76
119
 
77
120
  /** Tokens and cost summed over some runs. */
@@ -130,6 +173,10 @@ export interface RunsQuery {
130
173
  by?: string;
131
174
  /** Only the runs that made a commit after `since` on HEAD, or started after its commit date. */
132
175
  since?: string;
176
+ /** The revision whose trust policy judges the run statements (#3192). Default: origin/HEAD, then main, then master. */
177
+ base?: string;
178
+ /** Follow squash merges on HEAD to their pull requests' original commits, fetching missing pull request refs (#3035). */
179
+ followSquash?: boolean;
133
180
  }
134
181
 
135
182
  export interface RunsReason {
@@ -137,6 +184,31 @@ export interface RunsReason {
137
184
  message: string;
138
185
  }
139
186
 
187
+ /** The trust policy at base, as far as run statements need it (#3192). */
188
+ export interface RunsTrust {
189
+ base: string | null;
190
+ baseFrom: BaseSource | null;
191
+ /** The runner and steward keys at base, by principal and class. */
192
+ runners: { principal: string; class: "runner" | "service" }[];
193
+ /** Why the policy at base can't be read. Nothing verifies then. */
194
+ problems: string[];
195
+ }
196
+
197
+ /** The policy at base for run statements, and its summary. */
198
+ export function runsTrust(top: string, base?: string): { policy: TrustPolicy; trust: RunsTrust } {
199
+ const resolved = resolveBase(top, base);
200
+ const policy = policyAtBase(top, resolved);
201
+ return {
202
+ policy,
203
+ trust: { base: resolved.commit, baseFrom: resolved.from, runners: policy.runners.map((r) => ({ principal: r.principal, class: r.class })), problems: policy.problems },
204
+ };
205
+ }
206
+
207
+ /** Judge each run's statements in place against the runners at base. */
208
+ export function attestRuns(runs: Iterable<RunView>, policy: TrustPolicy): void {
209
+ for (const r of runs) r.attestation = attestRun(r, policy.problems.length > 0 ? [] : policy.runners);
210
+ }
211
+
140
212
  /** What `runs --json` prints. */
141
213
  export type RunsDocument =
142
214
  | {
@@ -145,8 +217,11 @@ export type RunsDocument =
145
217
  chant: string;
146
218
  workspace: { name: string; root: string };
147
219
  ledger: { branch: string; dir: string; commit: string | null };
148
- filter: { unit: string | null; decision: string | null; by: string | null; since: string | null };
149
- /** Newest first, by when each started. */
220
+ /** The options given. `followSquash` was added within contract 1 (#3035). */
221
+ filter: { unit: string | null; decision: string | null; by: string | null; since: string | null; followSquash?: boolean };
222
+ /** The trust policy the run statements are judged by (#3192). */
223
+ trust: RunsTrust;
224
+ /** Newest first, by when each started. Each carries its `attestation`. */
150
225
  runs: RunView[];
151
226
  totals: {
152
227
  all: RunTotals;
@@ -215,7 +290,18 @@ export async function workspaceRuns(query: RunsQuery): Promise<RunsDocument> {
215
290
  const { tip, dir, runs, malformed } = await readRuns(top, located.rootOnDisk);
216
291
  if (!tip) reasons.push({ code: "runs-no-ledger", message: `this checkout has no ${LEDGER_BRANCH} branch, so there are no agent runs to read; fetch it first if the remote has one` });
217
292
  if (malformed > 0) reasons.push({ code: "runs-ledger-malformed", message: `${malformed} ${malformed === 1 ? "line" : "lines"} of ${dir} on ${LEDGER_BRANCH} ${malformed === 1 ? "is" : "are"} not a run event and ${malformed === 1 ? "was" : "were"} left out` });
218
- joinTrailerCommits(runs, commitsByRunTrailer(top), top);
293
+ const byTrailer = commitsByRunTrailer(top);
294
+ joinTrailerCommits(runs, byTrailer, top);
295
+ // A commit that lost its trailer joins by content (#3036).
296
+ joinPatchIdCommits(runs, byTrailer, top);
297
+ // A squash merge joins the runs its pull request's commits join, when asked (#3035).
298
+ if (query.followSquash) {
299
+ for (const f of joinSquashCommits(runs, top)) {
300
+ if (f.problem) reasons.push({ code: "squash-unfollowed", message: `${f.sha.slice(0, 8)} squashes pull request #${f.pullRequest}, and ${f.problem}, so the runs of its original commits are not joined to it` });
301
+ }
302
+ }
303
+ const { policy, trust } = runsTrust(top, query.base);
304
+ attestRuns(runs.values(), policy);
219
305
  const all = [...runs.values()];
220
306
  await resolveRunDecisions(all, located.rootOnDisk, declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk)), reasons);
221
307
 
@@ -241,7 +327,8 @@ export async function workspaceRuns(query: RunsQuery): Promise<RunsDocument> {
241
327
  ...head,
242
328
  workspace: { name: declaration.name, root: located.root },
243
329
  ledger: { branch: LEDGER_BRANCH, dir, commit: tip },
244
- filter: { unit: query.unit ?? null, decision: query.decision ?? null, by: query.by ?? null, since: query.since ?? null },
330
+ filter: { unit: query.unit ?? null, decision: query.decision ?? null, by: query.by ?? null, since: query.since ?? null, followSquash: query.followSquash === true },
331
+ trust,
245
332
  runs: selected,
246
333
  totals: { all: totalRuns(selected), byUnit, byDecision, byPrincipal },
247
334
  malformed,
@@ -255,7 +342,7 @@ export async function workspaceRuns(query: RunsQuery): Promise<RunsDocument> {
255
342
 
256
343
  // ── The writes ───────────────────────────────────────────────────────────────
257
344
 
258
- export type RunsVerb = "start" | "end" | "record";
345
+ export type RunsVerb = "start" | "end" | "record" | "sign";
259
346
 
260
347
  /** What `runs start|end|record` print. */
261
348
  export type RunsWriteDocument =
@@ -268,6 +355,8 @@ export type RunsWriteDocument =
268
355
  /** The line a commit the run makes carries, `Chant-Run: <id>` (#3149). */
269
356
  trailer: string;
270
357
  ledger: RunLedgerWrite;
358
+ /** For `sign`: who signed the statement it stored, and the envelope (#3192). */
359
+ statement?: { signer: string; class: "runner" | "service"; keyid: string; envelope: DsseEnvelope };
271
360
  }
272
361
  | { $schema: string; contract: number; verb: RunsVerb | null; error: { code: RunsWriteErrorCode; message: string } };
273
362
 
@@ -311,6 +400,194 @@ export async function runsWrite(req: RunsWriteRequest): Promise<RunsWriteDocumen
311
400
  }
312
401
  }
313
402
 
403
+ // ── Run statements (#3192) ──────────────────────────────────────────────────
404
+
405
+ /** A run as the ledger holds it now, with the commits its trailer joins. */
406
+ async function currentRun(top: string, rootOnDisk: string, id: string): Promise<RunView | undefined> {
407
+ const { runs } = await readRuns(top, rootOnDisk);
408
+ joinTrailerCommits(runs, commitsByRunTrailer(top), top);
409
+ return runs.get(id);
410
+ }
411
+
412
+ export interface RunsSignRequest {
413
+ id: string;
414
+ cwd: string;
415
+ /** A runner key in PEM: chant builds the statement and signs it. */
416
+ keyPem?: string;
417
+ /** An envelope signed elsewhere, as text: chant checks it and stores it. */
418
+ envelope?: string;
419
+ base?: string;
420
+ now?: () => Date;
421
+ }
422
+
423
+ /**
424
+ * `runs sign <id>`: store a statement over an ended run, signed here with
425
+ * `keyPem` or signed elsewhere and given as `envelope`. Either way it must
426
+ * verify against a runner key at base and agree with the run's record before
427
+ * anything is written.
428
+ */
429
+ export async function runsSign(req: RunsSignRequest): Promise<RunsWriteDocument> {
430
+ const head = { $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION };
431
+ const fail = (code: RunsWriteErrorCode, message: string): RunsWriteDocument => ({ ...head, verb: "sign", error: { code, message } });
432
+ try {
433
+ const located = locateWorkspace(req.cwd);
434
+ const declaration = readDeclaration(located.tree, "", { rootChant: true });
435
+ const top = located.top;
436
+ if (!top) return fail("not-a-git-repository", "agent runs are kept on the chant/lifecycle branch, and this directory is not in a git repository");
437
+ const run = await currentRun(top, located.rootOnDisk, req.id);
438
+ if (!run) return fail("run-unknown", `the ledger has no agent run ${req.id}`);
439
+ if (!run.record) return fail("run-not-ended", `agent run ${req.id} has not ended; a statement is signed over a run's whole record, so record its end first`);
440
+ const { policy } = runsTrust(top, req.base);
441
+ if (policy.problems.length > 0) return fail("trust-policy-unreadable", `the policy at base cannot be read: ${policy.problems.join("; ")}`);
442
+
443
+ let envelope: DsseEnvelope;
444
+ if (req.keyPem !== undefined) {
445
+ let runner;
446
+ try {
447
+ runner = loadRunnerKey(req.keyPem);
448
+ } catch (err) {
449
+ return fail("runner-key-invalid", `the key is not an Ed25519 private key in PEM: ${err instanceof Error ? err.message : String(err)}`);
450
+ }
451
+ if (policy.signers.some((s) => s.key === runner.publicKey)) {
452
+ return fail("runner-key-is-signer", `this is a signer's key in ${policy.signersPath}. A run statement is signed by a runner or steward identity, never by a person's key`);
453
+ }
454
+ const listed = policy.runners.find((r) => r.key === runner.publicKey);
455
+ if (!listed) return fail("runner-key-unlisted", `the policy at base lists no runner with this key. Add it to .chant/trust.json "runners" first:\n ${runner.publicKey}`);
456
+ envelope = signRunStatement(buildRunStatement(run, listed.principal), runner.key, runner.publicKey);
457
+ } else {
458
+ try {
459
+ envelope = JSON.parse(req.envelope ?? "") as DsseEnvelope;
460
+ } catch (err) {
461
+ return fail("envelope-unreadable", `--envelope could not be read as JSON: ${err instanceof Error ? err.message : String(err)}`);
462
+ }
463
+ }
464
+ const verdict = verifyRunStatement(envelope, policy.runners, run);
465
+ if (verdict.status !== "verified") return fail(verdict.code, verdict.reason);
466
+
467
+ const ctx: RunWriteContext = { top, rootOnDisk: located.rootOnDisk, cwd: req.cwd, now: req.now };
468
+ const result = await appendRunStatement(run.id, envelope, run.record.sha256, ctx);
469
+ const view = foldRun(result.id, result.ledger.path, result.lines)!;
470
+ const joined = new Map([[view.id, view]]);
471
+ joinTrailerCommits(joined, commitsByRunTrailer(top), top);
472
+ await resolveRunDecisions([view], located.rootOnDisk, declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk)), []);
473
+ attestRuns([view], policy);
474
+ return {
475
+ ...head,
476
+ verb: "sign",
477
+ run: view,
478
+ trailer: `${RUN_TRAILER}: ${view.id}`,
479
+ ledger: result.ledger,
480
+ statement: { signer: verdict.signer, class: verdict.class, keyid: verdict.keyid, envelope },
481
+ };
482
+ } catch (err) {
483
+ if (err instanceof RunWriteError) return fail(err.code, err.message);
484
+ if (err instanceof WorkspaceReadError) return fail(err.code as RunsWriteErrorCode, err.describe());
485
+ throw err;
486
+ }
487
+ }
488
+
489
+ /** What `runs statement` and `runs verify` print. */
490
+ export type RunStatementDocument =
491
+ | {
492
+ $schema: string;
493
+ contract: number;
494
+ chant: string;
495
+ verb: "statement";
496
+ run: string;
497
+ /** The DSSE payload type to sign under. */
498
+ payloadType: string;
499
+ /** Base64 of the payload bytes to sign: the statement's canonical JSON. */
500
+ payload: string;
501
+ statement: RunStatement;
502
+ }
503
+ | {
504
+ $schema: string;
505
+ contract: number;
506
+ chant: string;
507
+ verb: "verify";
508
+ trust: RunsTrust;
509
+ require: "signed" | null;
510
+ runs: { id: string; state: "running" | "ended"; record: { sha256: string } | null; attestation: RunAttestation }[];
511
+ summary: { runs: number; signed: number; unsigned: number; failed: number; running: number };
512
+ /** Why `--require signed` fails. Empty without it: the report alone never fails (studio-035 d). */
513
+ failures: string[];
514
+ ok: boolean;
515
+ }
516
+ | { $schema: string; contract: number; chant: string; verb: "statement" | "verify" | null; error: { code: RunStatementErrorCode; message: string } };
517
+
518
+ function statementHead() {
519
+ return { $schema: RUN_STATEMENT_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, chant: readerVersion() };
520
+ }
521
+
522
+ /** `runs statement <id> --signer <principal>`: the statement a signer holding its key elsewhere signs. */
523
+ export async function runStatement(id: string, signer: string, cwd: string): Promise<RunStatementDocument> {
524
+ const fail = (code: RunStatementErrorCode, message: string): RunStatementDocument => ({ ...statementHead(), verb: "statement", error: { code, message } });
525
+ try {
526
+ const located = locateWorkspace(cwd);
527
+ readDeclaration(located.tree, "", { rootChant: true });
528
+ if (!located.top) return fail("not-a-git-repository", "agent runs are kept on the chant/lifecycle branch, and this directory is not in a git repository");
529
+ const run = await currentRun(located.top, located.rootOnDisk, id);
530
+ if (!run) return fail("run-unknown", `the ledger has no agent run ${id}`);
531
+ if (!run.record) return fail("run-not-ended", `agent run ${id} has not ended; a statement is signed over a run's whole record`);
532
+ const statement = buildRunStatement(run, signer);
533
+ return { ...statementHead(), verb: "statement", run: id, payloadType: IN_TOTO_PAYLOAD_TYPE, payload: runStatementPayload(statement).toString("base64"), statement };
534
+ } catch (err) {
535
+ if (err instanceof WorkspaceReadError) return fail(err.code as RunStatementErrorCode, err.describe());
536
+ throw err;
537
+ }
538
+ }
539
+
540
+ /**
541
+ * `runs verify [<id>] [--require signed]`: judge each run's statements
542
+ * against the runner keys at base. Report only unless `--require signed`
543
+ * asks every ended run, or the one named, to be signed.
544
+ */
545
+ export async function runsVerify(query: { cwd: string; id?: string; require?: "signed"; base?: string }): Promise<RunStatementDocument> {
546
+ const fail = (code: RunStatementErrorCode, message: string): RunStatementDocument => ({ ...statementHead(), verb: "verify", error: { code, message } });
547
+ try {
548
+ const located = locateWorkspace(query.cwd);
549
+ readDeclaration(located.tree, "", { rootChant: true });
550
+ const top = located.top;
551
+ if (!top) return fail("not-a-git-repository", "agent runs are kept on the chant/lifecycle branch, and this directory is not in a git repository");
552
+ const { runs } = await readRuns(top, located.rootOnDisk);
553
+ joinTrailerCommits(runs, commitsByRunTrailer(top), top);
554
+ if (query.id !== undefined && !runs.has(query.id)) return fail("run-unknown", `the ledger has no agent run ${query.id}`);
555
+ const { policy, trust } = runsTrust(top, query.base);
556
+ const selected = [...runs.values()]
557
+ .filter((r) => query.id === undefined || r.id === query.id)
558
+ .sort((a, b) => (a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : a.id < b.id ? 1 : -1));
559
+ attestRuns(selected, policy);
560
+ const out = selected.map((r) => ({ id: r.id, state: r.state, record: r.record, attestation: r.attestation! }));
561
+ const failures: string[] = [];
562
+ if (query.require === "signed") {
563
+ for (const r of out) {
564
+ if (r.state === "running" || r.attestation.status === "signed") continue;
565
+ failures.push(`${r.id} is ${r.attestation.status}, and --require signed was given: ${r.attestation.reason}`);
566
+ }
567
+ }
568
+ const count = (f: (r: (typeof out)[number]) => boolean) => out.filter(f).length;
569
+ return {
570
+ ...statementHead(),
571
+ verb: "verify",
572
+ trust,
573
+ require: query.require ?? null,
574
+ runs: out,
575
+ summary: {
576
+ runs: out.length,
577
+ signed: count((r) => r.attestation.status === "signed"),
578
+ unsigned: count((r) => r.attestation.status === "unsigned"),
579
+ failed: count((r) => r.attestation.status !== "signed" && r.attestation.status !== "unsigned"),
580
+ running: count((r) => r.state === "running"),
581
+ },
582
+ failures,
583
+ ok: failures.length === 0,
584
+ };
585
+ } catch (err) {
586
+ if (err instanceof WorkspaceReadError) return fail(err.code as RunStatementErrorCode, err.describe());
587
+ throw err;
588
+ }
589
+ }
590
+
314
591
  // ── Text ─────────────────────────────────────────────────────────────────────
315
592
 
316
593
  function money(t: RunTotals): string {
@@ -325,7 +602,8 @@ export function formatRuns(doc: Extract<RunsDocument, { runs: unknown }>): strin
325
602
  const model = [r.harness.name, r.model].filter(Boolean).join("/");
326
603
  const tokens = r.usage ? `${r.usage.inputTokens ?? "?"} in, ${r.usage.outputTokens ?? "?"} out` : "no usage";
327
604
  const cost = r.cost ? `${r.cost.amount} ${r.cost.currency} (${r.cost.source})` : "unpriced";
328
- out.push(`${r.id} ${r.state}${r.outcome ? ` ${r.outcome}` : ""} ${r.startedAt} ${model}${r.by ? ` for ${r.by}` : ""}${r.unit ? ` on ${r.unit.id}` : ""} ${tokens} ${cost} ${r.commits.length} commit${r.commits.length === 1 ? "" : "s"}`);
605
+ const signed = r.attestation ? (r.attestation.status === "signed" ? ` signed by ${r.attestation.signer}` : r.attestation.status === "unsigned" ? "" : ` statement ${r.attestation.status}`) : "";
606
+ out.push(`${r.id} ${r.state}${r.outcome ? ` ${r.outcome}` : ""} ${r.startedAt} ${model}${r.by ? ` for ${r.by}` : ""}${r.unit ? ` on ${r.unit.id}` : ""} ${tokens} ${cost} ${r.commits.length} commit${r.commits.length === 1 ? "" : "s"}${signed}`);
329
607
  }
330
608
  if (doc.runs.length === 0) out.push("no agent runs");
331
609
  const t = doc.totals.all;
@@ -345,13 +623,14 @@ export async function runWorkspaceRuns(ctx: CommandContext): Promise<number> {
345
623
  const { args } = ctx;
346
624
  const cwd = process.cwd();
347
625
  const verb = args.extraPositional;
626
+ if (verb === "sign" || verb === "statement" || verb === "verify") return runStatementVerb(ctx, verb, cwd);
348
627
  if (verb === "start" || verb === "end" || verb === "record") {
349
628
  const print = (doc: RunsWriteDocument): number => {
350
629
  console.log(JSON.stringify(doc, null, 2));
351
630
  return "error" in doc ? 1 : 0;
352
631
  };
353
632
  const usage = (message: string) => print({ $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, verb, error: { code: "write-usage-invalid", message } });
354
- for (const [flag, v] of [["--unit", args.unit], ["--decision", args.decision], ["--by", args.by], ["--since", args.since]] as const) {
633
+ for (const [flag, v] of [["--unit", args.unit], ["--decision", args.decision], ["--by", args.by], ["--since", args.since], ["--follow-squash", args.followSquash]] as const) {
355
634
  if (v !== undefined) return usage(`runs ${verb} takes its fields with --from, not ${flag}`);
356
635
  }
357
636
  if (verb === "end" && !args.extraPositional2) return usage("runs end needs the run id: runs end <run id>");
@@ -366,16 +645,66 @@ export async function runWorkspaceRuns(ctx: CommandContext): Promise<number> {
366
645
  return print(await runsWrite({ verb, id: args.extraPositional2, fields, cwd, agent: process.env[AGENT_ENV] || undefined }));
367
646
  }
368
647
  if (verb !== undefined) {
369
- console.error(formatError({ message: `chant workspace runs takes start, end or record, or no verb to read, not ${verb}`, hint: USAGE }));
648
+ console.error(formatError({ message: `chant workspace runs takes start, end, record, sign, statement or verify, or no verb to read, not ${verb}`, hint: USAGE }));
370
649
  return 1;
371
650
  }
372
651
  if (args.migrateFrom !== undefined) {
373
652
  console.error(formatError({ message: "runs is a read and takes no --from; write with runs start, end or record", hint: USAGE }));
374
653
  return 1;
375
654
  }
376
- const doc = await workspaceRuns({ cwd, unit: args.unit, decision: args.decision, by: args.by, since: args.since });
655
+ const doc = await workspaceRuns({ cwd, unit: args.unit, decision: args.decision, by: args.by, since: args.since, base: args.base, followSquash: args.followSquash });
377
656
  if (args.json) console.log(JSON.stringify(doc, null, 2));
378
657
  else if ("error" in doc) console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
379
658
  else console.log(formatRuns(doc));
380
659
  return "error" in doc ? 1 : 0;
381
660
  }
661
+
662
+ async function runStatementVerb(ctx: CommandContext, verb: "sign" | "statement" | "verify", cwd: string): Promise<number> {
663
+ const { args } = ctx;
664
+ const id = args.extraPositional2;
665
+ if (verb === "sign") {
666
+ const print = (doc: RunsWriteDocument): number => {
667
+ console.log(JSON.stringify(doc, null, 2));
668
+ return "error" in doc ? 1 : 0;
669
+ };
670
+ const usage = (message: string) => print({ $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, verb, error: { code: "write-usage-invalid", message } });
671
+ if (!id) return usage("runs sign needs the run id: runs sign <run id> --key <runner key.pem>");
672
+ if ((args.key === undefined) === (args.envelope === undefined)) return usage("runs sign takes --key <runner key.pem> to sign here, or --envelope <file|-> for a statement signed elsewhere, not both");
673
+ let keyPem: string | undefined;
674
+ let envelope: string | undefined;
675
+ try {
676
+ if (args.key !== undefined) keyPem = readFileSync(resolve(cwd, args.key), "utf-8");
677
+ else envelope = readFields(args.envelope, cwd);
678
+ } catch (err) {
679
+ const which = args.key !== undefined ? "runner-key-invalid" : "envelope-unreadable";
680
+ return print({ $schema: RUNS_WRITE_SCHEMA_ID, contract: RUNS_CONTRACT_VERSION, verb, error: { code: which, message: `${args.key ?? args.envelope} could not be read: ${err instanceof Error ? err.message : String(err)}` } });
681
+ }
682
+ return print(await runsSign({ id, cwd, keyPem, envelope, base: args.base }));
683
+ }
684
+ const print = (doc: RunStatementDocument): number => {
685
+ if (args.json || verb === "statement" || "error" in doc) console.log(JSON.stringify(doc, null, 2));
686
+ else console.log(formatRunsVerify(doc as Extract<RunStatementDocument, { verb: "verify"; runs: unknown }>));
687
+ return "error" in doc || ("ok" in doc && !doc.ok) ? 1 : 0;
688
+ };
689
+ const usage = (message: string) => print({ ...statementHead(), verb, error: { code: "write-usage-invalid", message } });
690
+ if (verb === "statement") {
691
+ if (!id) return usage("runs statement needs the run id: runs statement <run id> --signer <principal>");
692
+ if (!args.signer) return usage("runs statement needs --signer <principal>: the runner principal whose key will sign it, as .chant/trust.json lists it");
693
+ return print(await runStatement(id, args.signer, cwd));
694
+ }
695
+ if (args.require !== undefined && args.require !== "signed") return usage(`runs verify --require takes one level, signed, not ${JSON.stringify(args.require)}`);
696
+ return print(await runsVerify({ cwd, id, require: args.require === "signed" ? "signed" : undefined, base: args.base }));
697
+ }
698
+
699
+ /** `runs verify` as lines for a person. */
700
+ export function formatRunsVerify(doc: Extract<RunStatementDocument, { verb: "verify"; runs: unknown }>): string {
701
+ const out: string[] = [];
702
+ const t = doc.trust;
703
+ out.push(`trust at base ${t.base?.slice(0, 8) ?? "none"}: ${t.runners.length} runner key${t.runners.length === 1 ? "" : "s"}${t.runners.length ? ` (${t.runners.map((r) => `${r.principal} ${r.class}`).join(", ")})` : ""}`);
704
+ for (const p of t.problems) out.push(` ${p}`);
705
+ for (const r of doc.runs) out.push(` ${r.id} ${r.state === "running" ? "running" : r.attestation.status}${r.attestation.signer ? ` by ${r.attestation.signer}` : ""} ${r.attestation.reason}`);
706
+ const s = doc.summary;
707
+ out.push(`${s.runs} run${s.runs === 1 ? "" : "s"}: ${s.signed} signed, ${s.unsigned} unsigned, ${s.failed} with a statement that does not verify, ${s.running} running`);
708
+ for (const f of doc.failures) out.push(`failure ${f}`);
709
+ return out.join("\n");
710
+ }