import assert from "node:assert/strict"; import test from "node:test"; import { compileCapabilityResourceSource, compileCapabilitySource, type CapabilityImportEnvironment, } from "../src/capability-language/parser.js"; import type { InterfaceRevision, PackageRevision } from "../src/capability-model/types.js"; function fixture(declarations: string[]) { const interfaces = new Map(); const packages = new Map(); const environment: CapabilityImportEnvironment = { interfaces, packages }; for (const sourceText of declarations) { const result = compileCapabilityResourceSource(sourceText, { source: { repository: "https://example.test/rules.git", commit: "a".repeat(40) }, environment, }); assert.ok(result.ok, JSON.stringify(result.diagnostics)); if (result.resource.kind === "interface") interfaces.set(result.resource.revision.displayName, result.resource.revision); else packages.set(result.resource.revision.displayName, result.resource.revision); } return (body: string) => compileCapabilitySource( `workspace W id "w" revision "w@1" commit "${"a".repeat(40)}" { ${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")} ${[...packages.keys()].map((name) => `import package ${name};`).join("\n")} ${body} }`, "rules.qx", environment, ); } const ok = (result: ReturnType>) => { assert.ok(result.ok, JSON.stringify(result.diagnostics)); return result; }; const nameResources = [ 'interface Person id "person" revision "person@1" {value name id "p:name" : string {get id "p:get";set id "p:set";}}', 'interface HasName id "name" revision "name@1" {value name id "n:name" : string {get id "n:get";set id "n:set";}}', ]; const person = (name: string) => `atom ${name} id "${name}"; conform ${name} as Person id "${name}:person" { private state Name${name} id "${name}:name" on ${name} : string policy register default "hello"; bind name.get to state Name${name}.read; bind name.set to state Name${name}.write; }`; const namedRule = 'conform Person as HasName id "person-name" {bind name.get to interface Person.name.get;bind name.set to interface Person.name.set;}'; test("interface rules are order independent and preserve each original storage owner", () => { const compile = fixture(nameResources); const first = ok(compile(namedRule + person("Living") + person("Avatar"))); const second = ok(compile(person("Avatar") + person("Living") + namedRule)); const generated = (result: typeof first) => result.workspace.conformances .filter((c) => c.ruleOrigin) .map((c) => [c.atomId, c.id]) .sort(); assert.deepEqual(generated(first), generated(second)); for (const atom of ["Living", "Avatar"]) { const c = first.workspace.conformances.find((c) => c.atomId === atom && c.interfaceRevisionId === "name@1")!; assert(c); assert.equal(c.privateAttachments.length, 0); assert( c.operationBindings.every((b) => b.binding.kind === "interface" && b.binding.interfaceRevisionId === "person@1"), ); } }); test("rule conflicts identify competing implementations", () => { const result = fixture(nameResources)( person("Living") + namedRule + namedRule.replace('id "person-name"', 'id "other-rule"'), ); assert.equal(result.ok, false); assert.match(JSON.stringify(result.diagnostics), /duplicate-conformance/); }); test("rules validate unused bodies and reject storage and ambiguous names", () => { const compile = fixture(nameResources); for (const [body, code] of [ ["conform Person as HasName { }", "missing-rule-id"], ['conform Person as HasName id "r" {bind name.get to interface Person.missing.get;}', "unknown-operation"], ['conform Person as HasName id "r" { initialize {}; }', "unsupported-rule-storage"], ['atom Person id "p";' + namedRule, "ambiguous-conformance-source"], [namedRule + namedRule, "duplicate-rule-id"], ]) { const result = compile(body); assert.equal(result.ok, false, body); assert.match(JSON.stringify(result.diagnostics), new RegExp(code)); } }); const genericResources = [ 'interface Placeable id "placeable" revision "placeable@1" {}', 'interface Eligible id "eligible" revision "eligible@1" {}', 'interface Factory id "factory" revision "factory@1" {}', 'import interface Placeable; interface CanvasFactory id "canvas-factory" revision "canvas-factory@1" {}', ]; const factoryRule = 'conform Factory as CanvasFactory id "factory-canvas" {}'; test("generic bounds filter factories and can be supplied by later rules", () => { const result = ok( fixture(genericResources)(` ${factoryRule} atom Owner id "owner"; atom Product id "product"; atom Other id "other"; conform Owner as Factory id "unit-factory" {} conform Owner as Factory id "record-factory" {} conform Owner as Factory id "ineligible" {} conform Product as Eligible id "eligible-product" {} conform Eligible as Placeable id "eligible-placeable" {} `), ); const generated = result.workspace.conformances.filter((c) => c.ruleOrigin?.ruleId === "factory-canvas"); assert.equal(generated.length, 2); assert(generated.every((c) => c.atomId === "owner")); for (const c of generated) { const iface = result.workspace.interfaceImports.find((i) => i.revisionId === c.interfaceRevisionId)!; assert.deepEqual(iface.application?.arguments[1], { kind: "object", target: { kind: "atom", atomId: "product" } }); } }); test("rules reject recursive graphs, nested capture, wrong kinds and unbound parameters", () => { const compile = fixture([ 'interface A id "a" revision "a@1" {}', 'interface B id "b" revision "b@1" {}', ]); for (const [body, code] of [ ['conform A as A> id "grow" {}', "recursive-conformance-rule"], ['conform A as B id "ab" {} conform B as A id "ba" {}', "recursive-conformance-rule"], ['conform A> as B id "nested" {}', "unsupported-rule-pattern"], ['conform A as B id "uncaptured" {}', "uncaptured-rule-parameter"], ['conform A as B id "kind" {}', "parameter-kind"], ]) { const result = compile(body); assert.equal(result.ok, false, body); assert.match(JSON.stringify(result.diagnostics), new RegExp(code)); } }); test("package-backed rules retain declared interface dependencies", () => { const result = ok( fixture([ ...nameResources, 'import interface Person; package Names id "names" revision "names@1" {operation displayName id "read" : unit -> string mode call receiver any requires {interface person id "person-port" : Person using [name.get];};}', ])( person("Living") + `conform Person as HasName id "formatted" { bind name.get to package Names.displayName with {person to interface Person;}; bind name.set to interface Person.name.set; }`, ), ); const generated = result.workspace.conformances.find((c) => c.ruleOrigin)!; const binding = generated.operationBindings[0].binding; assert.equal(binding.kind, "package"); if (binding.kind === "package") assert.deepEqual(binding.dependencies[0].binding, { kind: "interface", interfaceRevisionId: "person@1" }); }); test("repeated arguments, closed patterns, Self and seedless bounds remain exact", () => { const compile = fixture([ 'interface Pair id "pair" revision "pair@1" {}', 'interface Same id "same" revision "same@1" {}', ...genericResources, ]); const result = ok( compile(` atom A id "atom-a"; conform A as Pair id "equal" {} conform A as Pair id "unequal" {} conform Pair as Same id "same-rule" {} conform A as Factory id "factory" {} ${factoryRule} conform Factory as Placeable id "seedless" {} conform Factory as Eligible id "seedless-other" {} `), ); const generated = result.workspace.conformances.filter((c) => c.ruleOrigin); assert.equal(generated.length, 1); const target = result.workspace.interfaceImports.find((i) => i.revisionId === generated[0].interfaceRevisionId)!; assert.deepEqual(target.application?.arguments[1], { kind: "object", target: { kind: "atom", atomId: "atom-a" } }); }); test("chains do not duplicate an application and explicit targets conflict", () => { const compile = fixture([ 'interface A id "a" revision "a@1" {}', 'interface B id "b" revision "b@1" {}', 'interface C id "c" revision "c@1" {}', ]); const source = `atom Item id "item"; conform Item as A id "a-impl" {} conform B as C id "bc" {} conform A as B id "ab" {}`; assert.equal(ok(compile(source)).workspace.conformances.length, 3); const conflict = compile(source + 'conform Item as C id "explicit-c" {}'); assert.equal(conflict.ok, false); assert.match(JSON.stringify(conflict.diagnostics), /explicit-c/); assert.match(JSON.stringify(conflict.diagnostics), /rule bc/); });