@spacefast/common 0.0.11 → 0.0.12
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/dist/agents/connect-targets.d.ts +4 -4
- package/dist/agents/connect-targets.js +3 -3
- package/dist/agents/private-key-oauth.d.ts +37 -0
- package/dist/agents/private-key-oauth.js +158 -0
- package/dist/config/domains.js +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +1 -0
- package/dist/config/postgres-budget.d.ts +126 -0
- package/dist/config/postgres-budget.js +135 -0
- package/dist/config/postgres-budget.json +50 -0
- package/dist/contracts/access.d.ts +42 -24
- package/dist/contracts/access.js +73 -34
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +5 -5
- package/dist/contracts/api-keys.d.ts +53 -45
- package/dist/contracts/api-keys.js +1 -0
- package/dist/contracts/archives.d.ts +8 -8
- package/dist/contracts/builds.d.ts +8 -8
- package/dist/contracts/builds.js +28 -48
- package/dist/contracts/channels.js +1 -2
- package/dist/contracts/comments.d.ts +55 -19
- package/dist/contracts/comments.js +56 -17
- package/dist/contracts/common.d.ts +4 -3
- package/dist/contracts/common.js +2 -0
- package/dist/contracts/deployments.d.ts +3 -3
- package/dist/contracts/device-auth.d.ts +14 -12
- package/dist/contracts/docs.d.ts +5 -5
- package/dist/contracts/domains.d.ts +261 -1330
- package/dist/contracts/domains.js +39 -301
- package/dist/contracts/email-preferences.d.ts +50 -0
- package/dist/contracts/email-preferences.js +44 -0
- package/dist/contracts/enums.d.ts +1 -1
- package/dist/contracts/error-code-meta.d.ts +84 -15
- package/dist/contracts/error-code-meta.js +28 -4
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +24 -6
- package/dist/contracts/events.d.ts +13 -13
- package/dist/contracts/feature-lifecycle.d.ts +644 -2
- package/dist/contracts/feature-lifecycle.js +685 -2
- package/dist/contracts/features.d.ts +236 -24
- package/dist/contracts/features.js +3 -272
- package/dist/contracts/ids.d.ts +1 -2
- package/dist/contracts/ids.js +1 -2
- package/dist/contracts/internal.d.ts +3 -3
- package/dist/contracts/mcp.d.ts +6 -6
- package/dist/contracts/me.js +1 -1
- package/dist/contracts/mounts.d.ts +142 -0
- package/dist/contracts/mounts.js +56 -0
- package/dist/contracts/oauth-resources.d.ts +5 -5
- package/dist/contracts/oauth-resources.js +1 -0
- package/dist/contracts/operations.d.ts +2 -2
- package/dist/contracts/pages.d.ts +2 -2
- package/dist/contracts/pages.js +8 -3
- package/dist/contracts/plan-policy.d.ts +1 -1
- package/dist/contracts/platform.d.ts +3 -3
- package/dist/contracts/resources.d.ts +12 -20
- package/dist/contracts/resources.js +13 -37
- package/dist/contracts/runtime-api.d.ts +330 -16
- package/dist/contracts/runtime-api.js +58 -14
- package/dist/contracts/sf-config-v1.js +1 -1
- package/dist/contracts/sites.d.ts +12 -12
- package/dist/contracts/sites.js +18 -11
- package/dist/contracts/space-config.d.ts +2 -3
- package/dist/contracts/space-config.js +3 -1
- package/dist/contracts/spaces.d.ts +143 -102
- package/dist/contracts/spaces.js +109 -43
- package/dist/contracts/superadmin-emails.d.ts +25 -1
- package/dist/contracts/superadmin-emails.js +10 -15
- package/dist/contracts/superadmin-runtime.d.ts +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +6 -14
- package/dist/contracts/tags.d.ts +43 -13
- package/dist/contracts/tags.js +195 -11
- package/dist/contracts/transfers.d.ts +1 -1
- package/dist/contracts/variables.d.ts +4 -4
- package/dist/contracts/variables.js +4 -3
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +329 -7
- package/dist/contracts/zero.js +121 -2
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/docs/agent-setup.d.ts +4 -2
- package/dist/docs/agent-setup.js +15 -7
- package/dist/docs/agent-solutions.js +1 -1
- package/dist/docs/catalog.d.ts +8 -10
- package/dist/docs/catalog.js +8 -10
- package/dist/docs/error-docs.js +86 -18
- package/dist/utils/access-grant.d.ts +14 -0
- package/dist/utils/access-grant.js +88 -0
- package/dist/utils/access-match.d.ts +7 -0
- package/dist/utils/access-match.fixtures.json +513 -0
- package/dist/utils/access-match.js +32 -16
- package/dist/utils/access-verdict.fixtures.json +421 -0
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/dns-instructions.d.ts +29 -0
- package/dist/utils/dns-instructions.js +78 -0
- package/dist/utils/domain-diagnostics.d.ts +29 -0
- package/dist/utils/domain-diagnostics.js +63 -0
- package/dist/utils/egress-policy.fixtures.json +51 -0
- package/dist/utils/id-hints.d.ts +1 -0
- package/dist/utils/id-hints.js +55 -13
- package/dist/utils/page-colors.d.ts +50 -0
- package/dist/utils/page-colors.js +167 -0
- package/dist/utils/page-fonts.d.ts +2 -0
- package/dist/utils/page-fonts.js +4 -0
- package/dist/utils/pages.d.ts +8 -1
- package/dist/utils/pages.js +123 -37
- package/dist/utils/production-runtime.d.ts +18 -0
- package/dist/utils/production-runtime.js +28 -0
- package/dist/utils/query-keys.d.ts +6 -7
- package/dist/utils/query-keys.js +11 -12
- package/dist/utils/sf-config-v1.fixtures.json +9 -0
- package/dist/utils/sf-config-v1.js +28 -19
- package/dist/utils/source-archive-policy.d.ts +2 -0
- package/dist/utils/source-archive-policy.js +2 -0
- package/dist/utils/static-runtime-policy.fixtures.json +57 -0
- package/dist/utils/static-runtime-policy.js +1 -1
- package/package.json +5 -4
- package/dist/contracts/annotations.d.ts +0 -327
- package/dist/contracts/annotations.js +0 -235
- package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
- package/dist/contracts/generated-feature-launch-entries.js +0 -518
- package/dist/contracts/publishes.d.ts +0 -135
- package/dist/contracts/publishes.js +0 -162
package/dist/docs/agent-setup.js
CHANGED
|
@@ -47,10 +47,12 @@ function isSafeHttpUrl(url) {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* The single shared grammar for handoff links:
|
|
50
|
-
*
|
|
50
|
+
* `<app-origin>/agent/<documentId>#<secret>`. The document ID must match
|
|
51
51
|
* `[A-Za-z0-9_-]{1,512}` and the secret `[A-Za-z0-9_-]{16,512}` (the minimum keeps secrets
|
|
52
52
|
* high-entropy and makes the public-projection leak check meaningful); the two must differ;
|
|
53
|
-
* queries and embedded credentials are forbidden
|
|
53
|
+
* queries and embedded credentials are forbidden. The URL must sit exactly on the caller's
|
|
54
|
+
* configured HTTP(S) app origin; that origin pin enforces scheme, hostname, and port without a
|
|
55
|
+
* second host allowlist that would reject legitimate dev or container origins.
|
|
54
56
|
* Normalization is part of the contract: the input is WHATWG-URL-normalized first (backslashes,
|
|
55
57
|
* dot segments, stray tab/CR/LF, empty userinfo, case, default ports), the grammar is enforced on
|
|
56
58
|
* the normalized URL, and only the reconstructed canonical URL ever leaves this function.
|
|
@@ -58,13 +60,17 @@ function isSafeHttpUrl(url) {
|
|
|
58
60
|
* grammar can never fork.
|
|
59
61
|
*/
|
|
60
62
|
export function parseAgentHandoffUrl(value, expectedOrigin) {
|
|
61
|
-
let
|
|
63
|
+
let expectedUrl;
|
|
62
64
|
try {
|
|
63
|
-
|
|
65
|
+
expectedUrl = new URL(expectedOrigin);
|
|
64
66
|
}
|
|
65
67
|
catch {
|
|
66
68
|
throw new TypeError("Agent handoff expectedOrigin must be a valid URL origin.");
|
|
67
69
|
}
|
|
70
|
+
if (expectedUrl.protocol !== "https:" && expectedUrl.protocol !== "http:") {
|
|
71
|
+
throw new TypeError("Agent handoff expectedOrigin must be an HTTP(S) origin.");
|
|
72
|
+
}
|
|
73
|
+
const canonicalExpectedOrigin = expectedUrl.origin;
|
|
68
74
|
let url;
|
|
69
75
|
try {
|
|
70
76
|
url = new URL(value);
|
|
@@ -74,14 +80,15 @@ export function parseAgentHandoffUrl(value, expectedOrigin) {
|
|
|
74
80
|
}
|
|
75
81
|
const documentId = /^\/agent\/([^/]+)$/.exec(url.pathname)?.[1];
|
|
76
82
|
const secret = url.hash.slice(1);
|
|
77
|
-
if (
|
|
78
|
-
url.
|
|
83
|
+
if (url.origin !== canonicalExpectedOrigin ||
|
|
84
|
+
url.username !== "" ||
|
|
85
|
+
url.password !== "" ||
|
|
79
86
|
value.includes("?") ||
|
|
80
87
|
!documentId ||
|
|
81
88
|
!opaqueHandoffDocumentId.test(documentId) ||
|
|
82
89
|
!opaqueHandoffSecret.test(secret) ||
|
|
83
90
|
documentId === secret) {
|
|
84
|
-
throw new TypeError("Agent handoff URL must be an
|
|
91
|
+
throw new TypeError("Agent handoff URL must be an /agent/<id> URL on the app origin with one opaque fragment secret (16-512 chars, distinct from the id) and no credentials or query.");
|
|
85
92
|
}
|
|
86
93
|
return { documentId, documentUrl: `${url.origin}${url.pathname}`, secret };
|
|
87
94
|
}
|
|
@@ -282,6 +289,7 @@ export function aiInstructionsText() {
|
|
|
282
289
|
"```",
|
|
283
290
|
"",
|
|
284
291
|
"Request `mcp:tools` plus the API scopes the tools need: `teams:read` for team context,",
|
|
292
|
+
"`teams:create` for creating teams,",
|
|
285
293
|
"`spaces:read` for status, versions, diffs, and logs, `spaces:write` for claim/create/update,",
|
|
286
294
|
"`publish:write` for publish and rollback, and `domains:read`/`domains:write` for domain",
|
|
287
295
|
"inventory, DNS diagnostics, assignment, verification, and mutation. Hosted MCP does not read",
|
|
@@ -22,7 +22,7 @@ function hostedMcpText(label, guideUrl) {
|
|
|
22
22
|
"Transport: Streamable HTTP",
|
|
23
23
|
"Resource: https://mcp.spacefast.com",
|
|
24
24
|
"Minimum scope: mcp:tools",
|
|
25
|
-
"Common scopes: teams:read spaces:read spaces:write publish:write domains:read domains:write",
|
|
25
|
+
"Common scopes: teams:read teams:create spaces:read spaces:write publish:write domains:read domains:write",
|
|
26
26
|
"",
|
|
27
27
|
"Hosted MCP is for cloud/browser agents. It does not read local paths; use the hosted workspace tools or signed uploads for artifacts.",
|
|
28
28
|
`Guide: ${guideUrl}`,
|
package/dist/docs/catalog.d.ts
CHANGED
|
@@ -24,8 +24,6 @@ export interface DocEntry {
|
|
|
24
24
|
}
|
|
25
25
|
/** Base URL all bundled docs links resolve against. */
|
|
26
26
|
export declare const DOCS_BASE_URL = "https://spacefast.com/docs";
|
|
27
|
-
/** Base URL for the standalone recipe pages (served at /recipes, not /docs). */
|
|
28
|
-
export declare const RECIPES_BASE_URL = "https://spacefast.com/recipes";
|
|
29
27
|
export declare const DOCS_CATALOG: readonly [{
|
|
30
28
|
readonly slug: "quickstart";
|
|
31
29
|
readonly title: "Quickstart";
|
|
@@ -244,13 +242,13 @@ export declare const DOCS_CATALOG: readonly [{
|
|
|
244
242
|
readonly body: "Connect a repository with `sf git connect`; each push runs your build on Spacefast and publishes a version, with branches and pull requests getting preview URLs.";
|
|
245
243
|
}, {
|
|
246
244
|
readonly slug: "collab";
|
|
247
|
-
readonly title: "
|
|
245
|
+
readonly title: "Comments";
|
|
248
246
|
readonly kind: "guide";
|
|
249
247
|
readonly tier: "full";
|
|
250
|
-
readonly summary: "Leave
|
|
251
|
-
readonly keywords: readonly ["
|
|
248
|
+
readonly summary: "Leave comments directly on a space.";
|
|
249
|
+
readonly keywords: readonly ["comments", "collaboration", "feedback"];
|
|
252
250
|
readonly url: "https://spacefast.com/docs/collab";
|
|
253
|
-
readonly body: "
|
|
251
|
+
readonly body: "Leave comments directly on a live space and archive them from the dashboard or with `sf comments`.";
|
|
254
252
|
}, {
|
|
255
253
|
readonly slug: "analytics";
|
|
256
254
|
readonly title: "Analytics";
|
|
@@ -618,7 +616,7 @@ export declare const DOCS_CATALOG: readonly [{
|
|
|
618
616
|
readonly tier: "full";
|
|
619
617
|
readonly summary: "Point your own domain at a space with automatic TLS.";
|
|
620
618
|
readonly keywords: readonly ["domain", "custom domain", "dns", "tls", "https", "recipe"];
|
|
621
|
-
readonly url: "https://spacefast.com/
|
|
619
|
+
readonly url: "https://spacefast.com/help/domains";
|
|
622
620
|
readonly body: "Add the domain with `sf domains add`, set the DNS records Spacefast prints, and wait for `sf domains check` to report a ready certificate.";
|
|
623
621
|
}, {
|
|
624
622
|
readonly slug: "recipes/password-protect";
|
|
@@ -627,7 +625,7 @@ export declare const DOCS_CATALOG: readonly [{
|
|
|
627
625
|
readonly tier: "full";
|
|
628
626
|
readonly summary: "Put one shared password in front of a whole space.";
|
|
629
627
|
readonly keywords: readonly ["password", "protect", "private", "access", "gate", "recipe"];
|
|
630
|
-
readonly url: "https://spacefast.com/
|
|
628
|
+
readonly url: "https://spacefast.com/help/access";
|
|
631
629
|
readonly body: "Run `sf password set` to gate a space behind a single shared secret; clear it later with `sf password clear`.";
|
|
632
630
|
}, {
|
|
633
631
|
readonly slug: "recipes/redirects";
|
|
@@ -636,7 +634,7 @@ export declare const DOCS_CATALOG: readonly [{
|
|
|
636
634
|
readonly tier: "reference";
|
|
637
635
|
readonly summary: "Add redirect rules that ship and roll back with a version.";
|
|
638
636
|
readonly keywords: readonly ["redirect", "rewrite", "routing", "301", "recipe"];
|
|
639
|
-
readonly url: "https://spacefast.com/
|
|
637
|
+
readonly url: "https://spacefast.com/help/redirects";
|
|
640
638
|
readonly body: "Add redirect rules to a `_redirects` file at your publish root so they publish as part of the version and roll back with it.";
|
|
641
639
|
}, {
|
|
642
640
|
readonly slug: "recipes/migrate-dns";
|
|
@@ -645,6 +643,6 @@ export declare const DOCS_CATALOG: readonly [{
|
|
|
645
643
|
readonly tier: "reference";
|
|
646
644
|
readonly summary: "Cut traffic over to a Spacefast-served domain safely.";
|
|
647
645
|
readonly keywords: readonly ["dns", "migrate", "cutover", "domain", "nameserver", "recipe"];
|
|
648
|
-
readonly url: "https://spacefast.com/
|
|
646
|
+
readonly url: "https://spacefast.com/help/domains";
|
|
649
647
|
readonly body: "Pre-stage the domain with `sf domains add`, verify the certificate is ready with `sf domains check`, then update DNS to flip traffic with zero downtime.";
|
|
650
648
|
}, ...DocEntry[]];
|
package/dist/docs/catalog.js
CHANGED
|
@@ -21,8 +21,6 @@ import { ERROR_DOCS } from "./error-docs.js";
|
|
|
21
21
|
export const DOC_KINDS = ["guide", "cli", "api", "recipe", "workflow", "error"];
|
|
22
22
|
/** Base URL all bundled docs links resolve against. */
|
|
23
23
|
export const DOCS_BASE_URL = "https://spacefast.com/docs";
|
|
24
|
-
/** Base URL for the standalone recipe pages (served at /recipes, not /docs). */
|
|
25
|
-
export const RECIPES_BASE_URL = "https://spacefast.com/recipes";
|
|
26
24
|
const HAND_WRITTEN_DOCS_CATALOG = [
|
|
27
25
|
// ----- Guides -----------------------------------------------------------
|
|
28
26
|
{
|
|
@@ -283,13 +281,13 @@ const HAND_WRITTEN_DOCS_CATALOG = [
|
|
|
283
281
|
},
|
|
284
282
|
{
|
|
285
283
|
slug: "collab",
|
|
286
|
-
title: "
|
|
284
|
+
title: "Comments",
|
|
287
285
|
kind: "guide",
|
|
288
286
|
tier: "full",
|
|
289
|
-
summary: "Leave
|
|
290
|
-
keywords: ["
|
|
287
|
+
summary: "Leave comments directly on a space.",
|
|
288
|
+
keywords: ["comments", "collaboration", "feedback"],
|
|
291
289
|
url: `${DOCS_BASE_URL}/collab`,
|
|
292
|
-
body: "
|
|
290
|
+
body: "Leave comments directly on a live space and archive them from the dashboard or with `sf comments`.",
|
|
293
291
|
},
|
|
294
292
|
{
|
|
295
293
|
slug: "analytics",
|
|
@@ -701,7 +699,7 @@ const HAND_WRITTEN_DOCS_CATALOG = [
|
|
|
701
699
|
tier: "full",
|
|
702
700
|
summary: "Point your own domain at a space with automatic TLS.",
|
|
703
701
|
keywords: ["domain", "custom domain", "dns", "tls", "https", "recipe"],
|
|
704
|
-
url:
|
|
702
|
+
url: "https://spacefast.com/help/domains",
|
|
705
703
|
body: "Add the domain with `sf domains add`, set the DNS records Spacefast prints, and wait for `sf domains check` to report a ready certificate.",
|
|
706
704
|
},
|
|
707
705
|
{
|
|
@@ -711,7 +709,7 @@ const HAND_WRITTEN_DOCS_CATALOG = [
|
|
|
711
709
|
tier: "full",
|
|
712
710
|
summary: "Put one shared password in front of a whole space.",
|
|
713
711
|
keywords: ["password", "protect", "private", "access", "gate", "recipe"],
|
|
714
|
-
url:
|
|
712
|
+
url: "https://spacefast.com/help/access",
|
|
715
713
|
body: "Run `sf password set` to gate a space behind a single shared secret; clear it later with `sf password clear`.",
|
|
716
714
|
},
|
|
717
715
|
{
|
|
@@ -721,7 +719,7 @@ const HAND_WRITTEN_DOCS_CATALOG = [
|
|
|
721
719
|
tier: "reference",
|
|
722
720
|
summary: "Add redirect rules that ship and roll back with a version.",
|
|
723
721
|
keywords: ["redirect", "rewrite", "routing", "301", "recipe"],
|
|
724
|
-
url:
|
|
722
|
+
url: "https://spacefast.com/help/redirects",
|
|
725
723
|
body: "Add redirect rules to a `_redirects` file at your publish root so they publish as part of the version and roll back with it.",
|
|
726
724
|
},
|
|
727
725
|
{
|
|
@@ -731,7 +729,7 @@ const HAND_WRITTEN_DOCS_CATALOG = [
|
|
|
731
729
|
tier: "reference",
|
|
732
730
|
summary: "Cut traffic over to a Spacefast-served domain safely.",
|
|
733
731
|
keywords: ["dns", "migrate", "cutover", "domain", "nameserver", "recipe"],
|
|
734
|
-
url:
|
|
732
|
+
url: "https://spacefast.com/help/domains",
|
|
735
733
|
body: "Pre-stage the domain with `sf domains add`, verify the certificate is ready with `sf domains check`, then update DNS to flip traffic with zero downtime.",
|
|
736
734
|
},
|
|
737
735
|
];
|
package/dist/docs/error-docs.js
CHANGED
|
@@ -36,6 +36,10 @@ export const ERROR_DOCS = {
|
|
|
36
36
|
summary: "This user already has the maximum number of pending handoffs for the Space.",
|
|
37
37
|
fix: "Redeem or revoke an existing pending handoff for the Space before creating another.",
|
|
38
38
|
},
|
|
39
|
+
agent_handoff_origin_unsupported: {
|
|
40
|
+
summary: "The dashboard origin this deployment is configured with cannot mint canonical agent handoff links.",
|
|
41
|
+
fix: "Point SPACEFAST_DASHBOARD_URL at a valid HTTP(S) origin; if you cannot change it, contact support.",
|
|
42
|
+
},
|
|
39
43
|
ambiguous_space_slug: {
|
|
40
44
|
summary: "The slug matches spaces in more than one of your teams, so it cannot be resolved.",
|
|
41
45
|
fix: "Pass a teamRef alongside the slug, or reference the space by its spc_ id.",
|
|
@@ -220,13 +224,13 @@ export const ERROR_DOCS = {
|
|
|
220
224
|
summary: "CSAM enforcement policy cannot be set below the mandatory account-suspend floor.",
|
|
221
225
|
fix: "Set the CSAM category enforcement tier to account suspension.",
|
|
222
226
|
},
|
|
223
|
-
|
|
224
|
-
summary: "
|
|
225
|
-
fix: "
|
|
227
|
+
custom_certificates_require_dedicated_site: {
|
|
228
|
+
summary: "Custom certificates are unavailable while this space uses shared placement.",
|
|
229
|
+
fix: "Move the space to dedicated placement before managing custom certificates.",
|
|
226
230
|
},
|
|
227
231
|
data_location_immutable: {
|
|
228
232
|
summary: "This request tried to change data location through an immutable path.",
|
|
229
|
-
fix: "
|
|
233
|
+
fix: "Data location is fixed when the space is created and cannot be changed afterwards. Create a new space in the region you want.",
|
|
230
234
|
},
|
|
231
235
|
device_authorization_failed: {
|
|
232
236
|
summary: "The device login request failed and cannot be completed.",
|
|
@@ -476,6 +480,10 @@ export const ERROR_DOCS = {
|
|
|
476
480
|
summary: "Basic-Auth via _headers is not available on the Free plan.",
|
|
477
481
|
fix: "Use space password protection instead. Still stuck? POST /v1/feedback with the error code and request id.",
|
|
478
482
|
},
|
|
483
|
+
google_play_webhook_identity_not_configured: {
|
|
484
|
+
summary: "Google Play Pub/Sub push authentication is not configured on this deployment.",
|
|
485
|
+
fix: "Configure the exact push audience and dedicated Pub/Sub service-account email before enabling RTDN delivery.",
|
|
486
|
+
},
|
|
479
487
|
idempotency_conflict_in_progress: {
|
|
480
488
|
summary: "Another request with the same Idempotency-Key is still in flight.",
|
|
481
489
|
fix: "Wait for the original request to finish; its response will be replayed.",
|
|
@@ -544,6 +552,10 @@ export const ERROR_DOCS = {
|
|
|
544
552
|
summary: "A file path in the request is not a valid space path.",
|
|
545
553
|
fix: "Use forward-slash relative paths without traversal segments.",
|
|
546
554
|
},
|
|
555
|
+
invalid_google_play_webhook_identity: {
|
|
556
|
+
summary: "The Google Play notification did not carry a valid Pub/Sub push identity.",
|
|
557
|
+
fix: "Send the notification through the configured authenticated Pub/Sub push subscription.",
|
|
558
|
+
},
|
|
547
559
|
invalid_grant: {
|
|
548
560
|
summary: "The authorization grant is invalid or was already used.",
|
|
549
561
|
fix: "Restart the login flow to obtain a new grant.",
|
|
@@ -660,6 +672,10 @@ export const ERROR_DOCS = {
|
|
|
660
672
|
summary: `${BRAND.productName} no longer observes DNS control for this domain.`,
|
|
661
673
|
fix: "Restore the documented DNS records, then re-run domain verification.",
|
|
662
674
|
},
|
|
675
|
+
maintenance_in_progress: {
|
|
676
|
+
summary: "This change is temporarily unavailable while maintenance finishes.",
|
|
677
|
+
fix: "Wait for the Retry-After window, then send the same request again.",
|
|
678
|
+
},
|
|
663
679
|
malware_detected: {
|
|
664
680
|
summary: "Malware was detected in the uploaded content.",
|
|
665
681
|
fix: "Remove the flagged files; contact support if you believe this is a false positive.",
|
|
@@ -917,8 +933,8 @@ export const ERROR_DOCS = {
|
|
|
917
933
|
fix: "Retry shortly; if the site stays unreachable, contact support.",
|
|
918
934
|
},
|
|
919
935
|
runtime_not_provisioned: {
|
|
920
|
-
summary: "The space has no
|
|
921
|
-
fix: "
|
|
936
|
+
summary: "The space has not served a published version yet, so it has no runtime data to read.",
|
|
937
|
+
fix: "Publish a version first. If a publish is already running, wait for it — the operation receipt reports progress.",
|
|
922
938
|
},
|
|
923
939
|
runtime_operation_missing: {
|
|
924
940
|
summary: "The runtime operation referenced by this request was not found.",
|
|
@@ -960,6 +976,10 @@ export const ERROR_DOCS = {
|
|
|
960
976
|
summary: "A secret variable was referenced in a template, which is blocked.",
|
|
961
977
|
fix: "Templates may only substitute non-secret variables; remove the secret reference.",
|
|
962
978
|
},
|
|
979
|
+
service_signature_invalid: {
|
|
980
|
+
summary: "The service request signature could not be verified.",
|
|
981
|
+
fix: "Sign the exact request method, path, body digest, timestamp, and a fresh nonce.",
|
|
982
|
+
},
|
|
963
983
|
site_already_deleted: {
|
|
964
984
|
summary: "The site was already deleted.",
|
|
965
985
|
fix: "No action needed; the site is gone.",
|
|
@@ -1000,6 +1020,18 @@ export const ERROR_DOCS = {
|
|
|
1000
1020
|
summary: "The slug is already in use within the relevant ownership scope.",
|
|
1001
1021
|
fix: "Choose another slug or use the available suggestion returned by the API.",
|
|
1002
1022
|
},
|
|
1023
|
+
source_archive_decompression_timeout: {
|
|
1024
|
+
summary: "The uploaded source archive did not finish decompressing within the validation limit.",
|
|
1025
|
+
fix: "Reduce the archive's compressed complexity or split it into a smaller source upload, then retry.",
|
|
1026
|
+
},
|
|
1027
|
+
source_archive_expansion_ratio_exceeded: {
|
|
1028
|
+
summary: "The uploaded source archive expands too much relative to its compressed size.",
|
|
1029
|
+
fix: "Rebuild the archive without highly compressible filler or generated content, then upload it again.",
|
|
1030
|
+
},
|
|
1031
|
+
source_archive_validation_busy: {
|
|
1032
|
+
summary: "The service is already validating its maximum number of source archives.",
|
|
1033
|
+
fix: "Wait briefly and retry the upload.",
|
|
1034
|
+
},
|
|
1003
1035
|
space_already_claimed: {
|
|
1004
1036
|
summary: "The space was already claimed; a claim can only succeed once.",
|
|
1005
1037
|
fix: "If you are the agent that published this space, exchange your claim token once at POST /v1/anonymous-claim/exchange for a durable access token and keep publishing to the same spaceId. Otherwise ask the owner for access — do not publish a duplicate space.",
|
|
@@ -1088,6 +1120,34 @@ export const ERROR_DOCS = {
|
|
|
1088
1120
|
summary: "This control file is not supported by the static runtime.",
|
|
1089
1121
|
fix: "Remove the file; see the routing docs for supported convention files.",
|
|
1090
1122
|
},
|
|
1123
|
+
static_mount_path_conflict: {
|
|
1124
|
+
summary: "The requested mount path overlaps another static mount on this space.",
|
|
1125
|
+
fix: "Choose a path that is not equal to, above, or below an existing mount path.",
|
|
1126
|
+
},
|
|
1127
|
+
static_mount_source_not_ready: {
|
|
1128
|
+
summary: "The website version that owns this mount is not ready.",
|
|
1129
|
+
fix: "Wait for the website version to finish finalizing, then retry.",
|
|
1130
|
+
},
|
|
1131
|
+
static_mount_target_forbidden: {
|
|
1132
|
+
summary: "The target space is not an authorized space in the same team.",
|
|
1133
|
+
fix: "Choose another space in the source space's team and authenticate with read access to its versions.",
|
|
1134
|
+
},
|
|
1135
|
+
static_mount_target_in_use: {
|
|
1136
|
+
summary: "A static mount dependency blocks changing or deleting this space.",
|
|
1137
|
+
fix: "Remove the reported static mount before retrying the operation.",
|
|
1138
|
+
},
|
|
1139
|
+
static_mount_target_not_live: {
|
|
1140
|
+
summary: "The target space does not have a production version to mount.",
|
|
1141
|
+
fix: "Publish or promote a ready version on the target space, then retry.",
|
|
1142
|
+
},
|
|
1143
|
+
static_mount_target_not_ready: {
|
|
1144
|
+
summary: "The target production version is not ready for static delivery.",
|
|
1145
|
+
fix: "Wait for the target version to finish finalizing, then retry.",
|
|
1146
|
+
},
|
|
1147
|
+
static_mount_target_not_static: {
|
|
1148
|
+
summary: "The target production version contains runtime functions.",
|
|
1149
|
+
fix: "Promote a fully static target version or serve the dynamic application without a static mount.",
|
|
1150
|
+
},
|
|
1091
1151
|
static_runtime_control_path_not_supported: {
|
|
1092
1152
|
summary: "This control path is not supported by the static runtime.",
|
|
1093
1153
|
fix: "Remove the path from the upload.",
|
|
@@ -1368,10 +1428,6 @@ export const ERROR_DOCS = {
|
|
|
1368
1428
|
summary: "The Zero endpoint artifact could not be parsed.",
|
|
1369
1429
|
fix: "Rebuild the Zero project; do not edit generated artifact JSON by hand.",
|
|
1370
1430
|
},
|
|
1371
|
-
zero_artifact_missing: {
|
|
1372
|
-
summary: "A Zero route points to an endpoint artifact that is missing.",
|
|
1373
|
-
fix: "Declare the endpoint in zero_endpoints, then rebuild and publish again.",
|
|
1374
|
-
},
|
|
1375
1431
|
zero_artifact_path_invalid: {
|
|
1376
1432
|
summary: "The Zero endpoint artifact path is invalid.",
|
|
1377
1433
|
fix: "Rebuild the Zero routes so all artifact paths stay inside the version bundle.",
|
|
@@ -1380,10 +1436,6 @@ export const ERROR_DOCS = {
|
|
|
1380
1436
|
summary: "The Zero endpoint artifact could not be read by the runtime.",
|
|
1381
1437
|
fix: "Verify the version finalized with all generated Zero files present.",
|
|
1382
1438
|
},
|
|
1383
|
-
zero_artifact_untrusted: {
|
|
1384
|
-
summary: "A Zero route points to an uploaded artifact that the runtime did not compile.",
|
|
1385
|
-
fix: "Declare the endpoint in zero_endpoints so finalize generates trusted bytecode.",
|
|
1386
|
-
},
|
|
1387
1439
|
zero_auth_unavailable: {
|
|
1388
1440
|
summary: "Hosted Zero auth is not configured for this deployment.",
|
|
1389
1441
|
fix: "Finalize the Zero version with hosted auth URLs or use guest/local auth.",
|
|
@@ -1472,6 +1524,10 @@ export const ERROR_DOCS = {
|
|
|
1472
1524
|
summary: "The Zero database operation has too many parameters.",
|
|
1473
1525
|
fix: "Reduce the number of bound parameters or split the operation.",
|
|
1474
1526
|
},
|
|
1527
|
+
zero_db_transaction_active: {
|
|
1528
|
+
summary: "The Zero runner tried to start a database transaction while one was already active.",
|
|
1529
|
+
fix: "Rebuild with the current Zero runner and report the failure if it repeats.",
|
|
1530
|
+
},
|
|
1475
1531
|
zero_db_transaction_commit_failed: {
|
|
1476
1532
|
summary: "The Zero database transaction could not be committed.",
|
|
1477
1533
|
fix: "Retry after checking database connectivity and transaction conflicts.",
|
|
@@ -1480,6 +1536,14 @@ export const ERROR_DOCS = {
|
|
|
1480
1536
|
summary: "The Zero database transaction mode is invalid.",
|
|
1481
1537
|
fix: "Use a supported generated transaction mode.",
|
|
1482
1538
|
},
|
|
1539
|
+
zero_db_transaction_missing: {
|
|
1540
|
+
summary: "The Zero runner tried to finish a database transaction that was not active.",
|
|
1541
|
+
fix: "Rebuild with the current Zero runner and report the failure if it repeats.",
|
|
1542
|
+
},
|
|
1543
|
+
zero_db_transaction_rollback_failed: {
|
|
1544
|
+
summary: "The Zero database transaction could not be rolled back cleanly.",
|
|
1545
|
+
fix: "Inspect the affected database state before retrying, then report the failure.",
|
|
1546
|
+
},
|
|
1483
1547
|
zero_db_transaction_start_failed: {
|
|
1484
1548
|
summary: "The Zero database transaction could not be started.",
|
|
1485
1549
|
fix: "Check database connectivity and retry the mutation.",
|
|
@@ -1588,10 +1652,6 @@ export const ERROR_DOCS = {
|
|
|
1588
1652
|
summary: "The Zero log query parameters are invalid.",
|
|
1589
1653
|
fix: "Use valid cursor, limit, mutationName, and requestId parameters.",
|
|
1590
1654
|
},
|
|
1591
|
-
zero_lookup_invalid: {
|
|
1592
|
-
summary: "The Zero lookup map contains an invalid path or action.",
|
|
1593
|
-
fix: "Use normalized relative paths and valid finalized Zero actions.",
|
|
1594
|
-
},
|
|
1595
1655
|
zero_method_not_allowed: {
|
|
1596
1656
|
summary: "The request method is not allowed for this Zero endpoint.",
|
|
1597
1657
|
fix: "Call the endpoint with one of the methods declared by the Zero route.",
|
|
@@ -1648,6 +1708,14 @@ export const ERROR_DOCS = {
|
|
|
1648
1708
|
summary: "The Zero request body exceeds the runtime size limit.",
|
|
1649
1709
|
fix: "Send a smaller request body or move large payloads to blob/object storage.",
|
|
1650
1710
|
},
|
|
1711
|
+
zero_response_header_forbidden: {
|
|
1712
|
+
summary: "A Zero endpoint attempted to set a platform-managed response header.",
|
|
1713
|
+
fix: "Remove the reserved response header from the endpoint response.",
|
|
1714
|
+
},
|
|
1715
|
+
zero_response_header_invalid: {
|
|
1716
|
+
summary: "A Zero endpoint response header is invalid or exceeds the header limits.",
|
|
1717
|
+
fix: "Use ASCII header names, keep values under the size limits, and avoid duplicate names.",
|
|
1718
|
+
},
|
|
1651
1719
|
zero_response_too_large: {
|
|
1652
1720
|
summary: "The Zero endpoint response exceeds the runtime size limit.",
|
|
1653
1721
|
fix: "Return a smaller response body or paginate the data.",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RuntimeRule } from "../contracts/access.js";
|
|
2
|
+
export declare const ACCESS_GRANT_GATE_RULE_ID = "access-grant-gate";
|
|
3
|
+
export declare const ACCESS_GRANT_ADMISSION_RULE_ID = "access-grant-admission";
|
|
4
|
+
export type AccessGrantExposure = {
|
|
5
|
+
anonymousAccess: "gated" | "gated_with_exceptions" | "partly_public" | "shadowed";
|
|
6
|
+
earlierAnonymousAllowRules: number;
|
|
7
|
+
earlierManagedAccessRules: number;
|
|
8
|
+
precedingTerminalRules: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function withAccessGrantGate(rules: RuntimeRule[], grant: string): RuntimeRule[];
|
|
11
|
+
export declare function withoutAccessGrantRule(rules: RuntimeRule[], selectedIndex: number): RuntimeRule[];
|
|
12
|
+
export declare function isAccessGrantAdmissionRule(rule: RuntimeRule): boolean;
|
|
13
|
+
export declare function isAccessGrantFallbackRule(rule: RuntimeRule): boolean;
|
|
14
|
+
export declare function accessGrantExposure(rules: RuntimeRule[]): AccessGrantExposure;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// `sf access grant` and the dashboard's Grant access card share one canonical
|
|
2
|
+
// pair: an authenticated admission allow plus an anonymous fallback challenge.
|
|
3
|
+
// Repeated grants widen both rules' OR-list. The split is essential under
|
|
4
|
+
// first-match evaluation: the admission can pass before managed challenges,
|
|
5
|
+
// while the fallback closes otherwise-public traffic after those challenges.
|
|
6
|
+
export const ACCESS_GRANT_GATE_RULE_ID = "access-grant-gate";
|
|
7
|
+
export const ACCESS_GRANT_ADMISSION_RULE_ID = "access-grant-admission";
|
|
8
|
+
export function withAccessGrantGate(rules, grant) {
|
|
9
|
+
// Raw access writes replace only the unmanaged lane. GET/PUT responses may
|
|
10
|
+
// include server-owned managed blocks, but sending those blocks back would
|
|
11
|
+
// be rejected and could let a client accidentally claim their ownership.
|
|
12
|
+
// Reserved access-grant rules are rebuilt as one canonical pair below.
|
|
13
|
+
const unmanagedRules = rules.filter((rule) => rule.managedBy === undefined &&
|
|
14
|
+
rule.id !== ACCESS_GRANT_ADMISSION_RULE_ID &&
|
|
15
|
+
rule.id !== ACCESS_GRANT_GATE_RULE_ID);
|
|
16
|
+
const existingRequiredGrants = rules.find((rule) => rule.id === ACCESS_GRANT_ADMISSION_RULE_ID)
|
|
17
|
+
?.auth?.requiredGrants;
|
|
18
|
+
const fallbackRequiredGrants = rules.find((rule) => rule.id === ACCESS_GRANT_GATE_RULE_ID)?.auth
|
|
19
|
+
?.requiredGrants;
|
|
20
|
+
const requiredGrants = [
|
|
21
|
+
...new Set([...(existingRequiredGrants ?? fallbackRequiredGrants ?? []), grant]),
|
|
22
|
+
];
|
|
23
|
+
return [
|
|
24
|
+
...unmanagedRules,
|
|
25
|
+
{
|
|
26
|
+
id: ACCESS_GRANT_ADMISSION_RULE_ID,
|
|
27
|
+
match: {},
|
|
28
|
+
effect: "allow",
|
|
29
|
+
auth: { requiredGrants },
|
|
30
|
+
reasonCode: "identity_grant_admitted",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: ACCESS_GRANT_GATE_RULE_ID,
|
|
34
|
+
match: {},
|
|
35
|
+
effect: "challenge",
|
|
36
|
+
auth: { requiredGrants },
|
|
37
|
+
reasonCode: "identity_grant_required",
|
|
38
|
+
message: "Sign in with an approved identity to continue.",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
export function withoutAccessGrantRule(rules, selectedIndex) {
|
|
43
|
+
const selectedRule = rules[selectedIndex];
|
|
44
|
+
const removesAccessGrant = selectedRule?.id === ACCESS_GRANT_ADMISSION_RULE_ID ||
|
|
45
|
+
selectedRule?.id === ACCESS_GRANT_GATE_RULE_ID;
|
|
46
|
+
return rules.filter((rule, index) => rule.managedBy === undefined &&
|
|
47
|
+
(removesAccessGrant
|
|
48
|
+
? rule.id !== ACCESS_GRANT_ADMISSION_RULE_ID && rule.id !== ACCESS_GRANT_GATE_RULE_ID
|
|
49
|
+
: index !== selectedIndex));
|
|
50
|
+
}
|
|
51
|
+
export function isAccessGrantAdmissionRule(rule) {
|
|
52
|
+
return (rule.id === ACCESS_GRANT_ADMISSION_RULE_ID &&
|
|
53
|
+
rule.effect === "allow" &&
|
|
54
|
+
Object.keys(rule.match).length === 0 &&
|
|
55
|
+
rule.auth !== undefined);
|
|
56
|
+
}
|
|
57
|
+
export function isAccessGrantFallbackRule(rule) {
|
|
58
|
+
return (rule.id === ACCESS_GRANT_GATE_RULE_ID &&
|
|
59
|
+
rule.effect === "challenge" &&
|
|
60
|
+
Object.keys(rule.match).length === 0 &&
|
|
61
|
+
rule.auth !== undefined);
|
|
62
|
+
}
|
|
63
|
+
// The admission allow protects approved identities without preempting the
|
|
64
|
+
// existing password/share/service challenge. The final fallback gate protects
|
|
65
|
+
// public fallthrough. A terminal rule from an earlier effective lane can still
|
|
66
|
+
// stop evaluation before admission; report that explicitly instead of claiming
|
|
67
|
+
// the space is simply gated.
|
|
68
|
+
export function accessGrantExposure(rules) {
|
|
69
|
+
const gateIndex = rules.findIndex(isAccessGrantFallbackRule);
|
|
70
|
+
const admissionIndex = rules.findIndex(isAccessGrantAdmissionRule);
|
|
71
|
+
const earlierRules = gateIndex === -1 ? rules : rules.slice(0, gateIndex);
|
|
72
|
+
const rulesBeforeAdmission = admissionIndex === -1 ? earlierRules : rules.slice(0, admissionIndex);
|
|
73
|
+
const precedingTerminalRules = rulesBeforeAdmission.filter((rule) => rule.effect === "challenge" || (rule.effect === "deny" && rule.auth === undefined)).length;
|
|
74
|
+
const earlierAnonymousAllowRules = earlierRules.filter((rule) => rule.effect === "allow" && !rule.auth).length;
|
|
75
|
+
const earlierManagedAccessRules = earlierRules.filter((rule) => rule.managedBy !== undefined && rule.effect === "allow").length;
|
|
76
|
+
return {
|
|
77
|
+
anonymousAccess: precedingTerminalRules > 0
|
|
78
|
+
? "shadowed"
|
|
79
|
+
: earlierAnonymousAllowRules > 0
|
|
80
|
+
? "partly_public"
|
|
81
|
+
: earlierManagedAccessRules > 0
|
|
82
|
+
? "gated_with_exceptions"
|
|
83
|
+
: "gated",
|
|
84
|
+
earlierAnonymousAllowRules,
|
|
85
|
+
earlierManagedAccessRules,
|
|
86
|
+
precedingTerminalRules,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export declare function filterGrantsByNamespace(grants: string[], namespaces: string[]): string[];
|
|
2
2
|
export declare function grantGlobMatches(pattern: string, grant: string): boolean;
|
|
3
3
|
export declare function grantsIntersect(grants: string[], requiredGrants: string[]): boolean;
|
|
4
|
+
export declare function requiredGrantsForRule(rule: {
|
|
5
|
+
managedBy?: string | undefined;
|
|
6
|
+
auth?: {
|
|
7
|
+
requiredGrants: string[];
|
|
8
|
+
} | undefined;
|
|
9
|
+
}): string[];
|
|
4
10
|
export declare function grantsSatisfyRule(input: {
|
|
5
11
|
grants: string[];
|
|
6
12
|
requiredGrants: string[];
|
|
@@ -31,6 +37,7 @@ export type RuleMatchInput = {
|
|
|
31
37
|
ipCidrs?: string[] | undefined;
|
|
32
38
|
agent?: string | undefined;
|
|
33
39
|
country?: string | undefined;
|
|
40
|
+
countryNotIn?: string[] | undefined;
|
|
34
41
|
header?: {
|
|
35
42
|
name: string;
|
|
36
43
|
value: string;
|