Keep cold Candidate verification cooperative with controller renewals
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { canonicalJson } from "../capability-model/evolution.js";
|
||||
import { type ExecutionPlan, type PlanPackage, ExecutionPlanError, hexDigest, executionVersions } from "./plan.js";
|
||||
import {
|
||||
type ExecutionPlan,
|
||||
type PlanPackage,
|
||||
ExecutionPlanError,
|
||||
hexDigest,
|
||||
hexDigestAsync,
|
||||
executionVersions,
|
||||
} from "./plan.js";
|
||||
|
||||
export interface ArtifactFile {
|
||||
path: string;
|
||||
@@ -359,7 +366,7 @@ export async function checkExecutionWorld(
|
||||
): 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");
|
||||
if ((await hexDigestAsync(plan.definition)) !== plan.digest) fail("plan", "Plan digest mismatch");
|
||||
const manifests = inputs.map(parsePackageArtifacts);
|
||||
unique(
|
||||
manifests.map((m) => m.packageRevisionId),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { graphExecutionRepresentation } from "./graph-execution.js";
|
||||
import { lstat } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
||||
import { hexDigest, hexDigestAsync, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
||||
import {
|
||||
loadExecutionInstallation,
|
||||
createClientContractCatalog,
|
||||
@@ -50,7 +50,7 @@ export async function loadCheckedInstallationCandidate(
|
||||
const input = await readJson(root, "candidate.json", scope);
|
||||
try {
|
||||
const { executionContracts: _legacyContracts, ...workspace } = input.value;
|
||||
return hexDigest(workspace);
|
||||
return await hexDigestAsync(workspace);
|
||||
} finally {
|
||||
input.releaseTree();
|
||||
}
|
||||
@@ -72,8 +72,8 @@ export async function loadCheckedInstallationCandidate(
|
||||
const [plan, artifacts, source, entryRequest, kernel, queries] = values;
|
||||
if (
|
||||
plan?.definition?.codec !== "camino-execution-plan-v1" ||
|
||||
hexDigest(plan.definition) !== plan.digest ||
|
||||
workspaceDigest !== hexDigest(plan.definition.workspace)
|
||||
(await hexDigestAsync(plan.definition)) !== plan.digest ||
|
||||
workspaceDigest !== (await hexDigestAsync(plan.definition.workspace))
|
||||
)
|
||||
throw new ExecutionPlanError(
|
||||
"CANDIDATE_PLAN_MISMATCH",
|
||||
@@ -117,7 +117,11 @@ export async function loadCheckedInstallationCandidate(
|
||||
["kernel", kernel, "camino-kernel-program-v1"],
|
||||
["queries", queries, "camino-query-program-v1"],
|
||||
] as const)
|
||||
if (program?.codec !== codec || program.plan !== kernel.plan || hexDigest(program) !== artifacts.programs?.[name])
|
||||
if (
|
||||
program?.codec !== codec ||
|
||||
program.plan !== kernel.plan ||
|
||||
(await hexDigestAsync(program)) !== artifacts.programs?.[name]
|
||||
)
|
||||
throw new ExecutionPlanError(
|
||||
"CANDIDATE_PROGRAM_MISMATCH",
|
||||
name,
|
||||
@@ -129,8 +133,8 @@ export async function loadCheckedInstallationCandidate(
|
||||
await (async () => {
|
||||
const input = await readJson(root, "graph-execution.json", scope);
|
||||
try {
|
||||
const digest = hexDigest(input.value);
|
||||
if (digest !== hexDigest(expectedGraph) || artifacts.execution !== digest)
|
||||
const digest = await hexDigestAsync(input.value);
|
||||
if (digest !== (await hexDigestAsync(expectedGraph)) || artifacts.execution !== digest)
|
||||
throw new ExecutionPlanError(
|
||||
"CANDIDATE_EXECUTION_MISMATCH",
|
||||
root,
|
||||
@@ -164,7 +168,7 @@ export async function loadCheckedInstallationCandidate(
|
||||
return {
|
||||
reference,
|
||||
workspace: plan.definition.workspace.workspaceId,
|
||||
world: hexDigest(expectedGraph),
|
||||
world: await hexDigestAsync(expectedGraph),
|
||||
plan: kernel.plan as string,
|
||||
graphExecution: expectedGraph,
|
||||
provenance,
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
OperationId,
|
||||
} from "../capability-model/types.js";
|
||||
import { validateWorkspaceRevision } from "../capability-model/validation.js";
|
||||
import { canonicalJson, contentDigest } from "../capability-model/evolution.js";
|
||||
import { canonicalJson, contentDigest, contentDigestAsync } 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";
|
||||
@@ -29,6 +29,8 @@ export class ExecutionPlanError extends Error {
|
||||
}
|
||||
}
|
||||
export const hexDigest = (value: unknown): string => contentDigest(value).slice("sha256:".length);
|
||||
export const hexDigestAsync = async (value: unknown): Promise<string> =>
|
||||
(await contentDigestAsync(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 =>
|
||||
|
||||
Reference in New Issue
Block a user