test(rectification): lock holdout cards, style-kind scoring, and strong B conflicts
Ask-holdout now requires a renderable choice frame. Scoring uses the same effective contrast kind as rendering, because the engine-varga.d9/d10 fork was live. varga_style weak_yes counts as a strong conflict; existence weak_yes still does not. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -591,6 +591,7 @@ export function conflictProbesFromContrast(
|
||||
...row.conflictsCandidateIds,
|
||||
]))];
|
||||
if (candidateIds.length < 2) return [];
|
||||
const choiceKind = effectiveContrastChoiceKind(probe);
|
||||
return [{
|
||||
id: probe.probeId,
|
||||
semantic_key: probe.semanticKey,
|
||||
@@ -602,10 +603,10 @@ export function conflictProbesFromContrast(
|
||||
expected_outcomes: outcomes,
|
||||
information_gain: probe.informationGain,
|
||||
source: "varga_contrast",
|
||||
...(probe.choiceKind === "varga_style"
|
||||
|| probe.choiceKind === "event_quality"
|
||||
|| probe.choiceKind === "existence"
|
||||
? { choice_kind: probe.choiceKind }
|
||||
...(choiceKind === "varga_style"
|
||||
|| choiceKind === "event_quality"
|
||||
|| choiceKind === "existence"
|
||||
? { choice_kind: choiceKind }
|
||||
: {}),
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -207,6 +207,7 @@ test("ask_holdout_validation is only returned when the same dossier can build a
|
||||
if (decision.nextAction !== "ask_holdout_validation") continue;
|
||||
const plan = holdoutFollowup(dossier);
|
||||
assert.ok(plan.next_followup, "ask_holdout_validation requires a holdout followup");
|
||||
assert.ok(plan.next_followup.choice_frame, "ask_holdout_validation requires a renderable card");
|
||||
assert.equal(decision.canConfirmExactMinute, false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { buildInferenceState } from "../src/lib/rectification-agentic/core/build
|
||||
import {
|
||||
buildCandidateContrastPacket,
|
||||
conflictProbesFromContrast,
|
||||
inspectDiscriminatorProbes,
|
||||
} from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
||||
import { asInferenceState } from "../src/lib/rectification-agentic/core/compose-receipt.ts";
|
||||
import { decisionStateFingerprint } from "../src/lib/rectification-agentic/core/decision-fingerprint.ts";
|
||||
@@ -118,6 +119,79 @@ test("old ConflictProbe receipts without choice_kind keep half-weight weak_yes",
|
||||
assert.equal(loaded.revision, state.revision);
|
||||
});
|
||||
|
||||
test("varga_style B-option weak_yes counts as strong conflict; existence weak_yes does not", () => {
|
||||
const style = styleProbeFrom(twoGroupStylePacket());
|
||||
assert.equal(style.choice_kind, "varga_style");
|
||||
const styleConflicted = style.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
|
||||
assert.ok(styleConflicted);
|
||||
let styleScores = Object.fromEntries(style.candidate_ids.map((id) => [id, 10]));
|
||||
let styleCounts: Record<string, number> = {};
|
||||
let eliminated = new Set<string>();
|
||||
for (let round = 0; round < 3; round += 1) {
|
||||
const applied = applyProbeOutcome(styleScores, style, "weak_yes", {
|
||||
strongConflictCounts: styleCounts,
|
||||
eliminatedIds: eliminated,
|
||||
});
|
||||
styleScores = { ...applied.scores };
|
||||
styleCounts = { ...applied.strong_conflict_counts };
|
||||
eliminated = new Set(applied.eliminated_ids);
|
||||
}
|
||||
assert.equal(styleCounts[styleConflicted], 3);
|
||||
assert.equal(eliminated.has(styleConflicted), true);
|
||||
|
||||
const existencePacket = buildCandidateContrastPacket({
|
||||
candidateSetVersion: "05:00-05:04",
|
||||
candidateTimes: ["05:00", "05:04"],
|
||||
transitions: [{ layer: "d9", at: "05:04" }],
|
||||
});
|
||||
const existence = conflictProbesFromContrast(existencePacket).find((item) => item.choice_kind === "existence");
|
||||
assert.ok(existence);
|
||||
const existenceConflicted = existence.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
|
||||
assert.ok(existenceConflicted);
|
||||
let existenceScores = Object.fromEntries(existence.candidate_ids.map((id) => [id, 10]));
|
||||
let existenceCounts: Record<string, number> = {};
|
||||
for (let round = 0; round < 3; round += 1) {
|
||||
const applied = applyProbeOutcome(existenceScores, existence, "weak_yes", {
|
||||
strongConflictCounts: existenceCounts,
|
||||
});
|
||||
existenceScores = { ...applied.scores };
|
||||
existenceCounts = { ...applied.strong_conflict_counts };
|
||||
assert.equal(applied.eliminated_ids.length, 0);
|
||||
}
|
||||
assert.equal(existenceCounts[existenceConflicted], 0);
|
||||
});
|
||||
|
||||
test("engine varga.d9/d10 without style_options scores with the render effective kind", () => {
|
||||
for (const semanticKey of ["varga.d9", "varga.d10"] as const) {
|
||||
const packet = {
|
||||
candidateSetVersion: "05:00-05:04",
|
||||
vargaDifferences: [],
|
||||
probes: [{
|
||||
probeId: `contrast:${semanticKey}.engine-no-signs`,
|
||||
candidateSetVersion: "05:00-05:04",
|
||||
question: "那几年相处更接近哪一种?",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "supports_05:00", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:04"] },
|
||||
{ outcomeId: "supports_05:04", supportsCandidateIds: ["05:04"], conflictsCandidateIds: ["05:00"] },
|
||||
],
|
||||
candidateSplitHash: `05:00-05:04:${semanticKey}.engine-no-signs`,
|
||||
informationGain: 1.2,
|
||||
sourceFeatures: [{ technique: semanticKey.slice(6).toUpperCase(), calculationResultId: null }],
|
||||
domain: "relationship",
|
||||
year: null,
|
||||
semanticKey,
|
||||
choiceKind: "varga_style" as const,
|
||||
}],
|
||||
};
|
||||
const scored = conflictProbesFromContrast(packet);
|
||||
const rendered = inspectDiscriminatorProbes(packet).selected;
|
||||
assert.equal(scored.length, 1, semanticKey);
|
||||
assert.ok(rendered, semanticKey);
|
||||
assert.equal(scored[0]?.choice_kind, rendered.choiceKind, semanticKey);
|
||||
assert.equal(rendered.choiceKind, "existence", semanticKey);
|
||||
}
|
||||
});
|
||||
|
||||
test("replaying a varga_style probe keeps candidate_set_id and a monotonic revision", () => {
|
||||
const probe = styleProbeFrom(twoGroupStylePacket());
|
||||
const before = buildInferenceState({
|
||||
|
||||
Reference in New Issue
Block a user