/** * Policy loader — parses and validates a `policy.yaml` file for the * `tenx-recur` CLI tick runner. * * The schema mirrors what `scheduler-manifest-emitter.ts` emits via * `emitPolicyYaml()`. Validation is intentionally lenient — unknown fields * are ignored so policies written by newer versions of the setup wizard * still load correctly on an older CLI binary. * * Minimal YAML parser note: we parse the policy.yaml with a hand-rolled * line-by-line parser rather than pulling in a heavy YAML library. The * policy format is simple (no anchors, no multi-document, no block * scalars) so this is safe and keeps the dependency footprint small. * * Policy shape (all fields with defaults): * * schema_version: "1.0" * * reduction: * target_services: [] # empty = all services * target_percent: 30 # integer 1-95 * exceptions: [] # service names never touched * min_delta_pp: 2 # minimum savings change before commit * lookback_window: "24h" # PromQL range for the volume query * severity_rules: # per-severity action overrides * ERROR: keep # kept at floor regardless of volume * INFO: auto # auto = apply cost policy * DEBUG: auto * WARN: auto * * schedule: * preset: daily-03utc * cron_utc: "0 3 * * *" * scheduler: k8s_cron * * config_plane: * repo: https://github.com/acme/log10x-config * env_id: */ export type SeverityAction = 'keep' | 'auto' | 'drop' | 'sample' | 'compact'; export interface SeverityRules { ERROR?: SeverityAction; CRITICAL?: SeverityAction; WARN?: SeverityAction; INFO?: SeverityAction; DEBUG?: SeverityAction; TRACE?: SeverityAction; } export interface ConfigPlane { /** * URL or local filesystem path to the customer gitops config repo. * The CLI clones/pulls from here, writes updated CSVs, and opens a PR * (or pushes directly, depending on the `commit_strategy` field below). */ repo: string; /** * Log10x env ID — scopes the PromQL metric queries. * Falls back to LOG10X_ENV_ID when absent. */ env_id?: string; /** * How the CLI commits changes. * 'pr' — open a GitHub PR via `gh pr create` (default). * 'direct_push' — push directly to the specified branch. */ commit_strategy?: 'pr' | 'direct_push'; /** Branch to push to (direct_push) or base branch for the PR. Default: main. */ base_branch?: string; } export interface Policy { schema_version: string; /** Services the policy targets. Empty = all services. */ target_services: string[]; /** Desired savings target (1-95). */ target_percent: number; /** * VOLUME BUDGET (thermostat): keep monthly ingest for the targeted scope at * or under this many GB/mo. When present it REPLACES target_percent as the * tick's goal: the tick scales the budget to its lookback window and cuts * only the overage — already under budget means every pattern passes. */ budget_gb_monthly?: number; /** Services the policy must never touch. */ exceptions: string[]; /** * Minimum change (percentage points) before a new CSV is committed. * Prevents noisy churn on small week-to-week fluctuations. */ min_delta_pp: number; /** * PromQL range window for the volume query (e.g. "24h", "7d"). * This is the lookback the tick uses when calling top_patterns. */ lookback_window: string; /** Per-severity action overrides. */ severity_rules: SeverityRules; cron_utc?: string; scheduler?: string; config_plane: ConfigPlane; } export declare class PolicyLoadError extends Error { readonly cause?: unknown | undefined; constructor(message: string, cause?: unknown | undefined); } /** * Parse a `policy.yaml` string into a validated `Policy` object. * * Missing fields receive sensible defaults so policies written by an older * setup wizard still load without error. The only required field is * `config_plane.repo` — the tick has nowhere to write without it. * * @throws PolicyLoadError on structural problems (missing repo, out-of-range * target_percent, negative min_delta_pp, etc.). */ export declare function parsePolicyYaml(text: string): Policy;