/**
 * RequestSlaSubscriber (INBOUND-1)
 *
 * Closes the user-facing reliability gap noted by Steve on 2026-04-27:
 * inbound user messages on Slack/Chat-v2 land as Requests today
 * (`slack-orchestrator-bridge.ts:377`), but no WorkItem is auto-created on
 * the orchestrator's plate. The orc therefore has no SLA to track and can
 * silently drop a user request when busy with other work.
 *
 * This subscriber listens to `request:created` and — when the Request was
 * sourced from an inbound user channel (tags include `slack` or `chat-v2`)
 * — auto-creates a `respond_to_user` WorkItem assigned to the orc with a
 * 5-minute SLA deadline. If the orc has not transitioned the WI to a
 * terminal status by 5 minutes, the subscriber emits `request:sla_breached`
 * (CRITICAL — surfaces in the orc terminal). At 10 minutes the subscriber
 * fires the *escalation* hook, which the production wiring uses to send a
 * Slack DM nudge back to the user (so they're never blind to the miss).
 *
 * Auto-close paths:
 *   (a) {@link markResolvedByThread} — `slack-orchestrator-bridge` calls
 *       this when the orc replies in a thread, so the WI auto-transitions
 *       to `done` and the SLA timers no-op against a terminal status.
 *   (b) {@link handleWorkItemQueued} (INBOUND-1.f1) — when the orc decomposes
 *       a Request into other WorkItems via `taskPool.addToPool`, every new
 *       WI fires `workitem:queued`. The handler treats decomposition as
 *       "the orc has done the right thing" and resolves the tracked
 *       respond_to_user WI with reason `workitem_decompose`. Self-recursion
 *       is prevented by an id-shape guard
 *       (`request:${requestId}:respond_to_user`) — the respond_to_user WI's
 *       own enqueue cannot trigger its own resolution.
 *   (c) Timer self-check — every breach handler reads the WI status before
 *       publishing or escalating, so a manual orc completeItem() also
 *       silences the chain.
 *
 * Idempotency contract (Arch Veto V1):
 *   The respond_to_user WorkItem id is deterministic
 *   (`request:${requestId}:respond_to_user`); the underlying
 *   {@link TaskPoolService.addToPool} already short-circuits on duplicate
 *   id. A redelivered `request:created` event therefore fires the handler
 *   again, the bridge re-builds the same id, and addToPool no-ops — no
 *   separate idempotency store is needed.
 *
 * No new ingress event types (Arch Veto V7 spirit):
 *   The orc's revised framing on 2026-04-27 explicitly preferred reusing
 *   the existing Request creation path over adding new `inbound:*` event
 *   vocabulary. INBOUND-1 adds two events (`request:created`,
 *   `request:sla_breached`) that match the Request lifecycle the rest of
 *   the system already knows about — no parallel ingress entity.
 *
 * @module services/v3/request-sla.subscriber
 */
import { type ComponentLogger } from '../core/logger.service.js';
import { type WorkItem, type WorkItemStatus } from '../../types/v2/work-item.types.js';
import { type Request, type RequestStatus } from '../../types/v2/request.types.js';
import type { EventType } from '../../types/event-bus.types.js';
import type { EventBusService } from '../event-bus/event-bus.service.js';
import type { TaskPoolService } from '../task-pool/task-pool.service.js';
import type { RequestService } from './request.service.js';
/**
 * Wire the subscriber instance accessible via {@link getRequestSlaSubscriber}.
 * Called once from boot before the slack listener can dispatch a reply.
 *
 * @param sub - The live subscriber, or null to clear (tests)
 */
export declare function setRequestSlaSubscriber(sub: RequestSlaSubscriber | null): void;
/**
 * Read the currently-wired subscriber, or null if boot has not finished yet.
 * Returns null in test setups that don't wire one — callers must tolerate.
 */
export declare function getRequestSlaSubscriber(): RequestSlaSubscriber | null;
/**
 * Default SLA breach threshold — orc must respond within this window or
 * `request:sla_breached` fires with breachLevel=5.
 */
export declare const DEFAULT_SLA_MS: number;
/**
 * Default escalation threshold — at this point the user-facing escalation
 * hook fires (production: Slack DM back to user).
 */
export declare const DEFAULT_ESCALATION_MS: number;
/**
 * Defensive retry delay for {@link RequestSlaSubscriber.markResolvedByThread}
 * and {@link RequestSlaSubscriber.markResolvedByChatV2} on index miss.
 *
 * The Slack bridge fires `slackResolve('')` from the queue processor right
 * after delivering a message — within ~10ms of `request:created`. The
 * `handleRequestCreated` reordering covers the common case (timer/index
 * setup is now synchronous, before the `taskPool.addToPool` await), but
 * the leading `await requestService.getById(...)` still leaves a ~1ms
 * window. This single delayed retry absorbs that residual race without
 * adding latency to the happy path.
 *
 * 250ms is comfortably above any reasonable in-memory lookup + microtask
 * scheduling latency, well below the 5min SLA window. Verified for the
 * 2026-05-03 ESTestNode auto-nudge variant 2 incident.
 */
export declare const MARK_RESOLVED_RETRY_MS = 250;
/**
 * Retry parameters for {@link RequestSlaSubscriber.findWorkItemWithRetry}.
 * Cover the case where {@link RequestSlaSubscriber.markResolved} is called
 * before {@link RequestSlaSubscriber.handleRequestCreated} has completed
 * its `taskPool.addToPool(wi)` await — i.e. the WI's tracking entry exists
 * in memory but the pool persistence is still in flight.
 *
 * 5 attempts × 50ms = 250ms worst-case wait, matching MARK_RESOLVED_RETRY_MS
 * so both race windows close on the same upper bound.
 */
export declare const FIND_WI_MAX_ATTEMPTS = 5;
export declare const FIND_WI_RETRY_INTERVAL_MS = 50;
/**
 * Tags we treat as "user-facing inbound channels" — Requests with any of
 * these tags get an SLA-tracked respond_to_user WI. Slack is wired today;
 * `chat-v2` is reserved for the channel-rail Phase E surface.
 */
export declare const DEFAULT_INBOUND_TAGS: readonly string[];
/**
 * Event types the subscriber listens to.
 *
 * - `request:created` (INBOUND-1) — seed a respond_to_user WI for inbound
 *   user messages.
 * - `workitem:queued` (INBOUND-1.f1) — auto-close path b: when the orc
 *   decomposes a Request into other WorkItems, the respond_to_user WI for
 *   that Request resolves automatically.
 *
 * Future iterations may add `request:cancelled` etc. for cleanup.
 */
export declare const REQUEST_SLA_SUBSCRIBED_EVENTS: readonly EventType[];
/**
 * Build the deterministic respond_to_user WorkItem id for a Request. The
 * SLA subscriber uses this both for creating the WI and as the id-shape
 * guard against self-resolve recursion in {@link handleWorkItemQueued}.
 *
 * @param requestId - The Request id
 * @returns The deterministic WI id
 */
export declare function respondToUserWorkItemId(requestId: string): string;
/**
 * Pick the legal terminal status for resolving an SLA-tracked WI. The V3
 * `WORK_ITEM_TRANSITIONS` matrix (see `types/v2/work-item.types.ts`)
 * forbids `queued → done`, so the previous `markResolved` always-`'done'`
 * path was throwing in production while the test fake silently accepted it.
 *
 * Mapping:
 *   - `running`        → `done`      (someone explicitly claimed; close cleanly).
 *   - `done_by_worker` → `verified`  (the only edge `done_by_worker` permits
 *     toward terminal-success; `done_by_worker → cancelled` is illegal).
 *   - `proposed`       → `accepted` then handled separately — but in practice
 *     the SLA WI never lands here, so we route to `cancelled` (legal).
 *   - everything else  → `cancelled` (the V3 matrix permits `* → cancelled`
 *     from all of `queued`/`scheduled`/`accepted`/`blocked`/`escalated`).
 *
 * @param current - The WI's current (non-terminal) status.
 * @returns The legal terminal status to transition into.
 */
export declare function pickResolveTarget(current: WorkItemStatus): WorkItemStatus;
/**
 * Pick the legal terminal status for FAILING an SLA-tracked WI on
 * escalation timeout (10-minute miss).
 *
 * Mapping:
 *   - `running`        → `failed`    (canonical fail edge).
 *   - `done_by_worker` → `rejected`  (the only fail-shaped edge from
 *     `done_by_worker`; `done_by_worker → failed` is illegal).
 *   - everything else  → `cancelled` (queued/scheduled/etc. cannot reach
 *     `failed` directly, so we route them to `cancelled` — matches the
 *     "we gave up tracking, nothing actually failed" semantic).
 *
 * @param current - The WI's current (non-terminal) status.
 * @returns The legal terminal status to transition into.
 */
export declare function pickFailTarget(current: WorkItemStatus): WorkItemStatus;
/**
 * Compute the legal Request status path from the current state to `done`.
 * Returns an empty array if the Request is already terminal (no work to
 * do) — the call site is expected to guard for this.
 *
 * Per `REQUEST_TRANSITIONS` in `types/v2/request.types.ts`:
 *   - `open` → `done`                         (direct)
 *   - `running` → `done`                      (direct)
 *   - `waiting_confirmation` → `done`         (direct)
 *   - `ready` → `running` → `done`            (two-step; ready→done illegal)
 *   - `blocked` → `running` → `done`          (two-step; blocked→done illegal)
 *
 * @param from - Current Request status (must be non-terminal).
 * @returns Ordered array of statuses to transition through (excluding `from`).
 */
export declare function closeRequestPath(from: RequestStatus): RequestStatus[];
/**
 * Reason tags that represent a VERIFIED actual reply / decomposition by an
 * agent and therefore permit the parent Request to cascade-close to `done`.
 *
 * Defense-in-depth gate for {@link RequestSlaSubscriber.maybeCloseRequest}
 * (Steve 2026-04-30 incident): even if an upstream caller somehow invokes
 * `markResolved` with a non-reply reason, the Request must NOT be flipped
 * to `done` unless the reason is one of these verified paths.
 *
 * - `orc_reply`           — slackResolve callback fired (real orc reply via
 *                           reply-slack skill). Gated by the `fromOrcReply`
 *                           flag on the orchestrator bridge so timeout
 *                           placeholders cannot reach this branch.
 * - `chatv2_reply`        — chat-v2 controller persisted an agent-typed
 *                           reply to the channel (real agent reply).
 * - `workitem_decompose`  — the orc decomposed the Request into other WIs;
 *                           those WIs carry the actual work, so the
 *                           respond_to_user tracker is silenced and the
 *                           Request close is gated separately by the
 *                           sibling-count check.
 *
 * Any other reason ({@link RequestSlaSubscriber.failOrphanRespondWi} fires
 * `escalation_timeout`, callers MAY pass arbitrary diagnostic strings) is
 * treated as "do NOT auto-close the parent Request".
 */
export declare const VERIFIED_REPLY_REASONS: ReadonlySet<string>;
/**
 * Extract the Slack thread timestamp from a Request's
 * `sourceConversationItemId`. The slack-orchestrator-bridge stamps these
 * as `slack-${channelId}-${ts}` (see slack-orchestrator-bridge.ts:372),
 * so the trailing dotted-decimal segment is the threadTs.
 *
 * @param sourceId - The Request's sourceConversationItemId
 * @returns The threadTs, or null if the id doesn't match the Slack shape
 */
export declare function extractSlackThreadTs(sourceId: string | undefined): string | null;
/**
 * Extract the Slack channel id from a sourceConversationItemId.
 *
 * @param sourceId - The Request's sourceConversationItemId
 * @returns The channelId, or null if the id doesn't match the Slack shape
 */
export declare function extractSlackChannelId(sourceId: string | undefined): string | null;
/**
 * Extract the chat-v2 channel id from a Request's
 * `sourceConversationItemId`. The chat-v2 controller (INBOUND-2) stamps
 * these as `chatv2-${channelId}__${messageId}` — UUID-safe delimiter.
 *
 * @param sourceId - The Request's sourceConversationItemId
 * @returns The channelId, or null if the id doesn't match the chat-v2 shape
 */
export declare function extractChatV2ChannelId(sourceId: string | undefined): string | null;
/**
 * Extract the chat-v2 message id from a `chatv2-${channelId}__${messageId}`
 * sourceConversationItemId. The messageId acts as the auto-close lookup
 * key analog to a Slack threadTs.
 *
 * @param sourceId - The Request's sourceConversationItemId
 * @returns The messageId, or null if the id doesn't match the chat-v2 shape
 */
export declare function extractChatV2MessageId(sourceId: string | undefined): string | null;
/**
 * Thrown when the subscriber cannot resolve a Request for an event. The
 * Request was deleted between publish and dispatch — rare, but observable
 * in tests, so we encode the case as a typed error.
 */
export declare class RequestNotFoundError extends Error {
    constructor(message: string);
}
/**
 * Pluggable callback that performs the user-facing 10-minute escalation —
 * production wires this to {@link SlackService.sendMessage} so we DM the
 * user back. Tests can fake.
 */
export type EscalationSlackCallback = (args: {
    channelId: string;
    threadTs: string;
    messageText: string;
    request: Request;
    workItem: WorkItem | null;
}) => Promise<void>;
/**
 * Optional dependency container. Production wiring uses
 * {@link RequestSlaSubscriber.boot}; tests construct directly with fakes.
 */
export interface RequestSlaSubscriberDependencies {
    /** Live event bus. */
    eventBus: EventBusService;
    /** TaskPool for addToPool + transitionStatus + findWorkItem. */
    taskPool: TaskPoolService;
    /** RequestService for tag/source lookups by id. */
    requestService: RequestService;
    /** Slack DM hook — fires at 10min escalation. Optional; subscriber
     *  no-ops if missing (logs at warn level instead). */
    sendEscalationDm?: EscalationSlackCallback;
    /** Override the orchestrator session (testing). */
    orchestratorSession?: string;
    /** Override the SLA window (testing). */
    slaMs?: number;
    /** Override the escalation window (testing). */
    escalationMs?: number;
    /** Override the inbound tag list (testing / future channel sources). */
    inboundTags?: readonly string[];
    /** Optional logger. */
    logger?: ComponentLogger;
}
/**
 * RequestSlaSubscriber — the INBOUND-1 deliverable.
 *
 * @example
 * ```typescript
 * const sub = RequestSlaSubscriber.boot(eventBus, slackEscalationFn);
 * sub.start();
 * // … on shutdown:
 * sub.stop();
 * ```
 */
export declare class RequestSlaSubscriber {
    private readonly eventBus;
    private readonly taskPool;
    private readonly requestService;
    private readonly sendEscalationDm?;
    private readonly orchestratorSession;
    private readonly slaMs;
    private readonly escalationMs;
    private readonly inboundTags;
    private readonly logger;
    private unsubscribers;
    private started;
    /** requestId → tracked record; primary index for breach handlers. */
    private trackedByRequest;
    /** Slack threadTs → requestId; secondary index for {@link markResolvedByThread}. */
    private threadIndex;
    /**
     * chat-v2 channelId → requestId. INBOUND-2 secondary index used by
     * {@link markResolvedByChatV2}. Last-write-wins when multiple inbound
     * Requests pile up in the same channel — v1 polish accepts the simpler
     * 1:1 semantics; the orphan handler still cleans the WI on escalation.
     */
    private chatV2Index;
    /** In-flight async dispatch promises (test affordance). */
    private pendingDispatches;
    constructor(deps: RequestSlaSubscriberDependencies);
    /**
     * Production wiring helper. Tests should use the constructor directly.
     *
     * @param eventBus - Live event bus
     * @param requestService - Live RequestService
     * @param taskPool - Live TaskPoolService
     * @param sendEscalationDm - Slack DM callback for the 10-minute escalation
     * @returns A subscriber ready to `start()`
     */
    static boot(eventBus: EventBusService, requestService: RequestService, taskPool: TaskPoolService, sendEscalationDm?: EscalationSlackCallback): RequestSlaSubscriber;
    /**
     * Subscribe + register the in-process handlers. Idempotent.
     */
    start(): void;
    /**
     * Number of inbound user Requests still awaiting an orchestrator response —
     * i.e. live `respond_to_user` SLA trackers that have neither been answered
     * nor breached-and-cleared. Powers the `/health` orchestrator-liveness signal
     * (issue #686): `agents.active === 0` while this is `> 0` means inbound user
     * messages are queued with no one to answer them (the silent 假死 symptom).
     *
     * @returns Count of pending tracked `respond_to_user` work items.
     */
    getPendingUserRequestCount(): number;
    /**
     * Wait for in-flight async dispatches to settle. Test affordance.
     */
    flushPending(): Promise<void>;
    /**
     * Detach subscriptions + clear all SLA timers. Safe to call repeatedly.
     */
    stop(): void;
    /**
     * Mark an in-flight respond_to_user WI as done because the orchestrator
     * just replied to the matching Slack thread. Called by
     * {@link slack-orchestrator-bridge.sendSlackResponse}.
     *
     * Best-effort: a non-Slack-shaped or unknown threadTs is a no-op.
     *
     * Defensive retry (Steve 2026-05-03 auto-nudge variant 2): the bridge
     * may call this *before* {@link handleRequestCreated} has populated
     * `threadIndex` (e.g. QueueProcessor fires `slackResolve('')` right
     * after delivery, ~10ms before the SLA subscriber finishes its async
     * `request:created` handler). On index-miss we schedule a single
     * delayed retry to absorb that race without holding up the caller.
     * The reordering inside {@link handleRequestCreated} is the primary
     * fix; this retry is belt-and-suspenders to absorb any residual
     * `await requestService.getById` skew.
     *
     * @param threadTs - The Slack message timestamp the orc replied to
     */
    markResolvedByThread(threadTs: string): Promise<void>;
    /**
     * INBOUND-2: mark the in-flight respond_to_user WI as done because an
     * agent replied in the chat-v2 channel where the user's message arrived.
     * Called by `chat-v2.controller.sendMessage` after a `senderType=agent`
     * message persists to the channel.
     *
     * Best-effort: a non-tracked or unknown channelId is a no-op.
     *
     * @param channelId - The chat-v2 channel where the agent just replied
     */
    markResolvedByChatV2(channelId: string): Promise<void>;
    /**
     * Orphan-cleanup path for the Slack case in {@link markResolvedByThread}.
     *
     * The threadIndex / chatV2Index are populated when an SLA tracker is
     * registered for the Request and cleared whenever {@link markResolved}
     * runs — including the `workitem_decompose` resolve. After decomposition,
     * the Request stays open with non-terminal child WIs while the SLA
     * indexes are empty. If the orc then replies directly in the original
     * thread, neither the primary `threadIndex` lookup nor the 250ms retry
     * will find anything, and the orphan child WIs sit queued/blocked
     * indefinitely (closie incident, 2026-05-21).
     *
     * This fallback authoritatively scans `RequestService.listAll()` for an
     * open Request whose `sourceConversationItemId` references `threadTs`,
     * then force-removes any non-terminal child WIs from the pool and
     * cascades the close. We bypass the index because the index is the
     * very thing that's stale.
     *
     * @param threadTs - The Slack message timestamp the orc replied to
     */
    private resolveOrphanedRequestByThread;
    /**
     * Orphan-cleanup path for the chat-v2 case in
     * {@link markResolvedByChatV2}. Mirrors {@link resolveOrphanedRequestByThread}
     * but matches on `chatV2-` source id shape.
     *
     * @param channelId - The chat-v2 channel id where the agent replied
     */
    private resolveOrphanedRequestByChatV2;
    /**
     * Cancel all non-terminal WorkItems belonging to a Request, then cascade
     * the Request to `done`. Shared tail of
     * {@link resolveOrphanedRequestByThread} and
     * {@link resolveOrphanedRequestByChatV2}.
     *
     * Each WI is transitioned individually so a single failure (illegal
     * transition, stale claim) does not block the others — the cascade
     * close at the end only fires if at least one WI flipped to terminal
     * (otherwise `countOtherActiveWorkItems` will keep the Request open
     * via {@link maybeCloseRequest}'s sibling-count gate).
     *
     * @param requestId - The Request to clean up
     * @param reason    - Diagnostic tag recorded on each cancelled WI's
     *   metadata and on the Request's `result` field
     */
    private cancelOrphansAndCloseRequest;
    /**
     * Mark an in-flight respond_to_user WI as resolved by Request id (e.g. the
     * orc replied on Slack, or decomposed the Request into other WorkItems and
     * we want to silence the SLA chain). After the WI transitions, we also
     * cascade the close to the parent Request when this was the last
     * non-terminal WI for it (Steve 2026-04-29: Requests stuck on "Active" in
     * /tasks UI even after the team replied).
     *
     * Transition path is selected from the WI's current status to satisfy the
     * V3 state machine — `transitionStatus` enforces `WORK_ITEM_TRANSITIONS`
     * and `queued → done` is NOT a legal edge:
     *   - `queued` → `cancelled`: SLA tracker was a placeholder, never claimed.
     *     Semantic: "no longer needed because the orc handled this directly".
     *   - `running` → `done`:    Someone explicitly claimed the SLA WI; close
     *     it as a normal completion.
     *   - terminal status:        no-op (already settled).
     *
     * Before this fix, `markResolved` always called `transitionStatus(_, 'done')`
     * which threw on the queued case, the catch swallowed it at warn level, the
     * WI stayed `queued` forever, and the Request never closed — the
     * user-reported "Active count never goes down" bug.
     *
     * @param requestId - The Request whose SLA chain should be silenced
     * @param reason    - Diagnostic tag (`orc_reply` / `chatv2_reply` /
     *   `workitem_decompose`) recorded in WI metadata + Request `result`.
     */
    markResolved(requestId: string, reason: string): Promise<void>;
    /**
     * Find a WorkItem in the task pool, retrying briefly on null. Covers the
     * race where {@link markResolved} is called before
     * {@link handleRequestCreated} has finished `taskPool.addToPool(wi)`
     * (Steve 2026-05-03 auto-nudge variant 2).
     *
     * Up to {@link FIND_WI_MAX_ATTEMPTS} attempts spaced by
     * {@link FIND_WI_RETRY_INTERVAL_MS}. Total worst-case wait stays well
     * below the 5min SLA window. Resolves with `null` if the WI never
     * appears (e.g. addToPool persistently failed); the caller logs and
     * moves on.
     *
     * @param workItemId - The deterministic respond_to_user WI id
     * @returns The WorkItem if found, or null after all retries exhausted
     */
    private findWorkItemWithRetry;
    /**
     * Cascade close the parent Request after an SLA-tracked WI resolves.
     *
     * The Request is moved to `done` only when ALL of:
     *   1. `reason` is in {@link VERIFIED_REPLY_REASONS} (defense-in-depth
     *      against false-resolve paths — see Steve 2026-04-30 incident,
     *      where a `responseTimeoutMs` placeholder fired
     *      `markResolvedByThread` and cascaded the Request to `done`
     *      without an actual orc reply).
     *   2. it exists and is not already terminal (`done`/`cancelled`), AND
     *   3. no other non-terminal WIs remain for it (the orc may have
     *      decomposed the Request into other WIs that are still in flight —
     *      in that case we leave the Request alone and let the existing
     *      `cascadeRequestStatus` machinery in v3-data.service close it
     *      when those WIs finish).
     *
     * Picks the shortest legal transition path per `REQUEST_TRANSITIONS`:
     *   - `open` / `running` / `waiting_confirmation` → `done` (direct)
     *   - `ready` / `blocked`                         → `running` → `done`
     *
     * Errors are caught at the call site (markResolved); this method must
     * never propagate, so a Request-update failure does not leak into the
     * Slack-reply flow.
     *
     * @param requestId - The Request to close.
     * @param reason    - The same reason tag recorded on the WI; passed
     *   through to `Request.result` so the UI shows why it auto-closed.
     */
    private maybeCloseRequest;
    /**
     * Count WorkItems linked to the given Request that are NOT the SLA tracker
     * AND are still non-terminal. Returns 0 when only the SLA tracker existed.
     *
     * @param requestId - The Request id to scan.
     * @returns Count of other in-flight WorkItems.
     */
    private countOtherActiveWorkItems;
    /**
     * Snapshot of the tracked-WI count. Test affordance.
     */
    get trackedCount(): number;
    /**
     * `request:created` handler. Filters by inbound tag, creates the
     * respond_to_user WI, and schedules the breach + escalation timers.
     */
    private handleRequestCreated;
    /**
     * `workitem:queued` handler (INBOUND-1.f1, auto-close path b).
     *
     * Treats decomposition of a Request into other WorkItems as "the orc has
     * done the right thing" and resolves the tracked respond_to_user WI for
     * that Request.
     *
     * Self-recursion guard: the respond_to_user WI itself fires
     * `workitem:queued` from {@link handleRequestCreated}'s `addToPool` call.
     * Without a guard this handler would call markResolved against its own
     * enqueue and prematurely close the SLA chain. The id-shape check
     * (`incomingId === respondToUserWorkItemId(requestId)`) is more reliable
     * than reading `wi.metadata.slaSource` — the metadata can in principle
     * be mutated, the id cannot.
     *
     * No-ops:
     *   - event missing `requestId` (orphan WI, can't correlate)
     *   - event missing `workItemId` (malformed publisher)
     *   - the respond_to_user WI's own enqueue (id-shape match)
     *   - no tracked respond_to_user WI for the requestId (already resolved
     *     or never tracked because the source Request wasn't inbound-tagged)
     *
     * @param event - The `workitem:queued` event from TaskPoolService.addToPool
     */
    private handleWorkItemQueued;
    /**
     * 5-minute breach handler. Re-checks the WI status and emits
     * `request:sla_breached` if the WI is still non-terminal.
     *
     * @param requestId - The Request whose breach is firing
     * @param level - Breach level: 5 (first SLA) or 10 (escalation)
     */
    private handleBreach;
    /**
     * 10-minute escalation handler. Emits the level-10 breach event and —
     * if a Slack DM callback is wired — sends the user a "still working on
     * it" nudge so they're never blind to the miss. After the DM (or DM-skip),
     * transitions the orphaned respond_to_user WI to `'failed'` with
     * `slaResolvedReason: 'escalation_timeout'` so the orc queue does not keep
     * a stale `queued` WI forever (Arch N3 on PR #357).
     */
    private handleEscalation;
    /**
     * Transition an escalated respond_to_user WI to `'failed'` with
     * `slaResolvedReason: 'escalation_timeout'` so the orc queue does not
     * keep a stale `queued` WI forever after a 10-min escalation. No-op if
     * the WI is already terminal (e.g. user gave up + an out-of-band cleanup
     * already closed it).
     *
     * Mirrors {@link markResolved}'s terminal-status guard. Errors are
     * logged but never propagated — the SLA chain is already terminal at
     * this point and we do not want to mask the original DM-path outcome.
     *
     * @param workItemId - The respond_to_user WI id to close.
     * @param requestId  - The originating Request id (logging context only).
     */
    private failOrphanRespondWi;
    /**
     * Drop tracking + clear timers for a Request. Used both on auto-resolve
     * and on terminal escalation — once we reach 10min the SLA chain is done.
     */
    private cleanupTracked;
    /**
     * Check whether a Request's tags include any of the configured inbound
     * channel tags.
     */
    private matchesInboundTag;
    /**
     * Build the respond_to_user WorkItem with the standard metadata invariants.
     *
     * INBOUND-2: metadata embeds slack-* OR chatV2-* fields based on the
     * source surface so downstream consumers (status panes, escalation
     * hooks) can branch without re-parsing `sourceConversationItemId`.
     */
    private buildRespondWorkItem;
    /**
     * Wrap a dispatch so a thrown handler is logged and isolated. Mirrors
     * EventToWorkItemBridge.safeDispatch.
     */
    private safeDispatch;
}
//# sourceMappingURL=request-sla.subscriber.d.ts.map