@intentius/chant 0.102.0 → 0.104.1

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 (471) 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/vendor/js-yaml-load.d.ts +56 -0
  114. package/dist/vendor/js-yaml-load.d.ts.map +1 -0
  115. package/dist/workspace/box-cli.d.ts +12 -0
  116. package/dist/workspace/box-cli.d.ts.map +1 -0
  117. package/dist/workspace/box-factory.d.ts +27 -3
  118. package/dist/workspace/box-factory.d.ts.map +1 -1
  119. package/dist/workspace/box-listing.d.ts +85 -0
  120. package/dist/workspace/box-listing.d.ts.map +1 -0
  121. package/dist/workspace/box-publish.d.ts +147 -0
  122. package/dist/workspace/box-publish.d.ts.map +1 -0
  123. package/dist/workspace/broker-protocol.d.ts +190 -0
  124. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  125. package/dist/workspace/checks.d.ts.map +1 -1
  126. package/dist/workspace/composites.d.ts +2 -2
  127. package/dist/workspace/composites.d.ts.map +1 -1
  128. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  129. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  130. package/dist/workspace/conformance/broker.d.ts +202 -0
  131. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  132. package/dist/workspace/conformance/index.d.ts +2 -1
  133. package/dist/workspace/conformance/index.d.ts.map +1 -1
  134. package/dist/workspace/conformance/vitest.d.ts +9 -2
  135. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  136. package/dist/workspace/conformance/writer.d.ts +108 -14
  137. package/dist/workspace/conformance/writer.d.ts.map +1 -1
  138. package/dist/workspace/decide.d.ts +126 -12
  139. package/dist/workspace/decide.d.ts.map +1 -1
  140. package/dist/workspace/decision-points.schema.json +11 -4
  141. package/dist/workspace/declaration.d.ts +65 -0
  142. package/dist/workspace/declaration.d.ts.map +1 -1
  143. package/dist/workspace/declaration.schema.json +153 -4
  144. package/dist/workspace/environments.d.ts +36 -10
  145. package/dist/workspace/environments.d.ts.map +1 -1
  146. package/dist/workspace/factory-rules.d.ts +180 -0
  147. package/dist/workspace/factory-rules.d.ts.map +1 -0
  148. package/dist/workspace/init.d.ts +21 -0
  149. package/dist/workspace/init.d.ts.map +1 -1
  150. package/dist/workspace/intent-cli.d.ts +1 -1
  151. package/dist/workspace/intent-cli.d.ts.map +1 -1
  152. package/dist/workspace/intent-record.d.ts +28 -1
  153. package/dist/workspace/intent-record.d.ts.map +1 -1
  154. package/dist/workspace/intent.d.ts +102 -5
  155. package/dist/workspace/intent.d.ts.map +1 -1
  156. package/dist/workspace/json-edit.d.ts +24 -0
  157. package/dist/workspace/json-edit.d.ts.map +1 -0
  158. package/dist/workspace/jsonc.d.ts +11 -0
  159. package/dist/workspace/jsonc.d.ts.map +1 -1
  160. package/dist/workspace/kinds.d.ts +48 -0
  161. package/dist/workspace/kinds.d.ts.map +1 -1
  162. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  163. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  164. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  165. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  166. package/dist/workspace/point-answer.schema.json +42 -2
  167. package/dist/workspace/points-cli.d.ts +10 -3
  168. package/dist/workspace/points-cli.d.ts.map +1 -1
  169. package/dist/workspace/points.d.ts +66 -5
  170. package/dist/workspace/points.d.ts.map +1 -1
  171. package/dist/workspace/reason-codes.d.ts +33 -4
  172. package/dist/workspace/reason-codes.d.ts.map +1 -1
  173. package/dist/workspace/record-sessions.d.ts +5 -7
  174. package/dist/workspace/record-sessions.d.ts.map +1 -1
  175. package/dist/workspace/records-cli.d.ts +7 -0
  176. package/dist/workspace/records-cli.d.ts.map +1 -1
  177. package/dist/workspace/records-close.d.ts +7 -3
  178. package/dist/workspace/records-close.d.ts.map +1 -1
  179. package/dist/workspace/records-write.d.ts +66 -6
  180. package/dist/workspace/records-write.d.ts.map +1 -1
  181. package/dist/workspace/records.d.ts +28 -9
  182. package/dist/workspace/records.d.ts.map +1 -1
  183. package/dist/workspace/runs-cli.d.ts +138 -6
  184. package/dist/workspace/runs-cli.d.ts.map +1 -1
  185. package/dist/workspace/runs.d.ts +132 -11
  186. package/dist/workspace/runs.d.ts.map +1 -1
  187. package/dist/workspace/session-kinds.d.ts +1 -1
  188. package/dist/workspace/session-kinds.d.ts.map +1 -1
  189. package/dist/workspace/squash.d.ts +80 -0
  190. package/dist/workspace/squash.d.ts.map +1 -0
  191. package/dist/workspace/status-gates.d.ts +2 -0
  192. package/dist/workspace/status-gates.d.ts.map +1 -1
  193. package/dist/workspace/status.d.ts +54 -0
  194. package/dist/workspace/status.d.ts.map +1 -1
  195. package/dist/workspace/symbols.d.ts +16 -5
  196. package/dist/workspace/symbols.d.ts.map +1 -1
  197. package/dist/workspace/trust/run-commits.d.ts +46 -0
  198. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  199. package/dist/workspace/trust/run-statement.d.ts +128 -0
  200. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  201. package/dist/workspace/trust/seal.d.ts +6 -1
  202. package/dist/workspace/trust/seal.d.ts.map +1 -1
  203. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  204. package/dist/workspace/trust/verify.d.ts +16 -2
  205. package/dist/workspace/trust/verify.d.ts.map +1 -1
  206. package/dist/workspace/wip-cli.d.ts +21 -0
  207. package/dist/workspace/wip-cli.d.ts.map +1 -0
  208. package/dist/workspace/wip.d.ts +268 -0
  209. package/dist/workspace/wip.d.ts.map +1 -0
  210. package/dist/workspace/work-cli.d.ts.map +1 -1
  211. package/dist/workspace/work-evidence.d.ts +1 -1
  212. package/dist/workspace/work-evidence.d.ts.map +1 -1
  213. package/dist/workspace/work-size.d.ts +55 -0
  214. package/dist/workspace/work-size.d.ts.map +1 -0
  215. package/dist/workspace/workspace-kinds.schema.json +122 -0
  216. package/dist/workspace/write-lock-cli.d.ts +60 -0
  217. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  218. package/dist/workspace/write-lock.d.ts +152 -0
  219. package/dist/workspace/write-lock.d.ts.map +1 -0
  220. package/dist/workspace/write-scope.d.ts +6 -2
  221. package/dist/workspace/write-scope.d.ts.map +1 -1
  222. package/dist/yaml.d.ts +1 -1
  223. package/dist/yaml.d.ts.map +1 -1
  224. package/package.json +6 -1
  225. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  226. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  227. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  228. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  229. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  230. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  231. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  232. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  233. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  234. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  235. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  236. package/src/audit/core.test.ts +15 -0
  237. package/src/audit/core.ts +8 -2
  238. package/src/audit/discover.test.ts +11 -0
  239. package/src/audit/discover.ts +7 -2
  240. package/src/change-set-bundle.test.ts +57 -0
  241. package/src/change-set.test.ts +215 -0
  242. package/src/change-set.ts +445 -0
  243. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  244. package/src/cli/commands/audit.ts +29 -2
  245. package/src/cli/commands/build.test.ts +19 -0
  246. package/src/cli/commands/build.ts +3 -2
  247. package/src/cli/handlers/build.ts +13 -1
  248. package/src/cli/handlers/change-set.test.ts +56 -0
  249. package/src/cli/handlers/change-set.ts +44 -0
  250. package/src/cli/handlers/fan-out.test.ts +221 -2
  251. package/src/cli/handlers/fan-out.ts +165 -75
  252. package/src/cli/handlers/graph.ts +3 -0
  253. package/src/cli/handlers/operator.test.ts +21 -0
  254. package/src/cli/handlers/operator.ts +34 -3
  255. package/src/cli/handlers/pr.ts +373 -0
  256. package/src/cli/handlers/run.ts +1 -0
  257. package/src/cli/main.test.ts +20 -0
  258. package/src/cli/main.ts +185 -23
  259. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  260. package/src/cli/mcp/op-tools.ts +24 -2
  261. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  262. package/src/cli/mcp/workspace-tools.ts +34 -6
  263. package/src/cli/registry.ts +63 -1
  264. package/src/components/auto-release.ts +1 -1
  265. package/src/components/capability.ts +55 -0
  266. package/src/components/changed-files.ts +66 -0
  267. package/src/components/cli-support.ts +28 -5
  268. package/src/components/component.schema.json +17 -0
  269. package/src/components/component.ts +25 -0
  270. package/src/components/deploy-units.ts +3 -0
  271. package/src/components/driver.test.ts +52 -0
  272. package/src/components/driver.ts +31 -13
  273. package/src/components/fan-out-output.ts +42 -1
  274. package/src/components/fan-out-record.ts +69 -0
  275. package/src/components/fan-out-run.test.ts +17 -0
  276. package/src/components/fan-out-run.ts +304 -1
  277. package/src/components/fan-out-support.ts +3 -0
  278. package/src/components/fan-out-waves.test.ts +375 -0
  279. package/src/components/fan-out.ts +18 -17
  280. package/src/components/gated-wave-pipeline.ts +82 -0
  281. package/src/components/index.ts +1 -0
  282. package/src/components/layers.ts +30 -0
  283. package/src/components/pr-pipeline.ts +91 -0
  284. package/src/components/pr-run.test.ts +205 -0
  285. package/src/components/pr-run.ts +280 -0
  286. package/src/components/promote.ts +5 -0
  287. package/src/components/verbs/shell.test.ts +11 -0
  288. package/src/components/verbs/shell.ts +7 -3
  289. package/src/components/wave-plan.ts +124 -0
  290. package/src/discovery/sandbox/bundle.ts +1 -1
  291. package/src/gated-waves-bundle.test.ts +62 -0
  292. package/src/gated-waves.test.ts +118 -0
  293. package/src/gated-waves.ts +240 -0
  294. package/src/index.ts +1 -0
  295. package/src/lexicon.ts +51 -0
  296. package/src/lifecycle/gate-ledger.ts +7 -0
  297. package/src/lifecycle/git.ts +102 -16
  298. package/src/lint/post-synth.test.ts +19 -0
  299. package/src/lint/post-synth.ts +19 -1
  300. package/src/meta/declared-imports.test.ts +1 -1
  301. package/src/op/activities/activity-contracts.ts +84 -0
  302. package/src/op/activities/change-set.ts +114 -0
  303. package/src/op/activities/decide.ts +3 -0
  304. package/src/op/activities/factory.ts +675 -0
  305. package/src/op/activities/index.ts +6 -0
  306. package/src/op/activity-contract.test.ts +21 -1
  307. package/src/op/activity-contract.ts +19 -9
  308. package/src/op/builders.ts +23 -0
  309. package/src/op/decide-backend.ts +5 -1
  310. package/src/op/decide-read-inputs.ts +33 -5
  311. package/src/op/factory.e2e.test.ts +232 -0
  312. package/src/op/factory.ts +118 -0
  313. package/src/op/index.ts +9 -1
  314. package/src/op/op-ir.test.ts +9 -0
  315. package/src/op/op-ir.ts +4 -0
  316. package/src/op/op-verb-class.ts +3 -0
  317. package/src/op/types.ts +8 -0
  318. package/src/op/work-lease-run.ts +3 -0
  319. package/src/plan-summary.test.ts +263 -0
  320. package/src/plan-summary.ts +845 -0
  321. package/src/pr-forge.test.ts +237 -0
  322. package/src/pr-forge.ts +332 -0
  323. package/src/pr-loop-bundle.test.ts +46 -0
  324. package/src/pr-loop.test.ts +171 -0
  325. package/src/pr-loop.ts +324 -0
  326. package/src/telemetry-attribution.test.ts +23 -1
  327. package/src/telemetry-attribution.ts +51 -0
  328. package/src/terraform/hcl2json.test.ts +21 -0
  329. package/src/terraform/hcl2json.ts +81 -0
  330. package/src/terraform/parse.ts +11 -74
  331. package/src/vendor/js-yaml-load.test.ts +115 -0
  332. package/src/vendor/js-yaml-load.ts +1939 -0
  333. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  334. package/src/workspace/app-kind.test.ts +196 -0
  335. package/src/workspace/box-cli.ts +113 -0
  336. package/src/workspace/box-factory.test.ts +4 -1
  337. package/src/workspace/box-factory.ts +37 -4
  338. package/src/workspace/box-listing-write.schema.json +204 -0
  339. package/src/workspace/box-listing.test.ts +234 -0
  340. package/src/workspace/box-listing.ts +324 -0
  341. package/src/workspace/box-publish.schema.json +498 -0
  342. package/src/workspace/box-publish.test.ts +204 -0
  343. package/src/workspace/box-publish.ts +420 -0
  344. package/src/workspace/box-ship.test.ts +96 -0
  345. package/src/workspace/broker-protocol.schema.json +186 -0
  346. package/src/workspace/broker-protocol.test.ts +122 -0
  347. package/src/workspace/broker-protocol.ts +317 -0
  348. package/src/workspace/builder-tiers.test.ts +146 -0
  349. package/src/workspace/change-set.schema.json +460 -0
  350. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  351. package/src/workspace/checks/boxes.test.ts +3 -0
  352. package/src/workspace/checks.ts +10 -3
  353. package/src/workspace/component-environments.test.ts +105 -0
  354. package/src/workspace/composites.schema.json +23 -4
  355. package/src/workspace/composites.test.ts +8 -8
  356. package/src/workspace/composites.ts +13 -2
  357. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  358. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  359. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
  360. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
  361. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  362. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  363. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
  364. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  365. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  366. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
  367. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
  368. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
  369. package/src/workspace/conformance/broker.test.ts +122 -0
  370. package/src/workspace/conformance/broker.ts +792 -0
  371. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  372. package/src/workspace/conformance/index.mjs +22 -1
  373. package/src/workspace/conformance/index.ts +5 -1
  374. package/src/workspace/conformance/vitest.ts +33 -1
  375. package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
  376. package/src/workspace/conformance/writer.test.ts +32 -2
  377. package/src/workspace/conformance/writer.ts +339 -28
  378. package/src/workspace/decide.test.ts +208 -4
  379. package/src/workspace/decide.ts +427 -73
  380. package/src/workspace/decision-points.schema.json +11 -4
  381. package/src/workspace/declaration.schema.json +153 -4
  382. package/src/workspace/declaration.ts +141 -0
  383. package/src/workspace/environments.ts +66 -14
  384. package/src/workspace/factory-rules.test.ts +62 -0
  385. package/src/workspace/factory-rules.ts +218 -0
  386. package/src/workspace/graph-cli.ts +2 -2
  387. package/src/workspace/graph.schema.json +67 -0
  388. package/src/workspace/identity.test.ts +23 -0
  389. package/src/workspace/init.ts +82 -2
  390. package/src/workspace/intent-cli.ts +26 -9
  391. package/src/workspace/intent-patch-id.test.ts +162 -0
  392. package/src/workspace/intent-record.schema.json +130 -3
  393. package/src/workspace/intent-record.ts +74 -28
  394. package/src/workspace/intent-reviews.test.ts +76 -0
  395. package/src/workspace/intent-squash.test.ts +213 -0
  396. package/src/workspace/intent-why.test.ts +5 -5
  397. package/src/workspace/intent.schema.json +234 -4
  398. package/src/workspace/intent.ts +260 -66
  399. package/src/workspace/json-edit.test.ts +121 -0
  400. package/src/workspace/json-edit.ts +142 -0
  401. package/src/workspace/jsonc.ts +20 -0
  402. package/src/workspace/kinds.ts +157 -2
  403. package/src/workspace/lexicon-symbols.test.ts +148 -0
  404. package/src/workspace/lexicon-symbols.ts +125 -0
  405. package/src/workspace/lineage-upgrade.test.ts +63 -0
  406. package/src/workspace/lineage-upgrade.ts +146 -5
  407. package/src/workspace/plan-summary.schema.json +155 -0
  408. package/src/workspace/point-answer.schema.json +42 -2
  409. package/src/workspace/points-cli.ts +42 -16
  410. package/src/workspace/points-write.schema.json +162 -4
  411. package/src/workspace/points.schema.json +157 -2
  412. package/src/workspace/points.test.ts +13 -1
  413. package/src/workspace/points.ts +118 -12
  414. package/src/workspace/pr-report.schema.json +95 -0
  415. package/src/workspace/profiles.test.ts +159 -0
  416. package/src/workspace/read-contract.test.ts +2 -2
  417. package/src/workspace/reason-codes.test.ts +13 -1
  418. package/src/workspace/reason-codes.ts +38 -4
  419. package/src/workspace/record-sessions.ts +9 -8
  420. package/src/workspace/records-amend.schema.json +78 -0
  421. package/src/workspace/records-cli.ts +20 -6
  422. package/src/workspace/records-close.schema.json +78 -0
  423. package/src/workspace/records-close.ts +31 -7
  424. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  425. package/src/workspace/records-concurrent.test.ts +145 -0
  426. package/src/workspace/records-formats.test.ts +46 -10
  427. package/src/workspace/records-new.schema.json +7 -0
  428. package/src/workspace/records-review.schema.json +78 -0
  429. package/src/workspace/records-sessions-write.test.ts +69 -0
  430. package/src/workspace/records-sessions.test.ts +22 -0
  431. package/src/workspace/records-write.ts +192 -25
  432. package/src/workspace/records.schema.json +16 -0
  433. package/src/workspace/records.ts +50 -9
  434. package/src/workspace/reference-kinds/app/package.json +10 -0
  435. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  436. package/src/workspace/run-statement.schema.json +622 -0
  437. package/src/workspace/runs-cli.ts +343 -14
  438. package/src/workspace/runs-statement.test.ts +239 -0
  439. package/src/workspace/runs-write.schema.json +416 -5
  440. package/src/workspace/runs.schema.json +451 -4
  441. package/src/workspace/runs.test.ts +39 -0
  442. package/src/workspace/runs.ts +296 -35
  443. package/src/workspace/session-kinds.ts +3 -3
  444. package/src/workspace/squash.ts +198 -0
  445. package/src/workspace/status-contract.test.ts +5 -2
  446. package/src/workspace/status-gates.ts +3 -1
  447. package/src/workspace/status.schema.json +126 -3
  448. package/src/workspace/status.ts +90 -1
  449. package/src/workspace/symbols.ts +20 -9
  450. package/src/workspace/trust/run-commits.ts +115 -0
  451. package/src/workspace/trust/run-statement.ts +226 -0
  452. package/src/workspace/trust/seal.ts +8 -2
  453. package/src/workspace/trust/verify-cli.ts +6 -5
  454. package/src/workspace/trust/verify.ts +39 -6
  455. package/src/workspace/wip-cli.ts +97 -0
  456. package/src/workspace/wip-write.schema.json +504 -0
  457. package/src/workspace/wip.schema.json +334 -0
  458. package/src/workspace/wip.test.ts +294 -0
  459. package/src/workspace/wip.ts +753 -0
  460. package/src/workspace/work-cli.ts +3 -0
  461. package/src/workspace/work-evidence.schema.json +3 -0
  462. package/src/workspace/work-size.test.ts +77 -0
  463. package/src/workspace/work-size.ts +93 -0
  464. package/src/workspace/workspace-kinds.schema.json +122 -0
  465. package/src/workspace/write-lock-cli.ts +131 -0
  466. package/src/workspace/write-lock.schema.json +172 -0
  467. package/src/workspace/write-lock.test.ts +189 -0
  468. package/src/workspace/write-lock.ts +474 -0
  469. package/src/workspace/write-scope.test.ts +16 -1
  470. package/src/workspace/write-scope.ts +33 -11
  471. package/src/yaml.ts +31 -21
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Following a squash merge to the pull request's original commits (#3035,
3
+ * ws-092).
4
+ *
5
+ * A squash merge folds a pull request's commits into one new commit, and with
6
+ * them the trailers and signatures that joined each to its run and its work.
7
+ * The forge keeps the originals: GitHub and Forgejo both keep a pull
8
+ * request's head at `refs/pull/<n>/head` on the remote. A read that is asked
9
+ * to follow squashes (`--follow-squash`) looks for that ref locally, fetches
10
+ * the missing ones from `origin` in one `git fetch`, and keeps what it
11
+ * fetched under `refs/chant/pull/<n>/head`, so the next read needs no network.
12
+ * Without the flag nothing here runs, and a read never touches the network
13
+ * (studio-032 b: follow only when asked).
14
+ *
15
+ * A commit is followed as a squash when its subject ends with `(#<n>)`, as
16
+ * GitHub and Forgejo write a squash's subject, it has one parent, and the
17
+ * pull request's head is not already in its history (a merge commit or a
18
+ * rebase keeps the originals, so there is nothing to follow). Its original
19
+ * commits are the head's commits that the squash's parent does not have,
20
+ * oldest first. When the ref can't be read or fetched, the read says so and
21
+ * keeps its answer without them.
22
+ */
23
+
24
+ import { execFileSync } from "node:child_process";
25
+
26
+ /** A forge chant knows the pull request refs of. */
27
+ export type Forge = "github" | "forgejo";
28
+
29
+ /** Where a read keeps the pull request heads it fetched. */
30
+ export const PULL_CACHE_PREFIX = "refs/chant/pull/";
31
+
32
+ /** The remote a squash's pull request ref is fetched from. */
33
+ export const SQUASH_REMOTE = "origin";
34
+
35
+ /** How long one fetch of pull request refs may take. */
36
+ const FETCH_TIMEOUT_MS = 60_000;
37
+
38
+ function git(top: string, args: string[], opts: { input?: string; timeout?: number } = {}): string | undefined {
39
+ try {
40
+ return execFileSync("git", args, {
41
+ cwd: top,
42
+ encoding: "utf-8",
43
+ input: opts.input,
44
+ timeout: opts.timeout,
45
+ stdio: [opts.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
46
+ maxBuffer: 256 * 1024 * 1024,
47
+ // A fetch must never wait on a credential prompt.
48
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
49
+ });
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+
55
+ /** The pull request number a squash's subject ends with, `(#<n>)`, or null. */
56
+ export function pullRequestOf(subject: string): number | null {
57
+ const m = subject.match(/\(#([0-9]+)\)\s*$/);
58
+ return m ? Number(m[1]) : null;
59
+ }
60
+
61
+ /**
62
+ * The forge a remote URL points at: `github` for github.com and GitHub
63
+ * Enterprise hosts named for it, `forgejo` for codeberg.org and hosts named
64
+ * forgejo or gitea. Null for any other, which is still read at
65
+ * `refs/pull/<n>/head`, the ref both keep.
66
+ */
67
+ export function forgeOf(url: string | null | undefined): Forge | null {
68
+ if (!url) return null;
69
+ const host = (url.match(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)/i)?.[1] ?? url.match(/^(?:[^@/]*@)?([^/:]+):/)?.[1] ?? "").toLowerCase();
70
+ if (host === "github.com" || host.endsWith(".github.com") || /(^|[.-])github([.-]|$)/.test(host)) return "github";
71
+ if (host === "codeberg.org" || /(^|[.-])(forgejo|gitea)([.-]|$)/.test(host)) return "forgejo";
72
+ return null;
73
+ }
74
+
75
+ /** The pull request ref a forge keeps for `n`. GitHub and Forgejo keep the same one. */
76
+ export function pullRef(_forge: Forge | null, n: number): string {
77
+ return `refs/pull/${n}/head`;
78
+ }
79
+
80
+ /** What following one squash commit found. */
81
+ export interface SquashFollow {
82
+ /** The squash commit. */
83
+ sha: string;
84
+ pullRequest: number;
85
+ /** The forge `origin` points at, or null when it is none chant knows, or there is no `origin`. */
86
+ forge: Forge | null;
87
+ /** The local ref the pull request's head was read from, or null when none could be read. */
88
+ ref: string | null;
89
+ /** The pull request's head commit, or null. */
90
+ head: string | null;
91
+ /** Whether this read fetched the ref from the remote. */
92
+ fetched: boolean;
93
+ /** Whether the original commits were read. */
94
+ followed: boolean;
95
+ /** The original commits, oldest first. Empty when not followed. */
96
+ commits: string[];
97
+ /** Why it was not followed, when it wasn't. */
98
+ problem: string | null;
99
+ }
100
+
101
+ const commitOf = (top: string, rev: string): string | undefined => git(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`])?.trim() || undefined;
102
+
103
+ /** The local ref holding pull request `n`'s head: chant's cache, the forge's own ref, or a `refs/remotes/<remote>/pr/<n>` mirror. */
104
+ function localPullRef(top: string, forge: Forge | null, n: number): { ref: string; head: string } | undefined {
105
+ for (const ref of [`${PULL_CACHE_PREFIX}${n}/head`, pullRef(forge, n), `refs/remotes/${SQUASH_REMOTE}/pr/${n}`]) {
106
+ const head = commitOf(top, ref);
107
+ if (head) return { ref, head };
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ /** Fetch each pull request's head into chant's cache, all in one fetch and then one by one if that fails. The numbers fetched. */
113
+ function fetchPullRefs(top: string, forge: Forge | null, numbers: number[]): Set<number> {
114
+ const spec = (n: number) => `+${pullRef(forge, n)}:${PULL_CACHE_PREFIX}${n}/head`;
115
+ const fetchRefs = (ns: number[]) => git(top, ["fetch", "--no-tags", "--quiet", "--no-write-fetch-head", SQUASH_REMOTE, ...ns.map(spec)], { timeout: FETCH_TIMEOUT_MS }) !== undefined;
116
+ if (numbers.length === 0) return new Set();
117
+ if (fetchRefs(numbers)) return new Set(numbers);
118
+ // One missing ref fails the whole fetch; the others may still be there.
119
+ return new Set(numbers.length === 1 ? [] : numbers.filter((n) => fetchRefs([n])));
120
+ }
121
+
122
+ /**
123
+ * Follow each squash commit among `commits` to its pull request's original
124
+ * commits (#3035). A commit that is not a squash (no `(#<n>)` subject, a
125
+ * merge, or a head already in its history) is left out of the result. With
126
+ * `fetchMissing`, a pull request ref missing locally is fetched from `origin`;
127
+ * without it, only refs already present are read.
128
+ */
129
+ export function followSquashes(top: string, commits: { sha: string; subject: string }[], opts: { fetchMissing: boolean }): Map<string, SquashFollow> {
130
+ const out = new Map<string, SquashFollow>();
131
+ const candidates = commits.map((c) => ({ sha: c.sha, n: pullRequestOf(c.subject) })).filter((c): c is { sha: string; n: number } => c.n !== null);
132
+ if (candidates.length === 0) return out;
133
+ const parents = new Map<string, string[]>();
134
+ for (const line of (git(top, ["log", "--no-walk=unsorted", "--stdin", "--format=%H %P"], { input: `${candidates.map((c) => c.sha).join("\n")}\n` }) ?? "").split("\n")) {
135
+ const [sha, ...ps] = line.trim().split(/\s+/);
136
+ if (sha) parents.set(sha, ps);
137
+ }
138
+ const squashes = candidates.filter((c) => parents.get(c.sha)?.length === 1);
139
+ if (squashes.length === 0) return out;
140
+ const url = git(top, ["config", "--get", `remote.${SQUASH_REMOTE}.url`])?.trim() || null;
141
+ const forge = forgeOf(url);
142
+ const local = new Map<number, { ref: string; head: string }>();
143
+ for (const n of new Set(squashes.map((c) => c.n))) {
144
+ const hit = localPullRef(top, forge, n);
145
+ if (hit) local.set(n, hit);
146
+ }
147
+ const missing = [...new Set(squashes.map((c) => c.n))].filter((n) => !local.has(n));
148
+ const fetched = opts.fetchMissing && url ? fetchPullRefs(top, forge, missing) : new Set<number>();
149
+ for (const n of fetched) {
150
+ const head = commitOf(top, `${PULL_CACHE_PREFIX}${n}/head`);
151
+ if (head) local.set(n, { ref: `${PULL_CACHE_PREFIX}${n}/head`, head });
152
+ }
153
+ for (const c of squashes) {
154
+ const hit = local.get(c.n);
155
+ const base: SquashFollow = { sha: c.sha, pullRequest: c.n, forge, ref: null, head: null, fetched: false, followed: false, commits: [], problem: null };
156
+ if (!hit) {
157
+ const problem = !opts.fetchMissing
158
+ ? `${pullRef(forge, c.n)} is not in this clone`
159
+ : !url
160
+ ? `there is no ${SQUASH_REMOTE} remote to fetch ${pullRef(forge, c.n)} from`
161
+ : `${pullRef(forge, c.n)} could not be fetched from ${SQUASH_REMOTE}: the forge has no such ref, or it can't be reached`;
162
+ out.set(c.sha, { ...base, problem });
163
+ continue;
164
+ }
165
+ // A head already in the commit's history was merged or rebased, not squashed: nothing to follow.
166
+ if (git(top, ["merge-base", "--is-ancestor", hit.head, c.sha]) !== undefined) continue;
167
+ const parent = parents.get(c.sha)![0];
168
+ const list = git(top, ["rev-list", "--reverse", "--no-merges", hit.head, `^${parent}`]);
169
+ if (list === undefined) {
170
+ out.set(c.sha, { ...base, ref: hit.ref, head: hit.head, fetched: fetched.has(c.n), problem: `the commits of ${hit.ref} could not be listed` });
171
+ continue;
172
+ }
173
+ const originals = list.split("\n").map((s) => s.trim()).filter(Boolean);
174
+ out.set(c.sha, { ...base, ref: hit.ref, head: hit.head, fetched: fetched.has(c.n), followed: true, commits: originals });
175
+ }
176
+ return out;
177
+ }
178
+
179
+ /**
180
+ * For the lines of `path` in a squash commit: which original commit last wrote
181
+ * each, by `git blame` at the pull request's head, when the file there is the
182
+ * same as in the squash (#3035). Undefined when it is not, since the lines
183
+ * then don't correspond one to one.
184
+ */
185
+ export function squashLineOrigins(top: string, squash: string, head: string, path: string, originals: Set<string>): Map<number, string> | undefined {
186
+ const a = git(top, ["rev-parse", "--verify", "--quiet", `${squash}:${path}`])?.trim();
187
+ const b = git(top, ["rev-parse", "--verify", "--quiet", `${head}:${path}`])?.trim();
188
+ if (!a || a !== b) return undefined;
189
+ const out = git(top, ["blame", "--porcelain", head, "--", path]);
190
+ if (out === undefined) return undefined;
191
+ const lines = new Map<number, string>();
192
+ // Every line has a header, `<sha> <line in that commit> <line now>`, with a count after it on a group's first.
193
+ for (const l of out.split("\n")) {
194
+ const m = l.match(/^([0-9a-f]{40,64}) \d+ (\d+)(?: \d+)?$/);
195
+ if (m && originals.has(m[1])) lines.set(Number(m[2]), m[1]);
196
+ }
197
+ return lines;
198
+ }
@@ -318,6 +318,9 @@ describe("chant workspace status on built workspaces", () => {
318
318
  services: [],
319
319
  factory: null,
320
320
  listing: null,
321
+ publisher: null,
322
+ ship: null,
323
+ replicate: null,
321
324
  },
322
325
  ]);
323
326
  });
@@ -451,7 +454,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
451
454
  state: "approved",
452
455
  recordedAt: "2026-09-20T00:00:00.000Z",
453
456
  expiresAt: "2026-12-31T00:00:00.000Z",
454
- approvals: [{ principal: "alice", channel: "cli", at: "2026-09-20T01:00:00.000Z" }],
457
+ approvals: [{ principal: "alice", channel: "cli", relayedBy: null, at: "2026-09-20T01:00:00.000Z" }],
455
458
  needed: 1,
456
459
  signed: null,
457
460
  approve: `chant approve web deploy --env staging --plan ${D("1")}`,
@@ -478,7 +481,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
478
481
  recordedAt: "2026-09-20T00:00:00.000Z",
479
482
  expiresAt: "2026-12-31T00:00:00.000Z",
480
483
  // The agent's approval doesn't count toward the quorum.
481
- approvals: [{ principal: "bob", channel: "cli", at: "2026-09-20T02:00:00.000Z" }],
484
+ approvals: [{ principal: "bob", channel: "cli", relayedBy: null, at: "2026-09-20T02:00:00.000Z" }],
482
485
  needed: 2,
483
486
  signed: null,
484
487
  approve: `chant approve web review --env staging --plan ${D("4")}`,
@@ -43,6 +43,8 @@ export interface StatusGateApproval {
43
43
  principal: string;
44
44
  /** The channel it was recorded on (`cli`, `mcp`, `acp`), or null for a record older than chant#2384. */
45
45
  channel: string | null;
46
+ /** Who relayed it for `principal` (`chant approve --relayed-by`, #3402), or null when nobody did. */
47
+ relayedBy: string | null;
46
48
  at: string;
47
49
  }
48
50
 
@@ -124,7 +126,7 @@ export function approveCommand(component: string, gate: string, env: string | nu
124
126
  return `chant approve ${component} ${gate}${env !== null ? ` --env ${env}` : ""}${planDigest ? ` --plan ${planDigest}` : ""}${signed ? " --sign" : ""}`;
125
127
  }
126
128
 
127
- const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, at: r.timestamp });
129
+ const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, relayedBy: r.relayedBy ?? null, at: r.timestamp });
128
130
 
129
131
  /** One gate in one environment, decided against its newest pending fact. */
130
132
  function decide(component: string, standing: PendingGateRecord, all: GateResolutionRecord[], now: string, rule: GateApprovalRule | null): StatusGate {
@@ -5,6 +5,50 @@
5
5
  "description": "What `chant workspace status <env> --json` prints, version 1 of the read contract for releases across members (#2524 D15, D19, #2544). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`. Each member's gates, read from its gate ledger on the same branch, are in the JSON only (#2674), and so is each member's box block, from the declaration (#2726), with its isolation resolved (#2727). So are its stewards, their Ops and each Op's last run (#2731), the work lease each Op's turn holds (#2748), and the open decision points a steward waits on (#2749).",
6
6
  "oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
7
7
  "$defs": {
8
+ "boxShip": {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "required": ["op", "gate", "env", "bookkeeping", "serving", "pending"],
12
+ "properties": {
13
+ "op": { "type": "string", "description": "The Op in the box member that commits the staged tree and releases it." },
14
+ "gate": { "type": "string", "description": "The gate the Op stops at for a person's approval." },
15
+ "env": { "type": "string", "description": "The environment whose release ledger, under the box member, the Op records each release in." },
16
+ "bookkeeping": { "type": "array", "items": { "type": "string" }, "description": "Path prefixes from the workspace root whose changes never count as waiting to ship." },
17
+ "serving": {
18
+ "description": "The latest release in env's ledger, the commit the box's site serves, or null when nothing has shipped or the ledger can't be read.",
19
+ "oneOf": [
20
+ { "type": "null" },
21
+ {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["commit", "digest", "component", "at", "actor"],
25
+ "properties": {
26
+ "commit": { "type": "string" },
27
+ "digest": { "type": "string" },
28
+ "component": { "type": "string" },
29
+ "at": { "type": "string" },
30
+ "actor": { "type": ["string", "null"] }
31
+ }
32
+ }
33
+ ]
34
+ },
35
+ "pending": {
36
+ "description": "What is waiting to ship: the files that differ between serving.commit and the working tree, tracked or untracked and not ignored, bookkeeping left out. paths is sorted and lists at most 200. Null when nothing has shipped or the serving commit isn't in this checkout.",
37
+ "oneOf": [
38
+ { "type": "null" },
39
+ {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["files", "paths"],
43
+ "properties": {
44
+ "files": { "type": "integer", "minimum": 0 },
45
+ "paths": { "type": "array", "items": { "type": "string" }, "maxItems": 200 }
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ },
8
52
  "result": {
9
53
  "description": "The declaration was read. Exit code 0, even when some ledgers can't be read or members differ.",
10
54
  "type": "object",
@@ -46,6 +90,10 @@
46
90
  "baseFrom": { "enum": ["origin/HEAD", "main", "master", null], "description": "Where the target branch was found; null when there is none." }
47
91
  }
48
92
  },
93
+ "replication": {
94
+ "description": "Added in contract 1 by #3172. When a box block declares replicate (ws-085), where each ref it names (work branches, kept attempts, work-in-progress snapshots, the ledger branch) stands against the remote: replicated, or how many commits ahead. Read from the local refs and what chant recorded at its last push or fetch under refs/chant/replica/<remote>/; the command never fetches. chant workspace wip --json prints the same, with each branch's snapshots. null when no box declares replicate.",
95
+ "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/replication" }]
96
+ },
49
97
  "plantable": {
50
98
  "description": "Added in contract 1 by #3146. Whether a host such as a studio's planter can plant the workspace as one box: exactly one member's box block declares services. Not a check: a records-only or infra workspace is valid and simply not plantable.",
51
99
  "$ref": "#/$defs/plantable"
@@ -122,11 +170,24 @@
122
170
  },
123
171
  "member": {
124
172
  "type": "object",
125
- "required": ["name", "dir", "kind", "environments", "compare", "readable", "gateLedger", "gates", "box", "stewards", "stewardReasons"],
173
+ "required": ["name", "dir", "kind", "fields", "environments", "compare", "readable", "gateLedger", "gates", "box", "stewards", "stewardReasons"],
126
174
  "properties": {
127
175
  "name": { "type": "string" },
128
176
  "dir": { "type": "string", "description": "Relative to the workspace root, with / separators; \".\" is the root member." },
129
177
  "kind": { "type": "string" },
178
+ "fields": {
179
+ "description": "The fields the member's kind declares (#3151), each set from the entry or its default, null when it has neither. Null when the kind declares no fields or no pinned package supplies it. A value of the wrong type reads as the default, and chant workspace check reports it (WSP005). Added in contract 1 by chant 0.103.0.",
180
+ "type": ["object", "null"],
181
+ "additionalProperties": {
182
+ "oneOf": [
183
+ { "type": "string" },
184
+ { "type": "integer" },
185
+ { "type": "boolean" },
186
+ { "type": "null" },
187
+ { "type": "object", "additionalProperties": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "boolean" }, { "type": "null" }] } }
188
+ ]
189
+ }
190
+ },
130
191
  "environments": {
131
192
  "description": "env first, then compareTo when it is given and differs from env.",
132
193
  "type": "array",
@@ -485,6 +546,41 @@
485
546
  "decided_on": { "type": ["string", "null"], "description": "The day it was decided, or null while it is proposed." }
486
547
  }
487
548
  },
549
+ "replication": {
550
+ "type": "object",
551
+ "required": ["policy", "remoteConfigured", "replicated", "refs"],
552
+ "properties": {
553
+ "policy": {
554
+ "type": "object",
555
+ "required": ["box", "remote", "refs", "on", "every"],
556
+ "properties": {
557
+ "box": { "type": "string", "description": "The member whose box block declares the policy." },
558
+ "remote": { "type": "string" },
559
+ "refs": { "type": "array", "items": { "enum": ["work", "kept", "wip", "ledger"] } },
560
+ "on": { "type": "array", "items": { "enum": ["save", "release"] } },
561
+ "every": { "type": ["string", "null"] }
562
+ }
563
+ },
564
+ "remoteConfigured": { "type": "boolean", "description": "Whether the checkout has a git remote of the policy's name." },
565
+ "replicated": { "type": "boolean", "description": "True when every ref listed is on the remote as it is locally." },
566
+ "refs": {
567
+ "type": "array",
568
+ "description": "Every local ref the policy names, sorted by ref.",
569
+ "items": {
570
+ "type": "object",
571
+ "required": ["ref", "class", "commit", "replica", "replicated", "ahead"],
572
+ "properties": {
573
+ "ref": { "type": "string", "description": "Such as refs/heads/chant/work/W-001, refs/chant/kept/W-001/<token>, refs/chant/wip/chant/work/W-001 or refs/heads/chant/lifecycle." },
574
+ "class": { "enum": ["work", "kept", "wip", "ledger"] },
575
+ "commit": { "type": "string", "pattern": "^[0-9a-f]{40,64}$" },
576
+ "replica": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40,64}$", "description": "What the remote held of the ref as chant last saw it (or, for a branch, git's remote-tracking ref), or null when unknown." },
577
+ "replicated": { "type": "boolean" },
578
+ "ahead": { "type": "integer", "minimum": 0, "description": "Commits reachable from commit that nothing known to be on the remote reaches; 0 when replicated." }
579
+ }
580
+ }
581
+ }
582
+ }
583
+ },
488
584
  "box": {
489
585
  "type": "object",
490
586
  "required": ["capabilities", "isolation", "intent", "services"],
@@ -497,6 +593,30 @@
497
593
  "description": "Added in contract 1 by #3146. What the box shows of itself on a home site, or null when the block declares no listing.",
498
594
  "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/listing" }]
499
595
  },
596
+ "publisher": {
597
+ "description": "Added in contract 1 by #3165. The command that publishes the box's work, as the box block declares it (ws-088), or null when it names none. A surface offers a publish only when it is set, and asks for one with chant workspace box publish rather than running it.",
598
+ "type": ["string", "null"]
599
+ },
600
+ "ship": {
601
+ "description": "Added in contract 1 by ws-100. How the box ships its staged work to its own site, with defaults filled in, the release its site serves and what is waiting to ship, or null when the box block declares no ship. A surface offers Ship only when it is set: it runs chant run <op> in the member's directory with the person in CHANT_SHIP_BY and approves <gate> as for any Op.",
602
+ "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/boxShip" }]
603
+ },
604
+ "replicate": {
605
+ "description": "Added in contract 1 by #3172. Where the box's work in progress is replicated (ws-085), with every default filled in, or null when the block declares no policy. At most one member's box has one; the top-level replication says how far each ref is from the remote.",
606
+ "oneOf": [
607
+ { "type": "null" },
608
+ {
609
+ "type": "object",
610
+ "required": ["remote", "refs", "on", "every"],
611
+ "properties": {
612
+ "remote": { "type": "string", "description": "The git remote, by name; origin when the block names none." },
613
+ "refs": { "type": "array", "items": { "enum": ["work", "kept", "wip", "ledger"] }, "description": "What is replicated, in this order; all four when the block names none." },
614
+ "on": { "type": "array", "items": { "enum": ["save", "release"] }, "description": "When chant pushes on its own; both when the block names none." },
615
+ "every": { "type": ["string", "null"], "description": "The interval at which the host runs chant workspace wip push, or null." }
616
+ }
617
+ }
618
+ ]
619
+ },
500
620
  "services": {
501
621
  "description": "The services the box block declares (#2880), in declaration order, or empty when it declares none. A reader has them before any converge tick; a steward's ConvergeOp's lastTick carries each one's verdict. cmd is as declared: its ${VAR} references are left for the process that applies it to expand. Added by chant 0.95.0.",
502
622
  "type": "array",
@@ -561,7 +681,7 @@
561
681
  },
562
682
  "factory": {
563
683
  "type": "object",
564
- "required": ["builds", "check", "checks", "builders", "publish"],
684
+ "required": ["builds", "check", "checks", "builders", "tiers", "builderFor", "publish"],
565
685
  "properties": {
566
686
  "builds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "The members the factory builds, in declaration order. A work item a person asks for constrains member:<the first>." },
567
687
  "check": {
@@ -580,6 +700,8 @@
580
700
  },
581
701
  "checks": { "type": ["string", "null"], "description": "Where a work item's builder writes its check, from the workspace root, or null for the orchestrator's choice." },
582
702
  "builders": { "type": ["string", "null"], "description": "The member that declares the builder agents, or null." },
703
+ "tiers": {"type": "array", "items": {"type": "object", "required": ["tier", "agent", "kinds", "session"], "properties": {"tier": {"type": "string"}, "agent": {"type": "string"}, "kinds": {"oneOf": [{"type": "null"}, {"type": "array", "items": {"type": "string"}}]}, "session": {"type": ["string", "null"]}}}, "description": "Which builder agent builds at which tier, as declared (#3152, ws-094), each { tier, agent, kinds, session } with kinds and session null when not declared; empty when none is. Added in contract 1 by chant 0.103.0."},
704
+ "builderFor": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": {"type": "object", "required": ["agent", "session"], "properties": {"agent": {"type": "string"}, "session": {"type": ["string", "null"]}}}}, "description": "The builder for each member the factory builds, by tier (#3152): member name, then tier, then { agent, session }. For a member the entry naming its kind is used, else the tier's entry with no kinds; a tier with neither is left out. Added in contract 1 by chant 0.103.0."},
583
705
  "publish": {
584
706
  "description": "Where a finished build is published, or null when the factory doesn't publish past the box.",
585
707
  "oneOf": [
@@ -703,10 +825,11 @@
703
825
  "type": "array",
704
826
  "items": {
705
827
  "type": "object",
706
- "required": ["principal", "channel", "at"],
828
+ "required": ["principal", "channel", "relayedBy", "at"],
707
829
  "properties": {
708
830
  "principal": { "type": "string", "description": "Who recorded it." },
709
831
  "channel": { "type": ["string", "null"], "description": "The channel it was recorded on, such as cli or mcp, or null for a record older than chant#2384." },
832
+ "relayedBy": { "type": ["string", "null"], "description": "Added in contract 1 by #3402. Who carried the approval to chant for principal (chant approve --relayed-by), such as a follower relaying a person's approval; a seal covers it. Null when nobody relayed it." },
710
833
  "at": { "type": "string" }
711
834
  }
712
835
  }
@@ -42,9 +42,11 @@ import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../li
42
42
  import { readReleasePlan, type ReleasePlan } from "../lifecycle/plan-ledger";
43
43
  import { listWorkLeases, type WorkLeaseState } from "../lifecycle/work-lease";
44
44
  import { readCheckoutHead, type CheckoutHead } from "./records-checkout";
45
+ import { policyView, replicationState, type ReplicationState } from "./wip";
45
46
  import { findWorkspaceRoot } from "../project-root";
46
47
  import { resolveBoxes, type ResolvedIsolation } from "./box-isolation";
47
48
  import { resolveBoxIntents, unresolvedIntent, type BoxIntent } from "./box-intent";
49
+ import { loadKindRegistry, resolveMemberFields, type MemberFields } from "./kinds";
48
50
  import { factoryView, listingView, plantability, treeBytes, type FactoryView, type ListingView, type Plantable } from "./box-factory";
49
51
  import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError, type Declaration, type ErrorLocation, type Member } from "./declaration";
50
52
  import type { ReasonCode } from "./reason-codes";
@@ -160,6 +162,13 @@ export interface StatusMember {
160
162
  name: string;
161
163
  dir: string;
162
164
  kind: string;
165
+ /**
166
+ * The fields the member's kind declares (#3151), each set from the entry or
167
+ * its default (null when it has none), or null when the kind declares no
168
+ * fields or no pinned package supplies it. A value of the wrong type reads
169
+ * as the default; `chant workspace check` reports it (WSP005).
170
+ */
171
+ fields: MemberFields["fields"];
163
172
  /** The environment asked for first, then the one compared to, if any. */
164
173
  environments: StatusEnvironment[];
165
174
  /** Null without `--compare-to`. */
@@ -178,6 +187,31 @@ export interface StatusMember {
178
187
  stewardReasons: { code: StewardReasonCode; message: string }[];
179
188
  }
180
189
 
190
+ /** The most paths `ship.pending.paths` lists; `files` still counts them all. */
191
+ export const SHIP_PENDING_PATHS_MAX = 200;
192
+
193
+ /**
194
+ * A box's ship (ws-100): the Op, gate and environment it declares, the
195
+ * release its site serves (the latest in that environment's release ledger
196
+ * under the box member), and what is waiting to ship.
197
+ */
198
+ export interface StatusBoxShip {
199
+ op: string;
200
+ gate: string;
201
+ env: string;
202
+ bookkeeping: string[];
203
+ /** The latest release in `env`'s ledger: the commit the box's site serves. Null when nothing has shipped, or the ledger can't be read. */
204
+ serving: { commit: string; digest: string; component: string; at: string; actor: string | null } | null;
205
+ /**
206
+ * The files that differ between `serving.commit` and the working tree
207
+ * (tracked changes, committed or not, and untracked files git does not
208
+ * ignore), bookkeeping left out; `paths` sorted, at most
209
+ * SHIP_PENDING_PATHS_MAX of them. Null when nothing has shipped or the
210
+ * serving commit isn't in this checkout.
211
+ */
212
+ pending: { files: number; paths: string[] } | null;
213
+ }
214
+
181
215
  /** A box's brokered capabilities, from the declaration (#2726). */
182
216
  export interface StatusBox {
183
217
  capabilities: { name: string; broker: string | null; scope: string[] }[];
@@ -196,6 +230,12 @@ export interface StatusBox {
196
230
  factory: FactoryView | null;
197
231
  /** What the box shows of itself on a home site, with the cover's sha256 (#3146), or null when the block declares no listing. */
198
232
  listing: ListingView | null;
233
+ /** The command that publishes the box's work, as declared (#3165, ws-088), or null when the block names none. */
234
+ publisher: string | null;
235
+ /** How the box ships its staged work, what its site serves and what is waiting (ws-100), or null when the block declares no ship. */
236
+ ship: StatusBoxShip | null;
237
+ /** Where the box's work in progress is replicated, with defaults filled in (#3172, ws-085), or null when the block declares no policy. */
238
+ replicate: { remote: string; refs: string[]; on: string[]; every: string | null } | null;
199
239
  }
200
240
 
201
241
  /** A declared box service as `status --json` prints it (#2880): every field present, null or false when not declared. */
@@ -252,6 +292,8 @@ export type StatusDocument =
252
292
  leases: StatusLease[];
253
293
  /** Each work item's acceptance criteria, met of total, by declared kind then item (#2772). Empty when no declared work kind has criteria. */
254
294
  acceptance: StatusAcceptance[];
295
+ /** How far each ref the box's replicate policy names is from its remote (#3172), read locally; null when no box declares one. */
296
+ replication: ReplicationState | null;
255
297
  summary: { members: number; released: number; unreadable: number; differing: number | null };
256
298
  }
257
299
  | {
@@ -321,6 +363,40 @@ async function ledgerFor(member: Member, env: string, cwd: string): Promise<Omit
321
363
  return { layout: "flat", path: `${env}/releases.jsonl` };
322
364
  }
323
365
 
366
+ /** A box's ship as status prints it (ws-100). Never throws: what can't be read is null. */
367
+ async function shipView(member: Member, cwd: string, read: LedgerReader): Promise<StatusBoxShip | null> {
368
+ const ship = member.box?.ship;
369
+ if (!ship) return null;
370
+ const view: StatusBoxShip = { op: ship.op, gate: ship.gate, env: ship.env, bookkeeping: [...ship.bookkeeping], serving: null, pending: null };
371
+ let records: ReleaseRecord[] = [];
372
+ try {
373
+ records = (await read((await ledgerFor(member, ship.env, cwd)).path, cwd)).records;
374
+ } catch {
375
+ return view;
376
+ }
377
+ const last = records[records.length - 1];
378
+ if (!last) return view;
379
+ view.serving = { commit: last.gitSha, digest: last.digest, component: last.component, at: last.timestamp, actor: last.actor ?? null };
380
+ const top = gitTop(cwd);
381
+ if (!top) return view;
382
+ const lines = (args: string[]): string[] | null => {
383
+ try {
384
+ return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 }).split("\0").filter(Boolean);
385
+ } catch {
386
+ return null;
387
+ }
388
+ };
389
+ // The commit must be here: a release from a commit this checkout never had says nothing about it.
390
+ if (lines(["cat-file", "-e", `${last.gitSha}^{commit}`]) === null) return view;
391
+ const changed = lines(["diff", "--name-only", "-z", "--no-renames", last.gitSha, "--"]);
392
+ const untracked = lines(["ls-files", "--others", "--exclude-standard", "-z"]);
393
+ if (changed === null || untracked === null) return view;
394
+ const bookkeeping = (path: string) => ship.bookkeeping.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
395
+ const paths = [...new Set([...changed, ...untracked])].filter((path) => !bookkeeping(path)).sort();
396
+ view.pending = { files: paths.length, paths: paths.slice(0, SHIP_PENDING_PATHS_MAX) };
397
+ return view;
398
+ }
399
+
324
400
  const defaultReader: LedgerReader = (path, cwd) => readReleaseLedger(path.slice(0, -"/releases.jsonl".length), { cwd });
325
401
 
326
402
  async function readEnvironment(member: Member, env: string, cwd: string, read: LedgerReader): Promise<StatusEnvironment> {
@@ -412,6 +488,8 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
412
488
  const isolation = new Map(resolveBoxes(declaration).map((b) => [b.member.name, b.isolation]));
413
489
  const intents = new Map((await resolveBoxIntents(declaration, found.dir)).map((i) => [i.member, i.record?.intent ?? unresolvedIntent(i.id)]));
414
490
  const coverBytes = treeBytes(workingTree(found.dir));
491
+ // The kinds the pins supply (#3151), for each member's fields. A pin that can't be read is check's to report (WSP002).
492
+ const kinds = loadKindRegistry(declaration.pins, found.dir).registry;
415
493
  // #3163: the declaration's identity.gates at base, read once for every member's gates.
416
494
  const ruleSource = scopeSource(found.dir);
417
495
  const rules = (gate: string) => gateAdmissionFrom(ruleSource, gate);
@@ -426,6 +504,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
426
504
  name: m.name,
427
505
  dir: m.dir,
428
506
  kind: m.kind,
507
+ fields: resolveMemberFields(kinds.get(m.kind), m.fields).fields,
429
508
  environments,
430
509
  compare: query.compareTo === undefined ? null : compareEnvironments(environments[0], environments[environments.length - 1]),
431
510
  readable: environments.every((e) => e.reason === null),
@@ -447,8 +526,11 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
447
526
  health: s.health,
448
527
  optional: s.optional,
449
528
  })),
450
- factory: factoryView(m.box.factory),
529
+ factory: factoryView(m.box.factory, declaration.members),
451
530
  listing: listingView(m.box.listing, coverBytes),
531
+ publisher: m.box.publisher,
532
+ ship: await shipView(m, found.dir, read),
533
+ replicate: m.box.replicate === null ? null : (({ box: _box, ...rest }) => rest)(policyView(m.name, m.box.replicate)),
452
534
  },
453
535
  stewards: stewards.stewards,
454
536
  stewardReasons: stewards.reasons,
@@ -475,6 +557,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
475
557
  members,
476
558
  leases,
477
559
  acceptance,
560
+ replication: replicationOf(declaration, top),
478
561
  summary: {
479
562
  members: members.length,
480
563
  released: members.filter((m) => m.environments[0].releases.length > 0).length,
@@ -631,3 +714,9 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
631
714
  );
632
715
  return lines.join("\n");
633
716
  }
717
+
718
+ /** Where the box's work in progress stands against its remote (#3172), or null when no box block declares replicate. */
719
+ function replicationOf(declaration: Declaration, top: string): ReplicationState | null {
720
+ const m = declaration.members.find((x) => x.box?.replicate);
721
+ return m ? replicationState(top, policyView(m.name, m.box!.replicate!)) : null;
722
+ }