@spacefast/common 0.0.26 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/agents/connect-targets.d.ts +6 -146
  2. package/dist/agents/connect-targets.js +63 -295
  3. package/dist/agents/private-key-oauth.d.ts +31 -1
  4. package/dist/agents/private-key-oauth.js +124 -33
  5. package/dist/agents/registry/core.d.ts +292 -0
  6. package/dist/agents/registry/core.js +1388 -0
  7. package/dist/agents/registry/deeplink-probes.d.ts +23 -0
  8. package/dist/agents/registry/deeplink-probes.js +18 -0
  9. package/dist/agents/registry/node.d.ts +56 -0
  10. package/dist/agents/registry/node.js +339 -0
  11. package/dist/brand-assets-build.js +1 -2
  12. package/dist/brand-assets.js +4 -7
  13. package/dist/brand.d.ts +17 -0
  14. package/dist/brand.js +24 -4
  15. package/dist/config/domains.d.ts +55 -28
  16. package/dist/config/domains.js +85 -41
  17. package/dist/config/index.js +1 -2
  18. package/dist/contracts/abuse.js +9 -24
  19. package/dist/contracts/access-profiles.d.ts +86 -0
  20. package/dist/contracts/access-profiles.js +89 -0
  21. package/dist/contracts/access.d.ts +21 -27
  22. package/dist/contracts/access.js +33 -64
  23. package/dist/contracts/activity.d.ts +18 -1
  24. package/dist/contracts/activity.js +70 -26
  25. package/dist/contracts/analytics.js +8 -10
  26. package/dist/contracts/api-keys.d.ts +77 -52
  27. package/dist/contracts/api-keys.js +44 -24
  28. package/dist/contracts/application-journal.d.ts +216 -0
  29. package/dist/contracts/application-journal.js +167 -0
  30. package/dist/contracts/auth.d.ts +90 -17
  31. package/dist/contracts/auth.js +86 -17
  32. package/dist/contracts/beta.d.ts +9 -4
  33. package/dist/contracts/beta.js +20 -16
  34. package/dist/contracts/billing.js +7 -11
  35. package/dist/contracts/bootstrap.d.ts +791 -0
  36. package/dist/contracts/bootstrap.js +51 -0
  37. package/dist/contracts/builds.d.ts +186 -7
  38. package/dist/contracts/builds.js +75 -21
  39. package/dist/contracts/channels.d.ts +43 -0
  40. package/dist/contracts/channels.js +25 -9
  41. package/dist/contracts/cli.d.ts +7 -0
  42. package/dist/contracts/cli.js +6 -0
  43. package/dist/contracts/collab-public.d.ts +1 -1
  44. package/dist/contracts/collab-public.js +5 -8
  45. package/dist/contracts/comments.d.ts +45 -35
  46. package/dist/contracts/comments.js +107 -77
  47. package/dist/contracts/commerce.d.ts +165 -0
  48. package/dist/contracts/commerce.js +146 -0
  49. package/dist/contracts/common.d.ts +3 -3
  50. package/dist/contracts/common.js +18 -29
  51. package/dist/contracts/content-contract-verification.d.ts +521 -0
  52. package/dist/contracts/content-contract-verification.js +320 -0
  53. package/dist/contracts/content-platform-fixture.d.ts +1278 -0
  54. package/dist/contracts/content-platform-fixture.js +44 -0
  55. package/dist/contracts/content-program.d.ts +994 -0
  56. package/dist/contracts/content-program.js +658 -0
  57. package/dist/contracts/content-sync.d.ts +290 -0
  58. package/dist/contracts/content-sync.js +167 -0
  59. package/dist/contracts/content.d.ts +432 -0
  60. package/dist/contracts/content.js +303 -0
  61. package/dist/contracts/continuation.d.ts +17 -1
  62. package/dist/contracts/continuation.js +23 -23
  63. package/dist/contracts/countries.js +3 -4
  64. package/dist/contracts/crons.d.ts +17 -0
  65. package/dist/contracts/crons.js +33 -15
  66. package/dist/contracts/dashboard-prefs.d.ts +56 -0
  67. package/dist/contracts/dashboard-prefs.js +51 -0
  68. package/dist/contracts/device-auth.d.ts +234 -37
  69. package/dist/contracts/device-auth.js +267 -53
  70. package/dist/contracts/docs.d.ts +6 -10
  71. package/dist/contracts/docs.js +5 -13
  72. package/dist/contracts/domains.d.ts +499 -102
  73. package/dist/contracts/domains.js +169 -58
  74. package/dist/contracts/email-preferences.d.ts +3 -3
  75. package/dist/contracts/email-preferences.js +6 -7
  76. package/dist/contracts/enums.d.ts +26 -26
  77. package/dist/contracts/enums.js +61 -54
  78. package/dist/contracts/error-code-meta.d.ts +269 -7
  79. package/dist/contracts/error-code-meta.js +80 -5
  80. package/dist/contracts/error-codes.d.ts +5 -5
  81. package/dist/contracts/error-codes.js +78 -16
  82. package/dist/contracts/events.d.ts +100 -20
  83. package/dist/contracts/events.js +107 -20
  84. package/dist/contracts/execution.d.ts +47 -66
  85. package/dist/contracts/execution.js +51 -64
  86. package/dist/contracts/feature-lifecycle.d.ts +70 -14
  87. package/dist/contracts/feature-lifecycle.js +86 -15
  88. package/dist/contracts/features.d.ts +100 -37
  89. package/dist/contracts/features.js +21 -27
  90. package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
  91. package/dist/contracts/frame-session.d.ts +192 -0
  92. package/dist/contracts/frame-session.js +164 -0
  93. package/dist/contracts/functions.d.ts +218 -211
  94. package/dist/contracts/functions.js +273 -260
  95. package/dist/contracts/grant-copy.d.ts +43 -0
  96. package/dist/contracts/grant-copy.js +44 -0
  97. package/dist/contracts/grants.d.ts +64 -56
  98. package/dist/contracts/grants.js +124 -23
  99. package/dist/contracts/ids.d.ts +4 -1
  100. package/dist/contracts/ids.js +4 -1
  101. package/dist/contracts/internal.d.ts +5 -4
  102. package/dist/contracts/internal.js +12 -3
  103. package/dist/contracts/mcp.d.ts +17 -0
  104. package/dist/contracts/mcp.js +23 -0
  105. package/dist/contracts/me.d.ts +6 -1
  106. package/dist/contracts/me.js +12 -6
  107. package/dist/contracts/notifications.d.ts +27 -29
  108. package/dist/contracts/notifications.js +9 -4
  109. package/dist/contracts/oauth-resources.d.ts +112 -10
  110. package/dist/contracts/oauth-resources.js +116 -11
  111. package/dist/contracts/oauth-scope-actions.d.ts +90 -0
  112. package/dist/contracts/oauth-scope-actions.js +126 -0
  113. package/dist/contracts/operations.d.ts +27 -4
  114. package/dist/contracts/operations.js +11 -5
  115. package/dist/contracts/pages.d.ts +29 -5
  116. package/dist/contracts/pages.js +22 -5
  117. package/dist/contracts/partner-notifications.d.ts +3 -0
  118. package/dist/contracts/partner-notifications.js +65 -0
  119. package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
  120. package/dist/contracts/{platform.js → partner.js} +27 -40
  121. package/dist/contracts/plan-policy.d.ts +1 -1
  122. package/dist/contracts/principal-assertion.d.ts +43 -0
  123. package/dist/contracts/principal-assertion.js +67 -0
  124. package/dist/contracts/principals.d.ts +73 -0
  125. package/dist/contracts/principals.js +59 -0
  126. package/dist/contracts/privacy.d.ts +11 -0
  127. package/dist/contracts/privacy.js +17 -0
  128. package/dist/contracts/problem-document.js +3 -4
  129. package/dist/contracts/publish-archive.d.ts +116 -18
  130. package/dist/contracts/publish-archive.js +6 -3
  131. package/dist/contracts/push-new.d.ts +4 -4
  132. package/dist/contracts/quotas.js +3 -3
  133. package/dist/contracts/realtime.d.ts +52 -0
  134. package/dist/contracts/realtime.js +54 -0
  135. package/dist/contracts/repository-connections.d.ts +1 -1
  136. package/dist/contracts/repository-connections.js +6 -2
  137. package/dist/contracts/resources.d.ts +105 -14
  138. package/dist/contracts/resources.js +145 -21
  139. package/dist/contracts/route-inventory.d.ts +358 -0
  140. package/dist/contracts/route-inventory.js +141 -0
  141. package/dist/contracts/runtime-api.d.ts +112 -35
  142. package/dist/contracts/runtime-api.js +86 -66
  143. package/dist/contracts/runtime-app.d.ts +11 -21
  144. package/dist/contracts/runtime-app.js +39 -24
  145. package/dist/contracts/runtime-components.d.ts +143 -0
  146. package/dist/contracts/runtime-components.js +199 -0
  147. package/dist/contracts/runtime-db.d.ts +45 -19
  148. package/dist/contracts/runtime-db.js +85 -20
  149. package/dist/contracts/runtime-purge.js +3 -5
  150. package/dist/contracts/runtime-services.d.ts +43 -75
  151. package/dist/contracts/runtime-services.js +29 -49
  152. package/dist/contracts/runtime-storage.d.ts +14 -14
  153. package/dist/contracts/runtime-storage.js +28 -13
  154. package/dist/contracts/sf-config-v1.d.ts +14 -18
  155. package/dist/contracts/slugs.js +13 -4
  156. package/dist/contracts/space-config.d.ts +235 -28
  157. package/dist/contracts/space-config.js +246 -91
  158. package/dist/contracts/spaces.d.ts +360 -255
  159. package/dist/contracts/spaces.js +190 -122
  160. package/dist/contracts/superadmin-activity.d.ts +3 -43
  161. package/dist/contracts/superadmin-activity.js +6 -30
  162. package/dist/contracts/superadmin-emails.d.ts +1 -0
  163. package/dist/contracts/superadmin-emails.js +17 -1
  164. package/dist/contracts/superadmin-runtime.d.ts +199 -0
  165. package/dist/contracts/superadmin-runtime.js +141 -0
  166. package/dist/contracts/superadmin-search.d.ts +22 -2
  167. package/dist/contracts/superadmin-search.js +14 -6
  168. package/dist/contracts/superadmin-spaces.d.ts +435 -186
  169. package/dist/contracts/superadmin-spaces.js +131 -25
  170. package/dist/contracts/superadmin-teams.d.ts +3 -0
  171. package/dist/contracts/superadmin-teams.js +1 -2
  172. package/dist/contracts/superadmin-tenants.d.ts +627 -6
  173. package/dist/contracts/superadmin-tenants.js +32 -7
  174. package/dist/contracts/superadmin.d.ts +98 -16
  175. package/dist/contracts/superadmin.js +63 -10
  176. package/dist/contracts/tags.d.ts +85 -58
  177. package/dist/contracts/tags.js +44 -34
  178. package/dist/contracts/teams.d.ts +72 -9
  179. package/dist/contracts/teams.js +84 -25
  180. package/dist/contracts/tenants.d.ts +135 -0
  181. package/dist/contracts/tenants.js +120 -0
  182. package/dist/contracts/test-triggers.d.ts +70 -0
  183. package/dist/contracts/test-triggers.js +116 -0
  184. package/dist/contracts/theme-json.js +3 -8
  185. package/dist/contracts/transfers.d.ts +1 -0
  186. package/dist/contracts/transfers.js +1 -0
  187. package/dist/contracts/usage.d.ts +209 -0
  188. package/dist/contracts/usage.js +124 -12
  189. package/dist/contracts/variables.d.ts +2 -2
  190. package/dist/contracts/variables.js +9 -13
  191. package/dist/contracts/webhooks.d.ts +13 -5
  192. package/dist/contracts/webhooks.js +3 -1
  193. package/dist/contracts/zero.d.ts +57 -64
  194. package/dist/contracts/zero.js +68 -66
  195. package/dist/dashboard-paths/index.d.ts +24 -21
  196. package/dist/dashboard-paths/index.js +35 -47
  197. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
  198. package/dist/dashboard-paths/route-reservations.generated.js +36 -0
  199. package/dist/docs/agent-arrival.d.ts +92 -0
  200. package/dist/docs/agent-arrival.js +227 -0
  201. package/dist/docs/agent-output-policy.d.ts +6 -0
  202. package/dist/docs/agent-output-policy.js +22 -0
  203. package/dist/docs/agent-prose.d.ts +11 -17
  204. package/dist/docs/agent-prose.js +62 -37
  205. package/dist/docs/agent-setup.d.ts +72 -41
  206. package/dist/docs/agent-setup.js +336 -436
  207. package/dist/docs/catalog.d.ts +147 -89
  208. package/dist/docs/catalog.js +157 -42
  209. package/dist/docs/error-docs.d.ts +2098 -1
  210. package/dist/docs/error-docs.js +281 -16
  211. package/dist/docs/index-build.d.ts +0 -1
  212. package/dist/docs/index-build.js +2 -3
  213. package/dist/docs/page-context-prompt.d.ts +77 -0
  214. package/dist/docs/page-context-prompt.js +131 -0
  215. package/dist/docs/reference-urls.d.ts +46 -0
  216. package/dist/docs/reference-urls.js +79 -0
  217. package/dist/docs/search.d.ts +6 -8
  218. package/dist/docs/search.js +10 -13
  219. package/dist/docs/skill-distribution.d.ts +4 -1
  220. package/dist/docs/skill-distribution.js +10 -5
  221. package/dist/docs/skills.d.ts +117 -117
  222. package/dist/docs/skills.js +120 -72
  223. package/dist/docs/start-prompts.d.ts +45 -0
  224. package/dist/docs/start-prompts.js +62 -0
  225. package/dist/domain-exploration.js +9 -13
  226. package/dist/og-template.d.ts +56 -0
  227. package/dist/og-template.js +56 -0
  228. package/dist/slug-policy/index.js +5 -11
  229. package/dist/utils/browser-credential.d.ts +13 -6
  230. package/dist/utils/browser-credential.js +14 -7
  231. package/dist/utils/build-settings.d.ts +16 -0
  232. package/dist/utils/build-settings.js +63 -17
  233. package/dist/utils/canonical-json.d.ts +3 -5
  234. package/dist/utils/canonical-json.js +3 -5
  235. package/dist/utils/cast-transport.d.ts +5 -17
  236. package/dist/utils/cast-transport.js +6 -19
  237. package/dist/utils/claim-token.js +1 -2
  238. package/dist/utils/comment-avatars.js +4 -6
  239. package/dist/utils/concurrency.js +2 -3
  240. package/dist/utils/content-type.d.ts +7 -6
  241. package/dist/utils/content-type.js +73 -51
  242. package/dist/utils/credential-policy.d.ts +50 -6
  243. package/dist/utils/credential-policy.js +123 -57
  244. package/dist/utils/egress-policy.fixtures.json +1 -1
  245. package/dist/utils/generate-space-name.d.ts +3 -8
  246. package/dist/utils/generate-space-name.js +3 -8
  247. package/dist/utils/grants.d.ts +0 -1
  248. package/dist/utils/grants.js +4 -7
  249. package/dist/utils/gravatar.d.ts +2 -4
  250. package/dist/utils/gravatar.js +6 -12
  251. package/dist/utils/id-hints.js +14 -23
  252. package/dist/utils/idempotency.d.ts +3 -3
  253. package/dist/utils/idempotency.js +6 -7
  254. package/dist/utils/local-space-state.d.ts +15 -15
  255. package/dist/utils/local-space-state.js +34 -23
  256. package/dist/utils/oauth-signed-query.d.ts +3 -5
  257. package/dist/utils/oauth-signed-query.js +6 -9
  258. package/dist/utils/one-shot-replay.d.ts +3 -5
  259. package/dist/utils/one-shot-replay.js +3 -5
  260. package/dist/utils/page-colors.d.ts +4 -10
  261. package/dist/utils/page-colors.js +11 -35
  262. package/dist/utils/page-fonts.js +6 -11
  263. package/dist/utils/page-preview.d.ts +0 -1
  264. package/dist/utils/page-preview.js +0 -1
  265. package/dist/utils/pages.d.ts +22 -11
  266. package/dist/utils/pages.js +70 -30
  267. package/dist/utils/privacy-regions.d.ts +19 -0
  268. package/dist/utils/privacy-regions.js +91 -0
  269. package/dist/utils/publish-form-data.d.ts +4 -5
  270. package/dist/utils/publish-form-data.js +2 -3
  271. package/dist/utils/publish-policy.d.ts +10 -23
  272. package/dist/utils/publish-policy.js +63 -116
  273. package/dist/utils/query-client.d.ts +2 -0
  274. package/dist/utils/query-client.js +10 -1
  275. package/dist/utils/runtime-paths.d.ts +4 -4
  276. package/dist/utils/runtime-paths.js +6 -8
  277. package/dist/utils/runtime-upload.d.ts +4 -0
  278. package/dist/utils/runtime-upload.js +5 -0
  279. package/dist/utils/secure-local-file.d.ts +3 -3
  280. package/dist/utils/secure-local-file.js +18 -24
  281. package/dist/utils/space-config.d.ts +11 -14
  282. package/dist/utils/space-config.js +10 -16
  283. package/dist/utils/static-runtime-policy.d.ts +5 -8
  284. package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
  285. package/dist/utils/static-runtime-policy.generated.js +70 -0
  286. package/dist/utils/static-runtime-policy.js +17 -84
  287. package/dist/utils/storage-policy.js +1 -2
  288. package/dist/utils/upload-session.js +8 -14
  289. package/dist/utils/version-path.js +11 -19
  290. package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
  291. package/dist/utils/wpcom-auth-redirects.js +1 -4
  292. package/dist/vocabulary.d.ts +7 -7
  293. package/dist/vocabulary.js +30 -16
  294. package/package.json +14 -2
  295. package/dist/agents/client-registry.d.ts +0 -22
  296. package/dist/agents/client-registry.js +0 -41
  297. package/dist/docs/agent-handoff-document.d.ts +0 -15
  298. package/dist/docs/agent-handoff-document.js +0 -180
  299. package/dist/docs/agent-solutions.d.ts +0 -110
  300. package/dist/docs/agent-solutions.js +0 -183
  301. package/dist/utils/query-keys.d.ts +0 -84
  302. package/dist/utils/query-keys.js +0 -108
@@ -2,21 +2,17 @@ import { BRAND } from "../brand.js";
2
2
  import { WORST_CASE_QUOTAS } from "../contracts/quotas.js";
3
3
  import { SPACE_CONFIG_PRIVATE_FILES } from "../contracts/space-config.js";
4
4
  import { ZERO_RUNTIME_DEPLOY_PATH } from "../contracts/zero.js";
5
- // The ONE static publish policy (the platform spec "Upload Contract"):
6
- // path denials, manifest scale caps, plan caps, duplicate detection, and the
7
- // free-plan content rules live here. Consumed by API manifest validation, CLI
8
- // local preflight, inline archive extraction, and the runtime parity fixtures
9
- // (the PHP twin is runtime/engine/admin/upload-policy.php).
5
+ // The ONE static publish policy (spec "Upload Contract"), shared by API
6
+ // manifest validation, CLI preflight and archive extraction. The PHP twin is
7
+ // runtime/engine/admin/upload-policy.php.
10
8
  import { canonicalizeVersionPath, normalizeVersionPath } from "./version-path.js";
11
- // Manifest scale caps (spec "Upload Contract"): the hard contract ceiling is
12
- // 100,000 files per version; plan policy may LOWER it (Free 20,000), never
13
- // raise it. Canonical paths cap at 1,024 bytes; manifest request bodies at
14
- // 16 MB, enforced fail-early before any upload session exists.
9
+ // Manifest scale caps (spec "Upload Contract"). Plan policy may LOWER the
10
+ // file-count ceiling (Free 20,000), never raise it. All are enforced before
11
+ // any upload session exists.
15
12
  export const MANIFEST_MAX_FILES_CEILING = 100_000;
16
13
  export const MANIFEST_MAX_PATH_BYTES = 1024;
17
14
  export const MANIFEST_MAX_BODY_BYTES = 16 * 1024 * 1024;
18
15
  export const MAX_VERSION_FILE_SIZE_BYTES = 64 * 1024 * 1024;
19
- // Worst-case artifact ceiling lives in the named raise-only quota table.
20
16
  export const MAX_VERSION_TOTAL_BYTES = WORST_CASE_QUOTAS.maxArtifactBytes;
21
17
  const IGNORED_EXACT_PATHS = new Set([
22
18
  ".DS_Store",
@@ -28,8 +24,7 @@ const IGNORED_EXACT_PATHS = new Set([
28
24
  ".env.test",
29
25
  ".npmrc",
30
26
  ".netrc",
31
- // CLI checkout state (current dir name plus the legacy pre-rename one —
32
- // user archives may contain either; both stay out of every publish).
27
+ // CLI checkout state. Both spellings appear in user archives.
33
28
  ".spacefast/state.json",
34
29
  ".stattic/state.json",
35
30
  "_routes.json",
@@ -57,33 +52,22 @@ const SENSITIVE_FILE_PATTERNS = [
57
52
  // Denied runtime private/control paths (spec "Static path policy").
58
53
  // Every list below is generated verbatim into runtime/engine/admin/upload-policy.php
59
54
  // by apps/control-plane/src/scripts/codegen-php-policy.ts, so the two enforcers
60
- // cannot hold different literals. Lowercase only both sides fold the path first.
55
+ // cannot hold different literals. Lowercase only; both sides fold the path first.
61
56
  export const STATIC_EXECUTION_CONTROL_FILES = [".htaccess", ".user.ini"];
62
- // Reserved runtime namespace segments are matched by PREFIX: the runtime owns
63
- // `/__spacefast/*` plus `__spacefast_generated/`, and the `__stattic` spelling
64
- // (`/__stattic_probe` today, more `/__stattic/*` routes planned) stays
65
- // reserved the same way. Mid-segment occurrences are ordinary content.
57
+ // Matched by PREFIX, so the runtime owns `__spacefast_generated/` as well as
58
+ // `/__spacefast/*`. Mid-segment occurrences are ordinary content.
66
59
  export const STATIC_RUNTIME_CONTROL_SEGMENT_PREFIXES = ["__spacefast", "__stattic"];
67
- // Reserved as WHOLE root segments. In runtime/engine/shared/context.php the
68
- // control table gives `/__sf` and `/__span` `match => 'namespace', fold => true`
69
- // rows, and `_stattic_control_path_row()` reads `namespace` as `=== '/__sf' ||
70
- // starts_with('/__sf/')`, so `/__sfx/...` and `/__spanish/page` are ordinary
71
- // tenant paths. Reserving more than the front door claims would refuse a publish
72
- // of content the front door then happily serves as tenant content.
60
+ // Reserved as WHOLE root segments, so `/__sfx/...` and `/__spanish/page` are
61
+ // ordinary tenant paths. Reserving more than the front door claims would refuse
62
+ // a publish of content the front door then serves anyway.
73
63
  //
74
- // This is a SUBSET of what the control table reserves, not a mirror of it: the
75
- // table also reserves the nested exact path `/__/collab` (the review link),
76
- // while the `/__/` prefix around it is tenant-publishable. Publish deliberately
77
- // does not claim that one — the reservation that matters is enforced at request
78
- // time, where serve.php refuses to let either a published file or a routing rule
79
- // answer a reserved route, and that refusal is what runtime/tests/collab-frame.test.ts
80
- // exercises by publishing a file at `__/collab` on purpose. The control table is
81
- // the authority; this list is a publish-time convenience, so keep it a subset
82
- // and never let it contradict the table.
64
+ // A SUBSET of what the control table in runtime/engine/shared/context.php
65
+ // reserves, never a mirror: the table also reserves `/__/collab`, enforced at
66
+ // request time by serve.php. The table is the authority; keep this list a
67
+ // subset of it.
83
68
  export const STATIC_RUNTIME_ROOT_SEGMENTS = ["__sf", "__span"];
84
- // Both the current and the legacy control-path spellings stay denied: spaces
85
- // published before the `.stattic` → `.spacefast` rename still serve from the
86
- // legacy on-disk layout, and user archives may carry either name.
69
+ // Both spellings stay denied: spaces published before the `.stattic`
70
+ // `.spacefast` rename still serve from the legacy on-disk layout.
87
71
  export const STATIC_RUNTIME_CONTROL_PATHS = [
88
72
  ".well-known/spacefast-runtime",
89
73
  ".well-known/stattic-runtime",
@@ -98,39 +82,23 @@ export function normalizePublishPath(relativePath) {
98
82
  return relativePath.replace(/\\/g, "/").replace(/^\/+/g, "");
99
83
  }
100
84
  /**
101
- * The one subtree inside the runtime control prefix that a publish may write.
85
+ * The one subtree inside the runtime control prefix a publish may write, so a
86
+ * compiled worker bundle travels the ordinary manifest and upload path while
87
+ * staying unservable as static content.
102
88
  *
103
- * `sf publish` compiles a version's worker into `__spacefast/functions/bundles/`
104
- * so the bundle travels through the ordinary manifest and upload path instead of
105
- * needing a parallel one. Keeping it under the control prefix is what makes it
106
- * unservable as static content by default — the runtime answers it only behind a
107
- * signature.
108
- *
109
- * Scoped to `bundles/` rather than the whole `__spacefast/functions/` directory
110
- * on purpose: the sibling `config.json` holds the key those signatures are
111
- * minted with, and a publish that could overwrite it would be a publish that
112
- * could mint its own tokens.
89
+ * Scoped to `bundles/`, not the whole `__spacefast/functions/` directory: the
90
+ * sibling `config.json` holds the key its signatures are minted with.
113
91
  */
114
92
  export const PUBLISHABLE_BUILD_ARTIFACT_PREFIX = "__spacefast/functions/bundles/";
115
- /**
116
- * The bundle carve-out's sibling for the Next-cache seed document: the same
117
- * reasoning (travel through the ordinary manifest and upload path, stay
118
- * unservable by default), the same deliberate narrowness (a prefix of its
119
- * own, never the whole `functions/` directory — see the bundle comment).
120
- */
93
+ /** The same carve-out for the Next-cache seed document. */
121
94
  export const PUBLISHABLE_SEED_ARTIFACT_PREFIX = "__spacefast/functions/seeds/";
122
95
  /**
123
- * The one FILE inside the control prefix a publish may write.
124
- *
125
- * A Zero capsule's compiled server travels as version metadata, not content, so
126
- * a server-only change ships a byte-identical app shell and the version intent's
127
- * manifest comparison reads it as "no changes". `sf publish` writes the deploy's
128
- * digest here so the manifest moves whenever the deploy does — same reasoning as
129
- * the Functions bundle above, same channel.
130
- *
131
- * One exact path, not a prefix: its siblings under `__spacefast/zero/` are the
132
- * runtime's own artifact, config and binary, and a publish that could overwrite
133
- * those would be a publish that could replace the runtime.
96
+ * ZERO_RUNTIME_DEPLOY_PATH is the one FILE a publish may write inside the
97
+ * control prefix. A Zero capsule's server travels as version metadata, so a
98
+ * server-only change would otherwise ship a byte-identical shell that the
99
+ * manifest comparison reads as "no changes"; the deploy digest moves it. One
100
+ * exact path, not a prefix: its siblings under `__spacefast/zero/` are the
101
+ * runtime's own artifact, config and binary.
134
102
  */
135
103
  function isPublishableBuildArtifactPath(normalizedPath, lowerPath) {
136
104
  return (lowerPath.startsWith(PUBLISHABLE_BUILD_ARTIFACT_PREFIX) ||
@@ -139,10 +107,9 @@ function isPublishableBuildArtifactPath(normalizedPath, lowerPath) {
139
107
  }
140
108
  export function publishPathIgnored(relativePath) {
141
109
  const path = normalizePublishPath(relativePath);
142
- // The agent state dirs hold one secret (state.json) plus committable tooling
143
- // (space.json). Drop only those files at any depth so a checkout archived one
144
- // level too high never ships a claim token — but keep serving legitimate
145
- // content in the same dirs (e.g. the legacy .stattic/routes.json convention).
110
+ // Drop these files at any depth so a checkout archived one level too high
111
+ // never ships a claim token, while other content in the same dirs still
112
+ // publishes (e.g. the legacy .stattic/routes.json convention).
146
113
  if (/(?:^|\/)\.(?:spacefast|stattic)\/(?:state|space)\.json$/.test(path) ||
147
114
  /(?:^|\/)\.spacefast-(?:[0-9a-f-]{36}\.(?:tmp|rollback|retired)|[0-9a-f]{24}\.lock)$/.test(path)) {
148
115
  return true;
@@ -159,11 +126,9 @@ export function publishPathIgnored(relativePath) {
159
126
  export function publishPathIsConvention(relativePath) {
160
127
  return RESERVED_CONVENTION_PATHS.has(normalizePublishPath(relativePath));
161
128
  }
162
- // Provider runtime features that have no Spacefast equivalent (spec "Routing And
163
- // Headers", migration diagnostics): Cloudflare Pages `_worker.js`/`_routes.json`
164
- // and Netlify's provider-specific function directory publish as inert content
165
- // with a warning so migrations are never silent. A root `functions/` directory
166
- // is Spacefast's own file-router convention and must never reach this warning.
129
+ // Provider runtime features with no Spacefast equivalent publish as inert
130
+ // content with a warning, so migrations are never silent. A root `functions/`
131
+ // directory is Spacefast's own convention and must never reach this warning.
167
132
  function providerRuntimeFeatureForPath(path) {
168
133
  if (path === "_worker.js") {
169
134
  return "Cloudflare Pages _worker.js";
@@ -218,9 +183,8 @@ export function staticUploadPathViolation(path, options = {}) {
218
183
  return pathViolation("invalid_file_path", "File path is invalid.", rawPath);
219
184
  }
220
185
  if (normalizedPath !== rawPath) {
221
- // The path the caller sent, not the normal form it missed: echoing a
222
- // rewritten path suggests the rewrite was accepted, and it is the one place
223
- // this policy used to report a different string than the runtime's copy.
186
+ // Echo the path the caller sent; a rewritten one would suggest the
187
+ // rewrite was accepted.
224
188
  return pathViolation("invalid_file_path", "File path is invalid.", rawPath);
225
189
  }
226
190
  if (new TextEncoder().encode(normalizedPath).length > MANIFEST_MAX_PATH_BYTES) {
@@ -288,12 +252,10 @@ export function canonicalizePublishRequestedRoute(route) {
288
252
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
289
253
  }
290
254
  /**
291
- * Expands artifact paths into the request routes they can serve. The runtime
292
- * resolves literal files, directory indexes, and clean `.html` aliases; two
293
- * different artifacts owning one request route is rejected at publish/finalize
294
- * instead of depending on lookup order. `routeOwnership` exposes the same
295
- * expansion in canonical access-route form so authorization and diagnostics
296
- * never have to infer a route from an artifact path.
255
+ * Expands artifact paths into the request routes they can serve: literal
256
+ * files, directory indexes and clean `.html` aliases. Two artifacts owning one
257
+ * route is rejected at publish rather than left to lookup order.
258
+ * `routeOwnership` restates the expansion in canonical access-route form.
297
259
  */
298
260
  export function analyzePublishRoutes(paths, options = {}) {
299
261
  const index = options.index === undefined ? "index.html" : options.index;
@@ -362,12 +324,10 @@ export function analyzePublishRoutes(paths, options = {}) {
362
324
  export function resolveManifestFileCap(maxFiles) {
363
325
  return Math.min(MANIFEST_MAX_FILES_CEILING, maxFiles && maxFiles > 0 ? maxFiles : MANIFEST_MAX_FILES_CEILING);
364
326
  }
365
- // The shared manifest validator (spec "Upload Contract"): canonicalizes every
366
- // path (decode-once + NFC), enforces the static path policy, the scale caps,
367
- // and plan caps, rejects post-canonicalization duplicates, and emits the
368
- // case-collision warning. Runs BEFORE any upload session is created; archive
369
- // extraction routes through the same checks. Returns the manifest with
370
- // canonical paths — callers use the returned files, never the input.
327
+ // The shared manifest validator (spec "Upload Contract"). Runs BEFORE any
328
+ // upload session is created; archive extraction routes through the same
329
+ // checks. Returns canonical paths, so callers use the returned files, never
330
+ // the input.
371
331
  export function validatePublishManifest(files, caps = {}) {
372
332
  const maxFiles = resolveManifestFileCap(caps.maxFiles);
373
333
  if (files.length > maxFiles) {
@@ -424,15 +384,13 @@ export function validatePublishManifest(files, caps = {}) {
424
384
  warnings: [],
425
385
  };
426
386
  }
427
- // Checkout state and credential paths (.spacefast/, .git/, .env*, …) are
428
- // silently dropped rather than rejected: agents that archive one level too
429
- // high must never publish a claim token to the live site, and failing the
430
- // whole manifest for it would punish the common accident.
387
+ // Dropped rather than rejected: failing a whole manifest over a stray
388
+ // .env or .spacefast/ would punish the common accident.
431
389
  if (publishPathIgnored(canonicalPath)) {
432
390
  continue;
433
391
  }
434
- // Case-only-differing paths are distinct (paths are byte-exact and
435
- // case-sensitive) but cannot round-trip case-insensitive filesystems.
392
+ // Distinct paths here, but they cannot round-trip a case-insensitive
393
+ // filesystem.
436
394
  const lowercase = canonicalPath.toLowerCase();
437
395
  const caseSibling = lowercasePaths.get(lowercase);
438
396
  if (caseSibling !== undefined && caseSibling !== canonicalPath) {
@@ -494,8 +452,7 @@ const FREE_PLAN_ALLOWED_UPLOAD_EXTENSIONS = new Set([
494
452
  ".txt",
495
453
  ".md",
496
454
  ".csv",
497
- // Opaque static payloads used by migration fixtures and served as
498
- // application/octet-stream by the runtime.
455
+ // Migration fixtures, served as application/octet-stream.
499
456
  ".customtype",
500
457
  ".xml",
501
458
  ".svg",
@@ -511,13 +468,10 @@ const FREE_PLAN_ALLOWED_UPLOAD_EXTENSIONS = new Set([
511
468
  ".ttf",
512
469
  ".otf",
513
470
  ]);
514
- // THE anonymous content enforcement (accept-and-scan posture): uploads are
515
- // accepted like any free publish, but until the space is claimed the runtime
516
- // only serves these content types, pushed as the generic route-config
517
- // `content_types` allowlist (exact types or `prefix/*` wildcards; the engine
518
- // enforces the list without knowing it means "anonymous"). Web-page content
519
- // only — no opaque octet-streams, archives (except scanned gzip, which
520
- // pagefind search indexes depend on), documents, or media containers.
471
+ // THE anonymous content enforcement: uploads are accepted like any free
472
+ // publish, but until the space is claimed the runtime serves only these types,
473
+ // pushed as the route-config `content_types` allowlist. Web-page content only.
474
+ // Gzip is the one archive, because pagefind search indexes need it.
521
475
  export const ANONYMOUS_SERVE_CONTENT_TYPES = [
522
476
  "text/*",
523
477
  "image/*",
@@ -542,20 +496,14 @@ function freePlanDeniedExecutablePath(filePath) {
542
496
  .split("/")
543
497
  .some((segment) => segment === "bin");
544
498
  }
545
- // Acceptance is deliberately identical for claimed-free and unclaimed
546
- // anonymous spaces (accept-and-scan posture): rejecting file types at upload
547
- // adds no safety — the bytes reach us before any scan either way so
548
- // publishes are accepted, every file goes through the scan pipeline, and the
549
- // SERVE-time content-type allowlist (ANONYMOUS_SERVE_CONTENT_TYPES via the
550
- // route-config `content_types` policy) is what keeps opaque binaries dark
551
- // until the space is claimed.
499
+ // Acceptance is deliberately identical for claimed-free and unclaimed spaces:
500
+ // rejecting types at upload adds no safety, since the bytes reach us before
501
+ // any scan either way. ANONYMOUS_SERVE_CONTENT_TYPES is what keeps opaque
502
+ // binaries dark until the space is claimed.
552
503
  export function freePlanCanUploadPath(filePath, options) {
553
504
  const path = normalizePublishPath(filePath);
554
- // Root Spacefast config files are platform machinery, not user content (spec "Space
555
- // Configuration Files": ships in the manifest, same lifecycle as
556
- // `_redirects`/`_headers`) — the plan's content-extension gate must never
557
- // eat it (e2e checkpoint 4: it was silently dropped on free publishes,
558
- // disabling fallback/meta/templates/theme for every free space).
505
+ // Platform machinery, not user content: same lifecycle as
506
+ // `_redirects`/`_headers`, so the content-extension gate must never eat them.
559
507
  if (SPACE_CONFIG_PRIVATE_FILES.includes(path)) {
560
508
  return true;
561
509
  }
@@ -579,8 +527,7 @@ export function freePlanCanUploadContentType(contentType) {
579
527
  normalized === "application/javascript" ||
580
528
  normalized === "application/json" ||
581
529
  normalized === "application/manifest+json" ||
582
- // Gzip stays allowed for anonymous spaces too: pagefind search indexes
583
- // ship as gzip fragments, and ClamAV decompresses gzip when scanning.
530
+ // pagefind ships gzip fragments, and ClamAV decompresses gzip when scanning.
584
531
  normalized === "application/gzip" ||
585
532
  normalized === "image/svg+xml" ||
586
533
  normalized === "application/xml" ||
@@ -4,4 +4,6 @@ export type ApiErrorLike = {
4
4
  };
5
5
  export declare function createSpacefastQueryClient(options?: {
6
6
  isClientError?: (error: unknown) => error is ApiErrorLike;
7
+ /** Default freshness window for every query, in milliseconds. */
8
+ staleTime?: number;
7
9
  }): QueryClient;
@@ -1,4 +1,13 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
+ /**
3
+ * Freshness for a caller that has not adopted a tiered caching policy.
4
+ *
5
+ * The tiers themselves (`STALE_TIME_MS`) live in `@spacefast/sdk/query`, which
6
+ * depends on this package — so they cannot be imported here and arrive as an
7
+ * argument instead. The dashboard passes `STALE_TIME_MS.normal`; superadmin
8
+ * still runs on this default.
9
+ */
10
+ const UNTIERED_STALE_TIME_MS = 60_000;
2
11
  export function createSpacefastQueryClient(options) {
3
12
  return new QueryClient({
4
13
  defaultOptions: {
@@ -12,7 +21,7 @@ export function createSpacefastQueryClient(options) {
12
21
  }
13
22
  return failureCount < 2;
14
23
  },
15
- staleTime: 60_000,
24
+ staleTime: options?.staleTime ?? UNTIERED_STALE_TIME_MS,
16
25
  refetchOnWindowFocus: true,
17
26
  refetchOnReconnect: true,
18
27
  },
@@ -2,13 +2,13 @@ export declare const SPACEFAST_RUNTIME_NAMESPACE: "/__spacefast";
2
2
  export declare const RUNTIME_MANAGEMENT_API_PATH: "/__spacefast/api.php";
3
3
  export declare const RUNTIME_UPLOAD_API_PATH: "/__spacefast/upload.php";
4
4
  export declare const RUNTIME_HEALTH_PATH: "/__spacefast/health.php";
5
+ export declare const RUNTIME_CONTENT_API_PATH: "/__spacefast/content.php";
5
6
  export declare const RUNTIME_COMMENTS_CONFIG_PATH: "/__spacefast/comments/config";
6
7
  export declare const RUNTIME_COMMENTS_TICKET_PATH: "/__spacefast/comments/ticket";
7
8
  export declare const RUNTIME_COMMENTS_VERSION_URLS_PATH: "/__spacefast/comments/version-urls";
8
- /** The review workspace the orb expands into a reserved control path on the
9
- * Space's own origin (collab-frame-plan §4), not part of the `/__spacefast`
10
- * exchange namespace because people paste this one into Slack.
11
- * The PHP mirror is SPACEFAST_COLLAB_FRAME_PATH in runtime/engine/shared/context.php. */
9
+ /** The review workspace, on the Space's own origin and outside the
10
+ * `/__spacefast` exchange namespace because people paste it into Slack.
11
+ * The PHP mirror is SPACEFAST_COLLAB_FRAME_PATH in shared/context.php. */
12
12
  export declare const RUNTIME_COLLAB_FRAME_PATH: "/__/collab";
13
13
  export declare function runtimeManagementPath(suffix: string): string;
14
14
  export declare function runtimeUploadPath(params: URLSearchParams | Record<string, string>): string;
@@ -2,17 +2,15 @@ export const SPACEFAST_RUNTIME_NAMESPACE = "/__spacefast";
2
2
  export const RUNTIME_MANAGEMENT_API_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/api.php`;
3
3
  export const RUNTIME_UPLOAD_API_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/upload.php`;
4
4
  export const RUNTIME_HEALTH_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/health.php`;
5
- // Same-origin Comments exchange paths served on every published host. The PHP
6
- // mirror is SPACEFAST_COMMENTS_*_PATH in runtime/engine/shared/context.php;
7
- // runtime/tests/spacefast-sdk.test.ts pins the PHP side and
8
- // packages/collab-sdk/test/entry.test.ts pins the SDK side to these literals.
5
+ export const RUNTIME_CONTENT_API_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/content.php`;
6
+ // Same-origin Comments exchange paths. The PHP mirror is
7
+ // SPACEFAST_COMMENTS_*_PATH in runtime/engine/shared/context.php.
9
8
  export const RUNTIME_COMMENTS_CONFIG_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/comments/config`;
10
9
  export const RUNTIME_COMMENTS_TICKET_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/comments/ticket`;
11
10
  export const RUNTIME_COMMENTS_VERSION_URLS_PATH = `${SPACEFAST_RUNTIME_NAMESPACE}/comments/version-urls`;
12
- /** The review workspace the orb expands into a reserved control path on the
13
- * Space's own origin (collab-frame-plan §4), not part of the `/__spacefast`
14
- * exchange namespace because people paste this one into Slack.
15
- * The PHP mirror is SPACEFAST_COLLAB_FRAME_PATH in runtime/engine/shared/context.php. */
11
+ /** The review workspace, on the Space's own origin and outside the
12
+ * `/__spacefast` exchange namespace because people paste it into Slack.
13
+ * The PHP mirror is SPACEFAST_COLLAB_FRAME_PATH in shared/context.php. */
16
14
  export const RUNTIME_COLLAB_FRAME_PATH = "/__/collab";
17
15
  export function runtimeManagementPath(suffix) {
18
16
  const route = normalizeRuntimeRoute(suffix);
@@ -9,6 +9,10 @@ export declare function runtimeUploadFileUrl(input: {
9
9
  uploadId: string;
10
10
  path: string;
11
11
  }): string;
12
+ export declare function runtimeUploadBlobsHaveUrl(input: {
13
+ baseUrl: string;
14
+ spaceId: string;
15
+ }): string;
12
16
  export declare function runtimeUploadBlobUrl(input: {
13
17
  baseUrl: string;
14
18
  spaceId: string;
@@ -22,6 +22,11 @@ export function runtimeUploadFileUrl(input) {
22
22
  path: runtimeUploadCanonicalPath(input.path),
23
23
  }), input.baseUrl).toString();
24
24
  }
25
+ export function runtimeUploadBlobsHaveUrl(input) {
26
+ return new URL(runtimeUploadPath({
27
+ route: `/spaces/${encodeURIComponent(input.spaceId)}/blobs/have`,
28
+ }), input.baseUrl).toString();
29
+ }
25
30
  export function runtimeUploadBlobUrl(input) {
26
31
  return new URL(runtimeUploadPath({
27
32
  route: `/spaces/${encodeURIComponent(input.spaceId)}/blobs/${encodeURIComponent(input.sha256)}`,
@@ -51,9 +51,9 @@ export declare function secureOutputLocation(trustedPath: string, outputPath: st
51
51
  };
52
52
  /**
53
53
  * Writes beneath an explicitly trusted root without following output-tree
54
- * symlinks. Linux uses descriptor-relative operations; other supported
55
- * platforms use exclusive staging plus before-and-after ancestor identity
56
- * validation and fail closed if any binding changes.
54
+ * symlinks. Linux uses descriptor-relative operations; other platforms use
55
+ * exclusive staging plus before-and-after ancestor identity validation, and
56
+ * fail closed if any binding changes.
57
57
  */
58
58
  export declare function secureWriteFile(options: SecureWriteOptions): Promise<void>;
59
59
  export declare function secureReadFile(options: SecureReadOptions & {
@@ -236,10 +236,9 @@ async function canonicalPortableWriteRoot(rootInput) {
236
236
  async function openPortableChildDirectory(identities, visibleParent, component, create) {
237
237
  assertSafeComponent(component);
238
238
  const childPath = path.join(visibleParent, component);
239
- // A missing directory is the normal "file not found" result for secure
240
- // reads. Probe before identity capture so callers can handle ENOENT. If the
241
- // directory disappears after this probe, visibleDirectoryIdentity still
242
- // fails closed because an observed ancestor binding changed mid-read.
239
+ // A missing directory is the normal "file not found" for secure reads, so
240
+ // probe before identity capture. A later disappearance still fails closed in
241
+ // visibleDirectoryIdentity.
243
242
  if (!create) {
244
243
  await lstat(childPath);
245
244
  }
@@ -465,8 +464,7 @@ async function unlinkOwnedLeafLockArtifact(lockPath, artifactPath, nonce) {
465
464
  return;
466
465
  }
467
466
  // The pathname changed after its owner was inspected. Restore the exact inode
468
- // when the name is still free; if another lock is already visible, keep this
469
- // one displaced so contenders detect it instead of deleting a fresh owner.
467
+ // when the name is free; otherwise keep it displaced so contenders detect it.
470
468
  let restored = false;
471
469
  try {
472
470
  await link(retiredPath, artifactPath);
@@ -544,14 +542,12 @@ async function acquireLeafLock(lockPath, temporaryPath) {
544
542
  if (released)
545
543
  return;
546
544
  released = true;
547
- // Move the canonical name into unique cleanup custody while its inode
548
- // is still active. Once that atomic rename happens a new owner may
549
- // publish, but cleanup only ever unlinks the retired name it inspected.
545
+ // Move the canonical name into unique custody while its inode is still
546
+ // active; cleanup only ever unlinks the retired name it inspected.
550
547
  await unlinkOwnedLeafLockArtifact(lockPath, lockPath, owner.nonce).catch(() => undefined);
551
548
  try {
552
- // A delayed stale reclaimer may already hold this inode at a displaced
553
- // name. Mark it complete through the handle so that artifact can be
554
- // reclaimed even though this process remains alive.
549
+ // A delayed stale reclaimer may hold this inode at a displaced name.
550
+ // Mark it complete through the handle so it can still be reclaimed.
555
551
  const releasedWrite = await acquiredHandle.write(Buffer.from("R"), 0, 1, 0);
556
552
  if (releasedWrite.bytesWritten === 1)
557
553
  await acquiredHandle.sync();
@@ -598,16 +594,15 @@ async function acquireLeafLock(lockPath, temporaryPath) {
598
594
  }
599
595
  const displaced = await readLeafLockRecord(displacedPath);
600
596
  if (!(await leafLockRecordIsStale(displaced))) {
601
- // A fresh owner won between our stale read and rename. Put its exact inode
602
- // back when possible; every contender also scans displaced names before
603
- // entering, so a live writer is never silently unlocked.
597
+ // A fresh owner won between our stale read and rename. Put its exact
598
+ // inode back; contenders scan displaced names before entering, so a live
599
+ // writer is never silently unlocked.
604
600
  try {
605
601
  await link(displacedPath, lockPath);
606
602
  }
607
603
  catch (error) {
608
604
  if (isMissing(error)) {
609
- // Its owner completed cleanup while we inspected the displaced inode.
610
- // Nothing remains to restore; compete again for the now-current name.
605
+ // Its owner finished cleanup; compete again for the current name.
611
606
  await delay(LEAF_LOCK_RETRY_DELAY_MS);
612
607
  continue;
613
608
  }
@@ -857,10 +852,9 @@ async function commitPortableStagedFile(input) {
857
852
  }
858
853
  }
859
854
  async function secureWriteFilePortable(options) {
860
- // The caller explicitly trusts the root. Canonicalize that boundary once so
861
- // supported system aliases such as macOS `/var -> /private/var` do not look
862
- // like attacker-controlled children; every path below it is still walked
863
- // component-by-component without following symlinks.
855
+ // Canonicalize the trusted root once so system aliases like macOS
856
+ // `/var -> /private/var` don't look like attacker-controlled children.
857
+ // Everything below it is still walked without following symlinks.
864
858
  const root = await canonicalPortableWriteRoot(options.root);
865
859
  const relativePath = validateRelativePath(options.relativePath, "write");
866
860
  const components = relativePath.split(path.sep);
@@ -923,9 +917,9 @@ async function secureWriteFilePortable(options) {
923
917
  }
924
918
  /**
925
919
  * Writes beneath an explicitly trusted root without following output-tree
926
- * symlinks. Linux uses descriptor-relative operations; other supported
927
- * platforms use exclusive staging plus before-and-after ancestor identity
928
- * validation and fail closed if any binding changes.
920
+ * symlinks. Linux uses descriptor-relative operations; other platforms use
921
+ * exclusive staging plus before-and-after ancestor identity validation, and
922
+ * fail closed if any binding changes.
929
923
  */
930
924
  export async function secureWriteFile(options) {
931
925
  if ((options.data === undefined) === (options.write === undefined)) {
@@ -16,18 +16,15 @@ type SpaceConfigBlockingIssue = SpaceConfigIssue & {
16
16
  code: SpaceConfigBlockingIssueCode;
17
17
  };
18
18
  /**
19
- * Keys a committed `sf.jsonc` owns outright: they are facts about the content
20
- * of a version (who may read it, what it is called, what runs it), so they roll
21
- * forward and back with the version instead of living in the space's mutable
22
- * overlay. API intake refuses them rather than storing or dropping them.
19
+ * Keys a committed `sf.jsonc` owns outright. They are facts about a version's
20
+ * content, so they roll forward and back with it instead of living in the
21
+ * space's mutable overlay. API intake refuses them.
23
22
  */
24
- export declare const SPACE_CONFIG_FILE_ONLY_KEYS: readonly ["access", "name", "runtime"];
23
+ export declare const SPACE_CONFIG_FILE_ONLY_KEYS: readonly ["access", "name", "runtime", "system"];
25
24
  /**
26
- * Validates a space `config` overlay at API intake (PATCH /spaces, publish
27
- * `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
28
- * shape caps. Returns the first blocking issue, or null when the overlay is
29
- * acceptable. Takes the raw request value, because the overlay schema strips
30
- * file-only keys and this is what has to see them.
25
+ * Validates a space `config` overlay at API intake, returning the first
26
+ * blocking issue or null. Takes the RAW request value: the overlay schema
27
+ * strips file-only keys, and this is what has to see them.
31
28
  */
32
29
  export declare function spaceConfigOverlayIssue(config: unknown): SpaceConfigBlockingIssue | null;
33
30
  export declare function stripJsonComments(raw: string): string;
@@ -37,16 +34,16 @@ export type ResolvedServingConfig = {
37
34
  path: string;
38
35
  status: 200 | 404;
39
36
  } | null;
40
- /** W7.1 clean URLs: serve flat `<name>.html` at its extensionless URL. */
37
+ /** Serve flat `<name>.html` at its extensionless URL. */
41
38
  cleanUrls: boolean;
42
39
  listing: boolean;
43
40
  viewer: boolean;
44
41
  meta: NonNullable<SpaceConfig["meta"]> | null;
45
- /** Theme tokens for every Spacefast-authored page; null when unset. */
42
+ /** Theme tokens for every Spacefast-authored page. */
46
43
  pageTheme: NonNullable<SpaceConfig["theme"]> | null;
47
- /** Hidden experimental build-pipeline flag; false keeps today's behavior. */
44
+ /** Hidden experimental build-pipeline flag. */
48
45
  experimentalGutenberg: boolean;
49
- /** Resolved inject snippets; only honored when experimentalGutenberg is true. */
46
+ /** Only honored when experimentalGutenberg is true. */
50
47
  inject: NonNullable<SpaceConfig["inject"]> | null;
51
48
  };
52
49
  export type SpaceSettingsPatch = {
@@ -42,9 +42,7 @@ function capIssues(candidate) {
42
42
  details: { count: templates.length, limit: SPACE_CONFIG_CAPS.templates },
43
43
  });
44
44
  }
45
- // inject snippets (hidden feature, honored only with experimental_gutenberg):
46
- // shape and byte caps fail with the stable codes here, before zod, so intake
47
- // reports inject_invalid / inject_snippet_too_large instead of shape errors.
45
+ // inject is a hidden feature, honored only with experimental_gutenberg.
48
46
  const inject = candidate.inject;
49
47
  if (inject !== undefined) {
50
48
  if (typeof inject !== "object" || inject === null || Array.isArray(inject)) {
@@ -126,18 +124,15 @@ function capIssues(candidate) {
126
124
  return issues;
127
125
  }
128
126
  /**
129
- * Keys a committed `sf.jsonc` owns outright: they are facts about the content
130
- * of a version (who may read it, what it is called, what runs it), so they roll
131
- * forward and back with the version instead of living in the space's mutable
132
- * overlay. API intake refuses them rather than storing or dropping them.
127
+ * Keys a committed `sf.jsonc` owns outright. They are facts about a version's
128
+ * content, so they roll forward and back with it instead of living in the
129
+ * space's mutable overlay. API intake refuses them.
133
130
  */
134
- export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime"];
131
+ export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime", "system"];
135
132
  /**
136
- * Validates a space `config` overlay at API intake (PATCH /spaces, publish
137
- * `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
138
- * shape caps. Returns the first blocking issue, or null when the overlay is
139
- * acceptable. Takes the raw request value, because the overlay schema strips
140
- * file-only keys and this is what has to see them.
133
+ * Validates a space `config` overlay at API intake, returning the first
134
+ * blocking issue or null. Takes the RAW request value: the overlay schema
135
+ * strips file-only keys, and this is what has to see them.
141
136
  */
142
137
  export function spaceConfigOverlayIssue(config) {
143
138
  if (config === null || config === undefined || typeof config !== "object") {
@@ -166,9 +161,8 @@ export function spaceConfigOverlayIssue(config) {
166
161
  }
167
162
  return capIssues(config)[0] ?? null;
168
163
  }
169
- // Strips JSONC comments and trailing commas (string-aware single pass). The
170
- // config file itself is parsed by the crate; this stays for the OTHER JSONC
171
- // files the CLI reads (agent configs, exported platform env).
164
+ // The config file itself is parsed by the crate; this stays for the OTHER
165
+ // JSONC files the CLI reads (agent configs, exported platform env).
172
166
  export function stripJsonComments(raw) {
173
167
  let output = "";
174
168
  let inString = false;