144 lines
6.7 KiB
TypeScript
144 lines
6.7 KiB
TypeScript
import { test, expect } from "vitest";
|
|
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { WorkspaceRuntimeConfigLeaseFactory } from "../src/workspaces/runtime-config-lease.js";
|
|
import { parseWorkspaceYaml, serializeWorkspaceYaml } from "../src/workspaces/schema.js";
|
|
|
|
const workspace = "abc";
|
|
const descriptor = `workspace:
|
|
schema_version: 3
|
|
id: ${workspace}
|
|
name: Lease
|
|
language: en
|
|
dwh:
|
|
engine: postgres
|
|
database: analytics
|
|
schema: mart
|
|
supported_transports: [postgres_direct]
|
|
semantic_index:
|
|
vector_store:
|
|
engine: qdrant
|
|
collection: abc
|
|
dimensions: 1024
|
|
distance: cosine
|
|
embedding:
|
|
provider: ollama_internal
|
|
model: qwen3-embedding:0.6b
|
|
dimensions: 1024
|
|
llm_policy:
|
|
allowed: [zai/glm-5.2]
|
|
`;
|
|
|
|
function fixture() {
|
|
const root = mkdtempSync(join(tmpdir(), "runtime-config-lease-"));
|
|
const canonicalDescriptor = serializeWorkspaceYaml(parseWorkspaceYaml(descriptor));
|
|
const snapshots = join(root, "snapshots");
|
|
const repo = join(root, "repo");
|
|
mkdirSync(join(repo, "workspaces"), { recursive: true });
|
|
execFileSync("git", ["init", "--initial-branch=main"], { cwd: repo });
|
|
execFileSync("git", ["config", "user.name", "Fixture"], { cwd: repo });
|
|
execFileSync("git", ["config", "user.email", "fixture@example.invalid"], { cwd: repo });
|
|
writeFileSync(join(repo, "workspaces", `${workspace}.yaml`), canonicalDescriptor);
|
|
execFileSync("git", ["add", "."], { cwd: repo });
|
|
execFileSync("git", ["commit", "-m", "fixture"], { cwd: repo });
|
|
const actualCommit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repo, encoding: "utf8" }).trim();
|
|
const blob = execFileSync("git", ["rev-parse", `HEAD:workspaces/${workspace}.yaml`], { cwd: repo, encoding: "utf8" }).trim();
|
|
const snapshotsDir = join(snapshots, actualCommit);
|
|
const snapshotPath = join(snapshotsDir, `${workspace}.yaml`);
|
|
const dataRoot = join(root, "data");
|
|
const harness = join(root, "harness");
|
|
mkdirSync(snapshotsDir, { recursive: true, mode: 0o700 });
|
|
chmodSync(snapshots, 0o700);
|
|
const publicFiles = {
|
|
[`${workspace}.yaml`]: canonicalDescriptor,
|
|
[`${workspace}.env.example`]: "# fixture\n",
|
|
[`${workspace}.md`]: "# Lease\n",
|
|
};
|
|
for (const [name, contents] of Object.entries(publicFiles)) writeFileSync(join(snapshotsDir, name), contents, { mode: 0o400 });
|
|
writeFileSync(join(snapshotsDir, "snapshot.json"), JSON.stringify({
|
|
head: actualCommit,
|
|
revisions: [{ id: workspace, commit: actualCommit, blob, snapshotPath }],
|
|
files: Object.fromEntries(Object.entries(publicFiles).map(([name, contents]) => [name, createHash("sha256").update(contents).digest("hex")])),
|
|
}), { mode: 0o400 });
|
|
mkdirSync(harness);
|
|
const secret = join(root, "password");
|
|
writeFileSync(secret, "secret", { mode: 0o600 });
|
|
const configPath = join(harness, "config.yaml");
|
|
writeFileSync(configPath, "profile: workstation\n");
|
|
const factory = new WorkspaceRuntimeConfigLeaseFactory({
|
|
dataRoot, runtimeSnapshotRoot: snapshots, harnessDir: harness, configPath,
|
|
env: {
|
|
THT_WS_ABC_DWH_TRANSPORT: "postgres_direct", THT_WS_ABC_DWH_HOST: "dwh",
|
|
THT_WS_ABC_DWH_PORT: "5432", THT_WS_ABC_DWH_USER: "reader",
|
|
THT_WS_ABC_DWH_PASSWORD_FILE: secret,
|
|
}, secretRoots: [root], semanticRuntime: {
|
|
internalQdrantUrl: "http://qdrant:6333", internalEmbeddingUrl: "http://embedding:11434",
|
|
internalEmbeddingModel: "qwen3-embedding:0.6b", internalEmbeddingDimensions: 1024,
|
|
},
|
|
});
|
|
return { root, snapshotPath, factory, canonicalDescriptor };
|
|
}
|
|
|
|
test("session and maintenance share deterministic bytes and path", () => {
|
|
const f = fixture();
|
|
try {
|
|
const session = f.factory.acquireSession(f.snapshotPath);
|
|
const maintenance = f.factory.acquireMaintenance({ snapshotPath: f.snapshotPath });
|
|
expect(session.path).toBe(maintenance.path);
|
|
expect(readFileSync(session.path, "utf8")).toBe(readFileSync(maintenance.path, "utf8"));
|
|
expect(lstatSync(session.path).mode & 0o777).toBe(0o400);
|
|
expect(existsSync(maintenance.manifestPath)).toBe(true);
|
|
const manifest = JSON.parse(readFileSync(maintenance.manifestPath, "utf8"));
|
|
expect(manifest).toMatchObject({ version: 1, descriptor_dev: expect.any(String), descriptor_ino: expect.any(String) });
|
|
expect(existsSync(join(dirname(dirname(maintenance.path)), "runtime-config.lock"))).toBe(false);
|
|
session.release(); maintenance.release();
|
|
expect(existsSync(session.path)).toBe(true);
|
|
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test("same revision changed bytes are refused", () => {
|
|
const f = fixture();
|
|
try {
|
|
const first = f.factory.acquireSession(f.snapshotPath);
|
|
chmodSync(first.path, 0o600);
|
|
writeFileSync(first.path, "changed", { mode: 0o600 });
|
|
chmodSync(first.path, 0o400);
|
|
expect(() => f.factory.acquireSession(f.snapshotPath)).toThrow(/changed|mismatch|trusted/i);
|
|
first.release();
|
|
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test("snapshot descriptor must equal the Git canonical descriptor", () => {
|
|
const f = fixture();
|
|
try {
|
|
const mutated = f.canonicalDescriptor.replace("database: analytics", "database: evil").replace("name: Lease", "name: Lease analytics");
|
|
chmodSync(f.snapshotPath, 0o600);
|
|
writeFileSync(f.snapshotPath, mutated, { mode: 0o600 });
|
|
chmodSync(f.snapshotPath, 0o400);
|
|
const manifestPath = join(dirname(f.snapshotPath), "snapshot.json");
|
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
manifest.files[`${workspace}.yaml`] = createHash("sha256").update(mutated).digest("hex");
|
|
chmodSync(manifestPath, 0o600);
|
|
writeFileSync(manifestPath, JSON.stringify(manifest), { mode: 0o600 });
|
|
chmodSync(manifestPath, 0o400);
|
|
expect(() => f.factory.acquireSession(f.snapshotPath)).toThrow(/Git descriptor|integrity|identity/i);
|
|
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test("same-byte replacement of the registry descriptor is refused", () => {
|
|
const f = fixture();
|
|
try {
|
|
const first = f.factory.acquireSession(f.snapshotPath);
|
|
const replacement = `${f.snapshotPath}.replacement`;
|
|
writeFileSync(replacement, readFileSync(f.snapshotPath), { mode: 0o400 });
|
|
chmodSync(f.snapshotPath, 0o600);
|
|
rmSync(f.snapshotPath);
|
|
writeFileSync(f.snapshotPath, readFileSync(replacement), { mode: 0o400 });
|
|
expect(() => f.factory.acquireSession(f.snapshotPath)).toThrow(/identity|changed|mismatch|trusted/i);
|
|
first.release();
|
|
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
});
|