@vibesdotdev/infra-core 0.0.1 → 1.0.0-rc.11

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 (332) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +34 -63
  3. package/dist/bootstrap/seed.descriptor.d.ts +1 -1
  4. package/dist/bootstrap/seed.descriptor.d.ts.map +1 -1
  5. package/dist/bootstrap/seed.descriptor.js +1 -1
  6. package/dist/bootstrap/seed.descriptor.js.map +1 -1
  7. package/dist/bootstrap/seed.impl-shape.d.ts +1 -1
  8. package/dist/bootstrap/seed.impl-shape.d.ts.map +1 -1
  9. package/dist/bootstrap/seed.kind.d.ts +1 -3
  10. package/dist/bootstrap/seed.kind.d.ts.map +1 -1
  11. package/dist/bootstrap/seed.kind.js +17 -4
  12. package/dist/bootstrap/seed.kind.js.map +1 -1
  13. package/dist/bootstrap/seed.runner.d.ts +9 -2
  14. package/dist/bootstrap/seed.runner.d.ts.map +1 -1
  15. package/dist/bootstrap/seed.runner.js +37 -18
  16. package/dist/bootstrap/seed.runner.js.map +1 -1
  17. package/dist/capped-id.d.ts +19 -0
  18. package/dist/capped-id.d.ts.map +1 -0
  19. package/dist/capped-id.js +64 -0
  20. package/dist/capped-id.js.map +1 -0
  21. package/dist/cli/generate/artifact/artifact.cli-command.descriptor.d.ts +23 -0
  22. package/dist/cli/generate/artifact/artifact.cli-command.descriptor.d.ts.map +1 -0
  23. package/dist/cli/generate/artifact/artifact.cli-command.descriptor.js +33 -0
  24. package/dist/cli/generate/artifact/artifact.cli-command.descriptor.js.map +1 -0
  25. package/dist/cli/generate/artifact/artifact.cli-command.impl.d.ts +24 -0
  26. package/dist/cli/generate/artifact/artifact.cli-command.impl.d.ts.map +1 -0
  27. package/dist/cli/generate/artifact/artifact.cli-command.impl.js +73 -0
  28. package/dist/cli/generate/artifact/artifact.cli-command.impl.js.map +1 -0
  29. package/dist/cli/generate/context.d.ts +17 -0
  30. package/dist/cli/generate/context.d.ts.map +1 -0
  31. package/dist/cli/generate/context.js +24 -0
  32. package/dist/cli/generate/context.js.map +1 -0
  33. package/dist/cli/generate/generate/generate.cli-command.descriptor.d.ts +30 -0
  34. package/dist/cli/generate/generate/generate.cli-command.descriptor.d.ts.map +1 -0
  35. package/dist/cli/generate/generate/generate.cli-command.descriptor.js +25 -0
  36. package/dist/cli/generate/generate/generate.cli-command.descriptor.js.map +1 -0
  37. package/dist/cli/generate/generate/generate.cli-command.impl.d.ts +25 -0
  38. package/dist/cli/generate/generate/generate.cli-command.impl.d.ts.map +1 -0
  39. package/dist/cli/generate/generate/generate.cli-command.impl.js +64 -0
  40. package/dist/cli/generate/generate/generate.cli-command.impl.js.map +1 -0
  41. package/dist/cli/generate/generate-core.d.ts +51 -0
  42. package/dist/cli/generate/generate-core.d.ts.map +1 -0
  43. package/dist/cli/generate/generate-core.js +172 -0
  44. package/dist/cli/generate/generate-core.js.map +1 -0
  45. package/dist/cli/generate/index.d.ts +5 -0
  46. package/dist/cli/generate/index.d.ts.map +1 -0
  47. package/dist/cli/generate/index.js +5 -0
  48. package/dist/cli/generate/index.js.map +1 -0
  49. package/dist/credentials/resolve.d.ts +82 -22
  50. package/dist/credentials/resolve.d.ts.map +1 -1
  51. package/dist/credentials/resolve.js +251 -77
  52. package/dist/credentials/resolve.js.map +1 -1
  53. package/dist/deployment-budget.d.ts +319 -0
  54. package/dist/deployment-budget.d.ts.map +1 -0
  55. package/dist/deployment-budget.js +660 -0
  56. package/dist/deployment-budget.js.map +1 -0
  57. package/dist/deployment-environment.d.ts +30 -0
  58. package/dist/deployment-environment.d.ts.map +1 -0
  59. package/dist/deployment-environment.js +53 -0
  60. package/dist/deployment-environment.js.map +1 -0
  61. package/dist/deployment-graph.d.ts +185 -0
  62. package/dist/deployment-graph.d.ts.map +1 -0
  63. package/dist/deployment-graph.js +705 -0
  64. package/dist/deployment-graph.js.map +1 -0
  65. package/dist/deployment.d.ts +25 -4
  66. package/dist/deployment.d.ts.map +1 -1
  67. package/dist/deployment.js +105 -0
  68. package/dist/deployment.js.map +1 -1
  69. package/dist/doks/cloud/base.d.ts +49 -0
  70. package/dist/doks/cloud/base.d.ts.map +1 -0
  71. package/dist/doks/cloud/base.js +138 -0
  72. package/dist/doks/cloud/base.js.map +1 -0
  73. package/dist/doks/cloud/factory.d.ts +18 -0
  74. package/dist/doks/cloud/factory.d.ts.map +1 -0
  75. package/dist/doks/cloud/factory.js +22 -0
  76. package/dist/doks/cloud/factory.js.map +1 -0
  77. package/dist/doks/cloud/index.d.ts +5 -0
  78. package/dist/doks/cloud/index.d.ts.map +1 -0
  79. package/dist/doks/cloud/index.js +4 -0
  80. package/dist/doks/cloud/index.js.map +1 -0
  81. package/dist/doks/types.d.ts +95 -0
  82. package/dist/doks/types.d.ts.map +1 -0
  83. package/dist/doks/types.js +14 -0
  84. package/dist/doks/types.js.map +1 -0
  85. package/dist/generate/index.d.ts +29 -0
  86. package/dist/generate/index.d.ts.map +1 -0
  87. package/dist/generate/index.js +28 -0
  88. package/dist/generate/index.js.map +1 -0
  89. package/dist/generate/infra-generate.plugin.d.ts +3 -0
  90. package/dist/generate/infra-generate.plugin.d.ts.map +1 -0
  91. package/dist/generate/infra-generate.plugin.js +23 -0
  92. package/dist/generate/infra-generate.plugin.js.map +1 -0
  93. package/dist/generate/plugin.d.ts +2 -0
  94. package/dist/generate/plugin.d.ts.map +1 -0
  95. package/dist/generate/plugin.js +2 -0
  96. package/dist/generate/plugin.js.map +1 -0
  97. package/dist/heartbeat/cron-heartbeat.d.ts +97 -0
  98. package/dist/heartbeat/cron-heartbeat.d.ts.map +1 -0
  99. package/dist/heartbeat/cron-heartbeat.js +200 -0
  100. package/dist/heartbeat/cron-heartbeat.js.map +1 -0
  101. package/dist/index.d.ts +37 -22
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +29 -14
  104. package/dist/index.js.map +1 -1
  105. package/dist/infra.plugin.d.ts +1 -1
  106. package/dist/infra.plugin.d.ts.map +1 -1
  107. package/dist/infra.plugin.js +64 -12
  108. package/dist/infra.plugin.js.map +1 -1
  109. package/dist/kinds/alerts.kind.d.ts +64 -25
  110. package/dist/kinds/alerts.kind.d.ts.map +1 -1
  111. package/dist/kinds/alerts.kind.js +43 -16
  112. package/dist/kinds/alerts.kind.js.map +1 -1
  113. package/dist/kinds/artifact.kind.d.ts +58 -10
  114. package/dist/kinds/artifact.kind.d.ts.map +1 -1
  115. package/dist/kinds/artifact.kind.js +26 -9
  116. package/dist/kinds/artifact.kind.js.map +1 -1
  117. package/dist/kinds/billing.kind.d.ts +88 -0
  118. package/dist/kinds/billing.kind.d.ts.map +1 -0
  119. package/dist/kinds/billing.kind.js +48 -0
  120. package/dist/kinds/billing.kind.js.map +1 -0
  121. package/dist/kinds/build-job.kind.d.ts +383 -0
  122. package/dist/kinds/build-job.kind.d.ts.map +1 -0
  123. package/dist/kinds/build-job.kind.js +218 -0
  124. package/dist/kinds/build-job.kind.js.map +1 -0
  125. package/dist/kinds/cache-byte-store.d.ts +23 -0
  126. package/dist/kinds/cache-byte-store.d.ts.map +1 -0
  127. package/dist/kinds/cache-byte-store.js +9 -0
  128. package/dist/kinds/cache-byte-store.js.map +1 -0
  129. package/dist/kinds/cache.kind.d.ts +38 -94
  130. package/dist/kinds/cache.kind.d.ts.map +1 -1
  131. package/dist/kinds/cache.kind.js +39 -50
  132. package/dist/kinds/cache.kind.js.map +1 -1
  133. package/dist/kinds/cluster.kind.d.ts +154 -0
  134. package/dist/kinds/cluster.kind.d.ts.map +1 -0
  135. package/dist/kinds/cluster.kind.js +99 -0
  136. package/dist/kinds/cluster.kind.js.map +1 -0
  137. package/dist/kinds/compute-source.kind.d.ts +155 -0
  138. package/dist/kinds/compute-source.kind.d.ts.map +1 -0
  139. package/dist/kinds/compute-source.kind.js +158 -0
  140. package/dist/kinds/compute-source.kind.js.map +1 -0
  141. package/dist/kinds/container-registry.kind.d.ts +62 -0
  142. package/dist/kinds/container-registry.kind.d.ts.map +1 -0
  143. package/dist/kinds/container-registry.kind.js +47 -0
  144. package/dist/kinds/container-registry.kind.js.map +1 -0
  145. package/dist/kinds/database-migrator.kind.d.ts +6 -0
  146. package/dist/kinds/database-migrator.kind.d.ts.map +1 -0
  147. package/dist/kinds/database-migrator.kind.js +28 -0
  148. package/dist/kinds/database-migrator.kind.js.map +1 -0
  149. package/dist/kinds/database.kind.d.ts +114 -93
  150. package/dist/kinds/database.kind.d.ts.map +1 -1
  151. package/dist/kinds/database.kind.js +93 -54
  152. package/dist/kinds/database.kind.js.map +1 -1
  153. package/dist/kinds/deployment-provider.kind.d.ts +5 -0
  154. package/dist/kinds/deployment-provider.kind.d.ts.map +1 -0
  155. package/dist/kinds/deployment-provider.kind.js +11 -0
  156. package/dist/kinds/deployment-provider.kind.js.map +1 -0
  157. package/dist/kinds/discovery.d.ts +5 -12
  158. package/dist/kinds/discovery.d.ts.map +1 -1
  159. package/dist/kinds/discovery.js +5 -17
  160. package/dist/kinds/discovery.js.map +1 -1
  161. package/dist/kinds/dns.kind.d.ts +162 -0
  162. package/dist/kinds/dns.kind.d.ts.map +1 -0
  163. package/dist/kinds/dns.kind.js +54 -0
  164. package/dist/kinds/dns.kind.js.map +1 -0
  165. package/dist/kinds/drift.kind.d.ts +115 -0
  166. package/dist/kinds/drift.kind.d.ts.map +1 -0
  167. package/dist/kinds/drift.kind.js +60 -0
  168. package/dist/kinds/drift.kind.js.map +1 -0
  169. package/dist/kinds/edge-hostname.kind.d.ts +124 -0
  170. package/dist/kinds/edge-hostname.kind.d.ts.map +1 -0
  171. package/dist/kinds/edge-hostname.kind.js +87 -0
  172. package/dist/kinds/edge-hostname.kind.js.map +1 -0
  173. package/dist/kinds/edge-tls.kind.d.ts +194 -0
  174. package/dist/kinds/edge-tls.kind.d.ts.map +1 -0
  175. package/dist/kinds/edge-tls.kind.js +58 -0
  176. package/dist/kinds/edge-tls.kind.js.map +1 -0
  177. package/dist/kinds/firewall.kind.d.ts +92 -0
  178. package/dist/kinds/firewall.kind.d.ts.map +1 -0
  179. package/dist/kinds/firewall.kind.js +59 -0
  180. package/dist/kinds/firewall.kind.js.map +1 -0
  181. package/dist/kinds/git-hosting.kind.d.ts +36 -38
  182. package/dist/kinds/git-hosting.kind.d.ts.map +1 -1
  183. package/dist/kinds/git-hosting.kind.js +23 -34
  184. package/dist/kinds/git-hosting.kind.js.map +1 -1
  185. package/dist/kinds/index.d.ts +41 -15
  186. package/dist/kinds/index.d.ts.map +1 -1
  187. package/dist/kinds/index.js +40 -14
  188. package/dist/kinds/index.js.map +1 -1
  189. package/dist/kinds/inventory.kind.d.ts +245 -0
  190. package/dist/kinds/inventory.kind.d.ts.map +1 -0
  191. package/dist/kinds/inventory.kind.js +243 -0
  192. package/dist/kinds/inventory.kind.js.map +1 -0
  193. package/dist/kinds/load-balancer.kind.d.ts +135 -0
  194. package/dist/kinds/load-balancer.kind.d.ts.map +1 -0
  195. package/dist/kinds/load-balancer.kind.js +71 -0
  196. package/dist/kinds/load-balancer.kind.js.map +1 -0
  197. package/dist/kinds/logs.kind.d.ts +74 -3
  198. package/dist/kinds/logs.kind.d.ts.map +1 -1
  199. package/dist/kinds/logs.kind.js +26 -17
  200. package/dist/kinds/logs.kind.js.map +1 -1
  201. package/dist/kinds/nfs.kind.d.ts +67 -0
  202. package/dist/kinds/nfs.kind.d.ts.map +1 -0
  203. package/dist/kinds/nfs.kind.js +45 -0
  204. package/dist/kinds/nfs.kind.js.map +1 -0
  205. package/dist/kinds/node-pool.kind.d.ts +134 -0
  206. package/dist/kinds/node-pool.kind.d.ts.map +1 -0
  207. package/dist/kinds/node-pool.kind.js +88 -0
  208. package/dist/kinds/node-pool.kind.js.map +1 -0
  209. package/dist/kinds/object-storage.kind.d.ts +50 -24
  210. package/dist/kinds/object-storage.kind.d.ts.map +1 -1
  211. package/dist/kinds/object-storage.kind.js +40 -23
  212. package/dist/kinds/object-storage.kind.js.map +1 -1
  213. package/dist/kinds/observability.kind.d.ts +104 -6
  214. package/dist/kinds/observability.kind.d.ts.map +1 -1
  215. package/dist/kinds/observability.kind.js +67 -9
  216. package/dist/kinds/observability.kind.js.map +1 -1
  217. package/dist/kinds/package-registry.kind.d.ts +141 -35
  218. package/dist/kinds/package-registry.kind.d.ts.map +1 -1
  219. package/dist/kinds/package-registry.kind.js +55 -25
  220. package/dist/kinds/package-registry.kind.js.map +1 -1
  221. package/dist/kinds/queue.kind.d.ts +67 -63
  222. package/dist/kinds/queue.kind.d.ts.map +1 -1
  223. package/dist/kinds/queue.kind.js +55 -33
  224. package/dist/kinds/queue.kind.js.map +1 -1
  225. package/dist/kinds/read-descriptor.d.ts +15 -0
  226. package/dist/kinds/read-descriptor.d.ts.map +1 -0
  227. package/dist/kinds/read-descriptor.js +11 -0
  228. package/dist/kinds/read-descriptor.js.map +1 -0
  229. package/dist/kinds/rum.kind.d.ts +36 -6
  230. package/dist/kinds/rum.kind.d.ts.map +1 -1
  231. package/dist/kinds/rum.kind.js +6 -7
  232. package/dist/kinds/rum.kind.js.map +1 -1
  233. package/dist/kinds/sandbox.kind.d.ts +136 -40
  234. package/dist/kinds/sandbox.kind.d.ts.map +1 -1
  235. package/dist/kinds/sandbox.kind.js +82 -43
  236. package/dist/kinds/sandbox.kind.js.map +1 -1
  237. package/dist/kinds/select-compute-source.d.ts +72 -0
  238. package/dist/kinds/select-compute-source.d.ts.map +1 -0
  239. package/dist/kinds/select-compute-source.js +170 -0
  240. package/dist/kinds/select-compute-source.js.map +1 -0
  241. package/dist/kinds/service-release.kind.d.ts +771 -0
  242. package/dist/kinds/service-release.kind.d.ts.map +1 -0
  243. package/dist/kinds/service-release.kind.js +676 -0
  244. package/dist/kinds/service-release.kind.js.map +1 -0
  245. package/dist/kinds/spend-guard.kind.d.ts +41 -0
  246. package/dist/kinds/spend-guard.kind.d.ts.map +1 -0
  247. package/dist/kinds/spend-guard.kind.js +140 -0
  248. package/dist/kinds/spend-guard.kind.js.map +1 -0
  249. package/dist/kinds/utilization.kind.d.ts +88 -0
  250. package/dist/kinds/utilization.kind.d.ts.map +1 -0
  251. package/dist/kinds/utilization.kind.js +62 -0
  252. package/dist/kinds/utilization.kind.js.map +1 -0
  253. package/dist/kinds/volume.kind.d.ts +54 -0
  254. package/dist/kinds/volume.kind.d.ts.map +1 -0
  255. package/dist/kinds/volume.kind.js +39 -0
  256. package/dist/kinds/volume.kind.js.map +1 -0
  257. package/dist/kinds/vpc.kind.d.ts +61 -0
  258. package/dist/kinds/vpc.kind.d.ts.map +1 -0
  259. package/dist/kinds/vpc.kind.js +51 -0
  260. package/dist/kinds/vpc.kind.js.map +1 -0
  261. package/dist/kinds/web-app.kind.d.ts +118 -45
  262. package/dist/kinds/web-app.kind.d.ts.map +1 -1
  263. package/dist/kinds/web-app.kind.js +69 -42
  264. package/dist/kinds/web-app.kind.js.map +1 -1
  265. package/dist/kinds/worker.kind.d.ts +129 -188
  266. package/dist/kinds/worker.kind.d.ts.map +1 -1
  267. package/dist/kinds/worker.kind.js +45 -123
  268. package/dist/kinds/worker.kind.js.map +1 -1
  269. package/dist/schemas/database-migration.d.ts +173 -0
  270. package/dist/schemas/database-migration.d.ts.map +1 -0
  271. package/dist/schemas/database-migration.js +88 -0
  272. package/dist/schemas/database-migration.js.map +1 -0
  273. package/dist/schemas/deployment-environment.d.ts +11 -0
  274. package/dist/schemas/deployment-environment.d.ts.map +1 -0
  275. package/dist/schemas/deployment-environment.js +9 -0
  276. package/dist/schemas/deployment-environment.js.map +1 -0
  277. package/dist/schemas/deployment-provider.d.ts +383 -0
  278. package/dist/schemas/deployment-provider.d.ts.map +1 -0
  279. package/dist/schemas/deployment-provider.js +211 -0
  280. package/dist/schemas/deployment-provider.js.map +1 -0
  281. package/dist/schemas/log-stream.d.ts +27 -0
  282. package/dist/schemas/log-stream.d.ts.map +1 -0
  283. package/dist/schemas/log-stream.js +31 -0
  284. package/dist/schemas/log-stream.js.map +1 -0
  285. package/dist/schemas/spend-guard.d.ts +155 -0
  286. package/dist/schemas/spend-guard.d.ts.map +1 -0
  287. package/dist/schemas/spend-guard.js +61 -0
  288. package/dist/schemas/spend-guard.js.map +1 -0
  289. package/dist/schemas.d.ts +195 -20
  290. package/dist/schemas.d.ts.map +1 -1
  291. package/dist/schemas.js +151 -31
  292. package/dist/schemas.js.map +1 -1
  293. package/dist/service.d.ts +87 -0
  294. package/dist/service.d.ts.map +1 -0
  295. package/dist/service.js +199 -0
  296. package/dist/service.js.map +1 -0
  297. package/dist/settle-work.d.ts +20 -0
  298. package/dist/settle-work.d.ts.map +1 -0
  299. package/dist/settle-work.js +22 -0
  300. package/dist/settle-work.js.map +1 -0
  301. package/dist/web-app-projection.d.ts +208 -0
  302. package/dist/web-app-projection.d.ts.map +1 -0
  303. package/dist/web-app-projection.js +95 -0
  304. package/dist/web-app-projection.js.map +1 -0
  305. package/package.json +123 -40
  306. package/SPEC.md +0 -169
  307. package/src/bootstrap/index.ts +0 -15
  308. package/src/bootstrap/seed.descriptor.ts +0 -24
  309. package/src/bootstrap/seed.impl-shape.ts +0 -5
  310. package/src/bootstrap/seed.kind.ts +0 -26
  311. package/src/bootstrap/seed.runner.ts +0 -87
  312. package/src/credentials/resolve.ts +0 -205
  313. package/src/deployment.ts +0 -57
  314. package/src/index.ts +0 -149
  315. package/src/infra.plugin.ts +0 -54
  316. package/src/kinds/alerts.kind.ts +0 -164
  317. package/src/kinds/artifact.kind.ts +0 -96
  318. package/src/kinds/cache.kind.ts +0 -104
  319. package/src/kinds/database.kind.ts +0 -120
  320. package/src/kinds/discovery.ts +0 -26
  321. package/src/kinds/git-hosting.kind.ts +0 -100
  322. package/src/kinds/index.ts +0 -139
  323. package/src/kinds/logs.kind.ts +0 -104
  324. package/src/kinds/object-storage.kind.ts +0 -101
  325. package/src/kinds/observability.kind.ts +0 -178
  326. package/src/kinds/package-registry.kind.ts +0 -95
  327. package/src/kinds/queue.kind.ts +0 -102
  328. package/src/kinds/rum.kind.ts +0 -135
  329. package/src/kinds/sandbox.kind.ts +0 -115
  330. package/src/kinds/web-app.kind.ts +0 -103
  331. package/src/kinds/worker.kind.ts +0 -203
  332. package/src/schemas.ts +0 -127
@@ -0,0 +1,660 @@
1
+ /**
2
+ * Deployment and build budgets, and the bundling-correctness gate a deploy runs before upload.
3
+ *
4
+ * A Worker bundle is correct when it carries only modules that belong in the scope it serves. A
5
+ * deploy walks the module graph the provider will bundle from the Worker entry and refuses
6
+ * (`deployment-scope-violation`, every violation at once) when that graph reaches:
7
+ * - a built-in module the provider does not supply, or supplies only as a non-functional stub;
8
+ * - a module whose file-scope suffix, or the export name that publishes it, declares a scope the
9
+ * provider's bundle does not serve (`*.consumer.*`, `*.cli.*`, `*.browser.*`), read by the
10
+ * kernel's one scope grammar;
11
+ * - a host package (`vibes.host`) the provider does not run on;
12
+ * - a browser-only target (`?worker`) or a path the app's `forbidden` list names.
13
+ * The provider declares what it supplies, the scopes it serves, how its bundler resolves and its
14
+ * hard upload limits (`infra/deployment-provider` `bundle`); none of it is a constant here.
15
+ *
16
+ * Size is evidence, not a rule. The total, the largest files with their package and importer
17
+ * chain, and the heaviest packages are measured and reported, and a declared `vibes.budgets`
18
+ * ceiling is compared and reported prominently, but a deploy refuses on bytes only past the
19
+ * provider's own hard limit, where the upload would fail anyway (`deployment-platform-limit-exceeded`).
20
+ *
21
+ * "vibes": { "budgets": {
22
+ * "build": { "maxSeconds": 120 },
23
+ * "deployment": {
24
+ * "outputDir": ".svelte-kit/cloudflare",
25
+ * "entry": ".svelte-kit/cloudflare/_worker.js",
26
+ * "maxBytes": 4194304,
27
+ * "forbidden": ["monaco"],
28
+ * "assets": { "maxBytes": 67108864, "maxFileBytes": 26214400 } } } }
29
+ *
30
+ * The deploy driver enforces it before any upload and the workspace check reports it; both read
31
+ * this one owner.
32
+ */
33
+ import * as z from 'zod/v4';
34
+ import { CapabilityError, matchesScope, parseScopeTokens, RuntimeHardwareSchema, RuntimeSurfaceSchema, SCOPE_HARDWARE_TOKENS, SCOPE_PURPOSE_TOKENS, SCOPE_SURFACE_TOKENS } from '@vibesdotdev/kernel';
35
+ import { jsoncCodec } from '@vibesdotdev/shared/jsonc';
36
+ import { chainOf, exportSubpathsOf, joinPath, ModuleResolver, normalizePath, relativeTo, walkModuleGraph } from "./deployment-graph.js";
37
+ export { scanModule } from "./deployment-graph.js";
38
+ /** How many of the largest files and packages a report names. */
39
+ export const DEPLOYMENT_LARGEST_FILE_COUNT = 10;
40
+ const ceiling = z.number().finite().nonnegative();
41
+ export const PackageBudgetsSchema = z.object({
42
+ build: z.object({ maxSeconds: ceiling.optional(), note: z.string().optional() }).optional(),
43
+ deployment: z
44
+ .object({
45
+ outputDir: z.string().min(1).optional(),
46
+ /** The Worker script entry, relative to the package. Absent, the provider's prepared upload paths. */
47
+ entry: z.string().min(1).optional(),
48
+ /** The Worker script size the owner expects: evidence, compared and reported, never a refusal. */
49
+ maxBytes: ceiling.optional(),
50
+ /** Substrings no Worker script file, bundled module or package may contain. */
51
+ forbidden: z.array(z.string()).optional(),
52
+ /** The static asset sizes the owner expects: evidence, compared and reported. */
53
+ assets: z.object({ maxBytes: ceiling.optional(), maxFileBytes: ceiling.optional() }).optional(),
54
+ note: z.string().optional()
55
+ })
56
+ .optional()
57
+ });
58
+ /** The manifest fields budgets read. Unknown fields are other owners' business. */
59
+ export const BudgetManifestSchema = z.object({
60
+ name: z.string().optional(),
61
+ scripts: z.record(z.string(), z.string()).optional(),
62
+ vibes: z.object({ budgets: PackageBudgetsSchema.optional() }).optional()
63
+ });
64
+ /** How a provider supplies one built-in module (named without its `node:` prefix). */
65
+ export const BundleBuiltinSchema = z.object({
66
+ /** `native` works; `partial` works for some of its API; `stub` imports but does nothing or throws. */
67
+ support: z.enum(['native', 'partial', 'stub']),
68
+ /** The provider compatibility date from which it is supplied, when it is date-gated. */
69
+ since: z.string().optional(),
70
+ /** The provider flag that supplies it before `since`. */
71
+ flag: z.string().optional(),
72
+ /** What works, or what does not, in the provider's words. */
73
+ note: z.string().optional()
74
+ });
75
+ /**
76
+ * What a deployment provider's Worker bundle is, declared by the provider on its
77
+ * `infra/deployment-provider` descriptor: the scopes the bundle serves, how its bundler resolves,
78
+ * which built-in modules it supplies and its hard size limits.
79
+ */
80
+ export const DeploymentBundleTargetSchema = z.object({
81
+ /** The runtime scopes one bundle serves. A module whose declared scope admits none of them does not belong. */
82
+ scopes: z.array(z.object({ hardware: RuntimeHardwareSchema, surface: RuntimeSurfaceSchema })).min(1),
83
+ /** Package `exports` conditions the provider's bundler resolves, in its order. */
84
+ conditions: z.array(z.string().min(1)).min(1),
85
+ /** Manifest fields read when a package declares no `exports`, in order. */
86
+ mainFields: z.array(z.string().min(1)).default(['module', 'main']),
87
+ /** Built-in modules by name, without the `node:` prefix. A `node:` module not named here is not supplied. */
88
+ builtins: z.record(z.string().min(1), BundleBuiltinSchema).default({}),
89
+ /** Specifier schemes the provider supplies whole (`cloudflare:`), and exact specifiers it injects. */
90
+ providedSchemes: z.array(z.string().regex(/^[a-z][a-z0-9+.-]*:$/)).default([]),
91
+ providedModules: z.array(z.string().min(1)).default([]),
92
+ /** Host packages (`vibes.host`) this provider runs; every other host package is out of scope. */
93
+ hostPackages: z.array(z.string().min(1)).default([]),
94
+ /**
95
+ * The field of the provider's rendered deployment artifact that names the Worker entry, relative
96
+ * to the app (`main` in a wrangler config), so a build-only check finds the entry without preparing.
97
+ */
98
+ entryField: z.string().min(1).optional(),
99
+ /** The provider's hard ceiling on one Worker script, past which its upload refuses. */
100
+ maxScriptBytes: ceiling.optional(),
101
+ /** The provider's hard ceiling on one static asset. */
102
+ maxAssetFileBytes: ceiling.optional()
103
+ });
104
+ /** The budgets a manifest declares; a malformed ceiling throws a zod error naming the field. */
105
+ export function budgetsOf(manifest) {
106
+ return PackageBudgetsSchema.parse(manifest?.vibes?.budgets ?? {});
107
+ }
108
+ /**
109
+ * Merge a package's budgets over the workspace defaults. `deployment` resolves whenever an output
110
+ * directory is known: the caller's (a deploy knows what it ships), the package's, or the
111
+ * workspace's, so no deployed package goes unmeasured. A build budget above the workspace's needs
112
+ * the package's own note; deployment sizes are evidence and need none.
113
+ */
114
+ export function resolveBudgets(defaults, own, options = {}) {
115
+ const checkedDefaults = PackageBudgetsSchema.parse(defaults);
116
+ const checkedOwn = PackageBudgetsSchema.parse(own);
117
+ const problems = [];
118
+ const buildMax = checkedOwn.build?.maxSeconds ?? checkedDefaults.build?.maxSeconds;
119
+ const buildNote = checkedOwn.build?.note;
120
+ let build;
121
+ if (buildMax !== undefined)
122
+ build = buildNote ? { maxSeconds: buildMax, note: buildNote } : { maxSeconds: buildMax };
123
+ const defaultBuildMax = checkedDefaults.build?.maxSeconds;
124
+ if (build && defaultBuildMax !== undefined && build.maxSeconds > defaultBuildMax && !build.note) {
125
+ problems.push({
126
+ dimension: 'build',
127
+ message: `build budget raised to ${build.maxSeconds}s, above the workspace default of ${defaultBuildMax}s, with no note explaining why`
128
+ });
129
+ }
130
+ const ownDeployment = checkedOwn.deployment;
131
+ const workspaceDeployment = checkedDefaults.deployment;
132
+ const outputDir = options.outputDir ?? ownDeployment?.outputDir ?? workspaceDeployment?.outputDir;
133
+ let deployment;
134
+ if (outputDir !== undefined) {
135
+ const ownMax = ownDeployment?.maxBytes;
136
+ const maxBytes = ownMax ?? workspaceDeployment?.maxBytes;
137
+ const assetsMax = ownDeployment?.assets?.maxBytes ?? workspaceDeployment?.assets?.maxBytes;
138
+ const assetFileMax = ownDeployment?.assets?.maxFileBytes ?? workspaceDeployment?.assets?.maxFileBytes;
139
+ const entry = ownDeployment?.entry ?? workspaceDeployment?.entry;
140
+ const forbidden = ownDeployment?.forbidden ?? workspaceDeployment?.forbidden;
141
+ const note = ownDeployment?.note ?? workspaceDeployment?.note;
142
+ let assets = {};
143
+ if (assetsMax !== undefined)
144
+ assets = { ...assets, maxBytes: assetsMax };
145
+ if (assetFileMax !== undefined)
146
+ assets = { ...assets, maxFileBytes: assetFileMax };
147
+ let built = { outputDir, assets };
148
+ if (maxBytes !== undefined)
149
+ built = { ...built, maxBytes, source: ownMax !== undefined ? 'package' : 'workspace' };
150
+ if (entry)
151
+ built = { ...built, entry };
152
+ if (forbidden)
153
+ built = { ...built, forbidden };
154
+ if (note)
155
+ built = { ...built, note };
156
+ deployment = built;
157
+ }
158
+ const resolved = { problems };
159
+ if (build)
160
+ resolved.build = build;
161
+ if (deployment)
162
+ resolved.deployment = deployment;
163
+ return resolved;
164
+ }
165
+ export function humanBytes(bytes) {
166
+ return bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(2)} MB` : `${Math.round(bytes / 1024)} KB`;
167
+ }
168
+ export const DeploymentViolationRuleSchema = z.enum(['builtin-unsupported', 'module-scope', 'host-package', 'browser-only', 'forbidden']);
169
+ async function filesUnder(files, directory, into) {
170
+ for (const entry of (await files.list(directory)) ?? []) {
171
+ const path = joinPath(directory, entry.name);
172
+ if (entry.kind === 'directory')
173
+ await filesUnder(files, path, into);
174
+ else if (entry.kind === 'file')
175
+ into.set(path, entry.sizeBytes ?? (await files.size(path)));
176
+ }
177
+ }
178
+ const ViteManifestSchema = z.record(z.string(), z.object({
179
+ file: z.string(),
180
+ src: z.string().optional(),
181
+ name: z.string().optional(),
182
+ isEntry: z.boolean().optional(),
183
+ imports: z.array(z.string()).optional(),
184
+ dynamicImports: z.array(z.string()).optional()
185
+ }));
186
+ /** A manifest that is not JSON or not Vite's shape is no attribution evidence; the measurement stands without it. */
187
+ function parseViteManifest(text) {
188
+ const decoded = jsoncCodec.parse(text);
189
+ if (decoded.errorOffsets.length > 0)
190
+ return undefined;
191
+ const parsed = ViteManifestSchema.safeParse(decoded.value);
192
+ return parsed.success ? parsed.data : undefined;
193
+ }
194
+ const METADATA_SKIPPED = new Set(['node_modules', '.git', 'src', 'static']);
195
+ const METADATA_DEPTH = 4;
196
+ /** Every `.vite/manifest.json` under `directory`, to a bounded depth. A malformed one is skipped: it is evidence, not a gate. */
197
+ async function viteManifestsUnder(files, directory, depth = 0) {
198
+ if (depth > METADATA_DEPTH)
199
+ return [];
200
+ const found = [];
201
+ for (const entry of (await files.list(directory)) ?? []) {
202
+ if (entry.kind !== 'directory' || METADATA_SKIPPED.has(entry.name))
203
+ continue;
204
+ const path = joinPath(directory, entry.name);
205
+ if (entry.name === '.vite') {
206
+ const text = await files.readText(joinPath(path, 'manifest.json'));
207
+ const manifest = text === undefined ? undefined : parseViteManifest(text);
208
+ if (manifest)
209
+ found.push({ base: directory, manifest });
210
+ continue;
211
+ }
212
+ found.push(...(await viteManifestsUnder(files, path, depth + 1)));
213
+ }
214
+ return found;
215
+ }
216
+ const CHAIN_DEPTH = 8;
217
+ /** The importing chain of one Vite manifest key, entry first and the key's own module last. */
218
+ export function importerChain(manifest, own) {
219
+ const importers = new Map();
220
+ for (const key of Object.keys(manifest)) {
221
+ const entry = manifest[key];
222
+ for (const imported of [...(entry?.imports ?? []), ...(entry?.dynamicImports ?? [])]) {
223
+ const list = importers.get(imported) ?? [];
224
+ list.push(key);
225
+ importers.set(imported, list);
226
+ }
227
+ }
228
+ const label = (key) => manifest[key]?.src ?? manifest[key]?.name ?? key;
229
+ const chain = [own];
230
+ const seen = new Set(chain);
231
+ let current = own;
232
+ while (chain.length < CHAIN_DEPTH && !manifest[current]?.isEntry) {
233
+ const next = (importers.get(current) ?? []).find((candidate) => !seen.has(candidate));
234
+ if (next === undefined)
235
+ break;
236
+ chain.push(next);
237
+ seen.add(next);
238
+ current = next;
239
+ }
240
+ return chain.reverse().map(label);
241
+ }
242
+ /** The npm package a bundled module source comes from; a workspace source keeps its leading path. */
243
+ export function packageOfModule(source) {
244
+ const index = source.lastIndexOf('node_modules/');
245
+ if (index >= 0) {
246
+ const [first, second] = source.slice(index + 'node_modules/'.length).split('/');
247
+ return first?.startsWith('@') && second ? `${first}/${second}` : (first ?? source);
248
+ }
249
+ return source.split('/').filter((segment) => segment !== '..' && segment !== '.').slice(0, 3).join('/');
250
+ }
251
+ /** The source module a Vite manifest names for a built chunk, relative to the manifest's package. */
252
+ function manifestSource(manifests, path) {
253
+ for (const located of manifests) {
254
+ for (const entry of Object.values(located.manifest)) {
255
+ if (normalizePath(joinPath(located.base, entry.file)) === path && entry.src)
256
+ return entry.src;
257
+ }
258
+ }
259
+ return undefined;
260
+ }
261
+ function largestFirst(entries) {
262
+ return [...entries].sort((left, right) => right[1] - left[1]);
263
+ }
264
+ const SCOPE_TOKENS = new Set([...SCOPE_HARDWARE_TOKENS, ...SCOPE_SURFACE_TOKENS, ...SCOPE_PURPOSE_TOKENS]);
265
+ const SOURCE_EXTENSION = /\.(?:m|c)?[jt]sx?$|\.svelte$|\.json$/;
266
+ /**
267
+ * The scope a file's name declares, read by the kernel's grammar: the scope tokens among the
268
+ * dot-separated segments after its stem (`report.impl.consumer.ts` is `consumer`,
269
+ * `ai.browser.plugin.ts` is `browser`). A file with none is universal.
270
+ */
271
+ export function fileScopeOf(path) {
272
+ const base = (path.split(/[?#]/)[0] ?? path).split('/').at(-1) ?? '';
273
+ const segments = base.replace(SOURCE_EXTENSION, '').split('.').slice(1);
274
+ return parseScopeTokens(segments.filter((segment) => SCOPE_TOKENS.has(segment)).join('.'), path);
275
+ }
276
+ /** A `*.descriptor.*` module: universal by contract, whatever scope tokens its name carries. */
277
+ export function isDescriptorFile(path) {
278
+ const base = (path.split(/[?#]/)[0] ?? path).split('/').at(-1) ?? '';
279
+ return base.replace(SOURCE_EXTENSION, '').split('.').slice(1).includes('descriptor');
280
+ }
281
+ /**
282
+ * The scope an export name claims, read by the same grammar: every scope token among its path
283
+ * and dot segments (`./consumer/plugin` is `consumer`, `./browser-automation/cli.plugin` is `cli`).
284
+ * An export name is an environment claim.
285
+ */
286
+ export function exportScopeOf(subpath) {
287
+ const tokens = subpath
288
+ .split('/')
289
+ .flatMap((segment) => segment.split('.'))
290
+ .filter((segment) => SCOPE_TOKENS.has(segment));
291
+ return parseScopeTokens(tokens.join('.'), subpath);
292
+ }
293
+ /** Whether a declared scope admits any scope the bundle serves. Purposes have no bearing on placement. */
294
+ export function scopeAdmitsTarget(constraint, target) {
295
+ if (!constraint)
296
+ return true;
297
+ const placement = {};
298
+ if (constraint.hardware?.length)
299
+ placement.hardware = constraint.hardware;
300
+ if (constraint.surfaces?.length)
301
+ placement.surfaces = constraint.surfaces;
302
+ return target.scopes.some((scope) => matchesScope({ hardware: scope.hardware, surface: scope.surface }, placement));
303
+ }
304
+ function spelling(constraint) {
305
+ return [...(constraint.hardware ?? []), ...(constraint.surfaces ?? []), ...(constraint.purposes ?? [])].join('.');
306
+ }
307
+ function targetSpelling(target) {
308
+ return target.scopes.map((scope) => `${scope.hardware}.${scope.surface}`).join(', ');
309
+ }
310
+ const BROWSER_ONLY_QUERY = /[?&](?:worker|sharedworker)(?:&|$)/;
311
+ /** Violations of one module, collected per (rule, package, detail) so a barrel that drags in fifty files reads as one finding. */
312
+ class ViolationCollector {
313
+ #byKey = new Map();
314
+ add(violation, groupBy) {
315
+ const key = `${violation.rule}\u0000${violation.package ?? ''}\u0000${groupBy}`;
316
+ const existing = this.#byKey.get(key);
317
+ if (existing)
318
+ existing.occurrences += 1;
319
+ else
320
+ this.#byKey.set(key, { violation, occurrences: 1 });
321
+ }
322
+ list() {
323
+ return [...this.#byKey.values()].map(({ violation, occurrences }) => ({ ...violation, occurrences }));
324
+ }
325
+ }
326
+ function moduleScopeDetail(module, target) {
327
+ const owner = module.owner;
328
+ // The file-scope grammar is the Vibes convention; third-party packages name files their own way
329
+ // (an `index.browser.js` is that package's choice for the `browser` condition the bundler asked for).
330
+ if (!owner?.vibes)
331
+ return undefined;
332
+ // A descriptor imports in every scope whatever scope its name carries: carrying no
333
+ // implementation is the point (kernel SPEC, file-scope grammar).
334
+ if (isDescriptorFile(module.path))
335
+ return undefined;
336
+ const suffix = fileScopeOf(module.path);
337
+ if (suffix && !scopeAdmitsTarget(suffix, target)) {
338
+ return `its file name declares scope '${spelling(suffix)}', which serves none of ${targetSpelling(target)}`;
339
+ }
340
+ const claims = exportSubpathsOf(owner, module.path)
341
+ .map((subpath) => ({ subpath, scope: exportScopeOf(subpath) }))
342
+ .filter((claim) => claim.scope !== undefined);
343
+ if (claims.length > 0 && !claims.some((claim) => scopeAdmitsTarget(claim.scope, target))) {
344
+ const [first] = claims;
345
+ if (first)
346
+ return `${owner.name} publishes it as '${first.subpath}', which claims scope '${spelling(first.scope)}' and serves none of ${targetSpelling(target)}`;
347
+ }
348
+ return undefined;
349
+ }
350
+ /**
351
+ * Measure a built deployment and check its Worker graph. `entries` are the Worker script entries
352
+ * (absolute); the declared `entry` wins over them. A missing output directory is a typed refusal:
353
+ * an unbuilt output is not a small one. Without a provider `target`, the graph is measured and
354
+ * the app's `forbidden` list applies, but no provider scope or built-in rule can.
355
+ */
356
+ export async function measureDeploymentOutput(input) {
357
+ const { files, packageDir, outputDirectory, budget } = input;
358
+ const target = input.target ? DeploymentBundleTargetSchema.parse(input.target) : undefined;
359
+ if ((await files.list(outputDirectory)) === undefined) {
360
+ throw new CapabilityError(`The deployment output ${outputDirectory} does not exist, so it cannot be measured.`, 'NOT_FOUND', {
361
+ reason: 'deployment-output-missing',
362
+ details: { outputDirectory },
363
+ recoveryHint: 'Build the app first; an unbuilt output is not a small one.'
364
+ });
365
+ }
366
+ const output = new Map();
367
+ await filesUnder(files, outputDirectory, output);
368
+ const entries = budget.entry ? [normalizePath(joinPath(packageDir, budget.entry))] : [...(input.entries ?? [])].map(normalizePath);
369
+ const resolver = new ModuleResolver(files, {
370
+ conditions: target?.conditions ?? ['import', 'module'],
371
+ mainFields: target?.mainFields ?? ['module', 'main'],
372
+ builtinNames: new Set(Object.keys(target?.builtins ?? {}))
373
+ });
374
+ let worker;
375
+ let graph;
376
+ if (entries.length > 0) {
377
+ graph = await walkModuleGraph(resolver, files, entries);
378
+ worker = await measureWorker({ graph, resolver, files, packageDir, budget, target, entries });
379
+ }
380
+ const shipped = new Set(graph ? [...graph.files.keys()] : []);
381
+ const assetFiles = [];
382
+ for (const [path, bytes] of largestFirst(output)) {
383
+ if (shipped.has(path) || shipped.has(await resolver.real(path)))
384
+ continue;
385
+ assetFiles.push({ path: relativeTo(packageDir, path), bytes });
386
+ }
387
+ const assetTotal = assetFiles.reduce((sum, file) => sum + file.bytes, 0);
388
+ const assetFileMax = budget.assets.maxFileBytes;
389
+ const oversized = assetFileMax === undefined ? [] : assetFiles.filter((file) => file.bytes > assetFileMax);
390
+ const platformAssetMax = target?.maxAssetFileBytes;
391
+ const assets = {
392
+ totalBytes: assetTotal,
393
+ fileCount: assetFiles.length,
394
+ largest: assetFiles.slice(0, DEPLOYMENT_LARGEST_FILE_COUNT),
395
+ oversized,
396
+ overPlatformLimit: platformAssetMax === undefined ? [] : assetFiles.filter((file) => file.bytes > platformAssetMax),
397
+ overBudget: (budget.assets.maxBytes !== undefined && assetTotal > budget.assets.maxBytes) || oversized.length > 0
398
+ };
399
+ let measurement = { outputDirectory, budget, assets };
400
+ if (target)
401
+ measurement = { ...measurement, target };
402
+ if (worker)
403
+ measurement = { ...measurement, worker };
404
+ return measurement;
405
+ }
406
+ async function measureWorker(input) {
407
+ const { graph, resolver, files, packageDir, budget, target } = input;
408
+ const realPackageDir = await resolver.real(packageDir);
409
+ const label = (path) => relativeTo(realPackageDir, path);
410
+ const chain = (path) => chainOf(graph, path).map(label);
411
+ const appPackage = await resolver.packageAt(realPackageDir);
412
+ const isOwnOutput = (path) => !path.includes('/node_modules/') && (path === realPackageDir || path.startsWith(`${realPackageDir}/`));
413
+ const manifests = await viteManifestsUnder(files, packageDir);
414
+ const weights = new Map();
415
+ const weigh = (name, bytes) => weights.set(name, (weights.get(name) ?? 0) + bytes);
416
+ const violations = new ViolationCollector();
417
+ const notices = [];
418
+ const forbidden = budget.forbidden ?? [];
419
+ let outputBytes = 0;
420
+ let dependencyBytes = 0;
421
+ const measured = [];
422
+ const checkModule = (module, packageName, staticallyReachable) => {
423
+ const relative = label(module.path);
424
+ const packageField = packageName ? { package: packageName } : {};
425
+ for (const pattern of forbidden) {
426
+ if (relative.includes(pattern) || (packageName ?? '').includes(pattern)) {
427
+ violations.add({ rule: 'forbidden', module: relative, importers: module.importers, detail: `the app forbids '${pattern}' in its Worker`, ...packageField }, pattern);
428
+ }
429
+ }
430
+ if (staticallyReachable && BROWSER_ONLY_QUERY.test(module.path)) {
431
+ violations.add({ rule: 'browser-only', module: relative, importers: module.importers, detail: 'a Web Worker target is browser-only and cannot run in a Worker script', ...packageField }, relative);
432
+ }
433
+ if (!target)
434
+ return;
435
+ if (module.owner?.host && !target.hostPackages.includes(module.owner.name)) {
436
+ violations.add({ rule: 'host-package', module: relative, importers: module.importers, detail: `${module.owner.name} is a host package (vibes.host) this provider does not run`, ...packageField }, module.owner.name);
437
+ }
438
+ const scoped = moduleScopeDetail(module, target);
439
+ if (scoped && staticallyReachable)
440
+ violations.add({ rule: 'module-scope', module: relative, importers: module.importers, detail: scoped, ...packageField }, relative);
441
+ };
442
+ for (const file of graph.files.values()) {
443
+ const fileStatic = graph.staticReachable.has(file.path);
444
+ const own = isOwnOutput(file.path);
445
+ const fileChain = chain(file.path);
446
+ const packageName = file.package?.name ?? appPackage?.name;
447
+ if (own)
448
+ outputBytes += file.bytes;
449
+ else
450
+ dependencyBytes += file.bytes;
451
+ let attributed = 0;
452
+ const regionWeights = new Map();
453
+ if (file.regions.length > 0) {
454
+ for (const region of file.regions) {
455
+ const source = region.source.startsWith('\0') ? undefined : normalizePath(joinPath(realPackageDir, region.source.split('?')[0] ?? region.source));
456
+ const owner = source ? await resolver.ownerOf(source) : undefined;
457
+ const name = owner?.name ?? packageOfModule(region.source);
458
+ weigh(name, region.bytes);
459
+ regionWeights.set(name, (regionWeights.get(name) ?? 0) + region.bytes);
460
+ attributed += region.bytes;
461
+ if (source) {
462
+ const query = region.source.includes('?') ? `?${region.source.split('?').slice(1).join('?')}` : '';
463
+ let module = { path: `${source}${query}`, importers: [...fileChain, label(source)] };
464
+ if (owner)
465
+ module = { ...module, owner };
466
+ checkModule(module, owner?.name ?? name, fileStatic);
467
+ }
468
+ }
469
+ }
470
+ // A built chunk without region markers is attributed by the bundler's manifest, when it names a source.
471
+ const source = own ? manifestSource(manifests, file.path) : undefined;
472
+ const sourceOwner = source && !source.startsWith('\0') ? await resolver.ownerOf(normalizePath(joinPath(realPackageDir, source))) : undefined;
473
+ const fallback = sourceOwner?.name ?? (source ? packageOfModule(source) : undefined) ?? packageName ?? '(unattributed)';
474
+ // A file is listed under the package that contributes most of its bytes.
475
+ const [dominant] = largestFirst(regionWeights);
476
+ const listedPackage = dominant && dominant[1] > file.bytes - attributed ? dominant[0] : fallback;
477
+ if (file.regions.length === 0) {
478
+ let module = { path: file.path, importers: fileChain };
479
+ if (file.package)
480
+ module = { ...module, owner: file.package };
481
+ checkModule(module, fallback, fileStatic);
482
+ }
483
+ const remainder = file.bytes - attributed;
484
+ if (remainder > 0)
485
+ weigh(fallback, remainder);
486
+ let entry = { path: label(file.path), bytes: file.bytes, importers: fileChain };
487
+ if (listedPackage !== '(unattributed)')
488
+ entry = { ...entry, package: listedPackage };
489
+ measured.push(entry);
490
+ }
491
+ for (const leaf of graph.builtins) {
492
+ const importerFile = graph.files.get(leaf.importer);
493
+ const importerPackage = importerFile?.package?.name ?? appPackage?.name;
494
+ const importers = [...chain(leaf.importer), leaf.specifier];
495
+ const packageField = importerPackage ? { package: importerPackage } : {};
496
+ if (!target)
497
+ continue;
498
+ if (target.providedModules.includes(leaf.specifier))
499
+ continue;
500
+ const scheme = /^([a-z][a-z0-9+.-]*:)/i.exec(leaf.specifier)?.[1];
501
+ if (scheme && scheme !== 'node:') {
502
+ if (target.providedSchemes.includes(scheme))
503
+ continue;
504
+ violations.add({ rule: 'builtin-unsupported', module: leaf.specifier, importers, detail: `the provider supplies no '${scheme}' modules`, ...packageField }, leaf.specifier);
505
+ continue;
506
+ }
507
+ const name = leaf.name;
508
+ const declared = target.builtins[name] ?? target.builtins[name.split('/')[0] ?? name];
509
+ if (!declared) {
510
+ violations.add({ rule: 'builtin-unsupported', module: `node:${name}`, importers, detail: `the provider does not supply node:${name}`, ...packageField }, name);
511
+ }
512
+ else if (declared.support === 'stub') {
513
+ violations.add({ rule: 'builtin-unsupported', module: `node:${name}`, importers, detail: `the provider supplies node:${name} only as a non-functional stub${declared.note ? ` (${declared.note})` : ''}`, ...packageField }, name);
514
+ }
515
+ else if (declared.support === 'partial') {
516
+ notices.push({ module: `node:${name}`, importers, detail: `the provider supplies node:${name} in part${declared.note ? `: ${declared.note}` : ''}`, ...packageField });
517
+ }
518
+ }
519
+ const totalBytes = outputBytes + dependencyBytes;
520
+ const sorted = [...measured].sort((left, right) => right.bytes - left.bytes);
521
+ return {
522
+ entries: input.entries.map((entry) => relativeTo(packageDir, entry)),
523
+ totalBytes,
524
+ fileCount: measured.length,
525
+ outputBytes,
526
+ dependencyBytes,
527
+ largest: sorted.slice(0, DEPLOYMENT_LARGEST_FILE_COUNT),
528
+ packages: largestFirst(weights).slice(0, DEPLOYMENT_LARGEST_FILE_COUNT).map(([name, bytes]) => ({ name, bytes })),
529
+ violations: violations.list(),
530
+ notices: dedupeNotices(notices),
531
+ unresolved: graph.unresolved.map((leaf) => ({ specifier: leaf.specifier, importer: label(leaf.importer) })),
532
+ overBudget: budget.maxBytes !== undefined && totalBytes > budget.maxBytes,
533
+ overPlatformLimit: target?.maxScriptBytes !== undefined && totalBytes > target.maxScriptBytes
534
+ };
535
+ }
536
+ function dedupeNotices(notices) {
537
+ const byKey = new Map();
538
+ for (const notice of notices) {
539
+ const key = `${notice.module}\u0000${notice.package ?? ''}`;
540
+ if (!byKey.has(key))
541
+ byKey.set(key, notice);
542
+ }
543
+ return [...byKey.values()];
544
+ }
545
+ const CHAIN_SHOWN = 10;
546
+ function chainText(importers) {
547
+ if (importers.length <= 1)
548
+ return '';
549
+ const shown = importers.length > CHAIN_SHOWN ? [...importers.slice(0, 3), '…', ...importers.slice(-(CHAIN_SHOWN - 4))] : importers;
550
+ return ` (imported via ${shown.join(' > ')})`;
551
+ }
552
+ function fileLines(list) {
553
+ return list.map((file, index) => {
554
+ const owner = file.package ? ` [${file.package}]` : '';
555
+ return ` ${index + 1}. ${humanBytes(file.bytes)} ${file.path}${owner}${chainText(file.importers ?? [])}`;
556
+ });
557
+ }
558
+ function declaredSize(budget) {
559
+ if (budget.maxBytes === undefined)
560
+ return 'no declared size';
561
+ return `a declared ${humanBytes(budget.maxBytes)} (${budget.source === 'package' ? 'the app' : 'workspace default'})`;
562
+ }
563
+ /** One line per violation: rule, module, package, why, and the chain that reached it. */
564
+ export function formatDeploymentViolations(violations) {
565
+ return violations.map((violation, index) => {
566
+ const owner = violation.package ? ` [${violation.package}]` : '';
567
+ const more = violation.occurrences > 1 ? ` (+${violation.occurrences - 1} more like it)` : '';
568
+ return ` ${index + 1}. ${violation.rule}: ${violation.module}${owner}: ${violation.detail}${more}${chainText(violation.importers)}`;
569
+ });
570
+ }
571
+ /** The Worker script and static asset report of a measurement: scope violations first, then the size evidence. */
572
+ export function formatDeploymentMeasurement(label, measurement) {
573
+ const { budget, worker, assets, target } = measurement;
574
+ const lines = [];
575
+ if (worker) {
576
+ if (worker.violations.length > 0) {
577
+ lines.push(`${label} Worker graph has ${worker.violations.length} scope violation(s): modules that do not belong in a ${target ? targetSpelling(target) : 'Worker'} bundle.`, ...formatDeploymentViolations(worker.violations));
578
+ }
579
+ else {
580
+ lines.push(`${label} Worker graph is scope-clean${target ? ` for ${targetSpelling(target)}` : ' (the provider declares no bundle scope, so only the forbidden list applied)'}.`);
581
+ }
582
+ if (worker.overPlatformLimit && target?.maxScriptBytes !== undefined) {
583
+ lines.push(`${label} Worker graph is ${humanBytes(worker.totalBytes)}, over the provider's hard script limit of ${humanBytes(target.maxScriptBytes)}: the upload would fail.`);
584
+ }
585
+ if (worker.overBudget && budget.maxBytes !== undefined) {
586
+ lines.push(`SIZE: ${label} Worker graph is ${humanBytes(worker.totalBytes)}, over ${declaredSize(budget)}. Size is evidence: find what the scope violations or the heaviest packages below brought in.`);
587
+ }
588
+ lines.push(`${label} Worker graph is ${humanBytes(worker.totalBytes)} in ${worker.fileCount} file(s) reachable from ${worker.entries.join(', ')} (${humanBytes(worker.outputBytes)} built output, ${humanBytes(worker.dependencyBytes)} resolved from packages; pre-minify, pre-tree-shake), against ${declaredSize(budget)}.`, 'Largest Worker files:', ...fileLines(worker.largest), 'Heaviest packages in the Worker:', ...worker.packages.map((weight, index) => ` ${index + 1}. ${humanBytes(weight.bytes)} ${weight.name}`));
589
+ if (worker.notices.length > 0) {
590
+ lines.push('Built-ins the provider supplies in part (check the APIs used):', ...worker.notices.map((notice, index) => ` ${index + 1}. ${notice.module}${notice.package ? ` [${notice.package}]` : ''}: ${notice.detail}${chainText(notice.importers)}`));
591
+ }
592
+ if (worker.unresolved.length > 0) {
593
+ lines.push(`Unresolved specifiers (${worker.unresolved.length}): ${worker.unresolved.slice(0, DEPLOYMENT_LARGEST_FILE_COUNT).map((leaf) => `${leaf.specifier} from ${leaf.importer}`).join('; ')}`);
594
+ }
595
+ }
596
+ else {
597
+ lines.push(`${label} Worker script not measured: no entry is declared (vibes.budgets.deployment.entry) and the provider prepared none.`);
598
+ }
599
+ const assetBudget = [
600
+ budget.assets.maxBytes !== undefined ? `${humanBytes(budget.assets.maxBytes)} total` : undefined,
601
+ budget.assets.maxFileBytes !== undefined ? `${humanBytes(budget.assets.maxFileBytes)} per file` : undefined
602
+ ].filter((part) => part !== undefined);
603
+ lines.push(`${label} static assets are ${humanBytes(assets.totalBytes)} in ${assets.fileCount} file(s)${assetBudget.length > 0 ? `, against a declared ${assetBudget.join(' and ')}` : ''}.`);
604
+ if (assets.overBudget)
605
+ lines.push(`SIZE: ${label} static assets are over their declared size; ${assets.oversized.length} file(s) over the declared per-file size.`);
606
+ for (const file of assets.overPlatformLimit) {
607
+ lines.push(`${label} static asset ${file.path} is ${humanBytes(file.bytes)}, over the provider's hard per-asset limit of ${humanBytes(target?.maxAssetFileBytes ?? 0)}: the upload would fail.`);
608
+ }
609
+ return lines.join('\n');
610
+ }
611
+ const SCOPE_RECOVERY_HINT = 'Fix each import at its owner: a broad barrel, a static import of an implementation or optional peer, or a plugin composed for the wrong scope. Consumer, CLI and browser code reaches a cloud bundle only through a *.consumer.plugin.ts or browser entry this bundle never imports. Run `vibes infra deploy run --build-only` to iterate without uploading.';
612
+ function violationDetails(measurement) {
613
+ const { worker, assets, target } = measurement;
614
+ return {
615
+ outputDirectory: measurement.outputDirectory,
616
+ scopes: target ? target.scopes.map((scope) => `${scope.hardware}.${scope.surface}`) : [],
617
+ violations: (worker?.violations ?? []).map((violation) => ({
618
+ rule: violation.rule,
619
+ module: violation.module,
620
+ package: violation.package ?? null,
621
+ importers: [...violation.importers],
622
+ detail: violation.detail,
623
+ occurrences: violation.occurrences
624
+ })),
625
+ worker: worker
626
+ ? {
627
+ entries: [...worker.entries],
628
+ totalBytes: worker.totalBytes,
629
+ maxScriptBytes: target?.maxScriptBytes ?? null,
630
+ overPlatformLimit: worker.overPlatformLimit,
631
+ largest: worker.largest.map((file) => ({ path: file.path, bytes: file.bytes, package: file.package ?? null, importers: [...(file.importers ?? [])] })),
632
+ packages: worker.packages.map((weight) => ({ name: weight.name, bytes: weight.bytes }))
633
+ }
634
+ : null,
635
+ assets: { totalBytes: assets.totalBytes, maxAssetFileBytes: target?.maxAssetFileBytes ?? null, overPlatformLimit: assets.overPlatformLimit.map((file) => file.path) }
636
+ };
637
+ }
638
+ /**
639
+ * The typed refusal for a measurement that must not ship. `deployment-scope-violation` lists every
640
+ * module that does not belong; `deployment-platform-limit-exceeded` is a Worker script or asset over
641
+ * the provider's own hard limit. Declared sizes never refuse. Undefined when it may ship.
642
+ */
643
+ export function deploymentRefusal(label, measurement) {
644
+ const { worker, assets } = measurement;
645
+ const summary = formatDeploymentMeasurement(label, measurement);
646
+ const details = violationDetails(measurement);
647
+ if (worker && worker.violations.length > 0) {
648
+ return new CapabilityError([`${label} Worker bundle carries ${worker.violations.length} module(s) that do not belong in its scope:`, ...formatDeploymentViolations(worker.violations), '', summary].join('\n'), 'INVALID_INPUT', { reason: 'deployment-scope-violation', retryable: false, details, recoveryHint: SCOPE_RECOVERY_HINT });
649
+ }
650
+ if (worker?.overPlatformLimit || assets.overPlatformLimit.length > 0) {
651
+ return new CapabilityError([`${label} deployment is over the provider's hard upload limit; the upload would fail.`, summary].join('\n'), 'INVALID_INPUT', {
652
+ reason: 'deployment-platform-limit-exceeded',
653
+ retryable: false,
654
+ details,
655
+ recoveryHint: 'Find what the heaviest packages and largest files brought in and remove it at its owner; the provider will not accept this upload.'
656
+ });
657
+ }
658
+ return undefined;
659
+ }
660
+ //# sourceMappingURL=deployment-budget.js.map