fix(rectification): offer/compare model views drop audit-only fields (R1, BUG-593 side path)

T5 of TASK-rectification-grounding-20260927.
- rectification-offer-candidates returned the full projection (~100 KB on the
  public AA case: *_pre_inference, 31 KB contrast packet, probe lists). It now
  returns the compare stripping (offerModelProjection); 100,012 → 28,799 bytes.
- agentVisibleLatestProjection also drops engine_indistinguishable_width_minutes
  (audit-only since BUG-593), keeps the verification Markdown once
  (skill_verification_report; range_delivery.verification_markdown was a
  byte-identical copy) and shows the slim candidate list.
- Receipt fingerprints stay over the full payloads; the case API projection
  (latestResultToolProjection) is unchanged.
Contract tests on a real local engine response (public AA chart).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 03:28:27 +08:00
co-authored by Claude Opus 5.5
parent 5b9fbe5b36
commit 1ed3e55579
2 changed files with 170 additions and 3 deletions
+28 -3
View File
@@ -114,7 +114,7 @@ import {
publicEvidenceItemStatus,
resolveEvidenceQuote,
} from "@/lib/rectification-agentic/v9/evidence-quote";
import { projectTurnDecision } from "@/lib/rectification-agentic/v9/turn-decision";
import { modelVisibleInference, projectTurnDecision } from "@/lib/rectification-agentic/v9/turn-decision";
import {
contrastPacketFromLatestResult,
decideFromDossier,
@@ -624,8 +624,16 @@ function agentVisibleLatestProjection(
representative_time_pre_inference: _preTime,
house_table_pre_inference: _preHouse,
natal_recast_pre_inference: _preNatal,
// R1 (TASK-rectification-grounding-20260927): the engine's own width is
// audit-only (BUG-593); the report width is skill_verification_report.
engine_indistinguishable_width_minutes: _engineWidth,
...rest
} = projection;
// R1: the verification Markdown is kept once, in skill_verification_report;
// range_delivery carried a byte-identical copy.
const rangeDelivery = rest.range_delivery && typeof rest.range_delivery === "object" && !Array.isArray(rest.range_delivery)
? (({ verification_markdown: _markdown, ...delivery }) => delivery)(rest.range_delivery as Record<string, unknown>)
: rest.range_delivery;
const currentQuestion = extras.openQuestion
? extras.openQuestion.unrenderable === true
? {
@@ -647,13 +655,15 @@ function agentVisibleLatestProjection(
probe_id: extras.openQuestion.probe_id,
}
: null;
// BUG-1057: the same slim candidate list the read-case shows.
const compactInference = inference && typeof inference === "object" && !Array.isArray(inference)
? inference as Record<string, unknown>
? modelVisibleInference(inference as Record<string, unknown>)
: null;
const renderableChoice = extras.openQuestion?.kind === "choice"
&& extras.openQuestion.unrenderable !== true;
return {
...rest,
...(rangeDelivery !== undefined ? { range_delivery: rangeDelivery } : {}),
current_question: currentQuestion,
current_probe: null,
inference_state: renderableChoice && compactInference
@@ -675,6 +685,17 @@ function agentVisibleLatestProjection(
};
}
/**
* What rectification-offer-candidates hands the model (R1): the compare
* stripping applied to the offer payload. No open question rides on an offer.
*/
export function offerModelProjection(payload: Record<string, unknown>): Record<string, unknown> {
return agentVisibleLatestProjection(payload, {
openQuestion: null,
focusStatus: "not_applicable",
});
}
function followupPlanForParsed(
parsed: DossierForTools,
latest: DossierForTools["latestResult"],
@@ -2202,7 +2223,11 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
inputFingerprint,
resultFingerprint: hashResult(payload),
});
return payload;
// R1 (BUG-593 side path): the model gets the same stripped projection
// compare returns — no *_pre_inference, contrast packet, probes,
// duplicate report Markdown or engine width. The receipt fingerprint
// above stays over the full payload.
return offerModelProjection(payload);
} catch (error) {
await failReceipt("rectification-offer-candidates", "candidates.updated", error, { inputFingerprint });
throw error;