287 lines
13 KiB
TypeScript
287 lines
13 KiB
TypeScript
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/);
|
|
});
|