Files
Jyotisha/frontend/tests/rectification-tie-break-entry-20260913.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

204 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { applyProbeOutcome } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts";
import type { ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts";
import { isTieBreakRoundSchema } from "../src/lib/rectification-agentic/v9/choice-card.ts";
import { rangeDeliveryForSnapshot } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import {
buildMethodFollowupPlan,
tieBreakPersonalityAvailable,
tieBreakPersonalityFollowup,
} from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { expectedAnswerSchemaFor, stableFollowupQuestionId } from "../src/lib/rectification-agentic/v9/server-focus.ts";
const TIMES = ["04:48", "04:53", "05:06", "05:07"] as const;
const LEDGER = [
{
status: "confirmed",
domain: "education",
datePrecision: "month",
occurredFrom: "2016-09-01",
occurredTo: "2016-09-30",
eventKind: "education_start",
summary: "上大学",
},
{
status: "confirmed",
domain: "career",
datePrecision: "month",
occurredFrom: "2020-04-01",
occurredTo: null,
eventKind: "career_entry",
summary: "入职实习",
},
{
status: "confirmed",
domain: "relationship",
datePrecision: "month",
occurredFrom: "2024-05-01",
occurredTo: null,
eventKind: "relationship_start",
summary: "确定关系",
},
] as const;
function contrastProbe(layer: "d9" | "d10") {
const signs = layer === "d9" ? ["巨蟹座", "狮子座"] as const : ["狮子座", "处女座"];
return {
probeId: `contrast:varga.${layer}.${signs[0]}/${signs[1]}`,
candidateSetVersion: "04:48-05:07",
question: layer === "d9"
? "亲密关系里更接近下面哪一种相处方式?"
: "平时做事,你更接近下面哪一种?",
expectedOutcomes: [
{ outcomeId: "yes", supportsCandidateIds: ["04:53"], conflictsCandidateIds: ["05:06"] },
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:06"], conflictsCandidateIds: ["04:53"] },
],
candidateSplitHash: `varga.${layer}.${signs[0]}/${signs[1]}`,
informationGain: layer === "d9" ? 1.4 : 0.9,
sourceFeatures: [{ technique: layer.toUpperCase(), calculationResultId: null }],
domain: layer === "d9" ? "relationship" : "career",
year: null,
semanticKey: `varga.${layer}.${signs[0]}/${signs[1]}`,
choiceKind: "varga_style" as const,
styleOptions: [
{ label: "相处里更在意照顾对方的感受", answerClass: "yes" as const, sign: signs[0] },
{ label: "习惯带头,也不排斥站到台前", answerClass: "weak_yes" as const, sign: signs[1] },
],
};
}
const D9 = contrastProbe("d9");
const D10 = contrastProbe("d10");
function planInput(askedProbeKeys: readonly string[] = []) {
return {
evidence: LEDGER,
declinedTopics: [],
sessionOutcome: "discriminate_candidates" as const,
candidatesSeparated: false,
remainingLayers: ["d9", "d10"],
remainingSplitTimes: ["04:48", "05:07"] as const,
remainingCandidateCount: 4,
topCandidateTimes: [...TIMES],
askedProbeKeys,
contrastPacket: {
candidateSetVersion: "04:48-05:07",
vargaDifferences: [],
probes: [D9, D10],
},
};
}
test("GET availability matches unasked D9/D10 varga_style, not window_scan", () => {
const open = planInput();
// 原值: 主 followup 无 choice_kind
// 新值: 未覆盖领域会先出定向存在性题
// 原因: D5 七条线都会问到;性格题仍是卡下可选入口
const followup = buildMethodFollowupPlan(open).next_followup;
if (followup?.intent === "collect_method_evidence") {
assert.equal(followup.choice_kind, "existence");
}
assert.equal(tieBreakPersonalityAvailable(open), true);
assert.match(tieBreakPersonalityFollowup(open)?.semantic_key ?? "", /varga\.d9/);
assert.equal(rangeDeliveryForSnapshot({
window_scan: { d9_candidates_differ: true, d10_candidates_differ: true },
tieBreakAvailable: tieBreakPersonalityAvailable(open),
}).tie_break_available, true);
const afterD9 = planInput([D9.semanticKey]);
const second = tieBreakPersonalityFollowup(afterD9);
assert.match(second?.semantic_key ?? "", /varga\.d10/);
assert.equal(second?.tie_break_round, true);
assert.equal(tieBreakPersonalityAvailable(afterD9), true);
const bothAsked = planInput([D9.semanticKey, D10.semanticKey]);
assert.equal(tieBreakPersonalityFollowup(bothAsked), null);
assert.equal(tieBreakPersonalityAvailable(bothAsked), false);
assert.equal(rangeDeliveryForSnapshot({
window_scan: { d9_candidates_differ: true, d10_candidates_differ: true },
tieBreakAvailable: tieBreakPersonalityAvailable(bothAsked),
}).tie_break_available, false);
assert.equal(tieBreakPersonalityAvailable({ ...open, accepted: true }), false);
});
test("one click stamps a round schema; scoring stays tie_break without elimination", () => {
const followup = tieBreakPersonalityFollowup(planInput());
assert.ok(followup?.choice_frame);
const schema = expectedAnswerSchemaFor(
followup.choice_frame,
stableFollowupQuestionId(followup),
null,
followup,
);
assert.equal(schema?.tie_break_round, true);
assert.equal(isTieBreakRoundSchema(schema), true);
assert.equal(isTieBreakRoundSchema({ choice_kind: "varga_style" }), false);
const d10: ConflictProbe = {
id: "probe:varga.d10.狮子座/处女座",
semantic_key: "varga.d10.狮子座/处女座",
candidate_split_hash: "04:48-05:07:varga.d10",
domain: "career",
year: 0,
question: "平时做事,你更接近下面哪一种?",
candidate_ids: [...TIMES],
expected_outcomes: [
{ answer_class: "yes", supports: ["04:53"], conflicts: ["05:06"] },
{ answer_class: "weak_yes", supports: ["05:06"], conflicts: ["04:53"] },
{ answer_class: "no", supports: [], conflicts: [] },
{ answer_class: "unsure", supports: [], conflicts: [] },
],
information_gain: 0.9,
source: "varga_contrast",
choice_kind: "varga_style",
style_options: [
{ label: "相处里更在意照顾对方的感受", answer_class: "yes", sign: "巨蟹座" },
{ label: "习惯带头,也不排斥站到台前", answer_class: "weak_yes", sign: "狮子座" },
],
};
const scored = applyProbeOutcome(
{ "04:48": 10, "04:53": 12, "05:06": 11, "05:07": 10 },
d10,
"yes",
);
assert.equal(scored.kind, "tie_break");
assert.equal(scored.deltas["04:53"], 1);
assert.equal(scored.eliminated_ids.length, 0);
});
test("click path does not surface 409 copy; round continue is opt-in only", () => {
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
const answer = readFileSync(new URL("../src/lib/rectification-agentic/v9/answer-choice.ts", import.meta.url), "utf8");
const panel = readFileSync(new URL("../src/lib/rectification-agentic/v9/divergence-panel.ts", import.meta.url), "utf8");
const pool = readFileSync(new URL("../src/lib/rectification-agentic/v9/collection-question-pool.ts", import.meta.url), "utf8");
const route = readFileSync(
new URL("../src/app/api/rectification/cases/[caseId]/tie-break/route.ts", import.meta.url),
"utf8",
);
// 原值: 客户端 requestTieBreak 把 tie_break_unavailable 当静默刷新,不 setError
// 新值: 卡上入口已删,客户端不再有这条 click path409 口径留在 API
// 原因: BUG-706
assert.doesNotMatch(chat, /requestTieBreak/);
assert.doesNotMatch(chat, /tie_break_unavailable/);
assert.match(route, /tie_break_unavailable/);
assert.match(answer, /continueTieBreakRound\?: boolean/);
assert.match(answer, /continueTieBreakRound === true/);
assert.match(answer, /isTieBreakRoundSchema/);
assert.doesNotMatch(panel, /d9_candidates_differ[\s\S]{0,80}tieBreakAvailable/);
assert.doesNotMatch(pool, /export function isTargetedCollectDeclined/);
assert.doesNotMatch(pool, /export function isTargetedCollectYearFollowup/);
});
test("Skill 10.0.26 lists the fourth targeted-collect skip option", () => {
const skill = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url), "utf8");
const strategy = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/references/conversation-strategy.md", import.meta.url), "utf8");
// 原值: /^version: 10\.0\.27$/ / 新值: /^version: 10\.0\.28$/ / 原因: D6 改写出卡句后 Skill bump
assert.match(skill, /^version: 10\.0\.28$/m);
assert.match(skill, /已拒绝(没有发生过 \/ 这类事都没有过)的目标不得换词重问;跳过的按服务器计划最多重问一次/);
assert.match(strategy, /跳过的线按服务器计划最多换一种问法再问一次/);
});