/* RFC-0231 attribution visibility policy. One vocabulary + one pure resolver that decides whether an attribution surface (material credit, prose byline, footer copyright) renders its VISIBLE form. Hiding never affects provenance: callers must keep emitting JSON-LD and the credits-page listing regardless of the result. Do not read content or globs; callers pass the resolved overrides. Do not render UI or emit JSON-LD; @warpgogol/werkstatt-site/ui owns presentation. RFC-0231: initial attribution visibility policy resolver + site schema. */ import { z } from "zod"; /** RFC-0231: every visibility level speaks the same two-word vocabulary. */ export type AttributionDisplay = "shown" | "hidden"; export type AttributionSurface = "material-credit" | "prose-authorship" | "footer-copyright"; /** Site default may defer to RFC-0228 editorial/decorative intent. */ export type AttributionSiteDefault = "byIntent" | "shown" | "hidden"; export const attributionDisplaySchema = z.enum(["shown", "hidden"]); export const attributionSiteDefaultSchema = z.enum(["byIntent", "shown", "hidden"]); /** RFC-0231: the `attribution` block authored in site/{lang}/labels.md. */ export const attributionPolicySchema = z .object({ /** Material-credit inline disclosures. Defaults to byIntent. */ credits: attributionSiteDefaultSchema.optional(), /** Visible prose byline. Defaults to shown. */ proseAuthorship: attributionDisplaySchema.optional(), /** Footer site-copyright line. Defaults to shown. */ footerCopyright: attributionDisplaySchema.optional(), }) .strict(); export type AttributionPolicy = z.infer; export interface AttributionDisplayInputs { surface: AttributionSurface; /** RFC-0228 intent of the target; editorial ⇒ shown, decorative ⇒ hidden under byIntent. */ intent?: "editorial" | "decorative"; /** Level 1: per-asset `display` in the sidecar (material credit only). */ assetOverride?: AttributionDisplay; /** Level 2: per-placement `creditDisplay` on the section/block content. */ placementOverride?: AttributionDisplay; /** Level 3: per-page `attribution.*` in page frontmatter. */ pageOverride?: AttributionDisplay; /** Level 4: site default from the `attribution` labels block. */ siteDefault?: AttributionSiteDefault; } /** * RFC-0231: resolve one visible-surface decision. Most-specific level wins; the * intrinsic default (editorial shown / decorative hidden) applies only when no * explicit level set a value and the site default is `byIntent` (or unset). */ export function resolveAttributionDisplay(inputs: AttributionDisplayInputs): AttributionDisplay { const explicit = inputs.assetOverride ?? inputs.placementOverride ?? inputs.pageOverride; if (explicit) return explicit; const site = inputs.siteDefault ?? "byIntent"; if (site === "shown" || site === "hidden") return site; // byIntent: decorative material is quiet by default; everything else shows. return inputs.intent === "decorative" ? "hidden" : "shown"; } /** RFC-0231: parse the optional site `attribution` block; tolerate absence/garbage. */ export function readAttributionPolicy( labels: Record | undefined, ): AttributionPolicy { const parsed = attributionPolicySchema.safeParse(labels?.attribution); return parsed.success ? parsed.data : {}; } /** RFC-0231: site-level default for material credits, defaulting to byIntent. */ export function readCreditsSiteDefault( labels: Record | undefined, ): AttributionSiteDefault { return readAttributionPolicy(labels).credits ?? "byIntent"; }