Files
Jyotisha/frontend/tests/rectification-precision-gate-20260916.test.ts
T
Jesse_ChenandClaude Fable 5.1 dc732825d8 fix(rectification): 有题就接着问,题问完才出卡;引导窗口不再硬贴领域
出卡时机只看题源有没有空:撤回「门槛达标就短路采集线」的写法,同时
按 D2 保住「题源全空就按现行规则出卡」——门槛只在还有题可问时挡住
出卡,precision_gate_met 改成只上报(新挂在决策与公开投影上),不再
单独决定时机。引导窗口题在无领域轨道上改问开放题,一个时间窗只问一
次;录入卡提交的是「YYYY 年 M 月,<领域>方面有一件事」,不再是题干
的三选一列表。记忆化 golden 只补一个新键并冻结墙钟。离线回放改成注
入真值方向的边界事件,另跑一组反方向对照。Skill 10.0.28。

BUG-747~752

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-16 12:16:55 +00:00

200 lines
6.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { RECTIFICATION_POLICY } from "../src/lib/rectification-policy.ts";
import {
decideRectification,
mayDeliverOnPrecision,
publicCanAdopt,
publicNextAction,
sessionOutcomeAllowsDelivery,
} from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { computePrecisionGateMet } from "../src/lib/rectification-agentic/core/precision-gate.ts";
import { RANGE_DELIVERY_TIE_PERCENT } from "../src/lib/rectification-agentic/user-copy.ts";
import { OPEN_ENGINE_CAPABILITY_CEILING } from "./rectification-v9-test-support.ts";
const TIED_FIVE = [
{ time: "04:51", score: 13 },
{ time: "04:53", score: 13 },
{ time: "04:56", score: 13 },
{ time: "04:58", score: 13 },
{ time: "05:11", score: 13 },
] as const;
function input(overrides: Partial<Parameters<typeof decideRectification>[0]> = {}) {
return {
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
methodCoverageAll: true,
trainingGateOpen: true,
candidateScores: TIED_FIVE.map((item) => ({ time: item.time, score: item.score })),
holdoutValidation: "unavailable" as const,
datedEventCount: 5,
datedDomainCount: 3,
discriminatorProbe: null,
targetedCollectExhausted: true,
refreshExhausted: true,
inferenceCredibleRange: ["04:51", "05:11"] as const,
openingCandidateRange: ["04:45", "05:15"] as const,
...overrides,
};
}
test("policy defines deliveryMaxWidthMinutes once", () => {
assert.equal(RECTIFICATION_POLICY.deliveryMaxWidthMinutes, 10);
assert.equal(RANGE_DELIVERY_TIE_PERCENT, 3);
});
test("precision gate requires width, display gap, and no exact tie", () => {
assert.equal(computePrecisionGateMet({
range: ["04:51", "05:11"],
topTwoPercents: [26, 26],
tiedForFirst: true,
}), false);
assert.equal(computePrecisionGateMet({
range: ["04:51", "04:59"],
topTwoPercents: [40, 35],
tiedForFirst: false,
}), true);
assert.equal(computePrecisionGateMet({
range: ["04:51", "04:59"],
topTwoPercents: [26, 26],
tiedForFirst: true,
}), false);
});
test("20 minute tied range keeps collecting with a guided question", () => {
const decision = decideRectification(input({
precisionGateMet: false,
guidedCollectExhausted: false,
}));
assert.equal(decision.nextAction, "ask_fact_collection");
assert.equal(decision.canOfferRange, false);
assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), false);
assert.equal(publicCanAdopt(decision), false);
});
test("8 minute range with a 5 point lead delivers once every line is asked", () => {
const decision = decideRectification(input({
candidateScores: [
{ time: "04:51", score: 20 },
{ time: "04:53", score: 15 },
{ time: "04:56", score: 10 },
],
inferenceCredibleRange: ["04:51", "04:59"],
precisionGateMet: true,
// 原值: guidedCollectExhausted: false
// 新值: guidedCollectExhausted: true
// 原因: D6——门槛只是必要条件,引导线没问完不出卡(BUG-751)
guidedCollectExhausted: true,
targetedCollectExhausted: true,
refreshExhausted: true,
}));
assert.equal(decision.canOfferRange, true);
assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), true);
assert.equal(decision.precisionGateMet, true);
});
test("gate met but the targeted line is still open keeps asking", () => {
const decision = decideRectification(input({
candidateScores: [
{ time: "04:51", score: 20 },
{ time: "04:53", score: 15 },
{ time: "04:56", score: 10 },
],
inferenceCredibleRange: ["04:51", "04:59"],
precisionGateMet: true,
guidedCollectExhausted: false,
targetedCollectExhausted: false,
refreshExhausted: true,
}));
assert.equal(decision.nextAction, "ask_fact_collection");
assert.equal(decision.canOfferRange, false);
assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), false);
});
test("empty guided pool with refresh untried does not deliver", () => {
const decision = decideRectification(input({
candidateScores: [
{ time: "04:51", score: 20 },
{ time: "04:53", score: 15 },
{ time: "04:56", score: 10 },
],
inferenceCredibleRange: ["04:51", "04:59"],
precisionGateMet: true,
guidedCollectExhausted: true,
targetedCollectExhausted: true,
refreshExhausted: false,
}));
assert.equal(decision.nextAction, "ask_fact_collection");
assert.equal(decision.canOfferRange, false);
assert.equal(
mayDeliverOnPrecision({
methodCoverageAll: true,
candidateScores: [],
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
precisionGateMet: true,
guidedCollectExhausted: true,
refreshExhausted: false,
targetedCollectExhausted: true,
}),
false,
);
});
test("8 minute exact tie keeps collecting", () => {
const decision = decideRectification(input({
candidateScores: [
{ time: "04:51", score: 13 },
{ time: "04:53", score: 13 },
],
inferenceCredibleRange: ["04:51", "04:59"],
precisionGateMet: false,
guidedCollectExhausted: false,
}));
assert.equal(decision.nextAction, "ask_fact_collection");
assert.equal(decision.canOfferRange, false);
});
test("every line asked but the gate unmet still delivers under the existing rules", () => {
// D2:题源都空了就是「用户确实补不出来」,卡片、采用按钮、三句正文不变,
// 也不加「未达门槛」标注。D6 只保证在还有题可问时不会提前出卡。
const decision = decideRectification(input({
precisionGateMet: false,
guidedCollectExhausted: true,
}));
assert.equal(decision.canOfferRange, true);
assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), true);
// 门槛值照常上报,不参与出卡判断。
assert.equal(decision.precisionGateMet, false);
});
test("the precision gate is reported on a collect exit too", () => {
const collecting = decideRectification(input({
precisionGateMet: false,
guidedCollectExhausted: false,
}));
assert.equal(collecting.nextAction, "ask_fact_collection");
assert.equal(collecting.precisionGateMet, false);
assert.equal(publicNextAction(collecting).precision_gate_met, false);
const helper = decideRectification(input());
assert.equal(helper.precisionGateMet, null);
});
test("userStopped delivers even when the precision gate is unmet", () => {
const decision = decideRectification(input({
userStopped: true,
precisionGateMet: false,
guidedCollectExhausted: false,
}));
assert.equal(decision.canOfferRange, true);
assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), true);
});
test("omitted precision flags keep the helper path delivering", () => {
assert.equal(mayDeliverOnPrecision({
methodCoverageAll: true,
candidateScores: [],
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
}), true);
});