/** * Signal intake POST-level + per-signal pipeline. * * Pure-ish: takes deps (registry, acceptance record, clock, etc.), * returns the response object. No Fastify. * * Pipeline: * 1. Shape valid → assumed guaranteed by TypeBox at route level. * 2. scanner_id known? → POST-level reject if unknown. * 3. Update liveness clock. * 4. Empty signals list? → no-op, return empty array. * 5–9. Per signal, independently: * 5. Well-formed (asset, direction, produced_at, valid_until, data)? * 6. Still valid in time? (now < valid_until). * 7. Newer than acceptance record? * 8. Data matches config.fields? * 9. Accepted → mint correlationId, persist record (high-water-mark), THEN * call onAccepted seam (persist-before-act: a persist failure dispatches * no trade, preventing a double-trade on retry). */ import type { AcceptanceRecord } from "./acceptance-record.js"; import type { ScannerRegistry } from "./scanner-registry.js"; import type { SignalsBody, IntakeSignal, IntakeTickFacts, PerSignalStatus } from "./types.js"; export interface ProcessSignalPostDeps { registry: ScannerRegistry; record: AcceptanceRecord; now: () => number; mintCorrelationId: () => string; updateLiveness: (scannerId: string, now: number) => void; onAccepted: (signal: IntakeSignal, correlationId: string, scannerId: string) => void; /** * The tick's own observability facts, when the producer sent any. Called beside the liveness * update and BEFORE the empty-signals return, because a quiet tick — the 99% case — is exactly * the one whose facts are the only thing proving the box is alive. */ onTick?: (scannerId: string, tick: IntakeTickFacts) => void; } export type ProcessSignalPostResult = { ok: true; signals: PerSignalStatus[]; } | { ok: false; reason: string; }; /** * Process a signal POST, applying the full acceptance pipeline. * Returns per-signal statuses in input order. */ /** * The acceptance high-water-mark is keyed by the scanner's STABLE identity * (runtime + scanner name), NOT the ephemeral scanner_id. A relaunch mints a * new scanner_id; keying on it would reset the high-water-mark and let a * replayed / already-accepted signal be accepted again under the new id. */ export declare function acceptanceKeyFor(entry: { runtimeId: string; scannerName: string; }): string; /** * Asset-slot key under which a SEEN signal_id is recorded in the AcceptanceRecord. * * The signal_id dedup layer is ADDITIVE on top of the per-(scanner,asset, * produced_at) high-water-mark. To persist it across POSTs without a separate * store (and so it rides the SAME durable seam as the high-water-mark — surviving * a restart with FileAcceptanceRecord), each accepted signal_id is recorded as a * reserved pseudo-"asset" slot in the record. The leading-space " sigid " prefix * cannot collide with a real asset symbol (asset names are trimmed, non-empty, * and never carry a leading space), so this never shadows a genuine * (scanner,asset) mark. */ /** * Reserved key prefix for signal_id dedup slots. The leading space is what makes * the slot keyspace disjoint from real asset symbols (which are trimmed and never * carry a leading space). Slots are pruned by matching this prefix. */ export declare const SIGNAL_ID_SLOT_PREFIX = " sigid "; export declare function signalIdSlot(signalId: string): string; export declare function processSignalPost(body: SignalsBody, deps: ProcessSignalPostDeps): Promise; //# sourceMappingURL=process-signal-post.d.ts.map