/* Zod schema for RFC-0072 section archetype catalog entries stored under packages/werkstatt-site/src/domain/ontology/archetypes/sections/ recursive YAML catalog files. Do not parse YAML here. Do not resolve imports or generate files here. RFC-0072: Initial archetype catalog contract. */ import { z } from "zod"; import { cosmicNameSchema } from "../cosmic/index.ts"; import { industrySchema } from "../enums.ts"; const archetypeIdSchema = z .string() .regex( /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/, "archetype id must be kebab-case segments separated by dots", ); const layoutHintSchema = z.enum([ "single-column", "split", "card-grid", "decision-card", "horizontal-row", "stacked-bands", ]); /** * RFC-0103: closed enum of canonical body kinds. Each non-composite archetype * declares which body component renders authored content (list / split-list / * stats / cards / paragraphs / comparison / rich). Composite archetypes (hero, * hero-decision-card, founder-trust-card, donation-card, price-card, faq-list, * markdown) declare `bodyKind: composite` and own their bespoke layout. */ const archetypeBodyKindSchema = z.enum([ "list", "split-list", "stats", "cards", "paragraphs", "comparison", "rich", "composite", ]); const recommendedItemCountSchema = z .object({ min: z.number().int().nonnegative().optional(), max: z.number().int().nonnegative().optional(), }) .strict(); /** * RFC-0101 + RFC-0102 + RFC-0103: archetype propsSchema accepts either * (a) a `shape:` string with a Zod / JSON Schema expression (legacy form), or * (b) a `compose: [...]` list of shared-section-props fragment ids that the * runtime resolves through composeManifestPropsSchema(). * Both forms may be combined when the archetype needs to add section-specific * fields on top of the shared fragments — see transparency.yaml. */ const propsSchemaFragmentSchema = z .object({ $shape: z.enum(["zod", "json-schema"]).default("zod"), shape: z.string().min(1).optional(), compose: z .array( z .string() .regex(/^[a-z][a-z0-9-]*(@\d+)?$/, "must match `` or `@` (RFC-0119)"), ) .optional(), }) .refine((v) => Boolean(v.shape) || Boolean(v.compose && v.compose.length > 0), { message: "propsSchema must declare `shape` or non-empty `compose` (or both)", }); export const sectionArchetypeSchema = z.object({ id: archetypeIdSchema, displayName: z.string().min(1), version: z.string().regex(/^\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?$/i, "version must be valid semver"), semanticRole: z.string().min(1), description: z.string().min(1), expectedIntents: z.array(z.string().min(1)).min(1), expectedIndustryFit: z.array(industrySchema).default([]), layoutHint: layoutHintSchema, /** RFC-0103: required for non-composite archetypes; composite signals bespoke layout. */ bodyKind: archetypeBodyKindSchema.optional(), /** RFC-0072: advisory min/max number of body items the archetype expects. */ recommendedItemCount: recommendedItemCountSchema.optional(), propsSchema: propsSchemaFragmentSchema, acceptedCosmicNames: z.array(cosmicNameSchema).min(1), constraints: z.record(z.string(), z.unknown()).default({}), }); export type ArchetypeBodyKind = z.infer; export type RecommendedItemCount = z.infer; export type SectionArchetypeContract = z.infer; export type SectionArchetypeId = z.infer; export type SectionArchetypeLayoutHint = z.infer;