/** * AttestryDecisionExporter — implements the OpenTelemetry SpanProcessor * interface from `@opentelemetry/sdk-trace-base`. Customers register * one instance with their TracerProvider; every ended span flows * through `onEnd`, gets queued if it looks like an AI decision, and * is flushed to the Attestry batch ingest endpoint in batches. * * Design constraints — every one of these is load-bearing: * * - **`onEnd` MUST NOT throw.** SpanProcessor is on the OTel hot path; * a throw here causes telemetry to drop silently for the whole * process. We wrap every step in try/catch and log via the * configured logger. * - **Batch on size OR interval, whichever first.** A bursty workload * fills the queue past the batch size and flushes immediately; a * quiet workload flushes the partial queue every `batchInterval` * so decisions arrive within a bounded delay of the span ending. * - **`shutdown` flushes the remaining queue.** Otherwise a graceful * Node.js exit (SIGTERM during a deploy, e.g.) loses the last * in-memory batch. We await the final flush. * - **Per-span work is bounded.** The exporter does NOT record raw * span input/output text — it records a sha256 digest of each. * A 100KB tool output becomes 64 hex chars in the wire payload. * - **Decision detection runs synchronously in `onEnd`.** The async * network call happens later in `flush`. We never block the OTel * pipeline on I/O. * - **Queue cap.** A misconfigured customer (no flush, slow server) * must not OOM the process. We cap the queue at 10× batch size; on * overflow, we drop the OLDEST records (FIFO) and log a warning. * Newest records are more diagnostically valuable than 5-minute-old * ones from a stalled flusher. * - **`flush` is reentrant-safe.** Two concurrent flushes draining the * same queue are guarded by `flushInFlight` — the second waits for * the first to finish, then re-checks the queue. */ import type { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base"; import type { AttestryExporterConfig, SpanFrameworkContext } from "./types.js"; export declare class AttestryDecisionExporter implements SpanProcessor { private readonly config; private readonly client; private readonly logger; private readonly batchSize; private readonly batchInterval; private readonly queueCap; private readonly sanitizer; private readonly tagger; private readonly decider; private queue; private flushTimer; private flushInFlight; private shutdownCalled; constructor(config: AttestryExporterConfig); onStart(span: Span): void; onEnd(span: ReadableSpan): void; forceFlush(): Promise; shutdown(): Promise; private flush; private spanToDecision; private scheduleFlush; } declare function defaultDecider(ctx: SpanFrameworkContext): boolean; declare function computeInputDigest(attrs: Record): string; declare function computeOutputDigest(attrs: Record): string | undefined; declare function normalizePolicyOutcome(raw: unknown): "permitted" | "denied" | "escalated" | undefined; declare function sha256Hex(input: string): string; /** * Sort object keys recursively before serialization so the digest is * stable across runs. NOT a JCS implementation — it's a poor man's * canonical form sufficient for content addressing on the wire. * Customers who need true RFC 8785 supply a sanitizer that runs JCS. */ declare function stableStringify(value: unknown): string; export declare const __test__: { defaultDecider: typeof defaultDecider; computeInputDigest: typeof computeInputDigest; computeOutputDigest: typeof computeOutputDigest; normalizePolicyOutcome: typeof normalizePolicyOutcome; sha256Hex: typeof sha256Hex; stableStringify: typeof stableStringify; DEFAULT_BATCH_SIZE: number; DEFAULT_BATCH_INTERVAL_MS: number; QUEUE_CAP_MULTIPLIER: number; FRAMEWORK_CLAIMS_CAP: number; }; export {}; //# sourceMappingURL=exporter.d.ts.map