/**
 * Teacher-mode strategy repository (design/14). Stores the *generalized* strategies a teacher gave
 * that actually worked, so a later similar problem can be solved without re-consulting the teacher
 * (Inter-Cascade cross-session reuse). Retrieval is deliberately **high-precision / low-recall**: a
 * wrong strategy injected into a student costs wasted turns + a likely escalation (net negative),
 * while a missed strategy just falls back to asking the teacher (baseline). `scope` is a **mandatory
 * isolation boundary** (multi-tenant: one user's strategies must never leak to another).
 */
/** How a strategy first entered its scope. `"earned"` (or absent — older data predates the field) =
 *  stored by the escalation loop after a verified success; `"seeded"` = pre-installed via
 *  {@link seedStrategies}. An audit trail, NOT a security property: the store cannot authenticate
 *  its caller, so a direct `save` can claim either value — trust in seed material is the caller's
 *  decision, this field only records what the caller declared. */
export type StrategyOrigin = "earned" | "seeded";
export interface StoredStrategy {
    /** Stable id. File-backed stores and {@link seedStrategies} require `[A-Za-z0-9_-]{1,64}` (the
     *  escalation loop mints UUIDs, which conform). NOTE: ids flow into host telemetry
     *  ({@link import("../agents/teacher.js").TeacherRunResult}`.strategiesInjected`) as join keys —
     *  do not encode sensitive text into an id. */
    id: string;
    /** The raw task objective that triggered the teacher's help — matched against future objectives. */
    problem: string;
    /** The teacher's generalized approach (1-2 sentences). */
    strategy: string;
    /** The teacher's confidence (0-3) when it gave this. */
    confidence: number;
    /** Tenant/isolation scope (e.g. "user:42"). Strategies are only ever retrieved within their scope. */
    scope: string;
    /** ISO timestamp stored. */
    ts: string;
    /** What the teacher leg was ADDRESSED as, for future staleness handling: the caller's word verbatim
     *  when the teacher was configured by string (a catalog key, tier word or CC alias), else the
     *  `Model.id` of the object it was configured with. NOT normalized to a served model id — two rows
     *  written by the same physical model under different spellings do not compare equal. */
    teacherModel?: string;
    /** Reserved for a future generalized signature (v3 semantic matching). */
    signature?: string;
    /** Provenance stamp (see {@link StrategyOrigin}). Absent = earned (backward compatible). */
    origin?: StrategyOrigin;
}
/**
 * One contained strategy-store incident, as disclosed by a persistent store's `onIncident` seat and by
 * `TeacherConfig.onStrategyStoreIncident`. Phases are split so "the write never happened" (`save`) is
 * distinguishable from "the write landed but housekeeping failed" (`evict`), and a quarantined corrupt
 * entry (`parse`) from a retrieval that degraded (`find`).
 */
export interface StrategyStoreIncident {
    op: "find" | "save" | "evict" | "parse";
    /** What was observed and what the operation degraded to. */
    error: string;
    /** The strategy id involved, when one is known. */
    id?: string;
    /** Absolute path of the offending file, when the incident is file-level (e.g. a `parse` quarantine). */
    path?: string;
}
export interface StrategyStore {
    /** Persist a strategy (implementations may merge an exact near-duplicate and cap per-scope size). */
    save(s: StoredStrategy): Promise<void> | void;
    /**
     * The most relevant strategies for `query`, within `scope`, at most `limit`.
     *
     * `limit` must be a non-negative integer, or `Infinity` for the explicit "no cap" (return every
     * match). Any other value — NaN, `-Infinity`, negative, fractional — is refused rather than folded
     * into some nearby number (ruled 2026-08-04): a retrieval cap that silently becomes 0 turns "give me
     * everything" into "give me nothing", and the caller sees only an empty result it cannot distinguish
     * from "no strategies stored".
     */
    find(scope: string, query: string, limit: number): Promise<StoredStrategy[]> | StoredStrategy[];
    /**
     * Optional: trim a scope to `maxSize` (evicting the least useful). `maxSize` must be a non-negative
     * integer; 0 is a legal "empty the scope". Unlike {@link find}'s `limit`, `Infinity` is REFUSED here
     * (ruled 2026-08-04): this is a capacity cap, and a capacity cap can be widened but never turned off.
     */
    prune?(scope: string, maxSize: number): Promise<void> | void;
    /**
     * Optional: how full a scope is (`used` entries out of `capacity`). {@link seedStrategies} uses it to
     * refuse a batch that would not fit — silently evicting freshly seeded entries would be the most
     * misleading possible "success". A store that cannot report usage simply omits this.
     */
    scopeUsage?(scope: string): Promise<{
        used: number;
        capacity: number;
    }> | {
        used: number;
        capacity: number;
    };
    /**
     * Optional: is a content-identical entry (same normalized problem+strategy — the save-side merge
     * key) already stored in `scope`? {@link seedStrategies} uses it to count only NET-NEW rows against
     * the remaining capacity, so REPLAYING a batch whose entries already landed is never refused for
     * "not fitting" space it does not need. A store that omits it gets the conservative whole-batch
     * check (sound, but a replay into a full scope is falsely refused).
     */
    hasStrategy?(scope: string, entry: Pick<StoredStrategy, "problem" | "strategy">): Promise<boolean> | boolean;
}
/**
 * Validate a retrieval cap fail-loud (ruled 2026-08-04, same posture as `WebFetchConfig.maxBytes` and
 * the workflow resilience knobs). The cap was previously applied as
 * `slice(0, Math.max(0, Number.isFinite(limit) ? limit : 0))`, which mapped NaN, `Infinity` and every
 * negative onto the SAME value — 0 — so the two opposite intentions ("no cap" and "a fumbled unit")
 * both came out as "return nothing", the most misleading possible answer for a high-precision store.
 * `Infinity` is HONORED here (a query cap can legitimately be absent), unlike a capacity cap.
 */
export declare function resolveFindLimit(limit: number): number;
/**
 * Validate a per-scope CAPACITY cap fail-loud (ruled 2026-08-04). Two silent failures lived here: a
 * negative `maxSize` was clamped to 0 and quietly wiped the whole scope, and a non-finite one made the
 * `arr.length > cap` comparison permanently false — eviction disabled, unbounded growth, no signal.
 * `Infinity` is refused by name (the "turn the cap off" misconception) rather than silently behaving
 * like the unbounded store it would create. Shared by the in-memory and file-backed stores.
 */
export declare function resolveCapacityCap(name: string, cap: number): number;
/** Id grammar for persisted/seeded strategies: filename-safe, no traversal, bounded. */
export declare const STRATEGY_ID_RE: RegExp;
/** Byte cap on `problem` (the matched objective text). */
export declare const MAX_STRATEGY_PROBLEM_BYTES = 8192;
/** Byte cap on `strategy` (the injected guidance — the teacher contract is 1-2 sentences). */
export declare const MAX_STRATEGY_TEXT_BYTES = 4096;
/** Byte caps on the remaining serialized string fields, so a legitimate save cannot mint an entry
 *  whose on-disk record dwarfs the payload caps above (the file store also bounds what it will READ,
 *  and these write-side caps are what make that read bound compatible with every honest record). */
export declare const MAX_STRATEGY_SCOPE_BYTES = 4096;
export declare const MAX_STRATEGY_TEACHER_MODEL_BYTES = 512;
export declare const MAX_STRATEGY_SIGNATURE_BYTES = 4096;
/** A real ISO-8601 timestamp is ~24-35 bytes; 64 leaves headroom for exotic offsets while keeping a
 *  "string that happens to sit in the ts seat" from bloating a serialized record. */
export declare const MAX_STRATEGY_TS_BYTES = 64;
/** A stored `ts` may not sit further in the future than this — a far-future timestamp would pin the
 *  entry at maximum recency forever (a rank-to-the-top vector). Small clock skew stays legal. */
export declare const MAX_STRATEGY_TS_FUTURE_MS: number;
/** Total byte budget across the strategy texts one retrieval hands to a prompt. Enforced by the file
 *  store's `find` (close to the tamperable data) AND at the escalation loop's injection site (so the
 *  bound holds for ANY backend — the in-memory store deliberately accepts legacy-shaped entries). */
export declare const MAX_STRATEGY_INJECTION_TOTAL_BYTES = 16384;
/**
 * Structural validity of a (possibly untrusted, e.g. read-from-disk) value as a {@link StoredStrategy}:
 * strings where strings are promised, id grammar (filename safety), byte caps, confidence domain
 * (finite, 0-3 per the declared contract), scope non-emptiness, origin closed set. Returns a reason
 * string when invalid, `null` when acceptable. Deliberately does NOT judge the timestamp's VALUE —
 * that is a write-side rule only ({@link validateStrategyForWrite}): applying it on read would let a
 * host clock stepping backwards mass-invalidate entries that were legal when written. A MALFORMED
 * (unparseable) `ts` is also allowed through — read-side ranking clamps it to "oldest", so it
 * self-buries instead of pinning.
 */
export declare function storedStrategyShapeIssue(v: unknown): string | null;
/**
 * The write-side door: refuse an entry a persistent store must not accept — the structural checks of
 * {@link storedStrategyShapeIssue} PLUS the future-timestamp bound (a far-future `ts` would pin the
 * entry at maximum recency; refusing it at write keeps the rank honest, while the read side merely
 * clamps so a backwards clock step cannot invalidate history).
 */
export declare function validateStrategyForWrite(s: StoredStrategy): void;
/**
 * Compile a query into the shared high-precision match predicate. Returns `null` when the query has
 * no significant terms (nothing meaningful to match on → don't inject random strategies). Single
 * source for the in-memory and file-backed stores, so retrieval semantics cannot fork between
 * backends.
 *
 * TWO acceptance arms, OR-combined (retrieval semantics, pinned):
 *  - full arm — every significant term (alphanumeric AND CJK) present in the problem;
 *  - alphanumeric arm — every significant ALPHANUMERIC term present (and there is ≥1), so a mixed
 *    query ("请帮我 extract tables from report.pdf") still matches an English-only stored problem —
 *    requiring the CJK boilerplate too would un-match that pair.
 *
 * ACCEPTED COST (documented, pinned): via the alphanumeric arm, a mixed query whose alphanumeric
 * terms all match can hit a problem whose CJK terms CONFLICT (提取 X vs 删除 X). The mitigation is
 * the surrounding depth (negation preamble, verify-before-use instruction, injection audit);
 * tightening precision is a measured-recalibration change, not a retrieval patch.
 */
export declare function compileStrategyQuery(query: string): ((problem: string) => boolean) | null;
export declare function normalizeStrategyText(s: string): string;
/** Duplicate key for save-side merge and read-side dedup: same normalized problem AND strategy. */
export declare function strategyDedupKey(s: Pick<StoredStrategy, "problem" | "strategy">): string;
/** Ranking score: confidence weighted by recency (no `uses` — avoids a reinforcement feedback loop). */
export declare function scoreStoredStrategy(s: StoredStrategy): number;
/**
 * Default in-memory store. Retrieval: a stored problem must contain **every significant** query term
 * (and there must be ≥1) — near-exact on the meaningful terms, ignoring stopword noise. High precision
 * by design. Ranked by `confidence × recency`. Per-scope capacity cap with eviction on save.
 */
export declare class InMemoryStrategyStore implements StrategyStore {
    private byScope;
    private readonly maxPerScope;
    constructor(maxPerScope?: number);
    save(s: StoredStrategy): void;
    find(scope: string, query: string, limit: number): StoredStrategy[];
    prune(scope: string, maxSize: number): void;
    scopeUsage(scope: string): {
        used: number;
        capacity: number;
    };
    hasStrategy(scope: string, entry: Pick<StoredStrategy, "problem" | "strategy">): boolean;
    /** Total stored strategies (across scopes); for tests/observability. */
    get size(): number;
}
/** One entry to seed: a {@link StoredStrategy} minus the fields the seeder stamps (`scope` comes from
 *  the call, `origin` is forced to `"seeded"`, `ts` defaults to now). */
export type SeedStrategyEntry = Omit<StoredStrategy, "scope" | "origin" | "ts"> & {
    ts?: string;
};
/** Result of {@link seedStrategies}: how many entries landed, and where the write phase stopped if it
 *  did. `failedAt` present = a PARTIAL seed (entries before it are in the store); the batch is
 *  idempotent by content, so re-running the same call is the supported recovery. */
export interface SeedStrategiesReport {
    written: number;
    failedAt?: {
        id: string;
        error: string;
    };
}
/**
 * Seed a batch of strategies into `scope`, in two phases:
 *
 *  1. **Validate the whole batch first** — any invalid entry (shape/id grammar/byte caps/timestamp
 *     discipline, see {@link validateStrategyForWrite}), a duplicate id OR duplicate normalized
 *     content within the batch (two content-identical entries would merge into one, making `written`
 *     over-report and one seeded id unfindable), or a batch larger than the scope's remaining
 *     capacity (when the store reports usage) refuses the WHOLE batch loudly with ZERO writes.
 *     Silently evicting freshly seeded entries would be the most misleading possible "success", so a
 *     batch that cannot fully fit does not start.
 *  2. **Write one by one** — a mid-batch failure returns a `{written, failedAt}` report instead of
 *     pretending to be a transaction; entries are idempotent (same id + content re-saves merge), so
 *     replaying the same batch completes the remainder.
 *
 * HONEST LIMITS (cache-store posture — no locks, no reservations):
 *  - the capacity preflight is a snapshot, not a reservation: with a CONCURRENT writer on the same
 *    scope, entries can still be evicted between the check and the writes (single-writer deployments
 *    — the aggregated backend under its boot lock — get the full guarantee);
 *  - an entry content-identical to one ALREADY STORED merges into the existing entry — the STORED id
 *    and the STORED `origin` stamp win (content earned before seeding stays `earned`; the requested
 *    id is not created). This counts as written: the content IS in the store, and replaying a batch
 *    relies on exactly that merge. Injection audit rows always carry the stored id, so joins hold.
 *
 * Every entry is stamped `origin: "seeded"`. That stamp is an AUDIT clue, not a security property —
 * the store cannot authenticate the seeder, and a caller with store access can write anything; trust
 * in the seed material is the caller's decision.
 */
export declare function seedStrategies(store: StrategyStore, scope: string, entries: readonly SeedStrategyEntry[]): Promise<SeedStrategiesReport>;
