@spacefast/common 0.0.10 → 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.
Files changed (122) hide show
  1. package/dist/agents/connect-targets.d.ts +4 -4
  2. package/dist/agents/connect-targets.js +3 -3
  3. package/dist/agents/private-key-oauth.d.ts +37 -0
  4. package/dist/agents/private-key-oauth.js +158 -0
  5. package/dist/config/domains.js +1 -1
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +1 -0
  8. package/dist/config/postgres-budget.d.ts +126 -0
  9. package/dist/config/postgres-budget.js +135 -0
  10. package/dist/config/postgres-budget.json +50 -0
  11. package/dist/contracts/access.d.ts +42 -24
  12. package/dist/contracts/access.js +73 -34
  13. package/dist/contracts/activity.d.ts +1 -1
  14. package/dist/contracts/activity.js +5 -5
  15. package/dist/contracts/api-keys.d.ts +53 -45
  16. package/dist/contracts/api-keys.js +1 -0
  17. package/dist/contracts/archives.d.ts +8 -8
  18. package/dist/contracts/builds.d.ts +8 -8
  19. package/dist/contracts/builds.js +28 -48
  20. package/dist/contracts/channels.js +1 -2
  21. package/dist/contracts/comments.d.ts +55 -19
  22. package/dist/contracts/comments.js +56 -17
  23. package/dist/contracts/common.d.ts +4 -3
  24. package/dist/contracts/common.js +2 -0
  25. package/dist/contracts/deployments.d.ts +3 -3
  26. package/dist/contracts/device-auth.d.ts +14 -12
  27. package/dist/contracts/docs.d.ts +5 -5
  28. package/dist/contracts/domains.d.ts +261 -1330
  29. package/dist/contracts/domains.js +39 -301
  30. package/dist/contracts/email-preferences.d.ts +50 -0
  31. package/dist/contracts/email-preferences.js +44 -0
  32. package/dist/contracts/enums.d.ts +1 -1
  33. package/dist/contracts/error-code-meta.d.ts +84 -15
  34. package/dist/contracts/error-code-meta.js +28 -4
  35. package/dist/contracts/error-codes.d.ts +1 -1
  36. package/dist/contracts/error-codes.js +24 -6
  37. package/dist/contracts/events.d.ts +13 -13
  38. package/dist/contracts/feature-lifecycle.d.ts +644 -2
  39. package/dist/contracts/feature-lifecycle.js +685 -2
  40. package/dist/contracts/features.d.ts +236 -24
  41. package/dist/contracts/features.js +3 -272
  42. package/dist/contracts/ids.d.ts +1 -2
  43. package/dist/contracts/ids.js +1 -2
  44. package/dist/contracts/internal.d.ts +3 -3
  45. package/dist/contracts/mcp.d.ts +6 -6
  46. package/dist/contracts/me.js +1 -1
  47. package/dist/contracts/mounts.d.ts +142 -0
  48. package/dist/contracts/mounts.js +56 -0
  49. package/dist/contracts/oauth-resources.d.ts +5 -5
  50. package/dist/contracts/oauth-resources.js +1 -0
  51. package/dist/contracts/operations.d.ts +2 -2
  52. package/dist/contracts/pages.d.ts +2 -2
  53. package/dist/contracts/pages.js +8 -3
  54. package/dist/contracts/plan-policy.d.ts +1 -1
  55. package/dist/contracts/platform.d.ts +3 -3
  56. package/dist/contracts/resources.d.ts +12 -20
  57. package/dist/contracts/resources.js +13 -37
  58. package/dist/contracts/runtime-api.d.ts +330 -16
  59. package/dist/contracts/runtime-api.js +58 -14
  60. package/dist/contracts/sf-config-v1.js +1 -1
  61. package/dist/contracts/sites.d.ts +12 -12
  62. package/dist/contracts/sites.js +18 -11
  63. package/dist/contracts/space-config.d.ts +2 -3
  64. package/dist/contracts/space-config.js +3 -1
  65. package/dist/contracts/spaces.d.ts +143 -102
  66. package/dist/contracts/spaces.js +109 -43
  67. package/dist/contracts/superadmin-emails.d.ts +25 -1
  68. package/dist/contracts/superadmin-emails.js +10 -15
  69. package/dist/contracts/superadmin-runtime.d.ts +2 -2
  70. package/dist/contracts/superadmin-spaces.d.ts +6 -14
  71. package/dist/contracts/tags.d.ts +43 -13
  72. package/dist/contracts/tags.js +195 -11
  73. package/dist/contracts/transfers.d.ts +1 -1
  74. package/dist/contracts/variables.d.ts +4 -4
  75. package/dist/contracts/variables.js +4 -3
  76. package/dist/contracts/webhooks.d.ts +2 -2
  77. package/dist/contracts/zero.d.ts +329 -7
  78. package/dist/contracts/zero.js +121 -2
  79. package/dist/dashboard-paths/index.d.ts +1 -1
  80. package/dist/docs/agent-setup.d.ts +4 -2
  81. package/dist/docs/agent-setup.js +15 -7
  82. package/dist/docs/agent-solutions.js +1 -1
  83. package/dist/docs/catalog.d.ts +8 -10
  84. package/dist/docs/catalog.js +8 -10
  85. package/dist/docs/error-docs.js +86 -18
  86. package/dist/utils/access-grant.d.ts +14 -0
  87. package/dist/utils/access-grant.js +88 -0
  88. package/dist/utils/access-match.d.ts +7 -0
  89. package/dist/utils/access-match.fixtures.json +513 -0
  90. package/dist/utils/access-match.js +32 -16
  91. package/dist/utils/access-verdict.fixtures.json +421 -0
  92. package/dist/utils/credential-policy.d.ts +1 -1
  93. package/dist/utils/dns-instructions.d.ts +29 -0
  94. package/dist/utils/dns-instructions.js +78 -0
  95. package/dist/utils/domain-diagnostics.d.ts +29 -0
  96. package/dist/utils/domain-diagnostics.js +63 -0
  97. package/dist/utils/egress-policy.fixtures.json +51 -0
  98. package/dist/utils/id-hints.d.ts +1 -0
  99. package/dist/utils/id-hints.js +55 -13
  100. package/dist/utils/page-colors.d.ts +50 -0
  101. package/dist/utils/page-colors.js +167 -0
  102. package/dist/utils/page-fonts.d.ts +2 -0
  103. package/dist/utils/page-fonts.js +4 -0
  104. package/dist/utils/pages.d.ts +8 -1
  105. package/dist/utils/pages.js +123 -37
  106. package/dist/utils/production-runtime.d.ts +18 -0
  107. package/dist/utils/production-runtime.js +28 -0
  108. package/dist/utils/query-keys.d.ts +6 -7
  109. package/dist/utils/query-keys.js +11 -12
  110. package/dist/utils/sf-config-v1.fixtures.json +9 -0
  111. package/dist/utils/sf-config-v1.js +28 -19
  112. package/dist/utils/source-archive-policy.d.ts +2 -0
  113. package/dist/utils/source-archive-policy.js +2 -0
  114. package/dist/utils/static-runtime-policy.fixtures.json +57 -0
  115. package/dist/utils/static-runtime-policy.js +1 -1
  116. package/package.json +5 -4
  117. package/dist/contracts/annotations.d.ts +0 -327
  118. package/dist/contracts/annotations.js +0 -235
  119. package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
  120. package/dist/contracts/generated-feature-launch-entries.js +0 -518
  121. package/dist/contracts/publishes.d.ts +0 -135
  122. package/dist/contracts/publishes.js +0 -162
@@ -47,10 +47,12 @@ function isSafeHttpUrl(url) {
47
47
  }
48
48
  /**
49
49
  * The single shared grammar for handoff links:
50
- * `https://<app-origin>/agent/<documentId>#<secret>`. The document ID must match
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; HTTPS is required except on loopback hosts.
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 canonicalExpectedOrigin;
63
+ let expectedUrl;
62
64
  try {
63
- canonicalExpectedOrigin = new URL(expectedOrigin).origin;
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 (!isSafeHttpUrl(url) ||
78
- url.origin !== canonicalExpectedOrigin ||
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 HTTPS /agent/<id> URL with one opaque fragment secret (16-512 chars, distinct from the id) and no credentials or query (HTTP is allowed on loopback hosts).");
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}`,
@@ -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: "Collaboration and annotations";
245
+ readonly title: "Comments";
248
246
  readonly kind: "guide";
249
247
  readonly tier: "full";
250
- readonly summary: "Leave review comments on a published space.";
251
- readonly keywords: readonly ["collab", "annotations", "comments", "review", "feedback", "annotate"];
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: "Annotate a live space with review comments and resolve them from the CLI with `sf annotations` or in the dashboard.";
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/recipes/custom-domain";
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/recipes/password-protect-a-space";
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/recipes/redirects";
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/recipes/custom-domain";
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[]];
@@ -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: "Collaboration and annotations",
284
+ title: "Comments",
287
285
  kind: "guide",
288
286
  tier: "full",
289
- summary: "Leave review comments on a published space.",
290
- keywords: ["collab", "annotations", "comments", "review", "feedback", "annotate"],
287
+ summary: "Leave comments directly on a space.",
288
+ keywords: ["comments", "collaboration", "feedback"],
291
289
  url: `${DOCS_BASE_URL}/collab`,
292
- body: "Annotate a live space with review comments and resolve them from the CLI with `sf annotations` or in the dashboard.",
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: `${RECIPES_BASE_URL}/custom-domain`,
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: `${RECIPES_BASE_URL}/password-protect-a-space`,
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: `${RECIPES_BASE_URL}/redirects`,
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: `${RECIPES_BASE_URL}/custom-domain`,
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
  ];
@@ -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
- datacenters_unavailable: {
224
- summary: "Placement regions are temporarily unavailable.",
225
- fix: "Retry after the datacenter list has synced.",
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: "Use PATCH /v1/spaces/{id} to queue a region move when region changes are supported for this space.",
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 provisioned runtime yet.",
921
- fix: "Wait for provisioning to finish; the operation receipt reports progress.",
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;