@soulcraft/kit-schema 2.0.0

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.
package/src/schema.ts ADDED
@@ -0,0 +1,705 @@
1
+ /**
2
+ * @module @soulcraft/kit-schema/schema
3
+ * @description Zod v4 validation schemas for the Soulcraft kit manifest format.
4
+ *
5
+ * These schemas are used by the kit loader to validate `kit.json` files before
6
+ * mounting them in a platform deployment. Invalid kit manifests are rejected with
7
+ * descriptive error messages pointing to the offending field.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { VenueKitSchema } from '@soulcraft/kit-schema/schema';
12
+ *
13
+ * const result = VenueKitSchema.safeParse(rawManifest);
14
+ * if (!result.success) {
15
+ * console.error('Invalid kit manifest:', result.error.format());
16
+ * }
17
+ * ```
18
+ */
19
+
20
+ import { z } from 'zod';
21
+
22
+ // ──────────────────────────────────────────────
23
+ // Shared base schemas
24
+ // ──────────────────────────────────────────────
25
+
26
+ /** Author metadata schema. */
27
+ const KitAuthorSchema = z.object({
28
+ name: z.string().min(1).max(200),
29
+ email: z.string().email().optional(),
30
+ url: z.string().url().optional()
31
+ });
32
+
33
+ /** Kit variable definition schema. */
34
+ const KitVariableSchema = z.object({
35
+ key: z.string().min(1).max(100).regex(/^[a-zA-Z][a-zA-Z0-9_]*$/),
36
+ label: z.string().min(1).max(200),
37
+ description: z.string().max(500).optional(),
38
+ type: z.enum(['string', 'email', 'url', 'phone', 'timezone', 'address', 'color', 'number']),
39
+ required: z.boolean().optional(),
40
+ example: z.string().max(200).optional(),
41
+ default: z.string().max(200).optional()
42
+ });
43
+
44
+ // ──────────────────────────────────────────────
45
+ // Venue kit schemas
46
+ // ──────────────────────────────────────────────
47
+
48
+ /** Venue theme configuration schema. */
49
+ const VenueKitThemeSchema = z.object({
50
+ primary: z.string().min(1),
51
+ bgBase: z.string().min(1),
52
+ accent: z.string().min(1).optional(),
53
+ textPrimary: z.string().min(1).optional(),
54
+ displayFont: z.string().max(100).optional(),
55
+ bodyFont: z.string().max(100).optional()
56
+ });
57
+
58
+ /** Feature flags schema — includes Venue-native flags and Workshop-published content flags. */
59
+ const VenueFeaturesSchema = z.object({
60
+ animals: z.boolean().optional(),
61
+ adoption: z.boolean().optional(),
62
+ memories: z.boolean().optional(),
63
+ loyalty: z.boolean().optional(),
64
+ giftCards: z.boolean().optional(),
65
+ waivers: z.boolean().optional(),
66
+ blog: z.boolean().optional(),
67
+ pos: z.boolean().optional(),
68
+ cms: z.boolean().optional(),
69
+ partners: z.boolean().optional(),
70
+ franchise: z.boolean().optional(),
71
+ customerAccounts: z.boolean().optional(),
72
+ /** Enable hosting of Workshop-published apps (Svelte component bundles). */
73
+ apps: z.boolean().optional(),
74
+ /** Enable hosting of Workshop-published documents. */
75
+ documents: z.boolean().optional(),
76
+ /** Enable hosting of Workshop-published visualizations. */
77
+ visualizations: z.boolean().optional(),
78
+ /** Enable WebSocket real-time features for Workshop apps hosted on this instance. */
79
+ realtime: z.boolean().optional(),
80
+ /** Enable push/email/SMS notifications via the notify prop of AppRuntimeContext. */
81
+ notifications: z.boolean().optional(),
82
+ /** Enable the public-facing website with CMS-managed pages, blog, and gallery. */
83
+ website: z.boolean().optional(),
84
+ /** Enable recurring subscription billing via Stripe Subscriptions. */
85
+ subscriptions: z.boolean().optional(),
86
+ /** Enable the customer support ticketing system. */
87
+ support: z.boolean().optional(),
88
+ /** Enable the built-in analytics pipeline (event tracking, funnel analysis, dashboards). */
89
+ analytics: z.boolean().optional(),
90
+ /** Enable outbound webhook delivery to external services (Zapier, Slack, custom HTTP). */
91
+ webhooks: z.boolean().optional(),
92
+ /** Enable API key management for third-party integrations and kiosk devices. */
93
+ apiKeys: z.boolean().optional(),
94
+ /** Enable the scheduled jobs system for time-based automation (cron tasks). */
95
+ scheduledJobs: z.boolean().optional()
96
+ });
97
+
98
+ /** Session attribute definition schema — declares what staff should log post-session. */
99
+ const SessionAttributeDefinitionSchema = z.object({
100
+ key: z.string().min(1).max(100).regex(/^[a-zA-Z][a-zA-Z0-9_]*$/),
101
+ label: z.string().min(1).max(200),
102
+ type: z.enum(['text', 'select', 'multiselect']),
103
+ perGuest: z.boolean(),
104
+ options: z.array(z.string().min(1).max(200)).optional(),
105
+ required: z.boolean().optional()
106
+ });
107
+
108
+ /** Experience type definition schema. */
109
+ const VenueExperienceTypeSchema = z.object({
110
+ slug: z.string().min(1).max(100).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
111
+ name: z.string().min(1).max(200),
112
+ description: z.string().min(1),
113
+ priceInCents: z.number().int().min(0),
114
+ durationMinutes: z.number().int().min(5),
115
+ minGuests: z.number().int().min(1),
116
+ maxGuests: z.number().int().min(1),
117
+ requiresWaiver: z.boolean(),
118
+ isCombo: z.boolean().optional(),
119
+ componentSlugs: z.array(z.string()).optional(),
120
+ sortOrder: z.number().int().min(0),
121
+ color: z.string().optional(),
122
+ imageUrl: z.string().url().optional(),
123
+ pricingTiers: z.array(z.object({
124
+ minGuests: z.number().int().min(1),
125
+ maxGuests: z.number().int().min(1),
126
+ pricePerPersonInCents: z.number().int().min(0)
127
+ })).optional(),
128
+ sessionAttributeDefinitions: z.array(SessionAttributeDefinitionSchema).optional()
129
+ });
130
+
131
+ /** Inventory category schema. */
132
+ const VenueInventoryCategorySchema = z.object({
133
+ id: z.string().min(1).max(100),
134
+ label: z.string().min(1).max(200),
135
+ icon: z.string().optional()
136
+ });
137
+
138
+ /** Staff role extension schema. */
139
+ const VenueStaffRoleSchema = z.object({
140
+ id: z.string().min(1).max(100),
141
+ label: z.string().min(1).max(200),
142
+ defaultCapabilities: z.array(z.string()).optional()
143
+ });
144
+
145
+ /** Chatbot suggestion chip schema. */
146
+ const VenueChatSuggestionSchema = z.object({
147
+ label: z.string().min(1).max(100),
148
+ message: z.string().min(1).max(500),
149
+ emoji: z.string().max(10).optional()
150
+ });
151
+
152
+ /** Pluggable back-office station schema. */
153
+ const VenueStationSchema = z.object({
154
+ id: z
155
+ .string()
156
+ .min(1)
157
+ .max(100)
158
+ .regex(/^[a-z0-9-]+$/, 'Station ID must be lowercase alphanumeric with hyphens'),
159
+ label: z.string().min(1).max(200),
160
+ route: z.string().min(1).max(500),
161
+ icon: z.string().min(1),
162
+ roles: z.array(z.enum(['staff', 'manager', 'owner'])).min(1),
163
+ scope: z.enum(['platform', 'kit']).optional(),
164
+ context: z.enum(['physical', 'digital', 'both']).optional(),
165
+ bundle: z.string().min(1).max(500).optional(),
166
+ group: z.string().min(1).max(100).optional()
167
+ });
168
+
169
+ /** Venue-specific config block schema. */
170
+ const VenueConfigSchema = z.object({
171
+ experienceTypes: z.array(VenueExperienceTypeSchema).default([]),
172
+ features: VenueFeaturesSchema,
173
+ theme: VenueKitThemeSchema.optional(),
174
+ inventoryCategories: z.array(VenueInventoryCategorySchema).optional(),
175
+ staffRoles: z.array(VenueStaffRoleSchema).optional(),
176
+ bookingNumberPrefix: z
177
+ .string()
178
+ .min(1)
179
+ .max(6)
180
+ .regex(/^[A-Z0-9]+$/)
181
+ .optional(),
182
+ subdomainPrefix: z
183
+ .string()
184
+ .min(1)
185
+ .max(30)
186
+ .regex(/^[a-z0-9-]+$/)
187
+ .optional(),
188
+ chatSuggestions: z.array(VenueChatSuggestionSchema).optional(),
189
+ inventoryLevelDetection: z.enum(['vision', 'manual', 'hybrid']).optional(),
190
+ stations: z.array(VenueStationSchema).optional()
191
+ });
192
+
193
+ /**
194
+ * Zod schema for validating a Venue kit manifest (`kit.json`).
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const parsed = VenueKitSchema.parse(rawJson);
199
+ * // parsed is typed as VenueKitConfig
200
+ * ```
201
+ */
202
+ export const VenueKitSchema = z
203
+ .object({
204
+ id: z
205
+ .string()
206
+ .min(1)
207
+ .max(100)
208
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
209
+ type: z.literal('venue'),
210
+ name: z.string().min(1).max(200),
211
+ description: z.string().min(1).max(500),
212
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
213
+ author: KitAuthorSchema,
214
+ minPlatformVersion: z.string().optional(),
215
+ tags: z.array(z.string()).optional(),
216
+ previewImageUrl: z.string().url().optional(),
217
+ role: z.enum(['primary', 'extension']).optional(),
218
+ requires: z.array(z.string().min(1).max(100)).optional(),
219
+ conflicts: z.array(z.string().min(1).max(100)).optional(),
220
+ variables: z.array(KitVariableSchema),
221
+ venue: VenueConfigSchema
222
+ })
223
+ .strict();
224
+
225
+ /** Inferred TypeScript type from the Venue kit schema. */
226
+ export type VenueKitInput = z.infer<typeof VenueKitSchema>;
227
+
228
+ // ──────────────────────────────────────────────
229
+ // Workshop kit schemas
230
+ // ──────────────────────────────────────────────
231
+
232
+ const WorkshopExploreViewSchema = z.enum([
233
+ 'graph', 'mindmap', 'timeline', 'board', 'matrix',
234
+ 'gallery', 'calendar', 'tree', 'stats', 'map', 'dev', 'app'
235
+ ]);
236
+
237
+ const WorkshopWorkspaceParadigmSchema = z.enum([
238
+ 'writer', 'researcher', 'planner', 'developer', 'creative', 'analyst', 'builder', 'custom'
239
+ ]);
240
+
241
+ const WorkshopWorkspaceConfigSchema = z.object({
242
+ paradigm: WorkshopWorkspaceParadigmSchema.optional(),
243
+ defaultTab: z.enum(['edit', 'explore', 'app']).optional(),
244
+ defaultView: WorkshopExploreViewSchema.optional(),
245
+ defaultFile: z.string().optional(),
246
+ fallbackFiles: z.array(z.string()).optional()
247
+ }).optional();
248
+
249
+ const WorkshopAIPersonaSchema = z.object({
250
+ role: z.string().min(1),
251
+ expertise: z.array(z.string().min(1)),
252
+ tone: z.enum(['mentor', 'editor', 'collaborator', 'assistant']),
253
+ avoidances: z.array(z.string()).optional()
254
+ }).optional();
255
+
256
+ const WorkshopDomainKnowledgeSchema = z.object({
257
+ glossary: z.record(z.string(), z.string()).optional(),
258
+ conventions: z.array(z.string()).optional(),
259
+ antiPatterns: z.array(z.string()).optional(),
260
+ formatting: z.array(z.string()).optional(),
261
+ references: z.array(z.object({
262
+ title: z.string().min(1),
263
+ description: z.string().min(1),
264
+ url: z.string().url().optional()
265
+ })).optional()
266
+ }).optional();
267
+
268
+ const WorkshopWorkflowSchema = z.object({
269
+ id: z.string().min(1).max(100),
270
+ name: z.string().min(1).max(200),
271
+ description: z.string().min(1),
272
+ steps: z.array(z.string().min(1)).min(1),
273
+ inputs: z.array(z.object({
274
+ name: z.string().min(1),
275
+ description: z.string().min(1),
276
+ required: z.boolean().optional()
277
+ })).optional(),
278
+ outputs: z.array(z.string()).optional(),
279
+ triggers: z.array(z.string()).optional(),
280
+ autoExecute: z.boolean().optional()
281
+ });
282
+
283
+ const WorkshopGraphGuidanceSchema = z.object({
284
+ conceptCreation: z.object({
285
+ triggers: z.array(z.string()).optional(),
286
+ typeMapping: z.record(z.string(), z.string())
287
+ }).optional(),
288
+ relationshipPatterns: z.array(z.object({
289
+ from: z.string().min(1),
290
+ to: z.string().min(1),
291
+ verb: z.string().min(1),
292
+ description: z.string().min(1)
293
+ })).optional(),
294
+ rules: z.array(z.string()).optional()
295
+ }).optional();
296
+
297
+ const WorkshopQualityGatesSchema = z.object({
298
+ fileChecks: z.array(z.object({
299
+ path: z.string().min(1),
300
+ required: z.array(z.string()).optional(),
301
+ optional: z.array(z.string()).optional()
302
+ })).optional(),
303
+ projectChecks: z.array(z.string()).optional(),
304
+ exportReady: z.array(z.string()).optional()
305
+ }).optional();
306
+
307
+ const WorkshopExporterSchema = z.object({
308
+ id: z.string().min(1).max(100),
309
+ name: z.string().min(1).max(200),
310
+ description: z.string().min(1),
311
+ icon: z.string().min(1),
312
+ format: z.string().min(1).max(20),
313
+ category: z.enum([
314
+ 'manuscript', 'ebook', 'archive', 'web', 'document', 'data', 'presentation', 'other'
315
+ ]),
316
+ handler: z.string().min(1)
317
+ });
318
+
319
+ const WorkshopExportPreferencesSchema = z.object({
320
+ defaultExporter: z.string().optional(),
321
+ fileNameTemplate: z.string().optional(),
322
+ instructions: z.string().optional(),
323
+ globalPrerequisites: z.array(z.string()).optional()
324
+ }).optional();
325
+
326
+ const WorkshopExportStructureSchema = z.object({
327
+ combineStrategy: z.enum(['graph-order', 'path-order', 'manual']),
328
+ orderRelationship: z.string().optional(),
329
+ includePatterns: z.array(z.string()).optional(),
330
+ excludePatterns: z.array(z.string()).optional(),
331
+ fileOrder: z.array(z.string()).optional()
332
+ }).optional();
333
+
334
+ /**
335
+ * Cross-product suggestion chip schema.
336
+ * Used in `SharedKitFoundationSchema.suggestions` and (deprecated) `WorkshopConfigSchema.suggestions`.
337
+ */
338
+ const KitSuggestionSchema = z.object({
339
+ label: z.string().min(1).max(50),
340
+ prompt: z.string().min(1).max(500)
341
+ });
342
+
343
+ /** @deprecated Suggestions moved to `SharedKitFoundationSchema.suggestions`. Alias preserved for backward compat. */
344
+ const WorkshopSuggestionSchema = KitSuggestionSchema;
345
+
346
+ const WorkshopPublishConfigSchema = z.object({
347
+ contentType: z.enum(['app', 'document', 'visualization', 'slideshow', 'static']),
348
+ publishable: z.boolean().optional(),
349
+ defaultSlug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).optional()
350
+ }).optional();
351
+
352
+ /** Template identity metadata schema. */
353
+ const WorkshopTemplateMetadataSchema = z.object({
354
+ identifyByStructure: z.boolean().optional(),
355
+ runnable: z.boolean().optional(),
356
+ runnableCommand: z.string().max(500).optional()
357
+ }).optional();
358
+
359
+ /** Cloud deployment configuration schema. */
360
+ const WorkshopDeployConfigSchema = z.object({
361
+ provider: z.string().max(100).optional(),
362
+ buildCommand: z.string().max(500).optional(),
363
+ outputDir: z.string().max(500).optional(),
364
+ config: z.record(z.string(), z.unknown()).optional(),
365
+ oneClickCommand: z.string().max(500).optional()
366
+ }).optional();
367
+
368
+ /** Sample data entity schema. */
369
+ const WorkshopSampleEntitySchema = z.object({
370
+ type: z.string().min(1).max(100),
371
+ name: z.string().min(1).max(500),
372
+ properties: z.record(z.string(), z.unknown()).optional()
373
+ });
374
+
375
+ /** Sample data schema for pre-populating a new project's knowledge graph. */
376
+ const WorkshopSampleDataSchema = z.object({
377
+ description: z.string().max(1000).optional(),
378
+ entities: z.array(WorkshopSampleEntitySchema).min(1),
379
+ relationships: z.array(z.object({
380
+ from: z.string().min(1).max(500),
381
+ verb: z.string().min(1).max(100),
382
+ to: z.string().min(1).max(500)
383
+ })).optional()
384
+ }).optional();
385
+
386
+ /** Connected workbench definition schema for Workshop remote sessions. */
387
+ const WorkshopConnectedWorkbenchSchema = z.object({
388
+ name: z.string().min(1).max(200),
389
+ view: z.enum(['graph', 'mindmap', 'tree', 'timeline', 'board', 'gallery', 'stats', 'matrix', 'calendar']),
390
+ entityTypes: z.array(z.string().min(1).max(100)).min(1),
391
+ description: z.string().max(500).optional()
392
+ });
393
+
394
+ /** Remote workspace configuration schema for Workshop remote sessions. */
395
+ const WorkshopRemoteWorkspaceSchema = z.object({
396
+ connectedWorkbenches: z.array(WorkshopConnectedWorkbenchSchema).optional()
397
+ }).optional();
398
+
399
+ /** Workshop-specific config block schema. */
400
+ const WorkshopConfigSchema = z
401
+ .object({
402
+ workspaceConfig: WorkshopWorkspaceConfigSchema,
403
+ graphGuidance: WorkshopGraphGuidanceSchema,
404
+ qualityGates: WorkshopQualityGatesSchema,
405
+ publishConfig: WorkshopPublishConfigSchema,
406
+ aiPersona: WorkshopAIPersonaSchema,
407
+ domainKnowledge: WorkshopDomainKnowledgeSchema,
408
+ workflows: z.array(WorkshopWorkflowSchema).optional(),
409
+ exporters: z.array(WorkshopExporterSchema).optional(),
410
+ exportPreferences: WorkshopExportPreferencesSchema,
411
+ exportStructure: WorkshopExportStructureSchema,
412
+ remoteWorkspace: WorkshopRemoteWorkspaceSchema,
413
+ difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
414
+ templateTier: z.number().int().min(1).max(5).optional(),
415
+ templateMetadata: WorkshopTemplateMetadataSchema,
416
+ deploy: WorkshopDeployConfigSchema,
417
+ sampleData: WorkshopSampleDataSchema
418
+ })
419
+ .optional();
420
+
421
+ /**
422
+ * Zod schema for validating a Workshop kit manifest.
423
+ */
424
+ export const WorkshopKitSchema = z
425
+ .object({
426
+ id: z
427
+ .string()
428
+ .min(1)
429
+ .max(100)
430
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
431
+ type: z.enum(['content', 'app']),
432
+ name: z.string().min(1).max(200),
433
+ description: z.string().min(1).max(500),
434
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
435
+ author: KitAuthorSchema,
436
+ minPlatformVersion: z.string().optional(),
437
+ tags: z.array(z.string()).optional(),
438
+ previewImageUrl: z.string().url().optional(),
439
+ role: z.enum(['primary', 'extension']).optional(),
440
+ requires: z.array(z.string().min(1).max(100)).optional(),
441
+ conflicts: z.array(z.string().min(1).max(100)).optional(),
442
+ variables: z.array(KitVariableSchema).optional(),
443
+ workshop: WorkshopConfigSchema
444
+ })
445
+ .strict();
446
+
447
+ /** Inferred TypeScript type from the Workshop kit schema. */
448
+ export type WorkshopKitInput = z.infer<typeof WorkshopKitSchema>;
449
+
450
+ // ──────────────────────────────────────────────
451
+ // Academy kit schema (reserved placeholder)
452
+ // ──────────────────────────────────────────────
453
+
454
+ /**
455
+ * Zod schema for validating an Academy kit manifest (reserved placeholder).
456
+ * The `academy` config block is intentionally permissive to allow future fields
457
+ * without breaking schema validation as the product design evolves.
458
+ */
459
+ export const AcademyKitSchema = z
460
+ .object({
461
+ id: z
462
+ .string()
463
+ .min(1)
464
+ .max(100)
465
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
466
+ type: z.literal('academy'),
467
+ name: z.string().min(1).max(200),
468
+ description: z.string().min(1).max(500),
469
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
470
+ author: KitAuthorSchema,
471
+ minPlatformVersion: z.string().optional(),
472
+ tags: z.array(z.string()).optional(),
473
+ previewImageUrl: z.string().url().optional(),
474
+ variables: z.array(KitVariableSchema).optional(),
475
+ academy: z.record(z.string(), z.unknown()).optional()
476
+ })
477
+ .strict();
478
+
479
+ /** Inferred TypeScript type from the Academy kit schema. */
480
+ export type AcademyKitInput = z.infer<typeof AcademyKitSchema>;
481
+
482
+ // ──────────────────────────────────────────────
483
+ // Publish protocol schemas (Workshop → Venue)
484
+ // ──────────────────────────────────────────────
485
+
486
+ const DocumentPublishContentSchema = z.object({
487
+ body: z.string().min(1),
488
+ mimeType: z.enum(['text/markdown', 'text/html']).optional()
489
+ });
490
+
491
+ const VisualizationPublishContentSchema = z.object({
492
+ graph: z.object({
493
+ nodes: z.array(z.object({
494
+ id: z.string().min(1),
495
+ label: z.string(),
496
+ type: z.string(),
497
+ metadata: z.record(z.string(), z.unknown()).optional()
498
+ })),
499
+ edges: z.array(z.object({
500
+ id: z.string().min(1),
501
+ from: z.string().min(1),
502
+ to: z.string().min(1),
503
+ type: z.string(),
504
+ metadata: z.record(z.string(), z.unknown()).optional()
505
+ }))
506
+ }),
507
+ viewType: WorkshopExploreViewSchema.optional(),
508
+ config: z.record(z.string(), z.unknown()).optional()
509
+ });
510
+
511
+ const SlideshowPublishContentSchema = z.object({
512
+ slides: z.array(z.string()).min(1),
513
+ config: z.record(z.string(), z.unknown()).optional()
514
+ });
515
+
516
+ const StaticPublishContentSchema = z.object({
517
+ files: z.record(z.string(), z.string()),
518
+ entryPoint: z.string().min(1)
519
+ });
520
+
521
+ const AppPublishContentSchema = z.object({
522
+ bundle: z.string().min(1),
523
+ sourceMap: z.string().optional(),
524
+ exportName: z.string().optional()
525
+ });
526
+
527
+ const PublishPayloadBaseSchema = z.object({
528
+ kitId: z.string().min(1),
529
+ title: z.string().min(1).max(500),
530
+ description: z.string().max(2000).optional(),
531
+ slug: z.string().min(1).max(100).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
532
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
533
+ sourceWorkshopUserId: z.string().min(1),
534
+ sourceWorkspaceId: z.string().min(1),
535
+ deployedAt: z.string().datetime(),
536
+ /**
537
+ * Optional theme catalog ID from `@soulcraft/theme` used to render this content.
538
+ * Workshop stores the user's chosen theme here; Venue calls `resolveTheme({ userThemeId })`
539
+ * when serving the deployment. `null` means "use the Venue instance's kit theme".
540
+ */
541
+ themeId: z.string().nullable().optional()
542
+ });
543
+
544
+ /**
545
+ * Zod schema for validating a Workshop-to-Venue publish payload.
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * const result = PublishPayloadSchema.safeParse(payload);
550
+ * if (!result.success) {
551
+ * console.error('Invalid publish payload:', result.error.format());
552
+ * }
553
+ * ```
554
+ */
555
+ export const PublishPayloadSchema = z.discriminatedUnion('contentType', [
556
+ PublishPayloadBaseSchema.extend({
557
+ contentType: z.literal('document'),
558
+ content: DocumentPublishContentSchema
559
+ }),
560
+ PublishPayloadBaseSchema.extend({
561
+ contentType: z.literal('visualization'),
562
+ content: VisualizationPublishContentSchema
563
+ }),
564
+ PublishPayloadBaseSchema.extend({
565
+ contentType: z.literal('slideshow'),
566
+ content: SlideshowPublishContentSchema
567
+ }),
568
+ PublishPayloadBaseSchema.extend({
569
+ contentType: z.literal('static'),
570
+ content: StaticPublishContentSchema
571
+ }),
572
+ PublishPayloadBaseSchema.extend({
573
+ contentType: z.literal('app'),
574
+ content: AppPublishContentSchema
575
+ })
576
+ ]);
577
+
578
+ /** Inferred TypeScript type from the publish payload schema. */
579
+ export type PublishPayloadInput = z.infer<typeof PublishPayloadSchema>;
580
+
581
+ // ──────────────────────────────────────────────
582
+ // Unified Soulcraft kit schema
583
+ // ──────────────────────────────────────────────
584
+
585
+ /** Per-product annotations for a shared data model entity. */
586
+ const SharedDataModelEntityProductsSchema = z.object({
587
+ workshop: z.object({
588
+ view: z.string().max(50).optional(),
589
+ icon: z.string().max(10).optional()
590
+ }).optional(),
591
+ venue: z.object({
592
+ mapsTo: z.string().max(100).optional(),
593
+ pos: z.boolean().optional(),
594
+ trackStock: z.boolean().optional()
595
+ }).optional(),
596
+ academy: z.object({
597
+ mapsTo: z.string().max(100).optional(),
598
+ assessable: z.boolean().optional()
599
+ }).optional()
600
+ }).optional();
601
+
602
+ /** Domain entity declaration schema. */
603
+ const SharedDataModelEntitySchema = z.object({
604
+ domain: z.string().min(1).max(100),
605
+ type: z.string().min(1).max(100),
606
+ description: z.string().min(1).max(500),
607
+ products: SharedDataModelEntityProductsSchema
608
+ });
609
+
610
+ /** Domain relationship declaration schema. */
611
+ const SharedDataModelRelationshipSchema = z.object({
612
+ from: z.string().min(1).max(100),
613
+ to: z.string().min(1).max(100),
614
+ verb: z.string().min(1).max(100),
615
+ description: z.string().min(1).max(500),
616
+ products: z.object({
617
+ venue: z.object({ triggers: z.string().max(100).optional() }).optional(),
618
+ academy: z.object({ creates: z.string().max(100).optional() }).optional()
619
+ }).optional()
620
+ });
621
+
622
+ /** Unified domain model block schema. */
623
+ const SharedDataModelSchema = z.object({
624
+ entities: z.array(SharedDataModelEntitySchema).optional(),
625
+ relationships: z.array(SharedDataModelRelationshipSchema).optional()
626
+ }).optional();
627
+
628
+ /** Cross-product shared foundation schema. */
629
+ const SharedKitFoundationSchema = z.object({
630
+ industry: z.string().min(1).max(100),
631
+ category: z.string().min(1).max(100).optional(),
632
+ glossary: z.record(z.string(), z.string()).optional(),
633
+ aiExpertise: z.array(z.string().min(1).max(200)).optional(),
634
+ dataModelHints: z.array(z.string().min(1).max(100)).optional(),
635
+ suggestions: z.array(KitSuggestionSchema).optional(),
636
+ dataModel: SharedDataModelSchema
637
+ }).optional();
638
+
639
+ /** Base fields common to all kit manifest types, for reuse in discriminated union entries. */
640
+ const BaseKitManifestFields = {
641
+ $schema: z.string().optional(),
642
+ id: z.string().min(1).max(100).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
643
+ name: z.string().min(1).max(200),
644
+ description: z.string().min(1).max(500),
645
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
646
+ author: KitAuthorSchema,
647
+ status: z.enum(['released', 'beta', 'draft']).optional(),
648
+ icon: z.string().max(10).optional(),
649
+ longDescription: z.string().optional(),
650
+ allowedUsers: z.array(z.string().min(1).max(500)).optional(),
651
+ minPlatformVersion: z.string().optional(),
652
+ tags: z.array(z.string()).optional(),
653
+ previewImageUrl: z.string().url().optional(),
654
+ role: z.enum(['primary', 'extension']).optional(),
655
+ requires: z.array(z.string().min(1).max(100)).optional(),
656
+ conflicts: z.array(z.string().min(1).max(100)).optional()
657
+ } as const;
658
+
659
+ /**
660
+ * Zod schema for validating a unified Soulcraft kit manifest (`type: 'soulcraft'`).
661
+ *
662
+ * All product sections (`venue`, `workshop`, `academy`) are optional — a kit
663
+ * may target only one product or all three. The `shared` block provides cross-product
664
+ * intelligence (glossary, AI expertise) consumed by all products.
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * const parsed = SoulcraftKitSchema.parse(rawJson);
669
+ * // parsed is typed as SoulcraftKitConfig
670
+ * ```
671
+ */
672
+ export const SoulcraftKitSchema = z
673
+ .object({
674
+ ...BaseKitManifestFields,
675
+ type: z.literal('soulcraft'),
676
+ variables: z.array(KitVariableSchema).optional(),
677
+ shared: SharedKitFoundationSchema,
678
+ venue: VenueConfigSchema.optional(),
679
+ workshop: WorkshopConfigSchema,
680
+ academy: z.record(z.string(), z.unknown()).optional()
681
+ })
682
+ .strict();
683
+
684
+ /** Inferred TypeScript type from the Soulcraft unified kit schema. */
685
+ export type SoulcraftKitInput = z.infer<typeof SoulcraftKitSchema>;
686
+
687
+ /**
688
+ * Discriminated union schema that validates any Soulcraft kit manifest.
689
+ *
690
+ * Selects the appropriate schema based on the `type` field:
691
+ * - `"venue"` → {@link VenueKitSchema}
692
+ * - `"content"` | `"app"` → {@link WorkshopKitSchema}
693
+ * - `"academy"` → {@link AcademyKitSchema}
694
+ * - `"soulcraft"` → {@link SoulcraftKitSchema}
695
+ */
696
+ export const KitManifestSchema = z.discriminatedUnion('type', [
697
+ VenueKitSchema,
698
+ WorkshopKitSchema.extend({ type: z.literal('content') }),
699
+ WorkshopKitSchema.extend({ type: z.literal('app') }),
700
+ AcademyKitSchema,
701
+ SoulcraftKitSchema
702
+ ]);
703
+
704
+ /** Inferred TypeScript type from the kit manifest union schema. */
705
+ export type KitManifestInput = z.infer<typeof KitManifestSchema>;