Files
quixos-protocol/test/creation-contracts.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

219 lines
10 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import {
compileCapabilityResourceSource,
compileCapabilitySource,
formatQx,
} from "../src/capability-language/index.js";
import type { InterfaceRevision } from "../src/capability-model/types.js";
const source = { repository: "https://example.test/factories.git", commit: "a".repeat(40) };
const seed = "record { data: record { title: string; }; tags: record { tags: list<string>; }; }";
const iface = (text: string): InterfaceRevision => {
const result = compileCapabilityResourceSource(text, { source });
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
return result.resource.revision;
};
const data = iface(`interface Data id "data" revision "data@1" {
value title id "title" : string { get id "title:get" execution snapshot targets [browser, server]; }
}`);
const tagged = iface(`interface Tagged id "tagged" revision "tagged@1" {
value tags id "tags" : list<string> { get id "tags:get" execution snapshot targets [browser, server]; }
}`);
const factory = iface(`external atom Note id "note";
interface Factory id "factory" revision "factory@1" {
static operation create id "create" : ${seed} -> atom-ref<Note> { call id "create:call" execution transaction targets [browser, server]; }
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction targets [browser, server]; }
}`);
const runtime = compileCapabilityResourceSource(
`external atom Note id "note";
package Runtime id "runtime" revision "runtime@1" {
function createNote id "create-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires {
creation note id "new-note" : Note input ${seed};
};
function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires {
creation note id "new-note" : Note input ${seed};
};
}`,
{ source },
);
assert.ok(runtime.ok && runtime.resource.kind === "package", JSON.stringify(runtime.diagnostics));
const packageRevision = runtime.resource.revision;
const text = `workspace Notes id "notes" revision "notes@1" commit "${"a".repeat(40)}" {
import interface Data; import interface Tagged; import interface Factory; import package Runtime;
atom Note id "note";
conform Note as Data id "owner:data" {
private state Title id "title-state" on Note : string policy register;
initialize { title to state Title; };
bind title.get to state Title.read;
}
conform Note as Tagged id "owner:tags" {
private state Tags id "tags-state" on Note : list<string> policy register;
initialize { tags to state Tags; };
bind tags.get to state Tags.read;
}
conform Note as Factory id "owner:factory" {
bind create.call to package Runtime.createNote with {
note to creation Note with { Data from input.data; Tagged from input.tags; };
};
bind importNote.call to package Runtime.importNote with {
note to creation Note with { Data from input.data; Tagged from input.tags; };
};
}
}`;
const compile = (textOverride = text) =>
compileCapabilitySource(formatQx(textOverride), "notes.qx", {
interfaces: new Map([
["Data", data],
["Tagged", tagged],
["Factory", factory],
]),
interfaceClosure: [data, tagged, factory],
packages: new Map([["Runtime", packageRevision]]),
packageClosure: [packageRevision],
});
test("two factories initialize two independent state owners without constructors", () => {
const result = compile();
assert.ok(result.ok, JSON.stringify(result.diagnostics));
assert.deepEqual(result.workspace.constructors, []);
assert.equal(result.workspace.conformances.filter((c) => c.initialization).length, 2);
const owners = result.workspace.conformances[2]!.operationBindings.map((b) =>
b.binding.kind === "package" ? b.binding.dependencies[0]!.binding : undefined,
);
for (const binding of owners) {
assert.ok(binding?.kind === "creation");
assert.deepEqual(binding.owners, [
{ interfaceRevisionId: "data@1", inputPath: ["data"] },
{ interfaceRevisionId: "tagged@1", inputPath: ["tags"] },
]);
}
});
test("rule-owned state uses the same checked initialization and creation obligations", () => {
const source = text
.replace('conform Note as Data id "owner:data"', 'conform Tagged as Data id "rule:data"')
.replace("on Note : string", "on Self : string");
const result = compile(source);
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const owner = result.workspace.conformances.find((c) => c.ruleOrigin?.ruleId === "rule:data")!;
assert.equal(owner.initialization?.[0]?.binding.kind, "state");
assert(owner.privateAttachments[0]!.id.startsWith("rule:sha256:"));
for (const [invalid, diagnostic] of [
[source.replaceAll("Data from input.data;", ""), "Required state"],
[source.replace("initialize { title to state Title; };", ""), "no declared initialization mapping"],
[source.replace("title to state Title;", "title to state Tags;"), "rule-private storage"],
[source.replaceAll("Data from input.data;", "Data from input.tags;"), "does not match"],
]) {
const failure = compile(invalid!);
assert.equal(failure.ok, false);
assert.match(JSON.stringify(failure.diagnostics), new RegExp(diagnostic!));
}
});
test("creation rejects missing seeds, wrong input shapes, and private initialization laundering", () => {
for (const [edit, message] of [
[text.replaceAll("Tagged from input.tags;", ""), "Required state tags-state"],
[text.replaceAll("Data from input.data;", "Data from input.tags;"), "does not match"],
[text.replace("title to state Title;", "title to state Tags;"), "owned by this conformance"],
[
text.replaceAll("Data from input.data;", "Data from input.data; Data from input.data;"),
"Duplicate initialization owner",
],
]) {
const result = compile(edit!);
assert.equal(result.ok, false);
assert.match(JSON.stringify(result.diagnostics), new RegExp(message!));
}
});
test("defaults supply unmapped state and seeds cannot invoke an undeclared owner", () => {
const withDefault = text
.replace("on Note : list<string> policy register;", "on Note : list<string> policy register default [];")
.replaceAll("Tagged from input.tags;", "");
const result = compile(withDefault);
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const missing = compile(text.replace("initialize { tags to state Tags; };", ""));
assert.equal(missing.ok, false);
assert.match(JSON.stringify(missing.diagnostics), /no declared initialization mapping/);
});
test("creation ports cannot be used in snapshot execution", () => {
const result = compileCapabilityResourceSource(
`external atom Note id "note";
package P id "p" revision "p@1" {
function createNote id "create" : unit -> atom-ref<Note> execution snapshot targets [browser, server] requires {
creation note id "note" : Note input record { title:string; };
};
}`,
{ source },
);
assert.equal(result.ok, false);
assert.match(JSON.stringify(result.diagnostics), /creation requires transaction/);
});
test("the authored storage policy is register, without an old spelling alias", () => {
const result = compileCapabilitySource(text.replaceAll("policy register", "policy optimistic-register"), "notes.qx");
assert.equal(result.ok, false);
assert.ok(result.diagnostics.some((d) => d.phase === "syntax"));
});
test("initialization contracts participate in executable source identity", async () => {
const { runtimeContracts } = await import("../src/capability-model/index.js");
const result = compile();
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const before = runtimeContracts(result.workspace);
const changed = structuredClone(result.workspace);
changed.conformances[0]!.initialization![0]!.input = "renamed";
assert.notDeepEqual(runtimeContracts(changed), before);
});
test("to-many initialization requires stable entry keys and typed targets", async () => {
const { validateInitialization } = await import("../src/capability-model/initialization.js");
const { capabilityId } = await import("../src/capability-model/index.js");
const result = compile();
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const workspace = result.workspace;
const owner = workspace.conformances[1]!;
const state = owner.privateAttachments[0]!;
assert.equal(state.kind, "state");
if (state.kind !== "state") return;
state.defaultValue = [];
const edgeId = capabilityId.edgeType("children"),
projectionId = capabilityId.edgeProjection("children:parent");
owner.privateAttachments.push({
kind: "edge",
id: edgeId,
displayName: "Children",
endpoints: [
{
projectionId,
displayName: "children",
constraint: { kind: "atom", atomId: owner.atomId },
cardinality: "many-unique",
ordered: false,
},
{
projectionId: capabilityId.edgeProjection("children:child"),
displayName: "parent",
constraint: { kind: "atom", atomId: owner.atomId },
cardinality: "optional-one",
ordered: false,
},
],
});
owner.initialization = [{ input: "children", binding: { kind: "edge", edgeTypeId: edgeId, projectionId } }];
const entryType = {
kind: "record" as const,
fields: {
key: { kind: "scalar" as const, name: "string" as const },
target: { kind: "object-ref" as const, expectation: { kind: "atom" as const, atomId: owner.atomId } },
},
};
for (const entry of workspace.packageImports[0]!.exports) {
const port = entry.dependencyPorts[0]!.requirement;
assert.ok(port.kind === "creation" && port.inputType.kind === "record");
port.inputType.fields.tags = { kind: "record", fields: { children: { kind: "list", value: entryType } } };
}
assert.deepEqual(validateInitialization(workspace), []);
Reflect.deleteProperty(entryType.fields, "key");
assert.ok(validateInitialization(workspace).some((i) => i.message.includes("does not match")));
});