@spacefast/common 0.0.24 → 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 (320) 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.d.ts +6 -146
  7. package/dist/agents/connect-targets.js +63 -295
  8. package/dist/agents/private-key-oauth.d.ts +31 -2
  9. package/dist/agents/private-key-oauth.js +166 -92
  10. package/dist/agents/registry/core.d.ts +292 -0
  11. package/dist/agents/registry/core.js +1388 -0
  12. package/dist/agents/registry/deeplink-probes.d.ts +23 -0
  13. package/dist/agents/registry/deeplink-probes.js +18 -0
  14. package/dist/agents/registry/node.d.ts +56 -0
  15. package/dist/agents/registry/node.js +339 -0
  16. package/dist/brand-assets-build.js +3 -6
  17. package/dist/brand-assets.d.ts +5 -15
  18. package/dist/brand-assets.js +12 -14
  19. package/dist/brand.d.ts +17 -0
  20. package/dist/brand.js +24 -4
  21. package/dist/config/domains.d.ts +79 -12
  22. package/dist/config/domains.js +128 -29
  23. package/dist/config/index.js +1 -2
  24. package/dist/contracts/abuse.js +9 -24
  25. package/dist/contracts/access-profiles.d.ts +86 -0
  26. package/dist/contracts/access-profiles.js +89 -0
  27. package/dist/contracts/access.d.ts +24 -30
  28. package/dist/contracts/access.js +33 -64
  29. package/dist/contracts/activity.d.ts +18 -1
  30. package/dist/contracts/activity.js +72 -26
  31. package/dist/contracts/analytics.js +8 -10
  32. package/dist/contracts/api-keys.d.ts +77 -52
  33. package/dist/contracts/api-keys.js +44 -24
  34. package/dist/contracts/application-journal.d.ts +216 -0
  35. package/dist/contracts/application-journal.js +167 -0
  36. package/dist/contracts/auth.d.ts +90 -17
  37. package/dist/contracts/auth.js +86 -17
  38. package/dist/contracts/beta.d.ts +9 -4
  39. package/dist/contracts/beta.js +20 -16
  40. package/dist/contracts/billing.d.ts +2 -1
  41. package/dist/contracts/billing.js +37 -11
  42. package/dist/contracts/bootstrap.d.ts +791 -0
  43. package/dist/contracts/bootstrap.js +51 -0
  44. package/dist/contracts/builds.d.ts +209 -18
  45. package/dist/contracts/builds.js +93 -27
  46. package/dist/contracts/channels.d.ts +43 -0
  47. package/dist/contracts/channels.js +25 -9
  48. package/dist/contracts/cli.d.ts +7 -0
  49. package/dist/contracts/cli.js +6 -0
  50. package/dist/contracts/collab-public.d.ts +1 -1
  51. package/dist/contracts/collab-public.js +5 -8
  52. package/dist/contracts/comments.d.ts +44 -34
  53. package/dist/contracts/comments.js +107 -77
  54. package/dist/contracts/commerce.d.ts +165 -0
  55. package/dist/contracts/commerce.js +146 -0
  56. package/dist/contracts/common.d.ts +3 -3
  57. package/dist/contracts/common.js +20 -31
  58. package/dist/contracts/content-contract-verification.d.ts +521 -0
  59. package/dist/contracts/content-contract-verification.js +320 -0
  60. package/dist/contracts/content-platform-fixture.d.ts +1278 -0
  61. package/dist/contracts/content-platform-fixture.js +44 -0
  62. package/dist/contracts/content-program.d.ts +994 -0
  63. package/dist/contracts/content-program.js +658 -0
  64. package/dist/contracts/content-sync.d.ts +290 -0
  65. package/dist/contracts/content-sync.js +167 -0
  66. package/dist/contracts/content.d.ts +432 -0
  67. package/dist/contracts/content.js +303 -0
  68. package/dist/contracts/continuation.d.ts +17 -1
  69. package/dist/contracts/continuation.js +23 -23
  70. package/dist/contracts/countries.js +3 -4
  71. package/dist/contracts/crons.d.ts +52 -0
  72. package/dist/contracts/crons.js +60 -0
  73. package/dist/contracts/dashboard-prefs.d.ts +56 -0
  74. package/dist/contracts/dashboard-prefs.js +51 -0
  75. package/dist/contracts/device-auth.d.ts +234 -37
  76. package/dist/contracts/device-auth.js +267 -53
  77. package/dist/contracts/docs.d.ts +1 -5
  78. package/dist/contracts/docs.js +7 -15
  79. package/dist/contracts/domains.d.ts +501 -93
  80. package/dist/contracts/domains.js +193 -55
  81. package/dist/contracts/email-preferences.d.ts +3 -3
  82. package/dist/contracts/email-preferences.js +6 -7
  83. package/dist/contracts/enums.d.ts +27 -26
  84. package/dist/contracts/enums.js +68 -61
  85. package/dist/contracts/error-code-meta.d.ts +283 -7
  86. package/dist/contracts/error-code-meta.js +97 -5
  87. package/dist/contracts/error-codes.d.ts +5 -5
  88. package/dist/contracts/error-codes.js +82 -17
  89. package/dist/contracts/events.d.ts +100 -20
  90. package/dist/contracts/events.js +107 -20
  91. package/dist/contracts/execution.d.ts +48 -64
  92. package/dist/contracts/execution.js +52 -62
  93. package/dist/contracts/feature-lifecycle.d.ts +73 -27
  94. package/dist/contracts/feature-lifecycle.js +86 -26
  95. package/dist/contracts/features.d.ts +68 -19
  96. package/dist/contracts/features.js +42 -41
  97. package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
  98. package/dist/contracts/frame-session.d.ts +192 -0
  99. package/dist/contracts/frame-session.js +164 -0
  100. package/dist/contracts/functions.d.ts +338 -164
  101. package/dist/contracts/functions.js +367 -198
  102. package/dist/contracts/git.d.ts +27 -33
  103. package/dist/contracts/git.js +10 -11
  104. package/dist/contracts/grant-copy.d.ts +43 -0
  105. package/dist/contracts/grant-copy.js +44 -0
  106. package/dist/contracts/grants.d.ts +62 -54
  107. package/dist/contracts/grants.js +124 -23
  108. package/dist/contracts/ids.d.ts +4 -1
  109. package/dist/contracts/ids.js +6 -3
  110. package/dist/contracts/internal.d.ts +8 -18
  111. package/dist/contracts/internal.js +13 -14
  112. package/dist/contracts/mcp.d.ts +28 -0
  113. package/dist/contracts/mcp.js +28 -0
  114. package/dist/contracts/me.d.ts +6 -1
  115. package/dist/contracts/me.js +12 -6
  116. package/dist/contracts/notifications.d.ts +27 -29
  117. package/dist/contracts/notifications.js +9 -4
  118. package/dist/contracts/oauth-resources.d.ts +112 -10
  119. package/dist/contracts/oauth-resources.js +116 -11
  120. package/dist/contracts/oauth-scope-actions.d.ts +90 -0
  121. package/dist/contracts/oauth-scope-actions.js +126 -0
  122. package/dist/contracts/operations.d.ts +27 -4
  123. package/dist/contracts/operations.js +12 -6
  124. package/dist/contracts/pages.d.ts +31 -7
  125. package/dist/contracts/pages.js +22 -5
  126. package/dist/contracts/partner-notifications.d.ts +3 -0
  127. package/dist/contracts/partner-notifications.js +65 -0
  128. package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
  129. package/dist/contracts/{platform.js → partner.js} +27 -40
  130. package/dist/contracts/plan-policy.js +1 -1
  131. package/dist/contracts/principal-assertion.d.ts +43 -0
  132. package/dist/contracts/principal-assertion.js +67 -0
  133. package/dist/contracts/principals.d.ts +73 -0
  134. package/dist/contracts/principals.js +59 -0
  135. package/dist/contracts/privacy.d.ts +11 -0
  136. package/dist/contracts/privacy.js +17 -0
  137. package/dist/contracts/problem-document.js +3 -4
  138. package/dist/contracts/publish-archive.d.ts +269 -161
  139. package/dist/contracts/publish-archive.js +11 -1
  140. package/dist/contracts/push-new.d.ts +8 -8
  141. package/dist/contracts/quotas.js +5 -5
  142. package/dist/contracts/realtime.d.ts +52 -0
  143. package/dist/contracts/realtime.js +54 -0
  144. package/dist/contracts/repository-connections.d.ts +44 -31
  145. package/dist/contracts/repository-connections.js +17 -5
  146. package/dist/contracts/resources.d.ts +105 -14
  147. package/dist/contracts/resources.js +150 -26
  148. package/dist/contracts/route-inventory.d.ts +358 -0
  149. package/dist/contracts/route-inventory.js +141 -0
  150. package/dist/contracts/runtime-api.d.ts +206 -82
  151. package/dist/contracts/runtime-api.js +131 -70
  152. package/dist/contracts/runtime-app.d.ts +13 -23
  153. package/dist/contracts/runtime-app.js +39 -24
  154. package/dist/contracts/runtime-components.d.ts +143 -0
  155. package/dist/contracts/runtime-components.js +199 -0
  156. package/dist/contracts/runtime-db.d.ts +49 -23
  157. package/dist/contracts/runtime-db.js +87 -22
  158. package/dist/contracts/runtime-purge.d.ts +8 -0
  159. package/dist/contracts/runtime-purge.js +5 -0
  160. package/dist/contracts/runtime-services.d.ts +43 -75
  161. package/dist/contracts/runtime-services.js +29 -49
  162. package/dist/contracts/runtime-storage.d.ts +15 -14
  163. package/dist/contracts/runtime-storage.js +30 -14
  164. package/dist/contracts/sf-config-v1.d.ts +48 -14
  165. package/dist/contracts/slugs.js +13 -4
  166. package/dist/contracts/space-config.d.ts +283 -19
  167. package/dist/contracts/space-config.js +430 -80
  168. package/dist/contracts/spaces.d.ts +376 -290
  169. package/dist/contracts/spaces.js +200 -135
  170. package/dist/contracts/superadmin-activity.d.ts +3 -43
  171. package/dist/contracts/superadmin-activity.js +6 -30
  172. package/dist/contracts/superadmin-emails.d.ts +1 -0
  173. package/dist/contracts/superadmin-emails.js +17 -1
  174. package/dist/contracts/superadmin-runtime.d.ts +199 -0
  175. package/dist/contracts/superadmin-runtime.js +141 -0
  176. package/dist/contracts/superadmin-search.d.ts +22 -2
  177. package/dist/contracts/superadmin-search.js +14 -6
  178. package/dist/contracts/superadmin-spaces.d.ts +438 -189
  179. package/dist/contracts/superadmin-spaces.js +131 -25
  180. package/dist/contracts/superadmin-teams.d.ts +5 -2
  181. package/dist/contracts/superadmin-teams.js +1 -2
  182. package/dist/contracts/superadmin-tenants.d.ts +627 -12
  183. package/dist/contracts/superadmin-tenants.js +32 -13
  184. package/dist/contracts/superadmin.d.ts +232 -37
  185. package/dist/contracts/superadmin.js +106 -22
  186. package/dist/contracts/tags.d.ts +85 -58
  187. package/dist/contracts/tags.js +47 -37
  188. package/dist/contracts/teams.d.ts +86 -26
  189. package/dist/contracts/teams.js +90 -31
  190. package/dist/contracts/tenants.d.ts +135 -0
  191. package/dist/contracts/tenants.js +120 -0
  192. package/dist/contracts/test-triggers.d.ts +70 -0
  193. package/dist/contracts/test-triggers.js +116 -0
  194. package/dist/contracts/theme-json.js +3 -8
  195. package/dist/contracts/transfers.d.ts +1 -0
  196. package/dist/contracts/transfers.js +3 -2
  197. package/dist/contracts/usage.d.ts +209 -0
  198. package/dist/contracts/usage.js +124 -12
  199. package/dist/contracts/variables.d.ts +2 -2
  200. package/dist/contracts/variables.js +9 -13
  201. package/dist/contracts/webhooks.d.ts +13 -5
  202. package/dist/contracts/webhooks.js +3 -1
  203. package/dist/contracts/zero.d.ts +72 -81
  204. package/dist/contracts/zero.js +80 -78
  205. package/dist/dashboard-paths/index.d.ts +24 -21
  206. package/dist/dashboard-paths/index.js +35 -47
  207. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
  208. package/dist/dashboard-paths/route-reservations.generated.js +36 -0
  209. package/dist/docs/agent-arrival.d.ts +92 -0
  210. package/dist/docs/agent-arrival.js +227 -0
  211. package/dist/docs/agent-output-policy.d.ts +6 -0
  212. package/dist/docs/agent-output-policy.js +22 -0
  213. package/dist/docs/agent-prose.d.ts +12 -18
  214. package/dist/docs/agent-prose.js +73 -51
  215. package/dist/docs/agent-setup.d.ts +72 -41
  216. package/dist/docs/agent-setup.js +339 -436
  217. package/dist/docs/catalog.d.ts +153 -95
  218. package/dist/docs/catalog.js +163 -48
  219. package/dist/docs/error-docs.d.ts +2098 -1
  220. package/dist/docs/error-docs.js +421 -144
  221. package/dist/docs/index-build.d.ts +0 -1
  222. package/dist/docs/index-build.js +2 -3
  223. package/dist/docs/page-context-prompt.d.ts +77 -0
  224. package/dist/docs/page-context-prompt.js +131 -0
  225. package/dist/docs/reference-urls.d.ts +46 -0
  226. package/dist/docs/reference-urls.js +79 -0
  227. package/dist/docs/search.d.ts +6 -8
  228. package/dist/docs/search.js +10 -13
  229. package/dist/docs/skill-distribution.d.ts +4 -1
  230. package/dist/docs/skill-distribution.js +10 -5
  231. package/dist/docs/skills.d.ts +120 -120
  232. package/dist/docs/skills.js +121 -73
  233. package/dist/docs/start-prompts.d.ts +45 -0
  234. package/dist/docs/start-prompts.js +62 -0
  235. package/dist/domain-exploration.js +9 -13
  236. package/dist/og-template.d.ts +56 -0
  237. package/dist/og-template.js +56 -0
  238. package/dist/slug-policy/index.js +5 -11
  239. package/dist/test-helpers/fetch-stub.js +1 -0
  240. package/dist/utils/browser-credential.d.ts +13 -6
  241. package/dist/utils/browser-credential.js +14 -7
  242. package/dist/utils/build-settings.d.ts +21 -1
  243. package/dist/utils/build-settings.js +133 -15
  244. package/dist/utils/canonical-json.d.ts +3 -5
  245. package/dist/utils/canonical-json.js +4 -5
  246. package/dist/utils/cast-transport.d.ts +5 -17
  247. package/dist/utils/cast-transport.js +6 -19
  248. package/dist/utils/claim-token.js +1 -2
  249. package/dist/utils/comment-avatars.js +4 -6
  250. package/dist/utils/concurrency.js +3 -3
  251. package/dist/utils/content-type.d.ts +7 -6
  252. package/dist/utils/content-type.js +73 -51
  253. package/dist/utils/credential-policy.d.ts +50 -6
  254. package/dist/utils/credential-policy.js +126 -59
  255. package/dist/utils/dns-instructions.js +9 -9
  256. package/dist/utils/egress-policy.fixtures.json +1 -1
  257. package/dist/utils/error-code.js +1 -0
  258. package/dist/utils/generate-space-name.d.ts +3 -8
  259. package/dist/utils/generate-space-name.js +3 -8
  260. package/dist/utils/grant-decision.fixtures.json +2 -2
  261. package/dist/utils/grants.d.ts +0 -1
  262. package/dist/utils/grants.js +7 -8
  263. package/dist/utils/gravatar.d.ts +2 -4
  264. package/dist/utils/gravatar.js +6 -12
  265. package/dist/utils/id-hints.js +14 -23
  266. package/dist/utils/idempotency.d.ts +3 -3
  267. package/dist/utils/idempotency.js +6 -7
  268. package/dist/utils/local-space-state.d.ts +23 -26
  269. package/dist/utils/local-space-state.js +46 -46
  270. package/dist/utils/oauth-signed-query.d.ts +3 -5
  271. package/dist/utils/oauth-signed-query.js +6 -9
  272. package/dist/utils/one-shot-replay.d.ts +7 -6
  273. package/dist/utils/one-shot-replay.js +3 -5
  274. package/dist/utils/page-colors.d.ts +15 -19
  275. package/dist/utils/page-colors.js +23 -45
  276. package/dist/utils/page-fonts.d.ts +1 -1
  277. package/dist/utils/page-fonts.js +8 -20
  278. package/dist/utils/page-preview.d.ts +0 -1
  279. package/dist/utils/page-preview.js +0 -1
  280. package/dist/utils/pages.d.ts +33 -3
  281. package/dist/utils/pages.js +83 -30
  282. package/dist/utils/privacy-regions.d.ts +19 -0
  283. package/dist/utils/privacy-regions.js +91 -0
  284. package/dist/utils/publish-form-data.d.ts +4 -5
  285. package/dist/utils/publish-form-data.js +2 -3
  286. package/dist/utils/publish-policy.d.ts +11 -17
  287. package/dist/utils/publish-policy.fixtures.json +11 -5
  288. package/dist/utils/publish-policy.js +65 -110
  289. package/dist/utils/query-client.d.ts +2 -0
  290. package/dist/utils/query-client.js +10 -1
  291. package/dist/utils/runtime-paths.d.ts +4 -4
  292. package/dist/utils/runtime-paths.js +6 -8
  293. package/dist/utils/runtime-upload.d.ts +9 -0
  294. package/dist/utils/runtime-upload.js +12 -0
  295. package/dist/utils/secure-local-file.d.ts +3 -3
  296. package/dist/utils/secure-local-file.js +23 -40
  297. package/dist/utils/space-config.d.ts +12 -15
  298. package/dist/utils/space-config.js +13 -16
  299. package/dist/utils/static-runtime-policy.d.ts +10 -7
  300. package/dist/utils/static-runtime-policy.fixtures.json +38 -41
  301. package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
  302. package/dist/utils/static-runtime-policy.generated.js +70 -0
  303. package/dist/utils/static-runtime-policy.js +31 -132
  304. package/dist/utils/storage-policy.js +1 -2
  305. package/dist/utils/upload-session.d.ts +4 -1
  306. package/dist/utils/upload-session.js +41 -19
  307. package/dist/utils/version-path.js +11 -19
  308. package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
  309. package/dist/utils/wpcom-auth-redirects.js +1 -4
  310. package/dist/vocabulary.d.ts +13 -13
  311. package/dist/vocabulary.js +52 -27
  312. package/package.json +15 -18
  313. package/dist/agents/client-registry.d.ts +0 -22
  314. package/dist/agents/client-registry.js +0 -41
  315. package/dist/docs/agent-handoff-document.d.ts +0 -15
  316. package/dist/docs/agent-handoff-document.js +0 -180
  317. package/dist/docs/agent-solutions.d.ts +0 -110
  318. package/dist/docs/agent-solutions.js +0 -183
  319. package/dist/utils/query-keys.d.ts +0 -84
  320. package/dist/utils/query-keys.js +0 -108
@@ -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
  }
@@ -273,7 +272,6 @@ function lockName(leaf) {
273
272
  const digest = createHash("sha256").update(identity).digest("hex").slice(0, 24);
274
273
  return `.spacefast-${digest}.lock`;
275
274
  }
276
- const LEGACY_LEAF_LOCK_STALE_MS = 2 * 60_000;
277
275
  function serializeLeafLockOwner(owner) {
278
276
  const { phase, ...identity } = owner;
279
277
  return `${phase === "active" ? "A" : "R"}${JSON.stringify(identity)}\n`;
@@ -293,6 +291,7 @@ function parseLeafLockOwner(raw) {
293
291
  (value.temporaryName === undefined ||
294
292
  (typeof value.temporaryName === "string" &&
295
293
  /^\.spacefast-[0-9a-f-]{36}\.tmp$/.test(value.temporaryName)))) {
294
+ // SAFETY: The preceding checks establish every required LeafLockOwner field and the optional temporaryName field.
296
295
  return { ...value, phase };
297
296
  }
298
297
  }
@@ -360,14 +359,8 @@ function processIsAlive(pid) {
360
359
  }
361
360
  }
362
361
  async function leafLockRecordIsStale(record) {
363
- // The original lock format was an empty exclusive file. It carries no owner
364
- // identity, so a short age fence is the only safe compatibility signal: a
365
- // fresh legacy writer stays exclusive, while crash wreckage can recover.
366
- if (record?.kind === "legacy") {
367
- return Date.now() - record.modifiedAtMs > LEGACY_LEAF_LOCK_STALE_MS;
368
- }
369
- // A malformed non-empty record is not proof that no writer owns it. New lock
370
- // records are atomically published, so corruption still needs explicit recovery.
362
+ // A malformed record is not proof that no writer owns it. New lock records are
363
+ // atomically published, so corruption still needs explicit recovery.
371
364
  if (record?.kind !== "owned")
372
365
  return false;
373
366
  const { owner } = record;
@@ -399,13 +392,8 @@ async function readLeafLockRecord(filePath) {
399
392
  visibleMetadata.ino !== metadata.ino) {
400
393
  return { kind: "invalid" };
401
394
  }
402
- const raw = await file.readFile("utf8");
403
- const owner = parseLeafLockOwner(raw);
404
- if (owner)
405
- return { kind: "owned", owner };
406
- return raw.length === 0
407
- ? { kind: "legacy", modifiedAtMs: metadata.mtimeMs }
408
- : { kind: "invalid" };
395
+ const owner = parseLeafLockOwner(await file.readFile("utf8"));
396
+ return owner ? { kind: "owned", owner } : { kind: "invalid" };
409
397
  }
410
398
  finally {
411
399
  await file.close();
@@ -476,8 +464,7 @@ async function unlinkOwnedLeafLockArtifact(lockPath, artifactPath, nonce) {
476
464
  return;
477
465
  }
478
466
  // The pathname changed after its owner was inspected. Restore the exact inode
479
- // when the name is still free; if another lock is already visible, keep this
480
- // 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.
481
468
  let restored = false;
482
469
  try {
483
470
  await link(retiredPath, artifactPath);
@@ -555,14 +542,12 @@ async function acquireLeafLock(lockPath, temporaryPath) {
555
542
  if (released)
556
543
  return;
557
544
  released = true;
558
- // Move the canonical name into unique cleanup custody while its inode
559
- // is still active. Once that atomic rename happens a new owner may
560
- // 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.
561
547
  await unlinkOwnedLeafLockArtifact(lockPath, lockPath, owner.nonce).catch(() => undefined);
562
548
  try {
563
- // A delayed stale reclaimer may already hold this inode at a displaced
564
- // name. Mark it complete through the handle so that artifact can be
565
- // 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.
566
551
  const releasedWrite = await acquiredHandle.write(Buffer.from("R"), 0, 1, 0);
567
552
  if (releasedWrite.bytesWritten === 1)
568
553
  await acquiredHandle.sync();
@@ -609,16 +594,15 @@ async function acquireLeafLock(lockPath, temporaryPath) {
609
594
  }
610
595
  const displaced = await readLeafLockRecord(displacedPath);
611
596
  if (!(await leafLockRecordIsStale(displaced))) {
612
- // A fresh owner won between our stale read and rename. Put its exact inode
613
- // back when possible; every contender also scans displaced names before
614
- // 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.
615
600
  try {
616
601
  await link(displacedPath, lockPath);
617
602
  }
618
603
  catch (error) {
619
604
  if (isMissing(error)) {
620
- // Its owner completed cleanup while we inspected the displaced inode.
621
- // Nothing remains to restore; compete again for the now-current name.
605
+ // Its owner finished cleanup; compete again for the current name.
622
606
  await delay(LEAF_LOCK_RETRY_DELAY_MS);
623
607
  continue;
624
608
  }
@@ -868,10 +852,9 @@ async function commitPortableStagedFile(input) {
868
852
  }
869
853
  }
870
854
  async function secureWriteFilePortable(options) {
871
- // The caller explicitly trusts the root. Canonicalize that boundary once so
872
- // supported system aliases such as macOS `/var -> /private/var` do not look
873
- // like attacker-controlled children; every path below it is still walked
874
- // 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.
875
858
  const root = await canonicalPortableWriteRoot(options.root);
876
859
  const relativePath = validateRelativePath(options.relativePath, "write");
877
860
  const components = relativePath.split(path.sep);
@@ -934,9 +917,9 @@ async function secureWriteFilePortable(options) {
934
917
  }
935
918
  /**
936
919
  * Writes beneath an explicitly trusted root without following output-tree
937
- * symlinks. Linux uses descriptor-relative operations; other supported
938
- * platforms use exclusive staging plus before-and-after ancestor identity
939
- * 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.
940
923
  */
941
924
  export async function secureWriteFile(options) {
942
925
  if ((options.data === undefined) === (options.write === undefined)) {
@@ -3,7 +3,7 @@ import { type SpaceConfig, type SpaceConfigFile } from "../contracts/space-confi
3
3
  export type SpaceConfigBlockingIssueCode = "config_file_too_large" | "config_templates_over_limit" | "config_meta_too_long" | "config_name_too_long" | "config_functions_key_removed" | "config_invalid" | "config_runtime_invalid_kind" | "config_runtime_entry_missing" | "inject_snippet_too_large" | "inject_invalid";
4
4
  export type SpaceConfigIssue = {
5
5
  severity: "info" | "warning" | "error";
6
- code: SpaceConfigBlockingIssueCode | "single_html_index" | "files_mode_inferred";
6
+ code: SpaceConfigBlockingIssueCode | "single_html_index" | "files_mode_inferred" | "listing_shadows_fallback";
7
7
  message: string;
8
8
  /** JSON-path-lite pointer into the config shape (e.g. `meta.title`). */
9
9
  path?: string;
@@ -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)) {
@@ -57,6 +55,7 @@ function capIssues(candidate) {
57
55
  }
58
56
  else {
59
57
  for (const key of ["head", "bodyStart", "bodyEnd", "noscript"]) {
58
+ // SAFETY: The enclosing branch proves inject is a non-null, non-array object.
60
59
  const snippets = inject[key];
61
60
  if (snippets === undefined) {
62
61
  continue;
@@ -125,18 +124,15 @@ function capIssues(candidate) {
125
124
  return issues;
126
125
  }
127
126
  /**
128
- * Keys a committed `sf.jsonc` owns outright: they are facts about the content
129
- * of a version (who may read it, what it is called, what runs it), so they roll
130
- * forward and back with the version instead of living in the space's mutable
131
- * 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.
132
130
  */
133
- export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime"];
131
+ export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime", "system"];
134
132
  /**
135
- * Validates a space `config` overlay at API intake (PATCH /spaces, publish
136
- * `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
137
- * shape caps. Returns the first blocking issue, or null when the overlay is
138
- * acceptable. Takes the raw request value, because the overlay schema strips
139
- * 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.
140
136
  */
141
137
  export function spaceConfigOverlayIssue(config) {
142
138
  if (config === null || config === undefined || typeof config !== "object") {
@@ -165,15 +161,15 @@ export function spaceConfigOverlayIssue(config) {
165
161
  }
166
162
  return capIssues(config)[0] ?? null;
167
163
  }
168
- // Strips JSONC comments and trailing commas (string-aware single pass). The
169
- // config file itself is parsed by the crate; this stays for the OTHER JSONC
170
- // 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).
171
166
  export function stripJsonComments(raw) {
172
167
  let output = "";
173
168
  let inString = false;
174
169
  let inLineComment = false;
175
170
  let inBlockComment = false;
176
171
  for (let i = 0; i < raw.length; i += 1) {
172
+ // SAFETY: The loop condition proves i addresses a character in raw.
177
173
  const char = raw[i];
178
174
  const next = raw[i + 1];
179
175
  if (inLineComment) {
@@ -220,6 +216,7 @@ export function stripJsonComments(raw) {
220
216
  // Trailing comma: skip when the next non-whitespace/non-comment char closes.
221
217
  let j = i + 1;
222
218
  while (j < raw.length) {
219
+ // SAFETY: The loop condition proves j addresses a character in raw.
223
220
  const ahead = raw[j];
224
221
  if (/\s/.test(ahead)) {
225
222
  j += 1;
@@ -1,18 +1,21 @@
1
+ import { INTERNAL_REDIRECT_RESPONSE_HEADERS, PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES, PLATFORM_MANAGED_RESPONSE_HEADERS } from "./static-runtime-policy.generated.js";
2
+ export { INTERNAL_REDIRECT_RESPONSE_HEADERS, PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES, PLATFORM_MANAGED_RESPONSE_HEADERS, };
1
3
  export declare const PHP_LIKE_EXTENSIONS: string[];
2
- export declare const INTERNAL_REDIRECT_RESPONSE_HEADERS: string[];
3
- export declare const PLATFORM_MANAGED_RESPONSE_HEADERS: string[];
4
- /** Reserved response-header namespaces owned by the platform's own serving signature. */
5
- export declare const PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES: string[];
6
4
  /** True when user rules may neither set nor remove this response header. */
7
5
  export declare function isPlatformManagedResponseHeader(name: string): boolean;
8
- export declare const NON_IMMUTABLE_EXTENSIONS: string[];
9
- export declare const GUARANTEED_IMMUTABLE_PREFIXES: string[];
6
+ /**
7
+ * The token grammar a `_headers` rule accepts for a header NAME. Anything that
8
+ * GENERATES `_headers` rules must gate on it too, so a generated rule can
9
+ * never fail the author's publish.
10
+ */
11
+ export declare const HEADER_RULE_NAME_PATTERN: RegExp;
12
+ /** `content-type` → `Content-Type`: the casing compiled rules and generated `_headers` carry. */
13
+ export declare function canonicalHeaderName(input: string): string;
10
14
  /** Lowercased extension without the leading dot, posix semantics. Dotfiles have no extension. */
11
15
  export declare function pathExtension(filePath: string): string;
12
16
  /** Basename without its extension (the "stem"), posix semantics. */
13
17
  export declare function pathStem(filePath: string): string;
14
18
  export declare function isPhpLikeStaticPath(filePath: string): boolean;
15
- export declare function isImmutableAssetPath(filePath: string): boolean;
16
19
  /** Forced inert serving policy for a committed static file. PHP-like files are never executable. */
17
20
  export declare function runtimeFileSafety(filePath: string): {
18
21
  executable: false;
@@ -1,44 +1,33 @@
1
1
  {
2
2
  "phpLike": [
3
- { "path": "index.php", "expected": true },
4
- { "path": "nested/app.phtml", "expected": true },
5
- { "path": "lib.phar", "expected": true },
6
- { "path": "legacy.php5", "expected": true },
7
- { "path": "index.html", "expected": false },
8
- { "path": "assets/main.js", "expected": false },
9
- { "path": ".env", "expected": false }
10
- ],
11
- "immutable": [
12
- { "path": "_next/static/chunks/main-app-4A_iFExJ.js", "expected": true },
13
- { "path": "_next/static/chunks/framework-2c1d4f8a.js", "expected": true },
14
- { "path": "_next/static/chunks/pages/index-a1b2c3d4e5f6.js", "expected": true },
15
- { "path": "_next/static/chunks/oton0Guf.js", "expected": true },
16
- { "path": "_next/static/css/app.plainname.css", "expected": true },
17
- { "path": "/_next/static/chunks/oton0Guf.js", "expected": true },
18
- { "path": "_app/immutable/entry/start.DYyuCeEr.js", "expected": true },
19
- { "path": "_nuxt/index.oton0Guf.js", "expected": true },
20
- { "path": "_astro/about.DYyuCeEr.css", "expected": true },
21
- { "path": "_next/static/index.html", "expected": false },
22
- { "path": "_next/static/sitemap.xml", "expected": false },
23
- { "path": "_app/immutable/manifest.txt", "expected": false },
24
- { "path": "_nextish/chunks/oton0Guf.js", "expected": false },
25
- { "path": "nested/_next/static/chunks/oton0Guf.js", "expected": false },
26
- { "path": "assets/index-BzeDGMM3.js", "expected": false },
27
- { "path": "assets/auth-client-P1Tx-Cyu.js", "expected": false },
28
- { "path": "assets/index-DYyuCeEr.js", "expected": false },
29
- { "path": "assets/index-9f8e7d6c.js", "expected": false },
30
- { "path": "assets/D3GeoMap.js", "expected": false },
31
- { "path": "assets/index.js", "expected": false },
32
- { "path": "assets/client-widget.js", "expected": false },
33
- { "path": "app.1a2b3c4d.css", "expected": false },
34
- { "path": "vendor.3f9a2b7e.js", "expected": false },
35
- { "path": "static/js/main.d41d8cd98f00b204e9800998.js", "expected": false },
36
- { "path": "index.html", "expected": false },
37
- { "path": "robots.txt", "expected": false },
38
- { "path": "config.xml", "expected": false },
39
- { "path": "page.php", "expected": false },
40
- { "path": "MyComponent.js", "expected": false },
41
- { "path": "image01.png", "expected": false }
3
+ {
4
+ "path": "index.php",
5
+ "expected": true
6
+ },
7
+ {
8
+ "path": "nested/app.phtml",
9
+ "expected": true
10
+ },
11
+ {
12
+ "path": "lib.phar",
13
+ "expected": true
14
+ },
15
+ {
16
+ "path": "legacy.php5",
17
+ "expected": true
18
+ },
19
+ {
20
+ "path": "index.html",
21
+ "expected": false
22
+ },
23
+ {
24
+ "path": "assets/main.js",
25
+ "expected": false
26
+ },
27
+ {
28
+ "path": ".env",
29
+ "expected": false
30
+ }
42
31
  ],
43
32
  "safeContentType": [
44
33
  {
@@ -46,12 +35,20 @@
46
35
  "mime": "application/x-httpd-php",
47
36
  "expected": "text/plain; charset=utf-8"
48
37
  },
49
- { "path": "lib.phar", "mime": "application/zip", "expected": "application/octet-stream" },
38
+ {
39
+ "path": "lib.phar",
40
+ "mime": "application/zip",
41
+ "expected": "application/octet-stream"
42
+ },
50
43
  {
51
44
  "path": "assets/main.js",
52
45
  "mime": "text/javascript; charset=utf-8",
53
46
  "expected": "text/javascript; charset=utf-8"
54
47
  },
55
- { "path": "logo.svg", "mime": "image/svg+xml", "expected": "image/svg+xml" }
48
+ {
49
+ "path": "logo.svg",
50
+ "mime": "image/svg+xml",
51
+ "expected": "image/svg+xml"
52
+ }
56
53
  ]
57
54
  }
@@ -0,0 +1,16 @@
1
+ /** Reserved response-header namespaces carrying the platform's own serving signature. */
2
+ export declare const PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES: readonly string[];
3
+ /**
4
+ * The internal-redirect/sendfile family, every vendor spelling: headers that make the web
5
+ * server produce a different response instead of describing this one. Factored out of the
6
+ * managed set below so the proxy relay can drop exactly these from an untrusted upstream
7
+ * while still relaying ordinary response headers the managed set also lists.
8
+ */
9
+ export declare const INTERNAL_REDIRECT_RESPONSE_HEADERS: readonly string[];
10
+ /**
11
+ * Rejected/platform-managed response headers: user `_headers` rules can never set or remove
12
+ * these. The internal-redirect family above is a strict subset — anything the proxy lane
13
+ * refuses to relay is also refused from `_headers`. Ask isPlatformManagedResponseHeader(),
14
+ * not this list alone: the reserved prefixes are the other half of the policy.
15
+ */
16
+ export declare const PLATFORM_MANAGED_RESPONSE_HEADERS: readonly string[];
@@ -0,0 +1,70 @@
1
+ // @generated by `cargo run -p stattic-runtime-compiler --bin protocol-codegen -- --policy-ts`.
2
+ // `crates/stattic-runtime-policy/src/lib.rs` is the only editable authority.
3
+ /** Reserved response-header namespaces carrying the platform's own serving signature. */
4
+ export const PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES = [
5
+ "x-spacefast-",
6
+ "x-stattic-",
7
+ ];
8
+ /**
9
+ * The internal-redirect/sendfile family, every vendor spelling: headers that make the web
10
+ * server produce a different response instead of describing this one. Factored out of the
11
+ * managed set below so the proxy relay can drop exactly these from an untrusted upstream
12
+ * while still relaying ordinary response headers the managed set also lists.
13
+ */
14
+ export const INTERNAL_REDIRECT_RESPONSE_HEADERS = [
15
+ "x-accel-buffering",
16
+ "x-accel-charset",
17
+ "x-accel-expires",
18
+ "x-accel-limit-rate",
19
+ "x-accel-redirect",
20
+ "x-lighttpd-send-file",
21
+ "x-lighttpd-sendfile",
22
+ "x-lighttpd-sendfile2",
23
+ "x-reproxy-url",
24
+ "x-sendfile",
25
+ ];
26
+ /**
27
+ * Rejected/platform-managed response headers: user `_headers` rules can never set or remove
28
+ * these. The internal-redirect family above is a strict subset — anything the proxy lane
29
+ * refuses to relay is also refused from `_headers`. Ask isPlatformManagedResponseHeader(),
30
+ * not this list alone: the reserved prefixes are the other half of the policy.
31
+ */
32
+ export const PLATFORM_MANAGED_RESPONSE_HEADERS = [
33
+ "accept-ranges",
34
+ "age",
35
+ "allow",
36
+ "alt-svc",
37
+ "cdn-cache-control",
38
+ "cloudflare-cdn-cache-control",
39
+ "connection",
40
+ "content-encoding",
41
+ "content-length",
42
+ "content-range",
43
+ "cookie",
44
+ "date",
45
+ "host",
46
+ "keep-alive",
47
+ "location",
48
+ "netlify-cdn-cache-control",
49
+ "proxy-authenticate",
50
+ "proxy-authorization",
51
+ "server",
52
+ "set-cookie",
53
+ "strict-transport-security",
54
+ "surrogate-control",
55
+ "te",
56
+ "trailer",
57
+ "transfer-encoding",
58
+ "upgrade",
59
+ "vary",
60
+ "x-accel-buffering",
61
+ "x-accel-charset",
62
+ "x-accel-expires",
63
+ "x-accel-limit-rate",
64
+ "x-accel-redirect",
65
+ "x-lighttpd-send-file",
66
+ "x-lighttpd-sendfile",
67
+ "x-lighttpd-sendfile2",
68
+ "x-reproxy-url",
69
+ "x-sendfile",
70
+ ];