Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/engine-client.ts
T
Jesse_ChenandCursor 814c924e4a
Independent Staging Quality Gate / validate (push) Successful in 9m20s
Independent Staging Quality Gate / publish (push) Successful in 6m51s
fix(rectification): exhaustion exit, explain layer, range reading, unknown-time scan (BUG-565–568)
Keep askable cards after exhaustion, explain each probe, read the adopted credible range in reports and chat, and compare declared periods before the minute grid when the clock is unknown.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 09:10:37 +08:00

772 lines
30 KiB
TypeScript

/**
* V9 deterministic engine client (Python, server-side only).
*
* The Python service owns Event Contract v2 normalization, candidate ranking,
* decision policy and the execution ledger. This client validates and projects
* that server contract; it never reconstructs rank, support, ties, confidence,
* permissions or executed methods from raw scores or event domains.
*/
import {
evidenceSubjectForDomain,
isBackgroundEvidenceKind,
isEvidenceDomain,
isEvidenceKind,
type EvidenceKind,
} from "./evidence-model";
import {
isPublicRectificationMethod,
type PublicRectificationMethod,
} from "./public-receipt";
import {
parseWindowScan,
type WindowScan,
} from "./varga-observations";
import { questionContractVersionIsCompatible } from "./probe-question-contract";
import { resolveAyanamsa } from "../../ayanamsa.ts";
import { parseBlockScanPayload, type BlockScanBlock } from "./block-scan.ts";
export class RectificationEngineError extends Error {
readonly code: string;
constructor(code: string, message: string) {
super(message);
this.name = "RectificationEngineError";
this.code = code;
}
}
export type V9EngineEvent = Readonly<{
id: string;
domain: string;
event_kind: string;
date_start: string;
date_end: string;
precision: "day" | "month" | "quarter" | "year" | "range";
summary: string;
date_source?: string | null;
date_reliability?: string | null;
date_corroboration?: string | null;
date_conflict_status?: string | null;
source_turn_id?: string | null;
subject?: string | null;
}>;
export type V9EngineCandidate = Readonly<{
candidateId: string;
time: string;
rank: number;
relativeSupport: number;
tiedMinuteCount: number;
}>;
export type V9DecisionReceipt = Readonly<Record<string, unknown>>;
export type V9ExecutionLedger = readonly Readonly<Record<string, unknown>>[];
export type V9EngineScoreResult = Readonly<{
engineResultId: string;
algorithmVersion: string;
eventContractVersion: string;
policyVersion: string;
candidateRange: { start_time: string; end_time: string };
candidates: readonly V9EngineCandidate[];
overallConfidence: "low" | "medium" | "high";
marginPercent: number | null;
acceptanceAllowed: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
confirmationAllowed: boolean;
representativeCandidateId: string | null;
representativeTime: string | null;
decisionReceipt: V9DecisionReceipt;
executionLedger: V9ExecutionLedger;
executedMethods: readonly PublicRectificationMethod[];
windowScan: WindowScan | null;
}>;
export type V9EngineDiagnostics = Readonly<{
algorithmVersion: string;
engineResultId: string;
eventContractVersion: string;
policyVersion: string;
diagnostics: Readonly<Record<string, unknown>>;
missingLayers: readonly string[];
canConfirmExactMinute: boolean;
decisionReceipt: V9DecisionReceipt;
executionLedger: V9ExecutionLedger;
executedMethods: readonly PublicRectificationMethod[];
}>;
const EVENT_CONTRACT_VERSION = "rectification-event-contract-v2";
const RECEIPT_VERSION = "candidate-decision-receipt-v2";
const EXECUTION_LEDGER_VERSION = "rectification-execution-ledger-v2";
const timePattern = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
function record(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
function engineNumber(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function clockMinute(value: string): number {
const [hour = 0, minute = 0] = value.split(":").map(Number);
return hour * 60 + minute;
}
function timeInRange(time: string, range: { start_time: string; end_time: string }): boolean {
const value = clockMinute(time);
const start = clockMinute(range.start_time);
const end = clockMinute(range.end_time);
return end >= start ? value >= start && value <= end : value >= start || value <= end;
}
/** Map a V9 evidence date precision to the Event Contract v2 vocabulary. */
export function enginePrecision(precision: string): V9EngineEvent["precision"] {
if (precision === "day" || precision === "month" || precision === "quarter" || precision === "range") {
return precision;
}
return "year";
}
export function toEngineEvents(
evidence: readonly Readonly<{
id: string;
eventKind: string;
domain: string;
occurredFrom: string | null;
occurredTo: string | null;
datePrecision: string;
summary: string;
dateSource?: string | null;
dateReliability?: string | null;
dateCorroboration?: string | null;
dateConflictStatus?: string | null;
sourceTurnId?: string | null;
subject?: string | null;
}>[],
): V9EngineEvent[] {
return evidence.flatMap((item): V9EngineEvent[] => {
if (!isEvidenceKind(item.eventKind) || !isEvidenceDomain(item.domain)) return [];
if (isBackgroundEvidenceKind(item.eventKind as EvidenceKind) && item.eventKind !== "horary_query") return [];
const start = item.occurredFrom ?? item.occurredTo;
const end = item.occurredTo ?? item.occurredFrom;
if (!start) return [];
return [{
id: item.id,
domain: item.domain,
event_kind: item.eventKind,
date_start: start.slice(0, 10),
date_end: (end ?? start).slice(0, 10),
precision: enginePrecision(item.datePrecision),
summary: item.summary,
date_source: item.dateSource ?? null,
date_reliability: item.dateReliability ?? null,
date_corroboration: item.dateCorroboration ?? null,
date_conflict_status: item.dateConflictStatus ?? null,
source_turn_id: item.sourceTurnId ?? null,
subject: evidenceSubjectForDomain(item.domain, item.subject),
}];
});
}
function engineBase(): string {
return process.env.JYOTISH_API_BASE?.trim() || "http://127.0.0.1:5200";
}
async function readEngineJson(path: string, init: RequestInit, timeoutMs: number): Promise<Record<string, unknown>> {
const response = await fetch(`${engineBase()}${path}`, {
...init,
signal: AbortSignal.timeout(timeoutMs),
});
const data = await response.json().catch(() => null);
if (!response.ok) {
const message = data && typeof data === "object"
? String((data as Record<string, unknown>).error ?? (data as Record<string, unknown>).message ?? `Jyotish API ${response.status}`)
: `Jyotish API ${response.status}`;
throw new RectificationEngineError("engine_request_failed", message);
}
if (!data || typeof data !== "object") {
throw new RectificationEngineError("engine_invalid_response", "Jyotish API returned an invalid response");
}
return data as Record<string, unknown>;
}
async function postEngine(path: string, body: unknown, timeoutMs = 60_000): Promise<Record<string, unknown>> {
return readEngineJson(path, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}, timeoutMs);
}
async function getEngine(path: string, timeoutMs = 5_000): Promise<Record<string, unknown>> {
return readEngineJson(path, {
method: "GET",
headers: { accept: "application/json" },
}, timeoutMs);
}
/** Coarse product id is not a scoring identity; never treat it as live algorithm. */
const COARSE_ENGINE_PRODUCT_ID = "rectification-v5";
export type LiveEngineScoringIdentity = Readonly<{
algorithmVersion: string | null;
policyVersion: string | null;
}>;
export function liveEngineScoringIdentityFromEnv(
env: NodeJS.ProcessEnv | Record<string, string | undefined> = process.env,
): LiveEngineScoringIdentity {
const engine = env.RECTIFICATION_ENGINE_VERSION?.trim() || "";
const algorithm = env.RECTIFICATION_ALGORITHM_VERSION?.trim()
|| (engine && engine !== COARSE_ENGINE_PRODUCT_ID ? engine : "");
const policy = env.RECTIFICATION_DECISION_POLICY_VERSION?.trim() || "";
return {
algorithmVersion: algorithm || null,
policyVersion: policy || null,
};
}
export function scoringIdentityFromEnginePayload(
payload: Readonly<Record<string, unknown>> | null | undefined,
): LiveEngineScoringIdentity {
const algorithm = typeof payload?.algorithm_version === "string" ? payload.algorithm_version.trim() : "";
const policy = typeof payload?.decision_policy_version === "string"
? payload.decision_policy_version.trim()
: "";
return {
algorithmVersion: algorithm || null,
policyVersion: policy || null,
};
}
export function cachedEngineScoreIsReusable(
stored: {
evidenceLedgerFingerprint?: string | null;
candidateRangeFingerprint?: string | null;
algorithmVersion?: string | null;
policyVersion?: string | null;
} | null | undefined,
fingerprints: { evidenceLedgerFingerprint: string; candidateRangeFingerprint: string },
live: LiveEngineScoringIdentity,
): boolean {
if (!stored) return false;
if (stored.evidenceLedgerFingerprint !== fingerprints.evidenceLedgerFingerprint) return false;
if (stored.candidateRangeFingerprint !== fingerprints.candidateRangeFingerprint) return false;
if (live.policyVersion && stored.policyVersion && stored.policyVersion !== live.policyVersion) {
return false;
}
if (live.algorithmVersion && stored.algorithmVersion && stored.algorithmVersion !== live.algorithmVersion) {
return false;
}
return true;
}
export async function readV9EngineScoringIdentity(): Promise<LiveEngineScoringIdentity> {
const fromEnv = liveEngineScoringIdentityFromEnv();
if (fromEnv.policyVersion || fromEnv.algorithmVersion) return fromEnv;
try {
return scoringIdentityFromEnginePayload(await getEngine("/api/rectification/v5/versions"));
} catch {
return { algorithmVersion: null, policyVersion: null };
}
}
function readCandidates(
value: unknown,
range: { start_time: string; end_time: string },
): V9EngineCandidate[] {
if (!Array.isArray(value) || value.length === 0) {
throw new RectificationEngineError("engine_invalid_candidate_decisions", "engine candidate decisions are missing");
}
const seenIds = new Set<string>();
const seenTimes = new Set<string>();
const candidates: V9EngineCandidate[] = [];
for (const item of value) {
const row = record(item);
const candidateId = row?.candidate_id;
const time = row?.time;
const rank = row?.rank;
const relativeSupport = row?.relative_support;
const tiedMinuteCount = row?.tied_minute_count;
if (
typeof candidateId !== "string" || !uuidPattern.test(candidateId)
|| typeof time !== "string" || !timePattern.test(time) || !timeInRange(time, range)
|| typeof rank !== "number" || !Number.isInteger(rank) || rank < 1
|| typeof relativeSupport !== "number" || !Number.isInteger(relativeSupport) || relativeSupport < 0 || relativeSupport > 100
|| typeof tiedMinuteCount !== "number" || !Number.isInteger(tiedMinuteCount) || tiedMinuteCount < 1
|| seenIds.has(candidateId) || seenTimes.has(time)
) {
throw new RectificationEngineError("engine_invalid_candidate_decisions", "engine candidate decisions are invalid");
}
seenIds.add(candidateId);
seenTimes.add(time);
candidates.push({ candidateId, time, rank, relativeSupport, tiedMinuteCount });
}
return candidates;
}
type ParsedReceipt = Readonly<{
raw: V9DecisionReceipt;
eventContractVersion: string;
policyVersion: string;
acceptanceAllowed: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
confirmationAllowed: boolean;
representativeCandidateId: string | null;
representativeTime: string | null;
overallConfidence: "low" | "medium" | "high";
marginPercent: number | null;
}>;
function invalidReceipt(): never {
throw new RectificationEngineError("engine_invalid_v2_receipt", "engine decision receipt is missing or invalid");
}
function readDecisionReceipt(value: unknown, candidates: readonly V9EngineCandidate[]): ParsedReceipt {
const row = record(value);
if (!row) return invalidReceipt();
const representativeCandidateId = row.representative_candidate_id;
const representativeTime = row.representative_time;
const confidence = row.overall_confidence;
const marginPercent = row.margin_percent === null || row.margin_percent === undefined
? null
: engineNumber(row.margin_percent);
if (
row.receipt_version !== RECEIPT_VERSION
|| row.contract_version !== "v2"
|| row.event_contract_version !== EVENT_CONTRACT_VERSION
|| typeof row.policy_version !== "string" || !row.policy_version
|| typeof row.decision_policy_version !== "string" || row.decision_policy_version !== row.policy_version
|| typeof row.display_allowed !== "boolean"
|| typeof row.selection_allowed !== "boolean"
|| typeof row.acceptance_allowed !== "boolean"
|| typeof row.propose_allowed !== "boolean"
|| typeof row.confirmation_allowed !== "boolean"
|| typeof row.accept_allowed !== "boolean"
|| typeof row.confirm_allowed !== "boolean"
|| row.selection_allowed !== row.acceptance_allowed
|| row.selection_allowed !== row.accept_allowed
|| row.confirmation_allowed !== row.confirm_allowed
|| (representativeCandidateId !== null && (typeof representativeCandidateId !== "string" || !uuidPattern.test(representativeCandidateId)))
|| (representativeTime !== null && (typeof representativeTime !== "string" || !timePattern.test(representativeTime)))
|| (confidence !== "low" && confidence !== "medium" && confidence !== "high")
|| (row.margin_percent !== null && row.margin_percent !== undefined && marginPercent === null)
) {
return invalidReceipt();
}
const representative = representativeCandidateId === null
? null
: candidates.find((candidate) => candidate.candidateId === representativeCandidateId) ?? null;
if (
(representativeCandidateId === null) !== (representativeTime === null)
|| (representativeCandidateId !== null && (!representative || representative.time !== representativeTime))
|| (row.confirmation_allowed === true && row.selection_allowed !== true)
|| (row.selection_allowed === true && row.display_allowed !== true)
|| (row.propose_allowed === true && row.selection_allowed !== true)
|| !questionContractVersionIsCompatible(row.question_contract_version ?? row.question_contract)
) {
return invalidReceipt();
}
return {
raw: row,
eventContractVersion: EVENT_CONTRACT_VERSION,
policyVersion: row.policy_version,
acceptanceAllowed: row.acceptance_allowed,
selectionAllowed: row.selection_allowed,
proposeAllowed: row.propose_allowed,
confirmationAllowed: row.confirmation_allowed,
representativeCandidateId,
representativeTime,
overallConfidence: confidence,
marginPercent,
};
}
function readExecutionLedger(version: unknown, value: unknown): V9ExecutionLedger {
if (version !== EXECUTION_LEDGER_VERSION || !Array.isArray(value) || value.length === 0) {
throw new RectificationEngineError("engine_invalid_execution_ledger", "engine execution ledger is missing or invalid");
}
return value.map((item) => {
const row = record(item);
if (
!row
|| row.ledger_version !== EXECUTION_LEDGER_VERSION
|| typeof row.stage !== "string" || !row.stage
|| (row.status !== "executed" && row.status !== "not_executed" && row.status !== "retained_not_scored")
|| typeof row.source !== "string" || !row.source
) {
throw new RectificationEngineError("engine_invalid_execution_ledger", "engine execution ledger is missing or invalid");
}
return row;
});
}
function publicMethodFromLedger(value: unknown): PublicRectificationMethod | null {
if (isPublicRectificationMethod(value)) return value;
if (typeof value !== "string") return null;
const normalized = value.toLowerCase();
if (normalized.includes("controlled_transit") || normalized.includes("gochara")) return "gochara";
if (normalized.includes("ashtakavarga")) return "ashtakavarga";
if (normalized.includes("shadbala")) return "shadbala";
if (normalized.includes("arudha")) return "arudha-pada";
if (normalized.includes("functional_benefic") || normalized.includes("functional_malefic")) return "functional-benefic-malefic";
if (normalized.includes("vimshottari") || normalized.includes("vimsottari")) return "vimshottari-dasha";
if (normalized.includes("narayana")) return "narayana-dasha";
return null;
}
function executedMethods(ledger: V9ExecutionLedger): PublicRectificationMethod[] {
const methods = new Set<PublicRectificationMethod>();
for (const entry of ledger) {
if (entry.status !== "executed") continue;
const direct = publicMethodFromLedger(entry.method);
if (direct) methods.add(direct);
if (Array.isArray(entry.technique_layers)) {
for (const layer of entry.technique_layers) {
const method = publicMethodFromLedger(layer);
if (method) methods.add(method);
}
}
}
return [...methods];
}
export function executedMethodsFromLedger(
ledger: V9ExecutionLedger | null | undefined,
): PublicRectificationMethod[] {
if (!ledger) return [];
return executedMethods(ledger);
}
function engineDiagnostics(data: Record<string, unknown>): Readonly<Record<string, unknown>> {
return record(data.diagnostics) ?? {};
}
function engineRequestBody(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string };
events: readonly V9EngineEvent[];
askedProbeKeys?: readonly string[];
}): Record<string, unknown> {
const snapshot = input.baselineBirthSnapshot;
const birthDate = String(snapshot.birth_date ?? "");
const lat = engineNumber(snapshot.latitude);
const lon = engineNumber(snapshot.longitude);
const tz = engineNumber(snapshot.timezone_offset);
if (!birthDate || lat === null || lon === null || tz === null) {
throw new RectificationEngineError("engine_profile_incomplete", "server profile snapshot is incomplete");
}
if (input.events.length === 0) {
throw new RectificationEngineError("no_scorable_evidence", "no scorable evidence for the engine");
}
return {
birth_date: birthDate,
start_time: input.candidateRange.start_time,
end_time: input.candidateRange.end_time,
lat,
lon,
tz,
ayanamsa: resolveAyanamsa(snapshot),
node_mode: "mean",
events: input.events,
birth_time_source: snapshot.birth_time_source,
timezone_id: snapshot.timezone_id,
timezone_source: snapshot.timezone_source,
local_time_status: snapshot.local_time_status,
...(input.askedProbeKeys?.length ? { asked_probe_keys: [...input.askedProbeKeys] } : {}),
};
}
export type V9VedastroValidateResult = Readonly<{
status: "passed" | "failed" | "not_evaluated";
canConfirmExactMinute: boolean;
minuteSensitiveStatus: "passed" | "failed" | "not_evaluated";
searchEventsSupportsLocalWinner: boolean;
failure: Readonly<{
code: "timeout" | "engine_request_failed" | "engine_invalid_response" | "unknown";
}> | null;
raw: Readonly<Record<string, unknown>>;
}>;
function vedastroValidateStatus(value: unknown): "passed" | "failed" | "not_evaluated" {
if (value === "pass" || value === "passed") return "passed";
if (value === "fail" || value === "failed") return "failed";
return "not_evaluated";
}
export function mergeVedastroValidateIntoReceipt(
receipt: Readonly<Record<string, unknown>>,
validation: V9VedastroValidateResult,
): Record<string, unknown> {
const next = { ...receipt };
const gates = record(next.gates) ? { ...record(next.gates)! } : {};
const exact = record(gates.exact_confirmation) ? { ...record(gates.exact_confirmation)! } : {};
exact.vedastro_event_validation = {
status: validation.status,
search_events_primary_supports_local_winner: validation.searchEventsSupportsLocalWinner,
can_confirm_exact_minute: validation.canConfirmExactMinute,
failure: validation.failure,
};
const minuteSensitiveStatus = validation.status === "passed"
? validation.minuteSensitiveStatus
: validation.status === "failed"
? "failed"
: "not_evaluated";
if (
(minuteSensitiveStatus === "passed" || minuteSensitiveStatus === "failed")
&& (exact.external_validation_status === "not_evaluated" || exact.external_validation_status == null)
) {
exact.external_validation_status = minuteSensitiveStatus;
}
gates.exact_confirmation = exact;
next.gates = gates;
next.unique_minute_claim = false;
return next;
}
export async function runV9VedastroValidate(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string };
events: readonly V9EngineEvent[];
candidateTimes: readonly [string, string];
}): Promise<V9VedastroValidateResult> {
const unevaluated = (): V9VedastroValidateResult => ({
status: "not_evaluated",
canConfirmExactMinute: false,
minuteSensitiveStatus: "not_evaluated",
searchEventsSupportsLocalWinner: false,
failure: null,
raw: { status: "not_evaluated", can_confirm_exact_minute: false },
});
if (input.candidateTimes[0] === input.candidateTimes[1]) return unevaluated();
try {
const data = await postEngine(
"/api/rectification/v5/vedastro-validate",
{ ...engineRequestBody(input), candidate_times: [...input.candidateTimes] },
12_000,
);
const eventValidation = record(data.event_validation);
const minuteValidation = record(data.minute_sensitive_validation);
const status = vedastroValidateStatus(data.status);
const minuteSensitiveStatus = status === "passed"
? vedastroValidateStatus(minuteValidation?.status)
: status === "failed"
? "failed"
: "not_evaluated";
return {
status,
canConfirmExactMinute: data.can_confirm_exact_minute === true
&& status === "passed"
&& minuteSensitiveStatus === "passed",
minuteSensitiveStatus,
searchEventsSupportsLocalWinner: eventValidation?.search_events_primary_supports_local_winner === true,
failure: null,
raw: data,
};
} catch (error) {
const code = error instanceof RectificationEngineError
? error.code === "engine_invalid_response"
? "engine_invalid_response"
: "engine_request_failed"
: error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError")
? "timeout"
: "unknown";
return {
status: "failed",
canConfirmExactMinute: false,
minuteSensitiveStatus: "failed",
searchEventsSupportsLocalWinner: false,
failure: { code },
raw: { status: "failed", failure: { code }, can_confirm_exact_minute: false },
};
}
}
export async function runV9CandidateScore(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string };
events: readonly V9EngineEvent[];
askedProbeKeys?: readonly string[];
}): Promise<V9EngineScoreResult> {
const data = await postEngine("/api/rectification/v5/score", engineRequestBody(input));
const candidates = readCandidates(data.candidate_decisions, input.candidateRange);
const receipt = readDecisionReceipt(data.decision_receipt, candidates);
const ledger = readExecutionLedger(data.execution_ledger_version, data.execution_ledger);
if (
data.event_contract_version !== receipt.eventContractVersion
|| data.decision_policy_version !== receipt.policyVersion
) {
return invalidReceipt();
}
return {
engineResultId: String(data.result_id ?? ""),
algorithmVersion: String(data.algorithm_version ?? ""),
eventContractVersion: receipt.eventContractVersion,
policyVersion: receipt.policyVersion,
candidateRange: input.candidateRange,
candidates,
overallConfidence: receipt.overallConfidence,
marginPercent: receipt.marginPercent,
acceptanceAllowed: receipt.acceptanceAllowed,
selectionAllowed: receipt.selectionAllowed,
proposeAllowed: receipt.proposeAllowed,
confirmationAllowed: receipt.confirmationAllowed,
representativeCandidateId: receipt.representativeCandidateId,
representativeTime: receipt.representativeTime,
decisionReceipt: receipt.raw,
executionLedger: ledger,
executedMethods: executedMethods(ledger),
windowScan: parseWindowScan(engineDiagnostics(data).window_scan),
};
}
export async function runV9Diagnostics(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string };
events: readonly V9EngineEvent[];
askedProbeKeys?: readonly string[];
}): Promise<V9EngineDiagnostics> {
const data = await postEngine("/api/rectification/v5/diagnostics", engineRequestBody(input));
const candidates = readCandidates(data.candidate_decisions, input.candidateRange);
const receipt = readDecisionReceipt(data.decision_receipt, candidates);
const ledger = readExecutionLedger(data.execution_ledger_version, data.execution_ledger);
if (
data.event_contract_version !== receipt.eventContractVersion
|| data.decision_policy_version !== receipt.policyVersion
) {
return invalidReceipt();
}
const diagnostics = engineDiagnostics(data);
const missingLayers = Array.isArray(data.missing_layers)
? data.missing_layers.filter((item): item is string => typeof item === "string")
: [];
return {
algorithmVersion: String(data.algorithm_version ?? ""),
engineResultId: String(data.result_id ?? ""),
eventContractVersion: receipt.eventContractVersion,
policyVersion: receipt.policyVersion,
diagnostics: {
primary_cluster_retention_rate: diagnostics.primary_cluster_retention_rate,
leave_one_event_out_retention_rate: diagnostics.leave_one_event_out_retention_rate,
leave_one_domain_out_retention_rate: diagnostics.leave_one_domain_out_retention_rate,
date_sensitivity_retention_rate: diagnostics.date_sensitivity_retention_rate,
neighbor_support_minutes: diagnostics.neighbor_support_minutes,
primary_secondary_margin_percent: diagnostics.primary_secondary_margin_percent,
unstable_event_ids: diagnostics.unstable_event_ids,
most_discriminating_layers: diagnostics.most_discriminating_layers,
candidate_splits: diagnostics.candidate_splits,
window_scan: parseWindowScan(diagnostics.window_scan),
},
missingLayers,
canConfirmExactMinute: receipt.confirmationAllowed,
decisionReceipt: receipt.raw,
executionLedger: ledger,
executedMethods: executedMethods(ledger),
};
}
export const v9EngineVersion = (): string =>
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5";
export type V9RangeReading = Readonly<{
stableThemes: readonly string[];
sensitiveThemes: readonly string[];
claimBoundary: string | null;
themeSensitivity: Readonly<Record<string, unknown>>;
window: Readonly<Record<string, unknown>> | null;
}>;
export async function runV9RangeReading(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string; representative_time?: string };
representativeTime?: string | null;
birthTimeAccuracy?: "provisional" | "approximate";
}): Promise<V9RangeReading | null> {
try {
const snapshot = input.baselineBirthSnapshot;
const birthDate = String(snapshot.birth_date ?? "");
const lat = engineNumber(snapshot.latitude);
const lon = engineNumber(snapshot.longitude);
const tz = engineNumber(snapshot.timezone_offset);
if (!birthDate || lat === null || lon === null || tz === null) return null;
const representative = input.representativeTime
?? input.candidateRange.representative_time
?? input.candidateRange.start_time;
const data = await postEngine("/api/rectification/v5/range_reading", {
birth_date: birthDate,
lat,
lon,
tz,
ayanamsa: resolveAyanamsa(snapshot),
node_mode: "mean",
birth_time_accuracy: input.birthTimeAccuracy ?? "provisional",
representative_time: representative,
candidate_range: {
start_time: input.candidateRange.start_time,
end_time: input.candidateRange.end_time,
representative_time: representative,
},
}, 8_000);
const stable = Array.isArray(data.stable_themes)
? data.stable_themes.filter((item): item is string => typeof item === "string")
: [];
const sensitive = Array.isArray(data.sensitive_themes)
? data.sensitive_themes.filter((item): item is string => typeof item === "string")
: [];
return {
stableThemes: stable,
sensitiveThemes: sensitive,
claimBoundary: typeof data.claim_boundary === "string" ? data.claim_boundary : null,
themeSensitivity: record(data.theme_sensitivity) ?? {},
window: record(data.window),
};
} catch {
return null;
}
}
export type V9BlockScanResult = Readonly<{
resultId: string | null;
algorithmVersion: string;
minuteStep: number;
blocks: readonly BlockScanBlock[];
}>;
export async function runV9BlockScan(input: {
baselineBirthSnapshot: Readonly<Record<string, unknown>>;
candidateRange: { start_time: string; end_time: string };
events: readonly V9EngineEvent[];
}): Promise<V9BlockScanResult> {
const data = await postEngine("/api/rectification/v5/block_scan", {
...engineRequestBody(input),
minute_step: 10,
});
const parsed = parseBlockScanPayload({
blocks: data.blocks,
algorithm_version: data.algorithm_version,
minute_step: data.minute_step,
evidence_ledger_fingerprint: data.evidence_ledger_fingerprint,
});
if (!parsed) {
throw new RectificationEngineError("engine_invalid_response", "block_scan payload is invalid");
}
return {
resultId: typeof data.result_id === "string" ? data.result_id : null,
algorithmVersion: parsed.algorithmVersion ?? String(data.algorithm_version ?? ""),
minuteStep: parsed.minuteStep ?? 10,
blocks: parsed.blocks,
};
}