Files
Jyotisha/frontend/tests/rectification-dated-algorithm-generation-20261002.test.ts
T
ClaudeandClaude Opus 5.5 f61d279aec fix(rectification): bump scoring identity to scoring-15 for #166 batch 2 (BUG-1590)
4b223eb8 makes Gemini Saturn (8, 9) and Virgo Saturn (5, 6) functionally
mixed; the *_functional_* rules read the functional layer, so the same input
scores differently for those ascendants. Per BUG-1181:
rectification-v5-matrix-scoring-14 -> -15. Policy v4, input contract v5 and
the dated window contract unchanged; the SQL guard already accepts it.

- Memoization golden v8 (source_commit 4b223eb8); v7 pinned by sha256. This
  public request's candidate scores are unchanged.
- scoring-15 cross-midnight golden (independent-process A/B 3 x 7 equal);
  scoring-14 golden kept as history and opens read-only with recompare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
2026-11-04 22:29:06 +08:00

145 lines
12 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-15", "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-15", (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)改变同输入分数。
// 原值: ...-scoring-14;新值: ...-scoring-15;原因: #166 第二批(第 4 条:双子 / 处女上升土星改为混合,BUG-1590)改变同输入分数。
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-15");
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-15"$/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-14 history opens read-only under scoring-15 and offers a recompare (BUG-621)", () => {
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-14.native.json", import.meta.url), "utf8")).response;
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-14");
const latest = { ...historical, candidates: historical.candidate_decisions };
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
assert.ok(dossier);
const view = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-15", policyVersion: historical.decision_policy_version });
assert.deepEqual([view.read_only, view.can_recompare, view.algorithm_version], [true, true, "rectification-v5-matrix-scoring-14"]);
});
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"));
});