Implement typed module contracts and conformance bindings

This commit is contained in:
Timothy J. Aveni
2026-09-22 13:51:32 -07:00
parent 60b44a6637
commit 7173751f37
33 changed files with 2963 additions and 2261 deletions
+31 -11
View File
@@ -39,8 +39,17 @@ const interfaces = new Map([
["Data", data],
["Other", other],
]);
const componentSource = (body = 'component Card id "component:card" subject Data runtime react;') =>
`import interface Data; package UI id "package:ui" revision "package:ui@1" { ${body} }`;
const role = compileCapabilityResourceSource(
`import interface Data;
interface View id "interface:view" revision "interface:view@1" {
module render id "member:view:render" contract "org.quixos.react.subject-only/1" subject interface Data;
}`,
{ source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } },
);
assert.ok(role.ok && role.resource.kind === "interface", JSON.stringify(role.diagnostics));
interfaces.set("View", role.resource.revision);
const componentSource = (body = 'module Card id "component:card" implements View.render;') =>
`import interface Data; import interface View; package UI id "package:ui" revision "package:ui@1" { ${body} }`;
const compilePackage = (body?: string) =>
compileCapabilityResourceSource(componentSource(body), {
source,
@@ -51,7 +60,7 @@ const workspace = (presentation = 'presentation Card id "presentation:card" for
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
return compileCapabilitySource(
`workspace Example id "workspace:example" revision "workspace:example@1" commit "${source.commit}" {
import interface Data; import interface Other; import package UI;
import interface Data; import interface Other; import interface View; import package UI;
atom One id "atom:one"; atom Two id "atom:two";
conform One as Data id "conformance:one:data" { }
conform Two as Data id "conformance:two:data" { }
@@ -88,21 +97,21 @@ test("component declarations survive formatting and syntax inspection", () => {
const text = formatQx(componentSource());
const parsed = parseQx(text);
assert.deepEqual(parsed.diagnostics, []);
assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "componentDecl"));
assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "moduleExport"));
assert.equal(formatQx(text), text);
});
test("component and presentation contracts reject wrong subjects, duplicates and unavailable runtimes", () => {
for (const [input, message] of [
['component Card id "component:card" subject Missing runtime react;', /Missing/],
['component Card id "component:card" subject Data runtime arduino;', /Unsupported component runtime/],
['module Card id "component:card" implements Missing.render;', /Missing/],
['module Card id "component:card" implements Data.missing;', /module member/],
[
'component Card id "component:card" subject Data runtime react; component Other id "component:card" subject Data runtime react;',
/Duplicate component/,
'module Card id "component:card" implements View.render; module Other id "component:card" implements View.render;',
/Duplicate or empty module/,
],
[
'component Card id "component:card" subject Data runtime react; component Card id "component:other" subject Data runtime react;',
/Duplicate component/,
'module Card id "component:card" implements View.render; module Card id "component:other" implements View.render;',
/Duplicate or empty module/,
],
] as const) {
const result = compilePackage(input);
@@ -131,6 +140,17 @@ test("model validation rechecks presentation metadata instead of trusting the so
assert.throws(() => compileExecutionPlan(result.workspace), /no installed package component/);
});
test("standalone presentation rejects a general component contract that may require extra props", () => {
const result = workspace();
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const candidate = structuredClone(result.workspace);
const member = candidate.interfaceImports.find((iface) => iface.revisionId === "interface:view@1")!.members[0]!;
assert.equal(member.kind, "module");
if (member.kind !== "module") throw Error("Expected module");
member.contract = "org.quixos.react.component/1";
assert.ok(validateWorkspaceRevision(candidate).some((issue) => /subject-only/.test(issue.message)));
});
test("checked component-only artifacts install without a service and reject missing or substituted entries", async () => {
const result = workspace();
assert.ok(result.ok, JSON.stringify(result.diagnostics));
@@ -150,7 +170,7 @@ test("checked component-only artifacts install without a service and reject miss
const body: Omit<ComponentArtifact, "contentDigest"> = {
id: "component:card",
subject: "interface:Data@1",
compatibility: "quixos-react-component-v1",
compatibility: "org.quixos.react.esm/1",
path: paths[0]!,
exportName: "Card",
files,