Compile closed execution plans and checked artifact manifests

This commit is contained in:
Timothy J. Aveni
2026-09-19 23:27:00 -07:00
parent 15c132a38b
commit ac36edf62d
11 changed files with 1193 additions and 39 deletions
+2
View File
@@ -39,6 +39,7 @@
export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:$PWD/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}" export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:$PWD/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}"
patchShebangs node_modules/.bin node_modules/@bufbuild/protoc-gen-es/bin patchShebangs node_modules/.bin node_modules/@bufbuild/protoc-gen-es/bin
yarn build yarn build
esbuild dist/src/execution-world/index.js --bundle --platform=node --target=node24 --format=esm --outfile=execution-world.mjs
esbuild dist/src/bindings/client.js --bundle --platform=node --target=node24 --format=esm --outfile=client-codegen.mjs esbuild dist/src/bindings/client.js --bundle --platform=node --target=node24 --format=esm --outfile=client-codegen.mjs
diff --recursive --unified "$TMPDIR/generated-before/proto" src/gen diff --recursive --unified "$TMPDIR/generated-before/proto" src/gen
diff --recursive --unified "$TMPDIR/generated-before/capability" src/capability-language/generated diff --recursive --unified "$TMPDIR/generated-before/capability" src/capability-language/generated
@@ -112,6 +113,7 @@
EOF EOF
chmod +x "$out/bin/quixos-lock-check" chmod +x "$out/bin/quixos-lock-check"
mkdir -p "$out/libexec/quixos-protocol" mkdir -p "$out/libexec/quixos-protocol"
install -m644 execution-world.mjs "$out/libexec/quixos-protocol/execution-world.mjs"
install -m644 client-codegen.mjs "$out/libexec/quixos-protocol/client-codegen.mjs" install -m644 client-codegen.mjs "$out/libexec/quixos-protocol/client-codegen.mjs"
install -m644 react-platform.mjs "$out/libexec/quixos-protocol/react-platform.mjs" install -m644 react-platform.mjs "$out/libexec/quixos-protocol/react-platform.mjs"
install -m644 quixos-codegen-ts.mjs quixos-qx.mjs "$out/libexec/quixos-protocol/" install -m644 quixos-codegen-ts.mjs quixos-qx.mjs "$out/libexec/quixos-protocol/"
+2 -1
View File
@@ -18,7 +18,8 @@
"./capability-model": "./dist/src/capability-model/index.js", "./capability-model": "./dist/src/capability-model/index.js",
"./capability-language": "./dist/src/capability-language/index.js", "./capability-language": "./dist/src/capability-language/index.js",
"./resource-lock": "./dist/src/resource-lock/index.js", "./resource-lock": "./dist/src/resource-lock/index.js",
"./gen/*": "./dist/src/gen/*" "./gen/*": "./dist/src/gen/*",
"./execution-world": "./dist/src/execution-world/index.js"
}, },
"scripts": { "scripts": {
"build": "yarn generate && tsc -p tsconfig.build.json", "build": "yarn generate && tsc -p tsconfig.build.json",
+9 -1
View File
@@ -4,6 +4,7 @@ import { readFile, writeFile } from "node:fs/promises";
import process from "node:process"; import process from "node:process";
import { compileWorkspaceRepository } from "./assembly.js"; import { compileWorkspaceRepository } from "./assembly.js";
import { createGitCapabilityResolver } from "./git-resolver.js"; import { createGitCapabilityResolver } from "./git-resolver.js";
import { compileExecutionPlan } from "../execution-world/index.js";
import { bindingSchema, specializeBindingSchema } from "../bindings/index.js"; import { bindingSchema, specializeBindingSchema } from "../bindings/index.js";
import { import {
planEvolution, planEvolution,
@@ -15,7 +16,7 @@ import {
const usage = `usage: quixos-workspace-compile --root DIRECTORY --checkout-root DIRECTORY const usage = `usage: quixos-workspace-compile --root DIRECTORY --checkout-root DIRECTORY
[--snapshot-map PATH] [--graph-out PATH] [--workspace-id ID] [--workspace-revision-id ID] [--snapshot-map PATH] [--graph-out PATH] [--workspace-id ID] [--workspace-revision-id ID]
[--source-root-commit GIT_REV] [--baseline PLAN_JSON] [--evolution-out PATH] [--source-root-commit GIT_REV] [--baseline PLAN_JSON] [--evolution-out PATH]
[--reviews REVIEW_JSON] [--schemas-out PATH] [--reviews REVIEW_JSON] [--schemas-out PATH] [--execution-plan-out PATH]
Resolves a workspace's recursive resource-lock graph, clones every exact Resolves a workspace's recursive resource-lock graph, clones every exact
resource revision, validates standalone interface/package manifests, and emits resource revision, validates standalone interface/package manifests, and emits
@@ -37,6 +38,7 @@ const parseArgs = (args: string[]) => {
checkoutRoot, checkoutRoot,
graphOut: values.get("--graph-out"), graphOut: values.get("--graph-out"),
schemasOut: values.get("--schemas-out"), schemasOut: values.get("--schemas-out"),
executionPlanOut: values.get("--execution-plan-out"),
snapshotMap: values.get("--snapshot-map"), snapshotMap: values.get("--snapshot-map"),
workspaceId: values.get("--workspace-id"), workspaceId: values.get("--workspace-id"),
workspaceRevisionId: values.get("--workspace-revision-id"), workspaceRevisionId: values.get("--workspace-revision-id"),
@@ -98,6 +100,12 @@ const main = async () => {
); );
} }
const candidate = { ...assembled.workspace, executionContracts: runtimeContracts(assembled.workspace) }; const candidate = { ...assembled.workspace, executionContracts: runtimeContracts(assembled.workspace) };
if (options.executionPlanOut) {
await writeFile(
options.executionPlanOut,
`${JSON.stringify(compileExecutionPlan(assembled.workspace), null, 2)}\n`,
);
}
if (options.schemasOut) { if (options.schemasOut) {
const schemas: Record<string, unknown> = {}; const schemas: Record<string, unknown> = {};
for (const node of assembled.resources.filter((entry) => entry.kind === "package")) { for (const node of assembled.resources.filter((entry) => entry.kind === "package")) {
+86
View File
@@ -0,0 +1,86 @@
# Closed execution plans and artifacts
`compileExecutionPlan` validates a workspace, links its checked queries, resolves
public forwarding, and emits one versioned plan with an SHA-256 digest. It retains
exact interface/package revisions and generic applications, owner initialization,
storage definitions/profiles, selected operation identities, query placement, and
source provenance. `quixos-workspace-compile --execution-plan-out PATH` writes it.
The compiler emits operation dependencies as a graph. A traversed interface port
retains its edge/projection and exact selected operations; native reads identify
the canonical storage inputs. Cycles do not become infinitely expanded paths.
Execution and admission must apply their runtime depth/work budgets when walking
this graph. The plan includes the full linked GraphQL selections, not just root
traversals or a list of fields.
Local and replica queries must have the same root contract. A local query cannot
introduce a relationship path absent from its replica source; authors expand the
source selection explicitly. Portable getters may require extra canonical fields
and exactly-one dependency traversals, recorded in the dependency graph. Admission
resolves those inputs on the server for selected objects. This never changes the
source query's candidate membership, and does not claim a limited source is a
complete interface extent. An incomplete runtime projection remains a coverage
state, not permission to invent a missing value.
A portable transaction cannot depend on a local presentation query, including
through a selector. Pass explicit references and preconditions as mutation input.
Otherwise an admitted top-N list could select different mutation targets locally
and at acceptance. Remote transactions can implement explicit server workflows.
This restriction does not ban local queries from snapshot selectors. Such query
ports are view-relative: both hosts must use the frame's admitted candidate
universe. An executor must report unavailable coverage when that view is absent,
not substitute a full-server extent. Query-port acquisition remains unavailable
until the query executor implements this rule; emitting its checked dependency
graph is not runtime support.
## Artifacts and world identity
`PackageArtifacts` describes one package output containing a process service and
optional target modules. Implementation IDs are stable manifest keys, separate
from export capability IDs and JavaScript registry names. Each implementation
covers its ABI, export map, entrypoint, and declared asset paths/digests/sizes/MIME.
Its digest excludes its own digest field. Paths must be normalized and relative.
`checkExecutionWorld` verifies plan identity, supported codecs, exact source and
binding schema, service fallback, promised targets, declared exports, and all
supplied file bytes. The reader must confine paths/symlinks to the checked output
and honor its byte limit before allocation. LF-05 supplies that filesystem adapter,
checks actual module registries/imports, and verifies build receipts; a manifest
claim alone does not establish the JavaScript exports or an isolated process.
The returned `camino-world-v1` descriptor includes every declared implementation
and its digest, so executor choice does not change semantics or silently load new
code. Each binding key is the JSON pair `[packageRevisionId, implementationId]`;
there can be both a service and module for the server target. World hashing uses
the core codec's canonical ordering and domain-separated SHA-256 framing. The
monorepo `camino-execution-world` check compares the compiler and core implementations.
No machine-local Nix path enters the world descriptor.
The current binding schema is the closed workspace schema. This is conservative:
an unrelated contract change can require checking/rebuilding several packages.
LF-05 may narrow it to exact checked per-package closures, but must use the same
schema for generation, receipts, and world validation. It must not substitute an
older package schema just because the source revision did not change.
## Transaction-port ABI
`quixos-transaction-ports-v1` uses coordinator-issued frame/invocation handles and
monotonic invocation-local sequence numbers. A package receives its export ID,
input, and optional receiver. Port calls name a declared port and its allowed
primitive or exact interface operation; they never select arbitrary private slots,
packages, or coordinator transactions. Creation uses an explicit stable call key. Interface calls into transaction
operations also require a stable key, checked after resolving the selected
operation. The coordinator extends the birth path; it never derives that path
from an asynchronous completion order or the transport sequence number.
The coordinator resolves each port through the installed operation binding,
checks effects/selected methods, validates typed arguments/references, and joins
nested calls to the outer logical transaction. A nested failure rolls back to its
frame boundary; process completion cannot independently commit. The coordinator
owns savepoints, cancellation, frame expiry, and rejection. These enforcement
mechanisms are LF-06/LF-08, not guarantees provided by TypeScript frame types.
The service control vocabulary includes readiness, cancellation, and shutdown;
transport framing and generated adapters are LF-05/LF-08. Portable modules and
services implement the same declared operation contracts. ABI compatibility is
not a statement about package trust, co-location permission, or multi-user isolation.
+281
View File
@@ -0,0 +1,281 @@
import { createHash } from "node:crypto";
import { canonicalJson } from "../capability-model/evolution.js";
import { type ExecutionPlan, type PlanPackage, ExecutionPlanError, hexDigest, executionVersions } from "./plan.js";
export interface ArtifactFile {
path: string;
digest: string;
bytes: number;
mediaType: string;
}
export interface ArtifactImplementation {
id: string;
target: string;
compatibility: "quixos-service-v1" | "quixos-js-module-v1";
path: string;
ports: "quixos-transaction-ports-v1";
registryExport?: string;
exports: string[];
/** Every executable/imported local asset, including the entrypoint, must be declared. */
files: ArtifactFile[];
contentDigest: string;
}
export interface PackageArtifacts {
schemaVersion: 1;
packageRevisionId: string;
bindingDigest: string;
sourceDigest: string;
versions: ExecutionPlan["definition"]["versions"];
implementations: ArtifactImplementation[];
}
export interface CheckedWorld {
codec: "camino-world-v1";
plan: string;
artifacts: { binding: string; target: string; digest: string }[];
versions: Record<string, string>;
}
const fail = (path: string, message: string): never => {
throw new ExecutionPlanError("INVALID_ARTIFACT", path, message);
};
const object = (value: unknown, path: string): Record<string, unknown> => {
if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype)
fail(path, "Expected a plain JSON record");
return value as Record<string, unknown>;
};
const keys = (record: Record<string, unknown>, required: string[], optional: string[], path: string) => {
if (
required.some((key) => !Object.hasOwn(record, key)) ||
Object.keys(record).some((key) => ![...required, ...optional].includes(key))
)
fail(path, "Unexpected or missing fields");
};
const text = (value: unknown, path: string): string => {
if (
typeof value !== "string" ||
!value.length ||
value.length > 4096 ||
value.includes("\0") ||
Buffer.from(value, "utf8").toString("utf8") !== value
)
fail(path, "Expected bounded nonempty text");
return value as string;
};
const digest = (value: unknown, path: string): string => {
if (typeof value !== "string" || !/^[0-9a-f]{64}$/.test(value)) fail(path, "Expected SHA-256 hex");
return value as string;
};
const array = (value: unknown, path: string): unknown[] => {
if (!Array.isArray(value) || value.length > 10000) fail(path, "Expected a bounded array");
return value as unknown[];
};
const unique = (values: string[], path: string) => {
if (new Set(values).size !== values.length) fail(path, "Duplicate identities");
};
export function artifactPath(value: unknown): string {
const path = text(value, "artifact path");
if (
path.startsWith("/") ||
path.includes("\\") ||
path.split("/").some((part) => !part || part === "." || part === "..") ||
/[\u0000-\u001f\u007f?#%]/.test(path)
)
fail(path, "Expected a normalized relative artifact path");
return path;
}
/** No self-reference: the digest covers ABI/export metadata and the digests of every declared file. */
export function implementationDigest(implementation: Omit<ArtifactImplementation, "contentDigest">): string {
return hexDigest({
...implementation,
exports: [...implementation.exports].sort(),
files: [...implementation.files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),
});
}
export function parsePackageArtifacts(value: unknown): PackageArtifacts {
const record = object(value, "manifest");
keys(
record,
["schemaVersion", "packageRevisionId", "bindingDigest", "sourceDigest", "versions", "implementations"],
[],
"manifest",
);
if (record.schemaVersion !== 1) fail("manifest", "Unsupported artifact manifest version");
const versions = object(record.versions, "versions");
keys(versions, ["value", "reference", "ports", "crdt"], [], "versions");
for (const [key, value] of Object.entries(versions)) text(value, `versions.${key}`);
const implementations = array(record.implementations, "implementations").map(
(value, index): ArtifactImplementation => {
const path = `implementations[${index}]`,
entry = object(value, path);
keys(
entry,
["id", "target", "compatibility", "path", "ports", "exports", "files", "contentDigest"],
["registryExport"],
path,
);
if (entry.compatibility !== "quixos-service-v1" && entry.compatibility !== "quixos-js-module-v1")
fail(path, "Unsupported artifact ABI");
if (entry.ports !== "quixos-transaction-ports-v1") fail(path, "Unsupported transaction port ABI");
if (
entry.compatibility === "quixos-service-v1" &&
(entry.target !== "server" || entry.registryExport !== undefined)
)
fail(path, "Service must target server and has no JS registry export");
if (entry.compatibility === "quixos-js-module-v1") text(entry.registryExport, `${path}.registryExport`);
const exports = array(entry.exports, `${path}.exports`).map((value) => text(value, `${path}.exports`));
unique(exports, `${path}.exports`);
const files = array(entry.files, `${path}.files`).map((value): ArtifactFile => {
const file = object(value, `${path}.file`);
keys(file, ["path", "digest", "bytes", "mediaType"], [], `${path}.file`);
if (
!Number.isSafeInteger(file.bytes) ||
(file.bytes as number) < 0 ||
(file.bytes as number) > 256 * 1024 * 1024
)
fail(path, "Artifact file exceeds size bound");
return {
path: artifactPath(file.path),
digest: digest(file.digest, path),
bytes: file.bytes as number,
mediaType: text(file.mediaType, path),
};
});
unique(
files.map((file) => file.path),
`${path}.files`,
);
const entryPath = artifactPath(entry.path);
if (!files.some((file) => file.path === entryPath)) fail(path, "Entrypoint is not covered by the file manifest");
const result: ArtifactImplementation = {
id: text(entry.id, path),
target: text(entry.target, path),
compatibility: entry.compatibility as ArtifactImplementation["compatibility"],
path: entryPath,
ports: "quixos-transaction-ports-v1",
exports: exports.sort(),
files: files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),
contentDigest: digest(entry.contentDigest, path),
...(entry.registryExport !== undefined ? { registryExport: entry.registryExport as string } : {}),
};
const { contentDigest, ...body } = result;
if (implementationDigest(body) !== contentDigest) fail(path, "Implementation metadata digest mismatch");
return result;
},
);
unique(
implementations.map((implementation) => implementation.id),
"implementations",
);
return {
schemaVersion: 1,
packageRevisionId: text(record.packageRevisionId, "packageRevisionId"),
bindingDigest: digest(record.bindingDigest, "bindingDigest"),
sourceDigest: digest(record.sourceDigest, "sourceDigest"),
versions: versions as unknown as PackageArtifacts["versions"],
implementations,
};
}
function checkCoverage(expected: PlanPackage, manifest: PackageArtifacts): void {
if (expected.bindingDigest !== manifest.bindingDigest || expected.sourceDigest !== manifest.sourceDigest)
fail(expected.revision, "Substituted binding schema or source revision");
for (const implementation of manifest.implementations)
for (const id of implementation.exports) {
const entry = expected.exports.find((e) => e.id === id);
if (!entry) fail(implementation.id, `Unknown export ${id}`);
if (
implementation.target !== "server" &&
(!entry?.execution ||
entry.execution.kind === "remote" ||
!entry.execution.targets.includes(implementation.target))
)
fail(implementation.id, `Export ${id} does not promise target ${implementation.target}`);
}
for (const entry of expected.exports) {
if (!manifest.implementations.some((i) => i.compatibility === "quixos-service-v1" && i.exports.includes(entry.id)))
fail(expected.revision, `Missing service fallback for ${entry.id}`);
if (entry.execution.kind !== "remote")
for (const target of entry.execution.targets)
if (
!manifest.implementations.some(
(i) =>
i.target === target &&
i.exports.includes(entry.id) &&
(target === "server" || i.compatibility !== "quixos-service-v1"),
)
)
fail(expected.revision, `Missing ${target} implementation for ${entry.id}`);
}
}
export type ArtifactReader = (packageRevisionId: string, path: string, maxBytes: number) => Promise<Uint8Array>;
/** Caller supplies a reader confined to the resolved immutable package output, including symlink checks. */
export async function checkExecutionWorld(
plan: ExecutionPlan,
inputs: readonly unknown[],
read: ArtifactReader,
): Promise<CheckedWorld> {
if (canonicalJson(plan.definition.versions) !== canonicalJson(executionVersions))
fail("world", "Unsupported plan codec versions");
if (hexDigest(plan.definition) !== plan.digest) fail("plan", "Plan digest mismatch");
const manifests = inputs.map(parsePackageArtifacts);
unique(
manifests.map((m) => m.packageRevisionId),
"packages",
);
if (manifests.length !== plan.definition.packages.length) fail("world", "Package closure differs from plan");
const artifacts: CheckedWorld["artifacts"] = [];
for (const expected of plan.definition.packages) {
const manifest =
manifests.find((m) => m.packageRevisionId === expected.revision) ??
fail(expected.revision, "Missing package artifact");
checkCoverage(expected, manifest);
if (canonicalJson(manifest.versions) !== canonicalJson(plan.definition.versions))
fail(expected.revision, "Incompatible runtime codecs");
const files = new Map<string, ArtifactFile>();
for (const implementation of manifest.implementations) {
for (const file of implementation.files) {
const existing = files.get(file.path);
if (existing && canonicalJson(existing) !== canonicalJson(file))
fail(file.path, "Conflicting file descriptors");
files.set(file.path, file);
}
artifacts.push({
binding: JSON.stringify([expected.revision, implementation.id]),
target: implementation.target,
digest: implementation.contentDigest,
});
}
for (const file of files.values()) {
const bytes = await read(expected.revision, file.path, file.bytes);
if (bytes.byteLength !== file.bytes || createHash("sha256").update(bytes).digest("hex") !== file.digest)
fail(file.path, "Artifact bytes do not match the checked content digest");
}
}
return {
codec: "camino-world-v1",
plan: plan.digest,
artifacts: artifacts.sort((a, b) =>
a.binding < b.binding ? -1 : a.binding > b.binding ? 1 : a.target < b.target ? -1 : a.target > b.target ? 1 : 0,
),
versions: { ...plan.definition.versions },
};
}
/** Node counterpart of replica-core worldDigest. The wire fixture checks cross-package agreement. */
export function checkedWorldDigest(world: CheckedWorld): string {
const normalized = {
...world,
artifacts: [...world.artifacts].sort((a, b) =>
a.binding < b.binding ? -1 : a.binding > b.binding ? 1 : a.target < b.target ? -1 : a.target > b.target ? 1 : 0,
),
};
const json = canonicalJson(normalized),
content = createHash("sha256").update(json).digest("hex");
const components = ["camino-world-v1", content].map((text) => Buffer.from(text, "utf8"));
const count = Buffer.alloc(4);
count.writeUInt32BE(components.length);
const pieces = [count];
for (const bytes of components) {
const length = Buffer.alloc(4);
length.writeUInt32BE(bytes.length);
pieces.push(length, bytes);
}
return createHash("sha256").update(Buffer.concat(pieces)).digest("hex");
}
+3
View File
@@ -0,0 +1,3 @@
export * from "./plan.js";
export * from "./artifacts.js";
export * from "./ports.js";
+401
View File
@@ -0,0 +1,401 @@
import type {
WorkspaceRevision,
ExecutionContract,
ValueType,
Binding,
DependencyBinding,
EdgeTraversal,
InterfaceRevisionId,
AtomId,
OperationId,
} from "../capability-model/types.js";
import { validateWorkspaceRevision } from "../capability-model/validation.js";
import { canonicalJson, contentDigest } from "../capability-model/evolution.js";
import { resolveForwardedBinding } from "../capability-model/forwarding.js";
import { linkQueries, type LinkedQuery } from "../query/link.js";
import { type BindingSchema } from "../bindings/index.js";
export class ExecutionPlanError extends Error {
constructor(
readonly code: string,
readonly path: string,
message: string,
) {
super(`${code} ${path}: ${message}`);
this.name = "ExecutionPlanError";
}
}
export const hexDigest = (value: unknown): string => contentDigest(value).slice("sha256:".length);
const sorted = <T>(values: readonly T[], key: (value: T) => string): T[] =>
[...values].sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0));
export const publicOperationKey = (atom: string, iface: string, operation: string): string =>
JSON.stringify([atom, iface, operation]);
export type DataDependency =
| { kind: "state"; slot: string; via?: EdgeTraversal }
| { kind: "edge"; edge: string; projection: string; via?: EdgeTraversal }
| { kind: "operation"; operation: string; port: string; via?: EdgeTraversal }
| { kind: "query"; query: string; port: string };
export interface PlannedOperation {
key: string;
atomId: string;
interfaceRevisionId: string;
operationId: string;
scope: "object" | "class";
execution: ExecutionContract;
input: ValueType;
output: ValueType;
/** Concrete target after public forwarding. Never chosen by a runtime caller. */
binding: Exclude<Binding, { kind: "interface" }>;
dependencies: DataDependency[];
}
export interface PlannedQuery {
id: string;
query: LinkedQuery;
/** Query-selected getter entrypoints; dependency traversal supplies canonical inputs. */
operations: string[];
/** Local query roots that admission must support, in addition to selected wire outputs. */
admittedOperations: string[];
}
export interface PlanPackage {
revision: string;
source: WorkspaceRevision["packageImports"][number]["source"];
sourceDigest: string;
bindingDigest: string;
exports: { id: string; execution: ExecutionContract }[];
}
export interface ExecutionPlanDefinition {
codec: "camino-execution-plan-v1";
workspace: WorkspaceRevision;
bindingSchema: BindingSchema;
packages: PlanPackage[];
owners: { id: string; atom: string; interface: string; initialization: unknown; storage: unknown }[];
operations: PlannedOperation[];
queries: PlannedQuery[];
profiles: { slot: string; profile: string }[];
versions: {
value: "camino-value-v1";
reference: "camino-ref-v1";
ports: "quixos-transaction-ports-v1";
crdt: "camino-automerge-3.4.1-structured-v1";
};
}
export interface ExecutionPlan {
definition: ExecutionPlanDefinition;
digest: string;
}
export const executionVersions: ExecutionPlanDefinition["versions"] = {
value: "camino-value-v1",
reference: "camino-ref-v1",
ports: "quixos-transaction-ports-v1",
crdt: "camino-automerge-3.4.1-structured-v1",
};
/** Selection only: the coordinator separately verifies the caller's grant and receiver witness. */
export function resolvePlannedOperation(
plan: ExecutionPlan,
atom: string,
iface: string,
operation: string,
): PlannedOperation {
const key = publicOperationKey(atom, iface, operation);
const selected = plan.definition.operations.find((entry) => entry.key === key);
if (!selected) throw new ExecutionPlanError("UNINSTALLED_OPERATION", key, "No installed public operation binding");
return selected;
}
/** Bound argument/state codec subset. A parsed type alone does not mean a runtime implements it. */
function checkValue(type: ValueType, path: string, allowChange = false): void {
switch (type.kind) {
case "scalar":
case "object-ref":
return;
case "builtin":
if (type.name === "unit") return;
break;
case "optional":
case "list":
checkValue(type.value, path, allowChange);
return;
case "record":
for (const [name, field] of Object.entries(type.fields)) checkValue(field, `${path}.${name}`, allowChange);
return;
case "change":
if (allowChange) {
checkValue(type.value, path);
return;
}
break;
}
throw new ExecutionPlanError("UNSUPPORTED_PORTABLE_CODEC", path, `Portable execution does not support ${type.kind}`);
}
/** Compile declarations into a closed dispatch/dependency graph before artifact selection. */
export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
const workspace = structuredClone(input);
const issues = validateWorkspaceRevision(workspace);
if (issues.length)
throw new ExecutionPlanError(
"INVALID_WORKSPACE",
workspace.id,
issues.map((i) => `${i.path}: ${i.message}`).join("\n"),
);
const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i]));
const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p]));
const attachments = [...workspace.sharedAttachments, ...workspace.conformances.flatMap((c) => c.privateAttachments)];
for (const pkg of workspace.packageImports)
for (const entry of pkg.exports)
if (entry.execution && entry.execution.kind !== "remote") {
checkValue(entry.inputType, `${pkg.revisionId}/${entry.id}/input`);
checkValue(entry.outputType, `${pkg.revisionId}/${entry.id}/output`);
}
for (const attachment of attachments)
if (attachment.kind === "state" && attachment.storagePolicy.kind === "crdt-document")
checkValue(attachment.valueType, attachment.id);
const operationMap = new Map<string, PlannedOperation>();
const queries = linkQueries(workspace);
const queryKey = (pkg: string, id: string) => JSON.stringify([pkg, id]);
const queryMap = new Map(queries.map((q) => [queryKey(q.packageRevisionId, q.checked.declaration.id), q]));
const declaration = (iface: InterfaceRevisionId, operation: OperationId) =>
interfaces
.get(iface)
?.members.flatMap((m) => m.operations)
.find((o) => o.id === operation);
const queryOperations = (query: LinkedQuery) =>
query.fields.map((f) => publicOperationKey(f.atomId, f.interfaceRevisionId, f.getter));
const targetAtoms = (binding: DependencyBinding, atom: AtomId): AtomId[] => {
if (!("via" in binding) || !binding.via) return [atom];
const edge = attachments.find((a) => a.id === binding.via!.edgeTypeId);
if (!edge || edge.kind !== "edge") throw new ExecutionPlanError("MISSING_EDGE", atom, "Missing traversal edge");
const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!;
const constraint = endpoint.constraint;
return workspace.atoms
.filter((a) =>
constraint.kind === "atom"
? constraint.atomId === a.id
: workspace.conformances.some(
(c) => c.atomId === a.id && c.interfaceRevisionId === constraint.interfaceRevisionId,
),
)
.map((a) => a.id);
};
const add = (atom: AtomId, iface: InterfaceRevisionId, operation: OperationId): PlannedOperation => {
const key = publicOperationKey(atom, iface, operation),
existing = operationMap.get(key);
if (existing) return existing;
const publicContract = declaration(iface, operation);
if (!publicContract) throw new ExecutionPlanError("MISSING_OPERATION", key, "Missing public contract");
const { binding } = resolveForwardedBinding(workspace, atom, iface, operation);
const planned: PlannedOperation = {
key,
atomId: atom,
interfaceRevisionId: iface,
operationId: operation,
scope: publicContract.scope === "class" ? "class" : "object",
execution: publicContract.execution ?? { kind: "remote" },
input: publicContract.inputType,
output: publicContract.outputType,
binding,
dependencies: [],
};
operationMap.set(key, planned);
const portable = planned.execution.kind !== "remote";
if (portable) {
checkValue(planned.input, `${key}/input`, binding.kind === "state" && binding.primitive === "edit");
checkValue(planned.output, `${key}/output`);
}
if (binding.kind === "state") planned.dependencies.push({ kind: "state", slot: binding.slotId });
else if (binding.kind === "edge")
planned.dependencies.push({ kind: "edge", edge: binding.edgeTypeId, projection: binding.projectionId });
else {
const entry = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId);
if (!entry) throw new ExecutionPlanError("MISSING_EXPORT", key, "Unresolved package export");
for (const dependency of binding.dependencies) {
const port = entry.dependencyPorts.find((p) => p.id === dependency.portId)!;
const target = dependency.binding;
const via = "via" in target && target.via ? { via: target.via } : {};
if ("via" in target && target.via)
planned.dependencies.push({ kind: "edge", edge: target.via.edgeTypeId, projection: target.via.projectionId });
if (target.kind === "state") planned.dependencies.push({ kind: "state", slot: target.slotId, ...via });
else if (target.kind === "edge")
planned.dependencies.push({ kind: "edge", edge: target.edgeTypeId, projection: target.projectionId, ...via });
else if (target.kind === "interface" && port.requirement.kind === "interface") {
const selected =
port.requirement.operations ??
interfaces.get(target.interfaceRevisionId)!.members.flatMap((m) => m.operations.map((o) => o.id));
for (const targetAtom of targetAtoms(target, atom))
for (const selectedOperation of selected) {
const operation = add(targetAtom, target.interfaceRevisionId, selectedOperation);
planned.dependencies.push({ kind: "operation", operation: operation.key, port: port.id, ...via });
}
} else if (target.kind === "query") {
const query = queryMap.get(queryKey(target.packageRevisionId, target.queryId));
if (!query) throw new ExecutionPlanError("MISSING_QUERY", key, `Unresolved query port ${port.id}`);
planned.dependencies.push({
kind: "query",
query: queryKey(target.packageRevisionId, target.queryId),
port: port.id,
});
}
}
}
planned.dependencies = sorted(planned.dependencies, canonicalJson);
return planned;
};
for (const conformance of workspace.conformances)
for (const entry of conformance.operationBindings)
add(conformance.atomId, conformance.interfaceRevisionId, entry.operationId);
// Check the transitive closure, retaining a dependency path for diagnostics.
for (const root of operationMap.values()) {
if (root.execution.kind === "remote") continue;
const requested = root.execution,
visited = new Set<string>();
const walk = (key: string, path: string[]) => {
if (visited.has(key)) return;
visited.add(key);
const node = operationMap.get(key)!;
if (node.execution.kind === "remote" || (requested.kind === "snapshot" && node.execution.kind !== "snapshot"))
throw new ExecutionPlanError(
"NONPORTABLE_DEPENDENCY",
path.join(" -> "),
"Execution effect is not allowed by the root contract",
);
for (const target of requested.targets)
if (!node.execution.targets.includes(target))
throw new ExecutionPlanError(
"MISSING_EXECUTION_TARGET",
path.join(" -> "),
`Dependency does not provide ${target}`,
);
for (const dependency of node.dependencies) {
if (dependency.kind === "operation")
walk(dependency.operation, [...path, dependency.port, dependency.operation]);
if (dependency.kind === "query") {
// An admitted presentation window is not an authoritative mutation read set.
if (requested.kind === "transaction")
throw new ExecutionPlanError(
"QUERY_IN_TRANSACTION",
[...path, dependency.port].join(" -> "),
"Capture explicit object references as mutation inputs; local presentation queries cannot define transaction membership",
);
const query = queryMap.get(dependency.query)!;
if (query.checked.declaration.placement?.kind !== "local")
throw new ExecutionPlanError(
"REMOTE_QUERY_DEPENDENCY",
path.join(" -> "),
"Portable code requires a local query",
);
for (const operation of queryOperations(query)) walk(operation, [...path, dependency.port, operation]);
}
}
};
walk(root.key, [root.key]);
}
const plannedQueries: PlannedQuery[] = queries.map((query) => ({
id: queryKey(query.packageRevisionId, query.checked.declaration.id),
query,
operations: queryOperations(query).sort(),
admittedOperations: [],
}));
for (const query of plannedQueries) {
const placement = query.query.checked.declaration.placement;
if (placement?.kind !== "local") continue;
const source = plannedQueries.find(
(q) => q.id === queryKey(query.query.packageRevisionId, placement.sourceQueryId),
);
if (!source || source.query.checked.declaration.placement?.kind !== "replica")
throw new ExecutionPlanError("MISSING_REPLICA_SOURCE", query.id, "Local query source must be a replica query");
const localRoot = query.query.checked.declaration.root,
sourceRoot = source.query.checked.declaration.root;
if (localRoot.kind !== sourceRoot.kind || localRoot.interface !== sourceRoot.interface)
throw new ExecutionPlanError(
"REPLICA_ROOT_MISMATCH",
query.id,
"Local and replica queries must use the same root contract",
);
const traversals = (linked: LinkedQuery) => {
const paths = new Set<string>();
const visit = (selections: LinkedQuery["checked"]["selection"], parents: string[]) => {
for (const selection of selections) {
const member = selection.interfaceRevisionId
? interfaces.get(selection.interfaceRevisionId)?.members.find((m) => m.id === selection.memberId)
: undefined;
const path = member?.kind === "relationship" ? [...parents, member.id] : parents;
if (member?.kind === "relationship") paths.add(JSON.stringify(path));
visit(selection.selection, path);
}
};
visit(linked.checked.selection, []);
return paths;
};
const admittedTraversals = traversals(source.query);
for (const traversal of traversals(query.query))
if (!admittedTraversals.has(traversal))
throw new ExecutionPlanError(
"REPLICA_TRAVERSAL_MISSING",
query.id,
`Replica source does not admit relationship path ${traversal}`,
);
// The admission executor follows these canonical dependencies on the admitted roots.
// They do not change source candidate membership or make local take/filters authoritative.
source.admittedOperations.push(...query.operations);
for (const operation of query.operations) {
const node = operationMap.get(operation)!;
if (node.execution.kind !== "snapshot" || !node.execution.targets.includes("browser"))
throw new ExecutionPlanError(
"NONPORTABLE_QUERY_FIELD",
`${query.id}/${operation}`,
"Local query getter must provide browser snapshot execution",
);
}
}
for (const query of plannedQueries) query.admittedOperations = [...new Set(query.admittedOperations)].sort();
const schema: BindingSchema = {
format: "quixos-bindings",
version: 1,
interfaces: sorted(
workspace.interfaceImports.filter((i) => !i.template),
(i) => i.revisionId,
),
interfaceTemplates: sorted(
workspace.interfaceImports.filter((i) => i.template),
(i) => i.revisionId,
),
packages: sorted(workspace.packageImports, (p) => p.revisionId),
};
const normalizedSchema = JSON.parse(canonicalJson(schema)) as BindingSchema;
const definition: ExecutionPlanDefinition = {
codec: "camino-execution-plan-v1",
workspace,
bindingSchema: normalizedSchema,
packages: sorted(workspace.packageImports, (p) => p.revisionId).map((p) => ({
revision: p.revisionId,
source: p.source,
sourceDigest: hexDigest({ revision: p.revisionId, source: p.source }),
bindingDigest: hexDigest(normalizedSchema),
exports: sorted(p.exports, (e) => e.id).map((e) => ({ id: e.id, execution: e.execution ?? { kind: "remote" } })),
})),
owners: sorted(workspace.conformances, (c) => c.id ?? "").map((c) => {
if (!c.id)
throw new ExecutionPlanError(
"OWNER_ID_REQUIRED",
`${c.atomId}/${c.interfaceRevisionId}`,
"Installed state owners require explicit stable identities",
);
return {
id: c.id,
atom: c.atomId,
interface: c.interfaceRevisionId,
initialization: c.initialization ?? [],
storage: c.privateAttachments,
};
}),
operations: sorted([...operationMap.values()], (o) => o.key),
queries: sorted(plannedQueries, (q) => q.id),
profiles: sorted(
attachments.filter((a) => a.kind === "state" && a.storagePolicy.kind === "crdt-document"),
(a) => a.id,
).map((a) => ({ slot: a.id, profile: "camino-automerge-3.4.1-structured-v1" })),
versions: { ...executionVersions },
};
const normalized = JSON.parse(canonicalJson(definition)) as ExecutionPlanDefinition;
return { definition: normalized, digest: hexDigest(normalized) };
}
+78
View File
@@ -0,0 +1,78 @@
/** Process/module ABI shared by generated adapters. Values are decoded against the checked
* operation/port schema by the coordinator, not trusted because this header parsed. */
export const transactionPortAbi = "quixos-transaction-ports-v1";
export type PortAction =
| { kind: "state"; primitive: "read" | "write" | "edit" }
| { kind: "edge"; primitive: "resolve" | "connect" | "disconnect" }
| { kind: "interface"; operationId: string; key?: string }
| { kind: "query" }
| { kind: "creation"; key: string };
export interface PortInvocation {
abi: typeof transactionPortAbi;
/** Coordinator-issued frame and invocation handles, not application object IDs. */
frame: string;
invocation: string;
sequence: number;
portId: string;
action: PortAction;
input: unknown;
}
export interface PackageInvocation {
abi: typeof transactionPortAbi;
frame: string;
invocation: string;
exportId: string;
effect: "snapshot" | "transaction" | "remote";
/** Capability arguments/receiver use the versioned managed-reference codec. */
input: unknown;
receiver: unknown | null;
}
export type InvocationReply =
| { abi: typeof transactionPortAbi; invocation: string; status: "returned"; output: unknown }
| { abi: typeof transactionPortAbi; invocation: string; status: "failed"; error: { code: string; message: string } };
export type ServiceControl =
| { abi: typeof transactionPortAbi; kind: "ready" }
| { abi: typeof transactionPortAbi; kind: "cancel"; invocation: string }
| { abi: typeof transactionPortAbi; kind: "shutdown" };
/** ABI-only header check. LF-08 resolves handles and enforces the frame/port authority. */
export function checkPortInvocation(value: unknown): PortInvocation {
if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype)
throw new Error("Invalid port invocation");
const fields = value as Record<string, unknown>;
const keys = ["abi", "frame", "invocation", "sequence", "portId", "action", "input"];
if (
Object.keys(fields).length !== keys.length ||
keys.some((key) => !Object.hasOwn(fields, key)) ||
fields.abi !== transactionPortAbi
)
throw new Error("Invalid port invocation fields or ABI");
for (const key of ["frame", "invocation", "portId"])
if (typeof fields[key] !== "string" || !fields[key] || fields[key].length > 4096)
throw new Error(`Invalid port invocation ${key}`);
if (!Number.isSafeInteger(fields.sequence) || (fields.sequence as number) < 1)
throw new Error("Invalid port invocation sequence");
const action = fields.action as Record<string, unknown>;
if (
!action ||
typeof action !== "object" ||
Array.isArray(action) ||
Object.getPrototypeOf(action) !== Object.prototype
)
throw new Error("Invalid port action");
const exact = (names: string[]) =>
Object.keys(action).length === names.length && names.every((name) => Object.hasOwn(action, name));
const identifier = (value: unknown) => typeof value === "string" && value.length > 0 && value.length <= 1024;
const valid =
action.kind === "state"
? exact(["kind", "primitive"]) && ["read", "write", "edit"].includes(action.primitive as string)
: action.kind === "edge"
? exact(["kind", "primitive"]) && ["resolve", "connect", "disconnect"].includes(action.primitive as string)
: action.kind === "interface"
? (exact(["kind", "operationId"]) || (exact(["kind", "operationId", "key"]) && identifier(action.key))) &&
identifier(action.operationId)
: action.kind === "creation"
? exact(["kind", "key"]) && identifier(action.key)
: action.kind === "query" && exact(["kind"]);
if (!valid) throw new Error("Invalid port action");
return { ...fields, action: { ...action } } as unknown as PortInvocation;
}
+286
View File
@@ -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 -37
View File
@@ -1,44 +1,8 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { test } from "node:test"; 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 { compileQuery } from "../src/query/compile.js";
import { linkQueries } from "../src/query/link.js"; import { linkQueries } from "../src/query/link.js";
const directory = resolve("test/fixtures/local-first-reader"); import { loadReaderWorkspace as load, readReaderFixture as read } from "./reader-fixture.js";
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;
};
test("complete reader contracts compose factories, portable selectors, CRDT edits, and six query scopes", async () => { test("complete reader contracts compose factories, portable selectors, CRDT edits, and six query scopes", async () => {
const workspace = await load(); const workspace = await load();
assert.equal(workspace.constructors.length, 0); assert.equal(workspace.constructors.length, 0);
+44
View File
@@ -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;
};