289 lines
11 KiB
TypeScript
289 lines
11 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
RectificationEngineError,
|
|
runV9CandidateScore,
|
|
runV9Diagnostics,
|
|
toEngineEvents,
|
|
type V9EngineScoreResult,
|
|
} from "../src/lib/rectification-agentic/v9/engine-client.ts";
|
|
|
|
const RANGE = { start_time: "04:50", end_time: "05:10" };
|
|
const CANDIDATE_ID = "88888888-8888-4888-8888-888888888881";
|
|
const SECOND_CANDIDATE_ID = "88888888-8888-4888-8888-888888888882";
|
|
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
|
|
|
|
const DECISION_RECEIPT = {
|
|
receipt_version: "candidate-decision-receipt-v2",
|
|
contract_version: "v2",
|
|
event_contract_version: "rectification-event-contract-v2",
|
|
policy_version: "rectification-candidate-policy-v2",
|
|
decision_policy_version: "rectification-candidate-policy-v2",
|
|
display_allowed: true,
|
|
selection_allowed: true,
|
|
acceptance_allowed: true,
|
|
confirmation_allowed: false,
|
|
accept_allowed: true,
|
|
confirm_allowed: false,
|
|
representative_candidate_id: CANDIDATE_ID,
|
|
representative_time: "04:50",
|
|
overall_confidence: "high",
|
|
margin_percent: 42.5,
|
|
};
|
|
|
|
const EXECUTION_LEDGER = [
|
|
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "d1-rashi", status: "executed", source: "python-engine" },
|
|
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "gochara", status: "executed", source: "python-engine" },
|
|
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "d24-chaturvimshamsha", status: "not_executed", source: "python-engine" },
|
|
];
|
|
|
|
const ENGINE_SCORE_RESPONSE_V2 = {
|
|
success: true,
|
|
endpoint: "rectification_v5_score",
|
|
result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62",
|
|
algorithm_version: "rectification-event-contract-v2",
|
|
event_contract_version: "rectification-event-contract-v2",
|
|
decision_policy_version: "rectification-candidate-policy-v2",
|
|
execution_ledger_version: "rectification-execution-ledger-v2",
|
|
candidate_decisions: [
|
|
{ candidate_id: CANDIDATE_ID, time: "04:50", rank: 1, relative_support: 57, tied_minute_count: 1 },
|
|
{ candidate_id: SECOND_CANDIDATE_ID, time: "04:51", rank: 3, relative_support: 18, tied_minute_count: 2 },
|
|
{ candidate_id: THIRD_CANDIDATE_ID, time: "04:52", rank: 2, relative_support: 25, tied_minute_count: 2 },
|
|
],
|
|
decision_receipt: DECISION_RECEIPT,
|
|
execution_ledger: EXECUTION_LEDGER,
|
|
diagnostics: {
|
|
primary_secondary_margin_percent: 99,
|
|
leave_one_event_out_retention_rate: 1,
|
|
},
|
|
candidate_scores: [
|
|
{ time: "04:59", score: 999999 },
|
|
],
|
|
};
|
|
|
|
const ENGINE_DIAGNOSTICS_RESPONSE_V2 = {
|
|
success: true,
|
|
endpoint: "rectification_v5_diagnostics",
|
|
result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62",
|
|
algorithm_version: "rectification-event-contract-v2",
|
|
event_contract_version: "rectification-event-contract-v2",
|
|
decision_policy_version: "rectification-candidate-policy-v2",
|
|
execution_ledger_version: "rectification-execution-ledger-v2",
|
|
candidate_decisions: ENGINE_SCORE_RESPONSE_V2.candidate_decisions,
|
|
diagnostics: {
|
|
primary_cluster_retention_rate: 0.86,
|
|
leave_one_event_out_retention_rate: 0.81,
|
|
leave_one_domain_out_retention_rate: 0.9,
|
|
date_sensitivity_retention_rate: 0.72,
|
|
neighbor_support_minutes: 2,
|
|
primary_secondary_margin_percent: 42.5,
|
|
unstable_event_ids: [],
|
|
most_discriminating_layers: ["vimsottari_dasha"],
|
|
candidate_splits: [{ candidate_id: SECOND_CANDIDATE_ID, width: 6 }],
|
|
},
|
|
missing_layers: ["KP_cusps"],
|
|
decision_receipt: DECISION_RECEIPT,
|
|
execution_ledger: EXECUTION_LEDGER,
|
|
};
|
|
|
|
function stubEngine(response: unknown, status = 200) {
|
|
const previous = globalThis.fetch;
|
|
globalThis.fetch = (async () => ({
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
json: async () => response,
|
|
})) as unknown as typeof fetch;
|
|
return () => {
|
|
globalThis.fetch = previous;
|
|
};
|
|
}
|
|
|
|
const SNAPSHOT = {
|
|
birth_date: "1997-08-08",
|
|
latitude: 36.420487,
|
|
longitude: 114.209936,
|
|
timezone_offset: 8,
|
|
birth_time_source: "family_exact",
|
|
};
|
|
|
|
const EVIDENCE = [
|
|
{
|
|
id: "00000000-0000-4000-8000-000000000001",
|
|
sourceTurnId: "33333333-3333-4333-8333-333333333333",
|
|
subject: "self",
|
|
eventKind: "education_start",
|
|
domain: "education",
|
|
occurredFrom: "2016-09-01",
|
|
occurredTo: "2016-09-30",
|
|
datePrecision: "month",
|
|
summary: "大学入学",
|
|
status: "confirmed",
|
|
supersedesEvidenceId: null,
|
|
createdAt: "2026-08-12T10:00:06.000Z",
|
|
dateSource: "user_quote",
|
|
dateReliability: "month_exact",
|
|
dateCorroboration: "录取通知书",
|
|
dateConflictStatus: "none",
|
|
},
|
|
];
|
|
|
|
test("toEngineEvents preserves Event Contract v2 detailed kind and provenance", () => {
|
|
const events = toEngineEvents(EVIDENCE);
|
|
assert.equal(events.length, 1);
|
|
assert.equal(events[0]!.domain, "education");
|
|
assert.equal(events[0]!.event_kind, "education_start");
|
|
assert.equal(events[0]!.date_start, "2016-09-01");
|
|
assert.equal(events[0]!.precision, "month");
|
|
assert.equal(events[0]!.date_source, "user_quote");
|
|
assert.equal(events[0]!.date_reliability, "month_exact");
|
|
assert.equal(events[0]!.date_corroboration, "录取通知书");
|
|
assert.equal(events[0]!.date_conflict_status, "none");
|
|
assert.equal(events[0]!.source_turn_id, EVIDENCE[0]!.sourceTurnId);
|
|
assert.equal(events[0]!.subject, "self");
|
|
});
|
|
|
|
test("toEngineEvents keeps all scoreable detailed v2 kinds and drops background kinds", () => {
|
|
const detailed = [
|
|
"education_completion",
|
|
"career_entry",
|
|
"promotion",
|
|
"career_pressure",
|
|
"relationship_commitment",
|
|
"relationship_separation",
|
|
"finance_gain",
|
|
"finance_loss",
|
|
"self_health_event",
|
|
].map((eventKind, index) => ({
|
|
...EVIDENCE[0]!,
|
|
id: `00000000-0000-4000-8000-${String(index + 2).padStart(12, "0")}`,
|
|
eventKind,
|
|
domain: eventKind.startsWith("education") ? "education"
|
|
: eventKind.startsWith("career") || eventKind === "promotion" ? "career"
|
|
: eventKind.startsWith("relationship") ? "relationship"
|
|
: eventKind.startsWith("finance") ? "finance"
|
|
: "health",
|
|
}));
|
|
const background = [
|
|
{ ...EVIDENCE[0]!, id: "00000000-0000-4000-8000-000000000099", eventKind: "family_event", domain: "family" },
|
|
{ ...EVIDENCE[0]!, id: "00000000-0000-4000-8000-000000000100", eventKind: "other", domain: "other" },
|
|
];
|
|
assert.deepEqual(toEngineEvents([...detailed, ...background]).map((event) => event.event_kind), detailed.map((event) => event.eventKind));
|
|
});
|
|
|
|
test("runV9CandidateScore strictly consumes server candidate decisions and v2 receipt", async () => {
|
|
const restore = stubEngine(ENGINE_SCORE_RESPONSE_V2);
|
|
try {
|
|
const result: V9EngineScoreResult = await runV9CandidateScore({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents(EVIDENCE),
|
|
});
|
|
assert.deepEqual(result.candidates, [
|
|
{ candidateId: CANDIDATE_ID, time: "04:50", rank: 1, relativeSupport: 57, tiedMinuteCount: 1 },
|
|
{ candidateId: SECOND_CANDIDATE_ID, time: "04:51", rank: 3, relativeSupport: 18, tiedMinuteCount: 2 },
|
|
{ candidateId: THIRD_CANDIDATE_ID, time: "04:52", rank: 2, relativeSupport: 25, tiedMinuteCount: 2 },
|
|
], "Web must preserve server rank/support/tie instead of deriving or sorting them");
|
|
assert.equal(result.representativeCandidateId, CANDIDATE_ID);
|
|
assert.equal(result.representativeTime, "04:50");
|
|
assert.equal(result.selectionAllowed, true);
|
|
assert.equal(result.confirmationAllowed, false);
|
|
assert.equal(result.overallConfidence, "high");
|
|
assert.equal(result.policyVersion, "rectification-candidate-policy-v2");
|
|
assert.deepEqual(result.decisionReceipt, DECISION_RECEIPT);
|
|
assert.deepEqual(result.executionLedger, EXECUTION_LEDGER);
|
|
assert.deepEqual(result.executedMethods, ["d1-rashi", "gochara"], "methods come only from executed server ledger entries");
|
|
assert.equal(JSON.stringify(result).includes("999999"), false, "raw internal score must not enter the TS projection");
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("runV9CandidateScore fails closed without a v2 decision receipt", async () => {
|
|
const missingReceipt = Object.fromEntries(
|
|
Object.entries(ENGINE_SCORE_RESPONSE_V2).filter(([key]) => key !== "decision_receipt"),
|
|
);
|
|
const restore = stubEngine(missingReceipt);
|
|
try {
|
|
await assert.rejects(
|
|
runV9CandidateScore({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents(EVIDENCE),
|
|
}),
|
|
(error: unknown) => error instanceof RectificationEngineError && error.code === "engine_invalid_v2_receipt",
|
|
);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("runV9CandidateScore fails closed on invalid server candidate UUIDs", async () => {
|
|
const restore = stubEngine({
|
|
...ENGINE_SCORE_RESPONSE_V2,
|
|
candidate_decisions: [{ ...ENGINE_SCORE_RESPONSE_V2.candidate_decisions[0], candidate_id: "04:50" }],
|
|
});
|
|
try {
|
|
await assert.rejects(
|
|
runV9CandidateScore({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents(EVIDENCE),
|
|
}),
|
|
(error: unknown) => error instanceof RectificationEngineError && error.code === "engine_invalid_candidate_decisions",
|
|
);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("runV9CandidateScore fails closed when no evidence is scoreable", async () => {
|
|
const restore = stubEngine(ENGINE_SCORE_RESPONSE_V2);
|
|
try {
|
|
await assert.rejects(
|
|
runV9CandidateScore({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents([{ ...EVIDENCE[0]!, eventKind: "family_event", domain: "family" }]),
|
|
}),
|
|
(error: unknown) => error instanceof RectificationEngineError && error.code === "no_scorable_evidence",
|
|
);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("runV9Diagnostics maps diagnostics and receipt-owned confirmation", async () => {
|
|
const restore = stubEngine(ENGINE_DIAGNOSTICS_RESPONSE_V2);
|
|
try {
|
|
const result = await runV9Diagnostics({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents(EVIDENCE),
|
|
});
|
|
assert.equal(result.canConfirmExactMinute, false);
|
|
assert.equal(result.missingLayers.join(","), "KP_cusps");
|
|
assert.equal(result.diagnostics.primary_secondary_margin_percent, 42.5);
|
|
assert.deepEqual(result.executedMethods, ["d1-rashi", "gochara"]);
|
|
assert.equal(result.policyVersion, "rectification-candidate-policy-v2");
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("engine http failures surface as safe engine errors, never raw stack traces", async () => {
|
|
const restore = stubEngine({ error: "invalid event kind" }, 400);
|
|
try {
|
|
await assert.rejects(
|
|
runV9CandidateScore({
|
|
baselineBirthSnapshot: SNAPSHOT,
|
|
candidateRange: RANGE,
|
|
events: toEngineEvents(EVIDENCE),
|
|
}),
|
|
(error: unknown) => error instanceof Error && error.message.includes("invalid event kind"),
|
|
);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|