fix(rectification): keep compare requests valid after style cards (BUG-577–580)

Engine asked_probe_keys no longer include varga split hashes that 400 the scorer, failed compares become visible and retry, user stop can still deliver a range on a stale snapshot, and holdout no longer reasks domains already in the ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 15:46:33 +08:00
co-authored by Cursor
parent 3f4d38d485
commit 4e0db55f03
34 changed files with 1079 additions and 156 deletions
@@ -29,9 +29,13 @@ import {
} from "../src/lib/rectification-agentic/v9/run-diagnostic.ts";
import { applyHoldoutAnswer, buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import { applyChoiceWithoutEvidence } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
import { RECTIFICATION_TERMINATION_COPY } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { RECTIFICATION_TERMINATION_COPY, ADOPT_OUTCOMES } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { containsBoundarySemantics, RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
import { parseV9CaseDossier, RectificationToolServiceError } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import {
evidenceLedgerFingerprint,
parseV9CaseDossier,
RectificationToolServiceError,
} from "../src/lib/rectification-agentic/v9/tool-service.ts";
import {
CASE_ID,
EVIDENCE_ID,
@@ -227,8 +231,20 @@ function rangeNarrationInference(leadSupport: number, trailSupport: number) {
});
}
function withCurrentEvidenceFingerprint(raw: ReturnType<typeof dossierFixture>) {
const parsed = parseV9CaseDossier(raw);
if (!parsed) return raw;
const latest = raw.latest_result && typeof raw.latest_result === "object"
? {
...(raw.latest_result as Record<string, unknown>),
evidence_ledger_fingerprint: evidenceLedgerFingerprint(parsed.evidence),
}
: raw.latest_result;
return { ...raw, latest_result: latest };
}
function rangeNarrationDossier(inference: ReturnType<typeof buildInferenceState>) {
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
latestResult: candidateSnapshotFixture({
decisionReceipt: { inference_state: inference },
}),
@@ -253,15 +269,16 @@ function rangeNarrationDossier(inference: ReturnType<typeof buildInferenceState>
},
}),
}),
});
}));
}
function choiceDossier() {
function choiceDossier(evidence?: ReturnType<typeof fourEventRows>) {
const inference = inferenceState();
const snapshot = candidateSnapshotFixture({
decisionReceipt: { inference_state: inference },
});
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
...(evidence ? { evidence, evidenceCount: evidence.length } : {}),
latestResult: snapshot,
conversationSummary: conversationSummaryFixture({
activeFocus: activeFocusFixture({
@@ -284,7 +301,7 @@ function choiceDossier() {
},
}),
}),
});
}));
}
function twoProbeInference() {
@@ -392,7 +409,7 @@ function twoProbeDossier() {
],
},
});
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
evidenceCount: 5,
evidence: fourEventRows(),
latestResult: snapshot,
@@ -428,7 +445,7 @@ function twoProbeDossier() {
created_at: "2026-08-28T07:36:54.000Z",
completed_at: "2026-08-28T07:37:34.000Z",
}],
});
}));
}
function familyCollectInference() {
@@ -459,7 +476,7 @@ function familyCollectDossier() {
evidence_collection_probes: [FAMILY_2021_COLLECT],
},
});
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
evidenceCount: 5,
evidence: fourEventRows(),
latestResult: snapshot,
@@ -486,7 +503,7 @@ function familyCollectDossier() {
},
}),
}),
});
}));
}
function adoptionInference() {
@@ -509,7 +526,7 @@ function adoptionInference() {
function adoptionDossier() {
const inference = adoptionInference();
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
evidenceCount: 6,
evidence: [
...fourEventRows(),
@@ -566,7 +583,7 @@ function adoptionDossier() {
},
}),
}),
});
}));
}
function persistChoiceAccounting(
@@ -1031,7 +1048,7 @@ test("keeps the applied answer when narration persistence fails", async () => {
});
test("stop_and_review does not write an inference transition", async () => {
const accounting = choiceAccounting();
const accounting = persistChoiceAccounting(choiceDossier(fourEventRows()));
const applied = await applyRectificationChoice(accounting.client, {
userId: USER_ID,
caseId: CASE_ID,
@@ -1044,8 +1061,14 @@ test("stop_and_review does not write an inference transition", async () => {
expectedRevision: inferenceState().revision,
});
assert.equal(applied.optionId, "stop");
assert.match(applied.narration, /已记录你的选择/);
assert.equal(applied.narration.split(RECTIFICATION_TERMINATION_COPY).length - 1, 1);
assert.ok(ADOPT_OUTCOMES.has(applied.nextAction.session_outcome));
assert.equal(applied.nextAction.can_adopt, true);
assert.match(applied.narration, /05:\d{2}|目前范围|眼下更站得住的是/);
assert.ok(
applied.narration.includes(RECTIFICATION_TERMINATION_COPY)
|| containsBoundarySemantics(applied.narration)
|| /眼下更站得住的是/.test(applied.narration),
);
const persist = accounting.calls.find((call) => call.fn === "apply_agentic_rectification_choice_action");
assert.equal(persist?.args.p_inference, null);
assert.equal(persist?.args.p_focus_status, "skipped");
@@ -1629,7 +1652,7 @@ function lastVerifyDossier() {
],
},
});
return dossierFixture({
return withCurrentEvidenceFingerprint(dossierFixture({
evidenceCount: 5,
evidence: fourEventRows(),
latestResult: snapshot,
@@ -1665,7 +1688,7 @@ function lastVerifyDossier() {
created_at: "2026-08-28T07:36:54.000Z",
completed_at: "2026-08-28T07:37:34.000Z",
}],
});
}));
}
test("skipping the last post-adopt verify question closes with start_consultation", async () => {