Implement typed module contracts and conformance bindings
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { WorkspaceRevision } from "./types.js";
|
||||
import type { CapabilityValidationIssue } from "./validation.js";
|
||||
import { moduleContract } from "./modules.js";
|
||||
|
||||
/** Presentation metadata has no graph authority and cannot name arbitrary module URLs. */
|
||||
export function validatePresentations(workspace: WorkspaceRevision): CapabilityValidationIssue[] {
|
||||
@@ -19,24 +20,6 @@ export function validatePresentations(workspace: WorkspaceRevision): CapabilityV
|
||||
text(alias, `packageImports[${index}].clientInterfaces`);
|
||||
subject(id, `packageImports[${index}].clientInterfaces.${alias}`);
|
||||
}
|
||||
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<string>(),
|
||||
names = new Set<string>();
|
||||
@@ -49,12 +32,18 @@ export function validatePresentations(workspace: WorkspaceRevision): CapabilityV
|
||||
ids.add(presentation.id);
|
||||
names.add(presentation.displayName);
|
||||
subject(presentation.subject, `${path}.subject`);
|
||||
const component = packages
|
||||
const module = 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}`);
|
||||
?.modules?.find((entry) => entry.id === presentation.componentId);
|
||||
if (module) {
|
||||
const contract = moduleContract(workspace.interfaceImports, module);
|
||||
if (contract?.contract !== "org.quixos.react.subject-only/1")
|
||||
fail(`${path}.componentId`, "Standalone presentation requires a subject-only module contract");
|
||||
if (contract?.subject?.kind !== "interface" || contract.subject.interfaceRevisionId !== presentation.subject)
|
||||
fail(`${path}.subject`, "Component requires its exact declared subject interface");
|
||||
continue;
|
||||
}
|
||||
fail(`${path}.componentId`, "Presentation names no installed package component");
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user