@spacefast/common 0.0.11 → 0.0.13

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 (160) hide show
  1. package/dist/agents/connect-targets.d.ts +4 -4
  2. package/dist/agents/connect-targets.js +3 -3
  3. package/dist/agents/private-key-oauth.d.ts +37 -0
  4. package/dist/agents/private-key-oauth.js +158 -0
  5. package/dist/config/domains.js +1 -1
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +1 -0
  8. package/dist/config/postgres-budget.d.ts +126 -0
  9. package/dist/config/postgres-budget.js +135 -0
  10. package/dist/config/postgres-budget.json +50 -0
  11. package/dist/contracts/access.d.ts +386 -1183
  12. package/dist/contracts/access.js +240 -1018
  13. package/dist/contracts/activity.d.ts +1 -1
  14. package/dist/contracts/activity.js +11 -19
  15. package/dist/contracts/api-keys.d.ts +69 -45
  16. package/dist/contracts/api-keys.js +3 -0
  17. package/dist/contracts/archives.d.ts +9 -10
  18. package/dist/contracts/archives.js +4 -6
  19. package/dist/contracts/builds.d.ts +8 -8
  20. package/dist/contracts/builds.js +28 -48
  21. package/dist/contracts/channels.js +1 -2
  22. package/dist/contracts/comments.d.ts +102 -93
  23. package/dist/contracts/comments.js +121 -63
  24. package/dist/contracts/common.d.ts +4 -3
  25. package/dist/contracts/common.js +2 -0
  26. package/dist/contracts/countries.js +2 -7
  27. package/dist/contracts/data-source-routes.d.ts +20 -0
  28. package/dist/contracts/data-source-routes.js +15 -0
  29. package/dist/contracts/deployments.d.ts +3 -3
  30. package/dist/contracts/device-auth.d.ts +21 -15
  31. package/dist/contracts/docs.d.ts +5 -5
  32. package/dist/contracts/domains.d.ts +261 -1330
  33. package/dist/contracts/domains.js +39 -301
  34. package/dist/contracts/email-preferences.d.ts +50 -0
  35. package/dist/contracts/email-preferences.js +44 -0
  36. package/dist/contracts/enums.d.ts +3 -5
  37. package/dist/contracts/enums.js +3 -8
  38. package/dist/contracts/error-code-meta.d.ts +99 -33
  39. package/dist/contracts/error-code-meta.js +36 -9
  40. package/dist/contracts/error-codes.d.ts +1 -1
  41. package/dist/contracts/error-codes.js +28 -11
  42. package/dist/contracts/execution.d.ts +212 -0
  43. package/dist/contracts/execution.js +288 -0
  44. package/dist/contracts/feature-lifecycle.d.ts +498 -2
  45. package/dist/contracts/feature-lifecycle.js +539 -2
  46. package/dist/contracts/features.d.ts +184 -74
  47. package/dist/contracts/features.js +4 -303
  48. package/dist/contracts/functions.d.ts +729 -0
  49. package/dist/contracts/functions.js +485 -0
  50. package/dist/contracts/git.d.ts +1 -1
  51. package/dist/contracts/grants.d.ts +1158 -0
  52. package/dist/contracts/grants.js +696 -0
  53. package/dist/contracts/ids.d.ts +6 -5
  54. package/dist/contracts/ids.js +6 -5
  55. package/dist/contracts/internal.d.ts +3 -3
  56. package/dist/contracts/mcp.d.ts +12 -12
  57. package/dist/contracts/me.js +1 -1
  58. package/dist/contracts/mounts.d.ts +142 -0
  59. package/dist/contracts/mounts.js +56 -0
  60. package/dist/contracts/oauth-resources.d.ts +5 -5
  61. package/dist/contracts/oauth-resources.js +2 -0
  62. package/dist/contracts/operations.d.ts +4 -4
  63. package/dist/contracts/pages.d.ts +6 -14
  64. package/dist/contracts/pages.js +11 -23
  65. package/dist/contracts/plan-policy.d.ts +2 -9
  66. package/dist/contracts/plan-policy.js +1 -4
  67. package/dist/contracts/platform.d.ts +80 -32
  68. package/dist/contracts/platform.js +59 -12
  69. package/dist/contracts/push-new.d.ts +26 -2
  70. package/dist/contracts/push-new.js +8 -0
  71. package/dist/contracts/quotas.d.ts +0 -1
  72. package/dist/contracts/quotas.js +0 -5
  73. package/dist/contracts/repository-connections.d.ts +1 -1
  74. package/dist/contracts/resources.d.ts +21 -85
  75. package/dist/contracts/resources.js +17 -47
  76. package/dist/contracts/runtime-api.d.ts +799 -192
  77. package/dist/contracts/runtime-api.js +231 -100
  78. package/dist/contracts/runtime-app.d.ts +138 -0
  79. package/dist/contracts/runtime-app.js +21 -0
  80. package/dist/contracts/sf-config-v1.d.ts +5 -0
  81. package/dist/contracts/sf-config-v1.js +1 -1
  82. package/dist/contracts/sites.d.ts +17 -12
  83. package/dist/contracts/sites.js +30 -12
  84. package/dist/contracts/space-config.d.ts +46 -117
  85. package/dist/contracts/space-config.js +323 -25
  86. package/dist/contracts/spaces.d.ts +392 -256
  87. package/dist/contracts/spaces.js +163 -83
  88. package/dist/contracts/superadmin-emails.d.ts +27 -3
  89. package/dist/contracts/superadmin-emails.js +10 -15
  90. package/dist/contracts/superadmin-runtime.d.ts +2 -2
  91. package/dist/contracts/superadmin-spaces.d.ts +19 -142
  92. package/dist/contracts/superadmin-spaces.js +7 -0
  93. package/dist/contracts/tags.d.ts +66 -36
  94. package/dist/contracts/tags.js +195 -11
  95. package/dist/contracts/teams.d.ts +9 -9
  96. package/dist/contracts/transfers.d.ts +1 -2
  97. package/dist/contracts/variables.d.ts +4 -4
  98. package/dist/contracts/variables.js +4 -3
  99. package/dist/contracts/webhooks.d.ts +2 -2
  100. package/dist/contracts/zero.d.ts +437 -193
  101. package/dist/contracts/zero.js +149 -158
  102. package/dist/dashboard-paths/index.d.ts +3 -3
  103. package/dist/dashboard-paths/index.js +1 -0
  104. package/dist/docs/agent-prose.js +10 -8
  105. package/dist/docs/agent-setup.d.ts +6 -4
  106. package/dist/docs/agent-setup.js +26 -16
  107. package/dist/docs/agent-solutions.js +1 -1
  108. package/dist/docs/catalog.d.ts +35 -37
  109. package/dist/docs/catalog.js +36 -38
  110. package/dist/docs/error-docs.js +108 -44
  111. package/dist/utils/asset-fingerprint.d.ts +11 -0
  112. package/dist/utils/asset-fingerprint.js +46 -0
  113. package/dist/utils/auth-redirect.d.ts +1 -3
  114. package/dist/utils/auth-redirect.js +1 -21
  115. package/dist/utils/build-settings.d.ts +2 -0
  116. package/dist/utils/build-settings.js +61 -9
  117. package/dist/utils/credential-policy.d.ts +1 -1
  118. package/dist/utils/credential-policy.js +4 -0
  119. package/dist/utils/dns-instructions.d.ts +29 -0
  120. package/dist/utils/dns-instructions.js +78 -0
  121. package/dist/utils/domain-diagnostics.d.ts +29 -0
  122. package/dist/utils/domain-diagnostics.js +63 -0
  123. package/dist/utils/egress-policy.fixtures.json +51 -0
  124. package/dist/utils/grants.d.ts +53 -0
  125. package/dist/utils/grants.js +160 -0
  126. package/dist/utils/id-hints.d.ts +1 -0
  127. package/dist/utils/id-hints.js +55 -13
  128. package/dist/utils/local-space-state.js +1 -0
  129. package/dist/utils/page-colors.d.ts +50 -0
  130. package/dist/utils/page-colors.js +167 -0
  131. package/dist/utils/page-fonts.d.ts +2 -0
  132. package/dist/utils/page-fonts.js +4 -0
  133. package/dist/utils/pages.d.ts +19 -6
  134. package/dist/utils/pages.js +122 -49
  135. package/dist/utils/production-runtime.d.ts +18 -0
  136. package/dist/utils/production-runtime.js +28 -0
  137. package/dist/utils/publish-policy.d.ts +34 -4
  138. package/dist/utils/publish-policy.js +132 -61
  139. package/dist/utils/query-keys.d.ts +7 -13
  140. package/dist/utils/query-keys.js +12 -18
  141. package/dist/utils/source-archive-policy.d.ts +2 -0
  142. package/dist/utils/source-archive-policy.js +2 -0
  143. package/dist/utils/space-config.d.ts +5 -1
  144. package/dist/utils/space-config.js +35 -0
  145. package/dist/utils/static-runtime-policy.d.ts +5 -0
  146. package/dist/utils/static-runtime-policy.fixtures.json +57 -0
  147. package/dist/utils/static-runtime-policy.js +54 -10
  148. package/dist/utils/upload-session.d.ts +3 -0
  149. package/dist/utils/upload-session.js +31 -0
  150. package/package.json +6 -4
  151. package/dist/contracts/annotations.d.ts +0 -327
  152. package/dist/contracts/annotations.js +0 -235
  153. package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
  154. package/dist/contracts/generated-feature-launch-entries.js +0 -518
  155. package/dist/contracts/publishes.d.ts +0 -135
  156. package/dist/contracts/publishes.js +0 -162
  157. package/dist/utils/access-match.d.ts +0 -39
  158. package/dist/utils/access-match.js +0 -296
  159. package/dist/utils/sf-config-v1.d.ts +0 -2
  160. package/dist/utils/sf-config-v1.js +0 -542
@@ -1,12 +1,15 @@
1
1
  import { z } from "zod";
2
- import { accessEventSchema, policyDocSchema } from "./access.js";
2
+ import { accessFenceKindSchema } from "./access.js";
3
+ import { functionsRuntimeConfigSchema } from "./functions.js";
4
+ import { grantSchema } from "./grants.js";
3
5
  import { conventionFilesSchema, manifestFileSchema } from "./internal.js";
4
6
  import { runtimePagesSchema } from "./pages.js";
7
+ import { zeroEndpointSchema, zeroRuntimeConfigSchema } from "./zero.js";
5
8
  const runtimeConventionFilesSchema = conventionFilesSchema.omit({ routes: true }).strict();
6
9
  const hostCanonicalRedirectSchema = z.object({
7
10
  from: z.string().min(1),
8
- to: z.string().min(1),
9
- status: z.literal(308).default(308),
11
+ to: z.string().url(),
12
+ status: z.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)]).default(308),
10
13
  });
11
14
  // Immutable version hosts carry their own target version: the intent is the
12
15
  // COMPLETE declarative hostname map for the space (every retained ready
@@ -15,18 +18,18 @@ const versionHostnameEntrySchema = z.object({
15
18
  hostname: z.string().min(1),
16
19
  version_id: z.string().min(1),
17
20
  });
18
- const proxyHostRouteEntrySchema = z.object({
21
+ const staticMountRouteEntrySchema = z.object({
19
22
  hostname: z.string().min(1),
20
- path_prefix: z.string().min(1).default("/"),
21
- upstream: z.string().url(),
22
- noindex: z.boolean().default(false),
23
+ path_prefix: z.string().startsWith("/").min(2),
24
+ target_space_id: z.string().min(1),
25
+ target_version_id: z.string().min(1),
23
26
  });
24
27
  const runtimeRouteIntentSchema = z.object({
25
28
  production_hostnames: z.array(z.string().min(1)).default([]),
26
29
  noindex_production_hostnames: z.array(z.string().min(1)).default([]),
27
30
  version_hostnames: z.array(versionHostnameEntrySchema).default([]),
28
31
  host_canonical_redirects: z.array(hostCanonicalRedirectSchema).default([]),
29
- proxy_host_routes: z.array(proxyHostRouteEntrySchema).default([]),
32
+ static_mount_routes: z.array(staticMountRouteEntrySchema).optional(),
30
33
  });
31
34
  // Changed-path purge contract (internal-docs/platform.md "Cache Management"):
32
35
  // finalize computes the changed-path set against the previous pointer target
@@ -56,15 +59,6 @@ export const runtimeVersionCreateRequestSchema = z.object({
56
59
  files: z.array(manifestFileSchema),
57
60
  retained_files: z.array(manifestFileSchema).default([]),
58
61
  reusable_version_id: z.string().min(1).optional(),
59
- // "declared" sessions validate every PUT against the declared manifest.
60
- // "open" sessions (internal-docs/platform.md "Upload Contract" shape 2) have
61
- // no manifest: the runtime enforces path policy and the session caps per
62
- // PUT, computes streaming hashes, and finalize derives the manifest.
63
- // Plan-policy caps ride in session state, never in the upload JWT, and are
64
- // valid only on open sessions.
65
- session_mode: z.enum(["declared", "open"]).default("declared"),
66
- max_total_bytes: z.number().int().positive().optional(),
67
- max_file_count: z.number().int().positive().optional(),
68
62
  // Session state stores the upload truth (spec "Upload Contract"): the version's
69
63
  // manifest hash and the draft expiry so resume answers come from runtime state.
70
64
  manifest_hash: z.string().optional(),
@@ -78,6 +72,27 @@ export const runtimeVersionCreateResponseSchema = z.object({
78
72
  upload_id: z.string(),
79
73
  created_at: z.string(),
80
74
  });
75
+ export const runtimeUploadSessionDescriptorSchema = z.object({
76
+ upload_id: z.string().min(1),
77
+ space_id: z.string().min(1),
78
+ version_id: z.string().min(1),
79
+ expires_at: z.string(),
80
+ manifest_hash: z.string().optional(),
81
+ files: z.array(manifestFileSchema),
82
+ // Retained files only travel in the authenticated finalize request: they are
83
+ // unbounded and unused while accepting a PUT.
84
+ retained_files: z.array(manifestFileSchema).optional(),
85
+ reusable_version_id: z.string().min(1).optional(),
86
+ metadata: z.record(z.string(), z.unknown()).default({}),
87
+ convention_files: runtimeConventionFilesSchema.default({}),
88
+ });
89
+ // Budget for the half that rides in the upload JWT (everything except the
90
+ // retained manifest): 4 KiB of JSON keeps the signed token inside common 8 KiB
91
+ // header buffers, since it is sent on every PUT.
92
+ export const RUNTIME_UPLOAD_SESSION_DESCRIPTOR_MAX_BYTES = 4096;
93
+ // Budget for the whole descriptor, which is persisted on the version row and
94
+ // replayed inside the durable finalize receipt.
95
+ export const RUNTIME_UPLOAD_SESSION_STORED_MAX_BYTES = 256 * 1024;
81
96
  // Runtime-truth upload session state (spec "Upload Contract"): the resume flow asks
82
97
  // the runtime which declared files/chunks are actually present instead of guessing
83
98
  // from control-plane pending-upload state.
@@ -85,7 +100,6 @@ export const runtimeUploadSessionResponseSchema = z.object({
85
100
  space_id: z.string(),
86
101
  version_id: z.string(),
87
102
  upload_id: z.string(),
88
- session_mode: z.enum(["declared", "open"]),
89
103
  created_at: z.string().nullable(),
90
104
  expires_at: z.string().nullable(),
91
105
  manifest_hash: z.string().nullable(),
@@ -95,39 +109,80 @@ export const runtimeUploadSessionResponseSchema = z.object({
95
109
  sha256: z.string().optional(),
96
110
  uploaded: z.boolean(),
97
111
  })),
98
- // Declared paths the runtime has not committed yet (empty for open sessions).
112
+ // Declared paths the runtime has not committed yet.
99
113
  pending_paths: z.array(z.string()),
100
114
  // Staged chunked-upload parts by canonical path (contiguity is checked at complete).
101
115
  chunks: z.record(z.string(), z.array(z.number().int().positive())),
102
116
  });
103
- // Route-pointer config is pure serving policy: THE resolved access policy plus
104
- // the anonymous claim countdown. Space passwords are NOT a separate config
105
- // channel they ride inside `policy.rules` as a password challenge rule (one
106
- // access model, spec "Runtime"). Per-version serving behavior
107
- // (index/fallback/listing) is a version artifact concern, never route config.
117
+ // Route-pointer config is pure serving policy plus the anonymous claim
118
+ // countdown. Per-version serving behavior (index/fallback/listing) remains a
119
+ // version artifact concern.
120
+ export const visitorJwkSchema = z.object({
121
+ kty: z.literal("OKP"),
122
+ crv: z.literal("Ed25519"),
123
+ kid: z.string().min(1),
124
+ alg: z.literal("EdDSA"),
125
+ x: z.string().min(1),
126
+ use: z.literal("sig").optional(),
127
+ });
128
+ export const visitorJwksSchema = z.object({
129
+ keys: z.array(visitorJwkSchema).min(1),
130
+ });
131
+ export const runtimePublicExposureDescriptorSchema = z.object({
132
+ v: z.number().int().positive(),
133
+ public: z.boolean(),
134
+ authorizationDigest: z.string().regex(/^[a-f0-9]{64}$/),
135
+ contentTypes: z.array(z.string()).nullable(),
136
+ externalProxy: z.boolean(),
137
+ unmodeled: z.string(),
138
+ });
139
+ export const runtimeAuthorizationProjectionSchema = z
140
+ .object({
141
+ generation: z.number().int().nonnegative(),
142
+ // Browser sessions follow their still-live authority references across
143
+ // ordinary policy/ownership projections. Only an explicit logout-all
144
+ // rotation invalidates every otherwise-valid session.
145
+ sessionVersion: z.number().int().nonnegative(),
146
+ fence: accessFenceKindSchema,
147
+ acquireUrl: z.string().url(),
148
+ spaceClaimed: z.boolean(),
149
+ grants: z.array(grantSchema.extend({
150
+ // The durable browser session records this generation for the exact
151
+ // authority it acquired. Rotating or editing one credential Grant
152
+ // therefore removes only that authority from the session.
153
+ generation: z.number().int().positive(),
154
+ })),
155
+ })
156
+ .strict();
108
157
  const runtimeRouteConfigSchema = z.object({
109
- // THE unified policy lane ({ rules: RuntimeRule[] }) the runtime enforcer
110
- // (access-rules.php) evaluates first-match-wins for every host entry this
111
- // space serves — route hosts, custom domains, and immutable version hosts
112
- // alike. Carries platform + file + cloud rules (platform prepended, always
113
- // wins). Explicit null clears the stored policy; absence leaves it untouched.
114
- policy: policyDocSchema.nullable().optional(),
115
- // Serving-secret map (name -> value) the policy's password acquires
116
- // reference by `ref` = "secret:<name>". Carries the space-password bcrypt
117
- // verifier hash (and the compiled `_headers` basicAuth credential hashes)
118
- // the runtime resolves and `password_verify`s. Explicit null clears stored
119
- // secrets; absence leaves them untouched.
120
- secrets: z.record(z.string().min(1), z.string().min(1)).nullable().optional(),
121
- // Space session version (access-plan §3.1): the runtime rejects a visitor
122
- // token whose `sv` claim mismatches this, and derives the space-local `pw:`
123
- // key from the password secret + this value. Absent = 0.
124
- session_version: z.number().int().nonnegative().nullable().optional(),
158
+ // Canonical scoped admission projection. When present it is the sole serving
159
+ // authority; the legacy rule document below is ignored.
160
+ authorization: runtimeAuthorizationProjectionSchema.nullable().optional(),
161
+ // Digest of every input that can change anonymous/public exposure (canonical
162
+ // authorization boundaries, content types, proxy entitlement, and
163
+ // unknown serving keys). Authority-only rotation deliberately leaves it
164
+ // stable so Link/Person changes do not block on an unnecessary edge purge.
165
+ public_exposure_digest: z
166
+ .string()
167
+ .regex(/^[a-f0-9]{64}$/)
168
+ .optional(),
169
+ // The digest detects equality; the descriptor preserves enough structure for
170
+ // the callback consumer to distinguish widening from narrowing without
171
+ // reconstructing a previous config from mutable control-plane state.
172
+ public_exposure: runtimePublicExposureDescriptorSchema.optional(),
173
+ // Monotonic authorization projection generation. Short-lived handoffs are
174
+ // bound to this exact generation, Space, and host. Browser sessions use the
175
+ // projection's independent sessionVersion revocation floor.
176
+ projection_generation: z.number().int().nonnegative().nullable().optional(),
177
+ // First-party visitor-token verification is explicit.
178
+ visitor_issuer: z.string().min(1).optional(),
179
+ visitor_jwks: visitorJwksSchema.optional(),
125
180
  // Anonymous claim-window countdown (ISO timestamp), compiled into serving
126
181
  // state so the runtime can render the expiry-rescue banner for visitors
127
182
  // holding the claim-link viewer session. Null/absent once claimed.
128
183
  anonymous_expires_at: z.string().nullable().optional(),
129
184
  // Serve-time content-type allowlist. Generic serving policy: the engine has
130
- // no notion of WHY a space is restricted — it refuses (403, blocked_message
185
+ // no notion of WHY a file type is blocked — it refuses (403, blocked_message
131
186
  // body) any file whose stored Content-Type matches no pattern. Patterns are
132
187
  // exact types or `prefix/*` wildcards ("text/*"). The control plane pushes
133
188
  // it for unclaimed anonymous spaces (no opaque binaries pre-claim); explicit
@@ -146,15 +201,15 @@ const runtimeRouteConfigSchema = z.object({
146
201
  // in V1 (set via the superadmin admission endpoint); absent means the
147
202
  // env-knob default applies.
148
203
  admission: z.object({ concurrency: z.number().int().positive() }).optional(),
149
- // Durable revocation SET (access-plan revocation hardening): every
150
- // currently-revoked link:/invite:/svc: grant id, computed from the
151
- // control-plane's Postgres projection (access/resolve.ts
152
- // `revocationsForSpace`). The runtime REPLACES its revocations store's
153
- // grant bucket with exactly this set on every sync — the backstop that
154
- // converges even when the instant best-effort revoke_grant/unrevoke_grant
155
- // call was dropped. `subs` (JWT-subject revocations) are left untouched.
156
- // Absence leaves the stored set untouched.
157
- revocations: z.array(z.string().min(1)).optional(),
204
+ // Same-host production tag JavaScript. It rides the mutable route projection
205
+ // so private Spaces never fetch an anonymously readable per-Space artifact.
206
+ tag_sdk: z
207
+ .object({
208
+ revision: z.string().min(1),
209
+ body: z.string().max(5 * 1024 * 1024),
210
+ })
211
+ .nullable()
212
+ .optional(),
158
213
  // Serve-time plan entitlements (proxy-routes.md: "the rule stays in your
159
214
  // config and activates the moment you upgrade — no redeploy needed"). Stored
160
215
  // per space beside `policy`; a `planGated` compiled proxy rule
@@ -251,8 +306,8 @@ const runtimeZeroEndpointCapabilitiesSchema = z
251
306
  })
252
307
  .default({});
253
308
  const runtimeZeroEndpointSchema = z.object({
254
- method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]),
255
- path: z.string().min(1).max(2048),
309
+ method: zeroEndpointSchema.shape.method,
310
+ path: zeroEndpointSchema.shape.path,
256
311
  source: z
257
312
  .string()
258
313
  .min(1)
@@ -279,13 +334,14 @@ const runtimeZeroRunSchema = z.object({
279
334
  export const runtimeZeroModeSchema = z.enum(["active", "activating"]);
280
335
  export const runtimeVersionFinalizeRequestSchema = z.object({
281
336
  upload_id: z.string().min(1),
337
+ session: runtimeUploadSessionDescriptorSchema.optional(),
282
338
  zero_mode: runtimeZeroModeSchema.optional(),
283
- // The version's authoritative ready timestamp (epoch seconds), passed by the
284
- // control plane when the version already reached ready (re-finalize, import
285
- // re-materialization, provider migration). The engine stamps it ONCE into the
286
- // version metadata; access-window rules (windowDays) compare against this
287
- // stored value never a filesystem mtime that resets on rebuilds.
288
- ready_at: z.number().int().positive().optional(),
339
+ zero: zeroRuntimeConfigSchema.optional(),
340
+ // Dispatch configuration for a Functions version. Absent for static versions,
341
+ // and absent when a worker was published while no execution host is
342
+ // configured — the version then serves as pure static content, because a
343
+ // config is the only thing that makes the origin dispatch at all.
344
+ functions: functionsRuntimeConfigSchema.optional(),
289
345
  convention_files: runtimeConventionFilesSchema.optional(),
290
346
  // Finalize-time `{{ vars.NAME }}` substitution output (spec "Variable
291
347
  // rules"): substituted bytes replace the served files; the engine keeps the
@@ -356,16 +412,33 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
356
412
  .default("production")
357
413
  .describe("Runtime route-pointer to activate; route names are runtime-only vocabulary."),
358
414
  config: runtimeRouteConfigSchema.optional(),
415
+ // Same contract as the route PUT's config_digest: stored with the
416
+ // pointer so later conditional route PUTs can no-op against it.
417
+ config_digest: z.string().min(1).optional(),
359
418
  changed_paths: changedPathsSchema,
360
419
  previous_version_id: previousVersionIdSchema,
361
420
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
362
421
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
363
422
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
364
423
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
365
- proxy_host_routes: runtimeRouteIntentSchema.shape.proxy_host_routes,
424
+ static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
366
425
  })
367
426
  .optional(),
368
427
  });
428
+ const runtimePublicReadinessStatusSchema = z.union([
429
+ z.literal(200),
430
+ z.literal(301),
431
+ z.literal(302),
432
+ z.literal(303),
433
+ z.literal(307),
434
+ z.literal(308),
435
+ z.literal(401),
436
+ z.literal(403),
437
+ ]);
438
+ export const runtimeReadinessTargetSchema = z.object({
439
+ path: z.string().startsWith("/"),
440
+ expected_statuses: z.array(z.union([runtimePublicReadinessStatusSchema, z.literal(404)])).min(1),
441
+ });
369
442
  export const runtimeVersionFinalizeResponseSchema = z.object({
370
443
  space_id: z.string(),
371
444
  version_id: z.string(),
@@ -373,32 +446,53 @@ export const runtimeVersionFinalizeResponseSchema = z.object({
373
446
  // Runtime-compiled truth from zero/endpoints-index.json. Optional so older
374
447
  // runtimes remain parseable during rolling deploys.
375
448
  zero_endpoint_count: z.number().int().nonnegative().optional(),
376
- // Open sessions: the manifest derived from what was actually uploaded
377
- // (path, size, streaming sha256). Absent for declared sessions.
449
+ // Canonical runtime-committed manifest (path, size, streaming sha256).
450
+ // Returned by both first and idempotent finalize responses so recovery
451
+ // never needs an already-consumed upload session.
378
452
  manifest: z.array(manifestFileSchema).optional(),
453
+ readiness_target: runtimeReadinessTargetSchema,
454
+ activation_event_id: z
455
+ .string()
456
+ .min(1)
457
+ .optional()
458
+ .describe("Stable route_updated event id when finalize also activated a route."),
379
459
  });
380
460
  export const runtimeRouteUpdateRequestSchema = z.object({
381
461
  version_id: z.string().min(1),
382
462
  config: runtimeRouteConfigSchema.optional(),
463
+ // Canonical digest of `config` (control-plane routeConfigDigest). The engine
464
+ // stores it with the route pointer and no-ops the whole PUT — no policy
465
+ // store, no journal event, no edge purge — when version, digest, and
466
+ // hostname intent all match the stored route. Absent = always write.
467
+ config_digest: z.string().min(1).optional(),
383
468
  changed_paths: changedPathsSchema,
384
469
  previous_version_id: previousVersionIdSchema,
385
470
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
386
471
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
387
472
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
388
473
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
389
- proxy_host_routes: runtimeRouteIntentSchema.shape.proxy_host_routes,
474
+ static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
390
475
  });
391
476
  export const runtimeHostnameIntentUpdateRequestSchema = z.object({
392
477
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
393
478
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
394
479
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
395
480
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
396
- proxy_host_routes: runtimeRouteIntentSchema.shape.proxy_host_routes,
481
+ static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
397
482
  });
398
483
  export const runtimeRouteUpdateResponseSchema = z.object({
399
484
  space_id: z.string(),
400
485
  route_name: z.string(),
401
486
  version_id: z.string(),
487
+ // True when the engine skipped the write because version_id, config_digest,
488
+ // and hostname intent all matched the stored route: nothing was journaled,
489
+ // so callers skip the event drain too.
490
+ unchanged: z.boolean().optional(),
491
+ activation_event_id: z
492
+ .string()
493
+ .min(1)
494
+ .optional()
495
+ .describe("Stable route_updated event id for this exact route command, including retries."),
402
496
  });
403
497
  export const runtimeHostnameIntentUpdateResponseSchema = z.object({
404
498
  space_id: z.string(),
@@ -429,20 +523,6 @@ export const runtimeRetentionPolicyUpdateResponseSchema = z.object({
429
523
  space_id: z.string(),
430
524
  prunable_count: z.number().int().nonnegative(),
431
525
  });
432
- // Instant per-grant tombstone (access-plan revocation hardening): the
433
- // control plane's best-effort synchronous call at revoke/unrevoke time,
434
- // hitting the SAME `revocations.json` the durable `config.revocations` full
435
- // replace (above) also converges. `grant` must be a `link:`/`invite:`/`svc:`
436
- // row id — the runtime rejects anything else.
437
- export const runtimeGrantRevocationRequestSchema = z.object({
438
- grant: z.string().min(1),
439
- });
440
- export const runtimeGrantRevocationResponseSchema = z.object({
441
- space_id: z.string(),
442
- target_type: z.literal("grant"),
443
- grant: z.string(),
444
- revoked: z.boolean(),
445
- });
446
526
  export const runtimeSpaceDeleteResponseSchema = z.object({
447
527
  space_id: z.string(),
448
528
  status: z.literal("deleted"),
@@ -465,11 +545,9 @@ const runtimeExportImportStatusSchema = z.enum([
465
545
  export const runtimeSpaceExportStartRequestSchema = z
466
546
  .object({
467
547
  version_ids: z.array(z.string().min(1)).min(1).optional(),
468
- // Honest export options (spec SpaceExportRequest): include_files=false
469
- // produces a config-only archive; include_explicit_config=false skips the
470
- // space access policy entry. Recorded in stattic.json `includes`.
548
+ // include_files=false produces a metadata-only archive that cannot be
549
+ // imported as a serving Space. Recorded in stattic.json `includes`.
471
550
  include_files: z.boolean().optional(),
472
- include_explicit_config: z.boolean().optional(),
473
551
  })
474
552
  .strict();
475
553
  export const runtimeSpaceExportResponseSchema = z.object({
@@ -530,6 +608,7 @@ export const runtimeTransferBundleSourceRequestSchema = z.object({
530
608
  space_id: z.string().min(1),
531
609
  version_ids: z.array(z.string().min(1)),
532
610
  cursor: z.string().nullable(),
611
+ include_serving_state: z.boolean().optional(),
533
612
  });
534
613
  export const runtimeTransferBundleTargetRequestSchema = z.object({
535
614
  space_id: z.string().min(1),
@@ -548,6 +627,7 @@ export const runtimeTransferCommitRequestSchema = z.object({
548
627
  space_id: z.string().min(1),
549
628
  version_ids: z.array(z.string().min(1)),
550
629
  live_version_id: z.string().nullable(),
630
+ install_serving_state: z.boolean().optional(),
551
631
  });
552
632
  export const runtimeTransferCommitResponseSchema = z.object({
553
633
  installed: z.literal(true),
@@ -638,6 +718,56 @@ export const runtimeCallbackEventSchema = z.looseObject({
638
718
  .optional()
639
719
  .describe("True when changed_paths is the complete diff, including a known-empty diff. Absent on older engines, where an empty array remains the host-wide fallback."),
640
720
  previous_version_id: previousVersionIdSchema,
721
+ previous_public_exposure_digest: z
722
+ .string()
723
+ .min(1)
724
+ .nullable()
725
+ .optional()
726
+ .describe("Public-exposure digest stored before a route config write. Field presence proves the route already existed; null means the previous projection was unreadable."),
727
+ public_exposure_digest: z
728
+ .string()
729
+ .min(1)
730
+ .nullable()
731
+ .optional()
732
+ .describe("Public-exposure digest stored by the route config write; null means the incoming projection was unreadable."),
733
+ previous_public_exposure: runtimePublicExposureDescriptorSchema
734
+ .nullable()
735
+ .optional()
736
+ .describe("Public-exposure descriptor stored before a route config write. Null means the previous projection was unreadable."),
737
+ public_exposure: runtimePublicExposureDescriptorSchema
738
+ .nullable()
739
+ .optional()
740
+ .describe("Public-exposure descriptor stored by the route config write. Null means the incoming projection was unreadable."),
741
+ });
742
+ const runtimeEventDeliveryIdentitySchema = z.object({
743
+ event_id: z.string().min(1),
744
+ delivery_id: z.string().min(1),
745
+ });
746
+ const runtimeEventDrainSessionIdSchema = z.string().min(1).max(128);
747
+ export const runtimeEventsDrainRequestSchema = z
748
+ .object({
749
+ session_id: runtimeEventDrainSessionIdSchema,
750
+ page_id: runtimeEventDrainSessionIdSchema.optional(),
751
+ target_event_id: z.string().min(1).max(128).optional(),
752
+ exclude_deliveries: z
753
+ .array(runtimeEventDeliveryIdentitySchema)
754
+ .max(100)
755
+ .optional()
756
+ .describe("Failed deliveries from the immediately preceding page."),
757
+ finish_session: z.boolean().optional(),
758
+ })
759
+ .superRefine((value, context) => {
760
+ if (value.finish_session === true) {
761
+ if (value.page_id || value.target_event_id || value.exclude_deliveries) {
762
+ context.addIssue({
763
+ code: "custom",
764
+ message: "A finished drain session cannot request a page.",
765
+ });
766
+ }
767
+ return;
768
+ }
769
+ if (!value.page_id)
770
+ context.addIssue({ code: "custom", message: "A drain page requires page_id." });
641
771
  });
642
772
  export const runtimeEventsDrainResponseSchema = z.object({
643
773
  delivered_count: z.number().int().nonnegative(),
@@ -649,16 +779,33 @@ export const runtimeEventsDrainResponseSchema = z.object({
649
779
  .optional()
650
780
  .describe("Undeliverable callbacks dropped after exceeding the attempt cap."),
651
781
  returned_count: z.number().int().nonnegative().optional(),
652
- // Pull fallback: events whose push origin was unreachable are handed back to
653
- // the drain caller (the control plane) instead of rotting in the journal.
654
- // Processing replays are no-ops on (event_id, hostname).
782
+ pending_count: z
783
+ .number()
784
+ .int()
785
+ .nonnegative()
786
+ .optional()
787
+ .describe("Journal entries not returned because of the response cap, another active lease, or this session's failed-delivery deferral. Non-zero means a later page or repair session still has work."),
788
+ // The drain is pull-first: the caller IS the control plane and is blocked on
789
+ // this response, so the engine hands its journal back here instead of posting
790
+ // each event over a separate HTTP round trip. Processing replays are no-ops
791
+ // on (event_id, hostname).
655
792
  events: z
656
793
  .array(z.object({
794
+ delivery_id: z.string().min(1),
657
795
  operation_id: z.string().optional(),
658
796
  event: runtimeCallbackEventSchema,
659
797
  }))
660
798
  .optional(),
661
799
  });
800
+ export const runtimeEventsAckRequestSchema = z.object({
801
+ session_id: runtimeEventDrainSessionIdSchema,
802
+ deliveries: z.array(runtimeEventDeliveryIdentitySchema).max(100),
803
+ });
804
+ export const runtimeEventsAckResponseSchema = z.object({
805
+ acknowledged_count: z.number().int().nonnegative(),
806
+ idempotent_count: z.number().int().nonnegative(),
807
+ stale_count: z.number().int().nonnegative(),
808
+ });
662
809
  // Engine job runner (plan §22, Placement V2): the box-local job runner behind
663
810
  // the two-lane tick. The control plane creates a job, drives it with repeated
664
811
  // ticks (kick-on-enqueue; the site-cron watchdog ticks the same route), and
@@ -707,19 +854,3 @@ export const runtimeEngineJobTickResponseSchema = z.object({
707
854
  job: runtimeEngineJobRecordSchema.nullable().optional(),
708
855
  drained: z.boolean().optional(),
709
856
  });
710
- // The management pull action for the Views panel (access-plan §5.6b, X-37):
711
- // `GET /access-events?file&offset&limit` — a cursor-resumed read of the
712
- // runtime's own per-hostname NDJSON access-event journal. `cursor` is the
713
- // position after the last returned event (feed it back verbatim); `done` is
714
- // false only when the response was truncated by `limit`; `dropped` totals the
715
- // writer's `.dropped` sidecars (events skipped past the per-file byte cap).
716
- export const runtimeAccessEventsCursorSchema = z.object({
717
- file: z.string().min(1).nullable(),
718
- offset: z.number().int().nonnegative(),
719
- });
720
- export const runtimeAccessEventsResponseSchema = z.object({
721
- events: z.array(accessEventSchema).default([]),
722
- cursor: runtimeAccessEventsCursorSchema,
723
- done: z.boolean(),
724
- dropped: z.number().int().nonnegative().default(0),
725
- });
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The one answer to "what is running on this space".
4
+ *
5
+ * A space is exactly one runtime kind — `versionRuntimeKind()` returns a single
6
+ * value and a publish carrying both declarations is refused at finalize — so
7
+ * this is a discriminated union rather than a bag of optional per-product
8
+ * fields. Anything that is not an execution tier has no member here and reads
9
+ * as `null`, which is the overwhelmingly common answer: a static space is not a
10
+ * degenerate app, it is the absence of one.
11
+ *
12
+ * It lives in its own module because it is the only place that needs to know
13
+ * about both products at once. `spaces.ts` imports it to describe runtime
14
+ * status; neither `zero.ts` nor `functions.ts` has to learn about the other.
15
+ */
16
+ export declare const runtimeAppSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
17
+ runtimeKind: z.ZodLiteral<"zero">;
18
+ artifactId: z.ZodString;
19
+ appName: z.ZodString;
20
+ serverRuntime: z.ZodLiteral<"quickjs-rust">;
21
+ inspect: z.ZodObject<{
22
+ policy: z.ZodEnum<{
23
+ public: "public";
24
+ private: "private";
25
+ }>;
26
+ }, z.core.$strict>;
27
+ install: z.ZodObject<{
28
+ runtimeKind: z.ZodLiteral<"zero">;
29
+ engine: z.ZodLiteral<"quickjs-rust">;
30
+ storageRoot: z.ZodLiteral<"__spacefast/zero">;
31
+ artifactPath: z.ZodLiteral<"__spacefast/zero/artifact.json">;
32
+ configPath: z.ZodLiteral<"__spacefast/zero/config.json">;
33
+ binary: z.ZodObject<{
34
+ path: z.ZodLiteral<"__spacefast/zero/bin/stattic-runtime">;
35
+ compatibilityTarget: z.ZodObject<{
36
+ profile: z.ZodLiteral<"wpcloud-linux-x86_64-gnu-v1">;
37
+ os: z.ZodLiteral<"linux">;
38
+ cpu: z.ZodLiteral<"x86_64">;
39
+ libc: z.ZodLiteral<"glibc">;
40
+ linking: z.ZodLiteral<"dynamic">;
41
+ requireExecutableBit: z.ZodLiteral<true>;
42
+ }, z.core.$strict>;
43
+ sha256: z.ZodDefault<z.ZodNullable<z.ZodString>>;
44
+ required: z.ZodLiteral<true>;
45
+ }, z.core.$strict>;
46
+ mysql: z.ZodObject<{
47
+ migrateAtFinalize: z.ZodLiteral<true>;
48
+ blockIncompatibleRollback: z.ZodLiteral<true>;
49
+ }, z.core.$strict>;
50
+ realtime: z.ZodObject<{
51
+ production: z.ZodLiteral<"stattic-central">;
52
+ local: z.ZodLiteral<"foreground-cli-ws">;
53
+ }, z.core.$strict>;
54
+ }, z.core.$strict>;
55
+ tables: z.ZodDefault<z.ZodArray<z.ZodString>>;
56
+ queries: z.ZodDefault<z.ZodArray<z.ZodString>>;
57
+ mutations: z.ZodDefault<z.ZodArray<z.ZodString>>;
58
+ endpoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
59
+ method: z.ZodEnum<{
60
+ POST: "POST";
61
+ PUT: "PUT";
62
+ GET: "GET";
63
+ HEAD: "HEAD";
64
+ PATCH: "PATCH";
65
+ DELETE: "DELETE";
66
+ OPTIONS: "OPTIONS";
67
+ }>;
68
+ path: z.ZodString;
69
+ }, z.core.$strict>>>;
70
+ sockets: z.ZodDefault<z.ZodArray<z.ZodString>>;
71
+ variables: z.ZodObject<{
72
+ source: z.ZodLiteral<"stattic-variables">;
73
+ names: z.ZodDefault<z.ZodArray<z.ZodString>>;
74
+ }, z.core.$strict>;
75
+ realtime: z.ZodObject<{
76
+ mode: z.ZodEnum<{
77
+ "stattic-central": "stattic-central";
78
+ "spacefast-cast": "spacefast-cast";
79
+ "local-dev": "local-dev";
80
+ "local-websocket": "local-websocket";
81
+ }>;
82
+ centralUrl: z.ZodDefault<z.ZodNullable<z.ZodString>>;
83
+ path: z.ZodOptional<z.ZodString>;
84
+ replayPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
85
+ replayUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
86
+ runPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
+ runUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
88
+ resourceKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
89
+ socketPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
90
+ spaceId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
91
+ versionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
92
+ }, z.core.$strict>;
93
+ }, z.core.$strict>, z.ZodObject<{
94
+ runtimeKind: z.ZodLiteral<"functions">;
95
+ artifactId: z.ZodString;
96
+ appName: z.ZodString;
97
+ entry: z.ZodString;
98
+ compatibilityDate: z.ZodString;
99
+ compatibilityFlags: z.ZodDefault<z.ZodArray<z.ZodString>>;
100
+ routes: z.ZodDefault<z.ZodArray<z.ZodObject<{
101
+ method: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
102
+ POST: "POST";
103
+ PUT: "PUT";
104
+ GET: "GET";
105
+ HEAD: "HEAD";
106
+ PATCH: "PATCH";
107
+ DELETE: "DELETE";
108
+ OPTIONS: "OPTIONS";
109
+ }>>>;
110
+ path: z.ZodString;
111
+ subtree: z.ZodDefault<z.ZodBoolean>;
112
+ }, z.core.$strict>>>;
113
+ capabilities: z.ZodDefault<z.ZodObject<{
114
+ db: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
115
+ fetch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
116
+ auth: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
117
+ env: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
118
+ realtime: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
119
+ logging: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
120
+ }, z.core.$strip>>;
121
+ executionState: z.ZodEnum<{
122
+ ready: "ready";
123
+ absent: "absent";
124
+ serving: "serving";
125
+ }>;
126
+ bundleSize: z.ZodNumber;
127
+ variables: z.ZodObject<{
128
+ source: z.ZodLiteral<"spacefast-variables">;
129
+ names: z.ZodDefault<z.ZodArray<z.ZodString>>;
130
+ }, z.core.$strict>;
131
+ inspect: z.ZodObject<{
132
+ policy: z.ZodEnum<{
133
+ public: "public";
134
+ private: "private";
135
+ }>;
136
+ }, z.core.$strict>;
137
+ }, z.core.$strict>], "runtimeKind">;
138
+ export type RuntimeApp = z.infer<typeof runtimeAppSchema>;