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"; const snapshot = { birth_date: "1997-08-08", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8, birth_time_source: "family_exact", reported_birth_time: "05:00", active_birth_time: null, birth_time_period: null, uncertainty_before_minutes: 10, uncertainty_after_minutes: 10, }; const fingerprint = "a".repeat(64); const range = { start_time: "04:50", end_time: "05:10" }; function rpcError(error: unknown): string { if (!error || typeof error !== "object") return ""; const value = error as { message?: unknown }; return typeof value.message === "string" ? value.message : ""; } function asObject(value: unknown): Record { if (typeof value === "string") return JSON.parse(value) as Record; if (value && typeof value === "object") return value as Record; return {}; } test("ingest P0: education kinds, batch confirm, opening focus reuse, precision lock", { 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); assert.match(migration.stdout, /applied 20260819010000_rectification_ingest_precision_plateau\.sql/); fixture.psqlAs( "identity_runtime", "identity-runtime-test-password", ` insert into identity.users (name, email, email_verified, email_verified_at) values ('Ingest', 'ingest-p0@example.com', true, now()) `, ); const userId = fixture.psql( "select id from identity.users where email = 'ingest-p0@example.com'", ); fixture.psql(` update public.profiles set birth_date = '1997-08-08', reported_birth_time = '05:00', birth_time_source = 'family_exact', latitude = 36.420487, longitude = 114.209936, timezone_offset = 8, birth_time_status = 'reported' where id = '${userId}'; `); 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: "jyotish-birth-time-rectification", p_skill_version: "10.0.3", p_baseline_profile_fingerprint: fingerprint, p_baseline_birth_snapshot: snapshot, p_candidate_range: range, }); assert.equal(opened.error, null, rpcError(opened.error)); const caseId = String(asObject(opened.data).case_id); const turn = await service.rpc("append_agentic_rectification_turn", { p_user_id: userId, p_case_id: caseId, p_user_message: "2016年6月高考结束,2016年9月大学入学", p_assistant_message: "已记下这两件教育经历。", p_model_name: "test-model", p_model_version: "1", p_status: "completed", p_request_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", }); assert.equal(turn.error, null, rpcError(turn.error)); const turnId = String(asObject(turn.data).turn_id); const batch = await service.rpc("record_agentic_rectification_evidence_batch", { p_user_id: userId, p_case_id: caseId, p_source_turn_id: turnId, p_focus_id: null, p_items: [ { idempotency_key: "ingest-gaokao", quote: "2016年6月高考结束", subject: "self", event_kind: "education_milestone", domain: "education", occurred_from: "2016-06-01", occurred_to: null, date_precision: "month", summary: "2016年6月高考结束", }, { idempotency_key: "ingest-university", quote: "2016年9月大学入学", subject: "self", event_kind: "education_milestone", domain: "education", occurred_from: "2016-09-01", occurred_to: null, date_precision: "month", summary: "2016年9月大学入学", }, ], }); assert.equal(batch.error, null, rpcError(batch.error)); const batchRow = asObject(batch.data); assert.equal(batchRow.accepted_count, 2); assert.equal(batchRow.rejected_count, 0); const batchItems = Array.isArray(batchRow.items) ? batchRow.items as Record[] : []; assert.equal(batchItems.length, 2); assert.equal(batchItems[0]?.outcome, "accepted"); assert.equal(batchItems[0]?.status, "confirmed"); assert.equal(batchItems[1]?.outcome, "accepted"); assert.equal(batchItems[1]?.status, "confirmed"); const secondTurn = await service.rpc("append_agentic_rectification_turn", { p_user_id: userId, p_case_id: caseId, p_user_message: "2016年6月高考结束,2016年9月大学入学,另外2014年8月8日搬家", p_assistant_message: "继续核对这几件经历。", p_model_name: "test-model", p_model_version: "1", p_status: "completed", p_request_id: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", }); assert.equal(secondTurn.error, null, rpcError(secondTurn.error)); const secondTurnId = String(asObject(secondTurn.data).turn_id); const openingFocus = await service.rpc("set_agentic_rectification_conversation_focus", { p_user_id: userId, p_case_id: caseId, p_question_id: "opening", p_intent: "collect_first_event", p_target_evidence_id: null, p_target_domain: null, p_target_kind: null, p_expected_answer_schema: {}, }); assert.equal(openingFocus.error, null, rpcError(openingFocus.error)); const focusPayload = asObject(openingFocus.data); const focusId = String(focusPayload.focus_id ?? asObject(focusPayload.focus).id); assert.match(focusId, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i); const firstDraft = await service.rpc("propose_agentic_rectification_evidence", { p_user_id: userId, p_case_id: caseId, p_source_turn_id: secondTurnId, p_user_quote: "2016年6月高考结束", p_subject: "self", p_event_kind: "education_milestone", p_domain: "education", p_occurred_from: "2016-06-01", p_occurred_to: null, p_date_precision: "month", p_summary: "2016年6月高考结束", }); assert.equal(firstDraft.error, null, rpcError(firstDraft.error)); assert.equal(asObject(firstDraft.data).outcome, "accepted"); const firstEvidenceId = String(asObject(firstDraft.data).evidence_id); const secondDraft = await service.rpc("propose_agentic_rectification_evidence", { p_user_id: userId, p_case_id: caseId, p_source_turn_id: secondTurnId, p_user_quote: "2016年9月大学入学", p_subject: "self", p_event_kind: "education_milestone", p_domain: "education", p_occurred_from: "2016-09-01", p_occurred_to: null, p_date_precision: "month", p_summary: "2016年9月大学入学", }); assert.equal(secondDraft.error, null, rpcError(secondDraft.error)); const secondEvidenceId = String(asObject(secondDraft.data).evidence_id); const firstConfirm = await service.rpc("confirm_agentic_rectification_evidence_v10", { p_user_id: userId, p_case_id: caseId, p_focus_id: focusId, p_evidence_id: firstEvidenceId, }); assert.equal(firstConfirm.error, null, rpcError(firstConfirm.error)); assert.equal( fixture.psql(`select status from public.agentic_rectification_conversation_focuses where id = '${focusId}'`), "active", ); const secondConfirm = await service.rpc("confirm_agentic_rectification_evidence_v10", { p_user_id: userId, p_case_id: caseId, p_focus_id: focusId, p_evidence_id: secondEvidenceId, }); assert.equal(secondConfirm.error, null, rpcError(secondConfirm.error)); const dayDraft = await service.rpc("propose_agentic_rectification_evidence", { p_user_id: userId, p_case_id: caseId, p_source_turn_id: secondTurnId, p_user_quote: "2014年8月8日搬家", p_subject: "self", p_event_kind: "relocation", p_domain: "relocation", p_occurred_from: "2014-08-08", p_occurred_to: null, p_date_precision: "day", p_summary: "2014年8月8日搬家", }); assert.equal(dayDraft.error, null, rpcError(dayDraft.error)); const dayEvidenceId = String(asObject(dayDraft.data).evidence_id); const dayConfirm = await service.rpc("confirm_agentic_rectification_evidence_v10", { p_user_id: userId, p_case_id: caseId, p_focus_id: null, p_evidence_id: dayEvidenceId, }); assert.equal(dayConfirm.error, null, rpcError(dayConfirm.error)); assert.equal( fixture.psql(`select date_precision from public.agentic_rectification_evidence where id = '${dayEvidenceId}'`), "day", ); const coarsen = await service.rpc("revise_agentic_rectification_evidence", { p_user_id: userId, p_case_id: caseId, p_evidence_id: dayEvidenceId, p_user_quote: "是", p_occurred_from: "2014-01-01", p_occurred_to: null, p_date_precision: "year", p_summary: "改成年级", }); assert.match(rpcError(coarsen.error), /agentic_rectification_precision_downgrade/); assert.equal( fixture.psql(`select date_precision from public.agentic_rectification_evidence where id = '${dayEvidenceId}'`), "day", ); } finally { try { await closeLocalPostgresDataPool( fixture.connectionUrl("service_runtime", "service-runtime-test-password"), ); } finally { fixture.stop(); } } });