@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.
- package/brand-assets/LICENSE-Haskoy.txt +93 -0
- package/brand-assets/haskoy-latin-variable.woff2 +0 -0
- package/brand-assets/spacefast-favicon.svg +4 -4
- package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
- package/brand-assets/spacefast-wordmark.svg +2 -4
- package/dist/agents/connect-targets.js +1 -1
- package/dist/agents/private-key-oauth.d.ts +0 -1
- package/dist/agents/private-key-oauth.js +46 -63
- package/dist/brand-assets-build.js +4 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +15 -14
- package/dist/config/domains.d.ts +47 -7
- package/dist/config/domains.js +66 -10
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +0 -1
- package/dist/contracts/abuse.d.ts +2 -0
- package/dist/contracts/abuse.js +12 -0
- package/dist/contracts/access.d.ts +124 -12
- package/dist/contracts/access.js +73 -9
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +5 -12
- package/dist/contracts/api-keys.d.ts +21 -21
- package/dist/contracts/api-keys.js +2 -2
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/billing.d.ts +3 -35
- package/dist/contracts/billing.js +33 -52
- package/dist/contracts/builds.d.ts +30 -18
- package/dist/contracts/builds.js +28 -9
- package/dist/contracts/channels.js +1 -1
- package/dist/contracts/cli.d.ts +10 -0
- package/dist/contracts/cli.js +10 -0
- package/dist/contracts/collab-public.d.ts +63 -0
- package/dist/contracts/collab-public.js +46 -0
- package/dist/contracts/comments.d.ts +282 -53
- package/dist/contracts/comments.js +173 -52
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +5 -5
- package/dist/contracts/crons.d.ts +35 -0
- package/dist/contracts/crons.js +42 -0
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +2 -2
- package/dist/contracts/docs.js +2 -2
- package/dist/contracts/domains.d.ts +38 -27
- package/dist/contracts/domains.js +29 -2
- package/dist/contracts/enums.d.ts +34 -30
- package/dist/contracts/enums.js +62 -48
- package/dist/contracts/error-code-meta.d.ts +50 -125
- package/dist/contracts/error-code-meta.js +29 -41
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +14 -33
- package/dist/contracts/execution.d.ts +47 -66
- package/dist/contracts/execution.js +53 -49
- package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
- package/dist/contracts/feature-lifecycle-core.js +2 -31
- package/dist/contracts/feature-lifecycle.d.ts +12 -52
- package/dist/contracts/feature-lifecycle.js +16 -59
- package/dist/contracts/features.d.ts +48 -60
- package/dist/contracts/features.js +59 -51
- package/dist/contracts/functions.d.ts +266 -72
- package/dist/contracts/functions.js +260 -37
- package/dist/contracts/git.d.ts +28 -34
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grants.d.ts +9 -5
- package/dist/contracts/grants.js +18 -9
- package/dist/contracts/ids.js +2 -2
- package/dist/contracts/internal.d.ts +8 -6
- package/dist/contracts/internal.js +12 -12
- package/dist/contracts/mcp.d.ts +11 -0
- package/dist/contracts/mcp.js +5 -0
- package/dist/contracts/me.js +1 -1
- package/dist/contracts/oauth-resources.d.ts +4 -3
- package/dist/contracts/oauth-resources.js +12 -3
- package/dist/contracts/operations.d.ts +90 -5
- package/dist/contracts/operations.js +77 -3
- package/dist/contracts/pages.d.ts +12 -7
- package/dist/contracts/pages.js +24 -1
- package/dist/contracts/plan-policy.d.ts +0 -2
- package/dist/contracts/plan-policy.js +2 -3
- package/dist/contracts/platform.d.ts +61 -148
- package/dist/contracts/platform.js +70 -126
- package/dist/contracts/publish-archive.d.ts +508 -0
- package/dist/contracts/publish-archive.js +32 -0
- package/dist/contracts/push-new.d.ts +4 -4
- package/dist/contracts/quotas.js +2 -2
- package/dist/contracts/repository-connections.d.ts +43 -30
- package/dist/contracts/repository-connections.js +11 -3
- package/dist/contracts/resources.d.ts +12 -24
- package/dist/contracts/resources.js +36 -16
- package/dist/contracts/runtime-api.d.ts +433 -192
- package/dist/contracts/runtime-api.js +421 -356
- package/dist/contracts/runtime-app.d.ts +127 -120
- package/dist/contracts/runtime-app.js +19 -10
- package/dist/contracts/runtime-db.d.ts +5 -5
- package/dist/contracts/runtime-db.js +2 -2
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +7 -0
- package/dist/contracts/runtime-services.d.ts +219 -0
- package/dist/contracts/runtime-services.js +437 -0
- package/dist/contracts/runtime-storage.d.ts +46 -32
- package/dist/contracts/runtime-storage.js +31 -8
- package/dist/contracts/sf-config-v1.d.ts +48 -10
- package/dist/contracts/space-config.d.ts +69 -18
- package/dist/contracts/space-config.js +226 -27
- package/dist/contracts/spaces.d.ts +236 -450
- package/dist/contracts/spaces.js +131 -151
- package/dist/contracts/superadmin-activity.d.ts +57 -4
- package/dist/contracts/superadmin-activity.js +48 -2
- package/dist/contracts/superadmin-search.d.ts +126 -0
- package/dist/contracts/superadmin-search.js +61 -0
- package/dist/contracts/superadmin-spaces.d.ts +361 -92
- package/dist/contracts/superadmin-spaces.js +97 -24
- package/dist/contracts/superadmin-teams.d.ts +28 -10
- package/dist/contracts/superadmin-teams.js +5 -8
- package/dist/contracts/superadmin-tenants.d.ts +0 -6
- package/dist/contracts/superadmin-tenants.js +1 -7
- package/dist/contracts/superadmin.d.ts +135 -54
- package/dist/contracts/superadmin.js +46 -23
- package/dist/contracts/tags.d.ts +12 -12
- package/dist/contracts/tags.js +3 -3
- package/dist/contracts/teams.d.ts +49 -14
- package/dist/contracts/teams.js +40 -22
- package/dist/contracts/transfers.d.ts +0 -49
- package/dist/contracts/transfers.js +4 -8
- package/dist/contracts/usage.d.ts +61 -0
- package/dist/contracts/usage.js +64 -0
- package/dist/contracts/variables.d.ts +2 -7
- package/dist/contracts/variables.js +9 -8
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +102 -73
- package/dist/contracts/zero.js +94 -32
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.d.ts +2 -2
- package/dist/docs/agent-prose.js +63 -70
- package/dist/docs/agent-setup.js +45 -15
- package/dist/docs/catalog.d.ts +24 -24
- package/dist/docs/catalog.js +35 -24
- package/dist/docs/error-docs.js +194 -270
- package/dist/docs/skills.d.ts +12 -12
- package/dist/docs/skills.js +4 -4
- package/dist/test-helpers/fetch-stub.d.ts +16 -0
- package/dist/test-helpers/fetch-stub.js +25 -0
- package/dist/utils/build-settings.d.ts +5 -1
- package/dist/utils/build-settings.js +91 -13
- package/dist/utils/canonical-json.d.ts +10 -0
- package/dist/utils/canonical-json.js +22 -0
- package/dist/utils/cast-transport.d.ts +28 -0
- package/dist/utils/cast-transport.js +52 -0
- package/dist/utils/claim-token.d.ts +0 -8
- package/dist/utils/claim-token.js +3 -15
- package/dist/utils/concurrency.js +1 -0
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -3
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/env-file.d.ts +17 -0
- package/dist/utils/env-file.js +96 -0
- package/dist/utils/error-code.d.ts +2 -0
- package/dist/utils/error-code.js +8 -0
- package/dist/utils/generate-space-name.d.ts +8 -10
- package/dist/utils/generate-space-name.js +206 -21
- package/dist/utils/grant-decision.fixtures.json +2938 -0
- package/dist/utils/grants.d.ts +2 -0
- package/dist/utils/grants.js +26 -1
- package/dist/utils/gravatar.d.ts +9 -0
- package/dist/utils/gravatar.js +31 -0
- package/dist/utils/idempotency.d.ts +6 -0
- package/dist/utils/idempotency.js +28 -0
- package/dist/utils/local-space-state.d.ts +9 -16
- package/dist/utils/local-space-state.js +20 -50
- package/dist/utils/one-shot-replay.d.ts +4 -1
- package/dist/utils/page-colors.d.ts +14 -12
- package/dist/utils/page-colors.js +15 -13
- package/dist/utils/page-fonts.d.ts +2 -2
- package/dist/utils/page-fonts.js +15 -4
- package/dist/utils/pages.d.ts +21 -3
- package/dist/utils/pages.js +42 -13
- package/dist/utils/publish-form-data.d.ts +12 -0
- package/dist/utils/publish-form-data.js +10 -0
- package/dist/utils/publish-policy.d.ts +27 -0
- package/dist/utils/publish-policy.fixtures.json +403 -0
- package/dist/utils/publish-policy.js +45 -20
- package/dist/utils/push-new.d.ts +7 -0
- package/dist/utils/push-new.js +7 -0
- package/dist/utils/query-keys.d.ts +4 -5
- package/dist/utils/query-keys.js +2 -3
- package/dist/utils/runtime-paths.d.ts +5 -0
- package/dist/utils/runtime-paths.js +5 -0
- package/dist/utils/runtime-upload-batch.d.ts +0 -5
- package/dist/utils/runtime-upload-batch.js +1 -27
- package/dist/utils/runtime-upload.d.ts +6 -11
- package/dist/utils/runtime-upload.js +10 -13
- package/dist/utils/secure-local-file.js +5 -16
- package/dist/utils/space-config.d.ts +1 -34
- package/dist/utils/space-config.js +8 -326
- package/dist/utils/static-runtime-policy.d.ts +9 -3
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- package/dist/utils/static-runtime-policy.js +22 -56
- package/dist/utils/storage-policy.d.ts +1 -1
- package/dist/utils/storage-policy.js +3 -1
- package/dist/utils/structured-error.d.ts +14 -0
- package/dist/utils/structured-error.js +22 -0
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +33 -5
- package/dist/utils/version-path.js +13 -4
- package/dist/vocabulary.d.ts +13 -11
- package/dist/vocabulary.js +42 -28
- package/package.json +7 -17
- package/dist/config/postgres-budget.d.ts +0 -129
- package/dist/config/postgres-budget.js +0 -140
- package/dist/config/postgres-budget.json +0 -51
- package/dist/contracts/archives.d.ts +0 -331
- package/dist/contracts/archives.js +0 -120
- package/dist/contracts/deployments.d.ts +0 -574
- package/dist/contracts/deployments.js +0 -97
- package/dist/contracts/sites.d.ts +0 -416
- package/dist/contracts/sites.js +0 -168
- package/dist/contracts/superadmin-queues.d.ts +0 -414
- package/dist/contracts/superadmin-queues.js +0 -202
- package/dist/contracts/superadmin-runtime.d.ts +0 -27
- package/dist/contracts/superadmin-runtime.js +0 -38
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/api.d.ts +0 -4
- 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 {
|
|
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
|
-
//
|
|
15
|
-
//
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
197
|
-
//
|
|
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
|
-
//
|
|
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
|
|
209
|
-
//
|
|
210
|
-
//
|
|
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
|
|
217
|
-
//
|
|
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
|
-
//
|
|
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)
|
|
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
|
|
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/*").
|
|
235
|
-
//
|
|
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
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
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
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
275
|
-
//
|
|
276
|
-
//
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
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
|
|
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
|
|
319
|
-
//
|
|
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
|
-
//
|
|
323
|
-
//
|
|
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
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
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
|
|
390
|
-
//
|
|
391
|
-
//
|
|
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
|
-
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
448
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
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
|
-
//
|
|
516
|
-
//
|
|
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
|
|
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
|
|
539
|
-
//
|
|
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
|
-
//
|
|
555
|
-
//
|
|
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
|
-
//
|
|
566
|
-
//
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
|
726
|
+
export const runtimePinReleaseResponseSchema = z.object({
|
|
664
727
|
space_id: z.string().min(1),
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
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
|
-
|
|
689
|
-
|
|
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
|
|
693
|
-
//
|
|
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.
|
|
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 (
|
|
755
|
-
//
|
|
756
|
-
//
|
|
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
|
|
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("
|
|
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
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
//
|
|
882
|
-
//
|
|
883
|
-
|
|
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
|
|
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
|
-
//
|
|
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(),
|