import type { UsageRepository } from '../db/repositories/usage-repository.js'; import type { ClientId } from '../models/usage-record.js'; export interface TokenSnapshot { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; reasoningTokens: number; cost?: number; records?: number; } export interface GrainComparison { label: string; snapshot: TokenSnapshot; /** Field-by-field difference against our database (source minus ours). */ delta: TokenSnapshot; matches: boolean; note?: string; /** * False for rows that are informational only and must not decide pass/fail * (a stale rollup, or the naive sum shown to demonstrate double counting). */ gating?: boolean; } export interface ClientVerification { client: ClientId; /** What we stored for the sessions the source still holds -- the compared figure. */ ours: TokenSnapshot; /** * What we stored for sessions the source no longer holds. Both clients delete * their own history (Claude Code's transcripts after `cleanupPeriodDays`, * default 30), and keeping it is this database's job, so it is reported beside * the comparison rather than counted as a mismatch. Also covers a store not read * this run (see `--all-stores`). Absent when there is none. */ retained?: TokenSnapshot & { sessions: number; }; grains: GrainComparison[]; available: boolean; reason?: string; } export interface VerifyReport { clients: ClientVerification[]; allMatch: boolean; /** * Only activity strictly before this instant is compared. Both clients are * appending to their stores while we read them, so without a shared cutoff the * source will always look a few requests ahead of the database and `verify` * would report a permanent, meaningless mismatch. */ cutoff: string; } /** * Re-derives usage straight from the source data and diffs it against what we * stored. Deliberately a *second, independent implementation* -- it shares no * reduction code with the collectors, so a bug in one will not hide in the other. */ export declare class VerifyService { private readonly repo; constructor(repo: UsageRepository); verify(options?: { allStores?: boolean; cutoff?: Date; }): Promise; /** Splits what we stored into sessions the source still holds and the rest. */ private oursFor; private verifyOpenCode; /** * Independent re-aggregation of the Claude Code transcripts. * * Uses a deliberately different reduction rule from the collector: prefer the * line carrying a non-null `stop_reason` (the line the API finished on), and * fall back to the maximum only when no such line exists. If this agrees with * the collector's max-per-field rule, two different readings of the same bytes * produced the same number. */ private verifyClaudeCode; /** * Independent re-aggregation of the Codex rollouts. * * The collector sums each request's `last_token_usage`, skipping events that * repeat the running total. This reads the other number Codex writes: the * running total itself, `total_token_usage`, taking its last value before the * cutoff in each session. If the dedupe dropped a request or kept a repeat, * the two disagree. A running total that ever goes DOWN is treated as Codex * starting a new count, and the run before it is kept rather than lost. * * Rollouts from 0.162 on also carry a `token_usage_record` per request, keyed * by `response_id`: a third reading of the same requests, compared against * the sessions that have them. */ private verifyCodex; }