import { fakeFetch } from "../helpers/fetch"; import { afterAll, test } from "bun:test"; import assert from "node:assert/strict"; import { chmodSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createSqliteDb, openDatabase } from "../../src/runtime/bun/sqlite"; import { MIGRATIONS, SCHEMA_VERSION, schemaState } from "../../src/core/migrations"; import { fetchStableRelease, stableVersionStatus } from "../../src/runtime/bun/release"; const root = mkdtempSync(join(tmpdir(), "titen-cli-")); const cli = join(import.meta.dir, "../../src/runtime/bun/cli.ts"); const version = (JSON.parse(readFileSync(join(import.meta.dir, "../../package.json"), "utf8")) as { version: string; }).version; const releasePreload = join(root, "release-preload.ts"); writeFileSync(releasePreload, ` const nativeFetch = globalThis.fetch; globalThis.fetch = (input, init) => { if (String(input) === "https://titen.dev/version.json" && process.env.TITEN_TEST_RELEASE) return Promise.resolve(new Response(process.env.TITEN_TEST_RELEASE, { status: 200 })); return nativeFetch(input, init); }; `); afterAll(() => rmSync(root, { recursive: true, force: true })); function run(name: string, args: string[], release?: unknown, env?: Record) { const cwd = join(root, name); mkdirSync(cwd, { recursive: true }); const result = Bun.spawnSync({ cmd: release === undefined ? ["bun", cli, ...args] : ["bun", "--preload", releasePreload, cli, ...args], cwd, env: { ...process.env, ...env, ...(release === undefined ? {} : { TITEN_TEST_RELEASE: JSON.stringify(release) }), }, }); return { exitCode: result.exitCode, output: `${result.stdout.toString()}${result.stderr.toString()}`, files: readdirSync(cwd), }; } test("help is side-effect free for every documented command", () => { const cases = [ ["top", ["--help"]], ["version", ["version", "--help"]], ["import-source", ["import-source", "--help"]], ["mcp", ["mcp", "--help"]], ["dashboard", ["dashboard", "--help"]], ["serve", ["serve", "--help"]], ["migrate", ["migrate", "--help"]], ["bootstrap", ["bootstrap", "--help"]], ["key-create", ["key", "create", "--help"]], ["key-list", ["key", "list", "--help"]], ["key-revoke", ["key", "revoke", "--help"]], ["principal-reassign", ["principal", "reassign", "--help"]], ["jobs-list", ["jobs", "list", "--help"]], ["jobs-retry", ["jobs", "retry", "--help"]], ["jobs-ack", ["jobs", "ack", "--help"]], ["jobs-purge", ["jobs", "purge", "--help"]], ["project-create", ["project", "create", "--help"]], ["project-update", ["project", "update", "--help"]], ["backup", ["backup", "--help"]], ["schema", ["schema", "--help"]], ] as const; for (const [name, args] of cases) { const result = run(`help-${name}`, [...args]); assert.equal(result.exitCode, 0, `${name} help must succeed: ${result.output}`); assert.match(result.output, /Usage:/); assert.doesNotMatch(result.output, /api_key:|titen listening/); assert.deepEqual(result.files, [], `${name} help created state`); } }); test("version is exact and side-effect free", () => { const result = run("version", ["--version"]); assert.equal(result.exitCode, 0); assert.equal(result.output, `${version}\n`); assert.deepEqual(result.files, []); const command = run("version-command", ["version"]); assert.equal(command.exitCode, 0); assert.equal(command.output, `${version}\nschema_version ${SCHEMA_VERSION}\n`); assert.deepEqual(command.files, []); }); test("version check validates and reports the stable CLI and plugin release", async () => { const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; const nextStable = `${match[1]}.${match[2]}.${Number(match[3]) + 1}`; const release = { schema: 1, channel: "stable", cli: { version: nextStable }, plugin: { version: "0.2.0" }, }; const result = run("version-check", ["version", "--check"], release); assert.equal(result.exitCode, 0, result.output); assert.equal(result.output, [ `CLI installed: ${version}`, `CLI stable: ${nextStable}`, "CLI status: update available", "Plugin stable: 0.2.0", `Release notes: https://titen.dev/releases/${nextStable}`, "Install/update: https://titen.dev/docs/install", "", ].join("\n")); assert.deepEqual(result.files, []); const invalid = run("version-check-invalid", ["version", "--check"], { ...release, channel: "preview", }); assert.notEqual(invalid.exitCode, 0); assert.match(invalid.output, /error: invalid stable release manifest/); assert.deepEqual(invalid.files, []); let requestInit: RequestInit | undefined; assert.deepEqual( await fetchStableRelease(fakeFetch(async (_input, init) => { requestInit = init; return new Response(JSON.stringify(release)); })), { cliVersion: nextStable, pluginVersion: "0.2.0" }, ); assert.equal(requestInit?.redirect, "error", "the fixed release URL must not follow redirects"); const rejected = [ [new Response("redirect", { status: 302 }), /release endpoint returned HTTP 302/], [new Response("unavailable", { status: 503 }), /release endpoint returned HTTP 503/], [new Response("{"), /release endpoint returned invalid JSON/], [new Response(JSON.stringify({ ...release, schema: 2 })), /invalid stable release manifest/], [new Response(JSON.stringify({ ...release, cli: { version: "v0.5.0" } })), /invalid stable release manifest/], [new Response(JSON.stringify({ ...release, plugin: { version: "latest" } })), /invalid stable release manifest/], ] as const; for (const [response, message] of rejected) await assert.rejects(fetchStableRelease(fakeFetch(async () => response)), message); assert.equal(stableVersionStatus("0.5.0", "0.5.0"), "current"); assert.equal(stableVersionStatus("0.6.0", "0.5.0"), "ahead"); assert.equal(stableVersionStatus("0.6.0-rc.1", "0.5.0"), "prerelease"); }); test("the bin names Bun and its install page when Bun is missing from PATH", () => { // npm and pnpm link `node_modules/.bin/titen` straight at this file, so the // kernel reads its shebang. Reproduce that exact entry rather than `bun cli.ts`. const linked = join(root, "bun-missing-bin"); mkdirSync(linked, { recursive: true }); const bin = join(linked, "titen"); symlinkSync(cli, bin); const emptyPath = join(root, "bun-missing-path"); mkdirSync(emptyPath, { recursive: true }); const missing = Bun.spawnSync({ cmd: [bin, "--version"], env: { PATH: emptyPath, HOME: process.env.HOME ?? "" }, }); const output = `${missing.stdout.toString()}${missing.stderr.toString()}`; assert.equal(missing.exitCode, 1, output); assert.match(output, /^titen: error: bun was not found on PATH\.$/m); assert.match(output, /Bun 1\.2 or newer.*https:\/\/bun\.sh/); assert.doesNotMatch(output, /No such file or directory/); // The same entry must still reach the CLI when Bun is the only runtime on PATH. const bunOnlyPath = join(root, "bun-only-path"); mkdirSync(bunOnlyPath, { recursive: true }); symlinkSync(process.execPath, join(bunOnlyPath, "bun")); const present = Bun.spawnSync({ cmd: [bin, "--version"], env: { PATH: bunOnlyPath, HOME: process.env.HOME ?? "" }, }); assert.equal( present.exitCode, 0, `${present.stdout.toString()}${present.stderr.toString()}`, ); assert.equal(present.stdout.toString(), `${version}\n`); }); test("malformed flags fail before side effects for every command", () => { const cases = [ ["serve", ["serve", "--port", "nope"]], ["import-source", ["import-source", "memory.md", "--from"]], ["mcp", ["mcp", "--api-key", "must-not-be-accepted"]], ["dashboard", ["dashboard", "--port", "nope"]], ["migrate", ["migrate", "--db"]], ["bootstrap", ["bootstrap", "--org"]], ["key-create", ["key", "create", "--org-id"]], ["key-list", ["key", "list", "--db"]], ["key-revoke", ["key", "revoke", "--id"]], ["project-create", ["project", "create", "--org-id"]], ["project-update", ["project", "update", "--default-visibility"]], ["backup", ["backup", "--out"]], ["schema", ["schema", "--unknown"]], ] as const; for (const [name, args] of cases) { const result = run(`invalid-${name}`, [...args]); assert.notEqual(result.exitCode, 0, `${name} malformed input unexpectedly succeeded`); assert.match(result.output, /error:/); assert.doesNotMatch(result.output, /api_key:|titen listening/); assert.deepEqual(result.files, [], `${name} malformed input created state`); } }); test("bootstrap creates owner login with a one-time random password", () => { const boot = run("bootstrap-owner-login", ["bootstrap", "--db", "titen.db", "--org", "Example"]); assert.equal(boot.exitCode, 0, boot.output); assert.match(boot.output, /^dashboard_username: owner$/m); const temporary = /^temporary_password: ([A-Za-z0-9_-]{24})$/m.exec(boot.output)?.[1]; assert.ok(temporary); assert.match(boot.output, /Change the temporary password on first dashboard sign-in\./); const database = openDatabase(join(root, "bootstrap-owner-login", "titen.db"), { create: false, readonly: true }); try { const account = database.query(`SELECT a.username, a.password_verifier, a.must_change_password, m.role FROM operator_accounts a JOIN memberships m ON m.org_id = a.org_id AND m.principal_id = a.principal_id WHERE a.username = 'owner'`).get() as { username: string; password_verifier: string; must_change_password: number; role: string; }; assert.equal(account.username, "owner"); assert.equal(account.role, "owner"); assert.equal(account.must_change_password, 1); assert.match(account.password_verifier, /^pbkdf2-sha256\$100000x6\$/); assert.ok(!account.password_verifier.includes(temporary)); } finally { database.close(); } }); test("port zero, non-decimal integers, and values outside the TCP range are rejected", () => { for (const value of ["0", "65536", "1.5", "1e3", "+80", "NaN"]) { const result = run(`port-${value.replace(".", "-")}`, ["serve", "--port", value]); assert.notEqual(result.exitCode, 0); assert.match(result.output, /--port must be an integer between 1 and 65535/); assert.deepEqual(result.files, []); } }); test("key lifecycle flags survive listing and verified backup", async () => { const boot = run("key-lifecycle", ["bootstrap", "--db", "source.db"]); assert.equal(boot.exitCode, 0, boot.output); const orgId = /^organization: (org_[^ ]+)/m.exec(boot.output)?.[1]; assert.ok(orgId); const notBefore = new Date(Date.now() + 86_400_000).toISOString(); const expiresAt = new Date(Date.now() + 90_000_000).toISOString(); const created = run("key-lifecycle", [ "key", "create", "--db", "source.db", "--org-id", orgId, "--not-before", notBefore, "--expires-at", expiresAt, ]); assert.equal(created.exitCode, 0, created.output); const keyId = /^key_id: (key_[^\s]+)/m.exec(created.output)?.[1]; assert.ok(keyId); const listed = run("key-lifecycle", ["key", "list", "--db", "source.db"]); assert.equal(listed.exitCode, 0, listed.output); assert.match(listed.output, new RegExp(`${keyId} pending.*not_before=${notBefore}.*expires_at=${expiresAt}`)); const expiredBefore = new Date(Date.now() - 7_200_000).toISOString(); const expiredAt = new Date(Date.now() - 3_600_000).toISOString(); const expired = run("key-lifecycle", [ "key", "create", "--db", "source.db", "--org-id", orgId, "--not-before", expiredBefore, "--expires-at", expiredAt, ]); assert.equal(expired.exitCode, 0, expired.output); const expiredId = /^key_id: (key_[^\s]+)/m.exec(expired.output)?.[1]; assert.ok(expiredId); const relisted = run("key-lifecycle", ["key", "list", "--db", "source.db"]); assert.match(relisted.output, new RegExp(`${expiredId} expired`)); const invalid = run("key-lifecycle", [ "key", "create", "--db", "source.db", "--org-id", orgId, "--not-before", expiresAt, "--expires-at", notBefore, ]); assert.notEqual(invalid.exitCode, 0); assert.match(invalid.output, /--not-before must be earlier/u); const backup = run("key-lifecycle", [ "backup", "--db", "source.db", "--out", "restored.db", ]); assert.equal(backup.exitCode, 0, backup.output); const handle = openDatabase(join(root, "key-lifecycle", "restored.db"), { create: false }); try { const row = await createSqliteDb(handle).all<{ not_before: string; expires_at: string; last_used_at: string | null; }>("SELECT not_before, expires_at, last_used_at FROM api_keys WHERE id = ?", [keyId]); assert.deepEqual(row, [{ not_before: notBefore, expires_at: expiresAt, last_used_at: null, }]); } finally { handle.close(); } }); test("key create stores subject and project fences and key list prints them", () => { const boot = run("key-fences", ["bootstrap", "--db", "source.db"]); assert.equal(boot.exitCode, 0, boot.output); const orgId = /^organization: (org_[^ ]+)/m.exec(boot.output)?.[1]; assert.ok(orgId); const created = run("key-fences", [ "key", "create", "--db", "source.db", "--org-id", orgId, "--subjects", "castle:profile:alice,castle:shared", "--projects", "project_alice", "--read-subjects", "castle:profile:*", ]); assert.equal(created.exitCode, 0, created.output); const keyId = /^key_id: (key_[^\s]+)/m.exec(created.output)?.[1]; assert.ok(keyId); const listed = run("key-fences", ["key", "list", "--db", "source.db"]); assert.match(listed.output, new RegExp( `${keyId} .* read_projects=- read_subjects=castle:profile:\\* write_projects=project_alice write_subjects=castle:profile:alice,castle:shared`, )); const database = openDatabase(join(root, "key-fences", "source.db"), { create: false, readonly: true }); try { const fences = database.query( `SELECT access, target_type, pattern FROM api_key_fences WHERE key_id = ? ORDER BY access, target_type, pattern`, ).all(keyId) as { access: string; target_type: string; pattern: string }[]; assert.deepEqual(fences, [ { access: "read", target_type: "subject", pattern: "castle:profile:*" }, { access: "write", target_type: "project", pattern: "project_alice" }, { access: "write", target_type: "subject", pattern: "castle:profile:alice" }, { access: "write", target_type: "subject", pattern: "castle:shared" }, ]); const audit = database.query( `SELECT actor_id, detail FROM audit_log WHERE action = 'key.create' AND resource_id = ?`, ).get(keyId) as { actor_id: string; detail: string }; assert.equal(audit.actor_id, "cli"); const detail = JSON.parse(audit.detail) as { write_subjects: string[]; write_projects: string[] }; assert.deepEqual(detail.write_subjects, ["castle:profile:alice", "castle:shared"]); assert.deepEqual(detail.write_projects, ["project_alice"]); } finally { database.close(); } const conflict = run("key-fences", [ "key", "create", "--db", "source.db", "--org-id", orgId, "--subjects", "castle:shared", "--write-subjects", "castle:shared", ]); assert.notEqual(conflict.exitCode, 0); assert.match(conflict.output, /pass only one of --write-subjects and --subjects/); const printed = run("key-fence-sql", [ "key", "create", "--print-sql", "--org-id", "org_remote", "--subjects", "castle:profile:alice", ]); assert.equal(printed.exitCode, 0, printed.output); assert.match(printed.output, /INSERT INTO api_key_fences/); assert.match(printed.output, /castle:profile:alice/); assert.deepEqual(printed.files, []); }); test("local key administration fails closed for missing organizations and keys", () => { const missingCases = [ ["list", ["key", "list", "--db", "missing.db"]], ["revoke", ["key", "revoke", "--db", "missing.db", "--id", "key_missing"]], ["create", ["key", "create", "--db", "missing.db", "--org-id", "org_missing"]], ] as const; for (const [name, args] of missingCases) { const result = run(`key-missing-${name}`, [...args]); assert.notEqual(result.exitCode, 0); assert.match(result.output, /error: database does not exist: .*missing\.db/); assert.doesNotMatch(result.output, /SQLiteError|node_modules|src\/runtime|\n\s+at /); assert.deepEqual(result.files, []); } const boot = run("key-fail-closed", ["bootstrap", "--db", "keys.db"]); assert.equal(boot.exitCode, 0, boot.output); const ownerKey = /^key_id: (key_[^\s]+)/m.exec(boot.output)?.[1]; assert.ok(ownerKey); const path = join(root, "key-fail-closed", "keys.db"); const countKeys = () => { const database = openDatabase(path, { create: false, readonly: true }); try { return Number((database.query("SELECT COUNT(*) AS count FROM api_keys").get() as { count: number }).count); } finally { database.close(); } }; const before = countKeys(); const unknownOrganization = run("key-fail-closed", [ "key", "create", "--db", "keys.db", "--org-id", "org_missing", ]); assert.notEqual(unknownOrganization.exitCode, 0); assert.equal(unknownOrganization.output, "error: organization not found: org_missing\n"); assert.doesNotMatch(unknownOrganization.output, /api_key:|SQLiteError|\n\s+at /); assert.equal(countKeys(), before); const unknownKey = run("key-fail-closed", [ "key", "revoke", "--db", "keys.db", "--id", "key_missing", ]); assert.notEqual(unknownKey.exitCode, 0); assert.equal(unknownKey.output, "error: key not found: key_missing\n"); const revoked = run("key-fail-closed", [ "key", "revoke", "--db", "keys.db", "--id", ownerKey, ]); assert.equal(revoked.exitCode, 0, revoked.output); assert.equal(revoked.output, `revoked ${ownerKey}\n`); const repeated = run("key-fail-closed", [ "key", "revoke", "--db", "keys.db", "--id", ownerKey, ]); assert.notEqual(repeated.exitCode, 0); assert.equal(repeated.output, `error: key already revoked: ${ownerKey}\n`); }); test("backup refuses a missing source and atomically refreshes a fixed target", async () => { const missing = run("backup-missing", ["backup", "--db", "missing.db", "--out", "latest.db"]); assert.notEqual(missing.exitCode, 0); assert.match(missing.output, /error: database does not exist/); assert.doesNotMatch(missing.output, /SQLiteError|\n\s+at /); assert.deepEqual(missing.files, []); assert.equal(run("backup-repeat", ["bootstrap", "--db", "source.db", "--org", "First"]).exitCode, 0); const first = run("backup-repeat", ["backup", "--db", "source.db", "--out", "latest.db"]); assert.equal(first.exitCode, 0, first.output); assert.match(first.output, /backup verified:/); assert.equal(statSync(join(root, "backup-repeat", "latest.db")).mode & 0o777, 0o600); const sourcePath = join(root, "backup-repeat", "source.db"); const source = openDatabase(sourcePath, { create: false }); try { source.run( "INSERT INTO titen_migrations (version, applied_at) VALUES (?, ?)", [SCHEMA_VERSION + 1, "2026-07-31T00:00:00.000Z"], ); } finally { source.close(); } const failedRefresh = run("backup-repeat", ["backup", "--db", "source.db", "--out", "latest.db"]); assert.notEqual(failedRefresh.exitCode, 0); assert.match(failedRefresh.output, /error: backup failed: schema verification failed/); assert.doesNotMatch(failedRefresh.output, /SQLiteError|\n\s+at /); const preserved = openDatabase(join(root, "backup-repeat", "latest.db"), { create: false }); try { assert.equal((preserved.query("SELECT COUNT(*) AS count FROM organizations").get() as { count: number }).count, 1); } finally { preserved.close(); } const repaired = openDatabase(sourcePath, { create: false }); try { repaired.run("DELETE FROM titen_migrations WHERE version = ?", [SCHEMA_VERSION + 1]); } finally { repaired.close(); } assert.equal(run("backup-repeat", ["bootstrap", "--db", "source.db", "--org", "Second", "--username", "owner-second"]).exitCode, 0); const second = run("backup-repeat", ["backup", "--db", "source.db", "--out", "latest.db"]); assert.equal(second.exitCode, 0, second.output); assert.doesNotMatch(second.output, /SQLiteError|\n\s+at /); assert.equal(statSync(join(root, "backup-repeat", "latest.db")).mode & 0o777, 0o600); const copy = openDatabase(join(root, "backup-repeat", "latest.db"), { create: false }); try { assert.equal((copy.query("SELECT COUNT(*) AS count FROM organizations").get() as { count: number }).count, 2); assert.deepEqual(await schemaState(createSqliteDb(copy)), { applied: SCHEMA_VERSION, expected: SCHEMA_VERSION, verified: true, }); assert.equal((copy.query("PRAGMA integrity_check").get() as { integrity_check: string }).integrity_check, "ok"); } finally { copy.close(); } assert.ok(!readdirSync(join(root, "backup-repeat")).some((name) => name.includes(".tmp-"))); }); test("migrate dry-run is read-only and schema output is deterministic", () => { const fresh = run("dry-run-fresh", ["migrate", "--db", "missing.db", "--dry-run"]); assert.equal(fresh.exitCode, 0, fresh.output); assert.match(fresh.output, /-- migration 1/); assert.match(fresh.output, new RegExp(`-- ${MIGRATIONS.length} migration\\(s\\) pending; database unchanged`)); assert.deepEqual(fresh.files, []); assert.equal(run("dry-run-current", ["bootstrap", "--db", "current.db"]).exitCode, 0); const currentDirectory = join(root, "dry-run-current"); const currentPath = join(currentDirectory, "current.db"); chmodSync(currentPath, 0o400); const before = statSync(currentPath); chmodSync(currentDirectory, 0o500); let current: ReturnType; try { current = run("dry-run-current", ["migrate", "--db", "current.db", "--dry-run"]); } finally { chmodSync(currentDirectory, 0o700); } const after = statSync(currentPath); assert.equal(current.exitCode, 0, current.output); assert.match(current.output, /-- 0 migration\(s\) pending; database unchanged/); assert.equal(after.size, before.size); assert.equal(after.mtimeMs, before.mtimeMs); assert.equal(after.mode & 0o777, 0o400); const first = run("schema-deterministic", ["schema"]); const second = run("schema-deterministic", ["schema"]); assert.equal(first.output, second.output); assert.match(first.output, /INSERT OR IGNORE INTO titen_migrations/); assert.match(first.output, /1970-01-01T00:00:00\.000Z/); }); test("serve reports a port collision without a stack trace", async () => { const blocker = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("busy"), }); try { const boot = run("busy-port", ["bootstrap", "--db", "titen.db"]); assert.equal(boot.exitCode, 0, boot.output); const result = run("busy-port", ["serve", "--db", "titen.db", "--port", String(blocker.port)]); assert.notEqual(result.exitCode, 0); assert.equal(result.output, `error: port ${blocker.port} is already in use\n`); assert.doesNotMatch(result.output, /node_modules|src\/runtime|\bat\s/); } finally { await blocker.stop(true); } }); test("serve --quiet accepts requests without startup or access output", async () => { const reservation = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("reserved"), }); const port = reservation.port; await reservation.stop(true); const cwd = join(root, "quiet-serve"); mkdirSync(cwd); const boot = run("quiet-serve", ["bootstrap", "--db", "titen.db"]); assert.equal(boot.exitCode, 0, boot.output); const process = Bun.spawn({ cmd: ["bun", cli, "serve", "--db", "titen.db", "--port", String(port), "--quiet"], cwd, stdout: "pipe", stderr: "pipe", }); try { let response: Response | undefined; const deadline = Date.now() + 5_000; while (Date.now() < deadline) { try { response = await fetch(`http://127.0.0.1:${port}/healthz`); if (response.ok) break; } catch { await Bun.sleep(25); } } assert.equal(response?.status, 200, "quiet server never became healthy"); } finally { process.kill("SIGTERM"); await process.exited; } const output = `${await new Response(process.stdout).text()}${await new Response(process.stderr).text()}`; assert.equal(output, ""); }); test("bootstrap and serve share one stable per-user database across directories", async () => { const home = join(root, "stable-home"); const first = run("stable-cwd-a", ["bootstrap", "--org", "Stable Org"], undefined, { HOME: home }); assert.equal(first.exitCode, 0, first.output); const dbPath = join(home, ".titen", "service.db"); assert.match(first.output, new RegExp(`^database: ${dbPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m")); assert.ok(statSync(dbPath).isFile()); assert.equal(statSync(join(home, ".titen")).mode & 0o777, 0o700); const key = /^api_key: (titen_sk_[A-Za-z0-9_-]+)$/m.exec(first.output)?.[1]; assert.ok(key); const reservation = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("reserved") }); const port = reservation.port; await reservation.stop(true); const cwd = join(root, "stable-cwd-b"); mkdirSync(cwd, { recursive: true }); const serverProcess = Bun.spawn({ cmd: ["bun", cli, "serve", "--port", String(port), "--quiet"], cwd, env: { ...process.env, HOME: home }, stdout: "pipe", stderr: "pipe", }); try { let response: Response | undefined; const deadline = Date.now() + 5_000; while (Date.now() < deadline) { try { response = await fetch(`http://127.0.0.1:${port}/v1/observations`, { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify({ subject_id: "stable-default-store", kind: "system_event", content: "Bootstrap and serve resolved the same per-user store.", source: { type: "contract", ref: "stable-store#1" }, }), }); if (response.status !== 503) break; } catch { await Bun.sleep(25); } } assert.equal(response?.status, 201, await response?.text()); } finally { serverProcess.kill("SIGTERM"); await serverProcess.exited; } }); test("serve refuses a missing store and unflagged commands do not bypass a legacy cwd store", () => { const home = join(root, "missing-home"); const missing = run("missing-default-store", ["serve", "--quiet"], undefined, { HOME: home }); assert.notEqual(missing.exitCode, 0); assert.match(missing.output, /database does not exist: .*\.titen\/service\.db/); assert.match(missing.output, /titen bootstrap/); assert.deepEqual(missing.files, []); const legacyCwd = join(root, "legacy-default-store"); mkdirSync(legacyCwd, { recursive: true }); writeFileSync(join(legacyCwd, "titen.db"), "legacy-placeholder"); const legacy = run("legacy-default-store", ["bootstrap"], undefined, { HOME: join(root, "legacy-home") }); assert.notEqual(legacy.exitCode, 0); assert.match(legacy.output, /legacy database found at .*titen\.db/); assert.match(legacy.output, /rerun with --db .*titen\.db/); assert.deepEqual(readdirSync(legacyCwd), ["titen.db"]); }); test("host account recovery clears only its account and forces password rotation", async () => { const cwdName = "account-recovery"; const dbPath = join(root, cwdName, "service.db"); const bootstrap = run(cwdName, ["bootstrap", "--db", dbPath, "--username", "recovery-owner"]); assert.equal(bootstrap.exitCode, 0); const listed = run(cwdName, ["account", "list", "--db", dbPath]); assert.equal(listed.exitCode, 0, listed.output); assert.match(listed.output, /recovery-owner/); assert.match(listed.output, /passkey_count/); const handle = openDatabase(dbPath); const { sha256Hex } = await import("../../src/core/ids"); const key = await sha256Hex("dashboard-login:recovery-owner"); handle.run("INSERT INTO login_throttles VALUES (?, 5, ?, ?)", [key, Date.now() + 60_000, Date.now()]); handle.run("INSERT INTO login_throttles VALUES (?, 5, ?, ?)", [`${key}.client-a`, Date.now() + 60_000, Date.now()]); handle.run("INSERT INTO login_throttles VALUES ('other-account', 5, ?, ?)", [Date.now() + 60_000, Date.now()]); const unlocked = run(cwdName, ["account", "unlock", "--db", dbPath, "--username", "RECOVERY-OWNER"]); assert.equal(unlocked.exitCode, 0, unlocked.output); assert.equal((handle.query("SELECT COUNT(*) AS count FROM login_throttles").get() as { count: number }).count, 1); const account = handle.query("SELECT * FROM operator_accounts").get() as any; const { createApiKey } = await import("../../src/core/auth"); const session = await createApiKey({ orgId: account.org_id, principalId: account.principal_id, principalKind: "human", label: "Dashboard session", scopes: ["*"], maxTrust: "asserted" }); const agent = await createApiKey({ orgId: account.org_id, principalId: account.principal_id, principalKind: "agent", label: "Agent key", scopes: ["*"], maxTrust: "asserted" }); await createSqliteDb(handle).batch([session.statement, agent.statement]); const reset = run(cwdName, ["account", "reset-password", "--db", dbPath, "--username", "recovery-owner"]); assert.equal(reset.exitCode, 0, reset.output); const temporary = reset.output.match(/temporary_password: (\S+)/)?.[1]; assert.ok(temporary); assert.equal((handle.query("SELECT must_change_password FROM operator_accounts").get() as { must_change_password: number }).must_change_password, 1); assert.ok((handle.query("SELECT revoked_at FROM api_keys WHERE id = ?").get(session.id) as { revoked_at: string | null }).revoked_at); assert.equal((handle.query("SELECT revoked_at FROM api_keys WHERE id = ?").get(agent.id) as { revoked_at: string | null }).revoked_at, null); const { createApp } = await import("../../src/core/app"); const app = createApp({ db: createSqliteDb(handle), runtime: "test" }); const response = await app(new Request("http://test/v1/dashboard-sessions", { method: "POST", body: JSON.stringify({ username: "recovery-owner", password: temporary }) })); assert.equal(response.status, 201); assert.equal((await response.json() as any).data.auth_stage, "password_change"); handle.close(); assert.equal(run(cwdName, ["account", "unlock", "--db", dbPath, "--username", "missing-user"]).exitCode, 1); assert.equal(run(cwdName, ["account", "list", "--db", join(root, "absent-account.db")]).exitCode, 1); }); test("project commands set default visibility on a new and an existing project", () => { const boot = run("project-visibility", ["bootstrap", "--db", "service.db", "--org", "Castle"]); assert.equal(boot.exitCode, 0, boot.output); const orgId = /^organization: (org_[^ ]+)/m.exec(boot.output)?.[1]; assert.ok(orgId); const created = run("project-visibility", [ "project", "create", "--db", "service.db", "--org-id", orgId, "--reference", "github.com/Castle/Shared.git", "--default-visibility", "organization", ]); assert.equal(created.exitCode, 0, created.output); assert.match(created.output, /reference: castle\/shared/); assert.match(created.output, /default_visibility: organization/); const projectId = /^project_id: (project_\S+)/m.exec(created.output)?.[1]; assert.ok(projectId); const plain = run("project-visibility", [ "project", "create", "--db", "service.db", "--org-id", orgId, "--reference", "castle/plain", ]); assert.equal(plain.exitCode, 0, plain.output); assert.match(plain.output, /default_visibility: private/); const plainId = /^project_id: (project_\S+)/m.exec(plain.output)?.[1]; assert.ok(plainId); const updated = run("project-visibility", [ "project", "update", "--db", "service.db", "--org-id", orgId, "--reference", "castle/plain", "--default-visibility", "organization", ]); assert.equal(updated.exitCode, 0, updated.output); assert.match(updated.output, new RegExp(`project_id: ${plainId}`)); assert.match(updated.output, /default_visibility: organization/); const database = openDatabase(join(root, "project-visibility", "service.db")); const shared = database.query( "SELECT default_visibility FROM projects WHERE id = ?", ).get(projectId) as { default_visibility: string }; const changed = database.query( "SELECT default_visibility FROM projects WHERE id = ?", ).get(plainId) as { default_visibility: string }; assert.equal(shared.default_visibility, "organization"); assert.equal(changed.default_visibility, "organization"); database.close(); }); test("jobs ack clears readiness and principal reassign can preview private ownership", () => { const boot = run("ops-feedback", ["bootstrap", "--db", "titen.db", "--org", "Example"]); assert.equal(boot.exitCode, 0, boot.output); const dbPath = join(root, "ops-feedback", "titen.db"); const database = openDatabase(dbPath); const org = database.query("SELECT id FROM organizations LIMIT 1").get() as { id: string }; const hex = "ab".repeat(32); const now = "2026-01-01T00:00:00.000Z"; database.query( `INSERT INTO enrichment_jobs ( id, org_id, lane, derivation_key, input_ids, input_hash, subject_id, actor_id, model_id, model_fingerprint, prompt_fingerprint, schema_fingerprint, policy_fingerprint, state, attempts, max_attempts, next_attempt_at, created_at, updated_at ) VALUES (?, ?, 'derivation', ?, '[]', ?, 'team:shared', 'agent_old', 'fixture-model', ?, ?, ?, ?, 'failed', 4, 4, ?, ?, ?)`, ).run("job_failed", org.id, hex, hex, hex, hex, hex, hex, now, now, now); database.query( `INSERT INTO observations ( id, org_id, subject_id, actor_id, kind, content, content_hash, source_type, source_ref, trust, visibility, ingested_at ) VALUES (?, ?, 'team:shared', 'agent_old', 'user_statement', 'private note', ?, 'chat', 'note', 'asserted', 'private', ?)`, ).run("obs_private", org.id, hex, now); database.close(); const listed = run("ops-feedback", ["jobs", "list", "--db", "titen.db", "--status", "terminal_error"]); assert.equal(listed.exitCode, 0, listed.output); assert.match(listed.output, /job_failed/); const preview = run("ops-feedback", [ "principal", "reassign", "--db", "titen.db", "--org-id", org.id, "--from", "agent_old", "--to", "agent_a", "--dry-run", ]); assert.equal(preview.exitCode, 0, preview.output); assert.match(preview.output, /"dry_run":true/); assert.match(preview.output, /"observations":1/); const acked = run("ops-feedback", ["jobs", "ack", "--db", "titen.db", "--status", "terminal_error", "--id", "job_failed"]); assert.equal(acked.exitCode, 0, acked.output); assert.match(acked.output, /"matched":1/); const again = run("ops-feedback", ["jobs", "list", "--db", "titen.db", "--status", "failed"]); assert.match(again.output, /job_failed/); const moved = run("ops-feedback", [ "principal", "reassign", "--db", "titen.db", "--org-id", org.id, "--from", "agent_old", "--to", "agent_a", ]); assert.equal(moved.exitCode, 0, moved.output); const check = openDatabase(dbPath, { create: false, readonly: true }); try { const actor = check.query("SELECT actor_id FROM observations WHERE id = 'obs_private'").get() as { actor_id: string }; const ackedAt = check.query("SELECT acked_at FROM enrichment_jobs WHERE id = 'job_failed'").get() as { acked_at: string }; assert.equal(actor.actor_id, "agent_a"); assert.ok(ackedAt.acked_at); } finally { check.close(); } });