import { z } from "zod"; import { capabilityIdSchema, packageRelativePathSchema } from "./identifiers.js"; import { semanticVersionRangeSchema, semanticVersionSchema } from "./versions.js"; /** * A **browser contribution**: one module's compiled renderer, declared for a capability another * module provides, and mounted by that other module inside a container it owns (ADR 0041). * * This is deliberately not a third UI role. `entrypoints.ui` and `entrypoints.display` are artifacts * *the platform* mounts, in surfaces the platform owns. A contribution is mounted by a **consumer * module**, at a moment and in a place only that module understands — a slot in its own layout — * and the platform never renders one on its own. * * ## What the platform knows, and the complete list of it * * Identity, version, target capability, compatible range, and one compiled artifact. That is all, * and the omissions are the point: nothing here says what the renderer draws, what shape its input * has, when it should be mounted, or what any of it means. Core could not learn what a "slide" or a * "timeline slot" is from this declaration, because the declaration cannot express it — the * domain contract belongs entirely to the consumer's own versioned input. * * Inert, like every other manifest declaration: reading one causes no import, no evaluation, and no * network or database access. */ /** `.` — exactly two segments. The prefix is checked against the declaring * module in `moduleManifestSchema`, which is what makes a contribution id globally unique without a * registry: module ids are already unique, so two modules cannot produce the same contribution id. */ export const contributionIdSchema = z .string() .min(3) .max(160) .regex( /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u, "Must be ., both lowercase kebab-case.", ); export const moduleContributionDeclarationSchema = z .object({ id: contributionIdSchema, /** This contribution's own version, independent of the module's. A consumer sees it and a host * verifies the artifact claims it, so a stale bundle is caught exactly as it is for a module * UI artifact. */ version: semanticVersionSchema, /** The capability this renderer plugs into — owned by whichever module provides it, never by * the contributing module. */ capability: capabilityIdSchema, /** The provider capability versions this renderer is compatible with. A provider outside this * range makes the contribution unpublishable rather than mounted and broken. */ capabilityRange: semanticVersionRangeSchema, /** The compiled browser artifact, resolved and validated during discovery exactly like every * other declared entrypoint: package-relative, inside the package before and after symlink * resolution, and a real `.js`/`.mjs` file. */ artifact: packageRelativePathSchema, description: z.string().trim().min(1).max(240).optional(), }) .strict(); export type ModuleContributionDeclaration = z.output; /** The local segment of a contribution id — the part after the declaring module's own id. Used by * the canonical builder to locate the fixed source directory, so a module author configures no * paths and cannot point the build somewhere else. */ export function contributionLocalName(contributionId: string): string { return contributionId.slice(contributionId.indexOf(".") + 1); }