f708edf365
SQL kinds now match the TypeScript ledger so batch ingest can confirm dated events. Confirm no longer burns the opening focus, recap uses server date labels, and the Agent sees indistinguishable width instead of a fake unique minute. 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.1",
|
|
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();
|
|
}
|
|
}
|
|
});
|