import type { ExternalFieldDefinition, ExternalScannerConfig, ScannerOutputs } from "./scanner-definition.js"; import type { ScannerId, Signal } from "./types.js"; /** * Top-level ingest request accepted by the runtime/module surface for one * `external_scanner`. * * The runtime/plugin resolves the strategy address separately, so this request * is already scoped to one running runtime and only needs to identify the * target scanner plus either one single-item payload (`data`) or a batch of * signal items (`signals`). */ export interface ExternalScannerIngestRequest { /** Configured runtime scanner name. */ scanner: ScannerId; /** * Producer-minted idempotency key from intake (the scaffold journal eid). * Observability only — threaded onto the built `Signal` so one signal is * greppable across the intake→ingest hops. Never sizes/gates/routes a trade. */ signal_id?: string; /** * Producer-minted id of the scan that produced this signal. Absent when the * producer sends none — the ingest never mints one, so a missing id stays * missing all the way to the event attribute. */ tick_id?: string; /** * Intake-minted id of the acceptance row that admitted this signal. Answers a * different question from `tick_id`: which acceptance, not which scan. */ correlationId?: string; /** Asset for a single ingested signal. */ asset?: string; /** Optional direction for a single ingested signal. */ direction?: Signal["direction"]; /** Optional confidence score for a single ingested signal. */ score?: number; /** Optional single-signal classification override. */ signal_type?: string; /** Optional per-signal margin as a percent (0–100) of withdrawable (overrides config sizing). */ marginPct?: number; /** Optional per-signal leverage override (overrides config default). */ leverage?: number; /** * Single ingest payload validated against `config.fields`. * * Signal-producing scanners treat this as the signal metadata payload. * Context-producing scanners retain this object verbatim as scanner context. */ data?: Record; /** * Batch signal payloads for signal-producing external scanners. * * Context-producing scanners reject batch payloads in v1 because retained * context has one committed value per scanner. */ signals?: ExternalScannerIngestSignalItem[]; } /** * One item inside a batch external-signal ingest request. * * Batch items intentionally mirror the single-signal fields so both request * shapes normalize into the same persisted signal representation. */ export interface ExternalScannerIngestSignalItem { asset: string; direction?: Signal["direction"]; score?: number; signal_type?: string; marginPct?: number; leverage?: number; /** * Producer-minted id of the scan that produced THIS item. One batch can mix ticks — a producer * retry rides along with the current tick's signals — so the item's own id wins over the * request-level one. Absent on both leaves the signal unnamed; the ingest never mints one. */ tick_id?: string; data: Record; } /** * Successful ingest response returned by the runtime/module surface. * * Invalid routes or payloads throw instead of returning `accepted: false`; the * plugin layer will later translate those errors into gateway response codes. */ export interface ExternalScannerIngestResult { accepted: true; address: string; scanner: ScannerId; timestamp: number; /** Number of committed signals; `0` for context-only ingests. */ signalCount: number; /** Whether retained context was written for this ingest. */ contextUpdated: boolean; } /** Machine-readable error codes used by external-scanner ingest validation. */ export type ExternalScannerIngestErrorCode = "INVALID_REQUEST" | "NOT_FOUND" | "UNAVAILABLE"; /** * Structured error raised by the external-scanner ingest path. * * Keeping a stable error code at the scanner/runtime layer makes it easy for * the future gateway RPC handler to map local failures into deterministic API * responses without parsing human-readable strings. */ export declare class ExternalScannerIngestError extends Error { readonly code: ExternalScannerIngestErrorCode; constructor(code: ExternalScannerIngestErrorCode, message: string); } /** * Normalized ingest payload produced after validating an external-signal * request. * * The engine uses this as the boundary between request-shape validation and the * normal scanner result/lifecycle pipeline. */ export interface PreparedExternalScannerIngest { timestamp: number; signals: Signal[]; scannedCount: number; retainedContext: Record | null; contextUpdated: boolean; summary: Record; } /** * Validates one external scanner ingest request against the runtime scanner * field schema and converts it into normalized signal/context payloads ready * for engine commit. * * This function deliberately owns only request-shape and field-schema * validation. Runtime routing concerns such as enablement, scanner type, * lifecycle emission, and persistence stay in the engine/module layers. * * The ingest contract is output-sensitive: * - signal-only scanners accept single-signal or batch-signal payloads * - context-only scanners accept one `data` object and retain it verbatim * - mixed-output scanners accept a single signal payload and also retain the * same `data` object as current context */ export declare function prepareExternalScannerIngest(options: { address: string; request: ExternalScannerIngestRequest; config: ExternalScannerConfig; outputs: ScannerOutputs; defaultSignalType: string; now?: () => number; }): PreparedExternalScannerIngest; /** * Validates user-provided `data` against the runtime-declared external field * schema, rejecting missing required fields, wrong types, and unknown keys. * * Rejecting unknown keys is intentional because external data is operator- * supplied and later surfaced to prompts/actions; silent drops would hide * malformed payloads and make debugging harder. */ export declare function validateDataAgainstFieldSchema(scannerId: ScannerId, data: Record, fields: Record): void; export declare function normalizeDirection(direction: Signal["direction"] | undefined): Signal["direction"]; //# sourceMappingURL=external-scanner-receiver.d.ts.map