/* Zod model of the werkstatt:intake/v1 hand-over envelope (RFC-1281, spec werkstatt-onboarding ON-01 / INTAKE-CONTRACT, AMD-001 §1) — the single producer → Werkstatt contract. Owns every envelope field of the three normative v0.1.0 spec schemas; pbp[] entities are checked at envelope level only (schema allow-list, id, type, status). Also renders the JSON Schema export that producers vendor byte-exact (intake.schema.generated.yaml, owner command onboarding.intake.schema.export). Do not redeclare PBP entity shapes (DNA-103) — full entity validation runs in @warpgogol/werkstatt-engine via pbpSchemaById (INTAKE-03); this package cannot import @warpgogol/pbp without a dependency cycle. Do not touch the filesystem or run bundle-level checks — INTAKE-01…11 live in @warpgogol/werkstatt-engine. pbp[] entities are checked at envelope level only Entity JSON Schemas are injected by the engine (RFC-1301) as $defs of the same export Zod is the source of truth (spec D2) RFC-1281: created — envelope Zod model of werkstatt:intake/v1 (envelope, gap, asset) + renderIntakeSchemaExport(). RFC-1281: intake contract and onboarding.intake.validate RFC-1301: export PBP entity JSON Schemas in werkstatt:intake/v1 (RFC-1301) RFC-1298: REVIEW-CODE-2026-10-16-01 findings — kebab flag guards, safeParse, reads declaration, family-append ordering, FONTS-SCRIPT-02 unverifiable-stack warning RFC-1328: review fix pass: envelope fields in vendored schema defs, locale merge prose guard, manifest defaultLocale threading F-1 blocking: inject statementByLocale/localeNeutral into every exported pbp def so producers can self-validate AMD-005 bundles. F-2/F-3: LOCALE_SCOPED_FIELDS never merge onto existing locale docs; defaultLocale param replaces the hardcoded de across semantic profile, currency, referral capacity. F-4..F-10: statementLang rewrite, prose drops on clones/variants, INTAKE-12 name+literals, currency-policy gate, predicate signature, doc drift. Unblocks dna-trace WS-WRITE-02 false positive (_io naming). RFC-1328 */ import { z } from "zod"; import YAML from "yaml"; export const INTAKE_SCHEMA_ID = "werkstatt:intake/v1" as const; /** * v1 allow-list of PBP schema ids an intake pbp[] entry may carry * (spec: pbpEntity.schema enum). Widening needs a spec amendment. */ export const INTAKE_PBP_SCHEMA_ALLOWLIST = [ "pbp/business@1", "pbp/legal-identity@1", "pbp/offering@1", "pbp/claim@1", "pbp/evidence-source@1", "pbp/disclosure@1", "pbp/contact-point@1", "pbp/brand@1", ] as const; // --------------------------------------------------------------------------- // $defs (byte-identical patterns to docs/specs/werkstatt-onboarding/schemas/) // --------------------------------------------------------------------------- const bcp47Tag = z .string() .regex(/^[a-z]{2,3}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?$/); const sha256Digest = z.string().regex(/^sha256:[0-9a-f]{64}$/); /** Evidence-source PBP entity id backing a statement. */ const sourceRef = z.string().min(1); const verbatimSchema = z .object({ /** Byte-exact substring of the referenced source file (INTAKE-05). */ text: z.string().min(1), sourceRef, speaker: z.enum(["client", "studio", "third-party"]).optional(), }) .strict(); // --------------------------------------------------------------------------- // classification // --------------------------------------------------------------------------- const classificationLocaleSchema = z .object({ tag: bcp47Tag, role: z.enum(["primary", "secondary"]), sourceLanguageBy: z.enum(["client", "studio", "translation"]), reviewer: z.enum(["client", "studio", "external", "unassigned"]), evidenceRefs: z.array(sourceRef).optional(), }) .strict(); const classificationSchema = z .object({ locales: z.array(classificationLocaleSchema).min(1), jurisdiction: z.string().regex(/^[A-Z]{2}$/), markets: z .array( z .object({ market: z.string(), confidence: z.enum(["high", "medium", "low", "unknown"]), }) .strict(), ) .optional(), audiences: z.array(z.string()).optional(), conversionModel: z.enum([ "information-only", "inquiry", "booking", "donation", "ecommerce", "referral", ]), hasEcommerce: z.boolean().optional(), existingSite: z.union([z.string(), z.null()]).optional(), domain: z.union([z.string().regex(/^([a-z0-9-]+\.)+[a-z]{2,}$/), z.null()]).optional(), profile: z.enum(["visitka", "standard", "asset-grade"]), pageSetIntent: z .array( z .object({ intent: z.string(), evidenceRefs: z.array(sourceRef), }) .strict(), ) .min(1), materialMentions: z.array(z.string()).optional(), }) .strict(); // --------------------------------------------------------------------------- // pbp[] entities — envelope-level check only (DNA-103, AMD-001 §1) // --------------------------------------------------------------------------- export const intakePbpEntitySchema = z.looseObject({ schema: z.enum(INTAKE_PBP_SCHEMA_ALLOWLIST), id: z.string().min(1), type: z.string(), status: z.enum(["draft", "published", "superseded"]), /** * AMD-005/RFC-1328: per-locale value of the entity's statement field * (`statement`, else `summary`) for published entities — keyed by the * BCP 47 tags of `classification.locales`. The root statement field is * the primary-locale value; this map covers the rest. Optional — an * entity without it is either `localeNeutral` or INTAKE-13. */ statementByLocale: z.record(bcp47Tag, z.string().min(1)).optional(), /** * AMD-005/RFC-1328: the statement is a name/identifier/address — the same * string is correct in every locale (INTAKE-13 exempt). */ localeNeutral: z.boolean().optional(), }); // --------------------------------------------------------------------------- // annotations / operatorDecisions // --------------------------------------------------------------------------- const annotationSchema = z .object({ verbatim: z.array(verbatimSchema).optional(), /** Gap whose closure publishes this draft entity (INTAKE-06). */ gapRef: z .string() .regex(/^GAP-[0-9]{2,}$/) .optional(), /** Composer context only — never publishable (D10 gate). */ internal: z.boolean().optional(), /** Originating evidence-ledger record id (e.g. CLM-023). */ ledgerRef: z.string().optional(), }) .strict(); const operatorDecisionSchema = z .object({ id: z.string().regex(/^(OPD|DEF)-[0-9]{2,}$/), kind: z.enum(["ruling", "default", "gate-verdict"]), gate: z.string(), question: z.string(), recommendation: z.string().optional(), answer: z.string(), answeredAt: z.iso.datetime().optional(), reversible: z.boolean().optional(), }) .strict(); // --------------------------------------------------------------------------- // voice // --------------------------------------------------------------------------- const voiceVerbatimSchema = z .object({ id: z.string().regex(/^VOI-[0-9]{2,}$/), text: z.string(), sourceRef, use: z.string(), claimRefs: z.array(z.string()).optional(), }) .strict(); const voiceDirectiveSchema = z .object({ id: z.string().regex(/^DIR-[0-9]{2,}$/), text: z.string(), sourceRef, appliesTo: z.array(z.enum(["composition", "publication", "visual", "research"])), }) .strict(); const voiceProfileSchema = z .object({ toneOfVoice: z .object({ tone: z.string(), posture: z.string(), readingLevel: z.string(), avoids: z.array(z.string()), prefers: z.array(z.string()), }) .strict(), /** Rules per BCP 47 locale tag (spec: propertyNames bcp47). */ linguisticRules: z.record(bcp47Tag, z.array(z.string())), culturalRules: z.array(z.string()), }) .strict(); const voiceSchema = z .object({ verbatim: z.array(voiceVerbatimSchema), directives: z.array(voiceDirectiveSchema), profile: voiceProfileSchema, }) .strict(); // --------------------------------------------------------------------------- // visual — axes keys are the biomeAxesSchema key set; the live schema check // is INTAKE-08 in the engine (spec delegates the shape to biomeAxesSchema). // --------------------------------------------------------------------------- const INTAKE_VISUAL_AXES_KEYS = [ "warmth", "contrast", "density", "typographySharpness", "diagramPresence", "photoStance", "motionStance", "textContrast", "cornerRadius", "borderWeight", ] as const; const visualSchema = z .object({ conceptId: z.string(), rejectedConceptIds: z.array(z.string()).optional(), axes: z.looseObject( Object.fromEntries(INTAKE_VISUAL_AXES_KEYS.map((key) => [key, z.unknown()])) as Record< (typeof INTAKE_VISUAL_AXES_KEYS)[number], z.ZodUnknown >, ), palette: z.record(z.string(), z.string()), fonts: z .array( z .object({ family: z.string(), role: z.enum(["heading", "body", "accent", "mono"]), source: z.string(), scripts: z .array(z.string().regex(/^[A-Z][a-z]{3}$/)) .min(1), }) .strict(), ) .min(1), moodAssetRefs: z .array(z.string().regex(/^AST-[0-9]{2,}$/)) .optional(), rationale: z.string(), }) .strict(); // --------------------------------------------------------------------------- // provenance // --------------------------------------------------------------------------- const provenanceInputSchema = z .object({ path: z.string(), sha256: sha256Digest, bytes: z.number().int().min(0), /** MUST be false (spec P1); present to make clipping auditable. */ clipped: z.literal(false).optional(), }) .strict(); const provenanceSchema = z .object({ producer: z.string(), producerVersion: z.string(), runId: z.string(), /** SHA-256 of the vendored werkstatt:intake/v1 JSON Schema export. */ contractSnapshot: sha256Digest, inputs: z.array(provenanceInputSchema).min(1), evidenceLedger: sha256Digest, models: z.array( z .object({ step: z.string(), model: z.string(), effort: z.enum(["low", "medium", "high", "xhigh", "max", "n/a"]), }) .strict(), ), quoteCheck: z .object({ result: z.enum(["pass", "fail"]), checked: z.number().int().min(0), failed: z.literal(0), }) .strict(), cost: z .object({ currency: z.literal("USD"), total: z.number().min(0), reportPath: z.literal("cost-report.json"), }) .strict(), }) .strict(); // --------------------------------------------------------------------------- // intake-gap.schema.yaml — the two allOf/if-then rules become superRefine. // --------------------------------------------------------------------------- export const intakeGapSchema = z .object({ id: z.string().regex(/^GAP-[0-9]{2,}$/), what: z.string().min(1), why: z.string().min(1), obtainableBy: z.enum(["client", "studio", "either"]), /** Required when obtainableBy is `client` or `either` (spec allOf). */ itemType: z.enum(["file", "text", "choice", "approval"]).optional(), /** Required when itemType is `choice` (spec allOf). */ choices: z.array(z.string()).optional(), clientPrompt: z.string().optional(), defaultIfSilent: z.string().min(1), /** PBP entity ids whose publication waits on this gap (may be empty). */ blocks: z.array(z.string()), severity: z.enum(["launch-blocking", "post-launch"]).default("post-launch"), ledgerRefs: z.array(z.string()).optional(), evidenceRefs: z.array(z.string()).optional(), }) .strict() .superRefine((gap, ctx) => { if ((gap.obtainableBy === "client" || gap.obtainableBy === "either") && !gap.itemType) { ctx.addIssue({ code: "custom", message: "itemType is required when obtainableBy is 'client' or 'either'", path: ["itemType"], }); } if (gap.itemType === "choice" && !gap.choices) { ctx.addIssue({ code: "custom", message: "choices is required when itemType is 'choice'", path: ["choices"], }); } }); // --------------------------------------------------------------------------- // intake-asset.schema.yaml — the two allOf/if-then rules become superRefine. // --------------------------------------------------------------------------- export const intakeAssetSchema = z .object({ id: z.string().regex(/^AST-[0-9]{2,}$/), kind: z.enum([ "logo", "photo", "product-photo", "illustration", "mood", "video", "document", "icon", ]), file: z.string().regex(/^assets\/[0-9a-f]{64}\.[a-z0-9]+$/), sha256: sha256Digest, mediaType: z.string(), width: z.number().int().min(1).optional(), height: z.number().int().min(1).optional(), origin: z.enum(["client", "generated", "stock", "studio"]), rights: z .object({ state: z.enum([ "cleared", "client-supplied", "generated", "licensed-stock", "claimed", "unknown", ]), holder: z.string().optional(), /** Mandatory for licensed-stock (spec allOf). */ licence: z.string().optional(), licenceUrl: z.url().optional(), evidenceRef: z.string().optional(), gapRef: z .string() .regex(/^GAP-[0-9]{2,}$/) .optional(), restrictions: z.array(z.string()).optional(), }) .strict(), /** Mandatory for origin `generated` (spec allOf, INTAKE-09). */ generator: z .object({ model: z.string(), promptSha256: sha256Digest, conceptId: z.string().optional(), generatedAt: z.iso.datetime(), }) .strict() .optional(), altText: z.record(z.string(), z.string()).optional(), subject: z.string().optional(), }) .strict() .superRefine((asset, ctx) => { if (asset.origin === "generated") { if (!asset.generator) { ctx.addIssue({ code: "custom", message: "generator is required when origin is 'generated'", path: ["generator"], }); } if (asset.rights.state !== "generated") { ctx.addIssue({ code: "custom", message: "rights.state must be 'generated' when origin is 'generated'", path: ["rights", "state"], }); } } if (asset.rights.state === "licensed-stock" && !asset.rights.licence) { ctx.addIssue({ code: "custom", message: "rights.licence is required when rights.state is 'licensed-stock'", path: ["rights", "licence"], }); } }); // --------------------------------------------------------------------------- // werkstatt-intake.schema.yaml — the envelope // --------------------------------------------------------------------------- export const intakeEnvelopeSchema = z .object({ schema: z.literal(INTAKE_SCHEMA_ID), intakeId: z .string() // Canonical intakeId format — mirrored in ontology/schemas/biome.ts // (intakeBiomeProvenanceSchema); update both on format change. .regex(/^int_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/), systemId: z.string().regex(/^[a-z][a-z0-9-]{1,48}$/), producedAt: z.iso.datetime(), classification: classificationSchema, pbp: z.array(intakePbpEntitySchema).min(1), annotations: z.record(z.string(), annotationSchema), gaps: z.array(intakeGapSchema), operatorDecisions: z.array(operatorDecisionSchema), voice: voiceSchema, visual: visualSchema, assets: z.array(intakeAssetSchema), provenance: provenanceSchema, }) .strict(); export type IntakeEnvelope = z.infer; export type IntakeGap = z.infer; export type IntakeAsset = z.infer; const INTAKE_EXPORT_ID = "https://werkstatt.local/specs/onboarding/v0.1.0/werkstatt-intake.schema.yaml"; /** JSON Schema (draft 2020-12) fragment for one allow-listed PBP entity type. */ export type IntakePbpEntityJsonSchemas = Partial< Record<(typeof INTAKE_PBP_SCHEMA_ALLOWLIST)[number], Record> >; /** `$defs` key for a PBP schema id: `pbp/legal-identity@1` → `pbp-legal-identity-1`. */ export function intakePbpDefKey(schemaId: string): string { return schemaId.replace("/", "-").replace("@", "-"); } /** * Render the JSON Schema (draft 2020-12) producers vendor byte-exact — the * RFC-0336 `#` header is added by the owning command, not here. * * RFC-1301: when `pbpEntitySchemas` is supplied (rendered by the engine from * the canonical @warpgogol/pbp Zod schemas — this package cannot import pbp), * each fragment lands under `$defs/` and `pbp.items` * dispatches to it by `schema` const, so the single exported file validates * the full entity shapes and one sha256 (provenance.contractSnapshot) covers * envelope and entities alike. */ export function renderIntakeSchemaExport( options: { pbpEntitySchemas?: IntakePbpEntityJsonSchemas } = {}, ): string { const jsonSchema = z.toJSONSchema(intakeEnvelopeSchema, { target: "draft-2020-12", unrepresentable: "any", }) as Record; const entities = options.pbpEntitySchemas; if (entities === undefined) { return YAML.stringify({ $id: INTAKE_EXPORT_ID, title: "Werkstatt Intake v1 envelope", ...jsonSchema, }); } const $defs: Record = {}; const dispatch: Record[] = []; for (const id of INTAKE_PBP_SCHEMA_ALLOWLIST) { const fragment = entities[id]; if (fragment === undefined) { throw new Error(`renderIntakeSchemaExport: missing JSON Schema for allow-listed ${id}`); } $defs[intakePbpDefKey(id)] = fragment; dispatch.push({ if: { properties: { schema: { const: id } }, required: ["schema"] }, then: { $ref: `#/$defs/${intakePbpDefKey(id)}` }, }); } const properties = jsonSchema["properties"] as Record>; const pbp = properties["pbp"] as { items: Record }; pbp.items = { ...pbp.items, allOf: dispatch }; return YAML.stringify({ $id: INTAKE_EXPORT_ID, title: "Werkstatt Intake v1 envelope", ...jsonSchema, $defs, }); }