fix: complete durable workspace runtime config handoff

This commit is contained in:
2026-08-11 09:59:25 +02:00
parent 390122480a
commit 971315aa80
6 changed files with 289 additions and 107 deletions
+18 -7
View File
@@ -322,29 +322,40 @@ export class ThtRunner {
}
let snapshotFd: number | undefined;
let canonicalFd: number | undefined;
let manifestFd: number | undefined;
let ch;
try {
snapshotFd = workspaceConfigPath && this.runtimeSnapshots.has(workspaceConfigPath)
? this.openTrustedRuntimeSnapshot(workspaceConfigPath) : undefined;
// Runtime lease publication is durable, but the child must consume the verified
// bytes rather than reopening a mutable pathname after spawn. Keep canonical -c
// for CLI compatibility and hand the same open file as fd 3.
canonicalFd = snapshotFd === undefined && workspaceConfigPath && this.runtimeLeases.has(workspaceConfigPath)
? openSync(workspaceConfigPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW) : undefined;
const lease = workspaceConfigPath ? this.runtimeLeases.get(workspaceConfigPath) : undefined;
// Registry leases retain the verified config bytes in fd 3 and the separately
// published manifest in fd 4. The argv remains the canonical -c pathname for
// diagnostics/compatibility; the harness never trusts that pathname for bytes.
canonicalFd = snapshotFd === undefined && lease
? openSync(lease.path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW) : undefined;
manifestFd = lease
? openSync(lease.manifestPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW) : undefined;
if (lease) {
env.THT_CONFIG_FD = "3";
env.THT_CONFIG_MANIFEST_FD = "4";
env.THT_CONFIG_MANIFEST_SHA256 = lease.manifestSha256;
}
const handoffFd = snapshotFd ?? canonicalFd;
if (canonicalFd !== undefined) env.THT_CONFIG_FD = "3";
ch = spawn(
this.cfg.thtBin,
snapshotFd === undefined ? this.buildArgv(args, workspaceConfigPath) : [...args, "-c", "/dev/fd/3"],
{
cwd: this.cfg.harnessDir,
env,
...(handoffFd === undefined ? {} : { stdio: ["ignore", "pipe", "pipe", handoffFd] }),
...(handoffFd === undefined ? {} : {
stdio: ["ignore", "pipe", "pipe", handoffFd, ...(manifestFd === undefined ? [] : [manifestFd])],
}),
},
);
} finally {
if (snapshotFd !== undefined) closeSync(snapshotFd);
if (canonicalFd !== undefined) closeSync(canonicalFd);
if (manifestFd !== undefined) closeSync(manifestFd);
}
let stdout = "";
let stderr = "";
+35 -34
View File
@@ -3,8 +3,7 @@ import { spawnSync } from "node:child_process";
import { isIP } from "node:net";
import { domainToASCII } from "node:url";
import {
closeSync, constants as fsConstants, fstatSync, lstatSync,
existsSync, mkdirSync, openSync, readFileSync,
existsSync, lstatSync, readFileSync,
} from "node:fs";
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
import { parseAllDocuments } from "yaml";
@@ -20,6 +19,8 @@ import { parseWorkspaceYaml, validateOperationalWorkspace, type WorkspaceDescrip
export interface RuntimeConfigLease {
path: string;
manifestPath: string;
/** SHA-256 of the exact durable manifest bytes handed to the child. */
manifestSha256: string;
workspaceId: string;
workspaceRevision: string;
release(): void;
@@ -53,12 +54,13 @@ interface SnapshotIdentity {
interface PublishedIdentity {
path: string;
manifestPath: string;
/** SHA-256 of the exact durable manifest bytes handed to the child. */
manifestSha256: string;
workspaceId: string;
workspaceRevision: string;
digest: string;
content: string;
manifest: string;
refs: number;
}
@@ -119,11 +121,12 @@ export class WorkspaceRuntimeConfigLeaseFactory {
private readonly env: NodeJS.ProcessEnv;
private readonly secretRoots: readonly string[];
private readonly installation: RuntimeInstallationOverlay;
private readonly published = new Map<string, PublishedIdentity>();
constructor(private readonly input: WorkspaceRuntimeConfigLeaseFactoryInput) {
if (!isAbsolute(input.dataRoot) || !isAbsolute(input.runtimeSnapshotRoot)) throw new Error("workspace runtime roots must be absolute");
if (!existsSync(input.runtimeSnapshotRoot)) mkdirSync(input.runtimeSnapshotRoot, { recursive: true, mode: 0o700 });
// Registry snapshots are produced by WorkspaceRegistry. Never recursively
// create this security boundary from a pathname (an ancestor could be swapped).
if (!existsSync(input.runtimeSnapshotRoot)) throw new Error("runtime snapshot root is unavailable");
this.assertDirectory(input.runtimeSnapshotRoot, "runtime snapshot root");
this.env = { ...(input.env ?? process.env) };
this.secretRoots = [...(input.secretRoots ?? [])];
@@ -149,7 +152,7 @@ export class WorkspaceRuntimeConfigLeaseFactory {
const renderedDigest = digest(rendered);
const base = {
workspace_id: snapshot.workspaceId, workspace_revision: snapshot.workspaceRevision,
descriptor_git_blob: snapshot.descriptorBlob ?? "unknown",
descriptor_git_blob: snapshot.descriptorBlob!,
descriptor_sha256: snapshot.digest,
config_sha256: renderedDigest,
config_dwh_binding: this.computeBinding(rendered),
@@ -158,9 +161,8 @@ export class WorkspaceRuntimeConfigLeaseFactory {
const identity: PublishedIdentity = {
path: result.path, manifestPath: result.manifestPath, workspaceId: snapshot.workspaceId,
workspaceRevision: snapshot.workspaceRevision, digest: renderedDigest, content: rendered,
manifest: result.manifest, refs: 1,
manifest: result.manifest, manifestSha256: result.manifest_sha256,
};
this.published.set(identity.path, identity);
return this.lease(identity);
}
@@ -194,14 +196,14 @@ export class WorkspaceRuntimeConfigLeaseFactory {
return { workspace_id: "unknown", config_fingerprint: `sha256:${digest(content)}`, input_fingerprint: `sha256:${digest(content)}` };
}
private publishSecure(workspaceId: string, revision: string, content: string, manifestBase: Record<string, unknown>): {path:string; manifestPath:string; manifest:string} {
private publishSecure(workspaceId: string, revision: string, content: string, manifestBase: Record<string, unknown>): {path:string; manifestPath:string; manifest:string; manifest_sha256:string} {
return this.helper("publish", { data_root: this.input.dataRoot, workspace_id: workspaceId,
workspace_revision: revision, config_hex: Buffer.from(content).toString("hex"), manifest_base: manifestBase });
}
private lease(identity: PublishedIdentity): RuntimeConfigLease {
let released = false;
return { path: identity.path, manifestPath: identity.manifestPath, workspaceId: identity.workspaceId, workspaceRevision: identity.workspaceRevision,
return { path: identity.path, manifestPath: identity.manifestPath, manifestSha256: identity.manifestSha256, workspaceId: identity.workspaceId, workspaceRevision: identity.workspaceRevision,
release: () => { if (released) return; released = true; /* Durable revision-owned state: release only drops our local handle/ref. */ }, };
}
@@ -217,31 +219,30 @@ export class WorkspaceRuntimeConfigLeaseFactory {
private readSnapshot(path: string): SnapshotIdentity {
if (!isAbsolute(path)) throw new Error("workspace snapshot path must be absolute");
const rel = relative(this.input.runtimeSnapshotRoot, path);
const root = resolve(this.input.runtimeSnapshotRoot);
const rel = relative(root, path);
const match = /^([0-9a-f]{40})\/([a-z][a-z0-9-]{2,62})\.yaml$/.exec(rel);
if (!match || rel.startsWith("..") || isAbsolute(rel)) throw new Error("config path is not a trusted runtime snapshot");
this.assertDirectory(join(this.input.runtimeSnapshotRoot, match[1]), "workspace snapshot parent");
const fd = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
try {
const before = fstatSync(fd);
if (!before.isFile() || before.nlink !== 1 || (before.mode & 0o077) !== 0) throw new Error("workspace snapshot is not a trusted file");
const source = readFileSync(fd, "utf8");
const after = fstatSync(fd);
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size) throw new Error("workspace snapshot changed while reading");
const workspace = validateOperationalWorkspace(parseWorkspaceYaml(source));
if (workspace.workspace.id !== match[2]) throw new Error("workspace snapshot identity does not match its path");
let descriptorBlob: string | undefined;
const repositoryRoot = join(dirname(this.input.runtimeSnapshotRoot), "repo");
const verified = this.helper("verified-snapshot", { snapshots_root: this.input.runtimeSnapshotRoot, ...(existsSync(repositoryRoot) ? { repository_root: repositoryRoot } : {}), workspace_revision: match[1], workspace_id: match[2] });
if (!verified || verified.sha256 !== digest(source) || verified.source !== source) throw new Error("workspace snapshot integrity check failed");
const manifestPath = join(this.input.runtimeSnapshotRoot, match[1], "snapshot.json");
if (!existsSync(manifestPath)) throw new Error("workspace snapshot integrity check failed");
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as any;
const record = Array.isArray(manifest.revisions) ? manifest.revisions.find((r: any) => r?.id === match[2]) : undefined;
const expectedFile = `${match[2]}.yaml`;
if (manifest.head !== match[1] || !record || record.commit !== match[1] || record.snapshotPath !== path || typeof record.blob !== "string" || manifest.files?.[expectedFile] !== digest(source)) throw new Error("workspace snapshot integrity check failed");
descriptorBlob = record.blob;
return { workspace, workspaceId: match[2], workspaceRevision: match[1], revisionContentRoot: dirname(path), digest: digest(source), descriptorBlob };
} finally { closeSync(fd); }
// The helper is the canonical registry capability boundary. It opens the exact
// production snapshot.json and descriptor component-by-component, and MUST prove
// the Git commit/blob identity; a pathname-shaped file is never sufficient.
const repositoryRoot = join(dirname(root), "repo");
const verified = this.helper("verified-snapshot", {
snapshots_root: root, repository_root: repositoryRoot,
workspace_revision: match[1], workspace_id: match[2],
});
if (!verified || typeof verified.source !== "string"
|| verified.sha256 !== digest(verified.source) || verified.snapshot_path !== path) {
throw new Error("workspace snapshot integrity check failed");
}
const workspace = validateOperationalWorkspace(parseWorkspaceYaml(verified.source));
if (workspace.workspace.id !== match[2] || typeof verified.descriptor_git_blob !== "string") {
throw new Error("workspace snapshot integrity check failed");
}
return {
workspace, workspaceId: match[2], workspaceRevision: match[1],
revisionContentRoot: join(root, match[1]), digest: verified.sha256,
descriptorBlob: verified.descriptor_git_blob,
};
}
}