@intentius/chant 0.100.0 → 0.102.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 (608) 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/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/build.d.ts +6 -0
  9. package/dist/build.d.ts.map +1 -1
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/cli/build-options.d.ts +2 -0
  12. package/dist/cli/build-options.d.ts.map +1 -1
  13. package/dist/cli/commands/build.d.ts.map +1 -1
  14. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/import.d.ts +8 -0
  18. package/dist/cli/commands/import.d.ts.map +1 -1
  19. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  20. package/dist/cli/commands/init.d.ts +19 -0
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts +9 -0
  23. package/dist/cli/commands/onboard.d.ts.map +1 -1
  24. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  25. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  26. package/dist/cli/handlers/dev.d.ts.map +1 -1
  27. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/misc.d.ts.map +1 -1
  30. package/dist/cli/handlers/operator.d.ts +9 -0
  31. package/dist/cli/handlers/operator.d.ts.map +1 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/lsp/server.d.ts.map +1 -1
  34. package/dist/cli/main.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  40. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  41. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  42. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  43. package/dist/cli/mcp/workspace-tools.d.ts +9 -1
  44. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  45. package/dist/cli/registry.d.ts +33 -2
  46. package/dist/cli/registry.d.ts.map +1 -1
  47. package/dist/codegen/fetch.d.ts.map +1 -1
  48. package/dist/codegen/naming.d.ts.map +1 -1
  49. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  50. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  51. package/dist/codegen/registry.d.ts.map +1 -1
  52. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  53. package/dist/components/capability.d.ts.map +1 -1
  54. package/dist/components/driver.d.ts.map +1 -1
  55. package/dist/components/fan-out.d.ts.map +1 -1
  56. package/dist/components/starter-plugin.d.ts.map +1 -1
  57. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  58. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  59. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  60. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  61. package/dist/components/verbs/sign.d.ts.map +1 -1
  62. package/dist/components/verbs/verify.d.ts.map +1 -1
  63. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  64. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  65. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  66. package/dist/composite.d.ts.map +1 -1
  67. package/dist/config.d.ts +30 -7
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/declarable.d.ts +16 -0
  70. package/dist/declarable.d.ts.map +1 -1
  71. package/dist/discovery/cache.d.ts.map +1 -1
  72. package/dist/discovery/fold-import.d.ts +15 -5
  73. package/dist/discovery/fold-import.d.ts.map +1 -1
  74. package/dist/discovery/param-deps.d.ts +16 -0
  75. package/dist/discovery/param-deps.d.ts.map +1 -1
  76. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  77. package/dist/discovery/specifier-resolve.d.ts +37 -0
  78. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  79. package/dist/errors.d.ts.map +1 -1
  80. package/dist/fold/fold.d.ts +3 -1
  81. package/dist/fold/fold.d.ts.map +1 -1
  82. package/dist/graph-detail.d.ts.map +1 -1
  83. package/dist/graph-ir.d.ts.map +1 -1
  84. package/dist/graph-layout.d.ts.map +1 -1
  85. package/dist/import/base-parser.d.ts.map +1 -1
  86. package/dist/import/embedded.d.ts +7 -0
  87. package/dist/import/embedded.d.ts.map +1 -1
  88. package/dist/import/parser-options.d.ts +14 -0
  89. package/dist/import/parser-options.d.ts.map +1 -0
  90. package/dist/import/parser.d.ts +17 -0
  91. package/dist/import/parser.d.ts.map +1 -1
  92. package/dist/kubectl-context.d.ts.map +1 -1
  93. package/dist/lexicon-output.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +3 -3
  95. package/dist/lexicon.d.ts +78 -3
  96. package/dist/lexicon.d.ts.map +1 -1
  97. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  98. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  99. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  100. package/dist/lifecycle/git.d.ts.map +1 -1
  101. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  102. package/dist/lifecycle/observe.d.ts.map +1 -1
  103. package/dist/lifecycle/plan-digest.d.ts +26 -5
  104. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  105. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  106. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  107. package/dist/lifecycle/scenario.d.ts.map +1 -1
  108. package/dist/lifecycle/status.d.ts.map +1 -1
  109. package/dist/lifecycle/work-lease.d.ts +61 -2
  110. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  111. package/dist/lint/config.d.ts +4 -4
  112. package/dist/lint/engine.d.ts +6 -0
  113. package/dist/lint/engine.d.ts.map +1 -1
  114. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  115. package/dist/op/activities/activity-contracts.d.ts +12 -11
  116. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  117. package/dist/op/activities/apply.d.ts +79 -9
  118. package/dist/op/activities/apply.d.ts.map +1 -1
  119. package/dist/op/activities/decide.d.ts.map +1 -1
  120. package/dist/op/activities/index.d.ts +1 -1
  121. package/dist/op/activities/index.d.ts.map +1 -1
  122. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  123. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  124. package/dist/op/activities/propose-upgrade.d.ts +2 -0
  125. package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
  126. package/dist/op/activity-contract.d.ts +1 -1
  127. package/dist/op/activity-failure.d.ts.map +1 -1
  128. package/dist/op/activity-profiles.d.ts.map +1 -1
  129. package/dist/op/builders.d.ts.map +1 -1
  130. package/dist/op/composites/apply-op.d.ts +12 -5
  131. package/dist/op/composites/apply-op.d.ts.map +1 -1
  132. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  133. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  134. package/dist/op/decide-backend.d.ts.map +1 -1
  135. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  136. package/dist/op/emulator-lifecycle.d.ts +21 -2
  137. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  138. package/dist/op/gate-approval.d.ts +5 -1
  139. package/dist/op/gate-approval.d.ts.map +1 -1
  140. package/dist/op/gate-wait.d.ts.map +1 -1
  141. package/dist/op/gate.d.ts +19 -3
  142. package/dist/op/gate.d.ts.map +1 -1
  143. package/dist/op/index.d.ts +3 -1
  144. package/dist/op/index.d.ts.map +1 -1
  145. package/dist/op/local-executor.d.ts +6 -0
  146. package/dist/op/local-executor.d.ts.map +1 -1
  147. package/dist/op/operator.d.ts.map +1 -1
  148. package/dist/op/run-context.d.ts +39 -0
  149. package/dist/op/run-context.d.ts.map +1 -0
  150. package/dist/op/run-live.d.ts.map +1 -1
  151. package/dist/op/runtimes/local.d.ts.map +1 -1
  152. package/dist/op/source-archive.d.ts.map +1 -1
  153. package/dist/op/steward-beside.d.ts.map +1 -1
  154. package/dist/op/steward-points.d.ts.map +1 -1
  155. package/dist/op/steward.d.ts.map +1 -1
  156. package/dist/op/types.d.ts +8 -3
  157. package/dist/op/types.d.ts.map +1 -1
  158. package/dist/op/work-lease-run.d.ts +24 -4
  159. package/dist/op/work-lease-run.d.ts.map +1 -1
  160. package/dist/project-root.d.ts.map +1 -1
  161. package/dist/provenance.d.ts +21 -0
  162. package/dist/provenance.d.ts.map +1 -1
  163. package/dist/pseudo-parameter.d.ts.map +1 -1
  164. package/dist/reconcile.d.ts.map +1 -1
  165. package/dist/secret-materialization.d.ts.map +1 -1
  166. package/dist/serializer.d.ts +8 -0
  167. package/dist/serializer.d.ts.map +1 -1
  168. package/dist/telemetry-attribution.d.ts +77 -0
  169. package/dist/telemetry-attribution.d.ts.map +1 -0
  170. package/dist/terraform/emit-source.d.ts.map +1 -1
  171. package/dist/terraform/parse.d.ts.map +1 -1
  172. package/dist/testing.d.ts.map +1 -1
  173. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  174. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  175. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  176. package/dist/workspace/agent-cli.d.ts +88 -0
  177. package/dist/workspace/agent-cli.d.ts.map +1 -0
  178. package/dist/workspace/box-factory.d.ts +73 -0
  179. package/dist/workspace/box-factory.d.ts.map +1 -0
  180. package/dist/workspace/changes-cli.d.ts.map +1 -1
  181. package/dist/workspace/changes.d.ts +8 -1
  182. package/dist/workspace/changes.d.ts.map +1 -1
  183. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  184. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  185. package/dist/workspace/checks/links.d.ts +1 -0
  186. package/dist/workspace/checks/links.d.ts.map +1 -1
  187. package/dist/workspace/checks/live.d.ts +40 -0
  188. package/dist/workspace/checks/live.d.ts.map +1 -0
  189. package/dist/workspace/checks.d.ts +26 -2
  190. package/dist/workspace/checks.d.ts.map +1 -1
  191. package/dist/workspace/compose-graph.d.ts +73 -0
  192. package/dist/workspace/compose-graph.d.ts.map +1 -1
  193. package/dist/workspace/conformance/index.d.ts +67 -6
  194. package/dist/workspace/conformance/index.d.ts.map +1 -1
  195. package/dist/workspace/conformance/vitest.d.ts +12 -3
  196. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  197. package/dist/workspace/conformance/writer.d.ts +368 -0
  198. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  199. package/dist/workspace/decide.d.ts +1 -1
  200. package/dist/workspace/decide.d.ts.map +1 -1
  201. package/dist/workspace/declaration.d.ts +119 -0
  202. package/dist/workspace/declaration.d.ts.map +1 -1
  203. package/dist/workspace/declaration.schema.json +368 -4
  204. package/dist/workspace/export-cli.d.ts +12 -0
  205. package/dist/workspace/export-cli.d.ts.map +1 -0
  206. package/dist/workspace/export.d.ts +145 -0
  207. package/dist/workspace/export.d.ts.map +1 -0
  208. package/dist/workspace/generated-files.d.ts.map +1 -1
  209. package/dist/workspace/graph-cache.d.ts.map +1 -1
  210. package/dist/workspace/graph-cli.d.ts +2 -0
  211. package/dist/workspace/graph-cli.d.ts.map +1 -1
  212. package/dist/workspace/identity.d.ts +111 -0
  213. package/dist/workspace/identity.d.ts.map +1 -0
  214. package/dist/workspace/import.d.ts +73 -0
  215. package/dist/workspace/import.d.ts.map +1 -0
  216. package/dist/workspace/intent-cli.d.ts +7 -4
  217. package/dist/workspace/intent-cli.d.ts.map +1 -1
  218. package/dist/workspace/intent-record.d.ts +8 -1
  219. package/dist/workspace/intent-record.d.ts.map +1 -1
  220. package/dist/workspace/intent.d.ts +149 -5
  221. package/dist/workspace/intent.d.ts.map +1 -1
  222. package/dist/workspace/kinds.d.ts +24 -6
  223. package/dist/workspace/kinds.d.ts.map +1 -1
  224. package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
  225. package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
  226. package/dist/workspace/lineage-adopt.d.ts +106 -0
  227. package/dist/workspace/lineage-adopt.d.ts.map +1 -0
  228. package/dist/workspace/lineage-check.d.ts +9 -2
  229. package/dist/workspace/lineage-check.d.ts.map +1 -1
  230. package/dist/workspace/lineage-cli.d.ts +6 -1
  231. package/dist/workspace/lineage-cli.d.ts.map +1 -1
  232. package/dist/workspace/lineage-hash-index.d.ts +110 -0
  233. package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
  234. package/dist/workspace/lineage-init.d.ts +10 -0
  235. package/dist/workspace/lineage-init.d.ts.map +1 -1
  236. package/dist/workspace/lineage-lock.d.ts +149 -2
  237. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  238. package/dist/workspace/lineage-migrations.d.ts +15 -3
  239. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  240. package/dist/workspace/lineage-provenance.d.ts +18 -0
  241. package/dist/workspace/lineage-provenance.d.ts.map +1 -0
  242. package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
  243. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
  244. package/dist/workspace/lineage-upgrade.d.ts +30 -1
  245. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  246. package/dist/workspace/lineage-versions.d.ts +116 -0
  247. package/dist/workspace/lineage-versions.d.ts.map +1 -0
  248. package/dist/workspace/links.d.ts +31 -5
  249. package/dist/workspace/links.d.ts.map +1 -1
  250. package/dist/workspace/ls-generated.d.ts +37 -0
  251. package/dist/workspace/ls-generated.d.ts.map +1 -0
  252. package/dist/workspace/ls.d.ts +4 -0
  253. package/dist/workspace/ls.d.ts.map +1 -1
  254. package/dist/workspace/member-commands.d.ts.map +1 -1
  255. package/dist/workspace/nested-graph.d.ts +56 -0
  256. package/dist/workspace/nested-graph.d.ts.map +1 -0
  257. package/dist/workspace/nesting.d.ts +21 -0
  258. package/dist/workspace/nesting.d.ts.map +1 -0
  259. package/dist/workspace/pin-cli.d.ts +10 -0
  260. package/dist/workspace/pin-cli.d.ts.map +1 -0
  261. package/dist/workspace/pin-integrity.d.ts +51 -0
  262. package/dist/workspace/pin-integrity.d.ts.map +1 -0
  263. package/dist/workspace/points.d.ts.map +1 -1
  264. package/dist/workspace/principal-classes.d.ts +114 -0
  265. package/dist/workspace/principal-classes.d.ts.map +1 -0
  266. package/dist/workspace/reason-codes.d.ts +47 -3
  267. package/dist/workspace/reason-codes.d.ts.map +1 -1
  268. package/dist/workspace/record-assets.d.ts.map +1 -1
  269. package/dist/workspace/record-sessions.d.ts +7 -11
  270. package/dist/workspace/record-sessions.d.ts.map +1 -1
  271. package/dist/workspace/records-checkout.d.ts +54 -0
  272. package/dist/workspace/records-checkout.d.ts.map +1 -0
  273. package/dist/workspace/records-cli.d.ts +57 -2
  274. package/dist/workspace/records-cli.d.ts.map +1 -1
  275. package/dist/workspace/records-close.d.ts +5 -2
  276. package/dist/workspace/records-close.d.ts.map +1 -1
  277. package/dist/workspace/records-since.d.ts.map +1 -1
  278. package/dist/workspace/records-write.d.ts +23 -4
  279. package/dist/workspace/records-write.d.ts.map +1 -1
  280. package/dist/workspace/records.d.ts +58 -5
  281. package/dist/workspace/records.d.ts.map +1 -1
  282. package/dist/workspace/returns.d.ts +129 -0
  283. package/dist/workspace/returns.d.ts.map +1 -0
  284. package/dist/workspace/runs-cli.d.ts +164 -0
  285. package/dist/workspace/runs-cli.d.ts.map +1 -0
  286. package/dist/workspace/runs.d.ts +423 -0
  287. package/dist/workspace/runs.d.ts.map +1 -0
  288. package/dist/workspace/source-block.d.ts +1 -1
  289. package/dist/workspace/source-block.d.ts.map +1 -1
  290. package/dist/workspace/status-gates.d.ts +15 -3
  291. package/dist/workspace/status-gates.d.ts.map +1 -1
  292. package/dist/workspace/status.d.ts +10 -0
  293. package/dist/workspace/status.d.ts.map +1 -1
  294. package/dist/workspace/symbols.d.ts +52 -0
  295. package/dist/workspace/symbols.d.ts.map +1 -0
  296. package/dist/workspace/template-manifest.d.ts +11 -3
  297. package/dist/workspace/template-manifest.d.ts.map +1 -1
  298. package/dist/workspace/trailer-joins.d.ts +46 -0
  299. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  300. package/dist/workspace/trailers.d.ts +89 -0
  301. package/dist/workspace/trailers.d.ts.map +1 -0
  302. package/dist/workspace/trust/attestor.d.ts +8 -0
  303. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  304. package/dist/workspace/trust/dsse.d.ts +58 -0
  305. package/dist/workspace/trust/dsse.d.ts.map +1 -0
  306. package/dist/workspace/trust/evidence-cli.d.ts +66 -0
  307. package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
  308. package/dist/workspace/trust/evidence.d.ts +93 -0
  309. package/dist/workspace/trust/evidence.d.ts.map +1 -0
  310. package/dist/workspace/trust/policy.d.ts +54 -1
  311. package/dist/workspace/trust/policy.d.ts.map +1 -1
  312. package/dist/workspace/trust/provenance.d.ts +21 -1
  313. package/dist/workspace/trust/provenance.d.ts.map +1 -1
  314. package/dist/workspace/trust/rotation.d.ts +132 -0
  315. package/dist/workspace/trust/rotation.d.ts.map +1 -0
  316. package/dist/workspace/trust/seal.d.ts +33 -0
  317. package/dist/workspace/trust/seal.d.ts.map +1 -1
  318. package/dist/workspace/trust/signers-cli.d.ts +49 -0
  319. package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
  320. package/dist/workspace/trust/ssh-commit.d.ts +12 -0
  321. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  322. package/dist/workspace/trust/test-repo.d.ts +13 -0
  323. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  324. package/dist/workspace/trust/verify.d.ts +15 -0
  325. package/dist/workspace/trust/verify.d.ts.map +1 -1
  326. package/dist/workspace/work-cli.d.ts +39 -3
  327. package/dist/workspace/work-cli.d.ts.map +1 -1
  328. package/dist/workspace/work-evidence.d.ts +1 -1
  329. package/dist/workspace/work-evidence.d.ts.map +1 -1
  330. package/dist/workspace/work.d.ts +26 -1
  331. package/dist/workspace/work.d.ts.map +1 -1
  332. package/dist/workspace/workspace-principals.schema.json +60 -0
  333. package/dist/workspace/write-scope.d.ts +259 -0
  334. package/dist/workspace/write-scope.d.ts.map +1 -0
  335. package/dist/yaml.d.ts +21 -39
  336. package/dist/yaml.d.ts.map +1 -1
  337. package/package.json +2 -2
  338. package/src/audit/prior-art.ts +15 -0
  339. package/src/build.ts +8 -0
  340. package/src/cli/build-options.ts +4 -1
  341. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  342. package/src/cli/commands/build.ts +2 -0
  343. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  344. package/src/cli/commands/carve-emit.ts +5 -19
  345. package/src/cli/commands/check-lexicon.test.ts +62 -1
  346. package/src/cli/commands/check-lexicon.ts +36 -6
  347. package/src/cli/commands/import-live.test.ts +87 -1
  348. package/src/cli/commands/import-parser-options.test.ts +133 -0
  349. package/src/cli/commands/import.ts +69 -25
  350. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  351. package/src/cli/commands/init.test.ts +8 -1
  352. package/src/cli/commands/init.ts +23 -8
  353. package/src/cli/commands/lint.test.ts +37 -0
  354. package/src/cli/commands/onboard.test.ts +53 -3
  355. package/src/cli/commands/onboard.ts +52 -4
  356. package/src/cli/commands/pinned-upgrade.ts +73 -33
  357. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  358. package/src/cli/handlers/components.ts +2 -2
  359. package/src/cli/handlers/dev.ts +8 -6
  360. package/src/cli/handlers/fan-out.test.ts +6 -6
  361. package/src/cli/handlers/fan-out.ts +2 -1
  362. package/src/cli/handlers/graph.test.ts +42 -0
  363. package/src/cli/handlers/graph.ts +22 -0
  364. package/src/cli/handlers/lifecycle.test.ts +67 -0
  365. package/src/cli/handlers/lifecycle.ts +6 -5
  366. package/src/cli/handlers/misc.ts +2 -0
  367. package/src/cli/handlers/operator.ts +88 -4
  368. package/src/cli/handlers/run.test.ts +111 -0
  369. package/src/cli/handlers/run.ts +85 -24
  370. package/src/cli/handlers/scenario.ts +2 -2
  371. package/src/cli/main.test.ts +33 -0
  372. package/src/cli/main.ts +168 -17
  373. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  374. package/src/cli/mcp/workspace-tools.ts +62 -4
  375. package/src/cli/registry.ts +33 -2
  376. package/src/cli/serve-mcp-workspace.test.ts +1 -1
  377. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  378. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  379. package/src/codegen/pinned-upgrade.test.ts +104 -0
  380. package/src/codegen/pinned-upgrade.ts +250 -56
  381. package/src/codegen/release-wiring.test.ts +5 -1
  382. package/src/codegen/testdata/postgres-tags.json +242 -0
  383. package/src/components/fan-out-output.test.ts +1 -1
  384. package/src/components/fan-out.test.ts +1 -1
  385. package/src/components/promote.test.ts +1 -1
  386. package/src/config.test.ts +21 -0
  387. package/src/config.ts +27 -0
  388. package/src/content-digest.test.ts +2 -2
  389. package/src/declarable.test.ts +21 -0
  390. package/src/declarable.ts +19 -0
  391. package/src/discovery/fold-composite.test.ts +41 -5
  392. package/src/discovery/fold-import.test.ts +129 -0
  393. package/src/discovery/fold-import.ts +643 -131
  394. package/src/discovery/fold-package-composite.test.ts +264 -0
  395. package/src/discovery/fold-specifiers.test.ts +191 -0
  396. package/src/discovery/param-deps.test.ts +66 -1
  397. package/src/discovery/param-deps.ts +45 -0
  398. package/src/discovery/specifier-resolve.ts +344 -0
  399. package/src/fold/fold.test.ts +39 -1
  400. package/src/fold/fold.ts +83 -9
  401. package/src/fold-provenance.test.ts +140 -0
  402. package/src/graph-ir.test.ts +83 -0
  403. package/src/graph-ir.ts +29 -0
  404. package/src/import/embedded.test.ts +20 -0
  405. package/src/import/embedded.ts +13 -0
  406. package/src/import/parser-options.ts +46 -0
  407. package/src/import/parser.ts +20 -0
  408. package/src/lexicon.ts +79 -3
  409. package/src/lifecycle/gate-ledger.test.ts +14 -0
  410. package/src/lifecycle/gate-ledger.ts +18 -1
  411. package/src/lifecycle/observe.test.ts +18 -0
  412. package/src/lifecycle/observe.ts +6 -4
  413. package/src/lifecycle/plan-digest.test.ts +54 -3
  414. package/src/lifecycle/plan-digest.ts +38 -8
  415. package/src/lifecycle/snapshot.ts +2 -2
  416. package/src/lifecycle/work-lease.test.ts +1 -1
  417. package/src/lifecycle/work-lease.ts +93 -2
  418. package/src/lint/engine.test.ts +55 -0
  419. package/src/lint/engine.ts +30 -4
  420. package/src/lint/output-docs.test.ts +59 -56
  421. package/src/op/activities/activity-contracts.ts +1 -0
  422. package/src/op/activities/apply.test.ts +189 -5
  423. package/src/op/activities/apply.ts +230 -8
  424. package/src/op/activities/index.ts +3 -0
  425. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  426. package/src/op/activities/lexicon-upgrade.ts +154 -51
  427. package/src/op/activities/propose-upgrade.ts +8 -5
  428. package/src/op/composites/apply-op.ts +12 -5
  429. package/src/op/composites/composites.test.ts +30 -3
  430. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  431. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  432. package/src/op/emulator-lifecycle.test.ts +23 -0
  433. package/src/op/emulator-lifecycle.ts +52 -14
  434. package/src/op/gate-approval.test.ts +91 -0
  435. package/src/op/gate-approval.ts +5 -1
  436. package/src/op/gate.ts +34 -8
  437. package/src/op/index.ts +3 -1
  438. package/src/op/local-executor.test.ts +15 -0
  439. package/src/op/local-executor.ts +40 -12
  440. package/src/op/local-output.ts +1 -1
  441. package/src/op/operator.ts +4 -0
  442. package/src/op/run-context.test.ts +141 -0
  443. package/src/op/run-context.ts +51 -0
  444. package/src/op/runtimes/local.test.ts +14 -0
  445. package/src/op/runtimes/local.ts +2 -0
  446. package/src/op/steward-beside.ts +2 -0
  447. package/src/op/types.ts +8 -3
  448. package/src/op/work-lease-run.test.ts +47 -0
  449. package/src/op/work-lease-run.ts +73 -11
  450. package/src/provenance.ts +35 -0
  451. package/src/serializer.ts +9 -0
  452. package/src/telemetry-attribution.test.ts +91 -0
  453. package/src/telemetry-attribution.ts +145 -0
  454. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  455. package/src/workspace/agent-cli.ts +138 -0
  456. package/src/workspace/agent.schema.json +378 -0
  457. package/src/workspace/behold-kinds.test.ts +1 -1
  458. package/src/workspace/box-factory.test.ts +320 -0
  459. package/src/workspace/box-factory.ts +103 -0
  460. package/src/workspace/changes-cli.ts +5 -0
  461. package/src/workspace/changes.schema.json +171 -1
  462. package/src/workspace/changes.ts +61 -4
  463. package/src/workspace/check-live.test.ts +192 -0
  464. package/src/workspace/check.schema.json +64 -0
  465. package/src/workspace/checks/boxes.test.ts +2 -0
  466. package/src/workspace/checks/links.ts +23 -2
  467. package/src/workspace/checks/live.ts +113 -0
  468. package/src/workspace/checks.test.ts +2 -2
  469. package/src/workspace/checks.ts +49 -8
  470. package/src/workspace/compose-graph.test.ts +47 -0
  471. package/src/workspace/compose-graph.ts +127 -3
  472. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
  473. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
  474. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  475. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  476. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  477. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  478. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  479. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  480. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  481. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  482. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  483. package/src/workspace/conformance/index.mjs +30 -3
  484. package/src/workspace/conformance/index.ts +103 -15
  485. package/src/workspace/conformance/vitest.ts +44 -2
  486. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  487. package/src/workspace/conformance/writer.test.ts +156 -0
  488. package/src/workspace/conformance/writer.ts +880 -0
  489. package/src/workspace/decide.ts +7 -1
  490. package/src/workspace/declaration.schema.json +368 -4
  491. package/src/workspace/declaration.ts +270 -1
  492. package/src/workspace/declared-kinds.test.ts +33 -0
  493. package/src/workspace/evidence.schema.json +279 -0
  494. package/src/workspace/export-cli.ts +180 -0
  495. package/src/workspace/export-import.test.ts +282 -0
  496. package/src/workspace/export.ts +486 -0
  497. package/src/workspace/graph-cli.ts +51 -2
  498. package/src/workspace/graph-contract.test.ts +89 -4
  499. package/src/workspace/graph.schema.json +453 -1
  500. package/src/workspace/identity.test.ts +196 -0
  501. package/src/workspace/identity.ts +186 -0
  502. package/src/workspace/import.ts +325 -0
  503. package/src/workspace/intent-cli.ts +64 -10
  504. package/src/workspace/intent-record.schema.json +208 -0
  505. package/src/workspace/intent-record.ts +35 -1
  506. package/src/workspace/intent-why.test.ts +274 -0
  507. package/src/workspace/intent.schema.json +718 -7
  508. package/src/workspace/intent.test.ts +11 -2
  509. package/src/workspace/intent.ts +495 -32
  510. package/src/workspace/kinds.test.ts +5 -5
  511. package/src/workspace/kinds.ts +62 -33
  512. package/src/workspace/lineage-adopt-cli.ts +103 -0
  513. package/src/workspace/lineage-adopt.test.ts +552 -0
  514. package/src/workspace/lineage-adopt.ts +452 -0
  515. package/src/workspace/lineage-check.ts +26 -5
  516. package/src/workspace/lineage-cli.ts +12 -2
  517. package/src/workspace/lineage-hash-index.ts +305 -0
  518. package/src/workspace/lineage-init.test.ts +9 -0
  519. package/src/workspace/lineage-init.ts +29 -9
  520. package/src/workspace/lineage-lock.ts +54 -0
  521. package/src/workspace/lineage-migrations.test.ts +27 -0
  522. package/src/workspace/lineage-migrations.ts +44 -14
  523. package/src/workspace/lineage-provenance.ts +40 -0
  524. package/src/workspace/lineage-upgrade-cli.ts +9 -5
  525. package/src/workspace/lineage-upgrade.test.ts +92 -1
  526. package/src/workspace/lineage-upgrade.ts +111 -16
  527. package/src/workspace/lineage-versions.ts +348 -0
  528. package/src/workspace/links.test.ts +121 -2
  529. package/src/workspace/links.ts +97 -6
  530. package/src/workspace/ls-contract.test.ts +84 -1
  531. package/src/workspace/ls-generated.ts +111 -0
  532. package/src/workspace/ls.schema.json +19 -0
  533. package/src/workspace/ls.ts +8 -1
  534. package/src/workspace/member-commands.ts +3 -1
  535. package/src/workspace/nested-graph.test.ts +176 -0
  536. package/src/workspace/nested-graph.ts +169 -0
  537. package/src/workspace/nesting.ts +37 -0
  538. package/src/workspace/pin-cli.test.ts +71 -0
  539. package/src/workspace/pin-cli.ts +57 -0
  540. package/src/workspace/pin-integrity.test.ts +121 -0
  541. package/src/workspace/pin-integrity.ts +104 -0
  542. package/src/workspace/points-write.schema.json +2 -0
  543. package/src/workspace/principal-classes.test.ts +175 -0
  544. package/src/workspace/principal-classes.ts +307 -0
  545. package/src/workspace/read-contract.test.ts +24 -0
  546. package/src/workspace/reason-codes.test.ts +20 -2
  547. package/src/workspace/reason-codes.ts +53 -3
  548. package/src/workspace/record-sessions.ts +12 -14
  549. package/src/workspace/records-amend.schema.json +6 -0
  550. package/src/workspace/records-checkout.ts +106 -0
  551. package/src/workspace/records-cli.ts +195 -22
  552. package/src/workspace/records-close.schema.json +7 -2
  553. package/src/workspace/records-close.ts +10 -2
  554. package/src/workspace/records-formats.test.ts +13 -5
  555. package/src/workspace/records-new.schema.json +6 -0
  556. package/src/workspace/records-review.schema.json +6 -0
  557. package/src/workspace/records-sessions.test.ts +23 -6
  558. package/src/workspace/records-uncommitted.test.ts +171 -0
  559. package/src/workspace/records-write.test.ts +65 -8
  560. package/src/workspace/records-write.ts +92 -6
  561. package/src/workspace/records.schema.json +127 -4
  562. package/src/workspace/records.ts +123 -5
  563. package/src/workspace/returns.ts +328 -0
  564. package/src/workspace/runs-cli.ts +381 -0
  565. package/src/workspace/runs-write.schema.json +583 -0
  566. package/src/workspace/runs.schema.json +810 -0
  567. package/src/workspace/runs.test.ts +242 -0
  568. package/src/workspace/runs.ts +709 -0
  569. package/src/workspace/signers.schema.json +206 -0
  570. package/src/workspace/status-contract.test.ts +5 -0
  571. package/src/workspace/status-gates.ts +24 -9
  572. package/src/workspace/status.schema.json +127 -2
  573. package/src/workspace/status.ts +21 -1
  574. package/src/workspace/symbols.ts +180 -0
  575. package/src/workspace/template-manifest.ts +22 -4
  576. package/src/workspace/trailer-joins.test.ts +171 -0
  577. package/src/workspace/trailer-joins.ts +82 -0
  578. package/src/workspace/trailers.ts +148 -0
  579. package/src/workspace/trust/attestor.ts +15 -0
  580. package/src/workspace/trust/dsse.ts +134 -0
  581. package/src/workspace/trust/evidence-cli.ts +195 -0
  582. package/src/workspace/trust/evidence.test.ts +241 -0
  583. package/src/workspace/trust/evidence.ts +207 -0
  584. package/src/workspace/trust/policy.ts +110 -3
  585. package/src/workspace/trust/provenance.ts +41 -4
  586. package/src/workspace/trust/record-seal.test.ts +1 -1
  587. package/src/workspace/trust/rotation.test.ts +258 -0
  588. package/src/workspace/trust/rotation.ts +336 -0
  589. package/src/workspace/trust/seal.ts +63 -1
  590. package/src/workspace/trust/signers-cli.ts +178 -0
  591. package/src/workspace/trust/ssh-commit.ts +53 -4
  592. package/src/workspace/trust/test-repo.ts +18 -0
  593. package/src/workspace/trust/trust.test.ts +8 -2
  594. package/src/workspace/trust/verify-cli.ts +1 -0
  595. package/src/workspace/trust/verify.ts +22 -0
  596. package/src/workspace/work-cli.e2e.test.ts +28 -1
  597. package/src/workspace/work-cli.ts +133 -8
  598. package/src/workspace/work-evidence.schema.json +6 -0
  599. package/src/workspace/work-history.schema.json +73 -1
  600. package/src/workspace/work-history.test.ts +48 -5
  601. package/src/workspace/work-kind-fields.test.ts +154 -0
  602. package/src/workspace/work-lease.schema.json +14 -1
  603. package/src/workspace/work.ts +82 -0
  604. package/src/workspace/workspace-principals.schema.json +60 -0
  605. package/src/workspace/write-scope.test.ts +466 -0
  606. package/src/workspace/write-scope.ts +573 -0
  607. package/src/yaml.test.ts +111 -1
  608. package/src/yaml.ts +141 -549
@@ -29,6 +29,7 @@ import { stepTimeoutProblem } from "./activity-profiles";
29
29
  import { describeGateMismatch, evaluateGate, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "./gate";
30
30
  import { gateName } from "./gate-name";
31
31
  import type { ResolvedGateApproval } from "./gate-approval";
32
+ import { withOpRunContext, type OpRunContext, type PassedGate } from "./run-context";
32
33
  import type { PendingGateRecord } from "../lifecycle/gate-ledger";
33
34
  import { isPointWait, type WaitingPoint } from "./steward-points";
34
35
  import { isGateWait } from "./gate-wait";
@@ -416,9 +417,11 @@ async function runStep(
416
417
  if (step.id) resultsById.set(step.id, result);
417
418
  const record: StepRecord = { ...base, status: "ok", durationMs: Date.now() - start };
418
419
  const attrs = outcomeAttributesOf(step);
419
- if (attrs.length > 0) {
420
- record.outcomes = attrs.map((a) => ({ name: a.name, value: resolvePath(result, a.from) }));
421
- record.outcome = record.outcomes[0];
420
+ // An attribute whose path the result does not hold is left off, rather than recorded as undefined.
421
+ const outcomes = attrs.map((a) => ({ name: a.name, value: resolvePath(result, a.from) })).filter((o) => o.value !== undefined);
422
+ if (outcomes.length > 0) {
423
+ record.outcomes = outcomes;
424
+ record.outcome = outcomes[0];
422
425
  }
423
426
  return { record, result };
424
427
  } catch (err) {
@@ -482,6 +485,8 @@ interface GateContext {
482
485
  runId?: string;
483
486
  /** Called once per settled step, in production order (#2121) — what `--progress-json` streams from. */
484
487
  onRecord?: (record: StepRecord) => void;
488
+ /** The run context's list of passed gates (#2522). A gate that passes is appended here. */
489
+ passed?: PassedGate[];
485
490
  /** The run's hold on its work item's lease (#2748). Absent in compensation phases, which run whatever the lease did. */
486
491
  work?: RunWorkLease;
487
492
  }
@@ -643,6 +648,17 @@ async function runGateStep(
643
648
 
644
649
  if (check.satisfied) {
645
650
  const { resolution } = check;
651
+ const passedApproval: NonNullable<StepRecord["approval"]> = {
652
+ gate: gateName(step),
653
+ resolvedBy: resolution.resolvedBy,
654
+ timestamp: resolution.timestamp,
655
+ ...(resolution.url ? { url: resolution.url } : {}),
656
+ ...(check.via ? { via: check.via } : {}),
657
+ ...(check.approvals ? { approvers: check.approvals.map((r) => r.resolvedBy) } : {}),
658
+ };
659
+ // #2522: a later step reads this gate's approval from the run context
660
+ // rather than re-tallying the ledger, so it sees the decision this run made.
661
+ gates.passed?.push({ gate: gateName(step), approval: passedApproval });
646
662
  return {
647
663
  record: {
648
664
  phase: phaseName,
@@ -650,14 +666,7 @@ async function runGateStep(
650
666
  args: {},
651
667
  status: "ok",
652
668
  durationMs: Date.now() - start,
653
- approval: {
654
- gate: gateName(step),
655
- resolvedBy: resolution.resolvedBy,
656
- timestamp: resolution.timestamp,
657
- ...(resolution.url ? { url: resolution.url } : {}),
658
- ...(check.via ? { via: check.via } : {}),
659
- ...(check.approvals ? { approvers: check.approvals.map((r) => r.resolvedBy) } : {}),
660
- },
669
+ approval: passedApproval,
661
670
  },
662
671
  };
663
672
  }
@@ -971,6 +980,12 @@ export interface RunOpOptions {
971
980
  now?: string;
972
981
  /** Identifies this run on any pending fact it records. */
973
982
  runId?: string;
983
+ /**
984
+ * The environment the run was started for (`--env`, #2522). The executor
985
+ * only hands it to activities through the run context (`./run-context.ts`).
986
+ * The run ledger path still comes from the Op's `labels.Env`.
987
+ */
988
+ env?: string;
974
989
  /**
975
990
  * Called once per settled step, in the order the records are produced
976
991
  * (#2121) — what the local op runtime (./runtimes/local.ts) feeds
@@ -1039,6 +1054,13 @@ export async function runOpLocally(
1039
1054
  const turn = currentStewardTurn();
1040
1055
  const steward = options.steward ?? turn?.steward;
1041
1056
  const restore = turn && turn.run !== runId ? enterStewardTurn({ ...turn, run: runId }) : undefined;
1057
+ // #2522: every activity in this run can read the run it is in.
1058
+ const run: OpRunContext = {
1059
+ op: config.name,
1060
+ runId,
1061
+ ...(options.env !== undefined ? { env: options.env } : {}),
1062
+ passedGates: [],
1063
+ };
1042
1064
  // A run that writes to the ledger also says what it is doing while it runs
1043
1065
  // (./run-live.ts), and removes that once its record is written.
1044
1066
  const live = options.ledger
@@ -1052,7 +1074,11 @@ export async function runOpLocally(
1052
1074
  })
1053
1075
  : undefined;
1054
1076
  try {
1055
- return await withLiveRun(live, () => runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) }));
1077
+ return await withLiveRun(live, () =>
1078
+ withOpRunContext(run, () =>
1079
+ runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) }, run),
1080
+ ),
1081
+ );
1056
1082
  } finally {
1057
1083
  live?.close();
1058
1084
  restore?.();
@@ -1065,6 +1091,7 @@ async function runOpInTurn(
1065
1091
  profiles: Record<string, ActivityProfile>,
1066
1092
  signal: AbortSignal | undefined,
1067
1093
  options: RunOpOptions & { runId: string },
1094
+ run: OpRunContext,
1068
1095
  ): Promise<OpRunResult> {
1069
1096
  const runId = options.runId;
1070
1097
 
@@ -1074,6 +1101,7 @@ async function runOpInTurn(
1074
1101
  ...(options.now ? { now: options.now } : {}),
1075
1102
  runId,
1076
1103
  ...(options.onRecord ? { onRecord: options.onRecord } : {}),
1104
+ passed: run.passedGates,
1077
1105
  };
1078
1106
 
1079
1107
  // Effect steps are ordered (read-compare-run-write): refuse them in a
@@ -66,7 +66,7 @@ export function renderHuman(result: OpRunResult, write: Writer = stderr): void {
66
66
  : work.released
67
67
  ? `released ${work.outcome}`
68
68
  : "not released; it runs out on its own";
69
- write(`[work] ${work.item} held by ${work.holder}${work.branch ? ` on ${work.branch}` : ""}: ${end}`);
69
+ write(`[work] ${work.item} held by ${work.holder}${work.branch ? ` on ${work.branch}` : ""}: ${end}${work.kept ? `; the attempt is kept at ${work.kept}` : ""}`);
70
70
  } else if (work?.refusal) {
71
71
  write(`[work] nothing claimed: ${work.refusal}`);
72
72
  }
@@ -577,6 +577,10 @@ export async function runOperatorRound(opts: OperatorRoundOptions): Promise<Oper
577
577
  try {
578
578
  const result = await runOpLocally(config, opts.activities, opts.profiles, opts.signal, {
579
579
  runId,
580
+ // #2522: the environment the operator was started for (`--env`, or a
581
+ // steward's environment) reaches the activities through the run
582
+ // context, as `chant run --env` does.
583
+ ...(opts.env ?? opts.stewardEnv ? { env: opts.env ?? opts.stewardEnv } : {}),
580
584
  ...(steward ? { steward: steward.name } : {}),
581
585
  ledger: { cwd: opts.cwd },
582
586
  // A steward's turn claims work leases as `<steward>/<op>@<holder>`
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The run context an activity reads (#2522): the Op, the run id the run
3
+ * ledger records, `--env`, and the gates the run has passed.
4
+ */
5
+ import { describe, test, expect } from "vitest";
6
+ import type { OpConfig } from "./types";
7
+ import type { ActivityFn, ActivityProfile } from "./activity-registry";
8
+ import { runOpLocally } from "./local-executor";
9
+ import { memoryGateLedgerPort } from "./gate";
10
+ import { currentOpRun, type OpRunContext } from "./run-context";
11
+ import { gatePolicyVersion, type GatePolicyRef, type ResolvedGateApproval } from "./gate-approval";
12
+ import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
13
+
14
+ const PROFILES: Record<string, ActivityProfile> = {
15
+ fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
16
+ };
17
+ const NOW = "2026-09-05T12:00:00.000Z";
18
+
19
+ function op(steps: OpConfig["phases"][number]["steps"]): OpConfig {
20
+ return { name: "release", overview: "", phases: [{ name: "main", steps }] };
21
+ }
22
+
23
+ describe("the Op run context (#2522)", () => {
24
+ test("an activity reads the Op, the run id and --env", async () => {
25
+ let seen: OpRunContext | undefined;
26
+ const activities = new Map<string, ActivityFn>([
27
+ ["deploy", async () => { await Promise.resolve(); seen = currentOpRun(); }],
28
+ ]);
29
+ const result = await runOpLocally(op([{ kind: "activity", fn: "deploy" }]), activities, PROFILES, undefined, {
30
+ runId: "local-123",
31
+ env: "staging",
32
+ });
33
+ expect(seen).toEqual({ op: "release", runId: "local-123", env: "staging", passedGates: [] });
34
+ expect(result.record?.id).toBe("local-123");
35
+ });
36
+
37
+ test("without --env the context carries none, and outside a run there is no context", async () => {
38
+ let seen: OpRunContext | undefined;
39
+ const activities = new Map<string, ActivityFn>([["deploy", async () => { seen = currentOpRun(); }]]);
40
+ await runOpLocally(op([{ kind: "activity", fn: "deploy" }]), activities, PROFILES, undefined, { runId: "r1" });
41
+ expect(seen).toEqual({ op: "release", runId: "r1", passedGates: [] });
42
+ expect(currentOpRun()).toBeUndefined();
43
+ });
44
+
45
+ test("a step after a passed gate reads the gate's approval", async () => {
46
+ const seen: OpRunContext[] = [];
47
+ const activities = new Map<string, ActivityFn>([
48
+ ["plan", async () => { seen.push(currentOpRun()!); }],
49
+ ["apply", async () => { seen.push(currentOpRun()!); }],
50
+ ]);
51
+ const port = memoryGateLedgerPort({
52
+ pending: [{
53
+ version: 1, kind: "pending", op: "release", gate: "ship",
54
+ timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
55
+ }],
56
+ resolutions: [{ version: 1, op: "release", gate: "ship", resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z" }],
57
+ });
58
+ const result = await runOpLocally(
59
+ op([
60
+ { kind: "activity", fn: "plan" },
61
+ { kind: "gate", gate: "ship" },
62
+ { kind: "activity", fn: "apply" },
63
+ ]),
64
+ activities,
65
+ PROFILES,
66
+ undefined,
67
+ { gates: port, now: NOW, runId: "r2" },
68
+ );
69
+ expect(result.status).toBe("ok");
70
+ expect(seen[0]!.passedGates).toEqual([]);
71
+ expect(seen[1]!.passedGates).toEqual([
72
+ { gate: "ship", approval: { gate: "ship", resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z" } },
73
+ ]);
74
+ expect(seen[1]!.passedGates[0]!.approval).toEqual(result.records.find((r) => r.fn === "gate:ship")?.approval);
75
+ });
76
+
77
+ describe("a policy permit and the gates a run passed (#2512)", () => {
78
+ const TEXT = 'permit (principal is Chant::Agent, action, resource) when { context.risk == "low" };\n';
79
+ const POLICY: GatePolicyRef = { kind: "gate-policy", lexicon: "cedar", name: "ship", version: gatePolicyVersion(TEXT), text: TEXT };
80
+ const enforce: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
81
+ const permit = (mode: "log-only" | "enforce"): GateResolutionRecord => ({
82
+ version: 1, op: "release", gate: "ship", resolvedBy: "release-bot", timestamp: "2026-09-05T11:00:00.000Z",
83
+ approver: { kind: "agent" },
84
+ policyDecision: { policy: "ship", version: POLICY.version, mode, decision: "allow", determining: ["ship-0"], errors: [] },
85
+ });
86
+
87
+ async function run(mode: "log-only" | "enforce") {
88
+ const seen: OpRunContext[] = [];
89
+ const activities = new Map<string, ActivityFn>([["apply", async () => { seen.push(currentOpRun()!); }]]);
90
+ const port = memoryGateLedgerPort({
91
+ pending: [{
92
+ version: 1, kind: "pending", op: "release", gate: "ship",
93
+ timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
94
+ approval: { ...enforce, mode },
95
+ }],
96
+ resolutions: [permit(mode)],
97
+ });
98
+ const result = await runOpLocally(
99
+ op([
100
+ { kind: "gate", gate: "ship", approval: { policy: POLICY, mode: "enforce" } },
101
+ { kind: "activity", fn: "apply" },
102
+ ]),
103
+ activities,
104
+ PROFILES,
105
+ undefined,
106
+ { gates: port, now: NOW, runId: "r3" },
107
+ );
108
+ return { result, seen };
109
+ }
110
+
111
+ test("an enforce-mode permit passes the gate, and the next step sees it as passed by policy", async () => {
112
+ const { result, seen } = await run("enforce");
113
+ expect(result.status).toBe("ok");
114
+ expect(seen[0]!.passedGates.map((g) => [g.gate, g.approval.via])).toEqual([["ship", "policy"]]);
115
+ });
116
+
117
+ test("a permit recorded under log-only does not pass the enforced gate, so no step sees it as passed", async () => {
118
+ const { result, seen } = await run("log-only");
119
+ expect(result.status).toBe("gated");
120
+ expect(seen).toEqual([]);
121
+ expect(result.records.find((r) => r.fn === "gate:ship")?.approval).toBeUndefined();
122
+ });
123
+ });
124
+
125
+ test("two runs in flight at once each see their own context", async () => {
126
+ const seen = new Map<string, string | undefined>();
127
+ const activities = new Map<string, ActivityFn>([
128
+ ["deploy", async () => {
129
+ await new Promise((r) => setTimeout(r, 5));
130
+ const ctx = currentOpRun()!;
131
+ seen.set(ctx.runId, ctx.env);
132
+ }],
133
+ ]);
134
+ const config = op([{ kind: "activity", fn: "deploy" }]);
135
+ await Promise.all([
136
+ runOpLocally(config, activities, PROFILES, undefined, { runId: "a", env: "staging" }),
137
+ runOpLocally(config, activities, PROFILES, undefined, { runId: "b", env: "prod" }),
138
+ ]);
139
+ expect(Object.fromEntries(seen)).toEqual({ a: "staging", b: "prod" });
140
+ });
141
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The run an activity is part of (#2522).
3
+ *
4
+ * An activity is called as `fn(args, signal)`, which says nothing about the
5
+ * run around it. An activity that deploys somewhere needs the environment the
6
+ * run was asked for, one that appends a ledger record of its own needs the run
7
+ * id to name the run that made it, and a step after a gate may need to know the
8
+ * gate passed and who approved it.
9
+ *
10
+ * `runOpLocally` (`./local-executor.ts`) sets this for the whole run, and
11
+ * {@link currentOpRun} reads it from inside an activity. It is backed by
12
+ * `AsyncLocalStorage`, so it follows the activity through every `await` and is
13
+ * `undefined` outside a run.
14
+ */
15
+
16
+ import { AsyncLocalStorage } from "node:async_hooks";
17
+ import type { StepRecord } from "./local-executor";
18
+
19
+ /** A gate this run has passed, with the approval that passed it. */
20
+ export interface PassedGate {
21
+ gate: string;
22
+ approval: NonNullable<StepRecord["approval"]>;
23
+ }
24
+
25
+ /** What an activity can read about the run it is in. */
26
+ export interface OpRunContext {
27
+ /** The Op's name. */
28
+ op: string;
29
+ /** The run id the run ledger record carries. */
30
+ runId: string;
31
+ /** `--env`, as handed to `OpRuntimeProvider.start`. Absent when the run was started without one. */
32
+ env?: string;
33
+ /** Every gate this run has passed so far, in the order it passed them. */
34
+ passedGates: PassedGate[];
35
+ }
36
+
37
+ const store = new AsyncLocalStorage<OpRunContext>();
38
+
39
+ /**
40
+ * The run the calling activity is part of, or `undefined` outside a run.
41
+ * `passedGates` is a copy taken at the time of the call.
42
+ */
43
+ export function currentOpRun(): OpRunContext | undefined {
44
+ const ctx = store.getStore();
45
+ return ctx ? { ...ctx, passedGates: [...ctx.passedGates] } : undefined;
46
+ }
47
+
48
+ /** Run `fn` with `ctx` as the current run. The executor calls this. A runtime that calls activities itself can too. */
49
+ export function withOpRunContext<T>(ctx: OpRunContext, fn: () => T): T {
50
+ return store.run(ctx, fn);
51
+ }
@@ -81,6 +81,20 @@ describe("the local op runtime", () => {
81
81
  expect(status.records?.map((r) => r.fn)).toEqual(["ok"]);
82
82
  });
83
83
 
84
+ test("an activity reads --env and the run id the ledger records (#2522)", async () => {
85
+ const { currentOpRun } = await import("../run-context");
86
+ let seen: ReturnType<typeof currentOpRun>;
87
+ loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => { seen = currentOpRun(); })]]));
88
+ const runtime = createLocalOpRuntime();
89
+
90
+ const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { env: "staging" });
91
+ const status = await handle.result();
92
+
93
+ expect(seen?.env).toBe("staging");
94
+ expect(seen?.runId).toBe(handle.runId);
95
+ expect(status.result?.record?.id).toBe(handle.runId);
96
+ });
97
+
84
98
  test("the project's configured lexicons decide which appliers load", async () => {
85
99
  loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
86
100
  const runtime = createLocalOpRuntime();
@@ -152,6 +152,8 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
152
152
  startOpts.signal,
153
153
  {
154
154
  runId,
155
+ // #2522: `--env` reaches the activities through the run context.
156
+ ...(startOpts.env !== undefined ? { env: startOpts.env } : {}),
155
157
  // The run's outcome is a durable fact (#2118): the executor
156
158
  // appends it here, at the one seam every local run passes
157
159
  // through, rather than in the CLI handler above it.
@@ -118,6 +118,8 @@ export function inProcessBesideLauncher(
118
118
  try {
119
119
  const result = await runOpLocally(start.op, activities, profiles, controller.signal, {
120
120
  steward: start.steward,
121
+ // #2522: the same `--env` the spawned launcher passes on the command line.
122
+ ...(start.env ? { env: start.env } : {}),
121
123
  ledger: { cwd: start.cwd },
122
124
  work: { holder: start.holder },
123
125
  });
package/src/op/types.ts CHANGED
@@ -91,8 +91,12 @@ export interface OpWorkLease {
91
91
  ttl?: string;
92
92
  /**
93
93
  * How the release records a run that ended `ok`: a reference to a step
94
- * output holding a short string (`done`, `not_done`, ...). Default `done`;
95
- * a failed run releases `not_done` and a gated one `gated`.
94
+ * output holding one of the closed list of lease outcomes
95
+ * (`WORK_LEASE_OUTCOMES` in `../lifecycle/work-lease.ts`, #3147: `done`,
96
+ * `not_done`, `abandoned`, `gated`, `waiting`, `dropped`, `redraft`,
97
+ * `ask`). Default `done`; a failed run releases `not_done`, a gated one
98
+ * `gated` and one waiting on a decision point `waiting`. A word outside
99
+ * the list is released `not_done`, with a note naming it.
96
100
  */
97
101
  outcome?: StepOutputRef;
98
102
  }
@@ -178,7 +182,8 @@ export interface ActivityStep {
178
182
  *
179
183
  * `from` is a dot-path into the return value (e.g. `"drifted"` for
180
184
  * `{ drifted: boolean }`); when omitted, the whole return value is
181
- * recorded.
185
+ * recorded. An attribute whose path the result does not hold (an optional
186
+ * field left out) is not recorded, rather than recorded as `undefined`.
182
187
  */
183
188
  outcomeAttribute?: OutcomeAttribute | OutcomeAttribute[];
184
189
  }
@@ -342,4 +342,51 @@ describe("an Op that changes the checkout", () => {
342
342
  expect(git(["status", "--porcelain"], dir).stdout).toBe(" M app/index.ts\n");
343
343
  });
344
344
  });
345
+
346
+ test("a run that ends not_done keeps its attempt at refs/chant/kept/<item>/<token>, committed and uncommitted work, and the branch goes back (#3147)", async () => {
347
+ await withTestDir(async (dir) => {
348
+ await initRepo(dir);
349
+ const head = git(["rev-parse", "HEAD"], dir).stdout.trim();
350
+ const activities = new Map<string, ActivityFn>([
351
+ [
352
+ "build",
353
+ async (args) => {
354
+ const worktree = String(args.worktree);
355
+ writeFileSync(join(worktree, "app", "feature.ts"), "export const feature = true;\n");
356
+ expect(git(["add", "-A"], worktree).status).toBe(0);
357
+ expect(git(["commit", "-q", "-m", "half a build"], worktree).status).toBe(0);
358
+ writeFileSync(join(worktree, "app", "notes.md"), "left uncommitted\n");
359
+ throw new Error("the check fails");
360
+ },
361
+ ],
362
+ ]);
363
+ const result = await run(dispatchOp({ workLease: { item: "W-8" }, changesCheckout: true }, [step("build", { worktree: workLeaseOutput("worktree") })]), activities, dir);
364
+ expect(result.status).toBe("fail");
365
+ const token = result.workLease!.token!;
366
+ const kept = `refs/chant/kept/W-8/${token}`;
367
+ expect(result.workLease).toMatchObject({ item: "W-8", released: true, outcome: "not_done", kept });
368
+ // Both the commit and the uncommitted file are in the kept attempt; the branch is where the run started.
369
+ expect(git(["show", `${kept}:app/feature.ts`], dir).stdout).toBe("export const feature = true;\n");
370
+ expect(git(["show", `${kept}:app/notes.md`], dir).stdout).toBe("left uncommitted\n");
371
+ expect(git(["rev-parse", "chant/work/W-8"], dir).stdout.trim()).toBe(head);
372
+ expect(git(["rev-parse", "HEAD"], dir).stdout.trim()).toBe(head);
373
+ });
374
+ });
375
+
376
+ test("a run whose step names a lease outcome releases it, and keeps nothing when it changed nothing; a word outside the list is not_done (#3147)", async () => {
377
+ await withTestDir(async (dir) => {
378
+ await initRepo(dir);
379
+ let word = "dropped";
380
+ const activities = new Map<string, ActivityFn>([["record", async () => ({ outcome: word })]]);
381
+ const op = () => dispatchOp({ workLease: { item: "W-9", outcome: stepOutput("record", "outcome") }, changesCheckout: true }, [step("record", {}, { id: "record" })]);
382
+ const dropped = await run(op(), activities, dir);
383
+ expect(dropped.workLease).toMatchObject({ released: true, outcome: "dropped", kept: null });
384
+ word = "skipped";
385
+ const other = await run(op(), activities, dir);
386
+ expect(other.workLease).toMatchObject({ released: true, outcome: "not_done", kept: null });
387
+ const last = (await readLeaseHistory("W-9", { cwd: dir })).records.at(-1);
388
+ expect(last).toMatchObject({ event: "release", outcome: "not_done" });
389
+ expect(last?.note).toMatch(/"skipped" is not a work lease outcome/);
390
+ });
391
+ });
345
392
  });
@@ -24,9 +24,11 @@
24
24
  * the lease is still this run's, with the same token. Refused, the run is
25
25
  * recorded as failed with `lease-lost`, never as done.
26
26
  * - Release. When the run ends, the lease is given back with the run's
27
- * outcome (`done`, `not_done`, `gated`, or the Op's own `outcome`), which
28
- * lands on `_leases/<id>.jsonl`. A lost lease is not released: it isn't
29
- * this run's to give back.
27
+ * outcome (`done`, `not_done`, `gated`, `waiting`, or the Op's own
28
+ * `outcome`), which lands on `_leases/<id>.jsonl`. Outcomes are a closed
29
+ * list (`WORK_LEASE_OUTCOMES`, #3147): an Op's own outcome outside it is
30
+ * released `not_done`, with a note naming it. A lost lease is not
31
+ * released: it isn't this run's to give back.
30
32
  *
31
33
  * Steps read the lease through {@link workLeaseOutput}: its item, holder,
32
34
  * token and expiry, and for an Op that changes the checkout, the worktree and
@@ -42,6 +44,13 @@
42
44
  * worktree is removed when the run ends and the branch keeps whatever the
43
45
  * steps committed, for the runner to deliver. The checkout a coding agent is
44
46
  * editing, its index and its uncommitted files, are never touched.
47
+ *
48
+ * A run that ends `not_done` keeps its attempt aside (#3147): whatever the
49
+ * steps committed on the branch since the run started, and anything they
50
+ * left uncommitted in the worktree, is committed to
51
+ * `refs/chant/kept/[<member>/]<item>/<token>`, and the branch goes back to
52
+ * where the run started. The next attempt starts clean, and the earlier one
53
+ * stays readable by its lease token (`chant workspace work history`).
45
54
  */
46
55
 
47
56
  import { existsSync, rmSync } from "node:fs";
@@ -50,6 +59,8 @@ import { getRuntime } from "../runtime-adapter";
50
59
  import {
51
60
  claimWorkLease,
52
61
  DEFAULT_WORK_LEASE_TTL_MS,
62
+ isWorkLeaseOutcome,
63
+ keptAttemptRef,
53
64
  releaseWorkLease,
54
65
  renewWorkLease,
55
66
  workLeaseRef,
@@ -97,8 +108,10 @@ export interface WorkLeaseRunResult {
97
108
  branch: string | null;
98
109
  /** Whether the lease was given back. False when it was lost, or never claimed. */
99
110
  released: boolean;
100
- /** The outcome the release recorded, or null when there was no release. */
111
+ /** The outcome the release recorded, one of `WORK_LEASE_OUTCOMES`, or null when there was no release. */
101
112
  outcome: string | null;
113
+ /** For an Op that changes the checkout and ended `not_done`: the ref that keeps the attempt's work, or null when there was nothing to keep. */
114
+ kept: string | null;
102
115
  /** Why the lease was lost, or null when it wasn't. */
103
116
  lost: string | null;
104
117
  /** Why nothing was claimed, or null when something was. */
@@ -159,6 +172,8 @@ export class RunWorkLease {
159
172
  attempted = false;
160
173
  lease: WorkLeaseOutput | undefined;
161
174
  refusal: string | undefined;
175
+ /** For an Op that changes the checkout: the branch's commit when the run started, and the members its branch is scoped by. */
176
+ private start: { base: string; members: readonly string[] } | undefined;
162
177
  lost: string | undefined;
163
178
 
164
179
  private readonly spec: NonNullable<OpConfig["workLease"]>;
@@ -277,6 +292,8 @@ export class RunWorkLease {
277
292
  ? await git(["worktree", "add", "--quiet", worktree, branch], cwd)
278
293
  : await git(["worktree", "add", "--quiet", "-b", branch, worktree, "HEAD"], cwd);
279
294
  if (!add.ok) throw new Error(`Op "${this.config.name}": could not make the worktree for ${id} on ${branch}: ${add.stderr}`);
295
+ const base = await git(["rev-parse", "HEAD"], worktree);
296
+ if (base.ok) this.start = { base: base.stdout, members };
280
297
  return { worktree, branch };
281
298
  }
282
299
 
@@ -365,28 +382,73 @@ export class RunWorkLease {
365
382
  branch: lease?.branch ?? null,
366
383
  released: false,
367
384
  outcome: null,
385
+ kept: null,
368
386
  lost: this.lost ?? null,
369
387
  refusal: this.refusal ?? null,
370
388
  };
371
389
  if (!lease) return base;
390
+ let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
391
+ let note = `op ${this.config.name}`;
392
+ if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
393
+ const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
394
+ if (typeof declared === "string" && declared.trim() !== "") {
395
+ const word = declared.trim();
396
+ if (isWorkLeaseOutcome(word)) outcome = word;
397
+ else {
398
+ outcome = "not_done";
399
+ note = `op ${this.config.name}: its outcome ${JSON.stringify(word.slice(0, 80))} is not a work lease outcome`;
400
+ }
401
+ }
402
+ }
403
+ let kept: string | null = null;
404
+ if (lease.worktree && this.lost === undefined && outcome === "not_done") {
405
+ kept = await this.keepAttempt(lease).catch(() => null);
406
+ }
372
407
  if (lease.worktree) {
373
408
  await git(["worktree", "remove", "--force", lease.worktree], this.opts.cwd).catch(() => undefined);
374
409
  rmSync(lease.worktree, { recursive: true, force: true });
375
410
  await git(["worktree", "prune"], this.opts.cwd).catch(() => undefined);
376
411
  }
377
412
  if (this.lost !== undefined) return base;
378
- let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
379
- if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
380
- const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
381
- if (typeof declared === "string" && declared.trim() !== "") outcome = declared.trim();
382
- }
383
413
  const released = await releaseWorkLease(lease.item, this.holder, {
384
414
  cwd: this.ledgerCwd,
385
415
  token: lease.token,
386
416
  outcome,
387
- note: `op ${this.config.name}`,
417
+ note,
388
418
  }).catch(() => undefined);
389
- return { ...base, released: released?.ok === true, outcome: released?.ok ? outcome : null };
419
+ return { ...base, kept, released: released?.ok === true, outcome: released?.ok ? outcome : null };
420
+ }
421
+
422
+ /**
423
+ * Keep a `not_done` attempt aside (#3147): commit what the steps left in
424
+ * the worktree, committed or not, at `refs/chant/kept/.../<item>/<token>`,
425
+ * and move the branch back to where the run started. Returns the ref, or
426
+ * null when the attempt changed nothing.
427
+ */
428
+ private async keepAttempt(lease: WorkLeaseOutput): Promise<string | null> {
429
+ const dir = lease.worktree;
430
+ const start = this.start;
431
+ if (!dir || !start) return null;
432
+ const head = await git(["rev-parse", "HEAD"], dir);
433
+ if (!head.ok) return null;
434
+ if (!(await git(["add", "-A"], dir)).ok) return null;
435
+ const tree = await git(["write-tree"], dir);
436
+ const headTree = await git(["rev-parse", `${head.stdout}^{tree}`], dir);
437
+ if (!tree.ok || !headTree.ok) return null;
438
+ let commit = head.stdout;
439
+ if (tree.stdout !== headTree.stdout) {
440
+ // An identity only when git has none, so a configured one is kept.
441
+ const ident = (await git(["var", "GIT_COMMITTER_IDENT"], dir)).ok && (await git(["var", "GIT_AUTHOR_IDENT"], dir)).ok;
442
+ const who = ident ? [] : ["-c", "user.name=chant", "-c", "user.email=chant@localhost"];
443
+ const made = await git([...who, "commit-tree", tree.stdout, "-p", head.stdout, "-m", `${lease.item}: not done, kept (lease ${lease.token})`], dir);
444
+ if (!made.ok) return null;
445
+ commit = made.stdout;
446
+ }
447
+ if (commit === start.base) return null;
448
+ const ref = keptAttemptRef(lease.item, lease.token, start.members);
449
+ if (!(await git(["update-ref", ref, commit], dir)).ok) return null;
450
+ await git(["reset", "-q", "--hard", start.base], dir);
451
+ return ref;
390
452
  }
391
453
  }
392
454
 
package/src/provenance.ts CHANGED
@@ -177,6 +177,41 @@ export function originOfPath(
177
177
  return best === undefined ? undefined : paths[best];
178
178
  }
179
179
 
180
+ const INTERPOLATION_FIELDS = Symbol.for("chant.interpolationFields");
181
+
182
+ /**
183
+ * For an entity a tagged template built: which of its `props` paths each
184
+ * interpolation fed, by interpolation index (chant #3212). Element `i` lists
185
+ * the paths the `i`-th `${...}` landed in, in {@link EntityProvenance.paths}'
186
+ * grammar (dotted names, no array segments). An interpolation that fed no
187
+ * field the author typed, such as a reference to another entity, lists none.
188
+ *
189
+ * Only the tag can say this. It is the tag that parsed the text, so a
190
+ * `${props.ttlDays}` inside `TTL ts + INTERVAL ${props.ttlDays} DAY` is known
191
+ * to have fed `ttl` only there. When a composite factory interpreted at fold
192
+ * time writes the template, `discovery/fold-import.ts` joins these paths with
193
+ * the factory parameters each interpolation reads, which is what lets drift on
194
+ * such a field be reported against the parameter. A tag that never calls this
195
+ * leaves its fields `unknown`, the honest answer, rather than claiming either.
196
+ *
197
+ * Stored like provenance itself: non-enumerable and symbol-keyed, so no
198
+ * serializer, spread or comparison of the output ever sees it.
199
+ */
200
+ export function setInterpolationFields(entity: object, fields: ReadonlyArray<readonly string[]>): void {
201
+ if (!Object.isExtensible(entity)) return;
202
+ Object.defineProperty(entity, INTERPOLATION_FIELDS, {
203
+ value: Object.freeze(fields.map((paths) => Object.freeze([...paths]))),
204
+ enumerable: false,
205
+ writable: true,
206
+ configurable: true,
207
+ });
208
+ }
209
+
210
+ /** What {@link setInterpolationFields} recorded on an entity, if anything. */
211
+ export function getInterpolationFields(entity: object): ReadonlyArray<readonly string[]> | undefined {
212
+ return (entity as Record<symbol, unknown>)[INTERPOLATION_FIELDS] as ReadonlyArray<readonly string[]> | undefined;
213
+ }
214
+
180
215
  /** One-line rendering of an origin, for diff output. */
181
216
  export function describePathOrigin(origin: PathOrigin): string {
182
217
  switch (origin.kind) {