fix(rectification): gate collect-phase adopt cards and keep post-adopt verify answerable
Independent Staging Quality Gate / validate (push) Successful in 9m50s
Independent Staging Quality Gate / publish (push) Successful in 1m53s

Public can_adopt follows session_outcome; reverse_verify reuses the persisted question id; offer cards settle on the owning message with a status-bar handoff.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-03 09:29:34 +08:00
co-authored by Cursor
parent 5c0bec0c94
commit 35e5781e66
31 changed files with 1774 additions and 109 deletions
@@ -101,6 +101,40 @@ export async function POST(request: Request, context: RouteContext) {
return NextResponse.json({ error: "校正记录与会话绑定不一致", code: "case_session_mismatch" }, { status: 409 });
}
try {
const { loadV9CaseDossier, evidenceLedgerFingerprint } = await import(
"@/lib/rectification-agentic/v9/tool-service"
);
const { decideFromDossier, overlayPublicDecision } = await import(
"@/lib/rectification-agentic/v9/interview-state"
);
const { publicDecisionFields } = await import(
"@/lib/rectification-agentic/core/rectification-decision"
);
const dossier = await loadV9CaseDossier(accounting, user.id, caseId);
const decision = decideFromDossier(dossier, {
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence),
});
const fields = publicDecisionFields(decision);
const overlaid = dossier.latestResult
? overlayPublicDecision(dossier.latestResult, decision)
: fields;
if (overlaid.can_adopt !== true) {
return NextResponse.json(
{ error: "adoption_not_allowed", session_outcome: fields.session_outcome },
{ status: 409 },
);
}
} catch (error) {
if (error instanceof RectificationToolServiceError) {
return errorResponse(error);
}
return NextResponse.json(
{ error: "暂时无法采用该候选时间", code: "candidate_accept_failed" },
{ status: 503 },
);
}
try {
const { data, error } = await accounting.rpc(
"accept_agentic_rectification_candidate_for_case_v2",
@@ -16,7 +16,7 @@ import {
} from "@/lib/rectification-agentic/v9/tool-service";
import { choiceCardFromCaseDossier, decideFromDossier, overlayPublicDecision } from "@/lib/rectification-agentic/v9/interview-state";
import { projectCurrentQuestion } from "@/lib/rectification-agentic/v9/turn-decision";
import { attachQuestionsToTurns } from "@/lib/rectification-agentic/v9/turn-question";
import { attachQuestionsToTurns, attachOfferResultToTurns } from "@/lib/rectification-agentic/v9/turn-question";
import { previousInferenceFromReceipt } from "@/lib/rectification-agentic/v9/inference-adapter";
import { publicDecisionFields } from "@/lib/rectification-agentic/core/rectification-decision";
import { slimDecisionReceipt } from "@/lib/rectification-agentic/v9/case-receipt-projection";
@@ -99,7 +99,16 @@ export function dossierResponse(
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence),
});
const listed = options.listed ?? { focuses: [] as const, available: true };
const turns = attachQuestionsToTurns(dossier.turns, listed.focuses);
const turnsWithQuestions = attachQuestionsToTurns(dossier.turns, listed.focuses);
const fields = publicDecisionFields(decision);
const overlaid = dossier.latestResult
? overlayPublicDecision(dossier.latestResult, decision)
: fields;
const turns = attachOfferResultToTurns(turnsWithQuestions, {
resultId: dossier.latestResult?.resultId ?? null,
canAdopt: overlaid.can_adopt === true,
acceptedTime: dossier.case.acceptedTime,
});
return {
case: {
case_id: dossier.case.caseId,
@@ -125,12 +134,13 @@ export function dossierResponse(
created_at: turn.createdAt,
receipt: turnReceipt(turn.id, receipts),
question: turn.question,
offer_result_id: turn.offer_result_id,
})),
evidence: dossier.evidence,
latest_result: dossier.latestResult
? publicLatestResult(dossier.latestResult, decision, dossier, options.fullDetail === true)
: null,
interview: publicDecisionFields(decision),
interview: fields,
current_question: projectCurrentQuestion(dossier.conversationSummary.activeFocus),
choice_card: choiceCardFromCaseDossier(dossier),
question_source: questionSourceFromFocusList(listed),
@@ -169,6 +179,9 @@ function turnReceipt(
tool: activity.tool,
status: activity.status,
methods: activity.methods,
started_at: activity.startedAt ?? null,
elapsed_ms: activity.elapsedMs ?? null,
detail: activity.detail ?? null,
})),
tools: receipt.tools,
methods: receipt.methods,