fix(rectification): bump scoring identity to scoring-10 for functional profile v2; dated contract by generation (BUG-1181)
Functional roles feed the *_functional_*_auxiliary rules, so57782aeachanges candidate scores for identical input (memoization fixture 12:00: 8.6274 -> 8.4977). Per the "scoring semantics change => bump ALGORITHM_VERSION" precedent (scoring-7 -> 8 -> 9), the identity moves to scoring-10; policy v3, input contract v5 and Skill versions are unchanged, history is not relabeled. Five frontend sites and one SQL guard tested `=== "...scoring-9"` for the dated candidate-window contract; they now use isDatedScoringAlgorithmVersion / a generation regex (>= 9). Migration 20261002010000 only recreates validate_dated_rectification_candidate (one-line guard change). Memoization golden v2 written by the test's own write_golden; v1 (scoring-8) frozen by sha256. Real-engine scoring-10 cross-midnight golden added. Research records re-frozen per ERR-110 (label functional_v2_2026_10_02) and scripts/functional_benefics.py added to the frozen production identity (ERR-114:57782aeachanged scores without tripping it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
57782aea8a
commit
3733b9787b
@@ -20,6 +20,23 @@ export type DatedCandidateRange = Readonly<{
|
||||
midnight_side_pending?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Scoring generations that carry the dated candidate-window contract. scoring-9
|
||||
* introduced it (BUG-982/983) and every later generation keeps it; compare the
|
||||
* generation number, never one literal, so a version bump (scoring-10 for the
|
||||
* functional benefic / malefic profile v2, BUG-1181) cannot silently turn new
|
||||
* results into legacy same-day ones. Mirrors the SQL guard in
|
||||
* 20261002010000_rectification_dated_algorithm_generation.sql.
|
||||
*/
|
||||
export const FIRST_DATED_SCORING_GENERATION = 9;
|
||||
const SCORING_ALGORITHM_VERSION = /^rectification-v5-matrix-scoring-([1-9][0-9]*)$/;
|
||||
|
||||
export function isDatedScoringAlgorithmVersion(value: unknown): boolean {
|
||||
if (typeof value !== "string") return false;
|
||||
const match = SCORING_ALGORITHM_VERSION.exec(value);
|
||||
return match !== null && Number(match[1]) >= FIRST_DATED_SCORING_GENERATION;
|
||||
}
|
||||
|
||||
export function parseCandidateIntervals(value: unknown): readonly CandidateInterval[] | null {
|
||||
if (!Array.isArray(value) || value.length < 1 || value.length > 2) return null;
|
||||
const rows: CandidateInterval[] = [];
|
||||
|
||||
@@ -26,7 +26,7 @@ import { questionContractVersionIsCompatible } from "./probe-question-contract";
|
||||
import { resolveAyanamsa } from "../../ayanamsa.ts";
|
||||
import { candidateSegmentMetadata } from "./segment-contract.ts";
|
||||
import type { AscendantSignature } from "../core/segment-summary.ts";
|
||||
import { requestCandidateIntervals, readCandidatePosition, type CandidatePosition, type DatedCandidateRange } from "../core/candidate-window.ts";
|
||||
import { isDatedScoringAlgorithmVersion, requestCandidateIntervals, readCandidatePosition, type CandidatePosition, type DatedCandidateRange } from "../core/candidate-window.ts";
|
||||
import { parseBlockScanPayload, type BlockScanBlock } from "./block-scan.ts";
|
||||
import { recordEngineCallTiming, reportTurnProgress } from "./turn-instrumentation.ts";
|
||||
|
||||
@@ -987,7 +987,7 @@ export async function runV9CandidateScore(input: {
|
||||
}): Promise<V9EngineScoreResult> {
|
||||
const data = await postEngine("/api/rectification/v5/score", engineRequestBody(input), 60_000, { busyRetry: true });
|
||||
const datedRange = { ...input.candidateRange, candidate_intervals: requestCandidateIntervals(input.baselineBirthSnapshot, input.candidateRange) };
|
||||
const dated = data.algorithm_version === "rectification-v5-matrix-scoring-9";
|
||||
const dated = isDatedScoringAlgorithmVersion(data.algorithm_version);
|
||||
const metadata = candidateSegmentMetadata(data);
|
||||
const candidates = readCandidates(data.candidate_decisions, datedRange, dated).map((candidate) => ({ ...candidate, ...metadata.byTime.get(candidate.time) }));
|
||||
const receipt = readDecisionReceipt(data.decision_receipt, candidates);
|
||||
@@ -1028,7 +1028,7 @@ export async function runV9Diagnostics(input: {
|
||||
}): Promise<V9EngineDiagnostics> {
|
||||
const data = await postEngine("/api/rectification/v5/diagnostics", engineRequestBody(input));
|
||||
const datedRange = { ...input.candidateRange, candidate_intervals: requestCandidateIntervals(input.baselineBirthSnapshot, input.candidateRange) };
|
||||
const dated = data.algorithm_version === "rectification-v5-matrix-scoring-9";
|
||||
const dated = isDatedScoringAlgorithmVersion(data.algorithm_version);
|
||||
const metadata = candidateSegmentMetadata(data);
|
||||
const candidates = readCandidates(data.candidate_decisions, datedRange, dated).map((candidate) => ({ ...candidate, ...metadata.byTime.get(candidate.time) }));
|
||||
const receipt = readDecisionReceipt(data.decision_receipt, candidates);
|
||||
@@ -1071,7 +1071,7 @@ export async function runV9Diagnostics(input: {
|
||||
// Keep the receipt default aligned with Python's ALGORITHM_VERSION; persisted
|
||||
// receipts retain their original identity (this is not a Skill/open gate).
|
||||
export const v9EngineVersion = (): string =>
|
||||
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-9";
|
||||
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-10";
|
||||
|
||||
export type V9RangeReading = Readonly<{
|
||||
stableThemes: readonly string[];
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
import { shouldForceMinuteAfterSubBlocks } from "./block-scan.ts";
|
||||
import { segmentOrderEnabledFor } from "../core/segment-probe-order.ts";
|
||||
import { isDatedScoringAlgorithmVersion } from "../core/candidate-window.ts";
|
||||
import { scanCaseSegments } from "./segment-scan.ts";
|
||||
import { targetChartsForDomain } from "../core/segment-summary.ts";
|
||||
import { buildProductCaseInferenceState } from "./product-inference.ts";
|
||||
@@ -178,7 +179,7 @@ export async function scoreAndPersistCurrentEvidence(input: {
|
||||
if (!candidateRange) throw new RectificationToolServiceError("case_range_missing");
|
||||
const compute = await loadV9CaseCompute(input.accounting, input.userId, input.caseId);
|
||||
const liveIdentity = await readV9EngineScoringIdentity();
|
||||
if (scoringIdentityIsTrusted(liveIdentity) && liveIdentity.algorithmVersion === "rectification-v5-matrix-scoring-9" && !candidateRange.candidate_intervals) {
|
||||
if (scoringIdentityIsTrusted(liveIdentity) && isDatedScoringAlgorithmVersion(liveIdentity.algorithmVersion) && !candidateRange.candidate_intervals) {
|
||||
candidateRange = await ensureV9DatedWindow(input.accounting, input.userId, input.caseId);
|
||||
}
|
||||
if (candidateRange.midnight_side_pending === true && scoringIdentityIsTrusted(liveIdentity)) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { segmentCandidateFields } from "./segment-contract.ts";
|
||||
import { parseSavedCivilFields } from "./saved-civil-fields.ts";
|
||||
import { candidatePositionFields, readCandidatePosition, parseCandidateIntervals, type CandidatePosition, type DatedCandidateRange } from "../core/candidate-window.ts";
|
||||
import { candidatePositionFields, isDatedScoringAlgorithmVersion, readCandidatePosition, parseCandidateIntervals, type CandidatePosition, type DatedCandidateRange } from "../core/candidate-window.ts";
|
||||
import { assertV9ResultWritable, assertV9WindowWritable } from "./result-identity.ts";
|
||||
import type { AdoptedCredibleRangePayload } from "../../report-candidate-range.ts";
|
||||
import {
|
||||
@@ -530,7 +530,7 @@ export function parseV9CandidateSnapshot(value: unknown): V9CandidateSnapshot |
|
||||
|| tiedMinuteCount === null || !Number.isInteger(tiedMinuteCount) || tiedMinuteCount < 1
|
||||
) return null;
|
||||
const position = readCandidatePosition(candidate);
|
||||
if ((row.algorithm_version === "rectification-v5-matrix-scoring-9" || candidate.candidate_date !== undefined) && !position) return null;
|
||||
if ((isDatedScoringAlgorithmVersion(row.algorithm_version) || candidate.candidate_date !== undefined) && !position) return null;
|
||||
seenIds.add(candidateId);
|
||||
candidates.push({
|
||||
candidateId,
|
||||
@@ -633,7 +633,7 @@ export async function loadV9CaseDossier(
|
||||
? { resultId: latest.resultId, candidateId, date, time, contract: "segment-v1" as const } : null;
|
||||
// The old confirmation SQL uses the persisted baseline date for a wholly legacy contract.
|
||||
// Do not infer that civil date from the scan, current profile or a matching clock.
|
||||
const legacy = segmentSelection && latest.algorithmVersion !== "rectification-v5-matrix-scoring-9"
|
||||
const legacy = segmentSelection && !isDatedScoringAlgorithmVersion(latest.algorithmVersion)
|
||||
&& !Object.hasOwn(latest.decisionReceipt ?? {}, "candidate_window_contract")
|
||||
&& !Object.hasOwn(latest.decisionReceipt ?? {}, "candidate_intervals")
|
||||
&& latest.candidates.every(candidate => candidate.candidate_date === undefined)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readCandidatePosition, parseWindowIntervals, type CandidatePosition, type WindowInterval } from "./rectification-agentic/core/candidate-window.ts";
|
||||
import { isDatedScoringAlgorithmVersion, readCandidatePosition, parseWindowIntervals, type CandidatePosition, type WindowInterval } from "./rectification-agentic/core/candidate-window.ts";
|
||||
import type {
|
||||
DashaAgreement,
|
||||
EventDashaLedgerRow,
|
||||
@@ -370,7 +370,7 @@ export function parseRectificationCandidateResult(value: unknown): Rectification
|
||||
const clusterStart = time(candidate.clusterStart ?? candidate.cluster_start);
|
||||
const clusterEnd = time(candidate.clusterEnd ?? candidate.cluster_end);
|
||||
const position = readCandidatePosition(candidate);
|
||||
if ((candidate.candidate_date !== undefined || snapshot.algorithmVersion === "rectification-v5-matrix-scoring-9") && !position) return null;
|
||||
if ((candidate.candidate_date !== undefined || isDatedScoringAlgorithmVersion(snapshot.algorithmVersion)) && !position) return null;
|
||||
candidates.push({
|
||||
...(position ?? {}),
|
||||
...(position?.cluster_intervals ? { clusterIntervals: position.cluster_intervals } : {}),
|
||||
|
||||
Reference in New Issue
Block a user