/**
 * FeatureMetricsLedger — per-feature observability for LLM-driven systems.
 *
 * Records, per call, which system (sentinel/gate) invoked the LLM, what it
 * cost (tokens, latency), and what it decided (fired/noop/error/shed), so that
 * every gate's cost and hit-rate becomes a tracked number instead of a guess.
 * 'shed' (circuit-open, no call) is counted separately so `realCalls` reflects
 * only real round-trips. Read-
 * only observability — it NEVER gates, blocks, or mutates any flow (same
 * guarantee as TokenLedger). Spec: docs/specs/llm-feature-metrics-spec.md.
 *
 * Phase 1a: this store + its read API. The single funnel tap that feeds it
 * (CircuitBreakingIntelligenceProvider.evaluate → record()) is Phase 1b, added
 * on top of #638's hardened funnel. The store is fully exercisable now via
 * record()/recordEvent() (used by tests and, later, the tap).
 *
 * The per-feature key is the existing IntelligenceOptions.attribution.component
 * tag (e.g. "MessagingToneGate"); calls without one bucket under "unlabeled".
 */
import type { Database as BetterSqliteDatabase } from 'better-sqlite3';
export type FeatureMetricKind = 'llm' | 'event';
/**
 * Outcome of a funnel call:
 *  - 'fired' — the gate acted (blocked/flagged). The fired-vs-noop verdict is
 *    Phase 2; today the funnel never sets this (the caller would).
 *  - 'noop'  — a REAL call completed and the gate took no action.
 *  - 'error' — a real call failed.
 *  - 'shed'  — the circuit was OPEN so no call ran (no token cost, no network
 *    round-trip). Distinct from 'noop' so `realCalls` (= calls − shed) reflects
 *    only real round-trips; otherwise breaker-shed load (0ms latency) inflates
 *    the call count and reads as completed work.
 */
export type FeatureMetricOutcome = 'fired' | 'noop' | 'error' | 'shed';
export interface FeatureMetricRecord {
    /** Source-side component label (IntelligenceOptions.attribution.component). */
    feature: string;
    /** 'llm' for a provider call; 'event' for a programmatic guard invocation. */
    kind?: FeatureMetricKind;
    /** What happened: fired (acted) vs noop (real call, no action) vs error vs shed (circuit-open, no call). */
    outcome: FeatureMetricOutcome;
    tokensIn?: number;
    tokensOut?: number;
    /**
     * Cache-read input tokens (token-audit-completeness). PINNED SEMANTICS
     * (P18 — the schema is the perception): tokensCached ⊆ tokensIn on every
     * framework — tokensIn's meaning is UNCHANGED (historical row continuity;
     * claude↔codex comparability), tokensCached is an informational subset,
     * and fresh cost is derivable as tokensIn − tokensCached. For claude this
     * is cache_read_input_tokens ONLY (cache CREATION costs ~1.25× fresh and
     * stays plain input; cache READS cost ~0.1× — collapsing them would point
     * the cost signal in two directions at once). For codex,
     * cached_input_tokens maps directly.
     */
    tokensCached?: number;
    latencyMs?: number;
    /** Resolved model string the provider actually ran (e.g. "gpt-5.4-mini", "claude-haiku-4-5"). */
    model?: string;
    /** Resolved framework that served the call (e.g. "codex-cli", "claude-code"). Observable Intelligence. */
    framework?: string;
    /** Post-#638: did this call wait for a rate-limit window before running. */
    waited?: boolean;
    waitMs?: number;
    /** For Phase-2 effectiveness correlation (verdict ↔ downstream outcome). */
    verdictId?: string;
    /** Defaults to now(). */
    ts?: number;
}
export interface FeatureRollup {
    feature: string;
    /** All recorded funnel rows (includes 'shed' no-calls). */
    calls: number;
    /** Real round-trips only (calls − shed) — the honest call count. */
    realCalls: number;
    llmCalls: number;
    events: number;
    tokensIn: number;
    tokensOut: number;
    /** Cache-read subset of tokensIn (token-audit-completeness). */
    tokensCached: number;
    /** Distinct frameworks that served this feature in the window (Observable Intelligence). */
    frameworks: string[];
    /** Distinct models this feature ran in the window. */
    models: string[];
    fired: number;
    noop: number;
    errors: number;
    /** Circuit-open no-calls: the breaker refused the call, nothing ran. */
    shed: number;
    /** fired / realCalls (0..1) — how often the system acts on a call that actually ran. */
    fireRate: number;
    avgLatencyMs: number;
    p50LatencyMs: number;
    p95LatencyMs: number;
    maxLatencyMs: number;
    waitedCalls: number;
    avgWaitMs: number;
    /** This feature's slice of the feature×model partition (summary() only). */
    byModel?: FeatureModelRollup[];
}
/**
 * One feature×model×framework group (token-audit-completeness, Slice 2).
 * llm-kind rows only. NULL model/framework render "unknown". The presence
 * counts ride the same single GROUP BY query — usage-presence is a NULL
 * test, not a SUM (a recorded 0 must count as reported; one large row must
 * not mask N null rows).
 */
export interface FeatureModelRollup {
    feature: string;
    model: string;
    framework: string;
    calls: number;
    realCalls: number;
    tokensIn: number;
    tokensOut: number;
    tokensCached: number;
    fired: number;
    noop: number;
    errors: number;
    shed: number;
    /** Success (fired+noop) rows whose tokens_in is non-NULL. */
    successRowsWithUsage: number;
    /** Error rows whose tokens_in is non-NULL (surfaces error-path recording). */
    errorRowsWithUsage: number;
}
/** Aggregate model×framework rollup across all features (totals.byModel). */
export interface ModelRollup {
    model: string;
    framework: string;
    calls: number;
    realCalls: number;
    tokensIn: number;
    tokensOut: number;
    tokensCached: number;
    fired: number;
    noop: number;
    errors: number;
    shed: number;
}
/**
 * Per-framework usage-reporting coverage (the drift tripwire's durable
 * surface). Denominator = SUCCESSFUL llm rows only (fired + noop) — error
 * rows legitimately lack usage (claude parses usage only on success; codex
 * timeouts can kill the child pre-flush), and including them would normalize
 * coverage below 1.0 and train operators to ignore the tripwire. Error rows
 * are reported alongside (errorRowsWithUsage also surfaces the error-path
 * recording directly). Exemption is keyed PER PROVIDER IMPLEMENTATION, not
 * per framework: claude-code rows with model 'interactive-pool' are excluded
 * from the claude denominator (that provider NEVER invokes onUsage by
 * documented contract); gemini-cli is exempt; pi-cli is NOT exempt
 * (PiCliIntelligenceProvider invokes onUsage — exempting it would mask a
 * future pi parse-rot).
 */
export interface UsageCoverageRow {
    framework: string;
    successRows: number;
    successRowsWithUsage: number;
    /** successRowsWithUsage / successRows (0..1); 0 when denominator is 0. */
    coverage: number;
    errorRows: number;
    errorRowsWithUsage: number;
    /** True = this framework's provider cannot surface usage by documented contract. */
    exempt: boolean;
    /** claude-code only: interactive-pool rows excluded from the denominator. */
    excludedRows?: number;
}
export interface FeatureMetricsSummary {
    sinceMs: number;
    totals: {
        calls: number;
        realCalls: number;
        llmCalls: number;
        events: number;
        tokensIn: number;
        tokensOut: number;
        tokensCached: number;
        fired: number;
        noop: number;
        errors: number;
        shed: number;
        /** Aggregate model×framework breakdown (token-audit-completeness). */
        byModel: ModelRollup[];
        /** Per-framework usage-reporting coverage (drift tripwire surface). */
        usageCoverage: UsageCoverageRow[];
        /** Unlabeled (tokensIn+tokensOut) share of total token spend. 0 on zero denominator. */
        unlabeledTokenShare: number;
        /**
         * Unlabeled realCalls share of total realCalls. Token-blind unlabeled
         * calls contribute 0/0 to the token share, so a token-weighted metric
         * alone reads 0.00 while unlabeled traffic runs at volume.
         */
        unlabeledCallShare: number;
    };
    features: FeatureRollup[];
}
export interface FeatureMetricsLedgerOptions {
    /** SQLite db path, or ':memory:' for tests. */
    dbPath: string;
    /** Test seam — inject a Database instance (e.g. an in-memory one). */
    databaseFactory?: (dbPath: string) => BetterSqliteDatabase;
    /** Injectable clock for deterministic tests. */
    now?: () => number;
}
export declare class FeatureMetricsLedger {
    private db;
    private now;
    private insertStmt;
    private closed;
    constructor(opts: FeatureMetricsLedgerOptions);
    /** Add post-ship columns to an existing table, idempotently (pragma-guarded). */
    private ensureAddedColumns;
    /** Record a metric row (typically one LLM funnel call). Never throws to callers. */
    record(r: FeatureMetricRecord): void;
    /** Convenience for programmatic (non-LLM) guards: invocation + verdict, no token cost. */
    recordEvent(feature: string, outcome: FeatureMetricOutcome, verdictId?: string): void;
    private sinceMsFrom;
    /** Per-feature rollup over the lookback window (default: all time). */
    byFeature(opts?: {
        sinceHours?: number;
    }): FeatureRollup[];
    /**
     * Core per-feature rollup. `includeProviderScan: false` skips the DISTINCT
     * frameworks/models window scan — summary() derives those arrays from the
     * byFeatureModel partition instead (its group keys ARE feature×framework×
     * model), so the per-model slice lands at NET-ZERO window scans, not +1.
     */
    private byFeatureCore;
    /**
     * Feature×model×framework partition (token-audit-completeness, Slice 2).
     * llm-kind rows only; NULL model/framework render "unknown". ONE composite-
     * key GROUP BY carries the usage-presence counts itself — SQLite
     * COUNT(expr) counts non-NULL, so `COUNT(CASE WHEN outcome IN
     * ('fired','noop') THEN tokens_in END)` is the success-rows-with-usage
     * count (a recorded 0 counts as reported; a SUM would let one large row
     * mask N null rows). No latency percentiles on this dimension.
     */
    byFeatureModel(opts?: {
        sinceHours?: number;
    }): FeatureModelRollup[];
    /**
     * Frameworks whose provider implementation cannot surface per-call usage by
     * documented contract (the cannot-surface list, keyed per implementation —
     * see the provider usage-contract test, which derives expectations from
     * fixtures, never from this list).
     */
    private static readonly USAGE_EXEMPT_FRAMEWORKS;
    /** Totals + per-feature rollup, enriched with the per-model partition. */
    summary(opts?: {
        sinceHours?: number;
    }): FeatureMetricsSummary;
    /**
     * Delete rows older than `cutoffMs`. Returns rows deleted. Fail-open.
     * Observable Intelligence is balanced by the Responsible Resource standard:
     * the audit trail is kept long enough to see behaviour/performance trends, then
     * aged out — never hoarded forever. Mirrors ResourceLedger.pruneOlderThan.
     */
    pruneOlderThan(cutoffMs: number): number;
    close(): void;
}
//# sourceMappingURL=FeatureMetricsLedger.d.ts.map