import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { test } from "node:test"; import { compileCapabilityResourceSource, compileCapabilitySource, } from "../src/capability-language/index.js"; import { capabilityFixtureSource, capabilityResourceSources, compileCapabilityFixture, fixtureId, } from "./fixtures/capability-model.js"; const compileWebStudioFixture = (packageTransform = (source: string) => source) => { const fixture = (name: string) => readFileSync( resolve(process.cwd(), "test/fixtures", name), "utf8", ); const react = compileCapabilityResourceSource( fixture("react-component.interface.qx"), { source: { repository: "https://repos.quixos.org/org-quixos-web-studio/interface-react-component.git", commit: "2".repeat(40), }, }, ); if (!react.ok || react.resource.kind !== "interface") { throw new Error("ReactComponent fixture did not compile"); } const named = compileCapabilityResourceSource( fixture("named.interface.qx"), { source: { repository: "https://repos.quixos.org/quixos-test/interface-named.git", commit: "5".repeat(40), }, }, ); if (!named.ok || named.resource.kind !== "interface") { throw new Error("Named fixture did not compile"); } const has = compileCapabilityResourceSource( fixture("has-react-component.interface.qx"), { source: { repository: "https://repos.quixos.org/org-quixos-web-studio/interface-has-react-component.git", commit: "3".repeat(40), }, environment: { interfaces: new Map([["ReactComponent", react.resource.revision]]), interfaceClosure: [react.resource.revision], }, }, ); if (!has.ok || has.resource.kind !== "interface") { throw new Error("HasReactComponent fixture did not compile"); } const component = compileCapabilityResourceSource( packageTransform(fixture("component-runtime.package.qx")), { source: { repository: "https://repos.quixos.org/quixos-test/package-component-runtime.git", commit: "4".repeat(40), }, environment: { interfaces: new Map([["Named", named.resource.revision]]), interfaceClosure: [named.resource.revision], }, }, ); if (!component.ok || component.resource.kind !== "package") { throw new Error("ComponentRuntime fixture did not compile"); } return compileCapabilitySource( fixture("web-studio.capabilities.qx"), "web-studio.capabilities.qx", { interfaces: new Map([ ["Named", named.resource.revision], ["ReactComponent", react.resource.revision], ["HasReactComponent", has.resource.revision], ]), packages: new Map([["ComponentRuntime", component.resource.revision]]), interfaceClosure: [named.resource.revision, react.resource.revision, has.resource.revision], packageClosure: [component.resource.revision], }, ); }; test("ANTLR parses and validates a complete capability workspace", () => { const result = compileCapabilityFixture(); assert.equal(result.ok, true); if (!result.ok) return; assert.equal(result.workspace.workspaceId, fixtureId.workspace); assert.equal(result.workspace.id, fixtureId.workspaceRevision); assert.equal(result.workspace.interfaceImports.length, 3); assert.equal(result.workspace.conformances.length, 4); assert.equal("id" in result.workspace.conformances[0]!, false); }); test("conformances do not accept authored IDs", () => { const result = compileCapabilityFixture({ workspace: capabilityFixtureSource.replace( "conform Project as Named {", 'conform Project as Named id "conformance:project:named" {', ), }); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax")); }); test("the v1 language has no implicit relationship materialization rule", () => { const result = compileCapabilityFixture({ workspace: capabilityFixtureSource.replace( /\n}\s*$/, '\n materialize ProjectOwner.owner if absent with Person;\n}\n', ), }); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax")); }); test("forward declarations make source order irrelevant", () => { const source = capabilityFixtureSource .replace(/ atom Project[^\n]*\n/, "") .replace(/ atom Person[^\n]*\n/, "") .replace( /\n}\s*$/, '\n atom Project id "atom:project";\n atom Person id "atom:person";\n}\n', ); assert.equal(compileCapabilityFixture({ workspace: source }).ok, true); }); test("interfaces can declare ordinary call operations", () => { const workspace = capabilityFixtureSource.replace( "bind summary.get to package TodoRuntime.summaryGet", "bind summarize.call to package TodoRuntime.summaryGet", ); const summary = capabilityResourceSources.summary.replace( " value summary id \"member:summary:summary\" : string {\n get id \"operation:summary:summary:get\";\n }", " operation summarize id \"member:summary:summarize\" : string -> string {\n call id \"operation:summary:summarize\";\n }", ); const todo = capabilityResourceSources.todo.replace( "operation summaryGet id \"export:todo-runtime:summary-get\" : unit -> string", "operation summaryGet id \"export:todo-runtime:summary-get\" : string -> string", ); const result = compileCapabilityFixture({ workspace, summary, todo }); assert.equal(result.ok, true); if (!result.ok) return; const member = result.workspace.interfaceImports .find((entry) => entry.displayName === "Summary")?.members[0]; assert.equal(member?.kind, "operation"); }); test("state defaults accept recursive JSON values", () => { const source = capabilityFixtureSource.replace( ' policy optimistic-register default "Untitled project";', ` policy optimistic-register default "Untitled project"; shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata" policy optimistic-register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`, ); const result = compileCapabilityFixture({ workspace: source }); assert.equal(result.ok, true); if (!result.ok) return; const metadata = result.workspace.sharedAttachments.find( (entry) => entry.id === "slot:project:metadata", ); assert.equal(metadata?.kind, "state"); if (metadata?.kind !== "state") return; assert.deepEqual(metadata.defaultValue, { labels: ["compiler", "runtime"], score: 1.5, enabled: true, extra: null, }); }); test("syntax errors retain source locations", () => { const result = compileCapabilityFixture({ workspace: capabilityFixtureSource.replace("atom Project", "atom Project ???"), }); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax" && entry.line > 0 )); }); test("unknown authoring names are lowering errors", () => { const result = compileCapabilityFixture({ workspace: capabilityFixtureSource.replace( "to state ProjectTitle.read", "to state NotAState.read", ), }); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "lowering" && entry.code === "unknown-symbol" && entry.message.includes("NotAState") )); }); test("well-formed but invalid programs report semantic paths", () => { const result = compileCapabilityFixture({ workspace: capabilityFixtureSource.replace( "bind name.get to state ProjectTitle.read", "bind name.get to state ProjectTitle.write", ), }); assert.equal(result.ok, false); if (result.ok) return; const diagnostic = result.diagnostics.find( (entry) => entry.code === "invalid-state-binding", ); assert.ok(diagnostic); assert.equal(diagnostic.phase, "validation"); assert.ok(diagnostic.path?.includes("operationBindings")); }); test("Web Studio sidecars declare lazy materialization and checked cross-object ports", () => { const result = compileWebStudioFixture(); assert.equal(result.ok, true); if (!result.ok) return; const host = result.workspace.conformances.find((entry) => entry.atomId === "atom:project" && entry.interfaceRevisionId === "interface:org.quixos.web-studio.has-react-component@1" ); assert.deepEqual(host?.relationshipMaterializations, [{ memberId: "member:org.quixos.web-studio.has-react-component:component", constructorAtomId: "atom:project-component", edgeTypeId: "edge:project:component", constructedProjectionId: "projection:component:subject", }]); const component = result.workspace.conformances.find((entry) => entry.atomId === "atom:project-component" ); const binding = component?.operationBindings[0]?.binding; assert.equal(binding?.kind, "package"); if (binding?.kind !== "package") return; assert.deepEqual(binding.dependencies[0]?.binding, { kind: "interface", interfaceRevisionId: "interface:named@1", via: { edgeTypeId: "edge:project:component", projectionId: "projection:component:subject", }, }); }); test("relationship materializers require a constructor from the host atom", () => { const result = compileWebStudioFixture((source) => source.replace( "constructs ProjectComponent : atom-ref;", "constructs ProjectComponent : unit;", )); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.code === "invalid-relationship-materialization" && entry.message.includes("must accept") )); }); test("callable interface ports require a full source dependency, not a nominal reference", () => { const result = compileCapabilityResourceSource(` external interface Named revision "interface:named@1"; package Runtime id "package:runtime" revision "package:runtime@1" { function readName id "export:runtime:read-name" : unit -> string requires { interface named id "port:runtime:named" : Named; }; } `, { source: { repository: "https://repos.quixos.org/quixos-test/package-runtime.git", commit: "6".repeat(40), }, }); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.code === "nominal-interface-port" && entry.message.includes("import interface Named") )); });