@spacefast/common 0.0.23 → 0.0.24

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 (176) hide show
  1. package/dist/config/domains.d.ts +1 -1
  2. package/dist/config/domains.js +1 -0
  3. package/dist/config/index.d.ts +0 -1
  4. package/dist/config/index.js +0 -1
  5. package/dist/contracts/abuse.d.ts +2 -0
  6. package/dist/contracts/abuse.js +12 -0
  7. package/dist/contracts/access.d.ts +121 -9
  8. package/dist/contracts/access.js +73 -9
  9. package/dist/contracts/activity.d.ts +1 -1
  10. package/dist/contracts/activity.js +0 -9
  11. package/dist/contracts/api-keys.d.ts +21 -21
  12. package/dist/contracts/beta.js +1 -1
  13. package/dist/contracts/billing.d.ts +1 -34
  14. package/dist/contracts/billing.js +4 -53
  15. package/dist/contracts/builds.d.ts +8 -8
  16. package/dist/contracts/builds.js +10 -3
  17. package/dist/contracts/cli.d.ts +10 -0
  18. package/dist/contracts/cli.js +10 -0
  19. package/dist/contracts/collab-public.d.ts +63 -0
  20. package/dist/contracts/collab-public.js +46 -0
  21. package/dist/contracts/comments.d.ts +283 -54
  22. package/dist/contracts/comments.js +173 -52
  23. package/dist/contracts/common.d.ts +3 -3
  24. package/dist/contracts/device-auth.d.ts +6 -6
  25. package/dist/contracts/device-auth.js +1 -1
  26. package/dist/contracts/docs.d.ts +5 -5
  27. package/dist/contracts/domains.d.ts +36 -36
  28. package/dist/contracts/enums.d.ts +33 -30
  29. package/dist/contracts/enums.js +52 -38
  30. package/dist/contracts/error-code-meta.d.ts +37 -126
  31. package/dist/contracts/error-code-meta.js +12 -41
  32. package/dist/contracts/error-codes.d.ts +1 -1
  33. package/dist/contracts/error-codes.js +10 -32
  34. package/dist/contracts/execution.d.ts +35 -57
  35. package/dist/contracts/execution.js +41 -40
  36. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  37. package/dist/contracts/feature-lifecycle-core.js +2 -31
  38. package/dist/contracts/feature-lifecycle.d.ts +8 -38
  39. package/dist/contracts/feature-lifecycle.js +16 -48
  40. package/dist/contracts/features.d.ts +15 -13
  41. package/dist/contracts/features.js +39 -38
  42. package/dist/contracts/functions.d.ts +97 -70
  43. package/dist/contracts/functions.js +101 -34
  44. package/dist/contracts/git.d.ts +1 -1
  45. package/dist/contracts/grants.d.ts +7 -3
  46. package/dist/contracts/grants.js +18 -9
  47. package/dist/contracts/internal.d.ts +14 -1
  48. package/dist/contracts/internal.js +17 -7
  49. package/dist/contracts/oauth-resources.d.ts +4 -3
  50. package/dist/contracts/oauth-resources.js +12 -3
  51. package/dist/contracts/operations.d.ts +90 -5
  52. package/dist/contracts/operations.js +75 -1
  53. package/dist/contracts/pages.d.ts +6 -1
  54. package/dist/contracts/pages.js +24 -1
  55. package/dist/contracts/plan-policy.d.ts +1 -3
  56. package/dist/contracts/plan-policy.js +1 -2
  57. package/dist/contracts/platform.d.ts +61 -148
  58. package/dist/contracts/platform.js +70 -126
  59. package/dist/contracts/publish-archive.d.ts +498 -0
  60. package/dist/contracts/publish-archive.js +25 -0
  61. package/dist/contracts/resources.d.ts +12 -24
  62. package/dist/contracts/resources.js +32 -12
  63. package/dist/contracts/runtime-api.d.ts +369 -175
  64. package/dist/contracts/runtime-api.js +372 -348
  65. package/dist/contracts/runtime-app.d.ts +127 -120
  66. package/dist/contracts/runtime-app.js +19 -10
  67. package/dist/contracts/runtime-db.d.ts +1 -1
  68. package/dist/contracts/runtime-services.d.ts +219 -0
  69. package/dist/contracts/runtime-services.js +437 -0
  70. package/dist/contracts/runtime-storage.d.ts +45 -32
  71. package/dist/contracts/runtime-storage.js +30 -8
  72. package/dist/contracts/space-config.d.ts +10 -16
  73. package/dist/contracts/space-config.js +14 -10
  74. package/dist/contracts/spaces.d.ts +241 -436
  75. package/dist/contracts/spaces.js +128 -145
  76. package/dist/contracts/superadmin-activity.d.ts +57 -4
  77. package/dist/contracts/superadmin-activity.js +48 -2
  78. package/dist/contracts/superadmin-search.d.ts +126 -0
  79. package/dist/contracts/superadmin-search.js +61 -0
  80. package/dist/contracts/superadmin-spaces.d.ts +358 -89
  81. package/dist/contracts/superadmin-spaces.js +97 -24
  82. package/dist/contracts/superadmin-teams.d.ts +28 -10
  83. package/dist/contracts/superadmin-teams.js +5 -8
  84. package/dist/contracts/superadmin.d.ts +0 -32
  85. package/dist/contracts/superadmin.js +3 -11
  86. package/dist/contracts/tags.d.ts +9 -9
  87. package/dist/contracts/teams.d.ts +38 -0
  88. package/dist/contracts/teams.js +34 -16
  89. package/dist/contracts/transfers.d.ts +0 -49
  90. package/dist/contracts/transfers.js +2 -6
  91. package/dist/contracts/usage.d.ts +61 -0
  92. package/dist/contracts/usage.js +64 -0
  93. package/dist/contracts/variables.d.ts +2 -7
  94. package/dist/contracts/variables.js +6 -5
  95. package/dist/contracts/webhooks.d.ts +2 -2
  96. package/dist/contracts/zero.d.ts +87 -56
  97. package/dist/contracts/zero.js +82 -20
  98. package/dist/dashboard-paths/index.d.ts +1 -1
  99. package/dist/dashboard-paths/index.js +1 -0
  100. package/dist/docs/agent-prose.d.ts +1 -1
  101. package/dist/docs/agent-prose.js +57 -61
  102. package/dist/docs/agent-setup.js +40 -13
  103. package/dist/docs/catalog.d.ts +19 -19
  104. package/dist/docs/catalog.js +30 -19
  105. package/dist/docs/error-docs.js +63 -151
  106. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  107. package/dist/test-helpers/fetch-stub.js +24 -0
  108. package/dist/utils/build-settings.js +9 -3
  109. package/dist/utils/canonical-json.d.ts +10 -0
  110. package/dist/utils/canonical-json.js +21 -0
  111. package/dist/utils/cast-transport.d.ts +28 -0
  112. package/dist/utils/cast-transport.js +52 -0
  113. package/dist/utils/claim-token.d.ts +0 -8
  114. package/dist/utils/claim-token.js +3 -15
  115. package/dist/utils/credential-policy.d.ts +1 -1
  116. package/dist/utils/env-file.d.ts +17 -0
  117. package/dist/utils/env-file.js +96 -0
  118. package/dist/utils/error-code.d.ts +2 -0
  119. package/dist/utils/error-code.js +7 -0
  120. package/dist/utils/generate-space-name.d.ts +8 -10
  121. package/dist/utils/generate-space-name.js +206 -21
  122. package/dist/utils/grant-decision.fixtures.json +2938 -0
  123. package/dist/utils/grants.d.ts +2 -0
  124. package/dist/utils/grants.js +25 -2
  125. package/dist/utils/gravatar.d.ts +9 -0
  126. package/dist/utils/gravatar.js +31 -0
  127. package/dist/utils/idempotency.d.ts +6 -0
  128. package/dist/utils/idempotency.js +28 -0
  129. package/dist/utils/local-space-state.d.ts +1 -5
  130. package/dist/utils/local-space-state.js +7 -26
  131. package/dist/utils/page-fonts.d.ts +2 -2
  132. package/dist/utils/page-fonts.js +22 -4
  133. package/dist/utils/pages.d.ts +1 -2
  134. package/dist/utils/pages.js +21 -5
  135. package/dist/utils/publish-form-data.d.ts +12 -0
  136. package/dist/utils/publish-form-data.js +10 -0
  137. package/dist/utils/publish-policy.d.ts +20 -0
  138. package/dist/utils/publish-policy.fixtures.json +397 -0
  139. package/dist/utils/publish-policy.js +36 -19
  140. package/dist/utils/push-new.d.ts +7 -0
  141. package/dist/utils/push-new.js +7 -0
  142. package/dist/utils/query-keys.d.ts +0 -1
  143. package/dist/utils/query-keys.js +0 -1
  144. package/dist/utils/runtime-paths.d.ts +5 -0
  145. package/dist/utils/runtime-paths.js +5 -0
  146. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  147. package/dist/utils/runtime-upload-batch.js +1 -27
  148. package/dist/utils/runtime-upload.d.ts +3 -13
  149. package/dist/utils/runtime-upload.js +4 -14
  150. package/dist/utils/space-config.d.ts +0 -33
  151. package/dist/utils/space-config.js +5 -326
  152. package/dist/utils/storage-policy.d.ts +1 -1
  153. package/dist/utils/storage-policy.js +3 -1
  154. package/dist/utils/structured-error.d.ts +14 -0
  155. package/dist/utils/structured-error.js +22 -0
  156. package/dist/utils/version-path.js +12 -3
  157. package/dist/vocabulary.d.ts +5 -3
  158. package/dist/vocabulary.js +13 -10
  159. package/package.json +10 -5
  160. package/dist/config/postgres-budget.d.ts +0 -129
  161. package/dist/config/postgres-budget.js +0 -140
  162. package/dist/config/postgres-budget.json +0 -51
  163. package/dist/contracts/archives.d.ts +0 -331
  164. package/dist/contracts/archives.js +0 -120
  165. package/dist/contracts/deployments.d.ts +0 -574
  166. package/dist/contracts/deployments.js +0 -97
  167. package/dist/contracts/sites.d.ts +0 -416
  168. package/dist/contracts/sites.js +0 -168
  169. package/dist/contracts/superadmin-queues.d.ts +0 -414
  170. package/dist/contracts/superadmin-queues.js +0 -202
  171. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  172. package/dist/contracts/superadmin-runtime.js +0 -38
  173. package/dist/index.d.ts +0 -1
  174. package/dist/index.js +0 -1
  175. package/dist/utils/api.d.ts +0 -4
  176. package/dist/utils/api.js +0 -13
@@ -49,26 +49,18 @@ export const ERROR_DOCS = {
49
49
  summary: "This anonymous space expired before it was claimed.",
50
50
  fix: "Publish a fresh space and share its claim link right away this time. If you meant to update a live space, check .spacefast/ (or your saved receipt) for its spaceId. Publish to that space with its credential instead of creating a new space.",
51
51
  },
52
- anonymous_pool_unavailable: {
53
- summary: "No pooled capacity is available for anonymous publishes right now.",
54
- fix: "Retry shortly. Capacity replenishes automatically.",
55
- },
56
52
  anonymous_publish_rate_limited: {
57
53
  summary: "Too many anonymous publishes came from this network in a short window.",
58
54
  fix: "Wait for the window to pass, or sign in with an API key for higher limits.",
59
55
  },
60
56
  anonymous_space_limit_reached: {
61
- summary: "Too many unclaimed anonymous spaces are already active for this client.",
62
- fix: "Log in to publish into an authenticated space, or claim/delete an existing anonymous space before creating another.",
57
+ summary: "This client already holds the maximum number of unclaimed Spaces.",
58
+ fix: "Sign in and publish into your account, or claim or delete one of the unclaimed Spaces you already have.",
63
59
  },
64
60
  api_key_scope_underivable: {
65
61
  summary: "A concrete access policy cannot be derived from the requested scope and permissions.",
66
62
  fix: "Point scope at a resource you can already reach (an existing space owned by a team). Request only permissions your credential already has.",
67
63
  },
68
- archive_body_required: {
69
- summary: "This request must carry an archive body but none was provided.",
70
- fix: "Send the zip bytes as the request body with the matching content-type header.",
71
- },
72
64
  archive_too_large: {
73
65
  summary: "The uploaded archive exceeds the maximum allowed size.",
74
66
  fix: "Reduce the archive size or upload files individually through a version upload session.",
@@ -81,26 +73,6 @@ export const ERROR_DOCS = {
81
73
  summary: "The device login is not approved yet.",
82
74
  fix: "Keep polling at the advised interval until the user approves the request.",
83
75
  },
84
- billing_receipt_invalid: {
85
- summary: "The store receipt or notification cannot be verified with the payment provider.",
86
- fix: "Re-check the transaction id or purchase token and retry. Contact support if it persists.",
87
- },
88
- billing_receipt_not_bound_to_team: {
89
- summary: "The store purchase was made under a different account token than this team's.",
90
- fix: "Verify the purchase from the team that initiated it, or contact support to rebind it.",
91
- },
92
- billing_store_not_configured: {
93
- summary: "App Store or Google Play billing is not configured on this deployment.",
94
- fix: "Use another billing method, or configure the store credentials on the server.",
95
- },
96
- billing_subscription_conflict: {
97
- summary: "The team already has an active subscription with another payment provider.",
98
- fix: "Cancel the existing subscription before subscribing through a different provider.",
99
- },
100
- billing_unknown_product: {
101
- summary: "The purchased store product does not map to any plan sold by this deployment.",
102
- fix: "Check the product id against the store catalog returned by the billing endpoints.",
103
- },
104
76
  build_command_missing: {
105
77
  summary: "A build or install command cannot be found during the build run.",
106
78
  fix: "Check the command spelling and ensure its tool is declared in package.json or available in the build image.",
@@ -137,6 +109,14 @@ export const ERROR_DOCS = {
137
109
  summary: "The build exceeded its time limit before producing a version.",
138
110
  fix: "Speed up the build or raise the build timeout in the build settings, then start a new build.",
139
111
  },
112
+ blob_gate_record_missing: {
113
+ summary: "The file behind this download link no longer exists.",
114
+ fix: "Ask the owner for a fresh link; the underlying object was deleted.",
115
+ },
116
+ blob_gate_token_invalid: {
117
+ summary: "The download token is invalid or expired.",
118
+ fix: "Request a fresh download link and use it within its lifetime.",
119
+ },
140
120
  cache_purge_domain_required: {
141
121
  summary: "A purge request must name the domain to purge.",
142
122
  fix: "Pass the domain to purge in the request.",
@@ -173,10 +153,6 @@ export const ERROR_DOCS = {
173
153
  summary: "This CLI version is too old to talk to the API safely.",
174
154
  fix: "Upgrade the CLI to the latest release and retry.",
175
155
  },
176
- comment_screenshot_rate_limited: {
177
- summary: "Too many comment screenshot uploads were attempted.",
178
- fix: "Wait for the one-minute window to reset, then retry the screenshot upload.",
179
- },
180
156
  config_file_too_large: {
181
157
  summary: "The sf.jsonc configuration file exceeds the maximum size.",
182
158
  fix: "Keep the configuration file under the documented size limit (256 KB).",
@@ -210,11 +186,11 @@ export const ERROR_DOCS = {
210
186
  fix: "Reduce the number of template entries in sf.jsonc. Still stuck? POST /v1/feedback with the error code and request id.",
211
187
  },
212
188
  continuation_unavailable: {
213
- summary: "This claim token can no longer be exchanged for an API key.",
214
- fix: "If the space is claimed, ask its owner for an API key (dashboard: Account → API keys). If it is still unclaimed, keep using the claim token as bearer auth instead of exchanging it. Still stuck? POST /v1/feedback with the error code and request id.",
189
+ summary: "This space key can no longer be exchanged for an API key.",
190
+ fix: "If the space is claimed, ask its owner for an API key (dashboard: Account → API keys). If it is still unclaimed, keep using the space key as bearer auth instead of exchanging it. Still stuck? POST /v1/feedback with the error code and request id.",
215
191
  },
216
192
  continuation_used: {
217
- summary: "This claim token was already exchanged for a durable API key.",
193
+ summary: "This space key was already exchanged for a durable API key.",
218
194
  fix: "Authenticate with the exchanged API key (check .spacefast/state.json or wherever it was saved). If it was lost, ask the space owner to create a new API key.",
219
195
  },
220
196
  credential_expired: {
@@ -241,10 +217,6 @@ export const ERROR_DOCS = {
241
217
  summary: "CSAM enforcement policy cannot be set below the mandatory account-suspend floor.",
242
218
  fix: "Set the CSAM category enforcement tier to account suspension.",
243
219
  },
244
- custom_certificates_require_dedicated_site: {
245
- summary: "Custom certificates are unavailable while this space uses shared placement.",
246
- fix: "Move the space to dedicated placement before managing custom certificates.",
247
- },
248
220
  data_location_immutable: {
249
221
  summary: "This request tried to change data location through an immutable path.",
250
222
  fix: "Data location is fixed when the space is created and cannot be changed afterwards. Create a new space in the region you want.",
@@ -473,14 +445,6 @@ export const ERROR_DOCS = {
473
445
  summary: "The token is expired.",
474
446
  fix: "Request a new token and retry.",
475
447
  },
476
- export_archive_not_ready: {
477
- summary: "The export archive is still being prepared.",
478
- fix: "Poll the export until its status is ready, then download the archive.",
479
- },
480
- export_version_not_found: {
481
- summary: "The version requested for export was not found.",
482
- fix: "List the space's versions and export an existing version id.",
483
- },
484
448
  feature_unavailable: {
485
449
  summary: "This feature is not available on this deployment or plan.",
486
450
  fix: "Check the feature's availability and try again when it is enabled for your team.",
@@ -497,10 +461,6 @@ export const ERROR_DOCS = {
497
461
  summary: "Proxy routes to external upstreams are not available on the Free plan.",
498
462
  fix: "Remove the proxy rules before publishing. Still stuck? POST /v1/feedback with the error code and request id.",
499
463
  },
500
- google_play_webhook_identity_not_configured: {
501
- summary: "Google Play Pub/Sub push authentication is not configured on this deployment.",
502
- fix: "Configure the exact push audience and dedicated Pub/Sub service-account email before enabling RTDN delivery.",
503
- },
504
464
  idempotency_conflict_in_progress: {
505
465
  summary: "Another request with the same Idempotency-Key is still in flight.",
506
466
  fix: "Wait for the original request to finish. Its response will be replayed.",
@@ -509,18 +469,6 @@ export const ERROR_DOCS = {
509
469
  summary: "The Idempotency-Key was reused with a different request body.",
510
470
  fix: "Use a fresh key for new requests. Keys bind to one exact request body.",
511
471
  },
512
- import_archive_not_uploaded: {
513
- summary: "The import has no uploaded archive yet.",
514
- fix: "Upload the archive to the import's upload target, then finalize.",
515
- },
516
- import_not_cancelable: {
517
- summary: "The import is past the point where it can be canceled.",
518
- fix: "Wait for the import to finish. Completed imports can be deleted as versions.",
519
- },
520
- import_not_waiting_for_archive: {
521
- summary: "The import is not waiting for an archive upload.",
522
- fix: "Check the import status. If the archive step is complete, continue to the next step.",
523
- },
524
472
  incumbent_controls_dns: {
525
473
  summary: "Another team controls this domain's DNS, so the operation is blocked.",
526
474
  fix: "Prove control by completing domain verification, or coordinate with the incumbent.",
@@ -538,8 +486,8 @@ export const ERROR_DOCS = {
538
486
  fix: "Retry the request. If it persists, contact support with the requestId.",
539
487
  },
540
488
  invalid_claim_token: {
541
- summary: "The claim token is invalid or does not match this space.",
542
- fix: "Use the exact claim URL returned by the publish that created the space.",
489
+ summary: "The space key is invalid or does not match this space.",
490
+ fix: "Use the exact space key or claim link returned by the publish that created the space.",
543
491
  },
544
492
  invalid_continuation_token: {
545
493
  summary: "The continuation token is invalid, expired, or already finished.",
@@ -565,10 +513,6 @@ export const ERROR_DOCS = {
565
513
  summary: "A file path in the request is not a valid space path.",
566
514
  fix: "Use forward-slash relative paths without traversal segments.",
567
515
  },
568
- invalid_google_play_webhook_identity: {
569
- summary: "The Google Play notification did not carry a valid Pub/Sub push identity.",
570
- fix: "Send the notification through the configured authenticated Pub/Sub push subscription.",
571
- },
572
516
  invalid_grant: {
573
517
  summary: "The authorization grant is invalid or was already used.",
574
518
  fix: "Restart the login flow to obtain a new grant.",
@@ -769,10 +713,6 @@ export const ERROR_DOCS = {
769
713
  summary: "The placement burstable option was used with an unsupported placement mode.",
770
714
  fix: 'Set placement.mode to "dedicated" before setting placement.burstable, or remove the burstable option.',
771
715
  },
772
- placement_mode_requires_plan: {
773
- summary: "The requested placement mode requires a plan entitlement.",
774
- fix: "Upgrade the team plan or choose shared placement.",
775
- },
776
716
  platform_tenant_immutable_client: {
777
717
  summary: "The tenant's provider client cannot be changed.",
778
718
  fix: "Create a new tenant with the desired provider client instead.",
@@ -781,10 +721,18 @@ export const ERROR_DOCS = {
781
721
  summary: "The requested key policy grants more than your own credential allows.",
782
722
  fix: "Request a policy that is a subset of the credential creating the key.",
783
723
  },
724
+ ingest_admission_exceeded: {
725
+ summary: "Too many concurrent uploads for this space.",
726
+ fix: "Wait for the Retry-After window, then resume; at most 4 uploads run per space at once.",
727
+ },
784
728
  principal_space_mismatch: {
785
729
  summary: "The space does not belong to the principal in the request.",
786
730
  fix: "Use the space's owning team or account in the request path.",
787
731
  },
732
+ promote_admission_exceeded: {
733
+ summary: "Too many archived files are being restored for this space right now.",
734
+ fix: "Retry shortly; restores are limited to protect the host.",
735
+ },
788
736
  provider_error: {
789
737
  summary: "The hosting provider reported an error.",
790
738
  fix: "Retry. If the error persists, contact support with the requestId.",
@@ -797,6 +745,10 @@ export const ERROR_DOCS = {
797
745
  summary: "A hosting-provider job timed out while processing this operation.",
798
746
  fix: "Retry the operation. If the provider is slow right now, wait and retry again.",
799
747
  },
748
+ provider_privilege: {
749
+ summary: "The hosting provider does not grant this client permission to change the setting.",
750
+ fix: "Leave the provider-owned setting unchanged or contact support to request the required provider capability.",
751
+ },
800
752
  provider_runtime_feature_unsupported: {
801
753
  summary: "The hosting provider does not support this runtime feature.",
802
754
  fix: "Check the feature's availability for this site's runtime.",
@@ -813,6 +765,10 @@ export const ERROR_DOCS = {
813
765
  summary: "The proxy upstream hostname cannot be resolved.",
814
766
  fix: "Verify the upstream DNS name resolves publicly, then publish again.",
815
767
  },
768
+ publish_session_expired: {
769
+ summary: "This publish session expired before it was finalized.",
770
+ fix: "Start a new publish; declared uploads are kept for 24 hours only.",
771
+ },
816
772
  publish_archive_compression_ratio_exceeded: {
817
773
  summary: "The archive's compression ratio is suspiciously high and was rejected.",
818
774
  fix: "Repackage the content without highly-compressible filler data.",
@@ -889,6 +845,14 @@ export const ERROR_DOCS = {
889
845
  summary: "The named job queue does not exist.",
890
846
  fix: "Check the queue name against the operator queue list.",
891
847
  },
848
+ response_headers_unservable: {
849
+ summary: "A file needs a response header that the platform cannot deliver for large files.",
850
+ fix: "Reduce the file below 100 MiB or drop the custom header.",
851
+ },
852
+ response_table_too_large: {
853
+ summary: "A compiled response table exceeded the 1 MiB per-file limit.",
854
+ fix: "This version has an unusually dense path set; contact support with the requestId.",
855
+ },
892
856
  rate_limited: {
893
857
  summary: "Too many requests were made in a short window.",
894
858
  fix: "Wait for the window in the Retry-After header to pass, then retry.",
@@ -905,6 +869,10 @@ export const ERROR_DOCS = {
905
869
  summary: "The resource changed after you loaded it, so the stale write was rejected.",
906
870
  fix: "Use the current resource returned in error.details.current, reapply the intended fields, and send its revision as expectedRevision.",
907
871
  },
872
+ review_item_claimed: {
873
+ summary: "Another reviewer already claimed this review item.",
874
+ fix: "Pick a different item, or ask the current assignee (error.details.assignedToUserId) to release it.",
875
+ },
908
876
  routing_rules_over_plan: {
909
877
  summary: "The published _redirects and _headers exceed the plan's routing-rule allowance.",
910
878
  fix: "Reduce the rule count. Still stuck? POST /v1/feedback with the error code and request id.",
@@ -925,10 +893,6 @@ export const ERROR_DOCS = {
925
893
  summary: "`sf dev` cannot run this runtime locally yet.",
926
894
  fix: "Run your framework's own dev server (`next dev`, `vite`, …) and publish with `sf publish` when you are ready.",
927
895
  },
928
- runtime_engine_zip_missing: {
929
- summary: "The runtime engine artifact is missing from this deployment.",
930
- fix: "Rebuild the control plane so dist/runtime-engine.zip exists.",
931
- },
932
896
  runtime_hostname_unassigned: {
933
897
  summary: "The hostname is not assigned to a runtime, so the request cannot be routed.",
934
898
  fix: `Verify the binding is active and DNS points at ${BRAND.productName}.`,
@@ -965,6 +929,10 @@ export const ERROR_DOCS = {
965
929
  summary: "The runtime operation referenced by this request was not found.",
966
930
  fix: "Re-request instructions and retry with a current operation id.",
967
931
  },
932
+ runtime_purge_failed: {
933
+ summary: "Content updated but the edge cache purge did not confirm.",
934
+ fix: "The runtime retries automatically; recent visitors may briefly see the previous version.",
935
+ },
968
936
  runtime_scope_forbidden: {
969
937
  summary: "The runtime token's scope does not cover this path or action.",
970
938
  fix: "Use the token minted for this exact session, space, and step.",
@@ -1059,19 +1027,15 @@ export const ERROR_DOCS = {
1059
1027
  },
1060
1028
  space_already_claimed: {
1061
1029
  summary: "The space was already claimed. A claim can only succeed once.",
1062
- fix: "If you published this space, exchange your claim token once at POST /v1/anonymous-claim/exchange. Keep publishing to the same spaceId with that durable API key. Otherwise ask the owner for access — do not publish a duplicate space.",
1063
- },
1064
- space_capacity_warming: {
1065
- summary: "This space is waiting on serving capacity that is being provisioned in the background.",
1066
- fix: "Wait for the Retry-After window, then send the same publish again. No version was created and no files were uploaded.",
1030
+ fix: "If you published this space, exchange your space key once at POST /v1/claim/exchange. Keep publishing to the same spaceId with that durable API key. Otherwise ask the owner for access — do not publish a duplicate space.",
1067
1031
  },
1068
1032
  space_claim_unavailable: {
1069
1033
  summary: "This space cannot be claimed.",
1070
- fix: "Only anonymous, unexpired spaces with a valid claim token can be claimed.",
1034
+ fix: "Only anonymous, unexpired spaces with a valid space key can be claimed.",
1071
1035
  },
1072
1036
  space_claimed_credential_available: {
1073
- summary: "The space was claimed and its owner kept agent publishing enabled; this claim token now works exactly once as an exchange voucher.",
1074
- fix: "POST /v1/anonymous-claim/exchange with this claim token as bearer auth. Save the returned API key (e.g. .spacefast/state.json), then retry the request with it as Authorization: Bearer. Do not publish a new space.",
1037
+ summary: "The space was claimed and its owner kept agent publishing enabled; this space key now works exactly once as an exchange voucher.",
1038
+ fix: "POST /v1/claim/exchange with this space key as bearer auth. Save the returned API key (e.g. .spacefast/state.json), then retry the request with it as Authorization: Bearer. Do not publish a new space.",
1075
1039
  },
1076
1040
  space_disabled: {
1077
1041
  summary: "The space is disabled and cannot be served or modified.",
@@ -1093,30 +1057,6 @@ export const ERROR_DOCS = {
1093
1057
  summary: "The space is missing its persisted default hostname identity.",
1094
1058
  fix: "Contact support. Retrying cannot safely recreate a permanent hostname identity.",
1095
1059
  },
1096
- space_import_archive_invalid: {
1097
- summary: `The import archive is not a valid ${BRAND.productName} export.`,
1098
- fix: "Export the source space again and upload the unmodified archive.",
1099
- },
1100
- space_import_archive_too_large: {
1101
- summary: "The import archive exceeds the maximum allowed size.",
1102
- fix: "Reduce the export size, or import versions individually.",
1103
- },
1104
- space_import_config_only_archive: {
1105
- summary: "The archive contains only configuration and cannot be imported as content.",
1106
- fix: "Include files in the export, or apply the configuration directly.",
1107
- },
1108
- space_import_runtime_schema_unsupported: {
1109
- summary: "The export was produced by an unsupported runtime schema version.",
1110
- fix: "Re-export from a current runtime, or upgrade before exporting.",
1111
- },
1112
- space_move_would_strand_zero_data: {
1113
- summary: "The Space runs Zero, whose database and object storage live on the site the Space is placed on. Relocating it would strand that data on the old site, so the move is refused.",
1114
- fix: "Nothing was moved and no data was lost. Migrating Zero data between sites is not implemented. Contact support if the space genuinely has to be relocated.",
1115
- },
1116
- space_moving: {
1117
- summary: "The space is moving between sites, which blocks this operation.",
1118
- fix: "Wait for the move to finish, then retry.",
1119
- },
1120
1060
  space_no_live_version: {
1121
1061
  summary: "The space has no live version, so this operation has nothing to act on.",
1122
1062
  fix: "Publish a version to the space, then retry.",
@@ -1174,36 +1114,36 @@ export const ERROR_DOCS = {
1174
1114
  fix: "Target a static-runtime space.",
1175
1115
  },
1176
1116
  storage_auth_required: {
1177
- summary: "This storage operation requires an authenticated Zero user.",
1178
- fix: "Sign in to the hosted Zero app, then retry the storage operation.",
1117
+ summary: "This storage operation requires an authenticated visitor.",
1118
+ fix: "Sign in to the space, then retry the storage operation.",
1179
1119
  },
1180
1120
  storage_bucket_unavailable: {
1181
1121
  summary: "No usable internal storage bucket is configured for this operation.",
1182
1122
  fix: "Check the storage bucket registry and credential environment references, then retry.",
1183
1123
  },
1184
1124
  storage_content_blocked: {
1185
- summary: "The object contains executable or active web content that Zero storage does not serve.",
1125
+ summary: "The object contains executable or active web content that Spacefast storage does not serve.",
1186
1126
  fix: "Upload a passive file type without HTML, script, SVG, PHP, or executable bytes.",
1187
1127
  },
1188
1128
  storage_delete_forbidden: {
1189
- summary: "The current Zero user did not upload this object.",
1129
+ summary: "The current visitor did not upload this object.",
1190
1130
  fix: "Sign in as the original uploader before deleting the object.",
1191
1131
  },
1192
1132
  storage_empty_file: {
1193
- summary: "The Zero storage upload is empty.",
1133
+ summary: "The storage upload is empty.",
1194
1134
  fix: "Upload a file containing at least 1 byte.",
1195
1135
  },
1196
1136
  storage_file_too_large: {
1197
- summary: "The Zero storage object exceeds the 5 MiB file limit.",
1137
+ summary: "The storage object exceeds the 5 MiB file limit.",
1198
1138
  fix: "Upload a smaller object.",
1199
1139
  },
1200
1140
  storage_key_invalid: {
1201
- summary: "The Zero storage key is malformed.",
1202
- fix: "Use the public or private object key returned by storage.upload.",
1141
+ summary: "The storage object id is malformed.",
1142
+ fix: "Use the 32-character object id returned by storage.upload.",
1203
1143
  },
1204
1144
  storage_object_not_found: {
1205
- summary: "The requested Zero storage object does not exist.",
1206
- fix: "Check the object key or upload the object again.",
1145
+ summary: "The requested storage object does not exist.",
1146
+ fix: "Check the object id or upload the object again.",
1207
1147
  },
1208
1148
  storage_quota_already_unlimited: {
1209
1149
  summary: "The team already has unlimited storage.",
@@ -1215,16 +1155,12 @@ export const ERROR_DOCS = {
1215
1155
  },
1216
1156
  storage_quota_exceeded: {
1217
1157
  summary: "The operation would exceed its storage allowance.",
1218
- fix: "Delete unused Zero objects or old versions, reduce the upload size, then retry.",
1158
+ fix: "Delete unused storage objects or old versions, reduce the upload size, then retry.",
1219
1159
  },
1220
1160
  storage_unavailable: {
1221
- summary: "The Zero runtime cannot read or persist object storage.",
1161
+ summary: "The runtime cannot read or persist object storage.",
1222
1162
  fix: "Retry on a healthy runtime. Contact support if the failure persists.",
1223
1163
  },
1224
- storage_usage_unavailable: {
1225
- summary: "Storage usage cannot be computed right now.",
1226
- fix: "Retry shortly.",
1227
- },
1228
1164
  stripe_error: {
1229
1165
  summary: "The billing provider reported an error.",
1230
1166
  fix: "Retry. If it persists, check the team's billing settings.",
@@ -1305,10 +1241,6 @@ export const ERROR_DOCS = {
1305
1241
  summary: "Spaces cannot be transferred across tenants.",
1306
1242
  fix: "Export the space and import it under the target tenant instead.",
1307
1243
  },
1308
- transfer_move_failed: {
1309
- summary: "The space move backing this transfer failed.",
1310
- fix: "Retry the transfer. If it persists, contact support with the transfer id.",
1311
- },
1312
1244
  transfer_not_cancelable: {
1313
1245
  summary: "The transfer is past the point where it can be canceled.",
1314
1246
  fix: "Wait for the transfer to finish.",
@@ -1361,10 +1293,6 @@ export const ERROR_DOCS = {
1361
1293
  summary: "A request field failed validation.",
1362
1294
  fix: "Fix the field referenced by pointer and details, then retry.",
1363
1295
  },
1364
- variable_cascade_failed: {
1365
- summary: "Republishing spaces after a variable change failed.",
1366
- fix: "Check the operation's diagnostics. Live content keeps serving the previous values.",
1367
- },
1368
1296
  verification_required: {
1369
1297
  summary: "Domain verification is required before this operation.",
1370
1298
  fix: "Add the verification DNS record, then run verify on the domain.",
@@ -1461,10 +1389,6 @@ export const ERROR_DOCS = {
1461
1389
  summary: "WHOIS data is unavailable for this domain right now.",
1462
1390
  fix: "Retry later.",
1463
1391
  },
1464
- zero_activating: {
1465
- summary: "Zero endpoints are activating.",
1466
- fix: "Wait for the dedicated runtime move to complete, then retry the request.",
1467
- },
1468
1392
  zero_ai_unavailable: {
1469
1393
  summary: "The local Zero AI service is not configured.",
1470
1394
  fix: "Configure an AI handler in the project, then restart `sf dev` before calling the AI service.",
@@ -1701,10 +1625,6 @@ export const ERROR_DOCS = {
1701
1625
  summary: "The Zero endpoints payload exceeds the supported endpoint count.",
1702
1626
  fix: "Reduce the project to at most 128 Zero endpoints, then publish again.",
1703
1627
  },
1704
- zero_requires_dedicated_plan: {
1705
- summary: "Zero endpoints require a plan with dedicated runtime isolation.",
1706
- fix: "Upgrade the team to a plan that includes Zero and dedicated runtime, then publish again.",
1707
- },
1708
1628
  zero_envelope_encode_failed: {
1709
1629
  summary: "The PHP bridge cannot encode the Zero runner request envelope.",
1710
1630
  fix: "Retry with valid request data. Report the issue if it persists.",
@@ -1877,14 +1797,6 @@ export const ERROR_DOCS = {
1877
1797
  summary: "The Zero run-handler payload exceeds the supported handler count.",
1878
1798
  fix: "Reduce the project to at most 128 Zero run handlers, then publish again.",
1879
1799
  },
1880
- zero_shopify_unavailable: {
1881
- summary: "The local Zero Shopify service is not configured.",
1882
- fix: "Configure a Shopify handler in the project, then restart `sf dev` before calling the Shopify service.",
1883
- },
1884
- wp_cloud_anonymous_pool_required: {
1885
- summary: "This operation requires an anonymous pool site.",
1886
- fix: "Target a pool-class site.",
1887
- },
1888
1800
  wp_cloud_assignment_kind_mismatch: {
1889
1801
  summary: "The domain assignment's kind does not match the site.",
1890
1802
  fix: "Verify the assignment against the site's hosting kind.",
@@ -0,0 +1,16 @@
1
+ /** The original fetch implementation captured before a test installs a stub. */
2
+ export declare const realFetch: typeof fetch;
3
+ export type FetchStubHandler = (request: Request) => Response | Promise<Response>;
4
+ export declare function stubFetch(handler: FetchStubHandler): void;
5
+ export declare function restoreFetch(): void;
6
+ /** Build the RFC 9457 problem response served by Spacefast APIs. */
7
+ export declare function problemResponse(input: {
8
+ code: string;
9
+ status: number;
10
+ detail: string;
11
+ pointer?: string;
12
+ details?: unknown;
13
+ suggestions?: readonly string[];
14
+ next?: readonly string[];
15
+ requestId?: string;
16
+ }): Response;
@@ -0,0 +1,24 @@
1
+ import { errorDocsUrl, errorTitle, PROBLEM_CONTENT_TYPE } from "../contracts/error-codes.js";
2
+ /** The original fetch implementation captured before a test installs a stub. */
3
+ export const realFetch = globalThis.fetch;
4
+ export function stubFetch(handler) {
5
+ globalThis.fetch = (async (input, init) => handler(input instanceof Request && init === undefined ? input : new Request(input, init)));
6
+ }
7
+ export function restoreFetch() {
8
+ globalThis.fetch = realFetch;
9
+ }
10
+ /** Build the RFC 9457 problem response served by Spacefast APIs. */
11
+ export function problemResponse(input) {
12
+ return new Response(JSON.stringify({
13
+ type: errorDocsUrl(input.code),
14
+ title: errorTitle(input.code),
15
+ status: input.status,
16
+ detail: input.detail,
17
+ code: input.code,
18
+ ...(input.pointer === undefined ? {} : { pointer: input.pointer }),
19
+ ...(input.details === undefined ? {} : { details: input.details }),
20
+ ...(input.suggestions === undefined ? {} : { suggestions: input.suggestions }),
21
+ ...(input.next === undefined ? {} : { next: input.next }),
22
+ requestId: input.requestId ?? "req_test",
23
+ }), { status: input.status, headers: { "content-type": PROBLEM_CONTENT_TYPE } });
24
+ }
@@ -8,6 +8,7 @@ const PACKAGE_MANAGER_ONLY_FILES = new Set([
8
8
  "pnpm-lock.yaml",
9
9
  "yarn.lock",
10
10
  "package-lock.json",
11
+ "npm-shrinkwrap.json",
11
12
  "Gemfile",
12
13
  "Gemfile.lock",
13
14
  ]);
@@ -711,7 +712,7 @@ function jekyllBuildCommand(files) {
711
712
  function frameworkInstallCommand(frameworkPreset, files) {
712
713
  return frameworkPreset === "jekyll" ? jekyllInstallCommand(files) : null;
713
714
  }
714
- function installCommandForManager(packageManager, packageJson) {
715
+ function installCommandForManager(packageManager, packageJson, files) {
715
716
  switch (packageManager) {
716
717
  case "bun":
717
718
  return "bun install --frozen-lockfile";
@@ -720,7 +721,11 @@ function installCommandForManager(packageManager, packageJson) {
720
721
  case "yarn":
721
722
  return "corepack enable && (yarn install --immutable || yarn install --frozen-lockfile)";
722
723
  default:
723
- return packageJson ? "npm ci" : null;
724
+ if (!packageJson)
725
+ return null;
726
+ return files.has("package-lock.json") || files.has("npm-shrinkwrap.json")
727
+ ? "npm ci"
728
+ : "npm install";
724
729
  }
725
730
  }
726
731
  function buildCommandForDefaults(packageJson, run, frameworkPreset, files) {
@@ -740,7 +745,7 @@ function packageDefaults(input, files) {
740
745
  const run = packageManager === "npm" ? "npm run" : packageManager === "bun" ? "bun run" : packageManager;
741
746
  const frameworkPreset = detectFrameworkPreset(files, dependencies);
742
747
  const installCommands = [
743
- installCommandForManager(packageManager, packageJson),
748
+ installCommandForManager(packageManager, packageJson, files),
744
749
  frameworkInstallCommand(frameworkPreset, files),
745
750
  ].filter((command) => Boolean(command));
746
751
  const jekyllConfig = input.jekyllYaml
@@ -768,6 +773,7 @@ export function detectBuildSettings(input) {
768
773
  files.has("pnpm-lock.yaml") ? "pnpm-lock.yaml" : null,
769
774
  files.has("yarn.lock") ? "yarn.lock" : null,
770
775
  files.has("package-lock.json") ? "package-lock.json" : null,
776
+ files.has("npm-shrinkwrap.json") ? "npm-shrinkwrap.json" : null,
771
777
  files.has("Gemfile") ? "Gemfile" : null,
772
778
  files.has("Gemfile.lock") ? "Gemfile.lock" : null,
773
779
  files.has("_config.yml") ? "_config.yml" : null,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
3
+ * (which canonicalizes key order) digests identically to one freshly built in
4
+ * JS. Arrays keep their original order since order is meaningful there.
5
+ *
6
+ * Every digest that has to agree across the control plane, the CLI and the
7
+ * capsule compiler is built on this exact spelling — change it and stored
8
+ * hashes stop matching freshly computed ones.
9
+ */
10
+ export declare function canonicalJson(value: unknown): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
3
+ * (which canonicalizes key order) digests identically to one freshly built in
4
+ * JS. Arrays keep their original order since order is meaningful there.
5
+ *
6
+ * Every digest that has to agree across the control plane, the CLI and the
7
+ * capsule compiler is built on this exact spelling — change it and stored
8
+ * hashes stop matching freshly computed ones.
9
+ */
10
+ export function canonicalJson(value) {
11
+ if (Array.isArray(value)) {
12
+ return `[${value.map(canonicalJson).join(",")}]`;
13
+ }
14
+ if (value && typeof value === "object") {
15
+ return `{${Object.entries(value)
16
+ .toSorted(([left], [right]) => left.localeCompare(right))
17
+ .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
18
+ .join(",")}}`;
19
+ }
20
+ return JSON.stringify(value);
21
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * How a client addresses Cast over Phoenix. Both Spacefast clients — the
3
+ * dashboard's notice subscription and the injected Collab SDK — build their
4
+ * socket and their topics here, and nowhere else.
5
+ */
6
+ /**
7
+ * The Phoenix topic a Cast room's messages travel on.
8
+ *
9
+ * Phoenix routes a broadcast by the *literal* topic string. Cast broadcasts
10
+ * anything computed outside a channel process — Space notices, REST-published
11
+ * room events — to the topic `Cast.RoomTopic.for/2` builds, so a client that
12
+ * encodes the room key any other way joins a real and permanently silent room.
13
+ * Both sides therefore encode exactly once, here and in that module: the `~n`
14
+ * table first (it survives round-tripping a key that itself contains `~` or
15
+ * `%`), then `encodeURIComponent`.
16
+ *
17
+ * Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
18
+ * strings, and on the other side `room_channel_notice_test.exs` and
19
+ * `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
20
+ * Cast broadcast arrives on them.
21
+ */
22
+ export declare function castRoomTopic(resourceKey: string, roomKey: string): string;
23
+ /**
24
+ * Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
25
+ * so a configured URL that already carries the suffix would otherwise become
26
+ * `/websocket/websocket`.
27
+ */
28
+ export declare function phoenixEndpoint(url: string): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * How a client addresses Cast over Phoenix. Both Spacefast clients — the
3
+ * dashboard's notice subscription and the injected Collab SDK — build their
4
+ * socket and their topics here, and nowhere else.
5
+ */
6
+ /**
7
+ * `~n` escapes, the inverse of `CastWeb.ApiContext.decode_path_segment/1`.
8
+ * `~` goes first in intent: the map is applied in one pass, so an already
9
+ * escaped marker can never be escaped twice.
10
+ */
11
+ const ESCAPED = new Map([
12
+ ["~", "~0"],
13
+ ["/", "~1"],
14
+ [":", "~2"],
15
+ ["\\", "~3"],
16
+ ["%", "~4"],
17
+ ["\0", "~5"],
18
+ ]);
19
+ /**
20
+ * The Phoenix topic a Cast room's messages travel on.
21
+ *
22
+ * Phoenix routes a broadcast by the *literal* topic string. Cast broadcasts
23
+ * anything computed outside a channel process — Space notices, REST-published
24
+ * room events — to the topic `Cast.RoomTopic.for/2` builds, so a client that
25
+ * encodes the room key any other way joins a real and permanently silent room.
26
+ * Both sides therefore encode exactly once, here and in that module: the `~n`
27
+ * table first (it survives round-tripping a key that itself contains `~` or
28
+ * `%`), then `encodeURIComponent`.
29
+ *
30
+ * Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
31
+ * strings, and on the other side `room_channel_notice_test.exs` and
32
+ * `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
33
+ * Cast broadcast arrives on them.
34
+ */
35
+ export function castRoomTopic(resourceKey, roomKey) {
36
+ let escaped = "";
37
+ for (const character of roomKey)
38
+ escaped += ESCAPED.get(character) ?? character;
39
+ return `room:${resourceKey}:${encodeURIComponent(escaped)}`;
40
+ }
41
+ /**
42
+ * Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
43
+ * so a configured URL that already carries the suffix would otherwise become
44
+ * `/websocket/websocket`.
45
+ */
46
+ export function phoenixEndpoint(url) {
47
+ const parsed = new URL(url);
48
+ if (parsed.pathname.endsWith("/websocket")) {
49
+ parsed.pathname = parsed.pathname.slice(0, -"/websocket".length);
50
+ }
51
+ return parsed.href.replace(/\/$/, "");
52
+ }