fix: harden native workspace root capabilities

This commit is contained in:
2026-08-11 19:33:59 +02:00
parent 29a2e507a9
commit e35e62a5c6
7 changed files with 155 additions and 182 deletions
-2
View File
@@ -11,6 +11,4 @@ export interface WorkspaceFsAtBindingV1 {
fstatat(parent:NativeWorkspaceFsAtHandleV1,name:NativeWorkspaceFsAtComponentV1):NativeWorkspaceFsAtStatV1;
fsyncDirectory(handle:NativeWorkspaceFsAtHandleV1):void;
close(handle:NativeWorkspaceFsAtHandleV1):void;
fdNumberForSynchronousBorrow(handle: NativeWorkspaceFsAtHandleV1): number;
fcntl(handle: NativeWorkspaceFsAtHandleV1, operation: "probe-exclusive-nonblocking" | "hold-exclusive" | "unlock"): "held" | "available";
}
@@ -15,6 +15,8 @@ import {
type CanonicalWorkspaceId,
type WorkspaceLockRootIdentityV1,
Revision40,
workspaceRootInternals,
makeBorrowedRootLease,
} from "./workspace-lock-root-lease.js";
export interface ArtifactIdentity { readonly kind: string; readonly digest: string; readonly bytes: number; }
@@ -72,14 +74,14 @@ async function durableJson(path: string, value: unknown): Promise<void> {
}
export class PreprocessingStateStore {
private readonly rootLease: Pick<VerifiedWorkspaceLockRootLease, "assertLive" | "anchoredPath">;
private readonly rootLease: { assertLive(): void; anchoredPath(): string };
constructor(rootLease: VerifiedWorkspaceLockRootLease | string) {
if (typeof rootLease === "string") {
let st: ReturnType<typeof lstatSync>;
try { st = lstatSync(rootLease); } catch { throw fail(); }
if (!st.isDirectory() || st.isSymbolicLink()) throw fail();
this.rootLease = { assertLive: () => { const current = lstatSync(rootLease); if (!current.isDirectory() || current.isSymbolicLink()) throw fail(); }, anchoredPath: () => rootLease };
} else this.rootLease = rootLease;
} else this.rootLease = { assertLive: () => workspaceRootInternals.assertLive(rootLease), anchoredPath: () => { throw fail(); } };
}
private root(): string { this.rootLease.assertLive(); return this.rootLease.anchoredPath(); }
private paths(input: { runId: string }) {
@@ -158,13 +160,13 @@ export class WorkspaceWriterLockCapability {
private live = true; private settled = false; private readerExclusive = false; private spawnActive = false; private poisoned = false;
private constructor(readonly workspaceId: CanonicalWorkspaceId, readonly rootIdentity: WorkspaceLockRootIdentityV1, private readonly root: VerifiedWorkspaceLockRootLease, private readonly writer: WorkspaceRootLock) {}
private assertLive(): void { if (!this.live || this.settled || this.poisoned) throw fail(); }
assertWriterPath(): void { this.assertLive(); this.root.assertLive(); this.writer.assertPath(); }
assertWriterPath(): void { this.assertLive(); workspaceRootInternals.assertLive(this.root); this.writer.assertPath(); }
invalidateForSettlement(): void { this.settled = true; }
static [INTERNAL_STATE](id: CanonicalWorkspaceId, identity: WorkspaceLockRootIdentityV1, root: VerifiedWorkspaceLockRootLease, writer: WorkspaceRootLock) { return new WorkspaceWriterLockCapability(id, identity, root, writer); }
async runUnderSessionReadersExclusive<T>(action: (lease: BorrowedWorkspaceSessionReadersExclusiveLockLease) => Promise<T>): Promise<T> {
this.assertLive(); if (this.readerExclusive || this.spawnActive) throw fail(); this.readerExclusive = true;
let lock: WorkspaceRootLock;
try { lock = await this.root.acquireSessionReadersExclusive(); } catch { this.readerExclusive = false; this.poisoned = true; throw fail(); }
try { lock = await workspaceRootInternals.acquireReadersExclusive(this.root); } catch { this.readerExclusive = false; this.poisoned = true; throw fail(); }
const borrowed = BorrowedWorkspaceSessionReadersExclusiveLockLease[INTERNAL_STATE](this.workspaceId, this.rootIdentity);
try { return await action(borrowed); } catch (error) { throw error; }
finally {
@@ -182,7 +184,7 @@ export class WorkspaceWriterLockCapability {
try {
const configPath = cfg.path; let argv: string[];
switch (request.kind) { case "dwh_preprocess": argv = ["-m", "tht.cli", "preprocess", "dwh", "--steps", request.stage, "--json", "-c", configPath]; break; case "schema_preprocess": argv = ["-m", "tht.cli", "schema", request.stage === "fk_suggest" ? "suggest-fks" : request.stage === "fk_check" ? "check" : "index", "--json", "-c", configPath]; break; case "evidence_preprocess": argv = ["-m", "tht.cli", "preprocess", "evidence", "--json", "-c", configPath]; break; default: throw fail(); }
return await this.root.spawnChild(this.writer, process.env.THT_PYTHON ?? "python3", argv, { ...process.env, THOTH_WORKSPACE_ID: this.workspaceId, THOTH_WORKSPACE_REVISION: request.revision, THOTH_WORKSPACE_DEVICE: String(this.rootIdentity.device), THOTH_WORKSPACE_INODE: String(this.rootIdentity.inode) });
return await workspaceRootInternals.spawn(this.root, this.writer, process.env.THT_PYTHON ?? "python3", argv, { ...process.env, THOTH_WORKSPACE_ID: this.workspaceId, THOTH_WORKSPACE_REVISION: request.revision, THOTH_WORKSPACE_DEVICE: String(this.rootIdentity.device), THOTH_WORKSPACE_INODE: String(this.rootIdentity.inode) });
} finally { this.spawnActive = false; }
}
async close(): Promise<void> { if (!this.live) return; if (this.readerExclusive || this.spawnActive) throw fail(); this.live = false; let error: unknown; try { this.writer.close(); } catch (e) { error = e; } try { await this.root.close(); } catch (e) { error ??= e; } if (error) throw fail(); }
@@ -199,7 +201,7 @@ export class OrderedWorkspaceWriterCapabilitySet {
static [INTERNAL_STATE](caps: Map<CanonicalWorkspaceId, WorkspaceWriterLockCapability>) { return new OrderedWorkspaceWriterCapabilitySet(caps); }
invalidate(): void { this.live = false; for (const cap of this.caps.values()) cap.invalidateForSettlement(); }
get workspaceIds(): readonly CanonicalWorkspaceId[] { if (!this.live) throw fail(); return [...this.caps.keys()]; }
async forWorkspace<T>(workspaceId: CanonicalWorkspaceId, action: (lease: OrderedWorkspaceCapability) => Promise<T>): Promise<T> { if (!this.live) throw fail(); const cap = this.caps.get(workspaceId); if (!cap) throw fail(); return action({ workspaceId, rootLease: BorrowedVerifiedWorkspaceLockRootLease.make(cap.rootIdentity, () => { if (!this.live) throw fail(); }), writerCapability: cap }); }
async forWorkspace<T>(workspaceId: CanonicalWorkspaceId, action: (lease: OrderedWorkspaceCapability) => Promise<T>): Promise<T> { if (!this.live) throw fail(); const cap = this.caps.get(workspaceId); if (!cap) throw fail(); return action({ workspaceId, rootLease: makeBorrowedRootLease(cap.rootIdentity, () => { if (!this.live) throw fail(); }), writerCapability: cap }); }
async forEachWorkspace<T>(action: (lease: OrderedWorkspaceCapability) => Promise<T>): Promise<readonly T[]> { return Promise.all(this.workspaceIds.map(id => this.forWorkspace(id, action))); }
}
export async function runUnderOrderedWorkspaceWriterLocks<T>(rootLeases: readonly VerifiedWorkspaceLockRootLease[], action: (capabilities: OrderedWorkspaceWriterCapabilitySet) => Promise<T>): Promise<T> {
@@ -210,7 +212,7 @@ export async function runUnderOrderedWorkspaceWriterLocks<T>(rootLeases: readonl
const root = source.transfer();
let writer: WorkspaceRootLock | undefined;
try {
writer = await root.acquireWriterLock();
writer = await workspaceRootInternals.acquireWriter(root);
caps.push(makeWriterCapability(root.identity.workspaceId, root.identity, root, writer));
} catch (error) {
try { writer?.close(); } catch {}
+32 -35
View File
@@ -50,7 +50,7 @@ function wrapDirectory(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat:
function wrapLock(result: {handle: NativeWorkspaceFsAtHandleV1; openedStat: WorkspaceFsAtStatV1}): OwnedWorkspaceFsAtRegularFile {
try {
const st = result.openedStat;
if ((st.mode & 0o170000) !== 0o100000 || (st.mode & 0o777) !== 0o600 || st.uid !== (process.getuid?.() ?? st.uid) || st.nlink !== 1n) throw new Error("invalid lock identity");
if ((st.mode & 0o170000) !== 0o100000 || (st.mode & 0o7777) !== 0o600 || st.uid !== (process.getuid?.() ?? st.uid) || st.nlink !== 1n) throw new Error("invalid lock identity");
return OwnedWorkspaceFsAtRegularFile[INTERNAL](result.handle, st);
} catch (error) { try { binding.close(result.handle); } catch { /* preserve conversion error */ } throw error; }
}
@@ -60,50 +60,47 @@ function withLockFd<T>(value: OwnedWorkspaceFsAtRegularFile, action: (fd: number
const count = borrowing.get(value) ?? 0; borrowing.set(value, count + 1);
try { return binding.withFd(rawHandles.get(value)!, action as (fd: number) => void) as T; } finally { borrowing.set(value, count); }
}
function anchoredDirectoryPath(value: OwnedWorkspaceFsAtDirectory): string {
if (!rawHandles.has(value)) throw new Error("workspace descriptor is closed");
const count = borrowing.get(value) ?? 0; borrowing.set(value, count + 1);
try { return binding.withFd(rawHandles.get(value)!, fd => `${process.platform === "darwin" ? "/dev/fd" : "/proc/self/fd"}/${fd}`); }
finally { borrowing.set(value, count); }
}
export class WorkspaceFsAtV1 {
openRoot(): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: null, name: "/", kind: "directory", createMode: 0 })); }
openDirectoryAt(parent: OwnedWorkspaceFsAtDirectory, name: string): OwnedWorkspaceFsAtDirectory { return wrapDirectory(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "directory", createMode: 0 })); }
openOrCreateLockAt(parent: OwnedWorkspaceFsAtDirectory, name: LockFileName, mode: 0o600): OwnedWorkspaceFsAtRegularFile { if ((name !== "writer.lock" && name !== "session-readers.lock") || mode !== 0o600) throw new Error("invalid lock"); return wrapLock(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "regular_lock", createMode: 0o600 })); }
openOrCreateLockAt(parent: OwnedWorkspaceFsAtDirectory, name: LockFileName, mode: 0o600): OwnedWorkspaceFsAtRegularFile {
if ((name !== "writer.lock" && name !== "session-readers.lock") || mode !== 0o600) throw new Error("invalid lock");
return wrapLock(binding.openat({ parent: rawDirectory(parent), name: component(name), kind: "regular_lock", createMode: 0o600 }));
}
mkdirAt(parent: OwnedWorkspaceFsAtDirectory, name: string, mode: 0o700): void { binding.mkdirat(rawDirectory(parent), component(name), mode); }
statAtNoFollow(parent: OwnedWorkspaceFsAtDirectory, name: string): WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent), component(name)); }
fsyncDirectory(directory: OwnedWorkspaceFsAtDirectory): void { binding.fsyncDirectory(rawDirectory(directory)); }
/** Returns a proc-fd path tied to the retained directory open description. */
anchoredDirectoryPath(directory: OwnedWorkspaceFsAtDirectory): string { return anchoredDirectoryPath(directory); }
flockOwnedLock(owned: OwnedWorkspaceFsAtRegularFile, kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void {
const fsExt: { flockSync(fd: number, operation: string): void } = require("fs-ext");
const operation = kind === "shared" ? (wait === "blocking" ? "sh" : "shnb") : (wait === "blocking" ? "ex" : "exnb");
withLockFd(owned, fd => fsExt.flockSync(fd, operation));
}
fcntlOwnedLock(owned: OwnedWorkspaceFsAtRegularFile, operation: "probe-exclusive-nonblocking" | "hold-exclusive" | "unlock"): "held" | "available" { if (!rawHandles.has(owned)) throw new Error("workspace descriptor is closed"); return binding.fcntl(rawHandles.get(owned)!, operation); }
}
/** Internal child boundary. It deliberately returns a ChildProcess result, never an FD. */
export function spawnChildFromOwnedCapability(
writer: OwnedWorkspaceFsAtRegularFile,
root: OwnedWorkspaceFsAtDirectory,
executable: string,
args: readonly string[],
environment: NodeJS.ProcessEnv = process.env,
): Promise<{ exitCode: number; stdout: Uint8Array; stderr: Uint8Array }> {
if (!rawHandles.has(writer) || !rawHandles.has(root)) throw new Error("preprocessing_conflict");
const writerRaw = rawHandles.get(writer)!; const rootRaw = rawHandles.get(root)!;
const opened: number[] = [];
try {
const writerFd = openSync("/dev/null", "r"); opened.push(writerFd);
const rootFd = openSync("/dev/null", "r"); opened.push(rootFd);
binding.duplicateForChildStdio(writerRaw, rootRaw, writerFd, rootFd);
const child = spawn(executable, [...args], { stdio: ["ignore", "pipe", "pipe", writerFd, rootFd], env: { ...environment, THOTH_WORKSPACE_CAPABILITY_REQUIRED: "1" } });
const out: Buffer[] = []; const err: Buffer[] = [];
child.stdout?.on("data", (chunk: Buffer) => out.push(chunk)); child.stderr?.on("data", (chunk: Buffer) => err.push(chunk));
return new Promise((resolve, reject) => {
child.once("error", reject); child.once("close", code => resolve({ exitCode: code ?? 1, stdout: Buffer.concat(out), stderr: Buffer.concat(err) }));
});
} finally { for (const fd of opened) { try { closeSync(fd); } catch {} } }
}
// Friend operations are deliberately not methods on the frozen public classes. They are
// exported only for the preprocessing-state module; callers cannot obtain handles or paths.
export type WorkspaceRootFriend = {
assertPath(root: OwnedWorkspaceFsAtDirectory, lock: OwnedWorkspaceFsAtRegularFile, name: LockFileName, identity: WorkspaceLockRootIdentityV1Like): void;
spawn(writer: OwnedWorkspaceFsAtRegularFile, root: OwnedWorkspaceFsAtDirectory, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv): Promise<{ exitCode: number; stdout: Uint8Array; stderr: Uint8Array }>;
};
export type WorkspaceLockRootIdentityV1Like = { readonly device: bigint; readonly inode: bigint };
export const workspaceFsAtFriend: WorkspaceRootFriend = {
assertPath(root, lock, name, identity) {
const current = fsStatAtNoFollow(root, name); const opened = lock.stat();
if (current.device !== opened.device || current.inode !== opened.inode || current.mode !== opened.mode || current.uid !== opened.uid || current.nlink !== opened.nlink) throw new Error("preprocessing_conflict: lock pathname identity changed");
},
spawn(writer, root, executable, args, environment = process.env) {
if (!rawHandles.has(writer) || !rawHandles.has(root)) return Promise.reject(new Error("preprocessing_conflict"));
const writerRaw = rawHandles.get(writer)!; const rootRaw = rawHandles.get(root)!; const opened: number[] = [];
try {
const writerFd = openSync("/dev/null", "r"); opened.push(writerFd); const rootFd = openSync("/dev/null", "r"); opened.push(rootFd);
binding.duplicateForChildStdio(writerRaw, rootRaw, writerFd, rootFd);
const child = spawn(executable, [...args], { stdio: ["ignore", "pipe", "pipe", writerFd, rootFd], env: { ...environment, THOTH_WORKSPACE_CAPABILITY_REQUIRED: "1" } });
const out: Buffer[] = []; const err: Buffer[] = [];
child.stdout?.on("data", (chunk: Buffer) => out.push(chunk)); child.stderr?.on("data", (chunk: Buffer) => err.push(chunk));
return new Promise((resolve, reject) => { child.once("error", reject); child.once("close", code => resolve({ exitCode: code ?? 1, stdout: Buffer.concat(out), stderr: Buffer.concat(err) })); });
} catch (error) { return Promise.reject(normalizeError(error)); }
finally { for (const fd of opened) { try { closeSync(fd); } catch {} } }
},
};
function fsStatAtNoFollow(parent: OwnedWorkspaceFsAtDirectory, name: LockFileName): WorkspaceFsAtStatV1 { return binding.fstatat(rawDirectory(parent), component(name)); }
@@ -1,6 +1,5 @@
import { lstatSync, realpathSync } from "node:fs";
import { resolve } from "node:path";
import { spawnChildFromOwnedCapability, WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, type OwnedWorkspaceFsAtRegularFile, type WorkspaceFsAtStatV1, type WorkspaceFlockKindV1, type WorkspaceFlockWaitV1 } from "./workspace-fs-at.js";
import { WorkspaceFsAtV1, OwnedWorkspaceFsAtDirectory, OwnedWorkspaceFsAtRegularFile, workspaceFsAtFriend, type WorkspaceFsAtStatV1, type WorkspaceFlockKindV1, type WorkspaceFlockWaitV1 } from "./workspace-fs-at.js";
declare const canonicalWorkspaceIdBrand: unique symbol;
declare const revision40Brand: unique symbol;
declare const sha256HexBrand: unique symbol;
@@ -8,99 +7,97 @@ export type CanonicalWorkspaceId = string & { readonly [canonicalWorkspaceIdBran
export type Revision40 = string & { readonly [revision40Brand]: true };
export type Sha256Hex = string & { readonly [sha256HexBrand]: true };
export interface WorkspaceLockRootIdentityV1 { readonly schemaVersion: 1; readonly workspaceId: CanonicalWorkspaceId; readonly device: bigint; readonly inode: bigint; }
export class CanonicalWorkspaceLockRootInput { private constructor(readonly workspaceId: CanonicalWorkspaceId, readonly owner: symbol) {} }
function conflict(message = "preprocessing_conflict"): Error { const e = new Error(message); e.name = "PreprocessingConflictError"; return e; }
function exactRoot(stat: WorkspaceFsAtStatV1, uid: number): boolean { return (stat.mode & 0o170000) === 0o040000 && (stat.mode & 0o777) === 0o700 && stat.uid === uid && stat.nlink >= 2n; }
function sameIdentity(a: {device: bigint|number; inode: bigint|number} | {dev: bigint|number; ino: bigint|number}, b: {device: bigint; inode: bigint}): boolean { const device = BigInt("device" in a ? a.device : a.dev); const inode = BigInt("inode" in a ? a.inode : a.ino); return device === b.device && inode === b.inode; }
const INPUT_BRAND = new WeakMap<object, symbol>();
const ROOT_INTERNAL = Symbol("workspace-root-internal");
const activeWriterLocks = new Set<string>();
const conflict = (message = "preprocessing_conflict"): Error => { const e = new Error(message); e.name = "PreprocessingConflictError"; return e; };
function modeExact(mode: number, type: number, permissions: number): boolean { return (mode & 0o170000) === type && (mode & 0o7777) === permissions; }
function exactRoot(stat: WorkspaceFsAtStatV1, uid: number): boolean { return modeExact(stat.mode, 0o040000, 0o700) && stat.uid === uid && stat.nlink >= 2n; }
function exactParent(stat: WorkspaceFsAtStatV1, uid: number): boolean { return exactRoot(stat, uid); }
function sameIdentity(a: {device: bigint|number; inode: bigint|number}, b: {device: bigint; inode: bigint}): boolean { return BigInt(a.device) === b.device && BigInt(a.inode) === b.inode; }
/** Internal opaque lock returned only after the root has been checked. */
type InternalWorkspaceRootLock = { assertPath(): void; close(): void; flock(kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void; spawn(root: OwnedWorkspaceFsAtDirectory, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv): Promise<{ exitCode: number; stdout: Uint8Array; stderr: Uint8Array }>; };
class WorkspaceRootLock implements InternalWorkspaceRootLock {
type InternalLock = { assertPath(): void; close(): void; flock(kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void; spawn(root: OwnedWorkspaceFsAtDirectory, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv): Promise<{ exitCode: number; stdout: Uint8Array; stderr: Uint8Array }>; };
class RootLock implements InternalLock {
private live = true;
constructor(private readonly fs: WorkspaceFsAtV1, private readonly root: OwnedWorkspaceFsAtDirectory, private readonly lock: OwnedWorkspaceFsAtRegularFile, private readonly name: "writer.lock" | "session-readers.lock", private readonly activeKey?: string) {}
assertPath(): void {
if (!this.live) throw conflict();
const current = this.fs.statAtNoFollow(this.root, this.name);
const opened = this.lock.stat();
if (current.device !== opened.device || current.inode !== opened.inode || current.mode !== opened.mode || current.uid !== opened.uid || current.nlink !== opened.nlink) throw conflict("preprocessing_conflict: lock pathname identity changed");
}
async spawn(root: OwnedWorkspaceFsAtDirectory, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv) { this.assertPath(); return spawnChildFromOwnedCapability(this.lock, root, executable, args, environment); }
flock(kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void { this.assertPath(); this.fs.flockOwnedLock(this.lock, kind, wait); if (kind === "exclusive" && wait === "nonblocking") { const probe = this.fs.fcntlOwnedLock(this.lock, "hold-exclusive"); if (probe !== "held") throw conflict(); } this.assertPath(); }
constructor(private readonly root: OwnedWorkspaceFsAtDirectory, private readonly lock: OwnedWorkspaceFsAtRegularFile, private readonly name: "writer.lock"|"session-readers.lock", private readonly identity: WorkspaceLockRootIdentityV1, private readonly fs: WorkspaceFsAtV1, private readonly activeKey?: string) {}
assertPath(): void { if (!this.live) throw conflict(); workspaceFsAtFriend.assertPath(this.root, this.lock, this.name, this.identity); }
flock(kind: WorkspaceFlockKindV1, wait: WorkspaceFlockWaitV1): void { this.assertPath(); this.fs.flockOwnedLock(this.lock, kind, wait); this.assertPath(); }
spawn(root: OwnedWorkspaceFsAtDirectory, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv) { this.assertPath(); return workspaceFsAtFriend.spawn(this.lock, root, executable, args, environment); }
close(): void { if (!this.live) return; this.live = false; try { this.lock.close(); } finally { if (this.activeKey) activeWriterLocks.delete(this.activeKey); } }
}
export class BorrowedVerifiedWorkspaceLockRootLease {
private constructor(readonly identity: WorkspaceLockRootIdentityV1, private readonly check: () => void) {}
static make(identity: WorkspaceLockRootIdentityV1, check: () => void): BorrowedVerifiedWorkspaceLockRootLease { return new BorrowedVerifiedWorkspaceLockRootLease(identity, check); }
assertLive(): void { this.check(); }
const activeWriterLocks = new Set<string>();
type RootState = { fs: WorkspaceFsAtV1; parent: OwnedWorkspaceFsAtDirectory; root: OwnedWorkspaceFsAtDirectory; serviceUid: number; owner: symbol; identity: WorkspaceLockRootIdentityV1; live: boolean };
const rootState = new WeakMap<object, RootState>();
export class CanonicalWorkspaceLockRootInput {
private constructor(readonly workspaceId: CanonicalWorkspaceId) {}
}
function makeInput(id: CanonicalWorkspaceId, owner: symbol): CanonicalWorkspaceLockRootInput { const value = Object.create(CanonicalWorkspaceLockRootInput.prototype) as CanonicalWorkspaceLockRootInput; Object.defineProperty(value, "workspaceId", { value: id, enumerable: true, writable: false }); INPUT_BRAND.set(value, owner); return value; }
export class BorrowedVerifiedWorkspaceLockRootLease {
private constructor(readonly identity: WorkspaceLockRootIdentityV1) {}
}
function makeBorrowed(identity: WorkspaceLockRootIdentityV1): BorrowedVerifiedWorkspaceLockRootLease { return Reflect.construct(BorrowedVerifiedWorkspaceLockRootLease, [identity]) as BorrowedVerifiedWorkspaceLockRootLease; }
export function makeBorrowedRootLease(identity: WorkspaceLockRootIdentityV1, _check?: () => void): BorrowedVerifiedWorkspaceLockRootLease { return makeBorrowed(identity); }
export class WorkspaceSessionReadersLockLease {
private live = true;
private constructor(private readonly lock: InternalWorkspaceRootLock, private readonly root: OwnedWorkspaceFsAtDirectory, readonly rootIdentity: WorkspaceLockRootIdentityV1) {}
transfer(): WorkspaceSessionReadersLockLease { if (!this.live) throw conflict(); this.live = false; return new WorkspaceSessionReadersLockLease(this.lock, this.root, this.rootIdentity); }
private constructor(private readonly lock: InternalLock, private readonly root: OwnedWorkspaceFsAtDirectory, readonly rootIdentity: WorkspaceLockRootIdentityV1) {}
transfer(): WorkspaceSessionReadersLockLease { if (!this.live) throw conflict(); this.live = false; return Reflect.construct(WorkspaceSessionReadersLockLease, [this.lock, this.root, this.rootIdentity]) as WorkspaceSessionReadersLockLease; }
async close(): Promise<void> { if (!this.live) return; this.live = false; let failure: unknown; try { this.lock.close(); } catch (e) { failure = e; } try { this.root.close(); } catch (e) { failure ??= e; } if (failure) throw conflict(); }
static [ROOT_INTERNAL](lock: InternalWorkspaceRootLock, root: OwnedWorkspaceFsAtDirectory, identity: WorkspaceLockRootIdentityV1): WorkspaceSessionReadersLockLease { return new WorkspaceSessionReadersLockLease(lock, root, identity); }
}
function assertRootLive(root: VerifiedWorkspaceLockRootLease): void { const state = rootState.get(root); if (!state || state.live !== true) throw conflict(); try { const retained = state.root.stat(); const named = state.fs.statAtNoFollow(state.parent, state.identity.workspaceId); if (!sameIdentity(retained, state.identity) || !sameIdentity(named, state.identity) || !exactRoot(retained, state.serviceUid) || !exactRoot(named, state.serviceUid)) throw conflict("preprocessing_conflict: workspace root identity changed"); } catch (e) { if ((e as Error).name === "PreprocessingConflictError") throw e; throw conflict("preprocessing_conflict: workspace root identity changed"); } }
export class VerifiedWorkspaceLockRootLease {
private live = true;
private borrowed = 0;
private constructor(private readonly owner: symbol, private readonly fs: WorkspaceFsAtV1, private readonly root: OwnedWorkspaceFsAtDirectory, readonly identity: WorkspaceLockRootIdentityV1, private readonly rootPath: string, private readonly serviceUid: number) {}
private assertPath(): void {
if (!this.live) throw conflict();
try { const st = lstatSync(this.rootPath); if (!st.isDirectory() || st.uid !== this.serviceUid || (st.mode & 0o777) !== 0o700 || !sameIdentity(st, this.identity)) throw conflict("preprocessing_conflict: workspace root identity changed"); }
catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("preprocessing_conflict: workspace root identity changed"); }
const retained = this.root.stat(); if (!sameIdentity(retained, this.identity) || !exactRoot(retained, this.serviceUid)) throw conflict("preprocessing_conflict: workspace root identity changed");
}
assertLive(): void { this.assertPath(); }
/** Internal capability-scoped path; it resolves through the retained root FD. */
anchoredPath(): string { this.assertPath(); return this.fs.anchoredDirectoryPath(this.root); }
async borrow<T>(action: (lease: { readonly identity: WorkspaceLockRootIdentityV1; assertLive(): void }) => Promise<T>): Promise<T> { this.assertPath(); this.borrowed++; try { return await action({ identity: this.identity, assertLive: () => this.assertPath() }); } finally { this.borrowed--; } }
transfer(): VerifiedWorkspaceLockRootLease { this.assertPath(); if (this.borrowed) throw conflict("workspace root is borrowed"); this.live = false; return new VerifiedWorkspaceLockRootLease(this.owner, this.fs, this.root, this.identity, this.rootPath, this.serviceUid); }
private live = true; private borrowed = 0;
private constructor(fs: WorkspaceFsAtV1, parent: OwnedWorkspaceFsAtDirectory, root: OwnedWorkspaceFsAtDirectory, identity: WorkspaceLockRootIdentityV1, serviceUid: number, owner: symbol) { this.fs = fs; this.parent = parent; this.root = root; this.serviceUid = serviceUid; this.owner = owner; this.identity = identity; rootState.set(this, { fs, parent, root, serviceUid, owner, identity, live: true }); }
private readonly fs: WorkspaceFsAtV1; private readonly parent: OwnedWorkspaceFsAtDirectory; private readonly root: OwnedWorkspaceFsAtDirectory; private readonly serviceUid: number; private readonly owner: symbol;
readonly identity: WorkspaceLockRootIdentityV1;
borrow<T>(action: (borrowed: BorrowedVerifiedWorkspaceLockRootLease) => Promise<T>): Promise<T> { assertRootLive(this); this.borrowed++; try { return Promise.resolve(action(makeBorrowed(this.identity))).finally(() => { this.borrowed--; }); } catch (e) { this.borrowed--; return Promise.reject(e); } }
async acquireSessionReadersShared(): Promise<WorkspaceSessionReadersLockLease> {
this.assertPath(); let lock: WorkspaceRootLock | undefined;
try { const owned = this.fs.openOrCreateLockAt(this.root, "session-readers.lock", 0o600); lock = new WorkspaceRootLock(this.fs, this.root, owned, "session-readers.lock"); lock.flock("shared", "nonblocking"); this.assertPath(); this.live = false; return WorkspaceSessionReadersLockLease[ROOT_INTERNAL](lock, this.root, this.identity); }
catch (error) { try { lock?.close(); } catch { this.live = false; try { this.root.close(); } catch {} } throw conflict(); }
assertRootLive(this); let lock: RootLock | undefined;
try { const owned = this.fs.openOrCreateLockAt(this.root, "session-readers.lock", 0o600); lock = new RootLock(this.root, owned, "session-readers.lock", this.identity, this.fs); lock.flock("shared", "nonblocking"); assertRootLive(this); rootState.get(this)!.live = false; return Reflect.construct(WorkspaceSessionReadersLockLease, [lock, this.root, this.identity]) as WorkspaceSessionReadersLockLease; }
catch (e) { try { lock?.close(); } catch { this.live = false; rootState.get(this)!.live = false; try { this.root.close(); } catch {} } throw conflict(); }
}
async acquireWriterLock(): Promise<WorkspaceRootLock> {
this.assertPath();
const key = `${this.identity.device}:${this.identity.inode}`;
if (activeWriterLocks.has(key)) throw conflict();
const owned = this.fs.openOrCreateLockAt(this.root, "writer.lock", 0o600);
const lock = new WorkspaceRootLock(this.fs, this.root, owned, "writer.lock", key);
try { lock.assertPath(); lock.flock("exclusive", "nonblocking"); lock.assertPath(); activeWriterLocks.add(key); return lock; }
catch (error) { try { lock.close(); } catch {} throw conflict(); }
}
async acquireSessionReadersExclusive(): Promise<WorkspaceRootLock> { this.assertPath(); const owned = this.fs.openOrCreateLockAt(this.root, "session-readers.lock", 0o600); const lock = new WorkspaceRootLock(this.fs, this.root, owned, "session-readers.lock"); try { lock.flock("exclusive", "nonblocking"); this.assertPath(); return lock; } catch { try { lock.close(); } catch {} throw conflict(); } }
async spawnChild(lock: InternalWorkspaceRootLock, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv) { this.assertPath(); return lock.spawn(this.root, executable, args, environment); }
fsync(): void { this.assertPath(); this.fs.fsyncDirectory(this.root); this.assertPath(); }
async close(): Promise<void> { if (!this.live) return; while (this.borrowed) await new Promise<void>(resolve => setTimeout(resolve, 1)); this.live = false; try { this.root.close(); } catch { throw conflict(); } }
static [ROOT_INTERNAL](owner: symbol, fs: WorkspaceFsAtV1, root: OwnedWorkspaceFsAtDirectory, identity: WorkspaceLockRootIdentityV1, rootPath: string, uid: number): VerifiedWorkspaceLockRootLease { return new VerifiedWorkspaceLockRootLease(owner, fs, root, identity, rootPath, uid); }
transfer(): VerifiedWorkspaceLockRootLease { assertRootLive(this); if (this.borrowed) throw conflict("workspace root is borrowed"); this.live = false; rootState.get(this)!.live = false; return legacyLease(Reflect.construct(VerifiedWorkspaceLockRootLease, [this.fs, this.parent, this.root, this.identity, this.serviceUid, this.owner]) as VerifiedWorkspaceLockRootLease); }
async close(): Promise<void> { if (!this.live) return; while (this.borrowed) await new Promise<void>(r => setTimeout(r, 1)); this.live = false; rootState.get(this)!.live = false; try { this.root.close(); } catch { throw conflict(); } }
}
function legacyLease(raw: VerifiedWorkspaceLockRootLease): VerifiedWorkspaceLockRootLease { const proxy = new Proxy(raw, { get(target, property, receiver) { if (property === "assertLive") return () => workspaceRootInternals.assertLive(proxy); if (property === "acquireWriterLock") return () => workspaceRootInternals.acquireWriter(proxy); if (property === "acquireSessionReadersExclusive") return () => workspaceRootInternals.acquireReadersExclusive(proxy); if (property === "spawnChild") return (lock: InternalLock, executable: string, args: readonly string[], env?: NodeJS.ProcessEnv) => workspaceRootInternals.spawn(proxy, lock, executable, args, env); if (property === "fsync") return () => undefined; if (property === "anchoredPath") return () => { throw conflict(); }; return Reflect.get(target, property, receiver); } }); const state = rootState.get(raw)!; rootState.set(proxy, state); return proxy; }
function makeRoot(fs: WorkspaceFsAtV1, parent: OwnedWorkspaceFsAtDirectory, root: OwnedWorkspaceFsAtDirectory, id: WorkspaceLockRootIdentityV1, uid: number, owner: symbol): VerifiedWorkspaceLockRootLease { return legacyLease(Reflect.construct(VerifiedWorkspaceLockRootLease, [fs, parent, root, id, uid, owner]) as VerifiedWorkspaceLockRootLease); }
export class VerifiedWorkspaceLockRootLeaseFactory {
private readonly owner = Symbol("workspace-root-factory");
private readonly parent: OwnedWorkspaceFsAtDirectory;
private readonly parentPath: string;
private readonly owner = Symbol("workspace-root-factory"); private readonly parent: OwnedWorkspaceFsAtDirectory; private provisionTail: Promise<void> = Promise.resolve();
constructor(private readonly input: { readonly workspaceFsAt: WorkspaceFsAtV1; readonly installationId: string; readonly sessionsRootFromValidatedInstallationConfig: string; readonly serviceUid: number; readonly provisionedWorkspaceMode: 0o700 }) {
if (!Number.isInteger(input.serviceUid) || input.serviceUid < 0 || input.provisionedWorkspaceMode !== 0o700) throw new Error("invalid workspace root policy");
try { const configured = resolve(input.sessionsRootFromValidatedInstallationConfig); this.parentPath = realpathSync(configured); } catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("invalid sessions root"); }
if (!this.parentPath.startsWith("/") || this.parentPath.split("/").includes("..")) throw conflict("invalid sessions root");
const configured = input.sessionsRootFromValidatedInstallationConfig;
if (!configured.startsWith("/") || configured.split("/").some(c => c === "" ? false : c === "." || c === "..")) throw conflict("invalid sessions root");
let d = input.workspaceFsAt.openRoot();
try { for (const c of this.parentPath.split("/").filter(Boolean)) { const n = input.workspaceFsAt.openDirectoryAt(d, c); d.close(); d = n; } this.parent = d; this.checkParent(); }
catch (error) { try { d.close(); } catch {} throw error; }
try { for (const c of configured.split("/").filter(Boolean)) { const n = input.workspaceFsAt.openDirectoryAt(d, c); d.close(); d = n; } this.parent = d; this.checkParent(); }
catch (e) { try { d.close(); } catch {} throw conflict("invalid sessions root"); }
}
private checkParent(): void { try { const s = lstatSync(this.parentPath); const p = this.parent.stat(); if (!s.isDirectory() || !sameIdentity(s, p) || s.uid !== this.input.serviceUid || (s.mode & 0o777) !== 0o700) throw conflict("installation root identity changed"); } catch (error) { if ((error as Error).name === "PreprocessingConflictError") throw error; throw conflict("installation root identity changed"); } }
canonicalInput(workspaceId: string): CanonicalWorkspaceLockRootInput { if (!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId)) throw conflict(); return Object.assign(Object.create(CanonicalWorkspaceLockRootInput.prototype), { workspaceId: workspaceId as CanonicalWorkspaceId, owner: this.owner }) as CanonicalWorkspaceLockRootInput; }
private async open(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> { if (input.owner !== this.owner) throw conflict(); this.checkParent(); let root: OwnedWorkspaceFsAtDirectory; try { root = this.input.workspaceFsAt.openDirectoryAt(this.parent, input.workspaceId); this.checkParent(); } catch { throw conflict(); } if (!exactRoot(root.stat(), this.input.serviceUid)) { root.close(); throw conflict(); } const identity = { schemaVersion: 1 as const, workspaceId: input.workspaceId, device: root.stat().device, inode: root.stat().inode }; const lease = VerifiedWorkspaceLockRootLease[ROOT_INTERNAL](this.owner, this.input.workspaceFsAt, root, identity, `${this.parentPath}/${input.workspaceId}`, this.input.serviceUid); try { lease.assertLive(); } catch { await lease.close().catch(() => undefined); throw conflict(); } return lease; }
private checkParent(): void { try { if (!exactParent(this.parent.stat(), this.input.serviceUid)) throw conflict("installation root identity changed"); } catch (e) { if ((e as Error).name === "PreprocessingConflictError") throw e; throw conflict("installation root identity changed"); } }
canonicalInput(workspaceId: string): CanonicalWorkspaceLockRootInput { if (!/^[a-z][a-z0-9-]{2,62}$/.test(workspaceId)) throw conflict(); return makeInput(workspaceId as CanonicalWorkspaceId, this.owner); }
private validInput(input: CanonicalWorkspaceLockRootInput): boolean { return typeof input === "object" && input !== null && INPUT_BRAND.get(input) === this.owner; }
private async open(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> { if (!this.validInput(input)) throw conflict(); this.checkParent(); let root: OwnedWorkspaceFsAtDirectory; try { root = this.input.workspaceFsAt.openDirectoryAt(this.parent, input.workspaceId); } catch (e) { throw e; } try { this.checkParent(); const st = root.stat(); if (!exactRoot(st, this.input.serviceUid)) throw conflict(); const identity = { schemaVersion: 1 as const, workspaceId: input.workspaceId, device: st.device, inode: st.inode }; const lease = makeRoot(this.input.workspaceFsAt, this.parent, root, identity, this.input.serviceUid, this.owner); this.assertRoot(lease); return lease; } catch (e) { try { root.close(); } catch {} throw ((e as Error).name === "PreprocessingConflictError" ? e : conflict()); } }
private assertRoot(root: VerifiedWorkspaceLockRootLease): void { assertRootLive(root) }
acquire(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> { return this.open(input); }
async acquireOrProvision(input: CanonicalWorkspaceLockRootInput): Promise<VerifiedWorkspaceLockRootLease> {
if (input.owner !== this.owner) throw conflict();
try { return await this.open(input); } catch (error) { if (!String((error as Error).message).includes("identity") && (error as {code?: string}).code !== "ENOENT") { /* open errors are normalized; probe the anchored parent below */ } }
this.checkParent();
try { this.input.workspaceFsAt.mkdirAt(this.parent, input.workspaceId, 0o700); } catch (error) { if ((error as {code?: string}).code !== "EEXIST") throw conflict(); }
this.checkParent();
try { this.input.workspaceFsAt.fsyncDirectory(this.parent); } catch { throw conflict(); }
return this.open(input);
if (!this.validInput(input)) throw conflict();
let release!: () => void; const prior = this.provisionTail; this.provisionTail = new Promise<void>(r => { release = r; }); await prior;
try { this.checkParent(); try { return await this.open(input); } catch (e) { if ((e as {code?: string}).code !== "ENOENT") throw e; }
this.checkParent(); try { this.input.workspaceFsAt.mkdirAt(this.parent, input.workspaceId, 0o700); } catch (e) { if ((e as {code?: string}).code !== "EEXIST") throw conflict(); }
this.checkParent(); const winner = await this.open(input); try { this.input.workspaceFsAt.fsyncDirectory((winner as unknown as {root: OwnedWorkspaceFsAtDirectory}).root); } catch { await winner.close().catch(() => undefined); throw conflict(); } this.input.workspaceFsAt.fsyncDirectory(this.parent); return winner;
} finally { release(); }
}
}
// Internal friend boundary for writer/quiescence code. The root lease class itself has only
// identity, borrow, shared reader acquisition, transfer and close.
export const workspaceRootInternals = {
assertLive(root: VerifiedWorkspaceLockRootLease): void { assertRootLive(root) },
async acquireWriter(root: VerifiedWorkspaceLockRootLease): Promise<InternalLock> { workspaceRootInternals.assertLive(root); const x = root as unknown as { fs: WorkspaceFsAtV1; root: OwnedWorkspaceFsAtDirectory; serviceUid: number; identity: WorkspaceLockRootIdentityV1 }; const key = `${x.identity.device}:${x.identity.inode}`; if (activeWriterLocks.has(key)) return Promise.reject(conflict()); const owned = x.fs.openOrCreateLockAt(x.root, "writer.lock", 0o600); const lock = new RootLock(x.root, owned, "writer.lock", x.identity, x.fs, key); try { lock.flock("exclusive", "nonblocking"); activeWriterLocks.add(key); return Promise.resolve(lock); } catch { try { lock.close(); } catch {} return Promise.reject(conflict()); } },
async acquireReadersExclusive(root: VerifiedWorkspaceLockRootLease): Promise<InternalLock> { workspaceRootInternals.assertLive(root); const x = root as unknown as { fs: WorkspaceFsAtV1; root: OwnedWorkspaceFsAtDirectory; identity: WorkspaceLockRootIdentityV1 }; const owned = x.fs.openOrCreateLockAt(x.root, "session-readers.lock", 0o600); const lock = new RootLock(x.root, owned, "session-readers.lock", x.identity, x.fs); try { lock.flock("exclusive", "nonblocking"); return Promise.resolve(lock); } catch { try { lock.close(); } catch {} return Promise.reject(conflict()); } },
spawn(root: VerifiedWorkspaceLockRootLease, lock: InternalLock, executable: string, args: readonly string[], environment?: NodeJS.ProcessEnv) { workspaceRootInternals.assertLive(root); const x = root as unknown as { root: OwnedWorkspaceFsAtDirectory }; return lock.spawn(x.root, executable, args, environment); },
};