/**
 * Request Decompose Subscriber — Pipeline-#4 follow-up (Option C).
 *
 * **What this fixes**
 *
 * `RequestService.plan()` is a PURE FUNCTION that returns a {@link RequestPlan}
 * but does NOT persist anything or call `taskPool.addToPool` for the planned
 * tasks. Pipeline-#4 (PR #453) wired the linkage path (workitem:queued →
 * linkWorkItem → maybeCloseRequest sibling-count gate), but never bridged
 * `plan() → addToPool`. As a result, any new actionable L2 Request landed
 * with empty `workItemIds[]` unless the orc manually invoked the
 * `break-down-request` skill.
 *
 * Steve dogfooded the symptom on 2026-05-05: POSTed Requests showed up in
 * the Requests UI with no children, and the orc had to be prompted to
 * decompose them. "Rails built but not ridden."
 *
 * **What this does**
 *
 * Listens on the bus for `request:created`. When the Request is L2 and
 * carries an actionable `intentCategory`, calls `RequestService.plan()` and
 * adds each planned task to the pool with `requestId` set on the WorkItem.
 * The existing PR #453 L4 fix at
 * `request-sla.subscriber.ts:1153` then auto-links each WorkItem ID into
 * `Request.workItemIds[]` via the `workitem:queued` → `linkWorkItem`
 * pipeline — no manual link call needed here.
 *
 * **Why a separate subscriber, not a method on RequestSlaSubscriber**
 *
 * The SLA subscriber's responsibility is "track the user-reply SLA on
 * inbound Slack/chat-v2 surfaces". Auto-decomposition is a different
 * concern (intentLevel filter, plan() invocation, fan-out add-to-pool) and
 * belongs in its own subscriber so the SLA module's surface stays focused.
 * Separation also means a future change to the decomposition heuristic
 * (e.g. richer plan() strategies, intent-aware routing) can land without
 * disturbing the SLA contract.
 *
 * **Co-existence with RequestSlaSubscriber**
 *
 * For an L2 inbound-Slack Request, both subscribers fire on the same
 * `request:created` event. Order is undefined but irrelevant:
 *   - SLA seeds the `respond_to_user` WI immediately, syncing the threadTs
 *     index for the bridge's `markResolvedByThread` lookup.
 *   - This subscriber plans + queues the actual decomposition WIs.
 *   - The first non-respond_to_user `workitem:queued` triggers the SLA
 *     subscriber's `handleWorkItemQueued`, which (a) calls `linkWorkItem`
 *     (PR #453 L4) and (b) calls `markResolved(requestId,
 *     'workitem_decompose')` to retire the SLA tracker.
 *   - The Pipeline-#4 L5 grace window inside `maybeCloseRequest` keeps the
 *     parent Request open until siblings are linked, suppressing the
 *     premature `orc_reply` cascade close that the original bug would have
 *     triggered.
 *
 * Net effect: a POSTed L2 Request now lands with WorkItems within ~ms of
 * `request:created` publish, and the close-cascade behaves as designed.
 *
 * **Idempotence on redelivery**
 *
 * Two safeguards:
 *   1. In-memory `decomposedRequestIds` Set guards against the same event
 *      being delivered twice within process lifetime. O(1) check, no
 *      cross-process synchronisation needed.
 *   2. Belt-and-suspenders: even if the in-memory guard misses (e.g.
 *      restart between deliveries), `taskPool.addToPool` already
 *      short-circuits on duplicate id. We use deterministic ids — the
 *      uuidv4 from `createWorkItem` is non-deterministic, so the dup-check
 *      protects us only against the in-memory replay; cross-process replay
 *      protection is the `request.workItemIds.length > 0` skip below.
 *
 * **Skip rules** (in order; first match wins):
 *   - `intentLevel !== 'L2'` (L0/L1 are non-decomposing surfaces)
 *   - `intentCategory ∉ ACTIONABLE_CATEGORIES`
 *   - `request.workItemIds.length > 0` (caller already manually decomposed)
 *   - `decomposedRequestIds.has(request.id)` (already processed in this
 *     process lifetime)
 *   - `plan.tasks.length === 0` or `plan.strategy === 'none'`
 *
 * @module services/v3/request-decompose.subscriber
 */
import { type ComponentLogger } from '../core/logger.service.js';
import type { EventBusService } from '../event-bus/event-bus.service.js';
import { RequestService } from './request.service.js';
import { TaskPoolService } from '../task-pool/task-pool.service.js';
import type { EventType } from '../../types/event-bus.types.js';
import type { IntentCategory } from '../../types/v2/request.types.js';
/**
 * Intent categories the auto-decompose subscriber will plan + fan-out for.
 *
 * Choice rationale:
 *   - `planning`     — explicit decomposition request; canonical use case.
 *   - `code_change`  — feature/refactor work; multi-step plan pays off.
 *   - `debugging`    — investigation + fix + verify benefits from split.
 *   - `deployment`   — multi-step coordination (build/ship/verify).
 *   - `research`     — survey + synthesise + report fans out cleanly.
 *
 * Excluded:
 *   - `query`        — single-shot lookup; decomposition adds noise.
 *   - `review`       — single artefact-bound; the orc handles inline.
 *   - `communication`— L1 surface; usually handled by SLA respond_to_user.
 *   - `other`        — explicitly opt-out for ambiguous shapes.
 *
 * The original brief listed `team_management` here — that is not a valid
 * IntentCategory in the type system (verified against
 * `backend/src/types/v2/request.types.ts:61-70`). Substituted `research`
 * which is genuinely actionable and decomposes well.
 */
export declare const ACTIONABLE_INTENT_CATEGORIES: ReadonlySet<IntentCategory>;
/**
 * Event types this subscriber listens to. Exported so wiring code can
 * cross-reference what's subscribed without re-parsing the class.
 */
export declare const REQUEST_DECOMPOSE_SUBSCRIBED_EVENTS: readonly EventType[];
/**
 * Constructor dependencies. Production wiring uses {@link RequestDecomposeSubscriber.boot};
 * tests construct directly with fakes.
 */
export interface RequestDecomposeSubscriberDependencies {
    /** Live event bus. */
    eventBus: EventBusService;
    /** Live RequestService for plan() + getById. */
    requestService: RequestService;
    /** Live TaskPoolService for addToPool. */
    taskPool: TaskPoolService;
    /** Override actionable categories (testing). */
    actionableCategories?: ReadonlySet<IntentCategory>;
    /** Optional logger override. */
    logger?: ComponentLogger;
}
/**
 * Production hook — set the active subscriber instance during boot so
 * shutdown code can find it. Tests bypass this by constructing directly.
 *
 * @param subscriber - The active subscriber, or `null` to clear.
 */
export declare function setRequestDecomposeSubscriber(subscriber: RequestDecomposeSubscriber | null): void;
/**
 * Read-only access to the active subscriber. Used by shutdown code in
 * `index.ts` and by debugging tooling.
 *
 * @returns The active subscriber, or `null` if not booted.
 */
export declare function getRequestDecomposeSubscriber(): RequestDecomposeSubscriber | null;
/**
 * Auto-decomposes new actionable L2 Requests into pool WorkItems.
 *
 * @example
 * ```typescript
 * const sub = RequestDecomposeSubscriber.boot(eventBus, requestService, taskPool);
 * sub.start();
 * // … on shutdown:
 * sub.stop();
 * ```
 */
export declare class RequestDecomposeSubscriber {
    private readonly eventBus;
    private readonly requestService;
    private readonly taskPool;
    private readonly actionableCategories;
    private readonly logger;
    private unsubscribers;
    private started;
    /**
     * In-memory dedupe of Request IDs that have already been auto-decomposed
     * by this process. Protects against bus redelivery within process
     * lifetime. Cross-process replay (restart) is handled by the
     * `request.workItemIds.length > 0` skip rule in {@link handleRequestCreated}.
     */
    private readonly decomposedRequestIds;
    /** In-flight async dispatch promises (test affordance). */
    private readonly pendingDispatches;
    constructor(deps: RequestDecomposeSubscriberDependencies);
    /**
     * Production wiring helper. Tests should use the constructor directly.
     *
     * @param eventBus - Live event bus
     * @param requestService - Live RequestService
     * @param taskPool - Live TaskPoolService
     * @returns A subscriber ready to `start()`
     */
    static boot(eventBus: EventBusService, requestService: RequestService, taskPool: TaskPoolService): RequestDecomposeSubscriber;
    /**
     * Subscribe + register the in-process handlers. Idempotent.
     */
    start(): void;
    /**
     * Wait for in-flight async dispatches to settle. Test affordance.
     *
     * @returns Promise that resolves when all pending dispatches finish.
     */
    flushPending(): Promise<void>;
    /**
     * Detach subscriptions. Safe to call repeatedly.
     */
    stop(): void;
    /**
     * Test-only accessor for the dedupe set. Useful for verifying the
     * in-memory guard from integration tests.
     *
     * @returns The set of Request IDs already processed by this instance.
     */
    getDecomposedRequestIdsSnapshot(): ReadonlySet<string>;
    /**
     * `request:created` handler. Filters by intentLevel + actionable
     * intentCategory, calls plan(), and adds each planned task to the pool
     * as a WorkItem with `requestId` set. The existing PR #453 L4 fix
     * (`request-sla.subscriber.ts:handleWorkItemQueued`) auto-links each WI
     * into `Request.workItemIds[]`, so no manual `linkWorkItem` call is
     * needed here.
     *
     * @param event - The `request:created` event from the bus.
     */
    private handleRequestCreated;
    /**
     * Filter predicate. Returns true iff the Request should be auto-decomposed.
     * Logs the specific skip reason at debug level so ops can correlate
     * "I POSTed a Request but no WIs appeared" with the rule that excluded it.
     *
     * @param request - The Request to evaluate.
     * @returns True if this subscriber should plan + addToPool for this Request.
     */
    private shouldDecompose;
    /**
     * Builds a pool-ready WorkItem from a planned task. Mirrors the
     * `break-down-request` skill's WI shape (owner=orchestrator, no preset
     * target → orc routes during fan-out) so consumers downstream can't
     * tell whether the decomposition came from the skill or this subscriber.
     *
     * @param task - The planned task from RequestService.plan()
     * @param request - The parent Request (provides id + missionId for back-refs)
     * @returns A queued WorkItem ready for `taskPool.addToPool`.
     */
    private buildWorkItemFromTask;
    /**
     * Combine PlannedTask description + acceptance criteria into a single
     * description block visible to the agent picking up the WorkItem.
     * Acceptance criteria render as a bullet list under a header so the
     * agent's prompt-builder can surface them inline.
     *
     * @param task - The planned task.
     * @returns Combined description + criteria block.
     */
    private formatDescription;
    /**
     * Wrap a dispatch so a thrown handler is logged and isolated. Mirrors
     * `RequestSlaSubscriber.safeDispatch`.
     *
     * @param eventType - The event type that triggered this dispatch
     * @param event - The event payload
     * @returns Promise that resolves when the handler finishes (or throws).
     */
    private safeDispatch;
}
//# sourceMappingURL=request-decompose.subscriber.d.ts.map