Files
quixos-protocol/test/conformance-rules.test.ts
T
Timothy J. Aveni 73148d76e1 Add checked private storage to reusable conformance rules
Scope attachments and projections by stable rule-application identity; keep lexical names private to each application. Lower ordinary initialization mappings and concrete captured creation targets through existing checking. Preserve stateless forwarding, conflict detection, and the materialization fence. Add tests for multiple same-atom specializations, name isolation, generic edges, factory seeds, and storage evolution. Full protocol Nix build and tests pass.
2026-09-25 11:04:22 -07:00

322 lines
16 KiB
TypeScript

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<string, InterfaceRevision>();
const packages = new Map<string, PackageRevision>();
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<ReturnType<typeof fixture>>) => {
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<value T : storable> id "source" revision "source@1" {}',
'interface Stored<value T : storable> id "stored" revision "stored@1" {value content id "value" : optional<T> {get id "get";set id "set";}}',
]);
const rule = `conform<value T : storable> Source<T> as Stored<T> id "store-rule" {
private state Value id "stored:value" on Self : optional<T> policy register default null;
bind content to state Value;
}`;
const atoms = `atom A id "a"; atom B id "b";
conform A as Source<string> id "a-string" {}
conform A as Source<int64> id "a-number" {}
conform B as Source<string> 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<int64> 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<object T> id "linkable" revision "linkable@1" {}',
'interface Links<object T> 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<atom Target> id "a-link" {} conform B as Linkable<atom Target> id "b-link" {}
conform<object T> Linkable<T> as Links<T> 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<object T> id "factory" revision "factory@1" {operation create id "create" : unit -> ref<T> {call id "call" execution transaction targets [browser,server];}}',
'interface Creates<object T> id "creates" revision "creates@1" {}',
'package Runtime id "runtime" revision "runtime@1" {operation create<object T> id "create" : unit -> ref<T> mode call execution transaction targets [browser,server] receiver any requires {creation result id "result" : T input unit;};}',
]);
const rule = `conform<object T> Creates<T> as Factory<T> id "factory-rule" {
bind create.call to package Runtime.create<T> with {result to creation T with {};};
}`;
const result = ok(
compile(`atom Subject id "subject"; atom Product id "product";
conform Subject as Creates<atom Product> 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<interface Creates<atom Subject>> 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<value Input, object Result> id "factory" revision "factory@1" {}',
'import interface Placeable; interface CanvasFactory<value Input, object Result implements Placeable> id "canvas-factory" revision "canvas-factory@1" {}',
];
const factoryRule =
'conform<value I, object R implements Placeable> Factory<I,R> as CanvasFactory<I,R> 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<unit,atom Product> id "unit-factory" {}
conform Owner as Factory<record { title:string; },atom Product> id "record-factory" {}
conform Owner as Factory<string,atom Other> 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<value T> id "a" revision "a@1" {}',
'interface B<value T> id "b" revision "b@1" {}',
]);
for (const [body, code] of [
['conform<value T> A<T> as A<list<T>> id "grow" {}', "recursive-conformance-rule"],
['conform<value T> A<T> as B<T> id "ab" {} conform<value T> B<T> as A<T> id "ba" {}', "recursive-conformance-rule"],
['conform<value T> A<list<T>> as B<T> id "nested" {}', "unsupported-rule-pattern"],
['conform<value T> A<string> as B<T> id "uncaptured" {}', "uncaptured-rule-parameter"],
['conform<object T> A<T> as B<string> 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<value A, value B> id "pair" revision "pair@1" {}',
'interface Same<value T, object Owner> id "same" revision "same@1" {}',
...genericResources,
]);
const result = ok(
compile(`
atom A id "atom-a";
conform A as Pair<string,string> id "equal" {}
conform A as Pair<string,int32> id "unequal" {}
conform<value T> Pair<T,T> as Same<T,Self> id "same-rule" {}
conform A as Factory<unit,atom A> id "factory" {}
${factoryRule}
conform<object R implements Eligible> Factory<unit,R> as Placeable id "seedless" {}
conform<object R implements Placeable> Factory<unit,R> 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/);
});