Files
Jyotisha/frontend/tests/rectification-pr4-database.test.ts
2026-08-15 00:56:06 +08:00

515 lines
21 KiB
TypeScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
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),
);
const migrationSql = readFileSync(
new URL("../supabase/migrations/20260814030000_rectification_event_decision_contract_v2.sql", import.meta.url),
"utf8",
);
function dockerAvailable(): boolean {
return spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], {
encoding: "utf8",
stdio: "ignore",
}).status === 0;
}
const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on this host";
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
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 candidateIds(value: unknown): string[] {
if (!value || typeof value !== "object") return [];
const rows = (value as { candidates?: unknown }).candidates;
if (!Array.isArray(rows)) return [];
return rows.flatMap((row) => {
if (!row || typeof row !== "object") return [];
const id = (row as { candidate_id?: unknown }).candidate_id;
return typeof id === "string" ? [id] : [];
});
}
function objectField(value: unknown, field: string): Record<string, unknown> {
if (!value || typeof value !== "object") return {};
const nested = (value as Record<string, unknown>)[field];
return nested && typeof nested === "object" && !Array.isArray(nested)
? (nested as Record<string, unknown>)
: {};
}
test("PR-4 migration binds receipts to server candidate UUIDs and restores V2 result fields", () => {
const persistStart = migrationSql.indexOf(
"create or replace function public.persist_agentic_rectification_candidate_v2(",
);
const caseStart = migrationSql.indexOf(
"create or replace function public.get_agentic_rectification_case(",
);
const dossierStart = migrationSql.indexOf(
"create or replace function public.get_agentic_rectification_case_dossier(",
);
const acceptStart = migrationSql.indexOf(
"create or replace function public.accept_agentic_rectification_candidate_for_case_v2(",
);
assert.ok(persistStart >= 0 && caseStart > persistStart && dossierStart > caseStart && acceptStart > dossierStart);
const persistFunction = migrationSql.slice(persistStart, caseStart);
assert.match(persistFunction, /p_decision_receipt - 'representative_candidate_id'/);
assert.match(persistFunction, /v_representative_candidate_id := v_candidate_id/);
assert.match(persistFunction, /\{representative_candidate_id\}/);
assert.match(persistFunction, /decision_receipt = v_saved_decision_receipt/);
assert.match(persistFunction, /'decision_receipt', v_saved_decision_receipt/);
assert.match(persistFunction, /'decision_receipt', v_cached\.decision_receipt/);
for (const functionSql of [
migrationSql.slice(caseStart, dossierStart),
migrationSql.slice(dossierStart, acceptStart),
]) {
for (const field of [
"event_contract_version",
"decision_policy_version",
"decision_receipt",
"execution_ledger",
"selected_candidate_id",
]) {
assert.match(functionSql, new RegExp(`'${field}', v_result\.${field}`));
}
}
const dossierFunction = migrationSql.slice(dossierStart, acceptStart);
assert.match(dossierFunction, /\(1, 'user'::text, recent\.user_message\)/);
assert.match(dossierFunction, /\(2, 'assistant'::text, recent\.assistant_message\)/);
});
test("PR-4 candidate decisions use server UUIDs, receipt-derived gates and separate acceptance/confirmation", { skip: skipWithoutDocker }, async () => {
const fixture = startPostgresFixture();
const schemaUrl = fixture.connectionUrl("schema_owner", "schema-owner-test-password");
const serviceUrl = fixture.connectionUrl("service_runtime", "service-runtime-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 20260814030000_rectification_event_decision_contract_v2\.sql/,
);
const userId = "10000000-0000-4000-8000-000000000001";
const sessionId = "20000000-0000-4000-8000-000000000001";
const secondSessionId = "20000000-0000-4000-8000-000000000002";
const caseId = "30000000-0000-4000-8000-000000000001";
const secondCaseId = "30000000-0000-4000-8000-000000000002";
const consentTurnId = "40000000-0000-4000-8000-000000000001";
const fakeCallerCandidateId = "50000000-0000-4000-8000-000000000001";
const fingerprint = "a".repeat(64);
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 range = { start_time: "04:50", end_time: "05:10" };
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
insert into identity.users (id, name, email, email_verified, email_verified_at)
values ('${userId}', 'PR4 Candidate Fixture', 'pr4-candidate@example.com', true, now())
`);
fixture.psql(`
update public.profiles
set birth_date = '1997-08-08',
reported_birth_time = '05:00',
active_birth_time = null,
birth_time = '05:00',
birth_time_status = 'reported',
birth_time_source = 'family_exact',
birth_time_period = null,
uncertainty_before_minutes = 10,
uncertainty_after_minutes = 10,
birth_place_label = '河北省邯郸市武安市',
latitude = 36.420487,
longitude = 114.209936,
timezone_id = 'Asia/Shanghai',
timezone_offset = 8
where id = '${userId}';
insert into public.chat_sessions (id, user_id, title, theme, session_type, messages)
values
('${sessionId}', '${userId}', 'PR4 candidate case', 'general', 'birth_time_rectification', '[]'::jsonb),
('${secondSessionId}', '${userId}', 'PR4 other case', 'general', 'birth_time_rectification', '[]'::jsonb);
insert into public.agentic_rectification_cases (
id, user_id, session_id, status, skill_name, skill_version,
baseline_profile_fingerprint, baseline_birth_snapshot, candidate_range
) values
(
'${caseId}', '${userId}', '${sessionId}', 'candidate_ready',
'jyotish-birth-time-rectification', '9.0.0', '${fingerprint}',
'${JSON.stringify(snapshot)}'::jsonb, '${JSON.stringify(range)}'::jsonb
),
(
'${secondCaseId}', '${userId}', '${secondSessionId}', 'candidate_ready',
'jyotish-birth-time-rectification', '9.0.0', '${"b".repeat(64)}',
'${JSON.stringify(snapshot)}'::jsonb, '${JSON.stringify(range)}'::jsonb
);
insert into public.agentic_rectification_turns (
id, case_id, request_id, user_message, assistant_message, status, model_name, completed_at
) values (
'${consentTurnId}', '${caseId}', '41000000-0000-4000-8000-000000000001',
'我确认使用05:07作为出生时间', '已记录确认请求', 'completed', 'fixture-model', now()
);
`);
const service = createLocalPostgresDataClient(serviceUrl, null, "service_role");
const candidates = [
{
candidate_id: fakeCallerCandidateId,
rank: 1,
time: "05:07",
relative_support: 60,
tied_minute_count: 1,
},
{ rank: 2, time: "05:08", relative_support: 40, tied_minute_count: 1 },
];
const decisionReceipt = {
display_allowed: true,
accept_allowed: true,
confirm_allowed: true,
representative_time: "05:07",
overall_confidence: "high",
margin_percent: 20,
representative_candidate_id: fakeCallerCandidateId,
};
const executionLedger = [
{ phase: "candidate.score", status: "completed", engine: "fixture-engine" },
{ phase: "decision.evaluate", status: "completed", policy: "decision-policy-v1" },
];
const persist = (engineResultId: string, policyVersion: string) =>
service.rpc("persist_agentic_rectification_candidate_v2", {
p_user_id: userId,
p_case_id: caseId,
p_engine_result_id: engineResultId,
p_evidence_ledger_fingerprint: "c".repeat(64),
p_candidate_range_fingerprint: "d".repeat(64),
p_skill_version: "9.0.0",
p_algorithm_version: "rectification-pr4-fixture-v1",
p_event_contract_version: "rectification-event-contract-v2",
p_decision_policy_version: policyVersion,
p_candidate_range: range,
p_candidates: candidates,
p_decision_receipt: decisionReceipt,
p_execution_ledger: executionLedger,
});
const first = await persist("pr4-engine-result-1", "decision-policy-v1");
assert.equal(first.error, null, rpcError(first.error));
const firstRow = first.data as Record<string, unknown>;
const firstResultId = String(firstRow.result_id);
const firstCandidateIds = candidateIds(firstRow);
assert.equal(firstRow.cached, false);
assert.equal(firstCandidateIds.length, 2);
assert.ok(firstCandidateIds.every((id) => UUID_PATTERN.test(id)));
assert.ok(!firstCandidateIds.includes(fakeCallerCandidateId));
assert.equal(
objectField(firstRow, "decision_receipt").representative_candidate_id,
firstCandidateIds[0],
);
const cached = await persist("pr4-engine-result-cache-ignored", "decision-policy-v1");
assert.equal(cached.error, null, rpcError(cached.error));
const cachedRow = cached.data as Record<string, unknown>;
assert.equal(cachedRow.cached, true);
assert.equal(cachedRow.result_id, firstResultId);
assert.deepEqual(candidateIds(cachedRow), firstCandidateIds);
assert.equal(
objectField(cachedRow, "decision_receipt").representative_candidate_id,
firstCandidateIds[0],
);
const changedPolicy = await persist("pr4-engine-result-2", "decision-policy-v2");
assert.equal(changedPolicy.error, null, rpcError(changedPolicy.error));
const changedPolicyRow = changedPolicy.data as Record<string, unknown>;
const resultId = String(changedPolicyRow.result_id);
const ids = candidateIds(changedPolicyRow);
const representativeCandidateId = ids[0]!;
const nonRepresentativeCandidateId = ids[1]!;
assert.equal(changedPolicyRow.cached, false);
assert.notEqual(resultId, firstResultId);
assert.equal(
objectField(changedPolicyRow, "decision_receipt").representative_candidate_id,
representativeCandidateId,
);
assert.equal(
fixture.psql(`
select (r.decision_receipt ->> 'representative_candidate_id') || ':' || c.id
from public.agentic_rectification_results r
join public.agentic_rectification_candidates c
on c.result_id = r.id and c.is_representative
where r.id = '${resultId}'
`),
`${representativeCandidateId}:${representativeCandidateId}`,
);
const restoredCase = await service.rpc("get_agentic_rectification_case", {
p_user_id: userId,
p_case_id: caseId,
});
assert.equal(restoredCase.error, null, rpcError(restoredCase.error));
const restoredCaseResult = objectField(restoredCase.data, "latest_result");
assert.deepEqual(
{
event_contract_version: restoredCaseResult.event_contract_version,
decision_policy_version: restoredCaseResult.decision_policy_version,
representative_candidate_id: objectField(
restoredCaseResult,
"decision_receipt",
).representative_candidate_id,
execution_ledger: restoredCaseResult.execution_ledger,
selected_candidate_id: restoredCaseResult.selected_candidate_id,
},
{
event_contract_version: "rectification-event-contract-v2",
decision_policy_version: "decision-policy-v2",
representative_candidate_id: representativeCandidateId,
execution_ledger: executionLedger,
selected_candidate_id: null,
},
);
const restoredDossier = await service.rpc("get_agentic_rectification_case_dossier", {
p_user_id: userId,
p_case_id: caseId,
});
assert.equal(restoredDossier.error, null, rpcError(restoredDossier.error));
const restoredDossierResult = objectField(restoredDossier.data, "latest_result");
assert.equal(restoredDossierResult.event_contract_version, "rectification-event-contract-v2");
assert.equal(restoredDossierResult.decision_policy_version, "decision-policy-v2");
assert.equal(
objectField(restoredDossierResult, "decision_receipt").representative_candidate_id,
representativeCandidateId,
);
assert.deepEqual(restoredDossierResult.execution_ledger, executionLedger);
assert.equal(restoredDossierResult.selected_candidate_id, null);
assert.deepEqual(
(restoredDossier.data as { turns?: Array<{ role?: unknown }> }).turns?.map(
(turn) => turn.role,
),
["user", "assistant"],
);
assert.equal(
fixture.psql(`
select event_contract_version || ':' || decision_policy_version || ':' ||
display_allowed || ':' || selection_allowed || ':' || confirmation_allowed || ':' ||
(decision_receipt ->> 'confirm_allowed') || ':' || jsonb_array_length(execution_ledger)
from public.agentic_rectification_results
where id = '${resultId}'
`),
"rectification-event-contract-v2:decision-policy-v2:true:true:true:true:2",
);
assert.equal(
fixture.psql(`
select
has_function_privilege('service_role', 'public.persist_agentic_rectification_candidate(uuid,uuid,text,text,text,text,text,jsonb,jsonb,text,numeric,boolean,boolean,time without time zone)', 'EXECUTE') || ':' ||
has_function_privilege('service_role', 'public.accept_agentic_rectification_candidate(uuid,uuid,uuid,time without time zone)', 'EXECUTE') || ':' ||
has_function_privilege('service_role', 'public.accept_agentic_rectification_candidate_for_case(uuid,uuid,uuid,time without time zone)', 'EXECUTE') || ':' ||
has_function_privilege('service_role', 'public.confirm_agentic_rectification_birth_time(uuid,uuid,uuid,time without time zone,text,uuid)', 'EXECUTE')
`),
"f:f:f:f",
);
const crossResult = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: firstResultId,
p_candidate_id: representativeCandidateId,
p_request_id: "60000000-0000-4000-8000-000000000001",
});
assert.match(rpcError(crossResult.error), /agentic_rectification_candidate_not_found/);
const crossCase = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: secondCaseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: "60000000-0000-4000-8000-000000000002",
});
assert.match(rpcError(crossCase.error), /agentic_rectification_candidate_not_found/);
const forged = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: "50000000-0000-4000-8000-000000000099",
p_request_id: "60000000-0000-4000-8000-000000000003",
});
assert.match(rpcError(forged.error), /agentic_rectification_candidate_not_found/);
const inexactConfirmation = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: nonRepresentativeCandidateId,
p_request_id: "70000000-0000-4000-8000-000000000001",
p_consent_quote: "确认使用05:07",
p_source_turn_id: consentTurnId,
});
assert.match(
rpcError(inexactConfirmation.error),
/agentic_rectification_confirmation_exact_gate_blocked/,
);
const acceptRequestId = "80000000-0000-4000-8000-000000000001";
const accepted = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: acceptRequestId,
});
assert.equal(accepted.error, null, rpcError(accepted.error));
assert.deepEqual(
{
status: (accepted.data as Record<string, unknown>).status,
case_status: (accepted.data as Record<string, unknown>).case_status,
saved_time: (accepted.data as Record<string, unknown>).saved_time,
candidate_id: (accepted.data as Record<string, unknown>).candidate_id,
},
{
status: "accepted",
case_status: "candidate_accepted",
saved_time: "05:07",
candidate_id: representativeCandidateId,
},
);
assert.equal(
fixture.psql(`
select active_birth_time || ':' || birth_time_status || ':' || selection_kind || ':' || selected_candidate_id
from public.profiles p
join public.agentic_rectification_results r on r.user_id = p.id
where r.id = '${resultId}'
`),
`05:07:00:accepted:user_accepted:${representativeCandidateId}`,
);
for (const rpcName of [
"get_agentic_rectification_case",
"get_agentic_rectification_case_dossier",
]) {
const restoredAfterAccept = await service.rpc(rpcName, {
p_user_id: userId,
p_case_id: caseId,
});
assert.equal(restoredAfterAccept.error, null, rpcError(restoredAfterAccept.error));
const latestResult = objectField(restoredAfterAccept.data, "latest_result");
assert.equal(latestResult.selected_candidate_id, representativeCandidateId);
}
const acceptReplay = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: acceptRequestId,
});
assert.equal(acceptReplay.error, null, rpcError(acceptReplay.error));
assert.equal((acceptReplay.data as Record<string, unknown>).idempotent, true);
const requestConflict = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: nonRepresentativeCandidateId,
p_request_id: acceptRequestId,
});
assert.match(
rpcError(requestConflict.error),
/agentic_rectification_candidate_request_conflict/,
);
const ungrounded = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: "90000000-0000-4000-8000-000000000001",
p_consent_quote: "这段话没有出现",
p_source_turn_id: consentTurnId,
});
assert.match(rpcError(ungrounded.error), /agentic_rectification_consent_not_grounded/);
const confirmRequestId = "90000000-0000-4000-8000-000000000002";
const confirmed = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: confirmRequestId,
p_consent_quote: "确认使用05:07",
p_source_turn_id: consentTurnId,
});
assert.equal(confirmed.error, null, rpcError(confirmed.error));
assert.deepEqual(
{
status: (confirmed.data as Record<string, unknown>).status,
case_status: (confirmed.data as Record<string, unknown>).case_status,
saved_time: (confirmed.data as Record<string, unknown>).saved_time,
},
{ status: "confirmed", case_status: "confirmed", saved_time: "05:07" },
);
assert.equal(
fixture.psql(`
select p.birth_time_status || ':' || r.selection_kind || ':' || c.status || ':' ||
c.confirmed_time || ':' || r.selected_candidate_id
from public.profiles p
join public.agentic_rectification_results r on r.user_id = p.id
join public.agentic_rectification_cases c on c.id = r.case_id
where r.id = '${resultId}'
`),
`confirmed:engine_confirmed:confirmed:05:07:00:${representativeCandidateId}`,
);
const confirmReplay = await service.rpc("confirm_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: representativeCandidateId,
p_request_id: confirmRequestId,
p_consent_quote: "确认使用05:07",
p_source_turn_id: consentTurnId,
});
assert.equal(confirmReplay.error, null, rpcError(confirmReplay.error));
assert.equal((confirmReplay.data as Record<string, unknown>).idempotent, true);
} finally {
try {
await closeLocalPostgresDataPool(serviceUrl);
} finally {
fixture.stop();
}
}
});