feat: pin sessions to workspace revisions
This commit is contained in:
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user