@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
@@ -1,5 +1,7 @@
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";
5
7
  import { zeroEndpointSchema, zeroRuntimeConfigSchema } from "./zero.js";
@@ -16,18 +18,13 @@ const versionHostnameEntrySchema = z.object({
16
18
  hostname: z.string().min(1),
17
19
  version_id: z.string().min(1),
18
20
  });
19
- const staticMountRouteEntrySchema = z.object({
20
- hostname: z.string().min(1),
21
- path_prefix: z.string().startsWith("/").min(2),
22
- target_space_id: z.string().min(1),
23
- target_version_id: z.string().min(1),
24
- });
21
+ // Future expansion: this intent could support explicit path-to-artifact composition
22
+ // after independently deployable components have a real product model.
25
23
  const runtimeRouteIntentSchema = z.object({
26
24
  production_hostnames: z.array(z.string().min(1)).default([]),
27
25
  noindex_production_hostnames: z.array(z.string().min(1)).default([]),
28
26
  version_hostnames: z.array(versionHostnameEntrySchema).default([]),
29
27
  host_canonical_redirects: z.array(hostCanonicalRedirectSchema).default([]),
30
- static_mount_routes: z.array(staticMountRouteEntrySchema).optional(),
31
28
  });
32
29
  // Changed-path purge contract (internal-docs/platform.md "Cache Management"):
33
30
  // finalize computes the changed-path set against the previous pointer target
@@ -46,7 +43,7 @@ const changedPathsSchema = z
46
43
  .describe("Canonical request path (decoded, leading slash) whose served bytes changed."))
47
44
  .max(RUNTIME_CHANGED_PATHS_MAX)
48
45
  .optional()
49
- .describe("Request paths changed vs the previous pointer target; absent = no diff available (host-wide purge fallback). Echoed verbatim into the route_updated journal event.");
46
+ .describe("Request paths changed from the previous pointer target. When absent, no diff is available and purge handling falls back to a host-wide purge. Echoed verbatim into the `route_updated` journal event.");
50
47
  const previousVersionIdSchema = z
51
48
  .string()
52
49
  .min(1)
@@ -57,15 +54,6 @@ export const runtimeVersionCreateRequestSchema = z.object({
57
54
  files: z.array(manifestFileSchema),
58
55
  retained_files: z.array(manifestFileSchema).default([]),
59
56
  reusable_version_id: z.string().min(1).optional(),
60
- // "declared" sessions validate every PUT against the declared manifest.
61
- // "open" sessions (internal-docs/platform.md "Upload Contract" shape 2) have
62
- // no manifest: the runtime enforces path policy and the session caps per
63
- // PUT, computes streaming hashes, and finalize derives the manifest.
64
- // Plan-policy caps ride in session state, never in the upload JWT, and are
65
- // valid only on open sessions.
66
- session_mode: z.enum(["declared", "open"]).default("declared"),
67
- max_total_bytes: z.number().int().positive().optional(),
68
- max_file_count: z.number().int().positive().optional(),
69
57
  // Session state stores the upload truth (spec "Upload Contract"): the version's
70
58
  // manifest hash and the draft expiry so resume answers come from runtime state.
71
59
  manifest_hash: z.string().optional(),
@@ -83,7 +71,6 @@ export const runtimeUploadSessionDescriptorSchema = z.object({
83
71
  upload_id: z.string().min(1),
84
72
  space_id: z.string().min(1),
85
73
  version_id: z.string().min(1),
86
- session_mode: z.literal("declared"),
87
74
  expires_at: z.string(),
88
75
  manifest_hash: z.string().optional(),
89
76
  files: z.array(manifestFileSchema),
@@ -94,10 +81,14 @@ export const runtimeUploadSessionDescriptorSchema = z.object({
94
81
  metadata: z.record(z.string(), z.unknown()).default({}),
95
82
  convention_files: runtimeConventionFilesSchema.default({}),
96
83
  });
97
- // Budget for the half that rides in the upload JWT (everything except the
98
- // retained manifest): 4 KiB of JSON keeps the signed token inside common 8 KiB
99
- // header buffers, since it is sent on every PUT.
84
+ // Candidate budget for the half that rides in the upload JWT (everything
85
+ // except the retained manifest). This bounds signing work, but is not itself a
86
+ // header-size proof: callback capabilities also ride in auto-finalize tokens.
100
87
  export const RUNTIME_UPLOAD_SESSION_DESCRIPTOR_MAX_BYTES = 4096;
88
+ // Hard wire budget for the complete `Authorization: Bearer <jwt>\r\n` line sent
89
+ // on every PUT. Lazy-session selection measures the signed line and falls back
90
+ // to eager runtime creation rather than returning an oversized credential.
91
+ export const RUNTIME_UPLOAD_AUTHORIZATION_HEADER_MAX_BYTES = 8 * 1024;
101
92
  // Budget for the whole descriptor, which is persisted on the version row and
102
93
  // replayed inside the durable finalize receipt.
103
94
  export const RUNTIME_UPLOAD_SESSION_STORED_MAX_BYTES = 256 * 1024;
@@ -108,7 +99,6 @@ export const runtimeUploadSessionResponseSchema = z.object({
108
99
  space_id: z.string(),
109
100
  version_id: z.string(),
110
101
  upload_id: z.string(),
111
- session_mode: z.enum(["declared", "open"]),
112
102
  created_at: z.string().nullable(),
113
103
  expires_at: z.string().nullable(),
114
104
  manifest_hash: z.string().nullable(),
@@ -118,39 +108,128 @@ export const runtimeUploadSessionResponseSchema = z.object({
118
108
  sha256: z.string().optional(),
119
109
  uploaded: z.boolean(),
120
110
  })),
121
- // Declared paths the runtime has not committed yet (empty for open sessions).
111
+ // Declared paths the runtime has not committed yet.
122
112
  pending_paths: z.array(z.string()),
123
113
  // Staged chunked-upload parts by canonical path (contiguity is checked at complete).
124
114
  chunks: z.record(z.string(), z.array(z.number().int().positive())),
125
115
  });
126
- // Route-pointer config is pure serving policy: THE resolved access policy plus
127
- // the anonymous claim countdown. Space passwords are NOT a separate config
128
- // channel they ride inside `policy.rules` as a password challenge rule (one
129
- // access model, spec "Runtime"). Per-version serving behavior
130
- // (index/fallback/listing) is a version artifact concern, never route config.
116
+ // Route-pointer config is pure serving policy plus the anonymous claim
117
+ // countdown. Per-version serving behavior (index/fallback/listing) remains a
118
+ // version artifact concern.
119
+ export const visitorJwkSchema = z.object({
120
+ kty: z.literal("OKP"),
121
+ crv: z.literal("Ed25519"),
122
+ kid: z.string().min(1),
123
+ alg: z.literal("EdDSA"),
124
+ x: z.string().min(1),
125
+ use: z.literal("sig").optional(),
126
+ });
127
+ export const visitorJwksSchema = z.object({
128
+ keys: z.array(visitorJwkSchema).min(1),
129
+ });
130
+ export const runtimePublicExposureDescriptorSchema = z.object({
131
+ v: z.number().int().positive(),
132
+ public: z.boolean(),
133
+ authorizationDigest: z.string().regex(/^[a-f0-9]{64}$/),
134
+ contentTypes: z.array(z.string()).nullable(),
135
+ externalProxy: z.boolean(),
136
+ unmodeled: z.string(),
137
+ });
138
+ // Everything the runtime needs to render the visitor access page itself.
139
+ // Presentation-only: which lanes exist is derived runtime-side from the
140
+ // projected grants; this descriptor carries what the grants cannot — the
141
+ // space's display identity, the central endpoints each lane talks to, and the
142
+ // deterministic credential that authenticates the runtime's own
143
+ // server-to-server exchange calls (password, verified email, request-invite)
144
+ // so the central authority may trust the visitor context the runtime forwards.
145
+ export const runtimeAccessPageDescriptorSchema = z
146
+ .object({
147
+ displayName: z.string().min(1).max(200).nullable(),
148
+ // API-origin account lane (`/v1/access/acquire/<target>`), null until the
149
+ // space is claimed. Doubles as the silent-SSO probe target.
150
+ accountUrl: z.string().url().nullable(),
151
+ // Identity connections rendered as SSO lanes. The runtime appends
152
+ // `host`/`return` to `startUrl` at render time.
153
+ connections: z.array(z.object({
154
+ id: z.string().min(1).max(64),
155
+ label: z.string().min(1).max(200),
156
+ startUrl: z.string().url(),
157
+ })),
158
+ exchange: z
159
+ .object({
160
+ passwordUrl: z.string().url(),
161
+ // Browser entry links and integration credentials are exchanged over
162
+ // separate runtime-authenticated lanes. Neither credential reaches
163
+ // customer code.
164
+ linkUrl: z.string().url(),
165
+ tokenUrl: z.string().url(),
166
+ emailUrl: z.string().url(),
167
+ requestUrl: z.string().url(),
168
+ logoutUrl: z.string().url(),
169
+ commentsConfigUrl: z.string().url().optional(),
170
+ commentsTicketUrl: z.string().url().optional(),
171
+ commentsVersionUrlsUrl: z.string().url().optional(),
172
+ zeroRealtimeTicketUrl: z.string().url().optional(),
173
+ credential: z.string().min(32).max(256),
174
+ })
175
+ .nullable(),
176
+ })
177
+ .strict();
178
+ // Runtime route artifacts are compiled into an authority-keyed index. Keep a
179
+ // hard ceiling as a last-resort safety boundary for route-apply CPU, artifact
180
+ // size, and a compromised producer; indexing, not this limit, owns hot-path
181
+ // performance.
182
+ export const RUNTIME_AUTHORIZATION_GRANT_LIMIT = 1_024;
183
+ export const runtimeAuthorizationProjectionSchema = z
184
+ .object({
185
+ generation: z.number().int().nonnegative(),
186
+ // Browser sessions follow their still-live authority references across
187
+ // ordinary policy/ownership projections. Only an explicit logout-all
188
+ // rotation invalidates every otherwise-valid session.
189
+ sessionVersion: z.number().int().nonnegative(),
190
+ fence: accessFenceKindSchema,
191
+ acquireUrl: z.string().url(),
192
+ accessPage: runtimeAccessPageDescriptorSchema,
193
+ spaceClaimed: z.boolean(),
194
+ grants: z
195
+ .array(grantSchema.extend({
196
+ // The durable browser session records this generation for the exact
197
+ // authority it acquired. Rotating or editing one credential Grant
198
+ // therefore removes only that authority from the session.
199
+ generation: z.number().int().positive(),
200
+ }))
201
+ .max(RUNTIME_AUTHORIZATION_GRANT_LIMIT),
202
+ })
203
+ .strict();
131
204
  const runtimeRouteConfigSchema = z.object({
132
- // THE unified policy lane ({ rules: RuntimeRule[] }) the runtime enforcer
133
- // (access-rules.php) evaluates first-match-wins for every host entry this
134
- // space serves — route hosts, custom domains, and immutable version hosts
135
- // alike. Carries platform + file + cloud rules (platform prepended, always
136
- // wins). Explicit null clears the stored policy; absence leaves it untouched.
137
- policy: policyDocSchema.nullable().optional(),
138
- // Serving-secret map (name -> value) the policy's password acquires
139
- // reference by `ref` = "secret:<name>". Carries the space-password bcrypt
140
- // verifier hash (and the compiled `_headers` basicAuth credential hashes)
141
- // the runtime resolves and `password_verify`s. Explicit null clears stored
142
- // secrets; absence leaves them untouched.
143
- secrets: z.record(z.string().min(1), z.string().min(1)).nullable().optional(),
144
- // Space session version (access-plan §3.1): the runtime rejects a visitor
145
- // token whose `sv` claim mismatches this, and derives the space-local `pw:`
146
- // key from the password secret + this value. Absent = 0.
147
- session_version: z.number().int().nonnegative().nullable().optional(),
205
+ // Canonical scoped admission projection. When present it is the sole serving
206
+ // authority; the legacy rule document below is ignored.
207
+ authorization: runtimeAuthorizationProjectionSchema.nullable().optional(),
208
+ // Digest of every input that can change anonymous/public exposure (canonical
209
+ // authorization boundaries, content types, proxy entitlement, and
210
+ // unknown serving keys). Authority-only rotation deliberately leaves it
211
+ // stable so Link/Person changes do not block on an unnecessary edge purge.
212
+ public_exposure_digest: z
213
+ .string()
214
+ .regex(/^[a-f0-9]{64}$/)
215
+ .optional(),
216
+ // The digest detects equality; the descriptor preserves enough structure for
217
+ // the callback consumer to distinguish widening from narrowing without
218
+ // reconstructing a previous config from mutable control-plane state.
219
+ public_exposure: runtimePublicExposureDescriptorSchema.optional(),
220
+ // Monotonic authorization projection generation. Short-lived handoffs are
221
+ // bound to this exact generation, Space, and host. Browser sessions use the
222
+ // projection's independent sessionVersion revocation floor.
223
+ projection_generation: z.number().int().nonnegative().nullable().optional(),
224
+ // First-party visitor-token verification is explicit.
225
+ visitor_issuer: z.string().min(1).optional(),
226
+ visitor_jwks: visitorJwksSchema.optional(),
148
227
  // Anonymous claim-window countdown (ISO timestamp), compiled into serving
149
228
  // state so the runtime can render the expiry-rescue banner for visitors
150
229
  // holding the claim-link viewer session. Null/absent once claimed.
151
230
  anonymous_expires_at: z.string().nullable().optional(),
152
231
  // Serve-time content-type allowlist. Generic serving policy: the engine has
153
- // no notion of WHY a space is restricted — it refuses (403, blocked_message
232
+ // no notion of WHY a file type is blocked — it refuses (403, blocked_message
154
233
  // body) any file whose stored Content-Type matches no pattern. Patterns are
155
234
  // exact types or `prefix/*` wildcards ("text/*"). The control plane pushes
156
235
  // it for unclaimed anonymous spaces (no opaque binaries pre-claim); explicit
@@ -169,15 +248,15 @@ const runtimeRouteConfigSchema = z.object({
169
248
  // in V1 (set via the superadmin admission endpoint); absent means the
170
249
  // env-knob default applies.
171
250
  admission: z.object({ concurrency: z.number().int().positive() }).optional(),
172
- // Durable revocation SET (access-plan revocation hardening): every
173
- // currently-revoked link:/invite:/svc: grant id, computed from the
174
- // control-plane's Postgres projection (access/resolve.ts
175
- // `revocationsForSpace`). The runtime REPLACES its revocations store's
176
- // grant bucket with exactly this set on every sync — the backstop that
177
- // converges even when the instant best-effort revoke_grant/unrevoke_grant
178
- // call was dropped. `subs` (JWT-subject revocations) are left untouched.
179
- // Absence leaves the stored set untouched.
180
- revocations: z.array(z.string().min(1)).optional(),
251
+ // Same-host production tag JavaScript. It rides the mutable route projection
252
+ // so private Spaces never fetch an anonymously readable per-Space artifact.
253
+ tag_sdk: z
254
+ .object({
255
+ revision: z.string().min(1),
256
+ body: z.string().max(5 * 1024 * 1024),
257
+ })
258
+ .nullable()
259
+ .optional(),
181
260
  // Serve-time plan entitlements (proxy-routes.md: "the rule stays in your
182
261
  // config and activates the moment you upgrade — no redeploy needed"). Stored
183
262
  // per space beside `policy`; a `planGated` compiled proxy rule
@@ -201,7 +280,7 @@ const runtimeServingConfigSchema = z.object({
201
280
  index: z
202
281
  .union([z.string().min(1), z.literal(false)])
203
282
  .optional()
204
- .describe("Directory index file name, or false to disable directory index resolution."),
283
+ .describe("Directory index file name, or `false` to disable directory index resolution."),
205
284
  fallback: z
206
285
  .object({
207
286
  path: z.string().min(1).describe("Committed file served when no exact route matches."),
@@ -214,7 +293,7 @@ const runtimeServingConfigSchema = z.object({
214
293
  clean_urls: z
215
294
  .boolean()
216
295
  .optional()
217
- .describe("Serve flat `<name>.html` files at their extensionless URLs (W7.1). Absent = default: on unless the fallback is the 200-status SPA shape."),
296
+ .describe("Serves flat `<name>.html` files at their extensionless URLs (W7.1). Defaults to on, unless the fallback is the 200-status SPA shape."),
218
297
  listing: z
219
298
  .boolean()
220
299
  .optional()
@@ -225,13 +304,13 @@ const runtimeServingConfigSchema = z.object({
225
304
  .describe("Generate the single-file viewer page when a version has exactly one public file."),
226
305
  meta: z
227
306
  .object({
228
- title: z.string().max(300).optional().describe("Listing/viewer/OG title."),
229
- description: z.string().max(1000).optional().describe("Listing/viewer/OG description."),
307
+ title: z.string().max(300).optional().describe("Listing, viewer, and OG title."),
308
+ description: z.string().max(1000).optional().describe("Listing, viewer, and OG description."),
230
309
  image: z.string().max(2000).optional().describe("OG image path."),
231
310
  })
232
311
  .nullable()
233
312
  .optional()
234
- .describe("Finalize-resolved listing/viewer/OG metadata (effective SpaceConfig meta); wins over session metadata."),
313
+ .describe("Finalize-resolved listing, viewer, and OG metadata (effective SpaceConfig metadata). This value wins over session metadata."),
235
314
  pages: runtimePagesSchema.optional(),
236
315
  // Hidden experimental build-pipeline flag: absent on every version finalized
237
316
  // without it (byte-identical payload to today). Only ever sent as true.
@@ -249,6 +328,7 @@ const runtimeServingConfigSchema = z.object({
249
328
  cast_api_base: z.string().url().max(2000).nullable().optional(),
250
329
  cast_ws_url: z.string().url().max(2000).nullable().optional(),
251
330
  cast_resource_key: z.string().min(1).max(256).nullable().optional(),
331
+ published_comments: z.boolean().optional(),
252
332
  })
253
333
  .optional(),
254
334
  // CP-validated inject snippets (<= 8 KB each, <= 16 per array); sent only
@@ -305,12 +385,11 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
305
385
  session: runtimeUploadSessionDescriptorSchema.optional(),
306
386
  zero_mode: runtimeZeroModeSchema.optional(),
307
387
  zero: zeroRuntimeConfigSchema.optional(),
308
- // The version's authoritative ready timestamp (epoch seconds), passed by the
309
- // control plane when the version already reached ready (re-finalize, import
310
- // re-materialization, provider migration). The engine stamps it ONCE into the
311
- // version metadata; access-window rules (windowDays) compare against this
312
- // stored value — never a filesystem mtime that resets on rebuilds.
313
- ready_at: z.number().int().positive().optional(),
388
+ // Dispatch configuration for a Functions version. Absent for static versions,
389
+ // and absent when a worker was published while no execution host is
390
+ // configured the version then serves as pure static content, because a
391
+ // config is the only thing that makes the origin dispatch at all.
392
+ functions: functionsRuntimeConfigSchema.optional(),
314
393
  convention_files: runtimeConventionFilesSchema.optional(),
315
394
  // Finalize-time `{{ vars.NAME }}` substitution output (spec "Variable
316
395
  // rules"): substituted bytes replace the served files; the engine keeps the
@@ -390,10 +469,23 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
390
469
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
391
470
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
392
471
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
393
- static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
394
472
  })
395
473
  .optional(),
396
474
  });
475
+ const runtimePublicReadinessStatusSchema = z.union([
476
+ z.literal(200),
477
+ z.literal(301),
478
+ z.literal(302),
479
+ z.literal(303),
480
+ z.literal(307),
481
+ z.literal(308),
482
+ z.literal(401),
483
+ z.literal(403),
484
+ ]);
485
+ export const runtimeReadinessTargetSchema = z.object({
486
+ path: z.string().startsWith("/"),
487
+ expected_statuses: z.array(z.union([runtimePublicReadinessStatusSchema, z.literal(404)])).min(1),
488
+ });
397
489
  export const runtimeVersionFinalizeResponseSchema = z.object({
398
490
  space_id: z.string(),
399
491
  version_id: z.string(),
@@ -405,6 +497,17 @@ export const runtimeVersionFinalizeResponseSchema = z.object({
405
497
  // Returned by both first and idempotent finalize responses so recovery
406
498
  // never needs an already-consumed upload session.
407
499
  manifest: z.array(manifestFileSchema).optional(),
500
+ readiness_target: runtimeReadinessTargetSchema,
501
+ activation_event_id: z
502
+ .string()
503
+ .min(1)
504
+ .optional()
505
+ .describe("Stable route_updated event id when finalize also activated a route."),
506
+ });
507
+ export const runtimeZeroMigrationsApplyResponseSchema = z.object({
508
+ space_id: z.string(),
509
+ version_id: z.string(),
510
+ applied: z.literal(true),
408
511
  });
409
512
  export const runtimeRouteUpdateRequestSchema = z.object({
410
513
  version_id: z.string().min(1),
@@ -420,14 +523,12 @@ export const runtimeRouteUpdateRequestSchema = z.object({
420
523
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
421
524
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
422
525
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
423
- static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
424
526
  });
425
527
  export const runtimeHostnameIntentUpdateRequestSchema = z.object({
426
528
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
427
529
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
428
530
  version_hostnames: runtimeRouteIntentSchema.shape.version_hostnames,
429
531
  host_canonical_redirects: runtimeRouteIntentSchema.shape.host_canonical_redirects,
430
- static_mount_routes: runtimeRouteIntentSchema.shape.static_mount_routes,
431
532
  });
432
533
  export const runtimeRouteUpdateResponseSchema = z.object({
433
534
  space_id: z.string(),
@@ -437,6 +538,11 @@ export const runtimeRouteUpdateResponseSchema = z.object({
437
538
  // and hostname intent all matched the stored route: nothing was journaled,
438
539
  // so callers skip the event drain too.
439
540
  unchanged: z.boolean().optional(),
541
+ activation_event_id: z
542
+ .string()
543
+ .min(1)
544
+ .optional()
545
+ .describe("Stable route_updated event id for this exact route command, including retries."),
440
546
  });
441
547
  export const runtimeHostnameIntentUpdateResponseSchema = z.object({
442
548
  space_id: z.string(),
@@ -467,20 +573,6 @@ export const runtimeRetentionPolicyUpdateResponseSchema = z.object({
467
573
  space_id: z.string(),
468
574
  prunable_count: z.number().int().nonnegative(),
469
575
  });
470
- // Instant per-grant tombstone (access-plan revocation hardening): the
471
- // control plane's best-effort synchronous call at revoke/unrevoke time,
472
- // hitting the SAME `revocations.json` the durable `config.revocations` full
473
- // replace (above) also converges. `grant` must be a `link:`/`invite:`/`svc:`
474
- // row id — the runtime rejects anything else.
475
- export const runtimeGrantRevocationRequestSchema = z.object({
476
- grant: z.string().min(1),
477
- });
478
- export const runtimeGrantRevocationResponseSchema = z.object({
479
- space_id: z.string(),
480
- target_type: z.literal("grant"),
481
- grant: z.string(),
482
- revoked: z.boolean(),
483
- });
484
576
  export const runtimeSpaceDeleteResponseSchema = z.object({
485
577
  space_id: z.string(),
486
578
  status: z.literal("deleted"),
@@ -503,11 +595,9 @@ const runtimeExportImportStatusSchema = z.enum([
503
595
  export const runtimeSpaceExportStartRequestSchema = z
504
596
  .object({
505
597
  version_ids: z.array(z.string().min(1)).min(1).optional(),
506
- // Honest export options (spec SpaceExportRequest): include_files=false
507
- // produces a config-only archive; include_explicit_config=false skips the
508
- // space access policy entry. Recorded in stattic.json `includes`.
598
+ // include_files=false produces a metadata-only archive that cannot be
599
+ // imported as a serving Space. Recorded in stattic.json `includes`.
509
600
  include_files: z.boolean().optional(),
510
- include_explicit_config: z.boolean().optional(),
511
601
  })
512
602
  .strict();
513
603
  export const runtimeSpaceExportResponseSchema = z.object({
@@ -529,10 +619,6 @@ export const runtimeSpaceExportResponseSchema = z.object({
529
619
  });
530
620
  export const runtimeSpaceImportStartRequestSchema = z
531
621
  .object({
532
- // Version installation and target-policy replacement are separate trust
533
- // decisions. Managed imports always pass false; trusted full-restore tools
534
- // must opt in explicitly before archive upload.
535
- install_access_policy: z.boolean().optional(),
536
622
  version_id_map: z.record(z.string(), z.string()).optional(),
537
623
  // Plan §9 gated activation, extended to import (I-4): shares
538
624
  // runtimeZeroModeSchema with runtimeVersionFinalizeRequestSchema.
@@ -611,12 +697,26 @@ export const runtimeSpaceStateSchema = z.object({
611
697
  space_id: z.string().min(1).describe("Space whose runtime route pointers follow."),
612
698
  routes: z
613
699
  .record(z.string(), z.string())
614
- .describe("Route pointer map: route name -> version id currently compiled for serving."),
700
+ .describe("Route pointer map from route name to version id currently compiled for serving."),
615
701
  tombstone_count: z
616
702
  .number()
617
703
  .int()
618
704
  .nonnegative()
619
705
  .describe("Hostnames the runtime serves a tombstone page for on this space."),
706
+ // Optional for the same reason the whole block is: the control plane deploys
707
+ // in one step while engines pull their bundle on their own schedule, so every
708
+ // field added here is absent on the fleet for hours. Absence means "this
709
+ // engine cannot report hostname scope", never "the scope is empty" — callers
710
+ // must degrade rather than sign a narrower callback claim than the engine
711
+ // will report.
712
+ intent_hostnames: z
713
+ .array(z.string().min(1))
714
+ .optional()
715
+ .describe("Exact hostname set in the space's current route intent."),
716
+ hostnames: z
717
+ .array(z.string().min(1))
718
+ .optional()
719
+ .describe("Exact hostname union that a space deletion event carries from route intent and tombstones."),
620
720
  });
621
721
  export const runtimeHealthResponseSchema = z.object({
622
722
  ok: z.literal(true),
@@ -659,7 +759,10 @@ export const runtimeStateResponseSchema = z.object({
659
759
  // replays are no-ops. The object is intentionally loose: engines may journal
660
760
  // extra diagnostic fields without breaking older control planes.
661
761
  export const runtimeCallbackEventSchema = z.looseObject({
662
- event: z.string().min(1).describe("Journal event name (e.g. route_updated, space_deleted)."),
762
+ event: z
763
+ .string()
764
+ .min(1)
765
+ .describe("Journal event name, for example `route_updated` or `space_deleted`."),
663
766
  event_id: z
664
767
  .string()
665
768
  .min(1)
@@ -669,6 +772,10 @@ export const runtimeCallbackEventSchema = z.looseObject({
669
772
  .string()
670
773
  .optional()
671
774
  .describe("Control-plane operation the management call ran under."),
775
+ operation_action: z
776
+ .string()
777
+ .optional()
778
+ .describe("Runtime management action authorized for the operation."),
672
779
  space_id: z.string().optional().describe("Space the event applies to, when space-scoped."),
673
780
  version_id: z.string().optional().describe("Version the event applies to, when version-scoped."),
674
781
  route_name: z.string().optional().describe("Route pointer the event updated, when route-scoped."),
@@ -680,8 +787,58 @@ export const runtimeCallbackEventSchema = z.looseObject({
680
787
  changed_paths_known: z
681
788
  .boolean()
682
789
  .optional()
683
- .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."),
790
+ .describe("True when `changed_paths` is the complete diff, including a known-empty diff. On older engines this field is absent. There, an empty array remains the host-wide fallback."),
684
791
  previous_version_id: previousVersionIdSchema,
792
+ previous_public_exposure_digest: z
793
+ .string()
794
+ .min(1)
795
+ .nullable()
796
+ .optional()
797
+ .describe("Public-exposure digest stored before a route config write. Field presence proves the route already existed. Null means the previous projection was unreadable."),
798
+ public_exposure_digest: z
799
+ .string()
800
+ .min(1)
801
+ .nullable()
802
+ .optional()
803
+ .describe("Public-exposure digest stored by the route config write. Null means the incoming projection was unreadable."),
804
+ previous_public_exposure: runtimePublicExposureDescriptorSchema
805
+ .nullable()
806
+ .optional()
807
+ .describe("Public-exposure descriptor stored before a route config write. Null means the previous projection was unreadable."),
808
+ public_exposure: runtimePublicExposureDescriptorSchema
809
+ .nullable()
810
+ .optional()
811
+ .describe("Public-exposure descriptor stored by the route config write. Null means the incoming projection was unreadable."),
812
+ });
813
+ const runtimeEventDeliveryIdentitySchema = z.object({
814
+ event_id: z.string().min(1),
815
+ delivery_id: z.string().min(1),
816
+ });
817
+ const runtimeEventDrainSessionIdSchema = z.string().min(1).max(128);
818
+ export const runtimeEventsDrainRequestSchema = z
819
+ .object({
820
+ session_id: runtimeEventDrainSessionIdSchema,
821
+ page_id: runtimeEventDrainSessionIdSchema.optional(),
822
+ target_event_id: z.string().min(1).max(128).optional(),
823
+ exclude_deliveries: z
824
+ .array(runtimeEventDeliveryIdentitySchema)
825
+ .max(100)
826
+ .optional()
827
+ .describe("Failed deliveries from the immediately preceding page."),
828
+ finish_session: z.boolean().optional(),
829
+ })
830
+ .superRefine((value, context) => {
831
+ if (value.finish_session === true) {
832
+ if (value.page_id || value.target_event_id || value.exclude_deliveries) {
833
+ context.addIssue({
834
+ code: "custom",
835
+ message: "A finished drain session cannot request a page.",
836
+ });
837
+ }
838
+ return;
839
+ }
840
+ if (!value.page_id)
841
+ context.addIssue({ code: "custom", message: "A drain page requires page_id." });
685
842
  });
686
843
  export const runtimeEventsDrainResponseSchema = z.object({
687
844
  delivered_count: z.number().int().nonnegative(),
@@ -693,16 +850,34 @@ export const runtimeEventsDrainResponseSchema = z.object({
693
850
  .optional()
694
851
  .describe("Undeliverable callbacks dropped after exceeding the attempt cap."),
695
852
  returned_count: z.number().int().nonnegative().optional(),
696
- // Pull fallback: events whose push origin was unreachable are handed back to
697
- // the drain caller (the control plane) instead of rotting in the journal.
698
- // Processing replays are no-ops on (event_id, hostname).
853
+ pending_count: z
854
+ .number()
855
+ .int()
856
+ .nonnegative()
857
+ .optional()
858
+ .describe("Journal entries not returned because of the response cap, another active lease, or this session's failed-delivery deferral. A non-zero value means a later page or repair session still has work."),
859
+ // The drain is pull-first: the caller IS the control plane and is blocked on
860
+ // this response, so the engine hands its journal back here instead of posting
861
+ // each event over a separate HTTP round trip. Processing replays are no-ops
862
+ // on (event_id, hostname).
699
863
  events: z
700
864
  .array(z.object({
865
+ delivery_id: z.string().min(1),
701
866
  operation_id: z.string().optional(),
867
+ callback_token: z.string().min(1),
702
868
  event: runtimeCallbackEventSchema,
703
869
  }))
704
870
  .optional(),
705
871
  });
872
+ export const runtimeEventsAckRequestSchema = z.object({
873
+ session_id: runtimeEventDrainSessionIdSchema,
874
+ deliveries: z.array(runtimeEventDeliveryIdentitySchema).max(100),
875
+ });
876
+ export const runtimeEventsAckResponseSchema = z.object({
877
+ acknowledged_count: z.number().int().nonnegative(),
878
+ idempotent_count: z.number().int().nonnegative(),
879
+ stale_count: z.number().int().nonnegative(),
880
+ });
706
881
  // Engine job runner (plan §22, Placement V2): the box-local job runner behind
707
882
  // the two-lane tick. The control plane creates a job, drives it with repeated
708
883
  // ticks (kick-on-enqueue; the site-cron watchdog ticks the same route), and
@@ -751,19 +926,3 @@ export const runtimeEngineJobTickResponseSchema = z.object({
751
926
  job: runtimeEngineJobRecordSchema.nullable().optional(),
752
927
  drained: z.boolean().optional(),
753
928
  });
754
- // The management pull action for the Views panel (access-plan §5.6b, X-37):
755
- // `GET /access-events?file&offset&limit` — a cursor-resumed read of the
756
- // runtime's own per-hostname NDJSON access-event journal. `cursor` is the
757
- // position after the last returned event (feed it back verbatim); `done` is
758
- // false only when the response was truncated by `limit`; `dropped` totals the
759
- // writer's `.dropped` sidecars (events skipped past the per-file byte cap).
760
- export const runtimeAccessEventsCursorSchema = z.object({
761
- file: z.string().min(1).nullable(),
762
- offset: z.number().int().nonnegative(),
763
- });
764
- export const runtimeAccessEventsResponseSchema = z.object({
765
- events: z.array(accessEventSchema).default([]),
766
- cursor: runtimeAccessEventsCursorSchema,
767
- done: z.boolean(),
768
- dropped: z.number().int().nonnegative().default(0),
769
- });