Files
Jyotisha/frontend/tests/rectification-confirmation-gate.test.ts
T
Jesse_ChenandClaude Opus 5.5 4e6e8d87ef feat(rectification): 少问几道就出卡,交付卡以范围为主(D1–D4)
- D1 引导窗口题每个校正最多 2 道(前端 GUIDED_WINDOW_CASE_LIMIT;引擎
  GUIDED_COLLECT_LIMIT 未动:event_probes.py 属冻结评分身份,改它需重新冻结)
- D2 七条定向线与跳过线重问问完即出卡,没问到的引导窗口不再挡卡,出卡后也不再挂窗口题
- D3 卡头加副标题「最可能 HH:MM」
- D4 前两列相差 ≥5 个百分点才显示相对可能性,否则一句「这几个时刻目前区分不开……」
- 离线回放 scripts/research/fewer_probes_card_replay.py:真值不降、宽度中位 ±1 分钟、提问 11.4→7.8
- Skill 10.0.29 → 10.0.30;DESIGN / VOICE / CHANGELOG / PROGRESS / 真机清单

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 14:27:12 +08:00

490 lines
20 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 { indistinguishableWidthMinutes } from "../src/lib/rectification-agentic/v9/candidate-plateau.ts";
import {
SEALED_MINUTE_HOLDOUT,
buildConfirmationGate,
readVedastroMinuteSensitiveStatus,
sessionOutcomeFromGate,
uniqueMinutePath,
} from "../src/lib/rectification-agentic/v9/confirmation-gate.ts";
import { RECTIFICATION_POLICY } from "../src/lib/rectification-policy.ts";
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.ts";
import { decideRectification, publicDecisionFields } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import {
createRectificationV9Tools,
latestResultToolProjection,
} from "../src/mastra/rectification-v9-tools.ts";
import { PUBLIC_RECTIFICATION_TOOLS } from "../src/lib/rectification-agentic/v9/public-receipt.ts";
import { safeToolErrorCode, RectificationToolServiceError } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import {
OPEN_ENGINE_CAPABILITY_CEILING,
CASE_ID,
CANDIDATE_ID,
RESULT_ID,
SECOND_CANDIDATE_ID,
TURN_ID,
USER_ID,
candidateSnapshotFixture,
dossierFixture,
fakeAccounting,
receiptHandlers,
} from "./rectification-v9-test-support.ts";
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
const holdoutReport = JSON.parse(readFileSync(
new URL("../../references/real_case_calibration/minute_rectification_holdout_v3_report.json", import.meta.url),
"utf8",
)) as {
benchmark_id: string;
evaluated_on: string;
validation: { valid_public_aa_cases: number; minimum_public_aa_cases: number };
frozen_scoring: {
algorithm_version: string;
implementation_hash_matches: boolean;
implementation_sha256: string;
};
release_metrics: {
top_1_rate: { actual: number };
top_3_rate: { actual: number };
mean_absolute_minute_error: { actual: number };
confirmation_coverage_rate: number;
};
};
const diagnosticReport = JSON.parse(readFileSync(
new URL(
"../../references/real_case_calibration/minute_rectification_holdout_v3_post_audit_diagnostic_report.json",
import.meta.url,
),
"utf8",
)) as {
trial_count: number;
is_blind_evaluation: boolean;
must_not_claim_release_gate_passed: boolean;
must_not_use_for_tuning: boolean;
frozen_scoring: {
implementation_hash_matches: boolean;
actual_sha256: string;
};
current_tree_unfrozen_diagnostic: {
actual_sha256: string;
trial_count: number;
metrics: {
top_1_rate: number;
top_3_rate: number;
mean_absolute_minute_error: number;
confirmation_coverage_rate: number;
};
metric_gates_passed: boolean;
verified_minute_claim_allowed: boolean;
};
};
// 原值:docs/research/sealed_holdout_rerun_cross_midnight_2026_09_20.json
// 新值:docs/research/sealed_holdout_rerun_midnight_anchor_2026_09_21.json
// 原因:午夜日期锚点修复的新冻结重跑另存报告;旧报告保留,确认门行为与阈值不变。
const currentTreeRerun = JSON.parse(readFileSync(
new URL("../../docs/research/sealed_holdout_rerun_midnight_anchor_2026_09_21.json", import.meta.url),
"utf8",
)) as {
frozen_record: { implementation_sha256: string };
official_valid_independent_blind: boolean;
};
type ProductHoldoutContract = typeof SEALED_MINUTE_HOLDOUT & {
metrics_produced_by: {
implementation_sha256: string;
algorithm_version: string;
implementation_hash_matches_at_replay: boolean;
source_report: string;
evaluated_on: string;
};
current_tree_scorer: {
implementation_sha256: string;
matches_metrics_scorer: boolean;
official_eval_implementation_hash_matches: boolean;
official_eval_trial_count: number;
source_report: string;
};
current_tree_unfrozen_diagnostic: {
is_blind_evaluation: boolean;
results_already_seen: boolean;
scorer_frozen: boolean;
source_audit_passed: boolean;
must_not_claim_as_release_metrics: boolean;
must_not_use_for_tuning: boolean;
report_path: string;
implementation_sha256: string;
trial_count: number;
top_1_rate: number;
top_3_rate: number;
mean_absolute_minute_error: number;
confirmation_coverage_rate: number;
metric_gates_passed: boolean;
verified_minute_claim_allowed: boolean;
};
};
const skill = readFileSync(
new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url),
"utf8",
);
const candidateComparison = readFileSync(
new URL("../../skills/jyotish-birth-time-rectification/references/candidate-comparison.md", import.meta.url),
"utf8",
);
const agentSource = readFileSync(
new URL("../src/mastra/agentic-rectification.ts", import.meta.url),
"utf8",
);
const UNIQUE_MINUTE = [
{ candidateId: CANDIDATE_ID, time: "05:02", rank: 1, relativeSupport: 70, tiedMinuteCount: 1 },
{ candidateId: SECOND_CANDIDATE_ID, time: "05:03", rank: 2, relativeSupport: 30, tiedMinuteCount: 2 },
];
const PLATEAU = [
{ candidateId: CANDIDATE_ID, time: "04:45", rank: 1, relativeSupport: 40, tiedMinuteCount: 25 },
{ candidateId: SECOND_CANDIDATE_ID, time: "04:46", rank: 2, relativeSupport: 35, tiedMinuteCount: 25 },
{ candidateId: THIRD_CANDIDATE_ID, time: "04:47", rank: 3, relativeSupport: 25, tiedMinuteCount: 25 },
];
function collectDecision(candidates: readonly { time: string; relativeSupport: number }[]) {
return decideRectification({
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
methodCoverageAll: false,
trainingGateOpen: false,
candidateScores: candidates.map((item) => ({ time: item.time, score: item.relativeSupport })),
});
}
function blocker(
gate: ReturnType<typeof buildConfirmationGate>,
id: string,
) {
const found = gate.blockers.find((item) => item.id === id);
assert.ok(found, `missing blocker ${id}`);
return found;
}
test("sealed holdout aggregates match the v3 report and still block confirmation", () => {
const productHoldout = JSON.parse(readFileSync(
new URL("../../references/rectification_sealed_holdout.v1.json", import.meta.url),
"utf8",
)) as ProductHoldoutContract;
const sidecar = diagnosticReport.current_tree_unfrozen_diagnostic;
assert.equal(SEALED_MINUTE_HOLDOUT.sealed_benchmark_id, holdoutReport.benchmark_id);
assert.equal(SEALED_MINUTE_HOLDOUT.valid_public_aa_cases, holdoutReport.validation.valid_public_aa_cases);
assert.equal(SEALED_MINUTE_HOLDOUT.required_cases, holdoutReport.validation.minimum_public_aa_cases);
assert.equal(SEALED_MINUTE_HOLDOUT.top_1_rate, holdoutReport.release_metrics.top_1_rate.actual);
assert.equal(
SEALED_MINUTE_HOLDOUT.confirmation_coverage_rate,
holdoutReport.release_metrics.confirmation_coverage_rate,
);
assert.equal(SEALED_MINUTE_HOLDOUT.valid_public_aa_cases, productHoldout.valid_public_aa_cases);
assert.equal(SEALED_MINUTE_HOLDOUT.status, productHoldout.status);
assert.ok(SEALED_MINUTE_HOLDOUT.valid_public_aa_cases >= SEALED_MINUTE_HOLDOUT.required_cases);
assert.equal(SEALED_MINUTE_HOLDOUT.status, "not_ready");
assert.equal(SEALED_MINUTE_HOLDOUT.confirmation_coverage_rate, 0);
assert.equal(SEALED_MINUTE_HOLDOUT.top_1_rate, productHoldout.top_1_rate);
assert.notEqual(SEALED_MINUTE_HOLDOUT.top_1_rate, sidecar.metrics.top_1_rate);
assert.equal(
productHoldout.metrics_produced_by.implementation_sha256,
holdoutReport.frozen_scoring.implementation_sha256,
);
assert.equal(
productHoldout.metrics_produced_by.algorithm_version,
holdoutReport.frozen_scoring.algorithm_version,
);
assert.equal(productHoldout.metrics_produced_by.implementation_hash_matches_at_replay, true);
assert.equal(
productHoldout.metrics_produced_by.source_report,
"references/real_case_calibration/minute_rectification_holdout_v3_report.json",
);
// 原值: 历史 sidecar 哈希 / 新值: 当前冻结重跑哈希 / 原因: BUG-978 身份新鲜度,不改变确认门。
assert.equal(
productHoldout.current_tree_scorer.implementation_sha256,
currentTreeRerun.frozen_record.implementation_sha256,
);
assert.equal(
productHoldout.current_tree_scorer.source_report,
"docs/research/sealed_holdout_rerun_midnight_anchor_2026_09_21.json",
);
assert.equal(currentTreeRerun.official_valid_independent_blind, false);
assert.equal(productHoldout.current_tree_scorer.matches_metrics_scorer, false);
assert.equal(productHoldout.current_tree_scorer.official_eval_implementation_hash_matches, false);
assert.equal(productHoldout.current_tree_scorer.official_eval_trial_count, diagnosticReport.trial_count);
assert.equal(diagnosticReport.trial_count, 0);
assert.notEqual(
productHoldout.current_tree_scorer.implementation_sha256,
productHoldout.metrics_produced_by.implementation_sha256,
);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.is_blind_evaluation, false);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.results_already_seen, true);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.scorer_frozen, false);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.source_audit_passed, false);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.must_not_claim_as_release_metrics, true);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.must_not_use_for_tuning, true);
assert.equal(
productHoldout.current_tree_unfrozen_diagnostic.report_path,
"references/real_case_calibration/minute_rectification_holdout_v3_post_audit_diagnostic_report.json",
);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.top_1_rate, sidecar.metrics.top_1_rate);
assert.equal(productHoldout.current_tree_unfrozen_diagnostic.top_3_rate, sidecar.metrics.top_3_rate);
assert.equal(
productHoldout.current_tree_unfrozen_diagnostic.mean_absolute_minute_error,
sidecar.metrics.mean_absolute_minute_error,
);
const gate = buildConfirmationGate({
engineConfirmationAllowed: true,
candidates: UNIQUE_MINUTE,
decisionReceipt: {
gates: { exact_confirmation: { external_validation_status: "passed" } },
},
});
assert.equal(gate.confirmation_allowed, false);
assert.equal(blocker(gate, "public_aa_holdout").status, "not_ready");
});
test("VedAstro not_evaluated is not described as fail and still forbids confirmation", () => {
const gate = buildConfirmationGate({
engineConfirmationAllowed: true,
candidates: UNIQUE_MINUTE,
decisionReceipt: {
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
},
});
const vedastro = blocker(gate, "vedastro_minute_sensitive");
const adjacent = blocker(gate, "adjacent_minutes_indistinguishable");
const holdout = blocker(gate, "public_aa_holdout");
assert.equal(readVedastroMinuteSensitiveStatus({
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
}), "not_evaluated");
assert.equal(vedastro.status, "not_evaluated");
assert.notEqual(vedastro.status, "fail");
assert.notEqual(vedastro.status, "failed");
assert.match(vedastro.user_meaning, /尚未执行/);
assert.doesNotMatch(vedastro.user_meaning, /执行失败|校验失败/);
assert.equal(adjacent.status, "passed");
assert.equal(holdout.status, "not_ready");
assert.equal(gate.confirmation_allowed, false);
assert.equal(gate.unique_minute_path, "closed_at_representative");
assert.match(holdout.user_meaning, /未达发布门槛/);
assert.match(holdout.user_meaning, /不能确认唯一分钟/);
assert.equal(uniqueMinutePath(false), "closed_at_representative");
assert.equal(uniqueMinutePath(true), "awaiting_user_consent");
});
test("a 25-minute plateau stays confirmation-blocked as an indistinguishable range", () => {
const projection = latestResultToolProjection({
resultId: RESULT_ID,
candidates: PLATEAU,
selectionAllowed: true,
confirmationAllowed: true,
representativeTime: "04:45",
selectedTime: null,
selectionKind: null,
algorithmVersion: "rectification-v5",
}, collectDecision(PLATEAU));
const gate = projection.confirmation_gate as ReturnType<typeof buildConfirmationGate>;
assert.ok(Number(projection.engine_indistinguishable_width_minutes) >= 25);
assert.equal(projection.confirmation_allowed, false);
assert.equal(gate.confirmation_allowed, false);
assert.equal(blocker(gate, "adjacent_minutes_indistinguishable").status, "blocked");
assert.equal(
blocker(gate, "adjacent_minutes_indistinguishable").max_confirmation_width_minutes,
RECTIFICATION_POLICY.maxConfirmationWidthMinutes,
);
assert.match(blocker(gate, "adjacent_minutes_indistinguishable").user_meaning, /不可分区间|代表性候选/);
assert.equal(projection.session_outcome, "collect_evidence");
assert.equal(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
}).kind, "collect_evidence");
assert.equal(sessionOutcomeFromGate({
selectionAllowed: true,
proposeAllowed: true,
confirmationAllowed: false,
interviewOpen: false,
}).kind, "adopt_representative");
assert.match(sessionOutcomeFromGate({
selectionAllowed: true,
proposeAllowed: true,
confirmationAllowed: false,
interviewOpen: false,
}).user_meaning, /可以采用代表性候选作当前排盘/);
assert.equal(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
userStopped: true,
}).kind, "provisional_range_user_stopped");
// Previously asserted “不是最终校正结果”; task 6 distinguishes completed range delivery from unique-minute confirmation.
assert.match(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
userStopped: true,
}).user_meaning, /交付已完成/);
assert.match(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
userStopped: true,
}).user_meaning, /当前最优结果是候选时间段/);
assert.doesNotMatch(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
userStopped: true,
}).user_meaning, /未完成|失败|遗憾/);
assert.doesNotMatch(sessionOutcomeFromGate({
selectionAllowed: true,
confirmationAllowed: false,
userStopped: true,
}).user_meaning, /已完成验证/);
});
test("holdout not_ready forbids unique-minute copy and still blocks confirm", async () => {
const projection = latestResultToolProjection({
resultId: RESULT_ID,
candidates: UNIQUE_MINUTE,
selectionAllowed: true,
confirmationAllowed: true,
representativeTime: "05:02",
selectedTime: null,
selectionKind: null,
algorithmVersion: "rectification-v5",
decisionReceipt: {
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
},
}, collectDecision(UNIQUE_MINUTE));
const serialized = JSON.stringify(projection);
assert.equal(projection.confirmation_allowed, false);
assert.equal(
(projection.confirmation_gate as ReturnType<typeof buildConfirmationGate>).unique_minute_path,
"closed_at_representative",
);
assert.equal(projection.unique_minute_claim, false);
assert.doesNotMatch(serialized, /±2 分钟/);
assert.match(serialized, /candidate_range_not_birth_time_truth/);
assert.match(serialized, /不是已确认唯一出生分钟|不得写成已确认唯一出生分钟/);
assert.doesNotMatch(serialized, /obama|barack|angelina|jolie/i);
assert.match(skill, /confirmation_gate/);
assert.match(skill, /not_evaluated/);
assert.match(skill, /session_outcome=adopt_representative/);
assert.doesNotMatch(skill, /本会话以代表性时间收口|本轮校正已收口/);
assert.match(skill, /unique_minute_path=closed_at_representative/);
assert.doesNotMatch(skill, /还不能确认唯一分钟/);
assert.match(candidateComparison, /confirmation_gate/);
assert.match(agentSource, /confirmation_allowed/);
assert.match(agentSource, /不得宣称唯一出生分钟/);
assert.doesNotMatch(agentSource, /±2 分钟/);
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 14);
// 原值: "10.0.25"
// 原值: "10.0.26"
// 新值: "10.0.27"
// 原因: 出卡精度门槛 + 引导式补经历
// 原因: BUG-668 定向补事第四选项写进 Skill
// 原值: "10.0.28" / 新值: "10.0.29" / 原因: 年月阶段改口述并禁止追问本人
// 原值: "10.0.29" / 新值: "10.0.30" / 原因: D2 出卡句与 D4 卡上百分比规则写进 Skill(2026-09-26)
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.30");
const accounting = fakeAccounting({
...receiptHandlers,
get_agentic_rectification_case_dossier: () => dossierFixture({
latestResult: {
...candidateSnapshotFixture({
confirmationAllowed: true,
selectionAllowed: true,
representativeTime: "05:02",
candidates: [
{ candidate_id: CANDIDATE_ID, rank: 1, time: "05:02", relative_support: 70, tied_minute_count: 1 },
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "05:03", relative_support: 30, tied_minute_count: 2 },
],
}),
confirmation_allowed: true,
decision_receipt: {
gates: { exact_confirmation: { external_validation_status: "not_evaluated" } },
},
},
}),
confirm_agentic_rectification_candidate_for_case_v2: () => ({
success: true,
saved_time: "05:02",
status: "confirmed",
result_id: RESULT_ID,
case_status: "confirmed",
idempotent: false,
}),
});
const tools = createRectificationV9Tools({
userId: USER_ID,
caseId: CASE_ID,
turnId: TURN_ID,
accounting: accounting.client as never,
});
await assert.rejects(
(tools["rectification-confirm-birth-time"] as unknown as {
execute(input: unknown): Promise<unknown>;
}).execute({
caseId: CASE_ID,
resultId: RESULT_ID,
candidateId: CANDIDATE_ID,
consentQuote: "就用05:02",
}),
(error: unknown) => error instanceof RectificationToolServiceError && error.code === "confirmation_blocked",
);
assert.equal(
accounting.calls.some((call) => call.fn === "confirm_agentic_rectification_candidate_for_case_v2"),
false,
);
assert.equal(safeToolErrorCode(new RectificationToolServiceError("confirmation_blocked")), "confirmation_blocked");
});
test("decideFromDossier passes cluster span into the confirmation gate", () => {
const src = readFileSync(
new URL("../src/lib/rectification-agentic/v9/decision-from-dossier.ts", import.meta.url),
"utf8",
);
assert.match(src, /clusterStart: candidate\.clusterStart \?\? candidate\.cluster_start/);
assert.match(src, /clusterEnd: candidate\.clusterEnd \?\? candidate\.cluster_end/);
});
test("confirmation width uses cluster span so representative minutes cannot open the gate", () => {
const candidates = [
{ time: "04:53", rank: 1, tiedMinuteCount: 1, clusterStart: "04:50", clusterEnd: "04:55" },
{ time: "04:59", rank: 2, tiedMinuteCount: 1, clusterStart: "04:56", clusterEnd: "05:02" },
];
assert.equal(indistinguishableWidthMinutes(candidates), 13);
const gate = buildConfirmationGate({
engineConfirmationAllowed: true,
candidates,
decisionReceipt: { vedastro_minute_sensitive: { status: "passed" } },
});
assert.equal(gate.confirmation_allowed, false);
const adjacent = gate.blockers.find((item) => item.id === "adjacent_minutes_indistinguishable");
assert.equal(adjacent?.indistinguishable_width_minutes, 13);
});
test("collect_evidence keeps public can_adopt closed while selection_allowed follows the decision", () => {
const decision = decideRectification({
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
methodCoverageAll: false,
datedMethodCollectOpen: true,
datedEventCount: 4,
datedDomainCount: 3,
trainingGateOpen: true,
snapshotCurrent: true,
candidateScores: [
{ time: "05:06", score: 14 },
{ time: "04:53", score: 13 },
],
confirmationAllowed: false,
});
// 原值: sessionOutcome=collect_evidence,公开 can_adopt=false
// 新值: 训练门开、无带年月探针 → adopt_representative,公开 can_adopt=true
// 原因: BUG-651 带年月池空即交付;确认门仍 fail-closed
assert.equal(decision.sessionOutcome, "adopt_representative");
assert.equal(decision.canAdopt, true);
const fields = publicDecisionFields(decision);
assert.equal(fields.can_adopt, true);
assert.equal(fields.selection_allowed, decision.selectionAllowed);
});