@spacefast/common 0.2.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/agents/marketplace-prompts.d.ts +1 -0
  2. package/dist/agents/marketplace-prompts.js +6 -0
  3. package/dist/agents/registry/core.d.ts +4 -12
  4. package/dist/agents/registry/core.js +7 -28
  5. package/dist/config/domains.d.ts +10 -8
  6. package/dist/config/domains.js +4 -7
  7. package/dist/contracts/access-core.d.ts +135 -0
  8. package/dist/contracts/access-core.js +242 -0
  9. package/dist/contracts/access-profiles.d.ts +8 -8
  10. package/dist/contracts/access-profiles.js +8 -8
  11. package/dist/contracts/access.d.ts +14 -103
  12. package/dist/contracts/access.js +10 -210
  13. package/dist/contracts/activity.d.ts +54 -55
  14. package/dist/contracts/activity.js +38 -9
  15. package/dist/contracts/api-keys.d.ts +69 -26
  16. package/dist/contracts/api-keys.js +29 -2
  17. package/dist/contracts/application-journal.d.ts +197 -4
  18. package/dist/contracts/application-journal.js +97 -16
  19. package/dist/contracts/auth.d.ts +20 -0
  20. package/dist/contracts/auth.js +18 -0
  21. package/dist/contracts/beta.d.ts +13 -0
  22. package/dist/contracts/beta.js +28 -0
  23. package/dist/contracts/billing.d.ts +2 -2
  24. package/dist/contracts/bootstrap.d.ts +23 -6
  25. package/dist/contracts/builds.d.ts +18 -4
  26. package/dist/contracts/builds.js +7 -4
  27. package/dist/contracts/channels.d.ts +43 -0
  28. package/dist/contracts/channels.js +22 -6
  29. package/dist/contracts/comments.d.ts +49 -0
  30. package/dist/contracts/comments.js +61 -2
  31. package/dist/contracts/commerce.d.ts +19 -13
  32. package/dist/contracts/connector-access.d.ts +28 -0
  33. package/dist/contracts/connector-access.js +31 -0
  34. package/dist/contracts/connectors.d.ts +2382 -0
  35. package/dist/contracts/connectors.js +941 -0
  36. package/dist/contracts/content-contract-verification.d.ts +112 -58
  37. package/dist/contracts/content-contract-verification.js +42 -54
  38. package/dist/contracts/content-model-emit.d.ts +97 -0
  39. package/dist/contracts/content-model-emit.js +306 -0
  40. package/dist/contracts/content-model-sdk.d.ts +101 -0
  41. package/dist/contracts/content-model-sdk.js +232 -0
  42. package/dist/contracts/{content-program.d.ts → content-model.d.ts} +338 -82
  43. package/dist/contracts/{content-program.js → content-model.js} +291 -63
  44. package/dist/contracts/content-platform-fixture.d.ts +380 -110
  45. package/dist/contracts/content-platform-fixture.js +8 -8
  46. package/dist/contracts/content-sync.d.ts +197 -74
  47. package/dist/contracts/content-sync.js +98 -37
  48. package/dist/contracts/content.d.ts +30 -422
  49. package/dist/contracts/content.js +34 -294
  50. package/dist/contracts/dashboard-prefs.d.ts +10 -3
  51. package/dist/contracts/dashboard-prefs.js +6 -3
  52. package/dist/contracts/data-location.d.ts +9 -0
  53. package/dist/contracts/data-location.js +7 -0
  54. package/dist/contracts/deployment-comparison.d.ts +67 -0
  55. package/dist/contracts/deployment-comparison.js +68 -0
  56. package/dist/contracts/device-auth.d.ts +55 -11
  57. package/dist/contracts/domains.d.ts +40 -16
  58. package/dist/contracts/domains.js +23 -0
  59. package/dist/contracts/enums.d.ts +1 -10
  60. package/dist/contracts/enums.js +0 -15
  61. package/dist/contracts/error-code-meta.d.ts +384 -20
  62. package/dist/contracts/error-code-meta.js +200 -5
  63. package/dist/contracts/error-codes.d.ts +17 -4
  64. package/dist/contracts/error-codes.js +108 -10
  65. package/dist/contracts/events.d.ts +5 -0
  66. package/dist/contracts/events.js +12 -3
  67. package/dist/contracts/execution.d.ts +5 -1
  68. package/dist/contracts/execution.js +4 -0
  69. package/dist/contracts/feature-lifecycle.d.ts +53 -20
  70. package/dist/contracts/feature-lifecycle.js +65 -23
  71. package/dist/contracts/features.d.ts +57 -31
  72. package/dist/contracts/features.js +0 -1
  73. package/dist/contracts/feedback.d.ts +124 -0
  74. package/dist/contracts/feedback.js +64 -0
  75. package/dist/contracts/fixtures/content-model-sdk-v1.generated.d.ts +129 -0
  76. package/dist/contracts/fixtures/content-model-sdk-v1.generated.js +48 -0
  77. package/dist/contracts/fixtures/content-platform-v1.json +101 -49
  78. package/dist/contracts/frame-session.d.ts +60 -10
  79. package/dist/contracts/frame-session.js +52 -12
  80. package/dist/contracts/functions.d.ts +28 -2
  81. package/dist/contracts/functions.js +25 -2
  82. package/dist/contracts/grant-copy.d.ts +2 -0
  83. package/dist/contracts/grant-copy.js +2 -0
  84. package/dist/contracts/grants-core.d.ts +95 -0
  85. package/dist/contracts/grants-core.js +435 -0
  86. package/dist/contracts/grants.d.ts +13 -84
  87. package/dist/contracts/grants.js +21 -369
  88. package/dist/contracts/ids-core.d.ts +11 -0
  89. package/dist/contracts/ids-core.js +14 -0
  90. package/dist/contracts/ids.d.ts +7 -4
  91. package/dist/contracts/ids.js +12 -4
  92. package/dist/contracts/internal.d.ts +18 -0
  93. package/dist/contracts/internal.js +19 -0
  94. package/dist/contracts/mcp.d.ts +8 -18
  95. package/dist/contracts/mcp.js +2 -23
  96. package/dist/contracts/notifications.d.ts +4 -1
  97. package/dist/contracts/notifications.js +1 -0
  98. package/dist/contracts/oauth-resources.d.ts +46 -9
  99. package/dist/contracts/oauth-resources.js +50 -1
  100. package/dist/contracts/oauth-scope-actions.d.ts +27 -3
  101. package/dist/contracts/oauth-scope-actions.js +18 -8
  102. package/dist/contracts/operations.d.ts +1 -1
  103. package/dist/contracts/partner-account.d.ts +629 -0
  104. package/dist/contracts/partner-account.js +18 -0
  105. package/dist/contracts/principal-assertion.d.ts +74 -1
  106. package/dist/contracts/principal-assertion.js +67 -3
  107. package/dist/contracts/publish-archive.d.ts +22 -1
  108. package/dist/contracts/push-new.d.ts +3 -3
  109. package/dist/contracts/push-new.js +1 -1
  110. package/dist/contracts/quotas.js +2 -1
  111. package/dist/contracts/repository-connections.d.ts +10 -6
  112. package/dist/contracts/repository-connections.js +15 -0
  113. package/dist/contracts/resources.d.ts +6 -5
  114. package/dist/contracts/resources.js +7 -0
  115. package/dist/contracts/route-inventory.d.ts +74 -19
  116. package/dist/contracts/route-inventory.js +20 -6
  117. package/dist/contracts/runtime-api.d.ts +415 -4
  118. package/dist/contracts/runtime-api.js +60 -15
  119. package/dist/contracts/runtime-app.d.ts +6 -1
  120. package/dist/contracts/runtime-components.d.ts +646 -18
  121. package/dist/contracts/runtime-components.js +304 -188
  122. package/dist/contracts/runtime-services.d.ts +15 -0
  123. package/dist/contracts/runtime-services.js +25 -0
  124. package/dist/contracts/runtime-storage.d.ts +32 -0
  125. package/dist/contracts/runtime-storage.js +37 -0
  126. package/dist/contracts/source-workspaces.d.ts +511 -0
  127. package/dist/contracts/source-workspaces.js +276 -0
  128. package/dist/contracts/space-config-spacefast.d.ts +394 -0
  129. package/dist/contracts/space-config-spacefast.js +70 -0
  130. package/dist/contracts/space-config.d.ts +177 -90
  131. package/dist/contracts/space-config.js +77 -92
  132. package/dist/contracts/spaces.d.ts +132 -24
  133. package/dist/contracts/spaces.js +77 -13
  134. package/dist/contracts/superadmin-connectors.d.ts +371 -0
  135. package/dist/contracts/superadmin-connectors.js +61 -0
  136. package/dist/contracts/superadmin-emails.d.ts +1 -1
  137. package/dist/contracts/superadmin-emails.js +1 -1
  138. package/dist/contracts/superadmin-entities.d.ts +105 -0
  139. package/dist/contracts/superadmin-entities.js +64 -0
  140. package/dist/contracts/superadmin-runtime.d.ts +35 -116
  141. package/dist/contracts/superadmin-runtime.js +13 -69
  142. package/dist/contracts/superadmin-search.d.ts +12 -14
  143. package/dist/contracts/superadmin-search.js +2 -2
  144. package/dist/contracts/superadmin-spaces.d.ts +15 -72
  145. package/dist/contracts/superadmin-spaces.js +2 -7
  146. package/dist/contracts/superadmin-teams.d.ts +12 -0
  147. package/dist/contracts/superadmin-teams.js +7 -1
  148. package/dist/contracts/superadmin-tenants.d.ts +44 -16
  149. package/dist/contracts/superadmin.d.ts +2 -2
  150. package/dist/contracts/tags.d.ts +12 -12
  151. package/dist/contracts/teams.d.ts +29 -1
  152. package/dist/contracts/teams.js +24 -2
  153. package/dist/contracts/tenants.d.ts +98 -0
  154. package/dist/contracts/tenants.js +20 -0
  155. package/dist/contracts/transfers.d.ts +92 -0
  156. package/dist/contracts/transfers.js +37 -0
  157. package/dist/contracts/usage.d.ts +9 -1
  158. package/dist/contracts/usage.js +2 -0
  159. package/dist/contracts/visual-review.d.ts +448 -0
  160. package/dist/contracts/visual-review.js +156 -0
  161. package/dist/contracts/wordpress-php-toolkit.json +9 -0
  162. package/dist/contracts/work-mode.d.ts +24 -0
  163. package/dist/contracts/work-mode.js +17 -0
  164. package/dist/contracts/wp-cli.d.ts +66 -0
  165. package/dist/contracts/wp-cli.js +68 -0
  166. package/dist/contracts/zero-catalog.d.ts +29 -0
  167. package/dist/contracts/zero-catalog.js +55 -0
  168. package/dist/contracts/zero-platform-abilities.generated.d.ts +31 -0
  169. package/dist/contracts/zero-platform-abilities.generated.js +373 -0
  170. package/dist/contracts/zero-spacefast.d.ts +444 -0
  171. package/dist/contracts/zero-spacefast.js +149 -0
  172. package/dist/contracts/zero-types.d.ts +47 -0
  173. package/dist/contracts/zero-types.js +263 -0
  174. package/dist/contracts/zero.d.ts +781 -14
  175. package/dist/contracts/zero.js +204 -10
  176. package/dist/dashboard-paths/index.d.ts +2 -2
  177. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -2
  178. package/dist/dashboard-paths/route-reservations.generated.js +5 -0
  179. package/dist/docs/agent-prose.d.ts +1 -1
  180. package/dist/docs/agent-prose.js +15 -18
  181. package/dist/docs/agent-setup.d.ts +1 -1
  182. package/dist/docs/agent-setup.js +5 -13
  183. package/dist/docs/catalog.d.ts +64 -28
  184. package/dist/docs/catalog.js +155 -28
  185. package/dist/docs/error-docs.d.ts +361 -17
  186. package/dist/docs/error-docs.js +365 -21
  187. package/dist/docs/reference-urls.js +1 -1
  188. package/dist/docs/skills.d.ts +84 -84
  189. package/dist/docs/skills.js +66 -47
  190. package/dist/docs/start-prompts.d.ts +14 -0
  191. package/dist/docs/start-prompts.js +20 -0
  192. package/dist/utils/account-labels.d.ts +7 -0
  193. package/dist/utils/account-labels.js +15 -0
  194. package/dist/utils/auth.d.ts +1 -0
  195. package/dist/utils/auth.js +1 -0
  196. package/dist/utils/credential-policy.d.ts +22 -1
  197. package/dist/utils/credential-policy.js +55 -6
  198. package/dist/utils/local-space-state.d.ts +7 -0
  199. package/dist/utils/local-space-state.js +7 -0
  200. package/dist/utils/logger.d.ts +34 -0
  201. package/dist/utils/logger.js +144 -0
  202. package/dist/utils/page-routes.d.ts +9 -0
  203. package/dist/utils/page-routes.js +82 -0
  204. package/dist/utils/pages.d.ts +2 -0
  205. package/dist/utils/pages.js +7 -0
  206. package/dist/utils/publish-form-data.d.ts +2 -0
  207. package/dist/utils/publish-form-data.js +2 -0
  208. package/dist/utils/publish-policy-spacefast.d.ts +13 -0
  209. package/dist/utils/publish-policy-spacefast.js +117 -0
  210. package/dist/utils/publish-policy.d.ts +22 -16
  211. package/dist/utils/publish-policy.fixtures.json +15 -0
  212. package/dist/utils/publish-policy.js +62 -139
  213. package/dist/utils/query-client.js +9 -1
  214. package/dist/utils/runtime-upload.d.ts +4 -0
  215. package/dist/utils/runtime-upload.js +5 -0
  216. package/dist/utils/space-config.d.ts +2 -3
  217. package/dist/utils/space-config.js +1 -1
  218. package/dist/utils/uploaded-source.d.ts +4 -0
  219. package/dist/utils/uploaded-source.js +12 -0
  220. package/dist/visual-review.d.ts +4 -0
  221. package/dist/visual-review.js +208 -0
  222. package/dist/vocabulary.d.ts +38 -0
  223. package/dist/vocabulary.js +55 -0
  224. package/package.json +7 -2
  225. package/dist/agents/registry/deeplink-probes.d.ts +0 -23
  226. package/dist/agents/registry/deeplink-probes.js +0 -18
@@ -1,14 +1,23 @@
1
1
  import { z } from "zod";
2
2
  import { appRoutePathSchema, executionSha256Schema, httpMethodSchema } from "./execution.js";
3
- import { grantCapabilitySchema } from "./grants.js";
4
- export const CONTENT_PROGRAM_FORMAT = "spacefast.zero-wordpress";
5
- export const CONTENT_PROGRAM_VERSION = 1;
6
- export const contentProgramSha256Schema = executionSha256Schema.brand();
7
- const stableIdSchema = z
3
+ import { grantCapabilitySchema } from "./grants-core.js";
4
+ import { ZERO_PLATFORM_ABILITIES } from "./zero-platform-abilities.generated.js";
5
+ export const CONTENT_MODEL_FORMAT = "spacefast.zero-wordpress";
6
+ export const CONTENT_MODEL_VERSION = 1;
7
+ export const contentModelSha256Schema = executionSha256Schema.brand();
8
+ /**
9
+ * The one spelling of a content model identifier.
10
+ *
11
+ * Exported because every "author → content model" compiler needs to normalize
12
+ * someone else's identifiers against it, and a hand-copied regex that drifts
13
+ * from this one produces ids the release then refuses.
14
+ */
15
+ export const contentStableIdSchema = z
8
16
  .string()
9
17
  .min(1)
10
18
  .max(160)
11
19
  .regex(/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/);
20
+ const stableIdSchema = contentStableIdSchema;
12
21
  export const contentResourceIdSchema = stableIdSchema.brand();
13
22
  export const contentFieldIdSchema = stableIdSchema.brand();
14
23
  export const contentTableIdSchema = stableIdSchema.brand();
@@ -16,6 +25,42 @@ export const contentAbilityIdSchema = stableIdSchema.brand();
16
25
  export const contentSchemaIdSchema = stableIdSchema.brand();
17
26
  export const contentComponentIdSchema = stableIdSchema.brand();
18
27
  export const contentSyncBindingIdSchema = stableIdSchema.brand();
28
+ /**
29
+ * The name WordPress publishes an Ability under, derived from its content model id.
30
+ *
31
+ * Content model ids stay the vocabulary everywhere Spacefast addresses an Ability —
32
+ * routes, SDK descriptors, executables, decisions, receipts, invalidations.
33
+ * WordPress is the exception: its Abilities registry admits
34
+ * `^[a-z0-9-]+/[a-z0-9-]+$`, one namespace and one slug, so a dotted id
35
+ * flattens onto a single Zero-branded slug and `content.projects.list` is
36
+ * published as `zero/content-projects-list`.
37
+ *
38
+ * `spacefast_content_model_ability_name` in runtime/engine/wordpress/content-model-kernel.php
39
+ * is the same derivation in PHP and the two must change together. Because
40
+ * flattening is many-to-one (`a.b` and `a_b` collide),
41
+ * `contentModelReleaseV1Schema` rejects a content model whose abilities would share
42
+ * a name.
43
+ */
44
+ export function contentAbilityWordPressName(id) {
45
+ return `zero/${id.replaceAll(".", "-").replaceAll("_", "-")}`;
46
+ }
47
+ /**
48
+ * The TypeScript identifier the generated content model SDK gives an Ability.
49
+ *
50
+ * This flattening is coarser than `contentAbilityWordPressName`: it drops every
51
+ * `.`/`_`/`-` separator and PascalCases the parts, so two ids that keep distinct
52
+ * WordPress names can still land on the same identifier (`posts.v2` and
53
+ * `posts.v.2` both become `PostsV2`, because a digit-led part has no letter to
54
+ * capitalize). `contentModelReleaseV1Schema` rejects that collision so the
55
+ * generated module never emits a duplicate `export type`.
56
+ */
57
+ export function contentAbilityTypeName(id) {
58
+ return id
59
+ .split(/[^a-z0-9]+/)
60
+ .filter((part) => part.length > 0)
61
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
62
+ .join("");
63
+ }
19
64
  export const contentSyncSourcePathSchema = z
20
65
  .string()
21
66
  .min(1)
@@ -27,7 +72,7 @@ export const contentSyncSourcePathSchema = z
27
72
  }), "source paths cannot contain controls or backslashes")
28
73
  .refine((source) => !source.startsWith("/") &&
29
74
  source.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."), "source paths must be normalized relative paths without traversal");
30
- export const contentProgramJsonValueSchema = z.json();
75
+ export const contentModelJsonValueSchema = z.json();
31
76
  export const contentResourceRefSchema = z
32
77
  .string()
33
78
  .regex(/^(?:content|table)\.[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/)
@@ -35,14 +80,14 @@ export const contentResourceRefSchema = z
35
80
  export const contentSchemaRefSchema = z
36
81
  .object({
37
82
  id: contentSchemaIdSchema,
38
- sha256: contentProgramSha256Schema,
83
+ sha256: contentModelSha256Schema,
39
84
  })
40
85
  .strict();
41
86
  export const contentSchemaDefinitionSchema = z
42
87
  .object({
43
88
  id: contentSchemaIdSchema,
44
- sha256: contentProgramSha256Schema,
45
- schema: contentProgramJsonValueSchema,
89
+ sha256: contentModelSha256Schema,
90
+ schema: contentModelJsonValueSchema,
46
91
  })
47
92
  .strict();
48
93
  const fieldValueSchema = z.discriminatedUnion("kind", [
@@ -78,7 +123,7 @@ const fieldValueSchema = z.discriminatedUnion("kind", [
78
123
  })
79
124
  .strict(),
80
125
  ]);
81
- export const contentProgramFieldSchema = z
126
+ export const contentModelFieldSchema = z
82
127
  .object({
83
128
  id: contentFieldIdSchema,
84
129
  name: stableIdSchema,
@@ -93,13 +138,13 @@ export const wordpressContentResourceSchema = z
93
138
  name: stableIdSchema,
94
139
  label: z.string().trim().min(1).max(160),
95
140
  kind: z.enum(["posts", "pages", "media", "collection"]),
96
- fields: z.array(contentProgramFieldSchema).max(100),
141
+ fields: z.array(contentModelFieldSchema).max(100),
97
142
  publicRead: z.boolean(),
98
143
  })
99
144
  .strict();
100
- export const wordpressProgramIrV1Schema = z
145
+ export const wordpressContentModelIrV1Schema = z
101
146
  .object({
102
- format: z.literal("spacefast.wordpress-program"),
147
+ format: z.literal("spacefast.wordpress-content-model"),
103
148
  version: z.literal(1),
104
149
  resources: z.array(wordpressContentResourceSchema).min(3).max(53),
105
150
  })
@@ -134,7 +179,7 @@ export const wordpressProgramIrV1Schema = z
134
179
  context.addIssue({
135
180
  code: "custom",
136
181
  path: ["resources"],
137
- message: `WordPress programs require exactly one native ${required} resource`,
182
+ message: `WordPress content models require exactly one native ${required} resource`,
138
183
  });
139
184
  }
140
185
  const native = wordpress.resources.find((resource) => resource.kind === required);
@@ -248,9 +293,9 @@ export const contentCacheProfileV1Schema = z.discriminatedUnion("kind", [
248
293
  })
249
294
  .strict(),
250
295
  ]);
251
- const abilityRoutePathSchema = appRoutePathSchema({ subject: "Content program routes" });
296
+ const abilityRoutePathSchema = appRoutePathSchema({ subject: "Content model routes" });
252
297
  export const contentAbilityRouteAdmissionV1Schema = z.enum(["authenticated", "public"]);
253
- export const contentProgramRouteSchema = z
298
+ export const contentModelRouteSchema = z
254
299
  .object({
255
300
  id: stableIdSchema,
256
301
  method: httpMethodSchema,
@@ -264,7 +309,7 @@ const contentAbilityExposureSchema = z.discriminatedUnion("kind", [
264
309
  .object({
265
310
  kind: z.literal("routed"),
266
311
  named: z.literal(true),
267
- route: contentProgramRouteSchema,
312
+ route: contentModelRouteSchema,
268
313
  })
269
314
  .strict(),
270
315
  ]);
@@ -405,7 +450,7 @@ export const contentAbilityReceiptV1Schema = z.discriminatedUnion("status", [
405
450
  abilityId: contentAbilityIdSchema,
406
451
  callId: stableIdSchema,
407
452
  status: z.literal("succeeded"),
408
- output: contentProgramJsonValueSchema,
453
+ output: contentModelJsonValueSchema,
409
454
  invalidates: z.array(contentAbilityIdSchema).max(500),
410
455
  })
411
456
  .strict(),
@@ -435,65 +480,219 @@ export const contentExecutableRefV1Schema = z
435
480
  .object({
436
481
  id: stableIdSchema,
437
482
  runtime: z.enum(["php", "quickjs"]),
438
- artifact: contentProgramSha256Schema,
483
+ artifact: contentModelSha256Schema,
439
484
  export: z.string().min(1).max(256),
440
485
  })
441
486
  .strict();
487
+ /**
488
+ * Which serializer stands between a source file and the blocks WordPress holds.
489
+ *
490
+ * The format is a property of the binding rather than of the request, because
491
+ * the file's extension decides it once at compile time and every later
492
+ * reconciliation has to agree. A caller cannot ask for the other serializer on
493
+ * a file this ContentModelRelease bound to one.
494
+ */
495
+ export const CONTENT_SYNC_FORMATS = ["md", "html", "tsx"];
496
+ export const contentSyncFormatSchema = z.enum(CONTENT_SYNC_FORMATS);
497
+ /**
498
+ * Formats that compile one way. No serializer reads them back out of blocks, so
499
+ * their bindings carry the digest of the markup they produced and drift is
500
+ * settled by recompiling and comparing rather than by merging.
501
+ */
502
+ export const CONTENT_SYNC_COMPILE_FORMATS = new Set(["tsx"]);
503
+ export function contentSyncFormatIsCompiled(format) {
504
+ return CONTENT_SYNC_COMPILE_FORMATS.has(format);
505
+ }
506
+ /** The source extension each serializer owns. */
507
+ export const CONTENT_SYNC_FORMAT_EXTENSIONS = new Map([
508
+ [".md", "md"],
509
+ [".markdown", "md"],
510
+ [".html", "html"],
511
+ [".htm", "html"],
512
+ [".tsx", "tsx"],
513
+ ]);
514
+ /** The binding format a source path implies, or `null` when no serializer claims it. */
515
+ export function contentSyncFormatForSource(source) {
516
+ const dot = source.lastIndexOf(".");
517
+ if (dot === -1)
518
+ return null;
519
+ return CONTENT_SYNC_FORMAT_EXTENSIONS.get(source.slice(dot).toLowerCase()) ?? null;
520
+ }
442
521
  export const contentSyncBindingV1Schema = z
443
522
  .object({
444
523
  id: contentSyncBindingIdSchema,
445
524
  resourceId: contentResourceIdSchema,
446
525
  fieldId: contentFieldIdSchema,
447
526
  source: contentSyncSourcePathSchema,
527
+ publicPath: abilityRoutePathSchema.optional(),
528
+ // Optional for backward compatibility: a ContentModelRelease compiled before
529
+ // the HTML interchange format carried no `format`. The value is fully
530
+ // derivable from the source extension (the check below enforces they agree
531
+ // when both are present), so a stored binding without one is filled from the
532
+ // source rather than rejected — otherwise re-parsing a pre-format release
533
+ // would throw on live read paths (e.g. versionDeclaredContentModel).
534
+ format: contentSyncFormatSchema.optional(),
535
+ // The digest of the block markup this source compiled to. Deterministic —
536
+ // same source, same compiler, same digest — which is why it may live in a
537
+ // content-addressed release, and why a later read can compare it against
538
+ // what WordPress holds to see whether the two have parted ways.
539
+ compiled: z.object({ sha256: contentModelSha256Schema }).strict().optional(),
540
+ documentSeed: z
541
+ .object({ text: z.string().max(1_000_000), sha256: contentModelSha256Schema })
542
+ .strict()
543
+ .optional(),
448
544
  slug: z.string().trim().min(1).max(200),
545
+ })
546
+ .strict()
547
+ .superRefine((binding, context) => {
548
+ const derived = contentSyncFormatForSource(binding.source);
549
+ if (binding.format === undefined) {
550
+ if (derived === null) {
551
+ context.addIssue({
552
+ code: "custom",
553
+ path: ["format"],
554
+ message: "sync binding source extension has no known format",
555
+ });
556
+ return;
557
+ }
558
+ }
559
+ else if (derived !== binding.format) {
560
+ context.addIssue({
561
+ code: "custom",
562
+ path: ["format"],
563
+ message: "sync binding format must match its source file extension",
564
+ });
565
+ }
566
+ const format = binding.format ?? derived;
567
+ if (format === null)
568
+ return;
569
+ if (contentSyncFormatIsCompiled(format)) {
570
+ if (binding.compiled === undefined) {
571
+ context.addIssue({
572
+ code: "custom",
573
+ path: ["compiled"],
574
+ message: "a one-way sync binding must carry the digest of its compiled markup",
575
+ });
576
+ }
577
+ }
578
+ else if (binding.compiled !== undefined) {
579
+ context.addIssue({
580
+ code: "custom",
581
+ path: ["compiled"],
582
+ message: "only one-way sync bindings carry a compiled digest",
583
+ });
584
+ }
585
+ })
586
+ .transform((binding) => ({
587
+ ...binding,
588
+ // SAFETY: the superRefine above already rejected the only case where this
589
+ // could be null — an absent `format` whose source extension names no
590
+ // serializer — so by the time a value reaches the transform, an absent
591
+ // format means the source resolves.
592
+ format: binding.format ?? contentSyncFormatForSource(binding.source),
593
+ }));
594
+ /**
595
+ * Where a collection puts a document that WordPress created and no file backs
596
+ * yet. Deterministic — it restates the glob the capsule declared — so it may
597
+ * live in a content-addressed release.
598
+ */
599
+ export const contentSyncMaterializationV1Schema = z
600
+ .object({
601
+ resourceId: contentResourceIdSchema,
602
+ fieldId: contentFieldIdSchema,
603
+ /** Capsule-relative directory, no trailing slash. */
604
+ directory: z.string().trim().min(1).max(512),
605
+ /** The filename suffix the glob named, e.g. ".md". */
606
+ suffix: z.string().trim().min(2).max(16),
607
+ format: contentSyncFormatSchema,
449
608
  })
450
609
  .strict();
451
- export const contentProgramReleaseV1Schema = z
610
+ export const contentModelReleaseV1Schema = z
452
611
  .object({
453
- format: z.literal(CONTENT_PROGRAM_FORMAT),
454
- version: z.literal(CONTENT_PROGRAM_VERSION),
455
- revision: contentProgramSha256Schema,
612
+ format: z.literal(CONTENT_MODEL_FORMAT),
613
+ version: z.literal(CONTENT_MODEL_VERSION),
614
+ revision: contentModelSha256Schema,
456
615
  schemas: z.array(contentSchemaDefinitionSchema).min(1).max(500),
457
- wordpress: wordpressProgramIrV1Schema,
616
+ wordpress: wordpressContentModelIrV1Schema,
458
617
  tables: z.array(contentTableIrV1Schema).max(100),
459
618
  abilities: z.array(contentAbilityDescriptorV1Schema).max(500),
460
619
  pages: z.array(contentClientPageIrV1Schema).max(10_000),
461
620
  hooks: z.array(contentExecutableRefV1Schema).max(500),
462
621
  syncBindings: z.array(contentSyncBindingV1Schema).max(10_000),
622
+ // A release published before materializations existed carries no such key.
623
+ // The strict schema is re-parsed against stored release JSON on live read
624
+ // paths (versionDeclaredContentModel), so this must default rather than be
625
+ // required — the PHP runtime already treats a missing key as "nothing
626
+ // materializes here".
627
+ materializations: z.array(contentSyncMaterializationV1Schema).max(100).default([]),
463
628
  })
464
629
  .strict()
465
- .superRefine((program, context) => {
466
- const schemas = new Map(program.schemas.map((schema) => [schema.id, schema.sha256]));
467
- if (schemas.size !== program.schemas.length) {
630
+ .superRefine((contentModel, context) => {
631
+ const schemas = new Map(contentModel.schemas.map((schema) => [schema.id, schema.sha256]));
632
+ if (schemas.size !== contentModel.schemas.length) {
468
633
  context.addIssue({
469
634
  code: "custom",
470
635
  path: ["schemas"],
471
- message: "program schema ids must be unique",
636
+ message: "content model schema ids must be unique",
472
637
  });
473
638
  }
474
- const resources = new Map(program.wordpress.resources.map((resource) => [resource.id, resource]));
475
- const tables = new Set(program.tables.map((table) => table.id));
639
+ const resources = new Map(contentModel.wordpress.resources.map((resource) => [resource.id, resource]));
640
+ const tables = new Set(contentModel.tables.map((table) => table.id));
476
641
  const ids = [
477
- ...program.wordpress.resources.map((resource) => resource.id),
478
- ...program.tables.map((table) => table.id),
479
- ...program.abilities.map((ability) => ability.id),
480
- ...program.pages.map((page) => page.id),
481
- ...program.hooks.map((hook) => hook.id),
482
- ...program.syncBindings.map((binding) => binding.id),
642
+ ...contentModel.wordpress.resources.map((resource) => resource.id),
643
+ ...contentModel.tables.map((table) => table.id),
644
+ ...contentModel.abilities.map((ability) => ability.id),
645
+ ...contentModel.pages.map((page) => page.id),
646
+ ...contentModel.hooks.map((hook) => hook.id),
647
+ ...contentModel.syncBindings.map((binding) => binding.id),
483
648
  ];
484
649
  if (new Set(ids).size !== ids.length) {
485
- context.addIssue({ code: "custom", message: "stable program ids must be globally unique" });
650
+ context.addIssue({
651
+ code: "custom",
652
+ message: "stable content model ids must be globally unique",
653
+ });
654
+ }
655
+ const abilityNames = contentModel.abilities.map(({ id }) => contentAbilityWordPressName(id));
656
+ if (new Set(abilityNames).size !== abilityNames.length) {
657
+ context.addIssue({
658
+ code: "custom",
659
+ path: ["abilities"],
660
+ message: "ability WordPress names must be unique",
661
+ });
662
+ }
663
+ // A content Ability that flattens onto a platform Ability's WordPress name
664
+ // is a second row under one name: the runtime engine already publishes that
665
+ // set (`zero-wp-users`, `zero-storage`), and the merged catalog would carry
666
+ // two rows the consumer keys by the same id. Reject it here so the collision
667
+ // never reaches `zeroAbilityCatalog` or the types `sf zero types` generates.
668
+ const platformAbilityNames = new Set(ZERO_PLATFORM_ABILITIES.map((ability) => ability.name));
669
+ for (const [index, ability] of contentModel.abilities.entries()) {
670
+ if (platformAbilityNames.has(contentAbilityWordPressName(ability.id))) {
671
+ context.addIssue({
672
+ code: "custom",
673
+ path: ["abilities", index, "id"],
674
+ message: `ability ${ability.id} collides with a platform Ability the engine already publishes`,
675
+ });
676
+ }
677
+ }
678
+ const abilityTypeNames = contentModel.abilities.map(({ id }) => contentAbilityTypeName(id));
679
+ if (new Set(abilityTypeNames).size !== abilityTypeNames.length) {
680
+ context.addIssue({
681
+ code: "custom",
682
+ path: ["abilities"],
683
+ message: "ability SDK type names must be unique",
684
+ });
486
685
  }
487
686
  const verifySchemaRef = (ref, path) => {
488
687
  if (schemas.get(ref.id) !== ref.sha256) {
489
688
  context.addIssue({
490
689
  code: "custom",
491
690
  path,
492
- message: `schema reference ${ref.id} does not match a program schema`,
691
+ message: `schema reference ${ref.id} does not match a content model schema`,
493
692
  });
494
693
  }
495
694
  };
496
- for (const [index, ability] of program.abilities.entries()) {
695
+ for (const [index, ability] of contentModel.abilities.entries()) {
497
696
  verifySchemaRef(ability.input, ["abilities", index, "input"]);
498
697
  verifySchemaRef(ability.output, ["abilities", index, "output"]);
499
698
  for (const [resourceIndex, resource] of [...ability.reads, ...ability.writes].entries()) {
@@ -523,10 +722,10 @@ export const contentProgramReleaseV1Schema = z
523
722
  }
524
723
  }
525
724
  }
526
- for (const [index, page] of program.pages.entries()) {
725
+ for (const [index, page] of contentModel.pages.entries()) {
527
726
  verifySchemaRef(page.props, ["pages", index, "props"]);
528
727
  }
529
- for (const [index, binding] of program.syncBindings.entries()) {
728
+ for (const [index, binding] of contentModel.syncBindings.entries()) {
530
729
  const resource = resources.get(binding.resourceId);
531
730
  const field = resource?.fields.find((candidate) => candidate.id === binding.fieldId);
532
731
  if (!field) {
@@ -540,50 +739,79 @@ export const contentProgramReleaseV1Schema = z
540
739
  context.addIssue({
541
740
  code: "custom",
542
741
  path: ["syncBindings", index, "fieldId"],
543
- message: "Markdown sync bindings require a block field",
742
+ message: "source sync bindings require a block field",
743
+ });
744
+ }
745
+ }
746
+ // One field materializes into one directory. A second answer to "where does
747
+ // a document WordPress just created belong" is the same class of mistake as
748
+ // two globs binding one field, and the runtime would have to guess.
749
+ const materialized = new Set();
750
+ for (const [index, materialization] of contentModel.materializations.entries()) {
751
+ const target = `${materialization.resourceId}${materialization.fieldId}`;
752
+ if (materialized.has(target)) {
753
+ context.addIssue({
754
+ code: "custom",
755
+ path: ["materializations", index],
756
+ message: `duplicate content materialization for ${materialization.resourceId}.${materialization.fieldId}`,
544
757
  });
545
758
  }
759
+ materialized.add(target);
546
760
  }
547
761
  });
548
- export const CONTENT_PROGRAM_ARTIFACT_FORMATS = {
549
- program: CONTENT_PROGRAM_FORMAT,
762
+ export const contentModelStageReceiptV1Schema = z
763
+ .object({
764
+ revision: contentModelSha256Schema,
765
+ artifactDigest: contentModelSha256Schema,
766
+ staged: z.boolean(),
767
+ })
768
+ .strict();
769
+ export const contentModelActivationReceiptV1Schema = z
770
+ .object({
771
+ revision: contentModelSha256Schema.nullable(),
772
+ tables: z.number().int().nonnegative(),
773
+ pages: z.number().int().nonnegative(),
774
+ })
775
+ .strict();
776
+ export const CONTENT_MODEL_ARTIFACT_FORMATS = {
777
+ model: CONTENT_MODEL_FORMAT,
550
778
  executables: "spacefast.zero-executables",
551
779
  sdk: "spacefast.sdk-descriptors",
552
780
  routes: "spacefast.route-contributions",
553
- requirements: "spacefast.program-requirements",
781
+ requirements: "spacefast.content-model-requirements",
554
782
  };
555
- export function contentProgramArtifactRefSchema(format) {
783
+ export function contentModelArtifactRefSchema(format) {
556
784
  return z
557
785
  .object({
558
786
  format: z.literal(format),
559
787
  version: z.literal(1),
560
- inputDigest: contentProgramSha256Schema,
561
- sha256: contentProgramSha256Schema,
788
+ inputDigest: contentModelSha256Schema,
789
+ sha256: contentModelSha256Schema,
562
790
  bytes: z.number().int().nonnegative(),
563
791
  mediaType: z.literal("application/json"),
564
792
  })
565
793
  .strict();
566
794
  }
567
- export const compiledContentProgramV1Schema = z
795
+ export const compiledContentModelV1Schema = z
568
796
  .object({
569
- format: z.literal("spacefast.compiled-program"),
797
+ format: z.literal("spacefast.compiled-content-model"),
570
798
  version: z.literal(1),
571
- program: contentProgramArtifactRefSchema(CONTENT_PROGRAM_ARTIFACT_FORMATS.program),
572
- executables: contentProgramArtifactRefSchema(CONTENT_PROGRAM_ARTIFACT_FORMATS.executables),
573
- sdk: contentProgramArtifactRefSchema(CONTENT_PROGRAM_ARTIFACT_FORMATS.sdk),
574
- routes: contentProgramArtifactRefSchema(CONTENT_PROGRAM_ARTIFACT_FORMATS.routes),
575
- requirements: contentProgramArtifactRefSchema(CONTENT_PROGRAM_ARTIFACT_FORMATS.requirements),
799
+ model: contentModelArtifactRefSchema(CONTENT_MODEL_ARTIFACT_FORMATS.model),
800
+ executables: contentModelArtifactRefSchema(CONTENT_MODEL_ARTIFACT_FORMATS.executables),
801
+ sdk: contentModelArtifactRefSchema(CONTENT_MODEL_ARTIFACT_FORMATS.sdk),
802
+ routes: contentModelArtifactRefSchema(CONTENT_MODEL_ARTIFACT_FORMATS.routes),
803
+ requirements: contentModelArtifactRefSchema(CONTENT_MODEL_ARTIFACT_FORMATS.requirements),
576
804
  })
577
805
  .strict();
578
806
  export const contentExecutableSetV1Schema = z
579
807
  .object({
580
- format: z.literal(CONTENT_PROGRAM_ARTIFACT_FORMATS.executables),
808
+ format: z.literal(CONTENT_MODEL_ARTIFACT_FORMATS.executables),
581
809
  version: z.literal(1),
582
- inputDigest: contentProgramSha256Schema,
810
+ inputDigest: contentModelSha256Schema,
583
811
  executables: z.array(contentExecutableRefV1Schema).max(500),
584
812
  })
585
813
  .strict();
586
- const contentSdkOperationDescriptorV1Schema = z
814
+ export const contentSdkOperationDescriptorV1Schema = z
587
815
  .discriminatedUnion("kind", [
588
816
  z
589
817
  .object({
@@ -630,9 +858,9 @@ const contentSdkOperationDescriptorV1Schema = z
630
858
  });
631
859
  export const contentSdkDescriptorSetV1Schema = z
632
860
  .object({
633
- format: z.literal(CONTENT_PROGRAM_ARTIFACT_FORMATS.sdk),
861
+ format: z.literal(CONTENT_MODEL_ARTIFACT_FORMATS.sdk),
634
862
  version: z.literal(1),
635
- inputDigest: contentProgramSha256Schema,
863
+ inputDigest: contentModelSha256Schema,
636
864
  operations: z.array(contentSdkOperationDescriptorV1Schema),
637
865
  sync: z.array(z
638
866
  .object({
@@ -645,13 +873,13 @@ export const contentSdkDescriptorSetV1Schema = z
645
873
  .strict();
646
874
  export const contentRouteContributionSetV1Schema = z
647
875
  .object({
648
- format: z.literal(CONTENT_PROGRAM_ARTIFACT_FORMATS.routes),
876
+ format: z.literal(CONTENT_MODEL_ARTIFACT_FORMATS.routes),
649
877
  version: z.literal(1),
650
- inputDigest: contentProgramSha256Schema,
878
+ inputDigest: contentModelSha256Schema,
651
879
  routes: z.array(z
652
880
  .object({
653
881
  abilityId: contentAbilityIdSchema,
654
- route: contentProgramRouteSchema,
882
+ route: contentModelRouteSchema,
655
883
  })
656
884
  .strict()),
657
885
  })