/** * PromQL query builders. * * Generates the PromQL queries used by each tool. * * Phase 2 of the CUSTOMER-PROM-BACKEND design: every builder now accepts * an optional `labels: LabelNameMap` parameter so per-env label renames * (the engine's `metricFieldNames` setting) can flow through to the * MCP's queries. Missing parameter defaults to `DEFAULT_LABELS`, which * is what every tool uses today — no behavior change until phase 4 * threads the env's label map through. */ /** * Per-env metric label name map. The 10x engine's * `pipelines/run/output/metric/{backend}/config.yaml` has a * `metricFieldNames` setting that lets a customer rename * `tenx_user_service` to `service`, `message_pattern` to * `pattern_hash`, etc. The MCP must build queries with the same names * the engine writes; this type is the customer-facing knob. */ export interface LabelNameMap { pattern: string; service: string; severity: string; env: string; /** Stable pattern-identity hash label (engine `symbolMessageHashField`). */ hash: string; } /** * Default label names — what the engine writes when no * `metricFieldNames` override is configured. Existing tools call * builders without passing a `labels` argument; this preserves their * current behavior. */ export declare const DEFAULT_LABELS: LabelNameMap; /** * Legacy alias of `DEFAULT_LABELS` exported for tool code that still * references label names directly when building filter records * (e.g., `filters[LABELS.service] = args.service`). Phase 4 will swap * these references to `env.labels` so per-env renames take effect. */ export declare const LABELS: LabelNameMap; /** * Filter value: either a plain string (default exact-match `=`) or an * object form `{op, val}`. * * The regex ops carry the cohort selectors. A cohort is a SET of routeState * values, not one, and `=`/`!=` cannot express that — which is exactly how * `dropped` came to mean only the literal `drop` action. See * `includeToSelector`. * * `escapeLabel` escapes only backslashes and quotes, so `|` alternation in a * regex value reaches PromQL intact. */ export type FilterValue = string | { op: '=' | '!=' | '=~' | '!~'; val: string; }; /** * Engine route-state action names. Mirrors `Action` in lib/cost.ts but is * declared locally so promql.ts stays dependency-free of the cost layer. * Per the per-service action-routing feature the receiver now stamps * `routeState=""` (drop | offload | tier_down | compact | sample | * pass) instead of only `drop`/`pass`. */ export type RouteStateAction = 'pass' | 'sample' | 'compact' | 'tier_down' | 'offload' | 'drop'; /** * The cohort a caller wants to scope to. The three legacy tokens keep their * exact original semantics; any single `RouteStateAction` selects that * action's stamped cohort directly. * * `kept` → `routeState=~"pass|"` — what actually reached the destination. * Absence-tolerant: the trailing `|` matches the empty string, * and PromQL treats an absent label as empty. * `dropped` → `routeState=~"offload|compact|tier_down|drop|sample"` — * everything the receiver acted on, so everything the * destination did NOT receive. NOT an alias of `'drop'`; pass * `'drop'` explicitly for the literal hard-drop cohort alone. * `both` → no selector; caller runs a dual query to recover the * acted-on slice for the `dropped_*` envelope fields. * ``→ `routeState=""` (exact) for any single action name, so * a caller can scope to e.g. the `offload` or `tier_down` cohort. */ export type IncludeCohort = 'kept' | 'dropped' | 'both' | RouteStateAction; /** * The two cohorts, defined the same way `services` defines its four axes — * which is the authoritative split, read straight off the engine's * `routeState` label. * * KEPT = what actually reached the destination: `pass`, plus series with no * `routeState` label at all. The trailing `|` in the alternation matches the * empty string, and PromQL treats an absent label as empty, so this stays * absence-tolerant for series predating the receiver's label stamping. * * ACTED = everything the receiver did something to, so everything the * destination did NOT receive: offload, compact, tier_down, drop, sample. * * Do NOT define `kept` as `routeState!="drop"` and `dropped` as * `routeState="drop"`. Both are wrong, in opposite directions, because a * cohort is a SET of states and `!=`/`=` can only name one: * * - `kept` then counts offloaded and compacted bytes as delivered. A * pattern 100% routed to S3 reports `kept_share_pct: 100`. * - `dropped` then sees only the literal `drop` action, so the offload * cohort is invisible to every tool that uses it. * * On the demo env that split had `services` reporting cart 88% offloaded * (260.95 GB offload / 34.90 GB passed) while `event_lookup`, `top_patterns` * and `pattern_trend` all reported cart 0% reduced, 100% kept — an 8x * disagreement on the single largest offload claim in the environment. The * same split produced 77% (services/savings, counting drop+offload) versus * 49% (estimate_savings, counting drop only) for one window, and made * `top_patterns(include:"dropped")` emit a false "routeState enrichment is * not wired" diagnostic whenever a service's cohort was offload rather than * drop. */ export declare const KEPT_STATES_RE = "pass|"; export declare const ACTED_STATES_RE = "offload|compact|tier_down|drop|sample"; /** * Map the user-facing cohort selector to a single `routeState` * filter-value (or null for the pre-decision union). * * NOT back-compatible: `kept` and `dropped` now select SETS of route states * (see KEPT_STATES_RE / ACTED_STATES_RE) rather than the single `drop` value. * Any other single action name still yields an exact `routeState=""` * selector (run alone, no dual query), so `'drop'` remains available for the * literal hard-drop cohort. * * `runBoth` tells the executor whether to issue the second acted-on-cohort * query in parallel (only `both` does). */ export declare function includeToSelector(include: IncludeCohort): { droppedFilter: FilterValue | null; runBoth: boolean; }; /** * Convert a (possibly fractional) day offset into a valid Prometheus duration * literal of the form `\d+[smhdwy]`. Prometheus rejects fractional units with * HTTP 400 — see pattern_diff's 1h window case where offsetDays = 1/24 was * being emitted as `offset 0.04166...d`. * * Picks the LARGEST integer-and-unit representation that exactly represents * the value: days when seconds % 86400 === 0, else hours when % 3600 === 0, * else minutes when % 60 === 0, else seconds. Returns a leading-space-prefixed * ` offset ` string suitable for direct interpolation into a PromQL * vector selector, or an empty string when offsetDays is falsy/zero. * * Throws if the derived expression somehow fails the duration grammar — a * defensive guardrail against future arithmetic regressions. */ export declare function formatPromOffset(offsetDays?: number): string; /** * Auto-pick a query_range step for a given window so the resulting * series carries ~12–30 buckets — enough resolution to see shape, not * so many that the renderer chokes or the agent loses signal in noise. * * Decision table: * - `15m` → `1m` (15 buckets) * - `1h` → `5m` (12 buckets) * - `6h` → `15m` (24 buckets) * - `24h` → `1h` (24 buckets) * - `1d` → `1h` (24 buckets, alias of 24h) * - `7d` → `6h` (28 buckets) * - `30d` → `1d` (30 buckets) * * Unknown windows fall through to `1h` — the historical default that * existed before this helper was introduced, so callers that pass * something we don't model never see worse behaviour than the prior code. * * Returned strings are valid `step` values for `trendSchema`'s enum * (`'1m' | '5m' | '15m' | '1h' | '6h' | '1d'`) and parseable by * `parseStep()` in trend.ts. */ export declare function autoStepForWindow(window: string): '1m' | '5m' | '15m' | '1h' | '6h' | '1d'; /** Bytes per pattern for a time window, with optional offset in days. */ export declare function bytesPerPattern(filters: Record, env: string, range: string, offsetDays?: number, labels?: LabelNameMap): string; /** Scope-total bytes for a time window — no grouping. Used by coverage probes. */ export declare function totalBytesInScope(filters: Record, env: string, range: string, labels?: LabelNameMap): string; /** Event count per pattern for a time window. */ export declare function eventsPerPattern(filters: Record, env: string, range: string, labels?: LabelNameMap): string; /** Event count per service for a specific pattern. */ export declare function eventsPerServiceForPattern(pattern: string, env: string, range: string, labels?: LabelNameMap): string; /** Top N patterns by bytes across all services. */ export declare function topPatterns(env: string, range: string, limit: number, labels?: LabelNameMap): string; /** Bytes per service for a specific pattern. */ export declare function patternAcrossServices(pattern: string, env: string, range: string, offsetDays?: number, labels?: LabelNameMap): string; /** Total bytes for a time window. */ export declare function totalBytes(env: string, range: string, labels?: LabelNameMap): string; /** Bytes per service. */ export declare function bytesPerService(env: string, range: string, labels?: LabelNameMap): string; /** Bytes per service, honoring the same filter scope as the main query * (so a service/severity-filtered top_patterns run shows a rollup over * the same subset, not the whole env). Used by the cost-center rollup * in log10x_top_patterns — the "where is the money" headline. */ export declare function bytesPerServiceScoped(filters: Record, env: string, range: string, labels?: LabelNameMap): string; /** Bytes per severity. */ export declare function bytesPerSeverity(env: string, range: string, labels?: LabelNameMap): string; /** Pattern bytes over time (for range queries / trends). */ export declare function patternBytesOverTime(pattern: string, env: string, step: string, labels?: LabelNameMap): string; /** * Probe: does edge env have data? * * `range` defaults to 7d — wide enough that a sparse-but-live real backend is * not misread as absent. The demo gateway rejects any range >3h with HTTP 400, * so the resolver retries this probe at a short range on error rather than * narrowing the window for every real customer (see resolve-env.ts). */ export declare function edgeProbe(labels?: LabelNameMap, range?: string): string; /** Probe: does edge env have data for specific filters? */ export declare function edgeProbeFiltered(filters: Record, labels?: LabelNameMap, range?: string): string; /** Pipeline instance count. */ export declare function pipelineUp(): string; /** Distinct services with data. */ export declare function distinctServices(range: string, labels?: LabelNameMap): string; /** Bytes entering the edge pipeline (reporter + receiver input). */ export declare function edgeInputBytes(range: string, labels?: LabelNameMap): string; /** Bytes emitted from the edge pipeline — receiver output (incl. compact). */ export declare function edgeEmittedBytes(range: string, labels?: LabelNameMap): string; /** * Measured per-service (per-k8s_container) realized compaction for the Phase-2 * advisory: ratio = total receiver-emitted bytes / receiver-classified input * bytes, over the SAME cohort on both sides (tenx_app="receiver", kept slice * routeState!="drop"). Used to choose compact (compresses well, stays * queryable) vs offload (compresses poorly, take the max cut). * * Both legs MUST share the same app + routeState cohort or the ratio is * garbage: all_events_summaryBytes_total is emitted by reporter AND receiver, * so an unscoped denominator double-counts a two-stage pipeline and biases the * ratio low (fake compaction). The numerator sums BOTH emitted metrics the way * edgeEmittedBytes does (optimized output for compacted events + full-size * emitted for pass-through), so it reconciles with the kept input. This mirrors * the ROI dashboard's input/emitted legs. `routeState!="drop"` also matches * series with no routeState label, so it is safe on engines that do not stamp * it. The caller passes an already-escaped container regex; a container missing * from the optimized leg falls back to the static destination band. */ export declare function compressibilityPerContainer(envId: string, range: string, containerRegex: string, labels?: LabelNameMap): { inputQ: string; optimizedQ: string; }; /** Bytes indexed into the customer's S3 by the Retriever. */ export declare function retrieverIndexedBytes(range: string, labels?: LabelNameMap): string; /** * Single-day `increase()` chunk for the indexed metric with an optional offset. * The indexed metric's ~12k active series makes a single 7d `increase()` blow * the server's query budget. Summing N × 1d chunks client-side stays per-chunk * small enough to complete. */ export declare function retrieverIndexedBytesChunk(offsetDays: number, labels?: LabelNameMap): string; /** Bytes actually streamed back out (i.e., served to a SIEM or dashboard). */ export declare function retrieverStreamedBytes(range: string, labels?: LabelNameMap): string; /** Single-day `increase()` chunk for the streamed metric. Same chunking rationale as retrieverIndexedBytesChunk. */ export declare function retrieverStreamedBytesChunk(offsetDays: number, labels?: LabelNameMap): string; /** Top N patterns by bytes with service + severity labels retained. */ export declare function topPatternsFull(filters: Record, env: string, range: string, limit: number, labels?: LabelNameMap): string; /** * Recent-activity rate per (pattern, service, severity) over a short window * (default 1h). Used as a freshness probe: if a top-N row from a longer * window has 0 (or missing) recent rate, it's residue from a closed incident, * not an active cost driver. */ export declare function recentRateByPattern(filters: Record, env: string, recentRange?: string, labels?: LabelNameMap): string; /** * Event count per (pattern, service, severity) over the window. Keyed * identically to topPatternsFull so the result joins 1:1 with the byte * rows (a pattern can appear under several services/severities; keying * on the triple avoids over-counting on a pattern-only join). */ export declare function eventsByPatternFull(filters: Record, env: string, range: string, offsetDays?: number, labels?: LabelNameMap): string; /** * Per-(pattern, service, severity) byte series for a query_range call: * each evaluation step is the per-bucket increase, so the matrix is a * volume-over-time sparkline source. Keyed identically to * topPatternsFull so it joins 1:1 with the byte rows. `stepSeconds` is * passed as the increase() inner range AND the query_range step. */ export declare function seriesByPatternFull(filters: Record, env: string, stepSeconds: number, labels?: LabelNameMap): string; /** Count of distinct patterns in scope (for "N of M patterns shown"). */ export declare function distinctPatternCount(filters: Record, env: string, range: string, labels?: LabelNameMap): string; /** Bytes per (pattern, service): which services each pattern impacts. */ export declare function servicesByPatternFull(filters: Record, env: string, range: string, labels?: LabelNameMap): string; /** Bytes grouped by an arbitrary label, ranked. */ export declare function bytesByLabel(label: string, filters: Record, env: string, range: string, labels?: LabelNameMap): string;