@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
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The factory's rules (#3406, ws-087): readiness, understand, retry, done
3
+ * and the implements proposals, as pure functions. The Op that runs them is
4
+ * tested in ../op/factory.e2e.test.ts.
5
+ */
6
+
7
+ import { describe, expect, test } from "vitest";
8
+ import { isAsk, pickable, retryState, doneVerdict, proposeImplements, understandOutcome, type FactoryContext, type FactoryItem } from "./factory-rules";
9
+
10
+ describe("the factory's rules", () => {
11
+ const base: FactoryItem = { id: "W-1", state: "open", openState: "open", proposedState: "proposed", ready: true, data: {}, tier: null, contract: null, warnings: [], answers: [], leased: false };
12
+ const ctx: FactoryContext = { intentDecided: null, claims: [], attempts: { exhausted: false }, branch: null, questions: { tier: null, understand: null } };
13
+ const hold = (i: Partial<FactoryItem>, c: Partial<FactoryContext> = {}) => {
14
+ const v = pickable({ ...base, ...i }, { ...ctx, ...c });
15
+ return v.ok ? "ok" : v.hold;
16
+ };
17
+
18
+ test("readiness", () => {
19
+ expect(hold({})).toBe("ok");
20
+ expect(hold({}, { intentDecided: false })).toBe("intent-undecided");
21
+ expect(hold({ ready: false })).toBe("not-ready");
22
+ expect(hold({ ready: false, state: "proposed", data: { source: { ask: { said: "x", by: "a" } } } })).toBe("ok");
23
+ expect(hold({ contract: { id: "C-1", state: "draft" }, warnings: ["work-contract-undecided"] })).toBe("contract-not-approved");
24
+ expect(hold({}, { branch: { state: "done", applied: false } })).toBe("built-not-applied");
25
+ expect(hold({}, { branch: { state: "done", applied: true } })).toBe("ok");
26
+ expect(hold({}, { branch: { state: "dropped", applied: false } })).toBe("dropped-on-branch");
27
+ expect(hold({}, { attempts: { exhausted: true } })).toBe("attempts-exhausted");
28
+ const failed = { claims: [{ token: "t1", ended: "released", outcome: "not_done", attempt: true }] };
29
+ expect(hold({}, failed)).toBe("awaiting-retry");
30
+ expect(hold({ data: { retry: { after: "t1", by: "alice" } } }, failed)).toBe("ok");
31
+ expect(hold({ data: { retry: { after: "t0", by: "alice" } } }, failed)).toBe("awaiting-retry");
32
+ expect(hold({}, { questions: { tier: { state: "escalated", answer: null }, understand: null } })).toBe("question-open");
33
+ expect(hold({ tier: "large" }, { questions: { tier: { state: "escalated", answer: null }, understand: null } })).toBe("ok");
34
+ const ask = { data: { source: { ask: { said: "x", by: "a" } } } };
35
+ expect(hold(ask, { questions: { tier: null, understand: { state: "answered", answer: "redraft" } } })).toBe("redraft-or-ask");
36
+ expect(hold({ leased: true })).toBe("leased");
37
+ });
38
+
39
+ test("understand, retry, done and implements", () => {
40
+ expect(isAsk({ source: { intent: { answer: "x" } } })).toBe(true);
41
+ expect([understandOutcome("proceed"), understandOutcome("refuse"), understandOutcome("redraft"), understandOutcome("ask")]).toEqual([null, "dropped", "redraft", "ask"]);
42
+ const claims = [{ token: "t1", ended: "released", outcome: "not_done", attempt: true }];
43
+ expect(retryState({}, claims, { exhausted: false }, false)).toEqual({ possible: true, after: "t1", asked: false });
44
+ expect(retryState({ retry: { after: "t1", by: "a" } }, claims, { exhausted: false }, false)).toMatchObject({ asked: true });
45
+ expect(retryState({}, claims, { exhausted: true }, false)).toMatchObject({ possible: false, reason: "attempts-exhausted" });
46
+ expect(retryState({}, [], { exhausted: false }, false)).toMatchObject({ possible: false, reason: "no-failed-build" });
47
+ const unit = { id: "AC-1", verification: "unit", met: true };
48
+ expect(doneVerdict({ finished: true, reverted: [] }, { ran: true, ok: true }, [unit])).toEqual({ done: true });
49
+ expect(doneVerdict({ finished: true, reverted: [] }, { ran: true, ok: true }, [unit, { id: "AC-2", verification: "manual", met: false }])).toMatchObject({ done: false });
50
+ expect(doneVerdict({ finished: true, reverted: ["x"] }, { ran: true, ok: true }, [unit])).toMatchObject({ done: false });
51
+ expect(doneVerdict({ finished: true, reverted: [] }, { ran: true, ok: false }, [unit])).toMatchObject({ done: false });
52
+ expect(
53
+ proposeImplements(
54
+ [
55
+ { path: "a.ts", decisions: [{ id: "d-1", state: "decided", granularity: "path" }, { id: "d-2", state: "decided", granularity: "member" }, { id: "d-3", state: "proposed", granularity: "path" }] },
56
+ { path: "b.ts", decisions: [{ id: "d-1", state: "decided", granularity: "path" }, { id: "d-4", state: "ratified", granularity: "path" }] },
57
+ ],
58
+ ["d-4"],
59
+ ),
60
+ ).toEqual([{ decision: "d-1", paths: ["a.ts", "b.ts"] }]);
61
+ });
62
+ });
@@ -0,0 +1,218 @@
1
+ /**
2
+ * The factory's rules (#3406, ws-087): what every orchestrator of a factory
3
+ * must agree on, as pure functions over what the read contract prints. The
4
+ * reference Op (`../op/factory.ts`) runs them; an orchestrator supplies only
5
+ * execution (the builder, the context, the check).
6
+ *
7
+ * - Readiness ({@link pickable}): on top of the work kind's `ready`, the box's
8
+ * intent is decided, a contract the item builds is approved and in force,
9
+ * the item is under its attempt limit, it is not done and waiting to be
10
+ * applied on its branch, its slice-tier and understand questions are not
11
+ * open, an understand answer of redraft or ask holds it, and a failed last
12
+ * build holds it until a person asks for a retry ({@link retryState}).
13
+ * - The understand point's answers ({@link understandOutcome}): proceed
14
+ * builds; refuse drops the item on its branch; redraft and ask build
15
+ * nothing and leave it for its author. Each maps to a lease outcome, and
16
+ * only a build's outcome counts as an attempt (#3147).
17
+ * - Done ({@link doneVerdict}): the builder finished, the check ran and
18
+ * passed, and every criterion the factory may tick has passing evidence. A
19
+ * criterion verified `manual` or `runtime` is not the factory's to tick, so
20
+ * an item with one is not done by the factory.
21
+ * - Retry ({@link retryState}): a failed build is never tried again on its
22
+ * own. A person's request, the item's `retry: { after, by, at }`, names the
23
+ * lease token of the failed build it follows, so one request starts one run.
24
+ * - Implements ({@link proposeImplements}): a build recorded done proposes
25
+ * each decision in force that constrains, by path, a path it changed.
26
+ */
27
+
28
+ /** The decision point that gates every build of a person's ask (#3150). */
29
+ export const UNDERSTAND_POINT = "understand";
30
+ /** The decision point that picks a builder tier (ws-057, #3150). */
31
+ export const TIER_POINT = "slice-tier";
32
+
33
+ /** What the understand point answers. */
34
+ export const UNDERSTAND_ANSWERS = ["proceed", "redraft", "ask", "refuse"] as const;
35
+ export type UnderstandAnswer = (typeof UNDERSTAND_ANSWERS)[number];
36
+
37
+ /** Why an item is not picked. Closed. */
38
+ export const FACTORY_HOLDS = [
39
+ "not-ready",
40
+ "intent-undecided",
41
+ "contract-not-approved",
42
+ "attempts-exhausted",
43
+ "awaiting-retry",
44
+ "built-not-applied",
45
+ "dropped-on-branch",
46
+ "question-open",
47
+ "redraft-or-ask",
48
+ "leased",
49
+ ] as const;
50
+ export type FactoryHold = (typeof FACTORY_HOLDS)[number];
51
+
52
+ /** A work item as the factory reads it: its record from `records --json`, trimmed to what the rules use. */
53
+ export interface FactoryItem {
54
+ id: string;
55
+ state: string | null;
56
+ /** The kind's open state, such as open. */
57
+ openState: string;
58
+ /** The kind's first state for a proposal, such as proposed; an ask in it is a candidate too. */
59
+ proposedState: string | null;
60
+ ready: boolean;
61
+ data: Record<string, unknown> | null;
62
+ /** The item's own tier, or null. */
63
+ tier: string | null;
64
+ /** The contract it builds, with that record's state, or null. */
65
+ contract: { id: string; state: string | null } | null;
66
+ /** The record's warnings' codes. */
67
+ warnings: string[];
68
+ /** The answers about it, as records --json joins them. */
69
+ answers: { id: string; point: string | null; state: string | null; answer: string | boolean | null }[];
70
+ /** Someone holds its lease now. */
71
+ leased: boolean;
72
+ }
73
+
74
+ /** One claim of the item's lease history, as `work history --json` prints it. */
75
+ export interface FactoryClaim {
76
+ token: string;
77
+ ended: string;
78
+ outcome: string | null;
79
+ attempt: boolean;
80
+ }
81
+
82
+ /** What the factory knows besides the item. */
83
+ export interface FactoryContext {
84
+ /** null when no box names an intent; else whether that decision is decided. */
85
+ intentDecided: boolean | null;
86
+ claims: FactoryClaim[];
87
+ attempts: { exhausted: boolean };
88
+ /** The item as its work branch holds it, when the branch exists: its state there and whether the branch is in the checkout. */
89
+ branch: { state: string | null; applied: boolean } | null;
90
+ /** The current question of each point for the item as it is now (a dry run), or null when not asked yet. */
91
+ questions: { tier: { state: string; answer: string | boolean | null } | null; understand: { state: string; answer: string | boolean | null } | null };
92
+ }
93
+
94
+ /** Whether a work item is a person's ask: its source names what they said, or it is the box's first build from its intent. */
95
+ export function isAsk(data: Record<string, unknown> | null): boolean {
96
+ const source = data?.source as { ask?: unknown; intent?: unknown } | undefined;
97
+ return !!(source && typeof source === "object" && (source.ask || source.intent));
98
+ }
99
+
100
+ /** The retry request on an item: `retry: { after, by, at }`, or null. */
101
+ export function retryRequest(data: Record<string, unknown> | null): { after: string; by: string; at: string } | null {
102
+ const r = data?.retry as { after?: unknown; by?: unknown; at?: unknown } | undefined;
103
+ if (!r || typeof r !== "object" || typeof r.after !== "string" || typeof r.by !== "string") return null;
104
+ return { after: r.after, by: r.by, at: typeof r.at === "string" ? r.at : "" };
105
+ }
106
+
107
+ export type RetryState =
108
+ /** The last claim was a failed build and a person may ask for one more. */
109
+ | { possible: true; after: string; asked: boolean }
110
+ | { possible: false; reason: "no-failed-build" | "attempts-exhausted" | "held"; message: string };
111
+
112
+ /**
113
+ * Whether a person may ask for item `data`'s failed build to run again: the
114
+ * last claim is a failed build, nobody holds the lease, and attempts remain.
115
+ * `asked` says a request naming that build is already on the item.
116
+ */
117
+ export function retryState(data: Record<string, unknown> | null, claims: readonly FactoryClaim[], attempts: { exhausted: boolean }, leased: boolean): RetryState {
118
+ if (leased) return { possible: false, reason: "held", message: "someone holds the item's lease now" };
119
+ const last = claims.at(-1);
120
+ if (!last || !last.attempt) return { possible: false, reason: "no-failed-build", message: "the item's last claim is not a failed build, so there is nothing to retry" };
121
+ if (attempts.exhausted) return { possible: false, reason: "attempts-exhausted", message: "the item has used every attempt its limit allows, so it is left to people" };
122
+ const asked = retryRequest(data)?.after === last.token;
123
+ return { possible: true, after: last.token, asked };
124
+ }
125
+
126
+ /** The lease outcome an understand answer leads to: null for proceed, which builds. */
127
+ export function understandOutcome(answer: unknown): "dropped" | "redraft" | "ask" | null {
128
+ if (answer === "refuse") return "dropped";
129
+ if (answer === "redraft" || answer === "ask") return answer;
130
+ return null;
131
+ }
132
+
133
+ const OPEN = new Set(["escalated", "proposed"]);
134
+
135
+ /**
136
+ * Whether the factory picks `item` now, or why not. Asks are candidates while
137
+ * proposed as well as open; any other item only when the work kind reads it
138
+ * ready.
139
+ */
140
+ export function pickable(item: FactoryItem, ctx: FactoryContext): { ok: true } | { ok: false; hold: FactoryHold; message: string } {
141
+ const no = (hold: FactoryHold, message: string) => ({ ok: false as const, hold, message });
142
+ if (ctx.intentDecided === false) return no("intent-undecided", "the box's intent is not decided yet, so nothing is picked");
143
+ if (item.leased) return no("leased", `${item.id} is leased`);
144
+ const ask = isAsk(item.data);
145
+ const candidate = item.ready || (ask && item.proposedState !== null && item.state === item.proposedState);
146
+ if (!candidate) return no("not-ready", `${item.id} is ${item.state ?? "in no state"} and not ready`);
147
+ if (item.contract && (item.warnings.includes("work-contract-undecided") || item.warnings.includes("work-contract-unknown"))) {
148
+ return no("contract-not-approved", `${item.id} builds ${item.contract.id}, which is not approved and in force`);
149
+ }
150
+ if (ctx.branch && !ctx.branch.applied && ctx.branch.state === "done") return no("built-not-applied", `${item.id} is built on its branch and waits to be applied`);
151
+ if (ctx.branch && !ctx.branch.applied && ctx.branch.state === "dropped") return no("dropped-on-branch", `${item.id} was dropped on its branch`);
152
+ if (ctx.attempts.exhausted) return no("attempts-exhausted", `${item.id} has used every attempt, so it is left to people`);
153
+ const last = ctx.claims.at(-1);
154
+ if (last?.attempt && retryRequest(item.data)?.after !== last.token) {
155
+ return no("awaiting-retry", `${item.id}'s last build failed; a person asks for another with retry.after: ${last.token}`);
156
+ }
157
+ for (const [name, q] of [[TIER_POINT, item.tier === null ? ctx.questions.tier : null], [UNDERSTAND_POINT, ask ? ctx.questions.understand : null]] as const) {
158
+ if (q && OPEN.has(q.state)) return no("question-open", `${item.id}'s ${name} question is ${q.state}; a person answers it first`);
159
+ }
160
+ const u = ask ? ctx.questions.understand : null;
161
+ if (u && u.state === "answered" && (u.answer === "redraft" || u.answer === "ask")) {
162
+ return no("redraft-or-ask", `${item.id}'s understand question was answered ${String(u.answer)}; its author changes the item, which asks again`);
163
+ }
164
+ return { ok: true };
165
+ }
166
+
167
+ /** One acceptance criterion's state, as records --json lists it. */
168
+ export interface CriterionView {
169
+ id: string;
170
+ verification: string;
171
+ met: boolean;
172
+ }
173
+
174
+ /** Whether a build is done, or why not. */
175
+ export function doneVerdict(build: { finished: boolean; reverted: string[] }, check: { ran: boolean; ok: boolean }, criteria: readonly CriterionView[]): { done: true } | { done: false; reason: string } {
176
+ if (!build.finished) return { done: false, reason: "the builder did not finish" };
177
+ if (build.reverted.length > 0) return { done: false, reason: `the guard put back what the builder changed out of scope: ${build.reverted.join(", ")}` };
178
+ if (!check.ran) return { done: false, reason: "no check ran" };
179
+ if (!check.ok) return { done: false, reason: "the check failed" };
180
+ const people = criteria.filter((c) => c.verification === "manual" || c.verification === "runtime");
181
+ if (people.length > 0) return { done: false, reason: `criteria ${people.map((c) => c.id).join(", ")} are verified ${people.map((c) => c.verification).join("/")}, which the factory never ticks` };
182
+ const unmet = criteria.filter((c) => !c.met);
183
+ if (unmet.length > 0) return { done: false, reason: `criteria ${unmet.map((c) => c.id).join(", ")} have no passing evidence` };
184
+ return { done: true };
185
+ }
186
+
187
+ /**
188
+ * The decisions a done build proposes it implements (studio#243): from the
189
+ * intent graph's `constrains` edges of granularity `path` to each changed
190
+ * path, the decisions in force, less those the item already implements.
191
+ */
192
+ export function proposeImplements(
193
+ changed: readonly { path: string; decisions: readonly { id: string; state: string | null; granularity: string }[] }[],
194
+ already: readonly string[],
195
+ inForce: readonly string[] = ["decided", "ratified"],
196
+ ): { decision: string; paths: string[] }[] {
197
+ const out = new Map<string, string[]>();
198
+ for (const c of changed) {
199
+ for (const d of c.decisions) {
200
+ if (d.granularity !== "path" || d.state === null || !inForce.includes(d.state) || already.includes(d.id)) continue;
201
+ const paths = out.get(d.id) ?? [];
202
+ if (!paths.includes(c.path)) paths.push(c.path);
203
+ out.set(d.id, paths);
204
+ }
205
+ }
206
+ return [...out].map(([decision, paths]) => ({ decision, paths }));
207
+ }
208
+
209
+ /**
210
+ * The key of the state an item was read in, for a steward's ready step: a
211
+ * new key is work no run was started for. It names the item, its last claim
212
+ * and its open questions, so a person's answer to either point is a new key.
213
+ */
214
+ export function readyKey(item: Pick<FactoryItem, "id">, ctx: Pick<FactoryContext, "claims" | "questions">, data: Record<string, unknown> | null): string {
215
+ const last = ctx.claims.at(-1)?.token ?? "none";
216
+ const q = (x: { state: string; answer: unknown } | null) => (x ? `${x.state}:${String(x.answer)}` : "-");
217
+ return `${item.id}@${last}#tier=${q(ctx.questions.tier)}#understand=${q(ctx.questions.understand)}#retry=${retryRequest(data)?.after ?? "-"}`;
218
+ }
@@ -59,7 +59,7 @@ export type GraphErrorCode = (typeof GRAPH_ERROR_CODES)[number];
59
59
  const LIVE_AT_REVISION = "--live reads the account as it stands now, and --at reads the source at a revision; chant workspace graph takes one of them";
60
60
 
61
61
  const USAGE =
62
- "chant workspace graph [dir] [--at <rev>] [--member <name>] [--kind <kind file>] [-o <file>] [--env <env>] [--live [--overlay] [--traffic <level>]] [--no-cache] [--dry-run] | chant workspace graph --composites [--at <rev>] [--member <name>] [-o <file>] | chant workspace graph --intent <path[:start-end]> [--at <rev>] [--kind <kind file>...] [--json]";
62
+ "chant workspace graph [dir] [--at <rev>] [--member <name>] [--kind <kind file>] [-o <file>] [--env <env>] [--live [--overlay] [--traffic <level>]] [--no-cache] [--dry-run] | chant workspace graph --composites [--at <rev>] [--member <name>] [-o <file>] | chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
63
63
 
64
64
  interface Head {
65
65
  $schema: string;
@@ -260,7 +260,7 @@ export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
260
260
  // Links (#2539) resolve against the declaration that was read, the revision's for --at, and the kinds installed now.
261
261
  const graph = composeWorkspaceGraph({ name: declaration.name, root: located.root }, inputs, { declaration, kinds });
262
262
  // The box's factory and listing (#3146), from the declaration read, so --at gives them at that revision.
263
- for (const member of graph.members) member.box = graphBox(declaration.members.find((m) => m.name === member.name)?.box ?? null, located.tree);
263
+ for (const member of graph.members) member.box = graphBox(declaration.members.find((m) => m.name === member.name)?.box ?? null, located.tree, declaration.members);
264
264
  // Nested workspaces (#2551, ws-071): read-only, through their own chant workspace graph.
265
265
  const nestedFailed = await expandNested(graph, declaration.members, query, located.rootOnDisk, located.at);
266
266
  let recordsFailed = false;
@@ -1114,6 +1114,8 @@
1114
1114
  "check",
1115
1115
  "checks",
1116
1116
  "builders",
1117
+ "tiers",
1118
+ "builderFor",
1117
1119
  "publish"
1118
1120
  ],
1119
1121
  "properties": {
@@ -1168,6 +1170,71 @@
1168
1170
  ],
1169
1171
  "description": "The member that declares the builder agents, or null."
1170
1172
  },
1173
+ "tiers": {
1174
+ "type": "array",
1175
+ "items": {
1176
+ "type": "object",
1177
+ "required": [
1178
+ "tier",
1179
+ "agent",
1180
+ "kinds",
1181
+ "session"
1182
+ ],
1183
+ "properties": {
1184
+ "tier": {
1185
+ "type": "string"
1186
+ },
1187
+ "agent": {
1188
+ "type": "string"
1189
+ },
1190
+ "kinds": {
1191
+ "oneOf": [
1192
+ {
1193
+ "type": "null"
1194
+ },
1195
+ {
1196
+ "type": "array",
1197
+ "items": {
1198
+ "type": "string"
1199
+ }
1200
+ }
1201
+ ]
1202
+ },
1203
+ "session": {
1204
+ "type": [
1205
+ "string",
1206
+ "null"
1207
+ ]
1208
+ }
1209
+ }
1210
+ },
1211
+ "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."
1212
+ },
1213
+ "builderFor": {
1214
+ "type": "object",
1215
+ "additionalProperties": {
1216
+ "type": "object",
1217
+ "additionalProperties": {
1218
+ "type": "object",
1219
+ "required": [
1220
+ "agent",
1221
+ "session"
1222
+ ],
1223
+ "properties": {
1224
+ "agent": {
1225
+ "type": "string"
1226
+ },
1227
+ "session": {
1228
+ "type": [
1229
+ "string",
1230
+ "null"
1231
+ ]
1232
+ }
1233
+ }
1234
+ }
1235
+ },
1236
+ "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."
1237
+ },
1171
1238
  "publish": {
1172
1239
  "description": "Where a finished build is published, or null when the factory doesn't publish past the box.",
1173
1240
  "oneOf": [
@@ -161,6 +161,29 @@ describe.skipIf(!hasSshKeygen)("identity.gates", () => {
161
161
  expect(unknown.refuses(signed(alice, approval("github:alice")))).toMatch(/no pinned package supplies/);
162
162
  });
163
163
 
164
+ test("a relayed approval: the seal covers relayedBy, and status lists it (#3402)", () => {
165
+ const { alice } = keys();
166
+ const policy = policyWith([{ principal: "github:alice", key: alice.pub }]);
167
+ const rule = gateAdmissionFrom({ declaration: decl({ gates: { ship: {} } }), policy, classes: coreClassRegistry() }, "ship")!;
168
+ const relayed = signed(alice, approval("github:alice", { relayedBy: "github:hud-follower" }));
169
+ expect(rule.refuses(relayed)).toBeNull();
170
+ // The relay can't be changed, dropped or added after sealing.
171
+ expect(rule.refuses({ ...relayed, relayedBy: "github:mallory" })).toMatch(/does not verify/);
172
+ const { relayedBy: _dropped, ...unrelayed } = relayed;
173
+ expect(rule.refuses(unrelayed)).toMatch(/does not verify/);
174
+ expect(rule.refuses({ ...signed(alice, approval("github:alice")), relayedBy: "github:hud-follower" })).toMatch(/does not verify/);
175
+ // A seal made before #3402, with no relay, still verifies.
176
+ expect(rule.refuses(signed(alice, approval("github:alice")))).toBeNull();
177
+
178
+ const pending: PendingGateRecord = { version: 1, kind: "pending", op: "web", gate: "ship", environment: "prod", planDigest: "sha256:" + "a".repeat(64), timestamp: "2026-10-03T11:00:00.000Z", expiresAt: "2026-10-10T11:00:00.000Z" };
179
+ const ledger = [pending, relayed, approval("bob")].map((l) => JSON.stringify(l)).join("\n");
180
+ const gate = gatesInLedger("web", ledger, ["prod"], "2026-10-03T13:00:00.000Z").gates[0];
181
+ expect(gate.approvals).toEqual([
182
+ { principal: "github:alice", channel: null, relayedBy: "github:hud-follower", at: "2026-10-03T12:00:00.000Z" },
183
+ { principal: "bob", channel: null, relayedBy: null, at: "2026-10-03T12:00:00.000Z" },
184
+ ]);
185
+ });
186
+
164
187
  test("a run and workspace status leave out approvals the rule refuses", async () => {
165
188
  const { alice } = keys();
166
189
  const policy = policyWith([{ principal: "github:alice", key: alice.pub }]);
@@ -21,10 +21,18 @@
21
21
  * never written.
22
22
  *
23
23
  * Nothing here runs project code: it reads file names and package.json files.
24
+ *
25
+ * `--profile ideation|app|infra` starts a workspace from one of the reference
26
+ * workspace's profiles instead (#3174, ws-096): records only with a stub app,
27
+ * an app with its factory, or an estate with a factory and no app. It is
28
+ * `chant init --from` on `reference-workspace/profiles/<profile>` of the chant
29
+ * repository at this chant's release tag, or of the repository or directory
30
+ * `--from` names, so the new workspace gets a lineage lock and can be upgraded
31
+ * like any template copy.
24
32
  */
25
33
 
26
34
  import { execFileSync } from "node:child_process";
27
- import { existsSync, readdirSync, writeFileSync } from "node:fs";
35
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
28
36
  import { basename, join, resolve } from "node:path";
29
37
  import { formatError, formatSuccess } from "../cli/format";
30
38
  import type { CommandContext } from "../cli/registry";
@@ -36,12 +44,34 @@ import {
36
44
  rootExclusions,
37
45
  WorkspaceReadError,
38
46
  isInside,
47
+ readerVersion,
39
48
  } from "./declaration";
40
49
  import { expandGlob } from "./glob";
50
+ import { setProperty } from "./json-edit";
41
51
  import { holdsChantProject } from "./kinds";
42
52
  import { gitTop, skippedDir, workingTree, type WorkspaceTree } from "./tree";
43
53
 
44
- const USAGE = "chant workspace init [dir] [--name <name>] [--yes] [--verbose]";
54
+ const USAGE = "chant workspace init [dir] [--name <name>] [--yes] [--verbose]\n chant workspace init --profile ideation|app|infra [dir] [--name <name>] [--param name=<display name>] [--from <repo>@<ref>|<chant checkout>] [--yes]";
55
+
56
+ /** The profiles `--profile` takes (#3174), each a directory under {@link PROFILES_DIR} in the chant repository. */
57
+ export const PROFILES = ["ideation", "app", "infra"] as const;
58
+ export type Profile = (typeof PROFILES)[number];
59
+
60
+ /** Where the profiles live in the chant repository. */
61
+ export const PROFILES_DIR = "reference-workspace/profiles";
62
+
63
+ /** The repository the profiles are read from by default, at this chant's release tag. */
64
+ export const PROFILES_REPO = "https://github.com/INTENTIUS/chant";
65
+
66
+ /**
67
+ * The `chant init --from` source for a profile: `from` (a `<repo>@<ref>` or
68
+ * a directory holding a chant checkout) when given, else the chant repository
69
+ * at the tag of the chant reading, `chant-v<version>`.
70
+ */
71
+ export function profileSource(profile: Profile, from: string | undefined, version: string): string {
72
+ const base = from ?? `${PROFILES_REPO}@chant-v${version}`;
73
+ return `${base}#${PROFILES_DIR}/${profile}`;
74
+ }
45
75
 
46
76
  /** Directory names whose children are examples or test fixtures, not members. */
47
77
  const EXAMPLE_DIRS = new Map<string, "examples" | "fixtures">([
@@ -544,6 +574,8 @@ export async function runWorkspaceInit(ctx: CommandContext): Promise<number> {
544
574
  return 1;
545
575
  }
546
576
 
577
+ if (args.profile !== undefined) return initProfile(args, root);
578
+
547
579
  let proposal: Proposal;
548
580
  try {
549
581
  proposal = proposeWorkspace(root, { name: args.selectName });
@@ -583,3 +615,51 @@ export async function runWorkspaceInit(ctx: CommandContext): Promise<number> {
583
615
  console.log(formatSuccess(`Wrote ${target}. chant workspace ls lists what it declares.`));
584
616
  return 0;
585
617
  }
618
+
619
+ /** `chant workspace init --profile <profile>` (#3174): copy a profile with `chant init --from`, then name the workspace. */
620
+ async function initProfile(args: CommandContext["args"], root: string): Promise<number> {
621
+ const profile = args.profile as Profile;
622
+ if (!(PROFILES as readonly string[]).includes(profile)) {
623
+ console.error(formatError({ message: `--profile ${JSON.stringify(args.profile)} is not a profile; the profiles are ${PROFILES.join(", ")}`, hint: USAGE }));
624
+ return 1;
625
+ }
626
+ const { parseParamArgs } = await import("./template-manifest");
627
+ let params: Record<string, string>;
628
+ try {
629
+ params = parseParamArgs(args.param ?? []);
630
+ } catch (err) {
631
+ console.error(formatError({ message: (err as Error).message, hint: USAGE }));
632
+ return 1;
633
+ }
634
+ const from = profileSource(profile, args.migrateFrom, readerVersion());
635
+ console.log(`chant workspace init --profile ${profile} copies ${from} into ${root}, as chant init --from would, with a lineage lock.`);
636
+ if (args.selectName !== undefined) console.log(`The workspace is named ${args.selectName}.`);
637
+ console.log("");
638
+ let write = !!args.yes;
639
+ if (!write) {
640
+ if (!process.stdin.isTTY) {
641
+ console.log("Nothing written. Re-run with --yes to write it.");
642
+ return 0;
643
+ }
644
+ write = await confirm(`Make the ${profile} workspace? [y/N] `);
645
+ }
646
+ if (!write) {
647
+ console.log("Nothing written.");
648
+ return 0;
649
+ }
650
+ const { initFromCommand } = await import("./lineage-init");
651
+ const result = await initFromCommand({ from, path: root, params, force: args.force });
652
+ if (!result.success) {
653
+ console.error(formatError({ message: result.error ?? `the ${profile} profile could not be copied`, hint: USAGE }));
654
+ return 1;
655
+ }
656
+ if (args.selectName !== undefined) {
657
+ const file = join(root, "chant.workspace.json");
658
+ writeFileSync(file, setProperty(readFileSync(file, "utf-8"), false, "", "name", args.selectName));
659
+ }
660
+ console.log(formatSuccess(`Made a ${profile} workspace in ${root}:`));
661
+ for (const f of result.createdFiles) console.log(` ${f}`);
662
+ console.log("");
663
+ console.log("chant workspace check checks it, and chant workspace upgrade takes a later version of the profile.");
664
+ return 0;
665
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--json]`
2
+ * `chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]`
3
3
  * (#2651, #3034): the intent graph over one region (`intent.ts`), printed as JSON
4
4
  * with `--json` or as a walk, one line per node, in the order of #2650
5
5
  * section B: the region, its decisions, their artifacts, the commits, and the
@@ -16,8 +16,8 @@ import type { CommandContext } from "../cli/registry";
16
16
  import { intentRecord, type IntentRecordDocument } from "./intent-record";
17
17
  import { intentGraph, type ArtifactNode, type CommitNode, type DecisionNode, type IntentDocument, type IntentEdge, type IntentNode, type RunNode, type WhyAnswer, type WorkNode } from "./intent";
18
18
 
19
- const USAGE = "chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--json]";
20
- const RECORD_USAGE = "chant workspace graph --intent --record <id> [--at <rev>] [--kind <kind file>...] [--json]";
19
+ const USAGE = "chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
20
+ const RECORD_USAGE = "chant workspace graph --intent --record <id> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
21
21
 
22
22
  type Result = Exclude<IntentDocument, { error: unknown }>;
23
23
  type RecordResult = Exclude<IntentRecordDocument, { error: unknown }>;
@@ -110,11 +110,27 @@ function commitLine(doc: Result, c: CommitNode): string[] {
110
110
  for (const s of edgesFrom(doc, e.to, "cites-evidence")) out.push(` evidence ${s.to.slice("evidence:".length)}`);
111
111
  }
112
112
  out.push(...joinLines(c.joins));
113
+ if (c.squash) {
114
+ const s = c.squash;
115
+ out.push(` squash #${s.pullRequest}${s.followed ? `: ${s.commits.length} original ${s.commits.length === 1 ? "commit" : "commits"} from ${s.ref}${s.fetched ? ", fetched" : ""}` : ", not followed"}`);
116
+ for (const o of s.commits) {
117
+ const carried = [...o.joins.records.map((r) => `${r.kind}:${r.id}`), ...(o.joins.lease ? [`lease ${o.joins.lease.token}`] : []), ...(o.joins.run ? [`run ${o.joins.run}`] : [])];
118
+ out.push(` original ${short(o.sha)} ${o.subject}; ${o.signature.level}${carried.length > 0 ? `; ${carried.join(", ")}` : ""}`);
119
+ }
120
+ }
113
121
  for (const e of edgesFrom(doc, c.id, "made-by")) {
114
122
  const r = doc.nodes.find((n): n is RunNode => n.kind === "run" && n.id === e.to);
115
123
  if (!r) continue;
116
124
  const what = r.recorded ? `${[r.harness?.name, r.model].filter(Boolean).join("/") || "no model recorded"}${r.by ? ` for ${r.by}` : ""}${r.cost ? `, ${r.cost.amount} ${r.cost.currency}` : ", unpriced"}` : "not in the run ledger";
117
- out.push(` run ${r.run}: ${what}`);
125
+ const content =
126
+ e.kind !== "made-by"
127
+ ? ""
128
+ : e.joinedBy.includes("patch-id")
129
+ ? `; joined by content${e.recordedAs ? `, as ${short(e.recordedAs)}` : ""}`
130
+ : e.joinedBy.length === 1 && e.joinedBy[0] === "squash"
131
+ ? `; through the squash, by ${(e.via ?? []).map(short).join(", ")}`
132
+ : "";
133
+ out.push(` run ${r.run}: ${what}${content}`);
118
134
  }
119
135
  return out;
120
136
  }
@@ -139,7 +155,7 @@ export function whyLines(doc: Result, why: WhyAnswer): string[] {
139
155
  out.push(` decision ${record(d.decision)} ${d.relevance}${d.lines > 0 ? `, ${d.lines} ${d.lines === 1 ? "line" : "lines"}` : ""}${d.current ? "" : ", superseded"}${d.closed ? ", closed" : ""}`);
140
156
  }
141
157
  for (const b of why.blame) {
142
- const by = b.runs.length > 0 ? `; ${b.runs.map(runLabel).join(", ")}${b.narrowedBy ? " (by its hunks)" : ""}` : b.sha ? "; no run" : "";
158
+ const by = b.runs.length > 0 ? `; ${b.runs.map(runLabel).join(", ")}${b.narrowedBy ? " (by its hunks)" : ""}${b.joinedBy?.includes("patch-id") ? " (joined by content)" : ""}${b.via && b.via.length > 0 ? ` (squashed from ${b.via.map(short).join(", ")})` : ""}` : b.sha ? "; no run" : "";
143
159
  out.push(` lines ${span(b)} ${b.sha ? short(b.sha) : "uncommitted"}${by}`);
144
160
  }
145
161
  for (const g of why.gaps) out.push(` gap ${g.code}: ${g.message}${g.lines ? ` (lines ${g.lines.map(span).join(", ")})` : ""}`);
@@ -195,8 +211,9 @@ export function formatIntentRecord(doc: RecordResult): string {
195
211
  : c.bucket === "within-other"
196
212
  ? `within ${c.alsoWithin.map((w) => w.record).join(", ")}`
197
213
  : "nothing else accounts for it";
198
- const runs = (c.runs ?? []).map((r) => `run ${r.id}${r.model ? ` (${r.model})` : ""}`).join(", ");
199
- out.push(`${c.bucket.padEnd(12)} ${short(c.sha)} ${c.date.slice(0, 10)} ${c.subject}; ${why}${runs ? `; ${runs}` : ""}; ${c.files.join(", ") || c.entries.join(", ")}`);
214
+ const runs = (c.runs ?? []).map((r) => `run ${r.id}${r.model ? ` (${r.model})` : ""}${r.joinedBy?.includes("squash") && !r.joinedBy.some((j) => j !== "squash") ? " through the squash" : ""}`).join(", ");
215
+ const squash = c.squash ? `; squash of #${c.squash.pullRequest}${c.squash.followed ? `, ${c.squash.commits.length} original ${c.squash.commits.length === 1 ? "commit" : "commits"}` : ", not followed"}` : "";
216
+ out.push(`${c.bucket.padEnd(12)} ${short(c.sha)} ${c.date.slice(0, 10)} ${c.subject}; ${why}${runs ? `; ${runs}` : ""}${squash}; ${c.files.join(", ") || c.entries.join(", ")}`);
200
217
  }
201
218
  for (const x of doc.reasons) out.push(`reason ${x.code}: ${x.message}`);
202
219
  const n = doc.counts;
@@ -206,7 +223,7 @@ export function formatIntentRecord(doc: RecordResult): string {
206
223
 
207
224
  async function runRecord(ctx: CommandContext, cwd: string, kinds: string[] | undefined): Promise<number> {
208
225
  const { args } = ctx;
209
- const { doc, failed } = await intentRecord({ cwd, record: args.record!, at: args.at, kinds: kinds?.map((k) => resolve(k)) });
226
+ const { doc, failed } = await intentRecord({ cwd, record: args.record!, at: args.at, kinds: kinds?.map((k) => resolve(k)), followSquash: args.followSquash });
210
227
  if (args.json) console.log(JSON.stringify(doc, null, 2));
211
228
  if ("error" in doc) {
212
229
  console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: RECORD_USAGE }));
@@ -231,7 +248,7 @@ export async function runWorkspaceIntent(ctx: CommandContext, cwd: string): Prom
231
248
  }
232
249
  // No --kind: undefined, so the walk reads the kinds the declaration names (#2680).
233
250
  const kinds = args.kinds ?? (args.kind !== undefined ? [args.kind] : undefined);
234
- const { doc, failed } = await intentGraph({ cwd, region: args.intent, at: args.at, kinds: kinds?.map((k) => resolve(k)) });
251
+ const { doc, failed } = await intentGraph({ cwd, region: args.intent, at: args.at, kinds: kinds?.map((k) => resolve(k)), followSquash: args.followSquash });
235
252
  if (args.json) console.log(JSON.stringify(doc, null, 2));
236
253
  if ("error" in doc) {
237
254
  console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));