Files
Jyotisha/frontend/tests/rectification-decision-authority.test.ts
T
Jesse_Chen 53eb815c95 fix(rectification): read credible_range as the still-valid union
Write and read used different range definitions at the same lead of 8, so a real separation always fail-closed the candidate projection.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 23:12:16 +08:00

712 lines
33 KiB
TypeScript

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 { selectDiscriminatorProbe } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
import { contrastPacketFromDossier, decideFromDossier, overlayPublicDecision } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts";
import { buildMethodFollowupPlan, conversationalSessionOutcome } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { projectTurnDecision } from "../src/lib/rectification-agentic/v9/turn-decision.ts";
import { parseV9CaseDossier, parseV9ComputeProjection } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import { safeCaseProjection } from "../src/mastra/rectification-v9-tools.ts";
import {
candidateSnapshotFixture,
computeFixture,
dossierFixture,
} from "./rectification-v9-test-support.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"],
[],
]);
assert.equal(packet.probes[0]?.expectedOutcomes.at(-1)?.outcomeId, "unsure");
});
test("answered inference probes drop out of the catalog by probe_id", () => {
const outcomes = [
{ answer_class: "yes" as const, supports: ["05:00"], conflicts: ["05:12"] },
{ answer_class: "no" as const, supports: ["05:12"], conflicts: ["05:00"] },
{ answer_class: "unsure" as const, supports: [], conflicts: [] },
];
const packet = contrastPacketFromDossier({
evidence: [],
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
latestResult: {
resultId: "result-answered-probe",
candidates: [
{ candidateId: "c-0500", time: "05:00", relativeSupport: 20 },
{ 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:12",
revision: 1,
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.6, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "05:12", time: "05:12", cluster_range: ["05:12", "05:12"], prior_score: 15, posterior_score: 15, probability: 0.4, status: "active", rank: 2, strong_conflict_count: 0 },
],
events: [],
probes: [{
id: "probe:career.2018.dasha_boundary:hash",
year: 2018,
domain: "career",
source: "dasha_boundary",
question: "2018 年 3 月前后 career",
semantic_key: "career.2018.dasha_boundary",
candidate_ids: ["05:00", "05:12"],
information_gain: 1.2,
expected_outcomes: outcomes,
candidate_split_hash: "hash",
}],
answered_probes: [{
probe_id: "probe:career.2018.dasha_boundary:hash",
semantic_key: "career.2018.dasha_boundary",
candidate_split_hash: "hash",
answer_class: "yes",
classified_from: "choice",
}],
rounds: [],
entropy: 1,
representative_time: "05:00",
credible_range: ["05:00", "05:12"],
},
},
},
case: { acceptedTime: null },
});
assert.equal(packet.probes.some((probe) => probe.semanticKey === "career.2018.dasha_boundary"), false);
const adapter = readSource("../src/lib/rectification-agentic/v9/decision-from-dossier.ts");
assert.match(adapter, /answered_probes[\s\S]{0,120}item\.probe_id/);
assert.doesNotMatch(adapter, /answered_probes[\s\S]{0,120}item\.id\)/);
});
test("scored inference catalog outranks a low-gain Python career probe when snapshot candidates are empty", () => {
const careerOutcomes = [
{ answer_class: "yes", supports: ["04:45", "05:00", "05:14"], conflicts: ["05:15"] },
{ answer_class: "weak_yes", supports: ["04:45", "05:00", "05:14"], conflicts: ["05:15"] },
{ answer_class: "no", supports: ["05:15"], conflicts: ["04:45", "05:00", "05:14"] },
{ answer_class: "unsure", supports: [], conflicts: [] },
];
const d24Outcomes = [
{ answer_class: "yes", supports: ["04:47"], conflicts: ["04:51", "04:53", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"] },
{ answer_class: "weak_yes", supports: ["04:51", "04:53"], conflicts: ["04:47", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"] },
{ answer_class: "no", supports: ["04:59"], conflicts: ["04:47", "04:51", "04:53", "05:00", "05:07", "05:12", "05:14", "05:15"] },
{ answer_class: "unsure", supports: [], conflicts: [] },
];
const inferenceCandidates = [
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 22, posterior_score: 22, probability: 0.51, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "active", rank: 2, strong_conflict_count: 0 },
{ id: "05:12", time: "05:12", cluster_range: ["05:12", "05:14"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "equivalent", rank: 3, strong_conflict_count: 0 },
{ id: "05:14", time: "05:14", cluster_range: ["05:12", "05:14"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "equivalent", rank: 4, strong_conflict_count: 0 },
{ id: "04:47", time: "04:47", cluster_range: ["04:47", "04:47"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 5, strong_conflict_count: 0 },
{ id: "04:51", time: "04:51", cluster_range: ["04:51", "04:51"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 6, strong_conflict_count: 0 },
{ id: "04:53", time: "04:53", cluster_range: ["04:53", "04:53"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 7, strong_conflict_count: 0 },
{ id: "04:59", time: "04:59", cluster_range: ["04:59", "04:59"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 8, strong_conflict_count: 0 },
{ id: "05:15", time: "05:15", cluster_range: ["05:15", "05:15"], prior_score: 3, posterior_score: 3, probability: 0.004, status: "active", rank: 9, strong_conflict_count: 0 },
];
const evidence = [
{ status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2016-09-01", occurredTo: null, eventKind: "education_start" },
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: null, eventKind: "relationship_end" },
{ status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: null, eventKind: "career_entry" },
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null, eventKind: "family_event" },
];
const dossier = {
evidence,
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
latestResult: {
resultId: "result-empty-snapshot",
candidates: [],
decisionReceipt: {
discriminating_event_probes: [{
role: "distinguish",
phase: "candidate_discriminator",
year: 2023,
year_label: "2023 年前后",
domain: "career",
event_family: "入职、升职或职责明显加重",
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: false,
unique_minute_claim: false,
user_meaning: "时间范围锁定 2023 年前后;领域锁定 career。",
choice_kind: "existence",
information_gain: 0.56,
semantic_key: "career.2023.dasha_activation",
candidate_split_hash: "500ce694938305201fbab9ba",
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
expected_outcomes: careerOutcomes,
style_options: [
{ label: "明确发生且时间吻合", answer_class: "yes" },
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
{ label: "明确没有发生", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
],
}],
inference_state: {
algorithm_version: "rectification-inference-v1",
candidate_set_id: "04:45-05:15:04:47,04:51,04:53,04:59,05:00,05:07,05:12,05:14,05:15",
revision: 1,
phase: "discrimination",
result_status: "discriminating",
range_start: "04:45",
range_end: "05:15",
candidates: inferenceCandidates,
events: [],
probes: [{
id: "probe:career.2023.dasha_activation:500ce694938305201fbab9ba",
year: 2023,
domain: "career",
source: "dasha_activation",
question: "时间范围锁定 2023 年前后;领域锁定 career。",
semantic_key: "career.2023.dasha_activation",
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
information_gain: 0.56,
expected_outcomes: careerOutcomes,
candidate_split_hash: "500ce694938305201fbab9ba",
}, {
id: "contrast:varga.d24.04:47/04:51|04:53/04:59/05:00/05:07|05:12/05:14|05:15",
year: 0,
domain: "education",
source: "varga_contrast",
question: "引擎给出的区分机会绑定 D24。",
semantic_key: "varga.d24.04:47/04:51|04:53/04:59/05:00/05:07|05:12/05:14|05:15",
candidate_ids: ["04:47", "04:51", "04:53", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"],
information_gain: 2.503258334775646,
expected_outcomes: d24Outcomes,
candidate_split_hash: "04:45-05:15:varga.d24",
}],
answered_probes: [],
rounds: [],
entropy: 2.0,
representative_time: "05:00",
credible_range: ["05:00", "05:14"],
},
window_scan: {
scanned: true,
d24_lagna_count: 6,
d24_candidates_differ: true,
transitions: [
{ layer: "d24", at: "04:48", from_sign: "白羊座", to_sign: "金牛座" },
{ layer: "d24", at: "04:54", from_sign: "金牛座", to_sign: "双子座" },
{ layer: "d24", at: "05:00", from_sign: "双子座", to_sign: "巨蟹座" },
{ layer: "d24", at: "05:06", from_sign: "巨蟹座", to_sign: "狮子座" },
{ layer: "d24", at: "05:13", from_sign: "狮子座", to_sign: "处女座" },
],
},
},
},
case: { acceptedTime: null },
};
const packet = contrastPacketFromDossier(dossier);
const selected = selectDiscriminatorProbe(packet);
assert.equal(packet.probes.some((probe) => probe.semanticKey.includes("career.2023")), false);
assert.match(selected?.semanticKey ?? "", /^varga\.d24\./);
assert.ok((selected?.informationGain ?? 0) > 2);
assert.doesNotMatch(selected?.semanticKey ?? "", /career\.2023/);
const plan = buildMethodFollowupPlan({
evidence,
eventProbes: [{
year: 2023,
year_label: "2023 年前后",
domain: "career",
event_family: "入职、升职或职责明显加重",
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: false,
unique_minute_claim: false,
user_meaning: "时间范围锁定 2023 年前后。",
role: "distinguish",
phase: "candidate_discriminator",
information_gain: 0.56,
semantic_key: "career.2023.dasha_activation",
candidate_split_hash: "500ce694938305201fbab9ba",
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
expected_outcomes: careerOutcomes,
style_options: [
{ label: "明确发生且时间吻合", answer_class: "yes" },
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
{ label: "明确没有发生", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
],
}],
contrastPacket: packet,
candidatesSeparated: false,
});
assert.equal(plan.next_followup?.semantic_key, "career.2023.dasha_activation");
assert.match(plan.next_followup?.choice_frame?.period ?? "", /2023 年前后/);
assert.doesNotMatch(plan.next_followup?.semantic_key ?? "", /varga\.d24/);
});
test("career and relationship training still discriminates before family or occupation coverage", () => {
const d24Outcomes = [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:07", "05:12"] },
{ answer_class: "weak_yes", supports: ["05:07"], conflicts: ["05:00", "05:12"] },
{ answer_class: "no", supports: ["05:12"], conflicts: ["05:00", "05:07"] },
{ answer_class: "unsure", supports: [], conflicts: [] },
];
const inferenceCandidates = [
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 23, posterior_score: 23, probability: 0.58, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 17, posterior_score: 17, probability: 0.21, status: "active", rank: 2, strong_conflict_count: 0 },
{ id: "05:12", time: "05:12", cluster_range: ["05:12", "05:12"], prior_score: 17, posterior_score: 17, probability: 0.21, status: "active", rank: 3, strong_conflict_count: 0 },
];
const evidence = [
{ id: "edu-2016", status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null, eventKind: "education_milestone" },
{ id: "career-exit", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-10-01", occurredTo: null, eventKind: "career_exit" },
{ id: "career-entry", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: null, eventKind: "career_entry" },
{ id: "rel-start", status: "confirmed", domain: "relationship", datePrecision: "month", occurredFrom: "2024-05-01", occurredTo: null, eventKind: "relationship_start" },
{ id: "rel-end", status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: null, eventKind: "relationship_end" },
];
const familyProbe = {
year: 2013,
year_label: "2013 年 3 月前后",
domain: "family",
event_family: "家人结婚、添丁或住院",
source: "dasha_boundary",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "时间范围锁定 2013 年 3 月前后。",
role: "distinguish",
phase: "candidate_discriminator",
information_gain: 1.2,
semantic_key: "family.2013.03.dasha_boundary",
candidate_split_hash: "family.2013.03",
candidate_ids: ["05:00", "05:07", "05:12"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:07", "05:12"] },
{ answer_class: "weak_yes", supports: ["05:07"], conflicts: ["05:00", "05:12"] },
{ answer_class: "no", supports: ["05:12"], conflicts: ["05:00", "05:07"] },
{ answer_class: "unsure", supports: [], conflicts: [] },
],
style_options: [
{ label: "明确发生且时间吻合", answer_class: "yes" },
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
{ label: "明确没有发生", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
],
};
const dossier = {
evidence,
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
latestResult: {
resultId: "result-unseparated",
candidates: [],
decisionReceipt: {
discriminating_event_probes: [familyProbe],
inference_state: {
algorithm_version: "rectification-inference-v1",
candidate_set_id: "05:00-05:12:05:00,05:07,05:12",
revision: 1,
phase: "discrimination",
result_status: "discriminating",
range_start: "05:00",
range_end: "05:12",
candidates: inferenceCandidates,
events: [
{ id: "edu-2016", year: 2016, usage: "training", domain: "education", precision: "year" },
{ id: "career-exit", year: 2020, usage: "training", domain: "career", precision: "month" },
{ id: "career-entry", year: 2020, usage: "holdout", domain: "career", precision: "month" },
{ id: "rel-start", year: 2024, usage: "training", domain: "relationship", precision: "month" },
{ id: "rel-end", year: 2024, usage: "training", domain: "relationship", precision: "day" },
],
probes: [{
id: "contrast:varga.d24.05:00|05:07|05:12",
year: 0,
domain: "education",
source: "varga_contrast",
question: "引擎给出的区分机会绑定 D24。",
semantic_key: "varga.d24.05:00|05:07|05:12",
candidate_ids: ["05:00", "05:07", "05:12"],
information_gain: 2.5,
expected_outcomes: d24Outcomes,
candidate_split_hash: "05:00-05:12:varga.d24",
}],
answered_probes: [],
rounds: [],
entropy: 1.5,
representative_time: "05:00",
credible_range: ["05:00", "05:12"],
},
window_scan: {
scanned: true,
d24_lagna_count: 3,
d24_candidates_differ: true,
transitions: [
{ layer: "d24", at: "05:07", from_sign: "巨蟹座", to_sign: "狮子座" },
{ layer: "d24", at: "05:12", from_sign: "狮子座", to_sign: "处女座" },
],
},
},
},
case: { acceptedTime: null },
};
const decision = decideFromDossier(dossier);
assert.equal(decision.nextAction, "ask_candidate_discriminator");
assert.equal(decision.sessionOutcome, "discriminate_candidates");
const packet = contrastPacketFromDossier(dossier);
const plan = buildMethodFollowupPlan({
evidence,
eventProbes: [familyProbe],
contrastPacket: packet,
sessionOutcome: decision.sessionOutcome,
});
assert.equal(plan.next_followup?.intent, "distinguish_candidates");
assert.equal(plan.next_followup?.semantic_key, "family.2013.03.dasha_boundary");
assert.ok(plan.next_followup?.choice_frame);
assert.match(plan.next_followup?.choice_frame?.period ?? "", /2013 年 3 月前后/);
assert.doesNotMatch(plan.next_followup?.choice_frame?.prompt ?? "", /2016 年前后/);
assert.equal(conversationalSessionOutcome({
selectionAllowed: false,
proposeAllowed: false,
confirmationAllowed: false,
nextFollowup: plan.next_followup,
methods: plan.methods,
discriminatorProbe: selectDiscriminatorProbe(packet),
candidateScores: [],
trainingGateOpen: true,
evidence,
}), "discriminate_candidates");
assert.equal(plan.methods.find((item) => item.method_id === "relatives")?.status, "uncovered");
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
});
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 [["04:00", "04:10"], ["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("turn decision and safe case projection share session_outcome and selection_allowed", () => {
const dossier = parseV9CaseDossier(dossierFixture({
latestResult: candidateSnapshotFixture({
selectionAllowed: true,
representativeTime: "05:00",
}),
}));
const compute = parseV9ComputeProjection(computeFixture());
assert.ok(dossier);
assert.ok(compute);
const turn = projectTurnDecision(dossier);
const safe = safeCaseProjection(dossier, compute);
assert.equal(
(turn.next_action as { session_outcome: unknown }).session_outcome,
(safe.latest_result as { session_outcome: unknown }).session_outcome,
);
assert.equal(
(turn.candidate_summary as { selection_allowed: unknown }).selection_allowed,
(safe.latest_result as { selection_allowed: unknown }).selection_allowed,
);
});
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 separation = readSource("../src/lib/rectification-agentic/core/candidate-separation.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, /decideFromDossier\(/);
assert.match(tools, /overlayPublicDecision/);
assert.match(separation, /sole_candidate/);
assert.doesNotMatch(separation, /top \? MIN_SEPARATION_LEAD/);
assert.doesNotMatch(tools, /decideConversationalSession\(/);
assert.doesNotMatch(tools, /sessionOutcome \?\? "collect_evidence"/);
assert.doesNotMatch(followup, /input\.sessionOutcome \?\? "collect_evidence"/);
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/);
});