@spacefast/common 0.0.11 → 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 (160) hide show
  1. package/dist/agents/connect-targets.d.ts +4 -4
  2. package/dist/agents/connect-targets.js +3 -3
  3. package/dist/agents/private-key-oauth.d.ts +37 -0
  4. package/dist/agents/private-key-oauth.js +158 -0
  5. package/dist/config/domains.js +1 -1
  6. package/dist/config/index.d.ts +1 -0
  7. package/dist/config/index.js +1 -0
  8. package/dist/config/postgres-budget.d.ts +126 -0
  9. package/dist/config/postgres-budget.js +135 -0
  10. package/dist/config/postgres-budget.json +50 -0
  11. package/dist/contracts/access.d.ts +386 -1183
  12. package/dist/contracts/access.js +240 -1018
  13. package/dist/contracts/activity.d.ts +1 -1
  14. package/dist/contracts/activity.js +11 -19
  15. package/dist/contracts/api-keys.d.ts +69 -45
  16. package/dist/contracts/api-keys.js +3 -0
  17. package/dist/contracts/archives.d.ts +9 -10
  18. package/dist/contracts/archives.js +4 -6
  19. package/dist/contracts/builds.d.ts +8 -8
  20. package/dist/contracts/builds.js +28 -48
  21. package/dist/contracts/channels.js +1 -2
  22. package/dist/contracts/comments.d.ts +102 -93
  23. package/dist/contracts/comments.js +121 -63
  24. package/dist/contracts/common.d.ts +4 -3
  25. package/dist/contracts/common.js +2 -0
  26. package/dist/contracts/countries.js +2 -7
  27. package/dist/contracts/data-source-routes.d.ts +20 -0
  28. package/dist/contracts/data-source-routes.js +15 -0
  29. package/dist/contracts/deployments.d.ts +3 -3
  30. package/dist/contracts/device-auth.d.ts +21 -15
  31. package/dist/contracts/docs.d.ts +5 -5
  32. package/dist/contracts/domains.d.ts +261 -1330
  33. package/dist/contracts/domains.js +39 -301
  34. package/dist/contracts/email-preferences.d.ts +50 -0
  35. package/dist/contracts/email-preferences.js +44 -0
  36. package/dist/contracts/enums.d.ts +3 -5
  37. package/dist/contracts/enums.js +3 -8
  38. package/dist/contracts/error-code-meta.d.ts +99 -33
  39. package/dist/contracts/error-code-meta.js +36 -9
  40. package/dist/contracts/error-codes.d.ts +1 -1
  41. package/dist/contracts/error-codes.js +28 -11
  42. package/dist/contracts/execution.d.ts +212 -0
  43. package/dist/contracts/execution.js +288 -0
  44. package/dist/contracts/feature-lifecycle.d.ts +498 -2
  45. package/dist/contracts/feature-lifecycle.js +539 -2
  46. package/dist/contracts/features.d.ts +184 -74
  47. package/dist/contracts/features.js +4 -303
  48. package/dist/contracts/functions.d.ts +729 -0
  49. package/dist/contracts/functions.js +485 -0
  50. package/dist/contracts/git.d.ts +1 -1
  51. package/dist/contracts/grants.d.ts +1158 -0
  52. package/dist/contracts/grants.js +696 -0
  53. package/dist/contracts/ids.d.ts +6 -5
  54. package/dist/contracts/ids.js +6 -5
  55. package/dist/contracts/internal.d.ts +3 -3
  56. package/dist/contracts/mcp.d.ts +12 -12
  57. package/dist/contracts/me.js +1 -1
  58. package/dist/contracts/mounts.d.ts +142 -0
  59. package/dist/contracts/mounts.js +56 -0
  60. package/dist/contracts/oauth-resources.d.ts +5 -5
  61. package/dist/contracts/oauth-resources.js +2 -0
  62. package/dist/contracts/operations.d.ts +4 -4
  63. package/dist/contracts/pages.d.ts +6 -14
  64. package/dist/contracts/pages.js +11 -23
  65. package/dist/contracts/plan-policy.d.ts +2 -9
  66. package/dist/contracts/plan-policy.js +1 -4
  67. package/dist/contracts/platform.d.ts +80 -32
  68. package/dist/contracts/platform.js +59 -12
  69. package/dist/contracts/push-new.d.ts +26 -2
  70. package/dist/contracts/push-new.js +8 -0
  71. package/dist/contracts/quotas.d.ts +0 -1
  72. package/dist/contracts/quotas.js +0 -5
  73. package/dist/contracts/repository-connections.d.ts +1 -1
  74. package/dist/contracts/resources.d.ts +21 -85
  75. package/dist/contracts/resources.js +17 -47
  76. package/dist/contracts/runtime-api.d.ts +799 -192
  77. package/dist/contracts/runtime-api.js +231 -100
  78. package/dist/contracts/runtime-app.d.ts +138 -0
  79. package/dist/contracts/runtime-app.js +21 -0
  80. package/dist/contracts/sf-config-v1.d.ts +5 -0
  81. package/dist/contracts/sf-config-v1.js +1 -1
  82. package/dist/contracts/sites.d.ts +17 -12
  83. package/dist/contracts/sites.js +30 -12
  84. package/dist/contracts/space-config.d.ts +46 -117
  85. package/dist/contracts/space-config.js +323 -25
  86. package/dist/contracts/spaces.d.ts +392 -256
  87. package/dist/contracts/spaces.js +163 -83
  88. package/dist/contracts/superadmin-emails.d.ts +27 -3
  89. package/dist/contracts/superadmin-emails.js +10 -15
  90. package/dist/contracts/superadmin-runtime.d.ts +2 -2
  91. package/dist/contracts/superadmin-spaces.d.ts +19 -142
  92. package/dist/contracts/superadmin-spaces.js +7 -0
  93. package/dist/contracts/tags.d.ts +66 -36
  94. package/dist/contracts/tags.js +195 -11
  95. package/dist/contracts/teams.d.ts +9 -9
  96. package/dist/contracts/transfers.d.ts +1 -2
  97. package/dist/contracts/variables.d.ts +4 -4
  98. package/dist/contracts/variables.js +4 -3
  99. package/dist/contracts/webhooks.d.ts +2 -2
  100. package/dist/contracts/zero.d.ts +437 -193
  101. package/dist/contracts/zero.js +149 -158
  102. package/dist/dashboard-paths/index.d.ts +3 -3
  103. package/dist/dashboard-paths/index.js +1 -0
  104. package/dist/docs/agent-prose.js +10 -8
  105. package/dist/docs/agent-setup.d.ts +6 -4
  106. package/dist/docs/agent-setup.js +26 -16
  107. package/dist/docs/agent-solutions.js +1 -1
  108. package/dist/docs/catalog.d.ts +35 -37
  109. package/dist/docs/catalog.js +36 -38
  110. package/dist/docs/error-docs.js +108 -44
  111. package/dist/utils/asset-fingerprint.d.ts +11 -0
  112. package/dist/utils/asset-fingerprint.js +46 -0
  113. package/dist/utils/auth-redirect.d.ts +1 -3
  114. package/dist/utils/auth-redirect.js +1 -21
  115. package/dist/utils/build-settings.d.ts +2 -0
  116. package/dist/utils/build-settings.js +61 -9
  117. package/dist/utils/credential-policy.d.ts +1 -1
  118. package/dist/utils/credential-policy.js +4 -0
  119. package/dist/utils/dns-instructions.d.ts +29 -0
  120. package/dist/utils/dns-instructions.js +78 -0
  121. package/dist/utils/domain-diagnostics.d.ts +29 -0
  122. package/dist/utils/domain-diagnostics.js +63 -0
  123. package/dist/utils/egress-policy.fixtures.json +51 -0
  124. package/dist/utils/grants.d.ts +53 -0
  125. package/dist/utils/grants.js +160 -0
  126. package/dist/utils/id-hints.d.ts +1 -0
  127. package/dist/utils/id-hints.js +55 -13
  128. package/dist/utils/local-space-state.js +1 -0
  129. package/dist/utils/page-colors.d.ts +50 -0
  130. package/dist/utils/page-colors.js +167 -0
  131. package/dist/utils/page-fonts.d.ts +2 -0
  132. package/dist/utils/page-fonts.js +4 -0
  133. package/dist/utils/pages.d.ts +19 -6
  134. package/dist/utils/pages.js +122 -49
  135. package/dist/utils/production-runtime.d.ts +18 -0
  136. package/dist/utils/production-runtime.js +28 -0
  137. package/dist/utils/publish-policy.d.ts +34 -4
  138. package/dist/utils/publish-policy.js +132 -61
  139. package/dist/utils/query-keys.d.ts +7 -13
  140. package/dist/utils/query-keys.js +12 -18
  141. package/dist/utils/source-archive-policy.d.ts +2 -0
  142. package/dist/utils/source-archive-policy.js +2 -0
  143. package/dist/utils/space-config.d.ts +5 -1
  144. package/dist/utils/space-config.js +35 -0
  145. package/dist/utils/static-runtime-policy.d.ts +5 -0
  146. package/dist/utils/static-runtime-policy.fixtures.json +57 -0
  147. package/dist/utils/static-runtime-policy.js +54 -10
  148. package/dist/utils/upload-session.d.ts +3 -0
  149. package/dist/utils/upload-session.js +31 -0
  150. package/package.json +6 -4
  151. package/dist/contracts/annotations.d.ts +0 -327
  152. package/dist/contracts/annotations.js +0 -235
  153. package/dist/contracts/generated-feature-launch-entries.d.ts +0 -2
  154. package/dist/contracts/generated-feature-launch-entries.js +0 -518
  155. package/dist/contracts/publishes.d.ts +0 -135
  156. package/dist/contracts/publishes.js +0 -162
  157. package/dist/utils/access-match.d.ts +0 -39
  158. package/dist/utils/access-match.js +0 -296
  159. package/dist/utils/sf-config-v1.d.ts +0 -2
  160. package/dist/utils/sf-config-v1.js +0 -542
@@ -0,0 +1,696 @@
1
+ import { z } from "zod";
2
+ import { ISO_COUNTRY_CODES } from "./countries.js";
3
+ import { registeredIdSchema } from "./ids.js";
4
+ export const GRANT_CAPABILITIES = [
5
+ "page.view",
6
+ "comments.read",
7
+ "comments.write",
8
+ "content.publish",
9
+ "access.manage",
10
+ ];
11
+ export const grantCapabilitySchema = z.enum(GRANT_CAPABILITIES);
12
+ export const GRANT_CAPABILITY_PRESETS = ["view", "comment", "publish", "manage"];
13
+ export const GRANT_PRESET_CAPABILITIES = {
14
+ view: ["page.view"],
15
+ comment: ["page.view", "comments.read", "comments.write"],
16
+ publish: ["page.view", "content.publish"],
17
+ manage: ["page.view", "comments.read", "comments.write", "content.publish", "access.manage"],
18
+ };
19
+ export const GRANT_AUDIENCE_KINDS = [
20
+ "public",
21
+ "team",
22
+ "person",
23
+ "link",
24
+ "password",
25
+ "machine",
26
+ "external",
27
+ ];
28
+ export const grantAudienceKindSchema = z.enum(GRANT_AUDIENCE_KINDS);
29
+ const grantReferenceSchema = z.string().trim().min(1).max(255);
30
+ export const grantAudienceSchema = z.discriminatedUnion("kind", [
31
+ z.object({ kind: z.literal("public") }).strict(),
32
+ z.object({ kind: z.literal("team"), teamId: grantReferenceSchema }).strict(),
33
+ z.object({ kind: z.literal("person"), personId: grantReferenceSchema }).strict(),
34
+ z.object({ kind: z.literal("link"), linkId: grantReferenceSchema }).strict(),
35
+ z.object({ kind: z.literal("password"), credentialId: grantReferenceSchema }).strict(),
36
+ z.object({ kind: z.literal("machine"), machineId: grantReferenceSchema }).strict(),
37
+ z
38
+ .object({
39
+ kind: z.literal("external"),
40
+ issuer: grantReferenceSchema,
41
+ subject: grantReferenceSchema,
42
+ })
43
+ .strict(),
44
+ ]);
45
+ function hasUnsafePatternCharacter(value) {
46
+ for (const character of value) {
47
+ const codePoint = character.codePointAt(0);
48
+ if (codePoint === undefined ||
49
+ codePoint <= 0x1f ||
50
+ codePoint === 0x7f ||
51
+ character === "\\" ||
52
+ character === "?" ||
53
+ character === "#") {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ /**
60
+ * Canonical resource-pattern syntax shared by config compilation and evaluation.
61
+ * `*` matches one path segment and `**` matches zero or more segments. Wildcards
62
+ * are whole segments so a pattern can never disguise an encoded separator.
63
+ *
64
+ * Unlike URL-route canonicalization, resource patterns retain a terminal
65
+ * `index.html`: Grants can constrain the visitor-facing route and the artifact
66
+ * it resolves to independently.
67
+ */
68
+ export function normalizeGrantResourcePattern(input) {
69
+ if (!input.startsWith("/") || input.startsWith("//") || hasUnsafePatternCharacter(input)) {
70
+ throw new TypeError("resource pattern must be a plain absolute URL path pattern");
71
+ }
72
+ if (/%2f|%5c|%2a/iu.test(input)) {
73
+ throw new TypeError("resource pattern contains an ambiguous encoded separator or wildcard");
74
+ }
75
+ let decoded;
76
+ try {
77
+ decoded = decodeURIComponent(input);
78
+ }
79
+ catch {
80
+ throw new TypeError("resource pattern contains malformed percent encoding");
81
+ }
82
+ if (hasUnsafePatternCharacter(decoded)) {
83
+ throw new TypeError("resource pattern contains an unsafe character");
84
+ }
85
+ const segments = [];
86
+ for (const rawSegment of decoded.split("/")) {
87
+ const segment = rawSegment.normalize("NFC");
88
+ if (segment === "" || segment === ".")
89
+ continue;
90
+ if (segment === "..") {
91
+ if (segments.length === 0)
92
+ throw new TypeError("resource pattern escapes the URL root");
93
+ segments.pop();
94
+ continue;
95
+ }
96
+ if (segment.includes("*") && segment !== "*" && segment !== "**") {
97
+ throw new TypeError("resource wildcards must occupy a whole path segment");
98
+ }
99
+ segments.push(segment);
100
+ }
101
+ return segments.length === 0 ? "/" : `/${segments.join("/")}`;
102
+ }
103
+ export const grantResourcePatternSchema = z
104
+ .string()
105
+ .min(1)
106
+ .max(1024)
107
+ .transform((value, context) => {
108
+ try {
109
+ return normalizeGrantResourcePattern(value);
110
+ }
111
+ catch (error) {
112
+ context.addIssue({
113
+ code: "custom",
114
+ message: error instanceof Error ? error.message : "invalid resource pattern",
115
+ });
116
+ return z.NEVER;
117
+ }
118
+ });
119
+ function uniqueArray(values) {
120
+ return new Set(values).size === values.length;
121
+ }
122
+ export const GRANT_RESOURCE_PATTERN_LIMIT = 100;
123
+ export const grantResourceSetSchema = z
124
+ .object({
125
+ include: z.array(grantResourcePatternSchema).min(1).max(GRANT_RESOURCE_PATTERN_LIMIT),
126
+ exclude: z.array(grantResourcePatternSchema).max(GRANT_RESOURCE_PATTERN_LIMIT).default([]),
127
+ })
128
+ .strict()
129
+ .superRefine((value, context) => {
130
+ if (value.include.length + value.exclude.length > GRANT_RESOURCE_PATTERN_LIMIT) {
131
+ context.addIssue({
132
+ code: "custom",
133
+ message: `resource sets support up to ${GRANT_RESOURCE_PATTERN_LIMIT} patterns`,
134
+ });
135
+ }
136
+ if (!uniqueArray(value.include)) {
137
+ context.addIssue({
138
+ code: "custom",
139
+ path: ["include"],
140
+ message: "include patterns must be unique",
141
+ });
142
+ }
143
+ if (!uniqueArray(value.exclude)) {
144
+ context.addIssue({
145
+ code: "custom",
146
+ path: ["exclude"],
147
+ message: "exclude patterns must be unique",
148
+ });
149
+ }
150
+ });
151
+ export const GRANT_TARGET_KINDS = ["live", "version", "branch", "all_versions"];
152
+ export const grantTargetKindSchema = z.enum(GRANT_TARGET_KINDS);
153
+ export const grantTargetSelectorSchema = z.discriminatedUnion("kind", [
154
+ z.object({ kind: z.literal("live") }).strict(),
155
+ z.object({ kind: z.literal("version"), versionId: grantReferenceSchema }).strict(),
156
+ z.object({ kind: z.literal("branch"), branch: grantReferenceSchema }).strict(),
157
+ z.object({ kind: z.literal("all_versions") }).strict(),
158
+ ]);
159
+ function parseIpv4(input) {
160
+ const parts = input.split(".");
161
+ if (parts.length !== 4)
162
+ return null;
163
+ let bits = 0n;
164
+ for (const part of parts) {
165
+ if (!/^(?:0|[1-9][0-9]{0,2})$/u.test(part))
166
+ return null;
167
+ const value = Number(part);
168
+ if (value > 255)
169
+ return null;
170
+ bits = (bits << 8n) | BigInt(value);
171
+ }
172
+ return { bits, width: 32 };
173
+ }
174
+ function parseIpv6(input) {
175
+ if (input === "" || input.includes("%") || input.split("::").length > 2)
176
+ return null;
177
+ let value = input.toLowerCase();
178
+ const ipv4Tail = value.match(/(?:^|:)([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/u)?.[1];
179
+ if (ipv4Tail) {
180
+ const parsed = parseIpv4(ipv4Tail);
181
+ if (!parsed)
182
+ return null;
183
+ value = `${value.slice(0, -ipv4Tail.length)}${Number((parsed.bits >> 16n) & 0xffffn).toString(16)}:${Number(parsed.bits & 0xffffn).toString(16)}`;
184
+ }
185
+ const [leftText, rightText] = value.split("::");
186
+ const left = leftText ? leftText.split(":") : [];
187
+ const right = rightText ? rightText.split(":") : [];
188
+ if ([...left, ...right].some((part) => !/^[0-9a-f]{1,4}$/u.test(part)))
189
+ return null;
190
+ const compressed = value.includes("::");
191
+ const missing = 8 - left.length - right.length;
192
+ if ((!compressed && missing !== 0) || (compressed && missing < 1))
193
+ return null;
194
+ const parts = [...left, ...Array.from({ length: missing }, () => "0"), ...right];
195
+ if (parts.length !== 8)
196
+ return null;
197
+ return {
198
+ bits: parts.reduce((bits, part) => (bits << 16n) | BigInt(`0x${part}`), 0n),
199
+ width: 128,
200
+ };
201
+ }
202
+ function parseIpAddress(input) {
203
+ return parseIpv4(input) ?? parseIpv6(input);
204
+ }
205
+ export function normalizeGrantNetworkCidr(input) {
206
+ const value = input.trim().toLowerCase();
207
+ const separator = value.lastIndexOf("/");
208
+ const address = separator === -1 ? value : value.slice(0, separator);
209
+ const parsed = parseIpAddress(address);
210
+ if (!parsed)
211
+ throw new TypeError("network rules require a valid IPv4 or IPv6 address");
212
+ if (separator === -1)
213
+ return value;
214
+ const prefixText = value.slice(separator + 1);
215
+ if (!/^(?:0|[1-9][0-9]{0,2})$/u.test(prefixText)) {
216
+ throw new TypeError("network rules require a valid CIDR prefix");
217
+ }
218
+ const prefix = Number(prefixText);
219
+ if (prefix > parsed.width)
220
+ throw new TypeError("network CIDR prefix exceeds its address width");
221
+ return `${address}/${prefix}`;
222
+ }
223
+ export function grantNetworkCidrMatches(cidr, ipAddress) {
224
+ let canonical;
225
+ try {
226
+ canonical = normalizeGrantNetworkCidr(cidr);
227
+ }
228
+ catch {
229
+ return false;
230
+ }
231
+ const separator = canonical.lastIndexOf("/");
232
+ const networkText = separator === -1 ? canonical : canonical.slice(0, separator);
233
+ const network = parseIpAddress(networkText);
234
+ const address = parseIpAddress(ipAddress.trim().toLowerCase());
235
+ if (!network || !address || network.width !== address.width)
236
+ return false;
237
+ const prefix = separator === -1 ? network.width : Number(canonical.slice(separator + 1));
238
+ if (prefix === 0)
239
+ return true;
240
+ const shift = BigInt(network.width - prefix);
241
+ return network.bits >> shift === address.bits >> shift;
242
+ }
243
+ const grantNetworkCidrSchema = z
244
+ .string()
245
+ .min(1)
246
+ .max(64)
247
+ .transform((value, context) => {
248
+ try {
249
+ return normalizeGrantNetworkCidr(value);
250
+ }
251
+ catch (error) {
252
+ context.addIssue({
253
+ code: "custom",
254
+ message: error instanceof Error ? error.message : "invalid network rule",
255
+ });
256
+ return z.NEVER;
257
+ }
258
+ });
259
+ const grantCountrySchema = z
260
+ .string()
261
+ .length(2)
262
+ .transform((country) => country.toUpperCase())
263
+ .refine((country) => ISO_COUNTRY_CODES.includes(country), "Country must be an ISO 3166-1 alpha-2 code.");
264
+ export const grantConstraintsSchema = z
265
+ .object({
266
+ notBefore: z.string().datetime().optional(),
267
+ expiresAt: z.string().datetime().optional(),
268
+ maxUses: z.number().int().positive().max(1_000_000).optional(),
269
+ requireVerifiedEmail: z.boolean().optional(),
270
+ network: z
271
+ .object({
272
+ ipCidrs: z.array(grantNetworkCidrSchema).min(1).max(50).optional(),
273
+ countries: z.array(grantCountrySchema).min(1).max(250).optional(),
274
+ excludedCountries: z.array(grantCountrySchema).min(1).max(250).optional(),
275
+ excludedUserAgentSubstrings: z
276
+ .array(z.string().trim().min(1).max(200))
277
+ .min(1)
278
+ .max(50)
279
+ .optional(),
280
+ })
281
+ .strict()
282
+ .superRefine((network, context) => {
283
+ if (network.ipCidrs === undefined &&
284
+ network.countries === undefined &&
285
+ network.excludedCountries === undefined &&
286
+ network.excludedUserAgentSubstrings === undefined) {
287
+ context.addIssue({
288
+ code: "custom",
289
+ message: "network requires at least one IP, country, or user-agent selector",
290
+ });
291
+ }
292
+ })
293
+ .optional(),
294
+ })
295
+ .strict()
296
+ .superRefine((value, context) => {
297
+ if (value.notBefore !== undefined &&
298
+ value.expiresAt !== undefined &&
299
+ Date.parse(value.notBefore) >= Date.parse(value.expiresAt)) {
300
+ context.addIssue({
301
+ code: "custom",
302
+ path: ["expiresAt"],
303
+ message: "expiresAt must be later than notBefore",
304
+ });
305
+ }
306
+ });
307
+ export const GRANT_SOURCE_KINDS = ["config", "managed", "system"];
308
+ export const grantSourceKindSchema = z.enum(GRANT_SOURCE_KINDS);
309
+ export const PRECLAIM_OPEN_GRANT_SOURCE_REFERENCE = "open:preclaim-author";
310
+ export const OWNING_TEAM_MANAGEMENT_GRANT_SOURCE_REFERENCE = "ownership:team";
311
+ export const OWNING_TEAM_VERSION_GRANT_SOURCE_REFERENCE = "ownership:team:versions";
312
+ export const OWNING_EXTERNAL_MANAGEMENT_GRANT_SOURCE_REFERENCE = "ownership:external";
313
+ export const OWNING_EXTERNAL_VERSION_GRANT_SOURCE_REFERENCE = "ownership:external:versions";
314
+ export const CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE = "sf.jsonc#$.access.public";
315
+ export const grantSourceSchema = z
316
+ .object({
317
+ kind: grantSourceKindSchema,
318
+ reference: grantReferenceSchema,
319
+ })
320
+ .strict();
321
+ const grantDimensionsSchema = z
322
+ .object({
323
+ name: z.string().trim().min(1).max(120).optional(),
324
+ audience: grantAudienceSchema,
325
+ resources: grantResourceSetSchema,
326
+ capabilities: z
327
+ .array(grantCapabilitySchema)
328
+ .min(1)
329
+ .max(GRANT_CAPABILITIES.length)
330
+ .refine(uniqueArray, "capabilities must be unique"),
331
+ constraints: grantConstraintsSchema.default({}),
332
+ target: grantTargetSelectorSchema,
333
+ })
334
+ .strict();
335
+ function validateGrantAudienceCapabilityBoundary(value, context) {
336
+ if (value.audience.kind === "public" &&
337
+ value.capabilities.some((capability) => capability === "content.publish" || capability === "access.manage")) {
338
+ context.addIssue({
339
+ code: "custom",
340
+ path: ["capabilities"],
341
+ message: "public audiences cannot receive content.publish or access.manage",
342
+ });
343
+ return;
344
+ }
345
+ if (value.audience.kind === "public" && value.target.kind === "branch") {
346
+ context.addIssue({
347
+ code: "custom",
348
+ path: ["target"],
349
+ message: "public audiences cannot target moving branches; use a Link or another explicit audience",
350
+ });
351
+ }
352
+ if (["link", "password"].includes(value.audience.kind) &&
353
+ value.capabilities.includes("access.manage")) {
354
+ context.addIssue({
355
+ code: "custom",
356
+ path: ["capabilities"],
357
+ message: `${value.audience.kind} audiences cannot receive access.manage`,
358
+ });
359
+ }
360
+ if (value.constraints.maxUses !== undefined &&
361
+ !["link", "password", "machine", "external"].includes(value.audience.kind)) {
362
+ context.addIssue({
363
+ code: "custom",
364
+ path: ["constraints", "maxUses"],
365
+ message: "maxUses is available only for Link, password, service-token, and external-identity credential redemptions",
366
+ });
367
+ }
368
+ if (value.constraints.requireVerifiedEmail === true &&
369
+ ["public", "machine"].includes(value.audience.kind)) {
370
+ context.addIssue({
371
+ code: "custom",
372
+ path: ["constraints", "requireVerifiedEmail"],
373
+ message: "requireVerifiedEmail is unavailable for public and machine audiences",
374
+ });
375
+ }
376
+ }
377
+ const grantObjectSchema = grantDimensionsSchema.extend({
378
+ id: z.string().trim().min(1).max(128),
379
+ source: grantSourceSchema,
380
+ });
381
+ function validateGrantCapabilityBoundary(value, context) {
382
+ validateGrantAudienceCapabilityBoundary(value, context);
383
+ if (value.audience.kind === "link" &&
384
+ value.capabilities.includes("content.publish") &&
385
+ (value.source.kind !== "system" ||
386
+ value.source.reference !== PRECLAIM_OPEN_GRANT_SOURCE_REFERENCE)) {
387
+ context.addIssue({
388
+ code: "custom",
389
+ path: ["capabilities"],
390
+ message: "link audiences can publish only through the system-issued pre-claim Open author Grant",
391
+ });
392
+ }
393
+ }
394
+ export const grantSchema = grantObjectSchema.superRefine(validateGrantCapabilityBoundary);
395
+ export const managedGrantWriteSchema = grantDimensionsSchema.superRefine((value, context) => {
396
+ validateGrantAudienceCapabilityBoundary(value, context);
397
+ if (value.audience.kind === "link" && value.capabilities.includes("content.publish")) {
398
+ context.addIssue({
399
+ code: "custom",
400
+ path: ["capabilities"],
401
+ message: "link audiences can publish only through the system-issued pre-claim Open author Grant",
402
+ });
403
+ }
404
+ });
405
+ export const directManagedGrantWriteSchema = managedGrantWriteSchema.superRefine((value, context) => {
406
+ if (!["public", "team", "person"].includes(value.audience.kind)) {
407
+ context.addIssue({
408
+ code: "custom",
409
+ path: ["audience", "kind"],
410
+ message: "direct managed Grants support only public, team, and person audiences; credential-backed audiences use their dedicated workflow",
411
+ });
412
+ }
413
+ });
414
+ export const grantSourceExplanationSchema = z
415
+ .object({
416
+ editable: z.boolean(),
417
+ label: z.string().min(1),
418
+ detail: z.string().min(1),
419
+ })
420
+ .strict();
421
+ export const spaceGrantSchema = z
422
+ .object({
423
+ grant: grantObjectSchema
424
+ .extend({ id: registeredIdSchema("grt") })
425
+ .superRefine(validateGrantCapabilityBoundary),
426
+ generation: z.number().int().positive(),
427
+ status: z.enum(["scheduled", "active", "expired"]),
428
+ sourceExplanation: grantSourceExplanationSchema,
429
+ createdAt: z.string().datetime(),
430
+ updatedAt: z.string().datetime(),
431
+ })
432
+ .strict();
433
+ export const grantCheckQuerySchema = z
434
+ .object({
435
+ path: z.string().min(1).max(2048).default("/"),
436
+ target: z.enum(["live", "version", "branch"]).default("live"),
437
+ targetId: z.string().trim().min(1).max(255).optional(),
438
+ audience: grantAudienceKindSchema.default("public"),
439
+ personId: z.string().trim().min(1).max(255).optional(),
440
+ linkId: z.string().trim().min(1).max(255).optional(),
441
+ passwordCredentialId: z.string().trim().min(1).max(255).optional(),
442
+ machineId: z.string().trim().min(1).max(255).optional(),
443
+ issuer: z.string().trim().min(1).max(255).optional(),
444
+ subject: z.string().trim().min(1).max(255).optional(),
445
+ verifiedEmail: z.enum(["true", "false"]).optional(),
446
+ ipAddress: z.string().trim().min(1).max(64).optional(),
447
+ })
448
+ .strict()
449
+ .superRefine((value, context) => {
450
+ if (value.target === "live" && value.targetId !== undefined) {
451
+ context.addIssue({
452
+ code: "custom",
453
+ path: ["targetId"],
454
+ message: "targetId is only valid for a version or branch target",
455
+ });
456
+ }
457
+ if (value.target !== "live" && value.targetId === undefined) {
458
+ context.addIssue({
459
+ code: "custom",
460
+ path: ["targetId"],
461
+ message: `targetId is required for a ${value.target} target`,
462
+ });
463
+ }
464
+ const audienceFields = [
465
+ ["person", "personId"],
466
+ ["link", "linkId"],
467
+ ["password", "passwordCredentialId"],
468
+ ["machine", "machineId"],
469
+ ["external", "issuer"],
470
+ ["external", "subject"],
471
+ ];
472
+ for (const [audience, field] of audienceFields) {
473
+ if (value.audience === audience && value[field] === undefined) {
474
+ context.addIssue({
475
+ code: "custom",
476
+ path: [field],
477
+ message: `${field} is required when simulating ${audience} access`,
478
+ });
479
+ }
480
+ if (value.audience !== audience && value[field] !== undefined) {
481
+ context.addIssue({
482
+ code: "custom",
483
+ path: [field],
484
+ message: `${field} is only valid when simulating ${audience} access`,
485
+ });
486
+ }
487
+ }
488
+ });
489
+ export const GRANT_CHECK_FAILURES = [
490
+ "space_unclaimed_config",
491
+ "audience",
492
+ "target",
493
+ "invalid_time",
494
+ "not_before",
495
+ "expired",
496
+ "max_uses",
497
+ "verified_email",
498
+ "network",
499
+ "resource_not_included",
500
+ "resource_excluded",
501
+ ];
502
+ export const grantCheckFailureSchema = z.enum(GRANT_CHECK_FAILURES);
503
+ export const grantCheckSchema = z
504
+ .object({
505
+ path: z.string().min(1),
506
+ target: z.discriminatedUnion("kind", [
507
+ z.object({ kind: z.literal("live") }).strict(),
508
+ z.object({ kind: z.literal("version"), versionId: z.string().min(1) }).strict(),
509
+ z.object({ kind: z.literal("branch"), branch: z.string().min(1) }).strict(),
510
+ ]),
511
+ simulatedAudience: grantAudienceSchema,
512
+ capabilities: z.array(grantCapabilitySchema),
513
+ matchingGrants: z.array(z
514
+ .object({
515
+ grant: spaceGrantSchema,
516
+ capabilities: z.array(grantCapabilitySchema).min(1),
517
+ })
518
+ .strict()),
519
+ evaluatedGrants: z.array(z
520
+ .object({
521
+ grant: spaceGrantSchema,
522
+ failures: z.array(grantCheckFailureSchema),
523
+ })
524
+ .strict()),
525
+ })
526
+ .strict();
527
+ const credentialGrantFields = {
528
+ name: z.string().trim().min(1).max(120),
529
+ resources: grantResourceSetSchema,
530
+ capabilities: z
531
+ .array(grantCapabilitySchema)
532
+ .min(1)
533
+ .max(GRANT_CAPABILITIES.length)
534
+ .refine(uniqueArray, "capabilities must be unique"),
535
+ constraints: grantConstraintsSchema.default({}),
536
+ target: grantTargetSelectorSchema,
537
+ };
538
+ export const passwordCredentialCreateSchema = z
539
+ .object({
540
+ ...credentialGrantFields,
541
+ password: z.string().min(10).max(1024),
542
+ })
543
+ .strict()
544
+ .superRefine((value, context) => {
545
+ const unsafeCapability = value.capabilities.find((capability) => capability === "content.publish" || capability === "access.manage");
546
+ if (unsafeCapability) {
547
+ context.addIssue({
548
+ code: "custom",
549
+ path: ["capabilities"],
550
+ message: `password audiences cannot receive ${unsafeCapability}`,
551
+ });
552
+ }
553
+ });
554
+ export const passwordCredentialRotateSchema = z
555
+ .object({ password: z.string().min(10).max(1024) })
556
+ .strict();
557
+ export const machineCredentialCreateSchema = z
558
+ .object(credentialGrantFields)
559
+ .strict()
560
+ .superRefine((value, context) => {
561
+ if (value.constraints.requireVerifiedEmail === true) {
562
+ context.addIssue({
563
+ code: "custom",
564
+ path: ["constraints", "requireVerifiedEmail"],
565
+ message: "requireVerifiedEmail is unavailable for machine audiences",
566
+ });
567
+ }
568
+ });
569
+ export const passwordCredentialSchema = z
570
+ .object({
571
+ id: registeredIdSchema("pwd"),
572
+ grant: spaceGrantSchema,
573
+ rotatedAt: z.string().datetime().nullable(),
574
+ lastUsedAt: z.string().datetime().nullable(),
575
+ revokedAt: z.string().datetime().nullable(),
576
+ })
577
+ .strict();
578
+ export const machineCredentialSchema = z
579
+ .object({
580
+ id: registeredIdSchema("mch"),
581
+ grant: spaceGrantSchema,
582
+ tokenHint: z.string().min(1).max(32),
583
+ rotatedAt: z.string().datetime().nullable(),
584
+ lastUsedAt: z.string().datetime().nullable(),
585
+ revokedAt: z.string().datetime().nullable(),
586
+ })
587
+ .strict();
588
+ export const machineCredentialSecretSchema = z
589
+ .object({
590
+ credential: machineCredentialSchema,
591
+ token: z.string().min(32).max(256),
592
+ })
593
+ .strict();
594
+ export const machineCredentialExchangeRequestSchema = z
595
+ .object({
596
+ spaceId: registeredIdSchema("spc"),
597
+ host: z.string().trim().min(1).max(255),
598
+ resource: z.string().min(1).max(1024),
599
+ target: z
600
+ .discriminatedUnion("kind", [
601
+ z.object({ kind: z.literal("live") }).strict(),
602
+ z.object({ kind: z.literal("version"), versionId: grantReferenceSchema }).strict(),
603
+ z.object({ kind: z.literal("branch"), branch: grantReferenceSchema }).strict(),
604
+ ])
605
+ .default({ kind: "live" }),
606
+ })
607
+ .strict();
608
+ export const machineCredentialExchangeResponseSchema = z
609
+ .object({
610
+ token: z.string().min(1),
611
+ expiresAt: z.string().datetime(),
612
+ })
613
+ .strict();
614
+ export const credentialRevocationSchema = z
615
+ .object({
616
+ grantId: registeredIdSchema("grt"),
617
+ revoked: z.literal(true),
618
+ })
619
+ .strict();
620
+ export const IDENTITY_CONNECTION_DEFAULT_TTL_SECONDS = 8 * 60 * 60;
621
+ export const EXTERNAL_IDENTITY_MAX_TTL_SECONDS = 30 * 60;
622
+ export const identityConnectionPublicKeySchema = z
623
+ .object({
624
+ kid: z.string().min(1).max(64),
625
+ publicKey: z
626
+ .string()
627
+ .regex(/^[A-Za-z0-9_-]{43}$/, "publicKey must be a raw Ed25519 public key"),
628
+ })
629
+ .strict();
630
+ export const identityConnectionCreateSchema = z.discriminatedUnion("type", [
631
+ z
632
+ .object({
633
+ type: z.literal("oidc"),
634
+ name: z.string().trim().min(1).max(200),
635
+ issuer: z.string().min(1).max(1024),
636
+ clientId: z.string().min(1).max(512),
637
+ clientSecret: z.string().min(1).max(2048),
638
+ sessionTtlSeconds: z
639
+ .number()
640
+ .int()
641
+ .min(60)
642
+ .max(24 * 60 * 60)
643
+ .optional(),
644
+ })
645
+ .strict(),
646
+ z
647
+ .object({
648
+ type: z.literal("signer"),
649
+ name: z.string().trim().min(1).max(200),
650
+ issuer: z.string().min(1).max(1024),
651
+ authorizeUrl: z.string().min(1).max(2048),
652
+ publicKeys: z.array(identityConnectionPublicKeySchema).min(1).max(5),
653
+ sessionTtlSeconds: z.number().int().min(60).max(EXTERNAL_IDENTITY_MAX_TTL_SECONDS).optional(),
654
+ })
655
+ .strict(),
656
+ ]);
657
+ export const identityConnectionUpdateSchema = z
658
+ .object({
659
+ name: z.string().trim().min(1).max(200).optional(),
660
+ clientId: z.string().min(1).max(512).optional(),
661
+ clientSecret: z.string().min(1).max(2048).optional(),
662
+ authorizeUrl: z.string().min(1).max(2048).optional(),
663
+ publicKeys: z.array(identityConnectionPublicKeySchema).min(1).max(5).optional(),
664
+ sessionTtlSeconds: z
665
+ .number()
666
+ .int()
667
+ .min(60)
668
+ .max(24 * 60 * 60)
669
+ .optional(),
670
+ })
671
+ .strict();
672
+ export const identityConnectionSchema = z
673
+ .object({
674
+ id: registeredIdSchema("acn"),
675
+ teamId: z.string().min(1),
676
+ type: z.enum(["oidc", "signer"]),
677
+ name: z.string().min(1),
678
+ issuer: z.string().min(1),
679
+ clientId: z.string().nullable(),
680
+ clientSecretSet: z.boolean(),
681
+ authorizeUrl: z.string().nullable(),
682
+ publicKeys: z.array(identityConnectionPublicKeySchema),
683
+ sessionTtlSeconds: z.number().int().positive(),
684
+ createdAt: z.string().datetime(),
685
+ rotatedAt: z.string().datetime().nullable(),
686
+ lastUsedAt: z.string().datetime().nullable(),
687
+ revokedAt: z.string().datetime().nullable(),
688
+ })
689
+ .strict();
690
+ export const externalGrantCreateSchema = z
691
+ .object({
692
+ connectionId: registeredIdSchema("acn"),
693
+ subject: z.string().min(1).max(255),
694
+ ...credentialGrantFields,
695
+ })
696
+ .strict();