/**
 * Webhook intake — single-route http.Server that verifies HMAC, dedups via
 * an in-memory idempotency cache, and hands a `WebhookEvent` to the daemon
 * dispatcher. Source: SCHED.1.
 *
 * Route: `POST /webhook/<subscription_id>` only — multiplexing live trigger
 * sources (schedule, file_changed, inbound_channel) belongs to the daemon,
 * not the HTTP layer.
 *
 * HMAC: header `X-OpenSquid-Signature: sha256=<lowercase-hex>`, body is the
 * raw request bytes (never re-JSON-serialized — that would re-key the
 * payload and invalidate any external signature). Compare via
 * `crypto.timingSafeEqual` on equal-length buffers; even on length mismatch
 * we still exercise the constant-time path to deny header-length probing.
 * No fallback header name — provider-specific signatures (Stripe etc.) land
 * as future adapter tasks.
 *
 * Idempotency: `Map<key, ts>` keyed `subscription_id:sha256(body)`. Duplicate
 * POSTs within `idempotencyTtlMs` (default 24h) return 200 + `idempotent:
 * true` and skip dispatch. A `setInterval` evicts stale entries hourly and
 * is `.unref()`'d so it never holds the process open. In-memory only —
 * restart loses the dedup window. DURABLE.4 may migrate to libsql later.
 *
 * Rate-limit: when `subscription.rateLimit` is set, the server consults the
 * daemon-provided `RateLimiter` under trigger kind `'webhook'`. Denied
 * webhooks return 429 + `Retry-After` (seconds). Never silently drop;
 * never silently allow.
 *
 * Logging discipline: the server NEVER logs `signingSecret` (audits carry
 * subscription id only) and NEVER logs request bodies (PII / bearer
 * tokens / carrier creds).
 */
import type { WebhookEvent } from './event.js';
import type { RateLimiter } from './rate_limit.js';
import type { Subscription } from './webhook_subscriptions.js';
/** Audit sink — caller plugs in a libsql writer / stderr / NotificationRouter. */
export type WebhookAuditEntry = {
    event: 'received';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'rejected_hmac';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'rejected_unknown';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'rejected_method';
    method: string;
    receivedAt: string;
} | {
    event: 'rejected_rate_limit';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'idempotent';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'dispatched';
    subscriptionId: string;
    receivedAt: string;
} | {
    event: 'deliver_only';
    subscriptionId: string;
    receivedAt: string;
    rendered: boolean;
    reason?: 'empty_template' | 'multicast_error' | 'misconfigured';
    emptyFieldCount?: number;
    redactedSecrets?: number;
    multicastSent?: number;
    multicastFailed?: number;
} | {
    event: 'error';
    reason: string;
    receivedAt: string;
};
export type WebhookAuditSink = (entry: WebhookAuditEntry) => void;
/** What the daemon hands the server for an authenticated webhook. */
export type WebhookDispatcher = (event: WebhookEvent) => Promise<void>;
/**
 * SCHED.2 zero-LLM handler. Caller supplies a closure that wires the
 * NotificationRouter + RoutingConfig; the server invokes it for any
 * subscription with `deliverOnly: true`. Returning a result lets the
 * server audit the outcome without coupling to mustache or channels.
 */
export type DeliverOnlyHandler = (sub: Subscription, body: unknown) => Promise<{
    rendered: boolean;
    reason?: 'empty_template' | 'multicast_error' | 'misconfigured';
    emptyFieldCount?: number;
    redactedSecrets?: number;
    multicast?: {
        sent: number;
        failed: number;
    };
}>;
export interface WebhookServerOpts {
    port: number;
    /** `'127.0.0.1'` (default) keeps the server unreachable from LAN. */
    host?: string;
    subscriptions: readonly Subscription[];
    dispatch: WebhookDispatcher;
    /** SCHED.2 — invoked for subscriptions with `deliverOnly: true`. When
     *  unset, deliver-only subscriptions audit as `misconfigured` and the
     *  request still returns 200 (never break on missing handler). */
    deliverOnly?: DeliverOnlyHandler;
    rateLimiter?: RateLimiter;
    auditLog?: WebhookAuditSink;
    /** Injected clock for tests. */
    now?: () => number;
    /** Idempotency window. Default 24h. */
    idempotencyTtlMs?: number;
}
/**
 * `http.Server` wrapper. Caller `start()`s once; `close()` waits for the
 * connection drain + clears the eviction timer. Restart = new instance.
 */
export declare class WebhookServer {
    private readonly opts;
    private readonly server;
    private readonly subscriptions;
    private readonly dispatch;
    private readonly deliverOnly;
    private readonly rateLimiter;
    private readonly auditLog;
    private readonly nowFn;
    private readonly idempotencyTtlMs;
    private readonly idempotencyCache;
    private evictTimer;
    private listening;
    constructor(opts: WebhookServerOpts);
    /** Bind to the configured port + start the eviction timer. */
    start(): Promise<void>;
    /** Stop accepting new connections, drain in-flight, clear the timer. */
    close(): Promise<void>;
    /** `Server.address()` resolves to a port number after `listen()` returns. */
    address(): {
        port: number;
        host: string;
    } | null;
    private handle;
    private evictIdempotencyCache;
}
//# sourceMappingURL=webhook_server.d.ts.map