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.
This commit is contained in:
@@ -27,8 +27,13 @@ import {
|
||||
const golden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-engine-version-cross-midnight-golden.json", import.meta.url,
|
||||
), "utf8"));
|
||||
// Historical golden identity stays fixed; it must not be relabeled as the live engine.
|
||||
const CURRENT = "rectification-v5-matrix-scoring-8";
|
||||
const PREVIOUS = "rectification-v5-matrix-scoring-7";
|
||||
const LIVE_CURRENT = "rectification-v5-matrix-scoring-9";
|
||||
const liveGolden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-midnight-date-anchor.delivery.native.json", import.meta.url,
|
||||
), "utf8"));
|
||||
const envKeys = [
|
||||
"RECTIFICATION_ENGINE_VERSION", "RECTIFICATION_ALGORITHM_VERSION",
|
||||
"RECTIFICATION_DECISION_POLICY_VERSION",
|
||||
@@ -47,15 +52,25 @@ function isolateIdentityEnv(t: test.TestContext) {
|
||||
|
||||
test("receipt default matches the cross-midnight native scoring version without changing Skill identity", (t) => {
|
||||
isolateIdentityEnv(t);
|
||||
assert.equal(v9EngineVersion(), CURRENT);
|
||||
assert.equal(v9EngineVersion(), LIVE_CURRENT);
|
||||
for (const blank of ["", " "]) {
|
||||
process.env.RECTIFICATION_ENGINE_VERSION = blank;
|
||||
assert.equal(v9EngineVersion(), CURRENT);
|
||||
assert.equal(v9EngineVersion(), LIVE_CURRENT);
|
||||
}
|
||||
assert.equal(golden.score.algorithm_version, CURRENT);
|
||||
assert.equal(golden.versions.algorithm_version, CURRENT);
|
||||
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
|
||||
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-8"/);
|
||||
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-9"/);
|
||||
assert.equal(liveGolden.response.algorithm_version, LIVE_CURRENT);
|
||||
const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) };
|
||||
const live = {
|
||||
algorithmVersion: liveGolden.response.algorithm_version,
|
||||
policyVersion: liveGolden.response.decision_policy_version,
|
||||
};
|
||||
const historical = { ...fingerprints, algorithmVersion: CURRENT, policyVersion: golden.versions.decision_policy_version };
|
||||
assert.equal(cachedEngineScoreIsReusable(historical, fingerprints, live), false);
|
||||
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: LIVE_CURRENT }, fingerprints, live), true);
|
||||
assert.equal(historical.algorithmVersion, CURRENT, "checking live identity never relabels history");
|
||||
// A receipt default must not become an environment override for cache identity.
|
||||
assert.deepEqual(liveEngineScoringIdentityFromEnv({}), { algorithmVersion: null, policyVersion: null });
|
||||
});
|
||||
@@ -338,14 +353,28 @@ test("missing stage source, absent results and nonlatest result IDs cannot autho
|
||||
assert.equal(resultIdentityView(parseV9CaseDossier(empty)!, { algorithmVersion: null, policyVersion: null }).read_only, false);
|
||||
});
|
||||
|
||||
const historicalGolden = golden;
|
||||
async function runGoldenToolSequence(t: test.TestContext, options: {
|
||||
compareVersion?: string;
|
||||
diagnosticsVersion?: string;
|
||||
diagnosticsFailure?: boolean;
|
||||
useLiveGolden?: boolean;
|
||||
} = {}) {
|
||||
isolateIdentityEnv(t);
|
||||
// A scoring-9 response needs its real dated contract, never a relabeled scoring-8 fixture.
|
||||
const golden = options.useLiveGolden ? {
|
||||
request: liveGolden.request,
|
||||
score: liveGolden.response,
|
||||
versions: {
|
||||
algorithm_version: liveGolden.response.algorithm_version,
|
||||
decision_policy_version: liveGolden.response.decision_policy_version,
|
||||
},
|
||||
} : historicalGolden;
|
||||
const request = golden.request;
|
||||
const candidateRange = { start_time: request.start_time, end_time: request.end_time };
|
||||
const candidateRange = {
|
||||
start_time: request.start_time, end_time: request.end_time,
|
||||
...(request.candidate_intervals ? { candidate_intervals: request.candidate_intervals } : {}),
|
||||
};
|
||||
const evidence = request.events.map((event: Record<string, string>) => ({
|
||||
id: event.id, source_turn_id: TURN_ID, subject: "self", event_kind: event.event_kind,
|
||||
domain: event.domain, occurred_from: event.date_start, occurred_to: event.date_end,
|
||||
@@ -395,11 +424,11 @@ async function runGoldenToolSequence(t: test.TestContext, options: {
|
||||
if (options.diagnosticsFailure && name === "rectification-read-diagnostics") await assert.rejects(execute);
|
||||
else {
|
||||
const projection = await execute() as { read_only?: boolean; algorithm_version?: string; can_confirm_exact_minute?: boolean };
|
||||
if (name === "rectification-compare-candidates" && options.compareVersion && options.compareVersion !== CURRENT) {
|
||||
if (name === "rectification-compare-candidates" && options.compareVersion && options.compareVersion !== golden.versions.algorithm_version) {
|
||||
assert.equal(projection.read_only, true, "rolling response mismatch stays read-only after saving true provenance");
|
||||
assert.equal(projection.algorithm_version, options.compareVersion);
|
||||
}
|
||||
if (name === "rectification-read-diagnostics" && options.diagnosticsVersion && options.diagnosticsVersion !== CURRENT) {
|
||||
if (name === "rectification-read-diagnostics" && options.diagnosticsVersion && options.diagnosticsVersion !== golden.versions.algorithm_version) {
|
||||
assert.equal(projection.read_only, true);
|
||||
assert.equal(projection.algorithm_version, options.diagnosticsVersion);
|
||||
assert.equal(projection.can_confirm_exact_minute, false, "rolling diagnostics cannot claim current validation");
|
||||
@@ -432,7 +461,7 @@ test("mixed completed identities require chronological aggregation (covered by d
|
||||
// remain the real native golden. This is not a claim to have run future algorithms.
|
||||
const version9 = "rectification-v5-matrix-scoring-9";
|
||||
const version10 = "rectification-v5-matrix-scoring-10";
|
||||
const receipts = await runGoldenToolSequence(t, { compareVersion: version9, diagnosticsVersion: version10 });
|
||||
const receipts = await runGoldenToolSequence(t, { compareVersion: version9, diagnosticsVersion: version10, useLiveGolden: true });
|
||||
const completed = receipts.filter((call) => call.args.p_status === "completed");
|
||||
assert.equal(new Set(completed.map((call) => call.args.p_turn_id)).size, 1);
|
||||
assert.deepEqual(completed.map((call) => call.args.p_engine_version), [version9, version10]);
|
||||
@@ -466,7 +495,7 @@ test("history opens with its bound Skill and reads old engine receipt values unc
|
||||
});
|
||||
assert.equal(opened.disposition, "readonly");
|
||||
const receipt = await loadV9TurnReceipt(accounting.client as never, USER_ID, CASE_ID, TURN_ID);
|
||||
assert.equal(v9EngineVersion(), CURRENT);
|
||||
assert.equal(v9EngineVersion(), LIVE_CURRENT);
|
||||
assert.equal(receipt?.engineVersion, oldVersion);
|
||||
assert.deepEqual(historical, before);
|
||||
assert.equal(accounting.calls.some((call) => /insert|persist|upgrade/.test(call.fn)), false);
|
||||
|
||||
Reference in New Issue
Block a user