@spacefast/common 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/config/domains.d.ts +1 -1
  2. package/dist/config/domains.js +1 -0
  3. package/dist/config/index.d.ts +0 -1
  4. package/dist/config/index.js +0 -1
  5. package/dist/contracts/abuse.d.ts +2 -0
  6. package/dist/contracts/abuse.js +12 -0
  7. package/dist/contracts/access.d.ts +121 -9
  8. package/dist/contracts/access.js +73 -9
  9. package/dist/contracts/activity.d.ts +1 -1
  10. package/dist/contracts/activity.js +0 -9
  11. package/dist/contracts/api-keys.d.ts +21 -21
  12. package/dist/contracts/beta.js +1 -1
  13. package/dist/contracts/billing.d.ts +1 -34
  14. package/dist/contracts/billing.js +4 -53
  15. package/dist/contracts/builds.d.ts +8 -8
  16. package/dist/contracts/builds.js +10 -3
  17. package/dist/contracts/cli.d.ts +10 -0
  18. package/dist/contracts/cli.js +10 -0
  19. package/dist/contracts/collab-public.d.ts +63 -0
  20. package/dist/contracts/collab-public.js +46 -0
  21. package/dist/contracts/comments.d.ts +283 -54
  22. package/dist/contracts/comments.js +173 -52
  23. package/dist/contracts/common.d.ts +3 -3
  24. package/dist/contracts/device-auth.d.ts +6 -6
  25. package/dist/contracts/device-auth.js +1 -1
  26. package/dist/contracts/docs.d.ts +5 -5
  27. package/dist/contracts/domains.d.ts +36 -36
  28. package/dist/contracts/enums.d.ts +33 -30
  29. package/dist/contracts/enums.js +52 -38
  30. package/dist/contracts/error-code-meta.d.ts +37 -126
  31. package/dist/contracts/error-code-meta.js +12 -41
  32. package/dist/contracts/error-codes.d.ts +1 -1
  33. package/dist/contracts/error-codes.js +10 -32
  34. package/dist/contracts/execution.d.ts +35 -57
  35. package/dist/contracts/execution.js +41 -40
  36. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  37. package/dist/contracts/feature-lifecycle-core.js +2 -31
  38. package/dist/contracts/feature-lifecycle.d.ts +8 -38
  39. package/dist/contracts/feature-lifecycle.js +16 -48
  40. package/dist/contracts/features.d.ts +15 -13
  41. package/dist/contracts/features.js +39 -38
  42. package/dist/contracts/functions.d.ts +97 -70
  43. package/dist/contracts/functions.js +101 -34
  44. package/dist/contracts/git.d.ts +1 -1
  45. package/dist/contracts/grants.d.ts +7 -3
  46. package/dist/contracts/grants.js +18 -9
  47. package/dist/contracts/internal.d.ts +14 -1
  48. package/dist/contracts/internal.js +17 -7
  49. package/dist/contracts/oauth-resources.d.ts +4 -3
  50. package/dist/contracts/oauth-resources.js +12 -3
  51. package/dist/contracts/operations.d.ts +90 -5
  52. package/dist/contracts/operations.js +75 -1
  53. package/dist/contracts/pages.d.ts +6 -1
  54. package/dist/contracts/pages.js +24 -1
  55. package/dist/contracts/plan-policy.d.ts +1 -3
  56. package/dist/contracts/plan-policy.js +1 -2
  57. package/dist/contracts/platform.d.ts +61 -148
  58. package/dist/contracts/platform.js +70 -126
  59. package/dist/contracts/publish-archive.d.ts +498 -0
  60. package/dist/contracts/publish-archive.js +25 -0
  61. package/dist/contracts/resources.d.ts +12 -24
  62. package/dist/contracts/resources.js +32 -12
  63. package/dist/contracts/runtime-api.d.ts +369 -175
  64. package/dist/contracts/runtime-api.js +372 -348
  65. package/dist/contracts/runtime-app.d.ts +127 -120
  66. package/dist/contracts/runtime-app.js +19 -10
  67. package/dist/contracts/runtime-db.d.ts +1 -1
  68. package/dist/contracts/runtime-services.d.ts +219 -0
  69. package/dist/contracts/runtime-services.js +437 -0
  70. package/dist/contracts/runtime-storage.d.ts +45 -32
  71. package/dist/contracts/runtime-storage.js +30 -8
  72. package/dist/contracts/space-config.d.ts +10 -16
  73. package/dist/contracts/space-config.js +14 -10
  74. package/dist/contracts/spaces.d.ts +241 -436
  75. package/dist/contracts/spaces.js +128 -145
  76. package/dist/contracts/superadmin-activity.d.ts +57 -4
  77. package/dist/contracts/superadmin-activity.js +48 -2
  78. package/dist/contracts/superadmin-search.d.ts +126 -0
  79. package/dist/contracts/superadmin-search.js +61 -0
  80. package/dist/contracts/superadmin-spaces.d.ts +358 -89
  81. package/dist/contracts/superadmin-spaces.js +97 -24
  82. package/dist/contracts/superadmin-teams.d.ts +28 -10
  83. package/dist/contracts/superadmin-teams.js +5 -8
  84. package/dist/contracts/superadmin.d.ts +0 -32
  85. package/dist/contracts/superadmin.js +3 -11
  86. package/dist/contracts/tags.d.ts +9 -9
  87. package/dist/contracts/teams.d.ts +38 -0
  88. package/dist/contracts/teams.js +34 -16
  89. package/dist/contracts/transfers.d.ts +0 -49
  90. package/dist/contracts/transfers.js +2 -6
  91. package/dist/contracts/usage.d.ts +61 -0
  92. package/dist/contracts/usage.js +64 -0
  93. package/dist/contracts/variables.d.ts +2 -7
  94. package/dist/contracts/variables.js +6 -5
  95. package/dist/contracts/webhooks.d.ts +2 -2
  96. package/dist/contracts/zero.d.ts +87 -56
  97. package/dist/contracts/zero.js +82 -20
  98. package/dist/dashboard-paths/index.d.ts +1 -1
  99. package/dist/dashboard-paths/index.js +1 -0
  100. package/dist/docs/agent-prose.d.ts +1 -1
  101. package/dist/docs/agent-prose.js +57 -61
  102. package/dist/docs/agent-setup.js +40 -13
  103. package/dist/docs/catalog.d.ts +19 -19
  104. package/dist/docs/catalog.js +30 -19
  105. package/dist/docs/error-docs.js +63 -151
  106. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  107. package/dist/test-helpers/fetch-stub.js +24 -0
  108. package/dist/utils/build-settings.js +9 -3
  109. package/dist/utils/canonical-json.d.ts +10 -0
  110. package/dist/utils/canonical-json.js +21 -0
  111. package/dist/utils/cast-transport.d.ts +28 -0
  112. package/dist/utils/cast-transport.js +52 -0
  113. package/dist/utils/claim-token.d.ts +0 -8
  114. package/dist/utils/claim-token.js +3 -15
  115. package/dist/utils/credential-policy.d.ts +1 -1
  116. package/dist/utils/env-file.d.ts +17 -0
  117. package/dist/utils/env-file.js +96 -0
  118. package/dist/utils/error-code.d.ts +2 -0
  119. package/dist/utils/error-code.js +7 -0
  120. package/dist/utils/generate-space-name.d.ts +8 -10
  121. package/dist/utils/generate-space-name.js +206 -21
  122. package/dist/utils/grant-decision.fixtures.json +2938 -0
  123. package/dist/utils/grants.d.ts +2 -0
  124. package/dist/utils/grants.js +25 -2
  125. package/dist/utils/gravatar.d.ts +9 -0
  126. package/dist/utils/gravatar.js +31 -0
  127. package/dist/utils/idempotency.d.ts +6 -0
  128. package/dist/utils/idempotency.js +28 -0
  129. package/dist/utils/local-space-state.d.ts +1 -5
  130. package/dist/utils/local-space-state.js +7 -26
  131. package/dist/utils/page-fonts.d.ts +2 -2
  132. package/dist/utils/page-fonts.js +22 -4
  133. package/dist/utils/pages.d.ts +1 -2
  134. package/dist/utils/pages.js +21 -5
  135. package/dist/utils/publish-form-data.d.ts +12 -0
  136. package/dist/utils/publish-form-data.js +10 -0
  137. package/dist/utils/publish-policy.d.ts +20 -0
  138. package/dist/utils/publish-policy.fixtures.json +397 -0
  139. package/dist/utils/publish-policy.js +36 -19
  140. package/dist/utils/push-new.d.ts +7 -0
  141. package/dist/utils/push-new.js +7 -0
  142. package/dist/utils/query-keys.d.ts +0 -1
  143. package/dist/utils/query-keys.js +0 -1
  144. package/dist/utils/runtime-paths.d.ts +5 -0
  145. package/dist/utils/runtime-paths.js +5 -0
  146. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  147. package/dist/utils/runtime-upload-batch.js +1 -27
  148. package/dist/utils/runtime-upload.d.ts +3 -13
  149. package/dist/utils/runtime-upload.js +4 -14
  150. package/dist/utils/space-config.d.ts +0 -33
  151. package/dist/utils/space-config.js +5 -326
  152. package/dist/utils/storage-policy.d.ts +1 -1
  153. package/dist/utils/storage-policy.js +3 -1
  154. package/dist/utils/structured-error.d.ts +14 -0
  155. package/dist/utils/structured-error.js +22 -0
  156. package/dist/utils/version-path.js +12 -3
  157. package/dist/vocabulary.d.ts +5 -3
  158. package/dist/vocabulary.js +13 -10
  159. package/package.json +10 -5
  160. package/dist/config/postgres-budget.d.ts +0 -129
  161. package/dist/config/postgres-budget.js +0 -140
  162. package/dist/config/postgres-budget.json +0 -51
  163. package/dist/contracts/archives.d.ts +0 -331
  164. package/dist/contracts/archives.js +0 -120
  165. package/dist/contracts/deployments.d.ts +0 -574
  166. package/dist/contracts/deployments.js +0 -97
  167. package/dist/contracts/sites.d.ts +0 -416
  168. package/dist/contracts/sites.js +0 -168
  169. package/dist/contracts/superadmin-queues.d.ts +0 -414
  170. package/dist/contracts/superadmin-queues.js +0 -202
  171. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  172. package/dist/contracts/superadmin-runtime.js +0 -38
  173. package/dist/index.d.ts +0 -1
  174. package/dist/index.js +0 -1
  175. package/dist/utils/api.d.ts +0 -4
  176. package/dist/utils/api.js +0 -13
@@ -1,5 +1,5 @@
1
1
  import { CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE, grantSchema, } from "../contracts/grants.js";
2
- import { SPACE_CONFIG_CAPS, SPACE_CONFIG_CANONICAL_FILE, SPACE_CONFIG_PRIVATE_FILES, spaceConfigFileSchema, spaceConfigSchema, } from "../contracts/space-config.js";
2
+ import { SPACE_CONFIG_CAPS, SPACE_CONFIG_CANONICAL_FILE, } from "../contracts/space-config.js";
3
3
  const TEXT_BYTES = new TextEncoder();
4
4
  export const CONFIG_PUBLIC_GRANT_COMPILED_ID = "config:access.public";
5
5
  function byteLength(value) {
@@ -107,6 +107,7 @@ function capIssues(candidate) {
107
107
  ["title", SPACE_CONFIG_CAPS.metaTitleChars],
108
108
  ["description", SPACE_CONFIG_CAPS.metaDescriptionChars],
109
109
  ["image", SPACE_CONFIG_CAPS.metaImageChars],
110
+ ["favicon", SPACE_CONFIG_CAPS.metaImageChars],
110
111
  ];
111
112
  for (const [key, limit] of caps) {
112
113
  const value = meta[key];
@@ -123,75 +124,6 @@ function capIssues(candidate) {
123
124
  }
124
125
  return issues;
125
126
  }
126
- // File-only keys (`name`, `runtime`) never reach the API overlay, so their
127
- // checks live beside the shared caps rather than inside them. Both run BEFORE
128
- // zod so a bad declaration reports its stable code instead of a shape error.
129
- const RUNTIME_KINDS = new Set(["zero", "functions"]);
130
- function fileOnlyIssues(candidate) {
131
- const issues = [];
132
- const name = candidate.name;
133
- if (typeof name === "string" && name.trim().length > SPACE_CONFIG_CAPS.nameChars) {
134
- issues.push({
135
- severity: "error",
136
- code: "config_name_too_long",
137
- message: `name supports up to ${SPACE_CONFIG_CAPS.nameChars} characters.`,
138
- path: "name",
139
- details: { length: name.trim().length, limit: SPACE_CONFIG_CAPS.nameChars },
140
- });
141
- }
142
- const runtime = candidate.runtime;
143
- if (runtime === undefined) {
144
- return issues;
145
- }
146
- if (typeof runtime !== "object" || runtime === null || Array.isArray(runtime)) {
147
- issues.push({
148
- severity: "error",
149
- code: "config_runtime_invalid_kind",
150
- message: 'runtime must be an object declaring a kind of "zero" or "functions".',
151
- path: "runtime",
152
- });
153
- return issues;
154
- }
155
- const declaration = runtime;
156
- const kind = declaration.kind;
157
- if (typeof kind !== "string" || !RUNTIME_KINDS.has(kind)) {
158
- issues.push({
159
- severity: "error",
160
- code: "config_runtime_invalid_kind",
161
- message: 'runtime.kind must be "zero" or "functions".',
162
- path: "runtime.kind",
163
- ...(typeof kind === "string" ? { details: { kind } } : {}),
164
- });
165
- return issues;
166
- }
167
- if (kind === "zero") {
168
- // Zero is never inferred, so a declaration that names neither entry has
169
- // nothing to compile — failing here beats publishing static files under a
170
- // config that promised a database app.
171
- for (const key of ["server", "client"]) {
172
- const value = declaration[key];
173
- if (typeof value !== "string" || value.trim().length === 0) {
174
- issues.push({
175
- severity: "error",
176
- code: "config_runtime_entry_missing",
177
- message: `runtime.${key} is required and must name a module when runtime.kind is "zero".`,
178
- path: `runtime.${key}`,
179
- });
180
- }
181
- }
182
- return issues;
183
- }
184
- const entry = declaration.entry;
185
- if (entry !== undefined && (typeof entry !== "string" || entry.trim().length === 0)) {
186
- issues.push({
187
- severity: "error",
188
- code: "config_runtime_entry_missing",
189
- message: "runtime.entry must name a module when present; omit it to detect the worker instead.",
190
- path: "runtime.entry",
191
- });
192
- }
193
- return issues;
194
- }
195
127
  /**
196
128
  * Keys a committed `sf.jsonc` owns outright: they are facts about the content
197
129
  * of a version (who may read it, what it is called, what runs it), so they roll
@@ -233,20 +165,9 @@ export function spaceConfigOverlayIssue(config) {
233
165
  }
234
166
  return capIssues(config)[0] ?? null;
235
167
  }
236
- // Other platforms' routing vocabulary. We have one matcher grammar; naming it
237
- // four more ways would be four more things to keep in sync.
238
- const ROUTING_KEYS = new Set(["proxies", "proxy", "routes"]);
239
- // OUR routing vocabulary. These keys are real sf.jsonc keys, owned end to end
240
- // by the routing compiler (crates/stattic-runtime-core): the strict v1 compiler
241
- // validates them and the merge folds them in behind `_redirects`/`_headers`.
242
- // This lane resolves the serving config, so it neither validates nor carries
243
- // them — it just leaves them alone.
244
- const ROUTING_RULE_KEYS = new Set(["redirects", "rewrites", "headers"]);
245
- // Replaced by the `runtime` declaration; never silently ignored like other
246
- // unknown keys, because it is the one key whose loss changes what gets served.
247
- const REMOVED_FUNCTIONS_KEY = "functions";
248
- const KNOWN_FILE_KEYS = new Set(Object.keys(spaceConfigFileSchema.shape));
249
- // Strips JSONC comments and trailing commas (string-aware single pass).
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).
250
171
  export function stripJsonComments(raw) {
251
172
  let output = "";
252
173
  let inString = false;
@@ -326,245 +247,3 @@ export function stripJsonComments(raw) {
326
247
  }
327
248
  return output;
328
249
  }
329
- /**
330
- * Parses a raw `sf.jsonc` at finalize: 256 KB cap, JSONC tolerance,
331
- * routing-key rejection, unknown-key diagnostics, shared shape caps. Errors
332
- * fail the version; warnings ride version diagnostics.
333
- */
334
- export function parseSpaceConfigFile(raw, options = {}) {
335
- const configPath = options.path ?? SPACE_CONFIG_CANONICAL_FILE;
336
- if (byteLength(raw) > SPACE_CONFIG_CAPS.fileBytes) {
337
- return {
338
- config: null,
339
- issues: [
340
- {
341
- severity: "error",
342
- code: "config_file_too_large",
343
- message: `${configPath} supports up to ${SPACE_CONFIG_CAPS.fileBytes} bytes.`,
344
- path: configPath,
345
- details: { size: byteLength(raw), limit: SPACE_CONFIG_CAPS.fileBytes },
346
- },
347
- ],
348
- };
349
- }
350
- let candidate;
351
- try {
352
- candidate = JSON.parse(stripJsonComments(raw));
353
- }
354
- catch (error) {
355
- return {
356
- config: null,
357
- issues: [
358
- {
359
- severity: "error",
360
- code: "config_invalid",
361
- message: `${configPath} is not valid JSONC: ${error instanceof Error ? error.message : "parse error"}.`,
362
- path: configPath,
363
- },
364
- ],
365
- };
366
- }
367
- if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) {
368
- return {
369
- config: null,
370
- issues: [
371
- {
372
- severity: "error",
373
- code: "config_invalid",
374
- message: `${configPath} must contain a JSON object.`,
375
- path: configPath,
376
- },
377
- ],
378
- };
379
- }
380
- const record = candidate;
381
- const issues = [];
382
- for (const key of Object.keys(record)) {
383
- if (KNOWN_FILE_KEYS.has(key)) {
384
- continue;
385
- }
386
- if (ROUTING_RULE_KEYS.has(key)) {
387
- delete record[key];
388
- continue;
389
- }
390
- if (key === REMOVED_FUNCTIONS_KEY) {
391
- // The runtime declaration replaced this key outright (spec "The old
392
- // top-level functions.* key is removed in the same cutover"): warning-and-
393
- // strip would publish static files under a config that asked for a
394
- // worker, so it blocks with its own code instead.
395
- issues.push({
396
- severity: "error",
397
- code: "config_functions_key_removed",
398
- message: `The "${REMOVED_FUNCTIONS_KEY}" key was removed from ${configPath}; declare runtime.kind "functions" instead.`,
399
- path: key,
400
- });
401
- }
402
- else if (ROUTING_KEYS.has(key)) {
403
- issues.push({
404
- severity: "error",
405
- code: "config_invalid",
406
- message: `Routing never lives in ${configPath}; move "${key}" into _redirects/_headers.`,
407
- path: key,
408
- });
409
- }
410
- else {
411
- issues.push({
412
- severity: "warning",
413
- code: "config_invalid",
414
- message: `Unknown ${configPath} key "${key}" was ignored.`,
415
- path: key,
416
- });
417
- }
418
- delete record[key];
419
- }
420
- issues.push(...capIssues(record), ...fileOnlyIssues(record));
421
- if (issues.some((issue) => issue.severity === "error")) {
422
- return { config: null, issues };
423
- }
424
- const parsed = spaceConfigFileSchema.safeParse(record);
425
- if (!parsed.success) {
426
- for (const issue of parsed.error.issues) {
427
- issues.push({
428
- severity: "error",
429
- code: "config_invalid",
430
- message: issue.message,
431
- path: issue.path.map(String).join(".") || configPath,
432
- });
433
- }
434
- return { config: null, issues };
435
- }
436
- return { config: parsed.data, issues };
437
- }
438
- // Mirrors the runtime's private-file rules (_stattic_runtime_file_private):
439
- // convention/config files and dot-segment paths never serve, so they don't
440
- // count toward root HTML inference below.
441
- const PRIVATE_SERVING_FILES = new Set([
442
- "_redirects",
443
- "_headers",
444
- "_config.json",
445
- "_routes.json",
446
- ...SPACE_CONFIG_PRIVATE_FILES,
447
- ]);
448
- function privateServingPath(path) {
449
- if (PRIVATE_SERVING_FILES.has(path) ||
450
- path === ".well-known/spacefast-runtime" ||
451
- path === ".well-known/stattic-runtime") {
452
- return true;
453
- }
454
- const segments = path.toLowerCase().split("/");
455
- return segments.some((segment, index) => !(index === 0 && segment === ".well-known") && segment.startsWith("."));
456
- }
457
- function rootHtmlPaths(manifestPaths) {
458
- const htmlPaths = [];
459
- for (const path of manifestPaths) {
460
- if (path === "" || privateServingPath(path)) {
461
- continue;
462
- }
463
- if (!path.includes("/") && /\.html?$/i.test(path)) {
464
- htmlPaths.push(path);
465
- }
466
- }
467
- return htmlPaths.toSorted((left, right) => left.localeCompare(right));
468
- }
469
- function normalizeFallback(fallback) {
470
- if (fallback === undefined) {
471
- return null;
472
- }
473
- if (typeof fallback === "string") {
474
- return { path: fallback.replace(/^\/+/, ""), status: 200 };
475
- }
476
- const status = fallback.status === 404 ? 404 : 200;
477
- return { path: fallback.path.replace(/^\/+/, ""), status };
478
- }
479
- /**
480
- * Resolves the effective SpaceConfig for a version at finalize (or
481
- * config-only regeneration). `manifestPaths` is the canonical committed file
482
- * set; `fileConfig` the parsed sf.jsonc (null when absent); `overlay`
483
- * the stored space config overlay (null when nothing overridden).
484
- */
485
- export function resolveEffectiveSpaceConfig(input) {
486
- const rootHtml = rootHtmlPaths(input.manifestPaths);
487
- const hasRootIndex = rootHtml.includes("index.html");
488
- const inferredRootHtmlIndex = hasRootIndex ? "index.html" : (rootHtml[0] ?? null);
489
- const explicitServingShape = input.fileConfig?.index !== undefined ||
490
- input.fileConfig?.listing !== undefined ||
491
- input.overlay?.index !== undefined ||
492
- input.overlay?.listing !== undefined;
493
- // Content-inferred defaults (spec: the old website/files modes are exactly
494
- // these two default bundles; never API fields). A Functions version is an
495
- // application even when it has no root HTML: unresolved requests, including
496
- // `/`, belong to its worker. Static versions retain the content inference —
497
- // a root index.html means website mode, any other root HTML becomes the
498
- // homepage rewrite, and no root HTML means files mode.
499
- const functionsApplication = input.runtimeKind === "functions";
500
- const defaults = inferredRootHtmlIndex
501
- ? { index: inferredRootHtmlIndex, listing: false }
502
- : { index: false, listing: !functionsApplication };
503
- const merged = { ...defaults };
504
- for (const layer of [input.fileConfig, input.overlay]) {
505
- if (!layer) {
506
- continue;
507
- }
508
- for (const key of Object.keys(spaceConfigSchema.shape)) {
509
- const value = layer[key];
510
- if (value !== undefined) {
511
- merged[key] = value;
512
- }
513
- }
514
- }
515
- const issues = [];
516
- const fallback = normalizeFallback(merged.fallback);
517
- // Invariant (spec "Serving resolution"): a fallback must point at a
518
- // committed file — no implicit extension guessing, fail at finalize.
519
- if (fallback && !input.manifestPaths.has(fallback.path)) {
520
- issues.push({
521
- severity: "error",
522
- code: "config_invalid",
523
- message: `fallback path "/${fallback.path}" is not a committed file in this version.`,
524
- path: "fallback.path",
525
- details: { path: fallback.path },
526
- });
527
- }
528
- const listing = merged.listing ?? (!functionsApplication && !inferredRootHtmlIndex);
529
- const serving = {
530
- index: merged.index ?? inferredRootHtmlIndex ?? false,
531
- fallback,
532
- // Clean URLs (runtime W7.1): an explicit config value wins; the default is
533
- // ON for plain static sites and OFF when a 200-status SPA fallback owns
534
- // extensionless routes (the fallback answers them instead). The runtime
535
- // resolver (management.php) applies the same default for artifacts
536
- // finalized without the key.
537
- cleanUrls: merged.cleanUrls ?? !(fallback && fallback.status === 200),
538
- listing,
539
- // The single-file viewer rides the listing bundle (artifact-style spaces).
540
- viewer: listing,
541
- meta: merged.meta ?? null,
542
- pageTheme: merged.theme && Object.keys(merged.theme).length > 0 ? merged.theme : null,
543
- experimentalGutenberg: merged.experimental_gutenberg === true,
544
- inject: merged.inject ?? null,
545
- };
546
- if (inferredRootHtmlIndex !== null && !hasRootIndex && serving.index === inferredRootHtmlIndex) {
547
- // Surfaced on the publish receipt and version diagnostics so the CLI and
548
- // agents learn the rewrite without probing the live URL.
549
- issues.push({
550
- severity: "info",
551
- code: "single_html_index",
552
- message: `${inferredRootHtmlIndex} is the root HTML file selected for this version, so it serves as the homepage: / rewrites to /${inferredRootHtmlIndex}.`,
553
- path: "index",
554
- details: { path: inferredRootHtmlIndex, rootHtmlCount: rootHtml.length },
555
- });
556
- }
557
- if (inferredRootHtmlIndex === null &&
558
- serving.index === false &&
559
- serving.listing &&
560
- !explicitServingShape) {
561
- issues.push({
562
- severity: "info",
563
- code: "files_mode_inferred",
564
- message: "No HTML files were found in the root of this version, so it serves in files mode with directory listing enabled.",
565
- path: "listing",
566
- details: { rootHtmlCount: 0 },
567
- });
568
- }
569
- return { config: merged, serving, issues };
570
- }
@@ -1,6 +1,6 @@
1
1
  export declare const STORAGE_OBJECT_MAX_FILE_BYTES: number;
2
- export declare const STORAGE_OBJECT_MAX_OWNER_BYTES: number;
3
2
  export declare const STORAGE_CONTENT_SNIFF_BYTES = 512;
3
+ export declare const STORAGE_ANON_DAILY_BYTES: number;
4
4
  export declare const STORAGE_BLOCKED_CONTENT_TYPES: string[];
5
5
  export declare const STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS: string[];
6
6
  export declare const STORAGE_BLOCKED_MAGIC_HEX: string[];
@@ -5,8 +5,10 @@
5
5
  // `bun --filter @spacefast/control-plane runtime:codegen-policy`. Both execute
6
6
  // the same storage-policy.fixtures.json corpus.
7
7
  export const STORAGE_OBJECT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8
- export const STORAGE_OBJECT_MAX_OWNER_BYTES = 100 * 1024 * 1024;
9
8
  export const STORAGE_CONTENT_SNIFF_BYTES = 512;
9
+ // Anonymous commenters share a per-space rolling daily upload budget; identified
10
+ // principals and Functions relays are accountable and carry no budget.
11
+ export const STORAGE_ANON_DAILY_BYTES = 200 * 1024 * 1024;
10
12
  export const STORAGE_BLOCKED_CONTENT_TYPES = [
11
13
  "application/java-archive",
12
14
  "application/java-vm",
@@ -0,0 +1,14 @@
1
+ export declare class StructuredError extends Error {
2
+ readonly code: string;
3
+ readonly details: Record<string, unknown> | undefined;
4
+ readonly exitCode: number;
5
+ readonly type: string | undefined;
6
+ constructor(input: {
7
+ code: string;
8
+ message: string;
9
+ details?: Record<string, unknown> | undefined;
10
+ exitCode?: number | undefined;
11
+ type?: string | undefined;
12
+ });
13
+ }
14
+ export declare function structuredValidationError(message: string, details?: Record<string, unknown>): StructuredError;
@@ -0,0 +1,22 @@
1
+ import { CLI_EXIT_CODES } from "../contracts/cli.js";
2
+ export class StructuredError extends Error {
3
+ code;
4
+ details;
5
+ exitCode;
6
+ type;
7
+ constructor(input) {
8
+ super(input.message);
9
+ this.name = "StructuredError";
10
+ this.code = input.code;
11
+ this.details = input.details;
12
+ this.exitCode = input.exitCode ?? CLI_EXIT_CODES.validation;
13
+ this.type = input.type;
14
+ }
15
+ }
16
+ export function structuredValidationError(message, details) {
17
+ return new StructuredError({
18
+ code: "validation_error",
19
+ message,
20
+ ...(details ? { details } : {}),
21
+ });
22
+ }
@@ -3,12 +3,20 @@
3
3
  // what manifest paths, finalize's normalized paths, route-map keys, and dedup
4
4
  // keys all mean. The PHP runtime applies the same transform (rawurldecode once
5
5
  // + Normalizer::normalize) at upload and serve time.
6
+ // oxlint-disable-next-line no-control-regex -- matching control characters is the point: they are never part of a servable path.
6
7
  const CONTROL_CHARS_PATTERN = /[\u0000-\u001f\u007f]/;
7
8
  // Structural normalization shared by lookups and intake: forward slashes,
8
- // no leading slashes, no empty/"."/".." segments. Idempotent; safe to apply
9
- // to already-canonical stored paths (NFC is idempotent, no percent-decoding).
9
+ // no leading slashes, no control characters, no empty/"."/".." segments.
10
+ // Idempotent; safe to apply to already-canonical stored paths (NFC is
11
+ // idempotent, no percent-decoding).
10
12
  export function normalizeVersionPath(input) {
11
13
  const normalized = input.replace(/\\/g, "/").replace(/^\/+/, "").normalize("NFC");
14
+ // Here rather than only in canonicalizeVersionPath: publish-policy's
15
+ // staticUploadPathViolation validates with this function, so a control
16
+ // character in an uploaded path used to pass the policy unclassified.
17
+ if (CONTROL_CHARS_PATTERN.test(normalized)) {
18
+ throw new Error(`invalid_version_path:${input}`);
19
+ }
12
20
  const segments = [];
13
21
  for (const segment of normalized.split("/")) {
14
22
  if (!segment || segment === ".") {
@@ -41,7 +49,8 @@ export function canonicalizeVersionPath(input) {
41
49
  const canonical = decoded.normalize("NFC");
42
50
  // Backslashes as separators are denied at intake (spec "Static path
43
51
  // policy"); only client-side helpers translate Windows separators.
44
- if (CONTROL_CHARS_PATTERN.test(canonical) || canonical.includes("\\")) {
52
+ // Control characters are rejected by normalizeVersionPath below.
53
+ if (canonical.includes("\\")) {
45
54
  throw new Error(`invalid_version_path:${input}`);
46
55
  }
47
56
  return normalizeVersionPath(canonical);
@@ -7,6 +7,8 @@ export type TermContext =
7
7
  "platform-api"
8
8
  /** The GitHub integration vocabulary (push deploys, deployment status). */
9
9
  | "github-integration"
10
+ /** Internal operator surfaces (superadmin, ops scripts, fleet inventory). */
11
+ | "operator"
10
12
  /** A fixed marketing phrase, spelled out verbatim in `allowedPhrases`. */
11
13
  | "fixed-phrase";
12
14
  /** A single canonical product concept and the words allowed to express it. */
@@ -38,8 +40,8 @@ export declare const CONCEPTS: {
38
40
  readonly id: "space";
39
41
  readonly canonicalNoun: "space";
40
42
  readonly aliases: readonly [];
41
- readonly externalTerms: readonly ["site"];
42
- readonly deprecated: readonly ["project"];
43
+ readonly externalTerms: readonly [];
44
+ readonly deprecated: readonly ["project", "site"];
43
45
  readonly description: string;
44
46
  };
45
47
  readonly publish: {
@@ -137,7 +139,7 @@ export declare const EXTERNAL_TERM_ALLOWLIST: readonly [{
137
139
  }, {
138
140
  readonly term: "site";
139
141
  readonly concept: "space";
140
- readonly allowedContexts: readonly ["platform-api"];
142
+ readonly allowedContexts: readonly ["operator"];
141
143
  readonly replacement: "space";
142
144
  readonly reason: string;
143
145
  }, {
@@ -8,9 +8,10 @@
8
8
  // It is additive — wiring consumers (CLI, MCP, dashboard, docs generators) onto
9
9
  // it happens in later chunks. The decisions encoded here are LOCKED:
10
10
  //
11
- // - `space` is the primitive noun on every user surface. `site` survives only
12
- // as the platform-API term for the shared-pool host a space is placed on
13
- // (see contracts/sites.ts, internal-docs/platform.md "Site").
11
+ // - `space` is the primitive noun everywhere, user surfaces and platform API
12
+ // alike. A space IS a site: there is no second host entity to name. `site`
13
+ // survives only as WP.Cloud's own word for the box, in operator surfaces
14
+ // and the warm-pool inventory.
14
15
  // - `publish` is the verb. AMENDED 2026-07-02: `deploy` / `deployments` are
15
16
  // first-class ALIASES of `publish` / `versions` (see DEPLOY_ALIASES) — they
16
17
  // stay listed and documented, always with the sanctioned disambiguation
@@ -30,10 +31,11 @@ export const CONCEPTS = {
30
31
  id: "space",
31
32
  canonicalNoun: "space",
32
33
  aliases: [],
33
- externalTerms: ["site"],
34
- deprecated: ["project"],
35
- description: "The primitive noun everywhere a user works. 'Site' is the platform-API " +
36
- "term for the shared host a space lands on; 'project' is never the primitive.",
34
+ externalTerms: [],
35
+ deprecated: ["project", "site"],
36
+ description: "The primitive noun everywhere, user surfaces and platform API alike. A " +
37
+ "space IS a site, so 'site' names nothing of ours; 'project' is never " +
38
+ "the primitive.",
37
39
  },
38
40
  publish: {
39
41
  id: "publish",
@@ -129,10 +131,11 @@ export const EXTERNAL_TERM_ALLOWLIST = [
129
131
  {
130
132
  term: "site",
131
133
  concept: "space",
132
- allowedContexts: ["platform-api"],
134
+ allowedContexts: ["operator"],
133
135
  replacement: "space",
134
- reason: "'Site' is the platform-API host primitive (shared pool capacity); user " +
135
- "surfaces always say 'space'.",
136
+ reason: "A space IS a site, so 'site' is no longer a Spacefast noun it is only " +
137
+ "WP.Cloud's word for the box, which operator surfaces and warm-pool " +
138
+ "inventory may use. Every customer-facing surface says 'space'.",
136
139
  },
137
140
  {
138
141
  term: "WP.Cloud",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spacefast/common",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "license": "MIT",
5
5
  "description": "Shared TypeScript contracts and utilities for Spacefast packages.",
6
6
  "repository": {
@@ -27,13 +27,13 @@
27
27
  "../../apps/www/dist/ai.txt",
28
28
  "../../apps/www/dist/llms-full.txt",
29
29
  "../../apps/www/dist/llms.txt",
30
- "../../apps/www/public/skill-references.md",
31
- "../../apps/www/public/skill.md",
30
+ "../cli/skill.template.md",
31
+ "../cli/skill.references.template.md",
32
+ "../cli/scripts/generate-skill.mjs",
32
33
  "../../e2e-tests/fixtures/zero-all-features/api-finalize-request.json",
33
34
  "../../e2e-tests/fixtures/zero-all-features/runtime-compiler-finalize-input.json",
34
35
  "../../runtime/engine/shared/fonts",
35
- "../cli/SKILL.md",
36
- "../cli/references.md"
36
+ "src/docs/agent-prose.ts"
37
37
  ]
38
38
  },
39
39
  "exports": {
@@ -75,6 +75,11 @@
75
75
  "import": "./dist/utils/*.js",
76
76
  "default": "./dist/utils/*.js"
77
77
  },
78
+ "./test-helpers/*": {
79
+ "types": "./dist/test-helpers/*.d.ts",
80
+ "import": "./dist/test-helpers/*.js",
81
+ "default": "./dist/test-helpers/*.js"
82
+ },
78
83
  "./slug-policy": {
79
84
  "types": "./dist/slug-policy/index.d.ts",
80
85
  "import": "./dist/slug-policy/index.js",