2026-09-19 22:20:55 -07:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { test } from "node:test";
|
|
|
|
|
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js";
|
|
|
|
|
const source = { repository: "https://example.test/edit.git", commit: "a".repeat(40) };
|
|
|
|
|
const content = "record { text:string; tags:list<string>; }";
|
|
|
|
|
const iface = compileCapabilityResourceSource(
|
|
|
|
|
`interface Comment id "comment" revision "comment@1" {
|
|
|
|
|
value content id "content" : ${content} {
|
|
|
|
|
get id "content:get" execution snapshot targets [browser, server];
|
|
|
|
|
edit id "content:edit" execution transaction targets [browser, server];
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
{ source },
|
|
|
|
|
);
|
|
|
|
|
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
|
|
|
|
const contract = iface.resource.revision;
|
|
|
|
|
const compile = (policy = `crdt(${content})`, binding = "edit") =>
|
|
|
|
|
compileCapabilitySource(
|
|
|
|
|
`
|
|
|
|
|
workspace Comments id "w" revision "w@1" commit "${"a".repeat(40)}" {
|
|
|
|
|
import interface Comment;
|
2026-09-23 00:01:03 -07:00
|
|
|
atom CommentAtom id "comment";
|
|
|
|
|
conform CommentAtom as Comment id "owner:comment" {
|
|
|
|
|
private state Content id "content" on CommentAtom : ${content} policy ${policy};
|
2026-09-19 22:20:55 -07:00
|
|
|
initialize { content to state Content; };
|
|
|
|
|
bind content.get to state Content.read;
|
|
|
|
|
bind content.edit to state Content.${binding};
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
"comments.qx",
|
|
|
|
|
{ interfaces: new Map([["Comment", contract]]), interfaceClosure: [contract] },
|
|
|
|
|
);
|
|
|
|
|
test("native edit is an explicit typed change capability over structured content", () => {
|
|
|
|
|
const result = compile();
|
|
|
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
|
|
|
const member = contract.members[0]!;
|
|
|
|
|
assert.equal(member.kind, "value");
|
|
|
|
|
if (member.kind !== "value") return;
|
|
|
|
|
assert.deepEqual(member.operations[1]!.inputType, { kind: "change", value: member.valueType });
|
|
|
|
|
assert.equal(result.workspace.conformances[0]!.operationBindings[1]!.binding.kind, "state");
|
|
|
|
|
});
|
|
|
|
|
test("edit cannot target a register, mismatched profile, or ordinary replacement", () => {
|
|
|
|
|
for (const [policy, binding] of [
|
|
|
|
|
["register", "edit"],
|
|
|
|
|
["crdt(bytes)", "edit"],
|
|
|
|
|
[`crdt(${content})`, "write"],
|
|
|
|
|
]) {
|
|
|
|
|
const result = compile(policy, binding);
|
|
|
|
|
assert.equal(result.ok, false);
|
|
|
|
|
assert.ok(
|
|
|
|
|
result.diagnostics.some((d) => d.code === "invalid-state-binding"),
|
|
|
|
|
JSON.stringify(result.diagnostics),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-09-20 22:57:27 -07:00
|
|
|
test("authored prepared changes retain their materialized type as transport values", () => {
|
2026-09-19 22:20:55 -07:00
|
|
|
const result = compileCapabilityResourceSource(
|
|
|
|
|
'package P id "p" revision "p@1" { function applyChange id "apply" : change<string> -> unit; }',
|
|
|
|
|
{ source },
|
|
|
|
|
);
|
2026-09-20 22:57:27 -07:00
|
|
|
assert.ok(result.ok && result.resource.kind === "package", JSON.stringify(result.diagnostics));
|
|
|
|
|
assert.deepEqual(result.resource.revision.exports[0]!.inputType, {
|
|
|
|
|
kind: "change",
|
|
|
|
|
value: { kind: "scalar", name: "string" },
|
|
|
|
|
});
|
2026-09-19 22:20:55 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("generic edit retains its materialized type through specialization", () => {
|
|
|
|
|
const generic = compileCapabilityResourceSource(
|
|
|
|
|
`interface Cell<value T : storable> id "cell" revision "cell@1" {
|
|
|
|
|
value content id "content" : T { get id "get"; edit id "edit" execution transaction targets [browser, server]; }
|
|
|
|
|
}`,
|
|
|
|
|
{ source },
|
|
|
|
|
);
|
|
|
|
|
assert.ok(generic.ok && generic.resource.kind === "interface", JSON.stringify(generic.diagnostics));
|
|
|
|
|
const result = compileCapabilitySource(
|
|
|
|
|
`workspace W id "w" revision "w@1" commit "${"a".repeat(40)}" {
|
|
|
|
|
import interface Cell;
|
|
|
|
|
atom Note id "note";
|
|
|
|
|
conform Note as Cell<${content}> {
|
|
|
|
|
private state Content id "content" on Note : ${content} policy crdt(${content});
|
|
|
|
|
bind content to state Content;
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
"cell.qx",
|
|
|
|
|
{ interfaces: new Map([["Cell", generic.resource.revision]]), interfaceClosure: [generic.resource.revision] },
|
|
|
|
|
);
|
|
|
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
|
|
|
const applied = result.workspace.interfaceImports.find((i) => i.application)!;
|
|
|
|
|
assert.equal(applied.members[0]!.operations[1]!.inputType.kind, "change");
|
|
|
|
|
});
|