import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { decideRectification, publicDecisionFields, } from "../src/lib/rectification-agentic/core/rectification-decision.ts"; import { decideNextAction } from "../src/lib/rectification-agentic/core/decide-next-action.ts"; import { contrastPacketFromDossier, overlayPublicDecision } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts"; import { conversationalSessionOutcome } from "../src/lib/rectification-agentic/v9/method-followup.ts"; const SEPARATED = [ { time: "04:48", score: 58 }, { time: "04:49", score: 42 }, ]; function readSource(relative: string) { return readFileSync(new URL(relative, import.meta.url), "utf8"); } test("public decision fields are derived from decideRectification", () => { const fixtures = [ { methodCoverageAll: false, trainingGateOpen: false, candidateScores: SEPARATED, }, { methodCoverageAll: true, trainingGateOpen: true, candidateScores: [ { time: "05:00", score: 34 }, { time: "05:01", score: 33 }, { time: "05:02", score: 33 }, ], }, { methodCoverageAll: true, trainingGateOpen: true, candidateScores: SEPARATED, holdoutValidation: "passed" as const, }, { methodCoverageAll: true, userStopped: true, candidateScores: SEPARATED, holdoutValidation: "not_started" as const, }, ]; for (const input of fixtures) { const decision = decideRectification(input); const fields = publicDecisionFields(decision); assert.deepEqual(fields, { type: decision.nextAction, session_outcome: decision.sessionOutcome, completion_status: decision.completionStatus, validated: decision.validated, can_offer_range: decision.canOfferRange, can_adopt: decision.canAdopt, can_confirm_exact_minute: decision.canConfirmExactMinute, selection_allowed: decision.selectionAllowed, propose_allowed: decision.proposeAllowed, precision_stage: decision.precisionStage, representative_time: decision.representativeTime, credible_range: decision.credibleRange, }); const publicOverlay = overlayPublicDecision({ selectionAllowed: true }, decision); assert.equal(publicOverlay.selectionAllowed, false); assert.equal(publicOverlay.validated, fields.validated); assert.equal(decideNextAction(input).type, decision.nextAction); assert.equal(conversationalSessionOutcome({ selectionAllowed: decision.selectionAllowed, proposeAllowed: decision.proposeAllowed, confirmationAllowed: false, nextFollowup: null, userStopped: input.userStopped, candidateScores: input.candidateScores, holdoutValidation: input.holdoutValidation, trainingGateOpen: input.trainingGateOpen, }), decision.sessionOutcome); } }); test("recorded education evidence does not suppress an unasked D24 discriminator", () => { const packet = contrastPacketFromDossier({ evidence: [{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null, eventKind: "education_exam", summary: "2016 年考试发挥失常", }], conversationSummary: { activeFocus: null, declinedSkippedTopics: [] }, latestResult: { resultId: "result-d24", candidates: [ { candidateId: "c-0500", time: "05:00", relativeSupport: 20 }, { candidateId: "c-0507", time: "05:07", relativeSupport: 15 }, { candidateId: "c-0512", time: "05:12", relativeSupport: 15 }, ], decisionReceipt: { inference_state: { algorithm_version: "rectification-inference-v1", candidate_set_id: "05:00-05:12:05:00,05:07,05:12", revision: 0, phase: "discrimination", result_status: "discriminating", range_start: "05:00", range_end: "05:12", candidates: [ { id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 20, posterior_score: 20, probability: 0.4, status: "active", rank: 1, strong_conflict_count: 0 }, { id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 15, posterior_score: 15, probability: 0.3, status: "active", rank: 2, strong_conflict_count: 0 }, { id: "05:12", time: "05:12", cluster_range: ["05:12", "05:12"], prior_score: 15, posterior_score: 15, probability: 0.3, status: "active", rank: 3, strong_conflict_count: 0 }, ], events: [], probes: [], answered_probes: [], rounds: [], entropy: 1, representative_time: "05:00", credible_range: ["05:00", "05:12"], }, window_scan: { scanned: true, d9_lagna_count: 1, d10_lagna_count: 1, d24_lagna_count: 3, transitions: [ { layer: "d24", at: "05:07", from_sign: "白羊座", to_sign: "金牛座" }, { layer: "d24", at: "05:12", from_sign: "金牛座", to_sign: "双子座" }, ], }, }, }, case: { acceptedTime: null }, }); assert.equal(packet.probes[0]?.semanticKey.startsWith("varga.d24."), true); assert.equal(packet.probes[0]?.informationGain > 1, true); assert.deepEqual(packet.probes[0]?.expectedOutcomes.map((row) => row.supportsCandidateIds), [ ["05:00"], ["05:07"], ["05:12"], ]); }); test("public candidate cards follow the inference ranking and hide an inconsistent state", () => { const decision = decideRectification({ methodCoverageAll: true, trainingGateOpen: true, candidateScores: SEPARATED, holdoutValidation: "passed", }); const base = { candidates: [ { candidateId: "c-0507", time: "05:07", rank: 1, relativeSupport: 13, tiedMinuteCount: 1 }, { candidateId: "c-0500", time: "05:00", rank: 2, relativeSupport: 12, tiedMinuteCount: 1 }, { candidateId: "c-0515", time: "05:15", rank: 3, relativeSupport: 10, tiedMinuteCount: 1 }, ], representativeTime: "05:07", decisionReceipt: { inference_state: { algorithm_version: "rectification-inference-v1", candidate_set_id: "05:00-05:15:05:00,05:07,05:15", revision: 2, phase: "discrimination", result_status: "discriminating", range_start: "05:00", range_end: "05:15", candidates: [ { id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 13, posterior_score: 15, probability: 0.4, status: "active", rank: 2, strong_conflict_count: 0 }, { id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 12, posterior_score: 20, probability: 0.6, status: "active", rank: 1, strong_conflict_count: 0 }, { id: "05:15", time: "05:15", cluster_range: ["05:15", "05:15"], prior_score: 10, posterior_score: 30, probability: 0, status: "eliminated", rank: 3, strong_conflict_count: 1 }, ], events: [], probes: [], answered_probes: [], rounds: [], entropy: 0.67, representative_time: "05:00", credible_range: ["05:00", "05:07"], }, }, }; const missingInference = overlayPublicDecision({ ...base, decisionReceipt: {}, }, decision); assert.deepEqual(missingInference.candidates, []); assert.equal(missingInference.representativeTime, null); assert.equal(missingInference.selectionAllowed, false); assert.equal(missingInference.can_adopt, false); const projected = overlayPublicDecision(base, decision); assert.deepEqual(projected.candidates.map((item) => item.time), ["05:00", "05:07"]); assert.deepEqual(projected.candidates.map((item) => item.relativeSupport), [20, 15]); assert.equal(projected.representativeTime, "05:00"); assert.deepEqual(projected.credible_range, ["05:00", "05:07"]); const incompleteCandidateStates = [ base.decisionReceipt.inference_state.candidates.slice(0, 2), [ ...base.decisionReceipt.inference_state.candidates.slice(0, 2), { ...base.decisionReceipt.inference_state.candidates[0], status: "eliminated", probability: 0 }, ], ]; for (const candidates of incompleteCandidateStates) { const incomplete = overlayPublicDecision({ ...base, decisionReceipt: { inference_state: { ...base.decisionReceipt.inference_state, candidates, }, }, }, decision); assert.deepEqual(incomplete.candidates, []); assert.equal(incomplete.representativeTime, null); assert.equal(incomplete.selectionAllowed, false); assert.equal(incomplete.can_adopt, false); } const malformedInferenceStates = [ { ...base.decisionReceipt.inference_state, revision: undefined }, { ...base.decisionReceipt.inference_state, candidate_set_id: "" }, { ...base.decisionReceipt.inference_state, candidate_set_id: "wrong-set" }, { ...base.decisionReceipt.inference_state, range_start: undefined }, { ...base.decisionReceipt.inference_state, events: [null] }, { ...base.decisionReceipt.inference_state, probes: [{ id: "broken" }] }, { ...base.decisionReceipt.inference_state, answered_probes: [{ probe_id: "broken" }] }, { ...base.decisionReceipt.inference_state, rounds: [{ round: 1 }] }, { ...base.decisionReceipt.inference_state, candidates: base.decisionReceipt.inference_state.candidates.map((candidate, index) => ( index === 0 ? { ...candidate, status: undefined } : candidate )), }, { ...base.decisionReceipt.inference_state, candidates: base.decisionReceipt.inference_state.candidates.map((candidate, index) => ( index === 0 ? { ...candidate, posterior_score: undefined } : candidate )), }, { ...base.decisionReceipt.inference_state, candidates: base.decisionReceipt.inference_state.candidates.map((candidate, index) => ( index === 0 ? { ...candidate, cluster_range: undefined } : candidate )), }, ]; for (const inferenceState of malformedInferenceStates) { const malformed = overlayPublicDecision({ ...base, decisionReceipt: { inference_state: inferenceState }, }, decision); assert.deepEqual(malformed.candidates, []); assert.equal(malformed.representativeTime, null); assert.equal(malformed.selectionAllowed, false); assert.equal(malformed.can_adopt, false); } const inconsistent = overlayPublicDecision({ ...base, decisionReceipt: { inference_state: { ...base.decisionReceipt.inference_state, representative_time: "05:15", }, }, }, decision); assert.deepEqual(inconsistent.candidates, []); assert.equal(inconsistent.selectionAllowed, false); assert.equal(inconsistent.can_adopt, false); for (const credibleRange of [["05:00", "05:00"], ["05:07", "05:00"]] as const) { const rangeMismatch = overlayPublicDecision({ ...base, decisionReceipt: { inference_state: { ...base.decisionReceipt.inference_state, credible_range: credibleRange, }, }, }, decision); assert.deepEqual(rangeMismatch.candidates, []); assert.equal(rangeMismatch.representativeTime, null); assert.equal(rangeMismatch.credible_range, null); assert.equal(rangeMismatch.selectionAllowed, false); assert.equal(rangeMismatch.can_adopt, false); } }); test("interview, choice, refresh and next-action all call the same reducer", () => { const interview = readSource("../src/lib/rectification-agentic/v9/interview-state.ts"); const adapter = readSource("../src/lib/rectification-agentic/v9/decision-from-dossier.ts"); const choice = readSource("../src/lib/rectification-agentic/v9/answer-choice.ts"); const refresh = readSource("../src/lib/rectification-agentic/v9/turn-decision.ts"); const followup = readSource("../src/lib/rectification-agentic/v9/method-followup.ts"); const tools = readSource("../src/mastra/rectification-v9-tools.ts"); const caseRoute = readSource("../src/app/api/rectification/cases/[caseId]/route.ts"); assert.match(interview, /decideFromDossier\(/); assert.match(refresh, /decideFromDossier\(/); assert.match(choice, /decideAfterInferenceChange\(/); assert.match(adapter, /decideRectification\(/); assert.match(followup, /decideRectification\(/); assert.match(tools, /decideConversationalSession\(/); assert.match(caseRoute, /overlayPublicDecision/); assert.match(caseRoute, /publicDecisionFields\(decision\)/); assert.doesNotMatch(interview, /sessionOutcomeFromGate/); assert.doesNotMatch(choice, /sessionOutcomeFromGate/); assert.doesNotMatch(refresh, /sessionOutcomeFromGate/); assert.doesNotMatch(tools, /sessionOutcomeFromGate/); assert.doesNotMatch(interview, /selectionAllowed: dossier\.latestResult/); assert.doesNotMatch(refresh, /selection_allowed: dossier\.latestResult\?\.selectionAllowed/); assert.doesNotMatch(tools, /selection_allowed: decision\?\.selectionAllowed \?\? latest/); assert.doesNotMatch(tools, /propose_allowed: decision\?\.proposeAllowed \?\? proposeAllowed/); assert.match(tools, /evidence: parsed\.evidence/); assert.match(followup, /evidence: input\.evidence/); assert.match(adapter, /trainingGateOpen: trainingGate\.open/); assert.match(adapter, /blockingMethodsCovered/); });