fix(rectification): ask from the scored probe catalog, not snapshot leftovers
Empty snapshot candidates were starving remaining D24 splits, so the TypeScript follow-up chain asked the low-gain Python career probe. Read paths now share one inference+engine catalog and yield a stale low-gain distinguish card to the current winner. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -80,8 +80,11 @@ export type EngineContrastProbe = Readonly<{
|
||||
information_gain?: number;
|
||||
expected_outcomes?: readonly Readonly<{
|
||||
answer_class?: string;
|
||||
outcomeId?: string;
|
||||
supports?: readonly string[];
|
||||
supportsCandidateIds?: readonly string[];
|
||||
conflicts?: readonly string[];
|
||||
conflictsCandidateIds?: readonly string[];
|
||||
}>[];
|
||||
left_time?: string;
|
||||
right_time?: string;
|
||||
@@ -312,11 +315,12 @@ export function buildCandidateContrastPacket(input: {
|
||||
candidateTimes: input.candidateTimes ?? [],
|
||||
transitions: input.transitions ?? [],
|
||||
});
|
||||
const presentKeys = new Set(fromEngine.map((item) => item.semanticKey));
|
||||
const fromVarga = vargaProbe(
|
||||
remainingSplits,
|
||||
input.candidateSetVersion,
|
||||
input.calculationResultId ?? null,
|
||||
asked,
|
||||
new Set([...asked, ...presentKeys]),
|
||||
);
|
||||
const probes = [...fromEngine, ...(fromVarga ? [fromVarga] : [])]
|
||||
.sort((left, right) => right.informationGain - left.informationGain);
|
||||
@@ -515,15 +519,30 @@ export function conflictProbesFromContrast(
|
||||
});
|
||||
}
|
||||
|
||||
function inferredContrastChoiceKind(
|
||||
semanticKey: string,
|
||||
explicit?: ContrastChoiceKind,
|
||||
): ContrastChoiceKind {
|
||||
if (explicit === "varga_style" || explicit === "event_quality" || explicit === "existence") {
|
||||
return explicit;
|
||||
}
|
||||
const layer = semanticKey.match(/^varga\.(d\d+)/)?.[1];
|
||||
if (layer === "d9" || layer === "d10") return "varga_style";
|
||||
if (layer === "d24" || layer === "d5") return "event_quality";
|
||||
return "existence";
|
||||
}
|
||||
|
||||
function probeFromEngine(
|
||||
probe: EngineContrastProbe,
|
||||
candidateSetVersion: string,
|
||||
calculationResultId: string | null,
|
||||
): CandidateDiscriminatorProbe | null {
|
||||
const outcomes = (probe.expected_outcomes ?? []).flatMap((row) => {
|
||||
const outcomeId = typeof row.answer_class === "string" ? row.answer_class : "";
|
||||
const supports = row.supports ?? [];
|
||||
const conflicts = row.conflicts ?? [];
|
||||
const outcomeId = typeof row.answer_class === "string" && row.answer_class.trim()
|
||||
? row.answer_class
|
||||
: typeof row.outcomeId === "string" ? row.outcomeId : "";
|
||||
const supports = row.supports ?? row.supportsCandidateIds ?? [];
|
||||
const conflicts = row.conflicts ?? row.conflictsCandidateIds ?? [];
|
||||
if (!outcomeId) return [];
|
||||
return [{ outcomeId, supportsCandidateIds: supports, conflictsCandidateIds: conflicts }];
|
||||
});
|
||||
@@ -553,7 +572,7 @@ function probeFromEngine(
|
||||
domain: probe.domain ?? null,
|
||||
year: probe.year ?? null,
|
||||
semanticKey,
|
||||
choiceKind: probe.choice_kind,
|
||||
choiceKind: inferredContrastChoiceKind(semanticKey, probe.choice_kind),
|
||||
styleOptions: styleOptionsFromEngine(probe.style_options),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
selectDiscriminatorProbe,
|
||||
volunteeredDomainsFromEvidence,
|
||||
type CandidateContrastPacket,
|
||||
type EngineContrastProbe,
|
||||
} from "../core/candidate-contrast-packet.ts";
|
||||
import {
|
||||
decideRectification,
|
||||
@@ -84,6 +85,22 @@ export function candidateScoresFromDossier(latest: DecisionDossier["latestResult
|
||||
return authoritativeCandidateProjection(latest).scores;
|
||||
}
|
||||
|
||||
const CLOCK_TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
||||
|
||||
export function discriminatorCandidateTimes(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
): string[] {
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
const fromInference = [...new Set(
|
||||
(inference?.candidates ?? [])
|
||||
.filter((item) => item.status !== "eliminated")
|
||||
.map((item) => item.time)
|
||||
.filter((time) => CLOCK_TIME.test(time)),
|
||||
)];
|
||||
if (fromInference.length >= 2) return fromInference;
|
||||
return candidateScoresFromDossier(latest).map((item) => item.time);
|
||||
}
|
||||
|
||||
function holdoutStatusFromInference(inference: ReturnType<typeof previousInferenceFromReceipt>) {
|
||||
if (!inference) return "unavailable" as const;
|
||||
const hasHoldout = inference.events.some((item) => item.usage === "holdout");
|
||||
@@ -105,15 +122,47 @@ function holdoutStatusFromState(state: InferenceState) {
|
||||
return "unavailable" as const;
|
||||
}
|
||||
|
||||
export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateContrastPacket {
|
||||
const windowScan = windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const candidateScores = candidateScoresFromDossier(dossier.latestResult);
|
||||
export function contrastPacketFromLatestResult(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
evidence: DecisionDossier["evidence"] = [],
|
||||
): CandidateContrastPacket {
|
||||
const windowScan = windowScanFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const refinement = refinementFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
const answered = new Set((inference?.answered_probes ?? []).map((item) => item.id));
|
||||
const fromInference: EngineContrastProbe[] = (inference?.probes ?? []).flatMap((probe) => {
|
||||
if (answered.has(probe.id) || probe.information_gain <= 0) return [];
|
||||
if (probe.source === "known_event_quality") return [];
|
||||
return [{
|
||||
semantic_key: probe.semantic_key,
|
||||
candidate_split_hash: probe.candidate_split_hash,
|
||||
domain: probe.domain,
|
||||
year: probe.year > 0 ? probe.year : undefined,
|
||||
user_meaning: probe.question,
|
||||
information_gain: probe.information_gain,
|
||||
expected_outcomes: probe.expected_outcomes,
|
||||
candidate_ids: probe.candidate_ids,
|
||||
}];
|
||||
});
|
||||
const merged = mergeEngineProbes(
|
||||
fromInference,
|
||||
refinement.discriminating_event_probes.map((probe) => ({
|
||||
semantic_key: probe.semantic_key,
|
||||
candidate_split_hash: probe.candidate_split_hash,
|
||||
domain: probe.domain,
|
||||
year: probe.year,
|
||||
user_meaning: probe.user_meaning,
|
||||
information_gain: probe.information_gain,
|
||||
expected_outcomes: probe.expected_outcomes,
|
||||
candidate_ids: probe.candidate_ids,
|
||||
choice_kind: probe.choice_kind,
|
||||
style_options: probe.style_options,
|
||||
})),
|
||||
);
|
||||
return buildCandidateContrastPacket({
|
||||
candidateSetVersion: inference?.candidate_set_id ?? dossier.latestResult?.resultId ?? "none",
|
||||
calculationResultId: dossier.latestResult?.resultId ?? null,
|
||||
engineProbes: refinement.discriminating_event_probes,
|
||||
candidateSetVersion: inference?.candidate_set_id ?? latest?.resultId ?? "none",
|
||||
calculationResultId: latest?.resultId ?? null,
|
||||
engineProbes: merged,
|
||||
vargaDifferences: [
|
||||
...(windowScan?.d9_candidates_differ && windowScan.d9_sign_names.length >= 2
|
||||
? [{ layer: "d9", signs: windowScan.d9_sign_names }]
|
||||
@@ -122,16 +171,57 @@ export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateCo
|
||||
? [{ layer: "d10", signs: windowScan.d10_sign_names }]
|
||||
: []),
|
||||
],
|
||||
candidateTimes: candidateScores.map((item) => item.time),
|
||||
candidateTimes: discriminatorCandidateTimes(latest),
|
||||
transitions: windowScan?.transitions ?? [],
|
||||
askedKeys: askedDiscriminatorKeys(
|
||||
dossier.latestResult?.decisionReceipt,
|
||||
dossier.evidence,
|
||||
),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(dossier.evidence),
|
||||
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
|
||||
});
|
||||
}
|
||||
|
||||
function mergeEngineProbes(
|
||||
...groups: ReadonlyArray<readonly EngineContrastProbe[] | undefined>
|
||||
): EngineContrastProbe[] {
|
||||
const byKey = new Map<string, EngineContrastProbe>();
|
||||
for (const group of groups) {
|
||||
for (const probe of group ?? []) {
|
||||
const key = probe.semantic_key?.trim() ?? "";
|
||||
if (!key) continue;
|
||||
const current = byKey.get(key);
|
||||
if (!current || (probe.information_gain ?? 0) > (current.information_gain ?? 0)) {
|
||||
byKey.set(key, probe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...byKey.values()];
|
||||
}
|
||||
|
||||
export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateContrastPacket {
|
||||
return contrastPacketFromLatestResult(dossier.latestResult, dossier.evidence);
|
||||
}
|
||||
|
||||
export function rectificationFollowupCatalog(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
evidence: DecisionDossier["evidence"] = [],
|
||||
) {
|
||||
const receipt = latest?.decisionReceipt ?? null;
|
||||
const refinement = refinementFromDecisionReceipt(receipt);
|
||||
const inference = previousInferenceFromReceipt(receipt);
|
||||
return {
|
||||
contrastPacket: contrastPacketFromLatestResult(latest, evidence),
|
||||
topCandidateTimes: discriminatorCandidateTimes(latest),
|
||||
askedProbeKeys: askedDiscriminatorKeys(receipt, evidence),
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
precisionStage: refinement.precision_stage?.current ?? null,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
holdoutEvents: (inference?.events ?? [])
|
||||
.filter((item) => item.usage === "holdout")
|
||||
.map((item) => ({ domain: item.domain, year: item.year })),
|
||||
};
|
||||
}
|
||||
|
||||
function contrastPacketFromState(state: InferenceState): CandidateContrastPacket {
|
||||
const answered = new Set(state.answered_probes.map((item) => item.probe_id));
|
||||
return buildCandidateContrastPacket({
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
* Card identity is the persisted focus UUID plus the inference revision.
|
||||
*/
|
||||
|
||||
import { askedKeysFromLedgerEvidence } from "../core/candidate-contrast-packet.ts";
|
||||
import { askedProbeKeysFromReceipt, previousInferenceFromReceipt } from "./inference-adapter";
|
||||
import { previousInferenceFromReceipt } from "./inference-adapter";
|
||||
import {
|
||||
contrastPacketFromDossier,
|
||||
decideFromDossier,
|
||||
rectificationFollowupCatalog,
|
||||
} from "./decision-from-dossier";
|
||||
import { evidenceLedgerFingerprint } from "./tool-service";
|
||||
import { projectRectificationChoiceCard } from "./method-followup";
|
||||
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import {
|
||||
internalObservationsFromWindowScan,
|
||||
windowScanFromDecisionReceipt,
|
||||
@@ -62,11 +60,11 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
};
|
||||
turns?: readonly Readonly<{ role: string; text: string | null }>[];
|
||||
}): RectificationChoiceCard | null {
|
||||
const windowScan = windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const observations = internalObservationsFromWindowScan(windowScan);
|
||||
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const catalog = rectificationFollowupCatalog(dossier.latestResult, dossier.evidence);
|
||||
const observations = internalObservationsFromWindowScan(
|
||||
windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null),
|
||||
);
|
||||
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const contrastPacket = contrastPacketFromDossier(dossier);
|
||||
const decision = decideFromDossier(dossier, {
|
||||
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence as never),
|
||||
});
|
||||
@@ -74,35 +72,23 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
.reverse()
|
||||
.find((turn) => turn.role === "assistant")
|
||||
?.text ?? null;
|
||||
const holdoutEvents = (inference?.events ?? [])
|
||||
.filter((item) => item.usage === "holdout")
|
||||
.map((item) => ({ domain: item.domain, year: item.year }));
|
||||
return projectRectificationChoiceCard({
|
||||
evidence: dossier.evidence,
|
||||
activeFocus: dossier.conversationSummary.activeFocus,
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome: decision.sessionOutcome,
|
||||
...catalog,
|
||||
precisionStage: decision.precisionStage === "collect_events"
|
||||
? "collect_events"
|
||||
: decision.precisionStage === "ready_to_adopt"
|
||||
? "ready_to_adopt"
|
||||
: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: [
|
||||
...askedProbeKeysFromReceipt(dossier.latestResult?.decisionReceipt),
|
||||
...askedKeysFromLedgerEvidence(dossier.evidence),
|
||||
],
|
||||
: catalog.precisionStage,
|
||||
accepted: Boolean(dossier.case.acceptedTime),
|
||||
selectionAllowed: decision.selectionAllowed,
|
||||
proposeAllowed: decision.proposeAllowed,
|
||||
confirmationAllowed: decision.canConfirmExactMinute,
|
||||
caseRevision: inference?.revision ?? 0,
|
||||
contrastPacket,
|
||||
candidateScores: decision.separation.ranked.map((item) => ({
|
||||
time: item.time,
|
||||
score: item.score,
|
||||
@@ -111,7 +97,6 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
latestAssistantText,
|
||||
candidatesSeparated: decision.separation.sufficient,
|
||||
holdoutValidation: decision.holdoutValidation,
|
||||
holdoutEvents,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
* Coverage complete never means adopt. Horary does not block cards.
|
||||
* A/B/C/D choice frames attach only when candidates already diverge
|
||||
* (event probes, precision stage, varga observation, nakshatra, or holdout).
|
||||
* An already-open distinguish card yields if the live catalog winner is a
|
||||
* different probe. Do not keep a low-gain Python event card over D24.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -499,9 +501,19 @@ function followupOwnedProbe(
|
||||
};
|
||||
}
|
||||
|
||||
function persistedFocusProbeKey(focus: MethodFollowupFocus | null | undefined): string {
|
||||
const key = focus?.expectedAnswerSchema?.semantic_key;
|
||||
return typeof key === "string" && key.trim() ? key.trim() : "";
|
||||
}
|
||||
|
||||
function rankedDiscriminatorKey(row: RankedDiscriminator | null): string {
|
||||
if (!row) return "";
|
||||
return row.eventProbe?.semantic_key ?? row.contrastProbe?.semanticKey ?? "";
|
||||
}
|
||||
|
||||
function rankRenderableDiscriminators(input: {
|
||||
eventProbes: readonly DiscriminatingEventProbe[];
|
||||
contrastProbe: CandidateDiscriminatorProbe | null;
|
||||
contrastProbes: readonly CandidateDiscriminatorProbe[];
|
||||
askedKeys: ReadonlySet<string>;
|
||||
topCandidateTimes?: readonly string[];
|
||||
}): RankedDiscriminator[] {
|
||||
@@ -520,7 +532,9 @@ function rankRenderableDiscriminators(input: {
|
||||
for (const probe of input.eventProbes) {
|
||||
push(renderableEventProbe(probe, input.askedKeys, top));
|
||||
}
|
||||
push(input.contrastProbe ? renderableContrastProbe(input.contrastProbe, input.askedKeys, top) : null);
|
||||
for (const probe of input.contrastProbes) {
|
||||
push(renderableContrastProbe(probe, input.askedKeys, top));
|
||||
}
|
||||
return rows.sort((left, right) => right.score - left.score || (right.eventProbe?.information_gain ?? right.contrastProbe?.informationGain ?? 0) - (left.eventProbe?.information_gain ?? left.contrastProbe?.informationGain ?? 0));
|
||||
}
|
||||
|
||||
@@ -811,6 +825,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
accepted?: boolean;
|
||||
candidatesSeparated?: boolean;
|
||||
contrastPacket?: CandidateContrastPacket | null;
|
||||
topCandidateTimes?: readonly string[];
|
||||
holdoutValidation?: HoldoutValidationStatus;
|
||||
holdoutEvents?: readonly Readonly<{ domain: string; year: number | null }>[];
|
||||
}): MethodFollowupPlan {
|
||||
@@ -879,7 +894,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
|
||||
const sessionOutcome = input.sessionOutcome ?? "collect_evidence";
|
||||
const candidatesSeparated = input.candidatesSeparated === true;
|
||||
const contrastProbe = selectDiscriminatorProbe(input.contrastPacket ?? null);
|
||||
const contrastProbes = candidatesSeparated ? [] : [...(input.contrastPacket?.probes ?? [])];
|
||||
// Legacy known-event quality cards were never backed by an inference probe.
|
||||
// Ignore them so existing cases resume evidence collection instead of exposing a stale card.
|
||||
const focus = input.activeFocus?.intent === "clarify_event" ? null : input.activeFocus ?? null;
|
||||
@@ -887,6 +902,20 @@ export function buildMethodFollowupPlan(input: {
|
||||
focus && (focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check"),
|
||||
);
|
||||
const coverageComplete = blockingMethodsCovered(methods);
|
||||
const askedKeys = new Set([
|
||||
...(input.askedProbeKeys ?? []),
|
||||
...askedKeysFromLedgerEvidence(input.evidence),
|
||||
]);
|
||||
const rankedDiscriminators = dashaCovered && meetsAcceptanceEventQuality(input.evidence)
|
||||
? rankRenderableDiscriminators({
|
||||
eventProbes: remainingConflictProbes(input.eventProbes, input.evidence, declined, askedKeys),
|
||||
contrastProbes,
|
||||
askedKeys,
|
||||
topCandidateTimes: input.topCandidateTimes,
|
||||
})
|
||||
: [];
|
||||
const bestDiscriminator = rankedDiscriminators[0] ?? null;
|
||||
const catalogWinnerKey = rankedDiscriminatorKey(bestDiscriminator);
|
||||
const staleCollectFocus = Boolean(
|
||||
focus
|
||||
&& focus.intent === "collect_method_evidence"
|
||||
@@ -898,9 +927,17 @@ export function buildMethodFollowupPlan(input: {
|
||||
|| (focus.targetDomain === "horary" && horaryStatus !== "uncovered")
|
||||
),
|
||||
);
|
||||
const staleDiscriminatorFocus = Boolean(
|
||||
focus
|
||||
&& focus.intent === "distinguish_candidates"
|
||||
&& catalogWinnerKey
|
||||
&& persistedFocusProbeKey(focus)
|
||||
&& persistedFocusProbeKey(focus) !== catalogWinnerKey
|
||||
);
|
||||
if (
|
||||
focus
|
||||
&& !staleCollectFocus
|
||||
&& !staleDiscriminatorFocus
|
||||
&& (sessionOutcome !== "adopt_representative"
|
||||
&& sessionOutcome !== "validated_range"
|
||||
&& sessionOutcome !== "exact_minute_confirmed"
|
||||
@@ -1018,18 +1055,6 @@ export function buildMethodFollowupPlan(input: {
|
||||
|
||||
let next: MethodFollowup | null = null;
|
||||
const stage = input.precisionStage ?? null;
|
||||
const askedKeys = new Set([
|
||||
...(input.askedProbeKeys ?? []),
|
||||
...askedKeysFromLedgerEvidence(input.evidence),
|
||||
]);
|
||||
const rankedDiscriminators = dashaCovered && meetsAcceptanceEventQuality(input.evidence)
|
||||
? rankRenderableDiscriminators({
|
||||
eventProbes: remainingConflictProbes(input.eventProbes, input.evidence, declined, askedKeys),
|
||||
contrastProbe: !candidatesSeparated ? contrastProbe : null,
|
||||
askedKeys,
|
||||
})
|
||||
: [];
|
||||
const bestDiscriminator = rankedDiscriminators[0] ?? null;
|
||||
const followupFromRanked = (ranked: RankedDiscriminator): MethodFollowup => {
|
||||
if (ranked.kind === "event" && ranked.eventProbe) {
|
||||
const conflictProbe = ranked.eventProbe;
|
||||
|
||||
Reference in New Issue
Block a user