Files
Jyotisha/frontend/tests/rectification-dated-algorithm-generation-20261002.test.ts
T
ClaudeandClaude Opus 5.5 1ce4eec53c fix(rectification): bump scoring identity to scoring-14 for the #166 rulings (BUG-1583..1588)
78f98eba changes 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_commit 78f98eba); 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
2026-11-04 21:10:43 +08:00

134 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"));
});