/**
 * ThreadManager — thin orchestrator over {@link ThreadStore} and
 * {@link SessionStore}.
 *
 * Owns user-facing lifecycle operations on the Thread topic layer plus the
 * archive-gate contract enforced at session-creation ingress sites.
 *
 * Phase 2.6 wired `ThreadManager.requireOpen` into three ingress paths:
 *   - {@link AgentManager.provisionSpawn} (child session creation)
 *   - `executeSingleHandoff` (recipient session creation)
 *   - `executeBroadcastHandoff` (N recipient sessions per fan-out)
 * Those call sites depend on this manager, so the one-method indirection
 * stopped being "structural overhead" the moment archive/delete needed the
 * session-presence cross-check anyway.
 *
 * Archive + delete require cross-store preconditions (session-presence
 * checks) — enforced here where both stores are in scope. The stores
 * themselves stay unaware of each other's layout (Convention #0), which is
 * why the gate lives at the manager layer rather than as a universal store
 * interceptor (see `archive` JSDoc for the direct-store-bypass boundary).
 */
import type { TenantId } from '../../types/ids/index.js';
import type { ProjectId, ThreadId } from '../../types/session/ids.js';
import type { SessionStore } from '../../types/session/store.js';
import type { Thread } from '../../types/thread/entity.js';
import type { CreateThreadParams, ThreadStore } from '../../types/thread/store.js';
export interface ThreadManagerDeps {
    readonly threadStore: ThreadStore;
    readonly sessionStore: SessionStore;
}
export declare class ThreadManager {
    private readonly deps;
    constructor(deps: ThreadManagerDeps);
    /** Persist a new Thread. Thin passthrough for uniformity at the manager surface. */
    create(params: CreateThreadParams, tenantId: TenantId): Promise<Thread>;
    /** Read a Thread by id; returns `null` when absent for the tenant. */
    get(threadId: ThreadId, tenantId: TenantId): Promise<Thread | null>;
    /**
     * CAS update on a Thread. Propagates {@link import('../../session/errors.js').StaleThreadError}
     * from the store on `ownerVersion` mismatch — callers re-read, re-apply,
     * and retry.
     */
    update(thread: Thread, tenantId: TenantId): Promise<void>;
    /** List Threads under a Project, ordered by `createdAt` ascending. */
    list(projectId: ProjectId, tenantId: TenantId): Promise<readonly Thread[]>;
    /**
     * Load a Thread and assert it is in `'open'` state. Used by the spawn path
     * as a precondition — a SubSession cannot be created under an archived
     * Thread. Throws on absence and on archival; returns the loaded Thread on
     * success so callers can avoid the second round-trip.
     *
     * Convention #5: deny-by-default. A missing Thread is a hard error, not a
     * silent "assume archived".
     */
    requireOpen(threadId: ThreadId, tenantId: TenantId): Promise<Thread>;
    /**
     * Flip a Thread to `'archived'` via CAS on {@link Thread.ownerVersion}.
     *
     * Preconditions (checked in order):
     *   1. Thread exists for the tenant (throws on absence).
     *   2. No attached Session is in a non-terminal state (see
     *      {@link ARCHIVAL_BLOCKING_STATUSES}). The presence check runs
     *      **before** the idempotent-archive short-circuit so that an already
     *      archived thread harboring a live session still surfaces as
     *      {@link ThreadNotEmptyError} rather than a silent success.
     *   3. If the thread is already `'archived'` the method short-circuits
     *      without an `updateThread` write (idempotent re-archival). The
     *      returned record reflects the current persisted state.
     *
     * On a fresh archive transition the underlying
     * {@link ThreadStore.updateThread} call commits with `ownerVersion + 1`.
     * A {@link import('../../session/errors.js').StaleThreadError} from a
     * concurrent writer propagates unchanged — the caller is expected to
     * re-read + retry (mirrors the `updateThread` contract).
     *
     * Gate scope (Phase 2.6): `ThreadManager.requireOpen` is wired into
     * `AgentManager.provisionSpawn` and both handoff flows, so the production
     * ingress paths cannot attach new sessions under an archived thread.
     * `SessionStore.createSession` / `updateSession` remain public and
     * ungated at the store layer — a direct caller can still mutate a
     * session after archival (the store has no `ThreadStore` handle by
     * design; cross-store awareness lives in the manager). The defensive
     * re-check above catches a smuggled live session on a subsequent
     * archive call, but does not prevent the direct-store write from
     * landing. That's an acceptable boundary — kernel callers must go
     * through the ingress paths; direct store consumers are out of scope
     * for the archive invariant.
     */
    archive(threadId: ThreadId, tenantId: TenantId): Promise<Thread>;
    /**
     * Hard-delete a Thread record. Rejects with {@link ThreadNotEmptyError}
     * (`op: 'delete'`) when ANY Session still references the Thread —
     * deletion is stricter than archival, which tolerates quiescent sessions.
     * Callers must first delete or archive-and-tombstone every attached
     * session (via {@link SessionStore.deleteSession}) before invoking.
     *
     * The session scan runs unconditionally, so orphaned sessions pointing at
     * a missing thread are still detected and reject the delete. Idempotent
     * for genuinely absent threads (no sessions, no thread record) — missing
     * thread + empty session list is a no-op at the store layer. Convention
     * #5: deny-by-default; no implicit cascade into SessionStore.
     */
    delete(threadId: ThreadId, tenantId: TenantId): Promise<void>;
}
//# sourceMappingURL=lifecycle.d.ts.map