import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { closeLocalPostgresDataPool, createLocalPostgresDataClient } from "../src/lib/db/local-postgres-client-core.ts"; import { startPostgresFixture } from "./helpers/postgres-fixture.ts"; const runnerPath = fileURLToPath( new URL("../scripts/db-migrate.mjs", import.meta.url), ); function dockerAvailable(): boolean { const probe = spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], { encoding: "utf8", stdio: "ignore", }); return probe.status === 0; } const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on this host"; function rpcError(error: unknown): string { if (!error || typeof error !== "object") return ""; const value = error as { message?: unknown }; return typeof value.message === "string" ? value.message : ""; } const snapshot = { birth_date: "1998-03-15", birth_place_label: "北京市", latitude: 39.9042, longitude: 116.4074, timezone_id: "Asia/Shanghai", timezone_offset: 8, birth_time_source: "unknown", reported_birth_time: null, active_birth_time: null, birth_time_period: null, uncertainty_before_minutes: null, uncertainty_after_minutes: null, }; const fingerprint = "b".repeat(64); const fullDay = { start_time: "00:00", end_time: "23:59" }; const skillName = "jyotish-birth-time-rectification"; const skillVersion = "9.0.0"; const blocks = { blocks: [ { period: "early_morning", start_time: "04:00", end_time: "07:59", relative_support: 12 }, { period: "morning", start_time: "08:00", end_time: "11:59", relative_support: 41 }, { period: "afternoon", start_time: "12:00", end_time: "17:59", relative_support: 28 }, { period: "evening", start_time: "18:00", end_time: "22:59", relative_support: 11 }, { period: "late_night", start_time: "23:00", end_time: "03:59", relative_support: 8 }, ], }; test("block_scan RPCs are service_role-only and advance a declared period", { skip: skipWithoutDocker }, async () => { const fixture = startPostgresFixture(); const schemaUrl = fixture.connectionUrl("schema_owner", "schema-owner-test-password"); try { const migration = spawnSync(process.execPath, [runnerPath], { encoding: "utf8", env: { ...process.env, SCHEMA_DATABASE_URL: schemaUrl }, }); assert.equal(migration.status, 0, migration.stderr); fixture.psqlAs( "identity_runtime", "identity-runtime-test-password", ` insert into identity.users (name, email, email_verified, email_verified_at) values ('Unknown', 'unknown-time@example.com', true, now()); `, ); const userId = fixture.psql( "select id from identity.users where email = 'unknown-time@example.com'", ); fixture.psql(` update public.profiles set birth_date = '1998-03-15', reported_birth_time = null, birth_time_source = 'unknown', latitude = 39.9042, longitude = 116.4074, timezone_offset = 8, birth_time_status = 'reported' where id = '${userId}'; `); fixture.psql(` with provider as ( insert into public.model_providers (code, name, provider_type, encrypted_api_key, enabled) values ('block-scan-test', 'Block Scan Test', 'openai', 'test-ciphertext', true) returning id ), config as ( insert into public.model_configs (model_id) values ('block-scan-default-model') returning id ) insert into public.model_config_versions ( config_id, version, provider_id, label, provider_model, enabled, is_default, status, published_at ) select config.id, 1, provider.id, 'Default', 'gpt-test', true, true, 'published', now() from config cross join provider; `); const service = createLocalPostgresDataClient( fixture.connectionUrl("service_runtime", "service-runtime-test-password"), null, "service_role", ); const opened = await service.rpc("open_agentic_rectification_case", { p_user_id: userId, p_request_id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", p_intent: "homepage", p_session_id: null, p_skill_name: skillName, p_skill_version: skillVersion, p_baseline_profile_fingerprint: fingerprint, p_baseline_birth_snapshot: snapshot, p_candidate_range: fullDay, }); assert.equal(opened.error, null, rpcError(opened.error)); const caseId = String((opened.data as Record).case_id); // BUG-984: identities belong to completed results, never deployment/start rows. // All rows are synthetic; the timestamp/id order, not the version text, wins. const turnId = fixture.psql(` insert into public.agentic_rectification_turns (case_id, status, model_name, assistant_message) values ('${caseId}', 'completed', 'fixture', 'fixture') returning id; `).split("\n")[0]; const receipt = async (owner = userId, turn = turnId) => service.rpc("get_agentic_rectification_turn_receipt", { p_user_id: owner, p_case_id: caseId, p_turn_id: turn, }); const addReceipt = (id: string, status: string, version: string | null, second: number, attempt: string | null = null, tool = "rectification-compare-candidates") => fixture.psql(` insert into public.agentic_rectification_tool_receipts (id, case_id, turn_id, tool_name, public_phase, status, engine_version, started_at, attempt_id) values ('${id}', '${caseId}', '${turnId}', '${tool}', 'candidates.updated', '${status}', ${version ? `'${version}'` : "null"}, '2026-09-20 00:00:${String(second).padStart(2, "0")}+00', ${attempt ? `'${attempt}'` : "null"}); `); const version = (n: number) => `rectification-v5-matrix-scoring-${n}`; addReceipt("10000000-0000-4000-8000-000000000001", "started", version(8), 1); addReceipt("10000000-0000-4000-8000-000000000002", "completed", version(7), 2); assert.equal((await receipt()).data?.engine_version, version(7)); addReceipt("10000000-0000-4000-8000-000000000003", "completed", version(9), 3); addReceipt("10000000-0000-4000-8000-000000000004", "completed", version(10), 4, null, "rectification-read-diagnostics"); addReceipt("10000000-0000-4000-8000-000000000005", "failed", version(99), 5); assert.equal((await receipt()).data?.engine_version, version(10)); // Same timestamp: stable id tie-break, independent of insertion/lexical version order. addReceipt("10000000-0000-4000-8000-000000000006", "completed", version(11), 4); assert.equal((await receipt()).data?.engine_version, version(11)); const attempt1 = fixture.psql(` insert into public.agentic_rectification_run_attempts (case_id, turn_id, attempt_number, status, completed_at) values ('${caseId}', '${turnId}', 1, 'completed', now()) returning id; `).split("\n")[0]; addReceipt("10000000-0000-4000-8000-000000000007", "completed", version(10), 6, attempt1); fixture.psql(`update public.agentic_rectification_turns set successful_attempt_id = '${attempt1}' where id = '${turnId}'`); const attempt2 = fixture.psql(` insert into public.agentic_rectification_run_attempts (case_id, turn_id, attempt_number, status, completed_at) values ('${caseId}', '${turnId}', 2, 'failed', now()) returning id; `).split("\n")[0]; addReceipt("10000000-0000-4000-8000-000000000008", "failed", version(99), 7, attempt2); assert.equal((await receipt()).data?.engine_version, version(10), "failed retry cannot replace successful attempt"); fixture.psql(`update public.agentic_rectification_turns set successful_attempt_id = null where id = '${turnId}'`); assert.equal((await receipt()).data?.engine_version, null, "latest failed-only attempt has no result provenance"); assert.match(rpcError((await receipt("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")).error), /case_not_found/); assert.match(rpcError((await receipt(userId, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")).error), /turn_not_found/); assert.equal(fixture.psql(`select engine_version from public.agentic_rectification_tool_receipts where id = '10000000-0000-4000-8000-000000000002'`), version(7), "historical rows are not relabelled"); assert.equal(fixture.psql(`select has_function_privilege('authenticated', 'public.get_agentic_rectification_turn_receipt(uuid,uuid,uuid)', 'EXECUTE')`), "f"); const staged = await service.rpc("set_agentic_rectification_case_stage", { p_user_id: userId, p_case_id: caseId, p_stage: "block_scan", }); assert.equal(staged.error, null, rpcError(staged.error)); const written = await service.rpc("write_agentic_rectification_block_scan", { p_user_id: userId, p_case_id: caseId, p_block_scan: blocks, }); assert.equal(written.error, null, rpcError(written.error)); const dossier = await service.rpc("get_agentic_rectification_case_dossier", { p_user_id: userId, p_case_id: caseId, }); assert.equal(dossier.error, null, rpcError(dossier.error)); const caseRow = (dossier.data as { case?: { stage?: string; block_scan?: unknown } }).case; assert.equal(caseRow?.stage, "block_scan"); assert.ok(caseRow?.block_scan); const advanced = await service.rpc("advance_agentic_rectification_case_from_block_scan", { p_user_id: userId, p_case_id: caseId, p_start_time: "08:00", p_end_time: "11:59", }); assert.equal(advanced.error, null, rpcError(advanced.error)); assert.equal((advanced.data as { stage?: string }).stage, "block_scan"); assert.deepEqual((advanced.data as { candidate_range?: unknown }).candidate_range, { start_time: "08:00", end_time: "11:59", }); assert.equal( fixture.psql(`select stage from public.agentic_rectification_cases where id = '${caseId}'`), "block_scan", ); const outside = await service.rpc("advance_agentic_rectification_case_from_block_scan", { p_user_id: userId, p_case_id: caseId, p_start_time: "04:50", p_end_time: "05:10", }); assert.match(rpcError(outside.error), /agentic_rectification_invalid_block_window/); const narrowed = await service.rpc("advance_agentic_rectification_case_from_block_scan", { p_user_id: userId, p_case_id: caseId, p_start_time: "08:00", p_end_time: "09:00", }); assert.equal(narrowed.error, null, rpcError(narrowed.error)); assert.equal((narrowed.data as { stage?: string }).stage, "minute"); const invalidPeriod = await service.rpc("advance_agentic_rectification_case_from_block_scan", { p_user_id: userId, p_case_id: caseId, p_start_time: "08:00", p_end_time: "09:00", }); assert.match(rpcError(invalidPeriod.error), /agentic_rectification_not_block_scan/); const widened = await service.rpc("widen_agentic_rectification_case_window", { p_user_id: userId, p_case_id: caseId, p_start_time: "07:30", p_end_time: "09:30", }); assert.equal(widened.error, null, rpcError(widened.error)); assert.deepEqual((widened.data as { candidate_range?: unknown }).candidate_range, { start_time: "07:30", end_time: "09:30", }); assert.equal( fixture.psql(`select adopted_credible_range is null from public.agentic_rectification_cases where id = '${caseId}'`), "t", ); const shrink = await service.rpc("widen_agentic_rectification_case_window", { p_user_id: userId, p_case_id: caseId, p_start_time: "08:00", p_end_time: "09:00", }); assert.match(rpcError(shrink.error), /agentic_rectification_invalid_widen_window/); const privileges = fixture.psql(` select concat_ws(':', has_function_privilege('service_role', 'public.set_agentic_rectification_case_stage(uuid,uuid,text)', 'EXECUTE'), has_function_privilege('anon', 'public.set_agentic_rectification_case_stage(uuid,uuid,text)', 'EXECUTE'), has_function_privilege('authenticated', 'public.write_agentic_rectification_block_scan(uuid,uuid,jsonb)', 'EXECUTE'), has_function_privilege('service_role', 'public.advance_agentic_rectification_case_from_block_scan(uuid,uuid,text,text)', 'EXECUTE'), has_function_privilege('service_role', 'public.widen_agentic_rectification_case_window(uuid,uuid,text,text)', 'EXECUTE'), has_function_privilege('anon', 'public.widen_agentic_rectification_case_window(uuid,uuid,text,text)', 'EXECUTE') ) `); assert.equal(privileges, "t:f:f:t:t:f"); } finally { try { await closeLocalPostgresDataPool( fixture.connectionUrl("service_runtime", "service-runtime-test-password"), ); } finally { fixture.stop(); } } });