Publish documentation / publish (push) Successful in 1m27s
Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation. Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
443 lines
16 KiB
TypeScript
443 lines
16 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import {
|
|
chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, 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
|
|
`;
|
|
|
|
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 workspaceSecretStore = new WorkspaceSecretStore({
|
|
root: join(root, "workspace-secrets"),
|
|
runtimeRoot: join(root, "workspace-secret-runtime"),
|
|
installationId: "test",
|
|
});
|
|
workspaceSecretStore.putMany("psd-clinical", {
|
|
"catalog.dwh.password": "dwh-password-value",
|
|
"evidence.signed_urls": secretContents["evidence-signed-urls.json"],
|
|
"evidence.access_key": secretContents["evidence-access"],
|
|
"evidence.secret_key": secretContents["evidence-secret"],
|
|
"evidence.session_token": secretContents["evidence-token"],
|
|
});
|
|
const catalogDatabase = {
|
|
id: "database-1",
|
|
workspaceId: "psd-clinical",
|
|
engine: "postgres" as const,
|
|
databaseName: "analytics",
|
|
schema: "mart",
|
|
binding: {
|
|
transport: "postgres_direct" as const,
|
|
host: "dwh.invalid",
|
|
port: 5432,
|
|
username: "reader",
|
|
},
|
|
version: 1,
|
|
createdAt: "2026-01-01T00:00:00Z",
|
|
updatedAt: "2026-01-01T00:00:00Z",
|
|
connectionStatus: "reachable" as const,
|
|
metadataContentRevision: 1,
|
|
preprocessingStatus: "failed" as const,
|
|
};
|
|
const catalogRepository = { getByWorkspace: async () => catalogDatabase } as any;
|
|
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,
|
|
workspaceSecretStore, catalogDatabase, catalogRepository,
|
|
};
|
|
}
|
|
|
|
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,
|
|
workspaceSecretStore: f.workspaceSecretStore,
|
|
catalogRepository: f.catalogRepository,
|
|
} 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([]);
|
|
}, 15_000);
|
|
|
|
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", "catalog.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,
|
|
catalogRepository: f.catalogRepository,
|
|
} as any);
|
|
|
|
const lease = await 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("separate runtime leases hand off byte-identical revision Evidence configs accepted by tht", async () => {
|
|
const f = await fixture();
|
|
const runner = runnerFor(f);
|
|
const first = await runner.acquireWorkspaceRuntime(f.revision.snapshotPath);
|
|
const second = await 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({
|
|
local_archive_root: join(f.registryConfig.root, "repo", "psd-clinical"),
|
|
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 = await 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 = await 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 = await 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: expect.stringContaining("/workspace-secret-runtime/"),
|
|
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 ephemeral vault materializations", 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 = await 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: expect.stringContaining("/workspace-secret-runtime/"),
|
|
secret_key_file: expect.stringContaining("/workspace-secret-runtime/"),
|
|
session_token_file: expect.stringContaining("/workspace-secret-runtime/"),
|
|
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();
|
|
}
|
|
});
|