Compile and dispatch authoritative acceptance hooks with target-specific package roots
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
|
||||
const contract = `interface Limit id "limit" revision "limit@1" {
|
||||
operation change id "change" : int32 -> unit {
|
||||
call id "change:call" execution transaction targets [browser, server];
|
||||
}
|
||||
}`;
|
||||
export const runtime = `external atom Item id "item";
|
||||
package Runtime id "runtime" revision "runtime@1" {
|
||||
operation change id "runtime:change" : int32 -> unit mode call execution transaction targets [browser, server]
|
||||
receiver atom Item requires { state level id "write:value" : int32 [write]; } accept using check;
|
||||
operation check id "runtime:check" : int32 -> unit mode call execution snapshot targets [server]
|
||||
receiver atom Item requires {
|
||||
state level id "read:value" : int32 [read];
|
||||
state ceiling id "read:ceiling" : int32 [read];
|
||||
};
|
||||
}`;
|
||||
export const workspace = `workspace LimitTest id "workspace:limit" revision "workspace:limit@1" commit "${"a".repeat(40)}" {
|
||||
import interface Limit;
|
||||
import package Runtime;
|
||||
atom Item id "item";
|
||||
conform Item as Limit id "item:limit" {
|
||||
private state Value id "value" on Item : int32 policy register default 0;
|
||||
private state Ceiling id "ceiling" on Item : int32 policy register default 100;
|
||||
bind change.call to package Runtime.change with { level to state Value; }
|
||||
accept with { level to state Value; ceiling to state Ceiling; };
|
||||
}
|
||||
}`;
|
||||
export function compile(packageSource = runtime, workspaceSource = workspace) {
|
||||
const source = { repository: "https://example.test/limit.git", commit: "a".repeat(40) };
|
||||
const iface = compileCapabilityResourceSource(contract, { source });
|
||||
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
||||
const pkg = compileCapabilityResourceSource(packageSource, { source });
|
||||
if (!pkg.ok) return pkg;
|
||||
assert.equal(pkg.resource.kind, "package");
|
||||
if (pkg.resource.kind !== "package") throw Error("Expected package");
|
||||
return compileCapabilitySource(workspaceSource, "workspace.qx", {
|
||||
interfaces: new Map([["Limit", iface.resource.revision]]),
|
||||
packages: new Map([["Runtime", pkg.resource.revision]]),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { compileCapabilityResourceSource } from "../src/capability-language/parser.js";
|
||||
import { formatQx } from "../src/capability-language/source.js";
|
||||
import { compileExecutionPlan, compileKernelProgram, checkExecutionWorld } from "../src/execution-world/index.js";
|
||||
import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js";
|
||||
import { buildArtifactFixture } from "./artifact-fixture.js";
|
||||
import { compile, runtime, workspace } from "./acceptance-fixture.js";
|
||||
test("acceptance syntax retains separate final-state ports through formatting, planning, adapters and artifacts", async () => {
|
||||
const result = compile();
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
const formatted = compile(formatQx(runtime), formatQx(workspace));
|
||||
assert.ok(formatted.ok, JSON.stringify(formatted.diagnostics));
|
||||
const plan = compileExecutionPlan(result.workspace);
|
||||
assert.equal(compileExecutionPlan(formatted.workspace).digest, plan.digest);
|
||||
const kernel = compileKernelProgram(plan) as {
|
||||
operations: { binding: { ports: { id: string }[]; acceptance: { export: string; ports: { id: string }[] } } }[];
|
||||
packages: { exports: { id: string; acceptance?: string }[] }[];
|
||||
};
|
||||
assert.deepEqual(
|
||||
kernel.operations[0]!.binding.ports.map((p) => p.id),
|
||||
["write:value"],
|
||||
);
|
||||
assert.equal(kernel.operations[0]!.binding.acceptance.export, "runtime:check");
|
||||
assert.deepEqual(kernel.operations[0]!.binding.acceptance.ports.map((p) => p.id).sort(), [
|
||||
"read:ceiling",
|
||||
"read:value",
|
||||
]);
|
||||
assert.equal(kernel.packages[0]!.exports.find((e) => e.id === "runtime:change")!.acceptance, "runtime:check");
|
||||
assert.ok(
|
||||
!generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "browser" }).includes(
|
||||
'"runtime:check"',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "server" }).includes(
|
||||
'"runtime:check"',
|
||||
),
|
||||
);
|
||||
const artifacts = buildArtifactFixture(plan, "runtime@1");
|
||||
await checkExecutionWorld(plan, [artifacts.manifest], artifacts.read);
|
||||
});
|
||||
test("acceptance declarations reject wrong effects/signatures and missing or mutable ports", () => {
|
||||
for (const [pkg, ws] of [
|
||||
[runtime.replace("snapshot targets [server]", "transaction targets [server]"), workspace],
|
||||
[runtime.replace("snapshot targets [server]", "snapshot targets [browser, server]"), workspace],
|
||||
[
|
||||
runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : string -> unit'),
|
||||
workspace,
|
||||
],
|
||||
[
|
||||
runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : int32 -> int32'),
|
||||
workspace,
|
||||
],
|
||||
[runtime.replace('id "read:ceiling" : int32 [read]', 'id "read:ceiling" : int32 [write]'), workspace],
|
||||
[runtime, workspace.replace("ceiling to state Ceiling;", "")],
|
||||
[runtime.replace(" accept using check", ""), workspace],
|
||||
])
|
||||
assert.equal(compile(pkg, ws).ok, false, `${pkg}\n${ws}`);
|
||||
});
|
||||
test("generic acceptance syntax rejects explicitly until specialization is supported", () => {
|
||||
const result = compileCapabilityResourceSource(
|
||||
runtime.replace("operation change id", "operation change<value T> id"),
|
||||
{
|
||||
source: { repository: "https://example.test/limit.git", commit: "a".repeat(40) },
|
||||
},
|
||||
);
|
||||
assert.equal(result.ok, false);
|
||||
assert.ok(result.diagnostics.some((d) => d.code === "unsupported-generic-acceptance"));
|
||||
});
|
||||
@@ -21,7 +21,9 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
|
||||
path,
|
||||
ports: "quixos-transaction-ports-v1" as const,
|
||||
...(compatibility === "quixos-js-module-v1" ? { registryExport: "handlers" } : {}),
|
||||
exports: expected.exports.map((e) => e.id),
|
||||
exports: expected.exports
|
||||
.filter((e) => target === "server" || (e.execution.kind !== "remote" && e.execution.targets.includes(target)))
|
||||
.map((e) => e.id),
|
||||
files: [
|
||||
{
|
||||
path,
|
||||
|
||||
@@ -89,6 +89,13 @@ void negative;
|
||||
);
|
||||
|
||||
const { asyncObjectPlan } = await import("./async-fixture.js");
|
||||
const { compile: compileAcceptance } = await import("./acceptance-fixture.js");
|
||||
const acceptance = compileAcceptance();
|
||||
if (!acceptance.ok) throw Error(JSON.stringify(acceptance.diagnostics));
|
||||
const acceptancePlan = compileExecutionPlan(acceptance.workspace);
|
||||
await mkdir("dist/portable-reader/acceptance", { recursive: true });
|
||||
await writeFile("dist/portable-reader/acceptance/plan.json", JSON.stringify(acceptancePlan));
|
||||
await writeFile("dist/portable-reader/acceptance/kernel.json", JSON.stringify(compileKernelProgram(acceptancePlan)));
|
||||
const { recurringTaskPlan } = await import("./recurring-fixture.js");
|
||||
const recurringPlan = await recurringTaskPlan();
|
||||
await mkdir("dist/portable-reader/recurring", { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user