@spacefast/common 0.0.12 → 0.0.13

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 (113) hide show
  1. package/dist/contracts/access.d.ts +386 -1201
  2. package/dist/contracts/access.js +240 -1057
  3. package/dist/contracts/activity.d.ts +1 -1
  4. package/dist/contracts/activity.js +6 -14
  5. package/dist/contracts/api-keys.d.ts +16 -0
  6. package/dist/contracts/api-keys.js +2 -0
  7. package/dist/contracts/archives.d.ts +1 -2
  8. package/dist/contracts/archives.js +4 -6
  9. package/dist/contracts/comments.d.ts +58 -85
  10. package/dist/contracts/comments.js +86 -67
  11. package/dist/contracts/countries.js +2 -7
  12. package/dist/contracts/data-source-routes.d.ts +20 -0
  13. package/dist/contracts/data-source-routes.js +15 -0
  14. package/dist/contracts/device-auth.d.ts +7 -3
  15. package/dist/contracts/enums.d.ts +2 -4
  16. package/dist/contracts/enums.js +3 -8
  17. package/dist/contracts/error-code-meta.d.ts +17 -20
  18. package/dist/contracts/error-code-meta.js +8 -5
  19. package/dist/contracts/error-codes.d.ts +1 -1
  20. package/dist/contracts/error-codes.js +4 -5
  21. package/dist/contracts/events.d.ts +13 -13
  22. package/dist/contracts/execution.d.ts +212 -0
  23. package/dist/contracts/execution.js +288 -0
  24. package/dist/contracts/feature-lifecycle.d.ts +21 -167
  25. package/dist/contracts/feature-lifecycle.js +27 -173
  26. package/dist/contracts/features.d.ts +13 -115
  27. package/dist/contracts/features.js +1 -31
  28. package/dist/contracts/functions.d.ts +729 -0
  29. package/dist/contracts/functions.js +485 -0
  30. package/dist/contracts/git.d.ts +1 -1
  31. package/dist/contracts/grants.d.ts +1158 -0
  32. package/dist/contracts/grants.js +696 -0
  33. package/dist/contracts/ids.d.ts +5 -3
  34. package/dist/contracts/ids.js +5 -3
  35. package/dist/contracts/mcp.d.ts +6 -6
  36. package/dist/contracts/oauth-resources.d.ts +5 -5
  37. package/dist/contracts/oauth-resources.js +1 -0
  38. package/dist/contracts/operations.d.ts +2 -2
  39. package/dist/contracts/pages.d.ts +6 -14
  40. package/dist/contracts/pages.js +6 -23
  41. package/dist/contracts/plan-policy.d.ts +1 -8
  42. package/dist/contracts/plan-policy.js +1 -4
  43. package/dist/contracts/platform.d.ts +78 -30
  44. package/dist/contracts/platform.js +59 -12
  45. package/dist/contracts/push-new.d.ts +26 -2
  46. package/dist/contracts/push-new.js +8 -0
  47. package/dist/contracts/quotas.d.ts +0 -1
  48. package/dist/contracts/quotas.js +0 -5
  49. package/dist/contracts/repository-connections.d.ts +1 -1
  50. package/dist/contracts/resources.d.ts +10 -66
  51. package/dist/contracts/resources.js +4 -10
  52. package/dist/contracts/runtime-api.d.ts +558 -265
  53. package/dist/contracts/runtime-api.js +178 -91
  54. package/dist/contracts/runtime-app.d.ts +138 -0
  55. package/dist/contracts/runtime-app.js +21 -0
  56. package/dist/contracts/sf-config-v1.d.ts +5 -0
  57. package/dist/contracts/sites.d.ts +5 -0
  58. package/dist/contracts/sites.js +12 -1
  59. package/dist/contracts/space-config.d.ts +46 -116
  60. package/dist/contracts/space-config.js +320 -24
  61. package/dist/contracts/spaces.d.ts +253 -158
  62. package/dist/contracts/spaces.js +68 -54
  63. package/dist/contracts/superadmin-emails.d.ts +2 -2
  64. package/dist/contracts/superadmin-spaces.d.ts +15 -130
  65. package/dist/contracts/superadmin-spaces.js +7 -0
  66. package/dist/contracts/tags.d.ts +23 -23
  67. package/dist/contracts/teams.d.ts +9 -9
  68. package/dist/contracts/transfers.d.ts +0 -1
  69. package/dist/contracts/zero.d.ts +195 -273
  70. package/dist/contracts/zero.js +78 -206
  71. package/dist/dashboard-paths/index.d.ts +2 -2
  72. package/dist/dashboard-paths/index.js +1 -0
  73. package/dist/docs/agent-prose.js +10 -8
  74. package/dist/docs/agent-setup.d.ts +2 -2
  75. package/dist/docs/agent-setup.js +11 -9
  76. package/dist/docs/agent-solutions.js +1 -1
  77. package/dist/docs/catalog.d.ts +28 -28
  78. package/dist/docs/catalog.js +29 -29
  79. package/dist/docs/error-docs.js +23 -27
  80. package/dist/utils/asset-fingerprint.d.ts +11 -0
  81. package/dist/utils/asset-fingerprint.js +46 -0
  82. package/dist/utils/auth-redirect.d.ts +1 -3
  83. package/dist/utils/auth-redirect.js +1 -21
  84. package/dist/utils/build-settings.d.ts +2 -0
  85. package/dist/utils/build-settings.js +61 -9
  86. package/dist/utils/credential-policy.d.ts +1 -1
  87. package/dist/utils/credential-policy.js +4 -0
  88. package/dist/utils/egress-policy.fixtures.json +1 -1
  89. package/dist/utils/grants.d.ts +53 -0
  90. package/dist/utils/grants.js +160 -0
  91. package/dist/utils/local-space-state.js +1 -0
  92. package/dist/utils/pages.d.ts +11 -5
  93. package/dist/utils/pages.js +19 -32
  94. package/dist/utils/publish-policy.d.ts +34 -4
  95. package/dist/utils/publish-policy.js +132 -61
  96. package/dist/utils/query-keys.d.ts +1 -6
  97. package/dist/utils/query-keys.js +1 -6
  98. package/dist/utils/space-config.d.ts +5 -1
  99. package/dist/utils/space-config.js +35 -0
  100. package/dist/utils/static-runtime-policy.d.ts +5 -0
  101. package/dist/utils/static-runtime-policy.js +54 -10
  102. package/dist/utils/upload-session.d.ts +3 -0
  103. package/dist/utils/upload-session.js +31 -0
  104. package/package.json +2 -1
  105. package/dist/utils/access-grant.d.ts +0 -14
  106. package/dist/utils/access-grant.js +0 -88
  107. package/dist/utils/access-match.d.ts +0 -46
  108. package/dist/utils/access-match.fixtures.json +0 -513
  109. package/dist/utils/access-match.js +0 -312
  110. package/dist/utils/access-verdict.fixtures.json +0 -421
  111. package/dist/utils/sf-config-v1.d.ts +0 -2
  112. package/dist/utils/sf-config-v1.fixtures.json +0 -9
  113. package/dist/utils/sf-config-v1.js +0 -551
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- import { accessEventSchema, policyDocSchema } from "./access.js";
2
+ import { accessFenceKindSchema } from "./access.js";
3
+ import { functionsRuntimeConfigSchema } from "./functions.js";
4
+ import { grantSchema } from "./grants.js";
3
5
  import { conventionFilesSchema, manifestFileSchema } from "./internal.js";
4
6
  import { runtimePagesSchema } from "./pages.js";
5
7
  import { zeroEndpointSchema, zeroRuntimeConfigSchema } from "./zero.js";
@@ -57,15 +59,6 @@ export const runtimeVersionCreateRequestSchema = z.object({
57
59
  files: z.array(manifestFileSchema),
58
60
  retained_files: z.array(manifestFileSchema).default([]),
59
61
  reusable_version_id: z.string().min(1).optional(),
60
- // "declared" sessions validate every PUT against the declared manifest.
61
- // "open" sessions (internal-docs/platform.md "Upload Contract" shape 2) have
62
- // no manifest: the runtime enforces path policy and the session caps per
63
- // PUT, computes streaming hashes, and finalize derives the manifest.
64
- // Plan-policy caps ride in session state, never in the upload JWT, and are
65
- // valid only on open sessions.
66
- session_mode: z.enum(["declared", "open"]).default("declared"),
67
- max_total_bytes: z.number().int().positive().optional(),
68
- max_file_count: z.number().int().positive().optional(),
69
62
  // Session state stores the upload truth (spec "Upload Contract"): the version's
70
63
  // manifest hash and the draft expiry so resume answers come from runtime state.
71
64
  manifest_hash: z.string().optional(),
@@ -83,7 +76,6 @@ export const runtimeUploadSessionDescriptorSchema = z.object({
83
76
  upload_id: z.string().min(1),
84
77
  space_id: z.string().min(1),
85
78
  version_id: z.string().min(1),
86
- session_mode: z.literal("declared"),
87
79
  expires_at: z.string(),
88
80
  manifest_hash: z.string().optional(),
89
81
  files: z.array(manifestFileSchema),
@@ -108,7 +100,6 @@ export const runtimeUploadSessionResponseSchema = z.object({
108
100
  space_id: z.string(),
109
101
  version_id: z.string(),
110
102
  upload_id: z.string(),
111
- session_mode: z.enum(["declared", "open"]),
112
103
  created_at: z.string().nullable(),
113
104
  expires_at: z.string().nullable(),
114
105
  manifest_hash: z.string().nullable(),
@@ -118,39 +109,80 @@ export const runtimeUploadSessionResponseSchema = z.object({
118
109
  sha256: z.string().optional(),
119
110
  uploaded: z.boolean(),
120
111
  })),
121
- // Declared paths the runtime has not committed yet (empty for open sessions).
112
+ // Declared paths the runtime has not committed yet.
122
113
  pending_paths: z.array(z.string()),
123
114
  // Staged chunked-upload parts by canonical path (contiguity is checked at complete).
124
115
  chunks: z.record(z.string(), z.array(z.number().int().positive())),
125
116
  });
126
- // Route-pointer config is pure serving policy: THE resolved access policy plus
127
- // the anonymous claim countdown. Space passwords are NOT a separate config
128
- // channel they ride inside `policy.rules` as a password challenge rule (one
129
- // access model, spec "Runtime"). Per-version serving behavior
130
- // (index/fallback/listing) is a version artifact concern, never route config.
117
+ // Route-pointer config is pure serving policy plus the anonymous claim
118
+ // countdown. Per-version serving behavior (index/fallback/listing) remains a
119
+ // version artifact concern.
120
+ export const visitorJwkSchema = z.object({
121
+ kty: z.literal("OKP"),
122
+ crv: z.literal("Ed25519"),
123
+ kid: z.string().min(1),
124
+ alg: z.literal("EdDSA"),
125
+ x: z.string().min(1),
126
+ use: z.literal("sig").optional(),
127
+ });
128
+ export const visitorJwksSchema = z.object({
129
+ keys: z.array(visitorJwkSchema).min(1),
130
+ });
131
+ export const runtimePublicExposureDescriptorSchema = z.object({
132
+ v: z.number().int().positive(),
133
+ public: z.boolean(),
134
+ authorizationDigest: z.string().regex(/^[a-f0-9]{64}$/),
135
+ contentTypes: z.array(z.string()).nullable(),
136
+ externalProxy: z.boolean(),
137
+ unmodeled: z.string(),
138
+ });
139
+ export const runtimeAuthorizationProjectionSchema = z
140
+ .object({
141
+ generation: z.number().int().nonnegative(),
142
+ // Browser sessions follow their still-live authority references across
143
+ // ordinary policy/ownership projections. Only an explicit logout-all
144
+ // rotation invalidates every otherwise-valid session.
145
+ sessionVersion: z.number().int().nonnegative(),
146
+ fence: accessFenceKindSchema,
147
+ acquireUrl: z.string().url(),
148
+ spaceClaimed: z.boolean(),
149
+ grants: z.array(grantSchema.extend({
150
+ // The durable browser session records this generation for the exact
151
+ // authority it acquired. Rotating or editing one credential Grant
152
+ // therefore removes only that authority from the session.
153
+ generation: z.number().int().positive(),
154
+ })),
155
+ })
156
+ .strict();
131
157
  const runtimeRouteConfigSchema = z.object({
132
- // THE unified policy lane ({ rules: RuntimeRule[] }) the runtime enforcer
133
- // (access-rules.php) evaluates first-match-wins for every host entry this
134
- // space serves — route hosts, custom domains, and immutable version hosts
135
- // alike. Carries platform + file + cloud rules (platform prepended, always
136
- // wins). Explicit null clears the stored policy; absence leaves it untouched.
137
- policy: policyDocSchema.nullable().optional(),
138
- // Serving-secret map (name -> value) the policy's password acquires
139
- // reference by `ref` = "secret:<name>". Carries the space-password bcrypt
140
- // verifier hash (and the compiled `_headers` basicAuth credential hashes)
141
- // the runtime resolves and `password_verify`s. Explicit null clears stored
142
- // secrets; absence leaves them untouched.
143
- secrets: z.record(z.string().min(1), z.string().min(1)).nullable().optional(),
144
- // Space session version (access-plan §3.1): the runtime rejects a visitor
145
- // token whose `sv` claim mismatches this, and derives the space-local `pw:`
146
- // key from the password secret + this value. Absent = 0.
147
- session_version: z.number().int().nonnegative().nullable().optional(),
158
+ // Canonical scoped admission projection. When present it is the sole serving
159
+ // authority; the legacy rule document below is ignored.
160
+ authorization: runtimeAuthorizationProjectionSchema.nullable().optional(),
161
+ // Digest of every input that can change anonymous/public exposure (canonical
162
+ // authorization boundaries, content types, proxy entitlement, and
163
+ // unknown serving keys). Authority-only rotation deliberately leaves it
164
+ // stable so Link/Person changes do not block on an unnecessary edge purge.
165
+ public_exposure_digest: z
166
+ .string()
167
+ .regex(/^[a-f0-9]{64}$/)
168
+ .optional(),
169
+ // The digest detects equality; the descriptor preserves enough structure for
170
+ // the callback consumer to distinguish widening from narrowing without
171
+ // reconstructing a previous config from mutable control-plane state.
172
+ public_exposure: runtimePublicExposureDescriptorSchema.optional(),
173
+ // Monotonic authorization projection generation. Short-lived handoffs are
174
+ // bound to this exact generation, Space, and host. Browser sessions use the
175
+ // projection's independent sessionVersion revocation floor.
176
+ projection_generation: z.number().int().nonnegative().nullable().optional(),
177
+ // First-party visitor-token verification is explicit.
178
+ visitor_issuer: z.string().min(1).optional(),
179
+ visitor_jwks: visitorJwksSchema.optional(),
148
180
  // Anonymous claim-window countdown (ISO timestamp), compiled into serving
149
181
  // state so the runtime can render the expiry-rescue banner for visitors
150
182
  // holding the claim-link viewer session. Null/absent once claimed.
151
183
  anonymous_expires_at: z.string().nullable().optional(),
152
184
  // Serve-time content-type allowlist. Generic serving policy: the engine has
153
- // no notion of WHY a space is restricted — it refuses (403, blocked_message
185
+ // no notion of WHY a file type is blocked — it refuses (403, blocked_message
154
186
  // body) any file whose stored Content-Type matches no pattern. Patterns are
155
187
  // exact types or `prefix/*` wildcards ("text/*"). The control plane pushes
156
188
  // it for unclaimed anonymous spaces (no opaque binaries pre-claim); explicit
@@ -169,15 +201,15 @@ const runtimeRouteConfigSchema = z.object({
169
201
  // in V1 (set via the superadmin admission endpoint); absent means the
170
202
  // env-knob default applies.
171
203
  admission: z.object({ concurrency: z.number().int().positive() }).optional(),
172
- // Durable revocation SET (access-plan revocation hardening): every
173
- // currently-revoked link:/invite:/svc: grant id, computed from the
174
- // control-plane's Postgres projection (access/resolve.ts
175
- // `revocationsForSpace`). The runtime REPLACES its revocations store's
176
- // grant bucket with exactly this set on every sync — the backstop that
177
- // converges even when the instant best-effort revoke_grant/unrevoke_grant
178
- // call was dropped. `subs` (JWT-subject revocations) are left untouched.
179
- // Absence leaves the stored set untouched.
180
- revocations: z.array(z.string().min(1)).optional(),
204
+ // Same-host production tag JavaScript. It rides the mutable route projection
205
+ // so private Spaces never fetch an anonymously readable per-Space artifact.
206
+ tag_sdk: z
207
+ .object({
208
+ revision: z.string().min(1),
209
+ body: z.string().max(5 * 1024 * 1024),
210
+ })
211
+ .nullable()
212
+ .optional(),
181
213
  // Serve-time plan entitlements (proxy-routes.md: "the rule stays in your
182
214
  // config and activates the moment you upgrade — no redeploy needed"). Stored
183
215
  // per space beside `policy`; a `planGated` compiled proxy rule
@@ -305,12 +337,11 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
305
337
  session: runtimeUploadSessionDescriptorSchema.optional(),
306
338
  zero_mode: runtimeZeroModeSchema.optional(),
307
339
  zero: zeroRuntimeConfigSchema.optional(),
308
- // The version's authoritative ready timestamp (epoch seconds), passed by the
309
- // control plane when the version already reached ready (re-finalize, import
310
- // re-materialization, provider migration). The engine stamps it ONCE into the
311
- // version metadata; access-window rules (windowDays) compare against this
312
- // stored value — never a filesystem mtime that resets on rebuilds.
313
- ready_at: z.number().int().positive().optional(),
340
+ // Dispatch configuration for a Functions version. Absent for static versions,
341
+ // and absent when a worker was published while no execution host is
342
+ // configured the version then serves as pure static content, because a
343
+ // config is the only thing that makes the origin dispatch at all.
344
+ functions: functionsRuntimeConfigSchema.optional(),
314
345
  convention_files: runtimeConventionFilesSchema.optional(),
315
346
  // Finalize-time `{{ vars.NAME }}` substitution output (spec "Variable
316
347
  // rules"): substituted bytes replace the served files; the engine keeps the
@@ -394,6 +425,20 @@ export const runtimeVersionFinalizeRequestSchema = z.object({
394
425
  })
395
426
  .optional(),
396
427
  });
428
+ const runtimePublicReadinessStatusSchema = z.union([
429
+ z.literal(200),
430
+ z.literal(301),
431
+ z.literal(302),
432
+ z.literal(303),
433
+ z.literal(307),
434
+ z.literal(308),
435
+ z.literal(401),
436
+ z.literal(403),
437
+ ]);
438
+ export const runtimeReadinessTargetSchema = z.object({
439
+ path: z.string().startsWith("/"),
440
+ expected_statuses: z.array(z.union([runtimePublicReadinessStatusSchema, z.literal(404)])).min(1),
441
+ });
397
442
  export const runtimeVersionFinalizeResponseSchema = z.object({
398
443
  space_id: z.string(),
399
444
  version_id: z.string(),
@@ -405,6 +450,12 @@ export const runtimeVersionFinalizeResponseSchema = z.object({
405
450
  // Returned by both first and idempotent finalize responses so recovery
406
451
  // never needs an already-consumed upload session.
407
452
  manifest: z.array(manifestFileSchema).optional(),
453
+ readiness_target: runtimeReadinessTargetSchema,
454
+ activation_event_id: z
455
+ .string()
456
+ .min(1)
457
+ .optional()
458
+ .describe("Stable route_updated event id when finalize also activated a route."),
408
459
  });
409
460
  export const runtimeRouteUpdateRequestSchema = z.object({
410
461
  version_id: z.string().min(1),
@@ -437,6 +488,11 @@ export const runtimeRouteUpdateResponseSchema = z.object({
437
488
  // and hostname intent all matched the stored route: nothing was journaled,
438
489
  // so callers skip the event drain too.
439
490
  unchanged: z.boolean().optional(),
491
+ activation_event_id: z
492
+ .string()
493
+ .min(1)
494
+ .optional()
495
+ .describe("Stable route_updated event id for this exact route command, including retries."),
440
496
  });
441
497
  export const runtimeHostnameIntentUpdateResponseSchema = z.object({
442
498
  space_id: z.string(),
@@ -467,20 +523,6 @@ export const runtimeRetentionPolicyUpdateResponseSchema = z.object({
467
523
  space_id: z.string(),
468
524
  prunable_count: z.number().int().nonnegative(),
469
525
  });
470
- // Instant per-grant tombstone (access-plan revocation hardening): the
471
- // control plane's best-effort synchronous call at revoke/unrevoke time,
472
- // hitting the SAME `revocations.json` the durable `config.revocations` full
473
- // replace (above) also converges. `grant` must be a `link:`/`invite:`/`svc:`
474
- // row id — the runtime rejects anything else.
475
- export const runtimeGrantRevocationRequestSchema = z.object({
476
- grant: z.string().min(1),
477
- });
478
- export const runtimeGrantRevocationResponseSchema = z.object({
479
- space_id: z.string(),
480
- target_type: z.literal("grant"),
481
- grant: z.string(),
482
- revoked: z.boolean(),
483
- });
484
526
  export const runtimeSpaceDeleteResponseSchema = z.object({
485
527
  space_id: z.string(),
486
528
  status: z.literal("deleted"),
@@ -503,11 +545,9 @@ const runtimeExportImportStatusSchema = z.enum([
503
545
  export const runtimeSpaceExportStartRequestSchema = z
504
546
  .object({
505
547
  version_ids: z.array(z.string().min(1)).min(1).optional(),
506
- // Honest export options (spec SpaceExportRequest): include_files=false
507
- // produces a config-only archive; include_explicit_config=false skips the
508
- // space access policy entry. Recorded in stattic.json `includes`.
548
+ // include_files=false produces a metadata-only archive that cannot be
549
+ // imported as a serving Space. Recorded in stattic.json `includes`.
509
550
  include_files: z.boolean().optional(),
510
- include_explicit_config: z.boolean().optional(),
511
551
  })
512
552
  .strict();
513
553
  export const runtimeSpaceExportResponseSchema = z.object({
@@ -529,10 +569,6 @@ export const runtimeSpaceExportResponseSchema = z.object({
529
569
  });
530
570
  export const runtimeSpaceImportStartRequestSchema = z
531
571
  .object({
532
- // Version installation and target-policy replacement are separate trust
533
- // decisions. Managed imports always pass false; trusted full-restore tools
534
- // must opt in explicitly before archive upload.
535
- install_access_policy: z.boolean().optional(),
536
572
  version_id_map: z.record(z.string(), z.string()).optional(),
537
573
  // Plan §9 gated activation, extended to import (I-4): shares
538
574
  // runtimeZeroModeSchema with runtimeVersionFinalizeRequestSchema.
@@ -682,6 +718,56 @@ export const runtimeCallbackEventSchema = z.looseObject({
682
718
  .optional()
683
719
  .describe("True when changed_paths is the complete diff, including a known-empty diff. Absent on older engines, where an empty array remains the host-wide fallback."),
684
720
  previous_version_id: previousVersionIdSchema,
721
+ previous_public_exposure_digest: z
722
+ .string()
723
+ .min(1)
724
+ .nullable()
725
+ .optional()
726
+ .describe("Public-exposure digest stored before a route config write. Field presence proves the route already existed; null means the previous projection was unreadable."),
727
+ public_exposure_digest: z
728
+ .string()
729
+ .min(1)
730
+ .nullable()
731
+ .optional()
732
+ .describe("Public-exposure digest stored by the route config write; null means the incoming projection was unreadable."),
733
+ previous_public_exposure: runtimePublicExposureDescriptorSchema
734
+ .nullable()
735
+ .optional()
736
+ .describe("Public-exposure descriptor stored before a route config write. Null means the previous projection was unreadable."),
737
+ public_exposure: runtimePublicExposureDescriptorSchema
738
+ .nullable()
739
+ .optional()
740
+ .describe("Public-exposure descriptor stored by the route config write. Null means the incoming projection was unreadable."),
741
+ });
742
+ const runtimeEventDeliveryIdentitySchema = z.object({
743
+ event_id: z.string().min(1),
744
+ delivery_id: z.string().min(1),
745
+ });
746
+ const runtimeEventDrainSessionIdSchema = z.string().min(1).max(128);
747
+ export const runtimeEventsDrainRequestSchema = z
748
+ .object({
749
+ session_id: runtimeEventDrainSessionIdSchema,
750
+ page_id: runtimeEventDrainSessionIdSchema.optional(),
751
+ target_event_id: z.string().min(1).max(128).optional(),
752
+ exclude_deliveries: z
753
+ .array(runtimeEventDeliveryIdentitySchema)
754
+ .max(100)
755
+ .optional()
756
+ .describe("Failed deliveries from the immediately preceding page."),
757
+ finish_session: z.boolean().optional(),
758
+ })
759
+ .superRefine((value, context) => {
760
+ if (value.finish_session === true) {
761
+ if (value.page_id || value.target_event_id || value.exclude_deliveries) {
762
+ context.addIssue({
763
+ code: "custom",
764
+ message: "A finished drain session cannot request a page.",
765
+ });
766
+ }
767
+ return;
768
+ }
769
+ if (!value.page_id)
770
+ context.addIssue({ code: "custom", message: "A drain page requires page_id." });
685
771
  });
686
772
  export const runtimeEventsDrainResponseSchema = z.object({
687
773
  delivered_count: z.number().int().nonnegative(),
@@ -693,16 +779,33 @@ export const runtimeEventsDrainResponseSchema = z.object({
693
779
  .optional()
694
780
  .describe("Undeliverable callbacks dropped after exceeding the attempt cap."),
695
781
  returned_count: z.number().int().nonnegative().optional(),
696
- // Pull fallback: events whose push origin was unreachable are handed back to
697
- // the drain caller (the control plane) instead of rotting in the journal.
698
- // Processing replays are no-ops on (event_id, hostname).
782
+ pending_count: z
783
+ .number()
784
+ .int()
785
+ .nonnegative()
786
+ .optional()
787
+ .describe("Journal entries not returned because of the response cap, another active lease, or this session's failed-delivery deferral. Non-zero means a later page or repair session still has work."),
788
+ // The drain is pull-first: the caller IS the control plane and is blocked on
789
+ // this response, so the engine hands its journal back here instead of posting
790
+ // each event over a separate HTTP round trip. Processing replays are no-ops
791
+ // on (event_id, hostname).
699
792
  events: z
700
793
  .array(z.object({
794
+ delivery_id: z.string().min(1),
701
795
  operation_id: z.string().optional(),
702
796
  event: runtimeCallbackEventSchema,
703
797
  }))
704
798
  .optional(),
705
799
  });
800
+ export const runtimeEventsAckRequestSchema = z.object({
801
+ session_id: runtimeEventDrainSessionIdSchema,
802
+ deliveries: z.array(runtimeEventDeliveryIdentitySchema).max(100),
803
+ });
804
+ export const runtimeEventsAckResponseSchema = z.object({
805
+ acknowledged_count: z.number().int().nonnegative(),
806
+ idempotent_count: z.number().int().nonnegative(),
807
+ stale_count: z.number().int().nonnegative(),
808
+ });
706
809
  // Engine job runner (plan §22, Placement V2): the box-local job runner behind
707
810
  // the two-lane tick. The control plane creates a job, drives it with repeated
708
811
  // ticks (kick-on-enqueue; the site-cron watchdog ticks the same route), and
@@ -751,19 +854,3 @@ export const runtimeEngineJobTickResponseSchema = z.object({
751
854
  job: runtimeEngineJobRecordSchema.nullable().optional(),
752
855
  drained: z.boolean().optional(),
753
856
  });
754
- // The management pull action for the Views panel (access-plan §5.6b, X-37):
755
- // `GET /access-events?file&offset&limit` — a cursor-resumed read of the
756
- // runtime's own per-hostname NDJSON access-event journal. `cursor` is the
757
- // position after the last returned event (feed it back verbatim); `done` is
758
- // false only when the response was truncated by `limit`; `dropped` totals the
759
- // writer's `.dropped` sidecars (events skipped past the per-file byte cap).
760
- export const runtimeAccessEventsCursorSchema = z.object({
761
- file: z.string().min(1).nullable(),
762
- offset: z.number().int().nonnegative(),
763
- });
764
- export const runtimeAccessEventsResponseSchema = z.object({
765
- events: z.array(accessEventSchema).default([]),
766
- cursor: runtimeAccessEventsCursorSchema,
767
- done: z.boolean(),
768
- dropped: z.number().int().nonnegative().default(0),
769
- });
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The one answer to "what is running on this space".
4
+ *
5
+ * A space is exactly one runtime kind — `versionRuntimeKind()` returns a single
6
+ * value and a publish carrying both declarations is refused at finalize — so
7
+ * this is a discriminated union rather than a bag of optional per-product
8
+ * fields. Anything that is not an execution tier has no member here and reads
9
+ * as `null`, which is the overwhelmingly common answer: a static space is not a
10
+ * degenerate app, it is the absence of one.
11
+ *
12
+ * It lives in its own module because it is the only place that needs to know
13
+ * about both products at once. `spaces.ts` imports it to describe runtime
14
+ * status; neither `zero.ts` nor `functions.ts` has to learn about the other.
15
+ */
16
+ export declare const runtimeAppSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
17
+ runtimeKind: z.ZodLiteral<"zero">;
18
+ artifactId: z.ZodString;
19
+ appName: z.ZodString;
20
+ serverRuntime: z.ZodLiteral<"quickjs-rust">;
21
+ inspect: z.ZodObject<{
22
+ policy: z.ZodEnum<{
23
+ public: "public";
24
+ private: "private";
25
+ }>;
26
+ }, z.core.$strict>;
27
+ install: z.ZodObject<{
28
+ runtimeKind: z.ZodLiteral<"zero">;
29
+ engine: z.ZodLiteral<"quickjs-rust">;
30
+ storageRoot: z.ZodLiteral<"__spacefast/zero">;
31
+ artifactPath: z.ZodLiteral<"__spacefast/zero/artifact.json">;
32
+ configPath: z.ZodLiteral<"__spacefast/zero/config.json">;
33
+ binary: z.ZodObject<{
34
+ path: z.ZodLiteral<"__spacefast/zero/bin/stattic-runtime">;
35
+ compatibilityTarget: z.ZodObject<{
36
+ profile: z.ZodLiteral<"wpcloud-linux-x86_64-gnu-v1">;
37
+ os: z.ZodLiteral<"linux">;
38
+ cpu: z.ZodLiteral<"x86_64">;
39
+ libc: z.ZodLiteral<"glibc">;
40
+ linking: z.ZodLiteral<"dynamic">;
41
+ requireExecutableBit: z.ZodLiteral<true>;
42
+ }, z.core.$strict>;
43
+ sha256: z.ZodDefault<z.ZodNullable<z.ZodString>>;
44
+ required: z.ZodLiteral<true>;
45
+ }, z.core.$strict>;
46
+ mysql: z.ZodObject<{
47
+ migrateAtFinalize: z.ZodLiteral<true>;
48
+ blockIncompatibleRollback: z.ZodLiteral<true>;
49
+ }, z.core.$strict>;
50
+ realtime: z.ZodObject<{
51
+ production: z.ZodLiteral<"stattic-central">;
52
+ local: z.ZodLiteral<"foreground-cli-ws">;
53
+ }, z.core.$strict>;
54
+ }, z.core.$strict>;
55
+ tables: z.ZodDefault<z.ZodArray<z.ZodString>>;
56
+ queries: z.ZodDefault<z.ZodArray<z.ZodString>>;
57
+ mutations: z.ZodDefault<z.ZodArray<z.ZodString>>;
58
+ endpoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
59
+ method: z.ZodEnum<{
60
+ POST: "POST";
61
+ PUT: "PUT";
62
+ GET: "GET";
63
+ HEAD: "HEAD";
64
+ PATCH: "PATCH";
65
+ DELETE: "DELETE";
66
+ OPTIONS: "OPTIONS";
67
+ }>;
68
+ path: z.ZodString;
69
+ }, z.core.$strict>>>;
70
+ sockets: z.ZodDefault<z.ZodArray<z.ZodString>>;
71
+ variables: z.ZodObject<{
72
+ source: z.ZodLiteral<"stattic-variables">;
73
+ names: z.ZodDefault<z.ZodArray<z.ZodString>>;
74
+ }, z.core.$strict>;
75
+ realtime: z.ZodObject<{
76
+ mode: z.ZodEnum<{
77
+ "stattic-central": "stattic-central";
78
+ "spacefast-cast": "spacefast-cast";
79
+ "local-dev": "local-dev";
80
+ "local-websocket": "local-websocket";
81
+ }>;
82
+ centralUrl: z.ZodDefault<z.ZodNullable<z.ZodString>>;
83
+ path: z.ZodOptional<z.ZodString>;
84
+ replayPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
85
+ replayUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
86
+ runPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
+ runUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
88
+ resourceKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
89
+ socketPath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
90
+ spaceId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
91
+ versionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
92
+ }, z.core.$strict>;
93
+ }, z.core.$strict>, z.ZodObject<{
94
+ runtimeKind: z.ZodLiteral<"functions">;
95
+ artifactId: z.ZodString;
96
+ appName: z.ZodString;
97
+ entry: z.ZodString;
98
+ compatibilityDate: z.ZodString;
99
+ compatibilityFlags: z.ZodDefault<z.ZodArray<z.ZodString>>;
100
+ routes: z.ZodDefault<z.ZodArray<z.ZodObject<{
101
+ method: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
102
+ POST: "POST";
103
+ PUT: "PUT";
104
+ GET: "GET";
105
+ HEAD: "HEAD";
106
+ PATCH: "PATCH";
107
+ DELETE: "DELETE";
108
+ OPTIONS: "OPTIONS";
109
+ }>>>;
110
+ path: z.ZodString;
111
+ subtree: z.ZodDefault<z.ZodBoolean>;
112
+ }, z.core.$strict>>>;
113
+ capabilities: z.ZodDefault<z.ZodObject<{
114
+ db: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
115
+ fetch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
116
+ auth: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
117
+ env: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
118
+ realtime: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
119
+ logging: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
120
+ }, z.core.$strip>>;
121
+ executionState: z.ZodEnum<{
122
+ ready: "ready";
123
+ absent: "absent";
124
+ serving: "serving";
125
+ }>;
126
+ bundleSize: z.ZodNumber;
127
+ variables: z.ZodObject<{
128
+ source: z.ZodLiteral<"spacefast-variables">;
129
+ names: z.ZodDefault<z.ZodArray<z.ZodString>>;
130
+ }, z.core.$strict>;
131
+ inspect: z.ZodObject<{
132
+ policy: z.ZodEnum<{
133
+ public: "public";
134
+ private: "private";
135
+ }>;
136
+ }, z.core.$strict>;
137
+ }, z.core.$strict>], "runtimeKind">;
138
+ export type RuntimeApp = z.infer<typeof runtimeAppSchema>;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { functionsAppSchema } from "./functions.js";
3
+ import { zeroAppSchema } from "./zero.js";
4
+ /**
5
+ * The one answer to "what is running on this space".
6
+ *
7
+ * A space is exactly one runtime kind — `versionRuntimeKind()` returns a single
8
+ * value and a publish carrying both declarations is refused at finalize — so
9
+ * this is a discriminated union rather than a bag of optional per-product
10
+ * fields. Anything that is not an execution tier has no member here and reads
11
+ * as `null`, which is the overwhelmingly common answer: a static space is not a
12
+ * degenerate app, it is the absence of one.
13
+ *
14
+ * It lives in its own module because it is the only place that needs to know
15
+ * about both products at once. `spaces.ts` imports it to describe runtime
16
+ * status; neither `zero.ts` nor `functions.ts` has to learn about the other.
17
+ */
18
+ export const runtimeAppSchema = z.discriminatedUnion("runtimeKind", [
19
+ zeroAppSchema,
20
+ functionsAppSchema,
21
+ ]);
@@ -1,3 +1,4 @@
1
+ import type { Grant } from "./grants.js";
1
2
  export declare const SF_CONFIG_V1_CANONICAL_FILE = "sf.jsonc";
2
3
  export declare const SF_CONFIG_V1_FILES: readonly ["sf.jsonc", "spacefast.jsonc", "spacefast.json", "sf.json", ".sf/sf.json", ".sf/config.jsonc", ".sf/config.json"];
3
4
  /** Published JSON Schema URL for the strict `sf.jsonc` v1 contract. */
@@ -26,6 +27,9 @@ export type SfConfigV1 = {
26
27
  substitute?: {
27
28
  files: string[];
28
29
  };
30
+ access?: {
31
+ public: string[];
32
+ };
29
33
  };
30
34
  export type EffectiveConfigV1 = {
31
35
  version: 1;
@@ -41,6 +45,7 @@ export type EffectiveConfigV1 = {
41
45
  };
42
46
  metadata?: SfConfigV1["metadata"];
43
47
  substitute?: SfConfigV1["substitute"];
48
+ grants?: Grant[];
44
49
  };
45
50
  export type SfConfigV1Issue = {
46
51
  code: "config_alias" | "config_conflict" | "config_identity_moved" | "config_invalid" | "config_key_removed" | "config_unknown_key" | "serve_index_required" | "template_syntax_retired";
@@ -407,4 +407,9 @@ export declare const SiteRebalanceResultSchema: z.ZodObject<{
407
407
  spaceId: z.ZodString;
408
408
  operationId: z.ZodString;
409
409
  }, z.core.$strip>>;
410
+ warning: z.ZodString;
411
+ domainsRequiringReverification: z.ZodArray<z.ZodObject<{
412
+ spaceId: z.ZodString;
413
+ hostname: z.ZodString;
414
+ }, z.core.$strip>>;
410
415
  }, z.core.$strip>;
@@ -135,7 +135,7 @@ export const siteRebalanceRequestSchema = z
135
135
  .optional()
136
136
  .describe("Limit the rebalance to these space ids; omitted means every live space on the site."),
137
137
  })
138
- .describe("Operator-only rebalance trigger: move spaces off this site.");
138
+ .describe("Operator-only rebalance trigger: move spaces off this site. This is the ONLY path that relocates an existing Space, and it is not free — see the reverification warning on the result.");
139
139
  export const SiteRebalanceResultSchema = z
140
140
  .object({
141
141
  siteId: z.string().describe("The site spaces are being moved off."),
@@ -145,5 +145,16 @@ export const SiteRebalanceResultSchema = z
145
145
  operationId: z.string().describe("The queued `space_move` operation id (`op_...`)."),
146
146
  }))
147
147
  .describe("One queued move operation per affected space."),
148
+ warning: z
149
+ .string()
150
+ .describe("Consequence of relocating these Spaces: each hostname is handed to another provider site, which re-runs the provider's domain-exclusivity check and is rejected unless the domain's ownership TXT record is still live in DNS."),
151
+ domainsRequiringReverification: z
152
+ .array(z.object({
153
+ spaceId: z.string().describe("Space the hostname serves."),
154
+ hostname: z
155
+ .string()
156
+ .describe("Custom hostname whose provider ownership check re-runs during the move."),
157
+ }))
158
+ .describe("Live custom hostnames on the moved Spaces. Empty means nothing customer-visible is at risk."),
148
159
  })
149
160
  .describe("Queued rebalance moves; track each operation for completion.");