Files
Jyotisha/frontend/tests/rectification-midnight-date-anchor.test.ts
T
Jesse_ChenandClaude Opus 5.5 3733b9787b fix(rectification): bump scoring identity to scoring-10 for functional profile v2; dated contract by generation (BUG-1181)
Functional roles feed the *_functional_*_auxiliary rules, so 57782aea changes
candidate scores for identical input (memoization fixture 12:00: 8.6274 ->
8.4977). Per the "scoring semantics change => bump ALGORITHM_VERSION"
precedent (scoring-7 -> 8 -> 9), the identity moves to scoring-10; policy v3,
input contract v5 and Skill versions are unchanged, history is not relabeled.

Five frontend sites and one SQL guard tested `=== "...scoring-9"` for the
dated candidate-window contract; they now use isDatedScoringAlgorithmVersion /
a generation regex (>= 9). Migration 20261002010000 only recreates
validate_dated_rectification_candidate (one-line guard change).

Memoization golden v2 written by the test's own write_golden; v1 (scoring-8)
frozen by sha256. Real-engine scoring-10 cross-midnight golden added. Research
records re-frozen per ERR-110 (label functional_v2_2026_10_02) and
scripts/functional_benefics.py added to the frozen production identity
(ERR-114: 57782aea changed scores without tripping it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-02 12:31:50 +08:00

143 lines
10 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { RectificationRangeDelivery } from "../src/components/rectification-range-delivery.tsx";
import { parseRectificationCandidateResult } from "../src/lib/rectification-candidate-result.ts";
import { buildRangeDelivery } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import { buildCaseInferenceState } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
import { engineRequestBody, runV9CandidateScore } from "../src/lib/rectification-agentic/v9/engine-client.ts";
import { unionStillValidRange } from "../src/lib/rectification-agentic/core/credible-range.ts";
import { indistinguishableWidthMinutes } from "../src/lib/rectification-agentic/v9/candidate-plateau.ts";
import type { InferenceCandidate } from "../src/lib/rectification-agentic/core/types.ts";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import { nakshatraBoundaryProbe } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
import { requestCandidateIntervals, parseCandidateIntervals } from "../src/lib/rectification-agentic/core/candidate-window.ts";
import { midnightSideChoice } from "../src/lib/rectification-agentic/v9/block-scan.ts";
import { buildMethodFollowupPlan } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { applyRectificationChoice } from "../src/lib/rectification-agentic/v9/answer-choice.ts";
import { expectedAnswerSchemaFor } from "../src/lib/rectification-agentic/v9/server-focus.ts";
import { CHOICE_ACTION } from "../src/lib/rectification-agentic/v9/choice-action.ts";
import { CASE_ID, FOCUS_ID, SESSION_ID, TURN_ID, USER_ID, activeFocusFixture, dossierFixture, fakeAccounting, receiptHandlers } from "./rectification-v9-test-support.ts";
const intervals = [{ start_index: 0, end_index: 2, start_offset_minutes: 0, end_offset_minutes: 2,
start_at: "2000-06-14T23:58", end_at: "2000-06-15T00:00", segment_index: 0 }];
test("983 request explicitly anchors midnight center on declared date", () => {
const body = engineRequestBody({ baselineBirthSnapshot: {
birth_date: "2000-06-15", reported_birth_time: "00:10", birth_time_source: "approximate",
latitude: 0, longitude: 0, timezone_offset: 0,
}, candidateRange: { start_time: "23:55", end_time: "00:25" }, events: [{
id: "00000000-0000-4000-8000-000000000001", domain: "career", event_kind: "career_entry",
date_start: "2020-01-01", date_end: "2020-01-01", precision: "day", summary: "fictional",
}] });
assert.deepEqual(body.candidate_intervals, [{ start_at: "2000-06-14T23:55", end_at: "2000-06-15T00:25" }]);
});
test("982 credible union uses ordinal endpoints, not clock min/max", () => {
const row: InferenceCandidate = { id: "a", time: "23:59", cluster_range: ["23:58", "00:00"],
cluster_intervals: intervals, window_index: 1, window_offset_minutes: 1,
candidate_date: "2000-06-14", prior_score: 10, posterior_score: 10,
probability: 1, status: "active", rank: 1, strong_conflict_count: 0 };
assert.deepEqual(unionStillValidRange([row as InferenceCandidate]), ["23:58", "00:00"]);
});
test("nakshatra earlier/later groups follow civil ordinals across midnight", () => {
const state = buildInferenceState({ range_start: "23:58", range_end: "00:00", events: [], probes: [],
candidates: ["23:58", "23:59", "00:00"].map((time, index) => ({ id: time, time, relative_support: 50,
candidate_date: index === 2 ? "2000-06-15" : "2000-06-14", window_index: index,
window_offset_minutes: index, segment_index: 0, cluster_intervals: intervals })) });
const probe = nakshatraBoundaryProbe(state, { near_boundary: true, user_meaning: null, options: [
{ key: "A", time_bias: "earlier", traits: ["直接"] }, { key: "B", time_bias: "later", traits: ["谨慎"] },
] });
assert.deepEqual(probe?.expected_outcomes[0]?.supports, ["23:58", "23:59"]);
assert.deepEqual(probe?.expected_outcomes[1]?.supports, ["00:00"]);
});
test("narrowed previous-day window never reanchors to declaration day", () => {
assert.deepEqual(requestCandidateIntervals({ birth_date: "2000-03-01", reported_birth_time: "00:10" },
{ start_time: "23:55", end_time: "23:59" }), [{ start_at: "2000-02-29T23:55", end_at: "2000-02-29T23:59" }]);
assert.equal(parseCandidateIntervals([{ start_at: "2000-02-30T00:00", end_at: "2000-03-01T00:00" }]), null);
});
for (const option of ["A", "B", "C", "skip_probe"] as const) test(`D1 initial no-result action ${option} reaches dated RPC once`, async () => {
const range = { start_time: "23:00", end_time: "03:59", midnight_side_pending: true, candidate_intervals: [
{ start_at: "2000-06-15T00:00", end_at: "2000-06-15T03:59" },
{ start_at: "2000-06-15T23:00", end_at: "2000-06-15T23:59" },
] };
const midnight = midnightSideChoice(range)!;
const plan = buildMethodFollowupPlan({ evidence: [], candidateRange: range, caseStage: "block_scan" });
assert.equal(plan.next_followup?.midnight_side, true);
const schema = expectedAnswerSchemaFor(midnight.frame, midnight.frame.question_id, null, plan.next_followup!);
assert.ok(schema, "initial followup must produce a real renderable focus schema");
let current = dossierFixture({ stage: "block_scan", candidateRange: range, latestResult: null, evidence: [],
conversationSummary: { active_focus: activeFocusFixture({ intent: "choose_birth_block", questionId: midnight.frame.question_id,
expectedAnswerSchema: schema }), declined_skipped_topics: [] } });
const accounting = fakeAccounting({ ...receiptHandlers,
get_agentic_rectification_case_dossier: () => current,
advance_agentic_rectification_dated_window: (_fn, args) => {
const narrowed = { start_time: String(args.p_start_time), end_time: String(args.p_end_time),
candidate_intervals: args.p_candidate_intervals, midnight_side_pending: false };
current = { ...current, case: { ...current.case, candidate_range: narrowed } };
return { candidate_range: narrowed };
},
apply_agentic_rectification_choice_action: (_fn, args) => ({ status: "applied", idempotent: false, question_id: args.p_question_id,
option_id: args.p_option_id, revision: 1, narration: args.p_narration, focus_status: args.p_focus_status }),
append_agentic_rectification_turn: () => ({ turn_id: TURN_ID, idempotent: false }),
set_agentic_rectification_conversation_focus: () => ({ focus: null, idempotent: false }),
});
await applyRectificationChoice(accounting.client, { userId: USER_ID, caseId: CASE_ID, sessionId: SESSION_ID,
actionId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa1", action: CHOICE_ACTION, focusId: FOCUS_ID, optionId: option, expectedRevision: 0 });
const calls = accounting.calls.filter((item) => item.fn === "advance_agentic_rectification_dated_window");
assert.equal(calls.length, 1);
assert.equal((calls[0].args.p_candidate_intervals as unknown[]).length, option === "A" || option === "B" ? 1 : 2);
assert.equal(midnightSideChoice(current.case.candidate_range), null);
});
// BUG-1181: the scoring-10 golden (real engine, functional profile v2) must parse as dated too;
// a literal "=== scoring-9" check would have turned it into a legacy same-day result.
for (const fixture of ["rectification-midnight-date-anchor.delivery.native.json", "rectification-midnight-date-anchor.delivery.scoring-10.native.json"]) test(`native golden crosses engine parsing, inference and dated delivery without reanchoring (${fixture})`, async () => {
const golden = JSON.parse(readFileSync(new URL(`./fixtures/${fixture}`, import.meta.url), "utf8"));
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => new Response(JSON.stringify(golden.response), { status: 200 })) as typeof fetch;
try {
const request = golden.request;
const range = { start_time: request.start_time, end_time: request.end_time, candidate_intervals: request.candidate_intervals };
const scored = await runV9CandidateScore({ baselineBirthSnapshot: { birth_date: request.birth_date,
latitude: request.lat, longitude: request.lon, timezone_offset: request.tz }, candidateRange: range, events: request.events });
const inference = buildCaseInferenceState({ range, candidates: scored.candidates, evidence: [], probes: [],
transitions: scored.windowScan?.transitions });
const delivery = buildRangeDelivery({ inference, publicCandidates: scored.candidates, windowScan: scored.windowScan });
const result = parseRectificationCandidateResult({ ...scored, resultId: scored.engineResultId,
decisionReceipt: { ...scored.decisionReceipt, inference_state: inference }, rangeDelivery: delivery });
assert.ok(result);
assert.equal(result.declaredBirthDate, request.birth_date);
assert.deepEqual(result.candidates.map((row) => row.candidate_date), scored.candidates.map((row) => row.candidate_date));
assert.equal(result.credibleIntervals?.[0]?.start_at, "2000-02-29T23:58");
assert.ok(delivery.columns.length > 0, "cross-midnight range must not filter out every candidate");
const html = renderToStaticMarkup(createElement(RectificationRangeDelivery, {
result, acceptingCandidateId: null, readonly: true, onAccept: () => undefined,
}));
assert.match(html, /前一天/);
assert.match(html, /2000-02-29/);
assert.match(html, /原填报日期保留/);
assert.doesNotMatch(html, /2000-03-02/);
} finally { globalThis.fetch = originalFetch; }
});
for (const invalid of [null, [], {}, "", [{ start_at: "2000-03-01T00:00" }]]) test(`explicit invalid intervals never fall back: ${JSON.stringify(invalid)}`, () => {
assert.throws(() => engineRequestBody({ baselineBirthSnapshot: { birth_date: "2000-03-01", latitude: 0, longitude: 0, timezone_offset: 0 },
candidateRange: { start_time: "23:58", end_time: "00:02", candidate_intervals: invalid } as never, events: [{
id: "00000000-0000-4000-8000-000000000001", domain: "career", event_kind: "career_entry",
date_start: "2020-01-01", date_end: "2020-01-01", precision: "day", summary: "fictional",
}] }), /invalid_candidate_intervals/);
});
test("982 plateau width retains actual minutes across midnight", () => {
assert.equal(indistinguishableWidthMinutes([{ time: "23:59", rank: 1, tiedMinuteCount: 1,
clusterStart: "23:58", clusterEnd: "00:00", clusterIntervals: intervals }]), 3);
});