/* RFC-1078: Pure freshness calculation functions for semantic.freshness.validate. Provides calculateAgeDays, isAssessmentStale, isItemStale, and the FreshnessViolation interface. All functions are pure and deterministic for a fixed reference date. Does not compile PBP profiles — that is the validator's job in werkstatt-site. Does not emit diagnostics — the validator collects violations and emits them. RFC-1078: initial — calculateAgeDays, isAssessmentStale, isItemStale, FreshnessViolation, DEFAULT_FRESHNESS_WINDOW_DAYS. */ /** RFC-1078: Default freshness window for evidence items without a declared maxAgeDays. */ export const DEFAULT_FRESHNESS_WINDOW_DAYS = 180; /** RFC-1078: A freshness violation detected by semantic.freshness.validate. */ export interface FreshnessViolation { rule: string; entityType: string; entityId: string; /** The field that is stale: assessment.observedAt, items[].retrievedAt, etc. */ field: string; /** The observed timestamp. */ observedAt: string; /** The maximum age in days. */ maxAgeDays: number; /** The actual age in days at validation time. */ actualAgeDays: number; message: string; } /** * RFC-1078: Calculate age in days from an ISO timestamp to a reference date. * Pure and deterministic for a fixed reference date. * Returns Number.MAX_SAFE_INTEGER for invalid date strings. */ export function calculateAgeDays(observedAt: string, referenceDate: Date = new Date()): number { const observed = new Date(observedAt); if (isNaN(observed.getTime())) return Number.MAX_SAFE_INTEGER; const diffMs = referenceDate.getTime() - observed.getTime(); return Math.floor(diffMs / (1000 * 60 * 60 * 24)); } /** RFC-1078: Check if a technical assessment is stale. Pure. */ export function isAssessmentStale( assessment: { observedAt: string; freshness: { maxAgeDays: number } }, referenceDate: Date = new Date(), ): boolean { const ageDays = calculateAgeDays(assessment.observedAt, referenceDate); return ageDays > assessment.freshness.maxAgeDays; } /** RFC-1078: Check if an evidence item is stale. Pure. Returns false if retrievedAt is missing. */ export function isItemStale( item: { retrievedAt?: string }, maxAgeDays: number, referenceDate: Date = new Date(), ): boolean { if (!item.retrievedAt) return false; const ageDays = calculateAgeDays(item.retrievedAt, referenceDate); return ageDays > maxAgeDays; }