fix(rectification): enforce trusted result identity and preserve receipt provenance
Independent Staging Quality Gate / validate (push) Successful in 10m4s
Independent Staging Quality Gate / publish (push) Successful in 10m26s

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>
This commit is contained in:
jesse-ux
2026-09-20 18:03:38 +08:00
co-authored by Claude Code
parent 6dd62207eb
commit 8d0359fc62
37 changed files with 5983 additions and 95 deletions
@@ -22,6 +22,7 @@ const acceptSchema = z.object({
function errorResponse(error: unknown) {
const message = error instanceof Error ? error.message : String(error);
const known = [
["result_identity_read_only", 409, "按旧算法产出,暂不能采用"],
["agentic_rectification_candidate_not_found", 404, "候选记录不存在或已过期"],
["agentic_rectification_candidate_expired", 409, "候选结果已过期,请重新比较"],
["agentic_rectification_candidate_selection_blocked", 409, "当前还不能采用候选"],
@@ -114,6 +115,8 @@ export async function POST(request: Request, context: RouteContext) {
const { publicDecisionFields } = await import(
"@/lib/rectification-agentic/core/rectification-decision"
);
const { assertV9ResultWritable } = await import("@/lib/rectification-agentic/v9/result-identity");
await assertV9ResultWritable(accounting, user.id, caseId, parsed.data.resultId);
const dossier = await loadV9CaseDossier(accounting, user.id, caseId);
const decision = decideFromDossier(dossier, {
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence),
@@ -12,7 +12,7 @@ import {
listV10ConversationFocuses,
} from "@/lib/rectification-agentic/v9/tool-service";
import { ensureNonTerminalTurnExit } from "@/lib/rectification-agentic/v9/answer-choice";
import { dossierResponse } from "@/lib/rectification-agentic/v9/case-dossier-response";
import { dossierResponseWithIdentity } from "@/lib/rectification-agentic/v9/case-dossier-response";
export const runtime = "nodejs";
@@ -77,7 +77,7 @@ export async function POST(request: Request, context: RouteContext) {
const listed = await listV10ConversationFocuses(accounting, user.id, caseId);
return NextResponse.json({
ok: true,
...dossierResponse(dossier, receipts, skillIdentity, { listed }),
...await dossierResponseWithIdentity(dossier, receipts, skillIdentity, { listed }),
});
} catch (error) {
if (error instanceof RectificationToolServiceError) {
@@ -13,7 +13,7 @@ import {
listV10ConversationFocuses,
safeToolErrorCode,
} from "@/lib/rectification-agentic/v9/tool-service";
import { dossierResponse } from "@/lib/rectification-agentic/v9/case-dossier-response";
import { dossierResponseWithIdentity } from "@/lib/rectification-agentic/v9/case-dossier-response";
export const runtime = "nodejs";
@@ -96,7 +96,7 @@ export async function GET(request: Request, context: RouteContext) {
}),
);
const listed = await listV10ConversationFocuses(accounting, user.id, caseId);
return NextResponse.json(dossierResponse(dossier, receipts, skillIdentity, { fullDetail, listed }));
return NextResponse.json(await dossierResponseWithIdentity(dossier, receipts, skillIdentity, { fullDetail, listed }));
} catch (error) {
if (error instanceof RectificationToolServiceError) {
const message = error.message;