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 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 { content to state Missing; }; }', "invalid-initialization"], ['conform Person as HasName id "r" { bind name to state Missing; }', "invalid-state-field-binding"], [ 'conform Person as HasName id "r" {private state Bad id "bad" on Self : Missing policy register;}', "unknown-type", ], ['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)); } }); test("rule storage is scoped per application, including repeated specializations on one atom", async () => { const compile = fixture([ 'interface Source id "source" revision "source@1" {}', 'interface Stored id "stored" revision "stored@1" {value content id "value" : optional {get id "get";set id "set";}}', ]); const rule = `conform Source as Stored id "store-rule" { private state Value id "stored:value" on Self : optional policy register default null; bind content to state Value; }`; const atoms = `atom A id "a"; atom B id "b"; conform A as Source id "a-string" {} conform A as Source id "a-number" {} conform B as Source id "b-string" {}`; const first = ok(compile(rule + atoms)).workspace; const second = ok(compile(atoms + rule)).workspace; const storage = (workspace: typeof first) => workspace.conformances .filter((c) => c.ruleOrigin) .map((c) => [c.id, c.privateAttachments, c.operationBindings]) .sort((a, b) => String(a[0]).localeCompare(String(b[0]))); assert.deepEqual(storage(first), storage(second)); const owners = first.conformances.filter((c) => c.ruleOrigin); assert.equal(owners.length, 3); assert.equal(new Set(owners.map((c) => c.privateAttachments[0]!.id)).size, 3); for (const owner of owners) { const state = owner.privateAttachments[0]!; assert.equal(state.kind, "state"); if (state.kind !== "state") throw Error("state expected"); assert.equal(state.attachedTo, owner.atomId); assert(owner.operationBindings.every((b) => b.binding.kind === "state" && b.binding.slotId === state.id)); } const { storageContracts, storageChangeRequiresMigration } = await import("../src/capability-model/evolution.js"); const before = storageContracts(first); const changed = ok( compile( atoms.replace('conform A as Source id "a-number" {}', "") + rule.replace("default null", 'default "new default"'), ), ).workspace; // Use the string specialization for default evolution; all nominal identities // remain stable, and changing a default must not rewrite existing state. const oldSlot = before.find((s) => s.id === owners.find((c) => c.atomId === "b")!.privateAttachments[0]!.id)!; const newSlot = storageContracts(changed).find((s) => s.id === oldSlot.id)!; assert.equal(storageChangeRequiresMigration(oldSlot, newSlot, new Set(["a", "b"])), false); assert.equal(storageChangeRequiresMigration(oldSlot, undefined, new Set(["a", "b"])), true); }); test("rule attachments shadow outside names without leaking into another owner", () => { const rule = `conform Person as HasName id "r" { private state NameLiving id "rule:name" on Self : string policy register default "rule"; bind name to state NameLiving; }`; const workspace = ok(fixture(nameResources)(rule + person("Living"))).workspace; const original = workspace.conformances.find((c) => c.interfaceRevisionId === "person@1")!; const derived = workspace.conformances.find((c) => c.interfaceRevisionId === "name@1")!; assert.notEqual(original.privateAttachments[0]!.id, derived.privateAttachments[0]!.id); assert( derived.operationBindings.every( (b) => b.binding.kind === "state" && b.binding.slotId === derived.privateAttachments[0]!.id, ), ); assert( original.operationBindings.every( (b) => b.binding.kind === "state" && b.binding.slotId === original.privateAttachments[0]!.id, ), ); }); test("rule private edges substitute Self and captured endpoints with stable projection IDs", () => { const compile = fixture([ 'interface Linkable id "linkable" revision "linkable@1" {}', 'interface Links id "links" revision "links@1" {relation items id "items" : many object T {resolve id "resolve";}}', ]); const source = `atom A id "a"; atom B id "b"; atom Target id "target"; conform A as Linkable id "a-link" {} conform B as Linkable id "b-link" {} conform Linkable as Links id "links" { private edge Items id "items" { object Self projection items id "forward" many; object T projection owners id "inverse" many; } bind items.resolve to edge Items.items.resolve; }`; const workspace = ok(compile(source)).workspace; const edges = workspace.conformances.filter((c) => c.ruleOrigin).map((c) => c.privateAttachments[0]!); assert.equal(edges.length, 2); assert.notEqual(edges[0]!.id, edges[1]!.id); const projections = edges.flatMap((edge) => { assert.equal(edge.kind, "edge"); if (edge.kind !== "edge") throw Error("edge expected"); assert.deepEqual(edge.endpoints[1].constraint, { kind: "atom", atomId: "target" }); return edge.endpoints.map((e) => e.projectionId); }); assert.equal(new Set(projections).size, 4); }); test("rule creation ports close captured atom parameters and retain initialization checking", () => { const compile = fixture([ 'interface Factory id "factory" revision "factory@1" {operation create id "create" : unit -> ref {call id "call" execution transaction targets [browser,server];}}', 'interface Creates id "creates" revision "creates@1" {}', 'package Runtime id "runtime" revision "runtime@1" {operation create id "create" : unit -> ref mode call execution transaction targets [browser,server] receiver any requires {creation result id "result" : T input unit;};}', ]); const rule = `conform Creates as Factory id "factory-rule" { bind create.call to package Runtime.create with {result to creation T with {};}; }`; const result = ok( compile(`atom Subject id "subject"; atom Product id "product"; conform Subject as Creates id "creates-product" {} ${rule}`), ); const owner = result.workspace.conformances.find((c) => c.ruleOrigin)!; const binding = owner.operationBindings[0]!.binding; assert.equal(binding.kind, "package"); if (binding.kind !== "package") throw Error("package expected"); assert.deepEqual(binding.dependencies[0]!.binding, { kind: "creation", atomId: "product", owners: [] }); const invalid = compile(`atom Subject id "subject"; conform Subject as Creates> id "creates-interface" {} ${rule}`); assert.equal(invalid.ok, false); assert.match(JSON.stringify(invalid.diagnostics), /concrete atom/); }); 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/); }); test("rule inverse reads retain the concrete conformance ownership checks", () => { const compile = fixture([ 'interface Parent id "parent" revision "parent@1" {}', 'interface Child id "child" revision "child@1" {}', 'import interface Parent; interface Owned id "owned" revision "owned@1" {queryable relation parent id "parent-ref" : optional-one interface Parent {resolve id "parent-read" execution snapshot targets [browser,server];}}', ]); const source = ` atom P id "p"; atom C id "c"; conform P as Parent id "parent-impl" { private edge Membership id "membership" { atom P projection children id "children" many-unique; interface Child projection parent id "parent" optional-one public-traversal; } } conform C as Child id "child-impl" {} conform Child as Owned id "owned-rule" {bind parent.resolve to edge Membership.parent.resolve;} `; ok(compile(source)); const hidden = compile(source.replace(" public-traversal", "")); assert.equal(hidden.ok, false); assert.match(JSON.stringify(hidden.diagnostics), /private|public|own/i); const missing = compile(source.replace("Membership.parent.resolve", "Missing.parent.resolve")); assert.equal(missing.ok, false); assert.match(JSON.stringify(missing.diagnostics), /Unknown attachment/); });