/** * plan-solver — the ONE routine that turns per-pattern data into a * "cut N% of the bill" plan, shared by the POC path (log10x_poc_from_local) * and the analysis path (log10x_estimate_savings / cost_options). Before this, * the two paths ran different algorithms — POC a byte "droppable fraction", * analysis a byte-denominated greedy — and disagreed (65% vs 0% on the same * environment). This makes them agree by construction. * * The model is the destination ladder, made explicit: * * keep everything, recoverable lossy, opt-in * ───────────────────────────── ────────────── * 1 compact in SIEM, smaller 4 sample keep 1 in N * 2 tier_down in SIEM, cheaper tier 5 drop keep nothing * 3 offload out of SIEM, in your S3 (needs the retriever) * * Rules the solver enforces: * - Errors and warnings are pinned at `pass` — never eligible for anything. * - The target is met with keep-everything rungs (1-3) FIRST. Lossy rungs are * surfaced only as a labelled opt-in when the keep-everything ceiling falls * short, and applied only when the caller passes allowLossy. * - offload (rung 3) is available only when the retriever is installed, since * without it the offloaded events are unreachable. So a destination with no * compact and no tier_down and no retriever collapses to drop — honestly. * - Accounting is in DOLLARS (bill = bytes × rate; each lever cuts its own * factor via cost.projectAction), so tier_down — which keeps every byte and * cuts the rate — reads as the real cut it is instead of a byte "0%". * - Scope is a set of services (or all). A pattern touching any scoped service * is a candidate, weighted by its bytes in those services. */ import { type Action, type PerEventTierMove } from './cost.js'; import { type SiemId } from './siem/pricing.js'; import { type SplunkPricing } from './siem/splunk-keep-raw.js'; /** * Lines of a statement over its cap that the shipped Receiver still passes per * window per node: the keep count for an error-class level, none for the rest * (config/pipelines/run/receive/rate/config.yaml keepCounts and severityFloors). * The plan writes caps and no service total, so keepMaxOverTotal never binds. */ export declare function keptOverCapLines(severity?: string | null): number; /** One pattern (message type) as the solver consumes it. */ export interface SolverPattern { /** * The template body with `$` marking variable slots — the message type's * own skeleton, the unit made visible. Present where the source has real * templates (the POC extraction); absent on the analysis path until its * metrics carry one. First line only for multi-line templates. */ skeleton?: string; /** Stable identity (tenx_hash / templateHash). */ hash: string; /** Human-readable message-type face. */ name: string; /** Service → bytes attributed to this pattern in that service. A pattern * usually maps to one service, but a shared log line can span several, so * the shape is a distribution. */ services: Record; /** Dominant severity for the pattern. */ severity: string; /** Total observed bytes for the pattern over `SolveOpts.spanSeconds`, the * span the Receiver's per-window sample and keep count are counted over. */ bytes: number; /** Average event size in bytes; degrades the compact ratio for tiny events. */ avgEventBytes?: number; /** * The same bytes in the Receiver's unit, at the pattern's busiest window's * rate (`ExtractedPattern.receiverCapBytes`, scaled like `bytes`). Caps are * set and compared in this unit; `bytes` stays the billing figure. */ capBytes?: number; } /** * What the plan is solving FOR. Three denominations: * - percent: "cut X% of the bill" — a one-shot project. On a destination * whose tier_down is billed per event (Datadog Flex), no share * of the bill can be stated for a plan that uses it, because * the Flex compute add-on is unpriced. There the percent is * read as a share of the volume moved OUT OF THE STANDARD * INDEX, and Plan.percentBasis says so. * - usd_budget: "keep the (scoped) bill under $B/mo" — a standing constraint; * the reduction is derived: max(0, bill - budget). Idempotent: * already under budget -> an empty plan with headroom. * - gb_budget: "keep (scoped) ingest under V GB/mo" — BYTE accounting. * tier_down keeps every byte, so it contributes NOTHING to this * target and is excluded from the ladder; compact counts only * where it lands on the billed wire (compactsInPlace). */ export type PlanTarget = { kind: 'percent'; value: number; } | { kind: 'usd_budget'; value: number; } | { kind: 'gb_budget'; value: number; }; export interface SolveOpts { destination: SiemId; /** Whether the S3 retriever is installed. Gates the offload rung. */ retrieverInstalled: boolean; /** Reduction goal as a percent of the (scoped) bill, e.g. 50. Ignored when * `target` is supplied; kept for the existing percent callers. */ targetPct?: number; /** The full target union; wins over targetPct when present. */ target?: PlanTarget; /** Services to solve for; omit or 'all' for the whole estate. */ scope?: string[] | 'all'; /** Permit the lossy rungs (sample/drop) to close a keep-everything shortfall. * Default false: the plan stops at the keep-everything ceiling and names the * gap instead of silently discarding data. */ allowLossy?: boolean; /** Services pinned at pass (the POC's exception_services): their patterns * stay in the bill but are never planned, exactly like protected severities. * Matched case-insensitively against the pattern's dominant service. */ exceptionServices?: string[]; /** * The customer's blended all-in $/GB for this destination (their contracted * or invoice-derived rate). When present, every dollar on the plan is scaled * from the list-price structure to this blend — the ladder physics (lever * ratios) stay list-structure, the absolute dollars become theirs. Provenance * is echoed on the plan (rateSource / rateBasis). Absent = list price. */ customerRatePerGb?: number; /** * Specific pattern hashes pinned at pass — the referenced-types exclusion: * a type a monitor or saved search names stays exactly as it is unless the * user explicitly trades it back in. Pinned like protected severities. */ pinnedHashes?: string[]; /** * Whether the customer runs this destination themselves. Decides whether * compaction is available on the Elasticsearch/OpenSearch family: the * expander is the l1es PLUGIN, installable on your own nodes and nowhere * else. Absent = unknown, and unknown prices only the levers that hold on * any deployment. Never inferred. */ selfManaged?: boolean; /** * Elasticsearch only: the version the user stated. Compact needs the l1es * plugin, which loads only on 8.17.0, so it is priced only when this is * 8.17.0 and selfManaged is true. Absent = unknown. Never inferred. */ elasticsearchVersion?: string; /** * OpenSearch only: the version the user stated. Compact is priced only when * this is 2.19.0 and selfManaged is true. Absent = unknown. Never inferred. */ opensearchVersion?: string; /** * Splunk only. `workload`: the licence meters vCPU, not bytes, and compact * leaves the lever set. Absent means ingest pricing, Splunk's common case. */ splunkPricing?: SplunkPricing; /** * Splunk only. Whether Enterprise Security (or any CIM data model) reads * this estate. True or absent keeps the add-on sourcetypes raw; false, * stated by the user, frees them. Never inferred. */ enterpriseSecurity?: boolean; /** * Specific pattern hashes the user EXPLICITLY unpinned from the severity * floor — a warn-level retry storm they chose to act on. Bypasses ONLY the * severity protection for the listed hashes; pinnedHashes and * exceptionServices still win. Never inferred, never estate-wide. */ unprotectPatterns?: string[]; /** The span the patterns' bytes cover, in seconds. Absent = a 30-day month. */ spanSeconds?: number; /** * Receiver nodes each container's pods run on. Each node keeps its own * always-pass sample and keep count, so a container absent here counts as * one node and the plan says so. */ nodesByContainer?: Record; } export interface PlannedRow { hash: string; name: string; /** Readable opener for the card: the identifier de-underscored and * whitespace-collapsed. Tool-emitted so every host opens the card with the * same noun instead of each agent deriving its own. Mechanical, never * interpretive: same tokens as `name`. */ displayName: string; dominantService: string; serviceMix: { service: string; sharePct: number; }[]; severity: string; billUsd: number; action: Action | 'pass'; /** * tier_down rows: the bytes the engine moves, the pattern's lines past the * larger of its container's cap and the always-pass sample, less the keep * count of an error-class level, on every node. In the plan's own scale. */ movedBytesMonthly?: number; savedUsd: number; /** The template skeleton ($-slots), passed through from the source. */ skeleton?: string; /** Volume-budget plans only: bytes this row removes from the billed wire. */ savedBytes?: number; /** * Per-event tier rows only (tier_down to Datadog Flex): the line the move * ADDS at the tier the plan's Flex volume lands in, at list, $/mo. On these * rows savedUsd is the Standard indexing line avoided at list, so neither * number is a net; perEventMove.text names the tier and how compute is billed. */ addedUsd?: number; /** Per-event tier rows only: the move as stated lines. Render its text. */ perEventMove?: PerEventTierMove; keepsEverything: boolean; } export interface Plan { destination: SiemId; retrieverInstalled: boolean; scope: string[] | 'all'; /** The ask, echoed. For budgets this is the user's budget, and targetPct is * the DERIVED reduction percent the solver actually chased. */ target: PlanTarget; targetPct: number; /** * What every percentage on this plan (targetPct, achievedPct, * keepEverythingCeilingPct, gap.remainingPct) is a share OF. 'bill' almost * everywhere. 'standard_index_volume' on Datadog, where Flex is the lever * and no share of the bill can be stated: there each percentage is the * share of volume moved out of the Standard index. Renderers must name the * basis beside every percentage they print. */ percentBasis: 'bill' | 'standard_index_volume'; billUsd: number; /** The keep-everything lever the destination resolves to (rung 1-3), or null * when only lossy rungs remain. */ keepEverythingLever: Action | null; /** Max percent of the bill removable while keeping everything (rungs 1-3 on * every non-error pattern). */ keepEverythingCeilingPct: number; achievedPct: number; met: boolean; /** Sum of planned rows' savedUsd — equals billUsd minus landsAtUsd (plus * totalAddedUsd where present), so the arithmetic on a rendered plan * closes visibly. */ totalSavedUsd: number; /** Per-event tier plans only: the Flex lines the plan adds at its tier, at list. */ totalAddedUsd?: number; /** * Per-event tier plans only (Datadog Flex): every tier_down row's move, * summed and stated as lines. Render its text verbatim. It carries no * percentage and no net, and neither may be derived from it. */ perEventMove?: PerEventTierMove; /** The pricing basis behind every dollar on this plan, one human-readable * line. Render it verbatim. */ rateBasis: string; /** Whose dollars these are: the destination list-price model, or the * customer's supplied blended rate scaled over the list structure. */ rateSource: 'list_price' | 'customer_supplied'; /** * True when this plan's dollars rest on a model rather than on the * destination's own meter. ClickHouse only, today: the per-GB rate behind * these figures is a storage rate and the bill is compute. Renderers must * carry the word "modeled" wherever they print one of these dollars. */ modeled: boolean; /** Why the dollars are modeled, in one line. Present only when modeled. */ modeledNote?: string; /** * Notes the cost model attached to this plan's projections, deduplicated. * The compute term's own sentences ride here, so a renderer that shows the * plan can show why a ClickHouse dollar is what it is. A tier_down plan adds * what the Receiver keeps over each cap and the node count it was counted on. */ notes?: string[]; /** * What the levers this plan actually uses REQUIRE — the app, plugin, tier, * or licence, with its platform and version constraint. Only for levers the * plan uses. A priced lever with an unstated prerequisite is a lever we are * guessing at, so the render states these before the user agrees. */ prerequisites: string[]; /** Echo of the supplied blended rate when rateSource is customer_supplied. */ customerRatePerGb?: number; /** Total scoped bytes/mo behind the bill — the reconciliation multiplicand: * bytesInMonthly times the rate should foot against the invoice line. */ bytesInMonthly: number; /** * Splunk only: sources pinned at pass by the destination's own rules (an * Enterprise Security add-on reads them, or Splunk meters them at the * Cisco rate), with the sentence that says why. Render each one. */ keptRaw?: { service: string; reason: 'cisco_rate' | 'enterprise_security_add_on'; text: string; }[]; /** usd_budget / percent targets: the bill after the plan, in $/mo. Absent * on a percent plan over a per-event tier (Datadog Flex): a before and * after pair there IS a bill percentage, and none can be stated. On a * Datadog dollar budget it counts priced lines only, scalable Flex compute excluded. */ landsAtUsd?: number; /** gb_budget targets: monthly bytes toward the destination after the plan. */ landsAtBytesMonthly?: number; /** * The engine's files, one row per container that hosts a planned pattern: * the action its excess takes and, for tier_down, the cap that moves the * planned patterns and no other, the largest kept pattern's bytes. In the * plan's own byte scale and span; the writer converts to bytes per * regulator window. */ containerCaps: Record; planned: PlannedRow[]; kept: PlannedRow[]; /** Set only on a copy trimmed for a tool response: how many kept rows the full plan has, and how many the copy leaves out. */ keptTotal?: number; keptOmitted?: number; /** Same, for planned rows (estimate_savings trims both). */ plannedTotal?: number; plannedOmitted?: number; /** Present when the keep-everything ceiling is below the target. Names the * remedies (install the retriever, or accept loss) rather than pretending. */ gap: null | { remainingPct: number; remedies: Array<'install_retriever' | 'accept_loss'>; message: string; }; } /** * The destination's best keep-everything lever, respecting the ladder and the * retriever gate. compact wins over tier_down (both keep in-SIEM queryability; * compact also shrinks the bytes); offload is last and needs the retriever. */ export declare function keepEverythingLever(destination: SiemId, retrieverInstalled: boolean, opts?: { selfManaged?: boolean; elasticsearchVersion?: string; opensearchVersion?: string; splunkPricing?: SplunkPricing; }): Action | null; export declare function solvePlan(rawPatterns: SolverPattern[], opts: SolveOpts): Plan;