fix(rectification): stop re-asking covered domains after adopt (BUG-590, BUG-591)
Independent Staging Quality Gate / validate (push) Successful in 11m33s
Independent Staging Quality Gate / publish (push) Successful in 7m49s

Yearless-to-collect now skips domains already confirmed, declined, or asked; adopt-exhausted turns skip leftover collect persist. Collect focus collisions only retry with :next when collect_retry is set.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-08 12:27:40 +08:00
parent 43fd8875ce
commit 7ca6ade244
11 changed files with 613 additions and 36 deletions
@@ -82,12 +82,14 @@ import {
buildNextUserAction,
exhaustionSpokenCollectFollowup,
isRemainingEvidenceCollect,
remainingEvidenceCollectStillOpen,
planWithDateReliability,
spokenCollectFallbackFollowup,
spokenFollowupForUser,
ledgerHasConfirmedDatedEvent,
type MethodCoverage,
type MethodFollowup,
type MethodFollowupEvidence,
type MethodFollowupPlan,
} from "./method-followup";
import { followupCaseArgs, isBlockChoiceSchema, isWidenWindowSchema } from "./block-scan.ts";
@@ -230,6 +232,10 @@ function shouldSkipFollowupPersist(input: {
followup: MethodFollowup | null;
methods?: readonly MethodCoverage[];
accepted?: boolean;
stopReason?: string | null;
sessionOutcome?: string | null;
evidence?: readonly MethodFollowupEvidence[];
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
}): boolean {
if (input.accepted) return false;
if (!input.canAdopt) return false;
@@ -244,7 +250,22 @@ function shouldSkipFollowupPersist(input: {
}
if (input.methods && !blockingMethodsCovered(input.methods)) return false;
if (isRemainingDiscriminatorFollowup(input.followup)) return false;
if (isRemainingEvidenceCollect(input.followup)) return false;
if (isRemainingEvidenceCollect(input.followup)) {
const adoptExhausted = (
input.nextAction === "ready_to_adopt"
|| input.sessionOutcome === "adopt_representative"
) && input.stopReason === "probe_pool_exhausted";
if (
!adoptExhausted
|| remainingEvidenceCollectStillOpen(
input.followup,
input.evidence ?? [],
input.declinedTopics ?? [],
)
) {
return false;
}
}
return true;
}
@@ -791,6 +812,10 @@ export async function persistNextInterviewAfterChoice(input: {
followup,
methods: plan.methods,
accepted: Boolean(input.dossier.case.acceptedTime),
stopReason: decision.stopReason ?? null,
sessionOutcome: input.nextAction.session_outcome,
evidence: input.dossier.evidence,
declinedTopics: input.dossier.conversationSummary.declinedSkippedTopics,
})) {
const facts = adoptDeliveryFacts(decision, liveDossier);
const fallback = adoptHostNarration({
@@ -948,7 +973,12 @@ async function persistFocusAfterChoice(input: {
prompt: null,
};
}
if (persisted.status === "created" || persisted.status === "already_open" || !input.followup) {
if (
persisted.status === "created"
|| persisted.status === "already_open"
|| persisted.status === "duplicate_focus"
|| !input.followup
) {
return persisted;
}
if (persisted.status === "skipped" && !followupHasPersistableDomain(input.followup)) {
@@ -1224,6 +1254,10 @@ export async function persistNextInterviewIfIdle(input: {
followup,
methods: plan.methods,
accepted: Boolean(dossier.case.acceptedTime),
stopReason: decision.stopReason ?? null,
sessionOutcome: decision.sessionOutcome,
evidence: dossier.evidence,
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
})) {
const facts = adoptDeliveryFacts(decision, dossier);
const fallback = adoptHostNarration({
@@ -1292,6 +1292,67 @@ export function isRemainingEvidenceCollect(
&& REMAINING_EVIDENCE_COLLECT_DOMAINS.has(followup.domain);
}
function collectFocusAskedForDomain(
topics: readonly Readonly<Record<string, unknown>>[],
domain: string,
): boolean {
if (domain === "health" || domain === "health_pressure") {
return domainCollectFocusAsked(topics, "health")
|| domainCollectFocusAsked(topics, "health_pressure");
}
return domainCollectFocusAsked(topics, domain);
}
function yearlessCollectDomainBlocked(
domain: string,
evidence: readonly MethodFollowupEvidence[],
declined: ReadonlySet<string>,
askedRows: readonly Readonly<Record<string, unknown>>[],
): boolean {
if (domain === "health" || domain === "health_pressure") {
return declinedHealth(declined)
|| hasConfirmedHealth(evidence)
|| collectFocusAskedForDomain(askedRows, domain);
}
return declined.has(domain)
|| hasConfirmedDomain(evidence, domain)
|| collectFocusAskedForDomain(askedRows, domain);
}
/** Remaining spoken collect that can still add a dated event in an uncovered domain. */
export function remainingEvidenceCollectStillOpen(
followup: Pick<MethodFollowup, "intent" | "domain" | "date_reliability_evidence_id"> | null | undefined,
evidence: readonly MethodFollowupEvidence[] = [],
declinedTopics: readonly Readonly<Record<string, unknown>>[] = [],
): boolean {
if (!isRemainingEvidenceCollect(followup) || !followup?.domain) return false;
const declined = declinedDomains(declinedTopics);
if (followup.domain === "occupation") {
return !hasConfirmedDomain(evidence, "occupation") && !declined.has("occupation");
}
if ((DATED_COLLECT_ORDER as readonly string[]).includes(followup.domain)) {
return !datedCollectDomainBlocked(
followup.domain as (typeof DATED_COLLECT_ORDER)[number],
evidence,
declined,
);
}
return true;
}
function holdoutOccupiedDomains(evidence: readonly MethodFollowupEvidence[]): Set<string> {
const occupied = new Set(
evidence
.filter((item) => isConfirmedDated(item) && evidenceYear(item) != null)
.map((item) => item.domain),
);
if (hasConfirmedHealth(evidence) || occupied.has("health") || occupied.has("health_pressure")) {
occupied.add("health");
occupied.add("health_pressure");
}
return occupied;
}
export function exhaustionSpokenCollectFollowup(input: {
evidence: readonly MethodFollowupEvidence[];
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
@@ -1682,17 +1743,22 @@ export function holdoutFollowupFor(
declined: ReadonlySet<string>,
): Omit<MethodFollowup, "must_not_label" | "choice_frame"> | null {
if (!meetsAcceptanceEventQuality(input.evidence)) return null;
const occupied = new Set(
input.evidence
.filter((item) => isConfirmedDated(item) && evidenceYear(item) != null)
.map((item) => item.domain),
);
const prompt = (input.oosBlindPrompts ?? []).find((item) => (
item.domain && !declined.has(item.domain) && !occupied.has(item.domain)
)) ?? null;
const reserved = (input.holdoutEvents ?? []).find((item) => (
item.year != null && !declined.has(item.domain) && !occupied.has(item.domain)
)) ?? null;
const occupied = holdoutOccupiedDomains(input.evidence);
const healthDeclined = declinedHealth(declined);
const prompt = (input.oosBlindPrompts ?? []).find((item) => {
if (!item.domain || occupied.has(item.domain)) return false;
if (item.domain === "health" || item.domain === "health_pressure") {
return !healthDeclined;
}
return !declined.has(item.domain);
}) ?? null;
const reserved = (input.holdoutEvents ?? []).find((item) => {
if (item.year == null || occupied.has(item.domain)) return false;
if (item.domain === "health" || item.domain === "health_pressure") {
return !healthDeclined;
}
return !declined.has(item.domain);
}) ?? null;
return holdoutAskFields(prompt, reserved);
}
@@ -2223,23 +2289,28 @@ export function buildMethodFollowupPlan(input: {
}
if (!next && dashaCovered && coverageComplete && occupationCovered) {
const allowLowGainDiscriminator = !coverageComplete || !candidatesSeparated;
const yearless = yearlessDiscriminators[0];
if (yearless && (allowLowGainDiscriminator || yearless.score >= 0.08)) {
const askedRows = [
...(input.closedCollectFocuses ?? []),
...(input.declinedTopics ?? []),
];
for (const yearless of yearlessDiscriminators) {
if (!allowLowGainDiscriminator && yearless.score < 0.08) continue;
const domain = contrastFollowupDomain(
yearless.eventProbe?.domain ?? yearless.contrastProbe?.domain ?? null,
);
const lead = YEARLESS_COLLECT_LEAD[domain];
if (lead && !declined.has(domain)) {
next = makeFollowup({
method_id: PROBE_METHOD_ID[domain],
intent: "collect_method_evidence",
ask_theme: REVERSE_VERIFY_THEME[domain],
domain,
kind_hint: REVERSE_VERIFY_KIND[domain],
user_prompt_hint: collect(lead, REVERSE_VERIFY_VARGA[domain]),
source: "method_coverage",
});
}
if (!lead) continue;
if (yearlessCollectDomainBlocked(domain, input.evidence, declined, askedRows)) continue;
next = makeFollowup({
method_id: PROBE_METHOD_ID[domain],
intent: "collect_method_evidence",
ask_theme: REVERSE_VERIFY_THEME[domain],
domain,
kind_hint: REVERSE_VERIFY_KIND[domain],
user_prompt_hint: collect(lead, REVERSE_VERIFY_VARGA[domain]),
source: "method_coverage",
});
break;
}
}
if (
@@ -356,6 +356,14 @@ async function persistCollectFocus(input: {
prompt: null,
};
}
if (input.followup.collect_retry !== true) {
return {
status: "duplicate_focus",
focus: input.activeFocus,
questionId,
prompt,
};
}
const retryId = `${questionId}:${COLLECT_FOCUS_RETRY_SUFFIX}`.slice(0, 160);
if (retryId === questionId) {
return {