@intentius/chant 0.101.0 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (739) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +2 -0
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts.map +1 -1
  6. package/dist/audit/nginx.d.ts.map +1 -1
  7. package/dist/audit/prior-art.d.ts +48 -0
  8. package/dist/audit/prior-art.d.ts.map +1 -1
  9. package/dist/behaviour-kinds.d.ts.map +1 -1
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/change-set.d.ts +232 -0
  12. package/dist/change-set.d.ts.map +1 -0
  13. package/dist/cli/commands/audit.d.ts +7 -0
  14. package/dist/cli/commands/audit.d.ts.map +1 -1
  15. package/dist/cli/commands/build.d.ts.map +1 -1
  16. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  17. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  18. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  19. package/dist/cli/commands/import.d.ts +8 -0
  20. package/dist/cli/commands/import.d.ts.map +1 -1
  21. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  22. package/dist/cli/commands/init.d.ts +19 -0
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/onboard.d.ts +9 -0
  25. package/dist/cli/commands/onboard.d.ts.map +1 -1
  26. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  27. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  28. package/dist/cli/handlers/build.d.ts.map +1 -1
  29. package/dist/cli/handlers/change-set.d.ts +11 -0
  30. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts +33 -0
  33. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +16 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/pr.d.ts +31 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -0
  40. package/dist/cli/handlers/run.d.ts.map +1 -1
  41. package/dist/cli/lsp/server.d.ts.map +1 -1
  42. package/dist/cli/main.d.ts.map +1 -1
  43. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  44. package/dist/cli/mcp/server.d.ts.map +1 -1
  45. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  46. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  47. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  48. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  49. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  50. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  51. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  52. package/dist/cli/mcp/workspace-tools.d.ts +4 -3
  53. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  54. package/dist/cli/registry.d.ts +74 -3
  55. package/dist/cli/registry.d.ts.map +1 -1
  56. package/dist/codegen/fetch.d.ts.map +1 -1
  57. package/dist/codegen/naming.d.ts.map +1 -1
  58. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  59. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  60. package/dist/codegen/registry.d.ts.map +1 -1
  61. package/dist/components/auto-release.d.ts +16 -0
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  64. package/dist/components/capability.d.ts +53 -0
  65. package/dist/components/capability.d.ts.map +1 -1
  66. package/dist/components/changed-files.d.ts +31 -0
  67. package/dist/components/changed-files.d.ts.map +1 -0
  68. package/dist/components/cli-support.d.ts +9 -1
  69. package/dist/components/cli-support.d.ts.map +1 -1
  70. package/dist/components/component.d.ts +24 -0
  71. package/dist/components/component.d.ts.map +1 -1
  72. package/dist/components/deploy-units.d.ts.map +1 -1
  73. package/dist/components/driver.d.ts +13 -0
  74. package/dist/components/driver.d.ts.map +1 -1
  75. package/dist/components/fan-out-output.d.ts +6 -0
  76. package/dist/components/fan-out-output.d.ts.map +1 -1
  77. package/dist/components/fan-out-record.d.ts +41 -0
  78. package/dist/components/fan-out-record.d.ts.map +1 -0
  79. package/dist/components/fan-out-run.d.ts +65 -0
  80. package/dist/components/fan-out-run.d.ts.map +1 -1
  81. package/dist/components/fan-out-support.d.ts +2 -0
  82. package/dist/components/fan-out-support.d.ts.map +1 -1
  83. package/dist/components/fan-out.d.ts +6 -0
  84. package/dist/components/fan-out.d.ts.map +1 -1
  85. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  86. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  87. package/dist/components/index.d.ts +1 -1
  88. package/dist/components/index.d.ts.map +1 -1
  89. package/dist/components/layers.d.ts +25 -0
  90. package/dist/components/layers.d.ts.map +1 -0
  91. package/dist/components/pr-pipeline.d.ts +61 -0
  92. package/dist/components/pr-pipeline.d.ts.map +1 -0
  93. package/dist/components/pr-run.d.ts +119 -0
  94. package/dist/components/pr-run.d.ts.map +1 -0
  95. package/dist/components/promote.d.ts.map +1 -1
  96. package/dist/components/starter-plugin.d.ts.map +1 -1
  97. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  98. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  99. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  100. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  101. package/dist/components/verbs/shell.d.ts.map +1 -1
  102. package/dist/components/verbs/sign.d.ts.map +1 -1
  103. package/dist/components/verbs/verify.d.ts.map +1 -1
  104. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  105. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  106. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  107. package/dist/components/wave-plan.d.ts +49 -0
  108. package/dist/components/wave-plan.d.ts.map +1 -0
  109. package/dist/composite.d.ts.map +1 -1
  110. package/dist/config.d.ts +19 -7
  111. package/dist/config.d.ts.map +1 -1
  112. package/dist/declarable.d.ts +16 -0
  113. package/dist/declarable.d.ts.map +1 -1
  114. package/dist/discovery/cache.d.ts.map +1 -1
  115. package/dist/discovery/fold-import.d.ts +15 -5
  116. package/dist/discovery/fold-import.d.ts.map +1 -1
  117. package/dist/discovery/param-deps.d.ts +16 -0
  118. package/dist/discovery/param-deps.d.ts.map +1 -1
  119. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  120. package/dist/discovery/specifier-resolve.d.ts +37 -0
  121. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  122. package/dist/errors.d.ts.map +1 -1
  123. package/dist/fold/fold.d.ts +3 -1
  124. package/dist/fold/fold.d.ts.map +1 -1
  125. package/dist/gated-waves.d.ts +147 -0
  126. package/dist/gated-waves.d.ts.map +1 -0
  127. package/dist/graph-detail.d.ts.map +1 -1
  128. package/dist/graph-ir.d.ts.map +1 -1
  129. package/dist/graph-layout.d.ts.map +1 -1
  130. package/dist/import/base-parser.d.ts.map +1 -1
  131. package/dist/import/embedded.d.ts +7 -0
  132. package/dist/import/embedded.d.ts.map +1 -1
  133. package/dist/import/parser-options.d.ts +14 -0
  134. package/dist/import/parser-options.d.ts.map +1 -0
  135. package/dist/import/parser.d.ts +17 -0
  136. package/dist/import/parser.d.ts.map +1 -1
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/kubectl-context.d.ts.map +1 -1
  140. package/dist/lexicon-output.d.ts.map +1 -1
  141. package/dist/lexicon-schema.d.ts +3 -3
  142. package/dist/lexicon.d.ts +103 -3
  143. package/dist/lexicon.d.ts.map +1 -1
  144. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  145. package/dist/lifecycle/gate-ledger.d.ts +22 -0
  146. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/git.d.ts +46 -1
  148. package/dist/lifecycle/git.d.ts.map +1 -1
  149. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  150. package/dist/lifecycle/observe.d.ts.map +1 -1
  151. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/scenario.d.ts.map +1 -1
  154. package/dist/lifecycle/status.d.ts.map +1 -1
  155. package/dist/lifecycle/work-lease.d.ts +61 -2
  156. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  157. package/dist/lint/config.d.ts +8 -8
  158. package/dist/lint/engine.d.ts +6 -0
  159. package/dist/lint/engine.d.ts.map +1 -1
  160. package/dist/lint/post-synth.d.ts +18 -0
  161. package/dist/lint/post-synth.d.ts.map +1 -1
  162. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  163. package/dist/op/activities/activity-contracts.d.ts +137 -11
  164. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  165. package/dist/op/activities/apply.d.ts +79 -9
  166. package/dist/op/activities/apply.d.ts.map +1 -1
  167. package/dist/op/activities/change-set.d.ts +62 -0
  168. package/dist/op/activities/change-set.d.ts.map +1 -0
  169. package/dist/op/activities/decide.d.ts +2 -0
  170. package/dist/op/activities/decide.d.ts.map +1 -1
  171. package/dist/op/activities/factory.d.ts +137 -0
  172. package/dist/op/activities/factory.d.ts.map +1 -0
  173. package/dist/op/activities/index.d.ts +5 -1
  174. package/dist/op/activities/index.d.ts.map +1 -1
  175. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  176. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  177. package/dist/op/activity-contract.d.ts +1 -1
  178. package/dist/op/activity-contract.d.ts.map +1 -1
  179. package/dist/op/activity-failure.d.ts.map +1 -1
  180. package/dist/op/activity-profiles.d.ts.map +1 -1
  181. package/dist/op/builders.d.ts +11 -0
  182. package/dist/op/builders.d.ts.map +1 -1
  183. package/dist/op/composites/apply-op.d.ts +12 -5
  184. package/dist/op/composites/apply-op.d.ts.map +1 -1
  185. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  186. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  187. package/dist/op/decide-backend.d.ts +5 -1
  188. package/dist/op/decide-backend.d.ts.map +1 -1
  189. package/dist/op/decide-read-inputs.d.ts +4 -2
  190. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  191. package/dist/op/emulator-lifecycle.d.ts +21 -2
  192. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  193. package/dist/op/factory.d.ts +83 -0
  194. package/dist/op/factory.d.ts.map +1 -0
  195. package/dist/op/gate-approval.d.ts +5 -1
  196. package/dist/op/gate-approval.d.ts.map +1 -1
  197. package/dist/op/gate-wait.d.ts.map +1 -1
  198. package/dist/op/gate.d.ts +19 -3
  199. package/dist/op/gate.d.ts.map +1 -1
  200. package/dist/op/index.d.ts +6 -1
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/local-executor.d.ts +6 -0
  203. package/dist/op/local-executor.d.ts.map +1 -1
  204. package/dist/op/op-ir.d.ts +2 -0
  205. package/dist/op/op-ir.d.ts.map +1 -1
  206. package/dist/op/op-verb-class.d.ts.map +1 -1
  207. package/dist/op/operator.d.ts.map +1 -1
  208. package/dist/op/run-context.d.ts +39 -0
  209. package/dist/op/run-context.d.ts.map +1 -0
  210. package/dist/op/run-live.d.ts.map +1 -1
  211. package/dist/op/runtimes/local.d.ts.map +1 -1
  212. package/dist/op/source-archive.d.ts.map +1 -1
  213. package/dist/op/steward-beside.d.ts.map +1 -1
  214. package/dist/op/steward-points.d.ts.map +1 -1
  215. package/dist/op/steward.d.ts.map +1 -1
  216. package/dist/op/types.d.ts +16 -3
  217. package/dist/op/types.d.ts.map +1 -1
  218. package/dist/op/work-lease-run.d.ts +24 -4
  219. package/dist/op/work-lease-run.d.ts.map +1 -1
  220. package/dist/plan-summary.d.ts +207 -0
  221. package/dist/plan-summary.d.ts.map +1 -0
  222. package/dist/pr-forge.d.ts +105 -0
  223. package/dist/pr-forge.d.ts.map +1 -0
  224. package/dist/pr-loop.d.ts +178 -0
  225. package/dist/pr-loop.d.ts.map +1 -0
  226. package/dist/project-root.d.ts.map +1 -1
  227. package/dist/provenance.d.ts +21 -0
  228. package/dist/provenance.d.ts.map +1 -1
  229. package/dist/pseudo-parameter.d.ts.map +1 -1
  230. package/dist/reconcile.d.ts.map +1 -1
  231. package/dist/secret-materialization.d.ts.map +1 -1
  232. package/dist/telemetry-attribution.d.ts +31 -0
  233. package/dist/telemetry-attribution.d.ts.map +1 -1
  234. package/dist/terraform/emit-source.d.ts.map +1 -1
  235. package/dist/terraform/hcl2json.d.ts +45 -0
  236. package/dist/terraform/hcl2json.d.ts.map +1 -0
  237. package/dist/terraform/parse.d.ts +4 -40
  238. package/dist/terraform/parse.d.ts.map +1 -1
  239. package/dist/testing.d.ts.map +1 -1
  240. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  241. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  242. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  243. package/dist/workspace/agent-cli.d.ts +5 -0
  244. package/dist/workspace/agent-cli.d.ts.map +1 -1
  245. package/dist/workspace/box-cli.d.ts +12 -0
  246. package/dist/workspace/box-cli.d.ts.map +1 -0
  247. package/dist/workspace/box-factory.d.ts +97 -0
  248. package/dist/workspace/box-factory.d.ts.map +1 -0
  249. package/dist/workspace/box-listing.d.ts +85 -0
  250. package/dist/workspace/box-listing.d.ts.map +1 -0
  251. package/dist/workspace/box-publish.d.ts +147 -0
  252. package/dist/workspace/box-publish.d.ts.map +1 -0
  253. package/dist/workspace/broker-protocol.d.ts +190 -0
  254. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  255. package/dist/workspace/changes.d.ts.map +1 -1
  256. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  257. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  258. package/dist/workspace/checks.d.ts +5 -0
  259. package/dist/workspace/checks.d.ts.map +1 -1
  260. package/dist/workspace/compose-graph.d.ts +10 -0
  261. package/dist/workspace/compose-graph.d.ts.map +1 -1
  262. package/dist/workspace/composites.d.ts +2 -2
  263. package/dist/workspace/composites.d.ts.map +1 -1
  264. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  265. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  266. package/dist/workspace/conformance/broker.d.ts +202 -0
  267. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  268. package/dist/workspace/conformance/index.d.ts +68 -6
  269. package/dist/workspace/conformance/index.d.ts.map +1 -1
  270. package/dist/workspace/conformance/vitest.d.ts +19 -3
  271. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  272. package/dist/workspace/conformance/writer.d.ts +462 -0
  273. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  274. package/dist/workspace/decide.d.ts +126 -12
  275. package/dist/workspace/decide.d.ts.map +1 -1
  276. package/dist/workspace/decision-points.schema.json +11 -4
  277. package/dist/workspace/declaration.d.ts +156 -4
  278. package/dist/workspace/declaration.d.ts.map +1 -1
  279. package/dist/workspace/declaration.schema.json +383 -5
  280. package/dist/workspace/environments.d.ts +36 -10
  281. package/dist/workspace/environments.d.ts.map +1 -1
  282. package/dist/workspace/factory-rules.d.ts +180 -0
  283. package/dist/workspace/factory-rules.d.ts.map +1 -0
  284. package/dist/workspace/generated-files.d.ts.map +1 -1
  285. package/dist/workspace/graph-cache.d.ts.map +1 -1
  286. package/dist/workspace/graph-cli.d.ts +2 -0
  287. package/dist/workspace/graph-cli.d.ts.map +1 -1
  288. package/dist/workspace/identity.d.ts +111 -0
  289. package/dist/workspace/identity.d.ts.map +1 -0
  290. package/dist/workspace/init.d.ts +21 -0
  291. package/dist/workspace/init.d.ts.map +1 -1
  292. package/dist/workspace/intent-cli.d.ts +7 -4
  293. package/dist/workspace/intent-cli.d.ts.map +1 -1
  294. package/dist/workspace/intent-record.d.ts +36 -2
  295. package/dist/workspace/intent-record.d.ts.map +1 -1
  296. package/dist/workspace/intent.d.ts +249 -8
  297. package/dist/workspace/intent.d.ts.map +1 -1
  298. package/dist/workspace/json-edit.d.ts +24 -0
  299. package/dist/workspace/json-edit.d.ts.map +1 -0
  300. package/dist/workspace/jsonc.d.ts +11 -0
  301. package/dist/workspace/jsonc.d.ts.map +1 -1
  302. package/dist/workspace/kinds.d.ts +66 -4
  303. package/dist/workspace/kinds.d.ts.map +1 -1
  304. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  305. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  306. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  307. package/dist/workspace/lineage-lock.d.ts +2 -2
  308. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  309. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  310. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  311. package/dist/workspace/links.d.ts.map +1 -1
  312. package/dist/workspace/point-answer.schema.json +42 -2
  313. package/dist/workspace/points-cli.d.ts +10 -3
  314. package/dist/workspace/points-cli.d.ts.map +1 -1
  315. package/dist/workspace/points.d.ts +66 -5
  316. package/dist/workspace/points.d.ts.map +1 -1
  317. package/dist/workspace/principal-classes.d.ts +114 -0
  318. package/dist/workspace/principal-classes.d.ts.map +1 -0
  319. package/dist/workspace/reason-codes.d.ts +53 -4
  320. package/dist/workspace/reason-codes.d.ts.map +1 -1
  321. package/dist/workspace/record-assets.d.ts.map +1 -1
  322. package/dist/workspace/record-sessions.d.ts +5 -7
  323. package/dist/workspace/record-sessions.d.ts.map +1 -1
  324. package/dist/workspace/records-checkout.d.ts +54 -0
  325. package/dist/workspace/records-checkout.d.ts.map +1 -0
  326. package/dist/workspace/records-cli.d.ts +34 -1
  327. package/dist/workspace/records-cli.d.ts.map +1 -1
  328. package/dist/workspace/records-close.d.ts +7 -3
  329. package/dist/workspace/records-close.d.ts.map +1 -1
  330. package/dist/workspace/records-since.d.ts.map +1 -1
  331. package/dist/workspace/records-write.d.ts +68 -7
  332. package/dist/workspace/records-write.d.ts.map +1 -1
  333. package/dist/workspace/records.d.ts +43 -9
  334. package/dist/workspace/records.d.ts.map +1 -1
  335. package/dist/workspace/runs-cli.d.ts +296 -0
  336. package/dist/workspace/runs-cli.d.ts.map +1 -0
  337. package/dist/workspace/runs.d.ts +544 -0
  338. package/dist/workspace/runs.d.ts.map +1 -0
  339. package/dist/workspace/session-kinds.d.ts +1 -1
  340. package/dist/workspace/session-kinds.d.ts.map +1 -1
  341. package/dist/workspace/source-block.d.ts +1 -1
  342. package/dist/workspace/source-block.d.ts.map +1 -1
  343. package/dist/workspace/squash.d.ts +80 -0
  344. package/dist/workspace/squash.d.ts.map +1 -0
  345. package/dist/workspace/status-gates.d.ts +17 -3
  346. package/dist/workspace/status-gates.d.ts.map +1 -1
  347. package/dist/workspace/status.d.ts +64 -0
  348. package/dist/workspace/status.d.ts.map +1 -1
  349. package/dist/workspace/symbols.d.ts +63 -0
  350. package/dist/workspace/symbols.d.ts.map +1 -0
  351. package/dist/workspace/trailer-joins.d.ts +46 -0
  352. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  353. package/dist/workspace/trailers.d.ts +89 -0
  354. package/dist/workspace/trailers.d.ts.map +1 -0
  355. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  356. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  357. package/dist/workspace/trust/run-commits.d.ts +46 -0
  358. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  359. package/dist/workspace/trust/run-statement.d.ts +128 -0
  360. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  361. package/dist/workspace/trust/seal.d.ts +38 -0
  362. package/dist/workspace/trust/seal.d.ts.map +1 -1
  363. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  364. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  365. package/dist/workspace/trust/verify.d.ts +16 -2
  366. package/dist/workspace/trust/verify.d.ts.map +1 -1
  367. package/dist/workspace/wip-cli.d.ts +21 -0
  368. package/dist/workspace/wip-cli.d.ts.map +1 -0
  369. package/dist/workspace/wip.d.ts +268 -0
  370. package/dist/workspace/wip.d.ts.map +1 -0
  371. package/dist/workspace/work-cli.d.ts +39 -3
  372. package/dist/workspace/work-cli.d.ts.map +1 -1
  373. package/dist/workspace/work-evidence.d.ts +1 -1
  374. package/dist/workspace/work-evidence.d.ts.map +1 -1
  375. package/dist/workspace/work-size.d.ts +55 -0
  376. package/dist/workspace/work-size.d.ts.map +1 -0
  377. package/dist/workspace/work.d.ts +26 -1
  378. package/dist/workspace/work.d.ts.map +1 -1
  379. package/dist/workspace/workspace-kinds.schema.json +122 -0
  380. package/dist/workspace/workspace-principals.schema.json +60 -0
  381. package/dist/workspace/write-lock-cli.d.ts +60 -0
  382. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  383. package/dist/workspace/write-lock.d.ts +152 -0
  384. package/dist/workspace/write-lock.d.ts.map +1 -0
  385. package/dist/workspace/write-scope.d.ts +83 -19
  386. package/dist/workspace/write-scope.d.ts.map +1 -1
  387. package/dist/yaml.d.ts +21 -39
  388. package/dist/yaml.d.ts.map +1 -1
  389. package/package.json +7 -2
  390. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  391. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  392. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  393. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  394. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  395. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  396. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  397. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  398. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  399. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  400. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  401. package/src/audit/core.test.ts +15 -0
  402. package/src/audit/core.ts +8 -2
  403. package/src/audit/discover.test.ts +11 -0
  404. package/src/audit/discover.ts +7 -2
  405. package/src/audit/prior-art.ts +15 -0
  406. package/src/change-set-bundle.test.ts +57 -0
  407. package/src/change-set.test.ts +215 -0
  408. package/src/change-set.ts +445 -0
  409. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  410. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  411. package/src/cli/commands/audit.ts +29 -2
  412. package/src/cli/commands/build.test.ts +19 -0
  413. package/src/cli/commands/build.ts +3 -2
  414. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  415. package/src/cli/commands/carve-emit.ts +5 -19
  416. package/src/cli/commands/check-lexicon.test.ts +62 -1
  417. package/src/cli/commands/check-lexicon.ts +36 -6
  418. package/src/cli/commands/import-live.test.ts +18 -0
  419. package/src/cli/commands/import-parser-options.test.ts +133 -0
  420. package/src/cli/commands/import.ts +21 -3
  421. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  422. package/src/cli/commands/init.test.ts +8 -1
  423. package/src/cli/commands/init.ts +23 -8
  424. package/src/cli/commands/lint.test.ts +37 -0
  425. package/src/cli/commands/onboard.test.ts +53 -3
  426. package/src/cli/commands/onboard.ts +52 -4
  427. package/src/cli/commands/pinned-upgrade.ts +73 -33
  428. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  429. package/src/cli/handlers/build.ts +13 -1
  430. package/src/cli/handlers/change-set.test.ts +56 -0
  431. package/src/cli/handlers/change-set.ts +44 -0
  432. package/src/cli/handlers/components.ts +2 -2
  433. package/src/cli/handlers/dev.ts +8 -6
  434. package/src/cli/handlers/fan-out.test.ts +221 -2
  435. package/src/cli/handlers/fan-out.ts +165 -75
  436. package/src/cli/handlers/graph.ts +3 -0
  437. package/src/cli/handlers/lifecycle.test.ts +67 -0
  438. package/src/cli/handlers/lifecycle.ts +6 -5
  439. package/src/cli/handlers/misc.ts +2 -0
  440. package/src/cli/handlers/operator.test.ts +21 -0
  441. package/src/cli/handlers/operator.ts +121 -6
  442. package/src/cli/handlers/pr.ts +373 -0
  443. package/src/cli/handlers/run.test.ts +111 -0
  444. package/src/cli/handlers/run.ts +86 -24
  445. package/src/cli/handlers/scenario.ts +2 -2
  446. package/src/cli/main.test.ts +21 -0
  447. package/src/cli/main.ts +246 -30
  448. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  449. package/src/cli/mcp/op-tools.ts +24 -2
  450. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  451. package/src/cli/mcp/workspace-tools.ts +62 -7
  452. package/src/cli/registry.ts +74 -3
  453. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  454. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  455. package/src/codegen/pinned-upgrade.test.ts +104 -0
  456. package/src/codegen/pinned-upgrade.ts +250 -56
  457. package/src/codegen/testdata/postgres-tags.json +242 -0
  458. package/src/components/auto-release.ts +1 -1
  459. package/src/components/capability.ts +55 -0
  460. package/src/components/changed-files.ts +66 -0
  461. package/src/components/cli-support.ts +28 -5
  462. package/src/components/component.schema.json +17 -0
  463. package/src/components/component.ts +25 -0
  464. package/src/components/deploy-units.ts +3 -0
  465. package/src/components/driver.test.ts +52 -0
  466. package/src/components/driver.ts +31 -13
  467. package/src/components/fan-out-output.ts +42 -1
  468. package/src/components/fan-out-record.ts +69 -0
  469. package/src/components/fan-out-run.test.ts +17 -0
  470. package/src/components/fan-out-run.ts +304 -1
  471. package/src/components/fan-out-support.ts +3 -0
  472. package/src/components/fan-out-waves.test.ts +375 -0
  473. package/src/components/fan-out.ts +18 -17
  474. package/src/components/gated-wave-pipeline.ts +82 -0
  475. package/src/components/index.ts +1 -0
  476. package/src/components/layers.ts +30 -0
  477. package/src/components/pr-pipeline.ts +91 -0
  478. package/src/components/pr-run.test.ts +205 -0
  479. package/src/components/pr-run.ts +280 -0
  480. package/src/components/promote.ts +5 -0
  481. package/src/components/verbs/shell.test.ts +11 -0
  482. package/src/components/verbs/shell.ts +7 -3
  483. package/src/components/wave-plan.ts +124 -0
  484. package/src/config.test.ts +21 -0
  485. package/src/config.ts +15 -0
  486. package/src/declarable.test.ts +21 -0
  487. package/src/declarable.ts +19 -0
  488. package/src/discovery/fold-composite.test.ts +41 -5
  489. package/src/discovery/fold-import.test.ts +129 -0
  490. package/src/discovery/fold-import.ts +643 -131
  491. package/src/discovery/fold-package-composite.test.ts +264 -0
  492. package/src/discovery/fold-specifiers.test.ts +191 -0
  493. package/src/discovery/param-deps.test.ts +66 -1
  494. package/src/discovery/param-deps.ts +45 -0
  495. package/src/discovery/sandbox/bundle.ts +1 -1
  496. package/src/discovery/specifier-resolve.ts +344 -0
  497. package/src/fold/fold.test.ts +39 -1
  498. package/src/fold/fold.ts +83 -9
  499. package/src/fold-provenance.test.ts +140 -0
  500. package/src/gated-waves-bundle.test.ts +62 -0
  501. package/src/gated-waves.test.ts +118 -0
  502. package/src/gated-waves.ts +240 -0
  503. package/src/graph-ir.test.ts +83 -0
  504. package/src/graph-ir.ts +29 -0
  505. package/src/import/embedded.test.ts +20 -0
  506. package/src/import/embedded.ts +13 -0
  507. package/src/import/parser-options.ts +46 -0
  508. package/src/import/parser.ts +20 -0
  509. package/src/index.ts +1 -0
  510. package/src/lexicon.ts +106 -3
  511. package/src/lifecycle/gate-ledger.ts +23 -0
  512. package/src/lifecycle/git.ts +102 -16
  513. package/src/lifecycle/observe.test.ts +18 -0
  514. package/src/lifecycle/observe.ts +6 -4
  515. package/src/lifecycle/snapshot.ts +2 -2
  516. package/src/lifecycle/work-lease.test.ts +1 -1
  517. package/src/lifecycle/work-lease.ts +93 -2
  518. package/src/lint/engine.test.ts +55 -0
  519. package/src/lint/engine.ts +30 -4
  520. package/src/lint/output-docs.test.ts +59 -56
  521. package/src/lint/post-synth.test.ts +19 -0
  522. package/src/lint/post-synth.ts +19 -1
  523. package/src/meta/declared-imports.test.ts +1 -1
  524. package/src/op/activities/activity-contracts.ts +84 -0
  525. package/src/op/activities/apply.test.ts +189 -5
  526. package/src/op/activities/apply.ts +230 -8
  527. package/src/op/activities/change-set.ts +114 -0
  528. package/src/op/activities/decide.ts +3 -0
  529. package/src/op/activities/factory.ts +675 -0
  530. package/src/op/activities/index.ts +9 -0
  531. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  532. package/src/op/activities/lexicon-upgrade.ts +154 -51
  533. package/src/op/activity-contract.test.ts +21 -1
  534. package/src/op/activity-contract.ts +19 -9
  535. package/src/op/builders.ts +23 -0
  536. package/src/op/composites/apply-op.ts +12 -5
  537. package/src/op/composites/composites.test.ts +30 -3
  538. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  539. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  540. package/src/op/decide-backend.ts +5 -1
  541. package/src/op/decide-read-inputs.ts +33 -5
  542. package/src/op/emulator-lifecycle.test.ts +23 -0
  543. package/src/op/emulator-lifecycle.ts +52 -14
  544. package/src/op/factory.e2e.test.ts +232 -0
  545. package/src/op/factory.ts +118 -0
  546. package/src/op/gate-approval.test.ts +74 -0
  547. package/src/op/gate-approval.ts +5 -1
  548. package/src/op/gate.ts +31 -5
  549. package/src/op/index.ts +11 -1
  550. package/src/op/local-executor.test.ts +15 -0
  551. package/src/op/local-executor.ts +40 -12
  552. package/src/op/local-output.ts +1 -1
  553. package/src/op/op-ir.test.ts +9 -0
  554. package/src/op/op-ir.ts +4 -0
  555. package/src/op/op-verb-class.ts +3 -0
  556. package/src/op/operator.ts +4 -0
  557. package/src/op/run-context.test.ts +141 -0
  558. package/src/op/run-context.ts +51 -0
  559. package/src/op/runtimes/local.test.ts +14 -0
  560. package/src/op/runtimes/local.ts +2 -0
  561. package/src/op/steward-beside.ts +2 -0
  562. package/src/op/types.ts +16 -3
  563. package/src/op/work-lease-run.test.ts +47 -0
  564. package/src/op/work-lease-run.ts +76 -11
  565. package/src/plan-summary.test.ts +263 -0
  566. package/src/plan-summary.ts +845 -0
  567. package/src/pr-forge.test.ts +237 -0
  568. package/src/pr-forge.ts +332 -0
  569. package/src/pr-loop-bundle.test.ts +46 -0
  570. package/src/pr-loop.test.ts +171 -0
  571. package/src/pr-loop.ts +324 -0
  572. package/src/provenance.ts +35 -0
  573. package/src/telemetry-attribution.test.ts +23 -1
  574. package/src/telemetry-attribution.ts +51 -0
  575. package/src/terraform/hcl2json.test.ts +21 -0
  576. package/src/terraform/hcl2json.ts +81 -0
  577. package/src/terraform/parse.ts +11 -74
  578. package/src/workspace/agent-cli.ts +7 -3
  579. package/src/workspace/agent.schema.json +22 -0
  580. package/src/workspace/app-kind.test.ts +196 -0
  581. package/src/workspace/box-cli.ts +113 -0
  582. package/src/workspace/box-factory.test.ts +323 -0
  583. package/src/workspace/box-factory.ts +136 -0
  584. package/src/workspace/box-listing-write.schema.json +204 -0
  585. package/src/workspace/box-listing.test.ts +234 -0
  586. package/src/workspace/box-listing.ts +324 -0
  587. package/src/workspace/box-publish.schema.json +498 -0
  588. package/src/workspace/box-publish.test.ts +204 -0
  589. package/src/workspace/box-publish.ts +420 -0
  590. package/src/workspace/box-ship.test.ts +96 -0
  591. package/src/workspace/broker-protocol.schema.json +186 -0
  592. package/src/workspace/broker-protocol.test.ts +122 -0
  593. package/src/workspace/broker-protocol.ts +317 -0
  594. package/src/workspace/builder-tiers.test.ts +146 -0
  595. package/src/workspace/change-set.schema.json +460 -0
  596. package/src/workspace/changes.schema.json +12 -20
  597. package/src/workspace/changes.ts +4 -1
  598. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  599. package/src/workspace/checks/boxes.test.ts +5 -0
  600. package/src/workspace/checks.ts +39 -8
  601. package/src/workspace/component-environments.test.ts +105 -0
  602. package/src/workspace/compose-graph.ts +7 -0
  603. package/src/workspace/composites.schema.json +23 -4
  604. package/src/workspace/composites.test.ts +8 -8
  605. package/src/workspace/composites.ts +13 -2
  606. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  607. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  608. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  609. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  610. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  611. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  612. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  613. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  614. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  615. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  616. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  617. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  618. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  619. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  620. package/src/workspace/conformance/broker.test.ts +122 -0
  621. package/src/workspace/conformance/broker.ts +792 -0
  622. package/src/workspace/conformance/conformance.e2e.test.ts +36 -3
  623. package/src/workspace/conformance/index.mjs +51 -3
  624. package/src/workspace/conformance/index.ts +107 -15
  625. package/src/workspace/conformance/vitest.ts +76 -2
  626. package/src/workspace/conformance/writer-conformance.e2e.test.ts +177 -0
  627. package/src/workspace/conformance/writer.test.ts +186 -0
  628. package/src/workspace/conformance/writer.ts +1191 -0
  629. package/src/workspace/decide.test.ts +208 -4
  630. package/src/workspace/decide.ts +433 -73
  631. package/src/workspace/decision-points.schema.json +11 -4
  632. package/src/workspace/declaration.schema.json +383 -5
  633. package/src/workspace/declaration.ts +315 -10
  634. package/src/workspace/environments.ts +66 -14
  635. package/src/workspace/factory-rules.test.ts +62 -0
  636. package/src/workspace/factory-rules.ts +218 -0
  637. package/src/workspace/graph-cli.ts +6 -3
  638. package/src/workspace/graph.schema.json +314 -0
  639. package/src/workspace/identity.test.ts +219 -0
  640. package/src/workspace/identity.ts +186 -0
  641. package/src/workspace/init.ts +82 -2
  642. package/src/workspace/intent-cli.ts +84 -13
  643. package/src/workspace/intent-patch-id.test.ts +162 -0
  644. package/src/workspace/intent-record.schema.json +336 -1
  645. package/src/workspace/intent-record.ts +96 -16
  646. package/src/workspace/intent-reviews.test.ts +76 -0
  647. package/src/workspace/intent-squash.test.ts +213 -0
  648. package/src/workspace/intent-why.test.ts +274 -0
  649. package/src/workspace/intent.schema.json +950 -9
  650. package/src/workspace/intent.test.ts +11 -2
  651. package/src/workspace/intent.ts +722 -65
  652. package/src/workspace/json-edit.test.ts +121 -0
  653. package/src/workspace/json-edit.ts +142 -0
  654. package/src/workspace/jsonc.ts +20 -0
  655. package/src/workspace/kinds.ts +201 -39
  656. package/src/workspace/lexicon-symbols.test.ts +148 -0
  657. package/src/workspace/lexicon-symbols.ts +125 -0
  658. package/src/workspace/lineage-upgrade.test.ts +63 -0
  659. package/src/workspace/lineage-upgrade.ts +146 -5
  660. package/src/workspace/plan-summary.schema.json +155 -0
  661. package/src/workspace/point-answer.schema.json +42 -2
  662. package/src/workspace/points-cli.ts +42 -16
  663. package/src/workspace/points-write.schema.json +163 -4
  664. package/src/workspace/points.schema.json +157 -2
  665. package/src/workspace/points.test.ts +13 -1
  666. package/src/workspace/points.ts +118 -12
  667. package/src/workspace/pr-report.schema.json +95 -0
  668. package/src/workspace/principal-classes.test.ts +175 -0
  669. package/src/workspace/principal-classes.ts +307 -0
  670. package/src/workspace/profiles.test.ts +159 -0
  671. package/src/workspace/read-contract.test.ts +2 -2
  672. package/src/workspace/reason-codes.test.ts +23 -3
  673. package/src/workspace/reason-codes.ts +61 -4
  674. package/src/workspace/record-sessions.ts +9 -8
  675. package/src/workspace/records-amend.schema.json +80 -0
  676. package/src/workspace/records-checkout.ts +106 -0
  677. package/src/workspace/records-cli.ts +123 -17
  678. package/src/workspace/records-close.schema.json +79 -0
  679. package/src/workspace/records-close.ts +31 -7
  680. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  681. package/src/workspace/records-concurrent.test.ts +145 -0
  682. package/src/workspace/records-formats.test.ts +46 -10
  683. package/src/workspace/records-new.schema.json +9 -0
  684. package/src/workspace/records-review.schema.json +80 -0
  685. package/src/workspace/records-sessions-write.test.ts +69 -0
  686. package/src/workspace/records-sessions.test.ts +22 -0
  687. package/src/workspace/records-uncommitted.test.ts +171 -0
  688. package/src/workspace/records-write.ts +219 -30
  689. package/src/workspace/records.schema.json +88 -1
  690. package/src/workspace/records.ts +76 -8
  691. package/src/workspace/reference-kinds/app/package.json +10 -0
  692. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  693. package/src/workspace/run-statement.schema.json +622 -0
  694. package/src/workspace/runs-cli.ts +710 -0
  695. package/src/workspace/runs-statement.test.ts +239 -0
  696. package/src/workspace/runs-write.schema.json +994 -0
  697. package/src/workspace/runs.schema.json +1257 -0
  698. package/src/workspace/runs.test.ts +281 -0
  699. package/src/workspace/runs.ts +970 -0
  700. package/src/workspace/session-kinds.ts +3 -3
  701. package/src/workspace/squash.ts +198 -0
  702. package/src/workspace/status-contract.test.ts +10 -2
  703. package/src/workspace/status-gates.ts +25 -9
  704. package/src/workspace/status.schema.json +252 -4
  705. package/src/workspace/status.ts +110 -1
  706. package/src/workspace/symbols.ts +191 -0
  707. package/src/workspace/trailer-joins.test.ts +171 -0
  708. package/src/workspace/trailer-joins.ts +82 -0
  709. package/src/workspace/trailers.ts +148 -0
  710. package/src/workspace/trust/run-commits.ts +115 -0
  711. package/src/workspace/trust/run-statement.ts +226 -0
  712. package/src/workspace/trust/seal.ts +58 -1
  713. package/src/workspace/trust/verify-cli.ts +6 -5
  714. package/src/workspace/trust/verify.ts +39 -6
  715. package/src/workspace/wip-cli.ts +97 -0
  716. package/src/workspace/wip-write.schema.json +504 -0
  717. package/src/workspace/wip.schema.json +334 -0
  718. package/src/workspace/wip.test.ts +294 -0
  719. package/src/workspace/wip.ts +753 -0
  720. package/src/workspace/work-cli.e2e.test.ts +28 -1
  721. package/src/workspace/work-cli.ts +136 -8
  722. package/src/workspace/work-evidence.schema.json +5 -0
  723. package/src/workspace/work-history.schema.json +73 -1
  724. package/src/workspace/work-history.test.ts +48 -5
  725. package/src/workspace/work-kind-fields.test.ts +154 -0
  726. package/src/workspace/work-lease.schema.json +14 -1
  727. package/src/workspace/work-size.test.ts +77 -0
  728. package/src/workspace/work-size.ts +93 -0
  729. package/src/workspace/work.ts +82 -0
  730. package/src/workspace/workspace-kinds.schema.json +122 -0
  731. package/src/workspace/workspace-principals.schema.json +60 -0
  732. package/src/workspace/write-lock-cli.ts +131 -0
  733. package/src/workspace/write-lock.schema.json +172 -0
  734. package/src/workspace/write-lock.test.ts +189 -0
  735. package/src/workspace/write-lock.ts +474 -0
  736. package/src/workspace/write-scope.test.ts +144 -3
  737. package/src/workspace/write-scope.ts +192 -45
  738. package/src/yaml.test.ts +111 -1
  739. package/src/yaml.ts +141 -549
@@ -0,0 +1,792 @@
1
+ /**
2
+ * Broker conformance (#3164, ws-097): the suite a broker of box capabilities
3
+ * runs, such as studio's lobby, fountain's broker or a self-hosted one, to
4
+ * show it serves the broker protocol (`../broker-protocol.ts`).
5
+ *
6
+ * The suite starts the upstreams a broker forwards to: a stand-in for the
7
+ * Anthropic API, one for Fountain's API, and an echo host for egress. It
8
+ * then calls `start(env)`, which starts the broker under test pointed at
9
+ * them, holding `env`'s credentials and secrets, and knowing the boxes
10
+ * `env.boxes` names. The broker returns its base URL and each box's token.
11
+ * The first box reports a declaration before each check; the second never
12
+ * reports. Every request a check makes is the box's, with its token, and
13
+ * every request an upstream receives is recorded.
14
+ *
15
+ * What it holds a broker to, per capability: a declared scope word is
16
+ * served, with the box's token swapped for the credential; an undeclared one
17
+ * is a 403 whose message names the capability and the word; a box that never
18
+ * reported is refused everything; and, over the whole run, no upstream ever
19
+ * saw a box's token and no answer to a box carried a credential or a secret.
20
+ *
21
+ * This module imports no test runner. {@link runBrokerConformance} returns
22
+ * the problems; `describeBrokerConformance` in `./vitest` makes one test per
23
+ * check. The caller passes `listen`, which serves the upstreams: chant
24
+ * itself never listens on a port (ws-052). chant's own test of this suite
25
+ * (`./broker.test.ts`) holds the smallest broker that passes.
26
+ */
27
+
28
+ import { randomBytes } from "node:crypto";
29
+ import { readFileSync } from "node:fs";
30
+ import type { IncomingMessage, ServerResponse } from "node:http";
31
+ import { createRequire } from "node:module";
32
+ import { dirname, join } from "node:path";
33
+ import { fileURLToPath } from "node:url";
34
+ import { isDeepStrictEqual } from "node:util";
35
+ import { BROKER_PROTOCOL_SCHEMA, BROKER_ROUTES, formatPayerHeader, PAYER_HEADER, parsePayerHeader, payerOf, refusalMessage, type BrokerCapabilitySpec } from "../broker-protocol";
36
+
37
+ /** The capabilities the suite knows how to exercise, in the order it runs them. */
38
+ export const BROKER_CONFORMANCE_CAPABILITIES = ["inference", "egress", "feedback", "fountain"] as const;
39
+ export type BrokerConformanceCapability = (typeof BROKER_CONFORMANCE_CAPABILITIES)[number];
40
+
41
+ /** What a studio lobby serves today; the default for `capabilities`. */
42
+ export const LOBBY_CAPABILITIES: readonly BrokerConformanceCapability[] = ["inference", "egress", "feedback"];
43
+
44
+ /** The box that reports its declaration, and the box that never does. */
45
+ export const CONFORMANCE_BOXES = ["conformance-a", "conformance-b"] as const;
46
+
47
+ /** The secret the first box may use through egress, and one it declares that the broker holds no value for. */
48
+ export const CONFORMANCE_SECRET = "CONFORMANCE";
49
+ export const CONFORMANCE_UNHELD_SECRET = "CONFORMANCE_UNHELD";
50
+
51
+ /** What the suite gives the broker under test. */
52
+ export interface BrokerConformanceEnv {
53
+ /** The broker word the boxes' declarations name, such as `lobby`. The broker keeps only entries naming it. */
54
+ broker: string;
55
+ /** The boxes the broker must know, each with a token it hands back in {@link StartedBroker.tokens}. */
56
+ boxes: readonly string[];
57
+ /** The Anthropic API stand-in: the broker forwards `/llm/anthropic` and asks `/decide` questions here, spending `credential`. */
58
+ anthropic: { url: string; credential: string };
59
+ /** Fountain's API stand-in: the broker forwards `/fountain` here with `credential` as the bearer. */
60
+ fountain: { url: string; credential: string };
61
+ /** The secrets the broker holds: each box's value for a name, and the one host it may be sent to. */
62
+ secrets: readonly { box: string; name: string; host: string; value: string }[];
63
+ }
64
+
65
+ /** The broker under test, started. */
66
+ export interface StartedBroker {
67
+ /** Its base URL, as a box is given it. */
68
+ url: string;
69
+ /** Each box's own token, by the names in {@link BrokerConformanceEnv.boxes}. */
70
+ tokens: Record<string, string>;
71
+ close?(): Promise<void> | void;
72
+ }
73
+
74
+ /** A capability a lexicon or runtime adds, and how the suite asks for one of its scope words. */
75
+ export interface ExtraCapability {
76
+ spec: BrokerCapabilitySpec;
77
+ /** Scope words to check, each served when declared and refused when not. */
78
+ words: readonly string[];
79
+ /** A request that needs exactly `word`. */
80
+ probe(word: string): { method: string; path: string; headers?: Record<string, string>; body?: unknown };
81
+ }
82
+
83
+ export interface BrokerConformanceConfig {
84
+ /** The broker's name in the report. */
85
+ name: string;
86
+ start(env: BrokerConformanceEnv): Promise<StartedBroker> | StartedBroker;
87
+ /** Serves the suite's upstreams on loopback ports; see {@link BrokerListen}. */
88
+ listen: BrokerListen;
89
+ /** The broker word, `lobby` when omitted. */
90
+ broker?: string;
91
+ /** The standard capabilities it serves, {@link LOBBY_CAPABILITIES} when omitted. The rest are reported as skipped. */
92
+ capabilities?: readonly BrokerConformanceCapability[];
93
+ /** Capabilities beyond the standard ones, held to the same rule. */
94
+ extra?: readonly ExtraCapability[];
95
+ /** How long one request may take, 15 s when omitted. */
96
+ timeoutMs?: number;
97
+ }
98
+
99
+ /** One check's outcome. */
100
+ export interface BrokerCheckResult {
101
+ id: string;
102
+ capability: string;
103
+ title: string;
104
+ problems: string[];
105
+ /** Why it did not run, when it did not. */
106
+ skipped?: string;
107
+ }
108
+
109
+ export interface BrokerConformanceReport {
110
+ name: string;
111
+ broker: string;
112
+ results: BrokerCheckResult[];
113
+ /** Every problem, each as `<check id>: <problem>`. Empty when the broker conforms. */
114
+ problems: string[];
115
+ }
116
+
117
+ /** One request an upstream received. */
118
+ export interface UpstreamRequest {
119
+ upstream: "anthropic" | "fountain" | "egress";
120
+ method: string;
121
+ /** The path and query as received. */
122
+ url: string;
123
+ headers: Record<string, string | string[] | undefined>;
124
+ body: string;
125
+ }
126
+
127
+ /** One answer the broker gave a box. */
128
+ export interface BoxAnswer {
129
+ status: number;
130
+ headers: Record<string, string>;
131
+ text: string;
132
+ json: unknown;
133
+ }
134
+
135
+ export interface BrokerCheck {
136
+ id: string;
137
+ capability: BrokerConformanceCapability | "declaration" | "all" | string;
138
+ title: string;
139
+ run(ctx: BrokerConformanceContext): Promise<string[]>;
140
+ }
141
+
142
+ /** What a check runs with: the env, the broker, every upstream request and every answer so far, and helpers. */
143
+ export interface BrokerConformanceContext {
144
+ env: BrokerConformanceEnv;
145
+ broker: StartedBroker;
146
+ served: readonly string[];
147
+ upstream: UpstreamRequest[];
148
+ answers: BoxAnswer[];
149
+ call(box: string | null, method: string, path: string, opts?: { headers?: Record<string, string>; body?: unknown; token?: string; auth?: "bearer" | "x-api-key" | "none" }): Promise<BoxAnswer>;
150
+ report(capabilities: { name: string; broker: string | null; scope: string[] }[]): Promise<BoxAnswer>;
151
+ validate(def: string, value: unknown): string[];
152
+ }
153
+
154
+ const HERE = dirname(fileURLToPath(import.meta.url));
155
+
156
+ export type BrokerSchemaValidate = ((value: unknown) => boolean) & { errors?: { instancePath: string; message?: string }[] | null };
157
+ let validators: ((def: string) => BrokerSchemaValidate) | undefined;
158
+
159
+ /** A validator for one `$defs` entry of the protocol's schema, from this package's own ajv 8. */
160
+ export function brokerSchemaValidator(def: string): BrokerSchemaValidate {
161
+ if (!validators) {
162
+ const schema = JSON.parse(readFileSync(join(HERE, "..", BROKER_PROTOCOL_SCHEMA), "utf-8")) as { $id: string };
163
+ const mod = createRequire(import.meta.url)("ajv/dist/2020") as { default?: unknown };
164
+ const Ajv = (mod.default ?? mod) as new (opts: object) => { addSchema(s: object): void; getSchema(ref: string): BrokerSchemaValidate | undefined };
165
+ const ajv = new Ajv({ strict: true, allErrors: true, allowUnionTypes: true });
166
+ ajv.addSchema(schema);
167
+ validators = (name) => {
168
+ const v = ajv.getSchema(`${schema.$id}#/$defs/${name}`);
169
+ if (!v) throw new Error(`${BROKER_PROTOCOL_SCHEMA} has no $defs/${name}`);
170
+ return v;
171
+ };
172
+ }
173
+ return validators(def);
174
+ }
175
+
176
+ const hex = (n = 16) => randomBytes(n).toString("hex");
177
+
178
+ async function readBody(req: IncomingMessage): Promise<string> {
179
+ let raw = "";
180
+ for await (const chunk of req) raw += chunk;
181
+ return raw;
182
+ }
183
+
184
+ /** A request handler, as `node:http`'s `createServer` takes one. */
185
+ export type BrokerHttpHandler = (req: IncomingMessage, res: ServerResponse) => void;
186
+
187
+ /**
188
+ * How the suite serves its upstreams: serve `handler` on a loopback port and
189
+ * return its URL and how to stop it. The caller passes it because chant
190
+ * itself never listens on a port (ws-052, ws-086, test/no-listener.test.ts);
191
+ * the test that runs the suite does.
192
+ */
193
+ export type BrokerListen = (handler: BrokerHttpHandler) => Promise<{ url: string; close(): Promise<void> | void }>;
194
+
195
+ /** An instance of a JSON Schema, enough to answer a structured-output request: the first enum, an even split of numbers. */
196
+ export function schemaInstance(schema: unknown): unknown {
197
+ const s = (schema ?? {}) as { type?: unknown; enum?: unknown[]; const?: unknown; properties?: Record<string, unknown>; items?: unknown };
198
+ if ("const" in s) return s.const;
199
+ if (Array.isArray(s.enum) && s.enum.length > 0) return s.enum[0];
200
+ const type = Array.isArray(s.type) ? s.type[0] : s.type;
201
+ if (type === "object" || s.properties) {
202
+ const props = Object.entries(s.properties ?? {});
203
+ const numbers = props.filter(([, p]) => (p as { type?: unknown })?.type === "number").length;
204
+ return Object.fromEntries(props.map(([k, p]) => [k, (p as { type?: unknown })?.type === "number" ? Math.round((1 / Math.max(numbers, 1)) * 1e6) / 1e6 : schemaInstance(p)]));
205
+ }
206
+ if (type === "array") return [];
207
+ if (type === "number" || type === "integer") return 0.5;
208
+ if (type === "boolean") return true;
209
+ if (type === "string") return "The conformance upstream's reason.";
210
+ return null;
211
+ }
212
+
213
+ /** The Anthropic API stand-in: a Messages API answer, structured when the request asks for a schema. */
214
+ function anthropicAnswer(req: UpstreamRequest, res: ServerResponse): void {
215
+ const path = req.url.split("?")[0];
216
+ const send = (status: number, value: unknown) => {
217
+ res.writeHead(status, { "content-type": "application/json", "request-id": "req_conformance" });
218
+ res.end(JSON.stringify(value));
219
+ };
220
+ if (req.method === "POST" && path === "/v1/messages") {
221
+ let body: { model?: string; output_config?: { format?: { schema?: unknown } } } = {};
222
+ try {
223
+ body = JSON.parse(req.body);
224
+ } catch {}
225
+ const schema = body.output_config?.format?.schema;
226
+ const text = schema ? JSON.stringify(schemaInstance(schema)) : "conformance";
227
+ return send(200, { id: "msg_conformance", type: "message", role: "assistant", model: body.model ?? "claude-conformance", content: [{ type: "text", text }], stop_reason: "end_turn", usage: { input_tokens: 1, output_tokens: 1 } });
228
+ }
229
+ if (req.method === "POST" && path === "/v1/messages/count_tokens") return send(200, { input_tokens: 1 });
230
+ if (req.method === "GET" && path.startsWith("/v1/models")) return send(200, { data: [{ id: "claude-conformance", type: "model" }] });
231
+ send(404, { type: "error", error: { type: "not_found_error", message: "not here" } });
232
+ }
233
+
234
+ /** The upstreams, recording every request. */
235
+ /** The upstreams' handlers, each recording every request it receives in `log`. */
236
+ export function upstreamHandlers(log: UpstreamRequest[]): { anthropic: BrokerHttpHandler; fountain: BrokerHttpHandler; egress: BrokerHttpHandler } {
237
+ const make =
238
+ (upstream: UpstreamRequest["upstream"], answer: (r: UpstreamRequest, res: ServerResponse) => void): BrokerHttpHandler =>
239
+ (req, res) => {
240
+ void readBody(req).then((body) => {
241
+ const r: UpstreamRequest = { upstream, method: req.method ?? "GET", url: req.url ?? "/", headers: { ...req.headers }, body };
242
+ log.push(r);
243
+ answer(r, res);
244
+ });
245
+ };
246
+ const [anthropic, fountain, egress] = [
247
+ make("anthropic", anthropicAnswer),
248
+ make("fountain", (r, res) => {
249
+ res.writeHead(200, { "content-type": "application/json" });
250
+ res.end(JSON.stringify({ path: r.url }));
251
+ }),
252
+ // The egress host repeats the authorization it was sent in a header, as an API that echoes its key would.
253
+ make("egress", (r, res) => {
254
+ res.writeHead(200, { "content-type": "application/json", "x-conformance-echo": String(r.headers.authorization ?? "") });
255
+ res.end(JSON.stringify({ path: r.url }));
256
+ }),
257
+ ];
258
+ return { anthropic, fountain, egress };
259
+ }
260
+
261
+ async function startUpstreams(log: UpstreamRequest[], listen: BrokerListen): Promise<{ anthropic: string; fountain: string; egress: string; close(): Promise<void> }> {
262
+ const handlers = upstreamHandlers(log);
263
+ const served = await Promise.all([listen(handlers.anthropic), listen(handlers.fountain), listen(handlers.egress)]);
264
+ return {
265
+ anthropic: served[0].url,
266
+ fountain: served[1].url,
267
+ egress: served[2].url,
268
+ close: async () => {
269
+ await Promise.all(served.map((s) => s.close()));
270
+ },
271
+ };
272
+ }
273
+
274
+ /** The problems with a refusal: not `status`, not a refusal body, or a message that does not name each of `names`. */
275
+ function refusalProblems(answer: BoxAnswer, names: string[], status: number | number[] = 403): string[] {
276
+ const ok = [status].flat();
277
+ const problems: string[] = [];
278
+ if (!ok.includes(answer.status)) problems.push(`expected ${ok.join(" or ")}, got ${answer.status}: ${answer.text.slice(0, 200)}`);
279
+ const message = refusalMessage(answer.json);
280
+ if (message === undefined) problems.push(`the refusal's body is not { "error": "<message>" } or { "error": { "message" } }: ${answer.text.slice(0, 200)}`);
281
+ else for (const n of names) if (!message.includes(n)) problems.push(`the refusal's message does not name ${n}: ${message}`);
282
+ return problems;
283
+ }
284
+
285
+ function untouched(ctx: BrokerConformanceContext, before: number, what: string): string[] {
286
+ const after = ctx.upstream.slice(before);
287
+ return after.length === 0 ? [] : [`${what}, and the broker forwarded it anyway: ${after.map((r) => `${r.upstream} ${r.method} ${r.url}`).join(", ")}`];
288
+ }
289
+
290
+ const declare = (ctx: BrokerConformanceContext, caps: [string, string[]][], others: { name: string; broker: string | null; scope: string[] }[] = []) =>
291
+ ctx.report([...caps.map(([name, scope]) => ({ name, broker: ctx.env.broker, scope })), ...others]);
292
+
293
+ async function declared(ctx: BrokerConformanceContext, caps: [string, string[]][]): Promise<string[]> {
294
+ const r = await declare(ctx, caps);
295
+ return r.status === 200 ? [] : [`reporting the declaration failed with ${r.status}: ${r.text.slice(0, 200)}`];
296
+ }
297
+
298
+ const A = CONFORMANCE_BOXES[0];
299
+ const B = CONFORMANCE_BOXES[1];
300
+
301
+ const MESSAGE = { model: "claude-conformance", max_tokens: 16, messages: [{ role: "user", content: "conformance" }] };
302
+ const DECIDE = {
303
+ model: "claude-conformance",
304
+ state: { change: "conformance" },
305
+ questions: {
306
+ "conformance-tier": { type: "choice", instructions: "Pick the tier.", criteria: { small: "a small change", large: "a large change" } },
307
+ "conformance-ship": { type: "noul", instructions: "Ship it?", criteria: { true: "ship", false: "hold" } },
308
+ },
309
+ };
310
+
311
+ /** Every check, in the order it runs. */
312
+ export const BROKER_CHECKS: readonly BrokerCheck[] = [
313
+ {
314
+ id: "declaration-kept",
315
+ capability: "declaration",
316
+ title: "a report is answered 200 with the entries that name this broker, and none that name another or no broker",
317
+ async run(ctx) {
318
+ const r = await declare(ctx, [["inference", ["agent", "decide"]]], [{ name: "elsewhere", broker: `not-${ctx.env.broker}`, scope: ["x"] }, { name: "unbrokered", broker: null, scope: ["x"] }]);
319
+ if (r.status !== 200) return [`expected 200, got ${r.status}: ${r.text.slice(0, 200)}`];
320
+ const problems = ctx.validate("declarationKept", r.json);
321
+ const kept = (r.json as { capabilities?: { name: string; scope: string[] }[] }).capabilities ?? [];
322
+ if (kept.some((c) => c.name === "elsewhere")) problems.push("it kept an entry that names another broker");
323
+ if (kept.some((c) => c.name === "unbrokered")) problems.push("it kept an entry that names no broker");
324
+ const inference = kept.filter((c) => c.name === "inference");
325
+ if (inference.length !== 1) problems.push(`it kept inference ${inference.length} times`);
326
+ else if (!isDeepStrictEqual([...inference[0].scope].sort(), ["agent", "decide"])) problems.push(`it kept inference's scope as ${JSON.stringify(inference[0].scope)}, not agent and decide`);
327
+ return problems;
328
+ },
329
+ },
330
+ {
331
+ id: "declaration-unknown-token",
332
+ capability: "declaration",
333
+ title: "a report with no token, or one that is no box's, is refused with a 401 or 403",
334
+ async run(ctx) {
335
+ const body = { capabilities: [] };
336
+ const problems: string[] = [];
337
+ for (const [what, token, auth] of [["no token", undefined, "none"], ["an unknown token", `unknown-${hex()}`, "bearer"]] as const) {
338
+ const r = await ctx.call(null, "POST", BROKER_ROUTES.declaration, { body, token, auth });
339
+ problems.push(...refusalProblems(r, [], [401, 403]).map((p) => `${what}: ${p}`));
340
+ }
341
+ return problems;
342
+ },
343
+ },
344
+ {
345
+ id: "declaration-malformed",
346
+ capability: "declaration",
347
+ title: "a report that is not { capabilities: [...] } is refused with a 400",
348
+ async run(ctx) {
349
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.declaration, { body: { capability: [] } });
350
+ return refusalProblems(r, [], 400);
351
+ },
352
+ },
353
+ {
354
+ id: "unreported-refused",
355
+ capability: "declaration",
356
+ title: "a box that has never reported is refused every capability with a 403",
357
+ async run(ctx) {
358
+ const probes: [string, string, string, object?][] = [];
359
+ if (ctx.served.includes("inference")) probes.push(["inference agent", "POST", `${BROKER_ROUTES.inference}/v1/messages`, MESSAGE], ["inference decide", "POST", BROKER_ROUTES.decide, DECIDE]);
360
+ if (ctx.served.includes("egress")) probes.push(["egress", "GET", `${BROKER_ROUTES.egress}/${CONFORMANCE_SECRET}/v1/echo`]);
361
+ if (ctx.served.includes("feedback")) probes.push(["feedback", "POST", BROKER_ROUTES.feedback, { entries: [{ note: "conformance" }] }]);
362
+ if (ctx.served.includes("fountain")) probes.push(["fountain", "GET", `${BROKER_ROUTES.fountain}/api/conversations`]);
363
+ const problems: string[] = [];
364
+ const before = ctx.upstream.length;
365
+ for (const [what, method, path, body] of probes) {
366
+ const r = await ctx.call(B, method, path, { body, auth: what === "inference agent" ? "x-api-key" : "bearer" });
367
+ problems.push(...refusalProblems(r, []).map((p) => `${what}: ${p}`));
368
+ }
369
+ return [...problems, ...untouched(ctx, before, "the box never reported")];
370
+ },
371
+ },
372
+ {
373
+ id: "inference-hello",
374
+ capability: "inference",
375
+ title: "HEAD /llm/anthropic/api/hello is answered 200 without a token, as the Anthropic client probes it",
376
+ async run(ctx) {
377
+ const r = await ctx.call(null, "HEAD", `${BROKER_ROUTES.inference}/api/hello`, { auth: "none" });
378
+ return r.status === 200 ? [] : [`expected 200, got ${r.status}`];
379
+ },
380
+ },
381
+ {
382
+ id: "inference-forwards",
383
+ capability: "inference",
384
+ title: "with agent declared, a Messages call reaches the upstream unchanged with the credential in place of the box's token, and its answer comes back",
385
+ async run(ctx) {
386
+ const problems = await declared(ctx, [["inference", ["agent"]]]);
387
+ const before = ctx.upstream.length;
388
+ const r = await ctx.call(A, "POST", `${BROKER_ROUTES.inference}/v1/messages?beta=true`, { body: MESSAGE, auth: "x-api-key", headers: { "anthropic-version": "2023-06-01" } });
389
+ if (r.status !== 200) return [...problems, `expected 200, got ${r.status}: ${r.text.slice(0, 200)}`];
390
+ const sent = ctx.upstream.slice(before).filter((u) => u.upstream === "anthropic");
391
+ if (sent.length !== 1) return [...problems, `the upstream received ${sent.length} requests, not 1`];
392
+ const [u] = sent;
393
+ if (u.method !== "POST" || u.url !== "/v1/messages?beta=true") problems.push(`the upstream received ${u.method} ${u.url}, not POST /v1/messages?beta=true`);
394
+ const credential = ctx.env.anthropic.credential;
395
+ if (u.headers["x-api-key"] !== credential && u.headers.authorization !== `Bearer ${credential}`) problems.push("the upstream request did not carry the credential as x-api-key or a bearer");
396
+ let body: unknown;
397
+ try {
398
+ body = JSON.parse(u.body);
399
+ } catch {}
400
+ if (!isDeepStrictEqual(body, MESSAGE)) problems.push("the upstream received a different body than the box sent");
401
+ if ((r.json as { id?: unknown })?.id !== "msg_conformance") problems.push("the answer is not the upstream's");
402
+ return problems;
403
+ },
404
+ },
405
+ {
406
+ id: "inference-unknown-route",
407
+ capability: "inference",
408
+ title: "a path the proxy does not forward is a 404 and reaches no upstream",
409
+ async run(ctx) {
410
+ const problems = await declared(ctx, [["inference", ["agent"]]]);
411
+ const before = ctx.upstream.length;
412
+ const r = await ctx.call(A, "POST", `${BROKER_ROUTES.inference}/v1/complete`, { body: MESSAGE, auth: "x-api-key" });
413
+ if (r.status !== 404) problems.push(`expected 404, got ${r.status}`);
414
+ return [...problems, ...untouched(ctx, before, "the path is not one the proxy forwards")];
415
+ },
416
+ },
417
+ {
418
+ id: "inference-unknown-token",
419
+ capability: "inference",
420
+ title: "a Messages call with no box's token is a 403, never a 401 (the Anthropic client retries a 401), and reaches no upstream",
421
+ async run(ctx) {
422
+ const before = ctx.upstream.length;
423
+ const r = await ctx.call(null, "POST", `${BROKER_ROUTES.inference}/v1/messages`, { body: MESSAGE, token: `unknown-${hex()}`, auth: "x-api-key" });
424
+ return [...refusalProblems(r, []), ...untouched(ctx, before, "the token is no box's")];
425
+ },
426
+ },
427
+ {
428
+ id: "inference-agent-refused",
429
+ capability: "inference",
430
+ title: "without agent in inference's scope, a Messages call is a 403 naming inference and agent",
431
+ async run(ctx) {
432
+ const problems = await declared(ctx, [["inference", ["decide"]]]);
433
+ const before = ctx.upstream.length;
434
+ const r = await ctx.call(A, "POST", `${BROKER_ROUTES.inference}/v1/messages`, { body: MESSAGE, auth: "x-api-key" });
435
+ return [...problems, ...refusalProblems(r, ["inference", "agent"]), ...untouched(ctx, before, "agent is not declared")];
436
+ },
437
+ },
438
+ {
439
+ id: "decide-answers",
440
+ capability: "inference",
441
+ title: "with decide declared, /decide/v1/systemone answers every question in the wire format, naming the model asked",
442
+ async run(ctx) {
443
+ const problems = await declared(ctx, [["inference", ["decide"]]]);
444
+ const before = ctx.upstream.length;
445
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.decide, { body: DECIDE });
446
+ if (r.status !== 200) return [...problems, `expected 200, got ${r.status}: ${r.text.slice(0, 200)}`];
447
+ problems.push(...ctx.validate("decideResponse", r.json));
448
+ const answer = r.json as { model?: string; answers?: Record<string, { type?: string }> };
449
+ if (answer.model !== DECIDE.model) problems.push(`the answer names the model ${answer.model}, not ${DECIDE.model} as asked`);
450
+ for (const [name, q] of Object.entries(DECIDE.questions)) {
451
+ const a = answer.answers?.[name];
452
+ if (!a) problems.push(`no answer for ${name}`);
453
+ else if (a.type !== q.type && a.type !== "unsupported") problems.push(`${name} is a ${q.type} question and was answered ${a.type}`);
454
+ }
455
+ const credential = ctx.env.anthropic.credential;
456
+ for (const u of ctx.upstream.slice(before).filter((u) => u.upstream === "anthropic")) {
457
+ if (u.headers["x-api-key"] !== credential && u.headers.authorization !== `Bearer ${credential}`) problems.push(`the upstream call ${u.method} ${u.url} did not carry the credential`);
458
+ }
459
+ return problems;
460
+ },
461
+ },
462
+ {
463
+ id: "decide-refused",
464
+ capability: "inference",
465
+ title: "without decide in inference's scope, /decide is a 403 naming inference and decide",
466
+ async run(ctx) {
467
+ const problems = await declared(ctx, [["inference", ["agent"]]]);
468
+ const before = ctx.upstream.length;
469
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.decide, { body: DECIDE });
470
+ return [...problems, ...refusalProblems(r, ["inference", "decide"]), ...untouched(ctx, before, "decide is not declared")];
471
+ },
472
+ },
473
+ {
474
+ id: "decide-invalid",
475
+ capability: "inference",
476
+ title: "a decide request that is not in the wire format is a 422 and asks no model",
477
+ async run(ctx) {
478
+ const problems = await declared(ctx, [["inference", ["decide"]]]);
479
+ const before = ctx.upstream.length;
480
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.decide, { body: { model: DECIDE.model, state: {}, questions: {} } });
481
+ return [...problems, ...refusalProblems(r, [], 422), ...untouched(ctx, before, "the request names no question")];
482
+ },
483
+ },
484
+ {
485
+ id: "decide-unknown-token",
486
+ capability: "inference",
487
+ title: "a decide request with no box's token is a 401 or 403",
488
+ async run(ctx) {
489
+ const r = await ctx.call(null, "POST", BROKER_ROUTES.decide, { body: DECIDE, token: `unknown-${hex()}` });
490
+ return refusalProblems(r, [], [401, 403]);
491
+ },
492
+ },
493
+ {
494
+ id: "egress-forwards",
495
+ capability: "egress",
496
+ title: "with the secret declared, /egress/NAME/... reaches only the secret's host with the value in place of the box's token, and the value never comes back",
497
+ async run(ctx) {
498
+ const problems = await declared(ctx, [["egress", [CONFORMANCE_SECRET]]]);
499
+ const before = ctx.upstream.length;
500
+ const token = ctx.broker.tokens[A];
501
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.egress}/${CONFORMANCE_SECRET}/v1/echo?q=1`);
502
+ if (r.status !== 200) return [...problems, `expected 200, got ${r.status}: ${r.text.slice(0, 200)}`];
503
+ const sent = ctx.upstream.slice(before);
504
+ if (sent.length !== 1 || sent[0].upstream !== "egress") return [...problems, `expected one request to the secret's host, got ${sent.map((u) => `${u.upstream} ${u.url}`).join(", ") || "none"}`];
505
+ const value = ctx.env.secrets.find((s) => s.box === A && s.name === CONFORMANCE_SECRET)!.value;
506
+ if (sent[0].url !== "/v1/echo?q=1") problems.push(`the host received ${sent[0].url}, not /v1/echo?q=1`);
507
+ if (sent[0].headers.authorization !== `Bearer ${value}`) problems.push("the host did not receive the secret's value where the box put its token");
508
+ if (r.headers["x-conformance-echo"] !== `Bearer ${token}`) problems.push("the host's header repeating the value did not come back to the box with its token in the value's place");
509
+ return problems;
510
+ },
511
+ },
512
+ {
513
+ id: "egress-refused",
514
+ capability: "egress",
515
+ title: "a secret the egress scope does not list is a 403 naming egress and the secret, and reaches no host",
516
+ async run(ctx) {
517
+ const problems = await declared(ctx, [["egress", ["CONFORMANCE_OTHER"]]]);
518
+ const before = ctx.upstream.length;
519
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.egress}/${CONFORMANCE_SECRET}/v1/echo`);
520
+ return [...problems, ...refusalProblems(r, ["egress", CONFORMANCE_SECRET]), ...untouched(ctx, before, "the secret is not declared")];
521
+ },
522
+ },
523
+ {
524
+ id: "egress-unheld",
525
+ capability: "egress",
526
+ title: "a declared secret the broker holds no value for is a 403, and reaches no host",
527
+ async run(ctx) {
528
+ const problems = await declared(ctx, [["egress", [CONFORMANCE_SECRET, CONFORMANCE_UNHELD_SECRET]]]);
529
+ const before = ctx.upstream.length;
530
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.egress}/${CONFORMANCE_UNHELD_SECRET}/v1/echo`);
531
+ return [...problems, ...refusalProblems(r, [CONFORMANCE_UNHELD_SECRET]), ...untouched(ctx, before, "the broker holds no value for the secret")];
532
+ },
533
+ },
534
+ {
535
+ id: "egress-bad-name",
536
+ capability: "egress",
537
+ title: "an egress path whose name is not a secret's name is a 404",
538
+ async run(ctx) {
539
+ const problems = await declared(ctx, [["egress", [CONFORMANCE_SECRET]]]);
540
+ const before = ctx.upstream.length;
541
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.egress}/conformance/v1/echo`);
542
+ if (r.status !== 404) problems.push(`expected 404, got ${r.status}`);
543
+ return [...problems, ...untouched(ctx, before, "the path names no secret")];
544
+ },
545
+ },
546
+ {
547
+ id: "feedback-taken",
548
+ capability: "feedback",
549
+ title: "with agent in feedback's scope, a batch of entries passes the declaration: not a 401 or 403",
550
+ async run(ctx) {
551
+ const problems = await declared(ctx, [["feedback", ["agent"]]]);
552
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.feedback, { body: { entries: [{ note: "conformance" }] } });
553
+ if (r.status === 401 || r.status === 403) problems.push(`the batch was refused with ${r.status}: ${r.text.slice(0, 200)}`);
554
+ return problems;
555
+ },
556
+ },
557
+ {
558
+ id: "feedback-refused",
559
+ capability: "feedback",
560
+ title: "counts without passive in feedback's scope are a 403 naming feedback and passive",
561
+ async run(ctx) {
562
+ const problems = await declared(ctx, [["feedback", ["agent"]]]);
563
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.feedback, { body: { counts: { refusals: 1 } } });
564
+ return [...problems, ...refusalProblems(r, ["feedback", "passive"])];
565
+ },
566
+ },
567
+ {
568
+ id: "feedback-empty",
569
+ capability: "feedback",
570
+ title: "a batch with neither entries nor counts is a 400",
571
+ async run(ctx) {
572
+ const problems = await declared(ctx, [["feedback", ["agent", "passive"]]]);
573
+ const r = await ctx.call(A, "POST", BROKER_ROUTES.feedback, { body: {} });
574
+ return [...problems, ...refusalProblems(r, [], 400)];
575
+ },
576
+ },
577
+ {
578
+ id: "fountain-forwards",
579
+ capability: "fountain",
580
+ title: "with conversations declared, /fountain/api/conversations/... reaches Fountain with the operator's key in place of the box's token",
581
+ async run(ctx) {
582
+ const problems = await declared(ctx, [["fountain", ["conversations"]]]);
583
+ const before = ctx.upstream.length;
584
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.fountain}/api/conversations/conformance`);
585
+ if (r.status !== 200) return [...problems, `expected 200, got ${r.status}: ${r.text.slice(0, 200)}`];
586
+ const sent = ctx.upstream.slice(before);
587
+ if (sent.length !== 1 || sent[0].upstream !== "fountain") return [...problems, `expected one request to Fountain, got ${sent.length}`];
588
+ if (sent[0].url !== "/api/conversations/conformance") problems.push(`Fountain received ${sent[0].url}`);
589
+ if (sent[0].headers.authorization !== `Bearer ${ctx.env.fountain.credential}`) problems.push("Fountain did not receive the operator's key as the bearer");
590
+ return problems;
591
+ },
592
+ },
593
+ {
594
+ id: "fountain-refused",
595
+ capability: "fountain",
596
+ title: "without sandboxes in fountain's scope, /fountain/api/sandboxes is a 403 naming fountain and sandboxes",
597
+ async run(ctx) {
598
+ const problems = await declared(ctx, [["fountain", ["conversations"]]]);
599
+ const before = ctx.upstream.length;
600
+ const r = await ctx.call(A, "GET", `${BROKER_ROUTES.fountain}/api/sandboxes`);
601
+ return [...problems, ...refusalProblems(r, ["fountain", "sandboxes"]), ...untouched(ctx, before, "sandboxes is not declared")];
602
+ },
603
+ },
604
+ {
605
+ id: "payer-consistent",
606
+ capability: "inference",
607
+ title: "a payer, when the broker says one (#3474), is well formed and the same on the declaration's answer and on a Messages answer's chant-payer header",
608
+ async run(ctx) {
609
+ const r = await declare(ctx, [["inference", ["agent"]]]);
610
+ if (r.status !== 200) return [`reporting the declaration failed with ${r.status}: ${r.text.slice(0, 200)}`];
611
+ const problems: string[] = [];
612
+ const raw = (r.json as { payer?: unknown } | null)?.payer;
613
+ const kept = payerOf(r.json);
614
+ if (raw !== undefined && !kept) problems.push(`the declaration's answer carries a payer the protocol does not know: ${JSON.stringify(raw)}`);
615
+ const m = await ctx.call(A, "POST", `${BROKER_ROUTES.inference}/v1/messages`, { body: MESSAGE, auth: "x-api-key", headers: { "anthropic-version": "2023-06-01" } });
616
+ if (m.status !== 200) return [...problems, `expected 200 from a Messages call, got ${m.status}: ${m.text.slice(0, 200)}`];
617
+ const header = m.headers[PAYER_HEADER];
618
+ const relayed = parsePayerHeader(header);
619
+ if (header !== undefined && !relayed) problems.push(`the ${PAYER_HEADER} header names no payer the protocol knows: ${JSON.stringify(header)}`);
620
+ if ((raw === undefined) !== (header === undefined)) problems.push(`the payer is said on ${raw === undefined ? `the ${PAYER_HEADER} header` : "the declaration's answer"} only; a broker that says it says it on both`);
621
+ else if (kept && relayed && (kept.kind !== relayed.kind || (kept.principal ?? null) !== (relayed.principal ?? null))) {
622
+ problems.push(`the declaration's answer says ${formatPayerHeader(kept)} and the Messages answer says ${formatPayerHeader(relayed)}`);
623
+ }
624
+ return problems;
625
+ },
626
+ },
627
+ ];
628
+
629
+ /** The last check: over every request of the run, no box token went upstream and no credential or secret came back. */
630
+ const ISOLATION: BrokerCheck = {
631
+ id: "token-isolation",
632
+ capability: "all",
633
+ title: "no upstream ever received a box's token, and no answer to a box carried a credential or a secret's value",
634
+ async run(ctx) {
635
+ const problems: string[] = [];
636
+ const tokens = Object.values(ctx.broker.tokens);
637
+ for (const u of ctx.upstream) {
638
+ const seen = [u.url, u.body, ...Object.values(u.headers).flat().map(String)].join("\n");
639
+ for (const t of tokens) if (seen.includes(t)) problems.push(`${u.upstream} received a box's token in ${u.method} ${u.url}`);
640
+ }
641
+ const secrets = [ctx.env.anthropic.credential, ctx.env.fountain.credential, ...ctx.env.secrets.map((s) => s.value)];
642
+ for (const a of ctx.answers) {
643
+ const seen = [a.text, ...Object.values(a.headers)].join("\n");
644
+ for (const s of secrets) if (seen.includes(s)) problems.push(`an answer to a box (status ${a.status}) carried a credential or a secret's value`);
645
+ }
646
+ return [...new Set(problems)];
647
+ },
648
+ };
649
+
650
+ function extraChecks(extra: ExtraCapability): BrokerCheck[] {
651
+ return extra.words.flatMap((word): BrokerCheck[] => [
652
+ {
653
+ id: `${extra.spec.name}-${word}-served`,
654
+ capability: extra.spec.name,
655
+ title: `with ${word} in ${extra.spec.name}'s scope, a request that needs it is not refused by the declaration`,
656
+ async run(ctx) {
657
+ const problems = await declared(ctx, [[extra.spec.name, [word]]]);
658
+ const p = extra.probe(word);
659
+ const r = await ctx.call(A, p.method, p.path, { headers: p.headers, body: p.body });
660
+ if (r.status === 401 || r.status === 403) problems.push(`refused with ${r.status}: ${r.text.slice(0, 200)}`);
661
+ return problems;
662
+ },
663
+ },
664
+ {
665
+ id: `${extra.spec.name}-${word}-refused`,
666
+ capability: extra.spec.name,
667
+ title: `without ${word} in ${extra.spec.name}'s scope, the request is a 403 naming ${extra.spec.name} and ${word}`,
668
+ async run(ctx) {
669
+ const problems = await declared(ctx, [[extra.spec.name, []]]);
670
+ const p = extra.probe(word);
671
+ const r = await ctx.call(A, p.method, p.path, { headers: p.headers, body: p.body });
672
+ return [...problems, ...refusalProblems(r, [extra.spec.name, word])];
673
+ },
674
+ },
675
+ ]);
676
+ }
677
+
678
+ /** The checks a config runs, and the ones it skips with why. */
679
+ export function brokerChecks(config: Pick<BrokerConformanceConfig, "capabilities" | "extra">): { check: BrokerCheck; skipped?: string }[] {
680
+ const served = config.capabilities ?? LOBBY_CAPABILITIES;
681
+ return [
682
+ ...BROKER_CHECKS.map((check) => ({
683
+ check,
684
+ skipped: check.capability === "declaration" || served.includes(check.capability as BrokerConformanceCapability) ? undefined : `the broker does not serve ${check.capability}`,
685
+ })),
686
+ ...(config.extra ?? []).flatMap(extraChecks).map((check) => ({ check })),
687
+ { check: ISOLATION },
688
+ ];
689
+ }
690
+
691
+ /** A run of the suite: the upstreams and the broker started, and a context to run checks in. Close it when done. */
692
+ export async function startBrokerConformance(config: BrokerConformanceConfig): Promise<{ ctx: BrokerConformanceContext; close(): Promise<void> }> {
693
+ const upstream: UpstreamRequest[] = [];
694
+ const answers: BoxAnswer[] = [];
695
+ const ups = await startUpstreams(upstream, config.listen);
696
+ const broker = config.broker ?? "lobby";
697
+ const env: BrokerConformanceEnv = {
698
+ broker,
699
+ boxes: CONFORMANCE_BOXES,
700
+ anthropic: { url: ups.anthropic, credential: `sk-ant-api03-conformance-${hex(24)}` },
701
+ fountain: { url: ups.fountain, credential: `fountain-conformance-${hex(24)}` },
702
+ secrets: [{ box: A, name: CONFORMANCE_SECRET, host: ups.egress, value: `conformance-secret-${hex(24)}` }],
703
+ };
704
+ let started: StartedBroker;
705
+ try {
706
+ started = await config.start(env);
707
+ } catch (err) {
708
+ await ups.close();
709
+ throw err;
710
+ }
711
+ for (const box of CONFORMANCE_BOXES) if (!started.tokens[box]) {
712
+ await started.close?.();
713
+ await ups.close();
714
+ throw new Error(`the broker returned no token for ${box}`);
715
+ }
716
+ const timeoutMs = config.timeoutMs ?? 15_000;
717
+ const base = started.url.replace(/\/+$/, "");
718
+ const ctx: BrokerConformanceContext = {
719
+ env,
720
+ broker: started,
721
+ served: config.capabilities ?? LOBBY_CAPABILITIES,
722
+ upstream,
723
+ answers,
724
+ async call(box, method, path, opts = {}) {
725
+ const token = opts.token ?? (box ? started.tokens[box] : undefined);
726
+ const headers: Record<string, string> = { ...(opts.headers ?? {}) };
727
+ const auth = opts.auth ?? "bearer";
728
+ if (token && auth === "bearer") headers.authorization = `Bearer ${token}`;
729
+ if (token && auth === "x-api-key") headers["x-api-key"] = token;
730
+ let body: string | undefined;
731
+ if (opts.body !== undefined) {
732
+ body = JSON.stringify(opts.body);
733
+ headers["content-type"] ??= "application/json";
734
+ }
735
+ const res = await fetch(`${base}${path}`, { method, headers, body, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
736
+ const text = method === "HEAD" ? "" : await res.text();
737
+ let json: unknown;
738
+ try {
739
+ json = text ? JSON.parse(text) : undefined;
740
+ } catch {}
741
+ const got: Record<string, string> = {};
742
+ res.headers.forEach((v, k) => {
743
+ got[k] = v;
744
+ });
745
+ const answer: BoxAnswer = { status: res.status, headers: got, text, json };
746
+ answers.push(answer);
747
+ return answer;
748
+ },
749
+ report(capabilities) {
750
+ return ctx.call(A, "POST", BROKER_ROUTES.declaration, { body: { capabilities } });
751
+ },
752
+ validate(def, value) {
753
+ const v = brokerSchemaValidator(def);
754
+ return v(value) ? [] : (v.errors ?? []).map((e) => `the body does not match ${def}: ${e.instancePath || "/"} ${e.message ?? ""}`.trim());
755
+ },
756
+ };
757
+ return {
758
+ ctx,
759
+ async close() {
760
+ await started.close?.();
761
+ await ups.close();
762
+ },
763
+ };
764
+ }
765
+
766
+ /** Run one check, catching what it throws as its problem. */
767
+ export async function runBrokerCheck(ctx: BrokerConformanceContext, check: BrokerCheck): Promise<string[]> {
768
+ try {
769
+ return await check.run(ctx);
770
+ } catch (err) {
771
+ return [`the check threw: ${err instanceof Error ? err.message : String(err)}`];
772
+ }
773
+ }
774
+
775
+ /** Run the whole suite against a broker, and return the report. */
776
+ export async function runBrokerConformance(config: BrokerConformanceConfig): Promise<BrokerConformanceReport> {
777
+ const run = await startBrokerConformance(config);
778
+ const results: BrokerCheckResult[] = [];
779
+ try {
780
+ for (const { check, skipped } of brokerChecks(config)) {
781
+ results.push({ id: check.id, capability: check.capability, title: check.title, problems: skipped ? [] : await runBrokerCheck(run.ctx, check), ...(skipped ? { skipped } : {}) });
782
+ }
783
+ } finally {
784
+ await run.close();
785
+ }
786
+ return {
787
+ name: config.name,
788
+ broker: config.broker ?? "lobby",
789
+ results,
790
+ problems: results.flatMap((r) => r.problems.map((p) => `${r.id}: ${p}`)),
791
+ };
792
+ }