Prove cross-package application composition through public capabilities (WI-15)

This commit is contained in:
Timothy J. Aveni
2026-09-21 19:07:32 -07:00
parent d824634859
commit e5544dde21
6 changed files with 70 additions and 11 deletions
+2 -2
View File
@@ -192,8 +192,8 @@ function methods(
`Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`, `Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`,
); );
if (execution.kind === "transaction" && !remote) writable(); if (execution.kind === "transaction" && !remote) writable();
if (req.receiver === "input" && (remote || execution.kind !== "snapshot")) if (req.receiver === "input" && remote)
throw new PortableBindingError(path, "Reference-selected dependencies require portable snapshot calls"); throw new PortableBindingError(path, "Reference-selected dependencies require portable execution");
return method( return method(
`${member.displayName}.${op.displayName}`, `${member.displayName}.${op.displayName}`,
{ kind: "interface", operationId: op.id }, { kind: "interface", operationId: op.id },
+3 -6
View File
@@ -68,14 +68,11 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
const selected = requirement.operations; const selected = requirement.operations;
if (requirement.receiver === "input") { if (requirement.receiver === "input") {
if (!entry.execution || entry.execution.kind === "remote" || !selected?.length) if (!entry.execution || entry.execution.kind === "remote" || !selected?.length)
fail( fail(portPath, "Reference-selected calls require portable execution and an explicit selection");
portPath,
"Reference-selected reads require a portable snapshot or transaction and an explicit selection",
);
for (const id of selected ?? []) { for (const id of selected ?? []) {
const operation = contract && operations(id, contract); const operation = contract && operations(id, contract);
if (operation?.execution?.kind !== "snapshot" || operation.scope === "class") if (!operation?.execution || operation.execution.kind === "remote" || operation.scope === "class")
fail(portPath, "Reference-selected dependencies currently support object snapshot operations only"); fail(portPath, "Reference-selected dependencies require portable object operations");
} }
} }
if (selected && (!selected.length || new Set(selected).size !== selected.length)) if (selected && (!selected.length || new Set(selected).size !== selected.length))
+16 -2
View File
@@ -46,11 +46,25 @@ pkg.controllers = parseControllerCatalog(
[iface, controllerInterface], [iface, controllerInterface],
pkg.checkedQueries, pkg.checkedQueries,
); );
const viewer = compileCapabilityResourceSource(await read("Viewer.qx"), {
source: { ...source, repository: "https://example.test/device-viewer.git" },
environment,
});
assert.ok(viewer.ok && viewer.resource.kind === "package", JSON.stringify(viewer.diagnostics));
const viewerPackage = viewer.resource.revision;
viewerPackage.checkedQueries = await Promise.all(
viewerPackage.queries!.map((query) => compileQuery(query, [iface], read)),
);
for (const variant of ["controller", "controller-next"]) { for (const variant of ["controller", "controller-next"]) {
const extra = variant.endsWith("next") ? 'atom Dashboard id "dashboard";' : ""; const extra = variant.endsWith("next")
? 'atom Dashboard id "dashboard"; import package DeviceViewer; presentation DeviceStatus id "device:status" for Device using DeviceViewer.Status;'
: "";
const result = compileCapabilitySource((await read("workspace.qx")).replace(/}\s*$/, extra + "\n}"), "workspace.qx", { const result = compileCapabilitySource((await read("workspace.qx")).replace(/}\s*$/, extra + "\n}"), "workspace.qx", {
...environment, ...environment,
packages: new Map([["Driver", pkg]]), packages: new Map([
["Driver", pkg],
...(variant.endsWith("next") ? [["DeviceViewer", viewerPackage] as const] : []),
]),
}); });
assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.ok(result.ok, JSON.stringify(result.diagnostics));
const plan = compileExecutionPlan(result.workspace), const plan = compileExecutionPlan(result.workspace),
+7
View File
@@ -0,0 +1,7 @@
import interface Device;
external atom Display id "device:display";
package DeviceViewer id "device-viewer" revision "device-viewer@1" {
component Status id "component:device:status" subject Device runtime react;
query Inputs id "device-viewer:inputs" root Device document "viewer.graphql" operation "Status" {replica;}
query View id "device-viewer:view" root Device document "viewer.graphql" operation "Status" {local from Inputs;watch;}
}
+1
View File
@@ -0,0 +1 @@
query Status {root {enabled requested {text version} observed {text version}}}
+41 -1
View File
@@ -56,7 +56,7 @@ test("reference-selected dependencies round trip and do not require a recursive
assert.ok(generated.includes('ManagedReference<"interface", "data@1">')); assert.ok(generated.includes('ManagedReference<"interface", "data@1">'));
assert.ok(generated.includes('"receiver"')); assert.ok(generated.includes('"receiver"'));
}); });
test("both sides explicitly grant reference selection and only object snapshots are permitted", () => { test("both sides explicitly grant reference selection and remote execution is excluded", () => {
assert.equal(compile(pkg, workspace.replace("Data from input", "Data")).ok, false); assert.equal(compile(pkg, workspace.replace("Data from input", "Data")).ok, false);
assert.equal(compile(pkg.replace(" from input", ""), workspace).ok, false); assert.equal(compile(pkg.replace(" from input", ""), workspace).ok, false);
assert.equal(compile(pkg.replace("execution snapshot targets [server]", "execution remote"), workspace).ok, false); assert.equal(compile(pkg.replace("execution snapshot targets [server]", "execution remote"), workspace).ok, false);
@@ -88,3 +88,43 @@ test("portable input-selected reads require the dependency's declared target sup
const result = compile(portable, workspace, iface.replaceAll("targets [server]", "targets [browser, server]")); const result = compile(portable, workspace, iface.replaceAll("targets [server]", "targets [browser, server]"));
assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.ok(result.ok, JSON.stringify(result.diagnostics));
}); });
test("input-selected transactions compose only within a compatible transaction contract", () => {
const txInterface = iface
.replace('get id "content:get" execution snapshot', 'get id "content:get" execution transaction')
.replace('call id "peek:call" execution snapshot', 'call id "peek:call" execution transaction');
const txPackage = pkg.replace("execution snapshot targets [server]", "execution transaction targets [server]");
// Use an operation for the selected write; value getters themselves must remain snapshots.
const contract = txInterface.replace(
'value content id "content" : string { get id "content:get" execution transaction targets [server]; }',
'operation update id "content" : unit -> string { call id "content:get" execution transaction targets [server]; }',
);
const implementation = txPackage.replace("using [content.get]", "using [update.call]");
const model = workspace.replace(
"bind content.get to state Content.read;",
"bind update.call to package Runtime.update with { data to state Content; };",
);
const sourcePackage = implementation.replace(
/\n}$/,
'\n operation update id "runtime:update" : unit -> string mode call execution transaction targets [server] receiver atom Item requires {state data id "data" : string [read,write];};\n}',
);
const result = compile(sourcePackage, model, contract);
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const plan = compileExecutionPlan(result.workspace);
const generated = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
target: "server",
});
assert.ok(generated.includes('"receiver"'));
assert.ok(generated.includes('"update.call"'));
assert.equal(
compile(
sourcePackage.replace(
'operation peek id "runtime:peek" : interface-ref<Data> -> string mode call execution transaction',
'operation peek id "runtime:peek" : interface-ref<Data> -> string mode call execution snapshot',
),
model,
contract,
).ok,
false,
);
});