@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
@@ -33,13 +33,25 @@
33
33
  * repository does not have, unless it names itself one of the four
34
34
  * exceptions.
35
35
  *
36
+ * 5. Concurrent writes (#3173, ws-089). When the writer performs `records
37
+ * amend`, the suite has it make three amendments of the script's
38
+ * decision at once, each from the digest the suite read
39
+ * (`--expect <digest>`), as two people and an agent would. Exactly one
40
+ * must be written; the other two must come back as chant's
41
+ * record-conflict refusal, naming the winner's digest, returned to the
42
+ * suite unchanged rather than retried blindly or hidden. The suite then
43
+ * has the writer retry each from the digest its refusal named, and each
44
+ * retry must be written. See {@link CONCURRENT_AMENDS}.
45
+ *
36
46
  * `facts()` may only read: every chant call it makes must be a read-contract
37
47
  * command, and it must leave the workspace as it was.
38
48
  *
39
49
  * The workspace is generated for each run, from the reader suite's fixture
40
50
  * and the `__writer_fixture__/` overlay this package ships beside this module:
41
- * the reference workspace's work, answer and session kinds, its decision
42
- * points, and one open work item, W-001. Nothing is written anywhere else.
51
+ * the reference workspace's work, answer and session kinds (with the
52
+ * contract and driver kinds a session's verdicts may also name), its
53
+ * decision points, and one open work item, W-001. Nothing is written
54
+ * anywhere else.
43
55
  *
44
56
  * Actions a writer does not list in `actions` are not applicable to it. The
45
57
  * suite still performs those steps itself, directly through chant, so a later
@@ -68,6 +80,7 @@ export const WRITE_CONTRACT_ACTIONS = [
68
80
  "records close",
69
81
  "points ask",
70
82
  "points answer",
83
+ "points retract",
71
84
  "work claim",
72
85
  "work renew",
73
86
  "work evidence",
@@ -75,6 +88,9 @@ export const WRITE_CONTRACT_ACTIONS = [
75
88
  "runs start",
76
89
  "runs end",
77
90
  "runs record",
91
+ "box listing set",
92
+ "wip save",
93
+ "wip restore",
78
94
  ] as const;
79
95
  export type WriteContractAction = (typeof WRITE_CONTRACT_ACTIONS)[number];
80
96
 
@@ -86,6 +102,7 @@ export const WRITE_CONTRACT_SCHEMAS: Record<WriteContractAction, string> = {
86
102
  "records close": "records-close.schema.json",
87
103
  "points ask": "points-write.schema.json",
88
104
  "points answer": "points-write.schema.json",
105
+ "points retract": "points-write.schema.json",
89
106
  "work claim": "work-lease.schema.json",
90
107
  "work renew": "work-lease.schema.json",
91
108
  "work evidence": "work-evidence.schema.json",
@@ -93,12 +110,15 @@ export const WRITE_CONTRACT_SCHEMAS: Record<WriteContractAction, string> = {
93
110
  "runs start": "runs-write.schema.json",
94
111
  "runs end": "runs-write.schema.json",
95
112
  "runs record": "runs-write.schema.json",
113
+ "box listing set": "box-listing-write.schema.json",
114
+ "wip save": "wip-write.schema.json",
115
+ "wip restore": "wip-write.schema.json",
96
116
  };
97
117
 
98
118
  /**
99
- * The flags a writer may add to each command. The record, points, evidence
100
- * and run writes always print their document, so a writer adds nothing or
101
- * `--json`; a lease prints its document only with `--json`.
119
+ * The flags a writer may add to each command. The record, points, evidence,
120
+ * run and listing writes always print their document, so a writer adds
121
+ * nothing or `--json`; a lease prints its document only with `--json`.
102
122
  */
103
123
  export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (readonly string[])[]> = {
104
124
  "records new": [[], ["--json"]],
@@ -107,6 +127,7 @@ export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (re
107
127
  "records close": [[], ["--json"]],
108
128
  "points ask": [[], ["--json"]],
109
129
  "points answer": [[], ["--json"]],
130
+ "points retract": [[], ["--json"]],
110
131
  "work claim": [["--json"]],
111
132
  "work renew": [["--json"]],
112
133
  "work evidence": [[], ["--json"]],
@@ -114,16 +135,22 @@ export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (re
114
135
  "runs start": [[], ["--json"]],
115
136
  "runs end": [[], ["--json"]],
116
137
  "runs record": [[], ["--json"]],
138
+ "box listing set": [[], ["--json"]],
139
+ "wip save": [[], ["--json"]],
140
+ "wip restore": [[], ["--json"]],
117
141
  };
118
142
 
119
143
  /** What each action is given. Field documents are JSON values; the command reads them from stdin. */
120
144
  export interface WriteParams {
121
145
  "records new": { kind: string; fields: Record<string, unknown> };
122
- "records amend": { id: string; kind: string; fields: Record<string, unknown> };
146
+ /** `expect` (#3173): the record's digest the write builds on, passed as `--expect`. */
147
+ "records amend": { id: string; kind: string; fields: Record<string, unknown>; expect?: string };
123
148
  "records review": { id: string; kind: string; verdict: "agree" | "dissent" | "abstain"; by: string; note?: string };
124
149
  "records close": { id: string; kind: string };
125
- "points ask": { point: string; kind: string; inputs: Record<string, unknown>; subject?: string };
126
- "points answer": { id: string; kind: string; answer: string; by: string };
150
+ /** `candidates` is an ad-hoc point's question and candidates (#3403), `{ question, criteria }`: they go on stdin, and the inputs as JSON on the command line. */
151
+ "points ask": { point: string; kind: string; inputs: Record<string, unknown>; subject?: string; candidates?: { question: string; criteria: Record<string, string> | string[] } };
152
+ "points answer": { id: string; kind: string; answer: string; by: string; note?: string };
153
+ "points retract": { id: string; kind: string; by: string; note?: string };
127
154
  "work claim": { id: string; kind: string; holder: string };
128
155
  "work renew": { id: string; kind: string; holder: string; token: string };
129
156
  "work evidence": { id: string; kind: string; holder: string; token: string; entry: Record<string, unknown> };
@@ -131,6 +158,12 @@ export interface WriteParams {
131
158
  "runs start": { run: Record<string, unknown> };
132
159
  "runs end": { id: string; fields: Record<string, unknown> };
133
160
  "runs record": { run: Record<string, unknown> };
161
+ /** A box's listing (#3308): the member, the listing fields, and an image to copy in as its cover. */
162
+ "box listing set": { member: string; fields: Record<string, unknown>; cover?: string };
163
+ /** A snapshot of the working tree, uncommitted records included, onto refs/chant/wip/<branch> (#3172): what the caller calls it, and who took it. */
164
+ "wip save": { label?: string; by?: string };
165
+ /** Put the working tree back as a snapshot holds it (#3172). */
166
+ "wip restore": { snapshot: string; by?: string };
134
167
  }
135
168
 
136
169
  /** One write the suite asks of the writer: the action, what it is given, and the command that performs it. */
@@ -142,7 +175,7 @@ export type WriteStep = {
142
175
  params: WriteParams[A];
143
176
  /** The arguments after `chant workspace <action>`, as {@link writeArgv} builds them from `params`. */
144
177
  args: string[];
145
- /** What the command reads on stdin (`--from -`, `--set -`, `--inputs -`), when it reads anything. */
178
+ /** What the command reads on stdin (`--from -`, `--set -`, `--inputs -`, `--candidates -`), when it reads anything. */
146
179
  input?: string;
147
180
  };
148
181
  }[WriteContractAction];
@@ -162,7 +195,7 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
162
195
  }
163
196
  case "records amend": {
164
197
  const q = p as WriteParams["records amend"];
165
- return { args: [q.id, "--kind", q.kind, "--set", "-"], input: json(q.fields) };
198
+ return { args: [q.id, "--kind", q.kind, "--set", "-", ...(q.expect !== undefined ? ["--expect", q.expect] : [])], input: json(q.fields) };
166
199
  }
167
200
  case "records review": {
168
201
  const q = p as WriteParams["records review"];
@@ -174,11 +207,18 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
174
207
  }
175
208
  case "points ask": {
176
209
  const q = p as WriteParams["points ask"];
177
- return { args: [q.point, "--inputs", "-", ...(q.subject !== undefined ? ["--subject", q.subject] : []), "--kind", q.kind], input: json(q.inputs) };
210
+ const subject = q.subject !== undefined ? ["--subject", q.subject] : [];
211
+ // An ad-hoc ask (#3403): the question and candidates read from stdin, and the inputs given as JSON.
212
+ if (q.candidates !== undefined) return { args: [q.point, "--inputs", json(q.inputs), "--candidates", "-", ...subject, "--kind", q.kind], input: json(q.candidates) };
213
+ return { args: [q.point, "--inputs", "-", ...subject, "--kind", q.kind], input: json(q.inputs) };
178
214
  }
179
215
  case "points answer": {
180
216
  const q = p as WriteParams["points answer"];
181
- return { args: [q.id, "--answer", q.answer, "--by", q.by, "--kind", q.kind] };
217
+ return { args: [q.id, "--answer", q.answer, "--by", q.by, ...(q.note !== undefined ? ["--note", q.note] : []), "--kind", q.kind] };
218
+ }
219
+ case "points retract": {
220
+ const q = p as WriteParams["points retract"];
221
+ return { args: [q.id, "--by", q.by, ...(q.note !== undefined ? ["--note", q.note] : []), "--kind", q.kind] };
182
222
  }
183
223
  case "work claim": {
184
224
  const q = p as WriteParams["work claim"];
@@ -205,6 +245,18 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
205
245
  const q = p as WriteParams["runs end"];
206
246
  return { args: [q.id, "--from", "-"], input: json(q.fields) };
207
247
  }
248
+ case "box listing set": {
249
+ const q = p as WriteParams["box listing set"];
250
+ return { args: [q.member, "--from", "-", ...(q.cover !== undefined ? ["--cover", q.cover] : [])], input: json(q.fields) };
251
+ }
252
+ case "wip save": {
253
+ const q = p as WriteParams["wip save"];
254
+ return { args: [...(q.label !== undefined ? ["--label", q.label] : []), ...(q.by !== undefined ? ["--by", q.by] : [])] };
255
+ }
256
+ case "wip restore": {
257
+ const q = p as WriteParams["wip restore"];
258
+ return { args: [q.snapshot, ...(q.by !== undefined ? ["--by", q.by] : [])] };
259
+ }
208
260
  default:
209
261
  throw new Error(`not a write-contract action: ${String(action)}`);
210
262
  }
@@ -218,6 +270,32 @@ export const WRITER_KINDS = {
218
270
  work: "work/work.kind.mjs",
219
271
  } as const;
220
272
 
273
+ /** The member of the writer workspace whose box block the script lists (#3308). */
274
+ export const WRITER_BOX = "app";
275
+
276
+ /**
277
+ * The files the suite makes for a run outside the workspace, for a step to
278
+ * hand chant: the cover image the listing step copies in (#3308), a PNG.
279
+ */
280
+ export const WRITER_INPUTS = {
281
+ cover: {
282
+ name: "cover.png",
283
+ bytes: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "base64"),
284
+ },
285
+ } as const;
286
+
287
+ /** Where the suite put {@link WRITER_INPUTS} for this run. */
288
+ export interface WriterInputs {
289
+ dir: string;
290
+ }
291
+
292
+ /** Write {@link WRITER_INPUTS} into `dir`. */
293
+ export function writeWriterInputs(dir: string): WriterInputs {
294
+ mkdirSync(dir, { recursive: true });
295
+ for (const f of Object.values(WRITER_INPUTS)) writeFileSync(join(dir, f.name), f.bytes);
296
+ return { dir };
297
+ }
298
+
221
299
  /** The principals the script writes as. */
222
300
  export const WRITER_PRINCIPALS = { holder: "conformance-writer", reviewer: "conformance-reviewer", by: "conformance" } as const;
223
301
 
@@ -225,10 +303,11 @@ export const WRITER_PRINCIPALS = { holder: "conformance-writer", reviewer: "conf
225
303
  export interface WriterScriptStep<A extends WriteContractAction = WriteContractAction> {
226
304
  id: string;
227
305
  action: A;
228
- params(done: Record<string, Record<string, unknown>>): WriteParams[A];
306
+ /** `inputs` is where the suite put {@link WRITER_INPUTS}; a step that hands chant one of them leaves it out without. */
307
+ params(done: Record<string, Record<string, unknown>>, inputs?: WriterInputs): WriteParams[A];
229
308
  }
230
309
 
231
- const step = <A extends WriteContractAction>(id: string, action: A, params: (done: Record<string, Record<string, unknown>>) => WriteParams[A]): WriterScriptStep => ({ id, action, params }) as WriterScriptStep;
310
+ const step = <A extends WriteContractAction>(id: string, action: A, params: (done: Record<string, Record<string, unknown>>, inputs?: WriterInputs) => WriteParams[A]): WriterScriptStep => ({ id, action, params }) as WriterScriptStep;
232
311
 
233
312
  const leaseToken = (done: Record<string, Record<string, unknown>>): string => String((done.claim?.lease as { token?: unknown } | undefined)?.token ?? "");
234
313
  const RUN_STARTED = { harness: { name: "conformance", version: "1" }, model: "none", provider: "none", by: WRITER_PRINCIPALS.by, unit: "W-001" };
@@ -236,9 +315,12 @@ const RUN_STARTED = { harness: { name: "conformance", version: "1" }, model: "no
236
315
  /**
237
316
  * The writes the suite drives, in order, each action at least once: a
238
317
  * decision made, amended and reviewed; a review session opened and closed; a
239
- * decision point asked and answered; the work item's lease claimed, renewed,
318
+ * decision point asked, answered with a note, the answer retracted and the
319
+ * question answered again (#3351); an ad-hoc question asked with its own
320
+ * question and candidates, as an agent asks the room, and answered (#3403); the work item's lease claimed, renewed,
240
321
  * evidence attached under it and released; one run started and ended, and one
241
- * recorded whole.
322
+ * recorded whole; the box's listing set, with a cover image copied in; and a
323
+ * snapshot of the working tree taken and restored (#3172).
242
324
  */
243
325
  export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
244
326
  step("decision", "records new", () => ({
@@ -275,6 +357,16 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
275
357
  opened: "2026-01-01T00:00:00Z",
276
358
  closed: null,
277
359
  verdicts: [],
360
+ // A review comment anchored in the app, as a UI review batch keeps it (#3350).
361
+ comments: [
362
+ {
363
+ text: "The status line should say which build is running.",
364
+ by: WRITER_PRINCIPALS.reviewer,
365
+ anchor: { route: "/", elements: [{ selector: "#status", tag: "p", text: "Running.", state: "anchored", basis: "id" }] },
366
+ answers: [{ round: 0, disposition: "needs-discussion", note: "Which build: the commit or the release?", by: WRITER_PRINCIPALS.holder, at: "2026-01-01T00:02:00Z" }],
367
+ },
368
+ ],
369
+ rounds: [{ round: 0, note: null, by: WRITER_PRINCIPALS.reviewer, at: "2026-01-01T00:01:00Z", ended: "2026-01-01T00:03:00Z" }],
278
370
  },
279
371
  })),
280
372
  step("close", "records close", (d) => ({ id: String(d.session.id), kind: WRITER_KINDS.session })),
@@ -284,7 +376,21 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
284
376
  subject: "W-001",
285
377
  inputs: { "work-item.criteria": 1, "work-item.files": 1, "work-item.words": 40, "work-item.fits_small": false, "work-item.fits_medium": false },
286
378
  })),
287
- step("answer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "medium", by: WRITER_PRINCIPALS.reviewer })),
379
+ step("answer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "medium", by: WRITER_PRINCIPALS.reviewer, note: "Two criteria and one file, but past the small limits." })),
380
+ step("retract", "points retract", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, by: WRITER_PRINCIPALS.reviewer, note: "Answered for the wrong work item." })),
381
+ step("reanswer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "large", by: WRITER_PRINCIPALS.reviewer })),
382
+ // #3403: an agent's question to the room, its text and options arriving with the ask.
383
+ step("adhoc", "points ask", () => ({
384
+ point: "agent-question",
385
+ kind: WRITER_KINDS.answer,
386
+ subject: "W-001",
387
+ inputs: { "ask.id": "conformance-ask-1", "ask.by": WRITER_PRINCIPALS.holder },
388
+ candidates: {
389
+ question: "Should the writer suite's item ship behind a flag?",
390
+ criteria: { flag: "Behind a flag, off by default.", now: "On for everyone at once." },
391
+ },
392
+ })),
393
+ step("adhoc-answer", "points answer", (d) => ({ id: String(d.adhoc.id), kind: WRITER_KINDS.answer, answer: "flag", by: WRITER_PRINCIPALS.reviewer })),
288
394
  step("claim", "work claim", () => ({ id: "W-001", kind: WRITER_KINDS.work, holder: WRITER_PRINCIPALS.holder })),
289
395
  step("renew", "work renew", (d) => ({ id: "W-001", kind: WRITER_KINDS.work, holder: WRITER_PRINCIPALS.holder, token: leaseToken(d) })),
290
396
  step("evidence", "work evidence", (d) => ({
@@ -312,8 +418,34 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
312
418
  ...RUN_STARTED,
313
419
  },
314
420
  })),
421
+ step("listing", "box listing set", (_d, inputs) => ({
422
+ member: WRITER_BOX,
423
+ fields: { published: true, title: "The writer suite's box", line: "Listed through chant, read back after amnesia." },
424
+ ...(inputs ? { cover: join(inputs.dir, WRITER_INPUTS.cover.name) } : {}),
425
+ })),
426
+ // #3172: a checkpoint of everything the script left uncommitted, and putting it back.
427
+ step("checkpoint", "wip save", () => ({ label: "turn:1", by: WRITER_PRINCIPALS.by })),
428
+ step("undo", "wip restore", (done) => ({ snapshot: String((done.checkpoint?.snapshot as { commit?: unknown } | undefined)?.commit ?? ""), by: WRITER_PRINCIPALS.by })),
315
429
  ];
316
430
 
431
+ /**
432
+ * The concurrent case (#3173): three amendments of the script's decision the
433
+ * writer makes at once, each setting its question, as two people and an
434
+ * agent answering the same record would. The suite gives each the digest it
435
+ * read as `expect`.
436
+ */
437
+ export const CONCURRENT_AMENDS: readonly { id: string; fields: Record<string, unknown> }[] = [
438
+ { id: "concurrent-alice", fields: { question: "How does a tool write a fact about this workspace, as alice reads it?" } },
439
+ { id: "concurrent-bob", fields: { question: "How does a tool write a fact about this workspace, as bob reads it?" } },
440
+ { id: "concurrent-agent", fields: { question: "How does a tool write a fact about this workspace, as the agent reads it?" } },
441
+ ];
442
+
443
+ /** The amend step of {@link CONCURRENT_AMENDS} entry `c`, on record `id`, from digest `expect`. */
444
+ export function concurrentAmendStep(c: { id: string; fields: Record<string, unknown> }, id: string, expect: string): WriteStep {
445
+ const params: WriteParams["records amend"] = { id, kind: WRITER_KINDS.decision, fields: c.fields, expect };
446
+ return { id: c.id, action: "records amend", params, ...writeArgv("records amend", params) } as WriteStep;
447
+ }
448
+
317
449
  /** The four things ws-074 lets a tool keep outside the repo. */
318
450
  export const PRIVATE_STATE_CATEGORIES = ["cache", "telemetry", "secret", "runtime"] as const;
319
451
  export type PrivateStateCategory = (typeof PRIVATE_STATE_CATEGORIES)[number];
@@ -395,8 +527,8 @@ export interface WorkspaceWriterConformanceReport {
395
527
  /** The actions not in `actions`, so not applicable to this writer. */
396
528
  skipped: WriteContractAction[];
397
529
  results: WriterStepResult[];
398
- /** The problems of the checks after the script, by check. */
399
- after: { facts: string[]; state: string[]; amnesia: string[]; holds: string[]; readBack: string[] };
530
+ /** The problems of the checks after the script, by check. `concurrent` is empty, and not run, when the writer does not perform records amend. */
531
+ after: { facts: string[]; state: string[]; amnesia: string[]; holds: string[]; readBack: string[]; concurrent: string[] };
400
532
  /** What `facts()` returned before amnesia and after. */
401
533
  facts: { before: unknown; after: unknown };
402
534
  /** The workspace written. Removed before the report is returned. */
@@ -482,14 +614,31 @@ export function isReadCall(argv: readonly string[]): boolean {
482
614
  case "runs":
483
615
  return !["start", "end", "record"].includes(sub ?? "");
484
616
  case "points":
485
- return !["ask", "answer"].includes(sub ?? "");
617
+ return !["ask", "answer", "retract"].includes(sub ?? "");
486
618
  case "work":
487
619
  return sub === "history";
620
+ case "wip":
621
+ return !["save", "restore", "push", "fetch"].includes(sub ?? "");
488
622
  default:
489
623
  return false;
490
624
  }
491
625
  }
492
626
 
627
+ /**
628
+ * Whether an argument a writer passed is the step's: the same text, or for a
629
+ * step argument that is JSON (an ad-hoc ask's inputs, #3403) the same value,
630
+ * whatever its key order or spacing.
631
+ */
632
+ function sameArg(given: string | undefined, want: string): boolean {
633
+ if (given === want) return true;
634
+ if (given === undefined || !/^[[{]/.test(want)) return false;
635
+ try {
636
+ return isDeepStrictEqual(JSON.parse(given), JSON.parse(want));
637
+ } catch {
638
+ return false;
639
+ }
640
+ }
641
+
493
642
  /**
494
643
  * What is wrong with the chant calls one step made: anything but exactly
495
644
  * one call, of `workspace <action>` with the step's arguments in order and
@@ -503,7 +652,7 @@ export function writerCallProblems(step: WriteStep, calls: readonly ChantRun[]):
503
652
  const prefix = ["workspace", ...step.action.split(" ")];
504
653
  if (prefix.some((t, i) => argv[i] !== t)) return [`${name}: ran chant ${argv.join(" ")}, which is not workspace ${step.action}`];
505
654
  const rest = argv.slice(prefix.length);
506
- const at = rest.findIndex((_, i) => step.args.every((a, j) => rest[i + j] === a));
655
+ const at = rest.findIndex((_, i) => step.args.every((a, j) => sameArg(rest[i + j], a)));
507
656
  if (step.args.length > 0 && at === -1) return [`${name}: ran chant ${argv.join(" ")}, which does not pass ${step.args.join(" ")} in order`];
508
657
  const extra = step.args.length > 0 ? [...rest.slice(0, at), ...rest.slice(at + step.args.length)] : rest;
509
658
  const allowed = WRITE_CONTRACT_JSON_FLAGS[step.action];
@@ -558,6 +707,16 @@ export function reportedWrites(action: WriteContractAction, doc: unknown): { pat
558
707
  const ledger = (d.ledger ?? {}) as Record<string, unknown>;
559
708
  return { paths: [], refs: str(ledger.commit) ? { "refs/heads/chant/lifecycle": String(ledger.commit) } : {} };
560
709
  }
710
+ if (action === "box listing set") return { paths: Array.isArray(d.paths) ? d.paths.filter((p): p is string => typeof p === "string") : [], refs: {} };
711
+ if (action === "wip save" || action === "wip restore") {
712
+ // The one snapshot ref, at the snapshot taken (the restore's checkpoint), and any ref a push recorded under refs/chant/replica/.
713
+ const snapshot = (action === "wip save" ? d.snapshot : (d.checkpoint as Record<string, unknown> | undefined)?.snapshot) as { commit?: unknown } | undefined;
714
+ const refs: Record<string, string> = {};
715
+ if (str(d.ref) && str(snapshot?.commit)) refs[String(d.ref)] = String(snapshot!.commit);
716
+ const replication = (d.replication ?? null) as { remote?: string; pushed?: { ref: string; commit: string }[]; upToDate?: number } | null;
717
+ for (const p of replication?.pushed ?? []) refs[`refs/chant/replica/${replication!.remote}/${p.ref.slice("refs/".length)}`] = p.commit;
718
+ return { paths: Array.isArray(d.paths) ? d.paths.filter((p): p is string => typeof p === "string") : [], refs };
719
+ }
561
720
  return { paths: str(d.path) ? [String(d.path)] : [], refs: {} };
562
721
  }
563
722
 
@@ -582,12 +741,24 @@ export function unreportedChanges(
582
741
  return problems;
583
742
  }
584
743
 
585
- /** Build the step a script step is at, from the documents of the steps before it. */
586
- export function buildStep(s: WriterScriptStep, done: Record<string, Record<string, unknown>>): WriteStep {
587
- const params = s.params(done);
744
+ /** Build the step a script step is at, from the documents of the steps before it and where the suite put its inputs. */
745
+ export function buildStep(s: WriterScriptStep, done: Record<string, Record<string, unknown>>, inputs?: WriterInputs): WriteStep {
746
+ const params = s.params(done, inputs);
588
747
  return { id: s.id, action: s.action, params, ...writeArgv(s.action, params) } as WriteStep;
589
748
  }
590
749
 
750
+ /** The box listings `status --json` prints, by member: what a home site shows of each box (#3308). */
751
+ export async function readListing(run: (argv: string[]) => Promise<ChantRun>): Promise<Record<string, unknown>> {
752
+ const doc = JSON.parse((await run(["workspace", "status", "dev", "--json"])).stdout) as { members?: { name: string; box: { listing?: unknown } | null }[] };
753
+ return Object.fromEntries((doc.members ?? []).filter((m) => m.box?.listing).map((m) => [m.name, m.box!.listing]));
754
+ }
755
+
756
+ /** Each branch's work-in-progress snapshots `wip --json` lists (#3172), by branch: each snapshot's kind and label, newest first. */
757
+ export async function readWip(run: (argv: string[]) => Promise<ChantRun>): Promise<Record<string, [string, string | null][]>> {
758
+ const doc = JSON.parse((await run(["workspace", "wip", "--json"])).stdout) as { branches?: { branch: string; snapshots: { kind: string; label: string | null }[] }[] };
759
+ return Object.fromEntries((doc.branches ?? []).map((b) => [b.branch, b.snapshots.map((s) => [s.kind, s.label] as [string, string | null])]));
760
+ }
761
+
591
762
  /** The smallest writer that conforms: each step is its one command, and its facts are read through the read contract. */
592
763
  export const referenceWriter: WorkspaceWriterFactory = (chant) => ({
593
764
  async write(step) {
@@ -603,6 +774,8 @@ export const referenceWriter: WorkspaceWriterFactory = (chant) => ({
603
774
  }
604
775
  const runs = JSON.parse((await chant.run(["workspace", "runs", "--json"])).stdout) as { runs: { id: string; state: string }[] };
605
776
  out.runs = runs.runs.map((r) => [r.id, r.state]).sort();
777
+ out.listing = await readListing((argv) => chant.run(argv));
778
+ out.wip = await readWip((argv) => chant.run(argv));
606
779
  return out;
607
780
  },
608
781
  });
@@ -616,7 +789,8 @@ export interface WriterConformanceWorkspace {
616
789
  /**
617
790
  * Generate the writer conformance workspace: the reader suite's workspace
618
791
  * ({@link createConformanceWorkspace}), with `__writer_fixture__/` copied over
619
- * it, the four kinds declared in its `records`, and that committed.
792
+ * it, the four kinds declared in its `records`, a box block on
793
+ * {@link WRITER_BOX} for the listing step, and that committed.
620
794
  */
621
795
  export function createWriterConformanceWorkspace(options: { chantCommand?: string[]; timeoutMs?: number } = {}): WriterConformanceWorkspace {
622
796
  const ws = createConformanceWorkspace({ chantCommand: options.chantCommand, timeoutMs: options.timeoutMs });
@@ -625,6 +799,8 @@ export function createWriterConformanceWorkspace(options: { chantCommand?: strin
625
799
  const declFile = join(ws.dir, "chant.workspace.json");
626
800
  const decl = JSON.parse(readFileSync(declFile, "utf-8")) as Record<string, unknown>;
627
801
  decl.records = Object.values(WRITER_KINDS).map((kind) => ({ kind }));
802
+ const box = (decl.members as { name: string; box?: unknown }[] | undefined)?.find((m) => m.name === WRITER_BOX);
803
+ if (box) box.box = {};
628
804
  writeFileSync(declFile, `${JSON.stringify(decl, null, 2)}\n`);
629
805
  const git = (...args: string[]) =>
630
806
  execFileSync("git", ["-c", "user.name=chant", "-c", "user.email=chant@localhost", "-c", "commit.gpgsign=false", ...args], {
@@ -712,6 +888,8 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
712
888
  const privateState = options.privateState ?? [];
713
889
  const ws = createWriterConformanceWorkspace({ chantCommand, timeoutMs });
714
890
  const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "chant-writer-state-")));
891
+ const inputsDir = realpathSync(mkdtempSync(join(tmpdir(), "chant-writer-inputs-")));
892
+ const inputs = writeWriterInputs(inputsDir);
715
893
  const calls: ChantRun[] = [];
716
894
  const transport: ChantTransport = {
717
895
  async run(argv, opts) {
@@ -728,14 +906,14 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
728
906
  throw new Error(`chant ${argv.join(" ")} printed no JSON (exit ${run.status}): ${run.stderr.trim()}`);
729
907
  }
730
908
  };
731
- const after = { facts: [] as string[], state: [] as string[], amnesia: [] as string[], holds: [] as string[], readBack: [] as string[] };
909
+ const after = { facts: [] as string[], state: [] as string[], amnesia: [] as string[], holds: [] as string[], readBack: [] as string[], concurrent: [] as string[] };
732
910
  const facts: { before: unknown; after: unknown } = { before: undefined, after: undefined };
733
911
  const results: WriterStepResult[] = [];
734
912
  try {
735
913
  let built = writer(transport, { stateDir });
736
914
  const done: Record<string, Record<string, unknown>> = {};
737
915
  for (const s of WRITER_SCRIPT) {
738
- const st = buildStep(s, done);
916
+ const st = buildStep(s, done, inputs);
739
917
  if (!checked.includes(st.action)) {
740
918
  const doc = await direct(["workspace", ...st.action.split(" "), ...st.args, ...WRITE_CONTRACT_JSON_FLAGS[st.action][0]], st.input);
741
919
  done[st.id] = doc;
@@ -765,6 +943,11 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
765
943
  results.push({ id: st.id, action: st.action, by: "writer", args: st.args, problems });
766
944
  }
767
945
 
946
+ // Concurrent writes (#3173): three amendments of one record at once, each from the digest read.
947
+ if (checked.includes("records amend")) {
948
+ after.concurrent.push(...(await concurrentAmends(built, calls, direct, String(done.decision?.id ?? ""), ws.dir)));
949
+ }
950
+
768
951
  // What the writer shows, read only through the read contract.
769
952
  const readFacts = async (w: WorkspaceWriter, when: string): Promise<unknown> => {
770
953
  const files = worktreeDigest(ws.dir);
@@ -849,13 +1032,22 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
849
1032
  }
850
1033
  const runs = new Map(((await direct(["workspace", "runs", "--json"])).runs as { id: string; state: string }[] | undefined ?? []).map((r) => [r.id, r.state]));
851
1034
  const history = await direct(["workspace", "work", "history", "W-001", "--kind", WRITER_KINDS.work, "--json"]);
1035
+ const listings = await readListing((argv) => runChant(chantCommand, argv, ws.dir, timeoutMs));
1036
+ const wipDoc = await direct(["workspace", "wip", "--json"]);
1037
+ const snapshots = new Set(((wipDoc.branches ?? []) as { snapshots: { commit: string }[] }[]).flatMap((b) => b.snapshots.map((x) => x.commit)));
852
1038
  const claims = (history.claims ?? []) as { token: string; ended: string | null; release?: { outcome?: string } | null }[];
1039
+ const questions = new Map(((await direct(["workspace", "points", "--kind", WRITER_KINDS.answer, "--json"])).questions as { id: string; retractions?: unknown[]; asked?: unknown }[] | undefined ?? []).map((q) => [q.id, q]));
853
1040
  for (const s of WRITER_SCRIPT) {
854
1041
  const doc = done[s.id] ?? {};
855
1042
  if (s.action.startsWith("records ") || s.action.startsWith("points ")) {
856
1043
  const kind = String((doc.kind as { file?: unknown } | undefined)?.file ?? "");
857
1044
  const id = String(doc.id ?? "");
858
1045
  if (!uncommitted.get(kind)?.has(id)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote ${id}, and records --uncommitted --kind ${kind} does not list it`);
1046
+ else if (s.action === "points retract" && !(questions.get(id)?.retractions?.length ?? 0)) after.readBack.push(`read back: ${s.id} (${s.action}) retracted the answer to ${id}, and points --json lists no retraction for it`);
1047
+ else if (s.action === "points ask") {
1048
+ const asked = (doc.question as { asked?: unknown } | undefined)?.asked;
1049
+ if (asked && !isDeepStrictEqual(questions.get(id)?.asked, asked)) after.readBack.push(`read back: ${s.id} (${s.action}) asked ${id} with its own question and candidates, and points --json does not list them as asked`);
1050
+ }
859
1051
  } else if (s.action === "work evidence") {
860
1052
  if (!uncommitted.get(WRITER_KINDS.work)?.has(String(doc.item ?? ""))) after.readBack.push(`read back: ${s.id} (${s.action}) amended ${String(doc.item)}, and records --uncommitted does not list it`);
861
1053
  } else if (s.action.startsWith("work ")) {
@@ -868,13 +1060,132 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
868
1060
  const want = s.action === "runs start" ? undefined : "ended";
869
1061
  if (!runs.has(id)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote run ${id}, and runs does not list it`);
870
1062
  else if (want && runs.get(id) !== want) after.readBack.push(`read back: ${s.id} (${s.action}) ended run ${id}, and runs says it is ${runs.get(id)}`);
1063
+ } else if (s.action === "box listing set") {
1064
+ const member = String(doc.member ?? "");
1065
+ if (!isDeepStrictEqual(listings[member], doc.listing)) after.readBack.push(`read back: ${s.id} (${s.action}) set ${member}'s listing to ${brief(doc.listing)}, and status --json says ${brief(listings[member] ?? null)}`);
1066
+ } else if (s.action === "wip save" || s.action === "wip restore") {
1067
+ const snap = (s.action === "wip save" ? doc.snapshot : (doc.checkpoint as Record<string, unknown> | undefined)?.snapshot) as { commit?: unknown } | undefined;
1068
+ const commit = String(snap?.commit ?? "");
1069
+ if (!snapshots.has(commit)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote snapshot ${commit}, and wip --json does not list it`);
871
1070
  }
872
1071
  }
873
1072
 
874
- const problems = [...results.flatMap((r) => r.problems), ...after.facts, ...after.state, ...after.amnesia, ...after.holds, ...after.readBack];
1073
+ const problems = [...results.flatMap((r) => r.problems), ...after.concurrent, ...after.facts, ...after.state, ...after.amnesia, ...after.holds, ...after.readBack];
875
1074
  return { problems, checked, skipped, results, after, facts, workspaceDir: ws.dir };
876
1075
  } finally {
877
1076
  rmSync(stateDir, { recursive: true, force: true });
1077
+ rmSync(inputsDir, { recursive: true, force: true });
878
1078
  ws.dispose();
879
1079
  }
880
1080
  }
1081
+
1082
+ /**
1083
+ * The concurrent case (#3173, ws-089): the writer makes the three
1084
+ * {@link CONCURRENT_AMENDS} of record `id` at once, each from the digest the
1085
+ * suite read. Exactly one is written and the others are refused with
1086
+ * record-conflict naming its digest; each is then retried from the digest
1087
+ * its refusal named, and written. Returns the problems, each starting
1088
+ * `concurrent:`.
1089
+ */
1090
+ export async function concurrentAmends(
1091
+ built: WorkspaceWriter,
1092
+ calls: ChantRun[],
1093
+ direct: (argv: string[], input?: string) => Promise<Record<string, unknown>>,
1094
+ id: string,
1095
+ dir: string,
1096
+ ): Promise<string[]> {
1097
+ const problems: string[] = [];
1098
+ const digestOf = async (): Promise<{ digest: string; path: string } | undefined> => {
1099
+ const doc = await direct(["workspace", "records", "--kind", WRITER_KINDS.decision, "--json"]);
1100
+ const r = ((doc.records ?? []) as { id: string; digest: string; path: string }[]).find((x) => x.id === id);
1101
+ return r ? { digest: r.digest, path: r.path } : undefined;
1102
+ };
1103
+ const start = await digestOf();
1104
+ if (!start) return [`concurrent: the script's decision ${id || "(none)"} can't be read back to amend`];
1105
+ const files = { before: worktreeDigest(dir), after: {} as Record<string, string> };
1106
+ const refs = { before: gitRefs(dir), after: {} as Record<string, string> };
1107
+ const steps = CONCURRENT_AMENDS.map((c) => concurrentAmendStep(c, id, start.digest));
1108
+ calls.length = 0;
1109
+ const docs = await Promise.all(
1110
+ steps.map(async (st) => {
1111
+ try {
1112
+ return { st, doc: await built.write(st) };
1113
+ } catch (e) {
1114
+ problems.push(`concurrent: ${st.id} (${st.action}): the writer threw: ${(e as Error).message}`);
1115
+ return { st, doc: undefined };
1116
+ }
1117
+ }),
1118
+ );
1119
+ // Each step's call: the one that gave chant its fields.
1120
+ const printed = new Map<string, Record<string, unknown> | undefined>();
1121
+ for (const { st, doc } of docs) {
1122
+ const mine = calls.filter((c) => c.input !== undefined && st.input !== undefined && isDeepStrictEqual(safeJson(c.input), JSON.parse(st.input)));
1123
+ const callProblems = writerCallProblems(st, mine);
1124
+ if (callProblems.length > 0) {
1125
+ problems.push(...callProblems.map((p) => `concurrent: ${p}`));
1126
+ continue;
1127
+ }
1128
+ const out = safeJson(mine[0].stdout) as Record<string, unknown> | undefined;
1129
+ printed.set(st.id, out);
1130
+ if (!isDeepStrictEqual(doc, out)) problems.push(`concurrent: ${st.id} (${st.action}): the writer must return the document chant printed, unchanged, refusal included, and it returned something else`);
1131
+ const { validate } = writeContractSchema(st.action);
1132
+ if (!validate(out)) problems.push(`concurrent: ${st.id} (${st.action}): the document does not validate against ${WRITE_CONTRACT_SCHEMAS[st.action]}: ${JSON.stringify(validate.errors)}`);
1133
+ }
1134
+ if (problems.length > 0) return problems;
1135
+ const won = steps.filter((st) => printed.get(st.id)?.error === undefined);
1136
+ const lost = steps.filter((st) => printed.get(st.id)?.error !== undefined);
1137
+ if (won.length !== 1) {
1138
+ problems.push(`concurrent: ${won.length} of the three amendments from digest ${start.digest} were written (${won.map((s) => s.id).join(", ") || "none"}); exactly one may be`);
1139
+ return problems;
1140
+ }
1141
+ const d1 = String(printed.get(won[0].id)?.digest ?? "");
1142
+ for (const st of lost) {
1143
+ const doc = printed.get(st.id)!;
1144
+ const code = (doc.error as { code?: string }).code;
1145
+ const conflict = doc.conflict as { digest?: string; expected?: string } | undefined;
1146
+ if (code !== "record-conflict") problems.push(`concurrent: ${st.id} was refused with ${code}, not record-conflict`);
1147
+ else if (conflict?.digest !== d1 || conflict?.expected !== start.digest) problems.push(`concurrent: ${st.id}'s conflict names ${brief(conflict)}, not expected ${start.digest} and digest ${d1}`);
1148
+ }
1149
+ files.after = worktreeDigest(dir);
1150
+ refs.after = gitRefs(dir);
1151
+ const changed = treeChanges(files.before, files.after).filter((c) => c.replace(/ \((added|changed|removed)\)$/, "") !== start.path);
1152
+ if (changed.length > 0) problems.push(`concurrent: files changed that chant did not report writing: ${changed.join(", ")}`);
1153
+ if (Object.keys(refChanges(refs.before, refs.after)).length > 0) problems.push(`concurrent: git refs changed: ${Object.keys(refChanges(refs.before, refs.after)).join(", ")}`);
1154
+ if (problems.length > 0) return problems;
1155
+ // Each loser retries from the digest it was told, one after the other.
1156
+ let digest = d1;
1157
+ for (const st of lost) {
1158
+ const retry = concurrentAmendStep(CONCURRENT_AMENDS.find((c) => c.id === st.id)!, id, digest);
1159
+ calls.length = 0;
1160
+ let doc: unknown;
1161
+ try {
1162
+ doc = await built.write(retry);
1163
+ } catch (e) {
1164
+ problems.push(`concurrent: ${st.id}'s retry: the writer threw: ${(e as Error).message}`);
1165
+ continue;
1166
+ }
1167
+ const p = writerCallProblems({ ...retry, id: `${st.id} retry` } as WriteStep, calls);
1168
+ if (p.length > 0) {
1169
+ problems.push(...p.map((x) => `concurrent: ${x}`));
1170
+ continue;
1171
+ }
1172
+ const out = safeJson(calls[0].stdout) as Record<string, unknown> | undefined;
1173
+ if (out?.error !== undefined) {
1174
+ problems.push(`concurrent: ${st.id}'s retry from ${digest} was refused: ${brief(out.error)}`);
1175
+ continue;
1176
+ }
1177
+ if (!isDeepStrictEqual(doc, out)) problems.push(`concurrent: ${st.id}'s retry: the writer must return the document chant printed, unchanged`);
1178
+ digest = String(out?.digest ?? "");
1179
+ }
1180
+ const end = await digestOf();
1181
+ if (problems.length === 0 && end?.digest !== digest) problems.push(`concurrent: after the retries records --json gives ${id} digest ${end?.digest}, and the last retry printed ${digest}`);
1182
+ return problems;
1183
+ }
1184
+
1185
+ function safeJson(text: string): unknown {
1186
+ try {
1187
+ return JSON.parse(text);
1188
+ } catch {
1189
+ return undefined;
1190
+ }
1191
+ }