Files
ThothII/backend/test/workspace-runtime-handoff.test.ts
T

428 lines
16 KiB
TypeScript

import { execFile } from "node:child_process";
import {
chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { promisify } from "node:util";
import { afterEach, expect, test, vi } from "vitest";
import { parse } from "yaml";
import { buildApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import { ThtRunner } from "../src/tht/tht-runner.js";
import { WorkspaceRegistry } from "../src/workspaces/registry.js";
import { WorkspaceSecretStore } from "../src/workspaces/secret-store.js";
import type { WorkspaceRegistryConfig } from "../src/workspaces/types.js";
const runFile = promisify(execFile);
const harnessDir = resolve("../harness");
const thtBin = join(harnessDir, ".venv", "bin", "tht");
const roots: string[] = [];
const canonicalWorkspace = `workspace:
schema_version: 4
id: psd-clinical
name: Runtime handoff
language: en
dwh:
engine: postgres
database: analytics
schema: mart
supported_transports: [postgres_direct]
`;
const filesystemWorkspace = `${canonicalWorkspace}evidence:
source:
type: filesystem
uri: psd-clinical/evidence
`;
function evidenceWorkspace(source: string, policy = ""): string {
return `${canonicalWorkspace}evidence:
source:
${source}${policy}`;
}
afterEach(() => {
vi.unstubAllEnvs();
roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true }));
});
async function git(cwd: string, args: string[]): Promise<string> {
return (await runFile("git", args, { cwd })).stdout.trim();
}
async function fixture(workspaceSource = filesystemWorkspace) {
const root = mkdtempSync(join(tmpdir(), "tht-runtime-handoff-"));
roots.push(root);
const remote = join(root, "remote.git");
const source = join(root, "source");
const registryRoot = join(root, "registry");
const secretRoot = join(root, "secrets");
const dataRoot = join(root, "data");
await git(root, ["init", "--bare", "--initial-branch=main", remote]);
mkdirSync(source);
await git(source, ["init", "--initial-branch=main"]);
await git(source, ["config", "user.name", "Runtime Handoff Test"]);
await git(source, ["config", "user.email", "runtime-handoff@example.invalid"]);
writeFileSync(join(source, "thoth-workspaces.yaml"), "schema_version: 1\nworkspaces: [{id: psd-clinical, name: Runtime handoff}]\n");
mkdirSync(join(source, "psd-clinical"), { recursive: true });
writeFileSync(join(source, "psd-clinical", "workspace.yaml"), workspaceSource);
const evidenceRoot = join(source, "psd-clinical", "evidence");
mkdirSync(evidenceRoot, { recursive: true });
writeFileSync(join(evidenceRoot, "guide.md"), "# Immutable revision evidence\n");
await git(source, ["add", "."]);
await git(source, ["commit", "-m", "Canonical workspace"]);
await git(source, ["remote", "add", "origin", remote]);
await git(source, ["push", "origin", "main"]);
mkdirSync(secretRoot);
const secretContents: Record<string, string> = {
"dwh-password": "dwh-password-value",
"evidence-signed-urls.json": JSON.stringify([
"https://evidence.example.test/guide.md?token=SIGNED-HANDOFF-CANARY",
]),
"evidence-access": "ACCESS-HANDOFF-CANARY",
"evidence-secret": "SECRET-HANDOFF-CANARY",
"evidence-token": "TOKEN-HANDOFF-CANARY",
};
for (const [name, contents] of Object.entries(secretContents)) {
const path = join(secretRoot, name);
writeFileSync(path, contents, { mode: 0o600 });
chmodSync(path, 0o600);
}
mkdirSync(dataRoot);
const registryConfig: WorkspaceRegistryConfig = {
root: registryRoot,
remoteUrl: remote,
branch: "main",
gitAuthorName: "Runtime Handoff Test",
gitAuthorEmail: "runtime-handoff@example.invalid",
installationId: "test",
secretRoots: [secretRoot],
maxImportBytes: 1024 * 1024,
maxImportEntries: 16,
};
const registry = new WorkspaceRegistry(registryConfig);
await registry.bootstrap();
const revision = (await registry.list())[0];
const environment = {
THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "postgres_direct",
THT_WS_PSD_CLINICAL_DWH_HOST: "dwh.invalid",
THT_WS_PSD_CLINICAL_DWH_PORT: "5432",
THT_WS_PSD_CLINICAL_DWH_USER: "reader",
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: join(secretRoot, "dwh-password"),
THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE: join(secretRoot, "evidence-signed-urls.json"),
THT_WS_PSD_CLINICAL_EVIDENCE_ACCESS_KEY_FILE: join(secretRoot, "evidence-access"),
THT_WS_PSD_CLINICAL_EVIDENCE_SECRET_KEY_FILE: join(secretRoot, "evidence-secret"),
THT_WS_PSD_CLINICAL_EVIDENCE_SESSION_TOKEN_FILE: join(secretRoot, "evidence-token"),
};
for (const [name, value] of Object.entries(environment)) vi.stubEnv(name, value);
vi.stubEnv("THT_HOME", join(root, "home"));
return { root, source, dataRoot, secretRoot, registry, registryConfig, revision };
}
function runnerFor(f: Awaited<ReturnType<typeof fixture>>): ThtRunner {
return new ThtRunner({
thtBin,
harnessDir,
configPath: "config/tht.yaml",
dataRoot: f.dataRoot,
runtimeSnapshotRoot: join(f.registryConfig.root, "snapshots", "runtime"),
secretRoots: f.registryConfig.secretRoots,
} as any);
}
test("real schema-v4 registry revision loads through ThtRunner and the harness contract", async () => {
const f = await fixture();
const runner = runnerFor(f);
expect(await runner.sessionList(f.revision.snapshotPath)).toEqual([]);
const created = await runner.sessionNew({
question: "runtime handoff",
workspaceConfigPath: f.revision.snapshotPath,
workspaceId: f.revision.id,
workspaceRevision: f.revision.commit,
});
expect(await runner.sessionShow(created.id, f.revision.snapshotPath)).toMatchObject({
id: created.id,
workspace_id: "psd-clinical",
workspace_revision: f.revision.commit,
});
expect(existsSync(join(
f.dataRoot, "sessions", "psd-clinical", "sessions", created.id, "session_manifest.yaml",
))).toBe(true);
expect(readdirSync(join(f.registryConfig.root, "snapshots", "runtime"))).toEqual([]);
});
test("ThtRunner uses a vault secret only for the lifetime of its runtime lease", async () => {
const f = await fixture();
vi.stubEnv("THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE", "");
const vaultRoot = join(f.root, "workspace-secrets");
const runtimeRoot = join(f.root, "workspace-secret-runtime");
const secretStore = new WorkspaceSecretStore({
root: vaultRoot,
runtimeRoot,
installationId: "test",
});
secretStore.put("psd-clinical", "dwh.password", "vault-runtime-password");
const runner = new ThtRunner({
thtBin,
harnessDir,
configPath: "config/tht.yaml",
dataRoot: f.dataRoot,
runtimeSnapshotRoot: join(f.registryConfig.root, "snapshots", "runtime"),
secretRoots: f.registryConfig.secretRoots,
workspaceSecretStore: secretStore,
} as any);
const lease = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
const rendered = parse(readFileSync(lease.path, "utf8")) as {
database: { password_file: string };
};
expect(readFileSync(rendered.database.password_file, "utf8")).toBe("vault-runtime-password");
lease.release();
expect(existsSync(rendered.database.password_file)).toBe(false);
});
test("ThtRunner binds and cleans the effective relationship snapshot with its runtime lease", async () => {
const f = await fixture();
const runner = runnerFor(f);
const relationships = JSON.stringify({
schemaVersion: 1,
workspaceId: "psd-clinical",
relationships: [],
});
const lease = runner.acquireWorkspaceRuntime(f.revision.snapshotPath, relationships);
const rendered = parse(readFileSync(lease.path, "utf8")) as {
paths: { effective_relationships: string };
};
expect(readFileSync(rendered.paths.effective_relationships, "utf8")).toBe(relationships);
lease.release();
expect(existsSync(rendered.paths.effective_relationships)).toBe(false);
});
test("separate runtime leases hand off byte-identical revision Evidence configs accepted by tht", async () => {
const f = await fixture();
const runner = runnerFor(f);
const first = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
const second = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
const expectedRoot = join(
f.registryConfig.root,
"snapshots",
f.revision.commit,
"psd-clinical",
"evidence",
);
try {
expect(first.path).not.toBe(second.path);
const firstYaml = readFileSync(first.path, "utf8");
const secondYaml = readFileSync(second.path, "utf8");
expect(secondYaml).toBe(firstYaml);
expect(parse(firstYaml).runtime_identity).toEqual({
workspace_id: "psd-clinical",
workspace_revision: f.revision.commit,
source_identity: "workspace://psd-clinical",
});
expect(parse(firstYaml).evidence).toEqual({
sources: [{
type: "filesystem",
root: expectedRoot,
patterns: ["**/*.md"],
max_bytes: 10_485_760,
}],
});
expect(parse(firstYaml).vector).toEqual({
max_chunk_chars: 4_000,
retain_published_generations: 3,
});
expect(expectedRoot).not.toContain(join(f.registryConfig.root, "repo"));
for (const lease of [first, second]) {
const checked = await runFile(thtBin, ["config", "check", "-c", lease.path], {
cwd: harnessDir,
env: { ...process.env, THT_HOME: join(f.root, "home") },
});
expect(`${checked.stdout}${checked.stderr}`).not.toContain("HANDOFF-CANARY");
}
first.release();
expect(existsSync(first.path)).toBe(false);
expect(existsSync(second.path)).toBe(true);
second.release();
expect(existsSync(second.path)).toBe(false);
} finally {
first.release();
second.release();
}
});
test("real Evidence-content-only commit changes runtime identity and root with identical descriptor YAML", async () => {
const f = await fixture();
const runner = runnerFor(f);
const first = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
const descriptorBefore = readFileSync(f.revision.snapshotPath, "utf8");
writeFileSync(
join(f.source, "psd-clinical", "evidence", "guide.md"),
"# Content-only revision two\n",
);
await git(f.source, ["add", "psd-clinical/evidence/guide.md"]);
await git(f.source, ["commit", "-m", "Update Evidence content only"]);
await git(f.source, ["push", "origin", "main"]);
await f.registry.pull();
const current = (await f.registry.list())[0];
const second = runner.acquireWorkspaceRuntime(current.snapshotPath);
try {
expect(current.commit).not.toBe(f.revision.commit);
expect(current.blob).toBe(f.revision.blob);
expect(readFileSync(current.snapshotPath, "utf8")).toBe(descriptorBefore);
const firstRendered = parse(readFileSync(first.path, "utf8"));
const secondRendered = parse(readFileSync(second.path, "utf8"));
expect(firstRendered.runtime_identity.workspace_revision).toBe(f.revision.commit);
expect(secondRendered.runtime_identity.workspace_revision).toBe(current.commit);
expect(secondRendered.evidence.sources[0].root).toBe(join(
f.registryConfig.root,
"snapshots",
current.commit,
"psd-clinical",
"evidence",
));
expect(secondRendered.evidence.sources[0].root).not.toBe(firstRendered.evidence.sources[0].root);
for (const lease of [first, second]) {
await expect(runFile(thtBin, ["config", "check", "-c", lease.path], {
cwd: harnessDir,
env: { ...process.env, THT_HOME: join(f.root, "home") },
})).resolves.toBeDefined();
}
} finally {
first.release();
second.release();
}
});
test("signed HTTP Evidence resolves its file binding and config check never captures its contents", async () => {
const f = await fixture(evidenceWorkspace(` type: http
uris: [https://evidence.example.test/guide.md]
authentication: signed_urls_file
connect_timeout_ms: 1250
read_timeout_ms: 30001
max_bytes: 12345
max_redirects: 2
allow_private_hosts: false
max_cache_bytes: 67890
`));
const runner = runnerFor(f);
const lease = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
try {
const yaml = readFileSync(lease.path, "utf8");
expect(parse(yaml).evidence.sources).toEqual([{
type: "http",
provenance_urls: ["https://evidence.example.test/guide.md"],
signed_urls_file: realpathSync(join(f.secretRoot, "evidence-signed-urls.json")),
connect_timeout: 1.25,
read_timeout: 30.001,
max_bytes: 12_345,
max_redirects: 2,
allow_private_hosts: false,
max_cache_bytes: 67_890,
}]);
expect(yaml).not.toContain("SIGNED-HANDOFF-CANARY");
const checked = await runFile(thtBin, ["config", "check", "-c", lease.path], {
cwd: harnessDir,
env: { ...process.env, THT_HOME: join(f.root, "home") },
});
expect(`${checked.stdout}${checked.stderr}`).not.toContain("SIGNED-HANDOFF-CANARY");
} finally {
lease.release();
}
});
test("static S3 Evidence resolves only configured secret-root file paths", async () => {
const f = await fixture(evidenceWorkspace(` type: s3
uri: s3://clinical-evidence/published/
endpoint_url: https://s3.example.test/
region: eu-west-1
credentials: static_files
trusted_endpoint: true
allow_private_endpoint: true
allow_insecure_endpoint: false
max_bytes: 222
max_objects: 33
max_pages: 4
page_size: 5
`, ` policy:
max_chunk_chars: 2500
retain_published_generations: 7
`));
const runner = runnerFor(f);
const lease = runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
try {
const yaml = readFileSync(lease.path, "utf8");
expect(parse(yaml).evidence.sources).toEqual([{
type: "s3",
bucket: "clinical-evidence",
prefix: "published/",
endpoint_url: "https://s3.example.test/",
region: "eu-west-1",
access_key_file: realpathSync(join(f.secretRoot, "evidence-access")),
secret_key_file: realpathSync(join(f.secretRoot, "evidence-secret")),
session_token_file: realpathSync(join(f.secretRoot, "evidence-token")),
trusted_endpoint: true,
allow_private_endpoint: true,
allow_insecure_endpoint: false,
max_bytes: 222,
max_objects: 33,
max_pages: 4,
page_size: 5,
}]);
expect(parse(yaml).vector).toEqual({
max_chunk_chars: 2_500,
retain_published_generations: 7,
});
for (const canary of ["ACCESS-HANDOFF-CANARY", "SECRET-HANDOFF-CANARY", "TOKEN-HANDOFF-CANARY"]) {
expect(yaml).not.toContain(canary);
}
const checked = await runFile(thtBin, ["config", "check", "-c", lease.path], {
cwd: harnessDir,
env: { ...process.env, THT_HOME: join(f.root, "home") },
});
const output = `${checked.stdout}${checked.stderr}`;
for (const canary of ["ACCESS-HANDOFF-CANARY", "SECRET-HANDOFF-CANARY", "TOKEN-HANDOFF-CANARY"]) {
expect(output).not.toContain(canary);
}
} finally {
lease.release();
}
});
test("local GET sessions mine uses the real canonical handoff and returns an empty inventory", async () => {
const f = await fixture();
const app = buildApp(loadConfig({
AUTH_MODE: "none",
THT_HARNESS_DIR: harnessDir,
THT_BIN: thtBin,
THT_DATA_ROOT: f.dataRoot,
THT_WORKSPACE_REGISTRY_ROOT: f.registryConfig.root,
THT_WORKSPACE_GIT_REMOTE: f.registryConfig.remoteUrl,
THT_WORKSPACE_SECRET_ROOTS: f.registryConfig.secretRoots.join(","),
}), {
thtRunner: runnerFor(f),
workspaceRegistry: f.registry,
mgr: { get: () => undefined } as any,
});
try {
const response = await app.inject({ method: "GET", url: "/sessions?scope=mine" });
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual([]);
} finally {
await app.close();
}
});