Separate graph execution identity from installation provenance

This commit is contained in:
Timothy J. Aveni
2026-09-22 18:27:59 -07:00
parent c5e79ae3c9
commit 1b296daf42
13 changed files with 243 additions and 20 deletions
+7 -5
View File
@@ -1,3 +1,4 @@
import { compileKernelProgram } from "../execution-world/kernel.js";
import { hexDigest, type ExecutionPlan } from "../execution-world/plan.js";
import { resultInterfaces } from "../execution-world/projections.js";
import { portableValueSchema, tsType, PortableBindingError } from "./portable.js";
@@ -10,6 +11,7 @@ export function generatePortableClientBindings(
options: { target: string; runtimeModule?: string },
): string {
if (hexDigest(plan.definition) !== plan.digest) throw new PortableBindingError("client", "Invalid plan digest");
const executionPlan = (compileKernelProgram(plan) as { plan: string }).plan;
const workspace = plan.definition.workspace,
atoms = new Map<string, string[]>(),
unavailable: { key: string; reason: string }[] = [];
@@ -39,7 +41,7 @@ export function generatePortableClientBindings(
const roots = resultInterfaces(plan, operation.key);
const descriptor = {
key: operation.key,
plan: plan.digest,
plan: executionPlan,
effect: operation.execution.kind,
input: portableValueSchema(operation.input),
output: portableValueSchema(operation.output),
@@ -91,7 +93,7 @@ export function generatePortableClientBindings(
JSON.stringify({
kind: "server-projection",
id: query.id,
plan: plan.digest,
plan: executionPlan,
root: declaration.root.interface,
rootKind: declaration.root.kind,
}) +
@@ -108,7 +110,7 @@ export function generatePortableClientBindings(
if (declaration.placement.kind === "local") {
const list = views.get(pkg.displayName) ?? [];
list.push(
`${JSON.stringify(declaration.displayName)}: ${JSON.stringify({ id: query.id, plan: plan.digest, source: JSON.stringify([query.query.packageRevisionId, declaration.placement.sourceQueryId]) })} as const satisfies LocalQueryDescriptor`,
`${JSON.stringify(declaration.displayName)}: ${JSON.stringify({ id: query.id, plan: executionPlan, source: JSON.stringify([query.query.packageRevisionId, declaration.placement.sourceQueryId]) })} as const satisfies LocalQueryDescriptor`,
);
views.set(pkg.displayName, list);
addResult();
@@ -116,11 +118,11 @@ export function generatePortableClientBindings(
}
const list = queries.get(pkg.displayName) ?? [];
list.push(
`${JSON.stringify(declaration.displayName)}: { id: ${JSON.stringify(query.id)}, plan: ${JSON.stringify(plan.digest)}, root: ${JSON.stringify(declaration.root.interface)}, rootKind: ${JSON.stringify(declaration.root.kind)} } as const satisfies ReplicaProjection<${JSON.stringify(declaration.root.interface)}, ${JSON.stringify(declaration.root.kind)}>`,
`${JSON.stringify(declaration.displayName)}: { id: ${JSON.stringify(query.id)}, plan: ${JSON.stringify(executionPlan)}, root: ${JSON.stringify(declaration.root.interface)}, rootKind: ${JSON.stringify(declaration.root.kind)} } as const satisfies ReplicaProjection<${JSON.stringify(declaration.root.interface)}, ${JSON.stringify(declaration.root.kind)}>`,
);
queries.set(pkg.displayName, list);
}
const groups = (entries: Map<string, string[]>) =>
[...entries].map(([name, values]) => `${JSON.stringify(name)}: {${values.join(",\n")}}`).join(",\n");
return `// Generated application descriptors. Regenerate; do not edit.\nimport { defineOperationDescriptor, decodeArgument, workspaceId, type Json, type PreparedChange, type ManagedReference, type ReplicaProjection, type ServerProjection, type LocalQueryDescriptor } from ${JSON.stringify(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const planDigest = ${JSON.stringify(plan.digest)};\nexport const operations = {${groups(atoms)}} as const;\nexport const queries = {${groups(queries)}} as const;\nexport const views = {${groups(views)}} as const;\nexport const serverProjections = {${groups(serverProjections)}} as const;\nexport const results = {${groups(results)}} as const;\nexport const unavailable = ${JSON.stringify(unavailable)} as const;\n`;
return `// Generated application descriptors. Regenerate; do not edit.\nimport { defineOperationDescriptor, decodeArgument, workspaceId, type Json, type PreparedChange, type ManagedReference, type ReplicaProjection, type ServerProjection, type LocalQueryDescriptor } from ${JSON.stringify(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const planDigest = ${JSON.stringify(executionPlan)};\nexport const operations = {${groups(atoms)}} as const;\nexport const queries = {${groups(queries)}} as const;\nexport const views = {${groups(views)}} as const;\nexport const serverProjections = {${groups(serverProjections)}} as const;\nexport const results = {${groups(results)}} as const;\nexport const unavailable = ${JSON.stringify(unavailable)} as const;\n`;
}
+1 -2
View File
@@ -75,9 +75,8 @@ export const specializePackageExport = (
const digest = createHash("sha256")
.update(
JSON.stringify([
"quixos-package-specialization-v1",
"quixos-package-specialization-v2",
pkg.revisionId,
[pkg.source.repository, pkg.source.commit],
definition.id,
arguments_.map(canonicalTypeArgument),
environment.self ?? null,
+1 -3
View File
@@ -258,10 +258,8 @@ export interface AppliedInterfaceIdentity {
export const appliedInterfaceId = (application: AppliedInterfaceIdentity): InterfaceRevisionId => {
const encoding = JSON.stringify([
"quixos-applied-interface-v1",
"quixos-applied-interface-v2",
application.definitionId,
application.source.repository,
application.source.commit.toLowerCase(),
application.arguments.map(canonicalTypeArgument),
application.self ?? null,
]);
+14 -4
View File
@@ -1,3 +1,4 @@
import { graphExecutionRepresentation } from "./graph-execution.js";
import { open, lstat } from "node:fs/promises";
import { constants } from "node:fs";
import { join } from "node:path";
@@ -24,7 +25,7 @@ export async function loadCheckedInstallationCandidate(
) {
if (!rootPattern.test(root) || !(await lstat(root)).isDirectory())
throw Error("Candidate must be an immutable Nix output directory");
const [candidate, plan, artifacts, source, entryRequest, kernel, queries] = await Promise.all([
const [candidate, plan, artifacts, source, entryRequest, kernel, queries, graph] = await Promise.all([
readJson(root, "candidate.json"),
readJson(root, "execution-plan.json"),
readJson(root, "execution-artifacts.json"),
@@ -32,6 +33,7 @@ export async function loadCheckedInstallationCandidate(
readJson(root, "entry.json"),
readJson(root, "kernel.json"),
readJson(root, "queries.json"),
readJson(root, "graph-execution.json"),
]);
const { executionContracts: _legacyContracts, ...workspace } = candidate;
if (
@@ -81,12 +83,19 @@ export async function loadCheckedInstallationCandidate(
["kernel", kernel, "camino-kernel-program-v1"],
["queries", queries, "camino-query-program-v1"],
] as const)
if (program?.codec !== codec || program.plan !== plan.digest || hexDigest(program) !== artifacts.programs?.[name])
if (program?.codec !== codec || program.plan !== kernel.plan || hexDigest(program) !== artifacts.programs?.[name])
throw new ExecutionPlanError(
"CANDIDATE_PROGRAM_MISMATCH",
name,
"Stored interpreter input must match the checked build and plan",
);
const expectedGraph = graphExecutionRepresentation(plan, installation, kernel, queries);
if (hexDigest(graph) !== hexDigest(expectedGraph) || artifacts.execution !== hexDigest(graph))
throw new ExecutionPlanError(
"CANDIDATE_EXECUTION_MISMATCH",
root,
"Stored graph execution differs from checked inputs",
);
const provenance = {
workspaceRevision: workspace.id as string,
source: { repository: source.repository as string, commit: source.commit as string },
@@ -110,8 +119,9 @@ export async function loadCheckedInstallationCandidate(
return {
reference,
workspace: plan.definition.workspace.workspaceId,
world: installation.world,
plan: plan.digest,
world: hexDigest(graph),
plan: kernel.plan as string,
graphExecution: graph,
provenance,
entry,
kernel,
+59
View File
@@ -0,0 +1,59 @@
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import type { ExecutionInstallation } from "./installation.js";
/** Execution inputs, not build evidence. Nominal revision IDs remain dispatch
* keys: replacing a contract with a structurally similar one is not continuity.
* Host placement/policy/credentials are compared separately by execution groups. */
export function graphExecutionRepresentation(
plan: ExecutionPlan,
installation: ExecutionInstallation,
kernel: unknown,
queries: unknown,
) {
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
throw new ExecutionPlanError("GRAPH_PLAN_MISMATCH", "graph", "Expected one checked installation");
if (
!kernel ||
typeof kernel !== "object" ||
Array.isArray(kernel) ||
!queries ||
typeof queries !== "object" ||
Array.isArray(queries)
)
throw new ExecutionPlanError("GRAPH_PROGRAM_MISMATCH", "graph", "Expected interpreter program records");
const input = kernel as Record<string, unknown>,
queryInput = queries as Record<string, unknown>;
const { plan: key, ...program } = input;
if (
input.codec !== "camino-kernel-program-v1" ||
queryInput.codec !== "camino-query-program-v1" ||
queryInput.plan !== key ||
key !== hexDigest({ versions: plan.definition.versions, program })
)
throw new ExecutionPlanError("GRAPH_PROGRAM_MISMATCH", "graph", "Interpreter inputs do not match execution key");
return {
codec: "camino-graph-execution-v1",
versions: plan.definition.versions,
kernel,
queries,
// Include every selected implementation, including acceptance/remote code
// and the browser dependency closure, not merely the public export names.
packages: installation.packages.map((pkg) => ({
revision: pkg.revision,
service: pkg.service ?? null,
module: pkg.module ?? null,
browser: pkg.browserImplementation ?? null,
})),
controllers: plan.definition.packages.map((pkg) => ({
revision: pkg.revision,
controllers: pkg.controllers ?? [],
})),
shared: {
core: installation.coreURL ?? null,
engine: installation.engineURL ?? null,
components: [
...new Map(installation.components.flatMap((component) => Object.entries(component.sharedModules))).entries(),
].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
},
};
}
+2
View File
@@ -19,3 +19,5 @@ export * from "./candidate.js";
export { reviewSemanticMajors } from "../capability-model/evolution.js";
export * from "./public-reflection.js";
export * from "./graph-execution.js";
+2
View File
@@ -112,6 +112,7 @@ export interface ExecutionInstallation {
readonly root: string;
readonly service?: ArtifactImplementation;
readonly module?: ArtifactImplementation;
readonly browserImplementation?: ArtifactImplementation;
readonly browserURL?: string;
readonly browserRegistry?: string;
}[];
@@ -270,6 +271,7 @@ function assembleExecutionInstallation(
root,
...(service ? { service } : {}),
...(module ? { module } : {}),
browserImplementation: browser,
browserURL: contentArtifactUrl(entry),
browserRegistry: browser.registryExport,
});
+17 -4
View File
@@ -36,7 +36,7 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
}
};
const ownerBySlot = new Map<string, string>();
const edges: unknown[] = [];
const edges: { id: string; owner: string; a: unknown; b: unknown }[] = [];
const owners = plan.definition.owners.map((owner) => {
const slots: unknown[] = [];
for (const storage of owner.storage as PersistentAttachment[]) {
@@ -166,7 +166,19 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
kind: "creation",
atom: bound.atomId,
schema: portableValueSchema(requirement.inputType),
recipe: hexDigest({ plan: plan.digest, operation: operation.key, port: dependency.portId }),
recipe: hexDigest({
codec: "camino-creation-recipe-v1",
versions: plan.definition.versions,
operation: operation.key,
export: entry.id,
port: dependency.portId,
requirement,
binding: bound,
owners: owners.filter((owner) => owner.atom === bound.atomId),
edges: edges.filter((edge) =>
owners.some((owner) => owner.atom === bound.atomId && owner.id === edge.owner),
),
}),
owners: bound.owners.map((seed) => {
const owner = owners.find((o) => o.atom === bound.atomId && o.interface === seed.interfaceRevisionId);
if (!owner)
@@ -230,11 +242,12 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
output: schema(entry.outputType),
})),
}));
return {
const body = {
codec: "camino-kernel-program-v1",
plan: plan.digest,
model: { workspace: workspace.workspaceId, atoms: workspace.atoms.map((atom) => atom.id), owners, edges },
operations,
packages,
};
// The interpreter's plan key covers its real input, not source/build evidence.
return { ...body, plan: hexDigest({ versions: plan.definition.versions, program: body }) };
}
+2 -1
View File
@@ -1,3 +1,4 @@
import { compileKernelProgram } from "./kernel.js";
import { compileInspectionTemplates } from "./inspection.js";
import { portableValueSchema } from "../bindings/portable.js";
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
@@ -26,7 +27,7 @@ export function compileQueryProgram(plan: ExecutionPlan): unknown {
const interfaces = plan.definition.workspace.interfaceImports;
return {
codec: "camino-query-program-v1",
plan: plan.digest,
plan: (compileKernelProgram(plan) as { plan: string }).plan,
queries: [
...compileInspectionTemplates(plan).queries,
...plan.definition.queries.map(({ id, query, admittedOperations }) => {