import assert from "node:assert/strict"; import test from "node:test"; import { dossierResponse } from "../src/lib/rectification-agentic/v9/case-dossier-response.ts"; import { choiceCardFromCaseDossier } from "../src/lib/rectification-agentic/v9/interview-state.ts"; import { buildSegmentSummary } from "../src/lib/rectification-agentic/core/segment-summary.ts"; import { parseV9CaseDossier } from "../src/lib/rectification-agentic/v9/tool-service.ts"; import { dossierFixture, activeFocusFixture, conversationSummaryFixture } from "./rectification-v9-test-support.ts"; import { AA_RANGE, buildGoldenLatest } from "./rectification-grounding-support.ts"; // Real public AA score for identity checks; explicitly fictional focus/unique // controls below verify projection only, not engine calibration or SQL. test("actual dossier projection preserves read-only gates and hides collect cards only for delivered consistency", async () => { const { latest, scored } = await buildGoldenLatest(); const raw = dossierFixture({ candidateRange: AA_RANGE, latestResult: latest, conversationSummary: conversationSummaryFixture({ activeFocus: activeFocusFixture({ intent: "collect_method_evidence", targetKind: "career_change", expectedAnswerSchema: { collect: true, prompt: "请说出第一次工作变化发生的年份。" } }) }) }); const dossier = parseV9CaseDossier(raw)!; const identity = { status: "verified", requiresSkillAdoption: false } as never; const live = { algorithmVersion: scored.algorithmVersion, policyVersion: scored.policyVersion }; const writable = dossierResponse(dossier, [], identity, { liveIdentity: live }); assert.equal(writable.case.result_identity.read_only, false); assert.ok(writable.current_question); assert.deepEqual(writable.choice_card, choiceCardFromCaseDossier(dossier), "normal projection keeps the production tap/time mutual-exclusion decision"); const tapRaw = dossierFixture({ candidateRange: AA_RANGE, latestResult: latest, conversationSummary: conversationSummaryFixture({ activeFocus: activeFocusFixture({ intent: "collect_method_evidence", questionId: "collect:targeted:career", targetKind: "targeted:career", expectedAnswerSchema: { collect: true, prompt: "工作这条线有没有发生过明显变化?" }, }) }) }); const tapDossier = parseV9CaseDossier(tapRaw)!; const tap = dossierResponse(tapDossier, [], identity, { liveIdentity: live }); assert.ok(tap.current_question); assert.ok(tap.choice_card, "the actual production projection must retain a valid persisted collect tap card"); assert.equal(tap.choice_card.scoring, false); const readOnlyTap = dossierResponse(tapDossier, [], identity, { liveIdentity: { ...live, algorithmVersion: "synthetic-old-version" } }); assert.equal(readOnlyTap.current_question, null); assert.equal(readOnlyTap.choice_card, null, "read-only removes a proven renderable card, not just an already-null fixture"); const readOnly = dossierResponse(dossier, [], identity, { liveIdentity: { ...live, algorithmVersion: "synthetic-old-version" } }); assert.equal(readOnly.case.result_identity.read_only, true); assert.equal(readOnly.current_question, null); assert.equal(readOnly.choice_card, null); assert.equal(readOnly.latest_result?.can_adopt, false); assert.equal(readOnly.latest_result?.confirmationAllowed, false); const range = { start_time: "09:00", end_time: "09:20" }; const minutes = Array.from({ length: 21 }, (_, offset) => ({ offset, date: "2000-06-14", time: `09:${String(offset).padStart(2, "0")}`, signs: { D1: 1, D9: 2, D10: 3 } })); const summary = buildSegmentSummary({ minutes, candidates: [], targets: ["D1", "D9", "D10"], windowMinutes: 21, scanComplete: true }); const closedRaw = dossierFixture({ candidateRange: range, status: "closed", latestResult: null, conversationSummary: raw.conversation_summary }); Object.assign(closedRaw.case, { rectification_domain: "general", checks: { contract: "segment-opening-v1", domain: "general", range_fingerprint: "a".repeat(64), status: "complete", summary, delivered_turn_id: "synthetic-opening-turn" } }); const closed = parseV9CaseDossier(closedRaw)!; const projected = dossierResponse(closed, [], identity, { liveIdentity: live }); assert.equal(projected.current_question, null); assert.equal(projected.choice_card, null); assert.equal(projected.latest_result, null); assert.equal(projected.step_state.headline, "盘型一致,不用校正"); assert.equal(projected.next_user_action.id, "start_consultation"); assert.equal(projected.case.accepted_time, null); assert.equal(projected.case.confirmed_time, null); });