Files
Jyotisha/frontend/tests/rectification-holdout-renderable.test.ts
T
Jesse_Chen 9f011194f2
Independent Staging Quality Gate / validate (push) Successful in 13m0s
Independent Staging Quality Gate / publish (push) Successful in 10m58s
fix(rectification): centralize delivery authority
2026-09-01 01:13:04 +08:00

229 lines
7.8 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 test from "node:test";
import { applyHoldoutAnswer } from "../src/lib/rectification-agentic/core/build-state.ts";
import { decideFromDossier, rectificationFollowupCatalog } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts";
import { buildCaseInferenceState } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
import { buildMethodFollowupPlan } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { evidenceLedgerFingerprint } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import type { InferenceState } from "../src/lib/rectification-agentic/core/types.ts";
const COVERED_EVIDENCE = [
{
id: "e-edu",
status: "confirmed",
domain: "education",
datePrecision: "year",
occurredFrom: "2016-01-01",
occurredTo: null,
eventKind: "education_milestone",
},
{
id: "e-career-a",
status: "confirmed",
domain: "career",
datePrecision: "year",
occurredFrom: "2018-01-01",
occurredTo: null,
eventKind: "career_entry",
},
{
id: "e-career-b",
status: "confirmed",
domain: "career",
datePrecision: "year",
occurredFrom: "2020-01-01",
occurredTo: null,
eventKind: "career_change",
},
{
id: "e-rel",
status: "confirmed",
domain: "relationship",
datePrecision: "year",
occurredFrom: "2024-01-01",
occurredTo: null,
eventKind: "relationship_start",
},
{
id: "e-fam",
status: "confirmed",
domain: "family",
datePrecision: "year",
occurredFrom: "2023-01-01",
occurredTo: null,
eventKind: "family_event",
},
{
id: "e-occ",
status: "confirmed",
domain: "occupation",
datePrecision: "unknown",
occurredFrom: null,
occurredTo: null,
eventKind: "occupation_note",
},
] as const;
function producedDossier(
evidence: readonly Readonly<{
id: string;
status: string;
domain: string;
datePrecision: string;
occurredFrom: string | null;
occurredTo: string | null;
eventKind?: string | null;
}>[],
options: { previous?: InferenceState | null } = {},
) {
const state = buildCaseInferenceState({
range: { start_time: "04:55", end_time: "05:02" },
candidates: [
{ candidateId: "05:02", time: "05:02", relativeSupport: 58 },
{ candidateId: "04:55", time: "04:55", relativeSupport: 42 },
],
evidence,
probes: [],
previous: options.previous,
});
const latest = {
resultId: "55555555-5555-4555-8555-555555555555",
candidates: state.candidates.map((item) => ({
candidateId: item.id,
time: item.time,
rank: item.rank,
relativeSupport: Math.round(item.posterior_score),
})),
representativeTime: state.representative_time,
evidenceLedgerFingerprint: evidenceLedgerFingerprint(evidence as never),
decisionReceipt: {
acceptance_allowed: true,
selection_allowed: true,
propose_allowed: true,
confirmation_allowed: false,
inference_state: state,
},
};
return {
state,
dossier: {
evidence,
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
latestResult: latest,
case: { acceptedTime: null },
},
};
}
function holdoutFollowup(dossier: ReturnType<typeof producedDossier>["dossier"]) {
const catalog = rectificationFollowupCatalog(dossier.latestResult, dossier.evidence);
return buildMethodFollowupPlan({
evidence: dossier.evidence,
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
sessionOutcome: "validate_holdout",
...catalog,
candidatesSeparated: true,
});
}
test("sticky holdout with unknown precision stays review-only", () => {
const { state, dossier: dated } = producedDossier(COVERED_EVIDENCE);
const holdout = state.events.find((item) => item.usage === "holdout");
assert.ok(holdout);
assert.notEqual(holdout.year, null);
assert.equal(decideFromDossier(dated).nextAction, "ask_holdout_validation");
const undated = COVERED_EVIDENCE.map((item) => (
item.id === holdout.id
? { ...item, datePrecision: "unknown", occurredFrom: null, occurredTo: null }
: item
));
const { state: sticky, dossier } = producedDossier(undated, { previous: state });
const stickyHoldout = sticky.events.find((item) => item.id === holdout.id);
assert.equal(stickyHoldout?.usage, "holdout");
assert.equal(stickyHoldout?.year, null);
assert.equal(stickyHoldout?.precision, "unknown");
const decision = decideFromDossier(dossier);
assert.notEqual(decision.nextAction, "ask_holdout_validation");
// 原值是 ready_to_adoptunknown precision 让 holdout 不可用,不能进入采用态。
assert.equal(decision.nextAction, "offer_provisional_range");
// 原值是 adopt_representative;未通过独立核对只能交付临时区间。
assert.equal(decision.sessionOutcome, "provisional_range");
assert.notEqual(decision.sessionOutcome, "validated_range");
assert.equal(decision.validated, false);
// 原值是 trueholdout unavailable 必须 fail-closed。
assert.equal(decision.canAdopt, false);
assert.equal(decision.canConfirmExactMinute, false);
});
test("dated holdout asks validation with a renderable followup card", () => {
const { dossier } = producedDossier(COVERED_EVIDENCE);
const decision = decideFromDossier(dossier);
assert.equal(decision.nextAction, "ask_holdout_validation");
assert.equal(decision.canConfirmExactMinute, false);
const plan = holdoutFollowup(dossier);
assert.ok(plan.next_followup);
assert.ok(plan.next_followup.choice_frame, "holdout card must be renderable");
assert.equal(plan.next_followup.choice_frame.scoring, false);
assert.ok(plan.next_followup.choice_frame.prompt);
});
test("passed holdout is a validated range, not a unique minute", () => {
const { state, dossier } = producedDossier(COVERED_EVIDENCE);
const passed = applyHoldoutAnswer(state, "yes");
const decided = decideFromDossier({
...dossier,
latestResult: {
...dossier.latestResult,
decisionReceipt: {
...dossier.latestResult.decisionReceipt,
inference_state: passed,
},
},
});
assert.equal(decided.sessionOutcome, "validated_range");
assert.equal(decided.canAdopt, true);
assert.equal(decided.validated, true);
assert.equal(decided.canConfirmExactMinute, false);
assert.notEqual(decided.nextAction, "ask_holdout_validation");
});
test("failed holdout keeps existing non-validated close or discriminate path", () => {
const { state, dossier } = producedDossier(COVERED_EVIDENCE);
const failed = applyHoldoutAnswer(state, "no");
assert.equal(failed.holdout_passed, false);
const decided = decideFromDossier({
...dossier,
latestResult: {
...dossier.latestResult,
decisionReceipt: {
...dossier.latestResult.decisionReceipt,
inference_state: failed,
},
},
});
assert.equal(decided.holdoutValidation, "failed");
assert.notEqual(decided.nextAction, "ask_holdout_validation");
assert.notEqual(decided.sessionOutcome, "validated_range");
assert.equal(decided.validated, false);
assert.equal(decided.canConfirmExactMinute, false);
});
test("ask_holdout_validation is only returned when the same dossier can build a holdout followup", () => {
const fixtures = [
producedDossier(COVERED_EVIDENCE).dossier,
producedDossier(COVERED_EVIDENCE.filter((item) => item.domain !== "occupation")).dossier,
];
for (const dossier of fixtures) {
const decision = decideFromDossier(dossier);
if (decision.nextAction !== "ask_holdout_validation") continue;
const plan = holdoutFollowup(dossier);
assert.ok(plan.next_followup, "ask_holdout_validation requires a holdout followup");
assert.ok(plan.next_followup.choice_frame, "ask_holdout_validation requires a renderable card");
assert.equal(decision.canConfirmExactMinute, false);
}
});