/* RFC-1291: term-registry contract — a site's .author/terms.yaml declares canonical terms per language with their forbidden variants, optionally scoped to segment kinds/route prefixes and pinned to a PBP field (pbpRef). text.corpus.validate enforces it over the rendered-text corpus (TERM-01..04). No global all-sites list — terms are per site. No matching logic — compileTermMatcher lives in werkstatt-site rules/terms.ts. RFC-1291: initial — termEntrySchema/termRegistrySchema (werkstatt/term-registry@1). RFC-1291: per-site term registry over the rendered corpus */ import { z } from "zod"; import { corpusSegmentKindSchema } from "./schema.ts"; export const termEntrySchema = z .object({ id: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/), /** BCP-47 primary tag as used by the site (de, uk, en). */ lang: z.string().min(2), /** Human note: what this term names. */ concept: z.string().min(1), canonical: z.string().min(1), forbidden: z.array(z.string().min(1)).min(1), /** Unicode letter/number boundaries. */ wholeWord: z.boolean().default(true), /** German nouns are case-significant; UK vs uk matters. */ caseSensitive: z.boolean().default(true), /** Limit to segment kinds (e.g. button, aria-label). */ kinds: z.array(corpusSegmentKindSchema).optional(), /** Limit to route prefixes. */ routes: z.array(z.string().startsWith("/")).optional(), /** Braceless content ref, e.g. business-profile.offerings/x.charges.activation.label. */ pbpRef: z.string().optional(), severity: z.enum(["error", "warning"]).default("error"), }) .strict(); export type TermEntry = z.infer; export const termRegistrySchema = z .object({ schema: z.literal("werkstatt/term-registry@1"), terms: z.array(termEntrySchema), }) .strict(); export type TermRegistry = z.infer;