feat: pin sessions to workspace revisions

This commit is contained in:
2026-08-04 04:52:06 +02:00
parent 2573d87a0e
commit 90894176b6
13 changed files with 313 additions and 80 deletions
+25 -4
View File
@@ -4,7 +4,7 @@ import {
closeSync, constants as fsConstants, existsSync, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync,
openSync, readFileSync, readSync, realpathSync, statSync, unlinkSync, writeFileSync,
} from "node:fs";
import { isAbsolute, join } from "node:path";
import { dirname, isAbsolute, join, relative } from "node:path";
import { clearPrincipalEnvironment, principalEnvironment, type PrincipalContext } from "../auth/principal.js";
import { secretValue, type SecretBundleConfig } from "../config/secret-bundle.js";
@@ -68,7 +68,8 @@ export class ThtRunner {
private configArg(workspaceConfigPath?: string): string[] {
if (workspaceConfigPath) {
if (isAbsolute(workspaceConfigPath)) {
this.assertTrustedRuntimeSnapshot(workspaceConfigPath);
if (this.runtimeSnapshots.has(workspaceConfigPath)) this.assertTrustedRuntimeSnapshot(workspaceConfigPath);
else this.assertWorkspaceSnapshot(workspaceConfigPath);
return ["-c", workspaceConfigPath];
}
if (workspaceConfigPath.includes("/")) {
@@ -83,6 +84,20 @@ export class ThtRunner {
return ["-c", this.cfg.configPath];
}
private assertWorkspaceSnapshot(path: string): void {
if (!this.cfg.runtimeSnapshotRoot) throw new Error("workspace snapshot root is not configured");
const snapshotsRoot = dirname(this.cfg.runtimeSnapshotRoot);
const pathRelative = relative(snapshotsRoot, path);
if (
pathRelative.startsWith("..") || isAbsolute(pathRelative)
|| !/^[0-9a-f]{40}\/[a-z][a-z0-9-]{2,62}\.yaml$/.test(pathRelative)
) throw new Error("config path is not a trusted runtime snapshot");
const entry = lstatSync(path);
if (!entry.isFile() || entry.isSymbolicLink()) {
throw new Error("config path is not a trusted runtime snapshot");
}
}
private runtimeSnapshotDirectory(): string {
if (!this.cfg.runtimeSnapshotRoot) throw new Error("runtime snapshot root is not configured");
if (!isAbsolute(this.cfg.runtimeSnapshotRoot)) throw new Error("runtime snapshot root must be absolute");
@@ -230,7 +245,7 @@ export class ThtRunner {
let snapshotFd: number | undefined;
let ch;
try {
snapshotFd = workspaceConfigPath && isAbsolute(workspaceConfigPath)
snapshotFd = workspaceConfigPath && this.runtimeSnapshots.has(workspaceConfigPath)
? this.openTrustedRuntimeSnapshot(workspaceConfigPath)
: undefined;
ch = spawn(
@@ -287,7 +302,11 @@ export class ThtRunner {
model?: string;
thinking?: string;
name?: string;
/** Legacy named-workspace compatibility; pinned sessions use workspaceConfigPath. */
workspace?: string;
workspaceConfigPath?: string;
workspaceId?: string;
workspaceRevision?: string;
}) {
const a = ["session", "new", o.question];
for (const [f, v] of [
@@ -295,11 +314,13 @@ export class ThtRunner {
["--model", o.model],
["--thinking", o.thinking],
["--name", o.name],
["--workspace-id", o.workspaceId],
["--workspace-revision", o.workspaceRevision],
] as const) {
if (v) a.push(f, v);
}
a.push("--json");
return this.json<{ id: string }>(a, o.workspace);
return this.json<{ id: string }>(a, o.workspaceConfigPath ?? o.workspace);
}
/** Build and persist the deterministic F1 retrieval pack for a new session. */