Files
Jyotisha/frontend/tests/database-rectification-dated-windows.test.ts
T
jesse-ux b85c4a686a
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s
fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
2026-09-21 02:55:00 +08:00

233 lines
19 KiB
TypeScript

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
import { engineRequestBody, RectificationEngineError } from "../src/lib/rectification-agentic/v9/engine-client.ts";
import { scoreAndPersistCurrentEvidence } from "../src/lib/rectification-agentic/v9/score-persist.ts";
const docker = spawnSync("docker", ["version"], { stdio: "ignore" }).status === 0;
test("dated window RPC preserves original midnight anchor, D1 choices and ownership", { skip: !docker && "docker unavailable" }, () => {
const fixture = startPostgresFixture();
try {
const migration = spawnSync(process.execPath, [fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url))], {
encoding: "utf8", env: { ...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password") },
});
assert.equal(migration.status, 0, migration.stderr);
const user = randomUUID();
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `insert into identity.users(id,name,email,email_verified) values('${user}','Fictional window','${user}@example.invalid',true)`);
const makeCase = (snapshot: object, range: object) => {
const id = randomUUID(), session = randomUUID();
fixture.psql(`insert into public.chat_sessions(id,user_id,title,theme,session_type,messages) values('${session}','${user}','Fictional window','general','birth_time_rectification','[]')`);
fixture.psql(`insert into public.agentic_rectification_cases(id,user_id,session_id,status,stage,skill_name,skill_version,baseline_profile_fingerprint,baseline_birth_snapshot,candidate_range)
values('${id}','${user}','${session}','candidate_ready','block_scan','jyotish-birth-time-rectification','9.0.0','${"a".repeat(64)}','${JSON.stringify(snapshot)}','${JSON.stringify(range)}')`);
return id;
};
const call = (sql: string) => JSON.parse(fixture.psql(`select ${sql}`));
const narrow = makeCase({ birth_date: "2000-03-01", reported_birth_time: "00:10", birth_time_source: "approximate", uncertainty_before_minutes: 15, uncertainty_after_minutes: 15 }, { start_time: "23:55", end_time: "23:59" });
const anchored = call(`public.ensure_agentic_rectification_dated_window('${user}','${narrow}')`);
assert.deepEqual(anchored.candidate_intervals, [{ start_at: "2000-02-29T23:55", end_at: "2000-02-29T23:59" }]);
for (const side of ["A", "B", "C", "skip"] as const) {
const id = makeCase({ birth_date: "2000-03-01", birth_time_source: "period_only", birth_time_period: "late_night" }, { start_time: "23:00", end_time: "03:59" });
const original = call(`public.ensure_agentic_rectification_dated_window('${user}','${id}')`);
assert.equal(original.midnight_side_pending, true);
const parts = side === "A" ? [original.candidate_intervals[1]] : side === "B" ? [original.candidate_intervals[0]] : original.candidate_intervals;
const start = side === "A" ? "23:00" : side === "B" ? "00:00" : "23:00";
const end = side === "A" ? "23:59" : "03:59";
const changed = call(`public.advance_agentic_rectification_dated_window('${user}','${id}','${start}','${end}','${JSON.stringify(parts)}',false)`);
assert.deepEqual(changed.candidate_range.candidate_intervals, parts);
const reloaded = call(`public.ensure_agentic_rectification_dated_window('${user}','${id}')`);
assert.equal(reloaded.midnight_side_pending, false);
assert.deepEqual(reloaded.candidate_intervals, parts);
}
assert.throws(() => call(`public.ensure_agentic_rectification_dated_window('${randomUUID()}','${narrow}')`), /case_not_found/);
assert.throws(() => call(`public.advance_agentic_rectification_dated_window('${user}','${narrow}','23:55','23:59','[{"start_at":"2000-03-01T23:55","end_at":"2000-03-01T23:59"}]',false)`), /invalid_block_window/);
} finally { fixture.stop(); }
});
test("dated widen preserves persisted dates and late-night allowed sets transactionally", { skip: !docker && "docker unavailable" }, async (t) => {
const fixture = startPostgresFixture();
try {
const migration = spawnSync(process.execPath, [fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url))], {
encoding: "utf8", env: { ...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password") },
});
assert.equal(migration.status, 0, migration.stderr);
const user = randomUUID();
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `insert into identity.users(id,name,email,email_verified) values('${user}','Fictional widen','${user}@example.invalid',true)`);
const snapshot = { birth_date: "2000-03-01", latitude: 0, longitude: 0, timezone_offset: 0, reported_birth_time: "00:10", birth_time_source: "approximate" };
const nightSnapshot = { ...snapshot, reported_birth_time: null, birth_time_source: "period_only", birth_time_period: "late_night" };
const both = [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T03:59" },
{ start_at: "2000-03-01T23:00", end_at: "2000-03-01T23:59" },
];
const makeCase = (range: object, birth: object = snapshot) => {
const id = randomUUID(), session = randomUUID();
fixture.psql(`insert into public.chat_sessions(id,user_id,title,theme,session_type,messages) values('${session}','${user}','Fictional widen','general','birth_time_rectification','[]');
insert into public.agentic_rectification_cases(id,user_id,session_id,status,stage,skill_name,skill_version,baseline_profile_fingerprint,baseline_birth_snapshot,candidate_range)
values('${id}','${user}','${session}','candidate_ready','minute','jyotish-birth-time-rectification','9.0.0','${"a".repeat(64)}','${JSON.stringify(birth)}','${JSON.stringify(range)}');
insert into public.agentic_rectification_results(user_id,session_id,case_id,engine_result_id,canonical_input_hash,algorithm_version,candidate_range,candidates,overall_confidence,baseline_birth_date,baseline_latitude,baseline_longitude,baseline_timezone_offset)
values('${user}','${session}','${id}','fictional-${id}','${"b".repeat(64)}','rectification-v5-matrix-scoring-8','${JSON.stringify(range)}','[]','low','2000-03-01',0,0,0)`);
return id;
};
const call = (sql: string) => JSON.parse(fixture.psql(`select ${sql}`));
const widen = (id: string, start: string, end: string, caller = user) => call(`public.widen_agentic_rectification_dated_window('${caller}','${id}','${start}','${end}')`);
const reload = (id: string) => call(`public.ensure_agentic_rectification_dated_window('${user}','${id}')`);
const state = (id: string) => fixture.psql(`select jsonb_build_object('case',(select to_jsonb(c) from public.agentic_rectification_cases c where id='${id}'),'results',(select jsonb_agg(to_jsonb(r) order by id) from public.agentic_rectification_results r where case_id='${id}'))`);
const rejectUnchanged = (id: string, start: string, end: string, error = /invalid_widen_window/, caller = user) => {
const before = state(id);
assert.throws(() => widen(id, start, end, caller), error);
assert.equal(state(id), before, "rejected transaction changes no case/result fields, including invalidation and timestamps");
};
const advance = (id: string, start: string, end: string, parts: object[]) => {
fixture.psql(`update public.agentic_rectification_cases set stage='block_scan' where id='${id}'`);
return call(`public.advance_agentic_rectification_dated_window('${user}','${id}','${start}','${end}','${JSON.stringify(parts)}',false)`);
};
const assertMinuteRequest = (id: string, expected: object[]) => {
const range = reload(id);
const birth = call(`(select baseline_birth_snapshot from public.agentic_rectification_cases where id='${id}')`);
const request = engineRequestBody({ baselineBirthSnapshot: birth, candidateRange: range, 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 widen event",
}] });
assert.deepEqual(range.candidate_intervals, expected);
assert.deepEqual(request.candidate_intervals, expected, "DB reload to minute engine request keeps exact dates and holes");
};
await t.test("ordinary minute widening uses persisted previous-day anchor, not baseline re-guess", () => {
const id = makeCase({ start_time: "00:00", end_time: "00:20", candidate_intervals: [{ start_at: "2000-02-29T00:00", end_at: "2000-02-29T00:20" }] });
widen(id, "00:00", "00:30");
assertMinuteRequest(id, [{ start_at: "2000-02-29T00:00", end_at: "2000-02-29T00:30" }]);
});
await t.test("ordinary narrowed cross-midnight range never acquires a late-night side marker", () => {
const id = makeCase({ start_time: "23:55", end_time: "00:25", candidate_intervals: [{ start_at: "2000-02-29T23:55", end_at: "2000-03-01T00:25" }] });
advance(id, "00:00", "00:20", [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:20" }]);
assert.equal(Object.hasOwn(reload(id), "midnight_side_pending"), false);
widen(id, "23:30", "00:30");
assertMinuteRequest(id, [{ start_at: "2000-02-29T23:30", end_at: "2000-03-01T00:30" }]);
});
for (const side of ["A", "B", "C", "skip"] as const) {
await t.test(`late-night ${side} keeps original allowed set through narrow/widen/reload`, () => {
const id = makeCase({ start_time: "23:00", end_time: "03:59", candidate_intervals: both, midnight_side_pending: true }, nightSnapshot);
const chosen = side === "A" ? [both[1]] : side === "B" ? [both[0]] : both;
advance(id, side === "B" ? "00:00" : "23:00", side === "A" ? "23:59" : "03:59", chosen);
const narrowed = side === "A" ? [{ start_at: "2000-03-01T23:20", end_at: "2000-03-01T23:30" }] : [{ start_at: "2000-03-01T00:10", end_at: "2000-03-01T00:20" }];
advance(id, side === "A" ? "23:20" : "00:10", side === "A" ? "23:30" : "00:20", narrowed);
if (side === "A" || side === "B") {
rejectUnchanged(id, side === "A" ? "23:10" : "23:30", "00:30");
widen(id, side === "A" ? "23:00" : "00:00", side === "A" ? "23:40" : "00:30");
assertMinuteRequest(id, side === "A" ? [{ start_at: "2000-03-01T23:00", end_at: "2000-03-01T23:40" }] : [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:30" }]);
} else {
widen(id, "23:30", "00:30");
assertMinuteRequest(id, [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:30" },
{ start_at: "2000-03-01T23:30", end_at: "2000-03-01T23:59" },
]);
}
assert.equal(reload(id).midnight_side_pending, false);
assert.deepEqual(reload(id).midnight_allowed_intervals, chosen, "a narrowed C/skip range is not a new user side choice");
});
}
await t.test("two surviving late-night segments widen without filling the daytime hole", () => {
const id = makeCase({ start_time: "23:50", end_time: "00:10", candidate_intervals: [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:10" },
{ start_at: "2000-03-01T23:50", end_at: "2000-03-01T23:59" },
], midnight_side_pending: false, midnight_allowed_intervals: both }, nightSnapshot);
widen(id, "23:40", "00:20");
assertMinuteRequest(id, [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:20" }, { start_at: "2000-03-01T23:40", end_at: "2000-03-01T23:59" }]);
});
await t.test("real DB dossier/compute reload reaches minute score fetch with widened dated union", async (serviceTest) => {
const id = makeCase({ start_time: "23:00", end_time: "03:59", candidate_intervals: both, midnight_side_pending: true }, nightSnapshot);
advance(id, "23:00", "03:59", both); // D1 C/skip, not a side selection.
advance(id, "00:10", "00:20", [{ start_at: "2000-03-01T00:10", end_at: "2000-03-01T00:20" }]);
widen(id, "23:30", "00:30");
assert.equal(fixture.psql(`select stage from public.agentic_rectification_cases where id='${id}'`), "minute");
const turn = randomUUID();
fixture.psql(`insert into public.agentic_rectification_turns(id,case_id,status,model_name,user_message) values('${turn}','${id}','pending','fictional-test','Fictional career event');
insert into public.agentic_rectification_evidence(case_id,source_turn_id,user_quote,subject,event_kind,domain,occurred_from,occurred_to,date_precision,summary,status,confirmed_at)
values('${id}','${turn}','Fictional career event','self','career_entry','career','2020-01-01','2020-01-01','day','Fictional career event','confirmed',now())`);
const calls: string[] = [];
const accounting = { rpc: async (fn: string, args: Record<string, unknown>) => {
calls.push(fn);
assert.ok(["get_agentic_rectification_case_dossier", "get_agentic_rectification_case_compute"].includes(fn));
assert.equal(args.p_user_id, user);
assert.equal(args.p_case_id, id);
return { data: call(`public.${fn}('${user}','${id}')`), error: null };
} };
const keys = ["RECTIFICATION_ENGINE_VERSION", "RECTIFICATION_ALGORITHM_VERSION", "RECTIFICATION_DECISION_POLICY_VERSION"];
const saved = keys.map(key => [key, process.env[key]] as const);
for (const key of keys) delete process.env[key];
serviceTest.after(() => { for (const [key, value] of saved) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } });
let scoreBody: Record<string, unknown> | null = null;
serviceTest.mock.method(globalThis, "fetch", async (url: unknown, init?: RequestInit) => {
if (String(url).endsWith("/versions")) return Response.json({ algorithm_version: "rectification-v5-matrix-scoring-9", decision_policy_version: "rectification-candidate-policy-v3" });
assert.ok(String(url).endsWith("/score"), "minute service must call score, not block_scan");
scoreBody = JSON.parse(String(init?.body));
// End at the network boundary: no fabricated engine contract is needed
// to prove the real DB -> parser -> service -> outgoing request chain.
return Response.json({ error: "fictional_network_stop_after_capture" }, { status: 400 });
});
await assert.rejects(scoreAndPersistCurrentEvidence({ accounting: accounting as never, userId: user, caseId: id }),
(error: unknown) => error instanceof RectificationEngineError
&& error.httpStatus === 400 && error.message === "fictional_network_stop_after_capture");
assert.deepEqual(calls, ["get_agentic_rectification_case_dossier", "get_agentic_rectification_case_compute"]);
assert.ok(scoreBody, "actual score fetch must be reached");
assert.equal((scoreBody as Record<string, unknown>).birth_date, "2000-03-01");
assert.equal((scoreBody as Record<string, unknown>).tz, 0);
assert.equal((scoreBody as Record<string, unknown>).start_time, "23:30");
assert.equal((scoreBody as Record<string, unknown>).end_time, "00:30");
assert.deepEqual((scoreBody as Record<string, unknown>).candidate_intervals, [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:30" },
{ start_at: "2000-03-01T23:30", end_at: "2000-03-01T23:59" },
]);
});
await t.test("legacy pending=false without allowed provenance never guesses a discarded side", () => {
const id = makeCase({ start_time: "00:00", end_time: "00:30", candidate_intervals: [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:30" },
], midnight_side_pending: false }, nightSnapshot);
rejectUnchanged(id, "23:30", "00:30");
assertMinuteRequest(id, [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:30" }]);
const two = makeCase({ start_time: "23:50", end_time: "00:10", candidate_intervals: [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:10" },
{ start_at: "2000-03-01T23:50", end_at: "2000-03-01T23:59" },
], midnight_side_pending: false }, nightSnapshot);
// Without recorded allowed provenance, retaining both existing segments
// and rejecting an unverifiable expansion is the safe legacy fallback.
rejectUnchanged(two, "23:40", "00:20");
assertMinuteRequest(two, [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:10" }, { start_at: "2000-03-01T23:50", end_at: "2000-03-01T23:59" }]);
});
await t.test("pending D1 cannot narrow with true or null; unchanged true remains compatible", () => {
for (const pending of ["true", "null"]) {
const id = makeCase({ start_time: "23:00", end_time: "03:59", candidate_intervals: both, midnight_side_pending: true }, nightSnapshot);
fixture.psql(`update public.agentic_rectification_cases set stage='block_scan' where id='${id}'`);
const before = state(id);
assert.throws(() => call(`public.advance_agentic_rectification_dated_window('${user}','${id}','00:00','03:59','${JSON.stringify([both[0]])}',${pending})`), /invalid_block_window/);
assert.equal(state(id), before, "invalid pending transition rolls back every case and result field");
}
const id = makeCase({ start_time: "23:00", end_time: "03:59", candidate_intervals: both, midnight_side_pending: true }, nightSnapshot);
fixture.psql(`update public.agentic_rectification_cases set stage='block_scan' where id='${id}'`);
const kept = call(`public.advance_agentic_rectification_dated_window('${user}','${id}','23:00','03:59','${JSON.stringify(both)}',true)`);
assert.equal(kept.candidate_range.midnight_side_pending, true);
assert.deepEqual(kept.candidate_range.candidate_intervals, both);
assert.deepEqual(kept.candidate_range.midnight_allowed_intervals, both);
});
await t.test("pending two segments preserve the gap and pending flag", () => {
const id = makeCase({ start_time: "23:50", end_time: "00:10", candidate_intervals: [
{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:10" },
{ start_at: "2000-03-01T23:50", end_at: "2000-03-01T23:59" },
], midnight_side_pending: true, midnight_allowed_intervals: both }, nightSnapshot);
widen(id, "23:40", "00:20");
assertMinuteRequest(id, [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:20" }, { start_at: "2000-03-01T23:40", end_at: "2000-03-01T23:59" }]);
assert.equal(reload(id).midnight_side_pending, true);
});
await t.test("old containment, strictly-wider, max-width, ownership and terminal guards roll back", () => {
const id = makeCase({ start_time: "00:00", end_time: "00:20", candidate_intervals: [{ start_at: "2000-03-01T00:00", end_at: "2000-03-01T00:20" }] });
rejectUnchanged(id, "00:10", "00:30");
rejectUnchanged(id, "00:00", "00:20");
rejectUnchanged(id, "00:00", "05:00");
rejectUnchanged(id, "00:00", "00:30", /case_not_found/, randomUUID());
fixture.psql(`update public.agentic_rectification_cases set status='closed',completed_at=now() where id='${id}'`);
rejectUnchanged(id, "00:00", "00:30", /case_terminal/);
});
} finally { fixture.stop(); }
});