Compile closed execution plans and checked artifact manifests
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { createHash } from "node:crypto";
|
||||
import { loadReaderWorkspace } from "./reader-fixture.js";
|
||||
import {
|
||||
compileExecutionPlan,
|
||||
checkExecutionWorld,
|
||||
checkedWorldDigest,
|
||||
implementationDigest,
|
||||
hexDigest,
|
||||
parsePackageArtifacts,
|
||||
artifactPath,
|
||||
type PackageArtifacts,
|
||||
type ExecutionPlan,
|
||||
type ArtifactImplementation,
|
||||
} from "../src/execution-world/index.js";
|
||||
|
||||
const buildFixture = (plan: ExecutionPlan) => {
|
||||
const expected = plan.definition.packages[0]!;
|
||||
const bytes = new TextEncoder().encode("export const handlers = Object.freeze({});\n");
|
||||
const implementation = (
|
||||
id: string,
|
||||
target: string,
|
||||
compatibility: ArtifactImplementation["compatibility"],
|
||||
): ArtifactImplementation => {
|
||||
const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${target}/module.mjs`;
|
||||
const value = {
|
||||
id,
|
||||
target,
|
||||
compatibility,
|
||||
path,
|
||||
ports: "quixos-transaction-ports-v1" as const,
|
||||
...(compatibility === "quixos-js-module-v1" ? { registryExport: "handlers" } : {}),
|
||||
exports: expected.exports.map((e) => e.id),
|
||||
files: [
|
||||
{
|
||||
path,
|
||||
digest: createHash("sha256").update(bytes).digest("hex"),
|
||||
bytes: bytes.length,
|
||||
mediaType: compatibility === "quixos-service-v1" ? "application/octet-stream" : "text/javascript",
|
||||
},
|
||||
],
|
||||
};
|
||||
return { ...value, contentDigest: implementationDigest(value) };
|
||||
};
|
||||
const manifest: PackageArtifacts = {
|
||||
schemaVersion: 1,
|
||||
packageRevisionId: expected.revision,
|
||||
bindingDigest: expected.bindingDigest,
|
||||
sourceDigest: expected.sourceDigest,
|
||||
versions: plan.definition.versions,
|
||||
implementations: [
|
||||
implementation("service", "server", "quixos-service-v1"),
|
||||
implementation("server", "server", "quixos-js-module-v1"),
|
||||
implementation("browser", "browser", "quixos-js-module-v1"),
|
||||
],
|
||||
};
|
||||
return { manifest, bytes, read: async () => bytes };
|
||||
};
|
||||
const resign = (implementation: ArtifactImplementation) => {
|
||||
const { contentDigest, ...body } = implementation;
|
||||
implementation.contentDigest = implementationDigest(body);
|
||||
};
|
||||
|
||||
test("reader execution plan closes selector storage and traversed interface dependencies", async () => {
|
||||
const workspace = await loadReaderWorkspace();
|
||||
const plan = compileExecutionPlan(workspace);
|
||||
assert.equal(plan.digest, compileExecutionPlan(structuredClone(workspace)).digest);
|
||||
assert.equal(plan.definition.owners.length, 5);
|
||||
const excerpt =
|
||||
plan.definition.operations.find((o) => o.operationId === "highlight:excerpt:get") ??
|
||||
plan.definition.operations.find((o) => o.binding.kind === "package" && o.binding.exportId === "reader:excerpt")!;
|
||||
assert.ok(excerpt.dependencies.some((d) => d.kind === "state" && d.slot === "slot:highlight:range"));
|
||||
assert.ok(
|
||||
excerpt.dependencies.some(
|
||||
(d) => d.kind === "edge" && d.edge === "edge:document-highlights" && d.projection === "projection:document",
|
||||
),
|
||||
);
|
||||
const calls = excerpt.dependencies.filter((d) => d.kind === "operation");
|
||||
assert.equal(calls.length, 2);
|
||||
for (const call of calls) {
|
||||
assert.equal(call.via?.projectionId, "projection:document");
|
||||
const target = plan.definition.operations.find((o) => o.key === call.operation)!;
|
||||
assert.ok(target.dependencies.some((d) => d.kind === "state" && d.slot.startsWith("slot:document:")));
|
||||
}
|
||||
const admission = plan.definition.queries.find((q) => q.query.checked.declaration.displayName === "ReplicaInputs")!;
|
||||
assert.ok(admission.admittedOperations.includes(excerpt.key));
|
||||
assert.equal(plan.definition.profiles[0]!.profile, "camino-automerge-3.4.1-structured-v1");
|
||||
const factory = plan.definition.operations.find(
|
||||
(o) => o.binding.kind === "package" && o.binding.exportId === "reader:create-document",
|
||||
)!;
|
||||
assert.equal(factory.scope, "class");
|
||||
assert.ok(
|
||||
factory.binding.kind === "package" && factory.binding.dependencies.some((d) => d.binding.kind === "creation"),
|
||||
);
|
||||
assert.ok(plan.definition.owners.some((o) => Array.isArray(o.initialization) && o.initialization.length > 0));
|
||||
});
|
||||
|
||||
test("artifact closure checks bytes, exact source/schema, ABI, exports and target coverage", async () => {
|
||||
const plan = compileExecutionPlan(await loadReaderWorkspace());
|
||||
const fixture = buildFixture(plan);
|
||||
const world = await checkExecutionWorld(plan, [fixture.manifest], fixture.read);
|
||||
assert.equal(world.artifacts.length, 3);
|
||||
assert.equal(
|
||||
checkedWorldDigest(world),
|
||||
checkedWorldDigest(await checkExecutionWorld(plan, [structuredClone(fixture.manifest)], fixture.read)),
|
||||
);
|
||||
const negative = async (change: (manifest: PackageArtifacts) => void, pattern: RegExp) => {
|
||||
const manifest = structuredClone(fixture.manifest);
|
||||
change(manifest);
|
||||
await assert.rejects(checkExecutionWorld(plan, [manifest], fixture.read), pattern);
|
||||
};
|
||||
await negative((m) => {
|
||||
m.bindingDigest = "0".repeat(64);
|
||||
}, /Substituted binding schema/);
|
||||
await negative((m) => {
|
||||
m.sourceDigest = "0".repeat(64);
|
||||
}, /source revision/);
|
||||
await negative((m) => {
|
||||
(m.versions as unknown as Record<string, string>).value = "v2";
|
||||
}, /Incompatible runtime codecs/);
|
||||
await negative((m) => {
|
||||
m.implementations = m.implementations.filter((i) => i.target !== "browser");
|
||||
}, /Missing browser implementation/);
|
||||
await negative((m) => {
|
||||
m.implementations = m.implementations.filter((i) => i.compatibility !== "quixos-service-v1");
|
||||
}, /Missing service fallback/);
|
||||
await negative((m) => {
|
||||
m.implementations[0]!.exports.push("wrong-export");
|
||||
resign(m.implementations[0]!);
|
||||
}, /Unknown export/);
|
||||
await negative((m) => {
|
||||
m.implementations[0]!.files[0]!.digest = "0".repeat(64);
|
||||
resign(m.implementations[0]!);
|
||||
}, /Artifact bytes/);
|
||||
await negative((m) => {
|
||||
m.implementations[0]!.exports.pop();
|
||||
}, /metadata digest mismatch/);
|
||||
await assert.rejects(
|
||||
checkExecutionWorld(plan, [fixture.manifest], async () => new Uint8Array([1])),
|
||||
/Artifact bytes/,
|
||||
);
|
||||
for (const path of ["/tmp/code", "../code", "a/../b", "a//b", "a\\b", "a%2fb", "a?query"])
|
||||
assert.throws(() => artifactPath(path), /normalized relative/);
|
||||
const changed = structuredClone(plan);
|
||||
changed.definition.versions.value = "wrong" as typeof changed.definition.versions.value;
|
||||
await assert.rejects(
|
||||
checkExecutionWorld(changed, [fixture.manifest], fixture.read),
|
||||
/Unsupported plan codec versions/,
|
||||
);
|
||||
});
|
||||
|
||||
test("artifact identity changes when code or registry changes without hashing its own digest", async () => {
|
||||
const plan = compileExecutionPlan(await loadReaderWorkspace()),
|
||||
fixture = buildFixture(plan);
|
||||
const before = checkedWorldDigest(await checkExecutionWorld(plan, [fixture.manifest], fixture.read));
|
||||
const manifest = structuredClone(fixture.manifest);
|
||||
manifest.implementations[2]!.registryExport = "anotherRegistry";
|
||||
resign(manifest.implementations[2]!);
|
||||
const after = checkedWorldDigest(await checkExecutionWorld(plan, [manifest], fixture.read));
|
||||
assert.notEqual(before, after);
|
||||
assert.equal(parsePackageArtifacts(manifest).implementations[2]!.registryExport, "anotherRegistry");
|
||||
assert.equal(hexDigest(plan.definition), plan.digest);
|
||||
});
|
||||
|
||||
test("transactions cannot derive their membership from an admitted presentation query", async () => {
|
||||
const workspace = await loadReaderWorkspace(),
|
||||
pkg = workspace.packageImports[0]!;
|
||||
const query = pkg.queries!.find((q) => q.placement?.kind === "local")!;
|
||||
const entry = pkg.exports.find((e) => e.id === "reader:add-highlight")!;
|
||||
const portId = "test:query-port" as (typeof entry.dependencyPorts)[number]["id"];
|
||||
entry.dependencyPorts.push({
|
||||
id: portId,
|
||||
displayName: "visible",
|
||||
requirement: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id },
|
||||
});
|
||||
const bound = workspace.conformances
|
||||
.flatMap((c) => c.operationBindings)
|
||||
.find((o) => o.binding.kind === "package" && o.binding.exportId === entry.id)!.binding;
|
||||
assert.equal(bound.kind, "package");
|
||||
if (bound.kind !== "package") return;
|
||||
bound.dependencies.push({
|
||||
portId,
|
||||
binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "object" },
|
||||
});
|
||||
assert.throws(() => compileExecutionPlan(workspace), /QUERY_IN_TRANSACTION.*test:query-port/);
|
||||
});
|
||||
|
||||
test("port ABI carries only declared port actions, never caller-selected storage IDs", async () => {
|
||||
const { checkPortInvocation } = await import("../src/execution-world/index.js");
|
||||
const frame = {
|
||||
abi: "quixos-transaction-ports-v1",
|
||||
frame: "frame1",
|
||||
invocation: "invoke1",
|
||||
sequence: 1,
|
||||
portId: "content",
|
||||
action: { kind: "state", primitive: "read" },
|
||||
input: { codec: "camino-value-v1", usage: "argument", value: null },
|
||||
};
|
||||
assert.deepEqual(checkPortInvocation(frame), frame);
|
||||
assert.throws(() => checkPortInvocation({ ...frame, slotId: "private" }), /fields or ABI/);
|
||||
assert.throws(
|
||||
() => checkPortInvocation({ ...frame, action: { kind: "state", primitive: "read", slotId: "private" } }),
|
||||
/Invalid port action/,
|
||||
);
|
||||
assert.throws(() => checkPortInvocation({ ...frame, sequence: NaN }), /sequence/);
|
||||
assert.throws(() => checkPortInvocation({ ...frame, action: { kind: "creation", key: "" } }), /Invalid port action/);
|
||||
});
|
||||
|
||||
test("local queries cannot silently expand the replica relationship traversal", async () => {
|
||||
const workspace = await loadReaderWorkspace((name, text) =>
|
||||
name === "queries/replica-inputs.graphql" ? "query ReplicaInputs { root { sourceRevision content } }" : text,
|
||||
);
|
||||
assert.throws(() => compileExecutionPlan(workspace), /REPLICA_TRAVERSAL_MISSING/);
|
||||
});
|
||||
|
||||
test("closed generic applications retain distinct identities and codec shapes", async () => {
|
||||
const { compileCapabilityResourceSource, compileCapabilitySource } =
|
||||
await import("../src/capability-language/index.js");
|
||||
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
|
||||
const definition = compileCapabilityResourceSource(
|
||||
`interface Cell<value V : storable> id "cell" revision "cell@1" {
|
||||
value content id "value" : V { get id "get" execution snapshot targets [browser, server]; }
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
assert.ok(definition.ok && definition.resource.kind === "interface", JSON.stringify(definition.diagnostics));
|
||||
const compiled = compileCapabilitySource(
|
||||
`workspace Demo id "ws" revision "ws@1" commit "${source.commit}" {
|
||||
import interface Cell; atom Item id "item";
|
||||
conform Item as Cell<string> id "text-owner" { private state Text id "text-slot" on Item : string policy register; bind content.get to state Text.read; }
|
||||
conform Item as Cell<int64> id "number-owner" { private state Number id "number-slot" on Item : int64 policy register; bind content.get to state Number.read; }
|
||||
}`,
|
||||
"workspace.qx",
|
||||
{ interfaces: new Map([["Cell", definition.resource.revision]]) },
|
||||
);
|
||||
assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics));
|
||||
const plan = compileExecutionPlan(compiled.workspace);
|
||||
assert.equal(plan.definition.operations.length, 2);
|
||||
assert.notEqual(
|
||||
plan.definition.operations[0]!.interfaceRevisionId,
|
||||
plan.definition.operations[1]!.interfaceRevisionId,
|
||||
);
|
||||
assert.deepEqual(
|
||||
new Set(plan.definition.operations.map((o) => (o.output.kind === "scalar" ? o.output.name : null))),
|
||||
new Set(["string", "int64"]),
|
||||
);
|
||||
assert.ok(plan.definition.bindingSchema.interfaces.every((i) => i.application));
|
||||
});
|
||||
|
||||
test("unbound portable exports cannot claim an unsupported transport codec", async () => {
|
||||
const workspace = await loadReaderWorkspace();
|
||||
workspace.packageImports[0]!.exports.find((e) => e.id === "reader:validate-range")!.outputType = {
|
||||
kind: "message",
|
||||
descriptorId: "opaque-message",
|
||||
};
|
||||
assert.throws(() => compileExecutionPlan(workspace), /UNSUPPORTED_PORTABLE_CODEC.*reader:validate-range/);
|
||||
});
|
||||
|
||||
test("query dependencies propagate target requirements through selected getters", async () => {
|
||||
const workspace = await loadReaderWorkspace(),
|
||||
pkg = workspace.packageImports[0]!;
|
||||
const query = pkg.queries!.find((q) => q.displayName === "HighlightList")!;
|
||||
const entry = pkg.exports.find((e) => e.id === "reader:excerpt")!;
|
||||
const portId = "test:selector-query" as (typeof entry.dependencyPorts)[number]["id"];
|
||||
entry.dependencyPorts.push({
|
||||
id: portId,
|
||||
displayName: "visible",
|
||||
requirement: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id },
|
||||
});
|
||||
const bound = workspace.conformances
|
||||
.flatMap((c) => c.operationBindings)
|
||||
.find((o) => o.binding.kind === "package" && o.binding.exportId === entry.id)!.binding;
|
||||
assert.equal(bound.kind, "package");
|
||||
if (bound.kind !== "package") return;
|
||||
bound.dependencies.push({
|
||||
portId,
|
||||
binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" },
|
||||
});
|
||||
compileExecutionPlan(workspace);
|
||||
const field = workspace.interfaceImports
|
||||
.find((i) => i.displayName === "HighlightData")!
|
||||
.members.find((m) => m.displayName === "color")!;
|
||||
field.operations.find((o) => o.displayName === "get")!.execution = { kind: "snapshot", targets: ["browser"] };
|
||||
assert.throws(() => compileExecutionPlan(workspace), /MISSING_EXECUTION_TARGET.*test:selector-query.*server/);
|
||||
});
|
||||
@@ -1,44 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import {
|
||||
compileCapabilityResourceSource,
|
||||
compileCapabilitySource,
|
||||
formatQx,
|
||||
} from "../src/capability-language/index.js";
|
||||
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import { linkQueries } from "../src/query/link.js";
|
||||
const directory = resolve("test/fixtures/local-first-reader");
|
||||
const read = (name: string) => readFile(resolve(directory, name), "utf8");
|
||||
const source = { repository: "https://example.test/reader.git", commit: "a".repeat(40) };
|
||||
const load = async (transform = (name: string, text: string) => text) => {
|
||||
const interfaces = new Map<string, InterfaceRevision>();
|
||||
for (const name of ["DocumentData", "HighlightData", "CommentData", "DocumentActions", "DocumentFactory"]) {
|
||||
const text = transform(`${name}.qx`, await read(`${name}.qx`));
|
||||
const result = compileCapabilityResourceSource(formatQx(text), { source });
|
||||
assert.ok(result.ok && result.resource.kind === "interface", `${name}: ${JSON.stringify(result.diagnostics)}`);
|
||||
interfaces.set(name, result.resource.revision);
|
||||
}
|
||||
const environment = { interfaces, interfaceClosure: [...interfaces.values()] };
|
||||
const pkg = compileCapabilityResourceSource(formatQx(transform("ReaderRuntime.qx", await read("ReaderRuntime.qx"))), {
|
||||
source,
|
||||
environment,
|
||||
});
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
const revision = pkg.resource.revision;
|
||||
revision.checkedQueries = await Promise.all(
|
||||
revision.queries!.map((query) => compileQuery(query, [...interfaces.values()], read)),
|
||||
);
|
||||
const result = compileCapabilitySource(formatQx(await read("workspace.qx")), "workspace.qx", {
|
||||
...environment,
|
||||
packages: new Map([["ReaderRuntime", revision]]),
|
||||
packageClosure: [revision],
|
||||
});
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
return result.workspace;
|
||||
};
|
||||
import { loadReaderWorkspace as load, readReaderFixture as read } from "./reader-fixture.js";
|
||||
test("complete reader contracts compose factories, portable selectors, CRDT edits, and six query scopes", async () => {
|
||||
const workspace = await load();
|
||||
assert.equal(workspace.constructors.length, 0);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import {
|
||||
compileCapabilityResourceSource,
|
||||
compileCapabilitySource,
|
||||
formatQx,
|
||||
} from "../src/capability-language/index.js";
|
||||
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
const directory = resolve("test/fixtures/local-first-reader");
|
||||
export const readReaderFixture = (name: string) => readFile(resolve(directory, name), "utf8");
|
||||
const source = { repository: "https://example.test/reader.git", commit: "a".repeat(40) };
|
||||
export const loadReaderWorkspace = async (transform = (name: string, text: string) => text) => {
|
||||
const interfaces = new Map<string, InterfaceRevision>();
|
||||
for (const name of ["DocumentData", "HighlightData", "CommentData", "DocumentActions", "DocumentFactory"]) {
|
||||
const text = transform(`${name}.qx`, await readReaderFixture(`${name}.qx`));
|
||||
const result = compileCapabilityResourceSource(formatQx(text), { source });
|
||||
assert.ok(result.ok && result.resource.kind === "interface", `${name}: ${JSON.stringify(result.diagnostics)}`);
|
||||
interfaces.set(name, result.resource.revision);
|
||||
}
|
||||
const environment = { interfaces, interfaceClosure: [...interfaces.values()] };
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
formatQx(transform("ReaderRuntime.qx", await readReaderFixture("ReaderRuntime.qx"))),
|
||||
{
|
||||
source,
|
||||
environment,
|
||||
},
|
||||
);
|
||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||
const revision = pkg.resource.revision;
|
||||
revision.checkedQueries = await Promise.all(
|
||||
revision.queries!.map((query) =>
|
||||
compileQuery(query, [...interfaces.values()], async (name) => transform(name, await readReaderFixture(name))),
|
||||
),
|
||||
);
|
||||
const result = compileCapabilitySource(formatQx(await readReaderFixture("workspace.qx")), "workspace.qx", {
|
||||
...environment,
|
||||
packages: new Map([["ReaderRuntime", revision]]),
|
||||
packageClosure: [revision],
|
||||
});
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
return result.workspace;
|
||||
};
|
||||
Reference in New Issue
Block a user