fix(rectification): anchor candidate windows to civil dates across midnight
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s

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.
This commit is contained in:
jesse-ux
2026-09-21 02:55:00 +08:00
parent 3be740f84d
commit b85c4a686a
115 changed files with 106484 additions and 315 deletions
@@ -0,0 +1,232 @@
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(); }
});