Prove PresentationMount with authored generic graph operations
This commit is contained in:
@@ -2182,6 +2182,10 @@ const lowerConformanceRule = (state: LoweringState, context: ConformanceDeclCont
|
|||||||
} else if (provider.STATE() || provider.EDGE()) {
|
} else if (provider.STATE() || provider.EDGE()) {
|
||||||
const name = identifier(provider.identifier(0));
|
const name = identifier(provider.identifier(0));
|
||||||
const expected = provider.STATE() ? "state" : "edge";
|
const expected = provider.STATE() ? "state" : "edge";
|
||||||
|
// A rule may traverse another owner's public inverse projection, just as
|
||||||
|
// a concrete conformance may. Resolve external edges after specialization;
|
||||||
|
// ordinary ownership validation still permits only public resolve access.
|
||||||
|
if (provider.EDGE() && !privateStorage.has(name)) continue;
|
||||||
if (privateStorage.get(name) !== expected)
|
if (privateStorage.get(name) !== expected)
|
||||||
loweringIssue(
|
loweringIssue(
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -319,3 +319,29 @@ test("chains do not duplicate an application and explicit targets conflict", ()
|
|||||||
assert.match(JSON.stringify(conflict.diagnostics), /explicit-c/);
|
assert.match(JSON.stringify(conflict.diagnostics), /explicit-c/);
|
||||||
assert.match(JSON.stringify(conflict.diagnostics), /rule bc/);
|
assert.match(JSON.stringify(conflict.diagnostics), /rule bc/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("rule inverse reads retain the concrete conformance ownership checks", () => {
|
||||||
|
const compile = fixture([
|
||||||
|
'interface Parent id "parent" revision "parent@1" {}',
|
||||||
|
'interface Child id "child" revision "child@1" {}',
|
||||||
|
'import interface Parent; interface Owned id "owned" revision "owned@1" {queryable relation parent id "parent-ref" : optional-one interface Parent {resolve id "parent-read" execution snapshot targets [browser,server];}}',
|
||||||
|
]);
|
||||||
|
const source = `
|
||||||
|
atom P id "p"; atom C id "c";
|
||||||
|
conform P as Parent id "parent-impl" {
|
||||||
|
private edge Membership id "membership" {
|
||||||
|
atom P projection children id "children" many-unique;
|
||||||
|
interface Child projection parent id "parent" optional-one public-traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conform C as Child id "child-impl" {}
|
||||||
|
conform Child as Owned id "owned-rule" {bind parent.resolve to edge Membership.parent.resolve;}
|
||||||
|
`;
|
||||||
|
ok(compile(source));
|
||||||
|
const hidden = compile(source.replace(" public-traversal", ""));
|
||||||
|
assert.equal(hidden.ok, false);
|
||||||
|
assert.match(JSON.stringify(hidden.diagnostics), /private|public|own/i);
|
||||||
|
const missing = compile(source.replace("Membership.parent.resolve", "Missing.parent.resolve"));
|
||||||
|
assert.equal(missing.ok, false);
|
||||||
|
assert.match(JSON.stringify(missing.diagnostics), /Unknown attachment/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -126,6 +126,21 @@ await mkdir("dist/portable-reader/collections", { recursive: true });
|
|||||||
await writeFile("dist/portable-reader/collections/plan.json", JSON.stringify(collections));
|
await writeFile("dist/portable-reader/collections/plan.json", JSON.stringify(collections));
|
||||||
await writeFile("dist/portable-reader/collections/kernel.json", JSON.stringify(compileKernelProgram(collections)));
|
await writeFile("dist/portable-reader/collections/kernel.json", JSON.stringify(compileKernelProgram(collections)));
|
||||||
|
|
||||||
|
const { presentationMountPlan } = await import("./presentation-mount-fixture.js");
|
||||||
|
const presentationMount = await presentationMountPlan();
|
||||||
|
await mkdir("dist/portable-reader/presentation-mount", { recursive: true });
|
||||||
|
await writeFile("dist/portable-reader/presentation-mount/plan.json", JSON.stringify(presentationMount));
|
||||||
|
await writeFile(
|
||||||
|
"dist/portable-reader/presentation-mount/kernel.json",
|
||||||
|
JSON.stringify(compileKernelProgram(presentationMount)),
|
||||||
|
);
|
||||||
|
await writeFile(
|
||||||
|
"dist/portable-reader/presentation-mount/browser.ts",
|
||||||
|
generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", {
|
||||||
|
target: "browser",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const { compoundCrdtPlan } = await import("./compound-crdt-fixture.js");
|
const { compoundCrdtPlan } = await import("./compound-crdt-fixture.js");
|
||||||
const compoundCrdt = await compoundCrdtPlan();
|
const compoundCrdt = await compoundCrdtPlan();
|
||||||
await mkdir("dist/portable-reader/compound-crdt", { recursive: true });
|
await mkdir("dist/portable-reader/compound-crdt", { recursive: true });
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
interface DocumentNavigation id "document-navigation" revision "document-navigation@1" requires PresentationState {
|
||||||
|
queryable value compact id "document-navigation:compact" : bool {
|
||||||
|
get id "document-navigation:compact:get" execution snapshot targets [browser,server];
|
||||||
|
set id "document-navigation:compact:set" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
interface Presentation<object Navigation implements PresentationState> id "presentation" revision "presentation@1" {}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
interface PresentationMount id "presentation-mount" revision "presentation-mount@1" {
|
||||||
|
static operation create id "presentation-mount:create" : unit -> interface-ref<PresentationMount> {
|
||||||
|
call id "presentation-mount:create:call" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
queryable relation members id "presentation-mount:members" : many-unique interface PresentationState {
|
||||||
|
resolve id "presentation-mount:members:resolve" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
queryable relation root id "presentation-mount:root" : optional-one interface PresentationState {
|
||||||
|
resolve id "presentation-mount:root:resolve" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
operation attach id "presentation-mount:attach" : interface-ref<PresentationState> -> unit {
|
||||||
|
call id "presentation-mount:attach:call" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
import interface PresentationMount;
|
||||||
|
import interface StateFactory;
|
||||||
|
external atom PresentationMountRecord id "presentation-mount-record";
|
||||||
|
package PresentationRuntime id "presentation-runtime" revision "presentation-runtime@1" {
|
||||||
|
function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [browser,server] requires {
|
||||||
|
creation presentationMount id "presentationMount" : PresentationMountRecord input unit;
|
||||||
|
};
|
||||||
|
operation attach id "presentation-runtime:attach" : interface-ref<PresentationState> -> unit mode call execution transaction targets [browser,server] receiver any requires {
|
||||||
|
edge members id "members" : many-unique interface PresentationState [resolve,connect];
|
||||||
|
edge root id "root" : optional-one interface PresentationState [resolve,connect];
|
||||||
|
interface metadata id "state" : PresentationState using [presentationMount.resolve,parent.resolve] from input;
|
||||||
|
};
|
||||||
|
operation ensure<object Navigation implements PresentationState> id "presentation-runtime:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> mode call execution transaction targets [browser,server] receiver any requires {
|
||||||
|
edge states id "states" : many-unique object Navigation [resolve,connect];
|
||||||
|
interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve] from input;
|
||||||
|
interface presentationMount id "presentationMount" : PresentationMount using [attach.call] from input;
|
||||||
|
interface factory id "factory" : StateFactory<Navigation> using [create.call];
|
||||||
|
};
|
||||||
|
function createState<object Navigation> id "presentation-runtime:create-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> execution transaction targets [browser,server] requires {
|
||||||
|
creation navigation id "navigation" : Navigation input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
external interface PresentationMount revision "presentation-mount@1";
|
||||||
|
interface PresentationState id "presentation-state" revision "presentation-state@1" {
|
||||||
|
queryable value occurrence id "state:occurrence" : string {
|
||||||
|
get id "state:occurrence:get" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
queryable relation presentationMount id "state:presentation-mount" : optional-one interface PresentationMount {
|
||||||
|
resolve id "state:presentation-mount:resolve" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
queryable relation parent id "state:parent" : optional-one interface PresentationState {
|
||||||
|
resolve id "state:parent:resolve" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
import interface PresentationMount;
|
||||||
|
interface PresentationStates<object Navigation implements PresentationState> id "presentation-states" revision "presentation-states@1" {
|
||||||
|
queryable relation states id "presentation-states:states" : many-unique object Navigation {
|
||||||
|
resolve id "presentation-states:states:resolve" execution snapshot targets [browser,server];
|
||||||
|
}
|
||||||
|
operation ensure id "presentation-states:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
|
||||||
|
call id "presentation-states:ensure:call" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
interface ReaderNavigation id "reader-navigation" revision "reader-navigation@1" requires PresentationState {
|
||||||
|
queryable value page id "reader-navigation:mode" : string {
|
||||||
|
get id "reader-navigation:mode:get" execution snapshot targets [browser,server];
|
||||||
|
set id "reader-navigation:mode:set" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import interface PresentationState;
|
||||||
|
interface StateFactory<object Navigation implements PresentationState> id "state-factory" revision "state-factory@1" {
|
||||||
|
static operation create id "state-factory:create" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
|
||||||
|
call id "state-factory:create:call" execution transaction targets [browser,server];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
interface StateRecord id "state-record" revision "state-record@1" {}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
workspace Example id "presentation-example" revision "presentation-example@1" commit "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||||
|
import interface PresentationState;
|
||||||
|
import interface StateRecord;
|
||||||
|
import interface PresentationMount;
|
||||||
|
import interface Presentation;
|
||||||
|
import interface PresentationStates;
|
||||||
|
import interface StateFactory;
|
||||||
|
import interface ReaderNavigation;
|
||||||
|
import interface DocumentNavigation;
|
||||||
|
import package PresentationRuntime;
|
||||||
|
atom PresentationMountRecord id "presentation-mount-record";
|
||||||
|
atom Library id "library";
|
||||||
|
atom Document id "document";
|
||||||
|
atom ReaderNavigationRecord id "reader-navigation-record";
|
||||||
|
atom DocumentNavigationRecord id "document-navigation-record";
|
||||||
|
|
||||||
|
conform PresentationMountRecord as PresentationMount id "presentation-mount-record:implementation" {
|
||||||
|
private edge Membership id "presentation-mount:membership" {
|
||||||
|
atom PresentationMountRecord projection members id "presentation-mount:members-projection" many-unique public-traversal;
|
||||||
|
interface PresentationState projection presentationMount id "presentation-mount:owner-projection" optional-one public-traversal;
|
||||||
|
}
|
||||||
|
private edge Root id "presentation-mount:root-edge" {
|
||||||
|
atom PresentationMountRecord projection root id "presentation-mount:root-projection" optional-one;
|
||||||
|
interface PresentationState projection rootOf id "presentation-mount:root-of-projection" optional-one;
|
||||||
|
}
|
||||||
|
bind members.resolve to edge Membership.members.resolve;
|
||||||
|
bind root.resolve to edge Root.root.resolve;
|
||||||
|
bind create.call to package PresentationRuntime.createPresentationMount with {presentationMount to creation PresentationMountRecord with {};};
|
||||||
|
bind attach.call to package PresentationRuntime.attach with {
|
||||||
|
members to edge Membership.members;
|
||||||
|
root to edge Root.root;
|
||||||
|
metadata to interface PresentationState from input;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
conform<object Navigation implements PresentationState> Presentation<Navigation> as PresentationStates<Navigation> id "presentation:state-association" {
|
||||||
|
private edge States id "presentation:states-edge" {
|
||||||
|
atom Self projection states id "presentation:states-projection" many-unique;
|
||||||
|
object Navigation projection subject id "presentation:subject-projection" exactly-one;
|
||||||
|
}
|
||||||
|
bind states.resolve to edge States.states.resolve;
|
||||||
|
bind ensure.call to package PresentationRuntime.ensure<Navigation> with {
|
||||||
|
states to edge States.states;
|
||||||
|
metadata to interface PresentationState from input;
|
||||||
|
presentationMount to interface PresentationMount from input;
|
||||||
|
factory to interface StateFactory<Navigation>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
conform StateRecord as PresentationState id "state-record:metadata" {
|
||||||
|
private state Occurrence id "metadata:occurrence" on Self : string policy register;
|
||||||
|
private edge Parent id "metadata:parent" {
|
||||||
|
atom Self projection parent id "metadata:parent-projection" optional-one;
|
||||||
|
interface PresentationState projection children id "metadata:children-projection" many-unique;
|
||||||
|
}
|
||||||
|
initialize {occurrence to state Occurrence;parent to edge Parent.parent;};
|
||||||
|
bind occurrence.get to state Occurrence.read;
|
||||||
|
bind parent.resolve to edge Parent.parent.resolve;
|
||||||
|
bind presentationMount.resolve to edge Membership.presentationMount.resolve;
|
||||||
|
}
|
||||||
|
conform ReaderNavigationRecord as StateRecord id "reader:state-record" {}
|
||||||
|
conform DocumentNavigationRecord as StateRecord id "document:state-record" {}
|
||||||
|
conform ReaderNavigationRecord as ReaderNavigation id "reader:navigation" {
|
||||||
|
private state Mode id "reader:mode" on Self : string policy register default "library";
|
||||||
|
bind page to state Mode;
|
||||||
|
}
|
||||||
|
conform DocumentNavigationRecord as DocumentNavigation id "document:navigation" {
|
||||||
|
private state Compact id "document:compact" on Self : bool policy register default false;
|
||||||
|
bind compact to state Compact;
|
||||||
|
}
|
||||||
|
conform Library as StateFactory<atom ReaderNavigationRecord> id "reader:factory" {
|
||||||
|
bind create.call to package PresentationRuntime.createState<atom ReaderNavigationRecord> with {
|
||||||
|
navigation to creation ReaderNavigationRecord with {PresentationState from input;};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
conform Document as StateFactory<atom DocumentNavigationRecord> id "document:factory" {
|
||||||
|
bind create.call to package PresentationRuntime.createState<atom DocumentNavigationRecord> with {
|
||||||
|
navigation to creation DocumentNavigationRecord with {PresentationState from input;};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
conform Library as Presentation<atom ReaderNavigationRecord> id "library:presentation" {}
|
||||||
|
conform Document as Presentation<atom DocumentNavigationRecord> id "document:presentation" {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
|
||||||
|
import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
||||||
|
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||||
|
export async function presentationMountPlan() {
|
||||||
|
const read = (name: string) => readFile(`test/fixtures/presentation-mount/${name}.qx`, "utf8");
|
||||||
|
const source = { repository: "https://example.test/presentation-mount.git", commit: "a".repeat(40) };
|
||||||
|
const interfaces = new Map<string, InterfaceRevision>();
|
||||||
|
for (const name of [
|
||||||
|
"StateRecord",
|
||||||
|
"PresentationState",
|
||||||
|
"PresentationMount",
|
||||||
|
"StateFactory",
|
||||||
|
"Presentation",
|
||||||
|
"PresentationStates",
|
||||||
|
"ReaderNavigation",
|
||||||
|
"DocumentNavigation",
|
||||||
|
]) {
|
||||||
|
const result = compileCapabilityResourceSource(await read(name), { source, environment: { interfaces } });
|
||||||
|
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
|
||||||
|
interfaces.set(name, result.resource.revision);
|
||||||
|
}
|
||||||
|
const pkg = compileCapabilityResourceSource(await read("PresentationRuntime"), {
|
||||||
|
source,
|
||||||
|
environment: { interfaces },
|
||||||
|
});
|
||||||
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||||
|
const result = compileCapabilitySource(await read("workspace"), "workspace.qx", {
|
||||||
|
interfaces,
|
||||||
|
packages: new Map([["PresentationRuntime", pkg.resource.revision]]),
|
||||||
|
});
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
return compileExecutionPlan(result.workspace);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user