fix(rectification): health distinguish focuses persist again; segment-gain order on by default for windows <= 21 minutes (BUG-1142, BUG-1143)

BUG-1142: distinguish focuses wrote the planner domain verbatim, and
health_pressure is not in the focus table's target_domain check. The write
failed inside a swallowed retry and the session went straight to delivery
although tap cards remained. Every focus write now goes through
focusTargetDomain (persistableFocusDomain, BUG-672), and focus-to-probe
matching uses sameCollectDomain.

BUG-1143: segmentOrderEnabledFor turns segment-gain probe order on by default
when the scanned window is at most 21 minutes, where the 77-case persisted
replay gained head hits with no lost truth segment. RECTIFICATION_SEGMENT_ORDER
=off disables it; =on keeps the 61-minute research envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-10-01 11:47:04 +08:00
co-authored by Claude Opus 5.5
parent 04c09cbf4d
commit 564eede340
6 changed files with 100 additions and 8 deletions
@@ -62,3 +62,19 @@ export function orderProbesBySegment(input: {
return input.probes.map((probe, index) => ({ probe, index, gain: segmentInformationGain(probe, weights, input.segments, input.offsets) }))
.sort((a, b) => b.gain - a.gain || a.index - b.index).map((row) => row.probe);
}
/**
* Production default for segment-gain probe order (BUG-1143). The 77-case persisted replay
* (2026-10-01) gained head hits with no lost truth segment at windows <= 21 minutes; at 61
* minutes it lost truth segments, traced to the health-focus write failure (BUG-1142), so the
* default stays at 21 until that replay is rerun. "off" disables; "on" keeps the research
* envelope used by the persisted replay harness.
*/
export const SEGMENT_ORDER_DEFAULT_MAX_WINDOW = 21;
export const SEGMENT_ORDER_RESEARCH_MAX_WINDOW = 61;
export function segmentOrderEnabledFor(windowMinutes: number | null | undefined, flag: string | undefined): boolean {
if (flag === "off" || !windowMinutes || windowMinutes <= 0) return false;
const limit = flag === "on" ? SEGMENT_ORDER_RESEARCH_MAX_WINDOW : SEGMENT_ORDER_DEFAULT_MAX_WINDOW;
return windowMinutes <= limit;
}
@@ -821,14 +821,14 @@ function liveDistinguishProbe(
isValidDistinguishProbe({ ...probe, role: "distinguish" })
&& (schemaKey
? probe.semantic_key === schemaKey
: (!focus.targetDomain || probe.domain === focus.targetDomain))
: (!focus.targetDomain || sameCollectDomain(probe.domain, focus.targetDomain)))
);
const fromEvents = (eventProbes ?? []).find(matchEvent) ?? null;
if (fromEvents) return fromEvents;
const matchContrast = (probe: CandidateDiscriminatorProbe) => (
schemaKey
? probe.semanticKey === schemaKey
: (!focus.targetDomain || probe.domain === focus.targetDomain)
: (!focus.targetDomain || sameCollectDomain(probe.domain, focus.targetDomain))
);
const fromContrast = (contrastProbes ?? []).find(matchContrast);
if (!fromContrast) return null;
@@ -2580,7 +2580,7 @@ export function buildMethodFollowupPlan(input: {
: themeFromDomain ?? (focus.intent === "out_of_sample_check" ? "oos_blind" : "education_style")
) as MethodFollowup["ask_theme"];
const matchingVerify = (input.eventProbes ?? []).find((probe) => (
probe.domain === focus.targetDomain
sameCollectDomain(probe.domain, focus.targetDomain)
|| REVERSE_VERIFY_THEME[probe.domain as keyof typeof REVERSE_VERIFY_THEME] === keepAskTheme
));
const schema = focus.expectedAnswerSchema;
@@ -5,6 +5,7 @@
* write a fresh inference_state, but with an empty answer ledger.
*/
import { shouldForceMinuteAfterSubBlocks } from "./block-scan.ts";
import { segmentOrderEnabledFor } from "../core/segment-probe-order.ts";
import { scanCaseSegments } from "./segment-scan.ts";
import { targetChartsForDomain } from "../core/segment-summary.ts";
import { buildProductCaseInferenceState } from "./product-inference.ts";
@@ -450,7 +451,7 @@ export async function scoreAndPersistCurrentEvidence(input: {
: undefined;
const inference = buildProductCaseInferenceState({
segmentMinutes, segmentTargets, segmentScanComplete: Boolean(segmentMinutes),
segmentOrderEnabled: process.env.RECTIFICATION_SEGMENT_ORDER === "on",
segmentOrderEnabled: segmentOrderEnabledFor(segmentMinutes?.length, process.env.RECTIFICATION_SEGMENT_ORDER),
range: candidateRange,
candidates: score.candidates,
evidence: scorable,
@@ -272,6 +272,20 @@ export function persistableFocusDomain(domain: string | null | undefined): strin
return null;
}
/**
* The domain a focus row may store. Every intent goes through persistableFocusDomain:
* the table only accepts education/career/relationship/relocation/finance/health/family/other,
* while planner domains include health_pressure and occupation (BUG-672). A raw
* health_pressure on a distinguish focus failed the check constraint, the write was
* swallowed and the session ended early (BUG-1142).
*/
export function focusTargetDomain(followup: Pick<MethodFollowup, "intent" | "domain">): string | null {
return persistableFocusDomain(followup.domain)
?? (followup.intent === "collect_method_evidence"
? persistableFocusDomain(collectQuestionDomain(followup.domain))
: null);
}
function clampFocusTargetKind(kind: string | null | undefined): string | null {
const value = kind?.trim() || "";
if (!value) return null;
@@ -672,10 +686,7 @@ async function persistServerOwnedFocusCore(input: {
questionId,
intent: followup.intent,
targetEvidenceId: followup.date_reliability_evidence_id ?? null,
targetDomain: followup.intent === "collect_method_evidence"
? persistableFocusDomain(followup.domain)
?? persistableFocusDomain(collectQuestionDomain(followup.domain))
: followup.domain,
targetDomain: focusTargetDomain(followup),
targetKind: followup.intent === "collect_method_evidence"
? collectFocusTargetKind(followup)
: null,