78f98ebachanges the same-input rectification scores: Shadbala Sthana and Drik feed the Shadbala auxiliary (BPHS Saptavargaja values, Moolatrikona 3 / 15 deg, whole-sign Kendradi, BPHS 26 special aspects, Mercury with any malefic) and jaimini.calc_arudha_padas feeds the event engine and feature facts. Per BUG-1181 the algorithm identity moves rectification-v5-matrix-scoring-13 -> -14. Policy v4, input contract v5 and the dated window contract (generation >= 9) are unchanged; the SQL guard accepts scoring-(9|[1-9][0-9]+), so no migration. - Memoization golden v7 written by write_golden (source_commit78f98eba); v6 stays byte-identical and is pinned by sha256. For this public request the candidate scores are unchanged (12:00 8.5336, 12:01 8.5242, 12:02 8.088); the Shadbala fingerprints, feature hash and candidate UUID moved. - New scoring-14 cross-midnight golden from midnight_date_anchor_regression.py --golden (baseline = this tree; independent-process A/B 3 x 7 fields equal); scoring-13 golden kept as history and opens read-only with recompare. - Dated-transitions scan unchanged (test passes without regeneration). - v9EngineVersion() default and the identity tests follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
134 lines
11 KiB
TypeScript
134 lines
11 KiB
TypeScript
// BUG-1181: the dated candidate-window contract belongs to scoring generation 9 and
|
||
// every later one. The bump to scoring-10 (functional benefic / malefic profile v2)
|
||
// must not turn new results into legacy same-day results anywhere in the stack.
|
||
import assert from "node:assert/strict";
|
||
import { readFileSync, readdirSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import { FIRST_DATED_SCORING_GENERATION, isDatedScoringAlgorithmVersion } from "../src/lib/rectification-agentic/core/candidate-window.ts";
|
||
import { v9EngineVersion } from "../src/lib/rectification-agentic/v9/engine-client.ts";
|
||
import { resultIdentityView } from "../src/lib/rectification-agentic/v9/result-identity.ts";
|
||
import { parseV9CaseDossier } from "../src/lib/rectification-agentic/v9/tool-service.ts";
|
||
import { dossierFixture } from "./rectification-v9-test-support.ts";
|
||
|
||
test("dated contract is a generation threshold, not one literal", () => {
|
||
assert.equal(FIRST_DATED_SCORING_GENERATION, 9);
|
||
for (const version of ["rectification-v5-matrix-scoring-9", "rectification-v5-matrix-scoring-10", "rectification-v5-matrix-scoring-11", "rectification-v5-matrix-scoring-12", "rectification-v5-matrix-scoring-13", "rectification-v5-matrix-scoring-14", "rectification-v5-matrix-scoring-100"]) {
|
||
assert.equal(isDatedScoringAlgorithmVersion(version), true, version);
|
||
}
|
||
for (const version of ["rectification-v5-matrix-scoring-8", "rectification-v5-matrix-scoring-1", "rectification-v5-matrix-scoring-09",
|
||
"rectification-v5", "rectification-v5-matrix-scoring-10 ", "x-rectification-v5-matrix-scoring-10", "", null, undefined, 10]) {
|
||
assert.equal(isDatedScoringAlgorithmVersion(version), false, String(version));
|
||
}
|
||
});
|
||
|
||
test("receipt default and Python identity agree on scoring-14", (t) => {
|
||
const saved = process.env.RECTIFICATION_ENGINE_VERSION;
|
||
delete process.env.RECTIFICATION_ENGINE_VERSION;
|
||
t.after(() => { if (saved === undefined) delete process.env.RECTIFICATION_ENGINE_VERSION; else process.env.RECTIFICATION_ENGINE_VERSION = saved; });
|
||
// 原值: rectification-v5-matrix-scoring-12;新值: ...-scoring-13;原因: D30 偶数星座按 BPHS、白羊 / 天蝎金星功能凶改变同输入分数(BUG-1560 / BUG-1570)。
|
||
// 原值: ...-scoring-13;新值: ...-scoring-14;原因: #166 占星师裁定(Shadbala Sthana / Drik、jaimini.py Arudha 例外,BUG-1583~1588)改变同输入分数。
|
||
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-14");
|
||
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
|
||
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-14"$/m);
|
||
});
|
||
|
||
test("no source file compares the algorithm identity with a scoring-N literal", () => {
|
||
const roots = ["../src/lib/", "../src/mastra/", "../src/app/"];
|
||
const hits: string[] = [];
|
||
const walk = (url: URL) => {
|
||
for (const entry of readdirSync(url, { withFileTypes: true })) {
|
||
const child = new URL(entry.name + (entry.isDirectory() ? "/" : ""), url);
|
||
if (entry.isDirectory()) walk(child);
|
||
else if (/\.(ts|tsx)$/.test(entry.name)) {
|
||
const text = readFileSync(child, "utf8");
|
||
for (const line of text.split("\n")) if (/[!=]==?\s*"rectification-v5-matrix-scoring-\d+"/.test(line)) hits.push(`${child.pathname}: ${line.trim()}`);
|
||
}
|
||
}
|
||
};
|
||
for (const root of roots) walk(new URL(root, import.meta.url));
|
||
assert.deepEqual(hits, []);
|
||
});
|
||
|
||
test("SQL guard compares the generation too (migration 20261002010000)", () => {
|
||
const sql = readFileSync(new URL("../supabase/migrations/20261002010000_rectification_dated_algorithm_generation.sql", import.meta.url), "utf8");
|
||
assert.match(sql, /create or replace function public\.validate_dated_rectification_candidate\(/);
|
||
assert.match(sql, /coalesce\(p_result\.algorithm_version, ''\) ~ '\^rectification-v5-matrix-scoring-\(9\|\[1-9\]\[0-9\]\+\)\$'/);
|
||
assert.doesNotMatch(sql, /algorithm_version = 'rectification-v5-matrix-scoring-9'/);
|
||
// Only the function body changes: no table, column, grant or data statements.
|
||
assert.doesNotMatch(sql, /^\s*(alter|drop|insert|update|delete|grant|revoke|create table|create index)\b/im);
|
||
});
|
||
|
||
test("a scoring-9 history opens read-only under scoring-10 and offers a recompare (BUG-621 / BUG-984 pattern)", () => {
|
||
// Real native scoring-9 dated golden as the persisted latest result; only the live identity differs.
|
||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.native.json", import.meta.url), "utf8")).response;
|
||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-9");
|
||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||
assert.ok(dossier);
|
||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-9", "history keeps its own identity");
|
||
const policyVersion = historical.decision_policy_version;
|
||
const underTen = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-10", policyVersion });
|
||
assert.deepEqual([underTen.read_only, underTen.can_recompare, underTen.algorithm_version], [true, true, "rectification-v5-matrix-scoring-9"]);
|
||
const underNine = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-9", policyVersion });
|
||
assert.deepEqual([underNine.read_only, underNine.can_recompare], [false, false]);
|
||
// Its candidates still parse as dated (generation >= 9), so the opened history keeps civil dates.
|
||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||
});
|
||
|
||
test("a scoring-10 history opens read-only under scoring-11 and offers a recompare (BUG-621)", () => {
|
||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-10.native.json", import.meta.url), "utf8")).response;
|
||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-10");
|
||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||
assert.ok(dossier);
|
||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-10");
|
||
const policyVersion = historical.decision_policy_version;
|
||
const underEleven = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-11", policyVersion });
|
||
assert.deepEqual([underEleven.read_only, underEleven.can_recompare, underEleven.algorithm_version], [true, true, "rectification-v5-matrix-scoring-10"]);
|
||
const underTwelve = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-12", policyVersion });
|
||
assert.deepEqual([underTwelve.read_only, underTwelve.can_recompare, underTwelve.algorithm_version], [true, true, "rectification-v5-matrix-scoring-10"]);
|
||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||
});
|
||
|
||
test("a scoring-11 history opens read-only under scoring-12 and offers a recompare (BUG-621)", () => {
|
||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-11.native.json", import.meta.url), "utf8")).response;
|
||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-11");
|
||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||
assert.ok(dossier);
|
||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-11");
|
||
const policyVersion = historical.decision_policy_version;
|
||
const underTwelve = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-12", policyVersion });
|
||
assert.deepEqual([underTwelve.read_only, underTwelve.can_recompare, underTwelve.algorithm_version], [true, true, "rectification-v5-matrix-scoring-11"]);
|
||
const underThirteen = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-13", policyVersion });
|
||
assert.deepEqual([underThirteen.read_only, underThirteen.can_recompare, underThirteen.algorithm_version], [true, true, "rectification-v5-matrix-scoring-11"]);
|
||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||
});
|
||
|
||
test("a scoring-12 history opens read-only under scoring-13 and offers a recompare (BUG-621)", () => {
|
||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-12.native.json", import.meta.url), "utf8")).response;
|
||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-12");
|
||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||
assert.ok(dossier);
|
||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-12");
|
||
const policyVersion = historical.decision_policy_version;
|
||
const underThirteen = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-13", policyVersion });
|
||
assert.deepEqual([underThirteen.read_only, underThirteen.can_recompare, underThirteen.algorithm_version], [true, true, "rectification-v5-matrix-scoring-12"]);
|
||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||
});
|
||
|
||
test("a scoring-13 history opens read-only under scoring-14 and offers a recompare (BUG-621)", () => {
|
||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-13.native.json", import.meta.url), "utf8")).response;
|
||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-13");
|
||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||
assert.ok(dossier);
|
||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-13");
|
||
const policyVersion = historical.decision_policy_version;
|
||
const underFourteen = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-14", policyVersion });
|
||
assert.deepEqual([underFourteen.read_only, underFourteen.can_recompare, underFourteen.algorithm_version], [true, true, "rectification-v5-matrix-scoring-13"]);
|
||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||
});
|