refactor: share registry runtime configuration leases
This commit is contained in:
+36
-108
@@ -5,16 +5,11 @@ import {
|
||||
openSync, readFileSync, readSync, realpathSync, statSync, unlinkSync, writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
||||
import { parseAllDocuments } from "yaml";
|
||||
import { clearPrincipalEnvironment, principalEnvironment, type PrincipalContext } from "../auth/principal.js";
|
||||
import { secretValue, type SecretBundleConfig } from "../config/secret-bundle.js";
|
||||
import { resolveRuntimeBindings } from "../workspaces/bindings.js";
|
||||
import {
|
||||
renderRuntimeConfig,
|
||||
type RuntimeInstallationOverlay,
|
||||
type RuntimePaths,
|
||||
type SemanticRuntimeConfig,
|
||||
} from "../workspaces/runtime-renderer.js";
|
||||
import { type SemanticRuntimeConfig } from "../workspaces/runtime-renderer.js";
|
||||
import { WorkspaceRuntimeConfigLeaseFactory, type RuntimeConfigLease } from "../workspaces/runtime-config-lease.js";
|
||||
export type { RuntimeConfigLease } from "../workspaces/runtime-config-lease.js";
|
||||
import {
|
||||
parseWorkspaceYaml,
|
||||
validateOperationalWorkspace,
|
||||
@@ -32,13 +27,6 @@ export interface ThtConfig extends SecretBundleConfig {
|
||||
qdrantRequest?: typeof fetch;
|
||||
}
|
||||
|
||||
export interface RuntimeConfigLease {
|
||||
path: string;
|
||||
workspaceId: string;
|
||||
workspaceRevision: string;
|
||||
release(): void;
|
||||
}
|
||||
|
||||
export interface SessionRow {
|
||||
id: string;
|
||||
status: string;
|
||||
@@ -98,11 +86,19 @@ interface RuntimeSnapshot {
|
||||
|
||||
export class ThtRunner {
|
||||
private readonly runtimeSnapshots = new Map<string, RuntimeSnapshot>();
|
||||
private readonly runtimeLeases = new Map<string, RuntimeConfigLease>();
|
||||
private runtimeLeaseFactory?: WorkspaceRuntimeConfigLeaseFactory;
|
||||
|
||||
constructor(private cfg: ThtConfig, private principal?: PrincipalContext) {}
|
||||
|
||||
/** Bind one trusted request principal to every child spawned by this runner. */
|
||||
withPrincipal(principal: PrincipalContext): ThtRunner { return new ThtRunner(this.cfg, principal); }
|
||||
withPrincipal(principal: PrincipalContext): ThtRunner {
|
||||
const runner = new ThtRunner(this.cfg, principal);
|
||||
// Registry config publication is process-scoped: principal-bound runners must share the
|
||||
// lease factory so two concurrent callers cannot release one another's deterministic path.
|
||||
runner.runtimeLeaseFactory = this.runtimeLeaseFactory;
|
||||
return runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `-c <config>` args. A named workspace MUST exist: silently falling
|
||||
@@ -113,7 +109,7 @@ export class ThtRunner {
|
||||
if (workspaceConfigPath) {
|
||||
if (isAbsolute(workspaceConfigPath)) {
|
||||
if (this.runtimeSnapshots.has(workspaceConfigPath)) this.assertTrustedRuntimeSnapshot(workspaceConfigPath);
|
||||
else this.assertWorkspaceSnapshot(workspaceConfigPath);
|
||||
else if (!this.runtimeLeases.has(workspaceConfigPath)) this.assertWorkspaceSnapshot(workspaceConfigPath);
|
||||
return ["-c", workspaceConfigPath];
|
||||
}
|
||||
if (workspaceConfigPath.includes("/")) {
|
||||
@@ -138,102 +134,29 @@ export class ThtRunner {
|
||||
|| !match
|
||||
) throw new Error("config path is not a trusted runtime snapshot");
|
||||
const entry = lstatSync(path);
|
||||
if (!entry.isFile() || entry.isSymbolicLink()) {
|
||||
if (!entry.isFile() || entry.isSymbolicLink() || entry.nlink !== 1 || lstatSync(dirname(path)).isSymbolicLink()) {
|
||||
throw new Error("config path is not a trusted runtime snapshot");
|
||||
}
|
||||
return { workspaceRevision: match[1], workspaceId: match[2] };
|
||||
}
|
||||
|
||||
private readCanonicalWorkspaceSnapshot(path: string): {
|
||||
workspace: ReturnType<typeof parseWorkspaceYaml>;
|
||||
workspaceId: string;
|
||||
workspaceRevision: string;
|
||||
revisionContentRoot: string;
|
||||
} {
|
||||
const identity = this.assertWorkspaceSnapshot(path);
|
||||
const fd = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
||||
try {
|
||||
const before = fstatSync(fd);
|
||||
if (!before.isFile()) throw new Error("workspace snapshot is not a 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 !== identity.workspaceId) {
|
||||
throw new Error("workspace snapshot identity does not match its path");
|
||||
}
|
||||
return { workspace, ...identity, revisionContentRoot: dirname(path) };
|
||||
} finally {
|
||||
closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
private runtimePaths(workspaceId: string): RuntimePaths {
|
||||
if (!this.cfg.dataRoot || !isAbsolute(this.cfg.dataRoot)) {
|
||||
throw new Error("registry workspace runtime requires an absolute data root");
|
||||
}
|
||||
// The portable stack persists one `sessions` store at <dataRoot>/sessions. Keep every
|
||||
// workspace's mutable harness roots below that mounted boundary.
|
||||
const root = join(this.cfg.dataRoot, "sessions", workspaceId);
|
||||
return {
|
||||
sessions: join(root, "sessions"),
|
||||
artifacts: join(root, "artifacts"),
|
||||
indexes: join(root, "indexes"),
|
||||
};
|
||||
}
|
||||
|
||||
private installationOverlay(): RuntimeInstallationOverlay {
|
||||
const path = isAbsolute(this.cfg.configPath)
|
||||
? this.cfg.configPath
|
||||
: resolve(this.cfg.harnessDir, this.cfg.configPath);
|
||||
if (!existsSync(path)) return {};
|
||||
const documents = parseAllDocuments(readFileSync(path, "utf8"), { uniqueKeys: true });
|
||||
if (documents.length !== 1) throw new Error("installation config must contain one YAML document");
|
||||
const document = documents[0];
|
||||
if (document.errors.length > 0 || document.warnings.length > 0) {
|
||||
throw new Error("installation config contains invalid YAML");
|
||||
}
|
||||
const parsed = document.toJSON();
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new Error("installation config must be a YAML mapping");
|
||||
}
|
||||
const source = parsed as Record<string, unknown>;
|
||||
return {
|
||||
...(source.session_storage === undefined ? {} : { session_storage: source.session_storage }),
|
||||
...(source.profile === undefined ? {} : { profile: source.profile }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Render one immutable canonical registry revision into a backend-owned harness config. */
|
||||
/** Render the pinned registry revision through the shared deterministic lease. */
|
||||
acquireWorkspaceRuntime(workspaceConfigPath: string): RuntimeConfigLease {
|
||||
const canonical = this.readCanonicalWorkspaceSnapshot(workspaceConfigPath);
|
||||
const bindings = resolveRuntimeBindings(
|
||||
canonical.workspace,
|
||||
process.env,
|
||||
this.cfg.secretRoots ?? [],
|
||||
);
|
||||
const config = renderRuntimeConfig(
|
||||
canonical.workspace,
|
||||
bindings,
|
||||
this.runtimePaths(canonical.workspaceId),
|
||||
canonical,
|
||||
this.installationOverlay(),
|
||||
this.cfg.semanticRuntime,
|
||||
);
|
||||
const path = this.createRuntimeSnapshot(config);
|
||||
let released = false;
|
||||
return {
|
||||
path,
|
||||
workspaceId: canonical.workspaceId,
|
||||
workspaceRevision: canonical.workspaceRevision,
|
||||
release: () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
this.cleanupRuntimeSnapshot(path);
|
||||
},
|
||||
};
|
||||
return this.runtimeConfigLeases().acquireSession(workspaceConfigPath);
|
||||
}
|
||||
|
||||
private runtimeConfigLeases(): WorkspaceRuntimeConfigLeaseFactory {
|
||||
if (this.runtimeLeaseFactory) return this.runtimeLeaseFactory;
|
||||
this.runtimeLeaseFactory = new WorkspaceRuntimeConfigLeaseFactory({
|
||||
dataRoot: this.cfg.dataRoot ?? "",
|
||||
runtimeSnapshotRoot: this.cfg.runtimeSnapshotRoot ?? "",
|
||||
harnessDir: this.cfg.harnessDir,
|
||||
configPath: this.cfg.configPath,
|
||||
secretRoots: this.cfg.secretRoots,
|
||||
env: process.env,
|
||||
semanticRuntime: this.cfg.semanticRuntime,
|
||||
});
|
||||
return this.runtimeLeaseFactory;
|
||||
}
|
||||
|
||||
private runtimeSnapshotDirectory(): string {
|
||||
@@ -366,6 +289,7 @@ export class ThtRunner {
|
||||
if (
|
||||
workspaceConfigPath && isAbsolute(workspaceConfigPath)
|
||||
&& !this.runtimeSnapshots.has(workspaceConfigPath)
|
||||
&& !this.runtimeLeases.has(workspaceConfigPath)
|
||||
) {
|
||||
let runtime: RuntimeConfigLease;
|
||||
try {
|
||||
@@ -373,7 +297,11 @@ export class ThtRunner {
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return this.run(args, runtime.path, timeoutMs).finally(runtime.release);
|
||||
this.runtimeLeases.set(runtime.path, runtime);
|
||||
return this.run(args, runtime.path, timeoutMs).finally(() => {
|
||||
this.runtimeLeases.delete(runtime.path);
|
||||
runtime.release();
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||
|
||||
Reference in New Issue
Block a user