import type { DatabaseSyncLike } from '../../db.js';
import type { Context } from '../../api.js';
/**
 * GitHub webhook DLQ. Mirrors the Slack DLQ shape (src/connectors/slack/dlq.ts)
 * but carries GitHub-specific metadata: event_name, delivery_id, signature,
 * installation_id, repo_full_name. Codex P1 #5 mandates this rich context
 * so a `hippo gh dlq replay` operator can triage without re-deriving anything
 * from the raw payload.
 *
 * Buckets:
 *   - parse_error     — raw_payload was not valid JSON
 *   - unroutable      — no tenant resolved for installation_id / repo_full_name
 *   - signature_failed — HMAC did not verify against the active webhook secret
 *   - unhandled       — parsed but no handler matched the event
 */
export type DlqBucket = 'parse_error' | 'unroutable' | 'signature_failed' | 'unhandled';
export interface DlqItem {
    id: number;
    tenantId: string;
    rawPayload: string;
    error: string;
    eventName: string | null;
    deliveryId: string | null;
    signature: string | null;
    installationId: string | null;
    repoFullName: string | null;
    retryCount: number;
    receivedAt: string;
    retriedAt: string | null;
    bucket: DlqBucket | string;
}
/**
 * `tenantId: null` means the connector could not resolve a tenant for the
 * envelope (unroutable installation/repo). Stored as the sentinel
 * `'__unroutable__'` so the NOT NULL column is honored — same convention as
 * Slack DLQ.
 */
export interface WriteDlqOpts {
    tenantId: string | null;
    rawPayload: string;
    error: string;
    bucket?: DlqBucket;
    eventName?: string | null;
    deliveryId?: string | null;
    signature?: string | null;
    installationId?: string | null;
    repoFullName?: string | null;
}
export declare function writeToDlq(db: DatabaseSyncLike, opts: WriteDlqOpts): number;
export declare function listDlq(db: DatabaseSyncLike, opts: {
    tenantId: string;
    limit?: number;
}): DlqItem[];
export declare function getDlqEntry(db: DatabaseSyncLike, id: number): DlqItem | null;
export interface ReplayDlqOpts {
    /** Current webhook secret. If omitted, signature check is skipped (force-only path). */
    webhookSecret?: string;
    /**
     * Previous webhook secret during rotation (v1.3.1 hotfix — claude P1).
     * Operators rotating GITHUB_WEBHOOK_SECRET would otherwise be forced into
     * --force on DLQ rows written under the old secret. Plumbed through to
     * verifyGitHubSignature.previousSecret.
     */
    previousSecret?: string;
    /** When true, skip signature verification (used for legacy entries after secret rotation). */
    force?: boolean;
}
export type ReplayStatus = 'replayed' | 'parse_error' | 'sig_fail' | 'sig_missing' | 'unhandled' | 'not_found';
export interface ReplayResult {
    ok: boolean;
    status: ReplayStatus;
    memoryId: string | null;
    retryCount: number;
    reason?: string;
}
/**
 * Hook the webhook route injects to actually re-ingest a row. Decoupling
 * the dispatch keeps this module free of every event-type handler — the
 * route already knows how to route an envelope, so it passes that capability
 * back in.
 *
 * v1.3.2 (claude review): the v1.3.1 contract advertised an `idempotencyKey`
 * field, but the v1.3.1 ingest re-derives the key from the parsed event
 * itself, so the field was a phantom — any future hook that trusted the
 * passed-in value would dedupe against a stale key. Field removed.
 */
export type IngestHook = (ctx: Context, args: {
    rawPayload: string;
    eventName: string;
    deliveryId: string;
}) => Promise<{
    memoryId: string | null;
}>;
/**
 * Replay a DLQ row through the normal ingest path. Behavior:
 *   1. Fetch row by id. Not found → `not_found`.
 *   2. If !force and webhookSecret provided, verify signature with the
 *      current secret. Fail → bump retry_count, `sig_fail`.
 *      Missing signature on the row → `sig_missing` (no bump; --force required).
 *   3. JSON.parse the raw payload. Fail → bump, `parse_error`.
 *   4. Type-guard the envelope. Fail → bump, `unhandled`.
 *   5. If an `ingestHook` is supplied, call it and return its memoryId.
 *      If not (dry-run path), bump retry_count and return status `replayed`
 *      with memoryId=null. The webhook route wires the real hook in Task 14.
 *
 * Mirrors Slack's "always use current routing" policy: replays use the
 * deployment state NOW, not at the time of original DLQing.
 */
export declare function replayDlqEntry(ctx: Context, id: number, opts?: ReplayDlqOpts & {
    ingestHook?: IngestHook;
}): Promise<ReplayResult>;
//# sourceMappingURL=dlq.d.ts.map