7c7a25773c
Adjacent minutes still cannot confirm a unique birth minute. A scored representative time can now be accepted, and the copy must say the minute is not unique. Co-authored-by: Cursor <cursoragent@cursor.com>
289 lines
10 KiB
TypeScript
289 lines
10 KiB
TypeScript
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<string, unknown> {
|
|
if (typeof value === "string") return JSON.parse(value) as Record<string, unknown>;
|
|
if (value && typeof value === "object") return value as Record<string, unknown>;
|
|
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<string, unknown>[] : [];
|
|
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();
|
|
}
|
|
}
|
|
});
|