@intentius/chant 0.56.0 → 0.58.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 (515) hide show
  1. package/dist/audit/catalog.d.ts +51 -1
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +20 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +14 -8
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/lineage.d.ts +10 -0
  8. package/dist/audit/lineage.d.ts.map +1 -0
  9. package/dist/audit/prior-art.d.ts +385 -0
  10. package/dist/audit/prior-art.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +16 -0
  12. package/dist/audit/proof.d.ts.map +1 -1
  13. package/dist/audit/report-html.d.ts.map +1 -1
  14. package/dist/audit/report-model.d.ts +8 -1
  15. package/dist/audit/report-model.d.ts.map +1 -1
  16. package/dist/audit/report.d.ts.map +1 -1
  17. package/dist/audit/rules-doc.d.ts +3 -0
  18. package/dist/audit/rules-doc.d.ts.map +1 -1
  19. package/dist/audit/secrets.d.ts +15 -0
  20. package/dist/audit/secrets.d.ts.map +1 -1
  21. package/dist/audit/terraform-state.d.ts +46 -0
  22. package/dist/audit/terraform-state.d.ts.map +1 -0
  23. package/dist/build.d.ts +17 -1
  24. package/dist/build.d.ts.map +1 -1
  25. package/dist/claimed-fields.d.ts +161 -0
  26. package/dist/claimed-fields.d.ts.map +1 -0
  27. package/dist/cli/build-params-cli.d.ts +2 -2
  28. package/dist/cli/command-group.d.ts +14 -0
  29. package/dist/cli/command-group.d.ts.map +1 -1
  30. package/dist/cli/commands/audit.d.ts +17 -1
  31. package/dist/cli/commands/audit.d.ts.map +1 -1
  32. package/dist/cli/commands/build.d.ts.map +1 -1
  33. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  34. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  35. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  36. package/dist/cli/commands/import.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  38. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  40. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  41. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/lint.d.ts.map +1 -1
  44. package/dist/cli/commands/onboard.d.ts +2 -0
  45. package/dist/cli/commands/onboard.d.ts.map +1 -1
  46. package/dist/cli/handlers/build.d.ts.map +1 -1
  47. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  48. package/dist/cli/handlers/operator.d.ts +82 -16
  49. package/dist/cli/handlers/operator.d.ts.map +1 -1
  50. package/dist/cli/handlers/run.d.ts +49 -42
  51. package/dist/cli/handlers/run.d.ts.map +1 -1
  52. package/dist/cli/main.d.ts +28 -1
  53. package/dist/cli/main.d.ts.map +1 -1
  54. package/dist/cli/mcp/op-tools.d.ts +16 -1
  55. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  56. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  57. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  58. package/dist/cli/registry.d.ts +26 -9
  59. package/dist/cli/registry.d.ts.map +1 -1
  60. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  61. package/dist/components/auto-release.d.ts +10 -20
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability.d.ts +4 -4
  64. package/dist/components/cli-support.d.ts +20 -21
  65. package/dist/components/cli-support.d.ts.map +1 -1
  66. package/dist/components/component.d.ts +3 -3
  67. package/dist/components/component.d.ts.map +1 -1
  68. package/dist/components/driver-output.d.ts.map +1 -1
  69. package/dist/components/driver.d.ts +66 -36
  70. package/dist/components/driver.d.ts.map +1 -1
  71. package/dist/components/index.d.ts +1 -2
  72. package/dist/components/index.d.ts.map +1 -1
  73. package/dist/components/manifest-persistence.d.ts +1 -9
  74. package/dist/components/manifest-persistence.d.ts.map +1 -1
  75. package/dist/components/run-progress.d.ts +6 -6
  76. package/dist/components/run-progress.d.ts.map +1 -1
  77. package/dist/components/verbs/run-agent.d.ts +9 -15
  78. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  79. package/dist/components/verbs/verify.d.ts +2 -2
  80. package/dist/deep-observation.d.ts +17 -0
  81. package/dist/deep-observation.d.ts.map +1 -1
  82. package/dist/discovery/files.d.ts +4 -4
  83. package/dist/discovery/fold-import.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +33 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  87. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  88. package/dist/fold/foldable-helpers.d.ts +5 -1
  89. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  90. package/dist/fold-provenance.d.ts +236 -0
  91. package/dist/fold-provenance.d.ts.map +1 -0
  92. package/dist/graph-ops.d.ts +1 -1
  93. package/dist/held-elsewhere.d.ts +85 -0
  94. package/dist/held-elsewhere.d.ts.map +1 -0
  95. package/dist/import/ir-utils.d.ts +1 -1
  96. package/dist/import/ir-utils.d.ts.map +1 -1
  97. package/dist/import/parser.d.ts +33 -0
  98. package/dist/import/parser.d.ts.map +1 -1
  99. package/dist/index.d.ts +3 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/kubectl-context.d.ts +3 -3
  102. package/dist/lexicon-config.d.ts +4 -4
  103. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  104. package/dist/lexicon.d.ts +104 -14
  105. package/dist/lexicon.d.ts.map +1 -1
  106. package/dist/lifecycle/change-set.d.ts +13 -0
  107. package/dist/lifecycle/change-set.d.ts.map +1 -1
  108. package/dist/lifecycle/deep-diff.d.ts +133 -6
  109. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  110. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  111. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  112. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  113. package/dist/lifecycle/index.d.ts +1 -0
  114. package/dist/lifecycle/index.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +2 -2
  116. package/dist/lifecycle/run-ledger.d.ts +53 -0
  117. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  118. package/dist/lifecycle/symptoms.d.ts +3 -3
  119. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  120. package/dist/lint/config.d.ts +81 -2
  121. package/dist/lint/config.d.ts.map +1 -1
  122. package/dist/lint/post-synth.d.ts +55 -2
  123. package/dist/lint/post-synth.d.ts.map +1 -1
  124. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  125. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  126. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  127. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  128. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  129. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  130. package/dist/lint/rules/comp/index.d.ts +1 -1
  131. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  132. package/dist/lint/rules/op/index.d.ts +16 -0
  133. package/dist/lint/rules/op/index.d.ts.map +1 -0
  134. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  135. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  136. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  137. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  138. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  139. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  140. package/dist/lint/rules/op/support.d.ts +12 -0
  141. package/dist/lint/rules/op/support.d.ts.map +1 -0
  142. package/dist/lint/suppressions.d.ts +108 -0
  143. package/dist/lint/suppressions.d.ts.map +1 -0
  144. package/dist/live-endpoint.d.ts +1 -1
  145. package/dist/op/activities/activity-contracts.d.ts +54 -0
  146. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  147. package/dist/op/activities/apply.d.ts +262 -0
  148. package/dist/op/activities/apply.d.ts.map +1 -0
  149. package/dist/op/activities/build.d.ts +13 -0
  150. package/dist/op/activities/build.d.ts.map +1 -0
  151. package/dist/op/activities/converge.d.ts +110 -0
  152. package/dist/op/activities/converge.d.ts.map +1 -0
  153. package/dist/op/activities/env-teardown.d.ts +76 -0
  154. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  155. package/dist/op/activities/guard-validate.d.ts +64 -0
  156. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  157. package/dist/op/activities/http-check.d.ts +34 -0
  158. package/dist/op/activities/http-check.d.ts.map +1 -0
  159. package/dist/op/activities/index.d.ts +31 -0
  160. package/dist/op/activities/index.d.ts.map +1 -0
  161. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  162. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  163. package/dist/op/activities/lifecycle.d.ts +42 -0
  164. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  165. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  166. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  167. package/dist/op/activities/policy.d.ts +17 -0
  168. package/dist/op/activities/policy.d.ts.map +1 -0
  169. package/dist/op/activities/reconcile.d.ts +89 -0
  170. package/dist/op/activities/reconcile.d.ts.map +1 -0
  171. package/dist/op/activities/shell.d.ts +13 -0
  172. package/dist/op/activities/shell.d.ts.map +1 -0
  173. package/dist/op/activities/teardown.d.ts +10 -0
  174. package/dist/op/activities/teardown.d.ts.map +1 -0
  175. package/dist/op/activities/util.d.ts +7 -0
  176. package/dist/op/activities/util.d.ts.map +1 -0
  177. package/dist/op/activities/wait.d.ts +28 -0
  178. package/dist/op/activities/wait.d.ts.map +1 -0
  179. package/dist/op/activities/workflow-audit.d.ts +92 -0
  180. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  181. package/dist/op/activity-contract-registry.d.ts +76 -0
  182. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  183. package/dist/op/activity-contract.d.ts +25 -18
  184. package/dist/op/activity-contract.d.ts.map +1 -1
  185. package/dist/op/activity-failure.d.ts +28 -0
  186. package/dist/op/activity-failure.d.ts.map +1 -0
  187. package/dist/op/activity-profiles.d.ts +118 -0
  188. package/dist/op/activity-profiles.d.ts.map +1 -0
  189. package/dist/op/activity-registry.d.ts +22 -38
  190. package/dist/op/activity-registry.d.ts.map +1 -1
  191. package/dist/op/activity-runtime.d.ts +3 -14
  192. package/dist/op/activity-runtime.d.ts.map +1 -1
  193. package/dist/op/builders.d.ts +84 -29
  194. package/dist/op/builders.d.ts.map +1 -1
  195. package/dist/op/composites/apply-op.d.ts +113 -0
  196. package/dist/op/composites/apply-op.d.ts.map +1 -0
  197. package/dist/op/composites/converge-op.d.ts +120 -0
  198. package/dist/op/composites/converge-op.d.ts.map +1 -0
  199. package/dist/op/composites/index.d.ts +26 -0
  200. package/dist/op/composites/index.d.ts.map +1 -0
  201. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  202. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  203. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  204. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  205. package/dist/op/composites/reconcile-op.d.ts +57 -0
  206. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  207. package/dist/op/composites/watch-op.d.ts +60 -0
  208. package/dist/op/composites/watch-op.d.ts.map +1 -0
  209. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  210. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  211. package/dist/op/converge-rule.d.ts +12 -10
  212. package/dist/op/converge-rule.d.ts.map +1 -1
  213. package/dist/op/cron.d.ts +51 -0
  214. package/dist/op/cron.d.ts.map +1 -0
  215. package/dist/op/discover.d.ts +9 -0
  216. package/dist/op/discover.d.ts.map +1 -1
  217. package/dist/op/duration.d.ts +11 -0
  218. package/dist/op/duration.d.ts.map +1 -0
  219. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  220. package/dist/op/gate.d.ts +90 -0
  221. package/dist/op/gate.d.ts.map +1 -0
  222. package/dist/op/generate-pipeline.d.ts +9 -0
  223. package/dist/op/generate-pipeline.d.ts.map +1 -1
  224. package/dist/op/index.d.ts +23 -7
  225. package/dist/op/index.d.ts.map +1 -1
  226. package/dist/op/local-executor.d.ts +114 -28
  227. package/dist/op/local-executor.d.ts.map +1 -1
  228. package/dist/op/local-output.d.ts +11 -1
  229. package/dist/op/local-output.d.ts.map +1 -1
  230. package/dist/op/op-ir.d.ts +184 -0
  231. package/dist/op/op-ir.d.ts.map +1 -0
  232. package/dist/op/op-verb-class.d.ts +10 -2
  233. package/dist/op/op-verb-class.d.ts.map +1 -1
  234. package/dist/op/operator.d.ts +30 -5
  235. package/dist/op/operator.d.ts.map +1 -1
  236. package/dist/op/receipt-store.d.ts +1 -1
  237. package/dist/op/receipt-store.d.ts.map +1 -1
  238. package/dist/op/resource.d.ts +15 -1
  239. package/dist/op/resource.d.ts.map +1 -1
  240. package/dist/op/runtime.d.ts +200 -0
  241. package/dist/op/runtime.d.ts.map +1 -0
  242. package/dist/op/runtimes/local.d.ts +29 -0
  243. package/dist/op/runtimes/local.d.ts.map +1 -0
  244. package/dist/op/step-output-ref.d.ts +15 -13
  245. package/dist/op/step-output-ref.d.ts.map +1 -1
  246. package/dist/op/types.d.ts +79 -27
  247. package/dist/op/types.d.ts.map +1 -1
  248. package/dist/ownership.d.ts +1 -1
  249. package/dist/provenance.d.ts +21 -0
  250. package/dist/provenance.d.ts.map +1 -1
  251. package/dist/serializer-walker.d.ts.map +1 -1
  252. package/dist/stack-output.d.ts +5 -0
  253. package/dist/stack-output.d.ts.map +1 -1
  254. package/package.json +6 -1
  255. package/src/audit/catalog.test.ts +79 -3
  256. package/src/audit/catalog.ts +111 -13
  257. package/src/audit/core.test.ts +26 -0
  258. package/src/audit/core.ts +61 -17
  259. package/src/audit/discover.test.ts +39 -0
  260. package/src/audit/discover.ts +91 -15
  261. package/src/audit/lineage.ts +100 -0
  262. package/src/audit/prior-art.ts +153 -0
  263. package/src/audit/proof.test.ts +67 -0
  264. package/src/audit/proof.ts +70 -0
  265. package/src/audit/report-html.ts +9 -4
  266. package/src/audit/report-model.ts +16 -2
  267. package/src/audit/report.test.ts +33 -0
  268. package/src/audit/report.ts +5 -3
  269. package/src/audit/rules-doc.test.ts +34 -2
  270. package/src/audit/rules-doc.ts +82 -9
  271. package/src/audit/secrets.ts +15 -3
  272. package/src/audit/terraform-state.test.ts +144 -0
  273. package/src/audit/terraform-state.ts +131 -0
  274. package/src/build.ts +31 -1
  275. package/src/claimed-fields.test.ts +145 -0
  276. package/src/claimed-fields.ts +208 -0
  277. package/src/cli/build-params-cli.ts +2 -2
  278. package/src/cli/command-group.ts +14 -0
  279. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  280. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  281. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  282. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  283. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  284. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  285. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  286. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  287. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  288. package/src/cli/commands/audit.test.ts +189 -11
  289. package/src/cli/commands/audit.ts +73 -27
  290. package/src/cli/commands/build.test.ts +292 -9
  291. package/src/cli/commands/build.ts +156 -24
  292. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  293. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  294. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  295. package/src/cli/commands/import.ts +6 -0
  296. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  297. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  298. package/src/cli/commands/init-lexicon.test.ts +97 -2
  299. package/src/cli/commands/init-lexicon.ts +4 -2
  300. package/src/cli/commands/init.test.ts +35 -1
  301. package/src/cli/commands/init.ts +14 -3
  302. package/src/cli/commands/lint.test.ts +48 -0
  303. package/src/cli/commands/lint.ts +123 -0
  304. package/src/cli/commands/onboard.test.ts +75 -0
  305. package/src/cli/commands/onboard.ts +54 -23
  306. package/src/cli/handlers/build.test.ts +81 -1
  307. package/src/cli/handlers/build.ts +15 -1
  308. package/src/cli/handlers/graph.test.ts +3 -3
  309. package/src/cli/handlers/lifecycle.test.ts +195 -12
  310. package/src/cli/handlers/lifecycle.ts +123 -6
  311. package/src/cli/handlers/misc.ts +1 -1
  312. package/src/cli/handlers/operator.test.ts +183 -11
  313. package/src/cli/handlers/operator.ts +260 -55
  314. package/src/cli/handlers/run.test.ts +431 -1287
  315. package/src/cli/handlers/run.ts +365 -915
  316. package/src/cli/main.test.ts +23 -15
  317. package/src/cli/main.ts +88 -33
  318. package/src/cli/mcp/op-tools.ts +147 -88
  319. package/src/cli/mcp/resource-handlers.ts +10 -37
  320. package/src/cli/mcp/resources/context.ts +11 -12
  321. package/src/cli/mcp/server.test.ts +25 -27
  322. package/src/cli/mcp/server.ts +2 -2
  323. package/src/cli/registry.ts +26 -9
  324. package/src/codegen/docs.ts +1 -1
  325. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  326. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  327. package/src/components/README.md +3 -2
  328. package/src/components/auto-release.test.ts +4 -20
  329. package/src/components/auto-release.ts +17 -42
  330. package/src/components/capability.ts +4 -4
  331. package/src/components/cli-support.test.ts +10 -5
  332. package/src/components/cli-support.ts +52 -44
  333. package/src/components/component.schema.json +2 -2
  334. package/src/components/component.ts +3 -3
  335. package/src/components/driver-output.ts +14 -2
  336. package/src/components/driver.test.ts +92 -22
  337. package/src/components/driver.ts +193 -83
  338. package/src/components/index.ts +1 -6
  339. package/src/components/manifest-persistence.test.ts +2 -20
  340. package/src/components/manifest-persistence.ts +4 -24
  341. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  342. package/src/components/run-progress.ts +6 -6
  343. package/src/components/verbs/run-agent.ts +11 -17
  344. package/src/components/verbs/verify.ts +2 -2
  345. package/src/deep-observation.test.ts +75 -0
  346. package/src/deep-observation.ts +48 -0
  347. package/src/discovery/collect.test.ts +3 -3
  348. package/src/discovery/files.ts +4 -4
  349. package/src/discovery/fold-import.ts +105 -2
  350. package/src/discovery/param-deps.test.ts +112 -1
  351. package/src/discovery/param-deps.ts +193 -0
  352. package/src/discovery/sandbox/bundle.ts +15 -1
  353. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  354. package/src/discovery/sandbox/config-wire.ts +2 -2
  355. package/src/fold/fold.test.ts +56 -0
  356. package/src/fold/foldable-helpers.ts +81 -1
  357. package/src/fold-provenance-exclusion.test.ts +186 -0
  358. package/src/fold-provenance.test.ts +495 -0
  359. package/src/fold-provenance.ts +441 -0
  360. package/src/graph-ops.test.ts +2 -2
  361. package/src/graph-ops.ts +1 -1
  362. package/src/held-elsewhere.test.ts +102 -0
  363. package/src/held-elsewhere.ts +114 -0
  364. package/src/import/ir-utils.ts +11 -1
  365. package/src/import/parser.ts +35 -0
  366. package/src/index.ts +3 -0
  367. package/src/kubectl-context.ts +3 -3
  368. package/src/lexicon-config.test.ts +5 -5
  369. package/src/lexicon-config.ts +4 -4
  370. package/src/lexicon-plugin-helpers.ts +1 -1
  371. package/src/lexicon.ts +120 -14
  372. package/src/lifecycle/change-set.test.ts +49 -0
  373. package/src/lifecycle/change-set.ts +67 -0
  374. package/src/lifecycle/converge-ledger.ts +1 -1
  375. package/src/lifecycle/deep-diff.test.ts +267 -14
  376. package/src/lifecycle/deep-diff.ts +217 -14
  377. package/src/lifecycle/deep-observe.test.ts +3 -1
  378. package/src/lifecycle/deep-observe.ts +16 -6
  379. package/src/lifecycle/gate-ledger.test.ts +74 -2
  380. package/src/lifecycle/gate-ledger.ts +143 -36
  381. package/src/lifecycle/index.ts +1 -0
  382. package/src/lifecycle/release-ledger.ts +2 -2
  383. package/src/lifecycle/run-ledger.test.ts +268 -0
  384. package/src/lifecycle/run-ledger.ts +201 -0
  385. package/src/lifecycle/symptoms.ts +3 -3
  386. package/src/lint/config.test.ts +82 -1
  387. package/src/lint/config.ts +112 -3
  388. package/src/lint/policy.test.ts +1 -1
  389. package/src/lint/post-synth.test.ts +31 -0
  390. package/src/lint/post-synth.ts +54 -1
  391. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  392. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  393. package/src/lint/rules/comp/comp.test.ts +1 -1
  394. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  395. package/src/lint/rules/comp/index.ts +3 -3
  396. package/src/lint/rules/op/index.ts +23 -0
  397. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  398. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  399. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  400. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  401. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  402. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  403. package/src/lint/rules/op/support.ts +11 -0
  404. package/src/lint/suppressions.test.ts +163 -0
  405. package/src/lint/suppressions.ts +204 -0
  406. package/src/live-endpoint.ts +1 -1
  407. package/src/op/activities/activity-contracts.ts +66 -0
  408. package/src/op/activities/apply.test.ts +567 -0
  409. package/src/op/activities/apply.ts +662 -0
  410. package/src/op/activities/build.ts +26 -0
  411. package/src/op/activities/converge.test.ts +330 -0
  412. package/src/op/activities/converge.ts +449 -0
  413. package/src/op/activities/env-teardown.test.ts +187 -0
  414. package/src/op/activities/env-teardown.ts +171 -0
  415. package/src/op/activities/guard-validate.test.ts +139 -0
  416. package/src/op/activities/guard-validate.ts +223 -0
  417. package/src/op/activities/http-check.test.ts +48 -0
  418. package/src/op/activities/http-check.ts +55 -0
  419. package/src/op/activities/index.ts +119 -0
  420. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  421. package/src/op/activities/lexicon-upgrade.ts +673 -0
  422. package/src/op/activities/lifecycle.ts +85 -0
  423. package/src/op/activities/pipeline-audit.test.ts +67 -0
  424. package/src/op/activities/pipeline-audit.ts +201 -0
  425. package/src/op/activities/policy.test.ts +19 -0
  426. package/src/op/activities/policy.ts +37 -0
  427. package/src/op/activities/reconcile.test.ts +81 -0
  428. package/src/op/activities/reconcile.ts +187 -0
  429. package/src/op/activities/shell.ts +26 -0
  430. package/src/op/activities/teardown.ts +22 -0
  431. package/src/op/activities/util.ts +6 -0
  432. package/src/op/activities/wait.ts +69 -0
  433. package/src/op/activities/workflow-audit.test.ts +90 -0
  434. package/src/op/activities/workflow-audit.ts +203 -0
  435. package/src/op/activity-contract-registry.test.ts +98 -0
  436. package/src/op/activity-contract-registry.ts +114 -0
  437. package/src/op/activity-contract.test.ts +1 -1
  438. package/src/op/activity-contract.ts +37 -33
  439. package/src/op/activity-failure.ts +34 -0
  440. package/src/op/activity-profiles.test.ts +56 -0
  441. package/src/op/activity-profiles.ts +116 -0
  442. package/src/op/activity-registry.test.ts +37 -28
  443. package/src/op/activity-registry.ts +27 -56
  444. package/src/op/activity-runtime.ts +3 -47
  445. package/src/op/builders-exports.test.ts +0 -49
  446. package/src/op/builders-typed.test.ts +164 -0
  447. package/src/op/builders.ts +123 -39
  448. package/src/op/composites/apply-op.ts +208 -0
  449. package/src/op/composites/composites.test.ts +366 -0
  450. package/src/op/composites/converge-op.test.ts +168 -0
  451. package/src/op/composites/converge-op.ts +214 -0
  452. package/src/op/composites/index.ts +26 -0
  453. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  454. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  455. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  456. package/src/op/composites/pipeline-audit-op.ts +78 -0
  457. package/src/op/composites/reconcile-op.test.ts +51 -0
  458. package/src/op/composites/reconcile-op.ts +107 -0
  459. package/src/op/composites/watch-op.ts +108 -0
  460. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  461. package/src/op/composites/workflow-audit-op.ts +88 -0
  462. package/src/op/converge-rule.ts +12 -10
  463. package/src/op/cron.test.ts +128 -0
  464. package/src/op/cron.ts +149 -0
  465. package/src/op/discover-root.test.ts +105 -1
  466. package/src/op/discover.test.ts +6 -2
  467. package/src/op/discover.ts +84 -16
  468. package/src/op/duration.ts +21 -0
  469. package/src/op/effect-step.test.ts +26 -11
  470. package/src/op/emulator-lifecycle.ts +1 -2
  471. package/src/op/gate.ts +155 -0
  472. package/src/op/generate-pipeline.test.ts +65 -3
  473. package/src/op/generate-pipeline.ts +19 -2
  474. package/src/op/index.ts +39 -6
  475. package/src/op/local-executor.test.ts +163 -34
  476. package/src/op/local-executor.ts +370 -102
  477. package/src/op/local-output.test.ts +95 -11
  478. package/src/op/local-output.ts +33 -4
  479. package/src/op/op-ir.test.ts +281 -0
  480. package/src/op/op-ir.ts +417 -0
  481. package/src/op/op-verb-class.ts +19 -5
  482. package/src/op/op.test.ts +26 -5
  483. package/src/op/operator.test.ts +175 -6
  484. package/src/op/operator.ts +63 -16
  485. package/src/op/receipt-store.ts +1 -1
  486. package/src/op/resource.ts +20 -2
  487. package/src/op/runtime.ts +203 -0
  488. package/src/op/runtimes/local.test.ts +181 -0
  489. package/src/op/runtimes/local.ts +212 -0
  490. package/src/op/step-output-ref.ts +15 -13
  491. package/src/op/types.ts +84 -24
  492. package/src/ownership.ts +1 -1
  493. package/src/provenance.test.ts +6 -0
  494. package/src/provenance.ts +21 -1
  495. package/src/serializer-walker.test.ts +42 -0
  496. package/src/serializer-walker.ts +21 -2
  497. package/src/stack-output.ts +6 -1
  498. package/dist/cli/handlers/op-progress.d.ts +0 -90
  499. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  500. package/dist/cli/handlers/run-client.d.ts +0 -127
  501. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  502. package/dist/cli/handlers/run-report.d.ts +0 -5
  503. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  504. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  505. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  506. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  507. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  508. package/src/cli/handlers/op-progress.test.ts +0 -268
  509. package/src/cli/handlers/op-progress.ts +0 -248
  510. package/src/cli/handlers/run-client.test.ts +0 -82
  511. package/src/cli/handlers/run-client.ts +0 -219
  512. package/src/cli/handlers/run-report.test.ts +0 -62
  513. package/src/cli/handlers/run-report.ts +0 -122
  514. package/src/components/temporal-codegen-loader.ts +0 -41
  515. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Fold provenance (#2161) — which composite parameter produced which emitted field.
3
+ *
4
+ * A composite is the one lossy thing chant emits. It takes a few typed
5
+ * arguments and expands to several resources, so a return leg that regenerates
6
+ * source from live produces flat typed resources and the composite is gone. The
7
+ * projection is not invertible from its output alone, which is the view-update
8
+ * problem landing on chant's record.
9
+ *
10
+ * chant can answer it where a general generator cannot, and for one reason: the
11
+ * evaluated subset is restricted on purpose. `discovery/fold-import.ts` already
12
+ * interprets an admissible factory body rather than calling it, so the
13
+ * expression each member property was WRITTEN as is in hand at build time. The
14
+ * same pass records where the field came from, in `discovery/param-deps.ts`'s
15
+ * {@link import("./discovery/param-deps").collectCompositeOrigins}.
16
+ *
17
+ * ## Four answers, and the fourth is a real one
18
+ *
19
+ * Every emitted path gets one of {@link FoldFieldOrigin}'s four kinds. Three of
20
+ * them are findings; `unknown` is the fourth, and it is a legal answer rather
21
+ * than a gap. A build that expanded a composite without interpreting its body
22
+ * (the run path, a factory outside the admissible subset, a sandboxed child
23
+ * whose provenance did not survive the wire) knows the field came from a
24
+ * composite and cannot say which parameter. Recording that is the whole point:
25
+ * an unknown must never fall through to `direct`, because `direct` licenses
26
+ * editing the field in the user's source and an unattributed composite field is
27
+ * exactly the case where that produces a flattened resource.
28
+ *
29
+ * `direct` is claimed only where the build has a provenance record AND that
30
+ * record says no composite expanded the entity. Absence of a record is
31
+ * `unknown`, never `direct`.
32
+ *
33
+ * ## Beside the output, never in it
34
+ *
35
+ * The record is build metadata, not declared configuration, and it must not
36
+ * reach the document an applier writes. It rides two channels, both outside the
37
+ * apply-bound document, and neither is a new mechanism:
38
+ *
39
+ * - per entity, the non-enumerable symbol-keyed side channel `./provenance.ts`
40
+ * already owns, invisible to every serializer and to every spread;
41
+ * - per build, {@link import("./build").BuildResult.foldProvenance}, a sibling
42
+ * of `buildParams` and `lexiconVersions`.
43
+ *
44
+ * Nothing is added to `SerializeContext`. That is the same posture
45
+ * `./effect-receipt.ts` takes at the same seam, one notch stricter: a receipt
46
+ * rides `SerializeContext.receipts` so a lexicon MAY render it outside
47
+ * `Resources`, whereas provenance has no rendering and is simply never handed
48
+ * to a serializer at all. `fold-provenance-exclusion.test.ts` drives a real
49
+ * build through a spy serializer and a mock applier to hold that line.
50
+ *
51
+ * ## The return leg
52
+ *
53
+ * {@link resolveDriftedField} is the consumer. #2160 has already ruled out the
54
+ * fields nobody declared, so a drifted field reaching it is one source actually
55
+ * declared, and the origin decides what may be proposed:
56
+ *
57
+ * - a composite parameter: propose changing that parameter, naming the file,
58
+ * the composite call and the parameter path. One line, composite intact.
59
+ * - a literal the composite fixes: refuse by name. The choice is to
60
+ * parameterize the field or stop using the composite there, and neither is
61
+ * something chant may pick.
62
+ * - a direct declaration: today's behaviour, unchanged.
63
+ * - unknown: today's behaviour, and say that is what happened.
64
+ *
65
+ * Proposing is as far as this goes. Rewriting a parameter in the user's source
66
+ * is a source-edit problem (the generator half of `chant import` emits whole
67
+ * files and nothing maps a property path back to a location in a `.ts` file),
68
+ * so a proposal here is a described action: it names the file, the composite
69
+ * call and the parameter path, and performs no edit.
70
+ */
71
+
72
+ import { isResourceDeclarable, type Declarable } from "./declarable";
73
+ import { originOfPath, type EntityProvenance, type PathOrigin } from "./provenance";
74
+ import type { DeepEntityDrift, PropertyDrift } from "./lifecycle/deep-diff";
75
+
76
+ /** Why a field's origin could not be determined. Both are facts about the BUILD, not about the field. */
77
+ export type UnknownOriginReason =
78
+ /**
79
+ * A composite expanded the entity and this build did not interpret its body,
80
+ * so parameter and fixed literal are indistinguishable. The run path, a
81
+ * factory outside `fold-import.ts`'s admissible subset, and a `propagate()`
82
+ * key both sides wrote all land here.
83
+ */
84
+ | "composite-not-interpreted"
85
+ /**
86
+ * The build recorded no provenance for this entity at all, so not even "did a
87
+ * composite make it" is answerable. A sandboxed child's entities arrive this
88
+ * way: `discovery/entity-wire-codec.ts` drops build metadata over the wire.
89
+ */
90
+ | "no-provenance";
91
+
92
+ /**
93
+ * What produced one emitted property path.
94
+ *
95
+ * The composite kinds carry `instance` — the export name every member of one
96
+ * composite call shares — because "the `AutoscaledService` composite" is not an
97
+ * address and "the `api` call of `AutoscaledService`" is.
98
+ */
99
+ export type FoldFieldOrigin =
100
+ | { kind: "composite-parameter"; composite: string; instance?: string; parameters: string[] }
101
+ | { kind: "composite-literal"; composite: string; instance?: string }
102
+ | { kind: "direct" }
103
+ | { kind: "unknown"; reason: UnknownOriginReason };
104
+
105
+ /** One entity's fold provenance: where it was declared, and an origin for every field it emits. */
106
+ export interface EntityFoldProvenance {
107
+ /** The source file that declared the entity, when the build recorded one. */
108
+ sourceFile?: string;
109
+ /** The composite type that expanded it, when one did. */
110
+ composite?: string;
111
+ /** The composite call (export name) it belongs to, when one expanded it. */
112
+ instance?: string;
113
+ /**
114
+ * Emitted property path to origin, in sorted key order. Every path the entity
115
+ * emits is present: a path with no attribution carries an `unknown` origin
116
+ * rather than being left out, so a consumer iterating fields never has to
117
+ * decide what a missing key meant.
118
+ */
119
+ fields: Record<string, FoldFieldOrigin>;
120
+ }
121
+
122
+ /** A whole build's fold provenance, keyed by chant entity name. */
123
+ export type FoldProvenance = Record<string, EntityFoldProvenance>;
124
+
125
+ /** True for an ordinary `{}`, false for an array, a class instance, an `AttrRef`. */
126
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
127
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
128
+ const proto = Object.getPrototypeOf(value) as unknown;
129
+ return proto === Object.prototype || proto === null;
130
+ }
131
+
132
+ /**
133
+ * Every property path an entity's `props` emits, in `EntityProvenance.paths`'
134
+ * grammar: dotted names only, plain objects descended into, everything else
135
+ * attributed whole at its own path.
136
+ *
137
+ * Arrays are deliberately not indexed. An index-shaped key would not survive an
138
+ * element moving, and the recorder that produces origins cannot index either,
139
+ * so indexing here would manufacture paths nothing can ever attribute. The diff
140
+ * addresses array elements with `[#key]`/`[n]` and `originOfPath` resolves
141
+ * those against the dotted key that covers them, which is the join between the
142
+ * two grammars.
143
+ *
144
+ * An empty object is a value, and a leaf. An `undefined` value is not emitted
145
+ * and yields no path.
146
+ */
147
+ export function emittedFieldPaths(props: unknown): string[] {
148
+ const out: string[] = [];
149
+
150
+ const walk = (value: unknown, prefix: string): void => {
151
+ if (value === undefined) return;
152
+ if (isPlainObject(value)) {
153
+ const keys = Object.keys(value).sort();
154
+ if (keys.length === 0) {
155
+ if (prefix) out.push(prefix);
156
+ return;
157
+ }
158
+ for (const key of keys) walk(value[key], prefix ? `${prefix}.${key}` : key);
159
+ return;
160
+ }
161
+ if (prefix) out.push(prefix);
162
+ };
163
+
164
+ walk(props, "");
165
+ return out;
166
+ }
167
+
168
+ /**
169
+ * The four-way answer for one path, from the recorded {@link PathOrigin} and
170
+ * the entity's own provenance.
171
+ *
172
+ * `origin` is what {@link originOfPath} resolved for the path, so a coarse key
173
+ * recorded at an ancestor governs a leaf, exactly as it does everywhere else.
174
+ */
175
+ export function classifyFieldOrigin(
176
+ origin: PathOrigin | undefined,
177
+ provenance: EntityProvenance | undefined,
178
+ ): FoldFieldOrigin {
179
+ if (!provenance) return { kind: "unknown", reason: "no-provenance" };
180
+ const instance = provenance.compositeInstance;
181
+
182
+ switch (origin?.kind) {
183
+ case "composite-parameter":
184
+ return {
185
+ kind: "composite-parameter",
186
+ composite: origin.composite,
187
+ ...(instance ? { instance } : {}),
188
+ parameters: [...origin.parameters],
189
+ };
190
+ case "composite-literal":
191
+ return { kind: "composite-literal", composite: origin.composite, ...(instance ? { instance } : {}) };
192
+ case "composite":
193
+ // The coarse kind: a composite expanded this, and nothing said which
194
+ // parameter. Not a parameter, not a fixed literal, and above all not a
195
+ // direct declaration.
196
+ return { kind: "unknown", reason: "composite-not-interpreted" };
197
+ case "authored":
198
+ case "build-param":
199
+ // A build parameter governs the expression the AUTHOR wrote in their own
200
+ // source, so the declaration is still theirs to edit.
201
+ return { kind: "direct" };
202
+ case undefined:
203
+ return provenance.composite
204
+ ? { kind: "unknown", reason: "composite-not-interpreted" }
205
+ : { kind: "direct" };
206
+ }
207
+ }
208
+
209
+ /** One entity's fold provenance, or `undefined` when it emits no properties at all. */
210
+ export function foldProvenanceOfEntity(
211
+ entity: Declarable,
212
+ provenance: EntityProvenance | undefined,
213
+ ): EntityFoldProvenance | undefined {
214
+ if (!isResourceDeclarable(entity)) return undefined;
215
+ const paths = emittedFieldPaths(entity.props);
216
+ if (paths.length === 0) return undefined;
217
+
218
+ const fields: Record<string, FoldFieldOrigin> = {};
219
+ for (const path of paths) {
220
+ fields[path] = classifyFieldOrigin(originOfPath(provenance?.paths, path), provenance);
221
+ }
222
+
223
+ return {
224
+ ...(provenance?.sourceFile ? { sourceFile: provenance.sourceFile } : {}),
225
+ ...(provenance?.composite ? { composite: provenance.composite } : {}),
226
+ ...(provenance?.compositeInstance ? { instance: provenance.compositeInstance } : {}),
227
+ fields,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * A whole build's fold provenance.
233
+ *
234
+ * `provenanceOf` is injected rather than read off the entity here so the same
235
+ * function serves a build (where it is `getProvenance`) and a test with a
236
+ * hand-written record. Entities with no properties are absent; sorted, because
237
+ * a build's outputs are compared byte for byte.
238
+ */
239
+ export function foldProvenanceOfEntities(
240
+ entities: ReadonlyMap<string, Declarable>,
241
+ provenanceOf: (entity: Declarable) => EntityProvenance | undefined,
242
+ ): FoldProvenance {
243
+ const out: FoldProvenance = {};
244
+ for (const name of [...entities.keys()].sort()) {
245
+ const entity = entities.get(name) as Declarable;
246
+ const record = foldProvenanceOfEntity(entity, provenanceOf(entity));
247
+ if (record) out[name] = record;
248
+ }
249
+ return out;
250
+ }
251
+
252
+ /** One-line rendering of a field origin, for a report. */
253
+ export function describeFoldFieldOrigin(origin: FoldFieldOrigin): string {
254
+ switch (origin.kind) {
255
+ case "composite-parameter":
256
+ return `parameter ${origin.parameters.join(", ")} of ${compositeCall(origin.composite, origin.instance)}`;
257
+ case "composite-literal":
258
+ return `fixed by ${compositeCall(origin.composite, origin.instance)}`;
259
+ case "direct":
260
+ return "declared directly";
261
+ case "unknown":
262
+ return `origin unknown (${unknownOriginText(origin.reason)})`;
263
+ }
264
+ }
265
+
266
+ /** Why an origin could not be determined, in words a report can print. */
267
+ export function unknownOriginText(reason: UnknownOriginReason): string {
268
+ switch (reason) {
269
+ case "composite-not-interpreted":
270
+ return "expanded by a composite whose factory this build did not interpret";
271
+ case "no-provenance":
272
+ return "this build recorded no provenance for the entity";
273
+ }
274
+ }
275
+
276
+ /** `AutoscaledService composite call \`api\``, or just the composite when the call has no name. */
277
+ function compositeCall(composite: string, instance: string | undefined): string {
278
+ return instance ? `the \`${instance}\` call of composite ${composite}` : `composite ${composite}`;
279
+ }
280
+
281
+ /** `in src/app.infra.ts`, or nothing when the build recorded no file. */
282
+ function inFile(sourceFile: string | undefined): string {
283
+ return sourceFile ? ` in ${sourceFile}` : "";
284
+ }
285
+
286
+ /** A value as a report prints it. */
287
+ function renderValue(value: unknown): string {
288
+ if (value === undefined) return "unset";
289
+ try {
290
+ return JSON.stringify(value) ?? String(value);
291
+ } catch {
292
+ return String(value);
293
+ }
294
+ }
295
+
296
+ /**
297
+ * What the return leg may do about one drifted field.
298
+ *
299
+ * Every arm is a DESCRIBED action. Nothing here edits a file, and
300
+ * `propose-parameter` deliberately stops at naming the edit: the source-edit
301
+ * half is the generator side of `chant import` and does not exist yet.
302
+ */
303
+ export type DriftResolution =
304
+ | {
305
+ kind: "propose-parameter";
306
+ composite: string;
307
+ instance?: string;
308
+ sourceFile?: string;
309
+ /** The factory parameter paths that govern the field. */
310
+ parameters: string[];
311
+ description: string;
312
+ }
313
+ | {
314
+ kind: "refuse-fixed";
315
+ composite: string;
316
+ instance?: string;
317
+ sourceFile?: string;
318
+ description: string;
319
+ }
320
+ | { kind: "edit-declaration"; sourceFile?: string; description: string }
321
+ | { kind: "fall-back"; reason: UnknownOriginReason; sourceFile?: string; description: string };
322
+
323
+ /** One drifted field and what may be done about it. */
324
+ export interface FieldReconcile {
325
+ entity: string;
326
+ path: string;
327
+ declared?: unknown;
328
+ live?: unknown;
329
+ origin: FoldFieldOrigin;
330
+ resolution: DriftResolution;
331
+ }
332
+
333
+ /**
334
+ * Classify one drifted field by its origin.
335
+ *
336
+ * The caller has already established this is drift in #2160's sense: a path
337
+ * source declared, whose live value moved. Undeclared fields are held
338
+ * elsewhere and never reach here, so the only question left is which of the
339
+ * four origins produced the field.
340
+ */
341
+ export function resolveDriftedField(input: {
342
+ entity: string;
343
+ path: string;
344
+ declared?: unknown;
345
+ live?: unknown;
346
+ origin?: PathOrigin;
347
+ provenance?: EntityProvenance;
348
+ }): FieldReconcile {
349
+ const origin = classifyFieldOrigin(input.origin, input.provenance);
350
+ const sourceFile = input.provenance?.sourceFile;
351
+ const move = `${renderValue(input.declared)} to ${renderValue(input.live)}`;
352
+
353
+ const resolution: DriftResolution = ((): DriftResolution => {
354
+ switch (origin.kind) {
355
+ case "composite-parameter": {
356
+ const parameters = origin.parameters;
357
+ return {
358
+ kind: "propose-parameter",
359
+ composite: origin.composite,
360
+ ...(origin.instance ? { instance: origin.instance } : {}),
361
+ ...(sourceFile ? { sourceFile } : {}),
362
+ parameters: [...parameters],
363
+ description:
364
+ `change ${parameters.length === 1 ? "parameter" : "parameters"} ` +
365
+ `${parameters.map((p) => `\`${p}\``).join(", ")} of ` +
366
+ `${compositeCall(origin.composite, origin.instance)}${inFile(sourceFile)} ` +
367
+ `so \`${input.path}\` moves from ${move}. The composite stays.`,
368
+ };
369
+ }
370
+ case "composite-literal":
371
+ return {
372
+ kind: "refuse-fixed",
373
+ composite: origin.composite,
374
+ ...(origin.instance ? { instance: origin.instance } : {}),
375
+ ...(sourceFile ? { sourceFile } : {}),
376
+ description:
377
+ `refused: \`${input.path}\` is fixed by ${compositeCall(origin.composite, origin.instance)}` +
378
+ `${inFile(sourceFile)}, so no argument at the call site moves it. ` +
379
+ `Parameterize the field in composite ${origin.composite}, or stop using the composite here.`,
380
+ };
381
+ case "direct":
382
+ return {
383
+ kind: "edit-declaration",
384
+ ...(sourceFile ? { sourceFile } : {}),
385
+ description: `change the declared value of \`${input.path}\`${inFile(sourceFile)} from ${move}.`,
386
+ };
387
+ case "unknown":
388
+ return {
389
+ kind: "fall-back",
390
+ reason: origin.reason,
391
+ ...(sourceFile ? { sourceFile } : {}),
392
+ description:
393
+ `origin unknown (${unknownOriginText(origin.reason)}), so falling back to changing the ` +
394
+ `declared value of \`${input.path}\`${inFile(sourceFile)} from ${move}.`,
395
+ };
396
+ }
397
+ })();
398
+
399
+ return {
400
+ entity: input.entity,
401
+ path: input.path,
402
+ ...("declared" in input ? { declared: input.declared } : {}),
403
+ ...("live" in input ? { live: input.live } : {}),
404
+ origin,
405
+ resolution,
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Every drifted field in a deep diff, classified.
411
+ *
412
+ * Reads `drifted` only. `unclaimed` is somebody else's field and
413
+ * `accepted` is a difference the baseline holds back, and neither may become an
414
+ * update, so neither is offered a resolution.
415
+ *
416
+ * A drift row of kind `absent` has no live value: the field is declared and the
417
+ * cloud does not have it. That is still drift on a declared path, and the same
418
+ * four origins decide what may be proposed about it.
419
+ */
420
+ export function resolveDeepDrift(
421
+ drifted: readonly DeepEntityDrift[],
422
+ provenanceOf: (entity: string) => EntityProvenance | undefined,
423
+ ): FieldReconcile[] {
424
+ const out: FieldReconcile[] = [];
425
+ for (const entity of drifted) {
426
+ const provenance = provenanceOf(entity.name);
427
+ for (const change of entity.changes as readonly PropertyDrift[]) {
428
+ out.push(
429
+ resolveDriftedField({
430
+ entity: entity.name,
431
+ path: change.path,
432
+ ...("declared" in change ? { declared: change.declared } : {}),
433
+ ...("live" in change ? { live: change.live } : {}),
434
+ ...(change.origin ? { origin: change.origin } : {}),
435
+ ...(provenance ? { provenance } : {}),
436
+ }),
437
+ );
438
+ }
439
+ }
440
+ return out;
441
+ }
@@ -50,14 +50,14 @@ describe("mergeProjectOps (#1675)", () => {
50
50
 
51
51
  const ir = buildGraphIr(result.entities, root);
52
52
  const node = ir.nodes.find((n) => n.id === "deploy");
53
- expect(node).toMatchObject({ kind: "Temporal::Op", lexicon: "temporal", sourceLoc: { file: "deploy.op.ts" } });
53
+ expect(node).toMatchObject({ kind: "Chant::Op", lexicon: "chant", sourceLoc: { file: "deploy.op.ts" } });
54
54
  expect(node?.attrs.name).toBe("deploy");
55
55
  expect(node?.attrs.depends).toEqual(["inner"]);
56
56
  expect(node?.attrs.phases).toEqual([
57
57
  { name: "Apply", steps: [{ kind: "activity", fn: "build" }, { kind: "gate", signalName: "approve" }] },
58
58
  ]);
59
59
  // The sourceDir op discovery already loaded is untouched, not duplicated.
60
- expect(ir.nodes.filter((n) => n.kind === "Temporal::Op")).toHaveLength(2);
60
+ expect(ir.nodes.filter((n) => n.kind === "Chant::Op")).toHaveLength(2);
61
61
  });
62
62
 
63
63
  test("is idempotent — a second merge adds nothing", async () => {
package/src/graph-ops.ts CHANGED
@@ -10,7 +10,7 @@ import { setProvenance } from "./provenance";
10
10
  * root beside `sourceDir: "src"`), and `chant op` / `chant graph` find them via
11
11
  * {@link discoverOps}, which scans from the git root. Join the two scopes: every
12
12
  * Op `discoverOps` finds that discovery did not already load becomes a
13
- * `Temporal::Op` entity in the map, keyed the way discovery keys a default
13
+ * `Chant::Op` entity in the map, keyed the way discovery keys a default
14
14
  * export (the file's basename sans `.op.ts`), so the IR carries the declared
15
15
  * DAG whatever the layout. Ops discovery already loaded (same file) are left
16
16
  * alone. Returns the op-discovery errors for the caller to surface.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `heldElsewhere()` (#2162) — the runtime marker itself, its JSON-safe
3
+ * normalized form, and the type-checking contract the compile-time-only
4
+ * section proves against a real generated-style property interface.
5
+ */
6
+
7
+ import { describe, test, expect } from "vitest";
8
+ import {
9
+ HELD_ELSEWHERE_MARKER,
10
+ HELD_ELSEWHERE_TAG,
11
+ heldElsewhere,
12
+ isHeldElsewhere,
13
+ isNormalizedHeldElsewhere,
14
+ normalizeHeldElsewhere,
15
+ } from "./held-elsewhere";
16
+
17
+ describe("heldElsewhere()", () => {
18
+ test("returns a marker carrying by/reason, symbol-tagged", () => {
19
+ const marker = heldElsewhere<number>({ by: "hpa", reason: "the autoscaler owns replicas after the first apply" });
20
+ expect(isHeldElsewhere(marker)).toBe(true);
21
+ expect((marker as unknown as { by: string }).by).toBe("hpa");
22
+ expect((marker as unknown as { reason: string }).reason).toBe("the autoscaler owns replicas after the first apply");
23
+ });
24
+
25
+ test("isHeldElsewhere is false for an ordinary value and for a lookalike object missing the symbol", () => {
26
+ expect(isHeldElsewhere(3)).toBe(false);
27
+ expect(isHeldElsewhere(undefined)).toBe(false);
28
+ expect(isHeldElsewhere(null)).toBe(false);
29
+ // Same shape, no marker: a plain object an author happened to write with
30
+ // `by`/`reason` keys must never be mistaken for the real thing.
31
+ expect(isHeldElsewhere({ by: "hpa", reason: "not actually held" })).toBe(false);
32
+ });
33
+
34
+ test("the marker's own symbol key never enumerates through Object.entries/JSON.stringify", () => {
35
+ const marker = heldElsewhere<number>({ by: "hpa", reason: "x" });
36
+ expect(Object.entries(marker as unknown as object)).toEqual([
37
+ ["by", "hpa"],
38
+ ["reason", "x"],
39
+ ]);
40
+ // The marker key is invisible to a JSON round-trip — this is exactly why
41
+ // `deep-observation.ts` normalizes it to a JSON-safe tagged form instead
42
+ // of relying on the symbol surviving a `--json` snapshot.
43
+ expect(JSON.parse(JSON.stringify(marker))).toEqual({ by: "hpa", reason: "x" });
44
+ });
45
+
46
+ test("HELD_ELSEWHERE_MARKER is the well-known chant symbol, stable across module instances", () => {
47
+ expect(HELD_ELSEWHERE_MARKER).toBe(Symbol.for("chant.heldElsewhere"));
48
+ });
49
+ });
50
+
51
+ describe("normalizeHeldElsewhere() / isNormalizedHeldElsewhere()", () => {
52
+ test("normalizes to a JSON-safe tagged object", () => {
53
+ const marker = heldElsewhere<number>({ by: "hpa", reason: "x" });
54
+ const normalized = normalizeHeldElsewhere(marker as never);
55
+ expect(normalized).toEqual({ heldElsewhere: HELD_ELSEWHERE_TAG, by: "hpa", reason: "x" });
56
+ expect(isNormalizedHeldElsewhere(normalized)).toBe(true);
57
+ // Round-trips through JSON — this is the shape that actually rides in
58
+ // `--json` output, unlike the symbol-tagged runtime marker above.
59
+ expect(isNormalizedHeldElsewhere(JSON.parse(JSON.stringify(normalized)))).toBe(true);
60
+ });
61
+
62
+ test("isNormalizedHeldElsewhere rejects an ordinary object and the un-normalized runtime marker", () => {
63
+ expect(isNormalizedHeldElsewhere({ by: "hpa", reason: "x" })).toBe(false);
64
+ expect(isNormalizedHeldElsewhere(heldElsewhere<number>({ by: "hpa", reason: "x" }))).toBe(false);
65
+ });
66
+ });
67
+
68
+ // ── Compile-time-only: authoring-time type errors (never executed) ──────────
69
+ //
70
+ // A generated resource's props interface, shaped like a real lexicon's
71
+ // generated constructor argument (chant #2162's whole point: `heldElsewhere`
72
+ // must typecheck in exactly the position the property's own value would go,
73
+ // and nowhere else).
74
+ interface DeploymentSpecProps {
75
+ replicas?: number;
76
+ image: string;
77
+ }
78
+
79
+ function _typeChecksOnly(): void {
80
+ // The success case: assignable where the property's own value goes,
81
+ // exactly like a literal `replicas: 3` would be.
82
+ const ok: DeploymentSpecProps = {
83
+ image: "my-app:latest",
84
+ replicas: heldElsewhere({ by: "hpa", reason: "the autoscaler owns replicas after the first apply" }),
85
+ };
86
+ void ok;
87
+
88
+ const badType: DeploymentSpecProps = {
89
+ image: "my-app:latest",
90
+ // @ts-expect-error — replicas is a number; heldElsewhere<string>() does not typecheck against it.
91
+ replicas: heldElsewhere<string>({ by: "hpa", reason: "x" }),
92
+ };
93
+ void badType;
94
+
95
+ const badProperty: DeploymentSpecProps = {
96
+ image: "my-app:latest",
97
+ // @ts-expect-error — "nope" is not a key of DeploymentSpecProps at all.
98
+ nope: heldElsewhere({ by: "hpa", reason: "x" }),
99
+ };
100
+ void badProperty;
101
+ }
102
+ void _typeChecksOnly;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `heldElsewhere` — a typed, per-property declaration that a field belongs to
3
+ * something else once the first apply happens (chant #2162).
4
+ *
5
+ * An autoscaler owns a Deployment's `replicas` after the first apply. A
6
+ * controller writes an annotation on every sync. An operator was granted one
7
+ * knob deliberately. None of these are drift, and reconciling them back to
8
+ * whatever chant last declared would fight the thing that is actually
9
+ * supposed to own the field.
10
+ *
11
+ * Terraform's answer is `lifecycle { ignore_changes = [...] }`: a list of
12
+ * strings, carrying no reason, invisible once written. This is the typed
13
+ * alternative — a marker written where the property's value would go, so a
14
+ * lexicon's generated types check it the same way they check any other value,
15
+ * and it cannot be attached to a property that does not exist.
16
+ *
17
+ * This is not a suppression comment. `chant-ignore` (#2111) hides a *lint*
18
+ * finding about source; a `heldElsewhere` marker is a fact about who operates
19
+ * a field at *runtime*, so it lives in the typed declaration, where
20
+ * `chant lifecycle plan` and `chant lifecycle diff --live` can render it,
21
+ * rather than in a comment only a linter reads.
22
+ */
23
+
24
+ /**
25
+ * Runtime-only marker, symbol-keyed so it never collides with a real
26
+ * property actually named `by` or `reason` and never serializes by accident
27
+ * through `JSON.stringify` or `Object.entries` (which do not visit symbol
28
+ * keys) — the two places a stray plain-object marker would otherwise leak
29
+ * into a snapshot or a wire payload unnoticed.
30
+ */
31
+ export const HELD_ELSEWHERE_MARKER = Symbol.for("chant.heldElsewhere");
32
+
33
+ export interface HeldElsewhereOptions {
34
+ /** Who holds the field at runtime — the autoscaler, the controller, the operator. Free text: whatever names the holder on this substrate ("hpa", "cluster-autoscaler", "the on-call operator who set this by hand"). */
35
+ by: string;
36
+ /** Why chant does not reconcile this field after the first apply. */
37
+ reason: string;
38
+ }
39
+
40
+ /** The runtime shape a `heldElsewhere()` call actually produces. */
41
+ export interface HeldElsewhereMarker extends HeldElsewhereOptions {
42
+ readonly [HELD_ELSEWHERE_MARKER]: true;
43
+ }
44
+
45
+ /**
46
+ * Declare a property held by something else after the first apply.
47
+ *
48
+ * Typed `T` — the property's own declared type — rather than
49
+ * `HeldElsewhereMarker`, so it type-checks in exactly the position the
50
+ * property's real value would go: `replicas: heldElsewhere({ by: "hpa",
51
+ * reason: "the autoscaler owns replicas after the first apply" })` against a
52
+ * generated `replicas?: number` typechecks the same way `replicas: 3` does,
53
+ * and writing it against a property that does not exist on the resource's
54
+ * props is an excess-property error like any other.
55
+ *
56
+ * The runtime value is the marker object, not a `T` — this function tells
57
+ * TypeScript otherwise on purpose. Every consumer that must tell the two
58
+ * apart checks {@link isHeldElsewhere} before trusting a declared value's
59
+ * real shape: the synth-time serializer (`serializer-walker.ts`, which omits
60
+ * the field from the applied payload so the property is never sent — the
61
+ * provider defaults it once at creation, and the holder owns it from there)
62
+ * and the deep-diff declared side (`deep-observation.ts`/`deep-diff.ts`,
63
+ * which reports a live difference as held rather than as drift).
64
+ */
65
+ export function heldElsewhere<T>(opts: HeldElsewhereOptions): T {
66
+ const marker: HeldElsewhereMarker = {
67
+ [HELD_ELSEWHERE_MARKER]: true,
68
+ by: opts.by,
69
+ reason: opts.reason,
70
+ };
71
+ return marker as unknown as T;
72
+ }
73
+
74
+ /**
75
+ * True when `value` is a {@link HeldElsewhereMarker} — the runtime shape a
76
+ * `heldElsewhere()` call produces, wherever it ends up (a resource's own
77
+ * prop, nested inside a property-kind Declarable's props).
78
+ */
79
+ export function isHeldElsewhere(value: unknown): value is HeldElsewhereMarker {
80
+ return (
81
+ typeof value === "object" &&
82
+ value !== null &&
83
+ (value as Record<symbol, unknown>)[HELD_ELSEWHERE_MARKER] === true
84
+ );
85
+ }
86
+
87
+ /**
88
+ * JSON-safe tag a held marker normalizes to (see `deep-observation.ts`) so
89
+ * its identity survives normalization, `flattenDeepProperties`, and a
90
+ * `--json` round-trip. The symbol marker above does not serialize and
91
+ * `Object.entries` does not enumerate it, so without a JSON-safe stand-in the
92
+ * normalization pass would flatten `{ by, reason }` into two ordinary leaf
93
+ * paths instead of keeping the marker recognizable as one value.
94
+ */
95
+ export const HELD_ELSEWHERE_TAG = "chant:heldElsewhere" as const;
96
+
97
+ /** The normalized, JSON-safe form of a {@link HeldElsewhereMarker}. */
98
+ export interface NormalizedHeldElsewhere extends HeldElsewhereOptions {
99
+ readonly heldElsewhere: typeof HELD_ELSEWHERE_TAG;
100
+ }
101
+
102
+ /** Normalize a runtime marker to its JSON-safe tagged form. */
103
+ export function normalizeHeldElsewhere(marker: HeldElsewhereMarker): NormalizedHeldElsewhere {
104
+ return { heldElsewhere: HELD_ELSEWHERE_TAG, by: marker.by, reason: marker.reason };
105
+ }
106
+
107
+ /** True when `value` is the normalized, JSON-safe tagged form of a held marker. */
108
+ export function isNormalizedHeldElsewhere(value: unknown): value is NormalizedHeldElsewhere {
109
+ return (
110
+ typeof value === "object" &&
111
+ value !== null &&
112
+ (value as { heldElsewhere?: unknown }).heldElsewhere === HELD_ELSEWHERE_TAG
113
+ );
114
+ }