/** * The seam between a finished scanner run and the live-telemetry publisher. * * The scanner runtime module is built per strategy, deep inside a runtime, and the publisher is one * object created once per plugin process. Neither holds a handle to the other, so the connection is a * process-global sink — the same shape the timeline buffer and the domain-event sinks already use. * * What travels over it is a LEVEL, not a record: the most recent state of a scanner, published so a * dashboard can show that a quiet box is alive. It is deliberately not routed through * `logger.event` — see {@link ../utils/telemetry-required-log.js} — because every event also reaches * `appendDomainEvent`, whose on-disk ring and bounded publisher buffer hold real decision records that * a per-tick fact would evict. Nothing here writes to either. * * Two properties this module exists to guarantee: * - With no sink registered the call is one comparison and a return. A box whose telemetry never * started pays nothing per scan, and nothing is built at the call site either: the run object the * terminal callback already holds is passed straight through. * - A sink that throws is swallowed. This runs inside the scanner's terminal callback, and a failure * in telemetry cannot be allowed to break a scan. */ import type { TickCall, TickCommitOutcome } from "./tick-frame.js"; /** * What a producer outside this process reports about its own tick, on top of what the run itself * says. An internal scanner supplies none of it; a supervised external scanner supplies what its * scaffold measured, carried in over the intake POST that scanner already makes every tick. * * Every field is optional and ABSENT means UNKNOWN. None may be defaulted to zero anywhere on the * way to the wire: "made no MCP calls" and "did not say" are different facts about a scanner. */ export interface ScannerTickExtras { /** MCP failures the author's own `except` caught and did not re-raise. */ mcpFailures?: number; /** Every MCP call the tick made, succeeded or failed, as the boundary timed it. */ mcpCalls?: readonly TickCall[]; /** Total time inside MCP this tick. */ mcpMs?: number; /** Consecutive ticks, this one included, that produced no signal. */ quietTicks?: number; /** Actual gap since the previous tick minus the configured interval. */ lagMs?: number; /** Signals from earlier ticks still awaiting delivery in the producer's journal. */ journalPending?: number; /** How the tick's state commit ended. `committed` is the normal case and is not published. */ commit?: TickCommitOutcome; /** The last COMMITTED state row — stale by one tick whenever the current tick failed. */ state?: Readonly>; /** Fields in that row before sanitising, so the drop stays countable. */ stateFieldCount?: number; /** Fields in that row whose value moved since the previously reported row. */ stateChangedCount?: number; } /** * The subset of a finished run a tick carries. Structural on purpose, so the engine's own `ScanResult` * satisfies it without conversion and this module needs no import from the scanner subsystem. */ export interface ScannerRunFacts { address: string; scannerId: string; /** Run status as the terminal callback reports it — the same value the required log calls `outcome`. */ status: string; /** The run's own signals. Absent for a producer that reports a count and holds no array. */ signals?: readonly unknown[]; /** Signals produced, when the producer counts them rather than handing them over. */ signalCount?: number; /** Candidates evaluated. Absent on a run that never evaluated any; never zero as a stand-in. */ scannedCount?: number; /** What the producer measured about the tick itself. Absent for an in-process scanner. */ tick?: ScannerTickExtras; } /** * `startedAt` is the run's own start and is absent for a run that never began, in which case no * duration may be claimed for it. `finishedAt` is the module clock at the terminal callback. */ export type ScannerTickSink = (run: ScannerRunFacts, startedAt: number | undefined, finishedAt: number) => void; /** * Whether anything is listening. * * For a caller that has to BUILD the run before it can hand it over — the intake routes, which * translate a producer's wire facts — so an unwatched box allocates nothing per tick instead of * building an object for a sink that is null. */ export declare function hasScannerTickSink(): boolean; /** Arm or release the process sink. The publisher arms it on start and releases it on stop. */ export declare function setScannerTickSink(next: ScannerTickSink | null): void; /** Hand one finished run to the publisher, if one is listening. Never throws. */ export declare function recordScannerTick(run: ScannerRunFacts, startedAt: number | undefined, finishedAt: number): void; //# sourceMappingURL=scanner-tick-feed.d.ts.map