fix(rectification): stop a stale candidate-set prefix from hiding the choice card
Independent Staging Quality Gate / validate (push) Successful in 11m31s
Independent Staging Quality Gate / publish (push) Successful in 14m41s

Scoring scoped the contrast packet to the previous round's candidate set while
minting probes for the new one, so every later read re-prefixed the stored hash
and the persisted focus schema could never match. Mint one prefix per set and
compare splits by probe identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVapmh2oGNyr6ECHKjPJY8
This commit is contained in:
Jesse_Chen
2026-08-29 08:51:19 +00:00
parent 0044d4f358
commit 3cecb25f42
6 changed files with 276 additions and 8 deletions
@@ -1,11 +1,15 @@
import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import {
buildCandidateContrastPacket,
isSameCandidateSplit,
mentionedVargaKeysFromLedgerEvidence,
remainingVargaSplits,
selectDiscriminatorProbe,
splitHashForCandidateSet,
volunteeredDomainsFromEvidence,
} from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
@@ -167,3 +171,62 @@ test("occupation mention does not skip remaining D10; answered varga.d10 still d
});
assert.equal(answeredPacket.probes.some((item) => item.semanticKey.startsWith("varga.d10.")), false);
});
test("a stale candidate-set prefix is re-minted instead of stacked", () => {
const current = "05:00-05:07:05:00,05:06,05:07";
const stale = "05:00-05:07:05:00,05:05,05:07";
const key = "varga.d9.05:00|05:06/05:07";
assert.equal(splitHashForCandidateSet(`${stale}:${key}`, key, current), `${current}:${key}`);
assert.equal(splitHashForCandidateSet(`${current}:${key}`, key, current), `${current}:${key}`);
assert.equal(splitHashForCandidateSet(null, key, current), `${current}:${key}`);
// opaque engine hashes stay scoped exactly once
assert.equal(splitHashForCandidateSet("f4cc4975", "career.2023", current), `${current}:f4cc4975`);
assert.equal(
splitHashForCandidateSet(`${current}:f4cc4975`, "career.2023", current),
`${current}:f4cc4975`,
);
});
test("engine probes keep one candidate-set prefix after the set changes", () => {
const current = "05:00-05:07:05:00,05:06,05:07";
const stale = "05:00-05:07:05:00,05:05,05:07";
const key = "varga.d9.05:00|05:06/05:07";
const packet = buildCandidateContrastPacket({
candidateSetVersion: current,
candidateTimes: ["05:00", "05:06", "05:07"],
engineProbes: [{
semantic_key: key,
candidate_split_hash: `${stale}:${key}`,
domain: "relationship",
user_meaning: "亲密关系里更接近下面哪一种相处方式?",
information_gain: 1.53,
choice_kind: "varga_style",
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] },
{ answer_class: "no", supports: ["05:07"], conflicts: ["05:00", "05:06"] },
],
}],
});
const probe = packet.probes.find((item) => item.semanticKey === key);
assert.ok(probe);
assert.equal(probe.candidateSplitHash, `${current}:${key}`);
assert.doesNotMatch(probe.candidateSplitHash, /05:05/);
});
test("split identity ignores the candidate-set prefix but not the probe", () => {
const key = "varga.d9.05:00|05:06/05:07";
assert.equal(isSameCandidateSplit(`a:${key}`, `b:${key}`, key), true);
assert.equal(isSameCandidateSplit(`a:${key}`, `a:varga.d10.05:00|05:06/05:07`, key), false);
assert.equal(isSameCandidateSplit("f4cc4975", "a:f4cc4975", "career.2023"), false);
assert.equal(isSameCandidateSplit("f4cc4975", "f4cc4975", "career.2023"), true);
});
test("scoring scopes the contrast packet to the candidate set it just produced", () => {
const source = readFileSync(new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url), "utf8");
const packetCall = source.slice(
source.indexOf("const scoredCandidateSetId = candidateSetId("),
source.indexOf("const inference = buildCaseInferenceState("),
);
assert.ok(packetCall.includes("candidateSetVersion: scoredCandidateSetId"));
assert.doesNotMatch(packetCall, /candidateSetVersion:\s*previousInferenceFromReceipt/);
});
@@ -1376,3 +1376,136 @@ test("dasha existence cards lock the event and use supplied dynamic labels", ()
assert.equal(copy.option_a, DYNAMIC_STYLE_OPTIONS[0].label);
assert.equal(copy.option_c, DYNAMIC_STYLE_OPTIONS[2].label);
});
const D9_STALE_KEY = "varga.d9.05:00|05:06/05:07";
const CURRENT_SET_ID = "05:00-05:07:05:00,05:06,05:07";
// The probe was minted while the candidate set still held 05:05 instead of 05:06.
const STALE_SET_ID = "05:00-05:07:05:00,05:05,05:07";
const D9_STYLE_OPTIONS = [
{ sign: "天秤座", label: "和谐、美感、合作", answer_class: "yes" },
{ sign: "天蝎座", label: "深刻、占有欲、转化", answer_class: "weak_yes" },
{ sign: "射手座", label: "自由、哲学、冒险", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
] as const;
function staleSplitDossier(schemaHash: string) {
return {
evidence: [
{ id: "e-career-a", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: "2020-04-01", eventKind: "career_entry" },
{ id: "e-career-b", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-10-01", occurredTo: "2020-10-01", eventKind: "career_exit" },
{ id: "e-rel-end", status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: "2024-08-08", eventKind: "relationship_end" },
{ id: "e-rel-start", status: "confirmed", domain: "relationship", datePrecision: "month", occurredFrom: "2024-05-01", occurredTo: "2024-05-31", eventKind: "relationship_start" },
],
conversationSummary: {
activeFocus: {
id: FOCUS_ID,
questionId: `probe:${D9_STALE_KEY}`,
intent: "distinguish_candidates",
targetDomain: "relationship",
targetKind: null,
expectedAnswerSchema: {
choice: {
prompt: "2024 年前后,相处方式更接近其中一种?",
option_a: D9_STYLE_OPTIONS[0].label,
option_b: D9_STYLE_OPTIONS[1].label,
option_c: D9_STYLE_OPTIONS[2].label,
option_d: D9_STYLE_OPTIONS[3].label,
options: [
{ key: "A", label: D9_STYLE_OPTIONS[0].label, answer_class: "yes" },
{ key: "B", label: D9_STYLE_OPTIONS[1].label, answer_class: "weak_yes" },
{ key: "C", label: D9_STYLE_OPTIONS[2].label, answer_class: "no" },
{ key: "D", label: D9_STYLE_OPTIONS[3].label, answer_class: "unsure" },
],
},
probe_id: `contrast:${D9_STALE_KEY}`,
semantic_key: D9_STALE_KEY,
candidate_split_hash: schemaHash,
choice_kind: "varga_style",
},
},
declinedSkippedTopics: [],
},
latestResult: {
resultId: "66666666-6666-4666-8666-666666666666",
selectionAllowed: false,
candidates: [
{ time: "05:00", relativeSupport: 34 },
{ time: "05:06", relativeSupport: 33 },
{ time: "05:07", relativeSupport: 33 },
],
decisionReceipt: {
window_scan: {
scanned: true,
d9_candidates_differ: true,
d9_sign_names: ["天秤座", "天蝎座", "射手座"],
transitions: [
{ layer: "d9", at: "05:06", from_sign: "天秤座", to_sign: "天蝎座" },
{ layer: "d9", at: "05:07", from_sign: "天蝎座", to_sign: "射手座" },
],
},
inference_state: {
algorithm_version: "rectification-inference-v1",
candidate_set_id: CURRENT_SET_ID,
revision: 1,
phase: "discrimination",
result_status: "discriminating",
range_start: "05:00",
range_end: "05:07",
candidates: [
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 34, posterior_score: 34, probability: 0.34, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "05:06", time: "05:06", cluster_range: ["05:06", "05:06"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 2, strong_conflict_count: 0 },
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 3, strong_conflict_count: 0 },
],
events: [
{ id: "e-career-a", domain: "career", year: 2020, precision: "month", usage: "training" },
{ id: "e-career-b", domain: "career", year: 2020, precision: "month", usage: "training" },
{ id: "e-rel-end", domain: "relationship", year: 2024, precision: "day", usage: "training" },
{ id: "e-rel-start", domain: "relationship", year: 2024, precision: "month", usage: "holdout" },
],
answered_probes: [],
probes: [{
id: `contrast:${D9_STALE_KEY}`,
semantic_key: D9_STALE_KEY,
// minted one scoring round earlier, so the prefix names the old set
candidate_split_hash: `${STALE_SET_ID}:${D9_STALE_KEY}`,
domain: "relationship",
year: 0,
question: "亲密关系里更接近下面哪一种相处方式?",
candidate_ids: ["05:00", "05:06", "05:07"],
information_gain: 1.53,
source: "varga_contrast",
choice_kind: "varga_style",
style_options: D9_STYLE_OPTIONS,
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] },
{ answer_class: "weak_yes", supports: ["05:06"], conflicts: ["05:00", "05:07"] },
{ answer_class: "no", supports: ["05:07"], conflicts: ["05:00", "05:06"] },
],
}],
rounds: [],
entropy: 1.5,
representative_time: "05:00",
credible_range: ["05:00", "05:07"],
},
},
},
case: { acceptedTime: null },
turns: [],
};
}
test("GET still renders the card when the probe hash carries an older candidate set prefix", () => {
const card = choiceCardFromCaseDossier(
staleSplitDossier(`${STALE_SET_ID}:${D9_STALE_KEY}`) as never,
);
assert.ok(card, "a stale candidate-set prefix must not silence the persisted card");
assert.equal(card.probe_id, `contrast:${D9_STALE_KEY}`);
assert.equal(card.question_id, `probe:${D9_STALE_KEY}`);
});
test("GET keeps hiding the card when the persisted split belongs to another probe", () => {
const card = choiceCardFromCaseDossier(
staleSplitDossier(`${CURRENT_SET_ID}:varga.d10.05:00|05:06/05:07`) as never,
);
assert.equal(card, null);
});