fix(rectification): enforce trusted result identity and preserve receipt provenance
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:
@@ -64,6 +64,7 @@ import {
|
||||
isAbortError,
|
||||
rectificationConversationState,
|
||||
rectificationInitialLiveLabel,
|
||||
recompareHistoricalResult,
|
||||
rectificationQuestionGapState,
|
||||
rectificationReadonlyRangeCopy,
|
||||
contrastProbesFromReceipt,
|
||||
@@ -389,7 +390,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const {
|
||||
caseId,
|
||||
sessionId,
|
||||
readonly,
|
||||
readonly: historyReadonly,
|
||||
shouldStartOpening,
|
||||
initialTurns,
|
||||
initialSnapshot,
|
||||
@@ -410,6 +411,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
headerSlot,
|
||||
} = props;
|
||||
|
||||
const [resultNotice, setResultNotice] = useState(initialSnapshot?.case?.result_notice ?? null);
|
||||
const [canRecompare, setCanRecompare] = useState(initialSnapshot?.case?.result_identity?.can_recompare === true);
|
||||
const readonly = historyReadonly || Boolean(resultNotice);
|
||||
const conversationReadonly = historyReadonly || (Boolean(resultNotice) && !canRecompare);
|
||||
const [messages, setMessages] = useState<RenderMessage[]>(() => messagesFromTurns(initialTurns));
|
||||
const [draft, setDraft] = useState("");
|
||||
const queued = useQueuedMessage();
|
||||
@@ -583,6 +588,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
} | null) => {
|
||||
if (!payload) return;
|
||||
const snapshot = payload as RectificationCaseSnapshotPayload;
|
||||
setResultNotice(snapshot.case?.result_notice ?? null);
|
||||
setCanRecompare(snapshot.case?.result_identity?.can_recompare === true);
|
||||
const nextCandidate = parseRectificationCandidateResult(payload.latest_result);
|
||||
const nextQuestion = currentQuestionFromSnapshot(payload.current_question);
|
||||
const nextChoice = parseRectificationChoiceCard(payload.choice_card);
|
||||
@@ -693,7 +700,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
continuation?: Readonly<{ reuseAssistantRenderKey: string; label: string }>,
|
||||
) => {
|
||||
const trimmed = action === "message" ? messageText.trim() : "";
|
||||
if ((action === "message" && !trimmed) || readonly) return;
|
||||
if ((action === "message" && !trimmed) || conversationReadonly) return;
|
||||
if (!continuation && busy) return;
|
||||
setError("");
|
||||
setPending(true);
|
||||
@@ -1018,7 +1025,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
lastRunOutcome.current = readonly ? "readonly" : runOutcome;
|
||||
setPending(false);
|
||||
}
|
||||
}, [beginLiveRun, busy, caseId, loadCaseSnapshot, onCompleted, onMessagesChange, onOpenBilling, onProfileIncomplete, readonly, rememberLiveActivity, selectedModelId, sessionId, setPending]);
|
||||
}, [beginLiveRun, busy, caseId, loadCaseSnapshot, onCompleted, onMessagesChange, onOpenBilling, onProfileIncomplete, readonly, conversationReadonly, rememberLiveActivity, selectedModelId, sessionId, setPending]);
|
||||
|
||||
useEffect(() => {
|
||||
if (busy) return;
|
||||
@@ -1688,6 +1695,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
aria-busy={busy || regeneratingMessageKey !== null}
|
||||
>
|
||||
<div className="message-list">
|
||||
{resultNotice && <p role="status" className="rectification-pending-note">{resultNotice}</p>}
|
||||
{resultNotice && canRecompare && !historyReadonly && (
|
||||
<Button type="button" variant="outline" disabled={busy} onClick={() => void recompareHistoricalResult({ canRecompare, historyReadonly, busy, send })}>重新比较</Button>
|
||||
)}
|
||||
{pendingConsultationQuestion?.trim() && (
|
||||
<p className="rectification-pending-note">
|
||||
先陪你核对出生时间范围;结束后新建对话,按采用的时间再问:“{pendingConsultationQuestion.trim()}”
|
||||
@@ -1822,7 +1833,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
</p>
|
||||
)}
|
||||
{error && <p className="error-message" role="alert">{error}</p>}
|
||||
{readonly && (
|
||||
{historyReadonly && (
|
||||
<div className="rectification-terminal-actions">
|
||||
<p className="rectification-terminal-note">该校正已结束,只能查看历史。</p>
|
||||
<Button type="button" onClick={onRestart}>再次校正</Button>
|
||||
@@ -1839,8 +1850,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
inputRef={composer}
|
||||
value={draft}
|
||||
remainingId={composerRemainingId}
|
||||
inputLabel={readonly ? "该校正已结束,只能查看历史" : "继续描述你的经历或回答"}
|
||||
placeholder={readonly
|
||||
inputLabel={resultNotice ? "当前结果仅供查看" : readonly ? "该校正已结束,只能查看历史" : "继续描述你的经历或回答"}
|
||||
placeholder={resultNotice
|
||||
? "当前结果仅供查看,暂不能选择或采用。"
|
||||
: readonly
|
||||
? "该校正已结束,只能查看历史;需要再次校正请新建。"
|
||||
: showLiveChoiceCard
|
||||
? "点上面的选项即可;想补一句细节再写"
|
||||
|
||||
Reference in New Issue
Block a user