@spacefast/common 0.0.12 → 0.0.17

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 (189) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +486 -1196
  14. package/dist/contracts/access.js +323 -1054
  15. package/dist/contracts/activity.d.ts +1 -1
  16. package/dist/contracts/activity.js +11 -15
  17. package/dist/contracts/api-keys.d.ts +16 -0
  18. package/dist/contracts/api-keys.js +11 -9
  19. package/dist/contracts/archives.d.ts +1 -2
  20. package/dist/contracts/archives.js +15 -17
  21. package/dist/contracts/beta.js +1 -1
  22. package/dist/contracts/builds.js +14 -14
  23. package/dist/contracts/channels.js +2 -2
  24. package/dist/contracts/comments.d.ts +143 -99
  25. package/dist/contracts/comments.js +194 -79
  26. package/dist/contracts/common.d.ts +12 -12
  27. package/dist/contracts/common.js +33 -30
  28. package/dist/contracts/continuation.js +5 -5
  29. package/dist/contracts/countries.js +2 -7
  30. package/dist/contracts/data-source-routes.d.ts +20 -0
  31. package/dist/contracts/data-source-routes.js +15 -0
  32. package/dist/contracts/device-auth.d.ts +13 -9
  33. package/dist/contracts/device-auth.js +12 -12
  34. package/dist/contracts/docs.d.ts +10 -10
  35. package/dist/contracts/domains.js +39 -33
  36. package/dist/contracts/enums.d.ts +4 -14
  37. package/dist/contracts/enums.js +3 -10
  38. package/dist/contracts/error-code-meta.d.ts +122 -56
  39. package/dist/contracts/error-code-meta.js +43 -19
  40. package/dist/contracts/error-codes.d.ts +7 -4
  41. package/dist/contracts/error-codes.js +48 -24
  42. package/dist/contracts/events.d.ts +22 -15
  43. package/dist/contracts/events.js +33 -5
  44. package/dist/contracts/execution.d.ts +238 -0
  45. package/dist/contracts/execution.js +309 -0
  46. package/dist/contracts/feature-lifecycle.d.ts +39 -188
  47. package/dist/contracts/feature-lifecycle.js +52 -196
  48. package/dist/contracts/features.d.ts +13 -115
  49. package/dist/contracts/features.js +5 -35
  50. package/dist/contracts/functions.d.ts +597 -0
  51. package/dist/contracts/functions.js +475 -0
  52. package/dist/contracts/grants.d.ts +1175 -0
  53. package/dist/contracts/grants.js +783 -0
  54. package/dist/contracts/ids.d.ts +7 -5
  55. package/dist/contracts/ids.js +7 -7
  56. package/dist/contracts/internal.js +2 -2
  57. package/dist/contracts/mcp.d.ts +17 -556
  58. package/dist/contracts/mcp.js +9 -103
  59. package/dist/contracts/me.js +3 -4
  60. package/dist/contracts/notifications.d.ts +108 -0
  61. package/dist/contracts/notifications.js +54 -0
  62. package/dist/contracts/oauth-resources.d.ts +22 -5
  63. package/dist/contracts/oauth-resources.js +23 -2
  64. package/dist/contracts/operations.d.ts +0 -48
  65. package/dist/contracts/operations.js +0 -19
  66. package/dist/contracts/pages.d.ts +10 -14
  67. package/dist/contracts/pages.js +17 -21
  68. package/dist/contracts/plan-policy.d.ts +1 -8
  69. package/dist/contracts/plan-policy.js +1 -4
  70. package/dist/contracts/platform.d.ts +78 -30
  71. package/dist/contracts/platform.js +69 -20
  72. package/dist/contracts/problem-document.d.ts +14 -0
  73. package/dist/contracts/problem-document.js +35 -0
  74. package/dist/contracts/push-new.d.ts +41 -2
  75. package/dist/contracts/push-new.js +4 -0
  76. package/dist/contracts/quotas.d.ts +0 -1
  77. package/dist/contracts/quotas.js +0 -5
  78. package/dist/contracts/repository-connections.d.ts +1 -1
  79. package/dist/contracts/resources.d.ts +16 -71
  80. package/dist/contracts/resources.js +13 -16
  81. package/dist/contracts/runtime-api.d.ts +666 -303
  82. package/dist/contracts/runtime-api.js +272 -113
  83. package/dist/contracts/runtime-app.d.ts +168 -0
  84. package/dist/contracts/runtime-app.js +42 -0
  85. package/dist/contracts/runtime-db.d.ts +424 -0
  86. package/dist/contracts/runtime-db.js +108 -0
  87. package/dist/contracts/runtime-storage.d.ts +120 -0
  88. package/dist/contracts/runtime-storage.js +40 -0
  89. package/dist/contracts/sf-config-v1.d.ts +54 -1
  90. package/dist/contracts/sf-config-v1.js +2 -2
  91. package/dist/contracts/sites.d.ts +6 -0
  92. package/dist/contracts/sites.js +30 -11
  93. package/dist/contracts/space-config.d.ts +91 -121
  94. package/dist/contracts/space-config.js +464 -42
  95. package/dist/contracts/spaces.d.ts +300 -171
  96. package/dist/contracts/spaces.js +128 -98
  97. package/dist/contracts/superadmin-emails.d.ts +2 -2
  98. package/dist/contracts/superadmin-spaces.d.ts +26 -130
  99. package/dist/contracts/superadmin-spaces.js +22 -0
  100. package/dist/contracts/superadmin-tenants.js +4 -4
  101. package/dist/contracts/superadmin.d.ts +4 -0
  102. package/dist/contracts/superadmin.js +14 -1
  103. package/dist/contracts/tags.d.ts +6 -6
  104. package/dist/contracts/tags.js +1 -1
  105. package/dist/contracts/teams.d.ts +9 -9
  106. package/dist/contracts/teams.js +25 -16
  107. package/dist/contracts/theme-json.js +1 -1
  108. package/dist/contracts/transfers.d.ts +0 -1
  109. package/dist/contracts/transfers.js +4 -4
  110. package/dist/contracts/variables.js +3 -3
  111. package/dist/contracts/zero.d.ts +384 -596
  112. package/dist/contracts/zero.js +167 -299
  113. package/dist/dashboard-paths/index.d.ts +18 -3
  114. package/dist/dashboard-paths/index.js +19 -0
  115. package/dist/docs/agent-handoff-document.d.ts +2 -2
  116. package/dist/docs/agent-handoff-document.js +8 -7
  117. package/dist/docs/agent-prose.d.ts +22 -3
  118. package/dist/docs/agent-prose.js +227 -55
  119. package/dist/docs/agent-setup.d.ts +14 -11
  120. package/dist/docs/agent-setup.js +289 -61
  121. package/dist/docs/agent-solutions.d.ts +6 -6
  122. package/dist/docs/agent-solutions.js +24 -22
  123. package/dist/docs/catalog.d.ts +53 -98
  124. package/dist/docs/catalog.js +90 -117
  125. package/dist/docs/error-docs.js +304 -236
  126. package/dist/docs/skill-distribution.d.ts +2 -0
  127. package/dist/docs/skill-distribution.js +7 -4
  128. package/dist/docs/skills.d.ts +546 -0
  129. package/dist/docs/skills.js +340 -0
  130. package/dist/utils/auth-redirect.d.ts +1 -3
  131. package/dist/utils/auth-redirect.js +1 -21
  132. package/dist/utils/browser-credential.d.ts +19 -0
  133. package/dist/utils/browser-credential.js +62 -0
  134. package/dist/utils/build-settings.d.ts +2 -0
  135. package/dist/utils/build-settings.js +61 -9
  136. package/dist/utils/comment-avatars.d.ts +7 -0
  137. package/dist/utils/comment-avatars.js +42 -0
  138. package/dist/utils/content-type.d.ts +3 -3
  139. package/dist/utils/content-type.js +38 -61
  140. package/dist/utils/credential-policy.d.ts +1 -1
  141. package/dist/utils/credential-policy.js +4 -0
  142. package/dist/utils/domain-diagnostics.d.ts +5 -5
  143. package/dist/utils/domain-diagnostics.js +21 -10
  144. package/dist/utils/egress-policy.fixtures.json +1 -1
  145. package/dist/utils/grants.d.ts +53 -0
  146. package/dist/utils/grants.js +160 -0
  147. package/dist/utils/local-space-state.d.ts +78 -4
  148. package/dist/utils/local-space-state.js +384 -82
  149. package/dist/utils/one-shot-replay.d.ts +14 -0
  150. package/dist/utils/one-shot-replay.js +21 -0
  151. package/dist/utils/page-preview.d.ts +7 -0
  152. package/dist/utils/page-preview.js +9 -0
  153. package/dist/utils/pages.d.ts +2 -5
  154. package/dist/utils/pages.js +64 -33
  155. package/dist/utils/publish-form-data.d.ts +22 -0
  156. package/dist/utils/publish-form-data.js +26 -0
  157. package/dist/utils/publish-policy.d.ts +34 -4
  158. package/dist/utils/publish-policy.js +149 -62
  159. package/dist/utils/query-keys.d.ts +8 -10
  160. package/dist/utils/query-keys.js +14 -10
  161. package/dist/utils/runtime-paths.d.ts +3 -0
  162. package/dist/utils/runtime-paths.js +7 -0
  163. package/dist/utils/secure-local-file.d.ts +73 -0
  164. package/dist/utils/secure-local-file.js +1373 -0
  165. package/dist/utils/space-config.d.ts +17 -4
  166. package/dist/utils/space-config.js +151 -8
  167. package/dist/utils/static-runtime-policy.d.ts +5 -0
  168. package/dist/utils/static-runtime-policy.js +54 -10
  169. package/dist/utils/storage-policy.d.ts +20 -0
  170. package/dist/utils/storage-policy.fixtures.json +174 -0
  171. package/dist/utils/storage-policy.js +193 -0
  172. package/dist/utils/upload-session.d.ts +15 -1
  173. package/dist/utils/upload-session.js +68 -2
  174. package/dist/utils/version-file-browser.d.ts +13 -0
  175. package/dist/utils/version-file-browser.js +59 -0
  176. package/package.json +20 -1
  177. package/dist/contracts/mounts.d.ts +0 -142
  178. package/dist/contracts/mounts.js +0 -56
  179. package/dist/utils/access-grant.d.ts +0 -14
  180. package/dist/utils/access-grant.js +0 -88
  181. package/dist/utils/access-match.d.ts +0 -46
  182. package/dist/utils/access-match.fixtures.json +0 -513
  183. package/dist/utils/access-match.js +0 -312
  184. package/dist/utils/access-verdict.fixtures.json +0 -421
  185. package/dist/utils/asset-fingerprint.d.ts +0 -53
  186. package/dist/utils/asset-fingerprint.js +0 -574
  187. package/dist/utils/sf-config-v1.d.ts +0 -2
  188. package/dist/utils/sf-config-v1.fixtures.json +0 -9
  189. package/dist/utils/sf-config-v1.js +0 -551
@@ -0,0 +1,475 @@
1
+ import { z } from "zod";
2
+ import { appRoutePathSchema, executionCapabilitiesSchema, executionLogEntrySchema, executionLogListQuerySchema, executionLogListResponseSchema, executionSha256Schema, httpMethodSchema, runtimeKindSchema, } from "./execution.js";
3
+ /**
4
+ * Spacefast Functions: tenant workers run as Cloudflare Dynamic Workers.
5
+ *
6
+ * The shape of this product follows from two constraints that are not
7
+ * negotiable and that together rule out the obvious architecture:
8
+ *
9
+ * 1. **Cloudflare is reached from wp.cloud, never from the control plane.**
10
+ * The control plane records intent; the space's own origin performs every
11
+ * Cloudflare call.
12
+ * 2. **Every asset is served by wp.cloud.** We deliberately decline
13
+ * Cloudflare's static-asset support for user workers. A worker receives
14
+ * only the requests that could not be answered from bytes on disk, and
15
+ * `/_next/static/*` and friends never leave our origin.
16
+ *
17
+ * Dynamic Workers is what makes both hold at once. There is no upload: a
18
+ * version's bundle stays in its own content store on wp.cloud, and the host
19
+ * worker pulls it on demand through the Worker Loader binding. So there is no
20
+ * script lifecycle here — no namespace, no script name, no tag budget, no
21
+ * eviction, no garbage collection — because there is no artefact of ours living
22
+ * on Cloudflare to manage. What Cloudflare holds is a warm isolate, which is a
23
+ * cache, not a record.
24
+ *
25
+ * That also keeps the exit cheap. The contract below is "here is a module
26
+ * graph, here is its configuration, run it", which is workerd's own shape; the
27
+ * managed runtime is an implementation of it rather than the thing we built
28
+ * against.
29
+ *
30
+ * What the worker gets in exchange is the thing we could not build: a real V8
31
+ * isolate that runs npm packages and framework output — OpenNext Next.js among
32
+ * them — without us having to sandbox any of it.
33
+ *
34
+ * The cost is that the worker is a continent away from the space's MySQL.
35
+ * `ctx.db` therefore travels back over an HTTPS relay to the same PHP broker
36
+ * the local tier speaks to over a pipe, which is slower and batched rather
37
+ * than transactional. That is why capsules are a different product rather
38
+ * than a placement option — see `endpointIsPortable` in `./execution.js`.
39
+ */
40
+ export const FUNCTIONS_RUNTIME_KIND = "functions";
41
+ export const FUNCTIONS_SOURCE_METADATA_KEY = "spacefastFunctions";
42
+ export const FUNCTIONS_PLATFORM_ROUTE_PREFIX = "/__spacefast/functions";
43
+ export const FUNCTIONS_RUNTIME_ROUTE_PATHS = {
44
+ logs: `${FUNCTIONS_PLATFORM_ROUTE_PREFIX}/logs`,
45
+ /** Where a dispatched worker calls back for brokered capabilities. */
46
+ relay: `${FUNCTIONS_PLATFORM_ROUTE_PREFIX}/relay`,
47
+ };
48
+ /** Public, signed read path. Distinct from storage so the token is never a filename. */
49
+ export const FUNCTIONS_BUNDLE_ROUTE_PREFIX = `${FUNCTIONS_PLATFORM_ROUTE_PREFIX}/b`;
50
+ export const FUNCTIONS_RUNTIME_STORAGE_ROOT = "__spacefast/functions";
51
+ /**
52
+ * Where a version's compiled bundle lives.
53
+ *
54
+ * Under the same `__spacefast/` prefix as every other platform-owned path, so
55
+ * there is one convention rather than one per product. Two consequences fall
56
+ * out of that placement, and both are wanted:
57
+ *
58
+ * - It is not servable as static content. The runtime treats the whole prefix
59
+ * as control paths, so a tenant's compiled source is not readable at a
60
+ * guessable URL — and the digest that would make it guessable appears in
61
+ * build output.
62
+ * - It is written by the ordinary publish path anyway. The upload policy
63
+ * carves out exactly this subtree (`publish-policy.ts`), so the bundle rides
64
+ * the normal manifest and upload passes instead of needing a parallel one.
65
+ *
66
+ * The dispatch configuration — host, capabilities, relay credential — is not
67
+ * here and is not content at all. The engine writes it beside the version's
68
+ * file tree rather than inside it, exactly as it does for Zero, so a publish
69
+ * cannot reach it: no carve-out, no upload path, nothing to guard. A publish
70
+ * able to write that file would be a publish able to grant itself authority it
71
+ * was never given.
72
+ */
73
+ export const FUNCTIONS_RUNTIME_BUNDLE_ROOT = `${FUNCTIONS_RUNTIME_STORAGE_ROOT}/bundles`;
74
+ export const FUNCTIONS_BUNDLE_FORMAT = "spacefast.functions.bundle.v1";
75
+ /**
76
+ * Ceiling on a bundle's decoded size.
77
+ *
78
+ * The whole module graph arrives in one response so a cold isolate costs a
79
+ * single round trip, which means this number is also how much the host holds in
80
+ * memory before instantiating anything. Set below the runtime's own script
81
+ * limit so an oversized build fails in our tooling, where we can say which
82
+ * modules are large, rather than at dispatch, where the tenant sees a 502.
83
+ */
84
+ export const FUNCTIONS_BUNDLE_MAX_BYTES = 8 * 1024 * 1024;
85
+ const functionsRoutePathSchema = appRoutePathSchema({
86
+ subject: "Function routes",
87
+ });
88
+ /**
89
+ * A worker's dynamic route table. Anything not matched here is answered from
90
+ * the version's static content by wp.cloud and never reaches Cloudflare, which
91
+ * is both the cheaper path and the one that keeps assets ours.
92
+ */
93
+ export const functionsRouteSchema = z
94
+ .object({
95
+ method: httpMethodSchema.nullable().default(null),
96
+ path: functionsRoutePathSchema,
97
+ /** A terminal `:splat` route claims the whole subtree below `path`. */
98
+ subtree: z.boolean().default(false),
99
+ })
100
+ .strict();
101
+ /**
102
+ * One module in a bundle, in the shape the Worker Loader consumes.
103
+ *
104
+ * `commonjs` exists because bundlers still emit it and rejecting it would fail
105
+ * builds for a format the runtime accepts. Binary module types are deliberately
106
+ * absent: nothing we build today emits them, and adding one later is additive.
107
+ */
108
+ export const functionsBundleModuleSchema = z.union([
109
+ z.object({ js: z.string() }).strict(),
110
+ z.object({ cjs: z.string() }).strict(),
111
+ z.object({ text: z.string() }).strict(),
112
+ z.object({ json: z.unknown() }).strict(),
113
+ ]);
114
+ /**
115
+ * The document served at a bundle URL and parsed by the host.
116
+ *
117
+ * Both sides of that exchange validate against this schema, which is the point
118
+ * of it living here: the CLI's bundler and the host's loader are separate
119
+ * deployments that must agree, and the only thing keeping them honest is a
120
+ * shared definition of the bytes in between.
121
+ */
122
+ export const functionsBundleManifestSchema = z
123
+ .object({
124
+ format: z.literal(FUNCTIONS_BUNDLE_FORMAT),
125
+ mainModule: z.string().min(1).max(1024),
126
+ modules: z.record(z.string().min(1).max(1024), functionsBundleModuleSchema),
127
+ })
128
+ .strict()
129
+ .refine((manifest) => Object.hasOwn(manifest.modules, manifest.mainModule), {
130
+ message: "A bundle manifest must contain its own entry module.",
131
+ path: ["mainModule"],
132
+ });
133
+ /**
134
+ * Where the worker's compiled bytes live in the version's own content store.
135
+ * Unlike Zero's bundles these are not carried inline in the finalize payload:
136
+ * framework output routinely runs to megabytes, and inlining it would put the
137
+ * whole thing through the control plane, which the direct-from-wp.cloud rule
138
+ * forbids.
139
+ */
140
+ export const functionsBundleRefSchema = z
141
+ .object({
142
+ path: z
143
+ .string()
144
+ .min(1)
145
+ .startsWith(`${FUNCTIONS_RUNTIME_BUNDLE_ROOT}/`)
146
+ .refine((path) => !path.split("/").includes(".."), {
147
+ message: "Function bundle paths cannot traverse outside the bundle root.",
148
+ }),
149
+ sha256: executionSha256Schema,
150
+ size: z.number().int().nonnegative().max(FUNCTIONS_BUNDLE_MAX_BYTES),
151
+ })
152
+ .strict();
153
+ /**
154
+ * The path a bundle is served from.
155
+ *
156
+ * Content-addressed, so the response is immutable and can be cached at our edge
157
+ * forever — a cold isolate anywhere in the world costs one CDN hit rather than
158
+ * a round trip to the space's origin.
159
+ *
160
+ * The token rides in the path rather than the query string for exactly that
161
+ * reason: a query parameter is subject to cache-key normalisation, and a token
162
+ * that gets stripped from the key would let one space's cached bundle answer
163
+ * another space's URL.
164
+ *
165
+ * Both halves are load-bearing. The digest alone is unguessable to anyone who
166
+ * lacks the bytes, but it is derivable by anyone who *has* them, and it shows up
167
+ * in build output and `sf runtime status`. The token adds the dimension the
168
+ * digest cannot: holding the code does not entitle you to fetch it from us, and
169
+ * rotating the site secret invalidates every URL at once.
170
+ */
171
+ export function functionsBundlePath(input) {
172
+ return `${FUNCTIONS_BUNDLE_ROUTE_PREFIX}/${bundleDigest(input.sha256)}/${input.token}/bundle.json`;
173
+ }
174
+ /**
175
+ * Where the bundle actually sits in the published version — the same path minus
176
+ * the token segment. The token authorises the read; it does not name the file,
177
+ * so rotating the signing key changes every URL without moving a single byte.
178
+ */
179
+ export function functionsBundleStoragePath(input) {
180
+ return `${FUNCTIONS_RUNTIME_BUNDLE_ROOT}/${bundleDigest(input.sha256)}/bundle.json`;
181
+ }
182
+ /**
183
+ * The digest is carried in contracts as `sha256:<hex>` but appears in paths as
184
+ * bare hex: a colon is legal in a path segment yet needlessly invites
185
+ * normalisation differences between the edge, the origin, and fetch.
186
+ */
187
+ function bundleDigest(sha256) {
188
+ return sha256.startsWith("sha256:") ? sha256.slice(7) : sha256;
189
+ }
190
+ /**
191
+ * Cloudflare pins runtime behaviour to a date rather than a version number, so
192
+ * a worker built today keeps today's semantics forever. We record the author's
193
+ * choice on the version, which is what makes rollback honest: promoting an old
194
+ * version restores the compatibility date it was built against.
195
+ */
196
+ const functionsCompatibilityDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
197
+ export const functionsWorkerArtifactSchema = z
198
+ .object({
199
+ format: z.literal("spacefast.functions.worker.v1"),
200
+ appName: z.string().min(1).max(128),
201
+ /**
202
+ * What the author wrote, for display: `handler.ts`, `.open-next/worker.js`.
203
+ * It names a file in their project, which after bundling no longer exists.
204
+ */
205
+ entry: z.string().min(1).max(1024),
206
+ /**
207
+ * The entry module's name *inside the bundle manifest* — what the loader
208
+ * instantiates. Carried here rather than read from the manifest because the
209
+ * origin dispatches without ever opening the bundle: the bytes go from our
210
+ * CDN straight to the isolate, and the origin only routes.
211
+ */
212
+ mainModule: z.string().min(1).max(1024),
213
+ bundle: functionsBundleRefSchema,
214
+ compatibilityDate: functionsCompatibilityDateSchema,
215
+ compatibilityFlags: z.array(z.string().min(1).max(128)).max(32).default([]),
216
+ routes: z.array(functionsRouteSchema).max(256).default([]),
217
+ /**
218
+ * Aggregate of what the worker's handlers reach, used to decide which
219
+ * brokered capabilities the relay will honour for this version. A worker
220
+ * that never declares `db` gets a relay that refuses `db.read` outright,
221
+ * so an injected dependency cannot widen authority at runtime.
222
+ */
223
+ capabilities: executionCapabilitiesSchema,
224
+ })
225
+ .strict();
226
+ /**
227
+ * The authority a dispatched worker may exercise, in the vocabulary the host
228
+ * speaks on the wire.
229
+ *
230
+ * Deliberately a different, narrower list than `executionCapabilitiesSchema`.
231
+ * That one records what a build *declared* it uses; this one is what the origin
232
+ * *grants*, resolved by the control plane at finalize so the origin forwards a
233
+ * decision rather than making one. Anything absent fails closed at the relay.
234
+ */
235
+ export const FUNCTIONS_DISPATCH_CAPABILITIES = ["db.read", "db.write", "fetch", "log"];
236
+ export const functionsDispatchCapabilitySchema = z.enum(FUNCTIONS_DISPATCH_CAPABILITIES);
237
+ /**
238
+ * Resolves a build's declared capabilities into the grant a dispatch carries.
239
+ *
240
+ * Three of the six declared capabilities have no dispatch counterpart, and each
241
+ * for its own reason:
242
+ *
243
+ * - `env` is not a runtime call at all. Values are resolved at finalize and
244
+ * travel in the config, so a worker reading `env` reaches nothing.
245
+ * - `auth` and `realtime` bind a handler to the local tier — see
246
+ * `endpointIsPortable` — so code declaring them deploys as a capsule and
247
+ * never arrives here.
248
+ *
249
+ * `db` widens to both read and write because a declaration says a handler
250
+ * touches the database, not how. Narrowing it would take per-statement analysis
251
+ * we do not do, and guessing "read-only" from a declaration would be a grant
252
+ * the author never asked for and cannot see.
253
+ */
254
+ export function functionsDispatchCapabilities(capabilities) {
255
+ const granted = new Set();
256
+ if (capabilities.db) {
257
+ granted.add("db.read");
258
+ granted.add("db.write");
259
+ }
260
+ if (capabilities.fetch) {
261
+ granted.add("fetch");
262
+ }
263
+ if (capabilities.logging) {
264
+ granted.add("log");
265
+ }
266
+ // Filtered from the canonical list so the order is stable: the grant is part
267
+ // of the host's isolate identity, and a reordering would retire warm isolates
268
+ // for no reason.
269
+ return FUNCTIONS_DISPATCH_CAPABILITIES.filter((capability) => granted.has(capability));
270
+ }
271
+ /** Everything except `log` needs the relay to mean anything. */
272
+ export function functionsGrantNeedsRelay(granted) {
273
+ return granted.some((capability) => capability !== "log");
274
+ }
275
+ /**
276
+ * What the space origin needs in order to dispatch to the host on its own. The
277
+ * shared secret is a wp.cloud site secret; it is never part of a version's
278
+ * content and never reaches tenant code, so it is not modelled here.
279
+ *
280
+ * There is no account id and no namespace: the origin talks to one host worker
281
+ * over HTTPS and holds no Cloudflare API credential at all.
282
+ */
283
+ export const functionsHostConfigSchema = z
284
+ .object({
285
+ /** The host worker's hostname, which wp.cloud dispatches to. */
286
+ hostname: z.string().min(1).max(255),
287
+ /**
288
+ * Where the host pulls this version's module graph, signed and minted at
289
+ * finalize.
290
+ *
291
+ * It is here rather than on the artifact because the artifact records what
292
+ * was built — a path and a digest, both stable — while this records how to
293
+ * reach it, which depends on a signing key and a hostname the build knew
294
+ * nothing about. Rotating the key changes every URL without changing a
295
+ * single artifact.
296
+ */
297
+ bundleUrl: z.string().url(),
298
+ })
299
+ .strict();
300
+ /**
301
+ * The callback a dispatched worker uses for `ctx.db`, `ctx.fetch` and logging.
302
+ *
303
+ * Each relay call re-enters the space's PHP-FPM pool, which is the deadlock
304
+ * hazard this product's admission control exists to bound: a proxied request
305
+ * occupies a worker for its whole life while its relay calls demand more. The
306
+ * batching allowance below is the other half of the answer — a handler that
307
+ * issues twenty statements must cost one relay round trip, not twenty. Measured
308
+ * against the PHP broker, batching twenty writes into one call is 8.3x faster
309
+ * than twenty separate ones.
310
+ */
311
+ export const functionsRelayConfigSchema = z
312
+ .object({
313
+ url: z.string().url(),
314
+ /**
315
+ * Bearer credential minted per version. Rotating it also retires the warm
316
+ * isolate holding it: the host folds this token into the isolate's identity
317
+ * precisely so that a revoked credential cannot outlive its revocation.
318
+ */
319
+ token: z.string().min(1),
320
+ maxBatchStatements: z.number().int().min(1).max(256),
321
+ maxConcurrentCalls: z.number().int().min(1).max(64),
322
+ })
323
+ .strict();
324
+ /**
325
+ * What a build declares about its worker — everything the CLI can know from the
326
+ * author's project alone.
327
+ *
328
+ * Unlike Zero, this is *not* the same document the runtime receives. A build
329
+ * cannot name the host it will be dispatched to, cannot mint the credential its
330
+ * relay calls will carry, and cannot decide what authority it is granted; all
331
+ * three are the platform's to say. Keeping them out of this schema is what makes
332
+ * that structural rather than a convention — a version's own metadata has no
333
+ * field in which to assert authority it was not given.
334
+ */
335
+ export const functionsSourceFinalizeSchema = z
336
+ .object({
337
+ runtimeKind: z.literal(FUNCTIONS_RUNTIME_KIND),
338
+ artifact: functionsWorkerArtifactSchema,
339
+ variables: z
340
+ .object({
341
+ source: z.literal("spacefast-variables"),
342
+ names: z.array(z.string().min(1)).default([]),
343
+ })
344
+ .strict()
345
+ .default({ source: "spacefast-variables", names: [] }),
346
+ })
347
+ .strict();
348
+ /**
349
+ * The document the control plane sends to the runtime, which the engine stores
350
+ * beside the version's files and the origin reads on every dispatch.
351
+ *
352
+ * It is the build's declaration plus the four things only the platform can
353
+ * supply: where to dispatch, what the worker is allowed to do, the credential
354
+ * that lets it do those things, and the resolved values of its variables.
355
+ */
356
+ export const functionsRuntimeConfigSchema = functionsSourceFinalizeSchema
357
+ .extend({
358
+ host: functionsHostConfigSchema,
359
+ grantedCapabilities: z.array(functionsDispatchCapabilitySchema).max(8).default([]),
360
+ relay: functionsRelayConfigSchema.nullable().default(null),
361
+ /**
362
+ * Resolved at finalize, so a worker reading configuration never calls back
363
+ * to us for it — and so rotating a variable is a republish with an audit
364
+ * trail rather than a live mutation of what running code sees.
365
+ */
366
+ variableValues: z.record(z.string(), z.string()).default({}),
367
+ })
368
+ .strict()
369
+ .refine((config) => !functionsGrantNeedsRelay(config.grantedCapabilities) || config.relay, {
370
+ message: "Capabilities were granted without a relay to serve them.",
371
+ path: ["relay"],
372
+ });
373
+ export const functionsDeployMetadataSchema = z
374
+ .object({
375
+ artifactId: z.string().min(1).optional(),
376
+ finalize: functionsSourceFinalizeSchema,
377
+ })
378
+ .strict();
379
+ /**
380
+ * Builds the declaration a publish attaches to its version.
381
+ *
382
+ * Exists so the CLI's publish step and every test that needs a Functions
383
+ * version agree on the shape by construction rather than by copying an object
384
+ * literal around — the same reason `createZeroRuntimeFinalizePayload` exists.
385
+ */
386
+ export function createFunctionsSourceFinalize(input) {
387
+ return functionsSourceFinalizeSchema.parse({
388
+ runtimeKind: FUNCTIONS_RUNTIME_KIND,
389
+ artifact: {
390
+ format: "spacefast.functions.worker.v1",
391
+ appName: input.appName,
392
+ entry: input.entry,
393
+ mainModule: input.mainModule,
394
+ bundle: input.bundle,
395
+ compatibilityDate: input.compatibilityDate,
396
+ compatibilityFlags: input.compatibilityFlags ?? [],
397
+ routes: input.routes ?? [],
398
+ capabilities: input.capabilities ?? {},
399
+ },
400
+ variables: {
401
+ source: "spacefast-variables",
402
+ names: input.variableNames ?? [],
403
+ },
404
+ });
405
+ }
406
+ /**
407
+ * What a version's worker is currently doing, as an observer of the space can
408
+ * actually determine.
409
+ *
410
+ * There is no upload state to report. A bundle is either in the version's
411
+ * content store or it is not, and dispatch is decided by which version is
412
+ * serving — so these three cover the whole space of answers, and each one is
413
+ * checkable rather than inferred from a job we ran earlier.
414
+ */
415
+ export const functionsExecutionStateSchema = z.enum([
416
+ /** No bundle at the version's bundle path. */
417
+ "absent",
418
+ /** Bundle present and dispatchable, but this version is not the live one. */
419
+ "ready",
420
+ /** This version is live: its routes dispatch to the host. */
421
+ "serving",
422
+ ]);
423
+ /**
424
+ * What a live worker is, as the `app` member of a space's runtime status.
425
+ *
426
+ * Shaped like `zeroAppSchema` and discriminated by the same `runtimeKind`, so a
427
+ * reader switches on one field rather than probing for which product answered.
428
+ * No `versionId`: the status response it rides in already names the live
429
+ * version.
430
+ *
431
+ * Everything but `executionState` is a field of the version's own finalize
432
+ * metadata, so this answers whether or not the origin is reachable. There is no
433
+ * `database` field: `capabilities.db` is the fact the whole `/db` family
434
+ * branches on, and stating it twice invites the two to disagree.
435
+ */
436
+ export const functionsAppSchema = z
437
+ .object({
438
+ runtimeKind: z.literal(FUNCTIONS_RUNTIME_KIND),
439
+ artifactId: z.string().min(1),
440
+ appName: z.string().min(1),
441
+ entry: z.string().min(1),
442
+ compatibilityDate: functionsCompatibilityDateSchema,
443
+ compatibilityFlags: z.array(z.string()).default([]),
444
+ routes: z.array(functionsRouteSchema).default([]),
445
+ capabilities: executionCapabilitiesSchema,
446
+ executionState: functionsExecutionStateSchema,
447
+ bundleSize: z.number().int().nonnegative(),
448
+ /** Digest of the bundle bytes; also this app's `artifactId` by default. */
449
+ bundleSha256: executionSha256Schema,
450
+ variables: z
451
+ .object({
452
+ source: z.literal("spacefast-variables"),
453
+ names: z.array(z.string()).default([]),
454
+ })
455
+ .strict(),
456
+ })
457
+ .strict();
458
+ /**
459
+ * The wire shape of the origin's log-read route, not a public API contract.
460
+ *
461
+ * Handler logs reach callers through `GET /v1/spaces/:spaceId/logs/runtime`,
462
+ * which serves the platform's own `runtimeLogListResponseSchema` for both
463
+ * execution tiers. These aliases describe the hop underneath it: a worker tails
464
+ * its output to the origin that dispatched it, and the control plane reads it
465
+ * back from there. Keeping them named after this product is the point — the
466
+ * origin is the only place a wp.cloud-hosted space keeps anything, and Zero's
467
+ * records never travel this path at all.
468
+ */
469
+ export const functionsLogEntrySchema = executionLogEntrySchema;
470
+ export const functionsLogListQuerySchema = executionLogListQuerySchema;
471
+ export const functionsLogListResponseSchema = executionLogListResponseSchema;
472
+ /** Narrows a space's live runtime kind to this product. */
473
+ export function isFunctionsRuntime(kind) {
474
+ return kind === FUNCTIONS_RUNTIME_KIND;
475
+ }