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[0]> = {}, ) { return decideRectification({ methodCoverageAll: true, trainingGateOpen: true, snapshotCurrent: true, candidateScores: SEPARATED, holdoutValidation: "passed", confirmationAllowed: true, datedEventCount: 3, datedDomainCount: 2, ...overrides, engineCeiling, } as Parameters[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=false(holdout 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 | null = null; let releaseWrite!: () => void; const writeGate = new Promise((resolve) => { releaseWrite = resolve; }); let markWriteStarted!: () => void; const writeStarted = new Promise((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/); });