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:
co-authored by
Claude Opus 5.5
parent
04c09cbf4d
commit
564eede340
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user