import { type StoredStrategy, type StrategyStore, type StrategyStoreIncident } from "../../core/strategy-store.js";
/**
 * File-backed {@link StrategyStore} — the persistence twin of `InMemoryStrategyStore`, so the teacher
 * mode's cross-session reuse premise actually holds on a local (TOC) deployment where every session is
 * a fresh process (an in-memory strategy repository there re-learns everything, every time).
 *
 * DESIGN STANCE — this is a **cache, not an authority store**: every entry is regenerable (worst case:
 * ask the teacher again), so losing one never loses correctness. It therefore deliberately skips the
 * journal/shadow armor of the memory file backend and does NOT fsync-harden beyond the shared
 * atomic-write primitive, does NOT take a cross-process lock, and accepts a narrow multi-process race
 * on save-side dedup (two processes can each write one copy of the same normalized entry — the read
 * side dedups, and a lost max-confidence merge is cache-grade). What it does NOT relax: scope
 * isolation and loud-refusal discipline, which are held to the same standard as the durable stores.
 *
 * Layout: `<root>/<scopeDir>/<id>.json`, one strategy per file, whole-file atomic replace on write.
 * `scopeDir = slug(scope) + "-" + sha256(scope).slice(0,24)` — the slug is a strict `[a-z0-9-]`
 * whitelist purely for readability; IDENTITY lives in the 96-bit hash, so no scope value, however
 * adversarial, can traverse out of the root or collide another scope's directory. Ids are gated to
 * `[A-Za-z0-9_-]{1,64}` at save (the escalation loop mints UUIDs; the interface is public, so a
 * hand-rolled id must not be able to name a path).
 *
 * `scope` is a NAMESPACE, not an authorization boundary — the store cannot authenticate its caller;
 * whoever holds the instance can address any scope (same posture as the memory store). Authorization
 * is the host's obligation.
 */
export interface FileStrategyStoreOptions {
    /** Directory to keep strategies under (created `0o700` if absent; a non-directory or unwritable
     *  path is refused loudly at construction — a store that cannot persist must not pretend to). */
    root: string;
    /** Per-scope capacity cap (entries), evicting the lowest-scoring on overflow. Default 100. */
    maxPerScope?: number;
    /**
     * Disclosure sink for contained store incidents (a corrupt entry quarantined, an eviction that
     * failed after a successful save, a degraded read). Absent ⇒ `console.warn`, once per op kind —
     * a contained fault must be loud somewhere, but must never flood.
     */
    onIncident?: (i: StrategyStoreIncident) => void;
}
export declare class FileStrategyStore implements StrategyStore {
    private readonly root;
    private readonly maxPerScope;
    private readonly onIncident?;
    /** Ops that already warned on the absent-sink fallback (bounded disclosure — never flood). */
    private readonly warnedOps;
    /** Isolation scope for the host incident sink (sync throw AND async rejection contained). */
    private readonly sinkNotifier;
    /** Scope dirs whose stale temps this instance already swept (once per scope per instance). */
    private readonly sweptTemps;
    constructor(opts: FileStrategyStoreOptions);
    /** `slug(scope)-sha256(scope)[0..24]` — slug is readability only; the hash is the injective key. */
    private scopeDirName;
    private scopeDirPath;
    /** The scope dir must be a REAL directory — a symlink here means the store's namespace was tampered
     *  with (a link could point retrieval at another scope's data, or writes out of the root). The
     *  check-then-use window against a same-uid untrusted local process is out of the threat model: such a
     *  process already holds the same rights as the store itself. */
    private assertScopeDirSafe;
    private incident;
    /** Sweep crashed writers' stale temps (once per scope per instance). Fresh temps are left alone —
     *  they may belong to a live concurrent writer. */
    private sweepStaleTemps;
    /** Load every VALID entry of a scope dir. Corrupt files are quarantined (`.bad` rename) with one
     *  disclosure; entries that violate the read-side invariants (foreign scope / id≠filename) are
     *  skipped with disclosure but NOT quarantined (a copied-in file may be someone's valid data);
     *  normalized duplicates collapse to the best-scoring copy. */
    private loadScope;
    /** Rename a corrupt file to `<name>.bad` — one disclosure now, zero parse cost on every later read
     *  (the suffix no longer matches the entry-file grammar). Never faults the calling operation. */
    private quarantine;
    /** Serialize + write one entry, refusing a record whose SERIALIZED form exceeds the read bound.
     *  The field caps bound RAW string bytes, but JSON escaping expands control characters up to 6x —
     *  without this door a save could succeed and then self-quarantine on the very next read (the
     *  worst possible shape: an accepted write the store itself later refuses to serve). */
    private writeEntry;
    save(s: StoredStrategy): void;
    private evict;
    find(scope: string, query: string, limit: number): StoredStrategy[];
    /** Bounded candidate view for the RETRIEVAL-adjacent paths (find/hasStrategy/save): an externally
     *  inflated directory must not make a hot operation unbounded. MAINTENANCE passes Infinity — see
     *  {@link prune}. */
    private retrievalReadCap;
    /**
     * Trims to `maxSize` over an UNCAPPED enumeration: prune is the reconciliation verb, so it must see
     * every entry file — under the bounded retrieval view, a capacity SHRINK across restarts (or
     * `maxPerScope: 0`) left files beyond the window untouched, reporting success while the "removed"
     * strategies sat on disk ready to resurrect under a later, larger capacity. Host-invoked
     * maintenance accepts the O(all files) cost the retrieval path refuses.
     */
    prune(scope: string, maxSize: number): void;
    /** Uncapped for the same reason as {@link prune}: this face feeds the seed capacity door, and an
     *  under-count there turns "refuse what cannot fit" into silent eviction of fresh seeds. */
    scopeUsage(scope: string): {
        used: number;
        capacity: number;
    };
    hasStrategy(scope: string, entry: Pick<StoredStrategy, "problem" | "strategy">): boolean;
}
