Files
Jyotisha/frontend/tests/rectification-engine-version-cross-midnight.test.ts
T
jesse-ux b85c4a686a
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s
fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
2026-09-21 02:55:00 +08:00

504 lines
33 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"));
// 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<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);
});
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<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 !== 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);
}
});