/** * Recurring tick runner — one deterministic execution of the cost-reduction * policy loop. * * Called by `bin/tenx-recur.ts` (CLI) once per invocation. * * Five-step flow: * 1. Load policy (already parsed by caller and passed in). * 2. For each scoped service, query Prometheus for top patterns over the * policy lookback window. * 3. Rank patterns by bytes; walk top-N applying severity + threshold rules. * 4. Compute delta vs. current applied state (read existing CSVs / action-intent). * 5a. If delta < min_delta_pp → exit 0 with "no change needed". * 5b. Else write mute/compact/action-intent files and commit via gh CLI or * direct git push. * * The runner NEVER directly imports heavy MCP tool modules — it only uses the * lower-level libs (cost.ts, compact-csv-writer.ts, mute-csv-writer.ts, * action-intent-writer.ts) so it can run as a standalone CLI binary. * * Prometheus queries go through the existing `customer-metrics.ts` adapter * (LOG10X_CUSTOMER_METRICS_URL / LOG10X_CUSTOMER_METRICS_TYPE / …) which is * what `log10x_top_patterns` also uses under the hood. */ import type { Policy } from './policy-loader.js'; import type { CustomerMetricsBackend } from './customer-metrics.js'; import type { Action } from './cost.js'; export interface TickOptions { dryRun?: boolean; verbose?: boolean; } export type TickStatus = 'no_change' | 'applied' | 'dry_run' | 'error'; export interface PatternDecision { /** * The pattern's message_pattern NAME (the engine's mute-file key — * rateReceiver fieldNames joins to message_pattern), NOT an 11-char * tenx_hash. The field name predates that distinction. */ pattern_hash: string; service: string; severity: string; bytes: number; share_pct: number; action: Action; reason: string; } export interface TickResult { status: TickStatus; message: string; /** Cumulative projected savings as a % of total observed bytes. */ projected_savings_pct: number; /** All per-pattern decisions the tick made (including 'pass'). */ applied_changes: PatternDecision[]; /** How many patterns changed vs. the prior state. */ delta_patterns: number; /** Delta in savings percentage points vs. prior state. */ delta_pp: number; /** Path to the appended run-history JSONL line (when not dry-run). */ history_path?: string; } /** * How the runner resolves its metrics backend. Defaults to the same * ambient-env resolution the MCP tools use (`loadBackendFromEnv`). * * Tests inject a deterministic mock via `_setBackendLoader()` instead of * monkey-patching the frozen `customer-metrics` ESM namespace (which throws * under strict ESM). Mirrors the `_set*` runner-injection seam used by other * libs in this package. */ type BackendLoader = () => Promise; /** Test seam: override how the runner resolves its metrics backend. */ export declare function _setBackendLoader(loader: BackendLoader): void; /** Test seam: restore the default ambient-env backend loader. */ export declare function _resetBackendLoader(): void; export declare class PromUnreachableError extends Error { constructor(message: string); } export declare function writeOutputFiles(repoPath: string, decisions: PatternDecision[], verbose: boolean): void; /** * The thermostat's audit trail: one markdown report per APPLIED tick, written * into the config repo so it rides the same commit as the CSVs it describes. * "When a scheduled run cuts something on a Tuesday, where do I see what it * did" — here, in the artifact the change-control process already reviews. * noop ticks write nothing (a daily no-change commit would be churn); they * land in the local history file instead. */ export declare function writeRunReport(repoPath: string, policy: Policy, decisions: PatternDecision[], totalBytes: number, projectedSavingsPct: number, deltaPatterns: number, deltaPp: number): void; /** * Run one tick of the recurring cost-reduction policy. * * @param policy Parsed and validated policy (from parsePolicyYaml). * @param opts Runtime options (dryRun, verbose). * @returns TickResult with status, projected savings, decisions made. * * @throws PromUnreachableError (exit code 2) when Prometheus cannot be reached. */ export declare function runTick(policy: Policy, opts?: TickOptions): Promise; /** * An S3 prefix can serve as the config plane instead of a git repo — the * AWS-native path for estates with no cluster and no GitHub (the Lambda * engine polls `TENX_RECEIVE_MUTE_S3_URI` from the same prefix). Requires * the aws CLI on PATH; state moves with `aws s3 cp` so no SDK dependency * is added. */ export declare function isS3ConfigPlane(repo: string): boolean; export {};