@spacefast/common 0.0.23 → 0.0.26

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 (224) hide show
  1. package/brand-assets/LICENSE-Haskoy.txt +93 -0
  2. package/brand-assets/haskoy-latin-variable.woff2 +0 -0
  3. package/brand-assets/spacefast-favicon.svg +4 -4
  4. package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
  5. package/brand-assets/spacefast-wordmark.svg +2 -4
  6. package/dist/agents/connect-targets.js +1 -1
  7. package/dist/agents/private-key-oauth.d.ts +0 -1
  8. package/dist/agents/private-key-oauth.js +46 -63
  9. package/dist/brand-assets-build.js +4 -6
  10. package/dist/brand-assets.d.ts +5 -15
  11. package/dist/brand-assets.js +15 -14
  12. package/dist/config/domains.d.ts +47 -7
  13. package/dist/config/domains.js +66 -10
  14. package/dist/config/index.d.ts +0 -1
  15. package/dist/config/index.js +0 -1
  16. package/dist/contracts/abuse.d.ts +2 -0
  17. package/dist/contracts/abuse.js +12 -0
  18. package/dist/contracts/access.d.ts +124 -12
  19. package/dist/contracts/access.js +73 -9
  20. package/dist/contracts/activity.d.ts +1 -1
  21. package/dist/contracts/activity.js +5 -12
  22. package/dist/contracts/api-keys.d.ts +21 -21
  23. package/dist/contracts/api-keys.js +2 -2
  24. package/dist/contracts/beta.js +1 -1
  25. package/dist/contracts/billing.d.ts +3 -35
  26. package/dist/contracts/billing.js +33 -52
  27. package/dist/contracts/builds.d.ts +30 -18
  28. package/dist/contracts/builds.js +28 -9
  29. package/dist/contracts/channels.js +1 -1
  30. package/dist/contracts/cli.d.ts +10 -0
  31. package/dist/contracts/cli.js +10 -0
  32. package/dist/contracts/collab-public.d.ts +63 -0
  33. package/dist/contracts/collab-public.js +46 -0
  34. package/dist/contracts/comments.d.ts +282 -53
  35. package/dist/contracts/comments.js +173 -52
  36. package/dist/contracts/common.d.ts +3 -3
  37. package/dist/contracts/common.js +5 -5
  38. package/dist/contracts/crons.d.ts +35 -0
  39. package/dist/contracts/crons.js +42 -0
  40. package/dist/contracts/device-auth.d.ts +6 -6
  41. package/dist/contracts/device-auth.js +2 -2
  42. package/dist/contracts/docs.js +2 -2
  43. package/dist/contracts/domains.d.ts +38 -27
  44. package/dist/contracts/domains.js +29 -2
  45. package/dist/contracts/enums.d.ts +34 -30
  46. package/dist/contracts/enums.js +62 -48
  47. package/dist/contracts/error-code-meta.d.ts +50 -125
  48. package/dist/contracts/error-code-meta.js +29 -41
  49. package/dist/contracts/error-codes.d.ts +1 -1
  50. package/dist/contracts/error-codes.js +14 -33
  51. package/dist/contracts/execution.d.ts +47 -66
  52. package/dist/contracts/execution.js +53 -49
  53. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  54. package/dist/contracts/feature-lifecycle-core.js +2 -31
  55. package/dist/contracts/feature-lifecycle.d.ts +12 -52
  56. package/dist/contracts/feature-lifecycle.js +16 -59
  57. package/dist/contracts/features.d.ts +48 -60
  58. package/dist/contracts/features.js +59 -51
  59. package/dist/contracts/functions.d.ts +266 -72
  60. package/dist/contracts/functions.js +260 -37
  61. package/dist/contracts/git.d.ts +28 -34
  62. package/dist/contracts/git.js +10 -11
  63. package/dist/contracts/grants.d.ts +9 -5
  64. package/dist/contracts/grants.js +18 -9
  65. package/dist/contracts/ids.js +2 -2
  66. package/dist/contracts/internal.d.ts +8 -6
  67. package/dist/contracts/internal.js +12 -12
  68. package/dist/contracts/mcp.d.ts +11 -0
  69. package/dist/contracts/mcp.js +5 -0
  70. package/dist/contracts/me.js +1 -1
  71. package/dist/contracts/oauth-resources.d.ts +4 -3
  72. package/dist/contracts/oauth-resources.js +12 -3
  73. package/dist/contracts/operations.d.ts +90 -5
  74. package/dist/contracts/operations.js +77 -3
  75. package/dist/contracts/pages.d.ts +12 -7
  76. package/dist/contracts/pages.js +24 -1
  77. package/dist/contracts/plan-policy.d.ts +0 -2
  78. package/dist/contracts/plan-policy.js +2 -3
  79. package/dist/contracts/platform.d.ts +61 -148
  80. package/dist/contracts/platform.js +70 -126
  81. package/dist/contracts/publish-archive.d.ts +508 -0
  82. package/dist/contracts/publish-archive.js +32 -0
  83. package/dist/contracts/push-new.d.ts +4 -4
  84. package/dist/contracts/quotas.js +2 -2
  85. package/dist/contracts/repository-connections.d.ts +43 -30
  86. package/dist/contracts/repository-connections.js +11 -3
  87. package/dist/contracts/resources.d.ts +12 -24
  88. package/dist/contracts/resources.js +36 -16
  89. package/dist/contracts/runtime-api.d.ts +433 -192
  90. package/dist/contracts/runtime-api.js +421 -356
  91. package/dist/contracts/runtime-app.d.ts +127 -120
  92. package/dist/contracts/runtime-app.js +19 -10
  93. package/dist/contracts/runtime-db.d.ts +5 -5
  94. package/dist/contracts/runtime-db.js +2 -2
  95. package/dist/contracts/runtime-purge.d.ts +8 -0
  96. package/dist/contracts/runtime-purge.js +7 -0
  97. package/dist/contracts/runtime-services.d.ts +219 -0
  98. package/dist/contracts/runtime-services.js +437 -0
  99. package/dist/contracts/runtime-storage.d.ts +46 -32
  100. package/dist/contracts/runtime-storage.js +31 -8
  101. package/dist/contracts/sf-config-v1.d.ts +48 -10
  102. package/dist/contracts/space-config.d.ts +69 -18
  103. package/dist/contracts/space-config.js +226 -27
  104. package/dist/contracts/spaces.d.ts +236 -450
  105. package/dist/contracts/spaces.js +131 -151
  106. package/dist/contracts/superadmin-activity.d.ts +57 -4
  107. package/dist/contracts/superadmin-activity.js +48 -2
  108. package/dist/contracts/superadmin-search.d.ts +126 -0
  109. package/dist/contracts/superadmin-search.js +61 -0
  110. package/dist/contracts/superadmin-spaces.d.ts +361 -92
  111. package/dist/contracts/superadmin-spaces.js +97 -24
  112. package/dist/contracts/superadmin-teams.d.ts +28 -10
  113. package/dist/contracts/superadmin-teams.js +5 -8
  114. package/dist/contracts/superadmin-tenants.d.ts +0 -6
  115. package/dist/contracts/superadmin-tenants.js +1 -7
  116. package/dist/contracts/superadmin.d.ts +135 -54
  117. package/dist/contracts/superadmin.js +46 -23
  118. package/dist/contracts/tags.d.ts +12 -12
  119. package/dist/contracts/tags.js +3 -3
  120. package/dist/contracts/teams.d.ts +49 -14
  121. package/dist/contracts/teams.js +40 -22
  122. package/dist/contracts/transfers.d.ts +0 -49
  123. package/dist/contracts/transfers.js +4 -8
  124. package/dist/contracts/usage.d.ts +61 -0
  125. package/dist/contracts/usage.js +64 -0
  126. package/dist/contracts/variables.d.ts +2 -7
  127. package/dist/contracts/variables.js +9 -8
  128. package/dist/contracts/webhooks.d.ts +2 -2
  129. package/dist/contracts/zero.d.ts +102 -73
  130. package/dist/contracts/zero.js +94 -32
  131. package/dist/dashboard-paths/index.d.ts +1 -1
  132. package/dist/dashboard-paths/index.js +1 -0
  133. package/dist/docs/agent-prose.d.ts +2 -2
  134. package/dist/docs/agent-prose.js +63 -70
  135. package/dist/docs/agent-setup.js +45 -15
  136. package/dist/docs/catalog.d.ts +24 -24
  137. package/dist/docs/catalog.js +35 -24
  138. package/dist/docs/error-docs.js +194 -270
  139. package/dist/docs/skills.d.ts +12 -12
  140. package/dist/docs/skills.js +4 -4
  141. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  142. package/dist/test-helpers/fetch-stub.js +25 -0
  143. package/dist/utils/build-settings.d.ts +5 -1
  144. package/dist/utils/build-settings.js +91 -13
  145. package/dist/utils/canonical-json.d.ts +10 -0
  146. package/dist/utils/canonical-json.js +22 -0
  147. package/dist/utils/cast-transport.d.ts +28 -0
  148. package/dist/utils/cast-transport.js +52 -0
  149. package/dist/utils/claim-token.d.ts +0 -8
  150. package/dist/utils/claim-token.js +3 -15
  151. package/dist/utils/concurrency.js +1 -0
  152. package/dist/utils/credential-policy.d.ts +1 -1
  153. package/dist/utils/credential-policy.js +4 -3
  154. package/dist/utils/dns-instructions.js +9 -9
  155. package/dist/utils/env-file.d.ts +17 -0
  156. package/dist/utils/env-file.js +96 -0
  157. package/dist/utils/error-code.d.ts +2 -0
  158. package/dist/utils/error-code.js +8 -0
  159. package/dist/utils/generate-space-name.d.ts +8 -10
  160. package/dist/utils/generate-space-name.js +206 -21
  161. package/dist/utils/grant-decision.fixtures.json +2938 -0
  162. package/dist/utils/grants.d.ts +2 -0
  163. package/dist/utils/grants.js +26 -1
  164. package/dist/utils/gravatar.d.ts +9 -0
  165. package/dist/utils/gravatar.js +31 -0
  166. package/dist/utils/idempotency.d.ts +6 -0
  167. package/dist/utils/idempotency.js +28 -0
  168. package/dist/utils/local-space-state.d.ts +9 -16
  169. package/dist/utils/local-space-state.js +20 -50
  170. package/dist/utils/one-shot-replay.d.ts +4 -1
  171. package/dist/utils/page-colors.d.ts +14 -12
  172. package/dist/utils/page-colors.js +15 -13
  173. package/dist/utils/page-fonts.d.ts +2 -2
  174. package/dist/utils/page-fonts.js +15 -4
  175. package/dist/utils/pages.d.ts +21 -3
  176. package/dist/utils/pages.js +42 -13
  177. package/dist/utils/publish-form-data.d.ts +12 -0
  178. package/dist/utils/publish-form-data.js +10 -0
  179. package/dist/utils/publish-policy.d.ts +27 -0
  180. package/dist/utils/publish-policy.fixtures.json +403 -0
  181. package/dist/utils/publish-policy.js +45 -20
  182. package/dist/utils/push-new.d.ts +7 -0
  183. package/dist/utils/push-new.js +7 -0
  184. package/dist/utils/query-keys.d.ts +4 -5
  185. package/dist/utils/query-keys.js +2 -3
  186. package/dist/utils/runtime-paths.d.ts +5 -0
  187. package/dist/utils/runtime-paths.js +5 -0
  188. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  189. package/dist/utils/runtime-upload-batch.js +1 -27
  190. package/dist/utils/runtime-upload.d.ts +6 -11
  191. package/dist/utils/runtime-upload.js +10 -13
  192. package/dist/utils/secure-local-file.js +5 -16
  193. package/dist/utils/space-config.d.ts +1 -34
  194. package/dist/utils/space-config.js +8 -326
  195. package/dist/utils/static-runtime-policy.d.ts +9 -3
  196. package/dist/utils/static-runtime-policy.fixtures.json +38 -41
  197. package/dist/utils/static-runtime-policy.js +22 -56
  198. package/dist/utils/storage-policy.d.ts +1 -1
  199. package/dist/utils/storage-policy.js +3 -1
  200. package/dist/utils/structured-error.d.ts +14 -0
  201. package/dist/utils/structured-error.js +22 -0
  202. package/dist/utils/upload-session.d.ts +4 -1
  203. package/dist/utils/upload-session.js +33 -5
  204. package/dist/utils/version-path.js +13 -4
  205. package/dist/vocabulary.d.ts +13 -11
  206. package/dist/vocabulary.js +42 -28
  207. package/package.json +7 -17
  208. package/dist/config/postgres-budget.d.ts +0 -129
  209. package/dist/config/postgres-budget.js +0 -140
  210. package/dist/config/postgres-budget.json +0 -51
  211. package/dist/contracts/archives.d.ts +0 -331
  212. package/dist/contracts/archives.js +0 -120
  213. package/dist/contracts/deployments.d.ts +0 -574
  214. package/dist/contracts/deployments.js +0 -97
  215. package/dist/contracts/sites.d.ts +0 -416
  216. package/dist/contracts/sites.js +0 -168
  217. package/dist/contracts/superadmin-queues.d.ts +0 -414
  218. package/dist/contracts/superadmin-queues.js +0 -202
  219. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  220. package/dist/contracts/superadmin-runtime.js +0 -38
  221. package/dist/index.d.ts +0 -1
  222. package/dist/index.js +0 -1
  223. package/dist/utils/api.d.ts +0 -4
  224. package/dist/utils/api.js +0 -13
@@ -2,38 +2,28 @@ import { z } from "zod";
2
2
  import { accessFenceKindSchema } from "./access.js";
3
3
  import { functionsRuntimeConfigSchema } from "./functions.js";
4
4
  import { grantSchema } from "./grants.js";
5
- import { conventionFilesSchema, manifestFileSchema } from "./internal.js";
5
+ import { manifestFileSchema } from "./internal.js";
6
6
  import { runtimePagesSchema } from "./pages.js";
7
+ import { runtimePurgeStatusSchema } from "./runtime-purge.js";
8
+ import { SPACE_CONFIG_CAPS } from "./space-config.js";
7
9
  import { zeroEndpointSchema, zeroRuntimeConfigSchema } from "./zero.js";
8
- const runtimeConventionFilesSchema = conventionFilesSchema.omit({ routes: true }).strict();
9
10
  const hostCanonicalRedirectSchema = z.object({
10
11
  from: z.string().min(1),
11
12
  to: z.string().url(),
12
13
  status: z.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)]).default(308),
13
14
  });
14
- // Immutable version hosts carry their own target version: the intent is the
15
- // COMPLETE declarative hostname map for the space (every retained ready
16
- // version), so a route update can never orphan an older version URL.
15
+ // The intent is the COMPLETE declarative hostname map for the space (every
16
+ // retained ready version), so a route update can never orphan a version URL.
17
17
  const versionHostnameEntrySchema = z.object({
18
18
  hostname: z.string().min(1),
19
19
  version_id: z.string().min(1),
20
20
  });
21
- // Future expansion: this intent could support explicit path-to-artifact composition
22
- // after independently deployable components have a real product model.
23
21
  const runtimeRouteIntentSchema = z.object({
24
22
  production_hostnames: z.array(z.string().min(1)).default([]),
25
23
  noindex_production_hostnames: z.array(z.string().min(1)).default([]),
26
24
  version_hostnames: z.array(versionHostnameEntrySchema).default([]),
27
25
  host_canonical_redirects: z.array(hostCanonicalRedirectSchema).default([]),
28
26
  });
29
- // Changed-path purge contract (internal-docs/platform.md "Cache Management"):
30
- // finalize computes the changed-path set against the previous pointer target
31
- // — expanded to canonical request paths (leading slash, directory-index
32
- // variants included) — and carries it through the runtime callback journal;
33
- // the callback receiver translates journal events into edge purges scoped to
34
- // changed paths, never a blanket purge when a diff exists. Past this cap (or
35
- // when no previous pointer target exists) the control plane omits the set and
36
- // purge handling falls back to one host-wide purge.
37
27
  export const RUNTIME_CHANGED_PATHS_MAX = 900;
38
28
  const changedPathsSchema = z
39
29
  .array(z
@@ -43,23 +33,66 @@ const changedPathsSchema = z
43
33
  .describe("Canonical request path (decoded, leading slash) whose served bytes changed."))
44
34
  .max(RUNTIME_CHANGED_PATHS_MAX)
45
35
  .optional()
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.");
36
+ .describe("Request paths changed from the previous pointer target. When absent, the runtime purges the whole domain.");
47
37
  const previousVersionIdSchema = z
48
38
  .string()
49
39
  .min(1)
50
40
  .optional()
51
41
  .describe("The pointer target this activation replaces (diff baseline).");
42
+ export const runtimePurgeReceiptSchema = z.object({
43
+ status: runtimePurgeStatusSchema,
44
+ mode: z.enum(["urls", "domain"]),
45
+ urls: z.number().int().nonnegative().optional(),
46
+ });
47
+ const runtimeMutationPurgeFields = {
48
+ purge: runtimePurgeReceiptSchema.optional(),
49
+ };
50
+ export const runtimeSpacePurgeRequestSchema = z.object({
51
+ urls: z.array(z.string().startsWith("/")).optional(),
52
+ });
53
+ export const runtimeSpacePurgeResponseSchema = z.object({
54
+ space_id: z.string().min(1),
55
+ purge: runtimePurgeReceiptSchema,
56
+ });
57
+ // One bounded page of a version's file catalog — the runtime's own answer to
58
+ // "which files does this version have". `view=source` is the uploaded,
59
+ // pre-substitution object; `view=served` is the immutable byte a visitor
60
+ // receives, and `variant_route` marks a channel's substituted bytes within it.
61
+ export const runtimeVersionFilesResponseSchema = z.object({
62
+ space_id: z.string().min(1),
63
+ version_id: z.string().min(1),
64
+ view: z.enum(["source", "served"]),
65
+ files: z.array(z.object({
66
+ path: z.string().min(1),
67
+ size: z.number().int().nonnegative(),
68
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
69
+ content_type: z.string().min(1).optional(),
70
+ public: z.boolean(),
71
+ variant_route: z.string().min(1).optional(),
72
+ })),
73
+ next_cursor: z.string().min(1).nullable().optional(),
74
+ });
75
+ export const runtimeRetentionModeSchema = z.enum(["all", "list", "none"]);
52
76
  export const runtimeVersionCreateRequestSchema = z.object({
53
77
  version_id: z.string().min(1).optional(),
54
78
  files: z.array(manifestFileSchema),
55
- retained_files: z.array(manifestFileSchema).default([]),
79
+ // Retention is stated, never inferred. `all` carries every path of
80
+ // `reusable_version_id` forward — the runtime materializes that list from the
81
+ // base version's own catalog at finalize, under the space lock, which is what
82
+ // keeps the lazy publish path free of a pre-upload round trip. `list` retains
83
+ // exactly `retained_files`, and an EMPTY list is meaningful: it is a publish
84
+ // that drops every path the base held. `none` retains nothing.
85
+ //
86
+ // `all` and `list` both require `reusable_version_id`. Only `list` may carry
87
+ // `retained_files`. Inferring `all` from "base named + empty list" used to
88
+ // silently undo deletes — the runtime has no delete list, so deletion IS
89
+ // omission from the retained set.
90
+ retention: runtimeRetentionModeSchema,
91
+ retained_files: z.array(manifestFileSchema).optional(),
56
92
  reusable_version_id: z.string().min(1).optional(),
57
- // Session state stores the upload truth (spec "Upload Contract"): the version's
58
- // manifest hash and the draft expiry so resume answers come from runtime state.
59
93
  manifest_hash: z.string().optional(),
60
94
  expires_at: z.string().optional(),
61
95
  metadata: z.record(z.string(), z.unknown()).default({}),
62
- convention_files: runtimeConventionFilesSchema.default({}),
63
96
  });
64
97
  export const runtimeVersionCreateResponseSchema = z.object({
65
98
  space_id: z.string(),
@@ -75,26 +108,33 @@ export const runtimeUploadSessionDescriptorSchema = z.object({
75
108
  manifest_hash: z.string().optional(),
76
109
  files: z.array(manifestFileSchema),
77
110
  // Retained files only travel in the authenticated finalize request: they are
78
- // unbounded and unused while accepting a PUT.
111
+ // unbounded and unused while accepting a PUT, which is also what keeps a lazy
112
+ // session's descriptor inside its JWT budget. Retention itself is stated on
113
+ // the wire — see the create request.
114
+ retention: runtimeRetentionModeSchema,
79
115
  retained_files: z.array(manifestFileSchema).optional(),
80
116
  reusable_version_id: z.string().min(1).optional(),
81
117
  metadata: z.record(z.string(), z.unknown()).default({}),
82
- convention_files: runtimeConventionFilesSchema.default({}),
83
118
  });
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.
119
+ /**
120
+ * The retention half of a create-version body or session descriptor, spelled
121
+ * once. `retained_files` is omitted when empty with an explicit `retention`
122
+ * that omission is unambiguous: it means retain nothing beyond what `all`
123
+ * materializes.
124
+ */
125
+ export function runtimeRetentionFields(input) {
126
+ return {
127
+ retention: input.retention,
128
+ ...(input.retainedFiles.length > 0 ? { retained_files: [...input.retainedFiles] } : {}),
129
+ ...(input.reusableVersionId ? { reusable_version_id: input.reusableVersionId } : {}),
130
+ };
131
+ }
87
132
  export const RUNTIME_UPLOAD_SESSION_DESCRIPTOR_MAX_BYTES = 4096;
88
133
  // Hard wire budget for the complete `Authorization: Bearer <jwt>\r\n` line sent
89
134
  // on every PUT. Lazy-session selection measures the signed line and falls back
90
135
  // to eager runtime creation rather than returning an oversized credential.
91
136
  export const RUNTIME_UPLOAD_AUTHORIZATION_HEADER_MAX_BYTES = 8 * 1024;
92
- // Budget for the whole descriptor, which is persisted on the version row and
93
- // replayed inside the durable finalize receipt.
94
137
  export const RUNTIME_UPLOAD_SESSION_STORED_MAX_BYTES = 256 * 1024;
95
- // Runtime-truth upload session state (spec "Upload Contract"): the resume flow asks
96
- // the runtime which declared files/chunks are actually present instead of guessing
97
- // from control-plane pending-upload state.
98
138
  export const runtimeUploadSessionResponseSchema = z.object({
99
139
  space_id: z.string(),
100
140
  version_id: z.string(),
@@ -110,12 +150,7 @@ export const runtimeUploadSessionResponseSchema = z.object({
110
150
  })),
111
151
  // Declared paths the runtime has not committed yet.
112
152
  pending_paths: z.array(z.string()),
113
- // Staged chunked-upload parts by canonical path (contiguity is checked at complete).
114
- chunks: z.record(z.string(), z.array(z.number().int().positive())),
115
153
  });
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
154
  export const visitorJwkSchema = z.object({
120
155
  kty: z.literal("OKP"),
121
156
  crv: z.literal("Ed25519"),
@@ -136,20 +171,13 @@ export const runtimePublicExposureDescriptorSchema = z.object({
136
171
  unmodeled: z.string(),
137
172
  });
138
173
  // 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
174
  export const runtimeAccessPageDescriptorSchema = z
146
175
  .object({
147
176
  displayName: z.string().min(1).max(200).nullable(),
148
177
  // API-origin account lane (`/v1/access/acquire/<target>`), null until the
149
178
  // space is claimed. Doubles as the silent-SSO probe target.
150
179
  accountUrl: z.string().url().nullable(),
151
- // Identity connections rendered as SSO lanes. The runtime appends
152
- // `host`/`return` to `startUrl` at render time.
180
+ // The runtime appends `host`/`return` to `startUrl` at render time.
153
181
  connections: z.array(z.object({
154
182
  id: z.string().min(1).max(64),
155
183
  label: z.string().min(1).max(200),
@@ -158,15 +186,11 @@ export const runtimeAccessPageDescriptorSchema = z
158
186
  exchange: z
159
187
  .object({
160
188
  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
189
  linkUrl: z.string().url(),
165
190
  tokenUrl: z.string().url(),
166
191
  emailUrl: z.string().url(),
167
192
  requestUrl: z.string().url(),
168
193
  logoutUrl: z.string().url(),
169
- commentsConfigUrl: z.string().url().optional(),
170
194
  commentsTicketUrl: z.string().url().optional(),
171
195
  commentsVersionUrlsUrl: z.string().url().optional(),
172
196
  zeroRealtimeTicketUrl: z.string().url().optional(),
@@ -175,17 +199,11 @@ export const runtimeAccessPageDescriptorSchema = z
175
199
  .nullable(),
176
200
  })
177
201
  .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
202
  export const RUNTIME_AUTHORIZATION_GRANT_LIMIT = 1_024;
183
203
  export const runtimeAuthorizationProjectionSchema = z
184
204
  .object({
185
205
  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.
206
+ // Only an explicit logout-all rotation invalidates otherwise-valid sessions.
189
207
  sessionVersion: z.number().int().nonnegative(),
190
208
  fence: accessFenceKindSchema,
191
209
  acquireUrl: z.string().url(),
@@ -193,48 +211,78 @@ export const runtimeAuthorizationProjectionSchema = z
193
211
  spaceClaimed: z.boolean(),
194
212
  grants: z
195
213
  .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.
214
+ // The browser session records this per acquired authority, so rotating
215
+ // one Grant removes only that authority from the session.
199
216
  generation: z.number().int().positive(),
200
217
  }))
201
218
  .max(RUNTIME_AUTHORIZATION_GRANT_LIMIT),
202
219
  })
203
220
  .strict();
221
+ // The space-level Comments projection the runtime serves as the whole answer
222
+ // for `/__spacefast/comments/config`; the runtime adds only per-request fields.
223
+ const runtimeSdkCommentsSchema = z.object({
224
+ // Comments boot on the live/published host.
225
+ live: z.boolean(),
226
+ // ...and on an immutable version (preview) host.
227
+ preview: z.boolean(),
228
+ // Null before the Space has ever published.
229
+ live_url: z.string().url().max(2000).nullable(),
230
+ theme: z.object({
231
+ accent: z
232
+ .string()
233
+ .regex(/^#[0-9a-fA-F]{6}$/)
234
+ .nullable(),
235
+ hide_branding: z.boolean(),
236
+ }),
237
+ features: z.object({
238
+ picker: z.boolean(),
239
+ drawing: z.boolean(),
240
+ capture: z.boolean(),
241
+ attachments: z.boolean(),
242
+ notices: z.boolean(),
243
+ }),
244
+ });
245
+ const runtimeSdkProjectionSchema = z.object({
246
+ // Content-derived identity, used to version the injected
247
+ // `/__spacefast/sdk.js?v=` URL.
248
+ revision: z.string().min(1).max(256),
249
+ config: z.object({
250
+ cast_api_base: z.string().url().max(2000).nullable().optional(),
251
+ cast_ws_url: z.string().url().max(2000).nullable().optional(),
252
+ cast_resource_key: z.string().min(1).max(256).nullable().optional(),
253
+ comments: runtimeSdkCommentsSchema.nullable().optional(),
254
+ }),
255
+ // Production tag JavaScript, served inside the same loader response so
256
+ // private Spaces never fetch an anonymously readable per-Space artifact.
257
+ body: z
258
+ .string()
259
+ .max(5 * 1024 * 1024)
260
+ .nullable()
261
+ .optional(),
262
+ });
204
263
  const runtimeRouteConfigSchema = z.object({
205
- // Canonical scoped admission projection. When present it is the sole serving
206
- // authority; the legacy rule document below is ignored.
264
+ // The sole serving admission authority when present.
207
265
  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.
266
+ // Digest of every input that can change anonymous/public exposure.
267
+ // Authority-only rotation deliberately leaves it stable, so Link/Person
268
+ // changes do not block on an unnecessary edge purge.
212
269
  public_exposure_digest: z
213
270
  .string()
214
271
  .regex(/^[a-f0-9]{64}$/)
215
272
  .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.
273
+ // The digest detects equality; the descriptor lets the consumer distinguish
274
+ // widening from narrowing.
219
275
  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.
276
+ // Short-lived handoffs are bound to this exact generation, Space, and host.
223
277
  projection_generation: z.number().int().nonnegative().nullable().optional(),
224
- // First-party visitor-token verification is explicit.
225
278
  visitor_issuer: z.string().min(1).optional(),
226
279
  visitor_jwks: visitorJwksSchema.optional(),
227
- // Anonymous claim-window countdown (ISO timestamp), compiled into serving
228
- // state so the runtime can render the expiry-rescue banner for visitors
229
- // holding the claim-link viewer session. Null/absent once claimed.
280
+ // Anonymous claim-window countdown (ISO timestamp). Null/absent once claimed.
230
281
  anonymous_expires_at: z.string().nullable().optional(),
231
- // Serve-time content-type allowlist. Generic serving policy: the engine has
232
- // no notion of WHY a file type is blocked — it refuses (403, blocked_message
282
+ // Serve-time content-type allowlist: the engine refuses (403, blocked_message
233
283
  // body) any file whose stored Content-Type matches no pattern. Patterns are
234
- // exact types or `prefix/*` wildcards ("text/*"). The control plane pushes
235
- // it for unclaimed anonymous spaces (no opaque binaries pre-claim); explicit
236
- // null clears the stored policy — route config is rebuilt wholesale, so a
237
- // claim clears it structurally on the next sync.
284
+ // exact types or `prefix/*` wildcards ("text/*"). Explicit null clears the
285
+ // stored policy.
238
286
  content_types: z
239
287
  .object({
240
288
  allowed: z.array(z.string().min(1)),
@@ -242,28 +290,18 @@ const runtimeRouteConfigSchema = z.object({
242
290
  })
243
291
  .nullable()
244
292
  .optional(),
245
- // Per-space admission-control override (plan §5/§25; contract A1): the
246
- // in-flight concurrency limit the engine enforces on uncacheable requests
247
- // for this space, superseding SPACEFAST_UNCACHEABLE_CONC_PER_SPACE. Ops-only
248
- // in V1 (set via the superadmin admission endpoint); absent means the
249
- // env-knob default applies.
293
+ // In-flight concurrency limit the engine enforces on uncacheable requests for
294
+ // this space, superseding SPACEFAST_UNCACHEABLE_CONC_PER_SPACE. Absent means
295
+ // the env-knob default applies.
250
296
  admission: z.object({ concurrency: z.number().int().positive() }).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(),
260
- // Serve-time plan entitlements (proxy-routes.md: "the rule stays in your
261
- // config and activates the moment you upgrade — no redeploy needed"). Stored
262
- // per space beside `policy`; a `planGated` compiled proxy rule
263
- // (packages/routing plan.ts) is checked against this doc at REQUEST time by
264
- // runtime/redirects.php, never baked into the compiled artifact. Explicit
265
- // null clears the stored doc (falls back to fail-closed); absence leaves it
266
- // untouched.
297
+ // Projection for the same-host loader at `/__spacefast/sdk.js`, and the
298
+ // source of the space overlay the runtime answers
299
+ // `/__spacefast/comments/config` from.
300
+ sdk: runtimeSdkProjectionSchema.nullable().optional(),
301
+ // Serve-time plan entitlements. A `planGated` compiled proxy rule is checked
302
+ // against this doc at REQUEST time, never baked into the compiled artifact.
303
+ // Explicit null clears the stored doc (falls back to fail-closed); absence
304
+ // leaves it untouched.
267
305
  entitlements: z
268
306
  .object({
269
307
  externalProxy: z.boolean(),
@@ -271,11 +309,9 @@ const runtimeRouteConfigSchema = z.object({
271
309
  .nullable()
272
310
  .optional(),
273
311
  });
274
- // Compiled SpaceConfig serving primitives (spec "Serving resolution"): the finalize
275
- // compiler resolves content defaults -> sf.jsonc -> space overlay into this shape;
276
- // the runtime stores it on the version's serving artifact and never re-infers. When
277
- // absent, the runtime infers content defaults (root index.html => website-style,
278
- // otherwise artifact-style listing + viewer).
312
+ // Compiled SpaceConfig serving primitives. When absent, the runtime infers
313
+ // content defaults (root index.html => website-style, otherwise artifact-style
314
+ // listing + viewer).
279
315
  const runtimeServingConfigSchema = z.object({
280
316
  index: z
281
317
  .union([z.string().min(1), z.literal(false)])
@@ -304,36 +340,34 @@ const runtimeServingConfigSchema = z.object({
304
340
  .describe("Generate the single-file viewer page when a version has exactly one public file."),
305
341
  meta: z
306
342
  .object({
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."),
309
- image: z.string().max(2000).optional().describe("OG image path."),
343
+ title: z
344
+ .string()
345
+ .max(SPACE_CONFIG_CAPS.metaTitleChars)
346
+ .optional()
347
+ .describe("Listing, viewer, and OG title."),
348
+ description: z
349
+ .string()
350
+ .max(SPACE_CONFIG_CAPS.metaDescriptionChars)
351
+ .optional()
352
+ .describe("Listing, viewer, and OG description."),
353
+ image: z.string().max(SPACE_CONFIG_CAPS.metaImageChars).optional().describe("OG image path."),
354
+ favicon: z
355
+ .string()
356
+ .max(SPACE_CONFIG_CAPS.metaImageChars)
357
+ .optional()
358
+ .describe("Favicon path."),
310
359
  })
311
360
  .nullable()
312
361
  .optional()
313
- .describe("Finalize-resolved listing, viewer, and OG metadata (effective SpaceConfig metadata). This value wins over session metadata."),
362
+ .describe("Finalize-resolved listing, viewer, sharing, and browser icon metadata (effective SpaceConfig metadata). This value wins over session metadata."),
314
363
  pages: runtimePagesSchema.optional(),
315
- // Hidden experimental build-pipeline flag: absent on every version finalized
316
- // without it (byte-identical payload to today). Only ever sent as true.
364
+ // Hidden experimental build-pipeline flag. Only ever sent as true.
317
365
  experimental_gutenberg: z.boolean().optional(),
318
- // Platform-owned head metadata injection for normal HTML finalization. This is
319
- // distinct from the hidden user build-pipeline flag: it only enables missing
320
- // title/description/OG/Twitter tags derived from finalized SpaceConfig meta.
366
+ // Platform-owned head metadata injection: enables missing title,
367
+ // description, favicon, OG, and Twitter tags from finalized SpaceConfig meta.
321
368
  platform_meta: z.boolean().optional(),
322
- // Platform-owned same-host SDK bootstrap metadata. The public Cast base is
323
- // finalized with the version so locally-created remote runtimes do not guess
324
- // between local/prod; resource identity is also per-version because one
325
- // runtime/Atomic can serve many spaces.
326
- spacefast_sdk: z
327
- .object({
328
- cast_api_base: z.string().url().max(2000).nullable().optional(),
329
- cast_ws_url: z.string().url().max(2000).nullable().optional(),
330
- cast_resource_key: z.string().min(1).max(256).nullable().optional(),
331
- published_comments: z.boolean().optional(),
332
- })
333
- .optional(),
334
- // CP-validated inject snippets (<= 8 KB each, <= 16 per array); sent only
335
- // when experimental_gutenberg is true. Char caps here are a sanity bound —
336
- // the byte caps are enforced by the shared SpaceConfig validator.
369
+ // Sent only when experimental_gutenberg is true. The char caps here are a
370
+ // sanity bound; the byte caps live in the shared SpaceConfig validator.
337
371
  inject: z
338
372
  .object({
339
373
  head: z.array(z.string().max(8192)).max(16).optional(),
@@ -375,61 +409,49 @@ const runtimeZeroRunSchema = z.object({
375
409
  capabilities: runtimeZeroEndpointCapabilitiesSchema.optional(),
376
410
  db: z.record(z.string(), z.unknown()).optional(),
377
411
  });
378
- // Gated-activation mode (plan §9, Placement V2): whether a version/import
379
- // installs as immediately live ("active") or staged pending an explicit
380
- // activation call ("activating", the engine's own fail-safe default when
381
- // absent). Shared by finalize and import — do not re-inline this enum.
382
- export const runtimeZeroModeSchema = z.enum(["active", "activating"]);
412
+ // One variable scope as finalize resolved it. Secrets are NEVER resolved: a
413
+ // secret match at any scope fails the publish in the control plane, so the
414
+ // runtime receives the flag and no value.
415
+ const runtimeVariableScopeSchema = z.object({
416
+ kind: z.enum(["space", "principal"]),
417
+ values: z.record(z.string().min(1), z.object({
418
+ value: z.string().optional(),
419
+ secret: z.boolean(),
420
+ channelValues: z.record(z.string().min(1), z.string()).optional(),
421
+ })),
422
+ });
423
+ const runtimeSubstitutionChannelSchema = z.object({
424
+ name: z.string().min(1).max(128),
425
+ route_name: z
426
+ .string()
427
+ .min(1)
428
+ .max(128)
429
+ .regex(/^[A-Za-z0-9._-]+$/)
430
+ .describe("Runtime route name this channel's variant files serve under."),
431
+ });
383
432
  export const runtimeVersionFinalizeRequestSchema = z.object({
384
433
  upload_id: z.string().min(1),
385
434
  session: runtimeUploadSessionDescriptorSchema.optional(),
386
- zero_mode: runtimeZeroModeSchema.optional(),
387
435
  zero: zeroRuntimeConfigSchema.optional(),
388
436
  // 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.
437
+ // and absent when no execution host is configured the version then serves
438
+ // as pure static content, because this config is what makes the origin
439
+ // dispatch at all.
392
440
  functions: functionsRuntimeConfigSchema.optional(),
393
- convention_files: runtimeConventionFilesSchema.optional(),
394
- // Finalize-time `{{ vars.NAME }}` substitution output (spec "Variable
395
- // rules"): substituted bytes replace the served files; the engine keeps the
396
- // pre-substitution bytes in `files-original/` so declared-sha dedup still
397
- // verifies upload identity. Keyed by canonical version-relative path.
398
- template_files: z
399
- .record(z.string().min(1).describe("Canonical committed path declared in `templates`."), z
400
- .string()
401
- .max(2 * 1024 * 1024)
402
- .describe("Substituted file contents (secret values never reach this payload)."))
403
- .refine((files) => Object.keys(files).length <= 100, {
404
- message: "template_files supports up to 100 entries.",
405
- })
406
- .optional()
407
- .describe("Substituted template contents applied over the committed tree at finalize."),
408
- // Per-channel template variants (spec "Per-channel values"): channelValues
409
- // overrides compile channel-specific contents of declared template files as
410
- // channel-scoped serving artifacts. Keyed by RUNTIME ROUTE NAME (route names
411
- // are runtime-only vocabulary; the live channel serves via `production`).
412
- // Variants serve only on the route's hosts — permanent Version URLs always
413
- // serve the version's own finalize-resolved values.
414
- template_variants: z
415
- .record(z
416
- .string()
417
- .min(1)
418
- .max(128)
419
- .regex(/^[A-Za-z0-9._-]+$/)
420
- .describe("Runtime route name this variant set serves."), z
421
- .record(z.string().min(1).describe("Canonical committed path declared in `templates`."), z
422
- .string()
423
- .max(2 * 1024 * 1024)
424
- .describe("Variant file contents (secret values never reach this payload)."))
425
- .refine((files) => Object.keys(files).length <= 100, {
426
- message: "template_variants supports up to 100 entries per route.",
427
- }))
428
- .refine((routes) => Object.keys(routes).length <= 8, {
429
- message: "template_variants supports up to 8 routes.",
430
- })
431
- .optional()
432
- .describe("Channel-variant template contents, keyed by runtime route name."),
441
+ // Variable resolution is the control plane's half of substitution and stays
442
+ // there: scopes come from its database, secret values are never decrypted for
443
+ // it, and `SPACEFAST_*` names are platform-provided. The finalizer owns the
444
+ // substitution itself — it has the staged bytes and the config that declares
445
+ // which paths are templates, so nothing pre-substituted crosses this wire.
446
+ variable_scopes: z.array(runtimeVariableScopeSchema).optional(),
447
+ system_variables: z.record(z.string().min(1), z.string()).optional(),
448
+ // The space's channels, so the finalizer can compile each one's variant files
449
+ // under the route name that serves them.
450
+ channels: z.array(runtimeSubstitutionChannelSchema).max(8).optional(),
451
+ // Hostnames this space answers on, which convention compilation needs to tell
452
+ // an internal redirect from an external proxy. Only the control plane knows
453
+ // the assignment.
454
+ routing_assigned_hostnames: z.array(z.string().min(1)).optional(),
433
455
  // Finalize-rendered Pages documents stored outside the public file tree.
434
456
  page_artifacts: z
435
457
  .record(z.string().min(1).max(240), z.string().max(2 * 1024 * 1024))
@@ -437,18 +459,24 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
437
459
  message: "page_artifacts supports up to 100,000 rendered documents.",
438
460
  })
439
461
  .optional(),
462
+ // Serving inputs only the control plane can produce. The compiled routing
463
+ // rules are NOT here: the finalizer compiles `_redirects`, `_headers` and the
464
+ // config's routing sections from the staged files itself.
440
465
  serving: z
441
466
  .object({
442
- headers_exact: z.record(z.string(), z.array(z.record(z.string(), z.unknown()))).default({}),
443
- headers_pattern: z.array(z.record(z.string(), z.unknown())).default([]),
444
- redirects_exact: z.record(z.string(), z.array(z.record(z.string(), z.unknown()))).default({}),
445
- redirects_pattern: z.array(z.record(z.string(), z.unknown())).default([]),
467
+ noindexHost: z.boolean().optional(),
446
468
  config: runtimeServingConfigSchema.optional(),
447
- // Trusted Zero endpoint source compiled by the runtime into private
448
- // endpoint-specific JS, QuickJS bytecode, and PHP manifest invoke actions.
469
+ // Origins the platform's own browser code loads from, keyed by the CSP
470
+ // directive each belongs in. Which origins those are is a control-plane
471
+ // decision — they come from its environment and are validated against the
472
+ // host this version will serve from — while the merge itself belongs to
473
+ // the finalizer, which is the only side that sees every CSP the version
474
+ // sets, in `_headers` and in config rules alike.
475
+ platform_csp_sources: z
476
+ .record(z.string().min(1).max(64), z.array(z.string().min(1).max(512)).max(16))
477
+ .optional(),
449
478
  zero_endpoints: z.array(runtimeZeroEndpointSchema).max(128).optional(),
450
- // Trusted Zero run-handler source compiled into private artifacts for
451
- // non-HTTP Zero execution surfaces. These are not PHP route actions.
479
+ // Non-HTTP Zero execution surfaces; not PHP route actions.
452
480
  zero_runs: z.array(runtimeZeroRunSchema).max(128).optional(),
453
481
  })
454
482
  .optional(),
@@ -460,10 +488,8 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
460
488
  .default("production")
461
489
  .describe("Runtime route-pointer to activate; route names are runtime-only vocabulary."),
462
490
  config: runtimeRouteConfigSchema.optional(),
463
- // Same contract as the route PUT's config_digest: stored with the
464
- // pointer so later conditional route PUTs can no-op against it.
491
+ // Same contract as the route PUT's config_digest.
465
492
  config_digest: z.string().min(1).optional(),
466
- changed_paths: changedPathsSchema,
467
493
  previous_version_id: previousVersionIdSchema,
468
494
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
469
495
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
@@ -486,23 +512,107 @@ export const runtimeReadinessTargetSchema = z.object({
486
512
  path: z.string().startsWith("/"),
487
513
  expected_statuses: z.array(z.union([runtimePublicReadinessStatusSchema, z.literal(404)])).min(1),
488
514
  });
515
+ // The scalars the control plane stores instead of a file list. Both digests are
516
+ // over the version's own catalog: `source` folds path → uploaded sha (two
517
+ // publishes of identical bytes agree even when serving config differs),
518
+ // `served` folds what the version actually answers with, per channel included.
519
+ export const runtimeCatalogDigestsSchema = z.object({
520
+ source: z.string().regex(/^sha256:[a-f0-9]{64}$/),
521
+ served: z.string().regex(/^sha256:[a-f0-9]{64}$/),
522
+ });
523
+ // What this publish changed against the version it supersedes. The runtime
524
+ // diffs two catalogs it already holds; the control plane persists the three
525
+ // counts for the changelog and forwards nothing.
526
+ export const runtimeCatalogDeltaSchema = z.object({
527
+ added: z.number().int().nonnegative(),
528
+ changed: z.number().int().nonnegative(),
529
+ removed: z.number().int().nonnegative(),
530
+ });
531
+ // One compiled diagnostic from the finalizer. `path` is the committed file it
532
+ // points at, carrying `file:line` when the compiler knew a line; `details` is
533
+ // what it points at INSIDE that file — the variable, the channel, the colliding
534
+ // routes — which is the difference between a publish failure you can act on and
535
+ // one you can only retry.
536
+ export const runtimeCompileDiagnosticSchema = z.object({
537
+ severity: z.enum(["info", "warning", "error"]),
538
+ code: z.string().min(1),
539
+ message: z.string(),
540
+ path: z.string().optional(),
541
+ details: z.record(z.string(), z.unknown()).optional(),
542
+ });
543
+ // What the version's conventions and config compiled TO. The counts are the
544
+ // control plane's version-row projection; `proxy_rules` is what it overlays the
545
+ // publishing team's plan onto, because the compiled artifact never bakes a plan
546
+ // verdict.
547
+ export const runtimeCompiledRoutingSchema = z.object({
548
+ redirect_rule_count: z.number().int().nonnegative(),
549
+ header_rule_count: z.number().int().nonnegative(),
550
+ proxy_rule_count: z.number().int().nonnegative(),
551
+ proxy_rules: z
552
+ .array(z.object({ source: z.string(), destination: z.string() }))
553
+ .max(1000)
554
+ .default([]),
555
+ });
556
+ // What one finalize RUN cost, stage by stage, straight off the native
557
+ // finalizer's envelope — which is why these keys are camelCase while the rest of
558
+ // this receipt is not: PHP passes the object through rather than restating it.
559
+ // Everything else here is read back from the version's immutable metadata;
560
+ // timings cannot be, because they differ on every run. `skippedFiles` is the
561
+ // incrementality gauge: it stays 0 while every finalize rebuilds everything.
562
+ export const runtimeFinalizeTelemetrySchema = z.object({
563
+ stagedFiles: z.number().int().nonnegative(),
564
+ generatedFiles: z.number().int().nonnegative(),
565
+ decoratedFiles: z.number().int().nonnegative(),
566
+ skippedFiles: z.number().int().nonnegative(),
567
+ stagingMs: z.number().int().nonnegative(),
568
+ templateSubstitutionMs: z.number().int().nonnegative(),
569
+ htmlPipelineMs: z.number().int().nonnegative(),
570
+ // The four stages below ship with the finalizer that splits them out; an
571
+ // engine predating the split reports the coarser stages only.
572
+ conventionsMs: z.number().int().nonnegative().optional(),
573
+ zeroCompileMs: z.number().int().nonnegative().optional(),
574
+ blobInstallMs: z.number().int().nonnegative(),
575
+ listingsMs: z.number().int().nonnegative().optional(),
576
+ responseTablesMs: z.number().int().nonnegative(),
577
+ catalogDeltaMs: z.number().int().nonnegative(),
578
+ artifactsWriteMs: z.number().int().nonnegative().optional(),
579
+ totalMs: z.number().int().nonnegative(),
580
+ });
489
581
  export const runtimeVersionFinalizeResponseSchema = z.object({
490
582
  space_id: z.string(),
491
583
  version_id: z.string(),
492
584
  status: z.literal("ready"),
493
- // Runtime-compiled truth from zero/endpoints-index.json. Optional so older
494
- // runtimes remain parseable during rolling deploys.
495
585
  zero_endpoint_count: z.number().int().nonnegative().optional(),
496
- // Canonical runtime-committed manifest (path, size, streaming sha256).
497
- // Returned by both first and idempotent finalize responses so recovery
498
- // never needs an already-consumed upload session.
586
+ // The finalizer is the one conventions/config compiler, so what it compiled is
587
+ // reported back rather than recomputed: the control plane runs no second
588
+ // compile pass to learn its own version row.
589
+ diagnostics: z.array(runtimeCompileDiagnosticSchema).default([]),
590
+ routing: runtimeCompiledRoutingSchema.optional(),
591
+ // Provenance for the substitution the finalizer performed: dependency name
592
+ // (`NAME` or `NAME@channel`) -> SHA-256 of the value it resolved to. This is
593
+ // what answers "does changing this variable require a republish?".
594
+ variable_digests: z.record(z.string().min(1), z.string()).default({}),
595
+ system_variable_dependencies: z.array(z.string().min(1)).default([]),
596
+ // Canonical runtime-committed manifest, returned by both first and idempotent
597
+ // finalize responses so recovery never needs a consumed upload session.
499
598
  manifest: z.array(manifestFileSchema).optional(),
599
+ catalog_digests: runtimeCatalogDigestsSchema.optional(),
600
+ // Absent when this version supersedes nothing, or when the previous version
601
+ // predates the catalog.
602
+ delta: runtimeCatalogDeltaSchema.optional(),
603
+ // The served public image the space card shows for this version, chosen from
604
+ // the catalog at finalize. Null when the version serves no image.
605
+ preview_image_path: z.string().min(1).nullable().optional(),
500
606
  readiness_target: runtimeReadinessTargetSchema,
607
+ // Absent whenever the runtime answered without finalizing: a replayed or
608
+ // idempotent finalize re-runs no stage and reports no timings.
609
+ telemetry: runtimeFinalizeTelemetrySchema.optional(),
501
610
  activation_event_id: z
502
611
  .string()
503
612
  .min(1)
504
613
  .optional()
505
614
  .describe("Stable route_updated event id when finalize also activated a route."),
615
+ ...runtimeMutationPurgeFields,
506
616
  });
507
617
  export const runtimeZeroMigrationsApplyResponseSchema = z.object({
508
618
  space_id: z.string(),
@@ -512,12 +622,12 @@ export const runtimeZeroMigrationsApplyResponseSchema = z.object({
512
622
  export const runtimeRouteUpdateRequestSchema = z.object({
513
623
  version_id: z.string().min(1),
514
624
  config: runtimeRouteConfigSchema.optional(),
515
- // Canonical digest of `config` (control-plane routeConfigDigest). The engine
516
- // stores it with the route pointer and no-ops the whole PUT no policy
517
- // store, no journal event, no edge purge — when version, digest, and
625
+ // The engine stores this with the route pointer and no-ops the whole PUT — no
626
+ // policy store, no journal event, no edge purge when version, digest, and
518
627
  // hostname intent all match the stored route. Absent = always write.
519
628
  config_digest: z.string().min(1).optional(),
520
- changed_paths: changedPathsSchema,
629
+ // No `changed_paths`: the runtime holds both catalogs and scopes the purge
630
+ // from `previous_version_id` itself.
521
631
  previous_version_id: previousVersionIdSchema,
522
632
  production_hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
523
633
  noindex_production_hostnames: runtimeRouteIntentSchema.shape.noindex_production_hostnames,
@@ -535,36 +645,36 @@ export const runtimeRouteUpdateResponseSchema = z.object({
535
645
  route_name: z.string(),
536
646
  version_id: z.string(),
537
647
  // True when the engine skipped the write because version_id, config_digest,
538
- // and hostname intent all matched the stored route: nothing was journaled,
539
- // so callers skip the event drain too.
648
+ // and hostname intent all matched: nothing was journaled, so callers skip the
649
+ // event drain too.
540
650
  unchanged: z.boolean().optional(),
541
651
  activation_event_id: z
542
652
  .string()
543
653
  .min(1)
544
654
  .optional()
545
655
  .describe("Stable route_updated event id for this exact route command, including retries."),
656
+ ...runtimeMutationPurgeFields,
546
657
  });
547
658
  export const runtimeHostnameIntentUpdateResponseSchema = z.object({
548
659
  space_id: z.string(),
549
660
  route_count: z.number().int().nonnegative(),
661
+ ...runtimeMutationPurgeFields,
550
662
  });
551
663
  export const runtimeTombstonesUpdateRequestSchema = z.object({
552
664
  hostnames: runtimeRouteIntentSchema.shape.production_hostnames,
553
665
  mode: z.enum(["replace", "add", "remove"]).default("replace"),
554
- // Optional reason/category drive a differentiated tombstone variant on the
555
- // runtime (e.g. csam -> bare 404, copyright -> 451, suspension -> 402). The
556
- // runtime falls back to a generic 404 when both are absent, so these are
557
- // additive and back-compatible.
666
+ // Select a differentiated tombstone variant (csam -> bare 404, copyright ->
667
+ // 451, suspension -> 402); absent means a generic 404.
558
668
  reason: z.string().optional(),
559
669
  category: z.string().optional(),
560
670
  });
561
671
  export const runtimeTombstonesUpdateResponseSchema = z.object({
562
672
  space_id: z.string(),
563
673
  tombstone_count: z.number().int().nonnegative(),
674
+ ...runtimeMutationPurgeFields,
564
675
  });
565
- // Retention/pruning policy artifact (Placement V2 tiering, plan §8/§26): the
566
- // control plane computes prunable version ids; the engine stores the list and
567
- // its housekeeping tick prunes trees after re-checking live route pointers.
676
+ // The control plane computes prunable version ids; the engine stores the list
677
+ // and its housekeeping tick prunes trees after re-checking live route pointers.
568
678
  // An empty list clears the policy.
569
679
  export const runtimeRetentionPolicyUpdateRequestSchema = z.object({
570
680
  prunable_version_ids: z.array(z.string()),
@@ -572,127 +682,83 @@ export const runtimeRetentionPolicyUpdateRequestSchema = z.object({
572
682
  export const runtimeRetentionPolicyUpdateResponseSchema = z.object({
573
683
  space_id: z.string(),
574
684
  prunable_count: z.number().int().nonnegative(),
575
- });
576
- export const runtimeSpaceDeleteResponseSchema = z.object({
577
- space_id: z.string(),
578
- status: z.literal("deleted"),
685
+ ...runtimeMutationPurgeFields,
579
686
  });
580
687
  export const runtimeVersionDeleteResponseSchema = z.object({
581
688
  space_id: z.string(),
582
689
  version_id: z.string(),
583
690
  status: z.literal("deleted"),
584
- });
585
- export const runtimeSpaceRepairResponseSchema = z.object({
586
- space_id: z.string(),
587
- status: z.literal("repaired"),
588
- });
589
- const runtimeExportImportStatusSchema = z.enum([
590
- "waiting_for_archive",
591
- "pending",
592
- "running",
593
- "complete",
691
+ ...runtimeMutationPurgeFields,
692
+ });
693
+ // A pin freezes a set of blob SHAs against the collector for as long as it
694
+ // lives. `snapshot` pins everything a set of versions plus space storage
695
+ // currently reference; `shas` pins an explicit list. `expires_at` is the
696
+ // engine's hard backstop, so a mover that dies mid-flight never wedges the
697
+ // collector.
698
+ const runtimeSha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
699
+ export const runtimePinCreateRequestSchema = z.union([
700
+ z.object({
701
+ mode: z.literal("snapshot"),
702
+ version_ids: z.array(z.string().min(1)),
703
+ include_uploads: z.boolean().default(true),
704
+ ttl_seconds: z
705
+ .number()
706
+ .int()
707
+ .positive()
708
+ .max(24 * 60 * 60),
709
+ }),
710
+ z.object({
711
+ mode: z.literal("shas"),
712
+ shas: z.array(runtimeSha256Schema).max(100_000),
713
+ ttl_seconds: z
714
+ .number()
715
+ .int()
716
+ .positive()
717
+ .max(24 * 60 * 60),
718
+ }),
594
719
  ]);
595
- export const runtimeSpaceExportStartRequestSchema = z
596
- .object({
597
- version_ids: z.array(z.string().min(1)).min(1).optional(),
598
- // include_files=false produces a metadata-only archive that cannot be
599
- // imported as a serving Space. Recorded in stattic.json `includes`.
600
- include_files: z.boolean().optional(),
601
- })
602
- .strict();
603
- export const runtimeSpaceExportResponseSchema = z.object({
604
- type: z.literal("space_export"),
605
- export_id: z.string(),
606
- space_id: z.string(),
607
- status: runtimeExportImportStatusSchema,
608
- cursor: z.number().int().nonnegative(),
609
- processed_files: z.number().int().nonnegative(),
610
- total_files: z.number().int().nonnegative(),
611
- version_ids: z.array(z.string()),
612
- archive_size: z.number().int().nonnegative().optional(),
613
- // SHA-256 of the finished archive (64 lowercase hex), computed once at
614
- // completion so the control plane can expose SpaceExport.archive.sha256.
615
- archive_sha256: z.string().optional(),
616
- created_at: z.string(),
617
- updated_at: z.string(),
618
- completed_at: z.string().optional(),
619
- });
620
- export const runtimeSpaceImportStartRequestSchema = z
621
- .object({
622
- version_id_map: z.record(z.string(), z.string()).optional(),
623
- // Plan §9 gated activation, extended to import (I-4): shares
624
- // runtimeZeroModeSchema with runtimeVersionFinalizeRequestSchema.
625
- // Absent/anything other than 'active' is the engine's own fail-safe
626
- // 'activating'.
627
- zero_mode: runtimeZeroModeSchema.optional(),
628
- })
629
- .strict();
630
- // Deferred version-id mapping: control-plane-driven uploads land on the
631
- // runtime before the control plane has read the archive, so the freshly
632
- // minted target ids (and, for upload-sourced imports, zero_mode) are
633
- // supplied here before the first step.
634
- export const runtimeSpaceImportMapRequestSchema = z
635
- .object({
636
- version_id_map: z.record(z.string(), z.string()),
637
- zero_mode: runtimeZeroModeSchema.optional(),
638
- })
639
- .strict();
640
- export const runtimeSpaceImportResponseSchema = z.object({
641
- type: z.literal("space_import"),
642
- import_id: z.string(),
643
- space_id: z.string(),
644
- status: runtimeExportImportStatusSchema,
645
- cursor: z.number().int().nonnegative(),
646
- processed_files: z.number().int().nonnegative(),
647
- total_files: z.number().int().nonnegative(),
648
- version_ids: z.array(z.string()),
649
- created_at: z.string(),
650
- updated_at: z.string(),
651
- completed_at: z.string().optional(),
652
- });
653
- const runtimeTransferBundleFileSchema = z.object({
654
- path: z.string().min(1),
655
- content_b64: z.string(),
656
- });
657
- export const runtimeTransferBundleSourceRequestSchema = z.object({
720
+ export const runtimePinCreateResponseSchema = z.object({
658
721
  space_id: z.string().min(1),
659
- version_ids: z.array(z.string().min(1)),
660
- cursor: z.string().nullable(),
661
- include_serving_state: z.boolean().optional(),
722
+ pin_id: z.string().min(1),
723
+ expires_at: z.string().min(1),
724
+ sha_count: z.number().int().nonnegative(),
662
725
  });
663
- export const runtimeTransferBundleTargetRequestSchema = z.object({
726
+ export const runtimePinReleaseResponseSchema = z.object({
664
727
  space_id: z.string().min(1),
665
- store: z.literal(true),
666
- files: z.array(runtimeTransferBundleFileSchema),
728
+ pin_id: z.string().min(1),
729
+ released: z.boolean(),
730
+ });
731
+ // Path-blind on purpose: the response names only the SHAs the space does not
732
+ // already hold.
733
+ export const runtimeBlobsHaveRequestSchema = z.object({
734
+ shas: z.array(runtimeSha256Schema),
735
+ });
736
+ export const runtimeBlobsHaveResponseSchema = z.object({
737
+ missing: z.array(runtimeSha256Schema),
738
+ });
739
+ export const runtimeBlobPutResponseSchema = z.object({
740
+ sha256: runtimeSha256Schema,
741
+ size: z.number().int().nonnegative(),
742
+ stored: z.boolean(),
743
+ });
744
+ // The mirror of the paged export lane: pages replay in the same table/ordering
745
+ // against a schema the target already migrated. Import is idempotent on primary
746
+ // key, so a retried page is not a duplicate row.
747
+ export const runtimeZeroDbImportRequestSchema = z.object({
748
+ schema_hash: z.string().min(1),
749
+ table: z.string().min(1),
750
+ rows: z.array(z.record(z.string(), z.unknown())).max(1_000),
667
751
  done: z.boolean(),
668
752
  });
669
- export const runtimeTransferBundleResponseSchema = z.object({
670
- chunk: z.object({ files: z.array(runtimeTransferBundleFileSchema) }),
671
- next_cursor: z.string().nullable(),
672
- });
673
- export const runtimeTransferBundleStoredResponseSchema = z.object({
674
- stored: z.literal(true),
675
- });
676
- export const runtimeTransferCommitRequestSchema = z.object({
677
- space_id: z.string().min(1),
678
- version_ids: z.array(z.string().min(1)),
679
- live_version_id: z.string().nullable(),
680
- install_serving_state: z.boolean().optional(),
681
- });
682
- export const runtimeTransferCommitResponseSchema = z.object({
683
- installed: z.literal(true),
684
- });
685
- export const runtimeTransferAbortRequestSchema = z.object({
753
+ export const runtimeZeroDbImportResponseSchema = z.object({
686
754
  space_id: z.string().min(1),
687
- });
688
- export const runtimeTransferAbortResponseSchema = z.object({
689
- aborted: z.boolean().optional(),
755
+ version_id: z.string().min(1),
756
+ table: z.string().min(1),
757
+ imported: z.number().int().nonnegative(),
690
758
  });
691
759
  // Per-space generation state reported by the authed state endpoint. The
692
- // reconciliation sweep compares this against control-plane expectations
693
- // (space liveVersionId vs the runtime's route pointers) and repairs targeted
694
- // mismatches — never a blanket re-sync (internal-docs/platform.md "Cache
695
- // Management": missed callbacks degrade to bounded staleness).
760
+ // reconciliation sweep compares it against control-plane expectations and
761
+ // repairs targeted mismatches never a blanket re-sync.
696
762
  export const runtimeSpaceStateSchema = z.object({
697
763
  space_id: z.string().min(1).describe("Space whose runtime route pointers follow."),
698
764
  routes: z
@@ -703,12 +769,6 @@ export const runtimeSpaceStateSchema = z.object({
703
769
  .int()
704
770
  .nonnegative()
705
771
  .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
772
  intent_hostnames: z
713
773
  .array(z.string().min(1))
714
774
  .optional()
@@ -739,25 +799,19 @@ export const runtimeStateResponseSchema = z.object({
739
799
  .min(1)
740
800
  .optional()
741
801
  .describe("Runtime engine bundle revision. Missing means the engine predates revision state."),
742
- // Optional generation-state fields (absent on engines predating the reconcile
743
- // handshake; the sweep treats absence as "cannot compare").
744
802
  routes_generation: z
745
803
  .string()
746
804
  .nullable()
747
805
  .optional()
748
- .describe("Active route-index generation id (routes/current.php pointer)."),
806
+ .describe("Active route-index generation id (routes/current.json pointer)."),
749
807
  spaces: z
750
808
  .array(runtimeSpaceStateSchema)
751
809
  .optional()
752
810
  .describe("Per-space route-pointer state for reconcile comparison."),
753
811
  });
754
- // Runtime callback journal event (internal-docs/platform.md "Cache
755
- // Management"): the engine journals every management mutation and delivers it
756
- // at-least-once to the control-plane callback receiver, which translates
757
- // serving-affecting events into provider edge purges. `event_id` is the stable
758
- // journal id — purge enqueues are idempotent on (event_id, hostname) so
759
- // replays are no-ops. The object is intentionally loose: engines may journal
760
- // extra diagnostic fields without breaking older control planes.
812
+ // Runtime callback journal event. Purging is complete (or durably queued for a
813
+ // maintenance retry) before a mutation returns, so this lane carries only
814
+ // observability and background events.
761
815
  export const runtimeCallbackEventSchema = z.looseObject({
762
816
  event: z
763
817
  .string()
@@ -767,7 +821,7 @@ export const runtimeCallbackEventSchema = z.looseObject({
767
821
  .string()
768
822
  .min(1)
769
823
  .optional()
770
- .describe("Stable journal/callback id; purge handling replays as no-ops on this id."),
824
+ .describe("Stable journal/callback id for idempotent delivery."),
771
825
  operation_id: z
772
826
  .string()
773
827
  .optional()
@@ -815,6 +869,13 @@ const runtimeEventDeliveryIdentitySchema = z.object({
815
869
  delivery_id: z.string().min(1),
816
870
  });
817
871
  const runtimeEventDrainSessionIdSchema = z.string().min(1).max(128);
872
+ export const runtimeJournalCursorSchema = z
873
+ .object({
874
+ file: z.string(),
875
+ offset: z.number().int().nonnegative(),
876
+ inode: z.number().int().nonnegative().optional(),
877
+ })
878
+ .strict();
818
879
  export const runtimeEventsDrainRequestSchema = z
819
880
  .object({
820
881
  session_id: runtimeEventDrainSessionIdSchema,
@@ -841,49 +902,55 @@ export const runtimeEventsDrainRequestSchema = z
841
902
  context.addIssue({ code: "custom", message: "A drain page requires page_id." });
842
903
  });
843
904
  export const runtimeEventsDrainResponseSchema = z.object({
905
+ // Cursor runtimes keep these at zero. Older engines report their legacy
906
+ // delivery counters here while a rolling bundle update is still converging.
844
907
  delivered_count: z.number().int().nonnegative(),
845
908
  failed_count: z.number().int().nonnegative(),
846
- expired_count: z
847
- .number()
848
- .int()
849
- .nonnegative()
850
- .optional()
851
- .describe("Undeliverable callbacks dropped after exceeding the attempt cap."),
909
+ expired_count: z.number().int().nonnegative().optional(),
852
910
  returned_count: z.number().int().nonnegative().optional(),
853
911
  pending_count: z
854
912
  .number()
855
913
  .int()
856
914
  .nonnegative()
857
915
  .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).
916
+ .describe("Whether the journal has records beyond this page. A non-zero value means a later drain still has work."),
863
917
  events: z
864
918
  .array(z.object({
865
919
  delivery_id: z.string().min(1),
866
920
  operation_id: z.string().optional(),
867
- callback_token: z.string().min(1),
868
921
  event: runtimeCallbackEventSchema,
869
922
  }))
870
923
  .optional(),
924
+ cursor: runtimeJournalCursorSchema
925
+ .optional()
926
+ .describe("The journal commit coordinate. Absent on pre-cursor runtime bundles."),
871
927
  });
872
- export const runtimeEventsAckRequestSchema = z.object({
873
- session_id: runtimeEventDrainSessionIdSchema,
874
- deliveries: z.array(runtimeEventDeliveryIdentitySchema).max(100),
928
+ export const runtimeEventsAckRequestSchema = z
929
+ .object({
930
+ // The cursor is the authoritative commit coordinate.
931
+ cursor: runtimeJournalCursorSchema.optional(),
932
+ session_id: runtimeEventDrainSessionIdSchema.optional(),
933
+ deliveries: z.array(runtimeEventDeliveryIdentitySchema).max(100).default([]),
934
+ })
935
+ .superRefine((value, context) => {
936
+ if (!value.cursor && (!value.session_id || value.deliveries.length === 0)) {
937
+ context.addIssue({
938
+ code: "custom",
939
+ message: "An event acknowledgement requires a cursor or legacy deliveries.",
940
+ });
941
+ }
875
942
  });
876
943
  export const runtimeEventsAckResponseSchema = z.object({
877
944
  acknowledged_count: z.number().int().nonnegative(),
878
945
  idempotent_count: z.number().int().nonnegative(),
879
946
  stale_count: z.number().int().nonnegative(),
947
+ cursor: runtimeJournalCursorSchema
948
+ .optional()
949
+ .describe("The committed journal coordinate. Absent after a legacy delivery acknowledgement."),
880
950
  });
881
- // Engine job runner (plan §22, Placement V2): the box-local job runner behind
882
- // the two-lane tick. The control plane creates a job, drives it with repeated
883
- // ticks (kick-on-enqueue; the site-cron watchdog ticks the same route), and
884
- // reads status from either the tick response or a direct GET. Field names
885
- // mirror the engine's job record (§22 "Storage layout & job record").
886
- export const runtimeEngineJobLaneSchema = z.enum(["interactive", "bulk"]);
951
+ // One lane; the enum keeps its single value because the engine's job record and
952
+ // tick response still carry the field.
953
+ export const runtimeEngineJobLaneSchema = z.literal("bulk");
887
954
  export const runtimeEngineJobStatusSchema = z.enum(["pending", "running", "complete", "failed"]);
888
955
  export const runtimeEngineJobErrorSchema = z.object({
889
956
  code: z.string().min(1),
@@ -912,15 +979,13 @@ export const runtimeEngineJobRecordSchema = z.object({
912
979
  export const runtimeEngineJobCreateRequestSchema = z.object({
913
980
  type: z.string().min(1),
914
981
  payload: z.record(z.string(), z.unknown()).default({}),
915
- // Unique per (type, space, operation) — create is upsert-by-key (§22).
982
+ // Unique per (type, space, operation) — create is upsert-by-key.
916
983
  idempotency_key: z.string().min(1),
917
984
  });
918
985
  export const runtimeEngineJobResponseSchema = z.object({
919
986
  job: runtimeEngineJobRecordSchema,
920
987
  });
921
- // The tick response carries the ticked job's fresh state when one was
922
- // claimed/advanced this call, so a driver loop can usually avoid a follow-up
923
- // GET; `job` is absent/null when the lane had nothing eligible to run.
988
+ // `job` is absent/null when the lane had nothing eligible to run.
924
989
  export const runtimeEngineJobTickResponseSchema = z.object({
925
990
  lane: runtimeEngineJobLaneSchema,
926
991
  job: runtimeEngineJobRecordSchema.nullable().optional(),