/** * log10x_configure_engine * * Converts a `target_percent` (or `budget_usd`) commitment into the engine's * policy files — caps.csv (per-container byte caps) + actions.csv (per-service * over-cap disposition) — plus the MCP-side data/action-intent.json, delivered * as a `gh` PR against the customer gitops repo or a kubectl ConfigMap write. * The engine hot-reloads the files on the next poll; no pipeline restart, no * event drops. * * Solver: greedy v1, ordered by (current_bytes_30d * severity_weight) DESC, * where severity_weight = audit:1.0, error:0.8, standard:0.5, debug:0.2, * synthetic:0.1. Replace with LP only if greedy is materially suboptimal * on a representative customer. * * Per-destination action resolution honors the cost lib's CompactMode: * - splunk (envelope) compact ⇒ encode-in-event * - elasticsearch (index-pruned) compact ⇒ pruned _source * - datadog/cw/azure/gcp/sumo/clickhouse (no-op) * compact is rejected and the solver * falls back to the destination's * FIRST LEGAL SAVING LEVER, never to * drop: cloudwatch ⇒ tier_down (IA), * datadog ⇒ tier_down (Flex Logs, still * searchable in Datadog), clickhouse ⇒ * offload, offload-only destinations ⇒ * offload. * * On Datadog `target_percent` is the share of volume moved OUT OF THE * STANDARD INDEX (to Flex, or out of Datadog), and a Flex move is reported as * the events and GB moved, the Standard indexing line avoided and the Flex * line added at the tier the plan's volume lands in, both at Datadog list, * with scalable Flex compute unpriced and excluded. No share of the Datadog * bill is stated for it and the dollar axis * of isPlanFeasible is not used there (lib/cost.ts projectPerEventTierMove). * * ClickHouse is in the no-op list because compaction there was worth about * 7% of table bytes, and table bytes are not the ClickHouse bill. That bill * is compute, and compute follows rows inserted, so offload is the lever. * * drop/sample are opt-in only on the destination-resolution path: they appear * in no DEFAULT_ACTION_BY_DESTINATION entry, so the per-service auto path * never selects a lossy lever. * * Tier defaults are the other half of that contract, and they are caller- * configurable via `action_defaults`: * - audit → always `pass` (not configurable) * - error → `pass` by default. Severity error/warn/warning is kept * verbatim unless the caller sets `action_defaults.error`, * which is the contract the POC report's recommendation * rules render (lib/poc-report-renderer.ts). See the note on * cap semantics in renderCsvDiff for why the per-pattern * action never reaches the engine. * - debug / synthetic → `drop` by default, a lossy proposal: it applies * when the operator merges the rendered PR (delivery `gitops`) * or runs the separate `auto_apply=true` call that writes the * ConfigMap (delivery `kubectl_configmap`); `action_defaults` keeps them lossless. * * Cross-validation: exactly one of target_percent / budget_usd is required; * else the tool returns a structured not-configured envelope. * * Phases (data.phase in the envelope): * - 'target_resolution' gitops repo / destination could not be resolved * - 'backend' customer metrics backend not configured * - 'resolution_prompt' service matched multiple containers — agent re-calls * with `containers=[...]` * - 'solver_failed' target unreachable without violating floors * - 'pr_rendered' success — `pr_command` ready to paste (or null when * current state already meets target) * * Zero engine ask: every query runs against existing receive-aggregator * metrics (all_events_summaryBytes_total labeled by k8s_container, * tenx_hash, severity_level). The PR machinery is the same `gh` pattern * configure_regulator already uses. * * Not-configured states return structured envelopes * (CustomerMetricsNotConfiguredError + the shapes configure_regulator * uses) rather than throwing. */ import { z } from 'zod'; import type { EnvConfig } from '../lib/environments.js'; import { type StructuredOutput } from '../lib/output-types.js'; import { COST_MODEL_BY_DESTINATION, getDestinationCostModel, type Action, type PerEventTierMove } from '../lib/cost.js'; import type { SiemId } from '../lib/siem/pricing.js'; import { type DependencyRule, type RuleDependencySummary } from '../lib/siem/deps/datadog-log-queries.js'; import { type EnforcingInstances } from '../lib/enforcing-instances.js'; import { type ServiceLimit } from '../lib/report/user-units.js'; import { type ServiceCapPlacement } from '../lib/service-cap.js'; import type { FleetDefaultReport } from '../lib/report/build-report-data.js'; declare const WINDOWS_PER_DAY: number; declare const WINDOWS_PER_MONTH: number; declare const MIN_REPORTER_DAYS = 7; /** * Dual-axis feasibility: a plan hits its target if it sheds enough BYTES or * saves enough DOLLARS. Rate-only actions (tier_down) have bytes_out == * bytes_in, so a byte-only test reports an all-tier_down plan (e.g. the * CloudWatch standard-tier default) infeasible despite real spend savings. * `saved_dollars` per row already captures tier_down's rate delta, so the * caller passes the summed row savings as `achievedSavedUsd`. */ export declare function isPlanFeasible(p: { targetShedBytes: number; remainingBytesToShed: number; currentMonthlyUsd: number; targetPercent: number; achievedSavedUsd: number; /** * false on a destination whose cheaper tier is billed per event (Datadog * Flex): its dollars are priced lines with the compute line excluded, so * judging them against a share of the bill would state the Flex percentage * the claims charter bars. The byte axis there counts the volume moved out * of the Standard index. Default true. */ dollarAxis?: boolean; }): { feasible: boolean; bytesFeasible: boolean; dollarsFeasible: boolean; }; export type Tier = 'audit' | 'error' | 'standard' | 'debug' | 'synthetic'; /** * The tiers whose action comes from `action_defaults`. `audit` is excluded: * it is unconditionally `pass` and callers cannot change it. */ export type ConfigurableTier = Exclude; /** Actions that discard events. Never a default for any tier but debug/synthetic. */ export declare const LOSSY_ACTIONS: readonly Action[]; /** * Whether a call executes its delivery. An explicit `auto_apply` wins; unset, * the gitops delivery opens its PR (the operator merges it) and the ConfigMap * delivery, which goes live at once, renders and stops. */ export declare function resolveAutoApply(args: { auto_apply?: boolean; delivery?: string; }): boolean; export declare const configureEngineSchema: { mode: z.ZodDefault>; delivery: z.ZodDefault>; kubectl_namespace: z.ZodOptional; kubectl_configmap_name: z.ZodOptional; tolerance_pct: z.ZodOptional; service: z.ZodString; containers: z.ZodOptional>; target_percent: z.ZodOptional; budget_usd: z.ZodOptional; destination: z.ZodOptional>; es_pruned: z.ZodOptional; contract_type: z.ZodDefault>; signal_floor: z.ZodDefault; events_per_min_per_pod: z.ZodOptional; }, z.core.$strip>>>; action_defaults: z.ZodPrefault>; standard: z.ZodDefault>; debug: z.ZodDefault>; synthetic: z.ZodDefault>; }, z.core.$strip>>; respect_default_action: z.ZodDefault; reduction: z.ZodDefault>; observationDays: z.ZodDefault; snapshot_id: z.ZodOptional; receiver_mode: z.ZodOptional>; nodes_by_container: z.ZodOptional>; gitops_repo: z.ZodOptional; gitops_branch: z.ZodDefault; lookup_path: z.ZodDefault; current_csv: z.ZodOptional; from_poc_id: z.ZodOptional; auto_apply: z.ZodOptional; read_only: z.ZodDefault; view: z.ZodDefault>; service_policy: z.ZodOptional>; keep_queryable: z.ZodOptional; }, z.core.$strip>>>; auto_recommend: z.ZodDefault; compact_worth_it_ratio: z.ZodDefault; service_compaction: z.ZodOptional>>; check_dependencies: z.ZodOptional; fleet_default: z.ZodOptional; action: z.ZodOptional>; error_floor: z.ZodOptional; }, z.core.$strip>>; }; declare const schemaObj: z.ZodObject<{ mode: z.ZodDefault>; delivery: z.ZodDefault>; kubectl_namespace: z.ZodOptional; kubectl_configmap_name: z.ZodOptional; tolerance_pct: z.ZodOptional; service: z.ZodString; containers: z.ZodOptional>; target_percent: z.ZodOptional; budget_usd: z.ZodOptional; destination: z.ZodOptional>; es_pruned: z.ZodOptional; contract_type: z.ZodDefault>; signal_floor: z.ZodDefault; events_per_min_per_pod: z.ZodOptional; }, z.core.$strip>>>; action_defaults: z.ZodPrefault>; standard: z.ZodDefault>; debug: z.ZodDefault>; synthetic: z.ZodDefault>; }, z.core.$strip>>; respect_default_action: z.ZodDefault; reduction: z.ZodDefault>; observationDays: z.ZodDefault; snapshot_id: z.ZodOptional; receiver_mode: z.ZodOptional>; nodes_by_container: z.ZodOptional>; gitops_repo: z.ZodOptional; gitops_branch: z.ZodDefault; lookup_path: z.ZodDefault; current_csv: z.ZodOptional; from_poc_id: z.ZodOptional; auto_apply: z.ZodOptional; read_only: z.ZodDefault; view: z.ZodDefault>; service_policy: z.ZodOptional>; keep_queryable: z.ZodOptional; }, z.core.$strip>>>; auto_recommend: z.ZodDefault; compact_worth_it_ratio: z.ZodDefault; service_compaction: z.ZodOptional>>; check_dependencies: z.ZodOptional; fleet_default: z.ZodOptional; action: z.ZodOptional>; error_floor: z.ZodOptional; }, z.core.$strip>>; }, z.core.$strip>; export type ConfigureEngineArgs = z.infer; export interface ConfigureFleetDefault extends FleetDefaultReport { /** The limit in lines per window per node, from the baseline statement's bytes per line, for a Receiver that counts events. */ cap_events_per_window_per_node?: number; /** The Receiver options after the app, in order, when computed. */ engine_options?: Array<[string, string]>; /** Env vars on the Receiver container. */ engine_env?: Array<[string, string]>; /** The first engine release that reads these options. */ min_engine: string; /** How the value is installed. */ install_note: string; } export interface PerPatternRow { pattern_hash: string; /** Human pattern name — renderers lead with this, never the hash. */ pattern: string; current_bytes_30d: number; cap_bytes_per_window: number; /** Events a month, scaled from the window like current_bytes_30d; absent when the window carried no count. */ events_monthly?: number; action: Action; /** k8s_container this slice belongs to (== the engine's actions.csv key). */ container?: string; saved_bytes_monthly: number; /** * On a Datadog Flex row this is the Standard indexing line the move avoids, * at list, and added_dollars_monthly is the Flex line it adds at the plan's * tier. Neither is a net. */ saved_dollars_monthly: number; /** Datadog Flex rows only: the Flex line the move adds at the plan's tier, at list, $/mo. */ added_dollars_monthly?: number; /** Datadog Flex rows only: the move as stated lines. */ per_event_move?: PerEventTierMove; projected_monthly_usd_low: number; projected_monthly_usd_expected: number; projected_monthly_usd_high: number; floor_reason?: string; reason: string; } export declare function executeConfigureEngine(args: ConfigureEngineArgs, env?: EnvConfig): Promise; interface ResolvedTarget { gitops_repo: string; lookup_path: string; gitops_branch: string; destination: SiemId; /** * Active env id resolved from envs.json / env vars (LOG10X_ENV_ID). Used * to anchor Prometheus selectors with `tenx_env=""` so per-pattern * scans don't fan out across every tenant on the shared prom backend. * Undefined for single-tenant prom backends (back-compat fallback). */ envId?: string; } interface ServiceCompressibility { /** optimized/input, clamped to [0.02, 1.0]; null when unmeasurable. */ ratio: number | null; input_bytes: number; optimized_bytes: number; /** * Where the ratio came from. `live` = the engine's realized optimize-mode * output (Prometheus, full production volume). `sample` = an on-demand * log10x_measure_compaction run (real codec on a sampled batch), used before * optimize mode is deployed. Live wins when both exist. Absent is treated as * `live` (the historical default). */ source?: 'live' | 'sample'; } interface ServiceActionDecision { action: Action; source: 'user_pinned' | 'auto' | 'global_default'; reason: string; ratio_source: 'measured_live' | 'measured_sample' | 'static_band'; measured_compression_pct: number | null; keep_queryable: boolean; /** Measured ratio threaded into projectActionRange; cost.ts honors it for compact only on envelope destinations. */ compact_ratio_override?: number; } /** * Resolve the standard-tier action for one service (k8s_container) under the * env's single destination. Precedence: explicit pin (validated legal) -> * the global non-compact action_defaults.standard or auto-off legacy fallback * -> compressibility-driven auto-recommendation (compact when it compresses * well and stays queryable; offload when it compresses poorly). Never emits an * action that is illegal or zero-saving on the destination. */ export declare function _resolveServiceAction(params: { container: string; destination: SiemId; model: ReturnType; compressibility?: ServiceCompressibility; policy?: { standard_action?: Action; keep_queryable?: boolean; }; autoRecommend: boolean; globalStandardAction: Action; compactWorthItRatio: number; warnings: string[]; }): ServiceActionDecision; export declare function inferTier(severity: string): Tier; /** Per-tier actions the solver applies, after `action_defaults` resolution. */ export interface TierActionDefaults { error: Action; standard: Action; debug: Action; synthetic: Action; } /** * Tier → action for a row that is NOT `signal_floor` pinned. * * The floor pin is deliberately NOT handled here. It is checked at the call * site, ahead of this function, so the floor keeps its visible precedence in * the solver loop instead of being buried in a helper. `_resolveTierAction` * therefore describes only what a tier does when nothing pre-empts it. * * Exported so `test/severity-policy-drift.test.ts` can table-test every tier * against the promises the POC report renders, without standing up a metrics * backend. That test is the reason this is a pure function. * * `audit` and `error` are the severity-protected tiers, and both resolve to * `pass` unless the caller opts out via `action_defaults.error`. Keeping * error-class lines verbatim is what poc-report-renderer tells the customer, * and the solver has to agree with the artifact the customer reads. */ export declare function _resolveTierAction(tier: Tier, defaults: TierActionDefaults, standardOverride?: Action): { action: Action; reason: string; defaultTier: ConfigurableTier | null; }; /** * Service limits from the per-pattern rows: a tier_down container's limit is * its largest pass row in lines a day; compact and sample caps are stated at * the container's average line size; offload and drop move every statement. * The moved statements are priced at a month. */ export declare function serviceLimitsFromRows(containers: string[], rows: PerPatternRow[], actionByContainer: ReadonlyMap, defaultAction: Action, destination: SiemId): ServiceLimit[]; /** * A container's tier_down cap, between its largest pass row and its smallest * moved row, each at its own rate per window (service-cap.ts). */ export declare function tierDownPlacement(rows: PerPatternRow[], container: string): ServiceCapPlacement; export declare function renderCsvDiff(containers: string[], currentCsv: string | undefined, rows: PerPatternRow[], defaultAction: Action, actionByContainer: ReadonlyMap, reduction: 'soft' | 'hard', commitment?: { targetPercent: number; baselineMonthlyBytes: number; }, instances?: EnforcingInstances, reasonByContainer?: ReadonlyMap): string; /** * Render the engine's per-service action file (`actions.csv`) — the sibling * of caps.csv that `rateReceiverActionLookupFile` points at. * * ENGINE GRAMMAR (rate-object-cap.js): * ,[:][:] * A service with no row takes the engine's default for its over-cap slice: * drop up to engine 1.1.139, pass on 1.1.140, and from 1.1.141 * `rateReceiverDefaultAction` (`pass` unless set). Either way the planned * action never happens, so this file ships WITH every caps.csv delivery. * * BOOT CONTRACT: up to engine 1.1.139 a missing actions.csv fails the * receiver launch when the cap variant is active; from 1.1.140 a missing * file loads as an empty table. Always emit the header plus at least one * row, and never deliver caps.csv without it. * * Merge semantics mirror the caps merge: rows for other services in the * existing file survive; the configured containers' rows are replaced. */ export declare function renderActionsCsv(containers: string[], actionByContainer: ReadonlyMap, defaultAction: Action, existingActionsCsv: string | undefined, reduction: 'soft' | 'hard', reasonByContainer?: ReadonlyMap): string; export declare function renderPrCommand(args: ConfigureEngineArgs, resolved: ResolvedTarget, csvDiff: string, siblings?: { actionsCsv?: string; intentJson?: string; flexStatement?: string; }): string; /** * The fleet default from the rows: each container's largest pass row as a * per-node cell (its bytes per reset window over the Receivers that enforce * the container), the rule in `fleet-default.ts`, the limit in lines a day at * that statement's line size across the median nodes per container, and the * Receiver options that install it. */ export declare function fleetDefaultFromRows(rows: PerPatternRow[], instances: EnforcingInstances, request: { headroom?: number; action?: 'tier_down' | 'offload'; error_floor?: number; }, destination: SiemId): ConfigureFleetDefault; /** * One rule per (container, action) for the rows that leave Standard. The * service the caller named is an alias: a monitor on `service:` reads * these containers' lines. */ export declare function dependencyRulesFromEngineRows(rows: PerPatternRow[], perPattern: Array<{ pattern_hash: string; container: string; severity: string; }>, service: string): DependencyRule[]; /** * The plan in user units with the check beside each rule: a service's * sentence, then the log monitors and log-based metrics that read its moved * lines; the fleet line; then the objects not tied to one service, and the * reason when the check did not run. */ export declare function userUnitLinesWithDependencies(sentenceByContainer: ReadonlyMap, fleetLine: string, deps: RuleDependencySummary | undefined): string[]; export { COST_MODEL_BY_DESTINATION, MIN_REPORTER_DAYS, WINDOWS_PER_DAY, WINDOWS_PER_MONTH }; /** * Merge a freshly-rendered actions.csv over the ConfigMap's existing one: * rows for other services survive, configured containers' rows win. Same * survival rule as mergeCapsRows; `container,action` header. */ export declare function mergeActionRows(existingCsv: string | undefined, newCsv: string): string;