feat: configure Git workspace registry

This commit is contained in:
2026-08-03 21:13:23 +02:00
parent 7ad0199f9b
commit 6e1321f93c
6 changed files with 213 additions and 2 deletions
+60
View File
@@ -1,4 +1,5 @@
import path from "node:path";
import type { WorkspaceRegistryConfig } from "./workspaces/types.js";
export interface AppConfig {
host: string; port: number; harnessDir: string; thtBin: string; piBin: string;
@@ -22,7 +23,32 @@ export interface AppConfig {
* pay the probe; the local dev launcher (run-stack.sh) opts in via THT_DWH_PRECHECK.
*/
dwhPrecheck: boolean;
workspaceRegistry: WorkspaceRegistryConfig;
}
function requiredRegistryValue(value: string, label: string): string {
if (value.length === 0 || value.trim() !== value || value.includes("\0")) {
throw new Error(`workspace registry ${label} configuration is invalid`);
}
return value;
}
function absoluteRegistryPath(value: string, label: string): string {
const pathValue = requiredRegistryValue(value, label);
if (!path.isAbsolute(pathValue)) {
throw new Error(`workspace registry ${label} must be absolute`);
}
return pathValue;
}
function positiveImportLimit(value: string | undefined, fallback: number): number {
const limit = Number(value ?? fallback);
if (!Number.isSafeInteger(limit) || limit <= 0) {
throw new Error("workspace import limit configuration is invalid");
}
return limit;
}
export function loadConfig(env: Record<string, string | undefined>): AppConfig {
const authMode = env.AUTH_MODE ?? "none";
if (!(["none", "mock", "upstream"] as const).includes(authMode as AppConfig["authMode"])) {
@@ -89,6 +115,39 @@ export function loadConfig(env: Record<string, string | undefined>): AppConfig {
"THT_VECTOR_MIGRATOR_PASSWORD_SECRET_FILE", "THT_VECTOR_READER_PASSWORD_SECRET_FILE",
"THT_VECTOR_WRITER_PASSWORD_SECRET_FILE",
]) secretFiles[name] = env[name];
const registryRoot = absoluteRegistryPath(
env.THT_WORKSPACE_REGISTRY_ROOT ?? "/data/workspace-registry",
"root",
);
const registryBranch = requiredRegistryValue(env.THT_WORKSPACE_GIT_BRANCH ?? "main", "branch");
const installationId = requiredRegistryValue(
env.THT_WORKSPACE_INSTALLATION_ID ?? "local",
"installation ID",
);
const remoteUrl = env.THT_WORKSPACE_GIT_REMOTE === undefined
? undefined
: requiredRegistryValue(env.THT_WORKSPACE_GIT_REMOTE, "remote");
const secretRoots = (env.THT_WORKSPACE_SECRET_ROOTS ?? "")
.split(",")
.filter((root) => root.length > 0)
.map((root) => absoluteRegistryPath(root, "secret root"));
const workspaceRegistry: WorkspaceRegistryConfig = {
root: registryRoot,
remoteUrl,
branch: registryBranch,
gitAuthorName: requiredRegistryValue(
env.THT_WORKSPACE_GIT_AUTHOR_NAME ?? "Thoth Workspace Registry",
"Git author name",
),
gitAuthorEmail: requiredRegistryValue(
env.THT_WORKSPACE_GIT_AUTHOR_EMAIL ?? "thoth-workspace-registry@localhost",
"Git author email",
),
installationId,
secretRoots,
maxImportBytes: positiveImportLimit(env.THT_WORKSPACE_MAX_IMPORT_BYTES, 10 * 1024 * 1024),
maxImportEntries: positiveImportLimit(env.THT_WORKSPACE_MAX_IMPORT_ENTRIES, 32),
};
return {
host: env.HOST ?? "127.0.0.1",
port: Number(env.PORT ?? 8787),
@@ -106,5 +165,6 @@ export function loadConfig(env: Record<string, string | undefined>): AppConfig {
secretFiles,
modelApiKeyFile,
dwhPrecheck: env.THT_DWH_PRECHECK === "true" || env.THT_DWH_PRECHECK === "1",
workspaceRegistry,
};
}
+17
View File
@@ -0,0 +1,17 @@
export interface WorkspaceRegistryConfig {
root: string;
remoteUrl?: string;
branch: string;
gitAuthorName: string;
gitAuthorEmail: string;
installationId: string;
secretRoots: readonly string[];
maxImportBytes: number;
maxImportEntries: number;
}
export type WorkspaceErrorCode =
| "workspace_invalid" | "binding_missing" | "workspace_not_activatable"
| "workspace_stale" | "workspace_conflict" | "git_unavailable"
| "git_auth_failed" | "git_non_fast_forward" | "git_push_rejected"
| "connector_unavailable" | "semantic_index_incompatible";