fix(rectification): skip covered-domain existence probes, then pick max gain
Independent Staging Quality Gate / validate (push) Successful in 9m42s
Independent Staging Quality Gate / publish (push) Failing after 8m9s

Dated evidence in a domain no longer yields another existence question
in that domain. Remaining varga discriminators all stay in the pool so
the next card is whichever unused split scores highest.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-28 09:59:45 +08:00
parent ca6252ecb6
commit 4767b34ff0
8 changed files with 205 additions and 33 deletions
@@ -251,6 +251,24 @@ export function askedEventProbeKeysFromLedgerEvidence(
return [...keys];
}
export function datedDomainsFromEvidence(
evidence: readonly Readonly<{
status?: string | null;
domain?: string | null;
occurredFrom?: string | null;
occurredTo?: string | null;
}>[],
): string[] {
const domains = new Set<string>();
for (const item of evidence) {
if (item.status && !LIVE_EVIDENCE.has(item.status)) continue;
if (!item.domain) continue;
const dated = [item.occurredFrom, item.occurredTo].some((value) => /^\d{4}/.test(value ?? ""));
if (dated) domains.add(item.domain);
}
return [...domains];
}
export function volunteeredDomainsFromEvidence(
evidence: readonly Readonly<{
status?: string | null;
@@ -286,18 +304,21 @@ export function buildCandidateContrastPacket(input: {
transitions?: readonly WindowScanTransition[];
askedKeys?: readonly string[];
volunteeredDomains?: readonly string[];
providedDomains?: readonly string[];
}): CandidateContrastPacket {
const asked = new Set(input.askedKeys ?? []);
const provided = new Set(input.providedDomains ?? []);
const fromEngine = (input.engineProbes ?? []).flatMap((probe) => {
const built = probeFromEngine(probe, input.candidateSetVersion, input.calculationResultId ?? null);
if (!built) return [];
const eventKey = built.domain && built.year ? `${built.domain}.${built.year}` : null;
const isStructured = built.choiceKind === "varga_style" || built.semanticKey.startsWith("varga.");
const isStructured = isStructuredDiscriminator(built);
if (
asked.has(built.semanticKey)
|| asked.has(built.candidateSplitHash)
|| asked.has(built.probeId)
|| (!isStructured && eventKey && asked.has(eventKey))
|| (!isStructured && built.domain && provided.has(built.domain))
) {
return [];
}
@@ -316,13 +337,13 @@ export function buildCandidateContrastPacket(input: {
transitions: input.transitions ?? [],
});
const presentKeys = new Set(fromEngine.map((item) => item.semanticKey));
const fromVarga = vargaProbe(
const fromVarga = vargaProbes(
remainingSplits,
input.candidateSetVersion,
input.calculationResultId ?? null,
new Set([...asked, ...presentKeys]),
);
const probes = [...fromEngine, ...(fromVarga ? [fromVarga] : [])]
const probes = [...fromEngine, ...fromVarga]
.sort((left, right) => right.informationGain - left.informationGain);
return {
candidateSetVersion: input.candidateSetVersion,
@@ -596,15 +617,23 @@ function styleOptionsFromEngine(
return parsed.length > 0 ? parsed : undefined;
}
function vargaProbe(
export function isStructuredDiscriminator(probe: Pick<CandidateDiscriminatorProbe, "choiceKind" | "semanticKey">): boolean {
return probe.choiceKind === "varga_style"
|| probe.choiceKind === "event_quality"
|| probe.semanticKey.startsWith("varga.");
}
function vargaProbes(
remainingSplits: readonly RemainingVargaSplit[],
candidateSetVersion: string,
calculationResultId: string | null,
asked: ReadonlySet<string>,
): CandidateDiscriminatorProbe | null {
const remaining = remainingSplits.find((item) => !vargaLayerAsked(asked, item.layer));
if (!remaining) return null;
return vargaProbeFromRemaining(remaining, candidateSetVersion, calculationResultId);
): CandidateDiscriminatorProbe[] {
return remainingSplits.flatMap((item) => {
if (vargaLayerAsked(asked, item.layer)) return [];
const probe = vargaProbeFromRemaining(item, candidateSetVersion, calculationResultId);
return probe ? [probe] : [];
});
}
function vargaLayerAsked(asked: ReadonlySet<string>, layer: string): boolean {
@@ -7,6 +7,7 @@
import {
buildCandidateContrastPacket,
datedDomainsFromEvidence,
selectDiscriminatorProbe,
volunteeredDomainsFromEvidence,
type CandidateContrastPacket,
@@ -175,6 +176,7 @@ export function contrastPacketFromLatestResult(
transitions: windowScan?.transitions ?? [],
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
providedDomains: datedDomainsFromEvidence(evidence),
});
}
@@ -26,10 +26,11 @@
* Method coverage asks for dated events in natural language.
* Known-event quality probes (exam went badly for a year already
* in the ledger) stamp a choice card as soon as that year is
* recorded. Dasha conflict probes wait until training event
* quality (3 training events in 2 training domains; holdout
* excluded), then jump ahead of remaining method rotation and
* block offering time cards so the window can be filtered.
* recorded. Dasha existence probes skip a domain once that domain
* already has dated evidence. Remaining chart discriminators
* (D9/D10/D24 and other varga splits) stay in the pool and the
* highest information-gain renderable probe is asked next, with
* no preferred domain.
* If holdout is already reserved but training is still short,
* keep collecting a dated event instead of discriminating.
* Once blocking methods are covered, move into candidate discrimination.
@@ -52,6 +53,8 @@ import { overlayChoicePromptFromSpoken } from "./turn-narration.ts";
import { decideRectification, type HoldoutValidationStatus } from "../core/rectification-decision.ts";
import {
askedKeysFromLedgerEvidence,
datedDomainsFromEvidence,
isStructuredDiscriminator,
selectDiscriminatorProbe,
type CandidateContrastPacket,
type CandidateDiscriminatorProbe,
@@ -203,6 +206,19 @@ function existenceNearbyYears(domain: string): number {
return domain === "education" ? EDUCATION_EXISTENCE_NEARBY_YEARS : 0;
}
function probeDomainAlreadyCovered(
evidence: readonly MethodFollowupEvidence[],
domain: string,
): boolean {
return evidence.some((item) => {
if (item.status !== "confirmed" && item.status !== "draft" && item.status !== "pending_confirmation") {
return false;
}
if (item.domain !== domain) return false;
return evidenceYear(item) !== null;
});
}
function probeYearAlreadyCovered(
evidence: readonly MethodFollowupEvidence[],
domain: string,
@@ -362,7 +378,7 @@ function remainingConflictProbes(
if (probe.source === "known_event_quality" || probe.role === "clarify") continue;
if (!isValidDistinguishProbe({ ...probe, role: "distinguish" })) continue;
if (declined.has(probe.domain)) continue;
if (probeYearAlreadyCovered(evidence, probe.domain, probe.year)) continue;
if (probeDomainAlreadyCovered(evidence, probe.domain)) continue;
const semantic = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
const split = probe.candidate_split_hash ?? "";
if (askedKeys.has(semantic) || (split && askedKeys.has(split))) continue;
@@ -516,8 +532,10 @@ function rankRenderableDiscriminators(input: {
contrastProbes: readonly CandidateDiscriminatorProbe[];
askedKeys: ReadonlySet<string>;
topCandidateTimes?: readonly string[];
providedDomains?: readonly string[];
}): RankedDiscriminator[] {
const top = input.topCandidateTimes ?? [];
const provided = new Set(input.providedDomains ?? []);
const rows: RankedDiscriminator[] = [];
const seen = new Set<string>();
const push = (row: RankedDiscriminator | null) => {
@@ -533,6 +551,7 @@ function rankRenderableDiscriminators(input: {
push(renderableEventProbe(probe, input.askedKeys, top));
}
for (const probe of input.contrastProbes) {
if (!isStructuredDiscriminator(probe) && probe.domain && provided.has(probe.domain)) continue;
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));
@@ -912,6 +931,7 @@ export function buildMethodFollowupPlan(input: {
contrastProbes,
askedKeys,
topCandidateTimes: input.topCandidateTimes,
providedDomains: datedDomainsFromEvidence(input.evidence),
})
: [];
const bestDiscriminator = rankedDiscriminators[0] ?? null;
@@ -99,6 +99,7 @@ import {
import {
buildCandidateContrastPacket,
conflictProbesFromContrast,
datedDomainsFromEvidence,
selectDiscriminatorProbe,
volunteeredDomainsFromEvidence,
} from "@/lib/rectification-agentic/core/candidate-contrast-packet";
@@ -1002,6 +1003,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
parsed.evidence,
),
volunteeredDomains: volunteeredDomainsFromEvidence(parsed.evidence),
providedDomains: datedDomainsFromEvidence(parsed.evidence),
});
const inference = buildCaseInferenceState({
range: parsed.case.candidateRange,