/** * The emitter and the feed fold (DES-TRIGGER-REGISTRY-001 §4.6-§4.8). * * EMIT. Outputs are micro-batched (every 2 s, or at 16 waiting) and each row goes to the bus through * the engine (`emitOnBus`). A key already on the bus resolves to the existing row: success. A failed * emit is retried at 1, 2 and 4 s, then DROPPED with an audit line `watch.emit.failed` and counted * in `health.emit.failed` [D-3]: watch rows are advisory, so a lost one is disclosed, never retried * forever and never blocking. * * FOLD. The bus is the one record. The feed is hydrated once at arm from the bus history, then * updated only from rows this emitter saw acknowledged, so `GET /watch` never re-reads the bus. * * RATE. Past an entry's `emit.rate.per_run` rows on one run, a roll-up row stands for the rest: each * further output raises the next roll-up row (`rolled_up` = how many it stands for) and clears the * previous one with `reason: "rolled_up"`, `replaced_by` the new row (§4.7). */ import type { WatchFinding, WatchFindingCleared } from 'wicked-crew-api-types'; import type { CheckOutput, LoadedEntry } from './types.js'; /** Redacted, capped facts; an over-size object is replaced by a marker rather than cut mid-structure. */ export declare function scrubFacts(facts: Record | undefined): Record; /** * #780: how many folded findings a roll-up row names (by short id) — enough that a resolved one * leaves the roll-up and the last one clears it. Past this the roll-up still counts, but cannot * know when it is empty (`rolled_ids_complete: false`), so it never clears by itself. */ export declare const ROLLUP_TRACK_MAX = 50; /** The short id a roll-up row names a folded finding by: 16 hex chars of its would-be `watch_id`. */ export declare function foldedId(watchId: string): string; /** One feed row: the raised payload, and its clearing once one was acknowledged. */ export interface FeedRow { raised: WatchFinding; cleared: WatchFindingCleared | null; } /** * The in-memory feed, BOUNDED (the daemon's bus is never swept, so a lifetime fold would grow * without limit): past `maxRows` the oldest cleared row goes first, then the oldest row. The bus * stays the record; an evicted row re-raised by a replay resolves to its existing bus row. */ export declare class WatchFeed { private readonly maxRows; readonly rows: Map; private readonly orphanClears; /** Cleared rows in clearing order: the first eviction candidates. */ private readonly clearedOrder; constructor(maxRows?: number); get orphanCount(): number; private evict; has(watchId: string): boolean; get(watchId: string): FeedRow | undefined; addRaised(f: WatchFinding): void; addCleared(c: WatchFindingCleared): void; /** Fold one bus row (the boot hydrate). */ foldBusRow(eventType: string, payload: unknown): void; /** Rows of one (run, entry), roll-ups apart (the rate count, rebuilt after a restart). */ countFor(runId: string | null, entryId: string): { plain: number; rollups: WatchFinding[]; }; } export interface EmitterDeps { dbPath: string; projectOf: (runId: string) => string | undefined; /** `audit.record('watch.emit.failed', …)` in the daemon. */ auditEmitFailed: (detail: Record) => void; /** The proposals sink (§4.11): crew's existing review queue. Never self-applied. */ submitProposal?: (finding: WatchFinding) => Promise; now?: () => number; sleep?: (ms: number) => Promise; /** Flush cadence, ms (tests pass 0 and call `flush`). */ flushMs?: number; log?: (msg: string) => void; /** Called once per acknowledged row (the registry folds nothing else). */ onAck?: (eventType: string, payload: WatchFinding | WatchFindingCleared) => void; } export declare class WatchEmitter { private readonly deps; readonly feed: WatchFeed; failed: number; proposalsFailed: number; private pending; private readonly queuedKeys; /** Raised rows dropped after their retries (a clearing that depends on one is skipped). */ private readonly failedKeys; /** Raised rows taken out of `pending` and on their way to the bus (a clearing can still find them). */ private readonly inflight; private readonly rate; /** The newest watch row on the bus at hydrate: an emit resolving to an id at or below it found an OLD row. */ private hydratedTail; private timer; private flushing; private readonly now; private readonly sleep; constructor(deps: EmitterDeps); /** Hydrate the feed once from the bus history (§4.8 step 1). */ hydrate(): Promise; start(): void; stop(): Promise; get waiting(): number; private rateOf; private envelope; /** * Queue a raise for `out` (rate and roll-up applied). `replay`: the input came from the boot * replay; over the rate such an output is DROPPED rather than rolled up, because the first life * may already have counted it and a roll-up row cannot tell (the count may then read low after a * restart, never high — a duplicate row is the failure G4 rules out). */ raise(entry: LoadedEntry, runId: string | null, out: Extract, anchorAt: number, replay?: boolean): void; /** The row is cleared, or its clearing is already queued. */ private closed; /** A raised row this emitter knows: on the bus (the feed), on its way there, or waiting in the batch. */ private raisedPayload; /** * #780: a finding that was folded into a roll-up (it never had a row of its own) resolved. It leaves * the roll-up: the roll-up is re-issued with the lower count (the `cleared{rolled_up}` + `raised` * pair, as when it grows), or, when it stood for nothing else, its row clears as resolved. A * subject no roll-up names (never raised at all) clears nothing. */ private clearFolded; /** Queue the clearing of the row `subject` names (no-op when it was never raised or is already cleared). */ clear(entry: LoadedEntry, runId: string | null, subject: string, how: { reason: 'resolved'; } | { reason: 'dismissed'; dismissed_by: string; }, re?: string): boolean; /** Queue the clearing of `watchId` (dismiss uses this directly). */ clearById(watchId: string, how: { reason: 'resolved'; } | { reason: 'dismissed'; dismissed_by: string; }, re?: string): boolean; private push; /** Emit everything waiting, in order. Concurrent calls share one pass. */ flush(): Promise; private drain; private emitOne; } //# sourceMappingURL=emit.d.ts.map