Host checked adapter-object controllers through bounded registries (WI-13)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import { parseControllerCatalog } from "../src/capability-model/controllers.js";
|
||||
const source = { repository: "https://example.test/device.git", commit: "a".repeat(40) };
|
||||
test("controller discovery requires complete bounded accepted-state membership", async () => {
|
||||
const result = compileCapabilityResourceSource(
|
||||
await readFile("test/fixtures/controller-device/DeviceController.qx", "utf8"),
|
||||
{ source },
|
||||
);
|
||||
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
|
||||
const iface = result.resource.revision;
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
'import interface DeviceController;package Driver id "driver" revision "driver@1" {query Enabled id "enabled" root workspace DeviceController document "enabled.graphql" operation "Enabled" {max rows 100;}}',
|
||||
{ source, environment: { interfaces: new Map([["DeviceController", iface]]), interfaceClosure: [iface] } },
|
||||
);
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
const declaration = pkg.resource.revision.queries![0]!;
|
||||
const metadata = JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
controllers: [
|
||||
{
|
||||
id: "driver",
|
||||
registry: "enabled",
|
||||
subject: "device-controller@1",
|
||||
snapshot: "device:snapshot:call",
|
||||
remote: "device:apply:call",
|
||||
input: "value",
|
||||
},
|
||||
],
|
||||
});
|
||||
const check = async (document: string) =>
|
||||
parseControllerCatalog(metadata, [iface], [await compileQuery(declaration, [iface], async () => document)]);
|
||||
assert.equal(
|
||||
(await check("query Enabled {root {objects(all:64,where:{enabled:{eq:true}}) {entries {node {_qx {ref}}}}}}"))
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
for (const document of [
|
||||
"query Enabled {root {objects(first:64) {entries {node {_qx {ref}}}}}}",
|
||||
"query Enabled {root {objects(all:65) {entries {node {_qx {ref}}}}}}",
|
||||
"query Enabled {root {devices:objects(all:64) {entries {node {_qx {ref}}}}}}",
|
||||
"query Enabled {root {objects(all:64) {entries {node {enabled}}}}}",
|
||||
"query Enabled($enabled:Boolean!) {root {objects(all:64,where:{enabled:{eq:$enabled}}) {entries {node {_qx {ref}}}}}}",
|
||||
])
|
||||
await assert.rejects(check(document), /registry|Controller/);
|
||||
assert.throws(() => parseControllerCatalog(metadata, [iface], []), /registry/);
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
||||
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/index.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import { parseControllerCatalog } from "../src/capability-model/controllers.js";
|
||||
import {
|
||||
compileExecutionPlan,
|
||||
generateInterfaceClientBindings,
|
||||
generatePackageQueryClientBindings,
|
||||
} from "../src/execution-world/index.js";
|
||||
const read = (file: string) => readFile("test/fixtures/controller-device/" + file, "utf8");
|
||||
const source = { repository: "https://example.test/device.git", commit: "a".repeat(40) };
|
||||
const device = compileCapabilityResourceSource(await read("Device.qx"), { source });
|
||||
assert.ok(device.ok && device.resource.kind === "interface", JSON.stringify(device.diagnostics));
|
||||
const control = compileCapabilityResourceSource(await read("DeviceController.qx"), { source });
|
||||
assert.ok(control.ok && control.resource.kind === "interface", JSON.stringify(control.diagnostics));
|
||||
const iface = device.resource.revision,
|
||||
controllerInterface = control.resource.revision,
|
||||
environment = {
|
||||
interfaces: new Map([
|
||||
["Device", iface],
|
||||
["DeviceController", controllerInterface],
|
||||
]),
|
||||
interfaceClosure: [iface, controllerInterface],
|
||||
};
|
||||
const driver = compileCapabilityResourceSource(await read("Driver.qx"), { source, environment });
|
||||
assert.ok(driver.ok && driver.resource.kind === "package", JSON.stringify(driver.diagnostics));
|
||||
const pkg = driver.resource.revision;
|
||||
pkg.checkedQueries = await Promise.all(
|
||||
pkg.queries!.map((query) => compileQuery(query, [iface, controllerInterface], read)),
|
||||
);
|
||||
pkg.controllers = parseControllerCatalog(
|
||||
JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
controllers: [
|
||||
{
|
||||
id: "device-driver",
|
||||
registry: "enabled",
|
||||
subject: "device-controller@1",
|
||||
snapshot: "device:snapshot:call",
|
||||
remote: "device:apply:call",
|
||||
input: "value",
|
||||
},
|
||||
],
|
||||
}),
|
||||
[iface, controllerInterface],
|
||||
pkg.checkedQueries,
|
||||
);
|
||||
for (const variant of ["controller", "controller-next"]) {
|
||||
const extra = variant.endsWith("next") ? 'atom Dashboard id "dashboard";' : "";
|
||||
const result = compileCapabilitySource((await read("workspace.qx")).replace(/}\s*$/, extra + "\n}"), "workspace.qx", {
|
||||
...environment,
|
||||
packages: new Map([["Driver", pkg]]),
|
||||
});
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
const plan = compileExecutionPlan(result.workspace),
|
||||
root = "dist/managed/" + variant;
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(root + "/client.ts", generateInterfaceClientBindings({ Device: iface }));
|
||||
await writeFile(root + "/queries.ts", generatePackageQueryClientBindings(pkg));
|
||||
for (const [file, value] of Object.entries({
|
||||
"candidate.json": result.workspace,
|
||||
"execution-plan.json": plan,
|
||||
"entry.json": null,
|
||||
"source.json": { kind: "workspace", ...source },
|
||||
}))
|
||||
await writeFile(root + "/" + file, JSON.stringify(value));
|
||||
}
|
||||
@@ -169,3 +169,5 @@ package Owner id "package:owner" revision "package:owner@1" {
|
||||
}))
|
||||
await writeFile(`${root}/${file}`, JSON.stringify(value));
|
||||
}
|
||||
|
||||
await import("./emit-controllers.js");
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
external atom Display id "device:display";
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
interface Device id "device" revision "device@1" {
|
||||
static operation create id "device:create" : string -> atom-ref<Display> {call id "device:create:call" execution transaction targets [browser,server];}
|
||||
queryable value enabled id "device:enabled" : bool {get id "device:enabled:get" execution snapshot targets [browser,server];set id "device:enabled:set" execution transaction targets [browser,server];}
|
||||
queryable value requested id "device:requested" : Request {get id "device:requested:get" execution snapshot targets [browser,server];}
|
||||
queryable value observed id "device:observed" : optional<Request> {get id "device:observed:get" execution snapshot targets [browser,server];}
|
||||
operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction targets [browser,server];}
|
||||
operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction targets [server];}
|
||||
operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction targets [server];}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
interface DeviceController id "device-controller" revision "device-controller@1" {
|
||||
queryable portable value enabled id "controller:enabled" : bool {get id "controller:enabled:get" execution snapshot targets [server];}
|
||||
operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" execution snapshot targets [server];}
|
||||
operation apply id "device:apply" : optional<Request> -> unit {call id "device:apply:call" execution remote;}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import interface Device;
|
||||
import interface DeviceController;
|
||||
external atom Display id "device:display";
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
package Driver id "driver" revision "driver@1" {
|
||||
function create id "driver:create" : string -> atom-ref<Display> execution transaction targets [browser,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional<Request>;};};
|
||||
operation request id "driver:request" : string -> unit mode call execution transaction targets [browser,server] receiver any requires {state requested id "requested" : Request [read,write];};
|
||||
operation snapshot id "driver:snapshot" : unit -> optional<Request> mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get,requested.get,observed.get];};
|
||||
operation recordObservation id "driver:record" : Request -> unit mode call execution transaction targets [server] receiver any requires {state requested id "requested" : Request [read];state observed id "observed" : optional<Request> [write];};
|
||||
operation apply id "driver:apply" : optional<Request> -> unit mode call execution remote receiver any requires {interface device id "device" : Device using [recordObservation.call];};
|
||||
operation enabled id "driver:enabled" : unit -> bool mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get];};
|
||||
query Enabled id "enabled" root workspace DeviceController document "enabled.graphql" operation "Enabled" {max rows 64;}
|
||||
query Inputs id "inputs" root Device document "inputs.graphql" operation "Inputs" {replica;}
|
||||
query View id "view" root Device document "inputs.graphql" operation "Inputs" {local from Inputs;watch;}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
query Enabled {root {objects(all:64, where:{enabled:{eq:true}}) {entries {node {_qx {ref}}}}}}
|
||||
@@ -0,0 +1 @@
|
||||
query Inputs {root {enabled requested {url text version} observed {url text version}}}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
workspace Controllers id "workspace:controllers" revision "workspace:controllers@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
import interface Device;
|
||||
import interface DeviceController;
|
||||
import package Driver;
|
||||
atom Display id "device:display";
|
||||
conform Display as Device id "display:device" {
|
||||
private state Enabled id "display:enabled" on Display : bool policy register;
|
||||
private state Requested id "display:requested" on Display : record {url:string;text:string;version:int32;} policy register;
|
||||
private state Observed id "display:observed" on Display : optional<record {url:string;text:string;version:int32;}> policy register;
|
||||
initialize {enabled to state Enabled;requested to state Requested;observed to state Observed;};
|
||||
bind create.call to package Driver.create with {device to creation Display with {Device from input;};};
|
||||
bind enabled.get to state Enabled.read;
|
||||
bind enabled.set to state Enabled.write;
|
||||
bind requested.get to state Requested.read;
|
||||
bind observed.get to state Observed.read;
|
||||
bind requestText.call to package Driver.request with {requested to state Requested;};
|
||||
bind recordObservation.call to package Driver.recordObservation with {requested to state Requested;observed to state Observed;};
|
||||
bind remove.call to lifecycle.delete;
|
||||
}
|
||||
conform Display as DeviceController id "display:controller" {
|
||||
bind enabled.get to package Driver.enabled with {data to interface Device;};
|
||||
bind snapshot.call to package Driver.snapshot with {data to interface Device;};
|
||||
bind apply.call to package Driver.apply with {device to interface Device;};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user