fix(rectification): stop stamping pending receipts and document aggregate blocker

Implement BUG-984 F2 option A and reproduce mixed completed identities through real tools. Stop at the required SQL authorization boundary; F1/F3/F4 remain pending.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-20 15:55:09 +08:00
co-authored by Claude Code
parent f09f3d809a
commit d575e89a83
9 changed files with 186 additions and 21 deletions
@@ -160,7 +160,6 @@ import {
readV9EngineScoringIdentity,
cachedEngineScoreIsReusable,
toEngineEvents,
v9EngineVersion,
executedMethodsFromLedger,
runV9RangeReading,
runV9BlockScan,
@@ -900,7 +899,6 @@ export function createRectificationV9ReadOnlyTools(ctx: RectificationV9Context)
export function createRectificationV9Tools(ctx: RectificationV9Context) {
const { accounting, userId, caseId, turnId, attemptId, userMessage } = ctx;
const engineVersion = v9EngineVersion();
let hasReadCase = false;
let spokenPromptFailures = 0;
let setFocusCompleted = 0;
@@ -934,7 +932,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
toolName: string,
publicPhase: string,
error: unknown,
extra: { inputFingerprint?: string | null; engineVersion?: string | null } = {},
extra: { inputFingerprint?: string | null } = {},
) => {
const code = safeToolErrorCode(error);
const message = engineMessageForReceipt(error);
@@ -947,7 +945,6 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
}));
await receipt(toolName, publicPhase, "failed", {
inputFingerprint: extra.inputFingerprint ?? null,
engineVersion: extra.engineVersion ?? null,
safeErrorCode: code,
resultFingerprint: JSON.stringify({
safe_error_code: code,
@@ -1925,7 +1922,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
execute: async (input) => {
assertCaseRef(input, caseId);
const inputFingerprint = canonicalToolInputFingerprint("rectification-compare-candidates", input);
await receipt("rectification-compare-candidates", "candidates.comparing", "started", { inputFingerprint, engineVersion });
await receipt("rectification-compare-candidates", "candidates.comparing", "started", { inputFingerprint });
try {
const scored = await scoreAndPersistCurrentEvidence(input.caseId);
const latest = scored.parsed.case.stage === "block_scan"
@@ -1979,14 +1976,13 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
vedastro_validate_ms: scored.timings.vedastro_validate_ms,
persist_ms: scored.timings.persist_ms,
}),
engineVersion: scored.persisted.algorithmVersion ?? engineVersion,
engineVersion: scored.persisted.algorithmVersion ?? null,
executedMethods: scored.score.executedMethods,
});
return { ...projection, executed_methods: scored.score.executedMethods };
} catch (error) {
await failReceipt("rectification-compare-candidates", "candidates.comparing", error, {
inputFingerprint,
engineVersion,
});
throw error;
}
@@ -2001,7 +1997,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
execute: async (input) => {
assertCaseRef(input, caseId);
const inputFingerprint = canonicalToolInputFingerprint("rectification-read-diagnostics", input);
await receipt("rectification-read-diagnostics", "diagnostics.completed", "started", { inputFingerprint, engineVersion });
await receipt("rectification-read-diagnostics", "diagnostics.completed", "started", { inputFingerprint });
try {
const dossier = await loadV9CaseDossier(accounting, userId, input.caseId);
const parsed = parseDossierForTools(dossier);
@@ -2034,7 +2030,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
});
return { ...projection, executed_methods: diagnostics.executedMethods };
} catch (error) {
await failReceipt("rectification-read-diagnostics", "diagnostics.completed", error, { inputFingerprint, engineVersion });
await failReceipt("rectification-read-diagnostics", "diagnostics.completed", error, { inputFingerprint });
throw error;
}
},
@@ -109,7 +109,11 @@ test("unreachable versions retain the existing unknown-identity cache fallback,
assert.equal(cachedEngineScoreIsReusable({ ...fingerprints, algorithmVersion: PREVIOUS }, fingerprints, live), true);
});
test("new Case scoring writes the new version on both started and completed receipts", async (t) => {
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 };
@@ -123,7 +127,13 @@ test("new Case scoring writes the new version on both started and completed rece
const path = new URL(String(url)).pathname;
calls.push(path);
if (path.endsWith("/versions")) return Response.json(golden.versions);
if (path.endsWith("/score") || path.endsWith("/diagnostics")) return Response.json(golden.score);
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({
@@ -152,15 +162,44 @@ test("new Case scoring writes the new version on both started and completed rece
});
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) {
await (tools[name] as unknown as { execute(input: unknown): Promise<unknown> }).execute({ caseId: CASE_ID });
const receipts = accounting.calls.filter((call) => call.fn === "insert_agentic_rectification_tool_receipt" && call.args.p_tool_name === name);
assert.deepEqual(receipts.map((call) => [call.args.p_status, call.args.p_engine_version]), [
["started", CURRENT], ["completed", CURRENT],
]);
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 await execute();
}
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, CURRENT);
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("BUG-984 blocker: real tool sequence permits mixed completed identities in one turn", 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]);
// Preserve the blocker as positive evidence, not a false passing acceptance test.
// F2 acceptance must replace this diagnostic once the approved aggregate fix lands.
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) => {