@intentius/chant 0.124.13 → 0.124.14

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 (907) hide show
  1. package/bin/chant +11 -0
  2. package/dist/agents/checks.js +326 -0
  3. package/dist/agents/discover.js +885 -0
  4. package/dist/agents/importer.js +17 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents/types.js +19 -0
  7. package/dist/apply.js +151 -0
  8. package/dist/attrref.js +70 -0
  9. package/dist/audit/catalog.js +350 -0
  10. package/dist/audit/core.js +252 -0
  11. package/dist/audit/discover.js +633 -0
  12. package/dist/audit/fetch.js +666 -0
  13. package/dist/audit/lineage.js +91 -0
  14. package/dist/audit/nginx.js +366 -0
  15. package/dist/audit/prior-art.js +132 -0
  16. package/dist/audit/proof.js +321 -0
  17. package/dist/audit/report-html.js +194 -0
  18. package/dist/audit/report-model.js +198 -0
  19. package/dist/audit/report.js +102 -0
  20. package/dist/audit/rules-doc.js +144 -0
  21. package/dist/audit/secrets.js +405 -0
  22. package/dist/audit/terraform-state.js +140 -0
  23. package/dist/audit/wrangler.js +262 -0
  24. package/dist/behaviour-delta.js +401 -0
  25. package/dist/behaviour-engine.js +357 -0
  26. package/dist/behaviour-graph.js +83 -0
  27. package/dist/behaviour-http.js +188 -0
  28. package/dist/behaviour-kinds.js +172 -0
  29. package/dist/behaviour-overlay.js +50 -0
  30. package/dist/behaviour-predict.js +188 -0
  31. package/dist/behaviour-request.js +194 -0
  32. package/dist/behaviour.js +1325 -0
  33. package/dist/build-params.js +108 -0
  34. package/dist/build.js +668 -0
  35. package/dist/builder.js +44 -0
  36. package/dist/cdk/advise.js +30 -0
  37. package/dist/cdk/assembly.js +122 -0
  38. package/dist/cdk/graph.js +444 -0
  39. package/dist/cdk/tier-map.js +64 -0
  40. package/dist/cdk/types.js +24 -0
  41. package/dist/change-set.js +312 -0
  42. package/dist/child-project.js +21 -0
  43. package/dist/claimed-fields.js +135 -0
  44. package/dist/cli/build-options.js +70 -0
  45. package/dist/cli/build-params-cli.js +139 -0
  46. package/dist/cli/command-group.js +123 -0
  47. package/dist/cli/commands/audit-agents.js +326 -0
  48. package/dist/cli/commands/audit.js +571 -0
  49. package/dist/cli/commands/build.js +920 -0
  50. package/dist/cli/commands/carve-apply.js +153 -0
  51. package/dist/cli/commands/carve-bridge.js +201 -0
  52. package/dist/cli/commands/carve-emit.js +332 -0
  53. package/dist/cli/commands/carve-status.js +119 -0
  54. package/dist/cli/commands/carve.js +204 -0
  55. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  56. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  57. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  58. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  59. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  60. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  61. package/dist/cli/commands/check-lexicon.js +766 -0
  62. package/dist/cli/commands/describe.js +85 -0
  63. package/dist/cli/commands/diff.js +175 -0
  64. package/dist/cli/commands/doctor.js +350 -0
  65. package/dist/cli/commands/import-agents.js +155 -0
  66. package/dist/cli/commands/import.js +670 -0
  67. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  68. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  69. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  70. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  71. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  72. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  73. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  74. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  75. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  76. package/dist/cli/commands/init-lexicon.js +172 -0
  77. package/dist/cli/commands/init.js +381 -0
  78. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  79. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  80. package/dist/cli/commands/licenses.js +125 -0
  81. package/dist/cli/commands/lint.d.ts.map +1 -1
  82. package/dist/cli/commands/lint.js +948 -0
  83. package/dist/cli/commands/list.js +63 -0
  84. package/dist/cli/commands/migrate.d.ts.map +1 -1
  85. package/dist/cli/commands/migrate.js +423 -0
  86. package/dist/cli/commands/onboard.js +403 -0
  87. package/dist/cli/commands/pinned-upgrade.js +191 -0
  88. package/dist/cli/commands/update.js +194 -0
  89. package/dist/cli/commands/vendor.js +217 -0
  90. package/dist/cli/conflict-check.js +104 -0
  91. package/dist/cli/debug.js +8 -0
  92. package/dist/cli/format.js +104 -0
  93. package/dist/cli/handlers/build.js +267 -0
  94. package/dist/cli/handlers/carve-apply.js +30 -0
  95. package/dist/cli/handlers/carve-bridge.js +28 -0
  96. package/dist/cli/handlers/carve-emit.js +50 -0
  97. package/dist/cli/handlers/carve-status.js +26 -0
  98. package/dist/cli/handlers/carve.js +40 -0
  99. package/dist/cli/handlers/change-set.js +46 -0
  100. package/dist/cli/handlers/components.js +730 -0
  101. package/dist/cli/handlers/dev.js +113 -0
  102. package/dist/cli/handlers/emulator.js +233 -0
  103. package/dist/cli/handlers/explain.js +54 -0
  104. package/dist/cli/handlers/fan-out.js +422 -0
  105. package/dist/cli/handlers/gate-resume.js +275 -0
  106. package/dist/cli/handlers/generate.js +84 -0
  107. package/dist/cli/handlers/graph.js +969 -0
  108. package/dist/cli/handlers/init.js +102 -0
  109. package/dist/cli/handlers/lifecycle.js +1727 -0
  110. package/dist/cli/handlers/lint.js +62 -0
  111. package/dist/cli/handlers/migrate.js +48 -0
  112. package/dist/cli/handlers/misc.js +391 -0
  113. package/dist/cli/handlers/operator.js +1162 -0
  114. package/dist/cli/handlers/override-approve.js +102 -0
  115. package/dist/cli/handlers/pr.js +503 -0
  116. package/dist/cli/handlers/promote.js +342 -0
  117. package/dist/cli/handlers/run-generate.js +206 -0
  118. package/dist/cli/handlers/run-wave.js +66 -0
  119. package/dist/cli/handlers/run.js +1191 -0
  120. package/dist/cli/handlers/scenario.js +325 -0
  121. package/dist/cli/handlers/search.js +924 -0
  122. package/dist/cli/handlers/serve.js +32 -0
  123. package/dist/cli/handlers/vendor.js +154 -0
  124. package/dist/cli/index.js +3 -0
  125. package/dist/cli/is-entry-point.js +23 -0
  126. package/dist/cli/lsp/capabilities.js +28 -0
  127. package/dist/cli/lsp/diagnostics.js +34 -0
  128. package/dist/cli/lsp/server.js +329 -0
  129. package/dist/cli/main.d.ts.map +1 -1
  130. package/dist/cli/main.js +2540 -0
  131. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  132. package/dist/cli/mcp/op-tools.js +306 -0
  133. package/dist/cli/mcp/resource-handlers.js +258 -0
  134. package/dist/cli/mcp/resources/context.js +85 -0
  135. package/dist/cli/mcp/server.js +353 -0
  136. package/dist/cli/mcp/tools/build.js +111 -0
  137. package/dist/cli/mcp/tools/composites.js +95 -0
  138. package/dist/cli/mcp/tools/explain.js +166 -0
  139. package/dist/cli/mcp/tools/import.js +49 -0
  140. package/dist/cli/mcp/tools/lint.js +41 -0
  141. package/dist/cli/mcp/tools/scaffold.js +111 -0
  142. package/dist/cli/mcp/tools/search.js +112 -0
  143. package/dist/cli/mcp/types.js +1 -0
  144. package/dist/cli/mcp/workspace-plugins.js +91 -0
  145. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  146. package/dist/cli/mcp/workspace-tools.js +564 -0
  147. package/dist/cli/mcp-config.js +70 -0
  148. package/dist/cli/plugins.js +313 -0
  149. package/dist/cli/registry.js +23 -0
  150. package/dist/cli/reporters/stylish.js +329 -0
  151. package/dist/cli/version.js +15 -0
  152. package/dist/cli/watch.js +79 -0
  153. package/dist/codegen/bound-property-types.js +82 -0
  154. package/dist/codegen/coverage.js +119 -0
  155. package/dist/codegen/docs-file-markers.js +60 -0
  156. package/dist/codegen/docs-pages.js +115 -0
  157. package/dist/codegen/docs-rule-scanning.js +164 -0
  158. package/dist/codegen/docs-rules.js +76 -0
  159. package/dist/codegen/docs-sections.js +117 -0
  160. package/dist/codegen/docs-sidebar.js +65 -0
  161. package/dist/codegen/docs-types.js +4 -0
  162. package/dist/codegen/docs.js +356 -0
  163. package/dist/codegen/fetch.js +340 -0
  164. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  165. package/dist/codegen/generate-registry.js +71 -0
  166. package/dist/codegen/generate-runtime-index.js +56 -0
  167. package/dist/codegen/generate-typescript.js +119 -0
  168. package/dist/codegen/generate.js +115 -0
  169. package/dist/codegen/json-patch.js +141 -0
  170. package/dist/codegen/json-schema-to-ts.js +344 -0
  171. package/dist/codegen/json-schema.js +246 -0
  172. package/dist/codegen/lexicon-regen.js +412 -0
  173. package/dist/codegen/naming.js +217 -0
  174. package/dist/codegen/okf-lexicon.js +245 -0
  175. package/dist/codegen/package.js +150 -0
  176. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  177. package/dist/codegen/pinned-upgrade.js +494 -0
  178. package/dist/codegen/registry.js +60 -0
  179. package/dist/codegen/rehype-base-url.mjs +68 -0
  180. package/dist/codegen/rolling-upgrade.js +217 -0
  181. package/dist/codegen/surface-snapshot.js +537 -0
  182. package/dist/codegen/testdata/postgres-tags.json +242 -0
  183. package/dist/codegen/topo-sort.js +36 -0
  184. package/dist/codegen/typecheck.js +115 -0
  185. package/dist/codegen/validate.js +200 -0
  186. package/dist/codegen/version-bump.js +35 -0
  187. package/dist/components/auto-release.js +143 -0
  188. package/dist/components/builders.js +47 -0
  189. package/dist/components/capability-plugin-loader.js +175 -0
  190. package/dist/components/capability-plugin-schema.js +46 -0
  191. package/dist/components/capability-plugin.js +99 -0
  192. package/dist/components/capability.js +60 -0
  193. package/dist/components/changed-files.js +72 -0
  194. package/dist/components/cli-support.js +427 -0
  195. package/dist/components/component.js +120 -0
  196. package/dist/components/component.schema.json +271 -0
  197. package/dist/components/config-defaults.js +130 -0
  198. package/dist/components/deploy-units.js +60 -0
  199. package/dist/components/discover.d.ts.map +1 -1
  200. package/dist/components/discover.js +189 -0
  201. package/dist/components/driver-output.js +86 -0
  202. package/dist/components/driver.js +728 -0
  203. package/dist/components/fan-out-output.js +165 -0
  204. package/dist/components/fan-out-record.js +94 -0
  205. package/dist/components/fan-out-run.js +373 -0
  206. package/dist/components/fan-out-support.js +60 -0
  207. package/dist/components/fan-out.js +273 -0
  208. package/dist/components/gate-plan.js +41 -0
  209. package/dist/components/gated-wave-pipeline.js +46 -0
  210. package/dist/components/index.js +24 -0
  211. package/dist/components/layers.js +28 -0
  212. package/dist/components/manifest-persistence.js +108 -0
  213. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  214. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  215. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  216. package/dist/components/pilots/lambda.pilot.js +52 -0
  217. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  218. package/dist/components/pilots/project.js +12 -0
  219. package/dist/components/pr-pipeline.js +134 -0
  220. package/dist/components/pr-run.js +363 -0
  221. package/dist/components/presets/ecs-fargate.js +72 -0
  222. package/dist/components/presets/index.js +28 -0
  223. package/dist/components/presets/lambda.js +34 -0
  224. package/dist/components/presets/single-host-compose.js +41 -0
  225. package/dist/components/promote.js +549 -0
  226. package/dist/components/registry.js +34 -0
  227. package/dist/components/run-progress.js +33 -0
  228. package/dist/components/sandbox/driver.d.ts.map +1 -1
  229. package/dist/components/sandbox/driver.js +93 -0
  230. package/dist/components/sandbox/run.js +115 -0
  231. package/dist/components/starter-plugin.js +104 -0
  232. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  233. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  234. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  235. package/dist/components/verbs/bom-writer.js +287 -0
  236. package/dist/components/verbs/build-archive.js +188 -0
  237. package/dist/components/verbs/build.js +142 -0
  238. package/dist/components/verbs/cloud-executor.js +110 -0
  239. package/dist/components/verbs/component-bom.js +125 -0
  240. package/dist/components/verbs/ensure-secret.js +50 -0
  241. package/dist/components/verbs/index.js +61 -0
  242. package/dist/components/verbs/license-policy.js +75 -0
  243. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  244. package/dist/components/verbs/process-runner.js +78 -0
  245. package/dist/components/verbs/r2-sync.js +93 -0
  246. package/dist/components/verbs/reproducibility.js +62 -0
  247. package/dist/components/verbs/run-agent.js +577 -0
  248. package/dist/components/verbs/sbom-generator.js +119 -0
  249. package/dist/components/verbs/sbom.js +97 -0
  250. package/dist/components/verbs/shell.js +32 -0
  251. package/dist/components/verbs/sign.js +221 -0
  252. package/dist/components/verbs/stub.js +28 -0
  253. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  254. package/dist/components/verbs/verify.js +112 -0
  255. package/dist/components/verbs/vex.js +131 -0
  256. package/dist/components/verbs/vuln-gate.js +152 -0
  257. package/dist/components/verbs/vuln-scan.js +288 -0
  258. package/dist/components/verbs/wait-verify.js +106 -0
  259. package/dist/components/verbs/wrangler.js +179 -0
  260. package/dist/components/wave-plan.js +102 -0
  261. package/dist/composite.js +305 -0
  262. package/dist/config-import.d.ts.map +1 -1
  263. package/dist/config-import.js +22 -0
  264. package/dist/config-sandbox.js +122 -0
  265. package/dist/config-static.js +206 -0
  266. package/dist/config.js +605 -0
  267. package/dist/content-digest.js +23 -0
  268. package/dist/declarable.js +114 -0
  269. package/dist/deep-observation.js +437 -0
  270. package/dist/detectLexicon.js +54 -0
  271. package/dist/discovery/cache.js +70 -0
  272. package/dist/discovery/collect.js +208 -0
  273. package/dist/discovery/cycles.js +95 -0
  274. package/dist/discovery/entity-wire-codec.js +391 -0
  275. package/dist/discovery/entity-wire.js +42 -0
  276. package/dist/discovery/files.js +121 -0
  277. package/dist/discovery/fold-import.d.ts.map +1 -1
  278. package/dist/discovery/fold-import.js +4178 -0
  279. package/dist/discovery/fold-rank.js +292 -0
  280. package/dist/discovery/graph.js +115 -0
  281. package/dist/discovery/import.d.ts.map +1 -1
  282. package/dist/discovery/import.js +65 -0
  283. package/dist/discovery/index.js +333 -0
  284. package/dist/discovery/param-deps.js +487 -0
  285. package/dist/discovery/resolve.js +39 -0
  286. package/dist/discovery/sandbox/bundle.js +214 -0
  287. package/dist/discovery/sandbox/child-errors.js +26 -0
  288. package/dist/discovery/sandbox/config-run.js +100 -0
  289. package/dist/discovery/sandbox/config-wire.js +183 -0
  290. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  291. package/dist/discovery/sandbox/driver.js +333 -0
  292. package/dist/discovery/sandbox/fork.js +229 -0
  293. package/dist/discovery/sandbox/policy-run.js +142 -0
  294. package/dist/discovery/sandbox/policy-wire.js +152 -0
  295. package/dist/discovery/sandbox/run.js +77 -0
  296. package/dist/discovery/specifier-resolve.js +320 -0
  297. package/dist/discovery/walk.js +284 -0
  298. package/dist/effect-receipt.js +340 -0
  299. package/dist/env.js +55 -0
  300. package/dist/errors.js +62 -0
  301. package/dist/fold/fold.js +1434 -0
  302. package/dist/fold/foldable-helpers.js +229 -0
  303. package/dist/fold/subset.js +696 -0
  304. package/dist/fold-provenance.js +414 -0
  305. package/dist/gate-decision.js +260 -0
  306. package/dist/gate-resume-run.js +154 -0
  307. package/dist/gated-waves.js +165 -0
  308. package/dist/governance.js +43 -0
  309. package/dist/graph-declared.js +45 -0
  310. package/dist/graph-detail.js +207 -0
  311. package/dist/graph-dot.js +54 -0
  312. package/dist/graph-effective.js +107 -0
  313. package/dist/graph-ir.js +623 -0
  314. package/dist/graph-layout.js +184 -0
  315. package/dist/graph-lens.js +105 -0
  316. package/dist/graph-mermaid.js +83 -0
  317. package/dist/graph-ops.js +39 -0
  318. package/dist/graph-refs.js +156 -0
  319. package/dist/held-elsewhere.js +87 -0
  320. package/dist/identity.js +251 -0
  321. package/dist/import/base-parser.js +34 -0
  322. package/dist/import/embedded.js +255 -0
  323. package/dist/import/generator.js +1 -0
  324. package/dist/import/ir-utils.js +85 -0
  325. package/dist/import/parser-options.js +45 -0
  326. package/dist/import/parser.js +1 -0
  327. package/dist/index.js +118 -0
  328. package/dist/intrinsic-interpolation.js +73 -0
  329. package/dist/intrinsic.js +31 -0
  330. package/dist/join-key.js +35 -0
  331. package/dist/kubectl-context.js +150 -0
  332. package/dist/lexicon-config.js +58 -0
  333. package/dist/lexicon-integrity.js +47 -0
  334. package/dist/lexicon-manifest.js +70 -0
  335. package/dist/lexicon-module.d.ts.map +1 -1
  336. package/dist/lexicon-module.js +241 -0
  337. package/dist/lexicon-output.js +218 -0
  338. package/dist/lexicon-plugin-helpers.js +104 -0
  339. package/dist/lexicon-schema.js +136 -0
  340. package/dist/lexicon.js +130 -0
  341. package/dist/lifecycle/affected.js +224 -0
  342. package/dist/lifecycle/assert-live.js +110 -0
  343. package/dist/lifecycle/build-ledger-store.js +160 -0
  344. package/dist/lifecycle/build-ledger.js +155 -0
  345. package/dist/lifecycle/change-set.js +356 -0
  346. package/dist/lifecycle/converge-ledger.js +161 -0
  347. package/dist/lifecycle/deep-diff.js +233 -0
  348. package/dist/lifecycle/deep-observe.js +147 -0
  349. package/dist/lifecycle/digest.js +143 -0
  350. package/dist/lifecycle/disruption.js +135 -0
  351. package/dist/lifecycle/gate-applied.js +113 -0
  352. package/dist/lifecycle/gate-audit.js +341 -0
  353. package/dist/lifecycle/gate-ledger-select.js +89 -0
  354. package/dist/lifecycle/gate-ledger.js +193 -0
  355. package/dist/lifecycle/gate-origin.js +117 -0
  356. package/dist/lifecycle/gate-pending.js +58 -0
  357. package/dist/lifecycle/git.js +1182 -0
  358. package/dist/lifecycle/identity.js +58 -0
  359. package/dist/lifecycle/index.js +26 -0
  360. package/dist/lifecycle/lease.js +283 -0
  361. package/dist/lifecycle/ledger-port.js +1 -0
  362. package/dist/lifecycle/legacy-digest.js +91 -0
  363. package/dist/lifecycle/live-diff.js +274 -0
  364. package/dist/lifecycle/local-note.js +15 -0
  365. package/dist/lifecycle/member-ledger.js +122 -0
  366. package/dist/lifecycle/observation-baseline.js +155 -0
  367. package/dist/lifecycle/observe.js +375 -0
  368. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  369. package/dist/lifecycle/override-ledger.js +54 -0
  370. package/dist/lifecycle/plan-digest.js +110 -0
  371. package/dist/lifecycle/plan-ledger.js +129 -0
  372. package/dist/lifecycle/receipt-plan.js +192 -0
  373. package/dist/lifecycle/release-ledger.js +155 -0
  374. package/dist/lifecycle/release-read.js +64 -0
  375. package/dist/lifecycle/replay.js +193 -0
  376. package/dist/lifecycle/rollback.js +102 -0
  377. package/dist/lifecycle/run-ledger.js +183 -0
  378. package/dist/lifecycle/scenario-eval.js +204 -0
  379. package/dist/lifecycle/scenario.js +214 -0
  380. package/dist/lifecycle/snapshot.js +239 -0
  381. package/dist/lifecycle/status.js +447 -0
  382. package/dist/lifecycle/symptoms.js +117 -0
  383. package/dist/lifecycle/teardown.js +255 -0
  384. package/dist/lifecycle/types.js +1 -0
  385. package/dist/lifecycle/unobserved-gate.js +33 -0
  386. package/dist/lifecycle/wave-plans.js +68 -0
  387. package/dist/lifecycle/work-item-id.js +36 -0
  388. package/dist/lifecycle/work-lease.js +393 -0
  389. package/dist/lint/component-checks.js +87 -0
  390. package/dist/lint/config.js +483 -0
  391. package/dist/lint/declarative.js +122 -0
  392. package/dist/lint/discover.d.ts.map +1 -1
  393. package/dist/lint/discover.js +138 -0
  394. package/dist/lint/engine.js +246 -0
  395. package/dist/lint/knowledge-checks.js +64 -0
  396. package/dist/lint/named-checks.js +22 -0
  397. package/dist/lint/output-checks.js +87 -0
  398. package/dist/lint/output-docs.js +163 -0
  399. package/dist/lint/parser.js +33 -0
  400. package/dist/lint/pipeline-change-gate.js +70 -0
  401. package/dist/lint/policy-import.d.ts +1 -1
  402. package/dist/lint/policy-import.d.ts.map +1 -1
  403. package/dist/lint/policy-import.js +62 -0
  404. package/dist/lint/policy-sandbox.js +86 -0
  405. package/dist/lint/policy.js +122 -0
  406. package/dist/lint/post-synth.js +87 -0
  407. package/dist/lint/presets/relaxed.json +26 -0
  408. package/dist/lint/presets/strict.json +24 -24
  409. package/dist/lint/receipt-checks.js +83 -0
  410. package/dist/lint/rule-loader.d.ts.map +1 -1
  411. package/dist/lint/rule-loader.js +89 -0
  412. package/dist/lint/rule-registry.js +86 -0
  413. package/dist/lint/rule.js +1 -0
  414. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  415. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  416. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  417. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  418. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  419. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  420. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  421. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  422. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  423. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  424. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  425. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  426. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  427. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  428. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  429. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  430. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  431. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  432. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  433. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  434. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  435. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  436. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  437. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  438. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  439. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  440. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  441. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  442. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  443. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  444. package/dist/lint/rules/comp/index.js +33 -0
  445. package/dist/lint/rules/comp/support.js +129 -0
  446. package/dist/lint/rules/composite-scope.js +51 -0
  447. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  448. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  449. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  450. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  451. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  452. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  453. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  454. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  455. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  456. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  457. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  458. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  459. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  460. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  461. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  462. package/dist/lint/rules/export-required.js +115 -0
  463. package/dist/lint/rules/file-declarable-limit.js +88 -0
  464. package/dist/lint/rules/flat-declarations.js +62 -0
  465. package/dist/lint/rules/index.js +90 -0
  466. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  467. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  468. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  469. package/dist/lint/rules/no-string-ref.js +59 -0
  470. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  471. package/dist/lint/rules/no-unused-declarable.js +142 -0
  472. package/dist/lint/rules/op/index.js +28 -0
  473. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  474. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  475. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  476. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  477. package/dist/lint/rules/op/support.js +11 -0
  478. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  479. package/dist/lint/rules/property-kind.js +56 -0
  480. package/dist/lint/rules/single-concern-file.js +71 -0
  481. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  482. package/dist/lint/selectors.js +168 -0
  483. package/dist/lint/suppressions.js +129 -0
  484. package/dist/lint/typescript.js +17 -0
  485. package/dist/live-endpoint.js +113 -0
  486. package/dist/live-read-session.js +76 -0
  487. package/dist/lsp/lexicon-providers.js +163 -0
  488. package/dist/lsp/types.js +5 -0
  489. package/dist/managed-fields.js +293 -0
  490. package/dist/mcp/types.js +4 -0
  491. package/dist/notify-verify.js +0 -1
  492. package/dist/notify.js +0 -1
  493. package/dist/notify.schema.json +72 -0
  494. package/dist/observation.js +266 -0
  495. package/dist/okf-read.js +134 -0
  496. package/dist/okf.js +275 -0
  497. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  498. package/dist/op/activities/activity-contracts.js +313 -0
  499. package/dist/op/activities/apply.js +582 -0
  500. package/dist/op/activities/build.js +18 -0
  501. package/dist/op/activities/change-coverage.js +40 -0
  502. package/dist/op/activities/change-set.js +64 -0
  503. package/dist/op/activities/converge.js +492 -0
  504. package/dist/op/activities/decide.js +211 -0
  505. package/dist/op/activities/env-teardown.js +110 -0
  506. package/dist/op/activities/factory.js +591 -0
  507. package/dist/op/activities/forge-pull-request.js +82 -0
  508. package/dist/op/activities/gh-host.js +78 -0
  509. package/dist/op/activities/guard-validate.js +165 -0
  510. package/dist/op/activities/http-check.js +40 -0
  511. package/dist/op/activities/index.js +59 -0
  512. package/dist/op/activities/lexicon-upgrade.js +629 -0
  513. package/dist/op/activities/lifecycle.js +109 -0
  514. package/dist/op/activities/pipeline-audit.js +147 -0
  515. package/dist/op/activities/policy.js +26 -0
  516. package/dist/op/activities/predict-behaviour.js +341 -0
  517. package/dist/op/activities/propose-upgrade.js +165 -0
  518. package/dist/op/activities/reconcile.js +997 -0
  519. package/dist/op/activities/shell.js +118 -0
  520. package/dist/op/activities/source-release.js +97 -0
  521. package/dist/op/activities/source-rollback.js +165 -0
  522. package/dist/op/activities/teardown.js +17 -0
  523. package/dist/op/activities/util.js +6 -0
  524. package/dist/op/activities/wait.js +51 -0
  525. package/dist/op/activities/work-evidence.js +35 -0
  526. package/dist/op/activities/workflow-audit.js +133 -0
  527. package/dist/op/activity-contract-registry.js +106 -0
  528. package/dist/op/activity-contract.js +232 -0
  529. package/dist/op/activity-failure.js +31 -0
  530. package/dist/op/activity-profiles.js +129 -0
  531. package/dist/op/activity-registry.js +105 -0
  532. package/dist/op/activity-runtime.js +28 -0
  533. package/dist/op/builders.js +801 -0
  534. package/dist/op/change-signal.js +118 -0
  535. package/dist/op/composites/apply-op.js +151 -0
  536. package/dist/op/composites/behaviour-op.js +65 -0
  537. package/dist/op/composites/converge-op.js +141 -0
  538. package/dist/op/composites/index.js +19 -0
  539. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  540. package/dist/op/composites/pipeline-audit-op.js +50 -0
  541. package/dist/op/composites/reconcile-op.js +84 -0
  542. package/dist/op/composites/watch-op.js +69 -0
  543. package/dist/op/composites/workflow-audit-op.js +57 -0
  544. package/dist/op/converge-rule.js +205 -0
  545. package/dist/op/cron.js +146 -0
  546. package/dist/op/decide-backend.js +181 -0
  547. package/dist/op/decide-config.js +68 -0
  548. package/dist/op/decide-read-inputs.js +141 -0
  549. package/dist/op/discover.d.ts.map +1 -1
  550. package/dist/op/discover.js +213 -0
  551. package/dist/op/duration.js +21 -0
  552. package/dist/op/emulator-freshness.js +80 -0
  553. package/dist/op/emulator-lifecycle.js +269 -0
  554. package/dist/op/factory.js +83 -0
  555. package/dist/op/gate-approval.js +172 -0
  556. package/dist/op/gate-name.js +86 -0
  557. package/dist/op/gate-plan-context.js +167 -0
  558. package/dist/op/gate-point-run.js +67 -0
  559. package/dist/op/gate-point.js +111 -0
  560. package/dist/op/gate-resume-plan.js +155 -0
  561. package/dist/op/gate-resume.js +31 -0
  562. package/dist/op/gate-review.js +290 -0
  563. package/dist/op/gate-run-locator.js +85 -0
  564. package/dist/op/gate-summary.js +100 -0
  565. package/dist/op/gate-wait.js +56 -0
  566. package/dist/op/gate.js +449 -0
  567. package/dist/op/generate-pipeline.js +124 -0
  568. package/dist/op/index.js +51 -0
  569. package/dist/op/lifecycle-receipt-store.js +45 -0
  570. package/dist/op/local-executor.js +1164 -0
  571. package/dist/op/local-output.js +140 -0
  572. package/dist/op/op-ir.js +317 -0
  573. package/dist/op/op-verb-class.js +131 -0
  574. package/dist/op/op-waves-notify.js +136 -0
  575. package/dist/op/op-waves-run.js +1657 -0
  576. package/dist/op/op-waves.js +409 -0
  577. package/dist/op/operator.js +743 -0
  578. package/dist/op/receipt-store.js +94 -0
  579. package/dist/op/resource.js +23 -0
  580. package/dist/op/run-context.js +28 -0
  581. package/dist/op/run-live.js +256 -0
  582. package/dist/op/runtime.js +21 -0
  583. package/dist/op/runtimes/local.js +195 -0
  584. package/dist/op/source-archive.js +131 -0
  585. package/dist/op/step-output-ref.js +397 -0
  586. package/dist/op/steward-beside.js +169 -0
  587. package/dist/op/steward-points.js +138 -0
  588. package/dist/op/steward-turn.js +59 -0
  589. package/dist/op/steward.js +317 -0
  590. package/dist/op/types.js +25 -0
  591. package/dist/op/work-lease-decl.js +81 -0
  592. package/dist/op/work-lease-run.js +393 -0
  593. package/dist/override-gate.js +284 -0
  594. package/dist/owner-chain.js +74 -0
  595. package/dist/ownership.js +92 -0
  596. package/dist/params.js +61 -0
  597. package/dist/plan-summary.js +711 -0
  598. package/dist/point-decision.js +211 -0
  599. package/dist/pr-forge.js +810 -0
  600. package/dist/pr-loop.js +203 -0
  601. package/dist/project-codegen.js +191 -0
  602. package/dist/project-root.js +74 -0
  603. package/dist/project-validation.js +50 -0
  604. package/dist/provenance.js +157 -0
  605. package/dist/pseudo-parameter.js +40 -0
  606. package/dist/reconcile.js +450 -0
  607. package/dist/resource-attributes.js +33 -0
  608. package/dist/runtime-adapter.js +77 -0
  609. package/dist/runtime.js +66 -0
  610. package/dist/secret-materialization.js +153 -0
  611. package/dist/secret-provenance.js +250 -0
  612. package/dist/serializer-walker.js +112 -0
  613. package/dist/serializer.js +1 -0
  614. package/dist/sort.js +45 -0
  615. package/dist/stack-output.js +115 -0
  616. package/dist/telemetry-attribution.js +149 -0
  617. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  618. package/dist/terraform/adopt-state.js +40 -0
  619. package/dist/terraform/aws-resources.js +384 -0
  620. package/dist/terraform/bridge.js +204 -0
  621. package/dist/terraform/carve-provider.js +155 -0
  622. package/dist/terraform/carve.js +138 -0
  623. package/dist/terraform/data-source-shape.js +57 -0
  624. package/dist/terraform/emit-source.js +36 -0
  625. package/dist/terraform/excise.js +123 -0
  626. package/dist/terraform/graduate.js +128 -0
  627. package/dist/terraform/graph.js +435 -0
  628. package/dist/terraform/hcl2json.js +60 -0
  629. package/dist/terraform/manifest.js +149 -0
  630. package/dist/terraform/parse.js +81 -0
  631. package/dist/terraform/providers/aws.js +146 -0
  632. package/dist/terraform/providers/gcp.js +294 -0
  633. package/dist/terraform/providers/index.js +17 -0
  634. package/dist/terraform/providers/kubernetes.js +253 -0
  635. package/dist/terraform/score.js +151 -0
  636. package/dist/terraform/state.js +67 -0
  637. package/dist/terraform/tier-map.js +74 -0
  638. package/dist/terraform/types.js +13 -0
  639. package/dist/terraform/unified-diff.js +132 -0
  640. package/dist/testing.js +295 -0
  641. package/dist/toml-emit.js +153 -0
  642. package/dist/toml-parse.js +355 -0
  643. package/dist/toml-utils.js +60 -0
  644. package/dist/toml.js +428 -0
  645. package/dist/ts-loader.d.ts +46 -0
  646. package/dist/ts-loader.d.ts.map +1 -0
  647. package/dist/ts-loader.js +103 -0
  648. package/dist/types.js +1 -0
  649. package/dist/utils.js +61 -0
  650. package/dist/validation.js +20 -0
  651. package/dist/vendor/js-yaml-load.js +1662 -0
  652. package/dist/workspace/__fixtures__/contract-repo.js +103 -0
  653. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  654. package/dist/workspace/__fixtures__/sessions.js +91 -0
  655. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  656. package/dist/workspace/agent-cli.js +99 -0
  657. package/dist/workspace/agent.schema.json +390 -0
  658. package/dist/workspace/answers-ledger.js +89 -0
  659. package/dist/workspace/box-cli.js +142 -0
  660. package/dist/workspace/box-factory-write.js +196 -0
  661. package/dist/workspace/box-factory-write.schema.json +342 -0
  662. package/dist/workspace/box-factory.js +88 -0
  663. package/dist/workspace/box-intent.js +126 -0
  664. package/dist/workspace/box-isolation.js +169 -0
  665. package/dist/workspace/box-listing-write.schema.json +204 -0
  666. package/dist/workspace/box-listing.js +298 -0
  667. package/dist/workspace/box-publish.js +352 -0
  668. package/dist/workspace/box-publish.schema.json +498 -0
  669. package/dist/workspace/box-services.js +39 -0
  670. package/dist/workspace/broker-protocol.js +359 -0
  671. package/dist/workspace/broker-protocol.schema.json +228 -0
  672. package/dist/workspace/change-set.schema.json +460 -0
  673. package/dist/workspace/changes-cli.js +56 -0
  674. package/dist/workspace/changes.js +446 -0
  675. package/dist/workspace/changes.schema.json +642 -0
  676. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  677. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  678. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  679. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  680. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  681. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  682. package/dist/workspace/chant-migrations.js +113 -0
  683. package/dist/workspace/check.schema.json +433 -0
  684. package/dist/workspace/checks/box-isolation.js +62 -0
  685. package/dist/workspace/checks/boxes.js +491 -0
  686. package/dist/workspace/checks/diagrams.js +104 -0
  687. package/dist/workspace/checks/generated.js +419 -0
  688. package/dist/workspace/checks/ledgers.js +212 -0
  689. package/dist/workspace/checks/links.js +191 -0
  690. package/dist/workspace/checks/live.js +100 -0
  691. package/dist/workspace/checks/pipelines.js +188 -0
  692. package/dist/workspace/checks/records.js +169 -0
  693. package/dist/workspace/checks.js +426 -0
  694. package/dist/workspace/ci-green-cli.js +125 -0
  695. package/dist/workspace/ci-green-forge.js +83 -0
  696. package/dist/workspace/ci-green-workflow.js +199 -0
  697. package/dist/workspace/ci-green.js +325 -0
  698. package/dist/workspace/ci-last-green.schema.json +158 -0
  699. package/dist/workspace/compose-graph.js +235 -0
  700. package/dist/workspace/compose-reports.js +146 -0
  701. package/dist/workspace/composites.js +264 -0
  702. package/dist/workspace/composites.schema.json +617 -0
  703. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  704. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  705. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  706. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  707. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  708. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  709. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  710. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  711. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  712. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  713. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  714. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  715. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  716. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  717. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  718. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  719. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  720. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  721. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  722. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  723. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  724. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  725. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  726. package/dist/workspace/conformance/broker.js +736 -0
  727. package/dist/workspace/conformance/index.js +673 -0
  728. package/dist/workspace/conformance/vitest.js +108 -0
  729. package/dist/workspace/conformance/writer.js +1080 -0
  730. package/dist/workspace/decide.js +879 -0
  731. package/dist/workspace/decision-points.schema.json +135 -135
  732. package/dist/workspace/declaration-write-cli.js +74 -0
  733. package/dist/workspace/declaration-write.js +238 -0
  734. package/dist/workspace/declaration.js +938 -0
  735. package/dist/workspace/declaration.schema.json +1413 -1413
  736. package/dist/workspace/declared-kinds.js +106 -0
  737. package/dist/workspace/environments.js +164 -0
  738. package/dist/workspace/evidence.schema.json +279 -0
  739. package/dist/workspace/export-cli.js +185 -0
  740. package/dist/workspace/export.js +499 -0
  741. package/dist/workspace/factory-rules.js +175 -0
  742. package/dist/workspace/generated-files.js +92 -0
  743. package/dist/workspace/glob.js +45 -0
  744. package/dist/workspace/graph-cache.js +352 -0
  745. package/dist/workspace/graph-cli.js +348 -0
  746. package/dist/workspace/graph.schema.json +1338 -0
  747. package/dist/workspace/identity-gate-rule.js +75 -0
  748. package/dist/workspace/identity.js +126 -0
  749. package/dist/workspace/import.js +283 -0
  750. package/dist/workspace/init.js +620 -0
  751. package/dist/workspace/intent-cli.js +263 -0
  752. package/dist/workspace/intent-joins.js +201 -0
  753. package/dist/workspace/intent-record.js +352 -0
  754. package/dist/workspace/intent-record.schema.json +864 -0
  755. package/dist/workspace/intent.js +1582 -0
  756. package/dist/workspace/intent.schema.json +2420 -0
  757. package/dist/workspace/json-edit.js +221 -0
  758. package/dist/workspace/jsonc.js +235 -0
  759. package/dist/workspace/kind-readers.js +103 -0
  760. package/dist/workspace/kinds.js +587 -0
  761. package/dist/workspace/lexicon-symbols.js +112 -0
  762. package/dist/workspace/lineage-adopt-cli.js +102 -0
  763. package/dist/workspace/lineage-adopt.js +360 -0
  764. package/dist/workspace/lineage-check.js +282 -0
  765. package/dist/workspace/lineage-cli.js +121 -0
  766. package/dist/workspace/lineage-hash-index.js +284 -0
  767. package/dist/workspace/lineage-init.js +507 -0
  768. package/dist/workspace/lineage-lock.js +387 -0
  769. package/dist/workspace/lineage-merge.js +45 -0
  770. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  771. package/dist/workspace/lineage-migrations.js +329 -0
  772. package/dist/workspace/lineage-provenance.js +36 -0
  773. package/dist/workspace/lineage-update.js +138 -0
  774. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  775. package/dist/workspace/lineage-upgrade.js +889 -0
  776. package/dist/workspace/lineage-vendor.js +171 -0
  777. package/dist/workspace/lineage-version.js +128 -0
  778. package/dist/workspace/lineage-versions.js +275 -0
  779. package/dist/workspace/links.js +330 -0
  780. package/dist/workspace/ls-generated.js +95 -0
  781. package/dist/workspace/ls.js +277 -0
  782. package/dist/workspace/ls.schema.json +333 -0
  783. package/dist/workspace/member-commands.js +508 -0
  784. package/dist/workspace/member-handles.js +216 -0
  785. package/dist/workspace/member-pipeline.js +258 -0
  786. package/dist/workspace/member-run.js +117 -0
  787. package/dist/workspace/member-write.schema.json +193 -0
  788. package/dist/workspace/nested-graph.js +156 -0
  789. package/dist/workspace/nesting.js +38 -0
  790. package/dist/workspace/patch.js +301 -0
  791. package/dist/workspace/patch.schema.json +321 -0
  792. package/dist/workspace/pin-cli.js +55 -0
  793. package/dist/workspace/pin-integrity.js +100 -0
  794. package/dist/workspace/plan-summary.schema.json +155 -0
  795. package/dist/workspace/point-answer.schema.json +129 -129
  796. package/dist/workspace/points-cli.js +291 -0
  797. package/dist/workspace/points-write.schema.json +660 -0
  798. package/dist/workspace/points.js +521 -0
  799. package/dist/workspace/points.schema.json +916 -0
  800. package/dist/workspace/pr-report.schema.json +108 -0
  801. package/dist/workspace/principal-classes.js +245 -0
  802. package/dist/workspace/reason-codes.js +326 -0
  803. package/dist/workspace/record-assets.js +151 -0
  804. package/dist/workspace/record-decided.js +105 -0
  805. package/dist/workspace/record-sessions.js +96 -0
  806. package/dist/workspace/record-source.js +114 -0
  807. package/dist/workspace/records-amend.schema.json +284 -0
  808. package/dist/workspace/records-checkout.js +76 -0
  809. package/dist/workspace/records-cli.js +707 -0
  810. package/dist/workspace/records-close.js +116 -0
  811. package/dist/workspace/records-close.schema.json +278 -0
  812. package/dist/workspace/records-new.schema.json +202 -0
  813. package/dist/workspace/records-review.schema.json +367 -0
  814. package/dist/workspace/records-since.js +317 -0
  815. package/dist/workspace/records-since.schema.json +218 -0
  816. package/dist/workspace/records-write.js +1229 -0
  817. package/dist/workspace/records.js +1345 -0
  818. package/dist/workspace/records.schema.json +604 -0
  819. package/dist/workspace/reference-kinds/app/package.json +10 -0
  820. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  821. package/dist/workspace/returns.js +303 -0
  822. package/dist/workspace/root-refusal.js +75 -0
  823. package/dist/workspace/run-statement.schema.json +622 -0
  824. package/dist/workspace/runs-cli.js +584 -0
  825. package/dist/workspace/runs-write.schema.json +994 -0
  826. package/dist/workspace/runs.js +708 -0
  827. package/dist/workspace/runs.schema.json +1257 -0
  828. package/dist/workspace/runtimes.js +76 -0
  829. package/dist/workspace/session-kinds.js +83 -0
  830. package/dist/workspace/signers.schema.json +206 -0
  831. package/dist/workspace/source-block.js +125 -0
  832. package/dist/workspace/squash.js +177 -0
  833. package/dist/workspace/status-gates.js +162 -0
  834. package/dist/workspace/status-stewards.js +330 -0
  835. package/dist/workspace/status.js +514 -0
  836. package/dist/workspace/status.schema.json +998 -0
  837. package/dist/workspace/steward-index.js +191 -0
  838. package/dist/workspace/symbols.js +188 -0
  839. package/dist/workspace/template-manifest.js +227 -0
  840. package/dist/workspace/template-pins.js +115 -0
  841. package/dist/workspace/trailer-joins.js +65 -0
  842. package/dist/workspace/trailers.js +111 -0
  843. package/dist/workspace/tree.js +136 -0
  844. package/dist/workspace/trust/attestor.js +63 -0
  845. package/dist/workspace/trust/dsse.js +108 -0
  846. package/dist/workspace/trust/evidence-cli.js +175 -0
  847. package/dist/workspace/trust/evidence.js +161 -0
  848. package/dist/workspace/trust/gate-seal.js +226 -0
  849. package/dist/workspace/trust/policy.js +190 -0
  850. package/dist/workspace/trust/provenance.js +180 -0
  851. package/dist/workspace/trust/rotation.js +298 -0
  852. package/dist/workspace/trust/run-commits.js +101 -0
  853. package/dist/workspace/trust/run-statement.js +182 -0
  854. package/dist/workspace/trust/seal.js +272 -0
  855. package/dist/workspace/trust/signers-cli.js +152 -0
  856. package/dist/workspace/trust/ssh-commit.js +178 -0
  857. package/dist/workspace/trust/test-repo.js +118 -0
  858. package/dist/workspace/trust/trust-files.js +193 -0
  859. package/dist/workspace/trust/verify-cli.js +63 -0
  860. package/dist/workspace/trust/verify.js +192 -0
  861. package/dist/workspace/which-chant.js +141 -0
  862. package/dist/workspace/wip-cli.js +95 -0
  863. package/dist/workspace/wip-write.schema.json +504 -0
  864. package/dist/workspace/wip.js +631 -0
  865. package/dist/workspace/wip.schema.json +334 -0
  866. package/dist/workspace/work-cli.js +448 -0
  867. package/dist/workspace/work-evidence.js +122 -0
  868. package/dist/workspace/work-evidence.schema.json +222 -0
  869. package/dist/workspace/work-history.schema.json +421 -0
  870. package/dist/workspace/work-lease.schema.json +246 -0
  871. package/dist/workspace/work-size.js +77 -0
  872. package/dist/workspace/work.js +283 -0
  873. package/dist/workspace/workspace-kinds.schema.json +259 -259
  874. package/dist/workspace/workspace-principals.schema.json +53 -53
  875. package/dist/workspace/write-lock-cli.js +105 -0
  876. package/dist/workspace/write-lock.js +424 -0
  877. package/dist/workspace/write-lock.schema.json +172 -0
  878. package/dist/workspace/write-scope.js +484 -0
  879. package/dist/yaml.js +459 -0
  880. package/package.json +2 -2
  881. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  882. package/src/cli/commands/lint.ts +3 -0
  883. package/src/cli/commands/migrate.ts +2 -0
  884. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  885. package/src/cli/main.ts +121 -27
  886. package/src/cli/mcp/workspace-tools.ts +1 -1
  887. package/src/codegen/pinned-upgrade.ts +2 -0
  888. package/src/components/discover.ts +2 -0
  889. package/src/components/sandbox/driver.ts +3 -1
  890. package/src/config-import.ts +3 -0
  891. package/src/discovery/fold-import.ts +23 -14
  892. package/src/discovery/import.ts +2 -0
  893. package/src/discovery/sandbox/driver.ts +3 -1
  894. package/src/lexicon-module.ts +3 -0
  895. package/src/lint/config.ts +1 -1
  896. package/src/lint/discover.ts +28 -8
  897. package/src/lint/policy-import.ts +4 -1
  898. package/src/lint/rule-loader.ts +2 -0
  899. package/src/op/discover.ts +3 -0
  900. package/src/ts-loader.test.ts +23 -0
  901. package/src/ts-loader.ts +122 -0
  902. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  903. package/src/workspace/declaration.ts +1 -1
  904. package/src/workspace/kinds.ts +1 -1
  905. package/src/workspace/lineage-migrations.ts +2 -0
  906. package/src/workspace/points.ts +1 -1
  907. package/src/workspace/principal-classes.ts +1 -1
@@ -0,0 +1,1727 @@
1
+ import { lexiconNames } from "../../lexicon-module.js";
2
+ import { resolve } from "node:path";
3
+ import { commandBuildParams } from "../build-params-cli.js";
4
+ import { build } from "../../build.js";
5
+ import { takeSnapshot } from "../../lifecycle/snapshot.js";
6
+ import { readSnapshot, readSnapshotAt, readEnvironmentSnapshots, listSnapshots, fetchLifecycle, pushLifecycle, snapshotStorageKey, StaleLifecycleBranchError } from "../../lifecycle/git.js";
7
+ import { deepDiffForLexicon } from "../../lifecycle/deep-observe.js";
8
+ import { countUnclaimed, countPending, countPropertyDrift, countHeld, suspiciousHeld } from "../../lifecycle/deep-diff.js";
9
+ import { describePathOrigin, getPathProvenance, getProvenance } from "../../provenance.js";
10
+ import { resolveDeepDrift, resolveDriftedField } from "../../fold-provenance.js";
11
+ import { acceptDeviations, baselineForLexicon, emptyBaseline, readObservationBaseline, writeObservationBaseline, OBSERVATION_BASELINE_FILE, } from "../../lifecycle/observation-baseline.js";
12
+ import { computeBuildDigest, declaredDefinitions, diffDigests } from "../../lifecycle/digest.js";
13
+ import { diffLive, diffLiveArtifacts, diffSnapshots, missingRow } from "../../lifecycle/live-diff.js";
14
+ import { buildChangeSet, renderChangeSet, renderChangeSetMarkdown, gitlabMrReport, unobservedPlanNotice } from "../../lifecycle/change-set.js";
15
+ import { mergeReceiptEntries, observedValueResolver, planReceipts, readReceiptValue } from "../../lifecycle/receipt-plan.js";
16
+ import { annotateDisruption, disruptionNotices } from "../../lifecycle/disruption.js";
17
+ import { collectEffectReceipts, isEffectReceipt } from "../../effect-receipt.js";
18
+ import { formatUnobserved, mergeObservations, normalizeObservation, unobservedAll, } from "../../observation.js";
19
+ import { describeIdentities, identityStatusText } from "../../identity.js";
20
+ import { discoverComponents } from "../../components/discover.js";
21
+ import { cfnDeployStacks } from "./components.js";
22
+ import { affectedStacks } from "../../lifecycle/affected.js";
23
+ import { rollbackToRevision } from "../../lifecycle/rollback.js";
24
+ import { loadChantConfig, environmentNames, matchesDeclaredEnvironment, resolveOwnershipStack } from "../../config.js";
25
+ import { unknownEnvError, isProdLikeEnvironment } from "../../env.js";
26
+ import { planTeardown, executeTeardown } from "../../lifecycle/teardown.js";
27
+ import { collectBuildRootContributors } from "../plugins.js";
28
+ import { resolveBuildModes, resolveProjectBuildOptions } from "../build-options.js";
29
+ import { applyLiveEndpoint } from "../../live-endpoint.js";
30
+ import { isObservableDeclarable } from "../../declarable.js";
31
+ import { formatError, formatWarning, formatSuccess, formatBold } from "../format.js";
32
+ /**
33
+ * Resolve the build root for a lifecycle command. The project root (where
34
+ * chant.config.ts lives) is always ".", but the *build* can be scoped to a
35
+ * subdirectory so a mixed-layout project — chant `src/` next to app code with
36
+ * import side effects — only synthesizes its infra. Precedence: `--src` flag,
37
+ * then `config.sourceDir`, then "." (the root). Snapshot/diff/plan all use this
38
+ * so their build digests stay consistent.
39
+ */
40
+ export function resolveBuildRoot(args, config) {
41
+ return resolve(args.src ?? config.sourceDir ?? ".");
42
+ }
43
+ /**
44
+ * The stacks a lifecycle command (`snapshot`/`diff`) iterates. A multi-stack
45
+ * project declares them via `stacks` in chant.config (#932) — each built from
46
+ * its own `src` and observed against its own live stack `name`. A single-stack
47
+ * project (no `stacks`) resolves to one target built from `sourceDir`/root and
48
+ * observed as the stack named after the environment (unchanged behavior). An
49
+ * explicit `--src` always wins and forces a single scoped target.
50
+ */
51
+ function resolveStackTargets(args, config) {
52
+ if (args.src)
53
+ return [{ root: resolve(args.src) }];
54
+ if (config.stacks && config.stacks.length > 0) {
55
+ return config.stacks.map((s) => ({ stack: s.name, root: resolve(s.src), region: s.region }));
56
+ }
57
+ return [{ root: resolveBuildRoot(args, config) }];
58
+ }
59
+ /**
60
+ * chant lifecycle snapshot <environment> [lexicon]
61
+ */
62
+ export async function runLifecycleSnapshot(ctx) {
63
+ const { args, plugins } = ctx;
64
+ const environment = args.extraPositional;
65
+ const lexiconFilter = args.extraPositional2;
66
+ if (!environment) {
67
+ console.error(formatError({ message: "Environment is required: chant lifecycle snapshot <environment> [lexicon]" }));
68
+ return 1;
69
+ }
70
+ // Validate environment against config
71
+ const projectPath = resolve(".");
72
+ const { config } = await loadChantConfig(projectPath);
73
+ // This invocation's parameters, so the declared side of the comparison is the estate
74
+ // the caller asked for rather than the parameter defaults (#1483).
75
+ const declaredParams = await commandBuildParams(config.buildParams, args);
76
+ if (!declaredParams)
77
+ return 1;
78
+ const declaredEnvNames = environmentNames(config.environments);
79
+ if (declaredEnvNames && !matchesDeclaredEnvironment(config.environments, environment)) {
80
+ console.error(formatError({
81
+ message: `Unknown environment "${environment}"`,
82
+ hint: `Defined environments: ${declaredEnvNames.join(", ")}`,
83
+ }));
84
+ return 1;
85
+ }
86
+ // Filter plugins if lexicon specified
87
+ const targetPlugins = lexiconFilter
88
+ ? plugins.filter((p) => p.name === lexiconFilter)
89
+ : plugins;
90
+ const targetSerializers = targetPlugins.map((p) => p.serializer);
91
+ const observingPlugins = targetPlugins.filter((p) => p.describeResources || p.listArtifacts);
92
+ if (observingPlugins.length === 0) {
93
+ console.error(formatError({
94
+ message: "No plugins implement describeResources or listArtifacts",
95
+ hint: lexiconFilter ? `Lexicon "${lexiconFilter}" does not support state snapshots` : undefined,
96
+ }));
97
+ return 1;
98
+ }
99
+ // One target per stack (single-stack projects: exactly one). Each stack builds
100
+ // from its own scoped root — so its logical ids match what it deploys — and
101
+ // snapshots against its own live stack name (#932).
102
+ const targets = resolveStackTargets(args, config);
103
+ let anySnapshotSaved = false;
104
+ let anyHardError = false;
105
+ // #1166 — same self-sufficiency as `chant graph --live`: a snapshot is
106
+ // always a live read, so an environment's declared endpoint applies here
107
+ // too, unless the ambient shell already set it.
108
+ const endpointResult = applyLiveEndpoint(config.environments, environment, observingPlugins);
109
+ if (endpointResult.notice)
110
+ console.error(formatWarning({ message: endpointResult.notice }));
111
+ // Build every stack first, so the ambient scan (#1278) can be bounded by the
112
+ // kinds the PROJECT manages rather than the ones this stack happens to
113
+ // declare. "Which of my security groups are unused" is a question about the
114
+ // estate; a region whose stack declares no security group still has a default
115
+ // one, and scoping the bound per stack silently drops it.
116
+ const built = [];
117
+ const buildRoots = collectBuildRootContributors(targetPlugins, config, projectPath);
118
+ for (const target of targets) {
119
+ const label = target.stack ? `stack "${target.stack}"` : "project";
120
+ const buildResult = await build(target.root, targetSerializers, undefined, { buildParams: declaredParams, buildRoots });
121
+ if (buildResult.errors.length > 0) {
122
+ console.error(formatError({ message: `Build failed for ${label} — fix errors before taking a snapshot` }));
123
+ anyHardError = true;
124
+ continue;
125
+ }
126
+ built.push({ target, buildResult });
127
+ }
128
+ const projectKinds = [
129
+ ...new Set(built.flatMap(({ buildResult }) => [...buildResult.entities.values()].map((e) => e.entityType))),
130
+ ];
131
+ try {
132
+ for (const { target, buildResult } of built) {
133
+ const label = target.stack ? `stack "${target.stack}"` : "project";
134
+ let result;
135
+ try {
136
+ result = await takeSnapshot(environment, observingPlugins, buildResult, {
137
+ stack: target.stack,
138
+ region: target.region,
139
+ deep: args.deep,
140
+ ambient: args.ambient,
141
+ ambientKinds: projectKinds,
142
+ });
143
+ }
144
+ catch (err) {
145
+ if (err instanceof StaleLifecycleBranchError) {
146
+ console.error(formatError({
147
+ message: `Another snapshot completed for chant/lifecycle after this run started (env: ${environment}).`,
148
+ hint: `Pull and retry: \`git fetch origin ${"chant/lifecycle"}:${"chant/lifecycle"}\` && \`chant lifecycle snapshot ${environment}\`.`,
149
+ }));
150
+ return 1;
151
+ }
152
+ throw err;
153
+ }
154
+ for (const w of result.warnings) {
155
+ console.error(formatWarning({ message: w }));
156
+ }
157
+ for (const e of result.errors) {
158
+ console.error(formatError({ message: e }));
159
+ }
160
+ if (result.snapshots.length > 0) {
161
+ anySnapshotSaved = true;
162
+ const prefix = target.stack ? `${target.stack}: ` : "";
163
+ const counts = result.snapshots
164
+ .map((s) => `${s.lexicon}(${Object.keys(s.resources).length})`)
165
+ .join(" ");
166
+ console.error(formatSuccess(`${prefix}Snapshot saved to chant/lifecycle (${counts})`));
167
+ }
168
+ if (result.errors.length > 0)
169
+ anyHardError = true;
170
+ }
171
+ return anyHardError && !anySnapshotSaved ? 1 : 0;
172
+ }
173
+ finally {
174
+ endpointResult.restore();
175
+ }
176
+ }
177
+ /**
178
+ * chant lifecycle show <environment> [lexicon]
179
+ */
180
+ export async function runLifecycleShow(ctx) {
181
+ const environment = ctx.args.extraPositional;
182
+ const lexiconFilter = ctx.args.extraPositional2;
183
+ if (!environment) {
184
+ console.error(formatError({ message: "Environment is required: chant lifecycle show <environment> [lexicon]" }));
185
+ return 1;
186
+ }
187
+ // Fetch from remote first
188
+ await fetchLifecycle();
189
+ if (lexiconFilter) {
190
+ const content = await readSnapshot(environment, lexiconFilter);
191
+ if (!content) {
192
+ console.error(formatError({ message: `No snapshot found for ${environment}/${lexiconFilter}` }));
193
+ return 1;
194
+ }
195
+ const snapshot = JSON.parse(content);
196
+ printSnapshotTable(snapshot);
197
+ }
198
+ else {
199
+ const snapshots = await readEnvironmentSnapshots(environment);
200
+ if (snapshots.size === 0) {
201
+ console.error(formatError({ message: `No snapshots found for environment "${environment}"` }));
202
+ return 1;
203
+ }
204
+ for (const [lexicon, content] of snapshots) {
205
+ const snapshot = JSON.parse(content);
206
+ // Depth is stated, not inferred (#1267). An identity snapshot cannot
207
+ // answer a property question, and a reader that assumes otherwise reads
208
+ // "no properties recorded" as "no such properties".
209
+ const depth = snapshot.depth ?? "identity";
210
+ const depthNote = depth === "deep"
211
+ ? ` — deep (${Object.keys(snapshot.properties ?? {}).length} property trees)`
212
+ : " — identity only";
213
+ console.log(`\n${formatBold(`${environment}/${lexicon}`)} — ${Object.keys(snapshot.resources).length} resources${depthNote} — ${snapshot.timestamp}`);
214
+ printSnapshotTable(snapshot);
215
+ }
216
+ }
217
+ return 0;
218
+ }
219
+ /**
220
+ * chant lifecycle rollback [<environment>] --to <ref>
221
+ *
222
+ * Open a PR restoring `sourceDir` to a prior git revision (#873). Source-level,
223
+ * reviewable, no cloud mutation — a human merges, then a gated apply rolls the
224
+ * env back. The `<env>` positional is PR context.
225
+ */
226
+ export async function runLifecycleRollback(ctx) {
227
+ const { args } = ctx;
228
+ const environment = args.extraPositional; // optional — PR context/title
229
+ const ref = args.migrateTo; // the --to value
230
+ if (!ref) {
231
+ console.error(formatError({ message: "Target revision required: chant lifecycle rollback [<env>] --to <ref>" }));
232
+ return 1;
233
+ }
234
+ const { config } = await loadChantConfig(resolve("."));
235
+ const sourceDir = config.sourceDir ?? ".";
236
+ try {
237
+ const result = await rollbackToRevision({ ref, env: environment, sourceDir, cwd: resolve("."), dryRun: args.dryRun });
238
+ if (result.noop) {
239
+ console.error(formatSuccess(`${sourceDir} already matches ${ref} — nothing to roll back`));
240
+ return 0;
241
+ }
242
+ if (args.dryRun) {
243
+ // The delta on stdout, so it pipes and diffs like any other patch; the
244
+ // summary on stderr, matching the PR path's split.
245
+ process.stdout.write(result.diff ?? "");
246
+ console.error(formatSuccess(`Rollback delta for ${ref} computed — no PR opened, nothing pushed`));
247
+ return 0;
248
+ }
249
+ console.log(result.prUrl); // the PR URL — the consumer (behold) reads this from stdout
250
+ console.error(formatSuccess(`Opened rollback PR on ${result.branch}`));
251
+ return 0;
252
+ }
253
+ catch (err) {
254
+ console.error(formatError({ message: `rollback failed — ${err instanceof Error ? err.message : String(err)}` }));
255
+ return 1;
256
+ }
257
+ }
258
+ /**
259
+ * chant lifecycle diff <environment> [lexicon]
260
+ */
261
+ export async function runLifecycleDiff(ctx) {
262
+ const { args, plugins, serializers } = ctx;
263
+ const environment = args.extraPositional;
264
+ const lexiconFilter = args.extraPositional2;
265
+ if (!environment) {
266
+ console.error(formatError({ message: "Environment is required: chant lifecycle diff <environment> [lexicon]" }));
267
+ return 1;
268
+ }
269
+ // `--between <refA> <refB>` (#822): diff two saved snapshots against each other.
270
+ // Purely historical — no build, no cloud query; reads the orphan branch.
271
+ if (args.betweenA && args.betweenB) {
272
+ return runLifecycleDiffBetween({
273
+ environment,
274
+ lexiconFilter,
275
+ refA: args.betweenA,
276
+ refB: args.betweenB,
277
+ json: !!args.json,
278
+ });
279
+ }
280
+ // Filter serializers to target lexicon before building
281
+ const targetSerializers = lexiconFilter
282
+ ? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
283
+ : serializers;
284
+ // Fetch previous snapshots once (all stacks share the orphan branch).
285
+ const { config } = await loadChantConfig(resolve("."));
286
+ // This invocation's parameters, so the declared side of the comparison is the estate
287
+ // the caller asked for rather than the parameter defaults (#1483).
288
+ const declaredParams = await commandBuildParams(config.buildParams, args);
289
+ if (!declaredParams)
290
+ return 1;
291
+ await fetchLifecycle();
292
+ // One target per stack (single-stack projects: exactly one), each built from
293
+ // its own scoped root and diffed against its own live stack name (#932).
294
+ const targets = resolveStackTargets(args, config);
295
+ const json = !!args.json;
296
+ const perStackJson = {};
297
+ let combinedLexiconsJson;
298
+ let totalDrift = 0;
299
+ let totalUnobserved = 0;
300
+ let totalChecked = 0;
301
+ let anyBuildError = false;
302
+ // Accepted-deviation baseline (#1014). Read once for the whole run — it is
303
+ // env-keyed, not stack-keyed, and every deep pass subtracts from the same
304
+ // committed set. Absent is the normal state (nothing accepted yet).
305
+ const baseline = args.live ? await readObservationBaseline(environment) : null;
306
+ const accepted = {};
307
+ // Effect receipts across every target's build (#1833) — `--update-baseline`
308
+ // refuses to accept drift on them (the effect step is their only writer),
309
+ // and recognition is marker-based so materialized lexicon rows are caught.
310
+ const receiptEntities = new Set();
311
+ // #1166 — an environment can declare its own endpoint (a local emulator like
312
+ // Floci), so `--live` is self-sufficient even when the ambient shell never
313
+ // exported e.g. AWS_ENDPOINT_URL. Ambient always wins when it's already set.
314
+ // Scoped to just the live reads below — restored in `finally`.
315
+ const liveLexicons = args.live ? plugins.filter((p) => p.describeResources || p.listArtifacts) : [];
316
+ const endpointResult = applyLiveEndpoint(config.environments, environment, liveLexicons);
317
+ if (endpointResult.notice)
318
+ console.error(formatWarning({ message: endpointResult.notice }));
319
+ // The build `chant build` would produce for this project (#2002's one
320
+ // place), so the diff folds by default as `chant build` does. A run build
321
+ // cannot say which composite argument produced a field, so without this
322
+ // every composite field in the live diff read as unknown (#3597).
323
+ const projectRoot = resolve(".");
324
+ const diffBuildOptions = resolveProjectBuildOptions({
325
+ config,
326
+ configDir: projectRoot,
327
+ plugins,
328
+ modes: resolveBuildModes(config, { fold: args.fold, sandbox: args.sandbox }),
329
+ buildParams: declaredParams,
330
+ });
331
+ try {
332
+ for (const target of targets) {
333
+ const buildResult = await build(target.root, targetSerializers, undefined, diffBuildOptions);
334
+ if (buildResult.errors.length > 0) {
335
+ const label = target.stack ? `stack "${target.stack}"` : "project";
336
+ console.error(formatError({ message: `Build failed for ${label} — fix errors before diffing` }));
337
+ anyBuildError = true;
338
+ continue;
339
+ }
340
+ // The lexicons the diff walks. Keyed on the BUILT manifest — which is
341
+ // right for the resource axis (a lexicon with no declared entities has
342
+ // nothing to diff) but drops the artifact axis whole: artifacts are
343
+ // context-keyed with no declared side at all (that is their defining
344
+ // property), so an estate that uses helm purely as component deploy
345
+ // steps builds zero helm entities and its releases were never listed —
346
+ // `observedArtifacts` silently absent for exactly the estates behold#146
347
+ // exists for (found live on kubemicrovm-ops, four releases invisible).
348
+ // On the live path, artifact-capable configured lexicons join the walk;
349
+ // their resource half stays gated on `describeResources` + declared
350
+ // entities as before.
351
+ const builtLexicons = Array.from(buildResult.manifest.lexicons);
352
+ const artifactLexicons = args.live ? plugins.filter((p) => p.listArtifacts).map((p) => p.name) : [];
353
+ const lexicons = lexiconFilter
354
+ ? [lexiconFilter]
355
+ : [...new Set([...builtLexicons, ...artifactLexicons])];
356
+ if (args.live) {
357
+ // Multi-stack component projects: observe each component's own cfn stack
358
+ // and union (same fix runGraphLive / plan use), so a deployed resource
359
+ // isn't reported "missing". Only when this target has no explicit stack.
360
+ let componentStacks = [];
361
+ if (!target.stack) {
362
+ try {
363
+ const disc = await discoverComponents(target.root, { sandbox: args.sandbox });
364
+ const set = new Set();
365
+ for (const { component } of disc.components.values())
366
+ for (const s of cfnDeployStacks(component.deploy))
367
+ set.add(s);
368
+ componentStacks = [...set];
369
+ }
370
+ catch {
371
+ // no components / discovery failed → single-stack observe path
372
+ }
373
+ }
374
+ const r = await runLifecycleDiffLive({
375
+ environment,
376
+ lexicons,
377
+ plugins,
378
+ buildResult,
379
+ json,
380
+ stack: target.stack,
381
+ region: target.region,
382
+ componentStacks,
383
+ baseline,
384
+ updateBaseline: args.updateBaseline,
385
+ ...(args.namespace ? { namespace: args.namespace } : {}),
386
+ projectRoot,
387
+ });
388
+ totalDrift += r.totalDrift;
389
+ totalUnobserved += r.totalUnobserved;
390
+ totalChecked += r.totalLexiconsChecked;
391
+ for (const [lexicon, deviations] of Object.entries(r.toAccept)) {
392
+ (accepted[lexicon] ?? (accepted[lexicon] = [])).push(...deviations);
393
+ }
394
+ if (args.updateBaseline) {
395
+ for (const name of collectEffectReceipts(buildResult.entities).keys())
396
+ receiptEntities.add(name);
397
+ }
398
+ if (json) {
399
+ if (target.stack)
400
+ perStackJson[target.stack] = r.byLexicon;
401
+ else
402
+ combinedLexiconsJson = r.byLexicon;
403
+ }
404
+ }
405
+ else {
406
+ await runLifecycleDiffDigest({ environment, lexicons, buildResult, stack: target.stack });
407
+ }
408
+ }
409
+ // `--update-baseline` (#1014): record what the deep pass just reported as
410
+ // accepted, so it stops re-alerting. Runs before the summary lines so the
411
+ // "no drift" verdict below still describes the run that produced it.
412
+ if (args.live && args.updateBaseline) {
413
+ await recordAcceptedBaseline(environment, baseline, accepted, json, receiptEntities);
414
+ }
415
+ if (args.live) {
416
+ if (json) {
417
+ // Single-stack keeps the original `{ environment, lexicons }` shape
418
+ // (behold's inspect diff, #852); multi-stack nests under `stacks`.
419
+ console.log(JSON.stringify(combinedLexiconsJson !== undefined
420
+ ? { environment, lexicons: combinedLexiconsJson }
421
+ : { environment, stacks: perStackJson }));
422
+ }
423
+ else if (totalChecked === 0) {
424
+ console.error(formatWarning({
425
+ message: "No lexicons implement describeResources or listArtifacts — nothing to diff in --live mode",
426
+ }));
427
+ return 1;
428
+ }
429
+ else if (totalDrift === 0) {
430
+ // Qualify the all-clear when part of the estate was never read (#1089):
431
+ // "no drift" over an incomplete observation is not the same claim.
432
+ console.error(totalUnobserved > 0
433
+ ? formatWarning({
434
+ message: `No drift detected across ${totalChecked} lexicon(s), but ${totalUnobserved} declared entity(ies) could not be observed — that part of the estate is unknown, not clean`,
435
+ })
436
+ : formatSuccess(`No drift detected across ${totalChecked} lexicon(s)`));
437
+ }
438
+ }
439
+ return anyBuildError ? 1 : 0;
440
+ }
441
+ finally {
442
+ endpointResult.restore();
443
+ }
444
+ }
445
+ /**
446
+ * Write the accepted-deviation baseline (#1014) for everything the deep pass
447
+ * reported this run, and push it on the same orphan branch the snapshots use.
448
+ *
449
+ * Acceptance is a deliberate, committed act — that is the whole difference
450
+ * between this and a suppression flag — so the write is loud: it names the
451
+ * count and the storage path, and a failed push says so rather than leaving
452
+ * the operator believing the team's baseline moved.
453
+ */
454
+ async function recordAcceptedBaseline(environment, existing, accepted, json, receipts) {
455
+ const total = Object.values(accepted).reduce((n, d) => n + d.length, 0);
456
+ if (total === 0) {
457
+ if (!json) {
458
+ console.error(formatWarning({
459
+ message: "--update-baseline: nothing to accept — no property-level deviations were reported",
460
+ }));
461
+ }
462
+ return;
463
+ }
464
+ try {
465
+ // `acceptDeviations` throws on an effect-receipt deviation (#1833) —
466
+ // inside the try so the refusal reaches the operator as a formatted
467
+ // error, with no baseline written at all.
468
+ let next = existing ?? emptyBaseline(environment);
469
+ for (const [lexicon, deviations] of Object.entries(accepted)) {
470
+ next = acceptDeviations(next, lexicon, deviations, { receipts });
471
+ }
472
+ await writeObservationBaseline(next);
473
+ const pushed = await pushLifecycle();
474
+ if (!json) {
475
+ console.error(formatSuccess(`--update-baseline: accepted ${total} deviation(s) into ${environment}/${OBSERVATION_BASELINE_FILE} on chant/lifecycle` +
476
+ (pushed ? " (pushed)" : " (local only — no remote configured or push refused)")));
477
+ }
478
+ }
479
+ catch (err) {
480
+ console.error(formatError({
481
+ message: `--update-baseline: baseline not updated — ${err instanceof Error ? err.message : String(err)}`,
482
+ }));
483
+ }
484
+ }
485
+ /**
486
+ * `chant lifecycle diff <env> --between <refA> <refB>` (#822). Diffs two saved
487
+ * snapshots against each other — read-only, no build, no cloud query. Reads
488
+ * `<ref>:<env>/<lexicon>.json` from the orphan branch at each ref and reports the
489
+ * two-way delta per lexicon.
490
+ */
491
+ async function runLifecycleDiffBetween(args) {
492
+ await fetchLifecycle();
493
+ const { config } = await loadChantConfig(resolve("."));
494
+ const lexicons = args.lexiconFilter ? [args.lexiconFilter] : lexiconNames(config.lexicons ?? []);
495
+ if (lexicons.length === 0) {
496
+ console.error(formatError({ message: "No lexicons to diff — pass a lexicon or declare `lexicons` in chant.config." }));
497
+ return 1;
498
+ }
499
+ const parseResources = (content) => {
500
+ if (content === null)
501
+ return null;
502
+ try {
503
+ return JSON.parse(content).resources ?? {};
504
+ }
505
+ catch {
506
+ return null;
507
+ }
508
+ };
509
+ const byLexicon = {};
510
+ for (const lexicon of lexicons) {
511
+ const prev = parseResources(await readSnapshotAt(args.environment, lexicon, args.refA));
512
+ const next = parseResources(await readSnapshotAt(args.environment, lexicon, args.refB));
513
+ if (prev === null && next === null) {
514
+ if (!args.json)
515
+ console.error(formatWarning({ message: `${lexicon}: not captured at either ref — skipping` }));
516
+ continue;
517
+ }
518
+ byLexicon[lexicon] = diffSnapshots(prev ?? {}, next ?? {});
519
+ }
520
+ if (args.json) {
521
+ console.log(JSON.stringify({ environment: args.environment, refA: args.refA, refB: args.refB, lexicons: byLexicon }, null, 2));
522
+ return 0;
523
+ }
524
+ if (Object.keys(byLexicon).length === 0) {
525
+ console.log(`No snapshots for ${args.environment} at ${args.refA} / ${args.refB}.`);
526
+ return 0;
527
+ }
528
+ console.log(formatBold(`Snapshot diff — ${args.environment}: ${args.refA} → ${args.refB}`));
529
+ for (const [lexicon, d] of Object.entries(byLexicon)) {
530
+ console.log(`\n${formatBold(lexicon)} — ${d.added.length} added, ${d.removed.length} removed, ${d.changed.length} changed, ${d.unchanged.length} unchanged`);
531
+ for (const name of d.added)
532
+ console.log(` + ${name}`);
533
+ for (const name of d.removed)
534
+ console.log(` - ${name}`);
535
+ for (const drift of d.changed)
536
+ console.log(` ~ ${drift.name} (${drift.changes.map((c) => c.path).join(", ")})`);
537
+ }
538
+ return 0;
539
+ }
540
+ async function runLifecycleDiffDigest(args) {
541
+ const currentDigest = computeBuildDigest(args.buildResult);
542
+ if (args.stack)
543
+ console.log(`\n${formatBold(`■ stack ${args.stack}`)}`);
544
+ for (const lexicon of args.lexicons) {
545
+ const content = await readSnapshot(args.environment, snapshotStorageKey(lexicon, args.stack));
546
+ let previousDigest = undefined;
547
+ if (content) {
548
+ const snapshot = JSON.parse(content);
549
+ previousDigest = snapshot.digest;
550
+ }
551
+ const diff = diffDigests(currentDigest, previousDigest);
552
+ console.log(`\n${formatBold(lexicon)}`);
553
+ console.log("RESOURCE".padEnd(20) + "STATUS".padEnd(12) + "TYPE");
554
+ console.log("-".repeat(60));
555
+ for (const name of diff.added) {
556
+ console.log(name.padEnd(20) + "added".padEnd(12) + (currentDigest.resources[name]?.type ?? ""));
557
+ }
558
+ for (const name of diff.changed) {
559
+ console.log(name.padEnd(20) + "changed".padEnd(12) + (currentDigest.resources[name]?.type ?? ""));
560
+ }
561
+ for (const name of diff.removed) {
562
+ console.log(name.padEnd(20) + "removed".padEnd(12) + (previousDigest?.resources[name]?.type ?? ""));
563
+ }
564
+ for (const name of diff.unchanged) {
565
+ console.log(name.padEnd(20) + "unchanged".padEnd(12) + (currentDigest.resources[name]?.type ?? ""));
566
+ }
567
+ // chant #1442 — a lexicon bump can change emitted output with no source
568
+ // change, so every resource above can read "unchanged" while the build is
569
+ // not the same build. Printed under the table rather than as a row: no
570
+ // resource's declaration moved, the thing that interpreted them did.
571
+ for (const change of diff.lexiconVersionChanges) {
572
+ const from = change.previous ?? "(absent)";
573
+ const to = change.current ?? "(absent)";
574
+ console.log(`\nlexicon ${change.lexicon}: ${from} → ${to}`);
575
+ }
576
+ }
577
+ return 0;
578
+ }
579
+ /**
580
+ * Read one lexicon's live resources, resolving the observation tri-state
581
+ * (#1089) for every declared entity: present, confirmed-absent, or not
582
+ * observed with a reason.
583
+ *
584
+ * Two behaviours the old inline call sites did not have. A throw no longer
585
+ * skips the lexicon — every entity it was asked about comes back
586
+ * NOT-OBSERVED (`read-failed`), so a failed read shows up in the plan instead
587
+ * of vanishing from it. And a multi-stack read merges with
588
+ * present > not-observed > absent, so one unreadable stack cannot un-observe a
589
+ * resource another stack returned.
590
+ */
591
+ async function observeLexicon(plugin, opts) {
592
+ const entityNames = Array.from(opts.declared);
593
+ const base = {
594
+ environment: opts.environment,
595
+ buildOutput: opts.buildOutput,
596
+ entityNames,
597
+ entities: opts.entities,
598
+ ...(opts.region ? { region: opts.region } : {}),
599
+ ...(opts.owned !== undefined ? { owned: opts.owned } : {}),
600
+ ...(opts.namespace ? { namespace: opts.namespace } : {}),
601
+ };
602
+ try {
603
+ if (opts.componentStacks && opts.componentStacks.length > 0) {
604
+ const parts = [];
605
+ for (const stack of opts.componentStacks) {
606
+ parts.push(normalizeObservation(await plugin.describeResources({ ...base, stack })));
607
+ }
608
+ return mergeObservations(parts);
609
+ }
610
+ return normalizeObservation(await plugin.describeResources({ ...base, ...(opts.stack ? { stack: opts.stack } : {}) }));
611
+ }
612
+ catch (err) {
613
+ const message = err instanceof Error ? err.message : String(err);
614
+ console.error(formatError({
615
+ message: `${plugin.name}: describeResources failed — ${message} (reporting ${entityNames.length} declared entity(ies) as not observed, not as absent)`,
616
+ }));
617
+ return {
618
+ resources: {},
619
+ unobserved: unobservedAll(entityNames, "read-failed", message, opts.entities),
620
+ queried: {},
621
+ sources: {},
622
+ notes: [],
623
+ };
624
+ }
625
+ }
626
+ /** Diff current build vs live cloud for one stack. Renders the human report
627
+ * inline; returns per-lexicon results so the caller emits the aggregate `--json`
628
+ * once (single-stack keeps the original shape; multi-stack nests under `stacks`). */
629
+ async function runLifecycleDiffLive(args) {
630
+ let totalDrift = 0;
631
+ let totalUnobserved = 0;
632
+ let totalLexiconsChecked = 0;
633
+ const byLexicon = {};
634
+ const toAccept = {};
635
+ if (!args.json && args.stack)
636
+ console.log(`\n${formatBold(`■ stack ${args.stack}`)}`);
637
+ for (const lexiconName of args.lexicons) {
638
+ const plugin = args.plugins.find((p) => p.name === lexiconName);
639
+ if (!plugin)
640
+ continue;
641
+ if (!plugin.describeResources && !plugin.listArtifacts) {
642
+ console.error(formatWarning({
643
+ message: `${lexiconName}: lexicon does not implement describeResources or listArtifacts — skipping (use without --live for digest diff)`,
644
+ }));
645
+ continue;
646
+ }
647
+ // Build per-lexicon entity index. Resource declarables only — outputs,
648
+ // parameters, serializer directives and property-kind declarables (read
649
+ // inside their parent, #3001) have no live counterpart of their own, and a
650
+ // declared name the reader can never resolve reads as missing (see
651
+ // lifecycle/observe.ts).
652
+ const declared = new Set();
653
+ const entities = new Map();
654
+ for (const [name, entity] of args.buildResult.entities) {
655
+ if (entity.lexicon === lexiconName && isObservableDeclarable(entity)) {
656
+ declared.add(name);
657
+ // Path origins are carried across explicitly (#1443): this map is plain
658
+ // objects, and the symbol-keyed provenance channel does not survive it.
659
+ const pathOrigins = getPathProvenance(entity);
660
+ entities.set(name, {
661
+ entityType: entity.entityType,
662
+ props: (entity.props != null ? entity.props : {}),
663
+ ...(pathOrigins ? { pathOrigins } : {}),
664
+ });
665
+ }
666
+ }
667
+ const rawOutput = args.buildResult.outputs.get(lexiconName);
668
+ const buildOutput = rawOutput === undefined
669
+ ? ""
670
+ : typeof rawOutput === "string"
671
+ ? rawOutput
672
+ : rawOutput.primary;
673
+ // Read previous snapshot once; both flows pull what they need.
674
+ let prevSnapshot;
675
+ const content = await readSnapshot(args.environment, snapshotStorageKey(lexiconName, args.stack));
676
+ if (content)
677
+ prevSnapshot = JSON.parse(content);
678
+ let lexiconChecked = false;
679
+ // ── Resources path (entity-keyed) ──────────────────────────────────────
680
+ if (plugin.describeResources) {
681
+ const observed = await observeLexicon(plugin, {
682
+ environment: args.environment,
683
+ buildOutput,
684
+ declared,
685
+ entities,
686
+ stack: args.stack,
687
+ region: args.region,
688
+ componentStacks: args.componentStacks,
689
+ ...(args.namespace ? { namespace: args.namespace } : {}),
690
+ });
691
+ const observedNow = observed.resources;
692
+ const observedThen = prevSnapshot?.resources;
693
+ const diff = diffLive({
694
+ declared,
695
+ observedNow,
696
+ observedThen,
697
+ unobserved: observed.unobserved,
698
+ queried: observed.queried,
699
+ // What each create would create (#3652), so the diff, and the plan
700
+ // digest ApplyOp's gate binds to it, moves when a missing entity's
701
+ // declaration does.
702
+ definitions: declaredDefinitions(entities),
703
+ });
704
+ // Unobserved entities are deliberately NOT drift: a hole in the read is
705
+ // not a change in the cloud. They are reported separately (#1089) so a
706
+ // "no drift detected" line can never be built on top of a failed read.
707
+ totalDrift += diff.driftedSinceSnapshot.length + diff.missing.length + diff.orphan.length + diff.disappeared.length;
708
+ totalUnobserved += diff.unobserved.length;
709
+ if (args.json) {
710
+ const entry = (byLexicon[lexiconName] ?? (byLexicon[lexiconName] = {}));
711
+ entry.resources = diff;
712
+ entry.observed = observedNow; // live state (#862) — status/attributes per resource
713
+ if (Object.keys(observed.unobserved).length > 0)
714
+ entry.unobserved = observed.unobserved;
715
+ }
716
+ else
717
+ renderLiveDiff(lexiconName, args.environment, diff);
718
+ lexiconChecked = true;
719
+ // ── Deep path (property-level, #1014) ───────────────────────────────
720
+ // Gated purely on the capability: a lexicon without a deep reader is
721
+ // completely unaffected, including its output.
722
+ if (plugin.observeResourcesDeep) {
723
+ const deep = await deepDiffForLexicon(plugin, {
724
+ environment: args.environment,
725
+ buildOutput,
726
+ entities,
727
+ stack: args.stack,
728
+ region: args.region,
729
+ componentStacks: args.componentStacks,
730
+ baseline: baselineForLexicon(args.baseline, lexiconName),
731
+ });
732
+ totalDrift += countPropertyDrift(deep) + countPending(deep);
733
+ // Only count a deep hole for an entity the thin read *did* resolve —
734
+ // otherwise one unreadable entity is counted twice.
735
+ totalUnobserved += deep.unobserved.filter((u) => !observed.unobserved[u.name]).length;
736
+ if (args.updateBaseline)
737
+ toAccept[lexiconName] = deviationsToAccept(deep);
738
+ // chant #2161 — the return leg, classified by fold provenance. Read off
739
+ // the build's own entities, which is where the record lives.
740
+ const provenanceOf = (name) => {
741
+ const entity = args.buildResult.entities.get(name);
742
+ return entity ? getProvenance(entity) : undefined;
743
+ };
744
+ if (args.json) {
745
+ const entry = (byLexicon[lexiconName] ?? (byLexicon[lexiconName] = {}));
746
+ entry.deep = deep;
747
+ entry.reconcile = resolveDeepDrift(deep.drifted, provenanceOf, { root: args.projectRoot });
748
+ }
749
+ else
750
+ renderDeepDiff(lexiconName, deep, provenanceOf, args.projectRoot);
751
+ // Printed on stderr regardless of `--json` (#2162), same reason the
752
+ // unobserved/disruption notices are: a shape with no column for
753
+ // "suspicious" must not read as clean.
754
+ for (const s of suspiciousHeld(deep)) {
755
+ console.error(formatWarning({
756
+ message: `${lexiconName}: ${s.name}.${s.path} is declared heldElsewhere(by: "${s.by}") but no live value ever showed up there — the hand-over may never have happened.`,
757
+ }));
758
+ }
759
+ }
760
+ }
761
+ // ── Artifacts path (context-keyed) ─────────────────────────────────────
762
+ if (plugin.listArtifacts) {
763
+ let observedNow;
764
+ try {
765
+ observedNow = await plugin.listArtifacts({ environment: args.environment, entities, stack: args.stack });
766
+ }
767
+ catch (err) {
768
+ console.error(formatError({
769
+ message: `${lexiconName}: listArtifacts failed — ${err instanceof Error ? err.message : String(err)}`,
770
+ }));
771
+ continue;
772
+ }
773
+ const observedThen = prevSnapshot?.artifacts;
774
+ const adiff = diffLiveArtifacts({ observedNow, observedThen });
775
+ totalDrift += adiff.added.length + adiff.removed.length + adiff.changed.length;
776
+ if (args.json) {
777
+ const lex = (byLexicon[lexiconName] ?? (byLexicon[lexiconName] = {}));
778
+ lex.artifacts = adiff;
779
+ // What was actually seen, not just how it moved (behold#146): the
780
+ // metadata was in hand and dropped, leaving JSON consumers unable to
781
+ // read an artifact's own status (a Helm release's "deployed").
782
+ lex.observedArtifacts = observedNow;
783
+ }
784
+ else
785
+ renderLiveArtifactDiff(lexiconName, args.environment, adiff);
786
+ lexiconChecked = true;
787
+ }
788
+ if (lexiconChecked)
789
+ totalLexiconsChecked++;
790
+ }
791
+ return { byLexicon, totalDrift, totalUnobserved, totalLexiconsChecked, toAccept };
792
+ }
793
+ /**
794
+ * Everything a deep diff reported this run, as deviations to record accepted.
795
+ * `--update-baseline` accepts what was *reported*, never what was already
796
+ * suppressed — re-accepting an unchanged suppression would rewrite its
797
+ * `recordedAt` on every run and turn the baseline into a churn file.
798
+ */
799
+ function deviationsToAccept(deep) {
800
+ const out = [];
801
+ for (const entity of deep.drifted) {
802
+ for (const change of entity.changes) {
803
+ // Only a value that is actually live can be accepted: `absent` means the
804
+ // cloud does not carry the declared property, which is a finding to fix
805
+ // in source or in the cloud, not a value to bless.
806
+ if (!("live" in change))
807
+ continue;
808
+ out.push({ entity: entity.name, type: entity.type, path: change.path, value: change.live });
809
+ }
810
+ }
811
+ return out;
812
+ }
813
+ /** Property-level drift report (#1014). Silent when a lexicon's deep read found nothing to say. */
814
+ function renderDeepDiff(lexiconName, deep, provenanceOf = () => undefined, projectRoot) {
815
+ const drift = countPropertyDrift(deep);
816
+ const unclaimedCount = countUnclaimed(deep);
817
+ const heldCount = countHeld(deep);
818
+ const pendingCount = countPending(deep);
819
+ if (drift === 0 &&
820
+ pendingCount === 0 &&
821
+ heldCount === 0 &&
822
+ unclaimedCount === 0 &&
823
+ deep.accepted.length === 0 &&
824
+ deep.unobserved.length === 0 &&
825
+ deep.undeclaredEntities.length === 0) {
826
+ return;
827
+ }
828
+ const acceptedCount = deep.accepted.reduce((n, e) => n + e.changes.length, 0);
829
+ console.log(`\n${formatBold(`${lexiconName} (properties)`)}`);
830
+ console.log(`${drift} property drift across ${deep.drifted.length} resource(s), ` +
831
+ `${acceptedCount} accepted, ${deep.unchanged.length} unchanged` +
832
+ (pendingCount > 0 ? `, ${pendingCount} pending` : "") +
833
+ (heldCount > 0 ? `, ${heldCount} held` : "") +
834
+ (unclaimedCount > 0 ? `, ${unclaimedCount} unclaimed` : "") +
835
+ (deep.unobserved.length > 0 ? `, ${deep.unobserved.length} unobserved` : ""));
836
+ console.log("-".repeat(80));
837
+ if (deep.unobserved.length > 0) {
838
+ console.log(formatBold("\nPROPERTIES UNOBSERVED (declared; the deep read could not look):"));
839
+ for (const u of deep.unobserved)
840
+ console.log(` ? ${formatUnobserved(u.name, u)}`);
841
+ }
842
+ if (deep.drifted.length > 0) {
843
+ console.log(formatBold("\nPROPERTY DRIFT (declared vs live; baseline shown where one exists):"));
844
+ for (const entity of deep.drifted) {
845
+ console.log(` - ${entity.name} (${entity.type})`);
846
+ const provenance = provenanceOf(entity.name);
847
+ for (const change of entity.changes) {
848
+ const declared = "declared" in change ? formatValue(change.declared) : "<undeclared>";
849
+ const live = "live" in change ? formatValue(change.live) : "<absent>";
850
+ const baseline = "baseline" in change ? ` [accepted: ${formatValue(change.baseline)}]` : "";
851
+ // Both sides of the attribution, where each is known (#1443/#1189).
852
+ const from = change.origin ? ` [from: ${describePathOrigin(change.origin)}]` : "";
853
+ const owner = change.owner ? ` [owner: ${change.owner}]` : "";
854
+ const seen = change.seenOn ? ` [seen on: ${change.seenOn}]` : "";
855
+ console.log(` ${change.path}: ${declared} → ${live}${baseline}${from}${owner}${seen}`);
856
+ // chant #2161 — what may be done about it, when the origin says
857
+ // something today's behaviour would get wrong. A field the author
858
+ // declared directly is left unremarked: editing it IS today's
859
+ // behaviour, and saying so on every row would bury the two that matter.
860
+ // #3597 — the line before the proposal names the composite call and
861
+ // the argument behind the field, with the file and line it was written on.
862
+ const { resolution, summary } = resolveDriftedField({
863
+ entity: entity.name,
864
+ entityType: entity.type,
865
+ path: change.path,
866
+ ...("declared" in change ? { declared: change.declared } : {}),
867
+ ...("live" in change ? { live: change.live } : {}),
868
+ ...(change.origin ? { origin: change.origin } : {}),
869
+ ...(provenance ? { provenance } : {}),
870
+ ...(projectRoot ? { root: projectRoot } : {}),
871
+ });
872
+ if (resolution.kind !== "edit-declaration") {
873
+ console.log(` ${summary}`);
874
+ console.log(` ${resolution.description}`);
875
+ }
876
+ }
877
+ }
878
+ }
879
+ if (pendingCount > 0) {
880
+ // #3706: changes an apply would make that no declared property shows
881
+ // (privileges granted by hand). Printed, so the plan digest moves with them.
882
+ console.log(formatBold("\nPENDING (changes an apply would make that no declared property shows):"));
883
+ for (const p of deep.pending ?? []) {
884
+ console.log(` ~ ${p.subject}: ${p.change}${p.entities && p.entities.length > 0 ? ` [from: ${p.entities.join(", ")}]` : ""}`);
885
+ }
886
+ }
887
+ if (deep.unclaimed.length > 0) {
888
+ // Not drift, and printed after it for that reason (#2160): chant never set
889
+ // these fields, so nothing here is a change chant proposes to make.
890
+ console.log(formatBold("\nUNCLAIMED (live values on properties chant never declared; not drift):"));
891
+ for (const entity of deep.unclaimed) {
892
+ console.log(` - ${entity.name} (${entity.type})`);
893
+ for (const field of entity.fields) {
894
+ const holder = field.heldBy
895
+ ? `held by ${field.heldBy}`
896
+ : "not in this declaration's claimed fields";
897
+ console.log(` ${field.path}: ${formatValue(field.live)} [${holder}]`);
898
+ }
899
+ }
900
+ }
901
+ if (heldCount > 0) {
902
+ console.log(formatBold("\nHELD (declared heldElsewhere(); not drift, never proposed for update):"));
903
+ for (const line of renderHeldLines(deep.held))
904
+ console.log(line);
905
+ }
906
+ if (deep.undeclaredEntities.length > 0) {
907
+ console.log(formatBold("\nUNDECLARED (read deeply, never declared in source):"));
908
+ for (const name of deep.undeclaredEntities)
909
+ console.log(` - ${name}`);
910
+ }
911
+ if (acceptedCount > 0) {
912
+ console.log(formatBold("\nACCEPTED (in the baseline; not drift):"));
913
+ for (const entity of deep.accepted) {
914
+ console.log(` - ${entity.name}: ${entity.changes.map((c) => c.path).join(", ")}`);
915
+ }
916
+ }
917
+ }
918
+ /**
919
+ * Render one `heldElsewhere()` property (#2162) — shared between the deep
920
+ * diff's HELD section and the plan's, so the two never disagree about what a
921
+ * held field looks like on the terminal.
922
+ */
923
+ function formatHeldProperty(h) {
924
+ const live = "live" in h ? formatValue(h.live) : "<absent>";
925
+ const owner = h.owner ? ` [owner: ${h.owner}]` : "";
926
+ const suspicious = h.suspicious
927
+ ? " [SUSPICIOUS: no live value ever showed up here — check the holder is actually running, or that this is the field it writes]"
928
+ : "";
929
+ return ` ${h.path}: held by ${h.by} — ${live}${owner} (${h.reason})${suspicious}`;
930
+ }
931
+ /** Lines for every held property across a lexicon's entities, indented and grouped by entity name. */
932
+ function renderHeldLines(held) {
933
+ const lines = [];
934
+ for (const entity of held) {
935
+ lines.push(` - ${entity.name} (${entity.type})`);
936
+ for (const h of entity.held)
937
+ lines.push(formatHeldProperty(h));
938
+ }
939
+ return lines;
940
+ }
941
+ function renderLiveDiff(lexiconName, environment, diff) {
942
+ const counts = `${diff.missing.length} missing, ${diff.orphan.length} orphan, ` +
943
+ `${diff.disappeared.length} disappeared, ${diff.newlyObserved.length} newly observed, ` +
944
+ `${diff.driftedSinceSnapshot.length} drifted, ${diff.unchanged.length} unchanged` +
945
+ (diff.runtimeChildren.length > 0 ? `, ${diff.runtimeChildren.length} runtime` : "") +
946
+ (diff.unobserved.length > 0 ? `, ${diff.unobserved.length} unobserved` : "");
947
+ console.log(`\n${formatBold(lexiconName)} — environment: ${environment}`);
948
+ console.log(counts);
949
+ console.log("-".repeat(80));
950
+ if (diff.unobserved.length > 0) {
951
+ console.log(formatBold("\nUNOBSERVED (declared; chant could not read live state — status unknown):"));
952
+ for (const u of diff.unobserved) {
953
+ console.log(` ? ${formatUnobserved(u.name, u)}`);
954
+ }
955
+ }
956
+ if (diff.missing.length > 0) {
957
+ console.log(formatBold("\nMISSING (declared, provider reports not in cloud):"));
958
+ for (const name of diff.missing) {
959
+ console.log(missingRow(diff, name));
960
+ }
961
+ }
962
+ if (diff.orphan.length > 0) {
963
+ console.log(formatBold("\nORPHAN (in cloud, not declared):"));
964
+ for (const name of diff.orphan)
965
+ console.log(` - ${name}`);
966
+ }
967
+ if (diff.runtimeChildren.length > 0) {
968
+ console.log(formatBold("\nRUNTIME (owned by a declared resource; not drift, not an orphan — #1077):"));
969
+ for (const r of diff.runtimeChildren)
970
+ console.log(` - ${r.name} (${r.type}) — owned by ${r.owner}`);
971
+ }
972
+ if (diff.disappeared.length > 0) {
973
+ console.log(formatBold("\nDISAPPEARED (in last snapshot, gone now):"));
974
+ for (const name of diff.disappeared)
975
+ console.log(` - ${name}`);
976
+ }
977
+ if (diff.newlyObserved.length > 0) {
978
+ console.log(formatBold("\nNEWLY OBSERVED (declared, observed, no prior snapshot):"));
979
+ for (const name of diff.newlyObserved)
980
+ console.log(` - ${name}`);
981
+ }
982
+ if (diff.driftedSinceSnapshot.length > 0) {
983
+ console.log(formatBold("\nDRIFTED (changed since last snapshot):"));
984
+ for (const drift of diff.driftedSinceSnapshot) {
985
+ console.log(` - ${drift.name} (${drift.type})`);
986
+ for (const change of drift.changes) {
987
+ const oldStr = formatValue(change.oldValue);
988
+ const newStr = formatValue(change.newValue);
989
+ console.log(` ${change.path}: ${oldStr} → ${newStr}`);
990
+ }
991
+ }
992
+ }
993
+ }
994
+ function formatValue(v) {
995
+ if (v === undefined)
996
+ return "<unset>";
997
+ if (typeof v === "string")
998
+ return v.length > 60 ? v.slice(0, 57) + "..." : v;
999
+ const json = JSON.stringify(v);
1000
+ return json.length > 60 ? json.slice(0, 57) + "..." : json;
1001
+ }
1002
+ function renderLiveArtifactDiff(lexiconName, environment, diff) {
1003
+ // Skip emission entirely when there's nothing to show — keeps the output
1004
+ // clean for lexicons that only implement describeResources.
1005
+ if (diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0 && diff.unchanged.length === 0) {
1006
+ return;
1007
+ }
1008
+ const counts = `${diff.added.length} added, ${diff.removed.length} removed, ` +
1009
+ `${diff.changed.length} changed, ${diff.unchanged.length} unchanged`;
1010
+ console.log(`\n${formatBold(lexiconName)} (artifacts) — environment: ${environment}`);
1011
+ console.log(counts);
1012
+ console.log("-".repeat(80));
1013
+ if (diff.added.length > 0) {
1014
+ console.log(formatBold("\nARTIFACTS ADDED (in cloud now, not in last snapshot):"));
1015
+ for (const name of diff.added)
1016
+ console.log(` - ${name}`);
1017
+ }
1018
+ if (diff.removed.length > 0) {
1019
+ console.log(formatBold("\nARTIFACTS REMOVED (in last snapshot, gone now):"));
1020
+ for (const name of diff.removed)
1021
+ console.log(` - ${name}`);
1022
+ }
1023
+ if (diff.changed.length > 0) {
1024
+ console.log(formatBold("\nARTIFACTS CHANGED (metadata differs since last snapshot):"));
1025
+ for (const drift of diff.changed) {
1026
+ console.log(` - ${drift.name} (${drift.type})`);
1027
+ for (const change of drift.changes) {
1028
+ const oldStr = formatValue(change.oldValue);
1029
+ const newStr = formatValue(change.newValue);
1030
+ console.log(` ${change.path}: ${oldStr} → ${newStr}`);
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ /**
1036
+ * chant lifecycle plan <environment> [lexicon]
1037
+ *
1038
+ * Promote the live diff to a typed, read-only change set: per-entity
1039
+ * create / update / delete / adopt / noop / unobserved. Strictly read-only —
1040
+ * never mutates, never deploys. Deletes are never proposed without ownership
1041
+ * data (added in #121); an undeclared live resource is `adopt`; a declared
1042
+ * entity chant could not read is `unobserved` and gets no proposal at all
1043
+ * (#1089) — a create is only ever proposed against a confirmed absence.
1044
+ */
1045
+ export async function runLifecyclePlan(ctx) {
1046
+ const { args, plugins, serializers } = ctx;
1047
+ const environment = args.extraPositional;
1048
+ const lexiconFilter = args.extraPositional2;
1049
+ if (!environment) {
1050
+ console.error(formatError({ message: "Environment is required: chant lifecycle plan <environment> [lexicon]" }));
1051
+ return 1;
1052
+ }
1053
+ const targetSerializers = lexiconFilter
1054
+ ? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
1055
+ : serializers;
1056
+ const { config } = await loadChantConfig(resolve("."));
1057
+ // This invocation's parameters, so the declared side of the comparison is the estate
1058
+ // the caller asked for rather than the parameter defaults (#1483).
1059
+ const declaredParams = await commandBuildParams(config.buildParams, args);
1060
+ if (!declaredParams)
1061
+ return 1;
1062
+ const buildResult = await build(resolveBuildRoot(args, config), targetSerializers, undefined, {
1063
+ buildParams: declaredParams,
1064
+ buildRoots: collectBuildRootContributors(plugins, config, resolve(".")),
1065
+ });
1066
+ if (buildResult.errors.length > 0) {
1067
+ console.error(formatError({ message: "Build failed — fix errors before planning" }));
1068
+ return 1;
1069
+ }
1070
+ await fetchLifecycle();
1071
+ const lexicons = lexiconFilter ? [lexiconFilter] : Array.from(buildResult.manifest.lexicons);
1072
+ // Multi-stack component projects deploy one CFN stack per component, so the
1073
+ // single-stack-per-env observe below would see nothing live and plan every
1074
+ // resource as "create". Resolve each component's cfn-deploy stack(s) and
1075
+ // observe them all — the same fix runGraphLive uses (graph.ts). Empty for a
1076
+ // single-stack project → the original single-call path is kept.
1077
+ let componentStacks = [];
1078
+ try {
1079
+ const disc = await discoverComponents(resolveBuildRoot(args, config), { sandbox: args.sandbox });
1080
+ const set = new Set();
1081
+ for (const { component } of disc.components.values())
1082
+ for (const s of cfnDeployStacks(component.deploy))
1083
+ set.add(s);
1084
+ componentStacks = [...set];
1085
+ }
1086
+ catch {
1087
+ // no components / discovery failed → single-stack path
1088
+ }
1089
+ const merged = { env: environment, entries: [] };
1090
+ let checked = 0;
1091
+ // Held properties (#2162), across every lexicon this plan checks — not an
1092
+ // action, so it rides beside `entries` rather than inside it.
1093
+ const heldSets = [];
1094
+ // Effect receipts (#1832): declared, diffed, and observed like any resource,
1095
+ // but observe-only to the generic apply path — the plan compares live value
1096
+ // to resolved expectation and proposes the fire, never a create/update.
1097
+ const receipts = collectEffectReceipts(buildResult.entities);
1098
+ const receiptReadings = new Map();
1099
+ // Every lexicon's observed resources merged, for resolving a receipt's
1100
+ // reference inputs against observed values / stack outputs at plan time.
1101
+ const allObservedResources = {};
1102
+ // #1166 — same self-sufficiency as `chant graph --live`: an environment can
1103
+ // declare its own endpoint, applied here unless the ambient shell already
1104
+ // set it. `chant lifecycle plan` is always a live read (no `--live` flag of
1105
+ // its own), so this applies unconditionally.
1106
+ // Names here, plugins there: the endpoint vars come from each lexicon's own
1107
+ // emulator capability (#1345), so this needs the loaded plugin, not the name.
1108
+ const endpointResult = applyLiveEndpoint(config.environments, environment, lexicons.map((name) => plugins.find((p) => p.name === name)).filter((p) => p !== undefined));
1109
+ if (endpointResult.notice)
1110
+ console.error(formatWarning({ message: endpointResult.notice }));
1111
+ try {
1112
+ for (const lexiconName of lexicons) {
1113
+ const plugin = plugins.find((p) => p.name === lexiconName);
1114
+ if (!plugin)
1115
+ continue;
1116
+ if (!plugin.describeResources) {
1117
+ // Plan is entity-keyed; artifact-only lexicons have no declared axis.
1118
+ if (!args.json) {
1119
+ console.error(formatWarning({
1120
+ message: `${lexiconName}: lexicon does not implement describeResources — skipping (no declared axis to plan against)`,
1121
+ }));
1122
+ }
1123
+ continue;
1124
+ }
1125
+ const declared = new Set();
1126
+ const entities = new Map();
1127
+ for (const [name, entity] of buildResult.entities) {
1128
+ // A receipt has no `props` payload of its own but is declared, diffed,
1129
+ // and observed like any resource (#1832) — it joins the declared axis
1130
+ // so its lexicon's observation can confirm presence or absence.
1131
+ if (entity.lexicon === lexiconName && (isObservableDeclarable(entity) || isEffectReceipt(entity))) {
1132
+ declared.add(name);
1133
+ entities.set(name, {
1134
+ entityType: entity.entityType,
1135
+ props: (isObservableDeclarable(entity) && entity.props != null ? entity.props : {}),
1136
+ });
1137
+ }
1138
+ }
1139
+ const rawOutput = buildResult.outputs.get(lexiconName);
1140
+ const buildOutput = rawOutput === undefined
1141
+ ? ""
1142
+ : typeof rawOutput === "string"
1143
+ ? rawOutput
1144
+ : rawOutput.primary;
1145
+ const observed = await observeLexicon(plugin, {
1146
+ environment,
1147
+ buildOutput,
1148
+ declared,
1149
+ entities,
1150
+ componentStacks,
1151
+ owned: args.owned,
1152
+ // A plan reading the wrong namespace proposes creates for resources
1153
+ // that are running (#1629) — the same override the live diff takes.
1154
+ ...(args.namespace ? { namespace: args.namespace } : {}),
1155
+ });
1156
+ Object.assign(allObservedResources, observed.resources);
1157
+ // Held properties (#2162), behind `--deep` since #2405.
1158
+ //
1159
+ // A held property is never a proposal: it is computed against this same
1160
+ // observation and carried on the plan beside `entries`, never folded
1161
+ // into one. So the entries a plan proposes are identical whether this
1162
+ // ran or not, and the only thing `--deep` buys is the HELD section.
1163
+ //
1164
+ // It used to run whenever the lexicon could, which made the read's cost
1165
+ // a function of how many resources an estate holds rather than how it is
1166
+ // organised. Measured on 528 resources across 8 stacks: 24 stack-shaped
1167
+ // calls, and 496 from this pass. At ten thousand resources that is
1168
+ // roughly twenty thousand calls against eighty, for a section the caller
1169
+ // did not ask for.
1170
+ //
1171
+ // `--deep` is the flag `lifecycle snapshot` already uses for the same
1172
+ // choice, and `lifecycle diff` still runs the pass unconditionally
1173
+ // because property drift is the question it exists to answer.
1174
+ if (args.deep && plugin.observeResourcesDeep) {
1175
+ const deep = await deepDiffForLexicon(plugin, {
1176
+ environment,
1177
+ buildOutput,
1178
+ entities,
1179
+ componentStacks,
1180
+ });
1181
+ for (const entity of deep.held)
1182
+ heldSets.push({ ...entity, lexicon: lexiconName });
1183
+ for (const s of suspiciousHeld(deep)) {
1184
+ console.error(formatWarning({
1185
+ message: `${lexiconName}: ${s.name}.${s.path} is declared heldElsewhere(by: "${s.by}") but no live value ever showed up there — the hand-over may never have happened.`,
1186
+ }));
1187
+ }
1188
+ }
1189
+ for (const [receiptName, receipt] of receipts) {
1190
+ if (receipt.lexicon !== lexiconName)
1191
+ continue;
1192
+ const live = observed.resources[receiptName];
1193
+ const hole = observed.unobserved[receiptName];
1194
+ if (live) {
1195
+ receiptReadings.set(receiptName, {
1196
+ observed: true,
1197
+ present: true,
1198
+ value: readReceiptValue(live.attributes),
1199
+ type: live.type,
1200
+ ...(live.physicalId ? { physicalId: live.physicalId } : {}),
1201
+ lexicon: lexiconName,
1202
+ });
1203
+ }
1204
+ else if (hole) {
1205
+ receiptReadings.set(receiptName, {
1206
+ observed: false,
1207
+ present: false,
1208
+ lexicon: lexiconName,
1209
+ ...(hole.type ? { type: hole.type } : {}),
1210
+ unobservedReason: hole.reason,
1211
+ ...(hole.detail ? { unobservedDetail: hole.detail } : {}),
1212
+ });
1213
+ }
1214
+ else {
1215
+ // Neither returned nor named unobserved: the lexicon looked and
1216
+ // confirmed the receipt absent — the same claim the change set
1217
+ // reads off a bare observation (#1089).
1218
+ receiptReadings.set(receiptName, { observed: true, present: false, lexicon: lexiconName });
1219
+ }
1220
+ }
1221
+ const content = await readSnapshot(environment, lexiconName);
1222
+ const observedThen = content ? JSON.parse(content).resources : undefined;
1223
+ const cs = buildChangeSet(environment, {
1224
+ declared,
1225
+ observedNow: observed.resources,
1226
+ observedThen,
1227
+ unobserved: observed.unobserved,
1228
+ // The resolved read address rides every entry, not just unobserved
1229
+ // ones — the diff path already passes it (#1620); plan lost it.
1230
+ queried: observed.queried,
1231
+ // A create carries its declaration's fingerprint (#3652), so the
1232
+ // plan's digest moves when what it would create does.
1233
+ definitions: declaredDefinitions(entities),
1234
+ }, {
1235
+ // Attribution survives the flat merge below (#1674).
1236
+ lexicon: lexiconName,
1237
+ });
1238
+ // Disruption (#1665) is asked of THIS lexicon, before the merge: the
1239
+ // deltas are paths into its own observation shape, and the replacement
1240
+ // rules are in the spec it compiled. A lexicon that answers nothing
1241
+ // leaves every update `unknown` — never `in-place`.
1242
+ const classified = await annotateDisruption(cs, environment, plugin.classifyDisruption ? (o) => plugin.classifyDisruption(o) : undefined);
1243
+ merged.entries.push(...classified.entries);
1244
+ checked++;
1245
+ }
1246
+ }
1247
+ finally {
1248
+ endpointResult.restore();
1249
+ }
1250
+ if (checked === 0) {
1251
+ console.error(formatWarning({
1252
+ message: "No lexicons implement describeResources — nothing to plan",
1253
+ }));
1254
+ return 1;
1255
+ }
1256
+ // Receipt classification (#1832): live value vs resolved expectation.
1257
+ // Absent/stale proposes the fire; unobservable is a loud hole; whatever the
1258
+ // generic classification proposed for a receipt (a create, a delete) is
1259
+ // replaced — the effect step is the sole writer, and a receipt is never a
1260
+ // prune candidate.
1261
+ if (receipts.size > 0) {
1262
+ const receiptEntries = planReceipts(receipts, receiptReadings, observedValueResolver(allObservedResources));
1263
+ mergeReceiptEntries(merged, receipts, receiptEntries);
1264
+ }
1265
+ merged.entries.sort((a, b) => a.name.localeCompare(b.name));
1266
+ if (heldSets.length > 0)
1267
+ merged.held = heldSets.sort((a, b) => a.name.localeCompare(b.name));
1268
+ // Say it on stderr too, so `--json` and `--report gitlab-mr` consumers (whose
1269
+ // shapes have no room for it) still learn the plan has a hole (#1089). The
1270
+ // `markdown` report carries the same wording in its own body instead — a
1271
+ // reviewer reading a comment never sees this stderr.
1272
+ for (const notice of unobservedPlanNotice(merged)) {
1273
+ console.error(formatWarning({ message: notice }));
1274
+ }
1275
+ // Same reason as above (#1665): the `--json` and `--report gitlab-mr` shapes
1276
+ // have no column for how much an update hurts.
1277
+ for (const notice of disruptionNotices(merged)) {
1278
+ console.error(formatWarning({ message: notice }));
1279
+ }
1280
+ // `--report gitlab-mr` emits the GitLab MR plan-widget artifact instead of the
1281
+ // human render. Write it to a file (`tfplan.json`) in CI and declare it as
1282
+ // `artifacts:reports:terraform` to light up the merge-request widget.
1283
+ if (args.reportFile === "gitlab-mr") {
1284
+ console.log(JSON.stringify(gitlabMrReport(merged)));
1285
+ return 0;
1286
+ }
1287
+ // `--report markdown` emits the reviewer-facing projection (#1983) — a
1288
+ // counts header, entries grouped and attributed to their lexicon, holes and
1289
+ // disruption both carried in the body rather than left to stderr.
1290
+ if (args.reportFile === "markdown") {
1291
+ console.log(renderChangeSetMarkdown(merged));
1292
+ return 0;
1293
+ }
1294
+ if (args.json) {
1295
+ console.log(JSON.stringify(merged, null, 2));
1296
+ }
1297
+ else {
1298
+ console.log(renderChangeSet(merged));
1299
+ }
1300
+ return 0;
1301
+ }
1302
+ /**
1303
+ * chant lifecycle teardown <environment> (#1222).
1304
+ *
1305
+ * Enumerates what deleting the environment would remove: every live resource
1306
+ * carrying this project's ownership marker (managed-by + `ownership.stack`)
1307
+ * with the requested env identity. Stateless — live markers only, no build, no
1308
+ * snapshot. Without `--yes` nothing is deleted; with it the planned set is
1309
+ * executed per lexicon with one bounded retry pass over failures, and every
1310
+ * candidate's outcome is reported (deleted / failed / not-prunable / retained / skipped —
1311
+ * never silence). A production-like environment name additionally requires an
1312
+ * interactive confirmation, or `--confirm-prod` non-interactively.
1313
+ */
1314
+ export async function runLifecycleTeardown(ctx) {
1315
+ const { args, plugins } = ctx;
1316
+ const environment = args.extraPositional;
1317
+ if (!environment) {
1318
+ console.error(formatError({ message: "Environment is required: chant lifecycle teardown <environment>" }));
1319
+ return 1;
1320
+ }
1321
+ const { config } = await loadChantConfig(resolve("."));
1322
+ // Refuse an env the project does not declare — a typo here is the difference
1323
+ // between tearing down `dev` and tearing down `prod`. Literal `environments`
1324
+ // entries only (#1221's pattern entries would extend this same check).
1325
+ const envErr = unknownEnvError(environment, config.environments);
1326
+ if (envErr) {
1327
+ console.error(formatError({ message: envErr }));
1328
+ return 1;
1329
+ }
1330
+ // Teardown selects on the ownership marker; a project that stamps none has
1331
+ // nothing to key on, and "delete what looks like mine" is not a fallback.
1332
+ const stack = resolveOwnershipStack(config);
1333
+ if (stack === undefined) {
1334
+ console.error(formatError({
1335
+ message: "This project declares no ownership.stack — teardown is marker-scoped and has nothing to select on",
1336
+ hint: 'Set `ownership: { stack: "<name>" }` in chant.config.ts and deploy, so resources carry the marker teardown keys on.',
1337
+ }));
1338
+ return 1;
1339
+ }
1340
+ // The prod guard (#1222): a production-like name never falls to `--yes`
1341
+ // alone. Interactive runs re-type the environment name; non-interactive
1342
+ // runs say `--confirm-prod` explicitly. Checked before any live read so a
1343
+ // refused teardown touches nothing at all.
1344
+ if (args.yes && isProdLikeEnvironment(environment) && !args.confirmProd) {
1345
+ if (!process.stdin.isTTY) {
1346
+ console.error(formatError({
1347
+ message: `"${environment}" looks like a production environment — --yes alone is not enough`,
1348
+ hint: "Re-run with --yes --confirm-prod to tear it down non-interactively.",
1349
+ }));
1350
+ return 1;
1351
+ }
1352
+ const confirmed = await promptProdTeardown(environment);
1353
+ if (!confirmed) {
1354
+ console.error(formatError({ message: "Confirmation did not match — nothing was deleted." }));
1355
+ return 1;
1356
+ }
1357
+ }
1358
+ // #1166 — teardown is always a live read (and with --yes, a live write), so
1359
+ // an environment's declared endpoint applies here too, unless the ambient
1360
+ // shell already set it.
1361
+ const readingPlugins = plugins.filter((p) => p.teardownOwned || p.describeResources || p.executeTeardown);
1362
+ const endpointResult = applyLiveEndpoint(config.environments, environment, readingPlugins);
1363
+ if (endpointResult.notice)
1364
+ console.error(formatWarning({ message: endpointResult.notice }));
1365
+ let plan;
1366
+ let report;
1367
+ try {
1368
+ // A multi-stack project's declared stacks, for stack-shaped teardowns
1369
+ // (aws enumerates and deletes whole CloudFormation stacks). A single-stack
1370
+ // project passes nothing and the env-named default convention applies.
1371
+ const deployedStacks = (config.stacks ?? []).map((s) => ({
1372
+ name: s.name,
1373
+ ...(s.region ? { region: s.region } : {}),
1374
+ }));
1375
+ plan = await planTeardown({
1376
+ environment,
1377
+ stack,
1378
+ plugins,
1379
+ ...(deployedStacks.length > 0 ? { deployedStacks } : {}),
1380
+ });
1381
+ if (args.yes) {
1382
+ report = await executeTeardown({
1383
+ environment,
1384
+ stack,
1385
+ plugins,
1386
+ plan,
1387
+ ...(deployedStacks.length > 0 ? { deployedStacks } : {}),
1388
+ });
1389
+ }
1390
+ }
1391
+ finally {
1392
+ endpointResult.restore();
1393
+ }
1394
+ if (args.json) {
1395
+ console.log(JSON.stringify(report ?? plan, null, 2));
1396
+ return report && report.outcomes.some((o) => o.outcome === "failed") ? 1 : 0;
1397
+ }
1398
+ console.log(formatBold(args.yes
1399
+ ? `Teardown — environment: ${environment}, stack: ${stack}`
1400
+ : `Teardown plan — environment: ${environment}, stack: ${stack} (plan only — nothing is deleted)`));
1401
+ if (plan.entries.length === 0) {
1402
+ console.error(formatWarning({
1403
+ message: `No live resources carry the marker stack "${stack}" + env "${environment}" — nothing would be deleted.` +
1404
+ (plan.holes.length > 0
1405
+ ? " But this plan has holes (below) — parts of the estate could not be read, so \"nothing\" is a claim about what was readable, not about the environment."
1406
+ : " If this environment is deployed, check that its resources were stamped (ownership marking on, and the env identity resolved at build time)."),
1407
+ }));
1408
+ }
1409
+ else {
1410
+ console.log(`\n${plan.entries.length} resource(s) would be deleted:`);
1411
+ console.log("RESOURCE".padEnd(28) + "TYPE".padEnd(32) + "MARKER".padEnd(24) + "LEXICON");
1412
+ console.log("-".repeat(96));
1413
+ for (const entry of plan.entries) {
1414
+ console.log(entry.name.padEnd(28) +
1415
+ entry.type.padEnd(32) +
1416
+ `${entry.marker.stack}/${entry.marker.env}`.padEnd(24) +
1417
+ entry.lexicon);
1418
+ }
1419
+ }
1420
+ // Holes are loud (#1089): an unreadable kind is unknown, not absent, and the
1421
+ // execution half must not treat this plan as the whole delete set.
1422
+ if (plan.holes.length > 0) {
1423
+ console.error(formatWarning({
1424
+ message: `${plan.holes.length} hole(s) — resources chant may own but could not read. This plan is incomplete, not clean.`,
1425
+ }));
1426
+ console.log(formatBold("\nHOLES (stamped kinds the read could not cover):"));
1427
+ for (const hole of plan.holes) {
1428
+ console.log(` ? ${hole.lexicon}: ${hole.name}${hole.type ? ` (${hole.type})` : ""} — ${hole.reason}${hole.detail ? `: ${hole.detail}` : ""}`);
1429
+ }
1430
+ }
1431
+ if (plan.skipped.length > 0) {
1432
+ console.error(formatWarning({
1433
+ message: `Skipped (no teardown enumeration and no describeResources): ${plan.skipped.join(", ")} — those lexicons' resources are not in this plan.`,
1434
+ }));
1435
+ }
1436
+ if (!report) {
1437
+ console.error(formatWarning({ message: "Plan only — re-run with --yes to execute." }));
1438
+ return 0;
1439
+ }
1440
+ if (report.outcomes.length > 0) {
1441
+ console.log(formatBold("\nOutcomes:"));
1442
+ console.log("RESOURCE".padEnd(28) + "OUTCOME".padEnd(14) + "LEXICON".padEnd(12) + "DETAIL");
1443
+ console.log("-".repeat(96));
1444
+ for (const o of report.outcomes) {
1445
+ console.log(o.name.padEnd(28) +
1446
+ o.outcome.padEnd(14) +
1447
+ o.lexicon.padEnd(12) +
1448
+ (o.detail ?? "") +
1449
+ (o.retried ? " (after retry)" : ""));
1450
+ }
1451
+ }
1452
+ const counts = { deleted: 0, failed: 0, "not-prunable": 0, retained: 0, skipped: 0 };
1453
+ for (const o of report.outcomes)
1454
+ counts[o.outcome]++;
1455
+ console.log(`\n${counts.deleted} deleted, ${counts.failed} failed, ${counts["not-prunable"]} not prunable, ` +
1456
+ `${counts.retained} retained, ${counts.skipped} skipped`);
1457
+ if (report.unimplemented.length > 0) {
1458
+ console.error(formatWarning({
1459
+ message: `Not executed (no teardown execution in these lexicons yet): ${report.unimplemented.join(", ")} — their candidates are reported as skipped, not deleted.`,
1460
+ }));
1461
+ }
1462
+ if (counts.retained > 0) {
1463
+ console.error(formatWarning({
1464
+ message: `${counts.retained} resource(s) retained — owned by this env but deliberately kept (generated-once secrets are never swept). ` +
1465
+ `The environment is NOT clean while they exist; delete them explicitly (e.g. kubectl delete) if you mean to.`,
1466
+ }));
1467
+ }
1468
+ if (plan.holes.length > 0) {
1469
+ console.error(formatWarning({
1470
+ message: "This teardown ran over an incomplete plan (holes above) — the environment cannot be called clean.",
1471
+ }));
1472
+ }
1473
+ if (counts.failed > 0) {
1474
+ console.error(formatError({
1475
+ message: `${counts.failed} candidate(s) failed to delete after the retry pass — see the outcomes above.`,
1476
+ }));
1477
+ return 1;
1478
+ }
1479
+ return 0;
1480
+ }
1481
+ /**
1482
+ * The interactive half of the prod guard: the operator re-types the
1483
+ * environment name. Anything else — including EOF — refuses.
1484
+ */
1485
+ async function promptProdTeardown(environment) {
1486
+ const { createInterface } = await import("node:readline");
1487
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1488
+ return new Promise((resolvePrompt) => {
1489
+ rl.question(`"${environment}" looks like a production environment. Type the environment name to confirm teardown: `, (answer) => {
1490
+ resolvePrompt(answer.trim() === environment);
1491
+ rl.close();
1492
+ });
1493
+ // EOF (Ctrl-D) closes the interface without answering — that is a refusal.
1494
+ rl.on("close", () => resolvePrompt(false));
1495
+ });
1496
+ }
1497
+ /**
1498
+ * chant lifecycle log [environment]
1499
+ */
1500
+ export async function runLifecycleLog(ctx) {
1501
+ const environment = ctx.args.extraPositional;
1502
+ await fetchLifecycle();
1503
+ const entries = await listSnapshots({ environment });
1504
+ if (entries.length === 0) {
1505
+ console.error(formatError({ message: "No state snapshots found" }));
1506
+ return 1;
1507
+ }
1508
+ for (const entry of entries) {
1509
+ const date = entry.date.split("T")[0];
1510
+ console.log(`${entry.commit.slice(0, 7)} ${date} ${entry.message}`);
1511
+ }
1512
+ return 0;
1513
+ }
1514
+ /**
1515
+ * The region core hands `describeIdentity` — the one the environment's stacks
1516
+ * declare, when they declare exactly one (#1261's `stacks[].region`).
1517
+ *
1518
+ * Stacks that declare none, or that disagree, resolve to `undefined`, and the
1519
+ * lexicon then resolves the region its own read path would. Guessing one of
1520
+ * several would report a binding half the estate does not use, which is the
1521
+ * failure this command exists to prevent.
1522
+ */
1523
+ function singleDeclaredRegion(targets) {
1524
+ const regions = new Set(targets.map((t) => t.region).filter((r) => Boolean(r)));
1525
+ return regions.size === 1 ? [...regions][0] : undefined;
1526
+ }
1527
+ /**
1528
+ * chant lifecycle whoami <environment> [lexicon] [--json] [--strict]
1529
+ *
1530
+ * Read-only pre-flight (#1982): who chant would act as in each configured
1531
+ * lexicon, and what that principal is scoped to, before it acts. Never
1532
+ * mutates, never gates — `--strict` is the only way this exits non-zero on an
1533
+ * unresolved row, and a lexicon that answers for nothing is reported as such
1534
+ * rather than as an empty identity.
1535
+ */
1536
+ export async function runLifecycleWhoami(ctx) {
1537
+ const { args, plugins } = ctx;
1538
+ const environment = args.extraPositional;
1539
+ const lexiconFilter = args.extraPositional2;
1540
+ if (!environment) {
1541
+ console.error(formatError({
1542
+ message: "Environment is required: chant lifecycle whoami <environment> [lexicon] [--json] [--strict]",
1543
+ }));
1544
+ return 1;
1545
+ }
1546
+ const projectPath = resolve(".");
1547
+ const { config } = await loadChantConfig(projectPath);
1548
+ const declaredEnvNames = environmentNames(config.environments);
1549
+ if (declaredEnvNames && !matchesDeclaredEnvironment(config.environments, environment)) {
1550
+ console.error(formatError({
1551
+ message: `Unknown environment "${environment}"`,
1552
+ hint: `Defined environments: ${declaredEnvNames.join(", ")}`,
1553
+ }));
1554
+ return 1;
1555
+ }
1556
+ const targetPlugins = lexiconFilter ? plugins.filter((p) => p.name === lexiconFilter) : plugins;
1557
+ if (targetPlugins.length === 0) {
1558
+ console.error(formatError({
1559
+ message: `Lexicon "${lexiconFilter}" is not configured for this project`,
1560
+ hint: `Configured: ${plugins.map((p) => p.name).join(", ") || "(none)"}`,
1561
+ }));
1562
+ return 1;
1563
+ }
1564
+ // The environment's declared endpoint applies here exactly as it does to a
1565
+ // `--live` read (#1166). Without it whoami would report the identity of real
1566
+ // AWS while `describeResources` reads the emulator — the wrong-target answer
1567
+ // this command exists to make visible.
1568
+ const endpointResult = applyLiveEndpoint(config.environments, environment, targetPlugins);
1569
+ if (endpointResult.notice)
1570
+ console.error(formatWarning({ message: endpointResult.notice }));
1571
+ const region = singleDeclaredRegion(resolveStackTargets(args, config));
1572
+ let rows;
1573
+ try {
1574
+ rows = await describeIdentities(targetPlugins, {
1575
+ environment,
1576
+ ...(region ? { region } : {}),
1577
+ cwd: projectPath,
1578
+ });
1579
+ }
1580
+ finally {
1581
+ endpointResult.restore();
1582
+ }
1583
+ if (args.json) {
1584
+ console.log(JSON.stringify({ environment, identities: rows }, null, 2));
1585
+ }
1586
+ else {
1587
+ printIdentityTable(rows);
1588
+ }
1589
+ // Never a gate by default: an unresolved identity is a report, and a
1590
+ // pre-flight command that fails the build has stopped being pre-flight.
1591
+ const unresolved = rows.filter((r) => r.status === "unresolved");
1592
+ if (args.strict && unresolved.length > 0) {
1593
+ console.error(formatError({
1594
+ message: `--strict: ${unresolved.length} lexicon(s) could not resolve an identity: ${unresolved.map((r) => r.lexicon).join(", ")}`,
1595
+ }));
1596
+ return 1;
1597
+ }
1598
+ return 0;
1599
+ }
1600
+ function printIdentityTable(rows) {
1601
+ if (rows.length === 0) {
1602
+ console.error(formatWarning({ message: "No lexicons are configured for this project" }));
1603
+ return;
1604
+ }
1605
+ const width = (pick) => Math.max(...rows.map((r) => pick(r).length));
1606
+ const lexWidth = Math.max(width((r) => r.lexicon), "LEXICON".length);
1607
+ const idWidth = Math.max(width((r) => r.identity ?? identityStatusText(r)), "IDENTITY".length);
1608
+ const scopeWidth = Math.max(width((r) => r.scope ?? ""), "SCOPE".length);
1609
+ console.log("LEXICON".padEnd(lexWidth + 2) + "IDENTITY".padEnd(idWidth + 2) + "SCOPE".padEnd(scopeWidth + 2) + "SOURCE");
1610
+ for (const row of rows) {
1611
+ const identity = row.status === "reported" ? (row.identity ?? "") : identityStatusText(row);
1612
+ console.log(row.lexicon.padEnd(lexWidth + 2) +
1613
+ identity.padEnd(idWidth + 2) +
1614
+ (row.scope ?? "").padEnd(scopeWidth + 2) +
1615
+ (row.source ?? ""));
1616
+ }
1617
+ // The resolved address and the reason's detail are both sentences, so they
1618
+ // go under the table rather than widening a column every row pays for.
1619
+ for (const row of rows) {
1620
+ if (row.endpoint)
1621
+ console.error(` ${row.lexicon}: resolved endpoint ${row.endpoint}`);
1622
+ if (row.detail)
1623
+ console.error(` ${row.lexicon}: ${row.detail}`);
1624
+ }
1625
+ }
1626
+ /**
1627
+ * Fallback for unknown state subcommands.
1628
+ */
1629
+ export async function runLifecycleUnknown(ctx) {
1630
+ console.error(formatError({
1631
+ message: `Unknown state subcommand: ${ctx.args.extraPositional ?? ctx.args.path}`,
1632
+ hint: "Available: chant lifecycle snapshot, chant lifecycle show, chant lifecycle diff, chant lifecycle plan, chant lifecycle whoami, chant lifecycle teardown, chant lifecycle log",
1633
+ }));
1634
+ return 1;
1635
+ }
1636
+ function printSnapshotTable(snapshot) {
1637
+ console.log("RESOURCE".padEnd(20) + "TYPE".padEnd(28) + "PHYSICAL ID".padEnd(44) + "STATUS");
1638
+ console.log("-".repeat(100));
1639
+ for (const [name, meta] of Object.entries(snapshot.resources)) {
1640
+ const physicalId = meta.physicalId ?? "";
1641
+ const truncId = physicalId.length > 40 ? physicalId.slice(0, 37) + "..." : physicalId;
1642
+ console.log(name.padEnd(20) +
1643
+ meta.type.padEnd(28) +
1644
+ truncId.padEnd(44) +
1645
+ meta.status);
1646
+ }
1647
+ }
1648
+ /**
1649
+ * chant lifecycle affected --base <ref> [--head <ref>] [--include-dependents] [--json]
1650
+ *
1651
+ * Read-only: report which stacks a change affects (directly-changed via artifact
1652
+ * diff, dependents via the cross-stack graph, external-input as indeterminate).
1653
+ * Returns the set; fanning plan/apply over it is an Op the user composes.
1654
+ *
1655
+ * `config.stacks` is passed through, so on a multi-stack project the answer is
1656
+ * keyed by deployed **stack name** — the name a component's `cfn-deploy` step
1657
+ * uses — instead of by lexicon partition. That is what lets `chant components
1658
+ * fan-out` join a change set to the components that deploy it; an aws-only
1659
+ * estate of fifteen stacks used to answer "aws" no matter which one moved. A
1660
+ * project with no `stacks` declared is untouched: one build of the source root,
1661
+ * one lexicon-keyed answer.
1662
+ *
1663
+ * The cost of per-stack mode is that `dependents` comes back empty (the build
1664
+ * carries cross-lexicon edges, not stack ones), so `--include-dependents` prints
1665
+ * a note saying where the downstream relation actually lives rather than showing
1666
+ * an empty list as if it meant "nothing downstream".
1667
+ */
1668
+ export async function runLifecycleAffected(ctx) {
1669
+ const { args, plugins } = ctx;
1670
+ if (!args.base) {
1671
+ console.error(formatError({
1672
+ message: "Base ref is required: chant lifecycle affected --base <ref> [--head <ref>] [--include-dependents]",
1673
+ }));
1674
+ return 1;
1675
+ }
1676
+ const { config } = await loadChantConfig(resolve("."));
1677
+ // A multi-stack project's `stacks[].src` entries are written relative to the
1678
+ // project root, and they are themselves the scoping, so `--src`/`sourceDir`
1679
+ // does not narrow the root a second time (see `AffectedStacksOptions.stacks`).
1680
+ const projectPath = config.stacks?.length ? resolve(".") : resolveBuildRoot(args, config);
1681
+ let result;
1682
+ try {
1683
+ result = await affectedStacks({
1684
+ projectPath,
1685
+ serializers: plugins.map((p) => p.serializer),
1686
+ stacks: config.stacks,
1687
+ baseRef: args.base,
1688
+ headRef: args.head,
1689
+ includeDependents: args.includeDependents,
1690
+ });
1691
+ }
1692
+ catch (err) {
1693
+ console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
1694
+ return 1;
1695
+ }
1696
+ // Per-stack mode has no stack graph to walk, so an empty `dependents` here
1697
+ // means "not computed", not "nothing downstream". Say so on stderr — silently
1698
+ // handing back an empty list is the failure this command exists to avoid.
1699
+ const multiStack = (config.stacks?.length ?? 0) > 0;
1700
+ if (multiStack && args.includeDependents) {
1701
+ console.error(formatWarning({
1702
+ message: "Dependents are not computed for a multi-stack project: the downstream relation between deployed stacks is stated by the components' dependsOn, not by the build.",
1703
+ hint: "Run chant components fan-out to walk it.",
1704
+ }));
1705
+ }
1706
+ if (args.json) {
1707
+ console.log(JSON.stringify(result, null, 2));
1708
+ return 0;
1709
+ }
1710
+ if (result.changed.length === 0 && result.dependents.length === 0) {
1711
+ console.error(formatSuccess("No stacks affected"));
1712
+ }
1713
+ else {
1714
+ console.log(formatBold("Directly changed:"));
1715
+ console.log(result.changed.length ? result.changed.map((s) => ` ${s}`).join("\n") : " (none)");
1716
+ if (args.includeDependents) {
1717
+ console.log(formatBold("\nDependents (consume a changed stack):"));
1718
+ console.log(result.dependents.length ? result.dependents.map((s) => ` ${s}`).join("\n") : " (none)");
1719
+ }
1720
+ }
1721
+ if (result.indeterminate.length > 0) {
1722
+ console.error(formatWarning({
1723
+ message: `External-input stacks — cannot confirm from source: ${result.indeterminate.join(", ")}`,
1724
+ }));
1725
+ }
1726
+ return 0;
1727
+ }