okengine 0.2.6 → 0.3.2

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 (681) hide show
  1. package/AGENTS.md +33 -30
  2. package/README.md +74 -340
  3. package/{spec/manifest.v1.schema.json → manifest.v1.schema.json} +40 -9
  4. package/package.json +47 -44
  5. package/site/content/docs/ai/llms-txt.mdx +54 -0
  6. package/site/content/docs/ai/mcp.mdx +123 -0
  7. package/site/content/docs/ai/meta.json +5 -0
  8. package/site/content/docs/ai/skills.mdx +53 -0
  9. package/site/content/docs/console/access.mdx +29 -0
  10. package/site/content/docs/console/ai.mdx +35 -0
  11. package/site/content/docs/console/architecture.mdx +35 -0
  12. package/site/content/docs/console/channels.mdx +37 -0
  13. package/site/content/docs/console/clock.mdx +31 -0
  14. package/site/content/docs/console/flows.mdx +31 -0
  15. package/site/content/docs/console/gates.mdx +35 -0
  16. package/site/content/docs/console/manifest-diff.mdx +34 -0
  17. package/site/content/docs/console/meta.json +23 -0
  18. package/site/content/docs/console/overview.mdx +40 -0
  19. package/site/content/docs/console/plugins.mdx +41 -0
  20. package/site/content/docs/console/privacy.mdx +32 -0
  21. package/site/content/docs/console/runs.mdx +40 -0
  22. package/site/content/docs/console/signals.mdx +31 -0
  23. package/site/content/docs/console/store.mdx +32 -0
  24. package/site/content/docs/console/tenancy.mdx +32 -0
  25. package/site/content/docs/console/traces.mdx +34 -0
  26. package/site/content/docs/console/vault.mdx +37 -0
  27. package/site/content/docs/elements/ai.mdx +180 -0
  28. package/site/content/docs/elements/channel.mdx +167 -0
  29. package/site/content/docs/elements/clock.mdx +182 -0
  30. package/site/content/docs/elements/flow.mdx +288 -0
  31. package/site/content/docs/elements/gate.mdx +171 -0
  32. package/site/content/docs/elements/meta.json +5 -0
  33. package/site/content/docs/elements/signal.mdx +171 -0
  34. package/site/content/docs/elements/store.mdx +320 -0
  35. package/site/content/docs/elements/vault.mdx +263 -0
  36. package/site/content/docs/get-started/basic-usage.mdx +124 -0
  37. package/site/content/docs/get-started/comparison.mdx +65 -0
  38. package/site/content/docs/get-started/installation.mdx +113 -0
  39. package/site/content/docs/get-started/introduction.mdx +123 -0
  40. package/site/content/docs/get-started/meta.json +5 -0
  41. package/site/content/docs/index.mdx +63 -0
  42. package/site/content/docs/meta.json +5 -0
  43. package/site/content/docs/plugins/compression.mdx +60 -0
  44. package/site/content/docs/plugins/cors.mdx +92 -0
  45. package/site/content/docs/plugins/csrf.mdx +96 -0
  46. package/site/content/docs/plugins/ip-allowlist.mdx +92 -0
  47. package/site/content/docs/plugins/maintenance-mode.mdx +101 -0
  48. package/site/content/docs/plugins/meta.json +15 -0
  49. package/site/content/docs/plugins/security-headers.mdx +136 -0
  50. package/site/content/docs/reference/cli.md +101 -0
  51. package/site/content/docs/reference/configuration.mdx +159 -0
  52. package/site/content/docs/reference/environment-variables.mdx +87 -0
  53. package/site/content/docs/reference/errors.mdx +80 -0
  54. package/site/content/docs/reference/fx.mdx +117 -0
  55. package/site/content/docs/reference/meta.json +5 -0
  56. package/site/content/docs/reference/plugins.mdx +249 -0
  57. package/site/content/docs/reference/security.md +63 -0
  58. package/src/auth/api-keys.ts +2 -5
  59. package/src/auth/attenuation.ts +2 -6
  60. package/src/auth/auth.test.ts +56 -31
  61. package/src/auth/invoke-as.ts +1 -3
  62. package/src/auth/operator.ts +8 -26
  63. package/src/auth/planes.ts +2 -7
  64. package/src/auth/plugin.ts +4 -7
  65. package/src/auth/roles.ts +1 -5
  66. package/src/auth/sessions.ts +10 -39
  67. package/src/cli/args.ts +1 -5
  68. package/src/cli/ask-dev-mode.ts +40 -0
  69. package/src/cli/build.ts +1 -2
  70. package/src/cli/client-add.test.ts +8 -15
  71. package/src/cli/client-add.ts +5 -14
  72. package/src/cli/completion.test.ts +1 -5
  73. package/src/cli/completion.ts +4 -17
  74. package/src/cli/db-auto-push.test.ts +64 -0
  75. package/src/cli/db-auto-push.ts +72 -0
  76. package/src/cli/db.test.ts +189 -0
  77. package/src/cli/db.ts +456 -0
  78. package/src/cli/dev-app-runner.ts +2 -8
  79. package/src/cli/dev-db-push.test.ts +193 -0
  80. package/src/cli/dev-mode.test.ts +45 -0
  81. package/src/cli/dev-mode.ts +78 -0
  82. package/src/cli/dev-schema-sync.test.ts +66 -0
  83. package/src/cli/dev-schema-sync.ts +139 -0
  84. package/src/cli/dev.test.ts +264 -15
  85. package/src/cli/dev.ts +355 -139
  86. package/src/cli/doc-staleness.test.ts +69 -0
  87. package/src/cli/docker-cli.test.ts +27 -8
  88. package/src/cli/docker.ts +11 -3
  89. package/src/cli/doctor-diff.test.ts +1 -5
  90. package/src/cli/doctor-diff.ts +17 -46
  91. package/src/cli/doctor-pii.ts +2 -7
  92. package/src/cli/doctor.test.ts +26 -4
  93. package/src/cli/doctor.ts +35 -11
  94. package/src/cli/drizzle-env.test.ts +67 -0
  95. package/src/cli/drizzle-env.ts +78 -0
  96. package/src/cli/ensure-drizzle-config.ts +50 -0
  97. package/src/cli/eval.ts +4 -14
  98. package/src/cli/gates-list.ts +1 -4
  99. package/src/cli/hero-meta.test.ts +17 -19
  100. package/src/cli/hero-meta.ts +20 -30
  101. package/src/cli/images.ts +3 -8
  102. package/src/cli/index.ts +11 -2
  103. package/src/cli/load-config.images.test.ts +14 -14
  104. package/src/cli/load-config.ts +47 -47
  105. package/src/cli/mcp-from-console.ts +1 -4
  106. package/src/cli/mode.ts +65 -0
  107. package/src/cli/openbao-bootstrap.test.ts +147 -0
  108. package/src/cli/openbao-bootstrap.ts +280 -0
  109. package/src/cli/openbao-restart.integration.test.ts +136 -0
  110. package/src/cli/ports.test.ts +7 -14
  111. package/src/cli/ports.ts +7 -5
  112. package/src/cli/privacy-erase.ts +1 -3
  113. package/src/cli/registry.ts +75 -25
  114. package/src/cli/resolve-dev-sql-env.test.ts +48 -0
  115. package/src/cli/resolve-dev-sql-env.ts +42 -0
  116. package/src/cli/schema.ts +3 -10
  117. package/src/cli/stack.ts +8 -10
  118. package/src/cli/start.ts +4 -15
  119. package/src/cli/upgrade.ts +1 -4
  120. package/src/cli/vault-cmd.ts +63 -0
  121. package/src/client/budget-entry.ts +1 -6
  122. package/src/client/create.test.ts +6 -12
  123. package/src/client/create.ts +2 -8
  124. package/src/client/errors.ts +1 -4
  125. package/src/client/index.ts +1 -6
  126. package/src/client/notes-contract.test.ts +4 -13
  127. package/src/client/transport.ts +9 -30
  128. package/src/client/types.ts +15 -30
  129. package/src/compiler/aot.test.ts +2 -9
  130. package/src/compiler/aot.ts +1 -4
  131. package/src/compiler/differential.test.ts +3 -11
  132. package/src/compiler/dynamic.ts +1 -4
  133. package/src/compiler/effects-infer.ts +8 -34
  134. package/src/compiler/emit.ts +1 -3
  135. package/src/compiler/extract.test.ts +136 -30
  136. package/src/compiler/extract.ts +485 -157
  137. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  138. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -3
  139. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -3
  140. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +4 -1
  141. package/src/compiler/fixtures/skyport.expected.json +24 -83
  142. package/src/compiler/fixtures/triggers/five-triggers.ts +1 -2
  143. package/src/compiler/http-parse.ts +2 -9
  144. package/src/compiler/index.ts +1 -5
  145. package/src/compiler/response.ts +16 -7
  146. package/src/compiler/sucrose.ts +11 -31
  147. package/src/config/define-config.test.ts +55 -34
  148. package/src/config/index.ts +177 -46
  149. package/src/config/resolve-driver.test.ts +28 -17
  150. package/src/console/budget.test.ts +1 -4
  151. package/src/console/index.ts +1 -4
  152. package/src/console/server/access.test.ts +15 -43
  153. package/src/console/server/access.ts +19 -49
  154. package/src/console/server/ai.test.ts +3 -11
  155. package/src/console/server/ai.ts +23 -46
  156. package/src/console/server/app.ts +16 -45
  157. package/src/console/server/auth-rate.test.ts +4 -12
  158. package/src/console/server/bind.ts +1 -5
  159. package/src/console/server/channels.test.ts +2 -6
  160. package/src/console/server/channels.ts +10 -35
  161. package/src/console/server/claim.ts +2 -8
  162. package/src/console/server/clock.test.ts +2 -6
  163. package/src/console/server/clock.ts +8 -26
  164. package/src/console/server/console.test.ts +1 -3
  165. package/src/console/server/diff.test.ts +5 -17
  166. package/src/console/server/diff.ts +10 -31
  167. package/src/console/server/dry-run.audit.test.ts +2 -8
  168. package/src/console/server/flows.ts +64 -233
  169. package/src/console/server/gates.test.ts +14 -50
  170. package/src/console/server/gates.ts +13 -48
  171. package/src/console/server/index.ts +2 -9
  172. package/src/console/server/live.ts +3 -12
  173. package/src/console/server/operator-db.test.ts +4 -15
  174. package/src/console/server/operator-db.ts +13 -32
  175. package/src/console/server/plugin.ts +50 -53
  176. package/src/console/server/plugins.test.ts +2 -6
  177. package/src/console/server/plugins.ts +17 -56
  178. package/src/console/server/runs-pii.ts +2 -7
  179. package/src/console/server/security.gate.test.ts +18 -40
  180. package/src/console/server/serve.ts +10 -25
  181. package/src/console/server/signals.test.ts +1 -3
  182. package/src/console/server/signals.ts +17 -40
  183. package/src/console/server/state.ts +21 -59
  184. package/src/console/server/store.test.ts +20 -22
  185. package/src/console/server/store.ts +31 -76
  186. package/src/console/server/vault.test.ts +12 -29
  187. package/src/console/server/vault.ts +44 -66
  188. package/src/console/ui/access/AccessA11yView.tsx +6 -21
  189. package/src/console/ui/access/a11y.test.tsx +3 -12
  190. package/src/console/ui/access/acknowledgement.test.ts +1 -4
  191. package/src/console/ui/access/acknowledgement.ts +1 -3
  192. package/src/console/ui/access/blast-radius.ts +1 -3
  193. package/src/console/ui/access/confirmation.test.ts +1 -5
  194. package/src/console/ui/access/confirmation.ts +1 -4
  195. package/src/console/ui/access/fixture.ts +1 -5
  196. package/src/console/ui/access/grantable.test.ts +1 -3
  197. package/src/console/ui/access/grantable.ts +2 -7
  198. package/src/console/ui/access/index.ts +2 -9
  199. package/src/console/ui/access/provenance.test.ts +1 -3
  200. package/src/console/ui/access/provenance.ts +2 -6
  201. package/src/console/ui/access/search.test.ts +1 -5
  202. package/src/console/ui/access/search.ts +2 -6
  203. package/src/console/ui/ai/AiA11yView.tsx +6 -17
  204. package/src/console/ui/ai/a11y.test.tsx +2 -5
  205. package/src/console/ui/ai/fixture.ts +2 -7
  206. package/src/console/ui/ai/format.ts +1 -3
  207. package/src/console/ui/ai/group.test.ts +1 -3
  208. package/src/console/ui/ai/group.ts +4 -12
  209. package/src/console/ui/ai/index.ts +2 -9
  210. package/src/console/ui/ai/promotion.test.ts +2 -7
  211. package/src/console/ui/ai/promotion.ts +4 -13
  212. package/src/console/ui/ai/search.test.ts +2 -11
  213. package/src/console/ui/ai/search.ts +3 -13
  214. package/src/console/ui/ai/types.ts +1 -8
  215. package/src/console/ui/architecture/ArchitectureA11yView.tsx +8 -29
  216. package/src/console/ui/architecture/a11y.test.tsx +1 -2
  217. package/src/console/ui/architecture/declared.ts +1 -5
  218. package/src/console/ui/architecture/index.ts +7 -35
  219. package/src/console/ui/architecture/layout.ts +1 -3
  220. package/src/console/ui/architecture/pathologies.ts +10 -19
  221. package/src/console/ui/architecture/search.ts +6 -15
  222. package/src/console/ui/architecture/traffic.ts +4 -14
  223. package/src/console/ui/architecture/types.ts +2 -10
  224. package/src/console/ui/architecture/view.test.ts +7 -19
  225. package/src/console/ui/architecture/view.ts +9 -36
  226. package/src/console/ui/channels/ChannelsA11yView.tsx +4 -13
  227. package/src/console/ui/channels/a11y.test.tsx +1 -2
  228. package/src/console/ui/channels/confirmation.test.ts +1 -4
  229. package/src/console/ui/channels/confirmation.ts +1 -4
  230. package/src/console/ui/channels/findings.ts +1 -5
  231. package/src/console/ui/channels/index.ts +2 -8
  232. package/src/console/ui/channels/locale.test.ts +1 -3
  233. package/src/console/ui/channels/locale.ts +2 -8
  234. package/src/console/ui/channels/search.ts +4 -13
  235. package/src/console/ui/channels/taxonomy.ts +1 -3
  236. package/src/console/ui/clock/ClockA11yView.tsx +9 -26
  237. package/src/console/ui/clock/a11y.test.tsx +2 -5
  238. package/src/console/ui/clock/confirmation.test.ts +1 -3
  239. package/src/console/ui/clock/confirmation.ts +1 -4
  240. package/src/console/ui/clock/findings.ts +1 -4
  241. package/src/console/ui/clock/health.ts +2 -7
  242. package/src/console/ui/clock/index.ts +2 -10
  243. package/src/console/ui/clock/search.test.ts +1 -3
  244. package/src/console/ui/clock/search.ts +2 -6
  245. package/src/console/ui/clock/timeline.test.ts +1 -3
  246. package/src/console/ui/clock/waiting-on.test.ts +1 -5
  247. package/src/console/ui/clock/waiting-on.ts +2 -7
  248. package/src/console/ui/diff/DiffA11yView.tsx +8 -17
  249. package/src/console/ui/diff/a11y.test.tsx +2 -5
  250. package/src/console/ui/diff/group.test.ts +3 -13
  251. package/src/console/ui/diff/group.ts +2 -8
  252. package/src/console/ui/diff/index.ts +2 -8
  253. package/src/console/ui/diff/search.test.ts +1 -5
  254. package/src/console/ui/diff/search.ts +4 -16
  255. package/src/console/ui/diff/summary.ts +1 -2
  256. package/src/console/ui/dist/assets/{index-Dy4jht9P.js → index-BWo8R7NR.js} +2 -2
  257. package/src/console/ui/dist/assets/{panel-access-Dd37LU2c.js → panel-access-C0J2D-a2.js} +1 -1
  258. package/src/console/ui/dist/assets/{panel-ai-CC7LR6-J.js → panel-ai-D_m6WQI8.js} +1 -1
  259. package/src/console/ui/dist/assets/{panel-architecture-B5b3iKCz.js → panel-architecture-CKnXFyUx.js} +1 -1
  260. package/src/console/ui/dist/assets/{panel-channels-CeNjTKXp.js → panel-channels-BOmQ-onL.js} +1 -1
  261. package/src/console/ui/dist/assets/{panel-clock-DgFTLoHV.js → panel-clock-giAq0Ccv.js} +1 -1
  262. package/src/console/ui/dist/assets/{panel-diff-DxehccqB.js → panel-diff-cdonmH8c.js} +1 -1
  263. package/src/console/ui/dist/assets/{panel-flows-BtrVn-Eg.js → panel-flows-DlCU5zjA.js} +2 -2
  264. package/src/console/ui/dist/assets/{panel-gates-Z9MKRGdH.js → panel-gates-XclZxWD5.js} +1 -1
  265. package/src/console/ui/dist/assets/{panel-overview-Dt_AeXgd.js → panel-overview-BznEOTnb.js} +1 -1
  266. package/src/console/ui/dist/assets/{panel-plugins-DWd0TowH.js → panel-plugins-CcGM1g64.js} +1 -1
  267. package/src/console/ui/dist/assets/{panel-runs-DGstFHeq.js → panel-runs-CGWNHLR4.js} +1 -1
  268. package/src/console/ui/dist/assets/{panel-signals-DzEa2Fnt.js → panel-signals-CNywkdak.js} +1 -1
  269. package/src/console/ui/dist/assets/{panel-store-BJkbNgxx.js → panel-store-KmTbFHMH.js} +1 -1
  270. package/src/console/ui/dist/assets/{panel-traces-BaRVO3gM.js → panel-traces-DBLx2ilD.js} +1 -1
  271. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +1 -0
  272. package/src/console/ui/dist/index.html +1 -1
  273. package/src/console/ui/flows/FlowsA11yView.tsx +7 -22
  274. package/src/console/ui/flows/a11y.test.tsx +2 -8
  275. package/src/console/ui/flows/buffer.test.ts +3 -11
  276. package/src/console/ui/flows/buffer.ts +4 -11
  277. package/src/console/ui/flows/confirmation.ts +1 -3
  278. package/src/console/ui/flows/contract.test.ts +3 -7
  279. package/src/console/ui/flows/contract.ts +12 -36
  280. package/src/console/ui/flows/graph.test.ts +3 -11
  281. package/src/console/ui/flows/graph.ts +5 -15
  282. package/src/console/ui/flows/index.ts +1 -6
  283. package/src/console/ui/flows/save-as-test.ts +1 -3
  284. package/src/console/ui/flows/search.ts +8 -25
  285. package/src/console/ui/flows/tiers.ts +2 -9
  286. package/src/console/ui/gates/GatesA11yView.tsx +4 -13
  287. package/src/console/ui/gates/a11y.test.tsx +2 -5
  288. package/src/console/ui/gates/audit.ts +1 -3
  289. package/src/console/ui/gates/denial.test.ts +1 -3
  290. package/src/console/ui/gates/denial.ts +4 -12
  291. package/src/console/ui/gates/findings.ts +1 -3
  292. package/src/console/ui/gates/group.test.ts +3 -3
  293. package/src/console/ui/gates/group.ts +3 -11
  294. package/src/console/ui/gates/index.ts +2 -10
  295. package/src/console/ui/gates/search.ts +3 -10
  296. package/src/console/ui/overview/OverviewA11yView.tsx +9 -27
  297. package/src/console/ui/overview/a11y.test.tsx +1 -2
  298. package/src/console/ui/overview/busiest.ts +1 -3
  299. package/src/console/ui/overview/compose.ts +1 -13
  300. package/src/console/ui/overview/cost.ts +34 -38
  301. package/src/console/ui/overview/fixture.ts +21 -26
  302. package/src/console/ui/overview/golden.ts +4 -14
  303. package/src/console/ui/overview/index.ts +2 -9
  304. package/src/console/ui/overview/rank.test.ts +3 -10
  305. package/src/console/ui/overview/rank.ts +2 -7
  306. package/src/console/ui/overview/slo.test.ts +5 -9
  307. package/src/console/ui/overview/slo.ts +6 -23
  308. package/src/console/ui/overview/verdict.ts +3 -10
  309. package/src/console/ui/plugins/PluginsA11yView.tsx +8 -18
  310. package/src/console/ui/plugins/a11y.test.tsx +1 -2
  311. package/src/console/ui/plugins/fixture.ts +2 -4
  312. package/src/console/ui/plugins/group.test.ts +1 -3
  313. package/src/console/ui/plugins/group.ts +1 -6
  314. package/src/console/ui/plugins/index.ts +3 -13
  315. package/src/console/ui/plugins/search.ts +7 -19
  316. package/src/console/ui/runs/RunsA11yView.tsx +5 -20
  317. package/src/console/ui/runs/a11y.test.tsx +2 -5
  318. package/src/console/ui/runs/explain.ts +2 -6
  319. package/src/console/ui/runs/group.ts +3 -12
  320. package/src/console/ui/runs/histogram.test.ts +2 -11
  321. package/src/console/ui/runs/histogram.ts +1 -4
  322. package/src/console/ui/runs/index.ts +4 -18
  323. package/src/console/ui/runs/project.ts +2 -6
  324. package/src/console/ui/runs/query.test.ts +1 -3
  325. package/src/console/ui/runs/query.ts +12 -35
  326. package/src/console/ui/runs/search.ts +5 -19
  327. package/src/console/ui/runs/trace-link.test.ts +2 -6
  328. package/src/console/ui/runs/trace-link.ts +4 -16
  329. package/src/console/ui/runs/types.ts +1 -3
  330. package/src/console/ui/shell/App.tsx +2 -10
  331. package/src/console/ui/shell/client.ts +22 -115
  332. package/src/console/ui/shell/components/ui.tsx +1 -4
  333. package/src/console/ui/shell/layout/Shell.tsx +2 -4
  334. package/src/console/ui/shell/main.tsx +31 -96
  335. package/src/console/ui/shell/panels/access/AccessPanel.tsx +34 -98
  336. package/src/console/ui/shell/panels/ai/AiPanel.tsx +35 -106
  337. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +18 -66
  338. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +20 -57
  339. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +22 -54
  340. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +20 -61
  341. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +6 -23
  342. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +13 -28
  343. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +9 -29
  344. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +24 -80
  345. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +20 -58
  346. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +16 -41
  347. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +31 -99
  348. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +38 -101
  349. package/src/console/ui/shell/panels/store/StorePanel.tsx +27 -66
  350. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +28 -89
  351. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +22 -53
  352. package/src/console/ui/shell/setup/Wizard.tsx +3 -5
  353. package/src/console/ui/shell/styles.css +1 -2
  354. package/src/console/ui/signals/SignalsA11yView.tsx +9 -33
  355. package/src/console/ui/signals/a11y.test.tsx +2 -5
  356. package/src/console/ui/signals/confirmation.test.ts +1 -5
  357. package/src/console/ui/signals/confirmation.ts +2 -6
  358. package/src/console/ui/signals/dry-run.ts +1 -3
  359. package/src/console/ui/signals/durable.ts +1 -3
  360. package/src/console/ui/signals/findings.ts +1 -3
  361. package/src/console/ui/signals/group.test.ts +2 -8
  362. package/src/console/ui/signals/group.ts +2 -10
  363. package/src/console/ui/signals/index.ts +1 -4
  364. package/src/console/ui/signals/monitor.ts +2 -7
  365. package/src/console/ui/signals/schema-form.test.ts +3 -14
  366. package/src/console/ui/signals/schema-form.ts +1 -4
  367. package/src/console/ui/signals/search.ts +4 -13
  368. package/src/console/ui/store/StoreA11yView.tsx +3 -9
  369. package/src/console/ui/store/a11y.test.tsx +2 -5
  370. package/src/console/ui/store/confirmation.test.ts +1 -3
  371. package/src/console/ui/store/confirmation.ts +1 -4
  372. package/src/console/ui/store/dry-run.ts +2 -5
  373. package/src/console/ui/store/fixture.ts +1 -2
  374. package/src/console/ui/store/group.test.ts +1 -6
  375. package/src/console/ui/store/group.ts +1 -4
  376. package/src/console/ui/store/index.ts +1 -5
  377. package/src/console/ui/store/search.ts +2 -6
  378. package/src/console/ui/store/will-not-fire.ts +1 -2
  379. package/src/console/ui/traces/TracesA11yView.tsx +10 -29
  380. package/src/console/ui/traces/a11y.test.tsx +1 -2
  381. package/src/console/ui/traces/chain.test.ts +5 -19
  382. package/src/console/ui/traces/chain.ts +1 -3
  383. package/src/console/ui/traces/critical-path.test.ts +1 -3
  384. package/src/console/ui/traces/critical-path.ts +2 -6
  385. package/src/console/ui/traces/filter.test.ts +2 -6
  386. package/src/console/ui/traces/filter.ts +5 -13
  387. package/src/console/ui/traces/fold.test.ts +4 -12
  388. package/src/console/ui/traces/fold.ts +1 -3
  389. package/src/console/ui/traces/mini.ts +1 -3
  390. package/src/console/ui/traces/replay.test.ts +1 -3
  391. package/src/console/ui/traces/replay.ts +1 -3
  392. package/src/console/ui/traces/sampling.ts +1 -4
  393. package/src/console/ui/traces/search.ts +4 -17
  394. package/src/console/ui/traces/tier.ts +1 -3
  395. package/src/console/ui/vault/VaultA11yView.tsx +5 -17
  396. package/src/console/ui/vault/a11y.test.tsx +1 -2
  397. package/src/console/ui/vault/blast-radius.ts +1 -3
  398. package/src/console/ui/vault/confirmation.test.ts +1 -5
  399. package/src/console/ui/vault/confirmation.ts +1 -4
  400. package/src/console/ui/vault/dormant.test.ts +1 -4
  401. package/src/console/ui/vault/export-safe.test.ts +1 -5
  402. package/src/console/ui/vault/export-safe.ts +1 -4
  403. package/src/console/ui/vault/fixture.ts +3 -3
  404. package/src/console/ui/vault/group.ts +1 -2
  405. package/src/console/ui/vault/index.ts +2 -10
  406. package/src/console/ui/vault/search.ts +2 -6
  407. package/src/console/ui/vault/types.ts +1 -1
  408. package/src/console/xss.gate.test.ts +1 -3
  409. package/src/docker/compose.ts +215 -27
  410. package/src/docker/derive.ts +6 -14
  411. package/src/docker/docker.test.ts +118 -15
  412. package/src/docker/dockerfile.integration.test.ts +12 -21
  413. package/src/docker/helpers.ts +5 -4
  414. package/src/docker/index.ts +9 -6
  415. package/src/docker/pin.ts +1 -9
  416. package/src/docker/recipes/index.ts +6 -6
  417. package/src/docker/recipes/mailpit.ts +22 -0
  418. package/src/docker/recipes/openbao.ts +47 -0
  419. package/src/docker/recipes/postgres.ts +17 -6
  420. package/src/docker/recipes/redis.ts +11 -2
  421. package/src/docker/recipes/rustfs.ts +32 -0
  422. package/src/docker/stack-id.test.ts +73 -5
  423. package/src/docker/stack-id.ts +124 -33
  424. package/src/docker/stack.integration.test.ts +6 -21
  425. package/src/docker/stack.ts +36 -4
  426. package/src/docker/types.ts +16 -4
  427. package/src/docs-origin.ts +4 -4
  428. package/src/drivers/ai-anthropic.ts +5 -14
  429. package/src/drivers/ai-mock.ts +2 -5
  430. package/src/drivers/ai-openai-compatible.ts +4 -10
  431. package/src/drivers/ai-providers.test.ts +6 -13
  432. package/src/drivers/bun-native-completeness.test.ts +6 -15
  433. package/src/drivers/channel-console.ts +6 -7
  434. package/src/drivers/channel-fcm.ts +20 -30
  435. package/src/drivers/channel-resend.ts +1 -3
  436. package/src/drivers/channel-smtp.ts +2 -6
  437. package/src/drivers/channel-types.ts +2 -12
  438. package/src/drivers/channel-unifonic.ts +2 -6
  439. package/src/drivers/channel-wa-cloud.ts +2 -6
  440. package/src/drivers/channel-webpush.ts +21 -60
  441. package/src/drivers/conformance.test.ts +2 -10
  442. package/src/drivers/conformance.ts +8 -21
  443. package/src/drivers/drizzle-dialect.test.ts +20 -0
  444. package/src/drivers/drizzle-dialect.ts +37 -0
  445. package/src/drivers/fs.ts +2 -8
  446. package/src/drivers/index.ts +12 -57
  447. package/src/drivers/memory.ts +287 -75
  448. package/src/drivers/pgvector.ts +7 -11
  449. package/src/drivers/postgres.ts +7 -13
  450. package/src/drivers/redis.ts +13 -37
  451. package/src/drivers/s3.ts +15 -18
  452. package/src/drivers/signal-engine.ts +12 -49
  453. package/src/drivers/signal-memory.ts +2 -8
  454. package/src/drivers/signal-nats.ts +4 -9
  455. package/src/drivers/signal-postgres.ts +62 -129
  456. package/src/drivers/signal-redis.ts +7 -21
  457. package/src/drivers/signal-types.ts +1 -4
  458. package/src/drivers/sqlite.ts +3 -12
  459. package/src/drivers/types.ts +4 -23
  460. package/src/drivers/vault-driver-removal.test.ts +55 -0
  461. package/src/drivers/vault-env.ts +2 -6
  462. package/src/drivers/vault-infisical.ts +1 -5
  463. package/src/drivers/vault-managed.ts +1 -5
  464. package/src/drivers/vault-memory.ts +1 -5
  465. package/src/drivers/vault-openbao.test.ts +97 -0
  466. package/src/drivers/vault-openbao.ts +103 -40
  467. package/src/drivers/vault-types.ts +3 -16
  468. package/src/elements/ai/declare.ts +4 -14
  469. package/src/elements/ai/eval.ts +2 -6
  470. package/src/elements/ai/pii.ts +1 -3
  471. package/src/elements/ai/runtime.ts +12 -50
  472. package/src/elements/ai/schema.ts +4 -15
  473. package/src/elements/ai.test.ts +13 -24
  474. package/src/elements/ai.ts +1 -5
  475. package/src/elements/channel/consent.ts +3 -12
  476. package/src/elements/channel/costs.ts +4 -15
  477. package/src/elements/channel/declare.ts +1 -4
  478. package/src/elements/channel/email-auth.ts +1 -4
  479. package/src/elements/channel/locale.ts +2 -10
  480. package/src/elements/channel/mask.ts +1 -3
  481. package/src/elements/channel/mime.ts +2 -12
  482. package/src/elements/channel/outcomes.test.ts +1 -5
  483. package/src/elements/channel/outcomes.ts +5 -15
  484. package/src/elements/channel/receipts.ts +2 -7
  485. package/src/elements/channel/runtime.ts +28 -74
  486. package/src/elements/channel/suppression.ts +8 -14
  487. package/src/elements/channel.test.ts +8 -31
  488. package/src/elements/channel.ts +3 -14
  489. package/src/elements/clock/actions.ts +6 -18
  490. package/src/elements/clock/declare.ts +1 -3
  491. package/src/elements/clock/dst.ts +1 -5
  492. package/src/elements/clock/durable.ts +4 -16
  493. package/src/elements/clock/health.ts +2 -7
  494. package/src/elements/clock/leader.ts +2 -9
  495. package/src/elements/clock/reconcile.ts +4 -15
  496. package/src/elements/clock/runtime.ts +2 -5
  497. package/src/elements/clock/schedule.ts +4 -20
  498. package/src/elements/clock.test.ts +2 -7
  499. package/src/elements/clock.ts +5 -23
  500. package/src/elements/gate/permissions.ts +1 -4
  501. package/src/elements/gate/runtime.ts +7 -28
  502. package/src/elements/gate.ts +1 -5
  503. package/src/elements/index.ts +9 -7
  504. package/src/elements/signal/declare.ts +2 -7
  505. package/src/elements/signal/reconcile.test.ts +1 -4
  506. package/src/elements/signal/runtime.ts +1 -3
  507. package/src/elements/signal.test.ts +3 -15
  508. package/src/elements/signal.ts +2 -8
  509. package/src/elements/store/cache.test.ts +46 -0
  510. package/src/elements/store/cache.ts +3 -11
  511. package/src/elements/store/classify.ts +5 -22
  512. package/src/elements/store/declare.ts +11 -17
  513. package/src/elements/store/domain-ddl.test.ts +86 -0
  514. package/src/elements/store/emit-drizzle.ts +363 -0
  515. package/src/elements/store/files-policy.test.ts +1 -5
  516. package/src/elements/store/files-policy.ts +4 -12
  517. package/src/elements/store/load-plugin-tables.ts +103 -0
  518. package/src/elements/store/missing-relation.test.ts +83 -0
  519. package/src/elements/store/missing-relation.ts +38 -0
  520. package/src/elements/store/replica.ts +1 -4
  521. package/src/elements/store/resource-list-docs.fixture.ts +56 -0
  522. package/src/elements/store/resource-list-docs.test.ts +79 -0
  523. package/src/elements/store/resource.test.ts +253 -0
  524. package/src/elements/store/resource.ts +786 -0
  525. package/src/elements/store/runtime.ts +25 -28
  526. package/src/elements/store/schema-decl.test.ts +227 -0
  527. package/src/elements/store/schema-decl.ts +516 -0
  528. package/src/elements/store/sql-condition.test.ts +132 -0
  529. package/src/elements/store/sql-condition.ts +293 -65
  530. package/src/elements/store/sql-session.test.ts +135 -0
  531. package/src/elements/store/sql-session.ts +241 -84
  532. package/src/elements/store/table.ts +69 -23
  533. package/src/elements/store.test.ts +17 -39
  534. package/src/elements/store.ts +53 -31
  535. package/src/elements/vault/chain.ts +34 -0
  536. package/src/elements/vault/declare.ts +22 -32
  537. package/src/elements/vault/fingerprint.ts +2 -7
  538. package/src/elements/vault/redact.ts +2 -6
  539. package/src/elements/vault/runtime.ts +19 -33
  540. package/src/elements/vault.test.ts +12 -55
  541. package/src/elements/vault.ts +9 -13
  542. package/src/index.ts +5 -2
  543. package/src/kernel/adopt-routes.ts +15 -29
  544. package/src/kernel/app.ts +92 -118
  545. package/src/kernel/auth-resolve.ts +2 -9
  546. package/src/kernel/boot-bind/ai.ts +1 -4
  547. package/src/kernel/boot-bind/channel.test.ts +60 -0
  548. package/src/kernel/boot-bind/channel.ts +61 -5
  549. package/src/kernel/boot-bind/clock.ts +2 -6
  550. package/src/kernel/boot-bind/gate.ts +2 -8
  551. package/src/kernel/boot-bind/runs.ts +1 -3
  552. package/src/kernel/boot-bind/signal.ts +3 -10
  553. package/src/kernel/boot-bind/store.test.ts +27 -18
  554. package/src/kernel/boot-bind/store.ts +93 -52
  555. package/src/kernel/boot-bind/vault.ts +2 -8
  556. package/src/kernel/boot.test.ts +4 -10
  557. package/src/kernel/boot.ts +30 -77
  558. package/src/kernel/budget.test.ts +1 -3
  559. package/src/kernel/capability.ts +3 -10
  560. package/src/kernel/dry-run.test.ts +1 -4
  561. package/src/kernel/dry-run.ts +3 -11
  562. package/src/kernel/edge.test.ts +68 -0
  563. package/src/kernel/effects.test.ts +1 -5
  564. package/src/kernel/effects.ts +4 -11
  565. package/src/kernel/errors.registry.test.ts +2 -8
  566. package/src/kernel/errors.ts +23 -32
  567. package/src/kernel/flow.test.ts +12 -25
  568. package/src/kernel/flow.ts +13 -25
  569. package/src/kernel/fx.test.ts +31 -40
  570. package/src/kernel/fx.ts +135 -90
  571. package/src/kernel/hook-timing.test.ts +3 -6
  572. package/src/kernel/hook-timing.ts +5 -14
  573. package/src/kernel/hooks.test.ts +43 -10
  574. package/src/kernel/hooks.ts +25 -12
  575. package/src/kernel/index.ts +11 -17
  576. package/src/kernel/journal.ts +7 -31
  577. package/src/kernel/on.ts +45 -9
  578. package/src/kernel/pipeline.test.ts +10 -19
  579. package/src/kernel/pipeline.ts +5 -17
  580. package/src/kernel/plug.ts +4 -11
  581. package/src/kernel/plugin/conflicts.test.ts +13 -25
  582. package/src/kernel/plugin/decorate.test.ts +8 -13
  583. package/src/kernel/plugin/scoping.test.ts +4 -11
  584. package/src/kernel/plugin.ts +81 -27
  585. package/src/kernel/registry-isolation.test.ts +74 -0
  586. package/src/kernel/registry.ts +102 -30
  587. package/src/kernel/router.test.ts +15 -13
  588. package/src/kernel/router.ts +9 -29
  589. package/src/kernel/routing-budget.test.ts +1 -3
  590. package/src/kernel/run-telemetry.ts +1 -3
  591. package/src/kernel/triggers.ts +63 -21
  592. package/src/manifest/diff.test.ts +26 -91
  593. package/src/manifest/diff.ts +95 -225
  594. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  595. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  596. package/src/manifest/index.ts +3 -10
  597. package/src/manifest/types.ts +30 -6
  598. package/src/manifest/undeclared.test.ts +4 -12
  599. package/src/manifest/undeclared.ts +1 -2
  600. package/src/manifest/validate.test.ts +2 -6
  601. package/src/manifest/validate.ts +5 -15
  602. package/src/mcp/authorization.ts +8 -27
  603. package/src/mcp/confirmation.ts +3 -10
  604. package/src/mcp/data.ts +2 -6
  605. package/src/mcp/docs-index.ts +4 -7
  606. package/src/mcp/docs-mcp.test.ts +3 -12
  607. package/src/mcp/docs-server.ts +7 -27
  608. package/src/mcp/docs-tools.ts +2 -6
  609. package/src/mcp/index.ts +1 -6
  610. package/src/mcp/injection.gate.test.ts +4 -18
  611. package/src/mcp/mcp.test.ts +18 -49
  612. package/src/mcp/protocol.ts +3 -10
  613. package/src/mcp/server.ts +11 -52
  614. package/src/mcp/session.ts +4 -14
  615. package/src/mcp/tools.ts +14 -50
  616. package/src/plugins/catalogue.test.ts +3 -7
  617. package/src/plugins/catalogue.ts +1 -3
  618. package/src/plugins/compression.test.ts +127 -0
  619. package/src/plugins/compression.ts +94 -0
  620. package/src/plugins/config-source.test.ts +204 -0
  621. package/src/plugins/config-source.ts +209 -0
  622. package/src/plugins/cors.test.ts +138 -0
  623. package/src/plugins/cors.ts +129 -0
  624. package/src/plugins/csrf.test.ts +102 -0
  625. package/src/plugins/csrf.ts +86 -0
  626. package/src/plugins/headers.ts +54 -0
  627. package/src/plugins/index.ts +26 -0
  628. package/src/plugins/ip-allowlist.test.ts +105 -0
  629. package/src/plugins/ip-allowlist.ts +76 -0
  630. package/src/plugins/maintenance-mode.test.ts +91 -0
  631. package/src/plugins/maintenance-mode.ts +85 -0
  632. package/src/plugins/node-import-scan.ts +2 -5
  633. package/src/plugins/security-headers.test.ts +243 -0
  634. package/src/plugins/security-headers.ts +255 -0
  635. package/src/plugins/supply-chain.ts +5 -17
  636. package/src/release/exports.ts +3 -8
  637. package/src/release/measure.exports.test.ts +1 -3
  638. package/src/release/measure.ts +16 -45
  639. package/src/runs/bench.test.ts +1 -3
  640. package/src/runs/collect.ts +8 -25
  641. package/src/runs/drivers/clickhouse.ts +2 -9
  642. package/src/runs/drivers/files.ts +9 -31
  643. package/src/runs/drivers/memory.ts +5 -22
  644. package/src/runs/drivers/postgres.ts +8 -20
  645. package/src/runs/duckdb.ts +2 -8
  646. package/src/runs/index.ts +3 -14
  647. package/src/runs/outlier.ts +3 -10
  648. package/src/runs/parquet.ts +8 -26
  649. package/src/runs/privacy.ts +2 -8
  650. package/src/runs/runs.test.ts +3 -9
  651. package/src/runs/runtime.ts +7 -32
  652. package/src/runs/shred.ts +10 -29
  653. package/src/runs/types.ts +1 -4
  654. package/src/runtime/bun.ts +4 -15
  655. package/src/runtime/cold-start.bench.ts +1 -3
  656. package/src/runtime/dev-request-log.test.ts +1 -4
  657. package/src/runtime/dev-request-log.ts +1 -4
  658. package/src/runtime/index.ts +1 -6
  659. package/src/runtime/primitives.ts +5 -4
  660. package/src/runtime/security.ts +3 -13
  661. package/src/runtime/serve.test.ts +4 -7
  662. package/src/runtime/types.ts +1 -4
  663. package/src/runtime/web-standard.ts +3 -15
  664. package/src/term.test.ts +4 -6
  665. package/src/term.ts +49 -98
  666. package/src/test/create-test-app.test.ts +4 -14
  667. package/src/test/create-test-app.ts +27 -33
  668. package/src/test/provisions.integration.test.ts +3 -11
  669. package/src/upgrade/codemods.test.ts +25 -6
  670. package/src/upgrade/codemods.ts +116 -3
  671. package/src/validation/standard-schema.test.ts +2 -5
  672. package/src/validation/standard-schema.ts +13 -33
  673. package/docs/spec/console.md +0 -739
  674. package/docs/spec/example.md +0 -1196
  675. package/docs/spec/four-applications.md +0 -1195
  676. package/docs/spec/unified-theory.md +0 -489
  677. package/src/cli/doc-drift.test.ts +0 -146
  678. package/src/cli/doc-drift.ts +0 -430
  679. package/src/cli/doctor-diff-examples.ts +0 -92
  680. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
  681. package/src/drivers/vault-sops.ts +0 -271
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Official `maintenance-mode` plugin — drain HTTP traffic with one flag.
3
+ * Uses only the public plugin API (unified-theory §14).
4
+ */
5
+
6
+ import { plugin, type PluginDef } from "../kernel/plugin.ts";
7
+ import {
8
+ isConfigSource,
9
+ pluginConfigSnapshot,
10
+ resolvePluginOptions,
11
+ withConfigTable,
12
+ type ConfigSource,
13
+ } from "./config-source.ts";
14
+
15
+ /** Options for {@link maintenanceMode}. */
16
+ export interface MaintenanceModeOptions {
17
+ /**
18
+ * Whether the mode is active. Default `true` — plugging the plugin turns
19
+ * it on. Drive it from the environment to flip without a code change:
20
+ * `enabled: process.env.MAINTENANCE_MODE === "1"`.
21
+ */
22
+ readonly enabled?: boolean;
23
+ /** `Retry-After` value in seconds on the 503. Omitted unless provided. */
24
+ readonly retryAfter?: number;
25
+ /**
26
+ * Path prefixes that keep serving while the mode is active (e.g.
27
+ * `["/health"]` so load-balancer checks stay green).
28
+ */
29
+ readonly allowPaths?: readonly string[];
30
+ /**
31
+ * Operator bypass: requests carrying this header name (any non-empty
32
+ * value) pass through. Presence-based — an ops convenience, not auth.
33
+ */
34
+ readonly bypassHeader?: string;
35
+ /** Failure message in the envelope. Default `"Service is under maintenance."` */
36
+ readonly message?: string;
37
+ }
38
+
39
+ /**
40
+ * Short-circuit every HTTP invocation at `onRequest` with a 503 failure
41
+ * envelope (`ServiceUnavailable`) plus an optional `Retry-After`. Excluded
42
+ * prefixes and the bypass header keep ops paths alive. The 503 still flows
43
+ * through `onResponse`, so response-shaping plugins (security headers,
44
+ * compression) apply to it too. Non-HTTP triggers no-op — clock flows and
45
+ * signal subscribers keep running.
46
+ *
47
+ * Accepts static options or a {@link ConfigSource} — the flagship use
48
+ * case: flip `enabled` from the database and drain traffic without a
49
+ * redeploy.
50
+ *
51
+ * @param options - Flag, retry hint, allow-list, bypass — or a config source
52
+ */
53
+ export function maintenanceMode(
54
+ options: MaintenanceModeOptions | ConfigSource<MaintenanceModeOptions> = {},
55
+ ): PluginDef {
56
+ const def = plugin("maintenance-mode", {
57
+ version: "0.0.2",
58
+ config: pluginConfigSnapshot(options),
59
+ }).hook("onRequest", (ctx) => {
60
+ const resolved = resolvePluginOptions(options);
61
+ if ((resolved.enabled ?? true) === false || !ctx.request) return;
62
+ const message = resolved.message ?? "Service is under maintenance.";
63
+
64
+ const { pathname } = new URL(ctx.request.url);
65
+ if (resolved.allowPaths?.some((prefix) => pathname.startsWith(prefix))) return;
66
+
67
+ const bypass = resolved.bypassHeader;
68
+ if (bypass !== undefined) {
69
+ const value = ctx.request.headers.get(bypass);
70
+ if (value !== null && value.length > 0) return;
71
+ }
72
+
73
+ const headers: Record<string, string> = {};
74
+ if (resolved.retryAfter !== undefined) headers["retry-after"] = String(resolved.retryAfter);
75
+ return Response.json(
76
+ {
77
+ data: null,
78
+ error: { code: "ServiceUnavailable", data: { retryAfter: resolved.retryAfter }, message },
79
+ },
80
+ { status: 503, headers },
81
+ );
82
+ });
83
+
84
+ return isConfigSource(options) ? withConfigTable(def, options) : def;
85
+ }
@@ -49,9 +49,7 @@ export function scanNodeImportsBypassingFx(
49
49
  out.push({
50
50
  source: file.path,
51
51
  specifier,
52
- line: typeof node.start === "number"
53
- ? lineAt(file.source, node.start)
54
- : null,
52
+ line: typeof node.start === "number" ? lineAt(file.source, node.start) : null,
55
53
  });
56
54
  });
57
55
  }
@@ -77,8 +75,7 @@ function importSpecifier(node: AstNode): string | null {
77
75
  };
78
76
  const callee = call.callee;
79
77
  const isRequire =
80
- callee?.type === "Identifier" &&
81
- (callee as { name?: string }).name === "require";
78
+ callee?.type === "Identifier" && (callee as { name?: string }).name === "require";
82
79
  if (!isRequire) return null;
83
80
  const arg0 = call.arguments?.[0];
84
81
  if (arg0 && (arg0.type === "Literal" || arg0.type === "StringLiteral")) {
@@ -0,0 +1,243 @@
1
+ /**
2
+ * `security-headers` plugin — defaults on success, presence on failure,
3
+ * and app-wins override semantics through the real pipeline.
4
+ */
5
+
6
+ import { beforeEach, describe, expect, test } from "bun:test";
7
+ import { oke } from "../kernel/app.ts";
8
+ import { flow, resetFlowSeq } from "../kernel/flow.ts";
9
+ import { fail } from "../kernel/index.ts";
10
+ import { on, resetBindings } from "../kernel/on.ts";
11
+ import { http } from "../kernel/triggers.ts";
12
+ import { defaultCspDirectives, securityHeaders } from "./security-headers.ts";
13
+
14
+ beforeEach(() => {
15
+ resetBindings();
16
+ resetFlowSeq();
17
+ });
18
+
19
+ describe("securityHeaders plugin", () => {
20
+ test("sets the default trio on a successful response", async () => {
21
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
22
+ const app = oke({ name: "sec" }).plug(securityHeaders());
23
+
24
+ const res = await app.fetch(new Request("http://localhost/x"));
25
+
26
+ expect(res.status).toBe(200);
27
+ expect(res.headers.get("x-content-type-options")).toBe("nosniff");
28
+ expect(res.headers.get("x-frame-options")).toBe("DENY");
29
+ expect(res.headers.get("referrer-policy")).toBe("no-referrer");
30
+ expect(res.headers.get("content-security-policy")).toBeNull();
31
+ });
32
+
33
+ test("helmet-parity defaults: agent cluster, dns prefetch, download, cross-domain, xss filter", async () => {
34
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
35
+ const app = oke({ name: "sec-parity" }).plug(securityHeaders());
36
+
37
+ const res = await app.fetch(new Request("http://localhost/x"));
38
+
39
+ expect(res.headers.get("origin-agent-cluster")).toBe("?1");
40
+ expect(res.headers.get("x-dns-prefetch-control")).toBe("off");
41
+ expect(res.headers.get("x-download-options")).toBe("noopen");
42
+ expect(res.headers.get("x-permitted-cross-domain-policies")).toBe("none");
43
+ expect(res.headers.get("x-xss-protection")).toBe("0");
44
+ });
45
+
46
+ test("each parity default can be switched off", async () => {
47
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
48
+ const app = oke({ name: "sec-off" }).plug(
49
+ securityHeaders({
50
+ originAgentCluster: false,
51
+ dnsPrefetchControl: false,
52
+ downloadOptions: false,
53
+ xssProtection: false,
54
+ }),
55
+ );
56
+
57
+ const res = await app.fetch(new Request("http://localhost/x"));
58
+
59
+ expect(res.headers.get("origin-agent-cluster")).toBeNull();
60
+ expect(res.headers.get("x-dns-prefetch-control")).toBeNull();
61
+ expect(res.headers.get("x-download-options")).toBeNull();
62
+ expect(res.headers.get("x-xss-protection")).toBeNull();
63
+ expect(res.headers.get("x-permitted-cross-domain-policies")).toBe("none");
64
+ });
65
+
66
+ test("dnsPrefetchControl allow, custom cross-domain policy, COEP stamps when configured", async () => {
67
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
68
+ const app = oke({ name: "sec-tuned" }).plug(
69
+ securityHeaders({
70
+ dnsPrefetchControl: { allow: true },
71
+ permittedCrossDomainPolicies: "by-content-type",
72
+ crossOriginEmbedderPolicy: "require-corp",
73
+ }),
74
+ );
75
+
76
+ const res = await app.fetch(new Request("http://localhost/x"));
77
+
78
+ expect(res.headers.get("x-dns-prefetch-control")).toBe("on");
79
+ expect(res.headers.get("x-permitted-cross-domain-policies")).toBe("by-content-type");
80
+ expect(res.headers.get("cross-origin-embedder-policy")).toBe("require-corp");
81
+ });
82
+
83
+ test("x-powered-by is removed by default; a string sets a decoy; false keeps the app's", async () => {
84
+ const poweredByApp = (options: Parameters<typeof securityHeaders>[0], name: string) => {
85
+ on(
86
+ http.get("/x"),
87
+ flow({
88
+ name: "x.get",
89
+ do: () =>
90
+ new Response("{}", {
91
+ headers: { "content-type": "application/json", "x-powered-by": "Express" },
92
+ }),
93
+ }),
94
+ );
95
+ return oke({ name }).plug(securityHeaders(options));
96
+ };
97
+
98
+ const removed = await poweredByApp({}, "sec-pb-off").fetch(new Request("http://localhost/x"));
99
+ expect(removed.headers.get("x-powered-by")).toBeNull();
100
+
101
+ const decoy = await poweredByApp(
102
+ { poweredBy: "PHP 4.2.0", override: true },
103
+ "sec-pb-decoy",
104
+ ).fetch(new Request("http://localhost/x"));
105
+ expect(decoy.headers.get("x-powered-by")).toBe("PHP 4.2.0");
106
+
107
+ const kept = await poweredByApp({ poweredBy: false }, "sec-pb-keep").fetch(
108
+ new Request("http://localhost/x"),
109
+ );
110
+ expect(kept.headers.get("x-powered-by")).toBe("Express");
111
+ });
112
+
113
+ test("structured CSP merges over helmet's defaults, camelCase keys, report-only mode", async () => {
114
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
115
+ const app = oke({ name: "sec-csp-builder" }).plug(
116
+ securityHeaders({
117
+ contentSecurityPolicy: {
118
+ directives: { scriptSrc: ["'self'", "https://cdn.example.com"] },
119
+ reportOnly: true,
120
+ },
121
+ }),
122
+ );
123
+
124
+ const res = await app.fetch(new Request("http://localhost/x"));
125
+
126
+ expect(res.headers.get("content-security-policy")).toBeNull();
127
+ const reportOnly = res.headers.get("content-security-policy-report-only");
128
+ expect(reportOnly).toContain("script-src 'self' https://cdn.example.com");
129
+ expect(reportOnly).toContain("default-src 'self'");
130
+ expect(reportOnly).toContain("upgrade-insecure-requests");
131
+ });
132
+
133
+ test("structured CSP with useDefaults: false emits exactly the given directives", async () => {
134
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
135
+ const app = oke({ name: "sec-csp-bare" }).plug(
136
+ securityHeaders({
137
+ contentSecurityPolicy: { useDefaults: false, directives: { "default-src": ["'none'"] } },
138
+ }),
139
+ );
140
+
141
+ const res = await app.fetch(new Request("http://localhost/x"));
142
+ expect(res.headers.get("content-security-policy")).toBe("default-src 'none'");
143
+ });
144
+
145
+ test("defaultCspDirectives matches helmet's documented default policy", () => {
146
+ expect(defaultCspDirectives["upgrade-insecure-requests"]).toEqual([]);
147
+ expect(defaultCspDirectives["script-src-attr"]).toEqual(["'none'"]);
148
+ expect(Object.keys(defaultCspDirectives)).toHaveLength(11);
149
+ });
150
+
151
+ test("headers land on failures too (onResponse runs after onError)", async () => {
152
+ on(
153
+ http.get("/deny"),
154
+ flow({ name: "x.deny", do: () => ({ ok: true }) }).hook("beforeHandle", () =>
155
+ fail("Forbidden", {}),
156
+ ),
157
+ );
158
+ const app = oke({ name: "sec-fail" }).plug(securityHeaders());
159
+
160
+ const res = await app.fetch(new Request("http://localhost/deny"));
161
+
162
+ expect(res.status).not.toBe(200);
163
+ expect(res.headers.get("x-content-type-options")).toBe("nosniff");
164
+ });
165
+
166
+ test("adds CSP only when configured", async () => {
167
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
168
+ const app = oke({ name: "sec-csp" }).plug(
169
+ securityHeaders({ contentSecurityPolicy: "default-src 'self'" }),
170
+ );
171
+
172
+ const res = await app.fetch(new Request("http://localhost/x"));
173
+
174
+ expect(res.headers.get("content-security-policy")).toBe("default-src 'self'");
175
+ });
176
+
177
+ test("HSTS is off by default", async () => {
178
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
179
+ const app = oke({ name: "sec-hsts-off" }).plug(securityHeaders());
180
+
181
+ const res = await app.fetch(new Request("http://localhost/x"));
182
+
183
+ expect(res.headers.get("strict-transport-security")).toBeNull();
184
+ });
185
+
186
+ test("hsts: true stamps a one-year max-age", async () => {
187
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
188
+ const app = oke({ name: "sec-hsts-on" }).plug(securityHeaders({ hsts: true }));
189
+
190
+ const res = await app.fetch(new Request("http://localhost/x"));
191
+
192
+ expect(res.headers.get("strict-transport-security")).toBe("max-age=31536000");
193
+ });
194
+
195
+ test("hsts object tunes max-age, subdomains, preload", async () => {
196
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
197
+ const app = oke({ name: "sec-hsts-tuned" }).plug(
198
+ securityHeaders({ hsts: { maxAge: 63072000, includeSubDomains: true, preload: true } }),
199
+ );
200
+
201
+ const res = await app.fetch(new Request("http://localhost/x"));
202
+
203
+ expect(res.headers.get("strict-transport-security")).toBe(
204
+ "max-age=63072000; includeSubDomains; preload",
205
+ );
206
+ });
207
+
208
+ test("permissions-policy and cross-origin policies stamp when configured", async () => {
209
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
210
+ const app = oke({ name: "sec-extra" }).plug(
211
+ securityHeaders({
212
+ permissionsPolicy: "camera=(), microphone=()",
213
+ crossOriginOpenerPolicy: "same-origin",
214
+ crossOriginResourcePolicy: "same-site",
215
+ }),
216
+ );
217
+
218
+ const res = await app.fetch(new Request("http://localhost/x"));
219
+
220
+ expect(res.headers.get("permissions-policy")).toBe("camera=(), microphone=()");
221
+ expect(res.headers.get("cross-origin-opener-policy")).toBe("same-origin");
222
+ expect(res.headers.get("cross-origin-resource-policy")).toBe("same-site");
223
+ });
224
+
225
+ test("an explicit app-set value wins by default", async () => {
226
+ on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
227
+ const app = oke({ name: "sec-keep" });
228
+ app.hook("onResponse", (ctx) => {
229
+ if (!ctx.response) return;
230
+ const headers = new Headers(ctx.response.headers);
231
+ headers.set("x-frame-options", "SAMEORIGIN");
232
+ ctx.response = new Response(ctx.response.body, {
233
+ status: ctx.response.status,
234
+ headers,
235
+ });
236
+ });
237
+ app.plug(securityHeaders());
238
+
239
+ const res = await app.fetch(new Request("http://localhost/x"));
240
+
241
+ expect(res.headers.get("x-frame-options")).toBe("SAMEORIGIN");
242
+ });
243
+ });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Official `security-headers` plugin — the complete secure-headers set on
3
+ * every HTTP response (helmet parity, API-first defaults). Uses only the
4
+ * public plugin API (unified-theory §14).
5
+ */
6
+
7
+ import { plugin, type PluginDef } from "../kernel/plugin.ts";
8
+ import {
9
+ isConfigSource,
10
+ pluginConfigSnapshot,
11
+ resolvePluginOptions,
12
+ withConfigTable,
13
+ type ConfigSource,
14
+ } from "./config-source.ts";
15
+ import { setUnlessPresent, withHeaders } from "./headers.ts";
16
+
17
+ /** HSTS value options (see {@link SecurityHeadersOptions.hsts}). */
18
+ export interface HstsOptions {
19
+ /** `max-age` in seconds. Default `31536000` (one year). */
20
+ readonly maxAge?: number;
21
+ /** Append `includeSubDomains`. Default `false`. */
22
+ readonly includeSubDomains?: boolean;
23
+ /** Append `preload`. Only meaningful with subdomains + a ≥1-year max-age. */
24
+ readonly preload?: boolean;
25
+ }
26
+
27
+ /** Structured CSP (see {@link SecurityHeadersOptions.contentSecurityPolicy}). */
28
+ export interface CspOptions {
29
+ /**
30
+ * Directive map — kebab-case (`script-src`) or camelCase (`scriptSrc`)
31
+ * keys, values as string arrays. An empty array emits a bare directive
32
+ * (e.g. `upgrade-insecure-requests`).
33
+ */
34
+ readonly directives: Readonly<Record<string, readonly string[]>>;
35
+ /** Merge over {@link defaultCspDirectives}. Default `true`. */
36
+ readonly useDefaults?: boolean;
37
+ /** Emit as `Content-Security-Policy-Report-Only`. Default `false`. */
38
+ readonly reportOnly?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Helmet's default CSP — the baseline `directives` merge over unless
43
+ * `useDefaults: false`.
44
+ */
45
+ export const defaultCspDirectives: Readonly<Record<string, readonly string[]>> = {
46
+ "default-src": ["'self'"],
47
+ "base-uri": ["'self'"],
48
+ "font-src": ["'self'", "https:", "data:"],
49
+ "form-action": ["'self'"],
50
+ "frame-ancestors": ["'self'"],
51
+ "img-src": ["'self'", "data:"],
52
+ "object-src": ["'none'"],
53
+ "script-src": ["'self'"],
54
+ "script-src-attr": ["'none'"],
55
+ "style-src": ["'self'", "https:", "'unsafe-inline'"],
56
+ "upgrade-insecure-requests": [],
57
+ };
58
+
59
+ /** Options for {@link securityHeaders}. */
60
+ export interface SecurityHeadersOptions {
61
+ /**
62
+ * Content-Security-Policy — a raw header string, or a structured
63
+ * {@link CspOptions} (directive builder over helmet's defaults, optional
64
+ * report-only mode). Omitted unless provided.
65
+ */
66
+ readonly contentSecurityPolicy?: string | CspOptions;
67
+ /** X-Frame-Options value. Default `"DENY"`. */
68
+ readonly frameOptions?: "DENY" | "SAMEORIGIN";
69
+ /** Referrer-Policy value. Default `"no-referrer"`. */
70
+ readonly referrerPolicy?: string;
71
+ /**
72
+ * Strict-Transport-Security. `true` → one-year `max-age`; pass an object
73
+ * to tune. Default off — HSTS is sticky, and local dev is plain HTTP, so
74
+ * enable it deliberately for HTTPS deployments.
75
+ */
76
+ readonly hsts?: boolean | HstsOptions;
77
+ /** Permissions-Policy value (e.g. `"camera=(), microphone=()"`). Omitted unless provided. */
78
+ readonly permissionsPolicy?: string;
79
+ /**
80
+ * Cross-Origin-Opener-Policy value. Omitted unless provided — opt-in
81
+ * because OKE serves APIs, where cross-origin clients are legitimate
82
+ * (helmet targets web pages and defaults it on).
83
+ */
84
+ readonly crossOriginOpenerPolicy?: "same-origin" | "same-origin-allow-popups" | "unsafe-none";
85
+ /** Cross-Origin-Resource-Policy value. Omitted unless provided (same API rationale). */
86
+ readonly crossOriginResourcePolicy?: "same-origin" | "same-site" | "cross-origin";
87
+ /** Cross-Origin-Embedder-Policy value. Omitted unless provided (helmet also defaults it off). */
88
+ readonly crossOriginEmbedderPolicy?: "require-corp" | "credentialless";
89
+ /** Origin-Agent-Cluster header. Default `true` → `?1`. */
90
+ readonly originAgentCluster?: boolean;
91
+ /**
92
+ * X-DNS-Prefetch-Control. Default `true` → `off` (privacy-preserving);
93
+ * `{ allow: true }` → `on`; `false` omits the header.
94
+ */
95
+ readonly dnsPrefetchControl?: boolean | { readonly allow: boolean };
96
+ /** X-Download-Options: noopen (legacy IE8 mitigation, helmet parity). Default `true`. */
97
+ readonly downloadOptions?: boolean;
98
+ /** X-Permitted-Cross-Domain-Policies value. Default `"none"`. */
99
+ readonly permittedCrossDomainPolicies?: "none" | "master-only" | "by-content-type" | "all";
100
+ /**
101
+ * X-Powered-By handling. Default `true` → remove the header (it leaks
102
+ * framework fingerprints). A string sets a custom value; `false` keeps it.
103
+ */
104
+ readonly poweredBy?: boolean | string;
105
+ /**
106
+ * X-XSS-Protection: 0 — disables the legacy, buggy browser XSS auditor
107
+ * (helmet parity; modern defense is CSP). Default `true`.
108
+ */
109
+ readonly xssProtection?: boolean;
110
+ /**
111
+ * Replace headers the app already set. Default `false` — an explicit
112
+ * app-level value always wins.
113
+ */
114
+ readonly override?: boolean;
115
+ }
116
+
117
+ /** Render the Strict-Transport-Security header value. */
118
+ function hstsValue(hsts: boolean | HstsOptions): string {
119
+ const opts: HstsOptions = typeof hsts === "object" ? hsts : {};
120
+ let value = `max-age=${opts.maxAge ?? 31536000}`;
121
+ if (opts.includeSubDomains) value += "; includeSubDomains";
122
+ if (opts.preload) value += "; preload";
123
+ return value;
124
+ }
125
+
126
+ /** camelCase directive keys → kebab-case (`scriptSrc` → `script-src`). */
127
+ function directiveName(name: string): string {
128
+ return name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
129
+ }
130
+
131
+ /** Build a CSP header value from a directive map. */
132
+ function buildCsp(directives: Readonly<Record<string, readonly string[]>>): string {
133
+ return Object.entries(directives)
134
+ .map(([name, values]) => {
135
+ const kebab = directiveName(name);
136
+ return values.length === 0 ? kebab : `${kebab} ${values.join(" ")}`;
137
+ })
138
+ .join("; ");
139
+ }
140
+
141
+ /** Resolve the CSP option into header name + value. */
142
+ function cspEntry(option: string | CspOptions): { readonly name: string; readonly value: string } {
143
+ if (typeof option === "string") return { name: "content-security-policy", value: option };
144
+ const useDefaults = option.useDefaults ?? true;
145
+ const merged: Record<string, readonly string[]> = useDefaults
146
+ ? { ...defaultCspDirectives, ...option.directives }
147
+ : { ...option.directives };
148
+ return {
149
+ name:
150
+ (option.reportOnly ?? false)
151
+ ? "content-security-policy-report-only"
152
+ : "content-security-policy",
153
+ value: buildCsp(merged),
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Apply the complete secure-headers set to every HTTP response, including
159
+ * failures (runs at `onResponse`, which fires after `onError` too). Covers
160
+ * every helmet.js middleware; API-first deviations (opt-in HSTS, COOP,
161
+ * CORP) are documented per option.
162
+ *
163
+ * Accepts static options or a {@link ConfigSource} for DB-driven config
164
+ * (e.g. flip `hsts` from the database without a redeploy).
165
+ *
166
+ * @param options - Header values, or a config source
167
+ */
168
+ export function securityHeaders(
169
+ options: SecurityHeadersOptions | ConfigSource<SecurityHeadersOptions> = {},
170
+ ): PluginDef {
171
+ const def = plugin("security-headers", {
172
+ version: "0.1.0",
173
+ config: pluginConfigSnapshot(options),
174
+ }).hook("onResponse", (ctx) => {
175
+ if (!ctx.response) return;
176
+ const resolved = resolvePluginOptions(options);
177
+ const override = resolved.override ?? false;
178
+
179
+ ctx.response = withHeaders(ctx.response, (headers) => {
180
+ setUnlessPresent(headers, "x-content-type-options", "nosniff", override);
181
+ setUnlessPresent(headers, "x-frame-options", resolved.frameOptions ?? "DENY", override);
182
+ setUnlessPresent(
183
+ headers,
184
+ "referrer-policy",
185
+ resolved.referrerPolicy ?? "no-referrer",
186
+ override,
187
+ );
188
+
189
+ if (resolved.originAgentCluster ?? true) {
190
+ setUnlessPresent(headers, "origin-agent-cluster", "?1", override);
191
+ }
192
+ const dns = resolved.dnsPrefetchControl ?? true;
193
+ if (dns !== false) {
194
+ const allow = typeof dns === "object" ? dns.allow : false;
195
+ setUnlessPresent(headers, "x-dns-prefetch-control", allow ? "on" : "off", override);
196
+ }
197
+ if (resolved.downloadOptions ?? true) {
198
+ setUnlessPresent(headers, "x-download-options", "noopen", override);
199
+ }
200
+ setUnlessPresent(
201
+ headers,
202
+ "x-permitted-cross-domain-policies",
203
+ resolved.permittedCrossDomainPolicies ?? "none",
204
+ override,
205
+ );
206
+ if (resolved.xssProtection ?? true) {
207
+ setUnlessPresent(headers, "x-xss-protection", "0", override);
208
+ }
209
+
210
+ const poweredBy = resolved.poweredBy ?? true;
211
+ if (poweredBy === true) {
212
+ headers.delete("x-powered-by");
213
+ } else if (typeof poweredBy === "string") {
214
+ setUnlessPresent(headers, "x-powered-by", poweredBy, override);
215
+ }
216
+
217
+ if (resolved.contentSecurityPolicy !== undefined) {
218
+ const csp = cspEntry(resolved.contentSecurityPolicy);
219
+ setUnlessPresent(headers, csp.name, csp.value, override);
220
+ }
221
+ if (resolved.hsts !== undefined && resolved.hsts !== false) {
222
+ setUnlessPresent(headers, "strict-transport-security", hstsValue(resolved.hsts), override);
223
+ }
224
+ if (resolved.permissionsPolicy !== undefined) {
225
+ setUnlessPresent(headers, "permissions-policy", resolved.permissionsPolicy, override);
226
+ }
227
+ if (resolved.crossOriginOpenerPolicy !== undefined) {
228
+ setUnlessPresent(
229
+ headers,
230
+ "cross-origin-opener-policy",
231
+ resolved.crossOriginOpenerPolicy,
232
+ override,
233
+ );
234
+ }
235
+ if (resolved.crossOriginResourcePolicy !== undefined) {
236
+ setUnlessPresent(
237
+ headers,
238
+ "cross-origin-resource-policy",
239
+ resolved.crossOriginResourcePolicy,
240
+ override,
241
+ );
242
+ }
243
+ if (resolved.crossOriginEmbedderPolicy !== undefined) {
244
+ setUnlessPresent(
245
+ headers,
246
+ "cross-origin-embedder-policy",
247
+ resolved.crossOriginEmbedderPolicy,
248
+ override,
249
+ );
250
+ }
251
+ });
252
+ });
253
+
254
+ return isConfigSource(options) ? withConfigTable(def, options) : def;
255
+ }
@@ -248,9 +248,7 @@ export function projectSupplyChainSync(
248
248
  };
249
249
  }
250
250
 
251
- function projectLifecycle(
252
- pkg: PackageJsonProbe | null,
253
- ): LifecycleScriptsSignal {
251
+ function projectLifecycle(pkg: PackageJsonProbe | null): LifecycleScriptsSignal {
254
252
  if (!pkg) {
255
253
  return {
256
254
  state: "unknown",
@@ -335,9 +333,7 @@ function projectNodeScan(
335
333
  };
336
334
  }
337
335
 
338
- async function projectProvenance(
339
- options: ProjectSupplyChainOptions,
340
- ): Promise<NpmProvenanceSignal> {
336
+ async function projectProvenance(options: ProjectSupplyChainOptions): Promise<NpmProvenanceSignal> {
341
337
  if (options.provenance === "attested") {
342
338
  return { state: "pass", detail: "npm provenance attestation present" };
343
339
  }
@@ -385,10 +381,7 @@ function projectBootConflicts(
385
381
  };
386
382
  }
387
383
 
388
- async function readPackageJson(
389
- cwd: string,
390
- packageName: string,
391
- ): Promise<PackageJsonProbe | null> {
384
+ async function readPackageJson(cwd: string, packageName: string): Promise<PackageJsonProbe | null> {
392
385
  const path = resolve(cwd, "node_modules", ...packageName.split("/"), "package.json");
393
386
  try {
394
387
  const file = Bun.file(path);
@@ -422,9 +415,7 @@ async function fetchPublishedAt(
422
415
  }
423
416
  }
424
417
 
425
- async function fetchNpmProvenance(
426
- packageName: string,
427
- ): Promise<NpmProvenanceSignal> {
418
+ async function fetchNpmProvenance(packageName: string): Promise<NpmProvenanceSignal> {
428
419
  try {
429
420
  const res = await fetch(`https://registry.npmjs.org/${packageName}`, {
430
421
  headers: { Accept: "application/json" },
@@ -437,10 +428,7 @@ async function fetchNpmProvenance(
437
428
  }
438
429
  const body = (await res.json()) as {
439
430
  "dist-tags"?: { latest?: string };
440
- versions?: Record<
441
- string,
442
- { dist?: { attestations?: unknown; signatures?: unknown } }
443
- >;
431
+ versions?: Record<string, { dist?: { attestations?: unknown; signatures?: unknown } }>;
444
432
  };
445
433
  const latest = body["dist-tags"]?.latest;
446
434
  const dist = latest ? body.versions?.[latest]?.dist : undefined;