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,
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { sameCollectDomain } from "../src/lib/rectification-agentic/v9/domain-alias.ts";
import { focusTargetDomain } from "../src/lib/rectification-agentic/v9/server-focus.ts";
// BUG-1142: a distinguish focus wrote the planner domain verbatim. 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 (BUG-672 alias family).
const migration = readFileSync(new URL("../supabase/migrations/20260814020000_rectification_v10_runtime.sql", import.meta.url), "utf8");
const allowedBlock = /target_domain is null or target_domain in \(([^)]*)\)/.exec(migration);
assert.ok(allowedBlock, "focus table target_domain check is readable");
const allowed = new Set([...allowedBlock[1]!.matchAll(/'([a-z_]+)'/g)].map((match) => match[1]!));
// Every domain the planner, engine catalog or collect pool can put on a followup.
const plannerDomains = [
"education", "relocation", "relationship", "career", "finance", "health_pressure", "family",
"appearance", "occupation", "health", "other", "horary", "unknown", "active_focus",
];
test("every followup domain maps to a value the focus table accepts, for every intent", () => {
for (const intent of ["distinguish_candidates", "collect_method_evidence", "reverse_verify", "out_of_sample_check"] as const) {
for (const domain of plannerDomains) {
const stored = focusTargetDomain({ intent, domain } as never);
assert.ok(stored === null || allowed.has(stored), `${intent}/${domain} -> ${stored}`);
}
}
});
test("a health distinguish focus stores health and still matches health_pressure probes", () => {
assert.equal(focusTargetDomain({ intent: "distinguish_candidates", domain: "health_pressure" } as never), "health");
assert.equal(focusTargetDomain({ intent: "distinguish_candidates", domain: "occupation" } as never), "other");
assert.equal(focusTargetDomain({ intent: "distinguish_candidates", domain: "relationship" } as never), "relationship");
assert.equal(sameCollectDomain("health_pressure", "health"), true);
});
test("focus writes and focus-to-probe matching never use a raw planner domain", () => {
const serverFocus = readFileSync(new URL("../src/lib/rectification-agentic/v9/server-focus.ts", import.meta.url), "utf8");
assert.doesNotMatch(serverFocus, /:\s*followup\.domain,\s*\n/, "no raw followup.domain passed as targetDomain");
assert.match(serverFocus, /targetDomain: focusTargetDomain\(followup\)/);
const followup = readFileSync(new URL("../src/lib/rectification-agentic/v9/method-followup.ts", import.meta.url), "utf8");
assert.doesNotMatch(followup, /probe\.domain === focus\.targetDomain/);
});
@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import test from "node:test";
import { segmentOrderEnabledFor, SEGMENT_ORDER_DEFAULT_MAX_WINDOW } from "../src/lib/rectification-agentic/core/segment-probe-order.ts";
// BUG-1143: segment-gain order is on by default only where the 77-case persisted replay lost no truth segment.
test("segment order is on by default for windows up to 21 minutes and off beyond", () => {
assert.equal(SEGMENT_ORDER_DEFAULT_MAX_WINDOW, 21);
assert.equal(segmentOrderEnabledFor(21, undefined), true);
assert.equal(segmentOrderEnabledFor(11, ""), true);
assert.equal(segmentOrderEnabledFor(22, undefined), false);
assert.equal(segmentOrderEnabledFor(31, undefined), false);
assert.equal(segmentOrderEnabledFor(61, undefined), false);
});
test("off disables everywhere; on keeps the research envelope; no scan means off", () => {
assert.equal(segmentOrderEnabledFor(21, "off"), false);
assert.equal(segmentOrderEnabledFor(61, "on"), true);
assert.equal(segmentOrderEnabledFor(121, "on"), false);
assert.equal(segmentOrderEnabledFor(undefined, undefined), false);
assert.equal(segmentOrderEnabledFor(0, "on"), false);
});