a88467ffa8
Keep unique-top and width on confirmation only, and stop lagna-frame follow-ups from blocking cards on an already-scored cluster. Co-authored-by: Cursor <cursoragent@cursor.com>
993 lines
41 KiB
TypeScript
993 lines
41 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import { buildMethodFollowupPlan, buildNextUserAction, conversationalSessionOutcome, isOfferBlockingFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
|
import {
|
|
internalObservationsFromWindowScan,
|
|
parseWindowScan,
|
|
} from "../src/lib/rectification-agentic/v9/varga-observations.ts";
|
|
import { WINDOW_SCAN_DISPLAY_LAYER_ORDER } from "../src/lib/rectification-agentic/v9/refinement-packet.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 { RectificationToolServiceError } from "../src/lib/rectification-agentic/v9/tool-service.ts";
|
|
import {
|
|
resolveExactSkillPackage,
|
|
} from "../src/lib/skill-package-registry.ts";
|
|
import {
|
|
CASE_ID,
|
|
CANDIDATE_ID,
|
|
EVIDENCE_ID,
|
|
RESULT_ID,
|
|
SECOND_CANDIDATE_ID,
|
|
TURN_ID,
|
|
USER_ID,
|
|
candidateSnapshotFixture,
|
|
computeFixture,
|
|
dossierFixture,
|
|
fakeAccounting,
|
|
receiptHandlers,
|
|
} from "./rectification-v9-test-support.ts";
|
|
|
|
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
|
|
const EDUCATION_ID = "44444444-4444-4444-8444-444444444441";
|
|
const UNIQUE_MINUTE_COPY = /±5 分钟确定性/;
|
|
|
|
const CLASSIC_COVERAGE = [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year" as const, occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year" as const, occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year" as const, occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year" as const, occurredFrom: "2020-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "appearance", datePrecision: "unknown" as const, occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "marks", datePrecision: "unknown" as const, occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "occupation", datePrecision: "unknown" as const, occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "horary", datePrecision: "day" as const, occurredFrom: "2024-01-01", occurredTo: null },
|
|
];
|
|
|
|
const ENGINE_SCORE = {
|
|
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: 2, relative_support: 25, tied_minute_count: 2 },
|
|
{ candidate_id: THIRD_CANDIDATE_ID, time: "04:52", rank: 3, relative_support: 18, tied_minute_count: 2 },
|
|
],
|
|
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,
|
|
},
|
|
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: "d9-navamsa", status: "executed", source: "python-engine" },
|
|
],
|
|
diagnostics: {
|
|
window_scan: {
|
|
scanned: true,
|
|
confirmation_allowed: false,
|
|
unique_minute_claim: false,
|
|
d9_lagna_count: 2,
|
|
d10_lagna_count: 1,
|
|
d9_candidates_differ: true,
|
|
d10_candidates_differ: false,
|
|
d9_sign_names: ["白羊座", "天蝎"],
|
|
},
|
|
},
|
|
};
|
|
|
|
const educationEvidence = {
|
|
id: EDUCATION_ID,
|
|
source_turn_id: TURN_ID,
|
|
subject: "self",
|
|
event_kind: "education_milestone",
|
|
domain: "education",
|
|
occurred_from: "2016-06-01",
|
|
occurred_to: null,
|
|
date_precision: "month",
|
|
summary: "2016年6月一次学业节点",
|
|
status: "confirmed",
|
|
supersedes_evidence_id: null,
|
|
created_at: "2026-08-12T10:00:06.000Z",
|
|
};
|
|
|
|
function stubEngine(response: unknown) {
|
|
const previous = globalThis.fetch;
|
|
globalThis.fetch = (async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => response,
|
|
})) as unknown as typeof fetch;
|
|
return () => {
|
|
globalThis.fetch = previous;
|
|
};
|
|
}
|
|
|
|
test("eight-method routing asks relationship after dated education, not relocation", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06-01",
|
|
occurredTo: null,
|
|
}],
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d9_relationship");
|
|
assert.equal(plan.next_followup?.domain, "relationship");
|
|
assert.equal(plan.stop_domain_rotation, true);
|
|
assert.deepEqual([...plan.not_in_rotation], ["relocation"]);
|
|
assert.equal(plan.methods.find((item) => item.method_id === "appearance")?.status, "uncovered");
|
|
});
|
|
|
|
test("user-stop action records stated events when the ledger is empty", () => {
|
|
const action = buildNextUserAction({
|
|
scorableCount: 0,
|
|
evidenceCount: 0,
|
|
hasLatestResult: false,
|
|
selectionAllowed: false,
|
|
sessionOutcome: "collect_evidence",
|
|
nextFollowup: null,
|
|
workingTime: "12:00",
|
|
});
|
|
assert.equal(action.id, "record_stated_events");
|
|
assert.equal(action.on_user_stop.id, "record_stated_events");
|
|
assert.match(action.on_user_stop.user_meaning, /batch/);
|
|
});
|
|
|
|
test("user-stop action explains the window when follow-up remains but the user stops", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06-01",
|
|
occurredTo: null,
|
|
}],
|
|
});
|
|
const action = buildNextUserAction({
|
|
scorableCount: 1,
|
|
evidenceCount: 1,
|
|
hasLatestResult: true,
|
|
selectionAllowed: false,
|
|
sessionOutcome: "collect_evidence",
|
|
nextFollowup: plan.next_followup,
|
|
workingTime: "12:00",
|
|
});
|
|
assert.equal(action.id, "ask_method_followup");
|
|
assert.equal(action.on_user_stop.id, "explain_current_window");
|
|
assert.match(action.on_user_stop.user_meaning, /12:00/);
|
|
assert.match(action.on_user_stop.user_meaning, /不要只说会话会保留/);
|
|
assert.equal(plan.methods.find((item) => item.method_id === "marks")?.status, "uncovered");
|
|
assert.equal(plan.methods.find((item) => item.method_id === "horary")?.status, "uncovered");
|
|
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
|
|
assert.equal(plan.next_followup?.method_id, "d9_relationship");
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /A\/B\/C\/D/);
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /D9/);
|
|
assert.doesNotMatch(JSON.stringify(plan), UNIQUE_MINUTE_COPY);
|
|
});
|
|
|
|
test("selectionAllowed with remaining method follow-up keeps collecting and only adopts on stop", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06-01",
|
|
occurredTo: null,
|
|
}],
|
|
});
|
|
const action = buildNextUserAction({
|
|
scorableCount: 3,
|
|
evidenceCount: 3,
|
|
hasLatestResult: true,
|
|
selectionAllowed: true,
|
|
sessionOutcome: "collect_evidence",
|
|
nextFollowup: plan.next_followup,
|
|
workingTime: "05:07",
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d9_relationship");
|
|
assert.equal(action.id, "ask_method_followup");
|
|
assert.equal(action.on_user_stop.id, "adopt_representative");
|
|
});
|
|
|
|
test("adopt_representative defers method follow-up instead of asking this turn", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06-01",
|
|
occurredTo: null,
|
|
}],
|
|
sessionOutcome: "adopt_representative",
|
|
});
|
|
assert.equal(plan.next_followup, null);
|
|
assert.equal(plan.deferred_followup?.method_id, "d9_relationship");
|
|
assert.equal(plan.session_outcome, "adopt_representative");
|
|
});
|
|
|
|
test("declined relationship skips to career and leaves horary uncovered", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
declinedTopics: [{ target_domain: "relationship", status: "declined" }],
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d10_career");
|
|
assert.equal(plan.next_followup?.domain, "career");
|
|
assert.deepEqual([...plan.do_not_poll], []);
|
|
assert.equal(plan.methods.find((item) => item.method_id === "horary")?.status, "uncovered");
|
|
});
|
|
|
|
test("D9 differ keeps sign names for the type-table report and still forbids unique-minute claims", () => {
|
|
const scan = parseWindowScan({
|
|
scanned: true,
|
|
confirmation_allowed: true,
|
|
unique_minute_claim: true,
|
|
d9_lagna_count: 2,
|
|
d10_lagna_count: 1,
|
|
d9_candidates_differ: true,
|
|
d10_candidates_differ: false,
|
|
d9_sign_names: ["白羊座", "天蝎"],
|
|
type_table: "热情冲动",
|
|
transitions: [{
|
|
layer: "d9",
|
|
at: "05:14",
|
|
user_meaning: "白羊座在 05:14 换成天蝎",
|
|
}],
|
|
});
|
|
assert.ok(scan);
|
|
assert.equal(scan.confirmation_allowed, false);
|
|
assert.equal(scan.unique_minute_claim, false);
|
|
assert.equal(scan.d9_candidates_differ, true);
|
|
assert.deepEqual(scan.d9_sign_names, ["白羊座", "天蝎"]);
|
|
assert.deepEqual(scan.transitions, [{
|
|
layer: "d9",
|
|
at: "05:14",
|
|
user_meaning: "D9 在 05:14 发生变化",
|
|
}]);
|
|
const observations = internalObservationsFromWindowScan(scan);
|
|
assert.deepEqual(observations, [
|
|
{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" },
|
|
{ layer: "d10", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d4", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d5", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d7", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d12", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d11", candidates_differ: false, ask_theme: null },
|
|
{ layer: "d30", candidates_differ: false, ask_theme: null },
|
|
]);
|
|
assert.doesNotMatch(JSON.stringify({ scan, observations }), UNIQUE_MINUTE_COPY);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: CLASSIC_COVERAGE,
|
|
observations,
|
|
});
|
|
assert.equal(plan.next_followup?.source, "varga_observation");
|
|
assert.equal(plan.next_followup?.ask_theme, "relationship_style");
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /A\/B\/C\/D/);
|
|
assert.doesNotMatch(JSON.stringify(plan), UNIQUE_MINUTE_COPY);
|
|
});
|
|
|
|
test("D24-only window change folds into education follow-up without a second layer", () => {
|
|
const scan = parseWindowScan({
|
|
scanned: true,
|
|
d9_lagna_count: 1,
|
|
d10_lagna_count: 1,
|
|
d5_lagna_count: 1,
|
|
d24_lagna_count: 2,
|
|
d5_candidates_differ: false,
|
|
d24_candidates_differ: true,
|
|
transitions: [
|
|
{ layer: "d24", at: "05:14" },
|
|
{ layer: "pada", at: "05:14" },
|
|
],
|
|
});
|
|
assert.ok(scan);
|
|
assert.equal(scan.d24_candidates_differ, true);
|
|
assert.equal(scan.d5_candidates_differ, false);
|
|
assert.deepEqual(scan.transitions, [
|
|
{ layer: "d24", at: "05:14", user_meaning: "D24 在 05:14 发生变化" },
|
|
{ layer: "pada", at: "05:14", user_meaning: "Nakshatra pada 在 05:14 发生变化" },
|
|
]);
|
|
const observations = internalObservationsFromWindowScan(scan);
|
|
assert.equal(observations.find((item) => item.layer === "d5")?.candidates_differ, true);
|
|
assert.equal(observations.find((item) => item.layer === "d5")?.ask_theme, "education_style");
|
|
assert.deepEqual(
|
|
observations.map((item) => item.layer),
|
|
["d9", "d10", "d4", "d5", "d7", "d12", "d11", "d30"],
|
|
);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: CLASSIC_COVERAGE,
|
|
observations,
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d5_education");
|
|
assert.equal(plan.next_followup?.ask_theme, "education_style");
|
|
});
|
|
|
|
test("D11-only window change folds into finance follow-up without delaying adopt", () => {
|
|
const scan = parseWindowScan({
|
|
scanned: true,
|
|
d9_lagna_count: 1,
|
|
d10_lagna_count: 1,
|
|
d2_lagna_count: 1,
|
|
d11_lagna_count: 2,
|
|
d30_lagna_count: 2,
|
|
d2_candidates_differ: false,
|
|
d11_candidates_differ: true,
|
|
d30_candidates_differ: true,
|
|
transitions: [
|
|
{ layer: "d11", at: "05:14" },
|
|
{ layer: "d30", at: "05:14" },
|
|
{ layer: "bhava", at: "05:14" },
|
|
{ layer: "pranapada", at: "05:14" },
|
|
],
|
|
});
|
|
assert.ok(scan);
|
|
assert.equal(scan.d11_candidates_differ, true);
|
|
assert.equal(scan.d2_candidates_differ, false);
|
|
assert.deepEqual(scan.transitions, [
|
|
{ layer: "d11", at: "05:14", user_meaning: "D11 在 05:14 发生变化" },
|
|
{ layer: "d30", at: "05:14", user_meaning: "D30 在 05:14 发生变化" },
|
|
{ layer: "bhava", at: "05:14", user_meaning: "Bhava Lagna 在 05:14 发生变化" },
|
|
{ layer: "pranapada", at: "05:14", user_meaning: "Pranapada Lagna 在 05:14 发生变化" },
|
|
]);
|
|
const observations = internalObservationsFromWindowScan(scan);
|
|
assert.equal(observations.find((item) => item.layer === "d11")?.candidates_differ, true);
|
|
assert.equal(observations.find((item) => item.layer === "d11")?.ask_theme, "finance_change");
|
|
assert.equal(observations.find((item) => item.layer === "d30")?.ask_theme, "health_pressure");
|
|
assert.deepEqual(
|
|
observations.map((item) => item.layer),
|
|
["d9", "d10", "d4", "d5", "d7", "d12", "d11", "d30"],
|
|
);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [
|
|
...CLASSIC_COVERAGE,
|
|
{ status: "confirmed", domain: "finance", datePrecision: "year", occurredFrom: "2021-01-01", occurredTo: null },
|
|
],
|
|
observations,
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d2_finance");
|
|
assert.equal(plan.next_followup?.source, "varga_observation");
|
|
});
|
|
|
|
test("window scan displays KP sub-lord changes without opening confirmation", () => {
|
|
assert.deepEqual(
|
|
["kp1", "kp4", "kp7", "kp10"].every((layer) => WINDOW_SCAN_DISPLAY_LAYER_ORDER.includes(layer as typeof WINDOW_SCAN_DISPLAY_LAYER_ORDER[number])),
|
|
true,
|
|
);
|
|
const scan = parseWindowScan({
|
|
scanned: true,
|
|
confirmation_allowed: true,
|
|
unique_minute_claim: true,
|
|
d9_lagna_count: 1,
|
|
d10_lagna_count: 1,
|
|
transitions: [
|
|
{ layer: "kp1", at: "05:14" },
|
|
{ layer: "kp10", at: "05:15" },
|
|
],
|
|
});
|
|
assert.ok(scan);
|
|
assert.equal(scan.confirmation_allowed, false);
|
|
assert.equal(scan.unique_minute_claim, false);
|
|
assert.deepEqual(scan.transitions, [
|
|
{ layer: "kp1", at: "05:14", user_meaning: "KP 1宫子主 在 05:14 发生变化" },
|
|
{ layer: "kp10", at: "05:15", user_meaning: "KP 10宫子主 在 05:15 发生变化" },
|
|
]);
|
|
});
|
|
|
|
test("read-case follows method plan and keeps D9/D10 type tables when SQL missing categories rotate", async () => {
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
evidence: [educationEvidence],
|
|
conversationSummary: {
|
|
confirmed_evidence_summary: [],
|
|
pending_revisions: [],
|
|
active_focus: null,
|
|
declined_skipped_topics: [],
|
|
candidate_divergence_summary: null,
|
|
missing_evidence_categories: ["relocation", "health", "finance"],
|
|
last_result_policy: null,
|
|
summary_version: 1,
|
|
updated_at: "2026-08-12T10:00:06.000Z",
|
|
},
|
|
latestResult: {
|
|
...candidateSnapshotFixture({
|
|
confirmationAllowed: true,
|
|
representativeTime: "04:45",
|
|
candidates: [
|
|
{ candidate_id: CANDIDATE_ID, rank: 1, time: "04:45", relative_support: 40, tied_minute_count: 25 },
|
|
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "04:46", relative_support: 35, tied_minute_count: 25 },
|
|
{ candidate_id: THIRD_CANDIDATE_ID, rank: 3, time: "04:47", relative_support: 25, tied_minute_count: 25 },
|
|
],
|
|
}),
|
|
selection_allowed: true,
|
|
confirmation_allowed: true,
|
|
decision_receipt: {
|
|
receipt_version: "candidate-decision-receipt-v2",
|
|
policy_version: "rectification-candidate-policy-v2",
|
|
selection_allowed: true,
|
|
acceptance_allowed: true,
|
|
confirmation_allowed: false,
|
|
representative_candidate_id: CANDIDATE_ID,
|
|
overall_confidence: "medium",
|
|
window_scan: {
|
|
scanned: true,
|
|
confirmation_allowed: false,
|
|
unique_minute_claim: false,
|
|
d9_lagna_count: 2,
|
|
d10_lagna_count: 1,
|
|
d9_candidates_differ: true,
|
|
d10_candidates_differ: false,
|
|
d9_sign_names: ["白羊", "天蝎"],
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
get_agentic_rectification_case_compute: () => computeFixture(),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
const projection = await (tools["rectification-read-case"] as unknown as {
|
|
execute(input: unknown): Promise<{
|
|
conversation_summary: { missing_evidence_categories: string[] };
|
|
method_followup_plan: {
|
|
next_followup: { method_id: string; domain: string | null } | null;
|
|
deferred_followup: { method_id: string; domain: string | null } | null;
|
|
session_outcome: string;
|
|
};
|
|
internal_observations: Array<{ layer: string; ask_theme: string | null }>;
|
|
latest_result: {
|
|
confirmation_allowed: boolean;
|
|
indistinguishable_width_minutes: number;
|
|
window_scan: { d9_candidates_differ: boolean } | null;
|
|
session_outcome: { kind: string };
|
|
};
|
|
}>;
|
|
}).execute({ caseId: CASE_ID });
|
|
assert.deepEqual(projection.conversation_summary.missing_evidence_categories, ["relocation", "health", "finance"]);
|
|
assert.equal(projection.method_followup_plan.next_followup?.method_id, "d9_relationship");
|
|
assert.equal(projection.method_followup_plan.next_followup?.domain, "relationship");
|
|
assert.equal(projection.method_followup_plan.deferred_followup, null);
|
|
assert.equal(projection.method_followup_plan.session_outcome, "collect_evidence");
|
|
assert.equal(
|
|
(projection as { next_user_action?: { id?: string; on_user_stop?: { id?: string } } }).next_user_action?.id,
|
|
"ask_method_followup",
|
|
);
|
|
assert.equal(
|
|
(projection as { next_user_action?: { on_user_stop?: { id?: string } } }).next_user_action?.on_user_stop?.id,
|
|
"adopt_representative",
|
|
);
|
|
assert.equal(projection.latest_result.session_outcome.kind, "collect_evidence");
|
|
assert.equal(projection.internal_observations.find((item) => item.layer === "d9")?.ask_theme, "relationship_style");
|
|
assert.equal(projection.latest_result.confirmation_allowed, false);
|
|
assert.ok(projection.latest_result.indistinguishable_width_minutes >= 25);
|
|
assert.equal(projection.latest_result.window_scan?.d9_candidates_differ, true);
|
|
assert.doesNotMatch(JSON.stringify(projection), UNIQUE_MINUTE_COPY);
|
|
assert.match(JSON.stringify(projection), /A\/B\/C\/D/);
|
|
});
|
|
|
|
test("accepted batch evidence triggers server rescore without offering adoption", async () => {
|
|
const restore = stubEngine(ENGINE_SCORE);
|
|
try {
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
evidence: [educationEvidence],
|
|
latestResult: null,
|
|
}),
|
|
get_agentic_rectification_case_compute: () => computeFixture(),
|
|
record_agentic_rectification_evidence_batch: () => ({
|
|
items: [{
|
|
index: 0,
|
|
outcome: "accepted",
|
|
evidence_id: EDUCATION_ID,
|
|
status: "confirmed",
|
|
idempotent: false,
|
|
clarification_fields: [],
|
|
error_code: null,
|
|
}],
|
|
accepted_count: 1,
|
|
needs_clarification_count: 0,
|
|
rejected_count: 0,
|
|
focus_id: null,
|
|
}),
|
|
persist_agentic_rectification_candidate_v2: () => ({
|
|
...candidateSnapshotFixture(),
|
|
cached: false,
|
|
}),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
const result = await (tools["rectification-record-evidence-batch"] as unknown as {
|
|
execute(input: unknown): Promise<{
|
|
accepted_count: number;
|
|
rescore: { status: string; executed_methods: string[]; error_code: string | null };
|
|
}>;
|
|
}).execute({
|
|
caseId: CASE_ID,
|
|
items: [{
|
|
quote: "2016年6月高考结束",
|
|
proposedKind: "education_milestone",
|
|
subject: "self",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06",
|
|
summary: "2016年6月一次学业节点",
|
|
}],
|
|
});
|
|
assert.equal(result.accepted_count, 1);
|
|
assert.equal(result.rescore.status, "completed");
|
|
assert.ok(result.rescore.executed_methods.includes("d1-rashi"));
|
|
const persistCall = accounting.calls.find((call) => call.fn === "persist_agentic_rectification_candidate_v2");
|
|
assert.ok(persistCall);
|
|
const receipt = persistCall.args.p_decision_receipt as { window_scan?: { d9_candidates_differ?: boolean; d9_sign_names?: unknown } };
|
|
assert.equal(receipt.window_scan?.d9_candidates_differ, true);
|
|
assert.deepEqual((receipt.window_scan as { d9_sign_names?: string[] } | undefined)?.d9_sign_names, ["白羊座", "天蝎"]);
|
|
const completedReceipt = accounting.calls.find((call) =>
|
|
call.fn === "insert_agentic_rectification_tool_receipt"
|
|
&& call.args.p_tool_name === "rectification-record-evidence-batch"
|
|
&& call.args.p_status === "completed"
|
|
);
|
|
assert.ok(completedReceipt);
|
|
assert.ok((completedReceipt.args.p_executed_methods as string[]).includes("d1-rashi"));
|
|
assert.equal(
|
|
accounting.calls.some((call) =>
|
|
call.fn === "transition_agentic_rectification_case_status"
|
|
&& call.args.p_to_status === "candidate_ready"
|
|
),
|
|
false,
|
|
);
|
|
assert.doesNotMatch(JSON.stringify(result), UNIQUE_MINUTE_COPY);
|
|
} finally {
|
|
restore();
|
|
}
|
|
});
|
|
|
|
test("rescore failure does not fail the evidence write", async () => {
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
evidence: [educationEvidence],
|
|
}),
|
|
get_agentic_rectification_case_compute: () => computeFixture(),
|
|
record_agentic_rectification_evidence_batch: () => ({
|
|
items: [{
|
|
index: 0,
|
|
outcome: "accepted",
|
|
evidence_id: EDUCATION_ID,
|
|
status: "confirmed",
|
|
idempotent: false,
|
|
clarification_fields: [],
|
|
error_code: null,
|
|
}],
|
|
accepted_count: 1,
|
|
needs_clarification_count: 0,
|
|
rejected_count: 0,
|
|
focus_id: null,
|
|
}),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
const result = await (tools["rectification-record-evidence-batch"] as unknown as {
|
|
execute(input: unknown): Promise<{
|
|
accepted_count: number;
|
|
rescore: { status: string; error_code: string | null };
|
|
}>;
|
|
}).execute({
|
|
caseId: CASE_ID,
|
|
items: [{
|
|
quote: "2016年6月高考结束",
|
|
proposedKind: "education_milestone",
|
|
subject: "self",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06",
|
|
summary: "2016年6月一次学业节点",
|
|
}],
|
|
});
|
|
assert.equal(result.accepted_count, 1);
|
|
assert.equal(result.rescore.status, "failed");
|
|
assert.ok(result.rescore.error_code);
|
|
});
|
|
|
|
test("public tool surface stays at 13 and new cases bind 10.0.10", () => {
|
|
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 13);
|
|
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.10");
|
|
const deprecated = resolveExactSkillPackage(
|
|
"jyotish-birth-time-rectification",
|
|
"10.0.2",
|
|
"8d7aa2d4bea0414e9a89ef908ccbc8c708c98f79f5b78ae4f7dc229b5f7dbb30",
|
|
);
|
|
assert.equal(deprecated.status, "deprecated");
|
|
const plateau = latestResultToolProjection({
|
|
resultId: RESULT_ID,
|
|
candidates: [
|
|
{ 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 },
|
|
],
|
|
selectionAllowed: true,
|
|
confirmationAllowed: true,
|
|
representativeTime: "04:45",
|
|
selectedTime: null,
|
|
selectionKind: null,
|
|
algorithmVersion: "rectification-v5",
|
|
});
|
|
assert.equal(plateau.confirmation_allowed, false);
|
|
assert.equal(plateau.unique_minute_claim, false);
|
|
assert.match(String(plateau.skill_verification_report), /Dasha \+ Gochara/);
|
|
assert.match(String(plateau.skill_verification_report), /candidate_range_not_birth_time_truth/);
|
|
assert.doesNotMatch(String(plateau.skill_verification_report), UNIQUE_MINUTE_COPY);
|
|
assert.equal((plateau.session_outcome as { kind: string }).kind, "collect_evidence");
|
|
const skill = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url), "utf8");
|
|
assert.match(skill, /method_followup_plan/);
|
|
assert.match(skill, /感情 → 事业 → 家人 → 外貌 → 疤痕 → 职业 → 占问/);
|
|
assert.match(skill, /KP 观察不计分、不挡提出门/);
|
|
assert.match(skill, /唯一领先和宽度≤5只挡确认门/);
|
|
assert.match(skill, /D9\/D10 类型表是校时方法/);
|
|
assert.doesNotMatch(skill, /±5 分钟确定性/);
|
|
assert.doesNotMatch(skill, /KP 政策跳过不挡提出门/);
|
|
});
|
|
|
|
test("family then appearance then marks then occupation then horary follow the method plan", () => {
|
|
const afterFamily = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
],
|
|
});
|
|
assert.equal(afterFamily.next_followup?.method_id, "appearance");
|
|
assert.equal(afterFamily.next_followup?.domain, "appearance");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: afterFamily.next_followup,
|
|
methods: afterFamily.methods,
|
|
}), "collect_evidence");
|
|
const afterAppearance = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "appearance", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
],
|
|
});
|
|
assert.equal(afterAppearance.next_followup?.method_id, "marks");
|
|
const afterMarks = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "appearance", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "marks", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
],
|
|
});
|
|
assert.equal(afterMarks.next_followup?.method_id, "occupation");
|
|
assert.equal(afterMarks.next_followup?.domain, "occupation");
|
|
assert.match(afterMarks.next_followup?.user_prompt_hint ?? "", /第 10 宫|D10/);
|
|
assert.match(afterMarks.next_followup?.user_prompt_hint ?? "", /A\/B\/C\/D/);
|
|
assert.match(afterMarks.next_followup?.user_prompt_hint ?? "", /事业类型表/);
|
|
const afterOccupation = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "appearance", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "marks", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "occupation", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
],
|
|
});
|
|
assert.equal(afterOccupation.next_followup?.method_id, "horary");
|
|
assert.equal(afterOccupation.next_followup?.domain, "horary");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: afterOccupation.next_followup,
|
|
methods: afterOccupation.methods,
|
|
}), "adopt_representative");
|
|
});
|
|
|
|
test("precision stage lagna_frame waits for uncovered career before asking another dated event", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
],
|
|
precisionStage: "lagna_frame",
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d10_career");
|
|
assert.equal(plan.next_followup?.source, "method_coverage");
|
|
assert.equal(isOfferBlockingFollowup(plan.next_followup, plan.methods), true);
|
|
assert.doesNotMatch(JSON.stringify(plan), UNIQUE_MINUTE_COPY);
|
|
});
|
|
|
|
test("lagna_frame after classic coverage does not keep blocking representative time cards", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: CLASSIC_COVERAGE,
|
|
precisionStage: "lagna_frame",
|
|
});
|
|
assert.equal(plan.next_followup?.source, "precision_stage");
|
|
assert.equal(plan.next_followup?.ask_theme, "dated_event");
|
|
assert.equal(isOfferBlockingFollowup(plan.next_followup, plan.methods), false);
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "adopt_representative");
|
|
});
|
|
|
|
test("precision stage d4 asks home change not family, and d5 asks education", () => {
|
|
const covered = CLASSIC_COVERAGE;
|
|
const d4 = buildMethodFollowupPlan({
|
|
evidence: covered,
|
|
precisionStage: "d4_refine",
|
|
});
|
|
assert.equal(d4.next_followup?.source, "precision_stage");
|
|
assert.equal(d4.next_followup?.method_id, "d4_home");
|
|
assert.equal(d4.next_followup?.domain, "relocation");
|
|
assert.equal(d4.next_followup?.ask_theme, "home_change");
|
|
assert.doesNotMatch(d4.next_followup?.user_prompt_hint ?? "", /家人/);
|
|
const legacyTheme = buildMethodFollowupPlan({
|
|
evidence: [...covered],
|
|
precisionStage: "theme_refine",
|
|
});
|
|
assert.equal(legacyTheme.next_followup?.ask_theme, "home_change");
|
|
const d5 = buildMethodFollowupPlan({
|
|
evidence: covered.filter((item) => item.domain !== "education"),
|
|
precisionStage: "d5_refine",
|
|
});
|
|
assert.equal(d5.next_followup?.method_id, "d5_education");
|
|
assert.equal(d5.next_followup?.domain, "education");
|
|
assert.doesNotMatch(JSON.stringify(d4), UNIQUE_MINUTE_COPY);
|
|
});
|
|
|
|
test("accepted representative time hands off to consultation", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-06-01",
|
|
occurredTo: null,
|
|
}],
|
|
accepted: true,
|
|
oosBlindPrompts: [{
|
|
domain: "family",
|
|
user_meaning: "校时还没用过家人这条线。有没有一件没提过、但记得大概时间的家人变化?",
|
|
used_for_scoring: false,
|
|
}],
|
|
});
|
|
assert.equal(plan.next_followup, null);
|
|
assert.equal(plan.deferred_followup?.method_id, "oos_blind");
|
|
const action = buildNextUserAction({
|
|
scorableCount: 3,
|
|
evidenceCount: 3,
|
|
hasLatestResult: true,
|
|
selectionAllowed: true,
|
|
sessionOutcome: "adopt_representative",
|
|
nextFollowup: null,
|
|
workingTime: "05:07",
|
|
accepted: true,
|
|
});
|
|
assert.equal(action.id, "start_consultation");
|
|
assert.match(action.user_meaning, /看盘/);
|
|
assert.doesNotMatch(JSON.stringify({ plan, action }), UNIQUE_MINUTE_COPY);
|
|
});
|
|
|
|
test("d9_refine after relationship still asks uncovered career first", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2016-09-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2020-06-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-05-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: null },
|
|
],
|
|
precisionStage: "d9_refine",
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d10_career");
|
|
assert.equal(plan.next_followup?.source, "method_coverage");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: false,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "collect_evidence");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "collect_evidence");
|
|
});
|
|
|
|
test("career evidence does not cover occupation; appearance still blocks until occupation is asked", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-05-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "day", occurredFrom: "2019-09-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
],
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "appearance");
|
|
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "collect_evidence");
|
|
});
|
|
|
|
test("declining occupation covers the method; declining horary is skipped_by_policy", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2019-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "appearance", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
{ status: "confirmed", domain: "marks", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
],
|
|
declinedTopics: [
|
|
{ target_domain: "occupation", status: "declined" },
|
|
{ target_domain: "horary", status: "declined" },
|
|
],
|
|
});
|
|
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "covered");
|
|
assert.equal(plan.methods.find((item) => item.method_id === "horary")?.status, "skipped_by_policy");
|
|
assert.equal(plan.next_followup, null);
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "adopt_representative");
|
|
});
|
|
|
|
test("horary follow-up does not block propose once occupation is covered", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: CLASSIC_COVERAGE.filter((item) => item.domain !== "horary"),
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "horary");
|
|
assert.equal(conversationalSessionOutcome({
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
nextFollowup: plan.next_followup,
|
|
methods: plan.methods,
|
|
}), "adopt_representative");
|
|
});
|
|
|
|
test("offer-candidates refuses while method coverage remains", async () => {
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
evidence: [educationEvidence],
|
|
latestResult: candidateSnapshotFixture({
|
|
selectionAllowed: true,
|
|
representativeTime: "04:48",
|
|
candidates: [
|
|
{ candidate_id: CANDIDATE_ID, rank: 1, time: "04:48", relative_support: 58, tied_minute_count: 1 },
|
|
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "04:49", relative_support: 42, tied_minute_count: 2 },
|
|
],
|
|
}),
|
|
}),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
await assert.rejects(
|
|
() => (tools["rectification-offer-candidates"] as unknown as {
|
|
execute(input: unknown): Promise<unknown>;
|
|
}).execute({ caseId: CASE_ID }),
|
|
(error: unknown) => error instanceof RectificationToolServiceError && error.code === "offer_not_allowed",
|
|
);
|
|
assert.equal(
|
|
accounting.calls.some((call) =>
|
|
call.fn === "transition_agentic_rectification_case_status"
|
|
&& call.args.p_status === "candidate_ready"
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("user stop with selection_allowed may offer the escape hatch", async () => {
|
|
const accounting = fakeAccounting({
|
|
...receiptHandlers,
|
|
get_agentic_rectification_case_dossier: () => dossierFixture({
|
|
evidence: [educationEvidence],
|
|
turns: [{
|
|
id: TURN_ID,
|
|
role: "user",
|
|
text: "暂时想不到了",
|
|
status: "completed",
|
|
created_at: "2026-08-12T10:00:00.000Z",
|
|
completed_at: "2026-08-12T10:00:05.000Z",
|
|
}],
|
|
latestResult: candidateSnapshotFixture({
|
|
selectionAllowed: true,
|
|
representativeTime: "04:48",
|
|
candidates: [
|
|
{ candidate_id: CANDIDATE_ID, rank: 1, time: "04:48", relative_support: 58, tied_minute_count: 1 },
|
|
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "04:49", relative_support: 42, tied_minute_count: 2 },
|
|
],
|
|
}),
|
|
}),
|
|
transition_agentic_rectification_case_status: () => ({
|
|
case_id: CASE_ID,
|
|
status: "candidate_ready",
|
|
idempotent: false,
|
|
}),
|
|
});
|
|
const tools = createRectificationV9Tools({
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
turnId: TURN_ID,
|
|
accounting: accounting.client as never,
|
|
});
|
|
const projection = await (tools["rectification-offer-candidates"] as unknown as {
|
|
execute(input: unknown): Promise<{ session_outcome: { kind: string } }>;
|
|
}).execute({ caseId: CASE_ID });
|
|
assert.equal(projection.session_outcome.kind, "adopt_representative");
|
|
assert.equal(
|
|
accounting.calls.some((call) =>
|
|
call.fn === "transition_agentic_rectification_case_status"
|
|
&& call.args.p_status === "candidate_ready"
|
|
),
|
|
true,
|
|
);
|
|
});
|
|
|