@spacefast/common 0.0.23 → 0.0.26

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 (224) hide show
  1. package/brand-assets/LICENSE-Haskoy.txt +93 -0
  2. package/brand-assets/haskoy-latin-variable.woff2 +0 -0
  3. package/brand-assets/spacefast-favicon.svg +4 -4
  4. package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
  5. package/brand-assets/spacefast-wordmark.svg +2 -4
  6. package/dist/agents/connect-targets.js +1 -1
  7. package/dist/agents/private-key-oauth.d.ts +0 -1
  8. package/dist/agents/private-key-oauth.js +46 -63
  9. package/dist/brand-assets-build.js +4 -6
  10. package/dist/brand-assets.d.ts +5 -15
  11. package/dist/brand-assets.js +15 -14
  12. package/dist/config/domains.d.ts +47 -7
  13. package/dist/config/domains.js +66 -10
  14. package/dist/config/index.d.ts +0 -1
  15. package/dist/config/index.js +0 -1
  16. package/dist/contracts/abuse.d.ts +2 -0
  17. package/dist/contracts/abuse.js +12 -0
  18. package/dist/contracts/access.d.ts +124 -12
  19. package/dist/contracts/access.js +73 -9
  20. package/dist/contracts/activity.d.ts +1 -1
  21. package/dist/contracts/activity.js +5 -12
  22. package/dist/contracts/api-keys.d.ts +21 -21
  23. package/dist/contracts/api-keys.js +2 -2
  24. package/dist/contracts/beta.js +1 -1
  25. package/dist/contracts/billing.d.ts +3 -35
  26. package/dist/contracts/billing.js +33 -52
  27. package/dist/contracts/builds.d.ts +30 -18
  28. package/dist/contracts/builds.js +28 -9
  29. package/dist/contracts/channels.js +1 -1
  30. package/dist/contracts/cli.d.ts +10 -0
  31. package/dist/contracts/cli.js +10 -0
  32. package/dist/contracts/collab-public.d.ts +63 -0
  33. package/dist/contracts/collab-public.js +46 -0
  34. package/dist/contracts/comments.d.ts +282 -53
  35. package/dist/contracts/comments.js +173 -52
  36. package/dist/contracts/common.d.ts +3 -3
  37. package/dist/contracts/common.js +5 -5
  38. package/dist/contracts/crons.d.ts +35 -0
  39. package/dist/contracts/crons.js +42 -0
  40. package/dist/contracts/device-auth.d.ts +6 -6
  41. package/dist/contracts/device-auth.js +2 -2
  42. package/dist/contracts/docs.js +2 -2
  43. package/dist/contracts/domains.d.ts +38 -27
  44. package/dist/contracts/domains.js +29 -2
  45. package/dist/contracts/enums.d.ts +34 -30
  46. package/dist/contracts/enums.js +62 -48
  47. package/dist/contracts/error-code-meta.d.ts +50 -125
  48. package/dist/contracts/error-code-meta.js +29 -41
  49. package/dist/contracts/error-codes.d.ts +1 -1
  50. package/dist/contracts/error-codes.js +14 -33
  51. package/dist/contracts/execution.d.ts +47 -66
  52. package/dist/contracts/execution.js +53 -49
  53. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  54. package/dist/contracts/feature-lifecycle-core.js +2 -31
  55. package/dist/contracts/feature-lifecycle.d.ts +12 -52
  56. package/dist/contracts/feature-lifecycle.js +16 -59
  57. package/dist/contracts/features.d.ts +48 -60
  58. package/dist/contracts/features.js +59 -51
  59. package/dist/contracts/functions.d.ts +266 -72
  60. package/dist/contracts/functions.js +260 -37
  61. package/dist/contracts/git.d.ts +28 -34
  62. package/dist/contracts/git.js +10 -11
  63. package/dist/contracts/grants.d.ts +9 -5
  64. package/dist/contracts/grants.js +18 -9
  65. package/dist/contracts/ids.js +2 -2
  66. package/dist/contracts/internal.d.ts +8 -6
  67. package/dist/contracts/internal.js +12 -12
  68. package/dist/contracts/mcp.d.ts +11 -0
  69. package/dist/contracts/mcp.js +5 -0
  70. package/dist/contracts/me.js +1 -1
  71. package/dist/contracts/oauth-resources.d.ts +4 -3
  72. package/dist/contracts/oauth-resources.js +12 -3
  73. package/dist/contracts/operations.d.ts +90 -5
  74. package/dist/contracts/operations.js +77 -3
  75. package/dist/contracts/pages.d.ts +12 -7
  76. package/dist/contracts/pages.js +24 -1
  77. package/dist/contracts/plan-policy.d.ts +0 -2
  78. package/dist/contracts/plan-policy.js +2 -3
  79. package/dist/contracts/platform.d.ts +61 -148
  80. package/dist/contracts/platform.js +70 -126
  81. package/dist/contracts/publish-archive.d.ts +508 -0
  82. package/dist/contracts/publish-archive.js +32 -0
  83. package/dist/contracts/push-new.d.ts +4 -4
  84. package/dist/contracts/quotas.js +2 -2
  85. package/dist/contracts/repository-connections.d.ts +43 -30
  86. package/dist/contracts/repository-connections.js +11 -3
  87. package/dist/contracts/resources.d.ts +12 -24
  88. package/dist/contracts/resources.js +36 -16
  89. package/dist/contracts/runtime-api.d.ts +433 -192
  90. package/dist/contracts/runtime-api.js +421 -356
  91. package/dist/contracts/runtime-app.d.ts +127 -120
  92. package/dist/contracts/runtime-app.js +19 -10
  93. package/dist/contracts/runtime-db.d.ts +5 -5
  94. package/dist/contracts/runtime-db.js +2 -2
  95. package/dist/contracts/runtime-purge.d.ts +8 -0
  96. package/dist/contracts/runtime-purge.js +7 -0
  97. package/dist/contracts/runtime-services.d.ts +219 -0
  98. package/dist/contracts/runtime-services.js +437 -0
  99. package/dist/contracts/runtime-storage.d.ts +46 -32
  100. package/dist/contracts/runtime-storage.js +31 -8
  101. package/dist/contracts/sf-config-v1.d.ts +48 -10
  102. package/dist/contracts/space-config.d.ts +69 -18
  103. package/dist/contracts/space-config.js +226 -27
  104. package/dist/contracts/spaces.d.ts +236 -450
  105. package/dist/contracts/spaces.js +131 -151
  106. package/dist/contracts/superadmin-activity.d.ts +57 -4
  107. package/dist/contracts/superadmin-activity.js +48 -2
  108. package/dist/contracts/superadmin-search.d.ts +126 -0
  109. package/dist/contracts/superadmin-search.js +61 -0
  110. package/dist/contracts/superadmin-spaces.d.ts +361 -92
  111. package/dist/contracts/superadmin-spaces.js +97 -24
  112. package/dist/contracts/superadmin-teams.d.ts +28 -10
  113. package/dist/contracts/superadmin-teams.js +5 -8
  114. package/dist/contracts/superadmin-tenants.d.ts +0 -6
  115. package/dist/contracts/superadmin-tenants.js +1 -7
  116. package/dist/contracts/superadmin.d.ts +135 -54
  117. package/dist/contracts/superadmin.js +46 -23
  118. package/dist/contracts/tags.d.ts +12 -12
  119. package/dist/contracts/tags.js +3 -3
  120. package/dist/contracts/teams.d.ts +49 -14
  121. package/dist/contracts/teams.js +40 -22
  122. package/dist/contracts/transfers.d.ts +0 -49
  123. package/dist/contracts/transfers.js +4 -8
  124. package/dist/contracts/usage.d.ts +61 -0
  125. package/dist/contracts/usage.js +64 -0
  126. package/dist/contracts/variables.d.ts +2 -7
  127. package/dist/contracts/variables.js +9 -8
  128. package/dist/contracts/webhooks.d.ts +2 -2
  129. package/dist/contracts/zero.d.ts +102 -73
  130. package/dist/contracts/zero.js +94 -32
  131. package/dist/dashboard-paths/index.d.ts +1 -1
  132. package/dist/dashboard-paths/index.js +1 -0
  133. package/dist/docs/agent-prose.d.ts +2 -2
  134. package/dist/docs/agent-prose.js +63 -70
  135. package/dist/docs/agent-setup.js +45 -15
  136. package/dist/docs/catalog.d.ts +24 -24
  137. package/dist/docs/catalog.js +35 -24
  138. package/dist/docs/error-docs.js +194 -270
  139. package/dist/docs/skills.d.ts +12 -12
  140. package/dist/docs/skills.js +4 -4
  141. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  142. package/dist/test-helpers/fetch-stub.js +25 -0
  143. package/dist/utils/build-settings.d.ts +5 -1
  144. package/dist/utils/build-settings.js +91 -13
  145. package/dist/utils/canonical-json.d.ts +10 -0
  146. package/dist/utils/canonical-json.js +22 -0
  147. package/dist/utils/cast-transport.d.ts +28 -0
  148. package/dist/utils/cast-transport.js +52 -0
  149. package/dist/utils/claim-token.d.ts +0 -8
  150. package/dist/utils/claim-token.js +3 -15
  151. package/dist/utils/concurrency.js +1 -0
  152. package/dist/utils/credential-policy.d.ts +1 -1
  153. package/dist/utils/credential-policy.js +4 -3
  154. package/dist/utils/dns-instructions.js +9 -9
  155. package/dist/utils/env-file.d.ts +17 -0
  156. package/dist/utils/env-file.js +96 -0
  157. package/dist/utils/error-code.d.ts +2 -0
  158. package/dist/utils/error-code.js +8 -0
  159. package/dist/utils/generate-space-name.d.ts +8 -10
  160. package/dist/utils/generate-space-name.js +206 -21
  161. package/dist/utils/grant-decision.fixtures.json +2938 -0
  162. package/dist/utils/grants.d.ts +2 -0
  163. package/dist/utils/grants.js +26 -1
  164. package/dist/utils/gravatar.d.ts +9 -0
  165. package/dist/utils/gravatar.js +31 -0
  166. package/dist/utils/idempotency.d.ts +6 -0
  167. package/dist/utils/idempotency.js +28 -0
  168. package/dist/utils/local-space-state.d.ts +9 -16
  169. package/dist/utils/local-space-state.js +20 -50
  170. package/dist/utils/one-shot-replay.d.ts +4 -1
  171. package/dist/utils/page-colors.d.ts +14 -12
  172. package/dist/utils/page-colors.js +15 -13
  173. package/dist/utils/page-fonts.d.ts +2 -2
  174. package/dist/utils/page-fonts.js +15 -4
  175. package/dist/utils/pages.d.ts +21 -3
  176. package/dist/utils/pages.js +42 -13
  177. package/dist/utils/publish-form-data.d.ts +12 -0
  178. package/dist/utils/publish-form-data.js +10 -0
  179. package/dist/utils/publish-policy.d.ts +27 -0
  180. package/dist/utils/publish-policy.fixtures.json +403 -0
  181. package/dist/utils/publish-policy.js +45 -20
  182. package/dist/utils/push-new.d.ts +7 -0
  183. package/dist/utils/push-new.js +7 -0
  184. package/dist/utils/query-keys.d.ts +4 -5
  185. package/dist/utils/query-keys.js +2 -3
  186. package/dist/utils/runtime-paths.d.ts +5 -0
  187. package/dist/utils/runtime-paths.js +5 -0
  188. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  189. package/dist/utils/runtime-upload-batch.js +1 -27
  190. package/dist/utils/runtime-upload.d.ts +6 -11
  191. package/dist/utils/runtime-upload.js +10 -13
  192. package/dist/utils/secure-local-file.js +5 -16
  193. package/dist/utils/space-config.d.ts +1 -34
  194. package/dist/utils/space-config.js +8 -326
  195. package/dist/utils/static-runtime-policy.d.ts +9 -3
  196. package/dist/utils/static-runtime-policy.fixtures.json +38 -41
  197. package/dist/utils/static-runtime-policy.js +22 -56
  198. package/dist/utils/storage-policy.d.ts +1 -1
  199. package/dist/utils/storage-policy.js +3 -1
  200. package/dist/utils/structured-error.d.ts +14 -0
  201. package/dist/utils/structured-error.js +22 -0
  202. package/dist/utils/upload-session.d.ts +4 -1
  203. package/dist/utils/upload-session.js +33 -5
  204. package/dist/utils/version-path.js +13 -4
  205. package/dist/vocabulary.d.ts +13 -11
  206. package/dist/vocabulary.js +42 -28
  207. package/package.json +7 -17
  208. package/dist/config/postgres-budget.d.ts +0 -129
  209. package/dist/config/postgres-budget.js +0 -140
  210. package/dist/config/postgres-budget.json +0 -51
  211. package/dist/contracts/archives.d.ts +0 -331
  212. package/dist/contracts/archives.js +0 -120
  213. package/dist/contracts/deployments.d.ts +0 -574
  214. package/dist/contracts/deployments.js +0 -97
  215. package/dist/contracts/sites.d.ts +0 -416
  216. package/dist/contracts/sites.js +0 -168
  217. package/dist/contracts/superadmin-queues.d.ts +0 -414
  218. package/dist/contracts/superadmin-queues.js +0 -202
  219. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  220. package/dist/contracts/superadmin-runtime.js +0 -38
  221. package/dist/index.d.ts +0 -1
  222. package/dist/index.js +0 -1
  223. package/dist/utils/api.d.ts +0 -4
  224. package/dist/utils/api.js +0 -13
@@ -1,12 +1,13 @@
1
1
  import { defaultTreeAdapter, parse } from "parse5";
2
2
  import { SPACEFAST_WORDMARK_PATH } from "../brand-assets.js";
3
- import { CUSTOM_PAGE_IDS, PAGE_CATALOG, PAGE_ELEMENTS, PAGE_LAYOUT_FILE, PAGE_SOURCE_MAX_BYTES, SITE_PAGE_IDS, } from "../contracts/pages.js";
3
+ import { COLLAB_PAGE_ELEMENTS, COLLAB_PAGE_SOURCE, CUSTOM_PAGE_IDS, PAGE_CATALOG, PAGE_ELEMENTS, PAGE_LAYOUT_FILE, PAGE_SOURCE_MAX_BYTES, SITE_PAGE_IDS, } from "../contracts/pages.js";
4
4
  import { SPACE_CONFIG_ACCEPTED_FILES } from "../contracts/space-config.js";
5
5
  import { derivePageColors, PAGE_COLOR_DEFAULTS } from "./page-colors.js";
6
- import { PAGE_EMBEDDED_FONT_FACES, PAGE_FONT_FACES } from "./page-fonts.js";
6
+ import { PAGE_FONT_FACES, PAGE_FONT_PRELOAD_HTML } from "./page-fonts.js";
7
7
  export { PAGE_PREVIEW_QUERY, previewPageHref } from "./page-preview.js";
8
8
  const encoder = new TextEncoder();
9
9
  const knownElements = new Set(PAGE_ELEMENTS);
10
+ const collabElements = new Set(COLLAB_PAGE_ELEMENTS);
10
11
  const customPageIds = new Set(CUSTOM_PAGE_IDS);
11
12
  export function isPagePublicPath(path) {
12
13
  const lower = path.toLowerCase();
@@ -32,15 +33,21 @@ export function escapePageHtml(value) {
32
33
  .replaceAll('"', """)
33
34
  .replaceAll("'", "'");
34
35
  }
35
- // Pushed colors are intent; the readability engine (page-colors.ts) derives
36
- // what the templates actually paint with. A custom background pins a complete
37
- // scheme for both OS modes emitting `--sf-bg` alone used to leave dark-mode
38
- // visitors with the dark foreground on the user's light background.
39
- function themeStyle(theme) {
36
+ /**
37
+ * The custom properties a theme paints with. Pushed colors are intent; the
38
+ * readability engine (page-colors.ts) derives what the templates actually use.
39
+ * A custom background pins a complete scheme for both OS modes — emitting
40
+ * `--sf-bg` alone used to leave dark-mode visitors with the dark foreground on
41
+ * the user's light background.
42
+ *
43
+ * Exported because the dashboard's live preview paints the same tokens from an
44
+ * unsaved draft: it passes the colors it has already derived, and gets exactly
45
+ * what the published page would carry.
46
+ */
47
+ export function pageThemeCss(theme, derived = derivePageColors(theme)) {
40
48
  const root = [];
41
49
  const dark = [];
42
50
  if (theme.accent || theme.background) {
43
- const derived = derivePageColors(theme);
44
51
  // `derived.accent` is the parse-validated input (or the default), never
45
52
  // an arbitrary string straight from the theme.
46
53
  if (theme.accent)
@@ -58,7 +65,11 @@ function themeStyle(theme) {
58
65
  if (root.length === 0)
59
66
  return "";
60
67
  const darkBlock = dark.length > 0 ? `@media(prefers-color-scheme:dark){:root{${dark.join(";")}}}` : "";
61
- return `<style>:root{${root.join(";")}}${darkBlock}</style>`;
68
+ return `:root{${root.join(";")}}${darkBlock}`;
69
+ }
70
+ function themeStyle(theme) {
71
+ const css = pageThemeCss(theme);
72
+ return css ? `<style>${css}</style>` : "";
62
73
  }
63
74
  const runtimeSlot = (name, fallback) => `<!--sf-runtime:${name}:start-->${fallback}<!--sf-runtime:${name}:end-->`;
64
75
  function renderFiles(files) {
@@ -83,7 +94,7 @@ function renderFile(file) {
83
94
  function renderSpacefastBranding(hidden) {
84
95
  const wordmark = hidden
85
96
  ? ""
86
- : `<span class="sf-powered" tabindex="0"><svg class="sf-wordmark" viewBox="0 0 326 62" role="img" aria-label="Spacefast"><rect class="sf-wordmark-shadow" x="14" y="12" width="306" height="48"></rect><rect class="sf-wordmark-plate" x="10" y="8" width="306" height="48"></rect><path class="sf-wordmark-paper" d="${SPACEFAST_WORDMARK_PATH}"></path></svg><span class="sf-powered-line"><a href="https://spacefast.com">Best way to share what your agent made</a></span></span>`;
97
+ : `<span class="sf-powered" tabindex="0"><svg class="sf-wordmark" viewBox="0 0 152 28.9062" role="img" aria-label="Spacefast"><path fill="currentColor" d="${SPACEFAST_WORDMARK_PATH}"></path></svg><span class="sf-powered-line"><a href="https://spacefast.com">Best way to share what your agent made</a></span></span>`;
87
98
  return `<footer class="sf-colophon"><div class="sf-colophon-row">${wordmark}<a class="sf-help" href="https://spacefast.com/help">Need help?</a></div></footer>`;
88
99
  }
89
100
  function renderElement(element, context) {
@@ -127,9 +138,17 @@ const ELEMENT_PATTERN = /<\s*(sf-[a-z0-9-]+)\b[^>]*>(?:\s*<\s*\/\s*\1\s*>)?|<\s*
127
138
  export function expandPageHtml(source, context) {
128
139
  let output = source.replace(ELEMENT_PATTERN, (_match, paired, unary) => {
129
140
  const name = (paired ?? unary)?.toLowerCase();
141
+ // SAFETY: The set membership check admits only names from the PageElement literal set.
130
142
  return name && knownElements.has(name) ? renderElement(name, context) : _match;
131
143
  });
132
- output = output.replaceAll("/*sf-default-fonts*/", context.embedPageFonts ? PAGE_EMBEDDED_FONT_FACES : PAGE_FONT_FACES);
144
+ if (output.includes("/*sf-default-fonts*/")) {
145
+ output = output.replaceAll("/*sf-default-fonts*/", PAGE_FONT_FACES);
146
+ // The faces point at shared font origins; preloading ahead of the inline
147
+ // stylesheet starts the cross-origin fetches with the parse.
148
+ output = /<\s*style\b/i.test(output)
149
+ ? output.replace(/<\s*style\b/i, (tag) => `${PAGE_FONT_PRELOAD_HTML}${tag}`)
150
+ : `${PAGE_FONT_PRELOAD_HTML}${output}`;
151
+ }
133
152
  const tokens = themeStyle(context.theme ?? {});
134
153
  if (tokens) {
135
154
  output = /<\s*\/\s*head\s*>/i.test(output)
@@ -179,7 +198,11 @@ export function validatePageSources(input) {
179
198
  const path = sourcePath.includes("_pages/") ? sourcePath : `_pages/${sourcePath}`;
180
199
  const filename = path.slice(path.lastIndexOf("_pages/") + 7);
181
200
  const id = filename.replace(/\.html$/i, "");
182
- if (!customPageIds.has(id) || filename !== `${id}.html`) {
201
+ // A collab layout is Space-level — one review room, one URL — so unlike the
202
+ // gate and listing pages it has no per-directory form.
203
+ if (!customPageIds.has(id) ||
204
+ filename !== `${id}.html` ||
205
+ (id === "collab" && path !== COLLAB_PAGE_SOURCE)) {
183
206
  diagnostics.push({
184
207
  severity: "error",
185
208
  code: "unknown_page_template",
@@ -204,6 +227,10 @@ export function validatePageSources(input) {
204
227
  path,
205
228
  });
206
229
  for (const element of foundElements(source)) {
230
+ // Collab elements are the browser SDK's, not the compiler's: legal in the
231
+ // review room, meaningless anywhere the SDK is not the point.
232
+ if (id === "collab" && collabElements.has(element))
233
+ continue;
207
234
  if (!knownElements.has(element))
208
235
  diagnostics.push({
209
236
  severity: "error",
@@ -361,7 +388,7 @@ export function defaultPageBody(id) {
361
388
  // The built-in palette runs through the same readability engine as pushed
362
389
  // themes, so buttons and links remain readable for every accepted accent.
363
390
  const defaultPageColors = derivePageColors({});
364
- const sharedCss = `/*sf-default-fonts*/:root{color-scheme:light dark;--sf-accent:${PAGE_COLOR_DEFAULTS.accent};--sf-bg:${PAGE_COLOR_DEFAULTS.bg};--sf-fg:${PAGE_COLOR_DEFAULTS.fg};--sf-muted:${PAGE_COLOR_DEFAULTS.muted};--sf-card:${PAGE_COLOR_DEFAULTS.card};--sf-border:${PAGE_COLOR_DEFAULTS.border};--sf-link:${defaultPageColors.link};--sf-on-accent:${defaultPageColors.onAccent};--sf-error:#a82d1a;--sf-font:"Haskoy",-apple-system,BlinkMacSystemFont,ui-sans-serif,"Segoe UI",sans-serif;--sf-display-font:"Merriweather",Georgia,serif;--sf-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace}*{box-sizing:border-box}html{min-width:320px;background:var(--sf-bg);-webkit-font-smoothing:antialiased}body{margin:0;min-height:100vh;background:var(--sf-bg);color:var(--sf-fg);font:14px/1.65 var(--sf-font)}a{color:inherit}.sf-page{min-height:100vh;display:flex;flex-direction:column;padding:clamp(22px,4vw,44px)}.sf-header{display:flex;align-items:center;min-height:32px}.sf-page-center .sf-header{justify-content:center}.sf-brand{display:inline-flex;min-width:0;align-items:center;gap:10px;color:var(--sf-fg);text-decoration:none}.sf-brand-mark{width:7px;height:7px;flex:none;background:var(--sf-accent)}.sf-logo{width:28px;height:28px;flex:none;object-fit:contain}.sf-brand strong{min-width:0;overflow-wrap:anywhere;font-size:13px;font-weight:600}.sf-main{flex:1;width:100%;padding:clamp(34px,9vh,84px) 0}.sf-page-plain .sf-main{max-width:680px}.sf-page-center .sf-main{max-width:620px;margin:auto;display:flex;flex-direction:column;align-items:center;text-align:center}.sf-eyebrow{margin:0 0 14px;color:var(--sf-muted);font:500 11px/1.5 var(--sf-mono);letter-spacing:.12em;text-transform:uppercase;overflow-wrap:anywhere}.sf-page-center .sf-eyebrow{margin-bottom:16px}h1{max-width:20ch;margin:0 0 12px;color:var(--sf-fg);font:430 clamp(1.9rem,5vw,2.8rem)/1.22 var(--sf-display-font);letter-spacing:-.012em;text-wrap:balance}.sf-copy,.sf-sub{max-width:52ch;margin:0;color:var(--sf-muted);text-wrap:pretty}.sf-inline-path{font-family:var(--sf-mono);font-size:.9em;color:var(--sf-fg);overflow-wrap:anywhere}.sf-actions{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:22px}.sf-page-center .sf-actions{justify-content:center}.sf-button{display:inline-flex;min-height:44px;align-items:center;justify-content:center;border:0;background:var(--sf-accent);color:var(--sf-on-accent);padding:10px 16px;font:600 13px/1.4 var(--sf-font);text-decoration:none;cursor:pointer}.sf-ghost{color:var(--sf-muted);text-underline-offset:3px}.sf-button:hover{filter:brightness(1.06)}a:focus-visible,button:focus-visible{outline:2px solid var(--sf-link);outline-offset:2px}.sf-copy+.sf-copy{margin-top:12px}.sf-files{width:100%;margin:24px 0 0;padding:0;list-style:none}.sf-files li{display:flex;min-width:0;align-items:baseline;gap:18px;border-bottom:1px solid var(--sf-border)}.sf-files a{min-width:0;flex:1;padding:10px 2px;color:var(--sf-fg);font-weight:500;text-decoration:none}.sf-files a:hover{color:var(--sf-link)}.sf-file-name{overflow-wrap:anywhere}.sf-file-size{flex:none;padding:10px 2px;color:var(--sf-muted);font:12px/1.5 var(--sf-mono);white-space:nowrap}.sf-empty{max-width:420px;margin:24px 0 0;border:1px dashed var(--sf-border);padding:18px 20px;color:var(--sf-muted)}.sf-preview-wrap{width:min(100%,560px);aspect-ratio:16/9;margin:10px 0 18px;overflow:hidden;background:#fff;outline:1px solid color-mix(in srgb,var(--sf-fg) 10%,transparent);outline-offset:-1px}.sf-preview{display:block;width:100%;height:100%;border:0;object-fit:contain}.sf-file-meta{width:min(100%,560px);margin:0}.sf-file-meta div{display:flex;min-width:0;justify-content:space-between;gap:20px;border-bottom:1px solid var(--sf-border);padding:9px 2px}.sf-file-meta dt{color:var(--sf-fg);font-weight:600}.sf-file-meta dd{min-width:0;margin:0;color:var(--sf-muted);font-family:var(--sf-mono);overflow-wrap:anywhere;text-align:right}.sf-colophon{display:flex;flex-direction:column;align-items:center;gap:10px;padding:44px 0 26px;margin-top:auto;color:var(--sf-muted)}.sf-page-plain .sf-colophon{align-items:flex-start}.sf-colophon-row{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.sf-powered{display:inline-flex;align-items:center;gap:10px;cursor:pointer;outline:none}.sf-powered-line{display:none;font-size:12px;color:var(--sf-muted)}.sf-powered:hover .sf-powered-line,.sf-powered:focus .sf-powered-line,.sf-powered:focus-within .sf-powered-line{display:inline}.sf-powered-line a{color:var(--sf-link);text-decoration:underline;text-underline-offset:3px}.sf-help{font-size:12px;color:var(--sf-muted);text-decoration:underline;text-underline-offset:3px}.sf-help:hover{color:var(--sf-fg)}.sf-wordmark{display:block;flex:none;width:auto;height:11px}.sf-wordmark-shadow{fill:var(--sf-accent)}.sf-wordmark-plate{fill:#141419}.sf-wordmark-paper{fill:#fbf8f1}@media(max-width:520px){.sf-page{padding:20px}.sf-button{min-height:48px}.sf-file-meta div{flex-direction:column;gap:2px}.sf-file-meta dd{text-align:left}.sf-colophon{padding-top:32px}}@media(prefers-color-scheme:dark){:root{--sf-bg:${PAGE_COLOR_DEFAULTS.darkBg};--sf-fg:${PAGE_COLOR_DEFAULTS.darkFg};--sf-muted:${PAGE_COLOR_DEFAULTS.darkMuted};--sf-card:${PAGE_COLOR_DEFAULTS.darkCard};--sf-border:${PAGE_COLOR_DEFAULTS.darkBorder};--sf-link:${defaultPageColors.linkDark};--sf-error:#ff8a7a}.sf-preview-wrap{outline-color:#ffffff1a}}@media(forced-colors:active){.sf-brand-mark{background:CanvasText}.sf-button{border:1px solid ButtonText}.sf-powered:focus{outline:1px solid CanvasText;outline-offset:3px}}`;
391
+ const sharedCss = `/*sf-default-fonts*/:root{color-scheme:light dark;--sf-accent:${PAGE_COLOR_DEFAULTS.accent};--sf-bg:${PAGE_COLOR_DEFAULTS.bg};--sf-fg:${PAGE_COLOR_DEFAULTS.fg};--sf-muted:${PAGE_COLOR_DEFAULTS.muted};--sf-card:${PAGE_COLOR_DEFAULTS.card};--sf-border:${PAGE_COLOR_DEFAULTS.border};--sf-link:${defaultPageColors.link};--sf-on-accent:${defaultPageColors.onAccent};--sf-error:#a82d1a;--sf-font:-apple-system,BlinkMacSystemFont,"SF Pro Text","SF Pro Display","Segoe UI",system-ui,Roboto,"Helvetica Neue",Arial,sans-serif;--sf-display-font:"Recoleta",Georgia,serif;--sf-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace}*{box-sizing:border-box}html{min-width:320px;background:var(--sf-bg);-webkit-font-smoothing:antialiased}body{margin:0;min-height:100vh;background:var(--sf-bg);color:var(--sf-fg);font:14px/1.65 var(--sf-font)}a{color:inherit}.sf-page{min-height:100vh;display:flex;flex-direction:column;padding:clamp(22px,4vw,44px)}.sf-header{display:flex;align-items:center;min-height:32px}.sf-page-center .sf-header{justify-content:center}.sf-brand{display:inline-flex;min-width:0;align-items:center;gap:10px;color:var(--sf-fg);text-decoration:none}.sf-brand-mark{width:7px;height:7px;flex:none;background:var(--sf-accent)}.sf-logo{width:28px;height:28px;flex:none;object-fit:contain}.sf-brand strong{min-width:0;overflow-wrap:anywhere;font-size:13px;font-weight:600}.sf-main{flex:1;width:100%;padding:clamp(34px,9vh,84px) 0}.sf-page-plain .sf-main{max-width:680px}.sf-page-center .sf-main{max-width:620px;margin:auto;display:flex;flex-direction:column;align-items:center;text-align:center}.sf-eyebrow{margin:0 0 14px;color:var(--sf-muted);font:500 11px/1.5 var(--sf-mono);letter-spacing:.12em;text-transform:uppercase;overflow-wrap:anywhere}.sf-page-center .sf-eyebrow{margin-bottom:16px}h1{max-width:20ch;margin:0 0 12px;color:var(--sf-fg);font:400 clamp(1.9rem,5vw,2.8rem)/1.22 var(--sf-display-font);letter-spacing:-.012em;text-wrap:balance}.sf-copy,.sf-sub{max-width:52ch;margin:0;color:var(--sf-muted);text-wrap:pretty}.sf-inline-path{font-family:var(--sf-mono);font-size:.9em;color:var(--sf-fg);overflow-wrap:anywhere}.sf-actions{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:22px}.sf-page-center .sf-actions{justify-content:center}.sf-button{display:inline-flex;min-height:44px;align-items:center;justify-content:center;border:0;background:var(--sf-accent);color:var(--sf-on-accent);padding:10px 16px;font:600 13px/1.4 var(--sf-font);text-decoration:none;cursor:pointer}.sf-ghost{color:var(--sf-muted);text-underline-offset:3px}.sf-button:hover{filter:brightness(1.06)}a:focus-visible,button:focus-visible{outline:2px solid var(--sf-link);outline-offset:2px}.sf-copy+.sf-copy{margin-top:12px}.sf-files{width:100%;margin:24px 0 0;padding:0;list-style:none}.sf-files li{display:flex;min-width:0;align-items:baseline;gap:18px;border-bottom:1px solid var(--sf-border)}.sf-files a{min-width:0;flex:1;padding:10px 2px;color:var(--sf-fg);font-weight:500;text-decoration:none}.sf-files a:hover{color:var(--sf-link)}.sf-file-name{overflow-wrap:anywhere}.sf-file-size{flex:none;padding:10px 2px;color:var(--sf-muted);font:12px/1.5 var(--sf-mono);white-space:nowrap}.sf-empty{max-width:420px;margin:24px 0 0;border:1px dashed var(--sf-border);padding:18px 20px;color:var(--sf-muted)}.sf-preview-wrap{width:min(100%,560px);aspect-ratio:16/9;margin:10px 0 18px;overflow:hidden;background:#fff;outline:1px solid color-mix(in srgb,var(--sf-fg) 10%,transparent);outline-offset:-1px}.sf-preview{display:block;width:100%;height:100%;border:0;object-fit:contain}.sf-file-meta{width:min(100%,560px);margin:0}.sf-file-meta div{display:flex;min-width:0;justify-content:space-between;gap:20px;border-bottom:1px solid var(--sf-border);padding:9px 2px}.sf-file-meta dt{color:var(--sf-fg);font-weight:600}.sf-file-meta dd{min-width:0;margin:0;color:var(--sf-muted);font-family:var(--sf-mono);overflow-wrap:anywhere;text-align:right}.sf-colophon{display:flex;flex-direction:column;align-items:center;gap:10px;padding:44px 0 26px;margin-top:auto;color:var(--sf-muted)}.sf-page-plain .sf-colophon{align-items:flex-start}.sf-colophon-row{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.sf-powered{display:inline-flex;align-items:center;gap:10px;cursor:pointer;outline:none}.sf-powered-line{display:none;font-size:12px;color:var(--sf-muted)}.sf-powered:hover .sf-powered-line,.sf-powered:focus .sf-powered-line,.sf-powered:focus-within .sf-powered-line{display:inline}.sf-powered-line a{color:var(--sf-link);text-decoration:underline;text-underline-offset:3px}.sf-help{font-size:12px;color:var(--sf-muted);text-decoration:underline;text-underline-offset:3px}.sf-help:hover{color:var(--sf-fg)}.sf-wordmark{display:block;flex:none;width:auto;height:14px;color:var(--sf-fg)}@media(max-width:520px){.sf-page{padding:20px}.sf-button{min-height:48px}.sf-file-meta div{flex-direction:column;gap:2px}.sf-file-meta dd{text-align:left}.sf-colophon{padding-top:32px}}@media(prefers-color-scheme:dark){:root{--sf-bg:${PAGE_COLOR_DEFAULTS.darkBg};--sf-fg:${PAGE_COLOR_DEFAULTS.darkFg};--sf-muted:${PAGE_COLOR_DEFAULTS.darkMuted};--sf-card:${PAGE_COLOR_DEFAULTS.darkCard};--sf-border:${PAGE_COLOR_DEFAULTS.darkBorder};--sf-link:${defaultPageColors.linkDark};--sf-error:#ff8a7a}.sf-preview-wrap{outline-color:#ffffff1a}}@media(forced-colors:active){.sf-brand-mark{background:CanvasText}.sf-button{border:1px solid ButtonText}.sf-powered:focus{outline:1px solid CanvasText;outline-offset:3px}}`;
365
392
  const accessCss = `.sf-page.access .sf-main{max-width:26rem;padding:clamp(42px,8vh,72px) 0}.sf-page.access h1{max-width:18ch;margin-bottom:14px;font:600 clamp(2rem,7vw,2.65rem)/1.12 var(--sf-font);letter-spacing:-.035em}.sf-page.access .sf-copy{font-size:15px;line-height:1.6}.sf-access-default{display:flex;width:100%;flex-direction:column;align-items:center}.sf-access-status{padding:10px 12px;border:1px solid color-mix(in srgb,var(--sf-fg) 10%,transparent);border-radius:8px;background:color-mix(in srgb,var(--sf-fg) 4%,transparent);color:var(--sf-fg)}.sf-access-lanes{display:flex;flex-direction:column;gap:16px;width:min(23rem,100%);margin-top:30px;text-align:left}.sf-access-lanes form{display:flex;flex-direction:column;gap:14px;margin:0}.sf-input{box-sizing:border-box;width:100%;min-height:46px;padding:11px 12px;border:1px solid color-mix(in srgb,var(--sf-fg) 18%,transparent);border-radius:8px;outline:2px solid transparent;outline-offset:-1px;background:color-mix(in srgb,var(--sf-bg) 92%,var(--sf-fg));color:var(--sf-fg);font:500 15px/1.45 var(--sf-font);appearance:none}.sf-input::placeholder{color:color-mix(in srgb,var(--sf-muted) 82%,transparent);font:inherit}.sf-input:hover{border-color:color-mix(in srgb,var(--sf-fg) 34%,transparent)}.sf-input:focus-visible{border-color:var(--sf-accent);outline-color:var(--sf-accent)}textarea.sf-input{min-height:92px;resize:vertical}.sf-label{color:var(--sf-fg);font:600 13px/1.4 var(--sf-font)}.sf-access-lanes .sf-button{width:100%;min-height:46px;border-radius:8px}.sf-access-or{display:flex;align-items:center;gap:12px;color:var(--sf-muted);font-size:13px}.sf-access-or:before,.sf-access-or:after{content:"";flex:1;border-top:1px solid color-mix(in srgb,var(--sf-fg) 12%,transparent)}.sf-error{margin:0;color:var(--sf-error);font-size:14px}.sf-access-password-row{display:flex;gap:8px}.sf-access-password-row .sf-input{min-width:0;flex:1}.sf-access-password-row .sf-button{width:auto;flex:none}.sf-access-request{padding-top:2px}.sf-access-request summary{padding:4px 0;cursor:pointer;color:var(--sf-muted);font:550 14px/1.5 var(--sf-font)}.sf-access-request summary:hover{color:var(--sf-fg)}.sf-access-request[open] summary{color:var(--sf-fg)}.sf-access-request form{margin-top:16px}@media(max-width:520px){.sf-page.access .sf-main{padding:38px 0}.sf-page.access h1{font-size:2rem}.sf-page.access .sf-copy{font-size:16px}.sf-input{min-height:48px;font-size:16px}.sf-access-lanes .sf-button{min-height:48px}.sf-access-password-row{flex-direction:column}.sf-access-password-row .sf-button{width:100%}}@media(prefers-color-scheme:dark){.sf-access-status{background:color-mix(in srgb,var(--sf-fg) 5%,transparent)}}`;
366
393
  const pageHeader = '<header class="sf-header"><a class="sf-brand" href="/" aria-label="Homepage"><span class="sf-brand-mark" aria-hidden="true"></span><sf-logo></sf-logo><strong><sf-name></sf-name></strong></a></header>';
367
394
  const pageFooter = "<sf-spacefast-branding></sf-spacefast-branding>";
@@ -384,8 +411,10 @@ export function defaultPageTemplate(id) {
384
411
  }
385
412
  export function pageIdFromFilename(filename) {
386
413
  const id = filename.replace(/\.html$/, "");
414
+ // SAFETY: The set membership check admits only ids from the CustomPageId literal set.
387
415
  return customPageIds.has(id) ? id : null;
388
416
  }
389
417
  export function allDefaultPageTemplates() {
418
+ // SAFETY: SITE_PAGE_IDS maps every SitePageId to its corresponding default template entry.
390
419
  return Object.fromEntries(SITE_PAGE_IDS.map((id) => [id, DEFAULT_PAGE_TEMPLATES[id]]));
391
420
  }
@@ -19,4 +19,16 @@ export declare function createPublishFormData(input: {
19
19
  slug?: string | undefined;
20
20
  title?: string | undefined;
21
21
  description?: string | undefined;
22
+ /** Live promotion intent for archive builds; null produces an unpromoted ready version. */
23
+ channel?: "live" | null | undefined;
24
+ /**
25
+ * Control-plane-only provenance for the version's build/deploy read models —
26
+ * a compiled runtime (Zero capsule, Functions worker) declares its artifact
27
+ * here under its own key (e.g. `statticZero`). Same shape the CLI sends on the
28
+ * declared-session path; the multipart intake reads it from the `metadata`
29
+ * JSON field and folds it into `publishRequestSchema.sourceMetadata`.
30
+ */
31
+ sourceMetadata?: Record<string, unknown> | undefined;
32
+ /** Bounded build transcript recorded on the version when a build ran. */
33
+ buildLog?: string | undefined;
22
34
  }): FormData;
@@ -5,6 +5,16 @@ export function createPublishFormData(input) {
5
5
  form.append("spaceId", input.spaceId);
6
6
  if (input.description)
7
7
  form.append("description", input.description);
8
+ // The multipart intake merges this JSON field into the parsed publish body
9
+ // (it reads `payload`/`publish`/`metadata`), so build provenance rides the
10
+ // one-shot form exactly as it rides the CLI's declared-session JSON body.
11
+ if (input.sourceMetadata || input.buildLog || input.channel !== undefined) {
12
+ form.append("metadata", JSON.stringify({
13
+ ...(input.sourceMetadata ? { sourceMetadata: input.sourceMetadata } : {}),
14
+ ...(input.buildLog ? { buildLog: input.buildLog } : {}),
15
+ ...(input.channel !== undefined ? { channel: input.channel } : {}),
16
+ }));
17
+ }
8
18
  if (input.slug || input.title) {
9
19
  form.append("space", JSON.stringify({
10
20
  ...(input.slug ? { slug: input.slug } : {}),
@@ -4,7 +4,34 @@ export declare const MANIFEST_MAX_PATH_BYTES = 1024;
4
4
  export declare const MANIFEST_MAX_BODY_BYTES: number;
5
5
  export declare const MAX_VERSION_FILE_SIZE_BYTES: number;
6
6
  export declare const MAX_VERSION_TOTAL_BYTES: number;
7
+ export declare const STATIC_EXECUTION_CONTROL_FILES: string[];
8
+ export declare const STATIC_RUNTIME_CONTROL_SEGMENT_PREFIXES: string[];
9
+ export declare const STATIC_RUNTIME_ROOT_SEGMENTS: string[];
10
+ export declare const STATIC_RUNTIME_CONTROL_PATHS: string[];
11
+ export declare const STATIC_RUNTIME_CONTROL_PREFIXES: string[];
7
12
  export declare function normalizePublishPath(relativePath: string): string;
13
+ /**
14
+ * The one subtree inside the runtime control prefix that a publish may write.
15
+ *
16
+ * `sf publish` compiles a version's worker into `__spacefast/functions/bundles/`
17
+ * so the bundle travels through the ordinary manifest and upload path instead of
18
+ * needing a parallel one. Keeping it under the control prefix is what makes it
19
+ * unservable as static content by default — the runtime answers it only behind a
20
+ * signature.
21
+ *
22
+ * Scoped to `bundles/` rather than the whole `__spacefast/functions/` directory
23
+ * on purpose: the sibling `config.json` holds the key those signatures are
24
+ * minted with, and a publish that could overwrite it would be a publish that
25
+ * could mint its own tokens.
26
+ */
27
+ export declare const PUBLISHABLE_BUILD_ARTIFACT_PREFIX = "__spacefast/functions/bundles/";
28
+ /**
29
+ * The bundle carve-out's sibling for the Next-cache seed document: the same
30
+ * reasoning (travel through the ordinary manifest and upload path, stay
31
+ * unservable by default), the same deliberate narrowness (a prefix of its
32
+ * own, never the whole `functions/` directory — see the bundle comment).
33
+ */
34
+ export declare const PUBLISHABLE_SEED_ARTIFACT_PREFIX = "__spacefast/functions/seeds/";
8
35
  export declare function publishPathIgnored(relativePath: string): boolean;
9
36
  export declare function publishPathIsConvention(relativePath: string): boolean;
10
37
  export declare function providerRuntimeFeatureWarnings(paths: readonly string[]): PublishManifestWarning[];
@@ -0,0 +1,403 @@
1
+ [
2
+ {
3
+ "path": "index.html",
4
+ "expected": null
5
+ },
6
+ {
7
+ "path": "index.php",
8
+ "expected": null,
9
+ "why": "never lands in the docroot, so it serves as inert bytes"
10
+ },
11
+ {
12
+ "path": "nested/deep/file.txt",
13
+ "expected": null
14
+ },
15
+ {
16
+ "path": "_redirects",
17
+ "expected": null
18
+ },
19
+ {
20
+ "path": ".well-known/security.txt",
21
+ "expected": null
22
+ },
23
+ {
24
+ "path": ".env.example",
25
+ "expected": null
26
+ },
27
+ {
28
+ "path": "docs/installer.php",
29
+ "expected": null
30
+ },
31
+ {
32
+ "path": "my__stattic_thing.txt",
33
+ "expected": null,
34
+ "why": "reserved names match a segment start, not mid-string"
35
+ },
36
+ {
37
+ "path": "assets/app__spacefast.js",
38
+ "expected": null
39
+ },
40
+ {
41
+ "path": "__sf",
42
+ "expected": "static_runtime_control_path_not_supported",
43
+ "why": "the '/__sf' namespace row matches the bare segment"
44
+ },
45
+ {
46
+ "path": "__sf/redeem",
47
+ "expected": "static_runtime_control_path_not_supported"
48
+ },
49
+ {
50
+ "path": "__SF/redeem",
51
+ "expected": "static_runtime_control_path_not_supported",
52
+ "why": "the row folds case"
53
+ },
54
+ {
55
+ "path": "__sf/access/open",
56
+ "expected": "static_runtime_control_path_not_supported"
57
+ },
58
+ {
59
+ "path": "__sfx/endpoint.json",
60
+ "expected": null,
61
+ "why": "'namespace' is whole-segment: /__sfx is not /__sf"
62
+ },
63
+ {
64
+ "path": "__SF_future/endpoint.json",
65
+ "expected": null
66
+ },
67
+ {
68
+ "path": "__sfoo.html",
69
+ "expected": null
70
+ },
71
+ {
72
+ "path": "nested/__sf/content.json",
73
+ "expected": null,
74
+ "why": "the table is only consulted for paths starting /__"
75
+ },
76
+ {
77
+ "path": "a/b/__sf/c.txt",
78
+ "expected": null
79
+ },
80
+ {
81
+ "path": "my__sf_thing.txt",
82
+ "expected": null
83
+ },
84
+ {
85
+ "path": "__span/index.html",
86
+ "expected": "static_runtime_control_path_not_supported"
87
+ },
88
+ {
89
+ "path": "__SPAN/collision.json",
90
+ "expected": "static_runtime_control_path_not_supported"
91
+ },
92
+ {
93
+ "path": "__spanish/page.html",
94
+ "expected": null,
95
+ "why": "the control table's own example of a tenant path"
96
+ },
97
+ {
98
+ "path": "nested/__span/x.json",
99
+ "expected": null
100
+ },
101
+ {
102
+ "path": "__spacefast/x.txt",
103
+ "expected": "static_runtime_control_path_not_supported"
104
+ },
105
+ {
106
+ "path": "__spacefast/api.php/state",
107
+ "expected": "static_runtime_control_path_not_supported"
108
+ },
109
+ {
110
+ "path": "__spacefast_generated/theme.css",
111
+ "expected": "static_runtime_control_path_not_supported",
112
+ "why": "the runtime writes generated assets here"
113
+ },
114
+ {
115
+ "path": "nested/__SPACEFAST/upl/files/index.html",
116
+ "expected": "static_runtime_control_path_not_supported"
117
+ },
118
+ {
119
+ "path": "__stattic/x.txt",
120
+ "expected": "static_runtime_control_path_not_supported"
121
+ },
122
+ {
123
+ "path": "__stattic_probe",
124
+ "expected": "static_runtime_control_path_not_supported"
125
+ },
126
+ {
127
+ "path": "a/__STATTIC_probe/x.txt",
128
+ "expected": "static_runtime_control_path_not_supported"
129
+ },
130
+ {
131
+ "path": "__stattic/functions/bundles/abc/bundle.json",
132
+ "expected": "static_runtime_control_path_not_supported",
133
+ "why": "the carve-out is __spacefast only"
134
+ },
135
+ {
136
+ "path": "__spacefast/functions/bundles/abc123/bundle.json",
137
+ "expected": null,
138
+ "why": "compiled Functions bundle"
139
+ },
140
+ {
141
+ "path": "__spacefast/functions/config.json",
142
+ "expected": "static_runtime_control_path_not_supported",
143
+ "why": "holds the key that signs bundle URLs"
144
+ },
145
+ {
146
+ "path": "__spacefast/functions/bundlesX/evil.json",
147
+ "expected": "static_runtime_control_path_not_supported"
148
+ },
149
+ {
150
+ "path": "__spacefast/functions/seeds/abc123/seed.json",
151
+ "expected": null,
152
+ "why": "the Next-cache seed document, the bundle carve-out's sibling"
153
+ },
154
+ {
155
+ "path": "__spacefast/functions/seeds.json",
156
+ "expected": "static_runtime_control_path_not_supported",
157
+ "why": "the carve-out is the seeds/ prefix, not anything starting with seeds"
158
+ },
159
+ {
160
+ "path": "__spacefast/zero/deploy.json",
161
+ "expected": null,
162
+ "why": "the Zero deploy record"
163
+ },
164
+ {
165
+ "path": "__spacefast/zero/DEPLOY.JSON",
166
+ "expected": "static_runtime_control_path_not_supported",
167
+ "why": "the deploy record is one exact path, not folded"
168
+ },
169
+ {
170
+ "path": "__Spacefast/zero/deploy.json",
171
+ "expected": "static_runtime_control_path_not_supported"
172
+ },
173
+ {
174
+ "path": "__spacefast/zero/deploy.json/evil",
175
+ "expected": "static_runtime_control_path_not_supported"
176
+ },
177
+ {
178
+ "path": "__spacefast/anything",
179
+ "expected": "static_runtime_control_path_not_supported"
180
+ },
181
+ {
182
+ "path": ".htaccess",
183
+ "expected": "static_control_file_not_supported"
184
+ },
185
+ {
186
+ "path": "a/.htaccess",
187
+ "expected": "static_control_file_not_supported"
188
+ },
189
+ {
190
+ "path": "nested/.HTACCESS",
191
+ "expected": "static_control_file_not_supported"
192
+ },
193
+ {
194
+ "path": ".user.ini",
195
+ "expected": "static_control_file_not_supported"
196
+ },
197
+ {
198
+ "path": "nested/.User.INI",
199
+ "expected": "static_control_file_not_supported"
200
+ },
201
+ {
202
+ "path": "custom-redirects.php",
203
+ "expected": "static_runtime_control_path_not_supported",
204
+ "why": "engine-manifest.json aliases it into the docroot root"
205
+ },
206
+ {
207
+ "path": "CUSTOM-REDIRECTS.PHP",
208
+ "expected": "static_runtime_control_path_not_supported"
209
+ },
210
+ {
211
+ "path": "engine-manifest.json",
212
+ "expected": "static_runtime_control_path_not_supported"
213
+ },
214
+ {
215
+ "path": "nested/custom-redirects.php",
216
+ "expected": null,
217
+ "why": "only the docroot root collides"
218
+ },
219
+ {
220
+ "path": "init.php",
221
+ "expected": null,
222
+ "why": "engine/init.php installs inside an immutable release, not the docroot"
223
+ },
224
+ {
225
+ "path": ".spacefast/storage/routes/current.php",
226
+ "expected": "static_runtime_control_path_not_supported"
227
+ },
228
+ {
229
+ "path": ".spacefast/engine/init.php",
230
+ "expected": "static_runtime_control_path_not_supported"
231
+ },
232
+ {
233
+ "path": ".stattic/storage/routes/current.php",
234
+ "expected": "static_runtime_control_path_not_supported",
235
+ "why": "legacy layout still serves"
236
+ },
237
+ {
238
+ "path": ".stattic/engine/init.php",
239
+ "expected": "static_runtime_control_path_not_supported"
240
+ },
241
+ {
242
+ "path": ".spacefast/config.json",
243
+ "expected": null,
244
+ "why": "state dirs also carry publishable configuration"
245
+ },
246
+ {
247
+ "path": ".spacefast/routes.json",
248
+ "expected": null
249
+ },
250
+ {
251
+ "path": "nested/.SPACEFAST/routes.json",
252
+ "expected": null
253
+ },
254
+ {
255
+ "path": ".spacefast/agents/settings.json",
256
+ "expected": null
257
+ },
258
+ {
259
+ "path": ".stattic/routes.json",
260
+ "expected": null,
261
+ "why": "the legacy state dir carries the same publishable configuration"
262
+ },
263
+ {
264
+ "path": ".well-known/spacefast-runtime",
265
+ "expected": "static_runtime_control_path_not_supported"
266
+ },
267
+ {
268
+ "path": ".well-known/stattic-runtime",
269
+ "expected": "static_runtime_control_path_not_supported"
270
+ },
271
+ {
272
+ "path": ".WELL-KNOWN/SPACEFAST-RUNTIME",
273
+ "expected": "static_runtime_control_path_not_supported"
274
+ },
275
+ {
276
+ "path": "../escape.txt",
277
+ "expected": "invalid_file_path"
278
+ },
279
+ {
280
+ "path": "a/../b.txt",
281
+ "expected": "invalid_file_path"
282
+ },
283
+ {
284
+ "path": "a//b.txt",
285
+ "expected": "invalid_file_path"
286
+ },
287
+ {
288
+ "path": "a/./b.txt",
289
+ "expected": "invalid_file_path"
290
+ },
291
+ {
292
+ "path": "./a.txt",
293
+ "expected": "invalid_file_path"
294
+ },
295
+ {
296
+ "path": "/absolute.txt",
297
+ "expected": "invalid_file_path"
298
+ },
299
+ {
300
+ "path": "a\\b.txt",
301
+ "expected": "invalid_file_path",
302
+ "why": "backslash is never a separator at intake"
303
+ },
304
+ {
305
+ "path": " leading.txt",
306
+ "expected": "invalid_file_path"
307
+ },
308
+ {
309
+ "path": "trailing.txt ",
310
+ "expected": "invalid_file_path"
311
+ },
312
+ {
313
+ "path": "trailing./x",
314
+ "expected": "invalid_file_path"
315
+ },
316
+ {
317
+ "path": "trailing /x",
318
+ "expected": "invalid_file_path"
319
+ },
320
+ {
321
+ "path": "dir./file.txt",
322
+ "expected": "invalid_file_path"
323
+ },
324
+ {
325
+ "path": "",
326
+ "expected": "invalid_file_path"
327
+ },
328
+ {
329
+ "path": ".",
330
+ "expected": "invalid_file_path"
331
+ },
332
+ {
333
+ "path": "..",
334
+ "expected": "invalid_file_path"
335
+ },
336
+ {
337
+ "path": "a/",
338
+ "expected": "invalid_file_path"
339
+ },
340
+ {
341
+ "path": "/",
342
+ "expected": "invalid_file_path"
343
+ },
344
+ {
345
+ "path": "caf\u00e9.txt",
346
+ "expected": null,
347
+ "why": "already NFC"
348
+ },
349
+ {
350
+ "path": "cafe\u0301.txt",
351
+ "expected": "invalid_file_path",
352
+ "why": "NFD: the canonical form is NFC, and intake never rewrites"
353
+ },
354
+ {
355
+ "path": "\u4e2d.txt",
356
+ "expected": null
357
+ },
358
+ {
359
+ "path": "\u00fcber/stra\u00dfe.html",
360
+ "expected": null
361
+ },
362
+ {
363
+ "path": "emoji-\ud83d\ude80.txt",
364
+ "expected": null
365
+ },
366
+ {
367
+ "path": "a%20b.txt",
368
+ "expected": null,
369
+ "why": "percent escapes are decoded once BEFORE this policy, never here"
370
+ },
371
+ {
372
+ "path": "a%2Fb.txt",
373
+ "expected": null
374
+ },
375
+ {
376
+ "path": "100%.txt",
377
+ "expected": null
378
+ },
379
+ {
380
+ "path": "nul\u0000.txt",
381
+ "expected": "invalid_file_path",
382
+ "why": "a control character can never be part of a servable path"
383
+ },
384
+ {
385
+ "path": "bell\u0007.txt",
386
+ "expected": "invalid_file_path"
387
+ },
388
+ {
389
+ "path": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt",
390
+ "expected": null,
391
+ "why": "exactly 1024 bytes"
392
+ },
393
+ {
394
+ "path": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt",
395
+ "expected": "path_too_long",
396
+ "why": "1025 bytes"
397
+ },
398
+ {
399
+ "path": "\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9\u00e9.test",
400
+ "expected": "path_too_long",
401
+ "why": "1025 bytes: the cap counts bytes, not code points"
402
+ }
403
+ ]