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>
214 lines
8.5 KiB
TypeScript
214 lines
8.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
SEALED_MINUTE_HOLDOUT,
|
|
buildConfirmationGate,
|
|
readVedastroMinuteSensitiveStatus,
|
|
sessionOutcomeFromGate,
|
|
} from "../src/lib/rectification-agentic/v9/confirmation-gate.ts";
|
|
import { RECTIFICATION_POLICY } from "../src/lib/rectification-policy.ts";
|
|
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.ts";
|
|
import {
|
|
createRectificationV9Tools,
|
|
latestResultToolProjection,
|
|
} from "../src/mastra/rectification-v9-tools.ts";
|
|
import { PUBLIC_RECTIFICATION_TOOLS } from "../src/lib/rectification-agentic/v9/public-receipt.ts";
|
|
import { safeToolErrorCode, RectificationToolServiceError } from "../src/lib/rectification-agentic/v9/tool-service.ts";
|
|
import {
|
|
CASE_ID,
|
|
CANDIDATE_ID,
|
|
RESULT_ID,
|
|
SECOND_CANDIDATE_ID,
|
|
TURN_ID,
|
|
USER_ID,
|
|
candidateSnapshotFixture,
|
|
dossierFixture,
|
|
fakeAccounting,
|
|
receiptHandlers,
|
|
} from "./rectification-v9-test-support.ts";
|
|
|
|
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
|
|
const holdoutReport = JSON.parse(readFileSync(
|
|
new URL("../../references/real_case_calibration/minute_rectification_holdout_v2_pilot_report.json", import.meta.url),
|
|
"utf8",
|
|
)) as {
|
|
case_gate: { valid_public_aa_cases: number; minimum_public_aa_cases: number };
|
|
metrics: { top_1_rate: number; confirmation_coverage_rate: number };
|
|
};
|
|
const skill = readFileSync(
|
|
new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const candidateComparison = readFileSync(
|
|
new URL("../../skills/jyotish-birth-time-rectification/references/candidate-comparison.md", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const agentSource = readFileSync(
|
|
new URL("../src/mastra/agentic-rectification.ts", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
const UNIQUE_MINUTE = [
|
|
{ candidateId: CANDIDATE_ID, time: "05:02", rank: 1, relativeSupport: 70, tiedMinuteCount: 1 },
|
|
{ candidateId: SECOND_CANDIDATE_ID, time: "05:03", rank: 2, relativeSupport: 30, tiedMinuteCount: 2 },
|
|
];
|
|
|
|
const PLATEAU = [
|
|
{ candidateId: CANDIDATE_ID, time: "04:45", rank: 1, relativeSupport: 40, tiedMinuteCount: 25 },
|
|
{ candidateId: SECOND_CANDIDATE_ID, time: "04:46", rank: 2, relativeSupport: 35, tiedMinuteCount: 25 },
|
|
{ candidateId: THIRD_CANDIDATE_ID, time: "04:47", rank: 3, relativeSupport: 25, tiedMinuteCount: 25 },
|
|
];
|
|
|
|
function blocker(
|
|
gate: ReturnType<typeof buildConfirmationGate>,
|
|
id: string,
|
|
) {
|
|
const found = gate.blockers.find((item) => item.id === id);
|
|
assert.ok(found, `missing blocker ${id}`);
|
|
return found;
|
|
}
|
|
|
|
test("sealed holdout aggregates match the v2 report and stay below the case gate", () => {
|
|
assert.equal(SEALED_MINUTE_HOLDOUT.valid_public_aa_cases, holdoutReport.case_gate.valid_public_aa_cases);
|
|
assert.equal(SEALED_MINUTE_HOLDOUT.required_cases, holdoutReport.case_gate.minimum_public_aa_cases);
|
|
assert.equal(SEALED_MINUTE_HOLDOUT.top_1_rate, holdoutReport.metrics.top_1_rate);
|
|
assert.equal(SEALED_MINUTE_HOLDOUT.confirmation_coverage_rate, holdoutReport.metrics.confirmation_coverage_rate);
|
|
assert.ok(SEALED_MINUTE_HOLDOUT.valid_public_aa_cases < SEALED_MINUTE_HOLDOUT.required_cases);
|
|
assert.equal(SEALED_MINUTE_HOLDOUT.status, "not_ready");
|
|
});
|
|
|
|
test("VedAstro not_evaluated is not described as fail and still forbids confirmation", () => {
|
|
const gate = buildConfirmationGate({
|
|
engineConfirmationAllowed: true,
|
|
candidates: UNIQUE_MINUTE,
|
|
decisionReceipt: {
|
|
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
|
|
},
|
|
});
|
|
const vedastro = blocker(gate, "vedastro_minute_sensitive");
|
|
const adjacent = blocker(gate, "adjacent_minutes_indistinguishable");
|
|
const holdout = blocker(gate, "public_aa_holdout");
|
|
assert.equal(readVedastroMinuteSensitiveStatus({
|
|
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
|
|
}), "not_evaluated");
|
|
assert.equal(vedastro.status, "not_evaluated");
|
|
assert.notEqual(vedastro.status, "fail");
|
|
assert.notEqual(vedastro.status, "failed");
|
|
assert.match(vedastro.user_meaning, /不等于失败|不是失败|尚未跑通/);
|
|
assert.doesNotMatch(vedastro.user_meaning, /失败了|校验失败/);
|
|
assert.equal(adjacent.status, "passed");
|
|
assert.equal(holdout.status, "not_ready");
|
|
assert.equal(gate.confirmation_allowed, false);
|
|
});
|
|
|
|
test("a 25-minute plateau stays confirmation-blocked as an indistinguishable range", () => {
|
|
const projection = latestResultToolProjection({
|
|
resultId: RESULT_ID,
|
|
candidates: PLATEAU,
|
|
selectionAllowed: true,
|
|
confirmationAllowed: true,
|
|
representativeTime: "04:45",
|
|
selectedTime: null,
|
|
selectionKind: null,
|
|
algorithmVersion: "rectification-v5",
|
|
});
|
|
const gate = projection.confirmation_gate as ReturnType<typeof buildConfirmationGate>;
|
|
assert.ok(Number(projection.indistinguishable_width_minutes) >= 25);
|
|
assert.equal(projection.confirmation_allowed, false);
|
|
assert.equal(gate.confirmation_allowed, false);
|
|
assert.equal(blocker(gate, "adjacent_minutes_indistinguishable").status, "blocked");
|
|
assert.equal(
|
|
blocker(gate, "adjacent_minutes_indistinguishable").max_confirmation_width_minutes,
|
|
RECTIFICATION_POLICY.maxConfirmationWidthMinutes,
|
|
);
|
|
assert.match(blocker(gate, "adjacent_minutes_indistinguishable").user_meaning, /不可分区间|代表性候选/);
|
|
assert.equal((projection.session_outcome as { kind: string }).kind, "adopt_representative");
|
|
assert.equal(sessionOutcomeFromGate({
|
|
selectionAllowed: true,
|
|
confirmationAllowed: false,
|
|
}).kind, "adopt_representative");
|
|
});
|
|
|
|
test("holdout not_ready forbids unique-minute copy and still blocks confirm", async () => {
|
|
const projection = latestResultToolProjection({
|
|
resultId: RESULT_ID,
|
|
candidates: UNIQUE_MINUTE,
|
|
selectionAllowed: true,
|
|
confirmationAllowed: true,
|
|
representativeTime: "05:02",
|
|
selectedTime: null,
|
|
selectionKind: null,
|
|
algorithmVersion: "rectification-v5",
|
|
decisionReceipt: {
|
|
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
|
|
},
|
|
});
|
|
const serialized = JSON.stringify(projection);
|
|
assert.equal(projection.confirmation_allowed, false);
|
|
assert.doesNotMatch(serialized, /±2|80%|60%/);
|
|
assert.doesNotMatch(serialized, /obama|barack|angelina|jolie/i);
|
|
assert.match(skill, /confirmation_gate/);
|
|
assert.match(skill, /not_evaluated/);
|
|
assert.match(skill, /session_outcome=adopt_representative/);
|
|
assert.match(skill, /还不能确认唯一分钟/);
|
|
assert.match(candidateComparison, /confirmation_gate/);
|
|
assert.match(agentSource, /confirmation_gate/);
|
|
assert.match(agentSource, /session_outcome=adopt_representative/);
|
|
assert.doesNotMatch(agentSource, /±2 分钟/);
|
|
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 13);
|
|
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.3");
|
|
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
latestResult: {
|
|
...candidateSnapshotFixture({
|
|
confirmationAllowed: true,
|
|
selectionAllowed: true,
|
|
representativeTime: "05:02",
|
|
candidates: [
|
|
{ candidate_id: CANDIDATE_ID, rank: 1, time: "05:02", relative_support: 70, tied_minute_count: 1 },
|
|
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "05:03", relative_support: 30, tied_minute_count: 2 },
|
|
],
|
|
}),
|
|
confirmation_allowed: true,
|
|
decision_receipt: {
|
|
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
|
|
},
|
|
},
|
|
}),
|
|
confirm_agentic_rectification_candidate_for_case_v2: () => ({
|
|
success: true,
|
|
saved_time: "05:02",
|
|
status: "confirmed",
|
|
result_id: RESULT_ID,
|
|
case_status: "confirmed",
|
|
idempotent: false,
|
|
}),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
await assert.rejects(
|
|
(tools["rectification-confirm-birth-time"] as unknown as {
|
|
execute(input: unknown): Promise<unknown>;
|
|
}).execute({
|
|
caseId: CASE_ID,
|
|
resultId: RESULT_ID,
|
|
candidateId: CANDIDATE_ID,
|
|
consentQuote: "就用05:02",
|
|
}),
|
|
(error: unknown) => error instanceof RectificationToolServiceError && error.code === "confirmation_blocked",
|
|
);
|
|
assert.equal(
|
|
accounting.calls.some((call) => call.fn === "confirm_agentic_rectification_candidate_for_case_v2"),
|
|
false,
|
|
);
|
|
assert.equal(safeToolErrorCode(new RectificationToolServiceError("confirmation_blocked")), "confirmation_blocked");
|
|
});
|