import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { cachedEngineScoreIsReusable, liveEngineScoringIdentityFromEnv, readV9EngineScoringIdentity, v9EngineVersion, } from "../src/lib/rectification-agentic/v9/engine-client.ts"; import { openRectificationCase } from "../src/lib/rectification-agentic/v9/case-service.ts"; import { loadV9TurnReceipt, parseV9CaseDossier, evidenceLedgerFingerprint, candidateRangeFingerprint } from "../src/lib/rectification-agentic/v9/tool-service.ts"; import { scoreAndPersistCurrentEvidence } from "../src/lib/rectification-agentic/v9/score-persist.ts"; import { acceptV9Candidate, confirmV9BirthTime, advanceV9CaseFromBlockScan } from "../src/lib/rectification-agentic/v9/tool-service.ts"; import { assertV9ResultWritable, resultIdentityView } from "../src/lib/rectification-agentic/v9/result-identity.ts"; import { ensureNonTerminalTurnExit, persistNextInterviewIfIdle } from "../src/lib/rectification-agentic/v9/answer-choice.ts"; import { dossierResponseWithIdentity } from "../src/lib/rectification-agentic/v9/case-dossier-response.ts"; import { recompareHistoricalResult } from "../src/lib/rectification-surface-state.ts"; import { resolveSkillPackageVersion } from "../src/lib/skill-package-registry.ts"; import { createRectificationV9Tools } from "../src/mastra/rectification-v9-tools.ts"; import { CASE_ID, SESSION_ID, TURN_ID, USER_ID, FOCUS_ID, computeFixture, dossierFixture, fakeAccounting, receiptHandlers, } from "./rectification-v9-test-support.ts"; // Real native-engine response, using explicitly fictional input, not a hand-built contract. const golden = JSON.parse(readFileSync(new URL( "./fixtures/rectification-engine-version-cross-midnight-golden.json", import.meta.url, ), "utf8")); // Historical golden identity stays fixed; it must not be relabeled as the live engine. const CURRENT = "rectification-v5-matrix-scoring-8"; const PREVIOUS = "rectification-v5-matrix-scoring-7"; const LIVE_CURRENT = "rectification-v5-matrix-scoring-9"; const liveGolden = JSON.parse(readFileSync(new URL( "./fixtures/rectification-midnight-date-anchor.delivery.native.json", import.meta.url, ), "utf8")); const envKeys = [ "RECTIFICATION_ENGINE_VERSION", "RECTIFICATION_ALGORITHM_VERSION", "RECTIFICATION_DECISION_POLICY_VERSION", ] as const; function isolateIdentityEnv(t: test.TestContext) { const saved = envKeys.map((key) => [key, process.env[key]] as const); for (const key of envKeys) delete process.env[key]; t.after(() => { for (const [key, value] of saved) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }); } test("receipt default matches the cross-midnight native scoring version without changing Skill identity", (t) => { isolateIdentityEnv(t); assert.equal(v9EngineVersion(), LIVE_CURRENT); for (const blank of ["", " "]) { process.env.RECTIFICATION_ENGINE_VERSION = blank; assert.equal(v9EngineVersion(), LIVE_CURRENT); } assert.equal(golden.score.algorithm_version, CURRENT); assert.equal(golden.versions.algorithm_version, CURRENT); const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8"); assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-9"/); assert.equal(liveGolden.response.algorithm_version, LIVE_CURRENT); const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) }; const live = { algorithmVersion: liveGolden.response.algorithm_version, policyVersion: liveGolden.response.decision_policy_version, }; const historical = { ...fingerprints, algorithmVersion: CURRENT, policyVersion: golden.versions.decision_policy_version }; assert.equal(cachedEngineScoreIsReusable(historical, fingerprints, live), false); assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: LIVE_CURRENT }, fingerprints, live), true); assert.equal(historical.algorithmVersion, CURRENT, "checking live identity never relabels history"); // A receipt default must not become an environment override for cache identity. assert.deepEqual(liveEngineScoringIdentityFromEnv({}), { algorithmVersion: null, policyVersion: null }); }); test("explicit engine override still wins and remains a deployment prerequisite, not silently rewritten", (t) => { isolateIdentityEnv(t); process.env.RECTIFICATION_ENGINE_VERSION = " rectification-v5 "; assert.equal(v9EngineVersion(), "rectification-v5"); assert.equal(liveEngineScoringIdentityFromEnv().algorithmVersion, null); process.env.RECTIFICATION_ENGINE_VERSION = ` ${PREVIOUS} `; assert.equal(v9EngineVersion(), PREVIOUS); // This existing cache-identity fallback predates this change; no new open gate is introduced. assert.equal(liveEngineScoringIdentityFromEnv().algorithmVersion, PREVIOUS); }); test("native versions invalidate matching-input old scores while same-version scores remain reusable", async (t) => { isolateIdentityEnv(t); t.mock.method(globalThis, "fetch", async (url: unknown) => { assert.ok(String(url).endsWith("/api/rectification/v5/versions")); return Response.json(golden.versions); }); const live = await readV9EngineScoringIdentity(); assert.equal(live.algorithmVersion, CURRENT); const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) }; const previous = { ...fingerprints, algorithmVersion: PREVIOUS, policyVersion: golden.versions.decision_policy_version }; assert.equal(cachedEngineScoreIsReusable(previous, fingerprints, live), false); assert.equal(cachedEngineScoreIsReusable({ ...previous, algorithmVersion: CURRENT }, fingerprints, live), true); assert.equal(previous.algorithmVersion, PREVIOUS); }); test("partial override is completed by native identity; complete override needs no request", async (t) => { isolateIdentityEnv(t); let fetches = 0; t.mock.method(globalThis, "fetch", async () => { fetches += 1; return Response.json(golden.versions); }); const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) }; const stored = { ...fingerprints, algorithmVersion: PREVIOUS, policyVersion: golden.versions.decision_policy_version }; process.env.RECTIFICATION_DECISION_POLICY_VERSION = golden.versions.decision_policy_version; const policyOnly = await readV9EngineScoringIdentity(); // 原值 null/true;新值 CURRENT/false;原因:部分覆盖不能绕过实际算法身份。 assert.equal(policyOnly.algorithmVersion, CURRENT); assert.equal(cachedEngineScoreIsReusable(stored, fingerprints, policyOnly), false); process.env.RECTIFICATION_ALGORITHM_VERSION = PREVIOUS; const staleOverride = await readV9EngineScoringIdentity(); assert.equal(staleOverride.algorithmVersion, PREVIOUS); assert.equal(cachedEngineScoreIsReusable(stored, fingerprints, staleOverride), true); assert.equal(fetches, 1); // 原值 0;部分覆盖必须补齐,完整覆盖仍不请求。 }); test("unreachable versions never validate a cached result as current", async (t) => { isolateIdentityEnv(t); t.mock.method(globalThis, "fetch", async () => { throw new Error("fixture versions unavailable"); }); const live = await readV9EngineScoringIdentity(); assert.deepEqual(live, { algorithmVersion: null, policyVersion: null }); const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) }; // 原值 true;新值 false;原因:BUG-984 策略 b,只读不是当前缓存命中。 assert.equal(cachedEngineScoreIsReusable({ ...fingerprints, algorithmVersion: PREVIOUS }, fingerprints, live), false); }); for (const storedVersion of [PREVIOUS, CURRENT]) { test(`native late-night block cache checks actual identity: ${storedVersion}`, async (t) => { isolateIdentityEnv(t); const block = JSON.parse(readFileSync(new URL("./fixtures/rectification-engine-version-cross-midnight-block-golden.json", import.meta.url), "utf8")); const range = { start_time: block.request.start_time, end_time: block.request.end_time }; const raw = dossierFixture({ stage: "block_scan", candidateRange: range, latestResult: null }); const fingerprint = evidenceLedgerFingerprint(parseV9CaseDossier(raw)!.evidence); raw.case.block_scan = { ...block.response, algorithm_version: storedVersion, decision_policy_version: block.response.decision_receipt.decision_policy_version, evidence_ledger_fingerprint: fingerprint, candidate_range_fingerprint: candidateRangeFingerprint(range, "a".repeat(64)), }; const accounting = fakeAccounting({ get_agentic_rectification_case_dossier: () => raw, get_agentic_rectification_case_compute: () => ({ ...computeFixture(), candidate_range: range }), write_agentic_rectification_block_scan: (_fn, args) => { raw.case.block_scan = args.p_block_scan as Record; return {}; }, }); const requests: string[] = []; t.mock.method(globalThis, "fetch", async (url: unknown) => { requests.push(String(url)); return Response.json(String(url).endsWith("/versions") ? golden.versions : block.response); }); const opened = await dossierResponseWithIdentity(parseV9CaseDossier(raw)!, [], { status: "verified", requiresSkillAdoption: false } as never); assert.equal(opened.case.result_identity.can_recompare, storedVersion === PREVIOUS); assert.equal(opened.case.result_identity.read_only, storedVersion === PREVIOUS); const result = await scoreAndPersistCurrentEvidence({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID }); assert.equal(result.persisted.algorithmVersion, CURRENT); assert.equal(result.persisted.cached, storedVersion === CURRENT); assert.equal(requests.filter((url) => url.endsWith("/block_scan")).length, storedVersion === CURRENT ? 0 : 1); assert.ok(requests.some((url) => url.endsWith("/versions"))); }); } for (const storedVersion of [PREVIOUS, CURRENT]) { test(`native minute cache checks actual identity: ${storedVersion}`, async (t) => { isolateIdentityEnv(t); const range = { start_time: golden.request.start_time, end_time: golden.request.end_time }; const raw = dossierFixture({ candidateRange: range, latestResult: null }); raw.latest_result = { ...golden.score, ...golden.score.decision_receipt, result_id: golden.score.result_id, candidates: golden.score.candidate_decisions, algorithm_version: storedVersion, decision_receipt: golden.score.decision_receipt, evidence_ledger_fingerprint: evidenceLedgerFingerprint(parseV9CaseDossier(raw)!.evidence), candidate_range_fingerprint: candidateRangeFingerprint(range, "a".repeat(64)), }; const accounting = fakeAccounting({ get_agentic_rectification_case_dossier: () => raw, get_agentic_rectification_case_compute: () => ({ ...computeFixture(), candidate_range: range }), persist_agentic_rectification_candidate_v2: (_fn, args) => ({ ...golden.score.decision_receipt, result_id: args.p_engine_result_id, candidates: args.p_candidates, algorithm_version: args.p_algorithm_version, decision_policy_version: args.p_decision_policy_version, decision_receipt: args.p_decision_receipt, }), }); const requests: string[] = []; t.mock.method(globalThis, "fetch", async (url: unknown) => { requests.push(String(url)); assert.ok(String(url).endsWith("/versions") || String(url).endsWith("/score")); return Response.json(String(url).endsWith("/versions") ? golden.versions : golden.score); }); const opened = await dossierResponseWithIdentity(parseV9CaseDossier(raw)!, [], { status: "verified", requiresSkillAdoption: false } as never); assert.equal(opened.case.result_identity.can_recompare, storedVersion === PREVIOUS); assert.equal(opened.case.result_identity.read_only, storedVersion === PREVIOUS); const result = await scoreAndPersistCurrentEvidence({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, keepAnswers: true }); assert.equal(result.persisted.algorithmVersion, CURRENT); assert.equal(result.persisted.cached, storedVersion === CURRENT); assert.equal(requests.filter((url) => url.endsWith("/score")).length, storedVersion === CURRENT ? 0 : 1); assert.ok(requests.some((url) => url.endsWith("/versions"))); }); } test("stale history exposes explicit recompare and real tools replace old provenance", async (t) => { isolateIdentityEnv(t); const block = JSON.parse(readFileSync(new URL("./fixtures/rectification-engine-version-cross-midnight-block-golden.json", import.meta.url), "utf8")); const range = { start_time: block.request.start_time, end_time: block.request.end_time }; const raw = dossierFixture({ stage: "block_scan", candidateRange: range, latestResult: null, blockScan: { ...block.response, algorithm_version: PREVIOUS } }); const accounting = fakeAccounting({ ...receiptHandlers, get_agentic_rectification_case_dossier: () => raw, get_agentic_rectification_case_compute: () => ({ ...computeFixture(), candidate_range: range }), write_agentic_rectification_block_scan: (_fn, args) => { raw.case.block_scan = args.p_block_scan as Record; return {}; }, set_agentic_rectification_conversation_focus: (_fn, args) => ({ focus: { id: FOCUS_ID, case_id: CASE_ID, question_id: args.p_question_id, intent: args.p_intent, target_domain: args.p_target_domain, expected_answer_schema: args.p_expected_answer_schema, status: "active" }, idempotent: false }), }); let scans = 0; t.mock.method(globalThis, "fetch", async (url: unknown) => { if (String(url).endsWith("/versions")) return Response.json(golden.versions); assert.ok(String(url).endsWith("/block_scan")); scans++; return Response.json(block.response); }); const tools = createRectificationV9Tools({ accounting: accounting.client as never, userId: USER_ID, caseId: CASE_ID, turnId: TURN_ID }); const read = await (tools["rectification-read-case"] as unknown as { execute(input: unknown): Promise> }).execute({ caseId: CASE_ID }); assert.equal(read.can_recompare, true); assert.equal((read.next_action as Record).type, "compare_candidates"); assert.equal(scans, 0, "opening history is not forced recomputation"); await (tools["rectification-compare-candidates"] as unknown as { execute(input: unknown): Promise }).execute({ caseId: CASE_ID }); assert.equal(scans, 1); const refreshed = await dossierResponseWithIdentity(parseV9CaseDossier(raw)!, [], { status: "verified", requiresSkillAdoption: false } as never); assert.equal(refreshed.case.result_identity.read_only, false); assert.equal(refreshed.case.result_identity.algorithm_version, CURRENT); assert.equal(refreshed.case.result_identity.policy_version, golden.versions.decision_policy_version); const component = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8"); assert.match(component, /resultNotice && canRecompare && !historyReadonly/); assert.match(component, /recompareHistoricalResult\(\{ canRecompare, historyReadonly, busy, send \}\)/); assert.match(component, /if \(\(action === "message" && !trimmed\) \|\| conversationReadonly\) return/); const sent: string[] = []; const send = async (action: "message", text: string) => { sent.push(`${action}:${text}`); }; await recompareHistoricalResult({ canRecompare: true, historyReadonly: false, busy: false, send }); assert.deepEqual(sent, ["message:请按当前算法重新比较候选。"]); for (const state of [{ canRecompare: false, historyReadonly: false, busy: false }, { canRecompare: true, historyReadonly: true, busy: false }, { canRecompare: true, historyReadonly: false, busy: true }]) { await recompareHistoricalResult({ ...state, send }); } assert.equal(sent.length, 1, "unknown identity, terminal history and busy clicks cannot dispatch"); }); test("partial conflicts and incomplete native pairs never authorize reuse", async (t) => { isolateIdentityEnv(t); process.env.RECTIFICATION_ALGORITHM_VERSION = PREVIOUS; t.mock.method(globalThis, "fetch", async () => Response.json(golden.versions)); assert.deepEqual(await readV9EngineScoringIdentity(), { algorithmVersion: null, policyVersion: null }); delete process.env.RECTIFICATION_ALGORITHM_VERSION; t.mock.method(globalThis, "fetch", async () => Response.json({ algorithm_version: CURRENT })); const incomplete = await readV9EngineScoringIdentity(); assert.equal(incomplete.policyVersion, null); assert.equal(cachedEngineScoreIsReusable({ algorithmVersion: CURRENT, evidenceLedgerFingerprint: "e", candidateRangeFingerprint: "r" }, { evidenceLedgerFingerprint: "e", candidateRangeFingerprint: "r" }, incomplete), false); }); for (const stage of ["minute", "block_scan"] as const) { for (const failure of ["http", "timeout", "partial"] as const) { test(`${stage} ${failure}: historical result stays read-only without recompute; server mutations reject`, async (t) => { isolateIdentityEnv(t); if (failure === "partial") process.env.RECTIFICATION_DECISION_POLICY_VERSION = golden.versions.decision_policy_version; const block = JSON.parse(readFileSync(new URL("./fixtures/rectification-engine-version-cross-midnight-block-golden.json", import.meta.url), "utf8")); const range = { start_time: golden.request.start_time, end_time: golden.request.end_time }; const raw = dossierFixture({ stage, candidateRange: range, latestResult: { ...golden.score, ...golden.score.decision_receipt, result_id: golden.score.result_id, candidates: golden.score.candidate_decisions, algorithm_version: PREVIOUS, decision_receipt: golden.score.decision_receipt, }, blockScan: { ...block.response, algorithm_version: PREVIOUS } }); const accounting = fakeAccounting({ ...receiptHandlers, get_agentic_rectification_case_dossier: () => raw, get_agentic_rectification_case_compute: () => ({ ...computeFixture(), candidate_range: range }), }); const requests: string[] = []; t.mock.method(globalThis, "fetch", async (url: unknown) => { requests.push(String(url)); assert.ok(String(url).endsWith("/versions"), "no scoring/oracle call on identity failure"); if (failure === "timeout") throw new DOMException("fixture timeout", "TimeoutError"); return Response.json({ error: "fixture_unavailable" }, { status: 503 }); }); const input = { accounting: accounting.client, userId: USER_ID, caseId: CASE_ID }; const result = await scoreAndPersistCurrentEvidence(input); assert.equal(result.readOnly, true); assert.equal(result.persisted.cached, false); assert.equal(result.persisted.algorithmVersion, PREVIOUS); assert.equal(result.persisted.selectionAllowed, false); assert.equal(result.persisted.confirmationAllowed, false); const response = await dossierResponseWithIdentity(parseV9CaseDossier(raw)!, [], { status: "verified", requiresSkillAdoption: false, } as never); assert.match(response.case.result_notice ?? "", /按旧算法产出/); assert.equal(response.case.result_identity.algorithm_version, PREVIOUS); assert.equal(response.case.result_identity.can_recompare, false); assert.equal(response.choice_card, null); const candidateId = golden.score.candidate_decisions[0].candidate_id; await assert.rejects(acceptV9Candidate(accounting.client, USER_ID, CASE_ID, golden.score.result_id, candidateId, TURN_ID), /result_identity_read_only/); await assert.rejects(confirmV9BirthTime(accounting.client, USER_ID, CASE_ID, { resultId: golden.score.result_id, candidateId, requestId: TURN_ID, consentQuote: "fixture", sourceTurnId: TURN_ID, }), /result_identity_read_only/); await assert.rejects(advanceV9CaseFromBlockScan(accounting.client, USER_ID, CASE_ID, range), /result_identity_read_only/); const tools = createRectificationV9Tools({ ...input, turnId: TURN_ID, accounting: accounting.client as never }); const projection = await (tools["rectification-compare-candidates"] as unknown as { execute(input: unknown): Promise<{ read_only: boolean }> }).execute({ caseId: CASE_ID }); assert.equal(projection.read_only, true); const completed = accounting.calls.find((call) => call.fn === "insert_agentic_rectification_tool_receipt" && call.args.p_status === "completed"); assert.equal(completed?.args.p_engine_version, PREVIOUS); const readProjection = await (tools["rectification-read-case"] as unknown as { execute(input: unknown): Promise> }).execute({ caseId: CASE_ID }); assert.equal(readProjection.read_only, true); assert.equal((readProjection.candidate_summary as Record).selection_allowed, false); const diagnostics = await (tools["rectification-read-diagnostics"] as unknown as { execute(input: unknown): Promise> }).execute({ caseId: CASE_ID }); assert.equal(diagnostics.read_only, true); assert.match(String(diagnostics.notice), /按旧算法产出/); assert.equal(diagnostics.algorithm_version, PREVIOUS); assert.equal(diagnostics.can_confirm_exact_minute, false); assert.equal(diagnostics.diagnostics, null); assert.deepEqual(diagnostics.executed_methods, []); const diagnosticsReceipt = accounting.calls.find((call) => call.fn === "insert_agentic_rectification_tool_receipt" && call.args.p_tool_name === "rectification-read-diagnostics" && call.args.p_status === "completed"); assert.equal(diagnosticsReceipt?.args.p_engine_version, PREVIOUS); await assert.rejects((tools["rectification-offer-candidates"] as unknown as { execute(input: unknown): Promise }).execute({ caseId: CASE_ID }), /result_identity_read_only/); assert.equal((await ensureNonTerminalTurnExit(input)).persisted, false); assert.equal((await persistNextInterviewIfIdle(input)).persisted, false); assert.ok(accounting.calls.every((call) => call.fn.startsWith("get_") || call.fn === "insert_agentic_rectification_tool_receipt"), "no candidate, focus, inference or validation writes"); assert.ok(requests.length > 0); }); } } test("missing stage source, absent results and nonlatest result IDs cannot authorize candidate writes", async (t) => { isolateIdentityEnv(t); process.env.RECTIFICATION_ALGORITHM_VERSION = CURRENT; process.env.RECTIFICATION_DECISION_POLICY_VERSION = golden.versions.decision_policy_version; const latest = { ...golden.score, result_id: golden.score.result_id, candidates: golden.score.candidate_decisions }; const block = JSON.parse(readFileSync(new URL("./fixtures/rectification-engine-version-cross-midnight-block-golden.json", import.meta.url), "utf8")).response; for (const raw of [ dossierFixture({ stage: "block_scan", latestResult: latest, blockScan: null }), dossierFixture({ stage: "minute", latestResult: null, blockScan: block }), dossierFixture({ stage: "minute", latestResult: null, blockScan: null }), dossierFixture({ stage: "minute", latestResult: latest }), ]) { const accounting = fakeAccounting({ get_agentic_rectification_case_dossier: () => raw }); await assert.rejects(assertV9ResultWritable(accounting.client, USER_ID, CASE_ID, TURN_ID), /result_identity_read_only/); assert.ok(accounting.calls.every((call) => call.fn.startsWith("get_"))); } const empty = dossierFixture({ latestResult: null, blockScan: null }); const accounting = fakeAccounting({ get_agentic_rectification_case_dossier: () => empty }); await assertV9ResultWritable(accounting.client, USER_ID, CASE_ID); // ordinary new-case collection remains allowed await assert.rejects(assertV9ResultWritable(accounting.client, USER_ID, CASE_ID, null), /result_identity_read_only/); assert.equal(resultIdentityView(parseV9CaseDossier(empty)!, { algorithmVersion: null, policyVersion: null }).read_only, false); }); const historicalGolden = golden; async function runGoldenToolSequence(t: test.TestContext, options: { compareVersion?: string; diagnosticsVersion?: string; diagnosticsFailure?: boolean; useLiveGolden?: boolean; } = {}) { isolateIdentityEnv(t); // A scoring-9 response needs its real dated contract, never a relabeled scoring-8 fixture. const golden = options.useLiveGolden ? { request: liveGolden.request, score: liveGolden.response, versions: { algorithm_version: liveGolden.response.algorithm_version, decision_policy_version: liveGolden.response.decision_policy_version, }, } : historicalGolden; const request = golden.request; const candidateRange = { start_time: request.start_time, end_time: request.end_time, ...(request.candidate_intervals ? { candidate_intervals: request.candidate_intervals } : {}), }; const evidence = request.events.map((event: Record) => ({ id: event.id, source_turn_id: TURN_ID, subject: "self", event_kind: event.event_kind, domain: event.domain, occurred_from: event.date_start, occurred_to: event.date_end, date_precision: event.precision, summary: event.summary, status: "confirmed", })); const calls: string[] = []; t.mock.method(globalThis, "fetch", async (url: unknown) => { const path = new URL(String(url)).pathname; calls.push(path); if (path.endsWith("/versions")) return Response.json(golden.versions); if (path.endsWith("/score")) { return Response.json({ ...golden.score, algorithm_version: options.compareVersion ?? CURRENT }); } if (path.endsWith("/diagnostics")) { if (options.diagnosticsFailure) return Response.json({ error: "fixture_unavailable" }, { status: 503 }); return Response.json({ ...golden.score, algorithm_version: options.diagnosticsVersion ?? CURRENT }); } throw new Error(`unexpected engine request ${path}`); }); const accounting = fakeAccounting({ ...receiptHandlers, get_agentic_rectification_case_dossier: () => dossierFixture({ candidateRange, evidence, latestResult: null }), get_agentic_rectification_case_compute: () => ({ ...computeFixture({ baselineBirthSnapshot: { birth_date: request.birth_date, latitude: request.lat, longitude: request.lon, timezone_offset: request.tz, timezone_id: "Asia/Shanghai", birth_time_source: "approximate", } }), candidate_range: candidateRange, }), persist_agentic_rectification_candidate_v2: (_fn, args) => ({ result_id: args.p_engine_result_id, cached: false, candidates: args.p_candidates, algorithm_version: args.p_algorithm_version, event_contract_version: args.p_event_contract_version, decision_policy_version: args.p_decision_policy_version, decision_receipt: args.p_decision_receipt, execution_ledger: args.p_execution_ledger, overall_confidence: golden.score.decision_receipt.overall_confidence, selection_allowed: golden.score.decision_receipt.selection_allowed, confirmation_allowed: false, representative_time: golden.score.decision_receipt.representative_time, }), set_agentic_rectification_conversation_focus: (_fn, args) => ({ focus: { 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" }, idempotent: false, }), }); const tools = createRectificationV9Tools({ userId: USER_ID, caseId: CASE_ID, turnId: TURN_ID, accounting: accounting.client as never }); for (const name of ["rectification-compare-candidates", "rectification-read-diagnostics"] as const) { const execute = () => (tools[name] as unknown as { execute(input: unknown): Promise }).execute({ caseId: CASE_ID }); if (options.diagnosticsFailure && name === "rectification-read-diagnostics") await assert.rejects(execute); else { const projection = await execute() as { read_only?: boolean; algorithm_version?: string; can_confirm_exact_minute?: boolean }; if (name === "rectification-compare-candidates" && options.compareVersion && options.compareVersion !== golden.versions.algorithm_version) { assert.equal(projection.read_only, true, "rolling response mismatch stays read-only after saving true provenance"); assert.equal(projection.algorithm_version, options.compareVersion); } if (name === "rectification-read-diagnostics" && options.diagnosticsVersion && options.diagnosticsVersion !== golden.versions.algorithm_version) { assert.equal(projection.read_only, true); assert.equal(projection.algorithm_version, options.diagnosticsVersion); assert.equal(projection.can_confirm_exact_minute, false, "rolling diagnostics cannot claim current validation"); } } } assert.ok(calls.includes("/api/rectification/v5/score")); const persisted = accounting.calls.find((call) => call.fn === "persist_agentic_rectification_candidate_v2"); assert.equal(persisted?.args.p_algorithm_version, options.compareVersion ?? CURRENT); return accounting.calls.filter((call) => call.fn === "insert_agentic_rectification_tool_receipt"); } test("new Case scoring writes actual identity only on completed receipts", async (t) => { const receipts = await runGoldenToolSequence(t); assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [ // 原值: started=CURRENT;新值: null;原因: BUG-984,开始不是成功结果身份。 ["started", null], ["completed", CURRENT], ["started", null], ["completed", CURRENT], ]); }); test("failed diagnostics do not claim an engine result identity", async (t) => { const receipts = await runGoldenToolSequence(t, { diagnosticsFailure: true }); assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [ ["started", null], ["completed", CURRENT], ["started", null], ["failed", null], ]); }); test("mixed completed identities require chronological aggregation (covered by database regression)", async (t) => { // Identity-only mutation models rolling backend versions; all response shape and values // remain the real native golden. This is not a claim to have run future algorithms. const version9 = "rectification-v5-matrix-scoring-9"; const version10 = "rectification-v5-matrix-scoring-10"; const receipts = await runGoldenToolSequence(t, { compareVersion: version9, diagnosticsVersion: version10, useLiveGolden: true }); const completed = receipts.filter((call) => call.args.p_status === "completed"); assert.equal(new Set(completed.map((call) => call.args.p_turn_id)).size, 1); assert.deepEqual(completed.map((call) => call.args.p_engine_version), [version9, version10]); // This proves why string max is invalid; database-rectification-block-scan.test.ts // executes the authorized aggregate migration and verifies chronological selection. assert.equal([version9, version10].sort().at(-1), version9); assert.notEqual([version9, version10].sort().at(-1), completed.at(-1)?.args.p_engine_version); }); test("history opens with its bound Skill and reads old engine receipt values unchanged", async (t) => { isolateIdentityEnv(t); const bound = resolveSkillPackageVersion("jyotish-birth-time-rectification", "10.0.17"); for (const oldVersion of ["rectification-v5", PREVIOUS]) { const historical = { turn_id: TURN_ID, engine_version: oldVersion, skill_name: bound.name, skill_version: bound.version, status: "completed" }; const before = structuredClone(historical); const open = { disposition: "readonly", case_id: CASE_ID, session_id: SESSION_ID, status: "closed", should_start_opening: false, skill_version: bound.version }; const accounting = fakeAccounting({ open_agentic_rectification_case: () => open, get_agentic_rectification_skill_identity: () => ({ skill_name: bound.name, skill_version: bound.version, skill_sha256: bound.sha256, skill_source_commit: bound.sourceCommit }), open_agentic_rectification_case_v2: (_fn, args) => { assert.equal(args.p_skill_version, bound.version); assert.equal(args.p_skill_sha256, bound.sha256); assert.equal("p_engine_version" in args, false); return open; }, get_agentic_rectification_turn_receipt: () => historical, }); const opened = await openRectificationCase(accounting.client as never, USER_ID, { intent: "session", requestId: "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", sessionId: SESSION_ID, }); assert.equal(opened.disposition, "readonly"); const receipt = await loadV9TurnReceipt(accounting.client as never, USER_ID, CASE_ID, TURN_ID); assert.equal(v9EngineVersion(), LIVE_CURRENT); assert.equal(receipt?.engineVersion, oldVersion); assert.deepEqual(historical, before); assert.equal(accounting.calls.some((call) => /insert|persist|upgrade/.test(call.fn)), false); } });