@intentius/chant 0.101.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 (739) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +2 -0
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts.map +1 -1
  6. package/dist/audit/nginx.d.ts.map +1 -1
  7. package/dist/audit/prior-art.d.ts +48 -0
  8. package/dist/audit/prior-art.d.ts.map +1 -1
  9. package/dist/behaviour-kinds.d.ts.map +1 -1
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/change-set.d.ts +232 -0
  12. package/dist/change-set.d.ts.map +1 -0
  13. package/dist/cli/commands/audit.d.ts +7 -0
  14. package/dist/cli/commands/audit.d.ts.map +1 -1
  15. package/dist/cli/commands/build.d.ts.map +1 -1
  16. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  17. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  18. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  19. package/dist/cli/commands/import.d.ts +8 -0
  20. package/dist/cli/commands/import.d.ts.map +1 -1
  21. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  22. package/dist/cli/commands/init.d.ts +19 -0
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/onboard.d.ts +9 -0
  25. package/dist/cli/commands/onboard.d.ts.map +1 -1
  26. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  27. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  28. package/dist/cli/handlers/build.d.ts.map +1 -1
  29. package/dist/cli/handlers/change-set.d.ts +11 -0
  30. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts +33 -0
  33. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +16 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/pr.d.ts +31 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -0
  40. package/dist/cli/handlers/run.d.ts.map +1 -1
  41. package/dist/cli/lsp/server.d.ts.map +1 -1
  42. package/dist/cli/main.d.ts.map +1 -1
  43. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  44. package/dist/cli/mcp/server.d.ts.map +1 -1
  45. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  46. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  47. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  48. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  49. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  50. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  51. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  52. package/dist/cli/mcp/workspace-tools.d.ts +4 -3
  53. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  54. package/dist/cli/registry.d.ts +74 -3
  55. package/dist/cli/registry.d.ts.map +1 -1
  56. package/dist/codegen/fetch.d.ts.map +1 -1
  57. package/dist/codegen/naming.d.ts.map +1 -1
  58. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  59. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  60. package/dist/codegen/registry.d.ts.map +1 -1
  61. package/dist/components/auto-release.d.ts +16 -0
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  64. package/dist/components/capability.d.ts +53 -0
  65. package/dist/components/capability.d.ts.map +1 -1
  66. package/dist/components/changed-files.d.ts +31 -0
  67. package/dist/components/changed-files.d.ts.map +1 -0
  68. package/dist/components/cli-support.d.ts +9 -1
  69. package/dist/components/cli-support.d.ts.map +1 -1
  70. package/dist/components/component.d.ts +24 -0
  71. package/dist/components/component.d.ts.map +1 -1
  72. package/dist/components/deploy-units.d.ts.map +1 -1
  73. package/dist/components/driver.d.ts +13 -0
  74. package/dist/components/driver.d.ts.map +1 -1
  75. package/dist/components/fan-out-output.d.ts +6 -0
  76. package/dist/components/fan-out-output.d.ts.map +1 -1
  77. package/dist/components/fan-out-record.d.ts +41 -0
  78. package/dist/components/fan-out-record.d.ts.map +1 -0
  79. package/dist/components/fan-out-run.d.ts +65 -0
  80. package/dist/components/fan-out-run.d.ts.map +1 -1
  81. package/dist/components/fan-out-support.d.ts +2 -0
  82. package/dist/components/fan-out-support.d.ts.map +1 -1
  83. package/dist/components/fan-out.d.ts +6 -0
  84. package/dist/components/fan-out.d.ts.map +1 -1
  85. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  86. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  87. package/dist/components/index.d.ts +1 -1
  88. package/dist/components/index.d.ts.map +1 -1
  89. package/dist/components/layers.d.ts +25 -0
  90. package/dist/components/layers.d.ts.map +1 -0
  91. package/dist/components/pr-pipeline.d.ts +61 -0
  92. package/dist/components/pr-pipeline.d.ts.map +1 -0
  93. package/dist/components/pr-run.d.ts +119 -0
  94. package/dist/components/pr-run.d.ts.map +1 -0
  95. package/dist/components/promote.d.ts.map +1 -1
  96. package/dist/components/starter-plugin.d.ts.map +1 -1
  97. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  98. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  99. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  100. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  101. package/dist/components/verbs/shell.d.ts.map +1 -1
  102. package/dist/components/verbs/sign.d.ts.map +1 -1
  103. package/dist/components/verbs/verify.d.ts.map +1 -1
  104. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  105. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  106. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  107. package/dist/components/wave-plan.d.ts +49 -0
  108. package/dist/components/wave-plan.d.ts.map +1 -0
  109. package/dist/composite.d.ts.map +1 -1
  110. package/dist/config.d.ts +19 -7
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/declarable.d.ts +16 -0
  113. package/dist/declarable.d.ts.map +1 -1
  114. package/dist/discovery/cache.d.ts.map +1 -1
  115. package/dist/discovery/fold-import.d.ts +15 -5
  116. package/dist/discovery/fold-import.d.ts.map +1 -1
  117. package/dist/discovery/param-deps.d.ts +16 -0
  118. package/dist/discovery/param-deps.d.ts.map +1 -1
  119. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  120. package/dist/discovery/specifier-resolve.d.ts +37 -0
  121. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  122. package/dist/errors.d.ts.map +1 -1
  123. package/dist/fold/fold.d.ts +3 -1
  124. package/dist/fold/fold.d.ts.map +1 -1
  125. package/dist/gated-waves.d.ts +147 -0
  126. package/dist/gated-waves.d.ts.map +1 -0
  127. package/dist/graph-detail.d.ts.map +1 -1
  128. package/dist/graph-ir.d.ts.map +1 -1
  129. package/dist/graph-layout.d.ts.map +1 -1
  130. package/dist/import/base-parser.d.ts.map +1 -1
  131. package/dist/import/embedded.d.ts +7 -0
  132. package/dist/import/embedded.d.ts.map +1 -1
  133. package/dist/import/parser-options.d.ts +14 -0
  134. package/dist/import/parser-options.d.ts.map +1 -0
  135. package/dist/import/parser.d.ts +17 -0
  136. package/dist/import/parser.d.ts.map +1 -1
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/kubectl-context.d.ts.map +1 -1
  140. package/dist/lexicon-output.d.ts.map +1 -1
  141. package/dist/lexicon-schema.d.ts +3 -3
  142. package/dist/lexicon.d.ts +103 -3
  143. package/dist/lexicon.d.ts.map +1 -1
  144. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  145. package/dist/lifecycle/gate-ledger.d.ts +22 -0
  146. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/git.d.ts +46 -1
  148. package/dist/lifecycle/git.d.ts.map +1 -1
  149. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  150. package/dist/lifecycle/observe.d.ts.map +1 -1
  151. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/scenario.d.ts.map +1 -1
  154. package/dist/lifecycle/status.d.ts.map +1 -1
  155. package/dist/lifecycle/work-lease.d.ts +61 -2
  156. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  157. package/dist/lint/config.d.ts +8 -8
  158. package/dist/lint/engine.d.ts +6 -0
  159. package/dist/lint/engine.d.ts.map +1 -1
  160. package/dist/lint/post-synth.d.ts +18 -0
  161. package/dist/lint/post-synth.d.ts.map +1 -1
  162. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  163. package/dist/op/activities/activity-contracts.d.ts +137 -11
  164. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  165. package/dist/op/activities/apply.d.ts +79 -9
  166. package/dist/op/activities/apply.d.ts.map +1 -1
  167. package/dist/op/activities/change-set.d.ts +62 -0
  168. package/dist/op/activities/change-set.d.ts.map +1 -0
  169. package/dist/op/activities/decide.d.ts +2 -0
  170. package/dist/op/activities/decide.d.ts.map +1 -1
  171. package/dist/op/activities/factory.d.ts +137 -0
  172. package/dist/op/activities/factory.d.ts.map +1 -0
  173. package/dist/op/activities/index.d.ts +5 -1
  174. package/dist/op/activities/index.d.ts.map +1 -1
  175. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  176. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  177. package/dist/op/activity-contract.d.ts +1 -1
  178. package/dist/op/activity-contract.d.ts.map +1 -1
  179. package/dist/op/activity-failure.d.ts.map +1 -1
  180. package/dist/op/activity-profiles.d.ts.map +1 -1
  181. package/dist/op/builders.d.ts +11 -0
  182. package/dist/op/builders.d.ts.map +1 -1
  183. package/dist/op/composites/apply-op.d.ts +12 -5
  184. package/dist/op/composites/apply-op.d.ts.map +1 -1
  185. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  186. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  187. package/dist/op/decide-backend.d.ts +5 -1
  188. package/dist/op/decide-backend.d.ts.map +1 -1
  189. package/dist/op/decide-read-inputs.d.ts +4 -2
  190. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  191. package/dist/op/emulator-lifecycle.d.ts +21 -2
  192. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  193. package/dist/op/factory.d.ts +83 -0
  194. package/dist/op/factory.d.ts.map +1 -0
  195. package/dist/op/gate-approval.d.ts +5 -1
  196. package/dist/op/gate-approval.d.ts.map +1 -1
  197. package/dist/op/gate-wait.d.ts.map +1 -1
  198. package/dist/op/gate.d.ts +19 -3
  199. package/dist/op/gate.d.ts.map +1 -1
  200. package/dist/op/index.d.ts +6 -1
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/local-executor.d.ts +6 -0
  203. package/dist/op/local-executor.d.ts.map +1 -1
  204. package/dist/op/op-ir.d.ts +2 -0
  205. package/dist/op/op-ir.d.ts.map +1 -1
  206. package/dist/op/op-verb-class.d.ts.map +1 -1
  207. package/dist/op/operator.d.ts.map +1 -1
  208. package/dist/op/run-context.d.ts +39 -0
  209. package/dist/op/run-context.d.ts.map +1 -0
  210. package/dist/op/run-live.d.ts.map +1 -1
  211. package/dist/op/runtimes/local.d.ts.map +1 -1
  212. package/dist/op/source-archive.d.ts.map +1 -1
  213. package/dist/op/steward-beside.d.ts.map +1 -1
  214. package/dist/op/steward-points.d.ts.map +1 -1
  215. package/dist/op/steward.d.ts.map +1 -1
  216. package/dist/op/types.d.ts +16 -3
  217. package/dist/op/types.d.ts.map +1 -1
  218. package/dist/op/work-lease-run.d.ts +24 -4
  219. package/dist/op/work-lease-run.d.ts.map +1 -1
  220. package/dist/plan-summary.d.ts +207 -0
  221. package/dist/plan-summary.d.ts.map +1 -0
  222. package/dist/pr-forge.d.ts +105 -0
  223. package/dist/pr-forge.d.ts.map +1 -0
  224. package/dist/pr-loop.d.ts +178 -0
  225. package/dist/pr-loop.d.ts.map +1 -0
  226. package/dist/project-root.d.ts.map +1 -1
  227. package/dist/provenance.d.ts +21 -0
  228. package/dist/provenance.d.ts.map +1 -1
  229. package/dist/pseudo-parameter.d.ts.map +1 -1
  230. package/dist/reconcile.d.ts.map +1 -1
  231. package/dist/secret-materialization.d.ts.map +1 -1
  232. package/dist/telemetry-attribution.d.ts +31 -0
  233. package/dist/telemetry-attribution.d.ts.map +1 -1
  234. package/dist/terraform/emit-source.d.ts.map +1 -1
  235. package/dist/terraform/hcl2json.d.ts +45 -0
  236. package/dist/terraform/hcl2json.d.ts.map +1 -0
  237. package/dist/terraform/parse.d.ts +4 -40
  238. package/dist/terraform/parse.d.ts.map +1 -1
  239. package/dist/testing.d.ts.map +1 -1
  240. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  241. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  242. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  243. package/dist/workspace/agent-cli.d.ts +5 -0
  244. package/dist/workspace/agent-cli.d.ts.map +1 -1
  245. package/dist/workspace/box-cli.d.ts +12 -0
  246. package/dist/workspace/box-cli.d.ts.map +1 -0
  247. package/dist/workspace/box-factory.d.ts +97 -0
  248. package/dist/workspace/box-factory.d.ts.map +1 -0
  249. package/dist/workspace/box-listing.d.ts +85 -0
  250. package/dist/workspace/box-listing.d.ts.map +1 -0
  251. package/dist/workspace/box-publish.d.ts +147 -0
  252. package/dist/workspace/box-publish.d.ts.map +1 -0
  253. package/dist/workspace/broker-protocol.d.ts +190 -0
  254. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  255. package/dist/workspace/changes.d.ts.map +1 -1
  256. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  257. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  258. package/dist/workspace/checks.d.ts +5 -0
  259. package/dist/workspace/checks.d.ts.map +1 -1
  260. package/dist/workspace/compose-graph.d.ts +10 -0
  261. package/dist/workspace/compose-graph.d.ts.map +1 -1
  262. package/dist/workspace/composites.d.ts +2 -2
  263. package/dist/workspace/composites.d.ts.map +1 -1
  264. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  265. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  266. package/dist/workspace/conformance/broker.d.ts +202 -0
  267. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  268. package/dist/workspace/conformance/index.d.ts +68 -6
  269. package/dist/workspace/conformance/index.d.ts.map +1 -1
  270. package/dist/workspace/conformance/vitest.d.ts +19 -3
  271. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  272. package/dist/workspace/conformance/writer.d.ts +462 -0
  273. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  274. package/dist/workspace/decide.d.ts +126 -12
  275. package/dist/workspace/decide.d.ts.map +1 -1
  276. package/dist/workspace/decision-points.schema.json +11 -4
  277. package/dist/workspace/declaration.d.ts +156 -4
  278. package/dist/workspace/declaration.d.ts.map +1 -1
  279. package/dist/workspace/declaration.schema.json +383 -5
  280. package/dist/workspace/environments.d.ts +36 -10
  281. package/dist/workspace/environments.d.ts.map +1 -1
  282. package/dist/workspace/factory-rules.d.ts +180 -0
  283. package/dist/workspace/factory-rules.d.ts.map +1 -0
  284. package/dist/workspace/generated-files.d.ts.map +1 -1
  285. package/dist/workspace/graph-cache.d.ts.map +1 -1
  286. package/dist/workspace/graph-cli.d.ts +2 -0
  287. package/dist/workspace/graph-cli.d.ts.map +1 -1
  288. package/dist/workspace/identity.d.ts +111 -0
  289. package/dist/workspace/identity.d.ts.map +1 -0
  290. package/dist/workspace/init.d.ts +21 -0
  291. package/dist/workspace/init.d.ts.map +1 -1
  292. package/dist/workspace/intent-cli.d.ts +7 -4
  293. package/dist/workspace/intent-cli.d.ts.map +1 -1
  294. package/dist/workspace/intent-record.d.ts +36 -2
  295. package/dist/workspace/intent-record.d.ts.map +1 -1
  296. package/dist/workspace/intent.d.ts +249 -8
  297. package/dist/workspace/intent.d.ts.map +1 -1
  298. package/dist/workspace/json-edit.d.ts +24 -0
  299. package/dist/workspace/json-edit.d.ts.map +1 -0
  300. package/dist/workspace/jsonc.d.ts +11 -0
  301. package/dist/workspace/jsonc.d.ts.map +1 -1
  302. package/dist/workspace/kinds.d.ts +66 -4
  303. package/dist/workspace/kinds.d.ts.map +1 -1
  304. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  305. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  306. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  307. package/dist/workspace/lineage-lock.d.ts +2 -2
  308. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  309. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  310. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  311. package/dist/workspace/links.d.ts.map +1 -1
  312. package/dist/workspace/point-answer.schema.json +42 -2
  313. package/dist/workspace/points-cli.d.ts +10 -3
  314. package/dist/workspace/points-cli.d.ts.map +1 -1
  315. package/dist/workspace/points.d.ts +66 -5
  316. package/dist/workspace/points.d.ts.map +1 -1
  317. package/dist/workspace/principal-classes.d.ts +114 -0
  318. package/dist/workspace/principal-classes.d.ts.map +1 -0
  319. package/dist/workspace/reason-codes.d.ts +53 -4
  320. package/dist/workspace/reason-codes.d.ts.map +1 -1
  321. package/dist/workspace/record-assets.d.ts.map +1 -1
  322. package/dist/workspace/record-sessions.d.ts +5 -7
  323. package/dist/workspace/record-sessions.d.ts.map +1 -1
  324. package/dist/workspace/records-checkout.d.ts +54 -0
  325. package/dist/workspace/records-checkout.d.ts.map +1 -0
  326. package/dist/workspace/records-cli.d.ts +34 -1
  327. package/dist/workspace/records-cli.d.ts.map +1 -1
  328. package/dist/workspace/records-close.d.ts +7 -3
  329. package/dist/workspace/records-close.d.ts.map +1 -1
  330. package/dist/workspace/records-since.d.ts.map +1 -1
  331. package/dist/workspace/records-write.d.ts +68 -7
  332. package/dist/workspace/records-write.d.ts.map +1 -1
  333. package/dist/workspace/records.d.ts +43 -9
  334. package/dist/workspace/records.d.ts.map +1 -1
  335. package/dist/workspace/runs-cli.d.ts +296 -0
  336. package/dist/workspace/runs-cli.d.ts.map +1 -0
  337. package/dist/workspace/runs.d.ts +544 -0
  338. package/dist/workspace/runs.d.ts.map +1 -0
  339. package/dist/workspace/session-kinds.d.ts +1 -1
  340. package/dist/workspace/session-kinds.d.ts.map +1 -1
  341. package/dist/workspace/source-block.d.ts +1 -1
  342. package/dist/workspace/source-block.d.ts.map +1 -1
  343. package/dist/workspace/squash.d.ts +80 -0
  344. package/dist/workspace/squash.d.ts.map +1 -0
  345. package/dist/workspace/status-gates.d.ts +17 -3
  346. package/dist/workspace/status-gates.d.ts.map +1 -1
  347. package/dist/workspace/status.d.ts +64 -0
  348. package/dist/workspace/status.d.ts.map +1 -1
  349. package/dist/workspace/symbols.d.ts +63 -0
  350. package/dist/workspace/symbols.d.ts.map +1 -0
  351. package/dist/workspace/trailer-joins.d.ts +46 -0
  352. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  353. package/dist/workspace/trailers.d.ts +89 -0
  354. package/dist/workspace/trailers.d.ts.map +1 -0
  355. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  356. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  357. package/dist/workspace/trust/run-commits.d.ts +46 -0
  358. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  359. package/dist/workspace/trust/run-statement.d.ts +128 -0
  360. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  361. package/dist/workspace/trust/seal.d.ts +38 -0
  362. package/dist/workspace/trust/seal.d.ts.map +1 -1
  363. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  364. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  365. package/dist/workspace/trust/verify.d.ts +16 -2
  366. package/dist/workspace/trust/verify.d.ts.map +1 -1
  367. package/dist/workspace/wip-cli.d.ts +21 -0
  368. package/dist/workspace/wip-cli.d.ts.map +1 -0
  369. package/dist/workspace/wip.d.ts +268 -0
  370. package/dist/workspace/wip.d.ts.map +1 -0
  371. package/dist/workspace/work-cli.d.ts +39 -3
  372. package/dist/workspace/work-cli.d.ts.map +1 -1
  373. package/dist/workspace/work-evidence.d.ts +1 -1
  374. package/dist/workspace/work-evidence.d.ts.map +1 -1
  375. package/dist/workspace/work-size.d.ts +55 -0
  376. package/dist/workspace/work-size.d.ts.map +1 -0
  377. package/dist/workspace/work.d.ts +26 -1
  378. package/dist/workspace/work.d.ts.map +1 -1
  379. package/dist/workspace/workspace-kinds.schema.json +122 -0
  380. package/dist/workspace/workspace-principals.schema.json +60 -0
  381. package/dist/workspace/write-lock-cli.d.ts +60 -0
  382. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  383. package/dist/workspace/write-lock.d.ts +152 -0
  384. package/dist/workspace/write-lock.d.ts.map +1 -0
  385. package/dist/workspace/write-scope.d.ts +83 -19
  386. package/dist/workspace/write-scope.d.ts.map +1 -1
  387. package/dist/yaml.d.ts +21 -39
  388. package/dist/yaml.d.ts.map +1 -1
  389. package/package.json +7 -2
  390. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  391. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  392. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  393. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  394. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  395. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  396. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  397. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  398. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  399. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  400. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  401. package/src/audit/core.test.ts +15 -0
  402. package/src/audit/core.ts +8 -2
  403. package/src/audit/discover.test.ts +11 -0
  404. package/src/audit/discover.ts +7 -2
  405. package/src/audit/prior-art.ts +15 -0
  406. package/src/change-set-bundle.test.ts +57 -0
  407. package/src/change-set.test.ts +215 -0
  408. package/src/change-set.ts +445 -0
  409. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  410. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  411. package/src/cli/commands/audit.ts +29 -2
  412. package/src/cli/commands/build.test.ts +19 -0
  413. package/src/cli/commands/build.ts +3 -2
  414. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  415. package/src/cli/commands/carve-emit.ts +5 -19
  416. package/src/cli/commands/check-lexicon.test.ts +62 -1
  417. package/src/cli/commands/check-lexicon.ts +36 -6
  418. package/src/cli/commands/import-live.test.ts +18 -0
  419. package/src/cli/commands/import-parser-options.test.ts +133 -0
  420. package/src/cli/commands/import.ts +21 -3
  421. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  422. package/src/cli/commands/init.test.ts +8 -1
  423. package/src/cli/commands/init.ts +23 -8
  424. package/src/cli/commands/lint.test.ts +37 -0
  425. package/src/cli/commands/onboard.test.ts +53 -3
  426. package/src/cli/commands/onboard.ts +52 -4
  427. package/src/cli/commands/pinned-upgrade.ts +73 -33
  428. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  429. package/src/cli/handlers/build.ts +13 -1
  430. package/src/cli/handlers/change-set.test.ts +56 -0
  431. package/src/cli/handlers/change-set.ts +44 -0
  432. package/src/cli/handlers/components.ts +2 -2
  433. package/src/cli/handlers/dev.ts +8 -6
  434. package/src/cli/handlers/fan-out.test.ts +221 -2
  435. package/src/cli/handlers/fan-out.ts +165 -75
  436. package/src/cli/handlers/graph.ts +3 -0
  437. package/src/cli/handlers/lifecycle.test.ts +67 -0
  438. package/src/cli/handlers/lifecycle.ts +6 -5
  439. package/src/cli/handlers/misc.ts +2 -0
  440. package/src/cli/handlers/operator.test.ts +21 -0
  441. package/src/cli/handlers/operator.ts +121 -6
  442. package/src/cli/handlers/pr.ts +373 -0
  443. package/src/cli/handlers/run.test.ts +111 -0
  444. package/src/cli/handlers/run.ts +86 -24
  445. package/src/cli/handlers/scenario.ts +2 -2
  446. package/src/cli/main.test.ts +21 -0
  447. package/src/cli/main.ts +246 -30
  448. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  449. package/src/cli/mcp/op-tools.ts +24 -2
  450. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  451. package/src/cli/mcp/workspace-tools.ts +62 -7
  452. package/src/cli/registry.ts +74 -3
  453. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  454. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  455. package/src/codegen/pinned-upgrade.test.ts +104 -0
  456. package/src/codegen/pinned-upgrade.ts +250 -56
  457. package/src/codegen/testdata/postgres-tags.json +242 -0
  458. package/src/components/auto-release.ts +1 -1
  459. package/src/components/capability.ts +55 -0
  460. package/src/components/changed-files.ts +66 -0
  461. package/src/components/cli-support.ts +28 -5
  462. package/src/components/component.schema.json +17 -0
  463. package/src/components/component.ts +25 -0
  464. package/src/components/deploy-units.ts +3 -0
  465. package/src/components/driver.test.ts +52 -0
  466. package/src/components/driver.ts +31 -13
  467. package/src/components/fan-out-output.ts +42 -1
  468. package/src/components/fan-out-record.ts +69 -0
  469. package/src/components/fan-out-run.test.ts +17 -0
  470. package/src/components/fan-out-run.ts +304 -1
  471. package/src/components/fan-out-support.ts +3 -0
  472. package/src/components/fan-out-waves.test.ts +375 -0
  473. package/src/components/fan-out.ts +18 -17
  474. package/src/components/gated-wave-pipeline.ts +82 -0
  475. package/src/components/index.ts +1 -0
  476. package/src/components/layers.ts +30 -0
  477. package/src/components/pr-pipeline.ts +91 -0
  478. package/src/components/pr-run.test.ts +205 -0
  479. package/src/components/pr-run.ts +280 -0
  480. package/src/components/promote.ts +5 -0
  481. package/src/components/verbs/shell.test.ts +11 -0
  482. package/src/components/verbs/shell.ts +7 -3
  483. package/src/components/wave-plan.ts +124 -0
  484. package/src/config.test.ts +21 -0
  485. package/src/config.ts +15 -0
  486. package/src/declarable.test.ts +21 -0
  487. package/src/declarable.ts +19 -0
  488. package/src/discovery/fold-composite.test.ts +41 -5
  489. package/src/discovery/fold-import.test.ts +129 -0
  490. package/src/discovery/fold-import.ts +643 -131
  491. package/src/discovery/fold-package-composite.test.ts +264 -0
  492. package/src/discovery/fold-specifiers.test.ts +191 -0
  493. package/src/discovery/param-deps.test.ts +66 -1
  494. package/src/discovery/param-deps.ts +45 -0
  495. package/src/discovery/sandbox/bundle.ts +1 -1
  496. package/src/discovery/specifier-resolve.ts +344 -0
  497. package/src/fold/fold.test.ts +39 -1
  498. package/src/fold/fold.ts +83 -9
  499. package/src/fold-provenance.test.ts +140 -0
  500. package/src/gated-waves-bundle.test.ts +62 -0
  501. package/src/gated-waves.test.ts +118 -0
  502. package/src/gated-waves.ts +240 -0
  503. package/src/graph-ir.test.ts +83 -0
  504. package/src/graph-ir.ts +29 -0
  505. package/src/import/embedded.test.ts +20 -0
  506. package/src/import/embedded.ts +13 -0
  507. package/src/import/parser-options.ts +46 -0
  508. package/src/import/parser.ts +20 -0
  509. package/src/index.ts +1 -0
  510. package/src/lexicon.ts +106 -3
  511. package/src/lifecycle/gate-ledger.ts +23 -0
  512. package/src/lifecycle/git.ts +102 -16
  513. package/src/lifecycle/observe.test.ts +18 -0
  514. package/src/lifecycle/observe.ts +6 -4
  515. package/src/lifecycle/snapshot.ts +2 -2
  516. package/src/lifecycle/work-lease.test.ts +1 -1
  517. package/src/lifecycle/work-lease.ts +93 -2
  518. package/src/lint/engine.test.ts +55 -0
  519. package/src/lint/engine.ts +30 -4
  520. package/src/lint/output-docs.test.ts +59 -56
  521. package/src/lint/post-synth.test.ts +19 -0
  522. package/src/lint/post-synth.ts +19 -1
  523. package/src/meta/declared-imports.test.ts +1 -1
  524. package/src/op/activities/activity-contracts.ts +84 -0
  525. package/src/op/activities/apply.test.ts +189 -5
  526. package/src/op/activities/apply.ts +230 -8
  527. package/src/op/activities/change-set.ts +114 -0
  528. package/src/op/activities/decide.ts +3 -0
  529. package/src/op/activities/factory.ts +675 -0
  530. package/src/op/activities/index.ts +9 -0
  531. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  532. package/src/op/activities/lexicon-upgrade.ts +154 -51
  533. package/src/op/activity-contract.test.ts +21 -1
  534. package/src/op/activity-contract.ts +19 -9
  535. package/src/op/builders.ts +23 -0
  536. package/src/op/composites/apply-op.ts +12 -5
  537. package/src/op/composites/composites.test.ts +30 -3
  538. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  539. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  540. package/src/op/decide-backend.ts +5 -1
  541. package/src/op/decide-read-inputs.ts +33 -5
  542. package/src/op/emulator-lifecycle.test.ts +23 -0
  543. package/src/op/emulator-lifecycle.ts +52 -14
  544. package/src/op/factory.e2e.test.ts +232 -0
  545. package/src/op/factory.ts +118 -0
  546. package/src/op/gate-approval.test.ts +74 -0
  547. package/src/op/gate-approval.ts +5 -1
  548. package/src/op/gate.ts +31 -5
  549. package/src/op/index.ts +11 -1
  550. package/src/op/local-executor.test.ts +15 -0
  551. package/src/op/local-executor.ts +40 -12
  552. package/src/op/local-output.ts +1 -1
  553. package/src/op/op-ir.test.ts +9 -0
  554. package/src/op/op-ir.ts +4 -0
  555. package/src/op/op-verb-class.ts +3 -0
  556. package/src/op/operator.ts +4 -0
  557. package/src/op/run-context.test.ts +141 -0
  558. package/src/op/run-context.ts +51 -0
  559. package/src/op/runtimes/local.test.ts +14 -0
  560. package/src/op/runtimes/local.ts +2 -0
  561. package/src/op/steward-beside.ts +2 -0
  562. package/src/op/types.ts +16 -3
  563. package/src/op/work-lease-run.test.ts +47 -0
  564. package/src/op/work-lease-run.ts +76 -11
  565. package/src/plan-summary.test.ts +263 -0
  566. package/src/plan-summary.ts +845 -0
  567. package/src/pr-forge.test.ts +237 -0
  568. package/src/pr-forge.ts +332 -0
  569. package/src/pr-loop-bundle.test.ts +46 -0
  570. package/src/pr-loop.test.ts +171 -0
  571. package/src/pr-loop.ts +324 -0
  572. package/src/provenance.ts +35 -0
  573. package/src/telemetry-attribution.test.ts +23 -1
  574. package/src/telemetry-attribution.ts +51 -0
  575. package/src/terraform/hcl2json.test.ts +21 -0
  576. package/src/terraform/hcl2json.ts +81 -0
  577. package/src/terraform/parse.ts +11 -74
  578. package/src/workspace/agent-cli.ts +7 -3
  579. package/src/workspace/agent.schema.json +22 -0
  580. package/src/workspace/app-kind.test.ts +196 -0
  581. package/src/workspace/box-cli.ts +113 -0
  582. package/src/workspace/box-factory.test.ts +323 -0
  583. package/src/workspace/box-factory.ts +136 -0
  584. package/src/workspace/box-listing-write.schema.json +204 -0
  585. package/src/workspace/box-listing.test.ts +234 -0
  586. package/src/workspace/box-listing.ts +324 -0
  587. package/src/workspace/box-publish.schema.json +498 -0
  588. package/src/workspace/box-publish.test.ts +204 -0
  589. package/src/workspace/box-publish.ts +420 -0
  590. package/src/workspace/box-ship.test.ts +96 -0
  591. package/src/workspace/broker-protocol.schema.json +186 -0
  592. package/src/workspace/broker-protocol.test.ts +122 -0
  593. package/src/workspace/broker-protocol.ts +317 -0
  594. package/src/workspace/builder-tiers.test.ts +146 -0
  595. package/src/workspace/change-set.schema.json +460 -0
  596. package/src/workspace/changes.schema.json +12 -20
  597. package/src/workspace/changes.ts +4 -1
  598. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  599. package/src/workspace/checks/boxes.test.ts +5 -0
  600. package/src/workspace/checks.ts +39 -8
  601. package/src/workspace/component-environments.test.ts +105 -0
  602. package/src/workspace/compose-graph.ts +7 -0
  603. package/src/workspace/composites.schema.json +23 -4
  604. package/src/workspace/composites.test.ts +8 -8
  605. package/src/workspace/composites.ts +13 -2
  606. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  607. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  608. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  609. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  610. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  611. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  612. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  613. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  614. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  615. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  616. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  617. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  618. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  619. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  620. package/src/workspace/conformance/broker.test.ts +122 -0
  621. package/src/workspace/conformance/broker.ts +792 -0
  622. package/src/workspace/conformance/conformance.e2e.test.ts +36 -3
  623. package/src/workspace/conformance/index.mjs +51 -3
  624. package/src/workspace/conformance/index.ts +107 -15
  625. package/src/workspace/conformance/vitest.ts +76 -2
  626. package/src/workspace/conformance/writer-conformance.e2e.test.ts +177 -0
  627. package/src/workspace/conformance/writer.test.ts +186 -0
  628. package/src/workspace/conformance/writer.ts +1191 -0
  629. package/src/workspace/decide.test.ts +208 -4
  630. package/src/workspace/decide.ts +433 -73
  631. package/src/workspace/decision-points.schema.json +11 -4
  632. package/src/workspace/declaration.schema.json +383 -5
  633. package/src/workspace/declaration.ts +315 -10
  634. package/src/workspace/environments.ts +66 -14
  635. package/src/workspace/factory-rules.test.ts +62 -0
  636. package/src/workspace/factory-rules.ts +218 -0
  637. package/src/workspace/graph-cli.ts +6 -3
  638. package/src/workspace/graph.schema.json +314 -0
  639. package/src/workspace/identity.test.ts +219 -0
  640. package/src/workspace/identity.ts +186 -0
  641. package/src/workspace/init.ts +82 -2
  642. package/src/workspace/intent-cli.ts +84 -13
  643. package/src/workspace/intent-patch-id.test.ts +162 -0
  644. package/src/workspace/intent-record.schema.json +336 -1
  645. package/src/workspace/intent-record.ts +96 -16
  646. package/src/workspace/intent-reviews.test.ts +76 -0
  647. package/src/workspace/intent-squash.test.ts +213 -0
  648. package/src/workspace/intent-why.test.ts +274 -0
  649. package/src/workspace/intent.schema.json +950 -9
  650. package/src/workspace/intent.test.ts +11 -2
  651. package/src/workspace/intent.ts +722 -65
  652. package/src/workspace/json-edit.test.ts +121 -0
  653. package/src/workspace/json-edit.ts +142 -0
  654. package/src/workspace/jsonc.ts +20 -0
  655. package/src/workspace/kinds.ts +201 -39
  656. package/src/workspace/lexicon-symbols.test.ts +148 -0
  657. package/src/workspace/lexicon-symbols.ts +125 -0
  658. package/src/workspace/lineage-upgrade.test.ts +63 -0
  659. package/src/workspace/lineage-upgrade.ts +146 -5
  660. package/src/workspace/plan-summary.schema.json +155 -0
  661. package/src/workspace/point-answer.schema.json +42 -2
  662. package/src/workspace/points-cli.ts +42 -16
  663. package/src/workspace/points-write.schema.json +163 -4
  664. package/src/workspace/points.schema.json +157 -2
  665. package/src/workspace/points.test.ts +13 -1
  666. package/src/workspace/points.ts +118 -12
  667. package/src/workspace/pr-report.schema.json +95 -0
  668. package/src/workspace/principal-classes.test.ts +175 -0
  669. package/src/workspace/principal-classes.ts +307 -0
  670. package/src/workspace/profiles.test.ts +159 -0
  671. package/src/workspace/read-contract.test.ts +2 -2
  672. package/src/workspace/reason-codes.test.ts +23 -3
  673. package/src/workspace/reason-codes.ts +61 -4
  674. package/src/workspace/record-sessions.ts +9 -8
  675. package/src/workspace/records-amend.schema.json +80 -0
  676. package/src/workspace/records-checkout.ts +106 -0
  677. package/src/workspace/records-cli.ts +123 -17
  678. package/src/workspace/records-close.schema.json +79 -0
  679. package/src/workspace/records-close.ts +31 -7
  680. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  681. package/src/workspace/records-concurrent.test.ts +145 -0
  682. package/src/workspace/records-formats.test.ts +46 -10
  683. package/src/workspace/records-new.schema.json +9 -0
  684. package/src/workspace/records-review.schema.json +80 -0
  685. package/src/workspace/records-sessions-write.test.ts +69 -0
  686. package/src/workspace/records-sessions.test.ts +22 -0
  687. package/src/workspace/records-uncommitted.test.ts +171 -0
  688. package/src/workspace/records-write.ts +219 -30
  689. package/src/workspace/records.schema.json +88 -1
  690. package/src/workspace/records.ts +76 -8
  691. package/src/workspace/reference-kinds/app/package.json +10 -0
  692. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  693. package/src/workspace/run-statement.schema.json +622 -0
  694. package/src/workspace/runs-cli.ts +710 -0
  695. package/src/workspace/runs-statement.test.ts +239 -0
  696. package/src/workspace/runs-write.schema.json +994 -0
  697. package/src/workspace/runs.schema.json +1257 -0
  698. package/src/workspace/runs.test.ts +281 -0
  699. package/src/workspace/runs.ts +970 -0
  700. package/src/workspace/session-kinds.ts +3 -3
  701. package/src/workspace/squash.ts +198 -0
  702. package/src/workspace/status-contract.test.ts +10 -2
  703. package/src/workspace/status-gates.ts +25 -9
  704. package/src/workspace/status.schema.json +252 -4
  705. package/src/workspace/status.ts +110 -1
  706. package/src/workspace/symbols.ts +191 -0
  707. package/src/workspace/trailer-joins.test.ts +171 -0
  708. package/src/workspace/trailer-joins.ts +82 -0
  709. package/src/workspace/trailers.ts +148 -0
  710. package/src/workspace/trust/run-commits.ts +115 -0
  711. package/src/workspace/trust/run-statement.ts +226 -0
  712. package/src/workspace/trust/seal.ts +58 -1
  713. package/src/workspace/trust/verify-cli.ts +6 -5
  714. package/src/workspace/trust/verify.ts +39 -6
  715. package/src/workspace/wip-cli.ts +97 -0
  716. package/src/workspace/wip-write.schema.json +504 -0
  717. package/src/workspace/wip.schema.json +334 -0
  718. package/src/workspace/wip.test.ts +294 -0
  719. package/src/workspace/wip.ts +753 -0
  720. package/src/workspace/work-cli.e2e.test.ts +28 -1
  721. package/src/workspace/work-cli.ts +136 -8
  722. package/src/workspace/work-evidence.schema.json +5 -0
  723. package/src/workspace/work-history.schema.json +73 -1
  724. package/src/workspace/work-history.test.ts +48 -5
  725. package/src/workspace/work-kind-fields.test.ts +154 -0
  726. package/src/workspace/work-lease.schema.json +14 -1
  727. package/src/workspace/work-size.test.ts +77 -0
  728. package/src/workspace/work-size.ts +93 -0
  729. package/src/workspace/work.ts +82 -0
  730. package/src/workspace/workspace-kinds.schema.json +122 -0
  731. package/src/workspace/workspace-principals.schema.json +60 -0
  732. package/src/workspace/write-lock-cli.ts +131 -0
  733. package/src/workspace/write-lock.schema.json +172 -0
  734. package/src/workspace/write-lock.test.ts +189 -0
  735. package/src/workspace/write-lock.ts +474 -0
  736. package/src/workspace/write-scope.test.ts +144 -3
  737. package/src/workspace/write-scope.ts +192 -45
  738. package/src/yaml.test.ts +111 -1
  739. package/src/yaml.ts +141 -549
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The factory reference Op (#3406, ws-087), run through the local executor
3
+ * with stub hooks on a small workspace that carries the reference
4
+ * workspace's decision, work and answer kinds and its points: one item from
5
+ * pick to done, one retry after a failed build, and one ask the understand
6
+ * point refuses. The rules themselves are tested in
7
+ * ../workspace/factory-rules.test.ts.
8
+ */
9
+
10
+ import { spawnSync } from "node:child_process";
11
+ import { cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { describe, expect, test } from "vitest";
14
+ import { withTestDir } from "@intentius/chant-test-utils";
15
+ import * as coreActivities from "./activities";
16
+ import type { ActivityFn } from "./activity-registry";
17
+ import { ACTIVITY_PROFILES } from "./activity-profiles";
18
+ import { factoryOpConfig } from "./factory";
19
+ import { OpRunFailure, runOpLocally, type OpRunResult } from "./local-executor";
20
+ import { answerPoint } from "../workspace/decide";
21
+ import { workspacePoints } from "../workspace/points-cli";
22
+ import { amendRecord } from "../workspace/records-write";
23
+ import { parseFrontMatter } from "../workspace/records";
24
+
25
+ const REF = join(import.meta.dirname, "..", "..", "..", "..", "reference-workspace");
26
+
27
+ const activities = new Map<string, ActivityFn>(Object.entries(coreActivities).filter(([, v]) => typeof v === "function") as [string, ActivityFn][]);
28
+
29
+ function git(args: string[], cwd: string): string {
30
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
31
+ if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
32
+ return r.stdout.trim();
33
+ }
34
+
35
+ const item = (id: string, fields: Record<string, unknown>, body = "") =>
36
+ `---\n${JSON.stringify({ schema: 1, id, title: `Item ${id}`, state: "open", implements: [], needs: [], constrains: ["member:app"], evidence: [], opened_on: "2026-10-01", source: { kind: "workspace", member: "app" }, supersedes: [], ...fields }, null, 2)}\n---\n\n# Item ${id}\n${body}`;
37
+
38
+ /**
39
+ * The builder stub: writes app/<item>.txt, and fails while .fail-<item> exists in the checkout
40
+ * the test controls (FACTORY_FAIL_DIR), so a retry can succeed.
41
+ */
42
+ const BUILDER = `
43
+ const fs = require("node:fs");
44
+ const item = process.env.FACTORY_ITEM;
45
+ if (fs.existsSync(require("node:path").join(process.env.FACTORY_FAIL_DIR, ".fail-" + item))) { console.error("the build broke"); process.exit(1); }
46
+ fs.mkdirSync("app", { recursive: true });
47
+ fs.writeFileSync("app/" + item + ".txt", "built at tier " + process.env.FACTORY_TIER + "\\n");
48
+ `;
49
+ const CHECK = `const fs = require("node:fs"); process.exit(fs.existsSync("app/" + process.env.FACTORY_ITEM + ".txt") ? 0 : 1);`;
50
+
51
+ function workspace(dir: string): void {
52
+ git(["init", "-q", "-b", "main"], dir);
53
+ git(["config", "user.email", "t@chant.dev"], dir);
54
+ git(["config", "user.name", "T"], dir);
55
+ git(["config", "commit.gpgsign", "false"], dir);
56
+ for (const d of ["decisions", "work", "answers"]) mkdirSync(join(dir, d), { recursive: true });
57
+ for (const f of ["decisions/decision.kind.mjs", "decisions/decision.schema.json", "decisions/points.json", "work/work.kind.mjs", "work/work.schema.json", "answers/answer.kind.mjs", "answers/answer.schema.json"]) {
58
+ cpSync(join(REF, f), join(dir, f));
59
+ }
60
+ writeFileSync(
61
+ join(dir, "chant.workspace.json"),
62
+ JSON.stringify({
63
+ name: "factory",
64
+ schema: 1,
65
+ records: [{ kind: "decisions/decision.kind.mjs" }, { kind: "work/work.kind.mjs" }, { kind: "answers/answer.kind.mjs" }],
66
+ members: [{ name: "app", dir: "app", kind: "other", because: "the app" }],
67
+ }),
68
+ );
69
+ mkdirSync(join(dir, "app"), { recursive: true });
70
+ writeFileSync(join(dir, "app", "README.md"), "# app\n");
71
+ mkdirSync(join(dir, "hooks"), { recursive: true });
72
+ writeFileSync(join(dir, "hooks", "builder.cjs"), BUILDER);
73
+ writeFileSync(join(dir, "hooks", "check.cjs"), CHECK);
74
+ writeFileSync(join(dir, "work", "W-101-the-feature.md"), item("W-101", { acceptance: [{ id: "AC-1", text: "app/W-101.txt exists", verification: "unit" }] }));
75
+ writeFileSync(join(dir, "work", "W-102-the-retried-one.md"), item("W-102", { acceptance: [{ id: "AC-1", text: "app/W-102.txt exists", verification: "unit" }] }));
76
+ writeFileSync(join(dir, "work", "W-103-an-ask.md"), item("W-103", { state: "proposed", source: { ask: { said: "Make the page purple.", by: "alice", via: "hud" } } }));
77
+ git(["add", "-A"], dir);
78
+ git(["commit", "-q", "-m", "the workspace"], dir);
79
+ }
80
+
81
+ async function run(dir: string, work?: string, extra: Partial<Parameters<typeof factoryOpConfig>[0]> = {}): Promise<OpRunResult> {
82
+ const config = factoryOpConfig({ cwd: dir, backends: { systemone: { url: "http://127.0.0.1:9", timeoutMs: 200 } }, builder: `${process.execPath} hooks/builder.cjs`, check: `${process.execPath} hooks/check.cjs`, ...extra });
83
+ const prev = process.env.FACTORY_FAIL_DIR;
84
+ process.env.FACTORY_FAIL_DIR = dir;
85
+ try {
86
+ return await runOpLocally(config, activities, ACTIVITY_PROFILES, undefined, { cwd: dir, ledger: { cwd: dir }, ...(work ? { work: { item: work } } : {}) });
87
+ } catch (err) {
88
+ if (err instanceof OpRunFailure) return err.result;
89
+ throw err;
90
+ } finally {
91
+ if (prev === undefined) delete process.env.FACTORY_FAIL_DIR;
92
+ else process.env.FACTORY_FAIL_DIR = prev;
93
+ }
94
+ }
95
+
96
+ const onBranch = (dir: string, id: string, path: string) => {
97
+ const fm = parseFrontMatter(git(["show", `chant/work/${id}:${path}`], dir));
98
+ if (!fm.ok) throw new Error(fm.message);
99
+ return fm.value;
100
+ };
101
+
102
+ describe("the factory reference Op (#3406)", () => {
103
+ test("builds an item from pick to done, retries a failed build only on a person's request, and drops an ask understand refuses", async () => {
104
+ await withTestDir(async (dir) => {
105
+ workspace(dir);
106
+ writeFileSync(join(dir, ".fail-W-102"), "");
107
+
108
+ // W-101: picked first, tier from slice-tier's table, built, checked, recorded done on its branch.
109
+ const first = await run(dir);
110
+ expect(first.workLease).toMatchObject({ item: "W-101", released: true, outcome: "done" });
111
+ const w101 = onBranch(dir, "W-101", "work/W-101-the-feature.md");
112
+ expect(w101).toMatchObject({ state: "done", result: { lease: first.workLease!.token } });
113
+ expect(git(["show", "chant/work/W-101:app/W-101.txt"], dir)).toBe("built at tier small");
114
+ const msg = git(["log", "-1", "--format=%B", "chant/work/W-101"], dir);
115
+ expect(msg).toContain("Chant-Record: work:W-101");
116
+ expect(msg).toContain(`Chant-Lease: ${first.workLease!.token}`);
117
+
118
+ // W-101 waits to be applied, so W-102 is next; its builder fails, and the attempt is kept.
119
+ const second = await run(dir);
120
+ expect(second.workLease).toMatchObject({ item: "W-102", outcome: "not_done" });
121
+ const failed = second.workLease!.token!;
122
+
123
+ // Neither W-101 (built, not applied) nor W-102 (failed, no retry) is picked; W-103 is an ask,
124
+ // and understand escalates it to people, so the run waits on the question.
125
+ const third = await run(dir);
126
+ expect(third.workLease).toMatchObject({ item: "W-103", outcome: "waiting" });
127
+
128
+ // A person asks for W-102 again, naming the failed build; the next run builds it.
129
+ const retry = await amendRecord({ kind: join(dir, "work", "work.kind.mjs"), id: "W-102", fields: JSON.stringify({ retry: { after: failed, by: "alice", at: "2026-10-03T10:00:00Z" } }), cwd: dir });
130
+ expect("error" in retry).toBe(false);
131
+ git(["add", "-A"], dir);
132
+ git(["commit", "-q", "-m", "retry W-102"], dir);
133
+ unlinkSync(join(dir, ".fail-W-102"));
134
+ const fourth = await run(dir, "W-102");
135
+ expect(fourth.workLease).toMatchObject({ item: "W-102", outcome: "done" });
136
+
137
+ // The understand question about W-103 is answered refuse; the next run drops it on its branch.
138
+ const points = await workspacePoints({ cwd: dir });
139
+ if ("error" in points) throw new Error(points.error.message);
140
+ const open = points.questions.find((q) => q.point === "understand" && q.open);
141
+ expect(open).toBeDefined();
142
+ const answered = await answerPoint({ cwd: dir, id: open!.id, answer: "refuse", by: ["alice"] });
143
+ expect("error" in answered).toBe(false);
144
+ git(["add", "-A"], dir);
145
+ git(["commit", "-q", "-m", "refuse W-103"], dir);
146
+ const fifth = await run(dir, "W-103");
147
+ expect(fifth.workLease).toMatchObject({ item: "W-103", outcome: "dropped" });
148
+ expect(onBranch(dir, "W-103", "work/W-103-an-ask.md")).toMatchObject({ state: "dropped" });
149
+ expect(existsSync(join(dir, "app", "W-103.txt"))).toBe(false);
150
+ expect(readFileSync(join(dir, "work", "W-101-the-feature.md"), "utf-8")).toContain('"state": "open"');
151
+ });
152
+ }, 120_000);
153
+ });
154
+
155
+ describe("the factory Op's seams for an orchestrator (studio#382)", () => {
156
+ test("prepare runs before Pick, an uncommitted item is carried into the worktree, the reports pass through, the check ticks each criterion by its own result, and after sees the outcome", async () => {
157
+ await withTestDir(async (dir) => {
158
+ workspace(dir);
159
+ // A builder that reports a run and a check that reports per criterion.
160
+ writeFileSync(join(dir, "hooks", "builder2.cjs"), `${BUILDER}\nconsole.log(JSON.stringify({ ok: true, run: { id: "run-7" }, agent: "builder-small", chantAgent: "factory", records: ["contract:C-1"] }));\n`);
161
+ writeFileSync(join(dir, "hooks", "check2.cjs"), `console.log(JSON.stringify({ criteria: { "AC-1": "pass", "AC-2": "fail" }, built: JSON.parse(process.env.FACTORY_BUILD).agent }));`);
162
+ writeFileSync(join(dir, "hooks", "after.cjs"), `require("node:fs").writeFileSync(require("node:path").join(process.env.FACTORY_FAIL_DIR, "after.json"), JSON.stringify({ outcome: process.env.FACTORY_OUTCOME, commit: process.env.FACTORY_COMMIT, reason: process.env.FACTORY_REASON, check: JSON.parse(process.env.FACTORY_CHECK) }));`);
163
+ // The prepare hook writes W-110 in the checkout and leaves it uncommitted; the others are taken out of the way.
164
+ writeFileSync(
165
+ join(dir, "hooks", "prepare.cjs"),
166
+ `require("node:fs").writeFileSync("work/W-110-prepared.md", ${JSON.stringify(item("W-110", { acceptance: [{ id: "AC-1", text: "one", verification: "unit" }, { id: "AC-2", text: "two", verification: "unit" }] }))});`,
167
+ );
168
+ for (const id of ["W-101", "W-102"]) writeFileSync(join(dir, ".fail-" + id), "");
169
+ git(["add", "-A"], dir);
170
+ git(["commit", "-q", "-m", "hooks"], dir);
171
+ const result = await run(dir, "W-110", {
172
+ kind: "./work/work.kind.mjs",
173
+ prepare: `${process.execPath} hooks/prepare.cjs`,
174
+ builder: `${process.execPath} hooks/builder2.cjs`,
175
+ check: `${process.execPath} hooks/check2.cjs`,
176
+ after: `${process.execPath} hooks/after.cjs`,
177
+ });
178
+ // AC-2 failed its own check, so the item is not done, though the check exited 0.
179
+ expect(result.workLease).toMatchObject({ item: "W-110", outcome: "not_done" });
180
+ const after = JSON.parse(readFileSync(join(dir, "after.json"), "utf-8"));
181
+ expect(after).toMatchObject({ outcome: "not_done", reason: "criteria AC-2 have no passing evidence", check: { built: "builder-small" } });
182
+ // The attempt was committed with why, and the run kept that commit.
183
+ expect(after.commit).toMatch(/^[0-9a-f]{40}$/);
184
+ // The kept attempt holds the carried item with AC-1 ticked and AC-2 failed.
185
+ const kept = git(["for-each-ref", "--format=%(refname)", "refs/chant/kept/"], dir).split("\n").find((r) => r.includes("W-110"))!;
186
+ expect(git(["rev-parse", kept], dir)).toBe(after.commit);
187
+ expect(git(["log", "-1", "--format=%B", kept], dir)).toMatch(/^W-110: not done\n\nNot done: criteria AC-2 have no passing evidence\n\nChant-Agent: factory\nChant-Lease: /);
188
+ const fm = parseFrontMatter(git(["show", `${kept}:work/W-110-prepared.md`], dir));
189
+ if (!fm.ok) throw new Error(fm.message);
190
+ const results = Object.fromEntries((fm.value.evidence as { criterion: string; result: string }[]).map((e) => [e.criterion, e.result]));
191
+ expect(results).toEqual({ "AC-1": "pass", "AC-2": "fail" });
192
+
193
+ // With both criteria passing, the item is done and its commit carries the builder's run.
194
+ writeFileSync(join(dir, "hooks", "check2.cjs"), `console.log(JSON.stringify({ criteria: { "AC-1": "pass", "AC-2": "pass" } }));`);
195
+ git(["add", "-A"], dir);
196
+ git(["commit", "-q", "-m", "a passing check"], dir);
197
+ const { readLeaseHistory } = await import("../lifecycle/work-lease");
198
+ const failed = (await readLeaseHistory("W-110", { cwd: dir })).records.filter((r) => r.event === "claim").at(-1) as { token: string };
199
+ const amended = await amendRecord({ kind: join(dir, "work", "work.kind.mjs"), id: "W-110", fields: JSON.stringify({ retry: { after: failed.token, by: "alice" } }), cwd: dir });
200
+ expect("error" in amended).toBe(false);
201
+ const done = await run(dir, "W-110", { builder: `${process.execPath} hooks/builder2.cjs`, check: `${process.execPath} hooks/check2.cjs`, after: `${process.execPath} hooks/after.cjs` });
202
+ expect(done.workLease).toMatchObject({ item: "W-110", outcome: "done" });
203
+ const body = git(["log", "-1", "--format=%B", "chant/work/W-110"], dir);
204
+ expect(body).toContain("Chant-Run: run-7");
205
+ expect(body).toContain("Chant-Agent: factory");
206
+ expect(body).toContain("Chant-Record: work:W-110\nChant-Record: contract:C-1");
207
+ expect(JSON.parse(readFileSync(join(dir, "after.json"), "utf-8")).outcome).toBe("done");
208
+ });
209
+ }, 120_000);
210
+ });
211
+
212
+ describe("an ask's item is the builder's only for its plan (studio#382)", () => {
213
+ test("a builder that changes an open ask beyond its acceptance criteria has the item put back, and the build is not done", async () => {
214
+ await withTestDir(async (dir) => {
215
+ workspace(dir);
216
+ for (const id of ["W-101", "W-102"]) writeFileSync(join(dir, ".fail-" + id), "");
217
+ writeFileSync(join(dir, "work", "W-120-an-open-ask.md"), item("W-120", { source: { ask: { said: "Add a page.", by: "alice", via: "hud" } } }));
218
+ writeFileSync(
219
+ join(dir, "hooks", "rewrite.cjs"),
220
+ `const fs = require("node:fs"); const f = "work/W-120-an-open-ask.md"; fs.writeFileSync(f, fs.readFileSync(f, "utf8").replace('"title": "Item W-120"', '"title": "Renamed"')); fs.writeFileSync("app/W-120.txt", "the page\\n");`,
221
+ );
222
+ git(["add", "-A"], dir);
223
+ git(["commit", "-q", "-m", "an open ask"], dir);
224
+ const result = await run(dir, "W-120", { builder: `${process.execPath} hooks/rewrite.cjs` });
225
+ expect(result.workLease).toMatchObject({ item: "W-120", outcome: "not_done" });
226
+ const kept = git(["for-each-ref", "--format=%(refname)", "refs/chant/kept/W-120/"], dir);
227
+ expect(git(["show", `${kept}:work/W-120-an-open-ask.md`], dir)).toContain('"title": "Item W-120"');
228
+ expect(git(["log", "-1", "--format=%B", kept], dir)).toContain("the guard put back what the builder changed out of scope: work/W-120-an-open-ask.md (it changed title, and only acceptance is the builder's)");
229
+ });
230
+ }, 120_000);
231
+ });
232
+
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The factory reference Op (#3406, ws-087): the factory's rules declared in
3
+ * core, and an orchestrator's execution plugged in through hooks.
4
+ *
5
+ * ```ts
6
+ * // ops/factory.op.ts
7
+ * export default factoryOp({
8
+ * builder: "node box/ops/factory/builder.mjs",
9
+ * });
10
+ * ```
11
+ *
12
+ * One run is one build of one work item, under that item's lease, in a
13
+ * worktree on `chant/work/<item>`:
14
+ *
15
+ * Pick factoryPick: the buildable items, in order; the lease claims the first nobody holds
16
+ * Ask factoryAsk: the tier (the item's own, or the slice-tier point) and, for an ask, the
17
+ * understand point; an open question stops the run waiting
18
+ * Build factoryBuild: the `builder` hook, unless understand said not to build
19
+ * Check factoryCheck: the `check` hook, or the box's `factory.check`, with its evidence
20
+ * Record factoryRecord: done, dropped, redraft, ask or not_done, which the lease's release
21
+ * takes; a not_done attempt is kept at refs/chant/kept/<item>/<token> by the run
22
+ *
23
+ * The hooks are commands, run with no shell in the worktree:
24
+ *
25
+ * - `builder` (required): how a builder runs, such as Claude Code in the box
26
+ * or a `fountainRun` wrapper. It gets FACTORY_ITEM, FACTORY_TIER,
27
+ * FACTORY_TOKEN, FACTORY_HOLDER, FACTORY_WORKTREE and FACTORY_CONTEXT, edits
28
+ * the worktree, and exits 0 when it finished. It may print a last JSON line
29
+ * `{ "reverted": [paths] }` when its guard put changes back.
30
+ * - `context` (optional): passed to the builder as FACTORY_CONTEXT, the prompt
31
+ * and context bundle the orchestrator assembles.
32
+ * - `check` (optional): the verdict command; the box block's `factory.check`
33
+ * when left out. An infra profile names a plan or `chant lint` here.
34
+ *
35
+ * Publishing a done item is the box's publisher (ws-088), run later by a
36
+ * person or a surface with `chant workspace box publish`; it is not a step.
37
+ */
38
+
39
+ import { Op, phase, activity, shell, type NamedActivityStep } from "./builders";
40
+ import { stepOutput } from "./step-output-ref";
41
+ import { workLeaseOutput } from "./work-lease-run";
42
+ import type { OpConfig } from "./types";
43
+
44
+ export interface FactoryOpOptions {
45
+ /** The Op's name. `factory` by default. */
46
+ name?: string;
47
+ /** The builder hook: a command run in the run's worktree. */
48
+ builder: string;
49
+ /** The context hook: what the builder receives as FACTORY_CONTEXT. */
50
+ context?: string;
51
+ /** The check hook; the box's `factory.check` when left out. It may print `{ "criteria": { "<id>": "pass" | "fail" } }` as its last line to tick each criterion by its own result. */
52
+ check?: string;
53
+ /** The prepare hook: a command run in the workspace before Pick, such as opening the first build's item once the box's intent is decided. */
54
+ prepare?: string;
55
+ /** The after hook: a command run in the worktree once the outcome is recorded, with FACTORY_OUTCOME and FACTORY_COMMIT, such as ending the build's run record. */
56
+ after?: string;
57
+ /** The work kind file, when the declaration names several. */
58
+ kind?: string;
59
+ /** How long a lease is held without a renewal, such as 15m. The lease's default otherwise. */
60
+ ttl?: string;
61
+ /** How long the builder may run. 2h by default. */
62
+ buildTimeout?: string;
63
+ schedule?: OpConfig["schedule"];
64
+ /** Where the workspace is read; the run's working directory when left out. */
65
+ cwd?: string;
66
+ /** Backends for the points' model deciders, in place of `decide.backends` in chant.config. */
67
+ backends?: Record<string, { url: string; key?: unknown; timeoutMs?: number }>;
68
+ }
69
+
70
+ /** The Op's configuration, for a steward that lists it or a test that runs it. */
71
+ export function factoryOpConfig(options: FactoryOpOptions): OpConfig {
72
+ if (!options || typeof options.builder !== "string" || options.builder.trim() === "") {
73
+ throw new Error("factoryOp needs a builder: the command that runs a builder in the run's worktree");
74
+ }
75
+ const lease = workLeaseOutput();
76
+ const kind = options.kind !== undefined ? { kind: options.kind } : {};
77
+ const at = options.cwd !== undefined ? { cwd: options.cwd } : {};
78
+ return {
79
+ name: options.name ?? "factory",
80
+ overview: "Build the next buildable work item under its lease: pick, ask, build, check and record, by the factory's rules (#3406, ws-087)",
81
+ workLease: {
82
+ item: stepOutput("pick", "candidates"),
83
+ outcome: stepOutput("record", "outcome"),
84
+ ...kind,
85
+ ...(options.ttl !== undefined ? { ttl: options.ttl } : {}),
86
+ },
87
+ changesCheckout: true,
88
+ ...(options.schedule ? { schedule: options.schedule } : {}),
89
+ phases: [
90
+ ...(options.prepare !== undefined ? [phase("Prepare", [shell(options.prepare, { ...(options.cwd !== undefined ? { cwd: options.cwd } : {}), id: "prepare" })])] : []),
91
+ phase("Pick", [activity("factoryPick", { ...kind, ...at }, { profile: "fastIdempotent", id: "pick" })]),
92
+ phase("Ask", [activity("factoryAsk", { lease, ...kind, ...at, ...(options.backends ? { backends: options.backends } : {}) }, { profile: "fastIdempotent", id: "ask" })]),
93
+ phase("Build", [
94
+ activity(
95
+ "factoryBuild",
96
+ { lease, ...at, ask: stepOutput("ask"), builder: options.builder, ...(options.context !== undefined ? { context: options.context } : {}) },
97
+ { profile: "atMostOnce", id: "build", timeout: options.buildTimeout ?? "2h" },
98
+ ),
99
+ ]),
100
+ phase("Check", [activity("factoryCheck", { lease, ...at, build: stepOutput("build"), ...(options.check !== undefined ? { check: options.check } : {}) }, { profile: "atMostOnce", id: "check" })]),
101
+ phase("Record", [activity("factoryRecord", { lease, ...at, ask: stepOutput("ask"), build: stepOutput("build"), check: stepOutput("check"), ...(options.after !== undefined ? { after: options.after } : {}) }, { profile: "atMostOnce", id: "record" })]),
102
+ ],
103
+ } as OpConfig;
104
+ }
105
+
106
+ /** The factory reference Op, as an `*.op.ts` file exports it. */
107
+ export function factoryOp(options: FactoryOpOptions): ReturnType<typeof Op> {
108
+ return Op(factoryOpConfig(options));
109
+ }
110
+
111
+ /**
112
+ * The steward's ready step for the factory Op (#382): the keys of the work buildable now, for
113
+ * `declareSteward({ beside: [{ op: factory, ready: factoryReady({ cwd }) }] })`. `also` names a
114
+ * command printing a JSON list of more keys, for work the pick can't see yet.
115
+ */
116
+ export function factoryReady(options: { cwd?: string; kind?: string; also?: string } = {}): NamedActivityStep {
117
+ return activity("factoryReady", { ...options }, { profile: "fastIdempotent" });
118
+ }
@@ -191,6 +191,80 @@ describe("gate approval — policy decisions (#2508)", () => {
191
191
  expect(check.satisfied).toBe(false);
192
192
  });
193
193
 
194
+ test("log-only then enforce: a permit recorded under log-only does not pass the enforced gate (#2512)", async () => {
195
+ const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
196
+ const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
197
+ // The pending fact was recorded while the gate was log-only, and the agent's allow was recorded against it.
198
+ const port = memoryGateLedgerPort({
199
+ resolutions: [approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
200
+ pending: [{ ...PENDING, approval: logOnly }],
201
+ });
202
+ const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
203
+ expect(check.satisfied).toBe(false);
204
+
205
+ const tally = tallyGateApprovals(
206
+ [approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
207
+ "ship", PENDING.timestamp, PLAN_A, enforce,
208
+ );
209
+ expect(tally.permit).toBeUndefined();
210
+ });
211
+
212
+ test("log-only then enforce: a human's approval recorded under log-only still counts toward the quorum (#2512)", async () => {
213
+ const logOnly: ResolvedGateApproval = { quorum: { count: 1 }, policy: POLICY, mode: "log-only" };
214
+ const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
215
+ const port = memoryGateLedgerPort({
216
+ resolutions: [approval({ resolvedBy: "alex", timestamp: "2026-09-01T01:00:00.000Z", policyDecision: allow("log-only") })],
217
+ pending: [{ ...PENDING, approval: logOnly }],
218
+ });
219
+ const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
220
+ expect(check.satisfied).toBe(true);
221
+ if (!check.satisfied) return;
222
+ expect(check.via).toBe("quorum");
223
+ expect(check.resolution.resolvedBy).toBe("alex");
224
+ });
225
+
226
+ test("log-only then enforce: the gated run re-records the pending fact under enforce, and only a fresh permit passes (#2512)", async () => {
227
+ const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
228
+ const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
229
+ const stale = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") });
230
+ const port = memoryGateLedgerPort({ resolutions: [stale], pending: [{ ...PENDING, approval: logOnly }] });
231
+
232
+ const first = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
233
+ expect(first.satisfied).toBe(false);
234
+ if (first.satisfied) return;
235
+ // The standing fact described the log-only gate, so a fresh one carries
236
+ // enforce, and `chant approve` evaluates the next agent approval under it.
237
+ expect(first.recorded).toBe(true);
238
+ expect(first.pending.approval?.mode).toBe("enforce");
239
+
240
+ // Re-running without a new approval still waits: the stale permit predates the new pending fact as well.
241
+ const again = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T12:30:00.000Z" });
242
+ expect(again.satisfied).toBe(false);
243
+
244
+ const fresh = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T13:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("enforce") });
245
+ const after = await evaluateGate(
246
+ memoryGateLedgerPort({ resolutions: [stale, fresh], pending: [first.pending] }),
247
+ { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T14:00:00.000Z" },
248
+ );
249
+ expect(after.satisfied).toBe(true);
250
+ if (!after.satisfied) return;
251
+ expect(after.via).toBe("policy");
252
+ expect(after.resolution.timestamp).toBe(fresh.timestamp);
253
+ });
254
+
255
+ test("enforce: a recorded decision with no mode does not pass the gate (#2512)", async () => {
256
+ const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
257
+ const { mode: _mode, ...noMode } = allow()!;
258
+ const { check } = await decide(
259
+ [approval({
260
+ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" },
261
+ policyDecision: noMode as GateResolutionRecord["policyDecision"],
262
+ })],
263
+ block,
264
+ );
265
+ expect(check.satisfied).toBe(false);
266
+ });
267
+
194
268
  test("enforce: a deny-by-default policy keeps the old behaviour, where only a human's approval counts", async () => {
195
269
  const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
196
270
  const deny = { ...allow()!, decision: "deny" as const, determining: ["floor"] };
@@ -119,7 +119,11 @@ export interface GatePolicyAnswer {
119
119
  export interface GatePolicyDecision extends GatePolicyAnswer {
120
120
  policy: string;
121
121
  version: string;
122
- /** The gate's mode when the decision was recorded. A run reads the gate's current mode, not this. */
122
+ /**
123
+ * The gate's mode when the decision was recorded. A permit passes a gate
124
+ * only when both this and the gate's current mode are `enforce` (#2512), so
125
+ * an allow recorded under `log-only` never binds after a switch.
126
+ */
123
127
  mode: GateApprovalMode;
124
128
  }
125
129
 
package/src/op/gate.ts CHANGED
@@ -74,6 +74,19 @@ export interface PendingGatePush {
74
74
  export interface GateLedgerPort {
75
75
  read(op: string): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[] }>;
76
76
  appendPending(input: PendingGateInput): Promise<PendingGatePush>;
77
+ /**
78
+ * The rule a workspace sets for approvals of `gate` (#3163, ws-080), or
79
+ * null when none applies: the declaration's `identity.gates`, read at base.
80
+ * Absent, every approval is judged as before.
81
+ */
82
+ approvalRule?(gate: string): Promise<GateApprovalRule | null>;
83
+ }
84
+
85
+ /** A workspace's rule for which approvals of a gate count (#3163): a signed approval, from a class when it names one. */
86
+ export interface GateApprovalRule {
87
+ requirement: { gate: string; class: string | null };
88
+ /** Why `approval` doesn't count, or null when it does. */
89
+ refuses(approval: GateResolutionRecord): string | null;
77
90
  }
78
91
 
79
92
  /**
@@ -102,6 +115,11 @@ export function gitGateLedgerPort(opts?: { cwd?: string }): GateLedgerPort {
102
115
  const { resolutions, pending } = await readGateLedger(op, opts);
103
116
  return { resolutions, pending };
104
117
  },
118
+ async approvalRule(gate) {
119
+ // Loaded on demand: the rule lives in the workspace modules, which import the Op modules.
120
+ const { gateAdmission } = await import("../workspace/identity");
121
+ return gateAdmission(opts?.cwd ?? process.cwd(), gate);
122
+ },
105
123
  async appendPending(input) {
106
124
  const { record } = await appendPendingGate(input, opts);
107
125
  try {
@@ -288,7 +306,7 @@ export interface GateTally {
288
306
  counted: GateResolutionRecord[];
289
307
  /** The quorum's count, 1 when the gate declares none. */
290
308
  need: number;
291
- /** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, and only under the gate's current policy version. */
309
+ /** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, only for a permit recorded under `enforce`, and only under the gate's current policy version. */
292
310
  permit?: GateResolutionRecord;
293
311
  /** The newest approval of this gate for a different plan, when the gate binds one. */
294
312
  mismatched?: GateResolutionRecord;
@@ -305,8 +323,9 @@ export interface GateTally {
305
323
  * Of those, only a human's counts toward the quorum, once per `resolvedBy`,
306
324
  * and only with one of the quorum's roles when it names any. An agent counts
307
325
  * only through a recorded `allow` in `enforce` mode, evaluated under the
308
- * policy version the gate declares now. A `log-only` decision never changes
309
- * the outcome, and a `deny` never removes a human's approval: a policy can add
326
+ * policy version the gate declares now and recorded while the gate was in
327
+ * `enforce` (#2512). A `log-only` decision never changes the outcome, even
328
+ * after the gate switches to `enforce`, and a `deny` never removes a human's approval: a policy can add
310
329
  * a way through the gate but cannot take one away.
311
330
  */
312
331
  export function tallyGateApprovals(
@@ -331,8 +350,12 @@ export function tallyGateApprovals(
331
350
  }
332
351
 
333
352
  const decision = r.policyDecision;
353
+ // #2512: the decision must also have been recorded under `enforce`. An
354
+ // allow recorded while the gate was log-only was never binding, and a
355
+ // later switch to enforce does not make it binding.
334
356
  if (
335
357
  approval.mode === "enforce" && approval.policy && decision?.decision === "allow" &&
358
+ decision.mode === "enforce" &&
336
359
  decision.version === approval.policy.version && (!permit || at(r) >= at(permit))
337
360
  ) {
338
361
  permit = r;
@@ -394,10 +417,13 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
394
417
  // a mismatch with a message instead of silently not counting.
395
418
  const env = input.environment;
396
419
  const pending = env === undefined ? ledger.pending : ledger.pending.filter((p) => p.environment === env);
397
- const resolutions =
420
+ // #3163: a gate the workspace's identity.gates names counts only signed approvals from its class.
421
+ const rule = (await port.approvalRule?.(input.gate)) ?? null;
422
+ const resolutions = (
398
423
  env === undefined
399
424
  ? ledger.resolutions
400
- : ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env);
425
+ : ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env)
426
+ ).filter((r) => rule === null || r.gate !== input.gate || rule.refuses(r) === null);
401
427
 
402
428
  const standing = latestPendingGate(pending, input.gate);
403
429
  const since = standing?.timestamp ?? EPOCH;
package/src/op/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { Op, phase, activity, gate, effect, build, kubectlApply, helmInstall, helmInstallPinned, waitForStack, waitForReady,
2
- gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
2
+ gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, composeChangeSet, lifecyclePlanChangeSet, readChangeSetPart, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
3
3
  k3sInstall, k3sUninstall, flociUp, flociDown,
4
4
  flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck,
5
5
  azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate,
@@ -60,6 +60,8 @@ export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
60
60
  export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
61
61
  export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
62
62
  export type { StepRecord, OpRunResult, RunOpOptions } from "./local-executor";
63
+ export { currentOpRun, withOpRunContext } from "./run-context";
64
+ export type { OpRunContext, PassedGate } from "./run-context";
63
65
  export { evaluateGate, gitGateLedgerPort, memoryGateLedgerPort, approveCommand, describeGateMismatch } from "./gate";
64
66
  export type { GateLedgerPort, GateCheck, GateCheckInput, PendingGatePush, GateDigestMismatch, GateQuorumProgress, GateTally } from "./gate";
65
67
  export { tallyGateApprovals, approverOf } from "./gate";
@@ -147,3 +149,11 @@ export type { ChangeSignalGate, ChangeSignalGateOptions, WakeReason } from "./ch
147
149
  export { classifyOpVerbClass, isGated } from "./op-verb-class";
148
150
  export type { OpVerbClass } from "./op-verb-class";
149
151
  export { takeProfileAndId } from "./builders";
152
+
153
+ // The factory reference Op (#3406, ws-087).
154
+ export { factoryOp, factoryOpConfig, factoryReady } from "./factory";
155
+ // The factory's rules themselves (#3406) are values in workspace code, which no level 0 command may load (#2526), so only their
156
+ // types are re-exported here. An orchestrator's own tools, such as a retry command a UI runs, import the values from
157
+ // `@intentius/chant/workspace/factory-rules`.
158
+ export type { FactoryItem, FactoryContext, FactoryClaim, FactoryHold, RetryState } from "../workspace/factory-rules";
159
+ export type { FactoryOpOptions } from "./factory";
@@ -232,6 +232,21 @@ describe("runOpLocally — outcomeAttribute", () => {
232
232
  expect(result.records[0].outcome).toEqual({ name: "Drift", value: true });
233
233
  });
234
234
 
235
+ test("an attribute whose path the result does not hold is left off, not recorded as undefined", async () => {
236
+ const done: ActivityFn = async () => ({ state: "done", switched: false });
237
+ const config = op({
238
+ phases: [{ name: "Switch", steps: [
239
+ { kind: "activity", fn: "migrationSwitch", outcomeAttribute: [{ name: "OldColumn", from: "oldColumn" }, { name: "State", from: "state" }] },
240
+ { kind: "activity", fn: "migrationRetain", outcomeAttribute: { name: "RetainUntil", from: "retainUntil" } },
241
+ ] }],
242
+ });
243
+ const result = await runOpLocally(config, new Map([["migrationSwitch", done], ["migrationRetain", done]]), PROFILES);
244
+ expect(result.records[0].outcomes).toEqual([{ name: "State", value: "done" }]);
245
+ expect(result.records[0].outcome).toEqual({ name: "State", value: "done" });
246
+ expect(result.records[1].outcomes).toBeUndefined();
247
+ expect(result.records[1].outcome).toBeUndefined();
248
+ });
249
+
235
250
  test("a step with no attribute leaves both fields absent", async () => {
236
251
  const noop: ActivityFn = async () => ({ ok: true });
237
252
  const config = op({ phases: [{ name: "Plan", steps: [{ kind: "activity", fn: "noop" }] }] });