import { MAX_ERROR_COUNT } from '../utils/constants'; /** * The policy that decides what happens to a Feed after a fetch attempt: * how to categorize an error message, when to auto-deactivate the Feed * (ADR-0009: two-threshold deactivation), and how to reset on success. */ export type ErrorCategory = 'permanent' | 'transient' | 'unknown'; /** Permanent errors deactivate after this many consecutive failures. */ export const PERMANENT_ERROR_THRESHOLD = 3; /** Transient and unknown errors share the higher (recoverable) threshold. */ export const TRANSIENT_ERROR_THRESHOLD = MAX_ERROR_COUNT; const PERMANENT_PATTERN = /HTTP 404|HTTP 410|HTTP 403|Feed validation failed|Unable to identify feed|Attribute without value|Invalid character/i; const TRANSIENT_PATTERN = /HTTP 5\d\d|HTTP 429|timed out|aborted|Unable to connect|unable to resolve|certificate|ECONNREFUSED|too large/i; /** * Classify a fetch error message. Permanent errors mean the Feed is unlikely * to recover (gone, broken, malformed). Transient errors are recoverable * (server hiccups, network blips). Unknown means we don't have a rule yet — * treated as recoverable for threshold purposes. */ export function categorizeError(message: string): ErrorCategory { if (PERMANENT_PATTERN.test(message)) return 'permanent'; if (TRANSIENT_PATTERN.test(message)) return 'transient'; return 'unknown'; } export interface ErrorPolicy { category: ErrorCategory; threshold: number; } /** * Return persistence policy for an already classified error. Error-count * advancement remains an atomic SQL concern in the Feed persistence seam. */ export function policyForErrorCategory(category: ErrorCategory): ErrorPolicy { const threshold = category === 'permanent' ? PERMANENT_ERROR_THRESHOLD : TRANSIENT_ERROR_THRESHOLD; return { category, threshold }; } /** * State to apply to a Feed row after a successful fetch — clears the error * field and resets the consecutive-error counter. */ export function resetAfterSuccess(): { fetchError: null; errorCount: 0; errorCategory: null } { return { fetchError: null, errorCount: 0, errorCategory: null }; }