import type { WorkspaceRevision } from "./types.js"; import type { CapabilityValidationIssue } from "./validation.js"; /** Presentation metadata has no graph authority and cannot name arbitrary module URLs. */ export function validatePresentations(workspace: WorkspaceRevision): CapabilityValidationIssue[] { const issues: CapabilityValidationIssue[] = []; const fail = (path: string, message: string) => issues.push({ code: "invalid-presentation", path, message }); const interfaces = new Map(workspace.interfaceImports.map((entry) => [entry.revisionId, entry])); const packages = new Map(workspace.packageImports.map((entry) => [entry.revisionId, entry])); const text = (value: string, path: string) => { if (!value.trim()) fail(path, "Presentation identities and names must be nonempty"); }; const subject = (id: string, path: string) => { const contract = interfaces.get(id as WorkspaceRevision["interfaceImports"][number]["revisionId"]); if (!contract || contract.template) fail(path, `Expected an imported closed subject interface: ${id}`); }; for (const [index, pkg] of workspace.packageImports.entries()) { const ids = new Set(pkg.exports.map((entry) => entry.id as string)); const names = new Set([ ...pkg.exports.map((entry) => entry.displayName), ...(pkg.queries ?? []).map((entry) => entry.displayName), ...(pkg.queryTemplates ?? []).map((entry) => entry.declaration.displayName), ...(pkg.genericExports ?? []).map((entry) => entry.displayName), ]); for (const [componentIndex, component] of (pkg.components ?? []).entries()) { const path = `packageImports[${index}].components[${componentIndex}]`; text(component.id, `${path}.id`); text(component.displayName, `${path}.displayName`); if (ids.has(component.id) || names.has(component.displayName)) fail(path, "Duplicate component identity or package member name"); ids.add(component.id); names.add(component.displayName); subject(component.subject, `${path}.subject`); if (component.runtime !== "react") fail(`${path}.runtime`, "Unsupported component runtime"); } } const ids = new Set(), names = new Set(); for (const [index, presentation] of (workspace.presentations ?? []).entries()) { const path = `presentations[${index}]`; text(presentation.id, `${path}.id`); text(presentation.displayName, `${path}.displayName`); if (ids.has(presentation.id) || names.has(presentation.displayName)) fail(path, "Duplicate presentation identity or name"); ids.add(presentation.id); names.add(presentation.displayName); subject(presentation.subject, `${path}.subject`); const component = packages .get(presentation.packageRevisionId) ?.components?.find((entry) => entry.id === presentation.componentId); if (!component) fail(`${path}.componentId`, "Presentation names no installed package component"); else if (component.subject !== presentation.subject) fail(`${path}.subject`, `Component requires ${component.subject}, not ${presentation.subject}`); } return issues; }