/** * The life-threatening-alert gate, the selection rule, and the banner copy. * * Zero I/O by design (CLAUDE.md design pattern 6): this module decides whether * a NOAA alert is life-threatening and renders the fixed banner, and something * else does the fetching. `src/handlers/criticalAlertBanner.ts` is that * something else — the one place that touches a service — so the failure * posture exists once rather than in each of the three render sites. * * **Every interpolated field is upstream free text and is treated as data.** * The banner carries an imperative line addressed to the reading model, so an * upstream that could break out of its own clause and continue the sentence * would be a prompt-injection surface — trusted publisher or not. Hence * `sanitizeField` below, and hence the rule that no upstream *prose* field * (`headline`, `description`, `instruction`) is read here at all. That rule has * no acceptable exception. * * The gate constants live in `src/config/displayThresholds.ts` so calibration * against the live national feed is a config change, not a code change. */ /** * The subset of NOAA `AlertProperties` this module reads. * * Every field is optional even though `src/types/noaa.ts:238-247` declares them * required: that interface describes the documented shape, and a third-party * response may omit any field at runtime. CLAUDE.md's "every field of a * third-party response optional" rule is the reason. A missing field must never * throw and must never fire the gate. */ export interface CriticalAlertCandidate { event?: string; severity?: string; urgency?: string; certainty?: string; response?: string; senderName?: string; expires?: string; } /** * Is this alert life-threatening enough to interrupt a weather question? * * ``` * critical ⟺ (severity ∈ severities && urgency ∈ urgencies && certainty ∈ certainties) * || response ∈ responses * ``` * * The starting rule is deliberately tight: `Extreme` severity catches Tornado, * Tsunami, Extreme Wind and Hurricane warnings while excluding Severe * Thunderstorm Warning (`Severe`), which is far too common to justify an * interrupt. The `response` leg stands alone because an official instruction to * evacuate is life-threatening whatever severity was attached to it. * * The three-axis leg is an **and**, so a single missing axis cannot fire it; * an all-undefined candidate is not critical. */ export declare function isCriticalAlert(a: CriticalAlertCandidate): boolean; /** * The first critical alert in the list's given order, or `undefined`. * * **First, not most severe, and the order is the caller's.** NOAA's * `/alerts/active?point=` returns newest-first, which is the order a reader * would expect the banner to follow, so re-sorting here would only invent a * second opinion about precedence that nothing else in the codebase holds. * * **No array cap.** G8 says a bounded array that trims must never be used for * an exclusion decision without disclosing the trim — and there is nothing * honest to disclose in a positive-assertion-only element, so the right answer * is not to cap at all. If a future change adds one, G8 binds and the cap has * to be surfaced. */ export declare function selectCriticalAlert(list: CriticalAlertCandidate[]): CriticalAlertCandidate | undefined; /** * The banner, or `''` when there is nothing to warn about. * * Every byte of this block is server-authored except four interpolated fields — * `event`, `senderName`, the formatted `expires`, and `response` — each of * which is sanitized or validated above. Absence of the banner claims nothing: * it is a positive assertion only, which is what makes the silent-omit failure * posture in `src/handlers/criticalAlertBanner.ts` safe (D1). * * @param a The alert to warn about, or `undefined` for "nothing to say" * @param totalActive How many alerts are active at the point, for the closing line * @param timezone IANA zone the expiry is rendered in; UTC when omitted */ export declare function formatCriticalAlertBanner(a: CriticalAlertCandidate | undefined, totalActive: number, timezone?: string): string; //# sourceMappingURL=criticalAlert.d.ts.map