import { type SessionRepo } from "../internal/harness.js";
import { type AcquiredSession, type PlacedSessionRow, type SessionPlacement, type SessionStore, type SessionStoreSummary } from "./session.js";
import type { StoreDurability } from "./checkpoint-store.js";
import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
export { SESSION_DEFAULT_TTL_DAYS };
/** What idle-eviction and `release` do to the underlying repo. */
export type EvictPolicy = "delete" | "forget";
export interface TtlSessionStoreOptions {
    /** Idle TTL before a cached session is evicted. Default 7 days. */
    defaultTtlDays?: number;
    /** Optional background sweep interval (ms). Off by default (sweeps opportunistically on acquire). */
    sweepIntervalMs?: number;
    /**
     * Backing session repo. Default `InMemorySessionRepo` (process-only, lost on restart). Supply a
     * **durable** repo (e.g. a TiDB-backed `SessionRepo`, or the vendored file repo) to persist
     * sessions: `acquire` then **resumes** an existing session via `repo.open`, so any stateless
     * runner instance — even after a restart or on another replica — continues the same conversation.
     */
    repo?: SessionRepo;
    /**
     * What idle-eviction and `release` do to the repo:
     * - `"delete"` — remove the session from the repo too (correct for the throwaway in-memory repo).
     * - `"forget"` — only drop the in-memory cache; the **durable** backend keeps the history so it can
     *   be resumed later (the backend runs its own retention/GC).
     *
     * Defaults to `"delete"` for the built-in in-memory repo and `"forget"` when a custom `repo` is
     * supplied — so a durable store never silently deletes history on an idle timer.
     */
    evict?: EvictPolicy;
    /**
     * design/173 §2.3 — the store's {@link StoreDurability} declaration. This cache layer cannot
     * inspect whether the injected {@link repo} persists across processes, so the DEPLOYMENT that
     * wired the repo states the fact (e.g. `FileStorageBackend` passes `"durable"`). Defaults:
     * without a custom `repo` the truth is knowable — `"process-local"` is declared; with a custom
     * `repo` and no explicit value the store declares NOTHING, which the wiring manifest reads
     * fail-closed as process-local (under-promise, never over-promise).
     */
    durability?: StoreDurability;
    /**
     * Subagent transcript persistence — the store's per-placement durability declaration (see
     * {@link SessionStore.placements}). Same declaration rule as {@link durability}: this cache layer
     * cannot inspect whether the injected repo persists placed sessions, so the DEPLOYMENT that wired
     * the repo states the fact (`FileStorageBackend` passes `{subagent:{durability:"durable"}}`).
     * Declaring it also arms the cache-layer placement obligations (list exclusion, claim refusal,
     * release-is-deletion, sweep-never-deletes) — they key on the repo-persisted tuple, which a repo
     * that ignores `placement` never mints, so declaring over such a repo is the deployment lying to
     * itself (declaration-制, wiring-manifest posture).
     */
    placements?: {
        subagent?: {
            durability: StoreDurability;
        };
    };
}
/**
 * TTL-cached {@link SessionStore} over a pluggable {@link SessionRepo}.
 *
 * Sessions are cached in process memory and evicted after `defaultTtlDays` of inactivity. With the
 * default in-memory repo they live only in this process (lost on restart). Supply a **durable** repo
 * and the same caching/concurrency logic becomes a "session center": on `acquire`, a session not in
 * the live cache is **resumed from the repo** (`repo.open`) rather than recreated — so a stateless
 * runner resumes a conversation after a restart or across replicas. The optimistic-lock seam
 * (`SessionWriteOptions.expectedLeafId` → `SessionError("conflict")`) keeps two replicas writing the
 * same session safe; the Runner reconciles a lost CAS and retries.
 *
 * See `design/10-会话持久化与会话中心.md` for the durable-backend (TiDB) contract.
 */
export declare class TtlSessionStore implements SessionStore {
    /** Managed-retention declaration (ruled 2026-08-05, retention.ts): with a custom repo the default
     *  evict policy is `forget` — cache eviction only, the durable rows live on — and there is no
     *  tenant-scoped scheduled-deletion contract on any arm. Honestly `"none"`: a deployment with a
     *  LOCKED retention policy refuses to start over this store. (The `SessionStore` interface itself
     *  is declaration-agnostic; the retention face is the structural `RetentionDeclaring` shape.) */
    readonly retention: "none";
    /** design/173 §2.3 — see {@link TtlSessionStoreOptions.durability} for the declaration rules. */
    readonly durability?: StoreDurability;
    /** Subagent transcript persistence — see {@link TtlSessionStoreOptions.placements}. */
    readonly placements?: {
        subagent?: {
            durability: StoreDurability;
        };
    };
    /** Placed-partition enumeration, present only when the backing repo can enumerate placements
     *  (see {@link SessionStore.listPlaced} — absence means the partition-GC leg is unavailable). */
    readonly listPlaced?: (kind: "subagent", opts?: {
        olderThanMs?: number;
        scope?: string;
    }) => Promise<PlacedSessionRow[]>;
    private repo;
    private entries;
    /** Session-ownership rows (the multi-tenant admission capability face — see SessionStore.ownerOf).
     *  Deliberately NOT part of `entries`: an evicted-but-durable session (`forget` / evict:"forget")
     *  still EXISTS, and its ownership row outliving the cache entry is what keeps a live session from
     *  reading as `undefined` (= re-claimable by anyone). Rows die only when the repo's history does. */
    private owners;
    /** In-flight acquisitions keyed by id, so concurrent acquire(sameId) share one session. */
    private pending;
    /** Subagent transcript persistence — placed ids whose durable deletion FAILED: the
     *  placed fact for the retry lives HERE, not on a retained cache entry, because the repo may have
     *  torn down the shared session authority before its removal failed (the File repo closes the
     *  append fd pre-rmSync) — a retained entry would serve a session whose log is closed while the
     *  file still exists (append → log_closed, transcript revival broken until restart). The entry is
     *  dropped instead, so a re-acquire replays a FRESH authority from disk; success clears the row. */
    private pendingPlacedDeletes;
    /** Subagent transcript persistence — placed deletions IN FLIGHT: acquire awaits the
     *  settlement before resolving the id, because the repo's delete tears down the shared session
     *  authority mid-flight — an acquire racing the await window could capture (or rebuild and then
     *  lose) an authority the delete is about to close, and the caller would hold a session whose
     *  first append answers log_closed while the file still exists. The gate is per-id and bounded by
     *  the delete's own settlement (both outcomes release it). */
    private deletingPlaced;
    /** Sessions pinned by a design/45 checkpoint — skipped by idle sweep until unpinned (B6/§5). */
    private pinned;
    private defaultTtlMs;
    private evictPolicy;
    private sweepTimer?;
    constructor(opts?: TtlSessionStoreOptions);
    /** Get an existing session by id (resuming from the repo if needed), or create one. */
    acquire(sessionId?: string, opts?: {
        requireExisting?: boolean;
        placement?: SessionPlacement;
    }): Promise<AcquiredSession>;
    /**
     * Placement obligation 6 (claim closure): an EXISTING placed session refuses the claim-form
     * acquire — no `requireExisting` (the read/revive form, obligation 5) and no creation placement
     * (the trusted-internals form; the creating call and its dedup/retry re-acquires stay legal, and
     * placement immutability means the argument can never REWRITE anything — the persisted tuple wins).
     *
     * …and the MIRROR direction (the pre-claim SQUAT race): the child session id is
     * published on the spawn frame BEFORE the child's prepare creates the session, so a concurrent
     * claim-form acquire could win first creation and hand the trusted placement caller an ORDINARY
     * session — host-listable, never really-deleted, the declared full tier falsified in silence. On
     * a store that DECLARES the partition (an ignoring store legally drops placements), an acquire
     * carrying a placement must therefore come back with a PERSISTED tuple whose join identity
     * matches the declaration — a missing or foreign tuple refuses loudly, on every resolution path
     * (cache hit, pending-promise inherit, open, idempotent re-create).
     */
    private assertPlacementAdmission;
    /** Resume an existing session from the repo, or create it if the backend has no such id (unless
     *  `requireExisting`, in which case a genuinely-missing id fails loud instead — design/114 Phase3). */
    private openOrCreate;
    private createAndStore;
    /** Refresh the idle timer for a session. */
    touch(sessionId: string): void;
    /** Record the most recent task run on a cached session (the `/resume` `lastRunId`); also bumps the idle
     *  timer. Best-effort — a no-op if the session isn't cached here (the run will still be re-attachable via
     *  its checkpoint). */
    noteTaskRun(sessionId: string, taskId: string, runId?: string): void;
    /** List the live cache as {@link SessionStoreSummary} projections, newest-first by `lastActiveAt` (so a
     *  shell's `/resume` shows recent sessions + their last run first). The in-memory store lists only its
     *  live cache, not evicted/durable history — a durable backend overrides this to list persisted sessions. */
    list(): Promise<SessionStoreSummary[]>;
    /**
     * design/110 (F3) — FORK a session: copy `sourceId`'s committed history into a NEW session and return its id
     * (or `null` when the source doesn't exist). Delegates to the backing repo's `fork`, so a durable repo persists the
     * branch; the in-memory repo holds it in-process. Registering the result in this cache makes `acquire(forkedId)`
     * hit immediately — the synchronous child run that `Agent(subagent_type:"fork")` launches resumes the fork without a repo round-trip.
     *
     * Adding this method is what makes {@link hasSessionFork} true for a `TtlSessionStore` wrapping ANY repo, so the
     * fork capability mounts (it was INERT before — the durable repo had `fork` but the store never exposed it).
     */
    fork(sourceId: string, owner?: string | null): Promise<string | null>;
    /** Drop a session from the live set. With a durable repo (`evict: "forget"`) the history is kept. */
    release(sessionId: string): Promise<void>;
    /** Drop only the cached view — the repo keeps the session regardless of `evictPolicy`, so a re-acquire
     *  re-opens the SAME history (B-17: the reconcile retry / prepare-throw paths must never delete). Cost
     *  on the default in-memory repo: ONE retained session object **per forgotten id** until process end
     *  (invisible to `size`/`sweep`, which track the cache) — accumulation across many distinct forgotten
     *  ids is the accepted trade-off vs silent history loss; deployments with churn use a durable repo,
     *  where retention is the backend's normal job. A pinned id stays pinned (a forget must never expose a
     *  suspended task's session to sweep eviction). */
    forget(sessionId: string): void;
    /** Pin a session so idle sweep can't evict it while a design/45 checkpoint awaits resume (§5). */
    pin(sessionId: string): void;
    /** Release a design/45 pin (on resume or checkpoint expiry); the session resumes normal idle eviction. */
    unpin(sessionId: string): void;
    /** Multi-tenant admission capability (see the SessionStore contract for the three-state semantics
     *  and the first-write-wins requirement — both are load-bearing for the deployment's owner gate). */
    ownerOf(sessionId: string): Promise<string | null | undefined>;
    /** First-write-wins idempotent upsert; a synchronous Map write is atomic per the contract. Makes no
     *  authorization decision — the authorizer re-reads {@link ownerOf} and judges there. */
    register(sessionId: string, owner: string | null): Promise<void>;
    /** The session row's incarnation cell (see the SessionStore contract), read from the ROW through the
     *  repo on every call — never from this cache: a cached view outlives a row another store over the
     *  same repo deleted or recreated, and its copy would name the previous occupant. No row ⇒
     *  `undefined`; any other repo failure rejects (a fault is not an answer). Never caches, never creates. */
    incarnationOf(sessionId: string): Promise<string | undefined>;
    /** Evict idle-expired sessions from the cache. Deletes durable history only when `evict: "delete"`. */
    sweep(now?: number): void;
    get size(): number;
    dispose(): void;
}
