import type { InterfaceRevision, ModuleInterfaceMember, PackageModule, WorkspaceRevision } from "./types.js"; import type { CapabilityValidationIssue } from "./validation.js"; /** Public nominal contract lookup; does not resolve an executable artifact or grant access. */ export function moduleContract( interfaces: readonly InterfaceRevision[], module: PackageModule, ): ModuleInterfaceMember | undefined { const iface = interfaces.find((entry) => entry.revisionId === module.interfaceRevisionId); if (!iface || iface.template) return undefined; const member = iface.members.find((entry) => entry.id === module.memberId); return member?.kind === "module" ? member : undefined; } export function validateModules(workspace: WorkspaceRevision): CapabilityValidationIssue[] { const issues: CapabilityValidationIssue[] = []; const fail = (path: string, message: string) => issues.push({ code: "invalid-module-contract", path, message }); for (const iface of workspace.interfaceImports) { for (const member of iface.template?.members ?? iface.members) { if (member.kind !== "module") continue; const path = `${iface.revisionId}.${member.id}`; if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+\/[1-9][0-9]*$/.test(member.contract)) fail(path, "Module contracts require a namespaced, versioned identifier"); if (member.operations.length) fail(path, "A module member cannot declare callable operations"); } } for (const pkg of workspace.packageImports) { const ids = new Set(pkg.exports.map((entry) => entry.id)); const names = new Set([ ...pkg.exports.map((entry) => entry.displayName), ...(pkg.genericExports ?? []).map((entry) => entry.displayName), ...(pkg.queries ?? []).map((entry) => entry.displayName), ...(pkg.queryTemplates ?? []).map((entry) => entry.declaration.displayName), ]); for (const module of pkg.modules ?? []) { const path = `${pkg.revisionId}.${module.id}`; if (!module.id.trim() || !module.displayName.trim() || ids.has(module.id) || names.has(module.displayName)) fail(path, "Duplicate or empty module identity or package member name"); ids.add(module.id); names.add(module.displayName); if (!moduleContract(workspace.interfaceImports, module)) fail(path, "Module export must implement a closed imported module member"); } } for (const conformance of workspace.conformances) { const iface = workspace.interfaceImports.find((entry) => entry.revisionId === conformance.interfaceRevisionId); if (!iface) continue; const bound = new Set(); for (const binding of conformance.moduleBindings ?? []) { const path = `${conformance.id ?? conformance.atomId}.${binding.memberId}`; const member = iface.members.find((entry) => entry.id === binding.memberId); const pkg = workspace.packageImports.find((entry) => entry.revisionId === binding.packageRevisionId); const module = pkg?.modules?.find((entry) => entry.id === binding.moduleId); if (bound.has(binding.memberId)) fail(path, "Module member is bound more than once"); bound.add(binding.memberId); if ( member?.kind !== "module" || !module || module.interfaceRevisionId !== iface.revisionId || module.memberId !== member.id ) { fail(path, "Module binding must implement this exact nominal interface member"); continue; } const subject = member.subject; if ( subject && !(subject.kind === "atom" ? subject.atomId === conformance.atomId : workspace.conformances.some( (entry) => entry.atomId === conformance.atomId && entry.interfaceRevisionId === subject.interfaceRevisionId, )) ) fail(path, "Implementing atom does not satisfy the module subject contract"); } for (const member of iface.members) if (member.kind === "module" && !bound.has(member.id)) fail(`${conformance.id ?? conformance.atomId}.${member.id}`, "Missing module member binding"); } return issues; }