/** * Mode selection for retriever_series. * * Two strategies: * - "full": one retriever query covering the whole window, * client-side bucketing + group-by aggregation. * Exact counts. Bounded by Lambda budget. * - "per_window_sampled": split the window into N sub-windows and run K * events per sub-window in parallel. Time * distribution preserved; within-sub-window bucket * density preserved; tail group-by values may be * absent in any sub-window's sample. * * Selection is volume-driven when Reporter has data for the pattern, and * window-length-driven as a fallback when it does not. The heuristic lives * in this module (NOT in tool descriptions or LLM prompts) so it produces * the same decision across calls. */ import type { EnvConfig } from './environments.js'; export type FidelityMode = 'full' | 'per_window_sampled'; export type ModeReason = 'estimated_events_under_threshold' | 'estimated_events_exceeded_threshold' | 'estimated_bytes_exceeded_threshold' | 'window_length_short_fallback' | 'window_length_long_fallback' | 'pattern_volume_unknown_fallback' | 'forced_full' | 'forced_per_window_sampled'; export type RefusalReason = 'estimated_events_exceed_safe_budget' | 'estimated_bytes_exceed_safe_budget'; /** * Tunables. Constants because they're load-bearing for the safety contract; * exposing them per-deployment via env vars would let ops accidentally * widen the refusal threshold past what Lambda can actually serve. The * `fidelity` arg on the tool gives the caller per-call override of mode + * K when they want it. */ export declare const FULL_MODE_EVENT_THRESHOLD = 50000000; export declare const FULL_MODE_BYTE_THRESHOLD: number; export declare const REFUSAL_EVENT_THRESHOLD = 10000000000; export declare const REFUSAL_BYTE_THRESHOLD: number; export declare const DEFAULT_K = 1000; /** Upper cap on N (parallel sub-windows). Keeps fan-out under reasonable Lambda concurrency. */ export declare const MAX_SUB_WINDOWS = 60; export interface FidelityDecision { mode: FidelityMode; reason: ModeReason; /** Best-effort estimate of total matching events in the window. Undefined if Reporter had no signal. */ estimatedEvents?: number; /** Best-effort estimate of bytes that would be fetched. Undefined if either count or avg-size is unknown. */ estimatedBytes?: number; /** When mode === "per_window_sampled". */ subWindows?: number; eventsPerSubWindow?: number; /** Diagnostic — what we asked Reporter and got back. */ reporter?: { pattern?: string; rateQuery?: string; bytesQuery?: string; rateEventsPerMinute?: number; bytesPerEvent?: number; note?: string; }; } export interface RefusalDecision { mode: 'refused'; reason: RefusalReason; estimatedEvents?: number; estimatedBytes?: number; recommendation: string; } /** Parse the user's `fidelity` arg. */ export declare function parseFidelityArg(arg: string | undefined): { forced: FidelityMode | undefined; k: number; }; /** Compute sub-window count from window length, capped at MAX_SUB_WINDOWS. */ export declare function subWindowCount(windowMs: number): number; /** * Window-only fallback used when Reporter has no volume signal for the * pattern (new pattern, sparse, no Reporter deployed, or query without a * bound `search` filter that would map to a Reporter pattern series). * * Threshold is conservative: without volume data we can't tell a * 100-event/sec pattern from a 100K-event/sec pattern. Anything past 4h * is sampled by default. The spec's "≤48h start full + abort if it * explodes" path was attractive but the retriever doesn't expose mid-query * progress, so an abort would land as a hard timeout — defeating the * purpose of the fidelity contract. */ export declare function fallbackByWindow(windowMs: number): { mode: FidelityMode; reason: ModeReason; }; /** * Best-effort pattern-rate fetch from Reporter. * * We extract a `tenx_user_pattern` value from the search expression * (matches what `event-lookup` does) and ask Reporter for that pattern's * event rate over the last 5 minutes plus its bytes/event ratio. Both are * Prometheus-side computations — single instant query each, cheap. * * Returns `undefined` for any field Reporter does not have a value for. */ export declare function fetchReporterPatternStats(env: EnvConfig, search: string | undefined): Promise<{ pattern?: string; rateQuery?: string; bytesQuery?: string; rateEventsPerMinute?: number; bytesPerEvent?: number; note?: string; }>; /** * Single entry point — combine Reporter signal + thresholds + window-length * fallback to produce the mode decision (or a structured refusal). */ export declare function decideFidelity(env: EnvConfig, args: { search?: string; windowMs: number; forced?: FidelityMode; k: number; }): Promise; /** * Pull a pattern name out of a TenX search expression. Matches: * tenx_user_pattern == "Foo" → "Foo" * tenx_user_pattern=="Foo" → "Foo" * Anything else returns undefined (we don't try to resolve a tenx_hash / * pattern_hash back to its pattern name; that requires a Reporter round-trip * we'd rather have the user do explicitly via event_lookup). */ export declare function extractPatternName(search: string | undefined): string | undefined; /** * Convert a relative or absolute time expression to epoch millis. Mirrors * the retriever normalizer's interpretation but returns a number we can * arithmetic on (for sub-window splitting and window-length math). */ export declare function timeExprToMs(expr: string, nowMs?: number): number;