/** * The two numbers this whole feature was built for. * * event.ts says it plainly: the fields worth having that the receiving table * did not already hold are "the holdout arm's outcome, and how often the graph * answered instead of a tool", and that today we learn them one machine at a * time. Both were still learned one machine at a time. The recorder was wired * to the MCP tool surface and to the proxy, and the hooks -- which is where the * holdout runs, where substitutions are served and where most users spend the * whole of their time with this product -- reported nothing at all. A user who * opted in and never called an MCP tool transmitted a version string. * * THE HOOKS ARE NOT MADE TO REPORT; THEIR MEASUREMENTS ARE READ. Each hook is a * process that lives for one tool call, so it can hold no window in memory, and * teaching it to would put a second consent decision in a second language -- * hooks-core imports nothing from dist/, so it cannot ask policy.ts. It already * writes every one of these measurements to the project's own log. So this reads * that log, at boot, from the one process that does own the policy, and nothing * on the hook path changes. * * WHAT IT SENDS IS COUNTS, NEVER THE LOG. The metrics log names real files from * a private codebase and may never be transmitted; `report()` reduces it to * integers, and only named integers from that reduction are copied here. * sanitiseProperties would drop a string anyway, which is the backstop, not the * plan. * * IT IS A GAUGE, NOT A COUNTER, and the receiver has to treat it as one. The * log is a sliding tail bounded by bytes and by event count, so two snapshots * overlap by an unknown amount and their difference is not a delta. Each row is * the state of one project's ledger at one instant. Sending real deltas would * need a high-water mark that the tail can slide past, which would silently * under-report exactly the busiest machines. * * A FIELD THAT IS ABSENT MEANS "NOT YET KNOWN". The holdout needs 20 treated * and 5 withheld reads before it estimates anything; below that `report()` * answers null and the field is omitted rather than sent as a zero, because a * zero saving and an unmeasured saving are the opposite finding. * * ONCE PER WINDOW PER MACHINE, not once per boot: restarting an editor six times * in an afternoon is not six observations of anything. The stamp is a file, so * the throttle survives the process it belongs to. */ import type { SafeValue } from './event.js'; /** How long one machine's snapshot stands before another is worth taking. */ export declare const SNAPSHOT_EVERY_MS: number; /** Where the last snapshot's time is remembered. */ export declare function snapshotStampFile(env?: NodeJS.ProcessEnv): string; /** When the last snapshot was taken, or null if none was or the stamp is unreadable. */ export declare function lastSnapshotAt(env?: NodeJS.ProcessEnv): number | null; /** * Is a snapshot due? * * An unreadable or absent stamp answers yes. The alternative -- treating a * missing stamp as "just taken" -- means a machine that cannot write the stamp * never reports at all, and says nothing about why. */ export declare function dueForSnapshot(now?: number, env?: NodeJS.ProcessEnv): boolean; /** Remembers that a snapshot was taken. Failure to remember costs a duplicate, not a throw. */ export declare function stampSnapshot(now?: number, env?: NodeJS.ProcessEnv): void; /** * The counts worth sending, or null when this project has not used the graph. * * A project where nothing was injected, withheld or substituted has nothing to * report, and a row of zeros per machine per window would be the majority of * the table -- outnumbering the rows that answer the question. */ export declare function snapshotProperties(report: unknown): Record | null; export interface SnapshotResult { /** The event that was recorded, or null when none was. */ readonly recorded: boolean; /** Why nothing was recorded, or null when something was. */ readonly refused: string | null; } /** * Record one project's graph ledger as a telemetry event, if it is due. * * Never throws: every failure is a refusal this returns, for the same reason * recorder.ts gives. The metrics reader is imported by path rather than by * package specifier because hooks-core ships unbuilt beside dist/ -- the same * idiom proxy/findings.ts uses -- and the import is awaited here rather than at * module load so that a caller who is not opted in pays nothing for it. */ export declare function flushHookSnapshot(env?: NodeJS.ProcessEnv, root?: string, now?: number): Promise; //# sourceMappingURL=hook-rollup.d.ts.map