// RFC-026 v4 P1 — unit tests for state + sweeper (covers H + J live- // test deferment per 通信牛 PR #299 review). These exercise the same // pure functions the daemon-facing MCP tools call into; the e2e // scenarios H/J would only re-verify these same code paths through // multi-daemon or crash-sim plumbing, which is Phase 3 work. import { describe, expect, test, beforeEach, afterAll } from "bun:test"; import { db } from "./db.js"; import { putPendingEnvBlob, takePendingEnvBlob, peekPendingEnvBlob, runOrphanSweepOnce, evictExpired, finalizeCreateOnFirstRegister, newRequestId, stopBackgroundTimersForTest, resolveCallerDaemonTokenBound, } from "./create-node.js"; import { hashToken } from "./db.js"; // ── helpers ──────────────────────────────────────────────────────── function freshFixtures() { // Wipe RFC-026 state between tests. Other suites use the same DB; // we only delete from tables RFC-026 owns. db.run("DELETE FROM node_create_requests"); db.run("DELETE FROM api_tokens WHERE role = 'child' OR role = 'test_child' OR (request_id IS NOT NULL AND request_id LIKE 'cr_test_%')"); stopBackgroundTimersForTest(); } function insertRequestRow( request_id: string, daemon_node_id: string, child_name: string, status: "pending" | "delivered" | "succeeded" | "failed" | "expired", created_at_ms: number, child_token_id: string | null = null, network_id: string = "test_net", ) { db.run( `INSERT INTO node_create_requests (request_id, daemon_node_id, child_name, network_id, runtime, model, flags_json, env_keys, status, child_token_id, created_at, created_by_token) VALUES (?1, ?2, ?3, ?4, 'claude-agent-sdk', 'x', '{}', '[]', ?5, ?6, ?7, 'tok_test')`, [request_id, daemon_node_id, child_name, network_id, status, child_token_id, created_at_ms], ); } function insertChildTokenRow(token_id: string, request_id: string) { db.run( `INSERT INTO api_tokens (token_id, token_hash, user_id, network_id, name, scope, role, request_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, 'child', ?7)`, [token_id, `hash_${token_id}`, "u_test_creator", "test_net", `node:test_child_${token_id}`, "network", request_id], ); } // ── tests ────────────────────────────────────────────────────────── beforeEach(() => freshFixtures()); afterAll(() => stopBackgroundTimersForTest()); describe("§4.4 F1 mint-stream-evict — pendingEnvBlobs Map (covers H C2)", () => { test("takePendingEnvBlob with WRONG daemon_node_id returns null (C2 cross-daemon guard)", () => { const id = newRequestId(); putPendingEnvBlob({ request_id: id, daemon_node_id: "node_daemon_A", env_blob: { ANTHROPIC_API_KEY: "secret-A" }, child_token: "ntok_test_child_A", child_token_id: "tok_test_child_A", }); // daemonB attempts to take daemonA's request → null expect(takePendingEnvBlob(id, "node_daemon_B")).toBeNull(); // But the entry IS NOT consumed by the wrong-caller attempt — daemonA // can still take it. (Otherwise an attacker could DoS legit dispatch // by spamming wrong-daemon takes.) const blob = takePendingEnvBlob(id, "node_daemon_A"); expect(blob).not.toBeNull(); expect(blob!.env_blob).toEqual({ ANTHROPIC_API_KEY: "secret-A" }); expect(blob!.child_token).toBe("ntok_test_child_A"); }); test("takePendingEnvBlob with RIGHT daemon_node_id evicts immediately (F1 one-shot consume)", () => { const id = newRequestId(); putPendingEnvBlob({ request_id: id, daemon_node_id: "node_daemon_X", env_blob: { K1: "v1" }, child_token: "ntok_x", child_token_id: "tok_x", }); expect(peekPendingEnvBlob(id)).not.toBeNull(); expect(takePendingEnvBlob(id, "node_daemon_X")).not.toBeNull(); // Second take is null — entry was evicted on first successful take expect(takePendingEnvBlob(id, "node_daemon_X")).toBeNull(); expect(peekPendingEnvBlob(id)).toBeNull(); }); test("takePendingEnvBlob with unknown id returns null", () => { expect(takePendingEnvBlob("cr_never_existed", "node_anything")).toBeNull(); }); test("evictExpired drops entries past TTL", () => { const id = newRequestId(); putPendingEnvBlob({ request_id: id, daemon_node_id: "node_z", env_blob: {}, child_token: "ntok_z", child_token_id: "tok_z", }); // Fast-forward past 60s TTL const past = Date.now() + 60_001; expect(evictExpired(past)).toBeGreaterThan(0); expect(takePendingEnvBlob(id, "node_z")).toBeNull(); }); }); describe("§4.4.8 C4 orphan sweeper — runOrphanSweepOnce (covers J F-1 + F-2)", () => { test("F-1: status='pending' age > TTL → revoke child-ntok + mark status='failed'", () => { const reqId = `cr_test_${Date.now()}_F1`; const tokId = `tok_test_${Date.now()}_F1`; const longAgo = Date.now() - 90_000; // 90s past — well > 60s TTL insertChildTokenRow(tokId, reqId); insertRequestRow(reqId, "node_daemon_F1", "child_F1", "pending", longAgo, tokId); const result = runOrphanSweepOnce(); expect(result.swept).toBeGreaterThanOrEqual(1); expect(result.revoked).toBeGreaterThanOrEqual(1); // Verify api_tokens.revoked_at is now set const tokRow = db.get<{ revoked_at: string | null }>( "SELECT revoked_at FROM api_tokens WHERE token_id = ?1", tokId, ); expect(tokRow?.revoked_at).not.toBeNull(); // Verify request status flipped to 'failed' const reqRow = db.get<{ status: string; error: string | null }>( "SELECT status, error FROM node_create_requests WHERE request_id = ?1", reqId, ); expect(reqRow?.status).toBe("failed"); expect(reqRow?.error).toContain("sweeper_revoked_before_delivery"); }); test("F-2: status='delivered' age > TTL → revoke child-ntok + mark status='expired'", () => { const reqId = `cr_test_${Date.now()}_F2`; const tokId = `tok_test_${Date.now()}_F2`; const longAgo = Date.now() - 120_000; // 120s past insertChildTokenRow(tokId, reqId); insertRequestRow(reqId, "node_daemon_F2", "child_F2", "delivered", longAgo, tokId); const result = runOrphanSweepOnce(); expect(result.swept).toBeGreaterThanOrEqual(1); expect(result.revoked).toBeGreaterThanOrEqual(1); const tokRow = db.get<{ revoked_at: string | null }>( "SELECT revoked_at FROM api_tokens WHERE token_id = ?1", tokId, ); expect(tokRow?.revoked_at).not.toBeNull(); const reqRow = db.get<{ status: string; error: string | null }>( "SELECT status, error FROM node_create_requests WHERE request_id = ?1", reqId, ); expect(reqRow?.status).toBe("expired"); expect(reqRow?.error).toContain("sweeper_revoked_after_delivery_no_ack"); }); test("does NOT sweep recent pending/delivered rows (within TTL)", () => { const reqId = `cr_test_${Date.now()}_fresh`; const tokId = `tok_test_${Date.now()}_fresh`; insertChildTokenRow(tokId, reqId); // created 5s ago — well within 60s TTL insertRequestRow(reqId, "node_daemon_fresh", "child_fresh", "pending", Date.now() - 5_000, tokId); const result = runOrphanSweepOnce(); expect(result.swept).toBe(0); expect(result.revoked).toBe(0); const tokRow = db.get<{ revoked_at: string | null }>( "SELECT revoked_at FROM api_tokens WHERE token_id = ?1", tokId, ); expect(tokRow?.revoked_at).toBeNull(); const reqRow = db.get<{ status: string }>( "SELECT status FROM node_create_requests WHERE request_id = ?1", reqId, ); expect(reqRow?.status).toBe("pending"); }); test("does NOT sweep terminal rows (succeeded/failed/expired)", () => { const reqId = `cr_test_${Date.now()}_terminal`; const tokId = `tok_test_${Date.now()}_terminal`; insertChildTokenRow(tokId, reqId); insertRequestRow(reqId, "node_daemon_t", "child_t", "succeeded", Date.now() - 120_000, tokId); const result = runOrphanSweepOnce(); expect(result.swept).toBe(0); expect(result.revoked).toBe(0); const tokRow = db.get<{ revoked_at: string | null }>( "SELECT revoked_at FROM api_tokens WHERE token_id = ?1", tokId, ); expect(tokRow?.revoked_at).toBeNull(); }); test("sweeper is idempotent — second run on same stale row is a no-op", () => { const reqId = `cr_test_${Date.now()}_idem`; const tokId = `tok_test_${Date.now()}_idem`; insertChildTokenRow(tokId, reqId); insertRequestRow(reqId, "node_daemon_i", "child_i", "pending", Date.now() - 120_000, tokId); const r1 = runOrphanSweepOnce(); expect(r1.swept).toBe(1); const r2 = runOrphanSweepOnce(); expect(r2.swept).toBe(0); // already terminal after first sweep }); }); describe("§4.1.4 C2 token-bound daemon resolution regression (PR #299 BLOCKER #1)", () => { // Calls the REAL resolveCallerDaemonTokenBound helper exported from // create-node.ts — same code path the daemon-facing MCP tools take // (per 通信龙 PR #299 nit 1, no inline-mirror SQL: anti-pattern that // would let helper-drift slip past). Setup: two daemons same alias // in different networks; two ntoks bound to each; confirm each // ntok's resolution returns ITS OWN node row (not the other). test("two daemons same alias different networks: helper resolves token to correct node", () => { db.run("DELETE FROM nodes WHERE alias = 'daemon-shared'"); db.run("DELETE FROM api_tokens WHERE token_id IN ('tok_test_A', 'tok_test_B')"); // daemonA in netA db.run( `INSERT INTO nodes (node_id, node_name, alias, runtime, model, network_id, updated_at) VALUES ('node_test_daemonA', 'daemon-shared', 'daemon-shared', 'claude-agent-sdk', 'm', 'net_test_A', datetime('now'))`, ); // daemonB in netB — SAME alias db.run( `INSERT INTO nodes (node_id, node_name, alias, runtime, model, network_id, updated_at) VALUES ('node_test_daemonB', 'daemon-shared', 'daemon-shared', 'claude-agent-sdk', 'm', 'net_test_B', datetime('now'))`, ); // ntok rows bound to each daemon (name='node:daemon-shared'; network_id distinguishes) db.run( `INSERT INTO api_tokens (token_id, token_hash, user_id, network_id, name, scope) VALUES ('tok_test_A', ?1, 'u_test_creator', 'net_test_A', 'node:daemon-shared', 'network')`, [hashToken("ntok_test_A")], ); db.run( `INSERT INTO api_tokens (token_id, token_hash, user_id, network_id, name, scope) VALUES ('tok_test_B', ?1, 'u_test_creator', 'net_test_B', 'node:daemon-shared', 'network')`, [hashToken("ntok_test_B")], ); // ntok A → must resolve to node_test_daemonA (NOT daemonB) const resA = resolveCallerDaemonTokenBound({ callerTokenIsNetwork: true, callerTokenId: "tok_test_A", enforceNetworkId: "net_test_A", }); expect(resA.ok).toBe(true); if (resA.ok) { expect(resA.daemonNodeId).toBe("node_test_daemonA"); expect(resA.networkId).toBe("net_test_A"); } // ntok B → must resolve to node_test_daemonB const resB = resolveCallerDaemonTokenBound({ callerTokenIsNetwork: true, callerTokenId: "tok_test_B", enforceNetworkId: "net_test_B", }); expect(resB.ok).toBe(true); if (resB.ok) expect(resB.daemonNodeId).toBe("node_test_daemonB"); // Pre-blocker buggy form (alias only) would be ambiguous — // confirm there really ARE 2 rows so the SQL was un-scoped before. const all = db.all<{ node_id: string }>( `SELECT node_id FROM nodes WHERE alias = ?1`, "daemon-shared", ); expect(all.length).toBe(2); // Mismatched scope: tok_test_A presented with enforceNetworkId=B // (compromised caller trying to pivot to wrong net) → reject. const resCross = resolveCallerDaemonTokenBound({ callerTokenIsNetwork: true, callerTokenId: "tok_test_A", enforceNetworkId: "net_test_B", }); expect(resCross.ok).toBe(false); // utok (callerTokenIsNetwork=false) → reject as not-a-daemon const resUtok = resolveCallerDaemonTokenBound({ callerTokenIsNetwork: false, callerTokenId: "tok_test_A", enforceNetworkId: "net_test_A", }); expect(resUtok.ok).toBe(false); // Revoked token → reject db.run("UPDATE api_tokens SET revoked_at = datetime('now') WHERE token_id = 'tok_test_A'"); const resRevoked = resolveCallerDaemonTokenBound({ callerTokenIsNetwork: true, callerTokenId: "tok_test_A", enforceNetworkId: "net_test_A", }); expect(resRevoked.ok).toBe(false); db.run("DELETE FROM nodes WHERE node_id IN ('node_test_daemonA', 'node_test_daemonB')"); db.run("DELETE FROM api_tokens WHERE token_id IN ('tok_test_A', 'tok_test_B')"); }); test("daemon B cannot take/ack daemon A's request even with same alias (Map-level guard)", () => { const id = newRequestId(); putPendingEnvBlob({ request_id: id, daemon_node_id: "node_legit_daemonA", env_blob: { ANTHROPIC_API_KEY: "s" }, child_token: "ntok_a", child_token_id: "tok_a", }); // daemonB-token resolves to node_id = node_attacker (different node_id) expect(takePendingEnvBlob(id, "node_attacker")).toBeNull(); // Original entry still there for daemonA expect(takePendingEnvBlob(id, "node_legit_daemonA")).not.toBeNull(); }); }); describe("finalizeCreateOnFirstRegister — content-match on child register", () => { test("matching child_name + network_id flips status='succeeded' + stamps child_node_id", () => { const reqId = `cr_test_${Date.now()}_finalize`; insertRequestRow(reqId, "node_daemon_F", "child_finalize_test", "delivered", Date.now() - 1_000, null, "test_net_finalize"); const r = finalizeCreateOnFirstRegister({ node_id: "node_child_real_F", alias: "child_finalize_test", network_id: "test_net_finalize", }); expect(r.finalized).toBe(1); const row = db.get<{ status: string; child_node_id: string | null }>( "SELECT status, child_node_id FROM node_create_requests WHERE request_id = ?1", reqId, ); expect(row?.status).toBe("succeeded"); expect(row?.child_node_id).toBe("node_child_real_F"); }); test("non-matching alias is a no-op", () => { const reqId = `cr_test_${Date.now()}_nomatch`; insertRequestRow(reqId, "node_daemon_nm", "expected_alias", "pending", Date.now(), null, "test_net_nm"); const r = finalizeCreateOnFirstRegister({ node_id: "node_intruder", alias: "different_alias", network_id: "test_net_nm", }); expect(r.finalized).toBe(0); const row = db.get<{ status: string }>( "SELECT status FROM node_create_requests WHERE request_id = ?1", reqId, ); expect(row?.status).toBe("pending"); }); test("cross-network: same child_name in different network is a no-op (network_id mismatch)", () => { const reqId = `cr_test_${Date.now()}_crossnet`; insertRequestRow(reqId, "node_daemon_xn", "shared_name", "delivered", Date.now(), null, "test_net_A"); const r = finalizeCreateOnFirstRegister({ node_id: "node_evil", alias: "shared_name", network_id: "test_net_B", // different network }); expect(r.finalized).toBe(0); }); });