@intentius/chant 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
@@ -508,3 +508,143 @@ describe("fold provenance over a real composite build (#2161)", () => {
508
508
  expect(verdicts[3].resolution.kind).toBe("edit-declaration");
509
509
  });
510
510
  });
511
+
512
+ // ─────────────────────────────────────────────────────────────────────────
513
+ // chant #3212 — a registered tag inside a composite factory.
514
+ // ─────────────────────────────────────────────────────────────────────────
515
+
516
+ const runtimePath = resolve(thisDir, "./runtime");
517
+ const provenancePath = resolve(thisDir, "./provenance");
518
+
519
+ /**
520
+ * A SQL-shaped tag, the shape the sql lexicon's `table` takes: it builds one
521
+ * entity out of its text and reports, per interpolation, which of the entity's
522
+ * fields that interpolation fed. An interpolated entity is a reference and
523
+ * feeds nothing the author typed, so it reports no field.
524
+ */
525
+ const TAGS = `
526
+ import { createResource } from ${JSON.stringify(runtimePath)};
527
+ import { setInterpolationFields } from ${JSON.stringify(provenancePath)};
528
+ const Table = createResource("Test::Table", ${JSON.stringify(LEXICON_NAME)}, {});
529
+ const CLAUSE = { TABLE: "name", "ENGINE =": "engine", "ORDER BY": "orderBy", TTL: "ttl", COMMENT: "comment" };
530
+
531
+ export function table(strings, ...values) {
532
+ const isEntity = (v) => typeof v === "object" && v !== null && "props" in v;
533
+ let ddl = strings[0];
534
+ const fields = values.map((v, i) => {
535
+ ddl += (isEntity(v) ? v.props.name : String(v)) + strings[i + 1];
536
+ if (isEntity(v)) return [];
537
+ const at = (k) => strings[i].lastIndexOf(k);
538
+ const clause = Object.keys(CLAUSE).sort((a, b) => at(b) - at(a))[0];
539
+ return at(clause) >= 0 ? [CLAUSE[clause], "ddl"] : ["ddl"];
540
+ });
541
+ const m = /CREATE TABLE (\\w+) ENGINE = (\\w+) ORDER BY (\\w+)(?: TTL (.+?))?(?: COMMENT (\\w+))?$/.exec(ddl);
542
+ if (!m) throw new Error("cannot parse: " + ddl);
543
+ const props = { name: m[1], engine: m[2], orderBy: m[3], ddl };
544
+ if (m[4]) props.ttl = m[4];
545
+ if (m[5]) props.comment = m[5];
546
+ const entity = new Table(props);
547
+ setInterpolationFields(entity, fields);
548
+ return entity;
549
+ }
550
+ `;
551
+
552
+ const TAG_COMPOSITES = `
553
+ import { table } from "./tags";
554
+ import { Composite } from ${JSON.stringify(compositePath)};
555
+
556
+ export const EventsTable = Composite((props) => {
557
+ const rollupName = \`\${props.name}_daily\`;
558
+ const events = table\`CREATE TABLE \${props.name} ENGINE = MergeTree ORDER BY ts TTL ts + INTERVAL \${props.ttlDays} DAY\`;
559
+ const rollup = table\`CREATE TABLE \${rollupName} ENGINE = \${"SummingMergeTree"} ORDER BY ts COMMENT \${events}\`;
560
+ return { events, rollup };
561
+ }, "EventsTable");
562
+ `;
563
+
564
+ const TAG_MAIN = `
565
+ import { table } from "../tags";
566
+ import { EventsTable } from "../composites";
567
+
568
+ export const audit = table\`CREATE TABLE audit ENGINE = MergeTree ORDER BY ts\`;
569
+ export const clicks = EventsTable({ name: "clicks", ttlDays: 30 });
570
+ `;
571
+
572
+ describe("fold provenance through a tagged template in a composite factory (#3212)", () => {
573
+ let testDir: string;
574
+ let srcDir: string;
575
+
576
+ beforeEach(async () => {
577
+ const dir = join(repoRoot, ".cache", `chant-3212-${process.pid}-${seq++}`);
578
+ await rm(dir, { recursive: true, force: true });
579
+ await mkdir(dir, { recursive: true });
580
+ testDir = await realpath(dir);
581
+ srcDir = join(testDir, "src");
582
+ await mkdir(srcDir, { recursive: true });
583
+ await writeFile(join(testDir, "tags.ts"), TAGS);
584
+ await writeFile(join(testDir, "composites.ts"), TAG_COMPOSITES);
585
+ await writeFile(join(srcDir, "main.ts"), TAG_MAIN);
586
+ });
587
+
588
+ afterEach(async () => {
589
+ await rm(testDir, { recursive: true, force: true });
590
+ });
591
+
592
+ const buildIt = () =>
593
+ build(srcDir, [namesSerializer], undefined, {
594
+ fold: true,
595
+ lexicons: [LEXICON_NAME],
596
+ intrinsics: [{ name: "table", isTag: true }],
597
+ });
598
+
599
+ test("a field fed by ${props.x} is that parameter, a field the text fixes is a literal", async () => {
600
+ const result = await buildIt();
601
+ expect(result.errors).toEqual([]);
602
+ const events = result.foldProvenance.clicksEvents;
603
+ const rollup = result.foldProvenance.clicksRollup;
604
+ const at = (parameters: string[]) => ({ kind: "composite-parameter", composite: "EventsTable", instance: "clicks", parameters });
605
+ const fixed = { kind: "composite-literal", composite: "EventsTable", instance: "clicks" };
606
+
607
+ expect(events.fields.name).toEqual(at(["name"]));
608
+ expect(events.fields.ttl).toEqual(at(["ttlDays"]));
609
+ expect(events.fields.ddl).toEqual(at(["name", "ttlDays"]));
610
+ expect(events.fields.engine).toEqual(fixed);
611
+ expect(events.fields.orderBy).toEqual(fixed);
612
+
613
+ // Through a body `const`; an interpolated literal string is fixed; an
614
+ // interpolated sibling entity is wiring, so it governs nothing.
615
+ expect(rollup.fields.name).toEqual(at(["name"]));
616
+ expect(rollup.fields.engine).toEqual(fixed);
617
+ expect(rollup.fields.comment).toEqual(fixed);
618
+ expect(rollup.fields.ddl).toEqual(at(["name"]));
619
+
620
+ expect(result.foldProvenance.audit.fields.ttl).toBeUndefined();
621
+ expect(result.foldProvenance.audit.fields.name).toEqual({ kind: "direct" });
622
+ });
623
+
624
+ test("drift on a parameter-fed field proposes the parameter at the call site", async () => {
625
+ const result = await buildIt();
626
+ const provenanceOf = (name: string): EntityProvenance | undefined => {
627
+ const entity = result.entities.get(name);
628
+ return entity ? getProvenance(entity) : undefined;
629
+ };
630
+ const drift = (name: string, path: string, declared: unknown, live: unknown): DeepEntityDrift => {
631
+ const origin = originOfPath(provenanceOf(name)?.paths, path);
632
+ return { name, type: "Test::Table", changes: [{ path, kind: "changed", declared, live, ...(origin ? { origin } : {}) }] };
633
+ };
634
+
635
+ const [ttl, engine] = resolveDeepDrift(
636
+ [
637
+ drift("clicksEvents", "ttl", "ts + INTERVAL 30 DAY", "ts + INTERVAL 90 DAY"),
638
+ drift("clicksEvents", "engine", "MergeTree", "ReplacingMergeTree"),
639
+ ],
640
+ provenanceOf,
641
+ );
642
+
643
+ expect(ttl.resolution.kind).toBe("propose-parameter");
644
+ if (ttl.resolution.kind !== "propose-parameter") throw new Error("unreachable");
645
+ expect(ttl.resolution.parameters).toEqual(["ttlDays"]);
646
+ expect(ttl.resolution.instance).toBe("clicks");
647
+ expect(ttl.resolution.sourceFile).toContain("main.ts");
648
+ expect(engine.resolution.kind).toBe("refuse-fixed");
649
+ });
650
+ });
@@ -287,3 +287,86 @@ describe("entity-declared references and stacks", () => {
287
287
  expect(ir.nodes.find((n) => n.id === "app/vpc")!.attrs).not.toHaveProperty("stack");
288
288
  });
289
289
  });
290
+
291
+ describe("whole declarables held in an entity's fields (#3257)", () => {
292
+ /** An entity shaped like the sql lexicon's: props non-enumerable, `dependsOn` enumerable. */
293
+ function holder(dependsOn: unknown[], extra: object = {}) {
294
+ const e = decl({ lexicon: "sql", entityType: "ClickHouse::Table", ...extra });
295
+ Object.defineProperty(e, "dependsOn", { value: dependsOn, enumerable: true });
296
+ return e;
297
+ }
298
+
299
+ test("draws an edge to each node named directly in an enumerable array", () => {
300
+ const db = decl({ lexicon: "sql", entityType: "ClickHouse::Database" });
301
+ const target = holder([db]);
302
+ const events = holder([db]);
303
+ const mv = holder([db, target, events]);
304
+ const entities = new Map<string, Declarable>([
305
+ ["analytics", db],
306
+ ["dailyActive", target],
307
+ ["events", events],
308
+ ["dailyActiveMv", mv],
309
+ ]);
310
+ resolveAttrRefs(entities);
311
+
312
+ const ir = buildGraphIr(entities);
313
+ const dep = (from: string, to: string) => ({ from, to, kind: "ref", viaAttr: "dependsOn" });
314
+ expect(ir.edges).toEqual([
315
+ dep("dailyActive", "analytics"),
316
+ dep("dailyActiveMv", "analytics"),
317
+ dep("dailyActiveMv", "dailyActive"),
318
+ dep("dailyActiveMv", "events"),
319
+ dep("events", "analytics"),
320
+ ]);
321
+ });
322
+
323
+ test("does not walk into the referenced node, and projects it as a $ref", () => {
324
+ const db = decl({ lexicon: "sql", entityType: "ClickHouse::Database", props: { name: "analytics" } });
325
+ const events = holder([db]);
326
+ const entities = new Map<string, Declarable>([
327
+ ["analytics", db],
328
+ ["events", events],
329
+ ]);
330
+ resolveAttrRefs(entities);
331
+
332
+ const ir = buildGraphIr(entities);
333
+ expect(ir.nodes.find((n) => n.id === "events")!.attrs.dependsOn).toEqual([{ $ref: "analytics" }]);
334
+ });
335
+
336
+ test("an AttrRef and the whole node to the same target collapse to one edge", () => {
337
+ const db = decl({ lexicon: "sql", entityType: "ClickHouse::Database" });
338
+ const events = decl({ lexicon: "sql", entityType: "ClickHouse::Table" });
339
+ const mv = holder([events, new AttrRef(events, "user_id")]);
340
+ const entities = new Map<string, Declarable>([
341
+ ["analytics", db],
342
+ ["events", events],
343
+ ["mv", mv],
344
+ ]);
345
+ resolveAttrRefs(entities);
346
+
347
+ const ir = buildGraphIr(entities);
348
+ expect(ir.edges).toEqual([{ from: "mv", to: "events", kind: "ref", viaAttr: "dependsOn" }]);
349
+ });
350
+
351
+ test("ignores a self reference, and still descends into an inlined property-kind resource", () => {
352
+ const self: Declarable = holder([]);
353
+ (self as unknown as { dependsOn: unknown[] }).dependsOn.push(self);
354
+ const vpc = decl({ lexicon: "aws", entityType: "Vpc" });
355
+ const inline = decl({
356
+ lexicon: "aws",
357
+ entityType: "Listener_Action",
358
+ kind: "property",
359
+ props: { vpc: new AttrRef(vpc, "Id") },
360
+ });
361
+ const lb = decl({ lexicon: "aws", entityType: "Lb", props: { actions: [inline] } });
362
+ const entities = new Map<string, Declarable>([
363
+ ["self", self],
364
+ ["vpc", vpc],
365
+ ["lb", lb],
366
+ ]);
367
+ resolveAttrRefs(entities);
368
+
369
+ const ir = buildGraphIr(entities);
370
+ expect(ir.edges).toEqual([{ from: "lb", to: "vpc", kind: "ref", viaAttr: "actions" }]);
371
+ });
372
+ });
package/src/graph-ir.ts CHANGED
@@ -384,6 +384,23 @@ function refTarget(ref: AttrRef, reverse: Map<object, string>): string | undefin
384
384
  return ref.getLogicalName() ?? (parent ? reverse.get(parent) : undefined);
385
385
  }
386
386
 
387
+ /**
388
+ * The node id a whole declarable held in a config bag stands for, or undefined
389
+ * when it is not a node of this graph (an inlined property-kind resource, say,
390
+ * which is config of the holder and gets walked instead). An entity can name
391
+ * another entity directly rather than through an AttrRef, as the sql lexicon's
392
+ * enumerable `dependsOn` does with the tables, views and databases a statement
393
+ * mentions (#3257); that is a reference to the node, not a nested bag.
394
+ */
395
+ function nodeReference(
396
+ value: unknown,
397
+ reverse: Map<object, string>,
398
+ nodeIds: Set<string>,
399
+ ): string | undefined {
400
+ const id = reverse.get(value as object);
401
+ return id !== undefined && nodeIds.has(id) ? id : undefined;
402
+ }
403
+
387
404
  /** Project a value into a JSON-safe, scrubbed form. References become `{ $ref }`. */
388
405
  function project(value: unknown, seen: Set<unknown>, reverse: Map<object, string>, nodeIds: Set<string>): unknown {
389
406
  if (value === null) return null;
@@ -414,6 +431,9 @@ function project(value: unknown, seen: Set<unknown>, reverse: Map<object, string
414
431
  // Nested declarables (e.g. an inlined property-kind resource) keep their
415
432
  // config in a non-enumerable `props` bag, like top-level nodes — project that.
416
433
  if (isDeclarable(value)) {
434
+ // Another node held whole is a reference to it, not config to inline.
435
+ const to = nodeReference(value, reverse, nodeIds);
436
+ if (to) return { $ref: to } satisfies AttrRefEnvelope;
417
437
  const out = projectConfig(value, seen, reverse, nodeIds);
418
438
  seen.delete(value);
419
439
  return out;
@@ -518,6 +538,15 @@ function collectEdges(
518
538
  if (to && to !== from && nodeIds.has(to)) edges.push({ from, to, kind: "ref", viaAttr });
519
539
  return;
520
540
  }
541
+ // A whole node held in props or an enumerable field (the sql lexicon's
542
+ // `dependsOn`) is a reference to it: draw the edge, and do not walk its
543
+ // config, whose refs belong to that node's own edges (#3257). Checked
544
+ // before `seen` so the same node under two attributes yields both edges.
545
+ const held = nodeReference(value, reverse, nodeIds);
546
+ if (held) {
547
+ if (held !== from) edges.push({ from, to: held, kind: "ref", viaAttr });
548
+ return;
549
+ }
521
550
  if (seen.has(value)) return;
522
551
  seen.add(value);
523
552
  // A nested declarable (an inlined property-kind resource, e.g. a listener's
@@ -85,6 +85,26 @@ describe("EmbeddedImports", () => {
85
85
  ]);
86
86
  });
87
87
 
88
+ test("an importer that keeps matched content as written is not asked to import it, and its reason is the warning", () => {
89
+ let imported = 0;
90
+ const resolver = new EmbeddedImports([
91
+ {
92
+ lexicon: "acme",
93
+ importer: {
94
+ ...widgets,
95
+ keepsAsWritten: (c) => ((c.document as { size?: number }).size === 3 ? "a size-3 widget builds back as size 4" : undefined),
96
+ import: (c) => (imported++, widgets.import(c)),
97
+ },
98
+ },
99
+ ]);
100
+ expect(resolver.resolve(site())).toBeUndefined();
101
+ expect(imported).toBe(0);
102
+ expect(resolver.files).toEqual([]);
103
+ expect(resolver.warnings).toEqual(['ConfigMap agent data["config.yaml"] is a widget, kept as written: a size-3 widget builds back as size 4']);
104
+ expect(isEmbeddedReference(resolver.resolve(site({ text: "kind: widget\nsize: 2\n" })))).toBe(true);
105
+ expect(imported).toBe(1);
106
+ });
107
+
88
108
  test("when two lexicons match, the first imports it and the other is named", () => {
89
109
  const resolver = new EmbeddedImports([
90
110
  { lexicon: "acme", importer: widgets },
@@ -102,6 +102,13 @@ export interface EmbeddedContentImporter {
102
102
  readonly what: string;
103
103
  /** Whether this content is one it imports. Must not throw on content that is not. */
104
104
  matches(content: EmbeddedContent): boolean;
105
+ /**
106
+ * Why content it matches is kept as written instead of imported, or
107
+ * `undefined` to import it. Asked before `import`; the reason is the
108
+ * warning's. grafana keeps a v2 dashboard, which it reads but would build
109
+ * back as v1 JSON (#3031).
110
+ */
111
+ keepsAsWritten?(content: EmbeddedContent): string | undefined;
105
112
  /** Import it. A throw keeps the content as written, with a warning. */
106
113
  import(content: EmbeddedContent): EmbeddedImport;
107
114
  }
@@ -233,6 +240,12 @@ export class EmbeddedImports implements EmbeddedContentResolver {
233
240
  );
234
241
  }
235
242
 
243
+ const kept = chosen.importer.keepsAsWritten?.(content);
244
+ if (kept !== undefined) {
245
+ this.warnings.push(`${content.location} is ${chosen.importer.what}, kept as written: ${kept}`);
246
+ return undefined;
247
+ }
248
+
236
249
  let result: EmbeddedImport;
237
250
  try {
238
251
  result = chosen.importer.import(content);
@@ -0,0 +1,46 @@
1
+ import type { LexiconPlugin } from "../lexicon";
2
+ import type { ParserOptions, ParserOptionSpec } from "./parser";
3
+
4
+ /**
5
+ * Check `--parser-option` entries (`key` or `key=value`) against what the
6
+ * lexicon declares in `parserOptions()` and convert each value to its declared
7
+ * type (#2994). Returns the options, or an error message naming what is wrong
8
+ * and what the lexicon accepts. An empty list is always valid.
9
+ */
10
+ export function resolveParserOptions(
11
+ plugin: Pick<LexiconPlugin, "name" | "parserOptions">,
12
+ entries: readonly string[] | undefined,
13
+ ): { options: ParserOptions } | { error: string } {
14
+ const options: ParserOptions = {};
15
+ if (!entries || entries.length === 0) return { options };
16
+
17
+ const specs: ParserOptionSpec[] = plugin.parserOptions?.() ?? [];
18
+ const accepted =
19
+ specs.length === 0
20
+ ? `lexicon "${plugin.name}" declares no parser options`
21
+ : `${plugin.name} accepts: ${specs.map((s) => `${s.name} (${s.type}): ${s.description}`).join("; ")}`;
22
+
23
+ for (const entry of entries) {
24
+ const eq = entry.indexOf("=");
25
+ const name = eq === -1 ? entry : entry.slice(0, eq);
26
+ const raw = eq === -1 ? undefined : entry.slice(eq + 1);
27
+ const spec = specs.find((s) => s.name === name);
28
+ if (!spec) {
29
+ return { error: `Unknown parser option "${name}" for lexicon "${plugin.name}"; ${accepted}.` };
30
+ }
31
+ if (spec.type === "boolean") {
32
+ if (raw === undefined || raw === "true") options[name] = true;
33
+ else if (raw === "false") options[name] = false;
34
+ else return { error: `Parser option "${name}" is a boolean: use ${name}, ${name}=true or ${name}=false, got "${raw}".` };
35
+ } else if (raw === undefined) {
36
+ return { error: `Parser option "${name}" needs a value: --parser-option ${name}=<${spec.type}>.` };
37
+ } else if (spec.type === "number") {
38
+ const n = Number(raw);
39
+ if (raw.trim() === "" || !Number.isFinite(n)) return { error: `Parser option "${name}" is a number, got "${raw}".` };
40
+ options[name] = n;
41
+ } else {
42
+ options[name] = raw;
43
+ }
44
+ }
45
+ return { options };
46
+ }
@@ -78,6 +78,26 @@ export interface ParseContext {
78
78
  readonly embedded?: EmbeddedContentResolver;
79
79
  }
80
80
 
81
+ /** A value a parser option takes. */
82
+ export type ParserOptionValue = string | number | boolean;
83
+
84
+ /** The options `chant import --parser-option` hands a lexicon's parser, by name. */
85
+ export type ParserOptions = Record<string, ParserOptionValue>;
86
+
87
+ /**
88
+ * One option a lexicon's parser accepts, declared by `LexiconPlugin.parserOptions()`.
89
+ * `chant import --parser-option <name>[=<value>]` is checked against this list and
90
+ * the value is converted to `type` before `templateParser(options)` sees it.
91
+ */
92
+ export interface ParserOptionSpec {
93
+ /** The key after `--parser-option`, e.g. "acceptLossyV1". */
94
+ name: string;
95
+ /** `boolean` takes `true`/`false`, and a bare `--parser-option name` means true. */
96
+ type: "boolean" | "string" | "number";
97
+ /** One line, shown when the option is refused and in the docs. */
98
+ description: string;
99
+ }
100
+
81
101
  /**
82
102
  * Interface for template parsers that convert external formats to IR
83
103
  */
package/src/lexicon.ts CHANGED
@@ -3,7 +3,7 @@ import type { Declarable } from "./declarable";
3
3
  import type { LintRule } from "./lint/rule";
4
4
  import type { RuleSpec } from "./lint/declarative";
5
5
  import type { PostSynthCheck } from "./lint/post-synth";
6
- import type { TemplateParser, TemplateIR, ParseContext } from "./import/parser";
6
+ import type { TemplateParser, TemplateIR, ParseContext, ParserOptions, ParserOptionSpec } from "./import/parser";
7
7
  import type { TypeScriptGenerator } from "./import/generator";
8
8
  import type { EmbeddedContentImporter } from "./import/embedded";
9
9
  import type { AgentConfigImporter } from "./agents/importer";
@@ -426,12 +426,26 @@ export interface InitTemplateSet {
426
426
  * repos or version-constant locations. See ./codegen/pinned-upgrade.ts.
427
427
  */
428
428
  export interface UpstreamPin {
429
+ /**
430
+ * Short name for this pin (`clickhouse`, `postgres-17`), used to pick one
431
+ * pin of a plugin that declares several (`chant dev pinned-upgrade <dir>
432
+ * <label>`). Optional on a lone `upstreamPin`, where it defaults to the
433
+ * lexicon's name; required on every entry of `upstreamPins`.
434
+ */
435
+ readonly label?: string;
429
436
  /** Source file (relative to the lexicon package root) holding the pinned version constant. */
430
437
  readonly file: string;
431
438
  /** Regex whose first capture group is the current pinned version in `file`. */
432
439
  readonly pattern: RegExp;
433
440
  /** Rebuild a line that matched `pattern`, substituting the new version. */
434
441
  replace(newVersion: string, line: string): string;
442
+ /**
443
+ * Set when the version line is not the whole pin: other constants (an image
444
+ * digest, a checksum) move with it and `replace` cannot compute them. The
445
+ * upgrade tooling then reports the newer version and this text, and edits
446
+ * nothing. Say what to move and how.
447
+ */
448
+ readonly alsoMoves?: string;
435
449
  /** Where to query for the latest stable upstream tag. */
436
450
  readonly upstream: {
437
451
  readonly owner: string;
@@ -440,6 +454,25 @@ export interface UpstreamPin {
440
454
  readonly kind: "releases" | "tags";
441
455
  /** Only consider tags ending with this suffix (e.g. "-ee" for GitLab). */
442
456
  readonly tagSuffix?: string;
457
+ /**
458
+ * Map a raw tag to the version string the pin stores, or null to skip the
459
+ * tag. For upstreams whose tags are not versions: postgres/postgres tags
460
+ * are `REL_18_6`, `REL_18_BETA1`, `REL9_6_24`, so
461
+ * `(t) => /^REL_(\d+)_(\d+)$/.exec(t)?.slice(1).join(".") ?? null` gives
462
+ * `18.6` and drops betas, release candidates and the old spelling.
463
+ *
464
+ * When set, the tool reads every page of the tag list (not just the first
465
+ * 50), because such lists are not ordered by version, and the version this
466
+ * returns is what `replace` receives and what the report shows. The pinned
467
+ * constant may hold either the mapped version or the raw tag.
468
+ */
469
+ readonly tagVersion?: (tag: string) => string | null;
470
+ /**
471
+ * Only consider versions whose first numeric segment equals the pinned
472
+ * version's, so a pin on `17.9` reports `17.11` and never `18.6`. For
473
+ * lexicons that pin one snapshot per major line. Default false.
474
+ */
475
+ readonly trackMajor?: boolean;
443
476
  };
444
477
  }
445
478
 
@@ -1160,8 +1193,19 @@ export interface LexiconPlugin {
1160
1193
  */
1161
1194
  detectTemplate?(data: unknown): boolean;
1162
1195
 
1163
- /** Return a parser for importing external templates into IR */
1164
- templateParser?(): TemplateParser;
1196
+ /**
1197
+ * Return a parser for importing external templates into IR. `options` holds
1198
+ * the `chant import --parser-option` values, already checked against
1199
+ * `parserOptions()` and converted to their declared types; it is empty when
1200
+ * none were given.
1201
+ */
1202
+ templateParser?(options?: ParserOptions): TemplateParser;
1203
+
1204
+ /**
1205
+ * The options `templateParser` accepts. `chant import --parser-option` refuses
1206
+ * any name not listed here, so a lexicon with no options leaves this out.
1207
+ */
1208
+ parserOptions?(): ParserOptionSpec[];
1165
1209
 
1166
1210
  /** Return a generator for converting IR to TypeScript */
1167
1211
  templateGenerator?(): TypeScriptGenerator;
@@ -1200,6 +1244,14 @@ export interface LexiconPlugin {
1200
1244
  /** How this lexicon pins its upstream schema version + where to check for a newer one (self-upgrade tooling — see ./codegen/pinned-upgrade.ts). Omit for lexicons with no pinned upstream. */
1201
1245
  readonly upstreamPin?: UpstreamPin;
1202
1246
 
1247
+ /**
1248
+ * Further pins, for a lexicon that pins more than one upstream (sql: the
1249
+ * ClickHouse pin in `upstreamPin` plus one Postgres server per major). Each
1250
+ * entry needs a unique `label`. The upgrade tooling checks `upstreamPin` and
1251
+ * every entry here, one report per pin. Omit when there is one pin.
1252
+ */
1253
+ readonly upstreamPins?: readonly (UpstreamPin & { readonly label: string })[];
1254
+
1203
1255
  /**
1204
1256
  * Generate a CI pipeline from the discovered component graph — the
1205
1257
  * `chant build --components --generate <name>` seam. Only CI-provider
@@ -187,6 +187,22 @@ export interface GateResolutionRecord {
187
187
  * was recorded. Present only when the gate declared a policy.
188
188
  */
189
189
  policyDecision?: GatePolicyDecision;
190
+ /**
191
+ * An ssh signature over this approval by `resolvedBy` (#3163, ws-080):
192
+ * `chant approve --sign`, in the `chant-gate` namespace over the op, gate,
193
+ * environment, plan digest, approver and timestamp. It attests the approver
194
+ * only when the signers file at base lists a key for them. A gate the
195
+ * declaration's `identity.gates` names counts only approvals whose seal
196
+ * verifies. Absent on an unsigned approval.
197
+ */
198
+ seal?: GateApprovalSeal;
199
+ }
200
+
201
+ /** A gate approval's seal (#3163): who signed, the key's fingerprint (reported, never trusted), and the armored signature. */
202
+ export interface GateApprovalSeal {
203
+ signer: string;
204
+ key: string;
205
+ signature: string;
190
206
  }
191
207
 
192
208
  export type GateResolutionInput = Omit<GateResolutionRecord, "version" | "kind">;
@@ -97,6 +97,24 @@ describe("observeResources", () => {
97
97
  expect(names).toEqual(["web-vpc"]);
98
98
  });
99
99
 
100
+ it("keeps property-kind declarables out of the observation universe (#3001)", async () => {
101
+ // A property-kind declarable (a grafana panel, an aws SecurityGroup_Ingress
102
+ // const) carries props but lives only inside the resource holding it.
103
+ // Asked about on its own, a reader can only say "absent" or "not observed".
104
+ const buildResult = {
105
+ outputs: new Map<string, string>([["aws", "{}"]]),
106
+ entities: new Map<string, unknown>([
107
+ ["web-sg", { lexicon: "aws", entityType: "AWS::EC2::SecurityGroup", kind: "resource", props: {} }],
108
+ ["ingress", { lexicon: "aws", entityType: "AWS::EC2::SecurityGroup.Ingress", kind: "property", props: { FromPort: 443 } }],
109
+ ]),
110
+ errors: [],
111
+ } as unknown as BuildResult;
112
+ let names: string[] = [];
113
+ const plugins = [awsPlugin(({ entityNames }) => { names = entityNames; return {}; })];
114
+ await observeResources("prod", plugins, buildResult);
115
+ expect(names).toEqual(["web-sg"]);
116
+ });
117
+
100
118
  it("collects a throwing plugin into errors instead of failing the whole graph, and reports its entities unobserved (#1089)", async () => {
101
119
  const plugins = [
102
120
  awsPlugin(() => { throw new Error("access denied"); }),
@@ -25,7 +25,7 @@ import {
25
25
  } from "../observation";
26
26
  import { zeroResourcesWarning } from "../live-endpoint";
27
27
  import { unqualifiedKey } from "./identity";
28
- import { isResourceDeclarable } from "../declarable";
28
+ import { isObservableDeclarable } from "../declarable";
29
29
 
30
30
  export interface ObserveResult {
31
31
  observations: LiveObservation[];
@@ -150,8 +150,10 @@ export async function observeResources(
150
150
  // parameters and serializer directives (gcp's `defaultAnnotations`) are
151
151
  // build-time inputs — declared, but with nothing in any cloud to compare
152
152
  // against, so keeping them in the universe makes every diff report a
153
- // hole (or worse, a deletion) for a resource that cannot exist.
154
- if (!isResourceDeclarable(entity)) continue;
153
+ // hole (or worse, a deletion) for a resource that cannot exist. A
154
+ // property-kind declarable is read as part of the resource holding it,
155
+ // never on its own (#3001).
156
+ if (!isObservableDeclarable(entity)) continue;
155
157
  entityNames.push(name);
156
158
  entities.set(name, {
157
159
  entityType: entity.entityType,
@@ -174,7 +176,7 @@ export async function observeResources(
174
176
  stackEntityNames = [];
175
177
  stackEntities = new Map();
176
178
  for (const [name, entity] of sb.entities) {
177
- if (entity.lexicon !== plugin.name || !isResourceDeclarable(entity)) continue;
179
+ if (entity.lexicon !== plugin.name || !isObservableDeclarable(entity)) continue;
178
180
  stackEntityNames.push(name);
179
181
  stackEntities.set(name, {
180
182
  entityType: entity.entityType,
@@ -18,7 +18,7 @@ import { formatUnobserved, normalizeObservation, unobservedAll, type UnobservedE
18
18
  // property tree is ever rendered or committed. Two lists would eventually
19
19
  // disagree about what counts as a secret.
20
20
  import { isSensitiveKey } from "../deep-observation";
21
- import { isResourceDeclarable } from "../declarable";
21
+ import { isObservableDeclarable } from "../declarable";
22
22
 
23
23
  /**
24
24
  * Check for potential sensitive data in resource attributes and return warnings.
@@ -128,7 +128,7 @@ export async function takeSnapshot(
128
128
  for (const [name, entity] of buildResult.entities) {
129
129
  // Resource declarables only — outputs, parameters and serializer
130
130
  // directives have no live counterpart (see lifecycle/observe.ts).
131
- if (entity.lexicon === plugin.name && isResourceDeclarable(entity)) {
131
+ if (entity.lexicon === plugin.name && isObservableDeclarable(entity)) {
132
132
  entityNames.push(name);
133
133
  entities.set(name, {
134
134
  entityType: entity.entityType,
@@ -107,7 +107,7 @@ describe("a work lease in one clone", () => {
107
107
 
108
108
  const again = await claimWorkLease("W-003", "carol", { cwd, ttlMs: 1_000, now: at(t0) });
109
109
  if (!again.ok) throw new Error(again.message);
110
- const closed = await releaseWorkLease("W-003", "dispatcher", { cwd, outcome: "expired", now: at("2026-09-25T11:00:00.000Z") });
110
+ const closed = await releaseWorkLease("W-003", "dispatcher", { cwd, outcome: "abandoned", now: at("2026-09-25T11:00:00.000Z") });
111
111
  expect(closed).toMatchObject({ ok: true, lease: { holder: "carol" } });
112
112
  });
113
113