@spacefast/common 0.0.24 → 0.2.1
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.d.ts +6 -146
- package/dist/agents/connect-targets.js +63 -295
- package/dist/agents/private-key-oauth.d.ts +31 -2
- package/dist/agents/private-key-oauth.js +166 -92
- package/dist/agents/registry/core.d.ts +292 -0
- package/dist/agents/registry/core.js +1388 -0
- package/dist/agents/registry/deeplink-probes.d.ts +23 -0
- package/dist/agents/registry/deeplink-probes.js +18 -0
- package/dist/agents/registry/node.d.ts +56 -0
- package/dist/agents/registry/node.js +339 -0
- package/dist/brand-assets-build.js +3 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +12 -14
- package/dist/brand.d.ts +17 -0
- package/dist/brand.js +24 -4
- package/dist/config/domains.d.ts +79 -12
- package/dist/config/domains.js +128 -29
- package/dist/config/index.js +1 -2
- package/dist/contracts/abuse.js +9 -24
- package/dist/contracts/access-profiles.d.ts +86 -0
- package/dist/contracts/access-profiles.js +89 -0
- package/dist/contracts/access.d.ts +24 -30
- package/dist/contracts/access.js +33 -64
- package/dist/contracts/activity.d.ts +18 -1
- package/dist/contracts/activity.js +72 -26
- package/dist/contracts/analytics.js +8 -10
- package/dist/contracts/api-keys.d.ts +77 -52
- package/dist/contracts/api-keys.js +44 -24
- package/dist/contracts/application-journal.d.ts +216 -0
- package/dist/contracts/application-journal.js +167 -0
- package/dist/contracts/auth.d.ts +90 -17
- package/dist/contracts/auth.js +86 -17
- package/dist/contracts/beta.d.ts +9 -4
- package/dist/contracts/beta.js +20 -16
- package/dist/contracts/billing.d.ts +2 -1
- package/dist/contracts/billing.js +37 -11
- package/dist/contracts/bootstrap.d.ts +791 -0
- package/dist/contracts/bootstrap.js +51 -0
- package/dist/contracts/builds.d.ts +209 -18
- package/dist/contracts/builds.js +93 -27
- package/dist/contracts/channels.d.ts +43 -0
- package/dist/contracts/channels.js +25 -9
- package/dist/contracts/cli.d.ts +7 -0
- package/dist/contracts/cli.js +6 -0
- package/dist/contracts/collab-public.d.ts +1 -1
- package/dist/contracts/collab-public.js +5 -8
- package/dist/contracts/comments.d.ts +44 -34
- package/dist/contracts/comments.js +107 -77
- package/dist/contracts/commerce.d.ts +165 -0
- package/dist/contracts/commerce.js +146 -0
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +20 -31
- package/dist/contracts/content-contract-verification.d.ts +521 -0
- package/dist/contracts/content-contract-verification.js +320 -0
- package/dist/contracts/content-platform-fixture.d.ts +1278 -0
- package/dist/contracts/content-platform-fixture.js +44 -0
- package/dist/contracts/content-program.d.ts +994 -0
- package/dist/contracts/content-program.js +658 -0
- package/dist/contracts/content-sync.d.ts +290 -0
- package/dist/contracts/content-sync.js +167 -0
- package/dist/contracts/content.d.ts +432 -0
- package/dist/contracts/content.js +303 -0
- package/dist/contracts/continuation.d.ts +17 -1
- package/dist/contracts/continuation.js +23 -23
- package/dist/contracts/countries.js +3 -4
- package/dist/contracts/crons.d.ts +52 -0
- package/dist/contracts/crons.js +60 -0
- package/dist/contracts/dashboard-prefs.d.ts +56 -0
- package/dist/contracts/dashboard-prefs.js +51 -0
- package/dist/contracts/device-auth.d.ts +234 -37
- package/dist/contracts/device-auth.js +267 -53
- package/dist/contracts/docs.d.ts +1 -5
- package/dist/contracts/docs.js +7 -15
- package/dist/contracts/domains.d.ts +501 -93
- package/dist/contracts/domains.js +193 -55
- package/dist/contracts/email-preferences.d.ts +3 -3
- package/dist/contracts/email-preferences.js +6 -7
- package/dist/contracts/enums.d.ts +27 -26
- package/dist/contracts/enums.js +68 -61
- package/dist/contracts/error-code-meta.d.ts +283 -7
- package/dist/contracts/error-code-meta.js +97 -5
- package/dist/contracts/error-codes.d.ts +5 -5
- package/dist/contracts/error-codes.js +82 -17
- package/dist/contracts/events.d.ts +100 -20
- package/dist/contracts/events.js +107 -20
- package/dist/contracts/execution.d.ts +48 -64
- package/dist/contracts/execution.js +52 -62
- package/dist/contracts/feature-lifecycle.d.ts +73 -27
- package/dist/contracts/feature-lifecycle.js +86 -26
- package/dist/contracts/features.d.ts +68 -19
- package/dist/contracts/features.js +42 -41
- package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
- package/dist/contracts/frame-session.d.ts +192 -0
- package/dist/contracts/frame-session.js +164 -0
- package/dist/contracts/functions.d.ts +338 -164
- package/dist/contracts/functions.js +367 -198
- package/dist/contracts/git.d.ts +27 -33
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grant-copy.d.ts +43 -0
- package/dist/contracts/grant-copy.js +44 -0
- package/dist/contracts/grants.d.ts +62 -54
- package/dist/contracts/grants.js +124 -23
- package/dist/contracts/ids.d.ts +4 -1
- package/dist/contracts/ids.js +6 -3
- package/dist/contracts/internal.d.ts +8 -18
- package/dist/contracts/internal.js +13 -14
- package/dist/contracts/mcp.d.ts +28 -0
- package/dist/contracts/mcp.js +28 -0
- package/dist/contracts/me.d.ts +6 -1
- package/dist/contracts/me.js +12 -6
- package/dist/contracts/notifications.d.ts +27 -29
- package/dist/contracts/notifications.js +9 -4
- package/dist/contracts/oauth-resources.d.ts +112 -10
- package/dist/contracts/oauth-resources.js +116 -11
- package/dist/contracts/oauth-scope-actions.d.ts +90 -0
- package/dist/contracts/oauth-scope-actions.js +126 -0
- package/dist/contracts/operations.d.ts +27 -4
- package/dist/contracts/operations.js +12 -6
- package/dist/contracts/pages.d.ts +31 -7
- package/dist/contracts/pages.js +22 -5
- package/dist/contracts/partner-notifications.d.ts +3 -0
- package/dist/contracts/partner-notifications.js +65 -0
- package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
- package/dist/contracts/{platform.js → partner.js} +27 -40
- package/dist/contracts/plan-policy.js +1 -1
- package/dist/contracts/principal-assertion.d.ts +43 -0
- package/dist/contracts/principal-assertion.js +67 -0
- package/dist/contracts/principals.d.ts +73 -0
- package/dist/contracts/principals.js +59 -0
- package/dist/contracts/privacy.d.ts +11 -0
- package/dist/contracts/privacy.js +17 -0
- package/dist/contracts/problem-document.js +3 -4
- package/dist/contracts/publish-archive.d.ts +269 -161
- package/dist/contracts/publish-archive.js +11 -1
- package/dist/contracts/push-new.d.ts +8 -8
- package/dist/contracts/quotas.js +5 -5
- package/dist/contracts/realtime.d.ts +52 -0
- package/dist/contracts/realtime.js +54 -0
- package/dist/contracts/repository-connections.d.ts +44 -31
- package/dist/contracts/repository-connections.js +17 -5
- package/dist/contracts/resources.d.ts +105 -14
- package/dist/contracts/resources.js +150 -26
- package/dist/contracts/route-inventory.d.ts +358 -0
- package/dist/contracts/route-inventory.js +141 -0
- package/dist/contracts/runtime-api.d.ts +206 -82
- package/dist/contracts/runtime-api.js +131 -70
- package/dist/contracts/runtime-app.d.ts +13 -23
- package/dist/contracts/runtime-app.js +39 -24
- package/dist/contracts/runtime-components.d.ts +143 -0
- package/dist/contracts/runtime-components.js +199 -0
- package/dist/contracts/runtime-db.d.ts +49 -23
- package/dist/contracts/runtime-db.js +87 -22
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +5 -0
- package/dist/contracts/runtime-services.d.ts +43 -75
- package/dist/contracts/runtime-services.js +29 -49
- package/dist/contracts/runtime-storage.d.ts +15 -14
- package/dist/contracts/runtime-storage.js +30 -14
- package/dist/contracts/sf-config-v1.d.ts +48 -14
- package/dist/contracts/slugs.js +13 -4
- package/dist/contracts/space-config.d.ts +283 -19
- package/dist/contracts/space-config.js +430 -80
- package/dist/contracts/spaces.d.ts +376 -290
- package/dist/contracts/spaces.js +200 -135
- package/dist/contracts/superadmin-activity.d.ts +3 -43
- package/dist/contracts/superadmin-activity.js +6 -30
- package/dist/contracts/superadmin-emails.d.ts +1 -0
- package/dist/contracts/superadmin-emails.js +17 -1
- package/dist/contracts/superadmin-runtime.d.ts +199 -0
- package/dist/contracts/superadmin-runtime.js +141 -0
- package/dist/contracts/superadmin-search.d.ts +22 -2
- package/dist/contracts/superadmin-search.js +14 -6
- package/dist/contracts/superadmin-spaces.d.ts +438 -189
- package/dist/contracts/superadmin-spaces.js +131 -25
- package/dist/contracts/superadmin-teams.d.ts +5 -2
- package/dist/contracts/superadmin-teams.js +1 -2
- package/dist/contracts/superadmin-tenants.d.ts +627 -12
- package/dist/contracts/superadmin-tenants.js +32 -13
- package/dist/contracts/superadmin.d.ts +232 -37
- package/dist/contracts/superadmin.js +106 -22
- package/dist/contracts/tags.d.ts +85 -58
- package/dist/contracts/tags.js +47 -37
- package/dist/contracts/teams.d.ts +86 -26
- package/dist/contracts/teams.js +90 -31
- package/dist/contracts/tenants.d.ts +135 -0
- package/dist/contracts/tenants.js +120 -0
- package/dist/contracts/test-triggers.d.ts +70 -0
- package/dist/contracts/test-triggers.js +116 -0
- package/dist/contracts/theme-json.js +3 -8
- package/dist/contracts/transfers.d.ts +1 -0
- package/dist/contracts/transfers.js +3 -2
- package/dist/contracts/usage.d.ts +209 -0
- package/dist/contracts/usage.js +124 -12
- package/dist/contracts/variables.d.ts +2 -2
- package/dist/contracts/variables.js +9 -13
- package/dist/contracts/webhooks.d.ts +13 -5
- package/dist/contracts/webhooks.js +3 -1
- package/dist/contracts/zero.d.ts +72 -81
- package/dist/contracts/zero.js +80 -78
- package/dist/dashboard-paths/index.d.ts +24 -21
- package/dist/dashboard-paths/index.js +35 -47
- package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
- package/dist/dashboard-paths/route-reservations.generated.js +36 -0
- package/dist/docs/agent-arrival.d.ts +92 -0
- package/dist/docs/agent-arrival.js +227 -0
- package/dist/docs/agent-output-policy.d.ts +6 -0
- package/dist/docs/agent-output-policy.js +22 -0
- package/dist/docs/agent-prose.d.ts +12 -18
- package/dist/docs/agent-prose.js +73 -51
- package/dist/docs/agent-setup.d.ts +72 -41
- package/dist/docs/agent-setup.js +339 -436
- package/dist/docs/catalog.d.ts +153 -95
- package/dist/docs/catalog.js +163 -48
- package/dist/docs/error-docs.d.ts +2098 -1
- package/dist/docs/error-docs.js +421 -144
- package/dist/docs/index-build.d.ts +0 -1
- package/dist/docs/index-build.js +2 -3
- package/dist/docs/page-context-prompt.d.ts +77 -0
- package/dist/docs/page-context-prompt.js +131 -0
- package/dist/docs/reference-urls.d.ts +46 -0
- package/dist/docs/reference-urls.js +79 -0
- package/dist/docs/search.d.ts +6 -8
- package/dist/docs/search.js +10 -13
- package/dist/docs/skill-distribution.d.ts +4 -1
- package/dist/docs/skill-distribution.js +10 -5
- package/dist/docs/skills.d.ts +120 -120
- package/dist/docs/skills.js +121 -73
- package/dist/docs/start-prompts.d.ts +45 -0
- package/dist/docs/start-prompts.js +62 -0
- package/dist/domain-exploration.js +9 -13
- package/dist/og-template.d.ts +56 -0
- package/dist/og-template.js +56 -0
- package/dist/slug-policy/index.js +5 -11
- package/dist/test-helpers/fetch-stub.js +1 -0
- package/dist/utils/browser-credential.d.ts +13 -6
- package/dist/utils/browser-credential.js +14 -7
- package/dist/utils/build-settings.d.ts +21 -1
- package/dist/utils/build-settings.js +133 -15
- package/dist/utils/canonical-json.d.ts +3 -5
- package/dist/utils/canonical-json.js +4 -5
- package/dist/utils/cast-transport.d.ts +5 -17
- package/dist/utils/cast-transport.js +6 -19
- package/dist/utils/claim-token.js +1 -2
- package/dist/utils/comment-avatars.js +4 -6
- package/dist/utils/concurrency.js +3 -3
- package/dist/utils/content-type.d.ts +7 -6
- package/dist/utils/content-type.js +73 -51
- package/dist/utils/credential-policy.d.ts +50 -6
- package/dist/utils/credential-policy.js +126 -59
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/error-code.js +1 -0
- package/dist/utils/generate-space-name.d.ts +3 -8
- package/dist/utils/generate-space-name.js +3 -8
- package/dist/utils/grant-decision.fixtures.json +2 -2
- package/dist/utils/grants.d.ts +0 -1
- package/dist/utils/grants.js +7 -8
- package/dist/utils/gravatar.d.ts +2 -4
- package/dist/utils/gravatar.js +6 -12
- package/dist/utils/id-hints.js +14 -23
- package/dist/utils/idempotency.d.ts +3 -3
- package/dist/utils/idempotency.js +6 -7
- package/dist/utils/local-space-state.d.ts +23 -26
- package/dist/utils/local-space-state.js +46 -46
- package/dist/utils/oauth-signed-query.d.ts +3 -5
- package/dist/utils/oauth-signed-query.js +6 -9
- package/dist/utils/one-shot-replay.d.ts +7 -6
- package/dist/utils/one-shot-replay.js +3 -5
- package/dist/utils/page-colors.d.ts +15 -19
- package/dist/utils/page-colors.js +23 -45
- package/dist/utils/page-fonts.d.ts +1 -1
- package/dist/utils/page-fonts.js +8 -20
- package/dist/utils/page-preview.d.ts +0 -1
- package/dist/utils/page-preview.js +0 -1
- package/dist/utils/pages.d.ts +33 -3
- package/dist/utils/pages.js +83 -30
- package/dist/utils/privacy-regions.d.ts +19 -0
- package/dist/utils/privacy-regions.js +91 -0
- package/dist/utils/publish-form-data.d.ts +4 -5
- package/dist/utils/publish-form-data.js +2 -3
- package/dist/utils/publish-policy.d.ts +11 -17
- package/dist/utils/publish-policy.fixtures.json +11 -5
- package/dist/utils/publish-policy.js +65 -110
- package/dist/utils/query-client.d.ts +2 -0
- package/dist/utils/query-client.js +10 -1
- package/dist/utils/runtime-paths.d.ts +4 -4
- package/dist/utils/runtime-paths.js +6 -8
- package/dist/utils/runtime-upload.d.ts +9 -0
- package/dist/utils/runtime-upload.js +12 -0
- package/dist/utils/secure-local-file.d.ts +3 -3
- package/dist/utils/secure-local-file.js +23 -40
- package/dist/utils/space-config.d.ts +12 -15
- package/dist/utils/space-config.js +13 -16
- package/dist/utils/static-runtime-policy.d.ts +10 -7
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
- package/dist/utils/static-runtime-policy.generated.js +70 -0
- package/dist/utils/static-runtime-policy.js +31 -132
- package/dist/utils/storage-policy.js +1 -2
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +41 -19
- package/dist/utils/version-path.js +11 -19
- package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
- package/dist/utils/wpcom-auth-redirects.js +1 -4
- package/dist/vocabulary.d.ts +13 -13
- package/dist/vocabulary.js +52 -27
- package/package.json +15 -18
- package/dist/agents/client-registry.d.ts +0 -22
- package/dist/agents/client-registry.js +0 -41
- package/dist/docs/agent-handoff-document.d.ts +0 -15
- package/dist/docs/agent-handoff-document.js +0 -180
- package/dist/docs/agent-solutions.d.ts +0 -110
- package/dist/docs/agent-solutions.js +0 -183
- package/dist/utils/query-keys.d.ts +0 -84
- package/dist/utils/query-keys.js +0 -108
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { serviceOrigin } from "../config/domains.js";
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeSlug } from "../utils/slug.js";
|
|
4
|
+
import { defaultPlanNameSchema, planCatalogSchema } from "./commerce.js";
|
|
5
|
+
import { GRANT_RESOURCE_PATTERN_LIMIT, identityConnectionPublicKeySchema, normalizeGrantResourcePattern, } from "./grants.js";
|
|
4
6
|
export const SPACE_CONFIG_CANONICAL_FILE = "sf.jsonc";
|
|
5
7
|
export const SPACE_CONFIG_ALIAS_FILES = [
|
|
6
8
|
"spacefast.jsonc",
|
|
@@ -15,18 +17,15 @@ export const SPACE_CONFIG_ACCEPTED_FILES = [
|
|
|
15
17
|
...SPACE_CONFIG_ALIAS_FILES,
|
|
16
18
|
];
|
|
17
19
|
/**
|
|
18
|
-
* Accepted config filenames in read precedence order, highest first.
|
|
19
|
-
* step with the finalizer's `
|
|
20
|
-
* protocol.rs)
|
|
21
|
-
* read choosing a different file than the publish serves is a split brain.
|
|
20
|
+
* Accepted config filenames in read precedence order, highest first. Must stay
|
|
21
|
+
* in step with the finalizer's `CONFIG_ACCEPTED_FILES`
|
|
22
|
+
* (crates/stattic-runtime-core protocol.rs), which is the selection authority.
|
|
22
23
|
*/
|
|
23
24
|
export const SPACE_CONFIG_PRECEDENCE = SPACE_CONFIG_ACCEPTED_FILES;
|
|
24
25
|
export const SPACE_CONFIG_PRIVATE_FILES = [...SPACE_CONFIG_ACCEPTED_FILES];
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// validation for the API `config` overlay. Plan policy may LOWER these caps,
|
|
29
|
-
// never raise them.
|
|
26
|
+
// Parser-safety caps, enforced by ONE shared validator at every intake seam:
|
|
27
|
+
// finalize for the `sf.jsonc` file, request validation for the API `config`
|
|
28
|
+
// overlay. Plan policy may LOWER these caps, never raise them.
|
|
30
29
|
export const SPACE_CONFIG_CAPS = {
|
|
31
30
|
/** Max raw `sf.jsonc` size in bytes. */
|
|
32
31
|
fileBytes: 256 * 1024,
|
|
@@ -52,13 +51,52 @@ export const SPACE_CONFIG_CAPS = {
|
|
|
52
51
|
themeNameChars: 120,
|
|
53
52
|
/** Max top-level `name` (space display name) length. Matches the stored column. */
|
|
54
53
|
nameChars: 255,
|
|
54
|
+
/** Max `crons` entries. One provider crontab slot each, so the cap is small and hard. */
|
|
55
|
+
crons: 8,
|
|
56
|
+
/** Max `crons[].path` length. */
|
|
57
|
+
cronPathChars: 512,
|
|
58
|
+
// `system` manifest (Partner API): the control-plane-only section a tenant's
|
|
59
|
+
// designated system space publishes. Every string is bounded and every URL is
|
|
60
|
+
// https; these caps are the enforcing authority (the Rust crate only passes
|
|
61
|
+
// the section through — it never validates it).
|
|
62
|
+
/** Max `system.presentation.name` display-name length. */
|
|
63
|
+
systemNameChars: 255,
|
|
64
|
+
/** Max length of any https URL or origin in the `system` manifest. */
|
|
65
|
+
systemUrlChars: 2048,
|
|
66
|
+
/** Max `system.hostnames.apex` length (a DNS name never exceeds 253). */
|
|
67
|
+
systemHostnameChars: 253,
|
|
68
|
+
/** Max `system.presentation.contacts` entries. */
|
|
69
|
+
systemContacts: 32,
|
|
70
|
+
/** Max length of a `system.presentation.contacts` key. */
|
|
71
|
+
systemContactKeyChars: 64,
|
|
72
|
+
/** Max length of a `system.presentation.contacts` value. */
|
|
73
|
+
systemContactValueChars: 2048,
|
|
74
|
+
/** Max `system.api.allowedOrigins` entries. */
|
|
75
|
+
systemApiAllowedOrigins: 32,
|
|
76
|
+
/** Max `system.tokenIssuers` entries. */
|
|
77
|
+
systemTokenIssuers: 16,
|
|
78
|
+
/** Max `system.tokenIssuers[].issuer` length. */
|
|
79
|
+
systemIssuerChars: 512,
|
|
80
|
+
/** Max `system.tokenIssuers[].keys` entries. */
|
|
81
|
+
systemIssuerKeys: 16,
|
|
82
|
+
/** Max `system.tokenIssuers[].keys[].kid` length. */
|
|
83
|
+
systemKeyIdChars: 64,
|
|
84
|
+
/** Exact raw Ed25519 public-key length after base64url encoding. */
|
|
85
|
+
systemPublicKeyChars: 43,
|
|
86
|
+
/** Max `system.tokenIssuers[].proof` length. */
|
|
87
|
+
systemProofChars: 4096,
|
|
88
|
+
/** Max `system.subscriptions` entries. */
|
|
89
|
+
systemSubscriptions: 32,
|
|
90
|
+
/** Max `system.subscriptions[].events` entries. */
|
|
91
|
+
systemSubscriptionEvents: 64,
|
|
92
|
+
/** Max length of a `system.subscriptions[].events` name. */
|
|
93
|
+
systemEventChars: 128,
|
|
55
94
|
};
|
|
56
95
|
/** Published JSON Schema URL for `sf.jsonc` ($schema autocomplete). */
|
|
57
96
|
export const SPACE_CONFIG_SCHEMA_URL = `${serviceOrigin("www", "prod")}/schemas/sf.json`;
|
|
58
|
-
// Pages theme tokens:
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// named), categorically rejecting anything that could escape a style context.
|
|
97
|
+
// Pages theme tokens: presentation only, never layout, behavior, or protocol.
|
|
98
|
+
// The color grammar accepts hex, rgb(a), hsl(a), and named CSS colors, and
|
|
99
|
+
// rejects anything that could escape a style context.
|
|
62
100
|
const PAGES_COLOR_PATTERN = new RegExp([
|
|
63
101
|
"^(?:",
|
|
64
102
|
"#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})",
|
|
@@ -113,10 +151,7 @@ export const spacePageThemeSchema = z
|
|
|
113
151
|
.describe("Theme tokens for Spacefast-authored pages.");
|
|
114
152
|
// WordPress-compatible `theme.json` is an inference input, not configuration.
|
|
115
153
|
// Only `settings.color.palette` and `settings.typography.fontFamilies` infer
|
|
116
|
-
// Pages tokens; every other
|
|
117
|
-
// WP theme.json key is accepted-and-ignored (this is not a full WP_Theme_JSON
|
|
118
|
-
// reimplementation, just the safe subset that reaches presentation-only gate
|
|
119
|
-
// pages.
|
|
154
|
+
// Pages tokens; every other WP key is accepted and ignored.
|
|
120
155
|
export const THEME_JSON_FILE = "theme.json";
|
|
121
156
|
export const spaceBuildSettingsSchema = z
|
|
122
157
|
.object({
|
|
@@ -134,9 +169,8 @@ export const spaceBuildSettingsSchema = z
|
|
|
134
169
|
.strict()
|
|
135
170
|
.describe("Remote build settings used by repository builds.");
|
|
136
171
|
// Every space gets its own site, so there is nothing to place between: no mode,
|
|
137
|
-
// no region knob. `burstable`
|
|
138
|
-
//
|
|
139
|
-
// creation, and never changes.
|
|
172
|
+
// no region knob. `burstable` says whether the space's site may burst provider
|
|
173
|
+
// PHP workers. Data location is chosen once, at creation, and never changes.
|
|
140
174
|
export const spacePlacementConfigSchema = z
|
|
141
175
|
.object({
|
|
142
176
|
burstable: z.boolean().optional(),
|
|
@@ -350,11 +384,9 @@ function refineDefaultDataSource(config, ctx) {
|
|
|
350
384
|
});
|
|
351
385
|
}
|
|
352
386
|
}
|
|
353
|
-
// Shared SpaceConfig fields
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
// Meta caps are the spec parser-safety numbers (title 300, description 1000,
|
|
357
|
-
// image 2000); plan policy may lower them but never raise them.
|
|
387
|
+
// Shared SpaceConfig fields: the declarative serving config also accepted by
|
|
388
|
+
// `sf.jsonc`. Cloud-only overlay fields such as pre-build data sources are
|
|
389
|
+
// added to `spaceConfigSchema` below.
|
|
358
390
|
const spaceConfigBaseSchema = z.object({
|
|
359
391
|
index: z
|
|
360
392
|
.union([z.string().min(1), z.literal(false)])
|
|
@@ -452,13 +484,12 @@ const spaceConfigBaseSchema = z.object({
|
|
|
452
484
|
.boolean()
|
|
453
485
|
.optional()
|
|
454
486
|
.describe("Accepted serving flag; runtime enforcement ships separately."),
|
|
455
|
-
//
|
|
456
|
-
// build pipeline
|
|
457
|
-
// diagnostics; absent/false keeps today's behavior byte-for-byte.
|
|
487
|
+
// Undescribed on purpose (hidden, experimental): enables the finalize-time
|
|
488
|
+
// build pipeline. Accepted without diagnostics.
|
|
458
489
|
experimental_gutenberg: z.boolean().optional(),
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
490
|
+
// Hidden, experimental: static HTML snippets, honored only when
|
|
491
|
+
// `experimental_gutenberg` is true. Byte caps are enforced by the shared
|
|
492
|
+
// validator (inject_snippet_too_large / inject_invalid).
|
|
462
493
|
inject: z
|
|
463
494
|
.object({
|
|
464
495
|
head: z.array(z.string()).max(SPACE_CONFIG_CAPS.injectSnippets).optional(),
|
|
@@ -493,12 +524,10 @@ export function spaceSuperpowersEnabled(config) {
|
|
|
493
524
|
const superpowers = config?.superpowers;
|
|
494
525
|
return superpowers?.enabled !== false && superpowers?.disable_all_injection !== true;
|
|
495
526
|
}
|
|
496
|
-
// The `sf.jsonc` FILE shape
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
// to the routing compiler, not to this shape: it resolves serving, they compile
|
|
501
|
-
// into the canonical rules and merge behind `_redirects`/`_headers`.
|
|
527
|
+
// The `sf.jsonc` FILE shape: SpaceConfig plus the editor `$schema` pointer and
|
|
528
|
+
// the optional `space` link (slug or `spc_` id). Routing rules
|
|
529
|
+
// (`redirects`/`rewrites`/`headers`) are sf.jsonc keys too, but they belong to
|
|
530
|
+
// the routing compiler and merge behind `_redirects`/`_headers`.
|
|
502
531
|
const spaceConfigPublicPatternSchema = z
|
|
503
532
|
.string()
|
|
504
533
|
.min(1)
|
|
@@ -560,10 +589,10 @@ export const spaceConfigFileAccessSchema = z
|
|
|
560
589
|
/**
|
|
561
590
|
* Zero: a transactional app with its own database.
|
|
562
591
|
*
|
|
563
|
-
* Both entries are required because Zero is never inferred
|
|
564
|
-
* a database must not spring into existence from a directory shape
|
|
565
|
-
*
|
|
566
|
-
*
|
|
592
|
+
* Both entries are required because Zero is never inferred: a runtime that owns
|
|
593
|
+
* a database must not spring into existence from a directory shape. Once
|
|
594
|
+
* declared, a server or client that cannot be resolved fails the publish rather
|
|
595
|
+
* than degrading to static files.
|
|
567
596
|
*/
|
|
568
597
|
export const spaceRuntimeZeroSchema = z
|
|
569
598
|
.object({
|
|
@@ -584,10 +613,8 @@ export const spaceRuntimeZeroSchema = z
|
|
|
584
613
|
* Functions: the worker `sf publish` compiles.
|
|
585
614
|
*
|
|
586
615
|
* Every field beyond `kind` is optional because the normal case is detection: a
|
|
587
|
-
* project with `handler.ts` or an OpenNext build needs no configuration
|
|
588
|
-
*
|
|
589
|
-
* unusual, or a compatibility date the author wants pinned rather than
|
|
590
|
-
* defaulted.
|
|
616
|
+
* project with `handler.ts` or an OpenNext build needs no configuration. These
|
|
617
|
+
* exist for the layouts detection cannot guess.
|
|
591
618
|
*/
|
|
592
619
|
export const spaceRuntimeFunctionsSchema = z
|
|
593
620
|
.object({
|
|
@@ -596,30 +623,313 @@ export const spaceRuntimeFunctionsSchema = z
|
|
|
596
623
|
.string()
|
|
597
624
|
.optional()
|
|
598
625
|
.describe("Path to the worker entry module, relative to the published directory. " +
|
|
599
|
-
"When absent, the server detects it automatically. Naming an entry that does not exist is an error, not a silent
|
|
626
|
+
"When absent, the server detects it automatically. Naming an entry that does not exist is an error, not a silent file-only publish."),
|
|
600
627
|
database: z
|
|
601
628
|
.boolean()
|
|
602
629
|
.default(false)
|
|
603
630
|
.describe("Give the worker `env.DB`, a D1-shaped binding to this space's database. " +
|
|
604
|
-
"
|
|
631
|
+
"Declared, not detected: a capability an author did not request is authority they cannot see. Defaults to off."),
|
|
632
|
+
fetch: z
|
|
633
|
+
.boolean()
|
|
634
|
+
.optional()
|
|
635
|
+
.describe("Give the worker `ctx.fetch`, brokered outbound HTTP through the relay. " +
|
|
636
|
+
"Declared, not detected, like `database`. There is no schema default: a Next build gets egress unless this says false, because SSR fetches while rendering, while a hand-written worker reaches nothing until it says so."),
|
|
605
637
|
compatibilityDate: z
|
|
606
638
|
.string()
|
|
607
639
|
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
608
640
|
.optional()
|
|
609
|
-
.describe("Runtime compatibility date this worker was written against. Pinning it
|
|
641
|
+
.describe("Runtime compatibility date this worker was written against. Pinning it means promoting an old version restores the semantics it was built with."),
|
|
610
642
|
})
|
|
611
643
|
.describe("Functions: a worker compiled at publish time.");
|
|
612
644
|
/**
|
|
613
|
-
* The one runtime declaration, discriminated on `kind`.
|
|
645
|
+
* The one runtime declaration, discriminated on `kind`. A version cannot
|
|
646
|
+
* express two runtimes at once.
|
|
614
647
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
* back with the version instead of being set through the API config overlay.
|
|
648
|
+
* File-only on purpose: a runtime is a fact about the content of a version, so
|
|
649
|
+
* it rolls forward and back with the version instead of being set through the
|
|
650
|
+
* API config overlay.
|
|
619
651
|
*/
|
|
620
652
|
export const spaceRuntimeSchema = z
|
|
621
653
|
.discriminatedUnion("kind", [spaceRuntimeZeroSchema, spaceRuntimeFunctionsSchema])
|
|
622
|
-
.describe("Runtime this version's code runs on. Omit it
|
|
654
|
+
.describe("Runtime this version's code runs on. Omit it when the space does not run code. Functions is also detected from the published directory when it is not stated.");
|
|
655
|
+
/**
|
|
656
|
+
* Scheduled requests: the provider crontab asks this space for these paths on
|
|
657
|
+
* these schedules. File-only for the same reason `runtime` is.
|
|
658
|
+
*
|
|
659
|
+
* The Rust config compiler is the selection authority for both fields; this
|
|
660
|
+
* schema exists so an editor and an agent get the same answer before publish.
|
|
661
|
+
*/
|
|
662
|
+
export const SPACE_CRON_NAMED_SCHEDULES = ["hourly", "daily", "twicedaily", "weekly"];
|
|
663
|
+
/** Field bounds, in crontab order: minute, hour, day of month, month, weekday. */
|
|
664
|
+
const CRON_FIELD_BOUNDS = [
|
|
665
|
+
[0, 59],
|
|
666
|
+
[0, 23],
|
|
667
|
+
[1, 31],
|
|
668
|
+
[1, 12],
|
|
669
|
+
[0, 7],
|
|
670
|
+
];
|
|
671
|
+
/** How many times per hour, day or week the `Nh`/`Nd`/`Nw` shorthand may ask. */
|
|
672
|
+
const CRON_SHORTHAND_LIMITS = new Map([
|
|
673
|
+
["h", 12],
|
|
674
|
+
["d", 23],
|
|
675
|
+
["w", 6],
|
|
676
|
+
]);
|
|
677
|
+
const CRON_STEP_MAX = 0xffff_ffff;
|
|
678
|
+
function cronNumberInBounds(value, min, max) {
|
|
679
|
+
if (!/^\d+$/.test(value))
|
|
680
|
+
return false;
|
|
681
|
+
const parsed = Number(value);
|
|
682
|
+
return parsed >= min && parsed <= max;
|
|
683
|
+
}
|
|
684
|
+
function cronFieldIsValid(field, [min, max]) {
|
|
685
|
+
if (field.length === 0)
|
|
686
|
+
return false;
|
|
687
|
+
return field.split(",").every((item) => {
|
|
688
|
+
const slash = item.indexOf("/");
|
|
689
|
+
const range = slash === -1 ? item : item.slice(0, slash);
|
|
690
|
+
const step = slash === -1 ? undefined : item.slice(slash + 1);
|
|
691
|
+
// A step only makes sense over a span, which is what `*` and `a-b` are;
|
|
692
|
+
// `5/2` names one value and then asks to skip within it.
|
|
693
|
+
let span;
|
|
694
|
+
if (range === "*") {
|
|
695
|
+
span = true;
|
|
696
|
+
}
|
|
697
|
+
else if (range.includes("-")) {
|
|
698
|
+
const [start, end, ...rest] = range.split("-");
|
|
699
|
+
if (rest.length > 0 ||
|
|
700
|
+
start === undefined ||
|
|
701
|
+
end === undefined ||
|
|
702
|
+
!cronNumberInBounds(start, min, max) ||
|
|
703
|
+
!cronNumberInBounds(end, min, max) ||
|
|
704
|
+
Number(start) > Number(end)) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
span = true;
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
if (!cronNumberInBounds(range, min, max))
|
|
711
|
+
return false;
|
|
712
|
+
span = false;
|
|
713
|
+
}
|
|
714
|
+
if (step === undefined)
|
|
715
|
+
return true;
|
|
716
|
+
const parsedStep = Number(step);
|
|
717
|
+
return (span &&
|
|
718
|
+
/^\d+$/.test(step) &&
|
|
719
|
+
Number.isInteger(parsedStep) &&
|
|
720
|
+
parsedStep >= 1 &&
|
|
721
|
+
parsedStep <= CRON_STEP_MAX);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* The wp.cloud schedule grammar: a named interval, an `Nh`/`Nd`/`Nw` count, or
|
|
726
|
+
* a five-field crontab expression. No `@daily` macros and no month/day names.
|
|
727
|
+
* The provider does not accept them either.
|
|
728
|
+
*/
|
|
729
|
+
export function isSpaceCronSchedule(schedule) {
|
|
730
|
+
if (SPACE_CRON_NAMED_SCHEDULES.some((named) => named === schedule))
|
|
731
|
+
return true;
|
|
732
|
+
const shorthand = /^(\d+)([hdw])$/.exec(schedule);
|
|
733
|
+
const limit = CRON_SHORTHAND_LIMITS.get(shorthand?.[2] ?? "");
|
|
734
|
+
if (shorthand?.[1] !== undefined && limit !== undefined) {
|
|
735
|
+
const count = Number(shorthand[1]);
|
|
736
|
+
return count >= 1 && count <= limit;
|
|
737
|
+
}
|
|
738
|
+
const fields = schedule.split(" ");
|
|
739
|
+
return (fields.length === CRON_FIELD_BOUNDS.length &&
|
|
740
|
+
fields.every((field, index) => {
|
|
741
|
+
const bounds = CRON_FIELD_BOUNDS[index];
|
|
742
|
+
return bounds !== undefined && cronFieldIsValid(field, bounds);
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* The artifact key for a cron path — the shared slug normalizer, so an accented
|
|
747
|
+
* path folds the same way it does everywhere else. Two paths that produce one
|
|
748
|
+
* key are rejected, so a key names exactly one schedule.
|
|
749
|
+
*/
|
|
750
|
+
export function spaceCronKey(path) {
|
|
751
|
+
return normalizeSlug(path);
|
|
752
|
+
}
|
|
753
|
+
/** Generated runtime file the serving engine reads, written only when a version declares a cron. */
|
|
754
|
+
export const SPACE_CRONS_ARTIFACT_PATH = "__spacefast/crons.json";
|
|
755
|
+
export const SPACE_CRONS_ARTIFACT_VERSION = 1;
|
|
756
|
+
export const spaceCronSchema = z.object({
|
|
757
|
+
path: z
|
|
758
|
+
.string()
|
|
759
|
+
.max(SPACE_CONFIG_CAPS.cronPathChars)
|
|
760
|
+
.regex(/^\/[^*:?#\s\\]*$/, "must be a literal path starting with / (no * splats, :placeholders, query or fragment)")
|
|
761
|
+
.refine((path) => spaceCronKey(path).length > 0, "must contain at least one letter or digit")
|
|
762
|
+
.describe("Path this space is asked for on the schedule."),
|
|
763
|
+
schedule: z
|
|
764
|
+
.string()
|
|
765
|
+
.refine(isSpaceCronSchedule, "must be hourly, daily, twicedaily, weekly, an Nh/Nd/Nw interval, or a five-field crontab expression")
|
|
766
|
+
.describe("hourly, daily, twicedaily, weekly, an Nh/Nd/Nw interval, or a five-field crontab expression."),
|
|
767
|
+
});
|
|
768
|
+
export const spaceCronsSchema = z
|
|
769
|
+
.array(spaceCronSchema)
|
|
770
|
+
.max(SPACE_CONFIG_CAPS.crons)
|
|
771
|
+
.superRefine((entries, context) => {
|
|
772
|
+
// Keying on the slug catches both defects: a path written twice, and two
|
|
773
|
+
// paths that slug to one key.
|
|
774
|
+
const seenByKey = new Map();
|
|
775
|
+
entries.forEach((entry, index) => {
|
|
776
|
+
const key = spaceCronKey(entry.path);
|
|
777
|
+
const at = seenByKey.get(key);
|
|
778
|
+
if (at !== undefined) {
|
|
779
|
+
// Name the exact duplicate the author wrote: the path when it repeats
|
|
780
|
+
// verbatim, the shared key when two distinct paths collide on it.
|
|
781
|
+
const collided = entries[at];
|
|
782
|
+
context.addIssue({
|
|
783
|
+
code: "custom",
|
|
784
|
+
path: [index, "path"],
|
|
785
|
+
message: `crons entry ${at} already declares ${collided?.path === entry.path ? entry.path : key}.`,
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
seenByKey.set(key, index);
|
|
790
|
+
});
|
|
791
|
+
})
|
|
792
|
+
.describe(`Scheduled requests Spacefast issues against this space's own paths; at most ${SPACE_CONFIG_CAPS.crons} entries.`);
|
|
793
|
+
/**
|
|
794
|
+
* The `system` manifest: the control-plane-only section a tenant's designated
|
|
795
|
+
* system space publishes to declare its partner-facing surface (presentation,
|
|
796
|
+
* white-label hostnames, API origins, trusted token issuers, event
|
|
797
|
+
* subscriptions).
|
|
798
|
+
*
|
|
799
|
+
* No serving behavior derives from it. The control plane freezes it at finalize
|
|
800
|
+
* and reconciles it into materialized state; other spaces' manifests are
|
|
801
|
+
* ignored. File-only, like `runtime` and `crons`: what a partner exposes is a
|
|
802
|
+
* fact about the live version, so it rolls forward and back with the version
|
|
803
|
+
* instead of living in the mutable API overlay.
|
|
804
|
+
*
|
|
805
|
+
* This zod contract is the validation authority: every string is bounded and
|
|
806
|
+
* every URL is https, with no HTML/CSS anywhere. The Rust config crate only
|
|
807
|
+
* recognizes the key and passes the section through verbatim — it never
|
|
808
|
+
* validates it and it never reaches the runtime effective-config lane.
|
|
809
|
+
*/
|
|
810
|
+
// `*` is excluded deliberately, not for hygiene: `system.api.allowedOrigins`
|
|
811
|
+
// feeds the CORS allow-list, whose matcher treats `*` as a wildcard. A partner
|
|
812
|
+
// could otherwise publish `https://*` and have the control plane reflect any
|
|
813
|
+
// requesting origin back with credentials.
|
|
814
|
+
const SYSTEM_HTTPS_URL_PATTERN = /^https:\/\/[^\s"'<>\\*]+$/;
|
|
815
|
+
const SYSTEM_HOSTNAME_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)+$/;
|
|
816
|
+
const systemHttpsUrlSchema = z
|
|
817
|
+
.string()
|
|
818
|
+
.trim()
|
|
819
|
+
.min(1)
|
|
820
|
+
.max(SPACE_CONFIG_CAPS.systemUrlChars)
|
|
821
|
+
.regex(SYSTEM_HTTPS_URL_PATTERN, "must be an https:// URL");
|
|
822
|
+
export const systemPresentationSchema = z
|
|
823
|
+
.object({
|
|
824
|
+
name: z.string().trim().min(1).max(SPACE_CONFIG_CAPS.systemNameChars).optional(),
|
|
825
|
+
logo: systemHttpsUrlSchema.optional(),
|
|
826
|
+
supportUrl: systemHttpsUrlSchema.optional(),
|
|
827
|
+
claimOrigin: systemHttpsUrlSchema.optional(),
|
|
828
|
+
accessHandoffOrigin: systemHttpsUrlSchema.optional(),
|
|
829
|
+
problemDocsBaseUrl: systemHttpsUrlSchema.optional(),
|
|
830
|
+
contacts: z
|
|
831
|
+
.record(z.string().trim().min(1).max(SPACE_CONFIG_CAPS.systemContactKeyChars), z.string().trim().min(1).max(SPACE_CONFIG_CAPS.systemContactValueChars))
|
|
832
|
+
.refine((contacts) => Object.keys(contacts).length <= SPACE_CONFIG_CAPS.systemContacts, `at most ${SPACE_CONFIG_CAPS.systemContacts} contacts`)
|
|
833
|
+
.optional()
|
|
834
|
+
.describe("Named contact URLs or addresses shown to claimants and partners."),
|
|
835
|
+
})
|
|
836
|
+
.strict()
|
|
837
|
+
.describe("Partner-facing branding and links for the white-label surface.");
|
|
838
|
+
export const systemHostnamesSchema = z
|
|
839
|
+
.object({
|
|
840
|
+
apex: z
|
|
841
|
+
.string()
|
|
842
|
+
.trim()
|
|
843
|
+
.min(1)
|
|
844
|
+
.max(SPACE_CONFIG_CAPS.systemHostnameChars)
|
|
845
|
+
.regex(SYSTEM_HOSTNAME_PATTERN, "must be a bare hostname (no scheme or path)")
|
|
846
|
+
.optional()
|
|
847
|
+
.describe("Apex hostname for the white-label surface."),
|
|
848
|
+
})
|
|
849
|
+
.strict();
|
|
850
|
+
export const systemApiSchema = z
|
|
851
|
+
.object({
|
|
852
|
+
origin: systemHttpsUrlSchema
|
|
853
|
+
.optional()
|
|
854
|
+
.describe("https origin the partner API is served from."),
|
|
855
|
+
allowedOrigins: z
|
|
856
|
+
.array(systemHttpsUrlSchema)
|
|
857
|
+
.max(SPACE_CONFIG_CAPS.systemApiAllowedOrigins)
|
|
858
|
+
.optional()
|
|
859
|
+
.describe("https origins permitted to call the partner API (CORS)."),
|
|
860
|
+
})
|
|
861
|
+
.strict();
|
|
862
|
+
export const systemTokenIssuerSchema = z
|
|
863
|
+
.object({
|
|
864
|
+
issuer: z
|
|
865
|
+
.string()
|
|
866
|
+
.trim()
|
|
867
|
+
.min(1)
|
|
868
|
+
.max(SPACE_CONFIG_CAPS.systemIssuerChars)
|
|
869
|
+
.describe("Issuer identifier (the iss claim tokens must carry)."),
|
|
870
|
+
keys: z
|
|
871
|
+
.array(identityConnectionPublicKeySchema)
|
|
872
|
+
.min(1)
|
|
873
|
+
.max(SPACE_CONFIG_CAPS.systemIssuerKeys)
|
|
874
|
+
.describe("Public verification keys, addressed by key id."),
|
|
875
|
+
proof: z
|
|
876
|
+
.string()
|
|
877
|
+
.trim()
|
|
878
|
+
.min(1)
|
|
879
|
+
.max(SPACE_CONFIG_CAPS.systemProofChars)
|
|
880
|
+
.optional()
|
|
881
|
+
.describe("Ownership proof that completes the two-publish issuer activation."),
|
|
882
|
+
})
|
|
883
|
+
.strict();
|
|
884
|
+
export const systemSubscriptionSchema = z
|
|
885
|
+
.object({
|
|
886
|
+
url: systemHttpsUrlSchema.describe("https endpoint events are delivered to."),
|
|
887
|
+
events: z
|
|
888
|
+
.array(z.string().trim().min(1).max(SPACE_CONFIG_CAPS.systemEventChars))
|
|
889
|
+
.min(1)
|
|
890
|
+
.max(SPACE_CONFIG_CAPS.systemSubscriptionEvents)
|
|
891
|
+
.describe("Event names delivered to this subscription."),
|
|
892
|
+
})
|
|
893
|
+
.strict();
|
|
894
|
+
export const spaceSystemManifestSchema = z
|
|
895
|
+
.object({
|
|
896
|
+
presentation: systemPresentationSchema.optional(),
|
|
897
|
+
hostnames: systemHostnamesSchema.optional(),
|
|
898
|
+
api: systemApiSchema.optional(),
|
|
899
|
+
tokenIssuers: z
|
|
900
|
+
.array(systemTokenIssuerSchema)
|
|
901
|
+
.max(SPACE_CONFIG_CAPS.systemTokenIssuers)
|
|
902
|
+
.optional()
|
|
903
|
+
.describe("Trusted token issuers registered for the tenant."),
|
|
904
|
+
subscriptions: z
|
|
905
|
+
.array(systemSubscriptionSchema)
|
|
906
|
+
.max(SPACE_CONFIG_CAPS.systemSubscriptions)
|
|
907
|
+
.optional()
|
|
908
|
+
.describe("Event subscriptions (webhooks) the tenant declares."),
|
|
909
|
+
plans: planCatalogSchema
|
|
910
|
+
.optional()
|
|
911
|
+
.describe("Per-tenant plan catalog: plan name → { features, quotas.spaces, quotaPolicy }. Feature keys validated against the live vocabulary; plan names are arbitrary."),
|
|
912
|
+
defaultPlan: defaultPlanNameSchema
|
|
913
|
+
.optional()
|
|
914
|
+
.describe("Plan unassigned principals fall back to. Must be a key in `plans`."),
|
|
915
|
+
})
|
|
916
|
+
.strict()
|
|
917
|
+
.superRefine((manifest, ctx) => {
|
|
918
|
+
if (manifest.defaultPlan === undefined) {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
// A defaultPlan is only meaningful against a declared catalog: it must name a
|
|
922
|
+
// plan the same manifest declares, or gate resolution would fall back to a
|
|
923
|
+
// plan that does not exist.
|
|
924
|
+
if (manifest.plans === undefined || !(manifest.defaultPlan in manifest.plans)) {
|
|
925
|
+
ctx.addIssue({
|
|
926
|
+
code: z.ZodIssueCode.custom,
|
|
927
|
+
path: ["defaultPlan"],
|
|
928
|
+
message: "defaultPlan must be one of the plan names declared in `plans`.",
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
})
|
|
932
|
+
.describe("Control-plane manifest for a tenant's designated system space. Parsed and reconciled by the control plane; ignored by other spaces.");
|
|
623
933
|
export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
|
|
624
934
|
$schema: z
|
|
625
935
|
.string()
|
|
@@ -641,6 +951,10 @@ export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
|
|
|
641
951
|
.describe("Display name for this space. While set, the API refuses to rename the space."),
|
|
642
952
|
access: spaceConfigFileAccessSchema.optional(),
|
|
643
953
|
runtime: spaceRuntimeSchema.optional(),
|
|
954
|
+
crons: spaceCronsSchema.optional(),
|
|
955
|
+
system: spaceSystemManifestSchema
|
|
956
|
+
.optional()
|
|
957
|
+
.describe("Control-plane system-space manifest. Honored only for a tenant's designated system space; other spaces' declarations are ignored."),
|
|
644
958
|
});
|
|
645
959
|
/**
|
|
646
960
|
* The published JSON Schema artifact for `sf.jsonc`, generated from the
|
|
@@ -653,11 +967,10 @@ export function spaceConfigJsonSchema() {
|
|
|
653
967
|
$id: SPACE_CONFIG_SCHEMA_URL,
|
|
654
968
|
title: "Spacefast space configuration (sf.jsonc)",
|
|
655
969
|
});
|
|
656
|
-
// `redirects`, `rewrites` and `headers` are real sf.jsonc keys
|
|
657
|
-
// compiler
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
// examples read as unknown keys.
|
|
970
|
+
// `redirects`, `rewrites` and `headers` are real sf.jsonc keys owned by the
|
|
971
|
+
// routing compiler, not by the serving shape above, so they are described
|
|
972
|
+
// here. Without them an editor reads the documented routing examples as
|
|
973
|
+
// unknown keys.
|
|
661
974
|
return {
|
|
662
975
|
...schema,
|
|
663
976
|
properties: {
|
|
@@ -666,6 +979,19 @@ export function spaceConfigJsonSchema() {
|
|
|
666
979
|
},
|
|
667
980
|
};
|
|
668
981
|
}
|
|
982
|
+
// The `!` and `name=:capture` of a `_redirects` line, shared by both rule
|
|
983
|
+
// sections and by `config::strict::public_json_schema`.
|
|
984
|
+
const SPACE_CONFIG_ROUTING_FORCE_SCHEMA = {
|
|
985
|
+
type: "boolean",
|
|
986
|
+
default: false,
|
|
987
|
+
description: "Answer even when the request path resolves to a committed file, the ! of a _redirects rule.",
|
|
988
|
+
};
|
|
989
|
+
const SPACE_CONFIG_ROUTING_QUERY_SCHEMA = {
|
|
990
|
+
type: "object",
|
|
991
|
+
description: 'Query parameters the request must carry, as parameter name to capture name: { "id": "id" } is the name=:capture token id=:id.',
|
|
992
|
+
propertyNames: { pattern: "^[A-Za-z][A-Za-z0-9_-]*$" },
|
|
993
|
+
additionalProperties: { type: "string", pattern: "^[A-Za-z][A-Za-z0-9_]*$" },
|
|
994
|
+
};
|
|
669
995
|
const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
670
996
|
redirects: {
|
|
671
997
|
type: "array",
|
|
@@ -688,6 +1014,8 @@ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
|
688
1014
|
default: 302,
|
|
689
1015
|
description: "Redirect status. To serve other content under this URL, use rewrites.",
|
|
690
1016
|
},
|
|
1017
|
+
force: SPACE_CONFIG_ROUTING_FORCE_SCHEMA,
|
|
1018
|
+
query: SPACE_CONFIG_ROUTING_QUERY_SCHEMA,
|
|
691
1019
|
},
|
|
692
1020
|
required: ["source", "destination"],
|
|
693
1021
|
additionalProperties: false,
|
|
@@ -713,6 +1041,13 @@ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
|
713
1041
|
const: "shared",
|
|
714
1042
|
description: "Let shared caches store the proxy response. Absolute-URL destinations only.",
|
|
715
1043
|
},
|
|
1044
|
+
force: SPACE_CONFIG_ROUTING_FORCE_SCHEMA,
|
|
1045
|
+
query: SPACE_CONFIG_ROUTING_QUERY_SCHEMA,
|
|
1046
|
+
notFound: {
|
|
1047
|
+
type: "boolean",
|
|
1048
|
+
default: false,
|
|
1049
|
+
description: "Serve the destination with status 404, the 404 of a _redirects rule. The destination must be a path.",
|
|
1050
|
+
},
|
|
716
1051
|
},
|
|
717
1052
|
required: ["source", "destination"],
|
|
718
1053
|
additionalProperties: false,
|
|
@@ -720,7 +1055,7 @@ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
|
720
1055
|
},
|
|
721
1056
|
headers: {
|
|
722
1057
|
type: "array",
|
|
723
|
-
description: "Response headers to set on matching requests. A _headers rule wins on the same header name.",
|
|
1058
|
+
description: "Response headers to set or remove on matching requests. A _headers rule wins on the same header name.",
|
|
724
1059
|
items: {
|
|
725
1060
|
type: "object",
|
|
726
1061
|
properties: {
|
|
@@ -729,13 +1064,29 @@ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
|
729
1064
|
type: "array",
|
|
730
1065
|
minItems: 1,
|
|
731
1066
|
items: {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1067
|
+
oneOf: [
|
|
1068
|
+
{
|
|
1069
|
+
type: "object",
|
|
1070
|
+
properties: {
|
|
1071
|
+
key: { type: "string", minLength: 1 },
|
|
1072
|
+
value: { type: "string" },
|
|
1073
|
+
},
|
|
1074
|
+
required: ["key", "value"],
|
|
1075
|
+
additionalProperties: false,
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
type: "object",
|
|
1079
|
+
properties: {
|
|
1080
|
+
key: { type: "string", minLength: 1 },
|
|
1081
|
+
remove: {
|
|
1082
|
+
const: true,
|
|
1083
|
+
description: "Remove this header instead of setting it, the !Name line of a _headers block.",
|
|
1084
|
+
},
|
|
1085
|
+
},
|
|
1086
|
+
required: ["key", "remove"],
|
|
1087
|
+
additionalProperties: false,
|
|
1088
|
+
},
|
|
1089
|
+
],
|
|
739
1090
|
},
|
|
740
1091
|
},
|
|
741
1092
|
},
|
|
@@ -745,16 +1096,15 @@ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
|
|
|
745
1096
|
},
|
|
746
1097
|
};
|
|
747
1098
|
/*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
* static-placement shape so unrelated properties of the same name survive.
|
|
1099
|
+
* `experimental_gutenberg` and its `inject` companion are ACCEPTED by the parser
|
|
1100
|
+
* but never published: the bare key names alone would document the feature to
|
|
1101
|
+
* every editor and agent reading a schema artifact. Every generator that derives
|
|
1102
|
+
* a public artifact from the SpaceConfig zod shape (the sf.jsonc JSON Schema,
|
|
1103
|
+
* the www publish-spec, the SDK OpenAPI documents) must run it through this
|
|
1104
|
+
* stripper. The published schemas set no additionalProperties constraint, so
|
|
1105
|
+
* configs using the hidden keys still validate. `inject` is removed only when it
|
|
1106
|
+
* carries the SpaceConfig static-placement shape, so unrelated properties of the
|
|
1107
|
+
* same name survive.
|
|
758
1108
|
*/
|
|
759
1109
|
export function stripHiddenSpaceConfigKeys(schema) {
|
|
760
1110
|
if (Array.isArray(schema)) {
|
|
@@ -769,14 +1119,14 @@ export function stripHiddenSpaceConfigKeys(schema) {
|
|
|
769
1119
|
if (name === "experimental_gutenberg") {
|
|
770
1120
|
return false;
|
|
771
1121
|
}
|
|
772
|
-
return !(name === "inject" &&
|
|
1122
|
+
return !(name === "inject" && isHiddenInjectSchema(value));
|
|
773
1123
|
}));
|
|
774
1124
|
return [key, stripHiddenSpaceConfigKeys(kept)];
|
|
775
1125
|
}
|
|
776
1126
|
return [key, stripHiddenSpaceConfigKeys(entry)];
|
|
777
1127
|
}));
|
|
778
1128
|
}
|
|
779
|
-
function
|
|
1129
|
+
function isHiddenInjectSchema(value) {
|
|
780
1130
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
781
1131
|
return false;
|
|
782
1132
|
}
|