Files
Jyotisha/frontend/tests/rectification-decision-authority.test.ts
T
Jesse_Chen 20df8c6022
Independent Staging Quality Gate / validate (push) Successful in 10m34s
Independent Staging Quality Gate / publish (push) Successful in 13m34s
fix(rectification): let engine-ready cases adopt a representative minute
deliveryCapability no longer ties adoption to minute separation or holdout;
those stay on the exact-minute confirmation gate so users can save a range.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 16:52:01 +08:00

1336 lines
58 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
decideRectification,
engineCapabilityCeilingFromReceipt,
publicDecisionFields,
} from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { decideNextAction } from "../src/lib/rectification-agentic/core/decide-next-action.ts";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import {
inspectDiscriminatorProbes,
selectDiscriminatorProbe,
type CandidateDiscriminatorProbe,
} 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 {
applyChoiceWithoutEvidence,
nakshatraBoundaryProbe,
stampChoiceSchemaWithProbe,
withNakshatraBoundaryProbe,
} from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
import { buildMethodFollowupPlan, conversationalSessionOutcome } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { awaitTurnExitBeforeResponse, finalizeSuccessfulTurnExit } from "../src/lib/rectification-agentic/v9/turn-exit.ts";
import type { DiscriminatingEventProbe } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
import { projectCurrentQuestion, projectTurnDecision } from "../src/lib/rectification-agentic/v9/turn-decision.ts";
import {
evidenceLedgerFingerprint,
parseV9CaseDossier,
parseV9ComputeProjection,
} from "../src/lib/rectification-agentic/v9/tool-service.ts";
import { safeCaseProjection } from "../src/mastra/rectification-v9-tools.ts";
import {
CASE_ID,
FOCUS_ID,
TURN_ID,
USER_ID,
candidateSnapshotFixture,
computeFixture,
dossierFixture,
fakeAccounting,
} from "./rectification-v9-test-support.ts";
const SEPARATED = [
{ time: "04:48", score: 58 },
{ time: "04:49", score: 42 },
];
type TestEngineCapabilityCeiling = Readonly<{
acceptanceAllowed: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
confirmationAllowed: boolean;
}>;
const ENGINE_OPEN: TestEngineCapabilityCeiling = {
acceptanceAllowed: true,
selectionAllowed: true,
proposeAllowed: true,
confirmationAllowed: true,
};
function capabilityCombinations(): TestEngineCapabilityCeiling[] {
return Array.from({ length: 16 }, (_, bits) => ({
acceptanceAllowed: Boolean(bits & 1),
selectionAllowed: Boolean(bits & 2),
proposeAllowed: Boolean(bits & 4),
confirmationAllowed: Boolean(bits & 8),
}));
}
function decideWithEngineCeiling(
engineCeiling: TestEngineCapabilityCeiling,
overrides: Partial<Parameters<typeof decideRectification>[0]> = {},
) {
return decideRectification({
methodCoverageAll: true,
trainingGateOpen: true,
snapshotCurrent: true,
candidateScores: SEPARATED,
holdoutValidation: "passed",
confirmationAllowed: true,
datedEventCount: 3,
datedDomainCount: 2,
...overrides,
engineCeiling,
} as Parameters<typeof decideRectification>[0]);
}
test("invariant 1: TypeScript delivery capabilities never exceed the engine ceiling", () => {
for (const ceiling of capabilityCombinations()) {
const decision = decideWithEngineCeiling(ceiling);
assert.equal(decision.canAdopt && !ceiling.acceptanceAllowed, false, JSON.stringify(ceiling));
assert.equal(decision.selectionAllowed && !ceiling.selectionAllowed, false, JSON.stringify(ceiling));
assert.equal(decision.proposeAllowed && !ceiling.proposeAllowed, false, JSON.stringify(ceiling));
assert.equal(decision.canConfirmExactMinute && !ceiling.confirmationAllowed, false, JSON.stringify(ceiling));
}
});
test("invariant 2: insufficient dated evidence never permits selection or adoption", () => {
for (const datedEventCount of [0, 2, 3]) {
for (const datedDomainCount of [0, 1, 2]) {
if (datedEventCount >= 3 && datedDomainCount >= 2) continue;
const decision = decideWithEngineCeiling(ENGINE_OPEN, {
confirmationAllowed: false,
datedEventCount,
datedDomainCount,
});
assert.equal(decision.canAdopt, false, `${datedEventCount} events / ${datedDomainCount} domains`);
assert.equal(decision.selectionAllowed, false, `${datedEventCount} events / ${datedDomainCount} domains`);
}
}
});
test("invariant 3: tied leading candidates can provisionally adopt, but exact-minute confirm stays closed", () => {
const tiedScores = [
{ time: "04:48", score: 12 },
{ time: "04:49", score: 12 },
{ time: "04:50", score: 11 },
];
const stopped = decideWithEngineCeiling(ENGINE_OPEN, {
candidateScores: tiedScores,
confirmationAllowed: false,
userStopped: true,
});
assert.equal(stopped.completionStatus, "provisional_range_user_stopped");
// 原断言 canAdopt=false(并列不得采用)→ 新断言 canAdopt=true。
// 产品 2026-09-01 授权:并列只挡唯一分钟确认门,不挡代表性采用。
assert.equal(stopped.canAdopt, true);
assert.equal(stopped.canConfirmExactMinute, false);
const continuing = decideWithEngineCeiling(ENGINE_OPEN, {
candidateScores: tiedScores,
confirmationAllowed: false,
userStopped: false,
});
assert.equal(continuing.separation.tiedForFirst, true);
assert.equal(continuing.canAdopt, true);
assert.equal(continuing.canConfirmExactMinute, false);
});
test("invariant 4: unavailable holdout still allows provisional adopt; exact-minute confirm stays closed", () => {
for (const userStopped of [false, true]) {
const decision = decideWithEngineCeiling(ENGINE_OPEN, {
confirmationAllowed: false,
holdoutValidation: "unavailable",
userStopped,
});
// 原断言 canAdopt=falseholdout unavailable 不放行)→ 新断言 canAdopt=true。
// holdout 只保留给唯一分钟确认门;引擎 ceiling 仍是硬上限。
assert.equal(decision.canAdopt, true, `userStopped=${userStopped}`);
assert.equal(decision.canConfirmExactMinute, false, `userStopped=${userStopped}`);
}
});
test("raw engine receipt contradictions fail closed before delivery", () => {
const openReceipt = {
acceptance_allowed: true,
selection_allowed: true,
propose_allowed: true,
confirmation_allowed: false,
};
const closed = {
acceptanceAllowed: false,
selectionAllowed: false,
proposeAllowed: false,
confirmationAllowed: false,
};
for (const receipt of [
{ ...openReceipt, accept_allowed: false },
{ ...openReceipt, confirm_allowed: true },
{ ...openReceipt, display_allowed: false },
{ ...openReceipt, confirmation_allowed: undefined },
]) {
assert.deepEqual(engineCapabilityCeilingFromReceipt(receipt), closed);
}
});
test("invariant 5: public overlay intersects all delivery gates with the engine receipt", () => {
const decision = decideWithEngineCeiling(ENGINE_OPEN);
const 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 },
];
const inferenceState = {
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 validEngineCeilings = capabilityCombinations().filter((ceiling) => (
ceiling.acceptanceAllowed === ceiling.selectionAllowed
&& (!ceiling.proposeAllowed || ceiling.selectionAllowed)
&& (!ceiling.confirmationAllowed || ceiling.selectionAllowed)
));
for (const ceiling of validEngineCeilings) {
const overlaid = overlayPublicDecision({
candidates,
representativeTime: "05:07",
decisionReceipt: {
acceptance_allowed: ceiling.acceptanceAllowed,
selection_allowed: ceiling.selectionAllowed,
propose_allowed: ceiling.proposeAllowed,
confirmation_allowed: ceiling.confirmationAllowed,
inference_state: inferenceState,
},
}, decision);
assert.equal(overlaid.can_adopt, ceiling.acceptanceAllowed, JSON.stringify(ceiling));
assert.equal(overlaid.selection_allowed, ceiling.selectionAllowed, JSON.stringify(ceiling));
assert.equal(overlaid.propose_allowed, ceiling.proposeAllowed, JSON.stringify(ceiling));
assert.equal(overlaid.can_confirm_exact_minute, ceiling.confirmationAllowed, JSON.stringify(ceiling));
}
});
function readSource(relative: string) {
return readFileSync(new URL(relative, import.meta.url), "utf8");
}
const ADVANCING_RECTIFICATION_ACTIONS = [
"opening",
"message",
"answer_choice",
"stop_and_review",
] as const;
function routeExitContract(route: string, turnExit: string) {
const immediateStart = route.indexOf("if (immediateResponse)");
const immediateEnd = route.indexOf("const requestTime", immediateStart);
const immediateExit = immediateStart >= 0 && immediateEnd > immediateStart
? route.slice(immediateStart, immediateEnd)
: "";
const awaitIndex = immediateExit.indexOf("await awaitTurnExitBeforeResponse");
const returnIndex = immediateExit.indexOf("return response", awaitIndex);
const immediateAwaited = awaitIndex >= 0
&& returnIndex > awaitIndex
&& /finalizeSuccessfulTurnExit\s*\(/.test(immediateExit.slice(awaitIndex, returnIndex));
const readOnlyExplicitlyExcluded = /input\.action\s*===\s*["']read_only["']/.test(turnExit);
const resultStart = route.indexOf("if (!result.ok)");
const doneIndex = route.indexOf('send({ type: "done"', resultStart);
const streamedSuccess = resultStart >= 0 && doneIndex > resultStart
? route.slice(resultStart, doneIndex)
: "";
const streamingAwaited = /await\s+finalizeSuccessfulTurnExit\s*\(/.test(streamedSuccess);
const executionBody = /RECTIFICATION_ACTION_EXECUTION\s*=\s*\{([\s\S]*?)\}\s*as const/.exec(turnExit)?.[1] ?? "";
const executionActions = [...executionBody.matchAll(/^\s*([a-z_]+):/gm)].map((match) => match[1]);
return { executionActions, immediateAwaited, readOnlyExplicitlyExcluded, streamingAwaited };
}
test("nonterminal invariant 1: every advancing action exits through an awaited common gate", () => {
const route = readSource("../src/app/api/rectification/agent/route.ts");
const turnExit = readSource("../src/lib/rectification-agentic/v9/turn-exit.ts");
const declared = /action:\s*z\.enum\(\[([^\]]+)\]\)/.exec(route)?.[1]
?.match(/["']([^"']+)["']/g)
?.map((value) => value.slice(1, -1)) ?? [];
assert.deepEqual(
declared.filter((action) => action !== "read_only"),
[...ADVANCING_RECTIFICATION_ACTIONS],
"new advancing actions must enter this invariant instead of silently bypassing the exit gate",
);
const contract = routeExitContract(route, turnExit);
const failures: string[] = [];
if (contract.executionActions.join(",") !== declared.join(",")) {
failures.push("schema actions and execution declarations differ");
}
for (const action of ADVANCING_RECTIFICATION_ACTIONS) {
const immediate = action === "message" || action === "answer_choice" || action === "stop_and_review";
const streamed = action === "opening" || action === "message";
if (immediate && !contract.immediateAwaited) {
failures.push(`${action}: HTTP 200 response can return without awaiting the common exit gate`);
}
if (streamed && !contract.streamingAwaited) {
failures.push(`${action}: stream can emit done before awaiting the common exit gate`);
}
}
if (!contract.readOnlyExplicitlyExcluded) {
failures.push("read_only: common gate exclusion is not explicit at the shared exit");
}
assert.deepEqual(failures, []);
});
test("nonterminal invariant 2: exhausted probes take the next available server-owned exit", () => {
const nakshatraProbe: CandidateDiscriminatorProbe = {
probeId: "probe:nakshatra-boundary:incident",
candidateSetVersion: "incident",
question: "哪一组日常节奏更像你?",
informationGain: 0.01,
semanticKey: "nakshatra-boundary:incident",
candidateSplitHash: "nakshatra-boundary:incident:early|late",
expectedOutcomes: [
{ outcomeId: "yes", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:07"] },
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:07"], conflictsCandidateIds: ["05:00"] },
{ outcomeId: "no", supportsCandidateIds: [], conflictsCandidateIds: [] },
{ outcomeId: "unsure", supportsCandidateIds: [], conflictsCandidateIds: [] },
],
sourceFeatures: [],
domain: "appearance",
year: null,
choiceKind: "varga_style",
styleOptions: [
{ label: "A 组:直接、外放", answerClass: "yes" },
{ label: "B 组:克制、内敛", answerClass: "weak_yes" },
{ label: "两组都不太像", answerClass: "no" },
{ label: "一时说不好", answerClass: "unsure" },
],
};
const cases = [
{
name: "holdout",
holdoutValidation: "not_started" as const,
datedMethodCollectOpen: false,
nakshatraBoundaryProbe: null,
nextAction: "offer_provisional_range",
},
{
name: "dated collect",
holdoutValidation: "unavailable" as const,
datedMethodCollectOpen: true,
nakshatraBoundaryProbe: null,
nextAction: "ask_fact_collection",
},
{
name: "nakshatra boundary",
holdoutValidation: "unavailable" as const,
datedMethodCollectOpen: false,
nakshatraBoundaryProbe: nakshatraProbe,
nextAction: "ask_candidate_discriminator",
},
{
name: "explicit range exit",
holdoutValidation: "unavailable" as const,
datedMethodCollectOpen: false,
nakshatraBoundaryProbe: null,
nextAction: "offer_provisional_range",
},
];
for (const fixture of cases) {
const decision = decideRectification({
methodCoverageAll: true,
trainingGateOpen: true,
snapshotCurrent: true,
candidateScores: [
{ time: "05:00", score: 34 },
{ time: "05:06", score: 33 },
{ time: "05:07", score: 33 },
],
discriminatorProbe: null,
holdoutValidation: fixture.holdoutValidation,
datedMethodCollectOpen: fixture.datedMethodCollectOpen,
nakshatraBoundaryProbe: fixture.nakshatraBoundaryProbe,
userStopped: false,
datedEventCount: 7,
datedDomainCount: 3,
engineCeiling: ENGINE_OPEN,
});
assert.equal(decision.separation.sufficient, false, fixture.name);
// 原断言 canAdopt=false(分离不足不得采用)→ 新断言 canAdopt=true。
// 分离不足只挡确认门;coverage/证据下限仍挡采用。本夹具覆盖已齐且事件够数。
assert.equal(decision.canAdopt, true, fixture.name);
assert.equal(decision.canConfirmExactMinute, false, fixture.name);
assert.equal(decision.nextAction, fixture.nextAction, fixture.name);
}
});
test("nakshatra boundary is a consumable four-answer probe and is not asked twice", () => {
const base = buildInferenceState({
range_start: "04:51",
range_end: "05:15",
candidates: [
{ id: "04:51", time: "04:51", relative_support: 34 },
{ id: "05:03", time: "05:03", relative_support: 33 },
{ id: "05:15", time: "05:15", relative_support: 32 },
],
events: Array.from({ length: 7 }, (_, index) => ({
id: `incident-event-${index + 1}`,
domain: ["career", "relationship", "education"][index % 3]!,
year: 2016 + index,
precision: "year" as const,
})),
probes: [],
});
const boundary = {
near_boundary: true,
user_meaning: "平时做决定时,哪一组节奏更像你?",
options: [
{ key: "A" as const, time_bias: "earlier" as const, traits: ["直接", "行动快"] },
{ key: "B" as const, time_bias: "later" as const, traits: ["克制", "先观察"] },
],
};
const probe = nakshatraBoundaryProbe(base, boundary);
assert.ok(probe);
assert.deepEqual(probe.style_options?.map((item) => item.answer_class), ["yes", "weak_yes", "no", "unsure"]);
assert.match(probe.style_options?.[0]?.label ?? "", /直接.*行动快/);
assert.match(probe.style_options?.[1]?.label ?? "", /克制.*先观察/);
const withProbe = withNakshatraBoundaryProbe(base, boundary);
assert.ok(withProbe);
const schema = stampChoiceSchemaWithProbe({
choice: {
options: [
{ key: "A", answer_class: "yes" },
{ key: "B", answer_class: "weak_yes" },
{ key: "C", answer_class: "no" },
{ key: "D", answer_class: "unsure" },
],
},
}, withProbe, "nakshatra-boundary:incident", {
probe_id: probe.id,
semantic_key: probe.semantic_key,
candidate_split_hash: probe.candidate_split_hash,
});
const applied = applyChoiceWithoutEvidence(withProbe, {
choiceKey: "A",
schema,
questionId: "nakshatra-boundary:incident",
domain: "appearance",
classifiedFrom: "choice",
});
assert.equal(applied.applied, true);
assert.equal(applied.reason, "applied");
assert.equal(applied.state.answered_probes.at(-1)?.probe_id, probe.id);
assert.equal(applied.state.answered_probes.at(-1)?.answer_class, "yes");
const deduplicated = withNakshatraBoundaryProbe(applied.state, boundary);
assert.ok(deduplicated);
assert.equal(deduplicated.probes.some((item) => item.source === "nakshatra_boundary"), false);
});
test("nonterminal invariant 3: answer_choice response waits until incident fallback focus is persisted", async () => {
const evidence = Array.from({ length: 7 }, (_, index) => ({
id: `44444444-4444-4444-8444-${String(index + 10).padStart(12, "0")}`,
source_turn_id: "33333333-3333-4333-8333-333333333333",
subject: "self",
event_kind: "dated_event",
domain: ["career", "relationship", "education"][index % 3]!,
occurred_from: `${2016 + index}-01-01`,
occurred_to: null,
date_precision: "year",
summary: `incident evidence ${index + 1}`,
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-01T00:00:00.000Z",
}));
const answeredProbes = Array.from({ length: 6 }, (_, index) => ({
id: `incident-answered-${index + 1}`,
semantic_key: `incident.answered.${index + 1}`,
candidate_split_hash: `incident-answered-split-${index + 1}`,
domain: ["career", "relationship", "education"][index % 3]!,
year: 2016 + index,
question: `incident answered question ${index + 1}`,
candidate_ids: ["04:51", "05:03", "05:15"],
expected_outcomes: [
{ answer_class: "yes" as const, supports: ["04:51"], conflicts: ["05:15"] },
{ answer_class: "no" as const, supports: ["05:15"], conflicts: ["04:51"] },
{ answer_class: "unsure" as const, supports: [], conflicts: [] },
],
information_gain: 0.5,
source: "dasha_boundary",
}));
const droppedProbes = Array.from({ length: 5 }, (_, index) => ({
id: `incident-dropped-${index + 1}`,
semantic_key: `varga.d${index + 2}.incident`,
candidate_split_hash: `incident-dropped-split-${index + 1}`,
domain: "career",
year: 0,
question: `yearless varga contrast ${index + 1}`,
candidate_ids: ["04:51", "05:03", "05:15"],
expected_outcomes: [
{ answer_class: "yes" as const, supports: ["04:51"], conflicts: ["05:15"] },
{ answer_class: "no" as const, supports: ["05:15"], conflicts: ["04:51"] },
],
information_gain: 1 + index / 10,
source: "varga_contrast",
}));
const incidentInference = buildInferenceState({
range_start: "04:51",
range_end: "05:15",
candidates: [
{ id: "04:51", time: "04:51", relative_support: 34 },
{ id: "05:03", time: "05:03", relative_support: 33 },
{ id: "05:15", time: "05:15", relative_support: 32 },
],
events: evidence.map((item) => ({
id: item.id,
domain: item.domain,
year: Number(item.occurred_from.slice(0, 4)),
precision: "year" as const,
})),
probes: [...answeredProbes, ...droppedProbes],
answered_probes: answeredProbes.map((probe) => ({
probe_id: probe.id,
semantic_key: probe.semantic_key,
candidate_split_hash: probe.candidate_split_hash,
answer_class: "unsure" as const,
classified_from: "choice" as const,
})),
});
const raw = dossierFixture({
evidence,
evidenceCount: evidence.length,
latestResult: candidateSnapshotFixture({
selectionAllowed: true,
decisionReceipt: {
inference_state: incidentInference,
diagnostic_quality: { passed: false, margin_percent: 4.476 },
date_sensitivity_retention_rate: 0.2857,
oos_blind_prompts: [
{ domain: "family", user_meaning: "家里有没有结婚、添丁或住院这类记得住时间的事?", used_for_scoring: false },
{ domain: "health_pressure", user_meaning: "有没有记得住时间的健康压力事件?", used_for_scoring: false },
],
nakshatra_boundary: {
near_boundary: true,
user_meaning: "平时做决定时,哪一组节奏更像你?",
options: [
{ key: "A", time_bias: "earlier", traits: ["直接", "行动快"] },
{ key: "B", time_bias: "later", traits: ["克制", "先观察"] },
],
},
},
candidates: [
{ candidate_id: "88888888-8888-4888-8888-888888888881", rank: 1, time: "04:51", relative_support: 34, tied_minute_count: 1 },
{ candidate_id: "88888888-8888-4888-8888-888888888882", rank: 2, time: "05:03", relative_support: 33, tied_minute_count: 1 },
{ candidate_id: "88888888-8888-4888-8888-888888888883", rank: 3, time: "05:15", relative_support: 32, tied_minute_count: 1 },
],
}),
});
const parsedIncident = parseV9CaseDossier(raw);
assert.ok(parsedIncident);
const decision = decideFromDossier(parsedIncident);
assert.equal(decision.canAdopt, false);
assert.notEqual(decision.nextAction, "offer_provisional_range");
let activeFocus: Record<string, unknown> | null = null;
let releaseWrite!: () => void;
const writeGate = new Promise<void>((resolve) => { releaseWrite = resolve; });
let markWriteStarted!: () => void;
const writeStarted = new Promise<void>((resolve) => { markWriteStarted = resolve; });
const accounting = fakeAccounting({
get_agentic_rectification_case_dossier: () => ({
...raw,
conversation_summary: {
...raw.conversation_summary,
active_focus: activeFocus,
},
}),
get_agentic_rectification_case_compute: () => computeFixture(),
set_agentic_rectification_conversation_focus: async (_fn, args) => {
markWriteStarted();
await writeGate;
activeFocus = {
id: FOCUS_ID,
case_id: CASE_ID,
question_id: args.p_question_id,
intent: args.p_intent,
target_evidence_id: args.p_target_evidence_id,
target_domain: args.p_target_domain,
target_kind: args.p_target_kind,
expected_answer_schema: args.p_expected_answer_schema,
status: "active",
asked_at: "2026-09-01T00:00:00.000Z",
resolved_at: null,
};
return { focus: activeFocus, idempotent: false };
},
append_agentic_rectification_turn: () => ({ turn_id: TURN_ID, idempotent: false }),
});
let responseVisible = false;
const responsePromise = awaitTurnExitBeforeResponse(
new Response(null, { status: 200 }),
() => finalizeSuccessfulTurnExit({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
action: "answer_choice",
}),
);
void responsePromise.then(() => { responseVisible = true; });
await writeStarted;
await Promise.resolve();
assert.equal(responseVisible, false, "answer_choice response resolved before focus persistence completed");
releaseWrite();
const response = await responsePromise;
const refreshed = parseV9CaseDossier({
...raw,
conversation_summary: {
...raw.conversation_summary,
active_focus: activeFocus,
},
});
assert.ok(refreshed);
const currentQuestion = projectCurrentQuestion(refreshed.conversationSummary.activeFocus);
assert.equal(response.status, 200);
assert.equal(decision.canAdopt, false);
assert.ok(currentQuestion?.prompt);
assert.equal(currentQuestion.prompt, "家里有没有结婚、添丁或住院这类记得住时间的事?不记得具体日子也可以先说有没有。");
const historyTurn = accounting.calls.find((call) => call.fn === "append_agentic_rectification_turn");
assert.equal(historyTurn?.args.p_request_id, FOCUS_ID);
assert.equal(historyTurn?.args.p_user_message, null);
assert.equal(historyTurn?.args.p_assistant_message, currentQuestion.prompt);
});
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, engineCeiling: ENGINE_OPEN });
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 inspected = inspectDiscriminatorProbes(packet);
assert.equal(packet.probes.some((probe) => probe.semanticKey.includes("career.2023")), true);
assert.ok(packet.probes.some((probe) => probe.semanticKey.startsWith("varga.d24.")));
assert.equal(inspected.selected?.semanticKey.includes("career.2023"), true);
assert.equal(inspected.dropped.some((item) => (
item.semantic_key.startsWith("varga.d24.") && item.reason === "yearless_ungrounded_contrast"
)), true);
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" as const,
event_family: "家人结婚、添丁或住院",
source: "dasha_boundary" as const,
tracks: ["vimshottari", "narayana"] as const,
tracks_agree: true,
unique_minute_claim: false as const,
user_meaning: "时间范围锁定 2013 年 3 月前后。",
role: "distinguish" as const,
phase: "candidate_discriminator" as const,
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" as const, supports: ["05:00"], conflicts: ["05:07", "05:12"] },
{ answer_class: "weak_yes" as const, supports: ["05:07"], conflicts: ["05:00", "05:12"] },
{ answer_class: "no" as const, supports: ["05:12"], conflicts: ["05:00", "05:07"] },
{ answer_class: "unsure" as const, supports: [], conflicts: [] },
],
style_options: [
{ label: "明确发生且时间吻合", answer_class: "yes" },
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
{ label: "明确没有发生", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
],
} satisfies DiscriminatingEventProbe;
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({
engineCeiling: ENGINE_OPEN,
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 base = parseV9CaseDossier(dossierFixture());
assert.ok(base);
const dossier = parseV9CaseDossier(dossierFixture({
latestResult: candidateSnapshotFixture({
selectionAllowed: true,
representativeTime: "05:02",
evidenceLedgerFingerprint: evidenceLedgerFingerprint(base.evidence),
decisionReceipt: {
inference_state: {
algorithm_version: "rectification-inference-v1",
candidate_set_id: "04:55-05:02:04:55,05:02",
revision: 0,
phase: "discrimination",
result_status: "discriminating",
range_start: "04:55",
range_end: "05:02",
candidates: [
{ id: "05:02", time: "05:02", cluster_range: ["05:02", "05:02"], prior_score: 58, posterior_score: 58, probability: 0.58, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "04:55", time: "04:55", cluster_range: ["04:55", "04:55"], prior_score: 42, posterior_score: 42, probability: 0.42, status: "active", rank: 2, strong_conflict_count: 0 },
],
events: [],
probes: [],
answered_probes: [],
rounds: [],
entropy: 0.98,
representative_time: "05:02",
credible_range: ["04:55", "05:02"],
},
},
}),
}));
const compute = parseV9ComputeProjection(computeFixture({
baselineProfileFingerprint: "c".repeat(64),
}));
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/);
});