Files
Jyotisha/frontend/tests/rectification-engine-version-cross-midnight.test.ts
T
jesse-uxandClaude Code 8d0359fc62
Independent Staging Quality Gate / validate (push) Successful in 10m4s
Independent Staging Quality Gate / publish (push) Successful in 10m26s
fix(rectification): enforce trusted result identity and preserve receipt provenance
Unify minute and block cache identity, keep unverifiable historical results read-only across server tools and write entrypoints, and aggregate completed receipt sources chronologically through a compatible function migration.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 18:03:38 +08:00

475 lines
31 KiB
TypeScript

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"));
const CURRENT = "rectification-v5-matrix-scoring-8";
const PREVIOUS = "rectification-v5-matrix-scoring-7";
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(), CURRENT);
for (const blank of ["", " "]) {
process.env.RECTIFICATION_ENGINE_VERSION = blank;
assert.equal(v9EngineVersion(), 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-8"/);
// 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<string, unknown>; 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<string, unknown>; 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<Record<string, unknown>> }).execute({ caseId: CASE_ID });
assert.equal(read.can_recompare, true);
assert.equal((read.next_action as Record<string, unknown>).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<unknown> }).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<Record<string, unknown>> }).execute({ caseId: CASE_ID });
assert.equal(readProjection.read_only, true);
assert.equal((readProjection.candidate_summary as Record<string, unknown>).selection_allowed, false);
const diagnostics = await (tools["rectification-read-diagnostics"] as unknown as { execute(input: unknown): Promise<Record<string, unknown>> }).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<unknown> }).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);
});
async function runGoldenToolSequence(t: test.TestContext, options: {
compareVersion?: string;
diagnosticsVersion?: string;
diagnosticsFailure?: boolean;
} = {}) {
isolateIdentityEnv(t);
const request = golden.request;
const candidateRange = { start_time: request.start_time, end_time: request.end_time };
const evidence = request.events.map((event: Record<string, string>) => ({
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<unknown> }).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 !== CURRENT) {
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 !== CURRENT) {
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 });
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(), CURRENT);
assert.equal(receipt?.engineVersion, oldVersion);
assert.deepEqual(historical, before);
assert.equal(accounting.calls.some((call) => /insert|persist|upgrade/.test(call.fn)), false);
}
});