@intentius/chant 0.124.13 → 0.124.15

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 (991) 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.d.ts +6 -0
  35. package/dist/build.d.ts.map +1 -1
  36. package/dist/build.js +669 -0
  37. package/dist/builder.js +44 -0
  38. package/dist/cdk/advise.js +30 -0
  39. package/dist/cdk/assembly.js +122 -0
  40. package/dist/cdk/graph.js +444 -0
  41. package/dist/cdk/tier-map.js +64 -0
  42. package/dist/cdk/types.js +24 -0
  43. package/dist/change-set.d.ts +24 -2
  44. package/dist/change-set.d.ts.map +1 -1
  45. package/dist/change-set.js +361 -0
  46. package/dist/child-project.js +21 -0
  47. package/dist/ci-jobs.d.ts +227 -0
  48. package/dist/ci-jobs.d.ts.map +1 -0
  49. package/dist/ci-jobs.js +448 -0
  50. package/dist/claimed-fields.js +135 -0
  51. package/dist/cli/build-options.js +70 -0
  52. package/dist/cli/build-params-cli.js +139 -0
  53. package/dist/cli/command-group.js +123 -0
  54. package/dist/cli/commands/audit-agents.js +326 -0
  55. package/dist/cli/commands/audit.js +571 -0
  56. package/dist/cli/commands/build.js +920 -0
  57. package/dist/cli/commands/carve-apply.js +153 -0
  58. package/dist/cli/commands/carve-bridge.js +201 -0
  59. package/dist/cli/commands/carve-emit.js +332 -0
  60. package/dist/cli/commands/carve-status.js +119 -0
  61. package/dist/cli/commands/carve.js +204 -0
  62. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  63. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  64. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  65. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  66. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  67. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  68. package/dist/cli/commands/check-lexicon.js +766 -0
  69. package/dist/cli/commands/describe.js +85 -0
  70. package/dist/cli/commands/diff.js +175 -0
  71. package/dist/cli/commands/doctor.js +350 -0
  72. package/dist/cli/commands/import-agents.js +155 -0
  73. package/dist/cli/commands/import.js +670 -0
  74. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  75. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  76. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  77. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  78. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  79. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  80. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  81. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  82. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  83. package/dist/cli/commands/init-lexicon.js +172 -0
  84. package/dist/cli/commands/init.js +381 -0
  85. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  86. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  87. package/dist/cli/commands/licenses.js +125 -0
  88. package/dist/cli/commands/lint.d.ts.map +1 -1
  89. package/dist/cli/commands/lint.js +948 -0
  90. package/dist/cli/commands/list.js +63 -0
  91. package/dist/cli/commands/migrate.d.ts.map +1 -1
  92. package/dist/cli/commands/migrate.js +423 -0
  93. package/dist/cli/commands/onboard.js +403 -0
  94. package/dist/cli/commands/pinned-upgrade.js +191 -0
  95. package/dist/cli/commands/update.js +194 -0
  96. package/dist/cli/commands/vendor.js +217 -0
  97. package/dist/cli/conflict-check.js +104 -0
  98. package/dist/cli/debug.js +8 -0
  99. package/dist/cli/format.js +104 -0
  100. package/dist/cli/handlers/build.js +267 -0
  101. package/dist/cli/handlers/carve-apply.js +30 -0
  102. package/dist/cli/handlers/carve-bridge.js +28 -0
  103. package/dist/cli/handlers/carve-emit.js +50 -0
  104. package/dist/cli/handlers/carve-status.js +26 -0
  105. package/dist/cli/handlers/carve.js +40 -0
  106. package/dist/cli/handlers/change-set.js +46 -0
  107. package/dist/cli/handlers/components.js +730 -0
  108. package/dist/cli/handlers/dev.js +113 -0
  109. package/dist/cli/handlers/emulator.js +233 -0
  110. package/dist/cli/handlers/explain.js +54 -0
  111. package/dist/cli/handlers/fan-out.js +422 -0
  112. package/dist/cli/handlers/gate-resume.js +275 -0
  113. package/dist/cli/handlers/generate.js +84 -0
  114. package/dist/cli/handlers/graph.js +969 -0
  115. package/dist/cli/handlers/init.js +102 -0
  116. package/dist/cli/handlers/lifecycle.js +1727 -0
  117. package/dist/cli/handlers/lint.js +62 -0
  118. package/dist/cli/handlers/migrate.js +48 -0
  119. package/dist/cli/handlers/misc.js +391 -0
  120. package/dist/cli/handlers/operator.js +1162 -0
  121. package/dist/cli/handlers/override-approve.js +102 -0
  122. package/dist/cli/handlers/pr.js +503 -0
  123. package/dist/cli/handlers/promote.js +342 -0
  124. package/dist/cli/handlers/run-generate.js +206 -0
  125. package/dist/cli/handlers/run-wave.js +66 -0
  126. package/dist/cli/handlers/run.js +1191 -0
  127. package/dist/cli/handlers/scenario.js +325 -0
  128. package/dist/cli/handlers/search.js +924 -0
  129. package/dist/cli/handlers/serve.js +32 -0
  130. package/dist/cli/handlers/vendor.js +154 -0
  131. package/dist/cli/index.js +3 -0
  132. package/dist/cli/is-entry-point.js +23 -0
  133. package/dist/cli/lsp/capabilities.js +28 -0
  134. package/dist/cli/lsp/diagnostics.js +34 -0
  135. package/dist/cli/lsp/server.js +329 -0
  136. package/dist/cli/main.d.ts.map +1 -1
  137. package/dist/cli/main.js +2540 -0
  138. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  139. package/dist/cli/mcp/op-tools.js +306 -0
  140. package/dist/cli/mcp/resource-handlers.js +258 -0
  141. package/dist/cli/mcp/resources/context.js +85 -0
  142. package/dist/cli/mcp/server.js +353 -0
  143. package/dist/cli/mcp/tools/build.js +111 -0
  144. package/dist/cli/mcp/tools/composites.js +95 -0
  145. package/dist/cli/mcp/tools/explain.js +166 -0
  146. package/dist/cli/mcp/tools/import.js +49 -0
  147. package/dist/cli/mcp/tools/lint.js +41 -0
  148. package/dist/cli/mcp/tools/scaffold.js +111 -0
  149. package/dist/cli/mcp/tools/search.js +112 -0
  150. package/dist/cli/mcp/types.js +1 -0
  151. package/dist/cli/mcp/workspace-plugins.js +91 -0
  152. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  153. package/dist/cli/mcp/workspace-tools.js +564 -0
  154. package/dist/cli/mcp-config.js +70 -0
  155. package/dist/cli/plugins.js +313 -0
  156. package/dist/cli/registry.js +23 -0
  157. package/dist/cli/reporters/stylish.js +329 -0
  158. package/dist/cli/version.js +15 -0
  159. package/dist/cli/watch.js +79 -0
  160. package/dist/codegen/bound-property-types.js +82 -0
  161. package/dist/codegen/coverage.js +119 -0
  162. package/dist/codegen/docs-file-markers.js +60 -0
  163. package/dist/codegen/docs-pages.js +115 -0
  164. package/dist/codegen/docs-rule-scanning.js +164 -0
  165. package/dist/codegen/docs-rules.js +76 -0
  166. package/dist/codegen/docs-sections.js +117 -0
  167. package/dist/codegen/docs-sidebar.js +65 -0
  168. package/dist/codegen/docs-types.js +4 -0
  169. package/dist/codegen/docs.js +356 -0
  170. package/dist/codegen/fetch.js +340 -0
  171. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  172. package/dist/codegen/generate-registry.js +71 -0
  173. package/dist/codegen/generate-runtime-index.js +56 -0
  174. package/dist/codegen/generate-typescript.js +119 -0
  175. package/dist/codegen/generate.js +115 -0
  176. package/dist/codegen/json-patch.js +141 -0
  177. package/dist/codegen/json-schema-to-ts.js +344 -0
  178. package/dist/codegen/json-schema.js +246 -0
  179. package/dist/codegen/lexicon-regen.js +412 -0
  180. package/dist/codegen/naming.js +217 -0
  181. package/dist/codegen/okf-lexicon.js +245 -0
  182. package/dist/codegen/package.js +150 -0
  183. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  184. package/dist/codegen/pinned-upgrade.js +494 -0
  185. package/dist/codegen/registry.js +60 -0
  186. package/dist/codegen/rehype-base-url.mjs +68 -0
  187. package/dist/codegen/rolling-upgrade.js +217 -0
  188. package/dist/codegen/surface-snapshot.js +537 -0
  189. package/dist/codegen/testdata/postgres-tags.json +242 -0
  190. package/dist/codegen/topo-sort.js +36 -0
  191. package/dist/codegen/typecheck.js +115 -0
  192. package/dist/codegen/validate.js +200 -0
  193. package/dist/codegen/version-bump.js +35 -0
  194. package/dist/components/auto-release.js +143 -0
  195. package/dist/components/builders.js +47 -0
  196. package/dist/components/capability-plugin-loader.js +175 -0
  197. package/dist/components/capability-plugin-schema.js +46 -0
  198. package/dist/components/capability-plugin.js +99 -0
  199. package/dist/components/capability.js +60 -0
  200. package/dist/components/changed-files.js +72 -0
  201. package/dist/components/cli-support.js +427 -0
  202. package/dist/components/component.js +120 -0
  203. package/dist/components/component.schema.json +271 -0
  204. package/dist/components/config-defaults.js +130 -0
  205. package/dist/components/deploy-units.js +60 -0
  206. package/dist/components/discover.d.ts.map +1 -1
  207. package/dist/components/discover.js +189 -0
  208. package/dist/components/driver-output.js +86 -0
  209. package/dist/components/driver.js +728 -0
  210. package/dist/components/fan-out-output.js +165 -0
  211. package/dist/components/fan-out-record.js +94 -0
  212. package/dist/components/fan-out-run.js +373 -0
  213. package/dist/components/fan-out-support.js +60 -0
  214. package/dist/components/fan-out.js +273 -0
  215. package/dist/components/gate-plan.js +41 -0
  216. package/dist/components/gated-wave-pipeline.js +46 -0
  217. package/dist/components/index.js +24 -0
  218. package/dist/components/layers.js +28 -0
  219. package/dist/components/manifest-persistence.js +108 -0
  220. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  221. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  222. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  223. package/dist/components/pilots/lambda.pilot.js +52 -0
  224. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  225. package/dist/components/pilots/project.js +12 -0
  226. package/dist/components/pr-pipeline.d.ts.map +1 -1
  227. package/dist/components/pr-pipeline.js +135 -0
  228. package/dist/components/pr-run.js +363 -0
  229. package/dist/components/presets/ecs-fargate.js +72 -0
  230. package/dist/components/presets/index.js +28 -0
  231. package/dist/components/presets/lambda.js +34 -0
  232. package/dist/components/presets/single-host-compose.js +41 -0
  233. package/dist/components/promote.js +549 -0
  234. package/dist/components/registry.js +34 -0
  235. package/dist/components/run-progress.js +33 -0
  236. package/dist/components/sandbox/driver.d.ts.map +1 -1
  237. package/dist/components/sandbox/driver.js +93 -0
  238. package/dist/components/sandbox/run.js +115 -0
  239. package/dist/components/starter-plugin.js +104 -0
  240. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  241. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  242. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  243. package/dist/components/verbs/bom-writer.js +287 -0
  244. package/dist/components/verbs/build-archive.js +188 -0
  245. package/dist/components/verbs/build.js +142 -0
  246. package/dist/components/verbs/cloud-executor.js +110 -0
  247. package/dist/components/verbs/component-bom.js +125 -0
  248. package/dist/components/verbs/ensure-secret.js +50 -0
  249. package/dist/components/verbs/index.js +61 -0
  250. package/dist/components/verbs/license-policy.js +75 -0
  251. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  252. package/dist/components/verbs/process-runner.js +78 -0
  253. package/dist/components/verbs/r2-sync.js +93 -0
  254. package/dist/components/verbs/reproducibility.js +62 -0
  255. package/dist/components/verbs/run-agent.js +577 -0
  256. package/dist/components/verbs/sbom-generator.js +119 -0
  257. package/dist/components/verbs/sbom.js +97 -0
  258. package/dist/components/verbs/shell.js +32 -0
  259. package/dist/components/verbs/sign.js +221 -0
  260. package/dist/components/verbs/stub.js +28 -0
  261. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  262. package/dist/components/verbs/verify.js +112 -0
  263. package/dist/components/verbs/vex.js +131 -0
  264. package/dist/components/verbs/vuln-gate.js +152 -0
  265. package/dist/components/verbs/vuln-scan.js +288 -0
  266. package/dist/components/verbs/wait-verify.js +106 -0
  267. package/dist/components/verbs/wrangler.js +179 -0
  268. package/dist/components/wave-plan.js +102 -0
  269. package/dist/composite.d.ts +10 -2
  270. package/dist/composite.d.ts.map +1 -1
  271. package/dist/composite.js +313 -0
  272. package/dist/config-import.d.ts.map +1 -1
  273. package/dist/config-import.js +22 -0
  274. package/dist/config-sandbox.js +122 -0
  275. package/dist/config-static.js +206 -0
  276. package/dist/config.js +605 -0
  277. package/dist/content-digest.js +23 -0
  278. package/dist/declarable.js +114 -0
  279. package/dist/deep-observation.js +437 -0
  280. package/dist/detectLexicon.js +54 -0
  281. package/dist/discovery/cache.js +70 -0
  282. package/dist/discovery/collect.d.ts +2 -1
  283. package/dist/discovery/collect.d.ts.map +1 -1
  284. package/dist/discovery/collect.js +223 -0
  285. package/dist/discovery/cycles.js +95 -0
  286. package/dist/discovery/entity-wire-codec.js +391 -0
  287. package/dist/discovery/entity-wire.js +42 -0
  288. package/dist/discovery/files.js +121 -0
  289. package/dist/discovery/fold-import.d.ts.map +1 -1
  290. package/dist/discovery/fold-import.js +4073 -0
  291. package/dist/discovery/fold-rank.js +292 -0
  292. package/dist/discovery/graph.js +115 -0
  293. package/dist/discovery/host-composite.d.ts +35 -0
  294. package/dist/discovery/host-composite.d.ts.map +1 -0
  295. package/dist/discovery/host-composite.js +93 -0
  296. package/dist/discovery/import.d.ts.map +1 -1
  297. package/dist/discovery/import.js +65 -0
  298. package/dist/discovery/index.d.ts +9 -0
  299. package/dist/discovery/index.d.ts.map +1 -1
  300. package/dist/discovery/index.js +333 -0
  301. package/dist/discovery/param-deps.js +487 -0
  302. package/dist/discovery/resolve.js +39 -0
  303. package/dist/discovery/sandbox/bundle.js +214 -0
  304. package/dist/discovery/sandbox/child-errors.js +26 -0
  305. package/dist/discovery/sandbox/config-run.js +100 -0
  306. package/dist/discovery/sandbox/config-wire.js +183 -0
  307. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  308. package/dist/discovery/sandbox/driver.js +333 -0
  309. package/dist/discovery/sandbox/fork.js +229 -0
  310. package/dist/discovery/sandbox/policy-run.js +142 -0
  311. package/dist/discovery/sandbox/policy-wire.js +152 -0
  312. package/dist/discovery/sandbox/run.js +77 -0
  313. package/dist/discovery/specifier-resolve.js +320 -0
  314. package/dist/discovery/walk.js +284 -0
  315. package/dist/effect-receipt.js +340 -0
  316. package/dist/env.js +55 -0
  317. package/dist/errors.js +62 -0
  318. package/dist/fold/fold.js +1434 -0
  319. package/dist/fold/foldable-helpers.js +229 -0
  320. package/dist/fold/subset.js +696 -0
  321. package/dist/fold-provenance.js +414 -0
  322. package/dist/gate-decision.d.ts +20 -9
  323. package/dist/gate-decision.d.ts.map +1 -1
  324. package/dist/gate-decision.js +313 -0
  325. package/dist/gate-resume-run.js +154 -0
  326. package/dist/gated-waves.js +165 -0
  327. package/dist/governance.js +43 -0
  328. package/dist/graph-declared.js +45 -0
  329. package/dist/graph-detail.js +207 -0
  330. package/dist/graph-dot.js +54 -0
  331. package/dist/graph-effective.js +107 -0
  332. package/dist/graph-ir.js +623 -0
  333. package/dist/graph-layout.js +184 -0
  334. package/dist/graph-lens.js +105 -0
  335. package/dist/graph-mermaid.js +83 -0
  336. package/dist/graph-ops.js +39 -0
  337. package/dist/graph-refs.js +156 -0
  338. package/dist/held-elsewhere.js +87 -0
  339. package/dist/identity.js +251 -0
  340. package/dist/import/base-parser.js +34 -0
  341. package/dist/import/embedded.js +255 -0
  342. package/dist/import/generator.js +1 -0
  343. package/dist/import/ir-utils.js +85 -0
  344. package/dist/import/parser-options.js +45 -0
  345. package/dist/import/parser.js +1 -0
  346. package/dist/index.js +118 -0
  347. package/dist/intrinsic-interpolation.js +73 -0
  348. package/dist/intrinsic.js +31 -0
  349. package/dist/join-key.js +35 -0
  350. package/dist/kubectl-context.js +150 -0
  351. package/dist/lexicon-config.js +58 -0
  352. package/dist/lexicon-integrity.js +47 -0
  353. package/dist/lexicon-manifest.js +70 -0
  354. package/dist/lexicon-module.d.ts.map +1 -1
  355. package/dist/lexicon-module.js +241 -0
  356. package/dist/lexicon-output.js +218 -0
  357. package/dist/lexicon-plugin-helpers.js +104 -0
  358. package/dist/lexicon-schema.js +136 -0
  359. package/dist/lexicon.js +130 -0
  360. package/dist/lifecycle/affected.js +224 -0
  361. package/dist/lifecycle/assert-live.js +110 -0
  362. package/dist/lifecycle/build-ledger-store.js +160 -0
  363. package/dist/lifecycle/build-ledger.js +155 -0
  364. package/dist/lifecycle/change-set.js +356 -0
  365. package/dist/lifecycle/converge-ledger.js +161 -0
  366. package/dist/lifecycle/deep-diff.js +233 -0
  367. package/dist/lifecycle/deep-observe.js +147 -0
  368. package/dist/lifecycle/digest.js +143 -0
  369. package/dist/lifecycle/disruption.js +135 -0
  370. package/dist/lifecycle/gate-applied.js +113 -0
  371. package/dist/lifecycle/gate-audit.d.ts +69 -2
  372. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  373. package/dist/lifecycle/gate-audit.js +415 -0
  374. package/dist/lifecycle/gate-ledger-select.js +89 -0
  375. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  376. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  377. package/dist/lifecycle/gate-ledger.js +201 -0
  378. package/dist/lifecycle/gate-origin.js +117 -0
  379. package/dist/lifecycle/gate-pending.js +58 -0
  380. package/dist/lifecycle/git.js +1182 -0
  381. package/dist/lifecycle/identity.js +58 -0
  382. package/dist/lifecycle/index.js +26 -0
  383. package/dist/lifecycle/lease.js +283 -0
  384. package/dist/lifecycle/ledger-port.js +1 -0
  385. package/dist/lifecycle/legacy-digest.js +91 -0
  386. package/dist/lifecycle/live-diff.js +274 -0
  387. package/dist/lifecycle/local-note.js +15 -0
  388. package/dist/lifecycle/member-ledger.js +122 -0
  389. package/dist/lifecycle/observation-baseline.js +155 -0
  390. package/dist/lifecycle/observe.js +375 -0
  391. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  392. package/dist/lifecycle/override-ledger.js +54 -0
  393. package/dist/lifecycle/plan-digest.js +110 -0
  394. package/dist/lifecycle/plan-ledger.js +129 -0
  395. package/dist/lifecycle/receipt-plan.js +192 -0
  396. package/dist/lifecycle/release-ledger.js +155 -0
  397. package/dist/lifecycle/release-read.js +64 -0
  398. package/dist/lifecycle/replay.js +193 -0
  399. package/dist/lifecycle/rollback.js +102 -0
  400. package/dist/lifecycle/run-ledger.js +183 -0
  401. package/dist/lifecycle/scenario-eval.js +204 -0
  402. package/dist/lifecycle/scenario.js +214 -0
  403. package/dist/lifecycle/snapshot.js +239 -0
  404. package/dist/lifecycle/status.js +447 -0
  405. package/dist/lifecycle/symptoms.js +117 -0
  406. package/dist/lifecycle/teardown.js +255 -0
  407. package/dist/lifecycle/types.js +1 -0
  408. package/dist/lifecycle/unobserved-gate.js +33 -0
  409. package/dist/lifecycle/wave-plans.js +68 -0
  410. package/dist/lifecycle/work-item-id.js +36 -0
  411. package/dist/lifecycle/work-lease.js +393 -0
  412. package/dist/lint/component-checks.js +87 -0
  413. package/dist/lint/config.js +483 -0
  414. package/dist/lint/declarative.js +122 -0
  415. package/dist/lint/discover.d.ts.map +1 -1
  416. package/dist/lint/discover.js +138 -0
  417. package/dist/lint/engine.js +246 -0
  418. package/dist/lint/knowledge-checks.js +64 -0
  419. package/dist/lint/named-checks.js +22 -0
  420. package/dist/lint/output-checks.js +87 -0
  421. package/dist/lint/output-docs.js +163 -0
  422. package/dist/lint/parser.js +33 -0
  423. package/dist/lint/pipeline-change-gate.js +70 -0
  424. package/dist/lint/policy-import.d.ts +1 -1
  425. package/dist/lint/policy-import.d.ts.map +1 -1
  426. package/dist/lint/policy-import.js +62 -0
  427. package/dist/lint/policy-sandbox.js +86 -0
  428. package/dist/lint/policy.js +122 -0
  429. package/dist/lint/post-synth.js +87 -0
  430. package/dist/lint/presets/relaxed.json +26 -0
  431. package/dist/lint/presets/strict.json +24 -24
  432. package/dist/lint/receipt-checks.js +83 -0
  433. package/dist/lint/rule-loader.d.ts.map +1 -1
  434. package/dist/lint/rule-loader.js +89 -0
  435. package/dist/lint/rule-registry.js +86 -0
  436. package/dist/lint/rule.js +1 -0
  437. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  438. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  439. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  440. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  441. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  442. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  443. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  444. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  445. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  446. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  447. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  448. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  449. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  450. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  451. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  452. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  453. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  454. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  455. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  456. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  457. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  458. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  459. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  460. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  461. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  462. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  463. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  464. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  465. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  466. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  467. package/dist/lint/rules/comp/index.js +33 -0
  468. package/dist/lint/rules/comp/support.js +129 -0
  469. package/dist/lint/rules/composite-scope.js +51 -0
  470. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  471. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  472. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  473. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  474. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  475. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  476. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  477. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  478. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  479. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  480. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  481. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  482. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  483. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  484. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  485. package/dist/lint/rules/export-required.js +115 -0
  486. package/dist/lint/rules/file-declarable-limit.js +88 -0
  487. package/dist/lint/rules/flat-declarations.js +62 -0
  488. package/dist/lint/rules/index.js +90 -0
  489. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  490. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  491. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  492. package/dist/lint/rules/no-string-ref.js +59 -0
  493. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  494. package/dist/lint/rules/no-unused-declarable.js +142 -0
  495. package/dist/lint/rules/op/index.js +28 -0
  496. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  497. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  498. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  499. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  500. package/dist/lint/rules/op/support.js +11 -0
  501. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  502. package/dist/lint/rules/property-kind.js +56 -0
  503. package/dist/lint/rules/single-concern-file.js +71 -0
  504. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  505. package/dist/lint/selectors.js +168 -0
  506. package/dist/lint/suppressions.js +129 -0
  507. package/dist/lint/typescript.js +17 -0
  508. package/dist/live-endpoint.js +113 -0
  509. package/dist/live-read-session.js +76 -0
  510. package/dist/lsp/lexicon-providers.js +163 -0
  511. package/dist/lsp/types.js +5 -0
  512. package/dist/managed-fields.js +293 -0
  513. package/dist/mcp/types.js +4 -0
  514. package/dist/notify-verify.js +0 -1
  515. package/dist/notify.js +0 -1
  516. package/dist/notify.schema.json +72 -0
  517. package/dist/observation.js +266 -0
  518. package/dist/okf-read.js +134 -0
  519. package/dist/okf.js +275 -0
  520. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  521. package/dist/op/activities/activity-contracts.d.ts +1 -1
  522. package/dist/op/activities/activity-contracts.js +313 -0
  523. package/dist/op/activities/apply.js +582 -0
  524. package/dist/op/activities/build.js +18 -0
  525. package/dist/op/activities/change-coverage.js +40 -0
  526. package/dist/op/activities/change-set.d.ts +8 -3
  527. package/dist/op/activities/change-set.d.ts.map +1 -1
  528. package/dist/op/activities/change-set.js +67 -0
  529. package/dist/op/activities/converge.js +492 -0
  530. package/dist/op/activities/decide.js +211 -0
  531. package/dist/op/activities/env-teardown.js +110 -0
  532. package/dist/op/activities/factory.js +591 -0
  533. package/dist/op/activities/forge-pull-request.js +82 -0
  534. package/dist/op/activities/gh-host.js +78 -0
  535. package/dist/op/activities/guard-validate.js +165 -0
  536. package/dist/op/activities/http-check.js +40 -0
  537. package/dist/op/activities/index.js +59 -0
  538. package/dist/op/activities/lexicon-upgrade.js +629 -0
  539. package/dist/op/activities/lifecycle.js +109 -0
  540. package/dist/op/activities/pipeline-audit.js +147 -0
  541. package/dist/op/activities/policy.js +26 -0
  542. package/dist/op/activities/predict-behaviour.js +341 -0
  543. package/dist/op/activities/propose-upgrade.js +165 -0
  544. package/dist/op/activities/reconcile.d.ts +28 -16
  545. package/dist/op/activities/reconcile.d.ts.map +1 -1
  546. package/dist/op/activities/reconcile.js +1011 -0
  547. package/dist/op/activities/shell.js +118 -0
  548. package/dist/op/activities/source-release.js +97 -0
  549. package/dist/op/activities/source-rollback.js +165 -0
  550. package/dist/op/activities/teardown.js +17 -0
  551. package/dist/op/activities/util.js +6 -0
  552. package/dist/op/activities/wait.js +51 -0
  553. package/dist/op/activities/work-evidence.js +35 -0
  554. package/dist/op/activities/workflow-audit.js +133 -0
  555. package/dist/op/activity-contract-registry.js +106 -0
  556. package/dist/op/activity-contract.js +232 -0
  557. package/dist/op/activity-failure.js +31 -0
  558. package/dist/op/activity-profiles.js +129 -0
  559. package/dist/op/activity-registry.js +105 -0
  560. package/dist/op/activity-runtime.js +28 -0
  561. package/dist/op/builders.d.ts +1 -1
  562. package/dist/op/builders.d.ts.map +1 -1
  563. package/dist/op/builders.js +801 -0
  564. package/dist/op/change-signal.js +118 -0
  565. package/dist/op/composites/apply-op.js +151 -0
  566. package/dist/op/composites/behaviour-op.js +65 -0
  567. package/dist/op/composites/converge-op.js +141 -0
  568. package/dist/op/composites/index.js +19 -0
  569. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  570. package/dist/op/composites/pipeline-audit-op.js +50 -0
  571. package/dist/op/composites/reconcile-op.js +84 -0
  572. package/dist/op/composites/watch-op.js +69 -0
  573. package/dist/op/composites/workflow-audit-op.js +57 -0
  574. package/dist/op/converge-rule.js +205 -0
  575. package/dist/op/cron.js +146 -0
  576. package/dist/op/decide-backend.js +181 -0
  577. package/dist/op/decide-config.js +68 -0
  578. package/dist/op/decide-read-inputs.js +141 -0
  579. package/dist/op/discover.d.ts.map +1 -1
  580. package/dist/op/discover.js +213 -0
  581. package/dist/op/duration.js +21 -0
  582. package/dist/op/emulator-freshness.js +80 -0
  583. package/dist/op/emulator-lifecycle.js +269 -0
  584. package/dist/op/factory.js +83 -0
  585. package/dist/op/gate-approval.js +172 -0
  586. package/dist/op/gate-name.js +86 -0
  587. package/dist/op/gate-plan-context.js +167 -0
  588. package/dist/op/gate-point-run.js +67 -0
  589. package/dist/op/gate-point.js +111 -0
  590. package/dist/op/gate-resume-plan.js +155 -0
  591. package/dist/op/gate-resume.js +31 -0
  592. package/dist/op/gate-review.js +290 -0
  593. package/dist/op/gate-run-locator.js +85 -0
  594. package/dist/op/gate-summary.js +100 -0
  595. package/dist/op/gate-wait.js +56 -0
  596. package/dist/op/gate.js +449 -0
  597. package/dist/op/generate-pipeline.js +124 -0
  598. package/dist/op/index.js +51 -0
  599. package/dist/op/lifecycle-receipt-store.js +45 -0
  600. package/dist/op/local-executor.js +1164 -0
  601. package/dist/op/local-output.js +140 -0
  602. package/dist/op/op-ir.js +317 -0
  603. package/dist/op/op-verb-class.js +131 -0
  604. package/dist/op/op-waves-notify.js +136 -0
  605. package/dist/op/op-waves-run.d.ts +1 -1
  606. package/dist/op/op-waves-run.d.ts.map +1 -1
  607. package/dist/op/op-waves-run.js +1657 -0
  608. package/dist/op/op-waves.js +409 -0
  609. package/dist/op/operator.js +743 -0
  610. package/dist/op/receipt-store.js +94 -0
  611. package/dist/op/resource.js +23 -0
  612. package/dist/op/run-context.js +28 -0
  613. package/dist/op/run-live.js +256 -0
  614. package/dist/op/runtime.js +21 -0
  615. package/dist/op/runtimes/local.js +195 -0
  616. package/dist/op/source-archive.js +131 -0
  617. package/dist/op/step-output-ref.js +397 -0
  618. package/dist/op/steward-beside.js +169 -0
  619. package/dist/op/steward-points.js +138 -0
  620. package/dist/op/steward-turn.js +59 -0
  621. package/dist/op/steward.js +317 -0
  622. package/dist/op/types.js +25 -0
  623. package/dist/op/work-lease-decl.js +81 -0
  624. package/dist/op/work-lease-run.js +393 -0
  625. package/dist/override-gate.js +284 -0
  626. package/dist/owner-chain.js +74 -0
  627. package/dist/ownership.js +92 -0
  628. package/dist/params.js +61 -0
  629. package/dist/plan-summary.js +711 -0
  630. package/dist/point-decision.js +211 -0
  631. package/dist/pr-forge.js +810 -0
  632. package/dist/pr-loop.js +203 -0
  633. package/dist/project-codegen.js +191 -0
  634. package/dist/project-root.js +74 -0
  635. package/dist/project-validation.js +50 -0
  636. package/dist/provenance.js +157 -0
  637. package/dist/pseudo-parameter.js +40 -0
  638. package/dist/reconcile.js +450 -0
  639. package/dist/resource-attributes.js +33 -0
  640. package/dist/runtime-adapter.js +77 -0
  641. package/dist/runtime.js +66 -0
  642. package/dist/secret-materialization.js +153 -0
  643. package/dist/secret-provenance.js +250 -0
  644. package/dist/serializer-walker.js +112 -0
  645. package/dist/serializer.js +1 -0
  646. package/dist/sort.js +45 -0
  647. package/dist/stack-output.js +115 -0
  648. package/dist/telemetry-attribution.js +149 -0
  649. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  650. package/dist/terraform/adopt-state.js +40 -0
  651. package/dist/terraform/aws-resources.js +384 -0
  652. package/dist/terraform/bridge.js +204 -0
  653. package/dist/terraform/carve-provider.js +155 -0
  654. package/dist/terraform/carve.js +138 -0
  655. package/dist/terraform/data-source-shape.js +57 -0
  656. package/dist/terraform/emit-source.js +36 -0
  657. package/dist/terraform/excise.js +123 -0
  658. package/dist/terraform/graduate.js +128 -0
  659. package/dist/terraform/graph.js +435 -0
  660. package/dist/terraform/hcl2json.js +60 -0
  661. package/dist/terraform/manifest.js +149 -0
  662. package/dist/terraform/parse.js +81 -0
  663. package/dist/terraform/providers/aws.js +146 -0
  664. package/dist/terraform/providers/gcp.js +294 -0
  665. package/dist/terraform/providers/index.js +17 -0
  666. package/dist/terraform/providers/kubernetes.js +253 -0
  667. package/dist/terraform/score.js +151 -0
  668. package/dist/terraform/state.js +67 -0
  669. package/dist/terraform/tier-map.js +74 -0
  670. package/dist/terraform/types.js +13 -0
  671. package/dist/terraform/unified-diff.js +132 -0
  672. package/dist/testing.js +295 -0
  673. package/dist/toml-emit.js +153 -0
  674. package/dist/toml-parse.js +355 -0
  675. package/dist/toml-utils.js +60 -0
  676. package/dist/toml.js +428 -0
  677. package/dist/ts-loader.d.ts +46 -0
  678. package/dist/ts-loader.d.ts.map +1 -0
  679. package/dist/ts-loader.js +103 -0
  680. package/dist/types.js +1 -0
  681. package/dist/utils.js +61 -0
  682. package/dist/validation.js +20 -0
  683. package/dist/vendor/js-yaml-load.js +1662 -0
  684. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  685. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  686. package/dist/workspace/__fixtures__/contract-repo.js +119 -0
  687. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  688. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  689. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  690. package/dist/workspace/__fixtures__/sessions.js +91 -0
  691. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  692. package/dist/workspace/agent-cli.js +99 -0
  693. package/dist/workspace/agent.schema.json +390 -0
  694. package/dist/workspace/answers-ledger.js +89 -0
  695. package/dist/workspace/box-cli.js +142 -0
  696. package/dist/workspace/box-factory-write.js +196 -0
  697. package/dist/workspace/box-factory-write.schema.json +342 -0
  698. package/dist/workspace/box-factory.js +88 -0
  699. package/dist/workspace/box-intent.js +126 -0
  700. package/dist/workspace/box-isolation.js +169 -0
  701. package/dist/workspace/box-listing-write.schema.json +204 -0
  702. package/dist/workspace/box-listing.js +298 -0
  703. package/dist/workspace/box-publish.js +352 -0
  704. package/dist/workspace/box-publish.schema.json +498 -0
  705. package/dist/workspace/box-services.js +39 -0
  706. package/dist/workspace/broker-protocol.js +359 -0
  707. package/dist/workspace/broker-protocol.schema.json +228 -0
  708. package/dist/workspace/change-set.schema.json +462 -0
  709. package/dist/workspace/changes-cli.js +56 -0
  710. package/dist/workspace/changes.js +446 -0
  711. package/dist/workspace/changes.schema.json +642 -0
  712. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  713. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  714. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  715. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  716. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  717. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  718. package/dist/workspace/chant-migrations.js +113 -0
  719. package/dist/workspace/check.schema.json +433 -0
  720. package/dist/workspace/checks/box-isolation.js +62 -0
  721. package/dist/workspace/checks/boxes.js +491 -0
  722. package/dist/workspace/checks/diagrams.js +104 -0
  723. package/dist/workspace/checks/generated.js +419 -0
  724. package/dist/workspace/checks/ledgers.js +212 -0
  725. package/dist/workspace/checks/links.js +191 -0
  726. package/dist/workspace/checks/live.js +100 -0
  727. package/dist/workspace/checks/pipelines.js +188 -0
  728. package/dist/workspace/checks/records.js +169 -0
  729. package/dist/workspace/checks.js +426 -0
  730. package/dist/workspace/ci-green-cli.js +125 -0
  731. package/dist/workspace/ci-green-forge.js +83 -0
  732. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  733. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  734. package/dist/workspace/ci-green-workflow.js +202 -0
  735. package/dist/workspace/ci-green.js +325 -0
  736. package/dist/workspace/ci-last-green.schema.json +158 -0
  737. package/dist/workspace/class-data.d.ts +84 -0
  738. package/dist/workspace/class-data.d.ts.map +1 -0
  739. package/dist/workspace/class-data.js +238 -0
  740. package/dist/workspace/compose-graph.js +235 -0
  741. package/dist/workspace/compose-reports.js +146 -0
  742. package/dist/workspace/composites.js +264 -0
  743. package/dist/workspace/composites.schema.json +617 -0
  744. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  745. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  746. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  747. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  748. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  749. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  750. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  751. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  752. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  753. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  754. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  755. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  756. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  757. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  758. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  759. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  760. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  761. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  762. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  763. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  764. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  765. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  766. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  767. package/dist/workspace/conformance/broker.js +736 -0
  768. package/dist/workspace/conformance/index.js +673 -0
  769. package/dist/workspace/conformance/vitest.js +108 -0
  770. package/dist/workspace/conformance/writer.js +1080 -0
  771. package/dist/workspace/decide.js +879 -0
  772. package/dist/workspace/decision-points.schema.json +135 -135
  773. package/dist/workspace/declaration-write-cli.js +74 -0
  774. package/dist/workspace/declaration-write.js +238 -0
  775. package/dist/workspace/declaration.js +938 -0
  776. package/dist/workspace/declaration.schema.json +1413 -1413
  777. package/dist/workspace/declared-kinds.js +106 -0
  778. package/dist/workspace/environments.js +164 -0
  779. package/dist/workspace/evidence.schema.json +279 -0
  780. package/dist/workspace/export-cli.js +185 -0
  781. package/dist/workspace/export.js +499 -0
  782. package/dist/workspace/factory-rules.js +175 -0
  783. package/dist/workspace/generated-files.js +92 -0
  784. package/dist/workspace/glob.js +45 -0
  785. package/dist/workspace/graph-cache.js +352 -0
  786. package/dist/workspace/graph-cli.js +348 -0
  787. package/dist/workspace/graph.schema.json +1338 -0
  788. package/dist/workspace/identity-gate-rule.js +75 -0
  789. package/dist/workspace/identity.js +126 -0
  790. package/dist/workspace/import.js +283 -0
  791. package/dist/workspace/init.js +620 -0
  792. package/dist/workspace/intent-cli.js +263 -0
  793. package/dist/workspace/intent-joins.js +201 -0
  794. package/dist/workspace/intent-record.js +352 -0
  795. package/dist/workspace/intent-record.schema.json +864 -0
  796. package/dist/workspace/intent.js +1582 -0
  797. package/dist/workspace/intent.schema.json +2420 -0
  798. package/dist/workspace/json-edit.js +221 -0
  799. package/dist/workspace/jsonc.js +235 -0
  800. package/dist/workspace/kind-readers.js +103 -0
  801. package/dist/workspace/kinds.d.ts +3 -7
  802. package/dist/workspace/kinds.d.ts.map +1 -1
  803. package/dist/workspace/kinds.js +553 -0
  804. package/dist/workspace/lexicon-symbols.js +112 -0
  805. package/dist/workspace/lineage-adopt-cli.js +102 -0
  806. package/dist/workspace/lineage-adopt.js +360 -0
  807. package/dist/workspace/lineage-check.js +282 -0
  808. package/dist/workspace/lineage-cli.js +121 -0
  809. package/dist/workspace/lineage-hash-index.js +284 -0
  810. package/dist/workspace/lineage-init.js +507 -0
  811. package/dist/workspace/lineage-lock.js +387 -0
  812. package/dist/workspace/lineage-merge.js +45 -0
  813. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  814. package/dist/workspace/lineage-migrations.js +329 -0
  815. package/dist/workspace/lineage-provenance.js +36 -0
  816. package/dist/workspace/lineage-update.js +138 -0
  817. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  818. package/dist/workspace/lineage-upgrade.js +889 -0
  819. package/dist/workspace/lineage-vendor.js +171 -0
  820. package/dist/workspace/lineage-version.js +128 -0
  821. package/dist/workspace/lineage-versions.js +275 -0
  822. package/dist/workspace/links.js +330 -0
  823. package/dist/workspace/ls-generated.js +95 -0
  824. package/dist/workspace/ls.js +277 -0
  825. package/dist/workspace/ls.schema.json +333 -0
  826. package/dist/workspace/member-commands.js +508 -0
  827. package/dist/workspace/member-handles.js +216 -0
  828. package/dist/workspace/member-pipeline.js +258 -0
  829. package/dist/workspace/member-run.js +117 -0
  830. package/dist/workspace/member-write.schema.json +193 -0
  831. package/dist/workspace/nested-graph.js +156 -0
  832. package/dist/workspace/nesting.js +38 -0
  833. package/dist/workspace/patch.js +301 -0
  834. package/dist/workspace/patch.schema.json +321 -0
  835. package/dist/workspace/pin-cli.js +55 -0
  836. package/dist/workspace/pin-integrity.js +100 -0
  837. package/dist/workspace/plan-summary.schema.json +155 -0
  838. package/dist/workspace/point-answer.schema.json +129 -129
  839. package/dist/workspace/points-cli.js +291 -0
  840. package/dist/workspace/points-write.schema.json +660 -0
  841. package/dist/workspace/points.js +521 -0
  842. package/dist/workspace/points.schema.json +916 -0
  843. package/dist/workspace/pr-report.schema.json +108 -0
  844. package/dist/workspace/principal-classes.d.ts +4 -11
  845. package/dist/workspace/principal-classes.d.ts.map +1 -1
  846. package/dist/workspace/principal-classes.js +162 -0
  847. package/dist/workspace/reason-codes.js +326 -0
  848. package/dist/workspace/record-assets.js +151 -0
  849. package/dist/workspace/record-decided.js +105 -0
  850. package/dist/workspace/record-sessions.js +96 -0
  851. package/dist/workspace/record-source.js +114 -0
  852. package/dist/workspace/records-amend.schema.json +284 -0
  853. package/dist/workspace/records-checkout.js +76 -0
  854. package/dist/workspace/records-cli.js +707 -0
  855. package/dist/workspace/records-close.js +116 -0
  856. package/dist/workspace/records-close.schema.json +278 -0
  857. package/dist/workspace/records-new.schema.json +202 -0
  858. package/dist/workspace/records-review.schema.json +367 -0
  859. package/dist/workspace/records-since.js +317 -0
  860. package/dist/workspace/records-since.schema.json +218 -0
  861. package/dist/workspace/records-write.js +1229 -0
  862. package/dist/workspace/records.js +1345 -0
  863. package/dist/workspace/records.schema.json +604 -0
  864. package/dist/workspace/reference-kinds/app/package.json +10 -0
  865. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  866. package/dist/workspace/returns.js +303 -0
  867. package/dist/workspace/root-refusal.js +75 -0
  868. package/dist/workspace/run-statement.schema.json +622 -0
  869. package/dist/workspace/runs-cli.js +584 -0
  870. package/dist/workspace/runs-write.schema.json +994 -0
  871. package/dist/workspace/runs.js +708 -0
  872. package/dist/workspace/runs.schema.json +1257 -0
  873. package/dist/workspace/runtimes.js +76 -0
  874. package/dist/workspace/session-kinds.js +83 -0
  875. package/dist/workspace/signers.schema.json +206 -0
  876. package/dist/workspace/source-block.js +125 -0
  877. package/dist/workspace/squash.js +177 -0
  878. package/dist/workspace/status-gates.js +162 -0
  879. package/dist/workspace/status-stewards.js +330 -0
  880. package/dist/workspace/status.js +514 -0
  881. package/dist/workspace/status.schema.json +998 -0
  882. package/dist/workspace/steward-index.js +191 -0
  883. package/dist/workspace/symbols.js +188 -0
  884. package/dist/workspace/template-manifest.js +227 -0
  885. package/dist/workspace/template-pins.js +115 -0
  886. package/dist/workspace/trailer-joins.js +65 -0
  887. package/dist/workspace/trailers.js +111 -0
  888. package/dist/workspace/tree.js +136 -0
  889. package/dist/workspace/trust/attestor.js +63 -0
  890. package/dist/workspace/trust/dsse.js +108 -0
  891. package/dist/workspace/trust/evidence-cli.js +175 -0
  892. package/dist/workspace/trust/evidence.js +161 -0
  893. package/dist/workspace/trust/gate-seal.js +226 -0
  894. package/dist/workspace/trust/policy.js +190 -0
  895. package/dist/workspace/trust/provenance.js +180 -0
  896. package/dist/workspace/trust/rotation.js +298 -0
  897. package/dist/workspace/trust/run-commits.js +101 -0
  898. package/dist/workspace/trust/run-statement.js +182 -0
  899. package/dist/workspace/trust/seal.js +272 -0
  900. package/dist/workspace/trust/signers-cli.js +152 -0
  901. package/dist/workspace/trust/ssh-commit.js +178 -0
  902. package/dist/workspace/trust/test-repo.js +118 -0
  903. package/dist/workspace/trust/trust-files.js +193 -0
  904. package/dist/workspace/trust/verify-cli.js +63 -0
  905. package/dist/workspace/trust/verify.js +192 -0
  906. package/dist/workspace/which-chant.js +141 -0
  907. package/dist/workspace/wip-cli.js +95 -0
  908. package/dist/workspace/wip-write.schema.json +504 -0
  909. package/dist/workspace/wip.js +631 -0
  910. package/dist/workspace/wip.schema.json +334 -0
  911. package/dist/workspace/work-cli.js +448 -0
  912. package/dist/workspace/work-evidence.js +122 -0
  913. package/dist/workspace/work-evidence.schema.json +222 -0
  914. package/dist/workspace/work-history.schema.json +421 -0
  915. package/dist/workspace/work-lease.schema.json +246 -0
  916. package/dist/workspace/work-size.js +77 -0
  917. package/dist/workspace/work.js +283 -0
  918. package/dist/workspace/workspace-kinds.schema.json +259 -259
  919. package/dist/workspace/workspace-principals.schema.json +53 -53
  920. package/dist/workspace/write-lock-cli.js +105 -0
  921. package/dist/workspace/write-lock.js +424 -0
  922. package/dist/workspace/write-lock.schema.json +172 -0
  923. package/dist/workspace/write-scope.js +484 -0
  924. package/dist/yaml.js +459 -0
  925. package/package.json +2 -2
  926. package/src/build.ts +8 -0
  927. package/src/change-set.test.ts +48 -0
  928. package/src/change-set.ts +52 -2
  929. package/src/ci-jobs.test.ts +49 -0
  930. package/src/ci-jobs.ts +515 -0
  931. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  932. package/src/cli/commands/lint.ts +3 -0
  933. package/src/cli/commands/migrate.ts +2 -0
  934. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  935. package/src/cli/main.ts +121 -27
  936. package/src/cli/mcp/workspace-tools.ts +1 -1
  937. package/src/codegen/pinned-upgrade.ts +2 -0
  938. package/src/components/discover.ts +2 -0
  939. package/src/components/pr-pipeline.ts +3 -2
  940. package/src/components/sandbox/driver.ts +3 -1
  941. package/src/composite.ts +15 -3
  942. package/src/config-import.ts +3 -0
  943. package/src/discovery/collect.ts +15 -1
  944. package/src/discovery/fold-build-parity.test.ts +71 -0
  945. package/src/discovery/fold-import.ts +25 -127
  946. package/src/discovery/fold-project-provenance.test.ts +21 -4
  947. package/src/discovery/host-composite.ts +101 -0
  948. package/src/discovery/import.ts +2 -0
  949. package/src/discovery/index.ts +11 -1
  950. package/src/discovery/sandbox/driver.ts +3 -1
  951. package/src/gate-decision-bundle.test.ts +5 -4
  952. package/src/gate-decision-identity.test.ts +59 -6
  953. package/src/gate-decision.ts +81 -21
  954. package/src/lexicon-module.ts +3 -0
  955. package/src/lifecycle/gate-audit.test.ts +55 -1
  956. package/src/lifecycle/gate-audit.ts +116 -3
  957. package/src/lifecycle/gate-ledger.test.ts +20 -1
  958. package/src/lifecycle/gate-ledger.ts +33 -1
  959. package/src/lint/config.ts +1 -1
  960. package/src/lint/discover.ts +28 -8
  961. package/src/lint/policy-import.ts +4 -1
  962. package/src/lint/rule-loader.ts +2 -0
  963. package/src/op/activities/activity-contracts.ts +1 -1
  964. package/src/op/activities/change-set.ts +11 -3
  965. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  966. package/src/op/activities/reconcile.test.ts +62 -2
  967. package/src/op/activities/reconcile.ts +46 -22
  968. package/src/op/builders.ts +1 -1
  969. package/src/op/discover.ts +3 -0
  970. package/src/op/op-waves-run.ts +4 -3
  971. package/src/op/op-waves.test.ts +2 -0
  972. package/src/op/work-lease-run.test.ts +12 -4
  973. package/src/ts-loader.test.ts +23 -0
  974. package/src/ts-loader.ts +122 -0
  975. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  976. package/src/workspace/__fixtures__/sessions.ts +3 -3
  977. package/src/workspace/change-set.schema.json +5 -3
  978. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  979. package/src/workspace/ci-green-workflow.test.ts +4 -4
  980. package/src/workspace/ci-green-workflow.ts +5 -2
  981. package/src/workspace/class-data.test.ts +64 -0
  982. package/src/workspace/class-data.ts +236 -0
  983. package/src/workspace/declaration.ts +1 -1
  984. package/src/workspace/kinds.ts +7 -36
  985. package/src/workspace/lineage-migrations.ts +2 -0
  986. package/src/workspace/points.ts +1 -1
  987. package/src/workspace/principal-classes.ts +12 -95
  988. package/src/workspace/records-formats.test.ts +7 -2
  989. package/src/workspace/records-sessions-write.test.ts +3 -3
  990. package/src/workspace/status-contract.e2e.test.ts +37 -0
  991. package/src/workspace/status-contract.test.ts +23 -21
@@ -0,0 +1,1325 @@
1
+ /**
2
+ * The behaviour prediction contract (#2356) — what a lexicon's
3
+ * `predictBehaviour()` is allowed to mean.
4
+ *
5
+ * `describeResources()` (./observation.ts) answers whether a declared entity
6
+ * exists. `observeResourcesDeep()` (./deep-observation.ts) answers what its
7
+ * properties currently are. Both report facts a substrate was asked for. This
8
+ * third axis reports something no substrate holds: what the declared estate
9
+ * would *do* at a stated traffic level — cost per hour, how far each entity is
10
+ * from saturation, the error rate to expect, whether it survives a named
11
+ * failure, and a smaller size that would still carry the load.
12
+ *
13
+ * None of that is a measurement, and the type is built so it cannot be read as
14
+ * one.
15
+ *
16
+ * ## A prediction is not a bill
17
+ *
18
+ * The one failure mode that matters here is somebody quoting a modeled figure
19
+ * as money owed. Four things in this module work against that, and it is worth
20
+ * being exact about how much they buy, because an overstated guarantee is how
21
+ * a consumer ends up trusting one:
22
+ *
23
+ * 1. Money appears in exactly one shape, {@link PredictedRate}, and that
24
+ * shape carries a literal `rate: "per-hour"` discriminant. There is no
25
+ * field anywhere for an amount, a period, an account, an invoice or a
26
+ * due date, so an elapsed charge is not expressible *in this module's
27
+ * types*.
28
+ * 2. A figure cannot exist without {@link PredictedBehaviour.at}, the traffic
29
+ * level it was predicted for. A bill is for an hour that happened; this is
30
+ * for an hour the engine was asked to imagine.
31
+ * 3. {@link BehaviourProvenance} is required on every entity, and its
32
+ * {@link BehaviourProvenance.basis} is a closed two-value enum: `modeled`
33
+ * off list prices, or `validated` against a real bill. A figure that will
34
+ * not say which of the two it is cannot be constructed.
35
+ * 4. A refusal is a separate member of the {@link BehaviourResult} union with
36
+ * no figures on it at all, so "the engine is gone" and "the engine says
37
+ * zero" are different objects rather than the same object with zeroes in
38
+ * it. This one is airtight: there is no `entities` key on the refusal arm
39
+ * to be empty and no total to be zero.
40
+ *
41
+ * **What this does not do is make a bill a type error.** A consumer holding a
42
+ * {@link PredictedBehaviour} can write
43
+ * `const { rate, ...rest } = cost; return { amount: rest.perHour * hours }`
44
+ * and TypeScript will not object; the discriminant is a field on an object, and
45
+ * a field can be dropped. behold's own validator rebuilds `cost` as
46
+ * `{ perHour, currency }` and its estate sum carries no discriminant at all, so
47
+ * the marker is stripped by the first consumer *by design*. What the shape
48
+ * genuinely buys is that a bill cannot be constructed **accidentally** — every
49
+ * route from a prediction to something that reads as money owed has a
50
+ * deliberate destructure or cast in it, and shows up in review as one. Treat
51
+ * rules 1 to 3 as a speed bump with a name, and rule 4 as the enforced one.
52
+ *
53
+ * ## The engine sees no credential and writes nothing
54
+ *
55
+ * {@link PredictBehaviourOptions} mirrors `observeResourcesDeep`'s options
56
+ * field for field, plus `traffic`, `edges` and `edgeCoverage`, and then works
57
+ * against credentials on two levels. The type declares every obvious name
58
+ * `?: never`, which catches the deliberate attempt; and
59
+ * {@link screenBehaviourRequest} walks the whole request at runtime, matching
60
+ * credential-shaped **values** as well as credential-shaped keys, which is what
61
+ * catches the accident.
62
+ *
63
+ * `screenBehaviourRequest` is the one entry point — its own doc comment carries
64
+ * the call sequence, and nothing else here restates it. The runtime walk is the
65
+ * load-bearing half, because the type cannot see the two channels that actually
66
+ * carry a secret in practice: `entities[*].props` is `Record<string, unknown>`
67
+ * straight out of the build, and a lexicon surfacing a connection string puts
68
+ * one there without deciding to. See {@link assertNoCredentialInOptions} for
69
+ * exactly what the walk detects and, more importantly, what it does not.
70
+ *
71
+ * `edges` is the one place the options mirror breaks, and the field's own doc
72
+ * says why: the epic's input is a resource graph, a deep read has no use for
73
+ * neighbours, and a prediction is nothing but statements about paths through
74
+ * the estate. It carries `IREdge` (./graph-ir.ts) rather than an edge type of
75
+ * this contract's own, because that is already the shape both the declared
76
+ * path and the live path produce.
77
+ *
78
+ * Nothing in the options is a handle. There is no client, no transport, no
79
+ * apply callback, no writer — only strings, a name list and the entity map the
80
+ * build already produced. An engine handed this cannot reach the account even
81
+ * if it wanted to, which is the structural half of "it never writes".
82
+ *
83
+ * ## The tri-state, and why it is not `UnobservedReason`
84
+ *
85
+ * Behaviour keeps the same three-verdict discipline `./observation.ts`
86
+ * established — PREDICTED, NOT-PREDICTABLE-FOR-THIS-KIND, and NOT-PREDICTED
87
+ * with a named reason — but on a stricter total: every entity the caller asked
88
+ * about lands in `entities` or in `unpredicted`, never in neither. The thin
89
+ * read needs a third position because "the provider says it is not there" is a
90
+ * real answer with no row to sit on. A prediction has no such answer. An entity
91
+ * either got a figure or it did not, and when it did not there is a reason,
92
+ * so an entity in neither map means the lexicon lost track of it.
93
+ *
94
+ * {@link BehaviourUnpredictedReason} derives from `UnobservedReason` rather
95
+ * than restating it, so the four shared verdicts provably keep their spelling
96
+ * and their meaning. It differs in two ways, both deliberate:
97
+ *
98
+ * - `no-credentials` is **excluded**. A behaviour read has no credential to
99
+ * be missing — see the section above — so an enum that could say it would
100
+ * be inviting a lexicon to send an operator hunting for a variable this
101
+ * contract forbids.
102
+ * - Four reasons about the predictor itself are **added** — `no-engine`,
103
+ * `engine-unreachable`, `engine-out-of-credit` and `engine-over-quota` —
104
+ * because the epic wants a missing engine named and `no-binding` is about
105
+ * the environment resolving to no target, a different axis. They are four
106
+ * rather than one because each has a different remedy, and a refusal that
107
+ * names the wrong remedy is worse than a slow one: set a variable, check an
108
+ * address, pay for the account, or wait for a window. The last two arrive
109
+ * from an engine that answered perfectly well (#2359), so folding them into
110
+ * `engine-unreachable` would send somebody to debug a network that is fine.
111
+ *
112
+ * ## Deltas: the invariant is here, the presentation is not
113
+ *
114
+ * The epic wants a declared prediction and a live prediction shown as a delta,
115
+ * and #2358 posts one on a merge request. This module deliberately ships no
116
+ * delta type and no differencing function. What it ships is the one thing a
117
+ * hand-rolled diff silently loses, which is that a figure's context does not
118
+ * survive subtraction: {@link compareFigures} classifies a pair `comparable`,
119
+ * `mixed-basis`, `mixed-level` or `mixed-engine`, and the rule this contract
120
+ * binds its consumers to is that anything but `comparable` must be marked
121
+ * wherever it is shown. A `modeled` figure minus a `validated` one is not a
122
+ * change in the estate; part of that difference is the gap between a price list
123
+ * and an invoice. Nor is a 100 rps figure minus a 1000 rps one, which is why
124
+ * {@link compareProvenance} — which cannot see `at` and never could — is not
125
+ * the function to reach for. How the mark looks is #2358's to define. Whether
126
+ * there is one is not.
127
+ *
128
+ * ## Shape compatibility with the overlay
129
+ *
130
+ * {@link PredictedBehaviour} is the object `chant graph --live --overlay` puts
131
+ * on a node as `attrs._behaviour`.
132
+ *
133
+ * On the graph, `meta._behaviour` takes {@link BehaviourReportMeta} **or the
134
+ * whole {@link BehaviourRefusalReport}** — not a bare {@link BehaviourRefusal}.
135
+ * behold reads that key as `{ engine?, version?, at?, total?, refusal? }` and
136
+ * branches on the presence of `refusal`, so a bare `BehaviourRefusal` has no
137
+ * `refusal` key, falls through to the engine check, and is dropped as
138
+ * "meta.engine missing" — a refusal that renders as nothing at all, which is
139
+ * the one outcome this contract exists to prevent. #2360 implements this
140
+ * sentence, so it says what behold does.
141
+ *
142
+ * behold reads those keys and does arithmetic on the engine's figures; it never
143
+ * produces one of its own. Fields beyond what it reads (`cause` and `source` on
144
+ * a refusal, `edgeCoverage` on the meta) are additive and ignorable.
145
+ *
146
+ * ## The transport is part of the contract (#2373, decided in #2359)
147
+ *
148
+ * The first version of this module named the address chain, said the address
149
+ * may be a URL, a socket path or a command on `PATH`, and stopped. The first
150
+ * consumer (#2357) then declared a private `BehaviourEngine` and its own mapping from
151
+ * what the wire said to which of the three engine-answered refusals to build.
152
+ * With a second implementation to generalise from, the seam is here:
153
+ * {@link BehaviourTransport} is one method, `send(body)`, taking the request
154
+ * as the lexicon rendered it and returning either the engine's answer as text
155
+ * or a {@link BehaviourRefusalReport} ready to return.
156
+ *
157
+ * Two things about that shape are decisions rather than defaults.
158
+ *
159
+ * - **The transport returns text, not a report.** #2373 proposed
160
+ * `send(request) → Promise<BehaviourResult>`. A report cannot be built
161
+ * without the request's `entityNames`, `traffic` and `edgeCoverage`, and
162
+ * what an answer's fields *mean* is the lexicon's wire version, not the
163
+ * contract's. A transport that built reports would have to know both, and
164
+ * every lexicon would need its own. One that carries bytes is one HTTP
165
+ * client and one subprocess spawner for every lexicon there will ever be.
166
+ * - **The failure arm is a finished refusal, not a `{cause, detail}` pair.**
167
+ * The transport is the one party that saw the wire condition and holds the
168
+ * lexicon name and the endpoint, so it is the one party that can name the
169
+ * condition, the address and the variable together. Handing back a cause
170
+ * for the lexicon to translate would put the same three-way switch in
171
+ * every lexicon, and one of them would fold `engine-over-quota` into
172
+ * `engine-unreachable` on a bad afternoon. {@link behaviourWireRefusal}
173
+ * is that switch, written once.
174
+ *
175
+ * What the wire says maps to the causes above as follows, and
176
+ * `./behaviour-http.ts` (the first adapter) and `./behaviour-engine.ts`'s command transport both
177
+ * hold to it: an HTTP 402 is `engine-out-of-credit`; a 429 is
178
+ * `engine-over-quota`; a connection refused, a timeout, a 5xx, a redirect, or
179
+ * an answer that is not JSON is `engine-unreachable`; an unset address is
180
+ * `no-engine` before any transport is built; and an unset or rejected bearer
181
+ * token is `no-engine` too, because its remedy is the same kind — set a
182
+ * variable — and the reason says which. A transport that spawns a child hands
183
+ * it {@link behaviourEngineChildEnvironment} and nothing more, for the reason
184
+ * given on that function.
185
+ *
186
+ * A bearer token on the wire is the transport's, and is not the credential
187
+ * rule 3 is about. "The engine is never handed a credential" is a statement
188
+ * about the *request body* — the graph — which {@link screenBehaviourRequest}
189
+ * still walks first. {@link behaviourTokenFrom} resolves the token an engine
190
+ * authenticates chant with, on a chain parallel to the address chain, and the
191
+ * token goes in a header the engine reads and nowhere else: never in the body,
192
+ * never in a refusal, never in a `detail`.
193
+ */
194
+ import { CREDENTIAL_ENV_NAME, CREDENTIAL_SHAPES, CREDENTIAL_TOKEN_SHAPES, REDACTED, redactCredentialMaterial, } from "./identity.js";
195
+ /**
196
+ * Every legal {@link BehaviourBasis}, for validation and conformance checks.
197
+ *
198
+ * Derived from a total witness rather than typed as `readonly BehaviourBasis[]`
199
+ * and written out by hand. A hand-written array is only checked for having
200
+ * legal members, never for having ALL of them, so a value added to the union
201
+ * leaves the array silently short and every runtime guard built on it starts
202
+ * rejecting a value the type accepts. Keying a `Record` off the union makes the
203
+ * omission a compile error at the point of the omission. Same construction for
204
+ * {@link RESILIENCE_VERDICTS} and {@link BEHAVIOUR_UNPREDICTED_REASONS}, and
205
+ * the last of those is the one that needed it: it derives from
206
+ * `UnobservedReason`, so a reason added *upstream* would otherwise widen this
207
+ * type with nothing here failing.
208
+ */
209
+ const BEHAVIOUR_BASIS_WITNESS = {
210
+ modeled: true,
211
+ validated: true,
212
+ };
213
+ export const BEHAVIOUR_BASES = Object.keys(BEHAVIOUR_BASIS_WITNESS);
214
+ /** True when `value` is a legal {@link BehaviourBasis}. */
215
+ export function isBehaviourBasis(value) {
216
+ return typeof value === "string" && BEHAVIOUR_BASES.includes(value);
217
+ }
218
+ /** Build a {@link PredictedRate}. Lexicons use this rather than writing the discriminant by hand. */
219
+ export function predictedRate(perHour, currency) {
220
+ return { rate: "per-hour", perHour, currency };
221
+ }
222
+ const RESILIENCE_VERDICT_WITNESS = {
223
+ survives: true,
224
+ degrades: true,
225
+ fails: true,
226
+ };
227
+ /** Every legal {@link ResilienceVerdict}, for validation and conformance checks. */
228
+ export const RESILIENCE_VERDICTS = Object.keys(RESILIENCE_VERDICT_WITNESS);
229
+ /** True when `value` is a legal {@link ResilienceVerdict}. */
230
+ export function isResilienceVerdict(value) {
231
+ return typeof value === "string" && RESILIENCE_VERDICTS.includes(value);
232
+ }
233
+ /**
234
+ * The total witness. This is the one that earns the construction: the type
235
+ * above derives from `UnobservedReason`, so adding a reason to THAT union —
236
+ * in ./observation.ts, for reasons that have nothing to do with prediction —
237
+ * silently widens this one. With a hand-written array, the widening compiled
238
+ * clean, every existing test stayed green, and the new reason was assignable to
239
+ * `BehaviourUnpredictedReason` while {@link isBehaviourUnpredictedReason}
240
+ * returned `false` for it and the conformance suite rejected it. Keyed off the
241
+ * union, the same change fails to compile here and somebody has to decide
242
+ * whether the new reason belongs to behaviour at all.
243
+ */
244
+ const BEHAVIOUR_UNPREDICTED_REASON_WITNESS = {
245
+ "read-failed": true,
246
+ "no-binding": true,
247
+ "unsupported-kind": true,
248
+ filtered: true,
249
+ "no-engine": true,
250
+ "engine-unreachable": true,
251
+ "engine-out-of-credit": true,
252
+ "engine-over-quota": true,
253
+ "credential-in-request": true,
254
+ };
255
+ /** Every legal {@link BehaviourUnpredictedReason}, for validation and conformance checks. */
256
+ export const BEHAVIOUR_UNPREDICTED_REASONS = Object.keys(BEHAVIOUR_UNPREDICTED_REASON_WITNESS);
257
+ /** True when `value` is a legal {@link BehaviourUnpredictedReason}. */
258
+ export function isBehaviourUnpredictedReason(value) {
259
+ return (typeof value === "string" &&
260
+ BEHAVIOUR_UNPREDICTED_REASONS.includes(value));
261
+ }
262
+ /** True when the lexicon refused rather than predicting. */
263
+ export function isBehaviourRefusalReport(value) {
264
+ return typeof value === "object" && value !== null && "refusal" in value;
265
+ }
266
+ /**
267
+ * True when `value` is either arm of the versioned {@link BehaviourResult}
268
+ * envelope.
269
+ *
270
+ * Both the discriminant AND an arm, because the discriminant alone is not the
271
+ * type. `{ behaviour: "v1" }` carries the version and is neither arm: it fails
272
+ * {@link isBehaviourRefusalReport}, so a consumer's `if (refusal) … else …`
273
+ * narrows it to {@link BehaviourReport}, and `result.entities` is `undefined`
274
+ * at a site TypeScript has been told cannot be.
275
+ */
276
+ export function isBehaviourResult(value) {
277
+ if (typeof value !== "object" || value === null)
278
+ return false;
279
+ const v = value;
280
+ if (v.behaviour !== "v1")
281
+ return false;
282
+ if (typeof v.refusal === "object" && v.refusal !== null)
283
+ return true;
284
+ return (typeof v.entities === "object" && v.entities !== null && typeof v.meta === "object" && v.meta !== null);
285
+ }
286
+ /**
287
+ * Words that state nothing. Rejected wherever this contract asks an engine to
288
+ * *state* something — its tolerance, and the failure a verdict is about.
289
+ *
290
+ * `"none"` as a `resilience.failure` was the hole: the module's own doc says a
291
+ * verdict with no named failure says nothing, and `"none"` names no failure
292
+ * while sailing through a non-empty-string check.
293
+ */
294
+ const STATES_NOTHING = ["n/a", "na", "none", "unknown", "-", "?", "tbd", "null"];
295
+ /** True when a stated value is one of the words that state nothing. */
296
+ function statesNothing(value) {
297
+ return STATES_NOTHING.includes(value.trim().toLowerCase().replace(/\.$/, ""));
298
+ }
299
+ const isFraction = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
300
+ const isFilled = (v) => typeof v === "string" && v.trim() !== "";
301
+ /**
302
+ * Every rule behold's `validateBehaviourBlock` applies, applied here instead —
303
+ * before the block is built rather than after it has travelled.
304
+ *
305
+ * The rule set is deliberately, literally the same set. behold
306
+ * (`behold/src/behaviour.ts:209`) validates each block on arrival and **drops
307
+ * the whole block** with a diagnostic when one fails, so a block chant's types
308
+ * accept and behold's validator rejects renders nothing at all, having looked
309
+ * perfectly legal every step of the way here. Types cannot carry most of these:
310
+ * `tolerance: string` accepts `""`, `errorRate: number` accepts `1.2`, and
311
+ * `cost.perHour: number` accepts `-4`.
312
+ *
313
+ * The mapping, by behold's own numbering, so a future change there has a named
314
+ * place to land here:
315
+ *
316
+ * 2 `at.traffic` non-empty · 3 `cost.perHour` finite · 4 not negative ·
317
+ * 5 `cost.currency` non-empty · 6 `headroom` present · 7/8 each axis a
318
+ * fraction · 9 at least one axis · 10 `errorRate` a fraction ·
319
+ * 11 `resilience.failure` non-empty · 12 verdict in the closed set ·
320
+ * 13 `rightSize` implies a `suggestion` · 14 `provenance.engine` non-empty ·
321
+ * 15 `version` non-empty · 16 `tolerance` non-empty · 17 `basis` in the closed
322
+ * set.
323
+ *
324
+ * Rules 9 and 16 are the two chant is stricter on. Rule 9 is also a type here
325
+ * ({@link BehaviourHeadroom} is a union requiring one axis), so this is the
326
+ * backstop for a JavaScript caller. And on 16, behold accepts any non-empty
327
+ * string while this rejects `n/a`, `none`, `unknown` and friends: the epic asks
328
+ * for the engine's *stated* tolerance, and a word meaning "I have none to
329
+ * state" passes behold's check while defeating its purpose.
330
+ */
331
+ export function validateBehaviourBlock(name, block) {
332
+ const bad = (why) => {
333
+ throw new Error(`predictBehaviour produced an invalid block for "${name}": ${why}. behold's own validator ` +
334
+ "(behold/src/behaviour.ts) drops a block failing this, so it would render nothing rather than " +
335
+ "render wrong — which is worse to debug, because everything here looked legal.");
336
+ };
337
+ if (!isFilled(block.at?.traffic))
338
+ bad("at.traffic is missing — name the traffic level you priced");
339
+ const perHour = block.cost?.perHour;
340
+ if (typeof perHour !== "number" || !Number.isFinite(perHour))
341
+ bad("cost.perHour is not a finite number");
342
+ if (perHour < 0)
343
+ bad("cost.perHour is negative");
344
+ if (!isFilled(block.cost?.currency))
345
+ bad("cost.currency is missing");
346
+ const headroom = block.headroom;
347
+ if (!headroom || typeof headroom !== "object")
348
+ bad("headroom is missing");
349
+ const h = headroom;
350
+ if (h.cpu !== undefined && !isFraction(h.cpu))
351
+ bad("headroom.cpu is not a fraction 0..1");
352
+ if (h.latency !== undefined && !isFraction(h.latency))
353
+ bad("headroom.latency is not a fraction 0..1");
354
+ if (h.cpu === undefined && h.latency === undefined) {
355
+ bad("headroom carries neither cpu nor latency — an axis you did not model is absent, and a block with no axis at all says nothing");
356
+ }
357
+ if (!isFraction(block.errorRate))
358
+ bad("errorRate is not a fraction 0..1");
359
+ if (!isFilled(block.resilience?.failure)) {
360
+ bad("resilience.failure is missing — a verdict with no named failure says nothing");
361
+ }
362
+ if (statesNothing(block.resilience.failure)) {
363
+ bad(`resilience.failure ${JSON.stringify(block.resilience.failure)} names no failure. behold takes ` +
364
+ "any non-empty string here, so this passes its validator and renders a confident verdict about " +
365
+ 'nothing. Name the event: "one zone lost", "primary database failover"');
366
+ }
367
+ if (!isResilienceVerdict(block.resilience?.verdict)) {
368
+ bad(`resilience.verdict ${JSON.stringify(block.resilience?.verdict)} is not survives/degrades/fails`);
369
+ }
370
+ if (block.rightSize !== undefined && !isFilled(block.rightSize.suggestion)) {
371
+ bad("rightSize is present without a suggestion");
372
+ }
373
+ const p = block.provenance;
374
+ if (!p || typeof p !== "object")
375
+ bad("provenance is missing");
376
+ if (!isFilled(p?.engine))
377
+ bad("provenance.engine is missing");
378
+ if (!isFilled(p?.version))
379
+ bad("provenance.version is missing");
380
+ if (!isFilled(p?.tolerance)) {
381
+ bad("provenance.tolerance is missing — a figure without a stated tolerance is not a prediction");
382
+ }
383
+ if (statesNothing(p.tolerance)) {
384
+ bad(`provenance.tolerance ${JSON.stringify(p.tolerance)} states no tolerance. An engine with nothing ` +
385
+ "to say about its own error bars has no business publishing a figure; say the number, however wide");
386
+ }
387
+ if (!isBehaviourBasis(p?.basis)) {
388
+ bad(`provenance.basis ${JSON.stringify(p?.basis)} is not modeled/validated`);
389
+ }
390
+ }
391
+ /**
392
+ * Build a {@link BehaviourReport}, and refuse to build an invalid one.
393
+ *
394
+ * Takes the request rather than a hand-assembled meta, and derives `at`,
395
+ * `edgeCoverage` and the asked-for names from it. A lexicon states only what is
396
+ * genuinely its own — which engine answered, at what version, and a total if
397
+ * the engine has one — so the three fields that must agree with the request
398
+ * cannot be restated differently.
399
+ *
400
+ * That is a check and not a proof, and the distinction matters enough to say
401
+ * plainly: {@link BehaviourReport} is a plain interface, {@link isBehaviourResult}
402
+ * accepts a hand-built one, and a lexicon calling this with
403
+ * `entityNames: Object.keys(entities)` self-certifies. What passing the request
404
+ * buys is that the ordinary route is checked and the check names what is wrong;
405
+ * a consumer that needs the guarantee runs its own
406
+ * `validateBehaviourResult(result, askedFor)` on arrival, which is #2358's and
407
+ * #2360's to write.
408
+ *
409
+ * Refusals, all naming what went wrong:
410
+ *
411
+ * - an entity in neither map — the tri-state's whole point, and the one a
412
+ * `continue` in a lexicon's loop produces silently;
413
+ * - an entity in both maps — priced and unpriced at once;
414
+ * - a figure for something nobody asked about;
415
+ * - a block priced at a level the run did not ask for;
416
+ * - an edge-coverage claim that names no gap ({@link validateEdgeCoverage});
417
+ * - any block failing {@link validateBehaviourBlock}.
418
+ */
419
+ export function behaviourReport(request, stamp, entities, unpredicted) {
420
+ if (!isFilled(stamp?.engine))
421
+ throw new Error("predictBehaviour: meta.engine is missing");
422
+ if (!isFilled(stamp?.version))
423
+ throw new Error("predictBehaviour: meta.version is missing");
424
+ if (!isFilled(request?.traffic))
425
+ throw new Error("predictBehaviour: meta.at.traffic is missing");
426
+ if (stamp.total && (!Number.isFinite(stamp.total.perHour) || stamp.total.perHour < 0)) {
427
+ throw new Error("predictBehaviour: meta.total.perHour is not a non-negative finite number");
428
+ }
429
+ validateEdgeCoverage(request.edgeCoverage);
430
+ const meta = {
431
+ engine: stamp.engine,
432
+ version: stamp.version,
433
+ at: { traffic: request.traffic },
434
+ ...(stamp.total ? { total: stamp.total } : {}),
435
+ // Copied, not aliased. `readonly` is erased at runtime, so assigning the
436
+ // request's object by reference let a caller mutate
437
+ // `request.edgeCoverage.unresolvedKinds` after construction and change what
438
+ // the report claims it was computed over — the one field whose whole job is
439
+ // to be the report's honest account of its own inputs.
440
+ edgeCoverage: copyEdgeCoverage(request.edgeCoverage),
441
+ };
442
+ const entityNames = request.entityNames;
443
+ const asked = new Set(entityNames);
444
+ const holes = new Set(Object.keys(unpredicted ?? {}));
445
+ for (const name of Object.keys(entities)) {
446
+ if (holes.has(name)) {
447
+ throw new Error(`predictBehaviour reported "${name}" as both priced and unpriced. An entity has one verdict.`);
448
+ }
449
+ if (!asked.has(name)) {
450
+ throw new Error(`predictBehaviour returned a figure for "${name}", which was not in entityNames. An engine ` +
451
+ "answering about entities nobody asked about is answering about the wrong estate.");
452
+ }
453
+ validateBehaviourBlock(name, entities[name]);
454
+ if (entities[name].at.traffic !== meta.at.traffic) {
455
+ throw new Error(`predictBehaviour priced "${name}" at ${JSON.stringify(entities[name].at.traffic)} in a run ` +
456
+ `whose meta.at.traffic is ${JSON.stringify(meta.at.traffic)}. One run, one level.`);
457
+ }
458
+ }
459
+ for (const name of holes) {
460
+ if (!asked.has(name)) {
461
+ throw new Error(`predictBehaviour reported "${name}" unpredicted, and it was not in entityNames.`);
462
+ }
463
+ if (!isBehaviourUnpredictedReason(unpredicted[name]?.reason)) {
464
+ throw new Error(`predictBehaviour gave "${name}" the reason ` +
465
+ `${JSON.stringify(unpredicted[name]?.reason)}, which is not one of ` +
466
+ `${BEHAVIOUR_UNPREDICTED_REASONS.join(", ")}.`);
467
+ }
468
+ }
469
+ const missing = entityNames.filter((name) => !Object.prototype.hasOwnProperty.call(entities, name) && !holes.has(name));
470
+ if (missing.length > 0) {
471
+ throw new Error(`predictBehaviour gave no verdict at all for ${missing.map((n) => `"${n}"`).join(", ")}. Every ` +
472
+ "entity asked about lands in `entities` or in `unpredicted` — there is no third position, " +
473
+ "because a prediction has no equivalent of \"the provider says it is not there\". An entity " +
474
+ "you could not price is `unsupported-kind`, not an omission.");
475
+ }
476
+ return {
477
+ behaviour: "v1",
478
+ meta,
479
+ entities,
480
+ ...(unpredicted && Object.keys(unpredicted).length > 0 ? { unpredicted } : {}),
481
+ };
482
+ }
483
+ /** Build a {@link BehaviourRefusalReport}. */
484
+ export function behaviourRefusal(refusal) {
485
+ return { behaviour: "v1", refusal };
486
+ }
487
+ /**
488
+ * Classify a pair of *provenances*. Almost always the wrong function to call —
489
+ * see {@link compareFigures}, which is the one consumers want.
490
+ *
491
+ * The limit is structural rather than an oversight: `at` lives on
492
+ * {@link PredictedBehaviour} and not on {@link BehaviourProvenance}, so this
493
+ * function cannot see the traffic level and will happily answer `comparable`
494
+ * for a figure at 100 rps and a figure at 1000 rps. Two predictions of the same
495
+ * estate by the same engine at different levels are not a delta of like things;
496
+ * they are answers to different questions. Use this only where the two figures
497
+ * are already known to share a level.
498
+ */
499
+ export function compareProvenance(a, b) {
500
+ if (a.engine !== b.engine || a.version !== b.version || a.tolerance !== b.tolerance) {
501
+ return "mixed-engine";
502
+ }
503
+ return a.basis === b.basis ? "comparable" : "mixed-basis";
504
+ }
505
+ /**
506
+ * The total witness, keyed by the union — the same construction the other four
507
+ * closed sets use, and this one needs it as much as any.
508
+ *
509
+ * {@link figureMismatches} filters {@link compareFigures}'s output through the
510
+ * array below. An axis added to the union and to `compareFigures` but not to
511
+ * the array would be reported by one and silently dropped by the other, which
512
+ * is exactly the "a mismatch went unsaid" failure the set return was added to
513
+ * prevent, reappearing in the display path.
514
+ *
515
+ * The value is the display rank rather than `true`, so the order is derived
516
+ * from the witness too and there is one place to state it.
517
+ */
518
+ const FIGURE_MISMATCH_WITNESS = {
519
+ "mixed-engine": 0,
520
+ "mixed-level": 1,
521
+ "mixed-currency": 2,
522
+ "mixed-basis": 3,
523
+ "mixed-failure": 4,
524
+ };
525
+ /**
526
+ * Every mismatch, in the order a consumer should show them — most fundamental
527
+ * first. Order is presentation; membership is the contract.
528
+ */
529
+ export const FIGURE_MISMATCHES = Object.keys(FIGURE_MISMATCH_WITNESS).sort((a, b) => FIGURE_MISMATCH_WITNESS[a] - FIGURE_MISMATCH_WITNESS[b]);
530
+ /**
531
+ * Classify a pair of figures for delta purposes. **This is the one to call.**
532
+ *
533
+ * Returns **every** axis on which the two disagree, not the first. An earlier
534
+ * version returned one label and stopped at the first mismatch, which meant a
535
+ * pair differing in level AND basis reported `mixed-level` and dropped the
536
+ * basis crossing on the floor — a consumer told "different traffic level" would
537
+ * caption it as such and show a modeled-minus-validated difference underneath
538
+ * with nothing said. Precedence is a fine way to decide what to show first and
539
+ * a bad way to decide what to know.
540
+ *
541
+ * The axes:
542
+ *
543
+ * - `mixed-engine` — engine, version or tolerance differs. Two models are not
544
+ * one scale.
545
+ * - `mixed-level` — different `at`. The same question asked of two different
546
+ * worlds.
547
+ * - `mixed-currency` — different `cost.currency`. chant converts nothing, so
548
+ * USD minus EUR is not a number. behold already refuses to *sum* these
549
+ * (`behold/src/behaviour.ts`); permitting them to be *differenced* left this
550
+ * contract laxer than its own consumer.
551
+ * - `mixed-basis` — one figure off a price list, the other off an invoice.
552
+ * - `mixed-failure` — different `resilience.failure`. "Survives one zone lost"
553
+ * against "survives a region lost" are two verdicts about two events, and
554
+ * differencing the costs beside them implies they answer the same question.
555
+ *
556
+ * The rule this contract binds its consumers to, in one sentence: **a delta
557
+ * between two figures whose mismatch set is not empty must be marked as such
558
+ * wherever it is shown, and must never be presented as a plain difference.**
559
+ * How it is marked is #2358's to choose. Whether it must be marked is not.
560
+ */
561
+ export function compareFigures(a, b) {
562
+ const out = new Set();
563
+ if (compareProvenance(a.provenance, b.provenance) === "mixed-engine")
564
+ out.add("mixed-engine");
565
+ if (a.at.traffic !== b.at.traffic)
566
+ out.add("mixed-level");
567
+ if (a.cost.currency !== b.cost.currency)
568
+ out.add("mixed-currency");
569
+ if (a.provenance.basis !== b.provenance.basis)
570
+ out.add("mixed-basis");
571
+ if (a.resilience.failure !== b.resilience.failure)
572
+ out.add("mixed-failure");
573
+ return out;
574
+ }
575
+ /** The mismatches, ordered for display. A convenience over {@link compareFigures}. */
576
+ export function figureMismatches(a, b) {
577
+ const found = compareFigures(a, b);
578
+ return FIGURE_MISMATCHES.filter((m) => found.has(m));
579
+ }
580
+ /** True when two whole figures may be shown as a plain difference, with no mark. */
581
+ export function isComparableFigure(a, b) {
582
+ return compareFigures(a, b).size === 0;
583
+ }
584
+ /** True when two provenances may be shown as a plain difference. Level-blind — see {@link compareProvenance}. */
585
+ export function isComparableProvenance(a, b) {
586
+ return compareProvenance(a, b) === "comparable";
587
+ }
588
+ /**
589
+ * The variables that can name a behaviour engine, most specific first. Exported
590
+ * so a refusal message and a test can agree on the chain without restating it.
591
+ *
592
+ * `lexicon` scopes the first entry, which is what lets an estate priced by two
593
+ * engines point each lexicon at its own without a per-call flag.
594
+ */
595
+ export function behaviourEngineVariables(lexicon) {
596
+ const scope = lexicon.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
597
+ return [`CHANT_BEHAVIOUR_ENGINE_${scope}`, "CHANT_BEHAVIOUR_ENGINE", "BEHAVIOUR_ENGINE"];
598
+ }
599
+ /**
600
+ * Resolve the engine one lexicon predicts against, most specific first. Pure —
601
+ * exported for testing.
602
+ *
603
+ * The chain is lexicon-scoped, then chant-scoped, then bare, for the same
604
+ * reason `gitlabNoteTokenFrom` reads `CHANT_GITLAB_TOKEN` before `GITLAB_TOKEN`
605
+ * (`./op/activities/reconcile.ts`): the narrower name exists so a project that
606
+ * has to differ can differ, and the wider one exists so a project that does not
607
+ * need to sets one variable.
608
+ *
609
+ * Returns `undefined` when nothing in the chain answered. That is `no-engine`,
610
+ * and the caller turns it into {@link noBehaviourEngineMessage} — never into an
611
+ * empty report.
612
+ */
613
+ export function behaviourEngineFrom(lexicon, env) {
614
+ for (const source of behaviourEngineVariables(lexicon)) {
615
+ const value = env[source]?.trim();
616
+ if (value)
617
+ return { value, source };
618
+ }
619
+ return undefined;
620
+ }
621
+ /** What a lexicon says when no variable in the chain names an engine. */
622
+ export function noBehaviourEngineMessage(lexicon) {
623
+ const [scoped, chantWide, bare] = behaviourEngineVariables(lexicon);
624
+ return (`predictBehaviour has the ${lexicon} estate to predict and no engine to predict it with. Set a ` +
625
+ `${chantWide} environment variable to the engine's address — a URL, a socket path, or a command on ` +
626
+ `PATH — or ${bare} where nothing else in the environment is chant's. ${scoped} is read first, for an ` +
627
+ "estate whose lexicons are priced by different engines. The address is not a credential: the engine " +
628
+ "is never handed one and never writes.");
629
+ }
630
+ /**
631
+ * The variables that can hold a behaviour engine's token, most specific first.
632
+ * Parallel to {@link behaviourEngineVariables} and deliberately not the same
633
+ * chain: `CHANT_BEHAVIOUR_ENGINE` is an address, and an address is printed in
634
+ * refusals, while a token is never printed anywhere. Reusing one chain for
635
+ * both would put the token in every message that names the address.
636
+ */
637
+ export function behaviourTokenVariables(lexicon) {
638
+ const scope = lexicon.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
639
+ return [`CHANT_BEHAVIOUR_TOKEN_${scope}`, "CHANT_BEHAVIOUR_TOKEN", "BEHAVIOUR_TOKEN"];
640
+ }
641
+ /**
642
+ * Resolve the token one lexicon's transport authenticates with, most specific
643
+ * first. Pure — exported for testing. The same shape as `gitlabNoteTokenFrom`
644
+ * (`./op/activities/reconcile.ts`): a chain, the first non-empty value wins,
645
+ * and the result names the variable so a refusal can say which one it read.
646
+ *
647
+ * Returns `undefined` when nothing in the chain answered. A transport whose
648
+ * engine bills an account turns that into {@link noBehaviourTokenRefusal}
649
+ * before sending anything: a request sent without the token is a request the
650
+ * engine will reject, and the refusal should name the variable rather than
651
+ * quote the engine's 401.
652
+ */
653
+ export function behaviourTokenFrom(lexicon, env) {
654
+ for (const source of behaviourTokenVariables(lexicon)) {
655
+ const value = env[source]?.trim();
656
+ if (value)
657
+ return { value, source };
658
+ }
659
+ return undefined;
660
+ }
661
+ /** What a transport says when the engine bills an account and no variable named a token. */
662
+ export function noBehaviourTokenMessage(lexicon, endpoint) {
663
+ const [scoped, chantWide, bare] = behaviourTokenVariables(lexicon);
664
+ return (`predictBehaviour has the ${lexicon} behaviour engine at ${redactEngineAddress(endpoint.value)}, ` +
665
+ `named by ${endpoint.source}, and no token to authenticate to it with, so nothing was sent. Set a ` +
666
+ `${chantWide} environment variable to the bearer token the engine issued — or ${bare} where nothing ` +
667
+ `else in the environment is chant's. ${scoped} is read first, for an estate whose lexicons are ` +
668
+ "priced by different engines on different accounts. The token goes in a header the engine reads " +
669
+ "and nowhere else; it is never in the request body and never in a message.");
670
+ }
671
+ /**
672
+ * The refusal for a metered engine with no token configured. `no-engine`,
673
+ * because that is the cause whose remedy is "set a variable" — the engine is
674
+ * named, reachable for all anyone knows, and unusable until one more variable
675
+ * is set. No `source`: the token chain is the chain this refusal is about, and
676
+ * nothing in it answered.
677
+ */
678
+ export function noBehaviourTokenRefusal(lexicon, endpoint) {
679
+ const [, chantWide] = behaviourTokenVariables(lexicon);
680
+ return behaviourRefusal({
681
+ cause: "no-engine",
682
+ reason: noBehaviourTokenMessage(lexicon, endpoint),
683
+ remedy: `Set ${chantWide} to the bearer token the engine at ${redactEngineAddress(endpoint.value)} issued.`,
684
+ });
685
+ }
686
+ /**
687
+ * The refusal for an engine that answered and rejected the token it was sent.
688
+ * Also `no-engine`: the address is fine, the account is not the problem, and
689
+ * the one action is to set the named variable to a token the engine accepts.
690
+ * `source` is the token variable, not the address variable, because that is
691
+ * the one a consumer would tell somebody to change.
692
+ *
693
+ * `detail` is whatever the engine said, and it goes through
694
+ * {@link scrubEngineDetail}. The token's own value is never in a message: a
695
+ * transport passes the variable's name here and keeps the value to itself.
696
+ */
697
+ export function rejectedBehaviourTokenRefusal(lexicon, endpoint, token, detail) {
698
+ return behaviourRefusal({
699
+ cause: "no-engine",
700
+ reason: `The ${lexicon} behaviour engine at ${redactEngineAddress(endpoint.value)}, named by ` +
701
+ `${endpoint.source}, answered and rejected the token ${token.source} holds ` +
702
+ `(${scrubEngineDetail(detail)}). The address is reachable and the account is not the problem; ` +
703
+ "the token is not one this engine accepts. No overlay is drawn and no figure is guessed locally.",
704
+ remedy: `Set ${token.source} to a bearer token the engine at ${redactEngineAddress(endpoint.value)} accepts.`,
705
+ source: token.source,
706
+ });
707
+ }
708
+ /** What a lexicon says when a variable named an engine and the engine did not answer. */
709
+ export function unreachableBehaviourEngineMessage(lexicon, endpoint, detail) {
710
+ return (`predictBehaviour reached for the ${lexicon} behaviour engine at ${redactEngineAddress(endpoint.value)}, ` +
711
+ `named by ${endpoint.source}, and it did not answer: ${scrubEngineDetail(detail)}. No overlay is drawn and no figure is ` +
712
+ `guessed locally. Check the engine is up and that ${endpoint.source} names the address this ` +
713
+ "environment can reach.");
714
+ }
715
+ /**
716
+ * The refusal for an estate with no engine configured. The whole of the
717
+ * `no-engine` path, so no lexicon has to assemble one by hand.
718
+ */
719
+ export function noBehaviourEngineRefusal(lexicon) {
720
+ const [, chantWide] = behaviourEngineVariables(lexicon);
721
+ return behaviourRefusal({
722
+ cause: "no-engine",
723
+ reason: noBehaviourEngineMessage(lexicon),
724
+ remedy: `Set ${chantWide} to the engine's address.`,
725
+ });
726
+ }
727
+ /** The refusal for a configured engine that did not answer. */
728
+ export function unreachableBehaviourEngineRefusal(lexicon, endpoint, detail) {
729
+ return behaviourRefusal({
730
+ cause: "engine-unreachable",
731
+ reason: unreachableBehaviourEngineMessage(lexicon, endpoint, detail),
732
+ remedy: `Check the engine at ${redactEngineAddress(endpoint.value)} is reachable, or repoint ${endpoint.source}.`,
733
+ source: endpoint.source,
734
+ });
735
+ }
736
+ /**
737
+ * What a lexicon says when the engine answered and refused for want of money
738
+ * (#2359). Distinct from unreachable on purpose: the address is fine, the
739
+ * request arrived, and telling somebody to check their networking wastes the
740
+ * one thing a refusal is for.
741
+ */
742
+ export function outOfCreditBehaviourEngineMessage(lexicon, endpoint, detail) {
743
+ return (`The ${lexicon} behaviour engine at ${redactEngineAddress(endpoint.value)}, named by ${endpoint.source}, ` +
744
+ `answered and refused: the account behind it is out of credit (${scrubEngineDetail(detail)}). The address is reachable and nothing ` +
745
+ "here is a networking problem. Add credit to the account this engine bills, or point " +
746
+ `${endpoint.source} at an engine on an account that has some. No overlay is drawn and no figure is ` +
747
+ "guessed locally.");
748
+ }
749
+ /** What a lexicon says when the engine answered and refused for a spent limit (#2359). */
750
+ export function overQuotaBehaviourEngineMessage(lexicon, endpoint, detail) {
751
+ return (`The ${lexicon} behaviour engine at ${redactEngineAddress(endpoint.value)}, named by ${endpoint.source}, ` +
752
+ `answered and refused: a rate or volume limit is spent (${scrubEngineDetail(detail)}). The account has credit and the address is ` +
753
+ "reachable, so this usually clears when the engine's window rolls over. Wait for it, raise the limit " +
754
+ `on the account, or point ${endpoint.source} at an engine with its own budget. No overlay is drawn ` +
755
+ "and no figure is guessed locally.");
756
+ }
757
+ /** The refusal for an engine that answered and said the account has no balance (#2359). */
758
+ export function outOfCreditBehaviourEngineRefusal(lexicon, endpoint, detail) {
759
+ return behaviourRefusal({
760
+ cause: "engine-out-of-credit",
761
+ reason: outOfCreditBehaviourEngineMessage(lexicon, endpoint, detail),
762
+ remedy: `Add credit to the account behind ${endpoint.source}, or repoint it at a funded engine.`,
763
+ source: endpoint.source,
764
+ });
765
+ }
766
+ /** The refusal for an engine that answered and said a limit is spent (#2359). */
767
+ export function overQuotaBehaviourEngineRefusal(lexicon, endpoint, detail) {
768
+ return behaviourRefusal({
769
+ cause: "engine-over-quota",
770
+ reason: overQuotaBehaviourEngineMessage(lexicon, endpoint, detail),
771
+ remedy: `Wait for the engine's window to roll over, or raise the limit on the account behind ${endpoint.source}.`,
772
+ source: endpoint.source,
773
+ });
774
+ }
775
+ /**
776
+ * One wire cause onto the refusal builder that names it. The whole of the
777
+ * mapping every transport applies, so it is written once: a transport that
778
+ * classified the wire correctly and then reached for the wrong builder would
779
+ * send an operator to check a network that is answering.
780
+ */
781
+ export function behaviourWireRefusal(lexicon, endpoint, cause, detail) {
782
+ switch (cause) {
783
+ case "engine-out-of-credit":
784
+ return outOfCreditBehaviourEngineRefusal(lexicon, endpoint, detail);
785
+ case "engine-over-quota":
786
+ return overQuotaBehaviourEngineRefusal(lexicon, endpoint, detail);
787
+ case "engine-unreachable":
788
+ return unreachableBehaviourEngineRefusal(lexicon, endpoint, detail);
789
+ }
790
+ }
791
+ /**
792
+ * The environment a transport hands a child process: `PATH`, and nothing else.
793
+ *
794
+ * Rule 3 says the engine never sees a credential, and
795
+ * {@link screenBehaviourRequest} enforces it on the request. A child that
796
+ * inherits `process.env` walks straight around that: the request is spotless
797
+ * and the child holds `AWS_SECRET_ACCESS_KEY` anyway. So a transport that
798
+ * spawns builds the environment from this and nothing more — `PATH` because
799
+ * the address is resolved against it, and no allowlist beyond that, because
800
+ * every name added is a name a credential could be sitting under. The
801
+ * command transport pins this by test (#2372); this is the rule it pins.
802
+ */
803
+ export function behaviourEngineChildEnvironment(env = process.env) {
804
+ return { PATH: env.PATH ?? "" };
805
+ }
806
+ /* -------------------------------------------------------------------------- */
807
+ /* Rendering */
808
+ /* -------------------------------------------------------------------------- */
809
+ const RED = "\x1b[31m";
810
+ const RESET = "\x1b[0m";
811
+ /**
812
+ * Render a refusal for a terminal, in red.
813
+ *
814
+ * Red rather than the amber a degradation gets, because a missing engine is not
815
+ * a partial answer: every behaviour-derived figure and colour is gone, and the
816
+ * one thing the reader must not do is assume the numbers are merely late.
817
+ *
818
+ * `color` defaults to the same rule the rest of the CLI uses (`NO_COLOR`, and a
819
+ * TTY on stdout); pass it explicitly where the output is asserted on.
820
+ */
821
+ export function renderBehaviourRefusal(refusal, options = {}) {
822
+ const color = options.color ?? (!process.env.NO_COLOR && process.stdout.isTTY !== false);
823
+ const body = `behaviour: refused (${refusal.cause}) — ${refusal.reason}\n ${refusal.remedy}`;
824
+ return color ? `${RED}${body}${RESET}` : body;
825
+ }
826
+ /* -------------------------------------------------------------------------- */
827
+ /* The request */
828
+ /* -------------------------------------------------------------------------- */
829
+ /**
830
+ * Names this contract refuses to carry, each declared `?: never` on
831
+ * {@link PredictBehaviourOptions} so the deliberate attempt is a compile error.
832
+ *
833
+ * This list is the *narrow* half of the guard and never the whole of it. A
834
+ * denylist of names cannot enforce "the engine never sees a credential" — the
835
+ * same argument that made `?: never` better than omission applies to every name
836
+ * the list omits, and `xApiKey`, `pat` and `Authorization` with a capital A all
837
+ * sailed past an earlier version of this file. What actually enforces the rule
838
+ * is {@link assertNoCredentialInOptions}, which walks values.
839
+ */
840
+ export const CREDENTIAL_OPTION_KEYS = [
841
+ "token",
842
+ "credential",
843
+ "credentials",
844
+ "secret",
845
+ "secrets",
846
+ "password",
847
+ "apiKey",
848
+ "accessKey",
849
+ "secretKey",
850
+ "sessionToken",
851
+ "auth",
852
+ "authorization",
853
+ "bearer",
854
+ "privateKey",
855
+ ];
856
+ /**
857
+ * Key names that are credential-shaped on their own but not as a substring —
858
+ * the two `CREDENTIAL_ENV_NAME` (./identity.ts) does not cover.
859
+ */
860
+ const EXTRA_CREDENTIAL_KEYS = ["pat", "cookie"];
861
+ /**
862
+ * Shortest string worth suspecting. Borrowed from `identity.ts`'s
863
+ * `MIN_CREDENTIAL_LENGTH`, and for the same reason: below it, a "secret" is a
864
+ * false positive.
865
+ */
866
+ const MIN_CREDENTIAL_LENGTH = 8;
867
+ /**
868
+ * Key suffixes that name a **reference to** a credential rather than a
869
+ * credential, and the handful of whole names that do the same.
870
+ *
871
+ * This is the correction that made the guard usable. Run over every distinct
872
+ * property key in this repo's own generated schemas, a bare substring match on
873
+ * `SECRET|TOKEN|PASSWORD|AUTH` refused 406 aws keys, 173 azure, 89 gcp and 7
874
+ * k8s — `imagePullSecrets`, `secretName`, `secretKeyRef` (all three are
875
+ * references to a Secret *by name*), `ClientToken` (an idempotency nonce on
876
+ * dozens of AWS resources), `CertificateAuthorityArn`, `authorizedNetworks`,
877
+ * `passwordPolicy`, `authMode`, `oauthScopes`. A `tags: { author: "…" }` was
878
+ * refused because `author` contains `auth`.
879
+ *
880
+ * None of those hold secret material, and every one of them is ordinary in real
881
+ * infrastructure, which #2357, #2359 and #2360 all assemble their requests from.
882
+ */
883
+ const REFERENCE_KEY_SUFFIXES = [
884
+ "ref",
885
+ "refs",
886
+ "name",
887
+ "names",
888
+ "id",
889
+ "ids",
890
+ "arn",
891
+ "arns",
892
+ "count",
893
+ "validity",
894
+ "mode",
895
+ "modes",
896
+ "policy",
897
+ "policies",
898
+ "scope",
899
+ "scopes",
900
+ "network",
901
+ "networks",
902
+ "type",
903
+ "types",
904
+ "enabled",
905
+ "required",
906
+ "version",
907
+ "uri",
908
+ "url",
909
+ "urls",
910
+ "config",
911
+ "configs",
912
+ "settings",
913
+ "options",
914
+ ];
915
+ /** Whole key names that are never credential material, whatever they contain. */
916
+ const REFERENCE_KEY_NAMES = [
917
+ "imagepullsecrets",
918
+ "clienttoken",
919
+ // A Kubernetes boolean: "mount the service account's token into this pod".
920
+ // It holds no token and never did.
921
+ "automountserviceaccounttoken",
922
+ "author",
923
+ "authors",
924
+ "authority",
925
+ "secretsmanager",
926
+ ];
927
+ /**
928
+ * `CREDENTIAL_ENV_NAME` with its underscores removed, derived from the same
929
+ * source so the two cannot drift.
930
+ *
931
+ * That pattern was written for environment variables, where the convention is
932
+ * `AWS_SECRET_ACCESS_KEY`, so several of its alternatives carry an underscore:
933
+ * `PRIVATE_KEY`, `ACCESS_KEY`, `SESSION_KEY`. Object keys are written
934
+ * `privateKey` and `accessKey`, which match none of them. Squashing both sides
935
+ * makes one rule cover `PRIVATE_KEY`, `privateKey`, `private-key` and
936
+ * `privatekey`.
937
+ */
938
+ const CREDENTIAL_ENV_NAME_SQUASHED = new RegExp(CREDENTIAL_ENV_NAME.source.replace(/_/g, ""), "i");
939
+ /**
940
+ * True when a string carries a password in userinfo — with or without a scheme.
941
+ *
942
+ * The scheme-less half matters: a DSN is routinely written
943
+ * `app:hunter2@db.internal:5432/prod`, with the driver supplying the scheme, and
944
+ * `new URL()` will not parse that at all.
945
+ */
946
+ function hasUrlPassword(value) {
947
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
948
+ try {
949
+ if (new URL(value).password !== "")
950
+ return true;
951
+ }
952
+ catch {
953
+ /* fall through to the scheme-less test */
954
+ }
955
+ }
956
+ // `user:pass@host` with no scheme. Both halves non-empty, no whitespace, and
957
+ // a host that looks like a host, so `mailto`-ish text and prose do not match.
958
+ return /^[^\s:@/]+:[^\s:@/]+@[A-Za-z0-9._-]+(?::\d+)?(?:[/?#]|$)/.test(value.trim());
959
+ }
960
+ /**
961
+ * True when a key name is credential-shaped, whatever its casing or separators.
962
+ *
963
+ * Reuses `CREDENTIAL_ENV_NAME`, which is a case-insensitive substring match over
964
+ * `SECRET|TOKEN|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|APIKEY|API_KEY|ACCESS_KEY|SESSION_KEY|AUTH`.
965
+ * That one expression covers `Authorization`, `xApiKey`, `clientSecret`,
966
+ * `refreshToken`, `awsSecretAccessKey` and `x-api-key` — every name an earlier
967
+ * fixed 14-entry list here missed — because it is chant's existing answer to
968
+ * the same question and had already been widened by everyone who hit a gap.
969
+ */
970
+ function isCredentialKey(key) {
971
+ const squashed = key.toLowerCase().replace(/[^a-z0-9]/g, "");
972
+ if (REFERENCE_KEY_NAMES.includes(squashed))
973
+ return false;
974
+ if (REFERENCE_KEY_SUFFIXES.some((s) => squashed.endsWith(s)))
975
+ return false;
976
+ if (EXTRA_CREDENTIAL_KEYS.includes(squashed))
977
+ return true;
978
+ return CREDENTIAL_ENV_NAME.test(key) || CREDENTIAL_ENV_NAME_SQUASHED.test(squashed);
979
+ }
980
+ /**
981
+ * True when a string is evidently a *pointer to* a secret rather than one.
982
+ *
983
+ * `AdminPassword` on `AWS::DirectoryService::MicrosoftAD` is a genuine
984
+ * credential field, and chant source almost always fills it with a
985
+ * `{{resolve:secretsmanager:…}}` string or an interpolation rather than a
986
+ * literal. Refusing those would refuse the correct way to write it.
987
+ */
988
+ function looksLikeSecretReference(value) {
989
+ return (/^\{\{[^}]+\}\}$/.test(value.trim()) ||
990
+ /^\$\{[^}]+\}$/.test(value.trim()) ||
991
+ /^!(?:Ref|GetAtt|Sub|ImportValue)\b/.test(value.trim()) ||
992
+ /^arn:[a-z0-9-]*:secretsmanager:/i.test(value.trim()) ||
993
+ /^projects\/[^/]+\/secrets\/[^/]+/.test(value.trim()));
994
+ }
995
+ /**
996
+ * What a credential-shaped value is, or `undefined`. Named so a refusal can say
997
+ * which rule fired rather than "something looked wrong".
998
+ */
999
+ function credentialValueShape(value) {
1000
+ for (const re of CREDENTIAL_SHAPES) {
1001
+ re.lastIndex = 0;
1002
+ if (re.test(value))
1003
+ return "a literal credential shape (PEM key, JWT or Authorization value)";
1004
+ }
1005
+ for (const { name, re } of CREDENTIAL_TOKEN_SHAPES) {
1006
+ re.lastIndex = 0;
1007
+ if (re.test(value))
1008
+ return name;
1009
+ }
1010
+ if (hasUrlPassword(value))
1011
+ return "a password in a URL's userinfo";
1012
+ return undefined;
1013
+ }
1014
+ /** A URL-ish string with its userinfo blanked and its query dropped. */
1015
+ function stripUrlSecrets(value) {
1016
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value))
1017
+ return value;
1018
+ try {
1019
+ const url = new URL(value);
1020
+ const hadUserinfo = url.username !== "" || url.password !== "";
1021
+ const hadQuery = url.search !== "";
1022
+ // The fragment too. OAuth's implicit flow puts the access token there
1023
+ // (`#access_token=…`), it never reaches a server, and it was untouched.
1024
+ const hadFragment = url.hash !== "";
1025
+ url.username = "";
1026
+ url.password = "";
1027
+ url.search = "";
1028
+ url.hash = "";
1029
+ // Markers are appended AFTER `toString()` and in wire order — query then
1030
+ // fragment. Appending the query marker to a string that still carried a
1031
+ // fragment used to land it inside the fragment.
1032
+ let out = url.toString();
1033
+ if (hadUserinfo)
1034
+ out = out.replace("://", `://${REDACTED}@`);
1035
+ if (hadQuery)
1036
+ out += `?${REDACTED}`;
1037
+ if (hadFragment)
1038
+ out += `#${REDACTED}`;
1039
+ return out;
1040
+ }
1041
+ catch {
1042
+ return value;
1043
+ }
1044
+ }
1045
+ /**
1046
+ * Blank the value of an inline command-line flag: `--token=x`, `-p x`,
1047
+ * `--api-key x`. A behaviour engine may be a command on `PATH`, and a command
1048
+ * carries its credential as an argument rather than in a URL, so the URL parse
1049
+ * has nothing to bite on and `redactCredentialMaterial` — which knows env
1050
+ * values and ten token prefixes — sees an inline flag value as neither.
1051
+ */
1052
+ function stripFlagSecrets(value) {
1053
+ return value
1054
+ .replace(/(--?[A-Za-z0-9-]*(?:secret|token|password|passwd|key|credential|auth|pat)[A-Za-z0-9-]*)([=\s])(\S+)/gi, (_m, flag, sep) => `${flag}${sep}${REDACTED}`)
1055
+ .replace(/(^|\s)(-[pPkK])(\s+)(\S+)/g, (_m, lead, flag, sp) => `${lead}${flag}${sp}${REDACTED}`);
1056
+ }
1057
+ /**
1058
+ * An engine address, safe to interpolate into a refusal that a merge-request
1059
+ * comment will carry (#2358).
1060
+ *
1061
+ * `CHANT_BEHAVIOUR_ENGINE=https://svc:s3cr3t@engine.internal/predict?key=abc` is
1062
+ * an ordinary way to point at an authenticated endpoint, and it is the shape
1063
+ * this contract's own "resolve auth on your own transport" guidance produces.
1064
+ * Printing it verbatim in a refusal publishes it.
1065
+ *
1066
+ * Three passes, in order:
1067
+ *
1068
+ * 1. The URL parse blanks `username`/`password`, and drops the query string and
1069
+ * the fragment **whole** rather than by known parameter name — `?key=`,
1070
+ * `?token=`, `?sig=` and `#access_token=` are all common and the set is not
1071
+ * enumerable. The fragment matters on its own account: OAuth's implicit
1072
+ * flow puts the access token there.
1073
+ * 2. Inline command-line flag values (`--token=…`, `-p …`), for an engine that
1074
+ * is a command on `PATH` rather than a URL.
1075
+ * 3. {@link redactCredentialMaterial}, for env-held values and the token
1076
+ * shapes chant knows.
1077
+ *
1078
+ * **What survives:** a credential written as a bare path segment
1079
+ * (`https://host/predict/<token>/go`) unless it matches a known token shape.
1080
+ * Nothing here can tell that segment from a resource id, and an earlier version
1081
+ * of this doc claimed pass 3 caught it, which was wrong. If an engine
1082
+ * authenticates by path, do not put its address in a variable whose refusal
1083
+ * text is published.
1084
+ */
1085
+ export function redactEngineAddress(value, env = process.env) {
1086
+ return redactCredentialMaterial(stripFlagSecrets(stripUrlSecrets(value)), env);
1087
+ }
1088
+ /**
1089
+ * Engine-supplied detail, bounded and scrubbed before it reaches a refusal.
1090
+ *
1091
+ * `detail` on a credit or quota refusal is whatever the engine said, echoed.
1092
+ * An engine is a third party: it can be verbose, and it can quote the request
1093
+ * back at you, which is how a URL or an id ends up in a public comment. So the
1094
+ * text is truncated, anything URL-shaped is reduced to its host, and the result
1095
+ * goes through {@link redactCredentialMaterial}.
1096
+ */
1097
+ export function scrubEngineDetail(detail, env = process.env) {
1098
+ const withoutUrls = detail.replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, (match) => {
1099
+ try {
1100
+ return new URL(match).host || REDACTED;
1101
+ }
1102
+ catch {
1103
+ return REDACTED;
1104
+ }
1105
+ });
1106
+ const scrubbed = redactCredentialMaterial(withoutUrls, env).replace(/\s+/g, " ").trim();
1107
+ return scrubbed.length > MAX_ENGINE_DETAIL ? `${scrubbed.slice(0, MAX_ENGINE_DETAIL)}…` : scrubbed;
1108
+ }
1109
+ /** How much engine-supplied text a refusal will repeat. */
1110
+ const MAX_ENGINE_DETAIL = 200;
1111
+ const EDGE_COVERAGE_WITNESS = {
1112
+ complete: true,
1113
+ partial: true,
1114
+ unknown: true,
1115
+ };
1116
+ /** Every legal {@link EdgeCoverageVerdict}. */
1117
+ export const EDGE_COVERAGE_VERDICTS = Object.keys(EDGE_COVERAGE_WITNESS);
1118
+ /** True when `value` is a legal {@link EdgeCoverageVerdict}. */
1119
+ export function isEdgeCoverageVerdict(value) {
1120
+ return typeof value === "string" && EDGE_COVERAGE_VERDICTS.includes(value);
1121
+ }
1122
+ /**
1123
+ * Refuse an edge-coverage claim that does not say anything.
1124
+ *
1125
+ * `partial` means "some references are known to be missing", and a `partial`
1126
+ * with neither `dangling` nor `unresolvedKinds` names none of them — which is
1127
+ * `unknown` wearing a more confident word. Use `unknown` for "I cannot say";
1128
+ * `partial` is for "I can say, and here it is".
1129
+ */
1130
+ /**
1131
+ * A detached copy, frozen. The report's account of its own inputs must not
1132
+ * change after the report exists, and `readonly` buys nothing at runtime.
1133
+ */
1134
+ export function copyEdgeCoverage(coverage) {
1135
+ const copy = {
1136
+ verdict: coverage.verdict,
1137
+ ...(coverage.dangling ? { dangling: Object.freeze(coverage.dangling.map((d) => Object.freeze({ ...d }))) } : {}),
1138
+ ...(coverage.unresolvedKinds ? { unresolvedKinds: Object.freeze([...coverage.unresolvedKinds]) } : {}),
1139
+ ...(coverage.containmentEdges
1140
+ ? { containmentEdges: Object.freeze(coverage.containmentEdges.map((e) => Object.freeze({ ...e }))) }
1141
+ : {}),
1142
+ };
1143
+ return Object.freeze(copy);
1144
+ }
1145
+ export function validateEdgeCoverage(coverage) {
1146
+ if (!isEdgeCoverageVerdict(coverage?.verdict)) {
1147
+ throw new Error(`predictBehaviour: edgeCoverage.verdict ${JSON.stringify(coverage?.verdict)} is not ` +
1148
+ `${EDGE_COVERAGE_VERDICTS.join("/")}.`);
1149
+ }
1150
+ if (coverage.verdict === "partial" &&
1151
+ (coverage.dangling?.length ?? 0) === 0 &&
1152
+ (coverage.unresolvedKinds?.length ?? 0) === 0) {
1153
+ throw new Error('predictBehaviour: edgeCoverage is "partial" and names nothing missing. Populate `dangling` ' +
1154
+ 'or `unresolvedKinds`, or say "unknown" — a partial that lists no gap is an unknown with a ' +
1155
+ "more confident word on it.");
1156
+ }
1157
+ }
1158
+ /** How deep the credential walk goes before it stops descending. */
1159
+ /**
1160
+ * How deep the credential walk goes before it stops descending and says so.
1161
+ *
1162
+ * Budget the levels honestly: `options → entities → .get(name) → props` spends
1163
+ * three before a single declared property is reached, so the number here is not
1164
+ * the depth a lexicon author is thinking about. A Kubernetes workload tree
1165
+ * (`spec.template.spec.containers[].env[].valueFrom.secretKeyRef.key`) or a
1166
+ * nested CloudFormation `Properties` block runs to a dozen on its own, and
1167
+ * exceeding this is now a *refusal* rather than silence — so a budget set too
1168
+ * low refuses real estates instead of protecting them.
1169
+ */
1170
+ const CREDENTIAL_WALK_DEPTH = 32;
1171
+ /**
1172
+ * Every objection the walk has to a request. Pure, and exported so a caller can
1173
+ * decide what to do rather than take this module's word for it.
1174
+ */
1175
+ export function findCredentialsInOptions(options) {
1176
+ const findings = [];
1177
+ const seen = new WeakSet();
1178
+ const walk = (value, path, depth) => {
1179
+ if (depth > CREDENTIAL_WALK_DEPTH) {
1180
+ // Not silence. A request deeper than the walk goes is a request this
1181
+ // guard has not actually checked, and saying nothing about it is the
1182
+ // shape of failure this whole contract exists to refuse.
1183
+ findings.push({
1184
+ path,
1185
+ rule: "walk-depth",
1186
+ what: `a structure deeper than ${CREDENTIAL_WALK_DEPTH} levels, which the credential walk did not read`,
1187
+ });
1188
+ return;
1189
+ }
1190
+ if (typeof value === "string") {
1191
+ const shape = credentialValueShape(value);
1192
+ if (shape)
1193
+ findings.push({ path, rule: "value-shape", what: shape });
1194
+ return;
1195
+ }
1196
+ if (value === null || typeof value !== "object")
1197
+ return;
1198
+ if (seen.has(value))
1199
+ return;
1200
+ seen.add(value);
1201
+ if (Array.isArray(value)) {
1202
+ value.forEach((item, i) => walk(item, `${path}[${i}]`, depth + 1));
1203
+ return;
1204
+ }
1205
+ if (value instanceof Map) {
1206
+ for (const [key, item] of value) {
1207
+ const label = typeof key === "string" ? key : String(key);
1208
+ const child = `${path}.get(${label})`;
1209
+ // Both halves. A Map's KEY is as likely to be `DB_PASSWORD` as an
1210
+ // object's is — an env block is the obvious case — and walking only
1211
+ // values missed it entirely.
1212
+ if (typeof key === "string")
1213
+ checkKey(key, child, item);
1214
+ walk(item, child, depth + 1);
1215
+ }
1216
+ return;
1217
+ }
1218
+ if (value instanceof Set) {
1219
+ let i = 0;
1220
+ for (const item of value)
1221
+ walk(item, `${path}.item[${i++}]`, depth + 1);
1222
+ return;
1223
+ }
1224
+ for (const [key, item] of Object.entries(value)) {
1225
+ const child = `${path}.${key}`;
1226
+ checkKey(key, child, item);
1227
+ walk(item, child, depth + 1);
1228
+ }
1229
+ };
1230
+ /**
1231
+ * The name arm. Gated on the value being a string long enough to be a secret,
1232
+ * and not an evident reference to one — see {@link REFERENCE_KEY_SUFFIXES}
1233
+ * for why the ungated version was unusable.
1234
+ */
1235
+ function checkKey(key, path, item) {
1236
+ if (typeof item !== "string")
1237
+ return;
1238
+ if (item.length < MIN_CREDENTIAL_LENGTH)
1239
+ return;
1240
+ if (looksLikeSecretReference(item))
1241
+ return;
1242
+ if (!isCredentialKey(key))
1243
+ return;
1244
+ findings.push({ path, rule: "key-name", what: `a credential-shaped field name ("${key}")` });
1245
+ }
1246
+ walk(options, "options", 0);
1247
+ return findings;
1248
+ }
1249
+ /**
1250
+ * Throw when the request carries something that **is** a credential.
1251
+ *
1252
+ * **Not the entry point.** This applies one of the three rules and discards the
1253
+ * other two, so a `key-name` hit and a `walk-depth` hit both pass it silently.
1254
+ * Call {@link screenBehaviourRequest}, which runs this and then acts on what is
1255
+ * left. This stays exported because "did the request contain an actual token"
1256
+ * is a question worth asking on its own, and because the split is what keeps
1257
+ * the blast radii different.
1258
+ *
1259
+ * Only the value-shape rule throws, and throwing is deliberate: a live token in
1260
+ * a request bound for a third party is not a degradation to report, it is a
1261
+ * stop. Throwing is the whole-lexicon failure per `lexicon.ts`, which is the
1262
+ * right blast radius for this and the wrong one for a suspicious field name.
1263
+ */
1264
+ export function assertNoCredentialInOptions(options) {
1265
+ const found = findCredentialsInOptions(options).filter((f) => f.rule === "value-shape");
1266
+ if (found.length === 0)
1267
+ return;
1268
+ const [first] = found;
1269
+ throw new Error(`predictBehaviour was passed ${first.what} at ${first.path}. The behaviour engine is never handed ` +
1270
+ "a credential: it is given the resource graph and a traffic level, and nothing it receives can " +
1271
+ "reach the account. Remove it — a lexicon that needs authenticated access to its own engine " +
1272
+ "resolves that on its own transport, not through this contract.");
1273
+ }
1274
+ /**
1275
+ * Screen a whole request. **This is the entry point, and the only one.**
1276
+ *
1277
+ * This doc comment is the source of truth for the sequence; the module header
1278
+ * and the authoring page both point here rather than restating it. A lexicon's
1279
+ * `predictBehaviour` opens with exactly this and nothing else:
1280
+ *
1281
+ * ```ts
1282
+ * const refusal = screenBehaviourRequest("acme", options);
1283
+ * if (refusal) return refusal;
1284
+ * ```
1285
+ *
1286
+ * Calling {@link assertNoCredentialInOptions} instead applies one rule of three
1287
+ * and drops the rest, which is how a `ghp_…` token nested past the walk's depth
1288
+ * budget, and an `awsSecretAccessKey` in `props`, both sailed through into a
1289
+ * request that was then sent.
1290
+ *
1291
+ * Two outcomes, because two things are being caught and they deserve different
1292
+ * blast radii:
1293
+ *
1294
+ * - a value that IS a credential throws, via
1295
+ * {@link assertNoCredentialInOptions};
1296
+ * - a merely suspicious field name, or a structure too deep to have been read,
1297
+ * returns a {@link BehaviourRefusalReport} with cause
1298
+ * `credential-in-request`. No overlay is drawn and the reason names the
1299
+ * path, which is the contract's own answer to "no faked numbers" applied to
1300
+ * its own guard.
1301
+ *
1302
+ * The split exists because the name arm is a heuristic and the old version
1303
+ * threw on it. One `tags: { author: "…" }` anywhere in an estate killed the
1304
+ * entire overlay with a stack trace, which is the exact failure mode constraint
1305
+ * 2 was written against.
1306
+ *
1307
+ * Returns `undefined` when the request is clean, so a lexicon reads
1308
+ * `const refusal = screenBehaviourRequest(name, options); if (refusal) return refusal;`
1309
+ */
1310
+ export function screenBehaviourRequest(lexicon, options) {
1311
+ assertNoCredentialInOptions(options);
1312
+ const findings = findCredentialsInOptions(options);
1313
+ if (findings.length === 0)
1314
+ return undefined;
1315
+ const [first] = findings;
1316
+ const more = findings.length > 1 ? ` (and ${findings.length - 1} more)` : "";
1317
+ return behaviourRefusal({
1318
+ cause: "credential-in-request",
1319
+ reason: `The ${lexicon} behaviour request carries ${first.what} at ${first.path}${more}, so it was not ` +
1320
+ "sent. The engine is a third party and this contract hands it the resource graph and a traffic " +
1321
+ "level only. No overlay is drawn and no figure is guessed locally.",
1322
+ remedy: "Remove the value from the declaration, or hold it in a secret reference the build does not " +
1323
+ "expand. If the field is a false positive, it still leaves the process in this request.",
1324
+ });
1325
+ }