Files
Jyotisha/frontend/tests/rectification-engine-version-cross-midnight.test.ts
T
Jesse_ChenandClaude Opus 5.5 3733b9787b fix(rectification): bump scoring identity to scoring-10 for functional profile v2; dated contract by generation (BUG-1181)
Functional roles feed the *_functional_*_auxiliary rules, so 57782aea changes
candidate scores for identical input (memoization fixture 12:00: 8.6274 ->
8.4977). Per the "scoring semantics change => bump ALGORITHM_VERSION"
precedent (scoring-7 -> 8 -> 9), the identity moves to scoring-10; policy v3,
input contract v5 and Skill versions are unchanged, history is not relabeled.

Five frontend sites and one SQL guard tested `=== "...scoring-9"` for the
dated candidate-window contract; they now use isDatedScoringAlgorithmVersion /
a generation regex (>= 9). Migration 20261002010000 only recreates
validate_dated_rectification_candidate (one-line guard change).

Memoization golden v2 written by the test's own write_golden; v1 (scoring-8)
frozen by sha256. Real-engine scoring-10 cross-midnight golden added. Research
records re-frozen per ERR-110 (label functional_v2_2026_10_02) and
scripts/functional_benefics.py added to the frozen production identity
(ERR-114: 57782aea changed scores without tripping it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-02 12:31:50 +08:00

517 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
import { rectificationChatSurface } from "./rectification-chat-surface.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";
// 原值: LIVE_CURRENT = "rectification-v5-matrix-scoring-9"(liveGolden 同时充当「当前版本」)
// 新值: LIVE_CURRENT = "...-scoring-10";scoring-9 的 dated golden 留作历史(DATED_HISTORICAL),
// 当前版本另有真实引擎生成的 scoring-10 dated golden(currentGolden)
// 原因: 功能吉凶 v2 改变同输入分数,按「打分语义变化必须 bump」升算法身份(BUG-1181)
const LIVE_CURRENT = "rectification-v5-matrix-scoring-10";
const DATED_HISTORICAL = "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 currentGolden = JSON.parse(readFileSync(new URL(
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-10.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-10"/);
assert.equal(liveGolden.response.algorithm_version, DATED_HISTORICAL);
assert.equal(currentGolden.response.algorithm_version, LIVE_CURRENT);
const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) };
const live = {
algorithmVersion: currentGolden.response.algorithm_version,
policyVersion: currentGolden.response.decision_policy_version,
};
const historical = { ...fingerprints, algorithmVersion: CURRENT, policyVersion: golden.versions.decision_policy_version };
assert.equal(cachedEngineScoreIsReusable(historical, fingerprints, live), false);
// BUG-1181: a scoring-9 cache (old functional grouping) is not current under scoring-10.
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: DATED_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);
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
const component = rectificationChatSurface;
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);
}
});