/**
 * design/376 片① — the LLM distillation DRIVER's single source (the "driver half" of design/339
 * §1.1: the driver proposes, the engine adjudicates). Everything here is the product code the
 * memory benchmark measures — the bench adapters import THESE exports, so every compare run is an
 * end-to-end regression of the shipped driver (the true-load-path law: the bytes the rig scores
 * are the bytes the npm package ships).
 *
 * Structure, and why each piece lives here:
 *  - the PROMPT CONTRACT (frozen, versioned): the prompt is a behavior surface. v1 of the product
 *    contract is the dl-3 contract BYTE-IDENTICAL — the version whose readings the benchmark
 *    archived. Any change to these strings is a NEW contract version and must produce bench
 *    readings before it ships (no-readings prompt edits are unreviewed behavior changes).
 *  - `parseJsonAnswer` / `sanitizeLlmGroups` / `planParseRepairs`: the structural adapter between
 *    a model answer and a protocol-shaped proposal, every repair COUNTED (a silent repair lets a
 *    sloppy plan read as a clean one).
 *  - `scheduleUnderFuse`: the driver-side plan selection under the engine's supersession fuse
 *    (next-fit decreasing, no backfill). The ceiling itself is NEVER re-derived here — callers
 *    compute it with the engine's own exported `supersessionFuseCeiling` (a second implementation
 *    would be a drift seam).
 *  - `mintLlmConsolidationPlan`: the live half (grouping + writing passes), archiving everything
 *    the model was told and answered.
 *  - `llmPlanDistiller`: a minted plan replayed as a per-cycle distiller (the run-cache form: mint
 *    once, filter per cycle — never re-mint inside one run).
 *  - `driveConsolidationToFixpoint`: the host loop over the four engine verbs (snapshot → commit →
 *    reconcile, repeated), counting exclusively STORE deltas.
 *
 * The engine holds no distillation power and no model seat (design/339 §1.1 boundary): this module
 * calls the engine's HOST verbs and nothing else writes.
 */
import type { MemoryEntry } from "./types.js";
import type { ConsolidationProposal } from "./consolidation.js";
export declare const DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT = 32;
export interface LlmDistillerContract {
    /** The contract's registered version id (a NEW id for any byte change, with bench readings). */
    version: string;
    grouping: {
        system: string;
        instructionLines: readonly string[];
        instructions: string;
    };
    writing: {
        system: string;
        instructions: string;
    };
    sampling: {
        temperature: number;
        maxTokens: {
            grouping: number;
            writing: number;
        };
    };
    /** Format-failure retries per call. Frozen INTO the contract: the archived readings were taken
     *  under this value, so changing it moves the thing under measurement. */
    retries: number;
}
export declare const LLM_DISTILLER_CONTRACT: LlmDistillerContract;
/** dl-2: the SAME contract with the retrievability clause disambiguated, nothing else touched. */
export declare const LLM_DISTILLER_CONTRACT_DL2: LlmDistillerContract;
/** dl-3: dl-2 with the SIZE clause substituted, nothing else touched. */
export declare const LLM_DISTILLER_CONTRACT_DL3: LlmDistillerContract;
export declare const LLM_DISTILLER_CONTRACTS: Record<string, LlmDistillerContract>;
/**
 * The PRODUCT contract, v1 = dl-3 byte-identical (the version whose benchmark readings are
 * archived). The shipping rule this alias exists to make auditable: a change to the product prompt
 * is a NEW `version` (pd-2, …) and must produce rig readings BEFORE it ships — the contract-hash
 * pin in the test suite reds on any silent byte drift under an unchanged version id.
 *
 * STANDING OBLIGATION on pd-2 (design/376-C1, ruled — mandatory, not optional): the next contract
 * version MUST carry, as its FIRST A/B item, the root-elimination arm for the shared-grouping
 * influence channel — the clean arm's writing pass without the grouping-authored `intent` (the
 * template's own "(none stated)" arm) and without the `key` name fallback, which empties the one
 * byte path an unattributed candidate has into a clean product and retires most of the D-1b guard
 * machinery. The A/B must publish an EXPLICIT continuation verdict, one of three states — switch
 * (root elimination ships) / hold (guard form stays, with readings) / redirect — it may not be
 * left hanging. The exposure-partitioned topology itself deliberately did NOT bump this version
 * (the candidate-set composition is topology, not wording; the wording axis stays single-variable
 * for exactly this A/B).
 *
 * WHERE THAT OBLIGATION NOW LIVES IN CODE: the root-elimination arm is the writing-channel option pair on
 * {@link mintLlmConsolidationPlan} (`writingIntent: "none-stated"` + `productKeyFallback: false`, recorded as
 * `minting.writingChannel`); the A/B readings, verdict and ruling are tracked in `docs/memory-distillation-ga.json`.
 */
export declare const MEMORY_DISTILLER_CONTRACT_V1: LlmDistillerContract;
/**
 * The A/B witness: which grouping lines differ between two contracts, by index — published beside
 * readings so "only the wording moved" is a checkable statement about the prompts actually sent.
 */
export declare function contractGroupingDiff(a: LlmDistillerContract | undefined, b: LlmDistillerContract | undefined): {
    sameLineCount: boolean;
    changedClauseIndexes: number[];
    before: Array<string | null>;
    after: Array<string | null>;
    otherClausesIdentical: boolean;
    writingPromptIdentical: boolean;
    systemPromptsIdentical: boolean;
    samplingIdentical: boolean;
};
/** The snapshot row shape the engine's `snapshotConsolidationCandidates` answers. */
export interface DistillerCandidate {
    entry: MemoryEntry;
    marked: boolean;
}
/** One JSON-only completion request (no streaming, no tools — the contract's whole call shape). */
export interface DistillerChatRequest {
    system: string;
    user: string;
    maxTokens: number;
    temperature: number;
}
export interface DistillerChatAnswer {
    content: string;
    /** The provider's finish reason; `"length"` marks a token-ceiling cut-off (retried, disclosed). */
    finishReason?: string | null;
    usage?: {
        promptTokens?: number;
        completionTokens?: number;
    } | null;
    /** The model id the provider REPORTS having served (audit anchor beside the requested id). */
    model?: string | null;
}
export type DistillerChatFn = (req: DistillerChatRequest) => Promise<DistillerChatAnswer>;
/** True when the id is an ALIAS spelling (re-pointable upstream), refused wherever a model id is
 *  taken. A conservative SCREEN over the common alias grammars, not a full catalog — the honest
 *  direction is one-way: `true` ⇒ alias, while a `false` only means "not a spelling this screen
 *  knows". Screened: any id whose FINAL dash segment is `latest`/`default` regardless of how many
 *  segments the family name has (`chatgpt-4o-latest`, `claude-3-5-sonnet-latest`,
 *  `gemini-1.5-pro-latest`), plus the single-family `-chat` form (`deepseek-chat`, `qwen-chat` —
 *  the alias family upstream re-points have actually ridden). Multi-segment `-chat` ids are NOT
 *  screened on purpose: that suffix also names pinned chat variants of fixed weights
 *  (`llama-2-7b-chat`), and refusing those would reject explicit ids. */
export declare function isAliasModelId(model: string): boolean;
/**
 * An OpenAI-compatible chat seat over an explicit endpoint. Screened at CONSTRUCTION (the
 * bad-value loudness law): a missing key, an alias model id or an unservable request root refuses
 * with a code before any call is made — an alias can be re-pointed upstream and would move the
 * driven model with no trace in the archived plan.
 *
 * The root is judged by {@link assertServableRequestRoot} — the SAME law the brains apply, because
 * emptiness alone let userinfo, a non-http(s) scheme and a non-URL spelling reach `fetch` as a
 * TRANSPORT error. Only the ENVELOPE is this seat's; the driver routes config throws apart. */
export declare function openAiCompatChatSeat(seat: {
    baseUrl: string;
    apiKey: string;
    model: string;
}): DistillerChatFn;
export type ParsedJsonAnswer = {
    ok: true;
    value: unknown;
    repaired: boolean;
} | {
    ok: false;
    error: string;
    sample: string;
};
/**
 * Parse ONE JSON object out of a model answer. Fenced and prefixed answers are tolerated (a
 * formatting failure, not a distillation one) and the tolerance is COUNTED. A truncated answer is
 * never salvaged — half a plan scored (or committed) as a whole plan is the failure mode this
 * function exists to refuse.
 */
export declare function parseJsonAnswer(text: unknown): ParsedJsonAnswer;
export interface PlanParseRepairs {
    grouping: number;
    writing: number;
    groupingCalls: number;
    writingCalls: number;
    /** Calls whose answer was cut off at the token ceiling — a HARNESS parameter biting, named per
     *  call and never folded into a repair count it is not. */
    lengthCapped: string[];
    retries: number;
}
/**
 * Format repairs per PHASE, re-derived from a plan's own archived transcript — one derivation
 * produces the number (the grouping stage cannot be counted while the writing stage silently is
 * not), and any reader can re-run it against the archive and get the same answer.
 */
export declare function planParseRepairs(plan: {
    minting?: {
        transcript?: Array<{
            label?: unknown;
            answer?: unknown;
            finishReason?: unknown;
        }>;
    };
} | undefined | null): PlanParseRepairs;
export interface SanitizedLlmGroups {
    groups: Array<{
        key: string;
        intent: string;
        members: Array<{
            n: number;
            id: string;
        }>;
    }>;
    repairs: {
        hallucinatedMembers: number;
        duplicateMembers: number;
        emptyGroups: number;
        oversizeGroups: Array<{
            key: string;
            members: number;
            cap: number;
        }>;
        nonIntegerMembers: number;
    };
    claimedIds: string[];
}
/**
 * Turn the model's groups into a protocol-shaped product list, repairing only STRUCTURE and
 * counting every repair:
 *
 *   hallucinatedMembers — a number that is not in the candidate list at all;
 *   duplicateMembers    — a number claimed by a second group (first claim, in the model's own
 *                         group order, wins; the later claim is dropped);
 *   emptyGroups         — a group left with no members after the two above;
 *   oversizeGroups      — a group over `maxInputsPerProduct`. NOT split: chunking a group is a
 *                         distillation decision, and making it on the model's behalf would put
 *                         this module in the driver's seat. They ride on and the schedule reports
 *                         them as structurally infeasible;
 *   nonIntegerMembers   — a member reference that is not a strict integer. STRICT because
 *                         `parseInt` is a silent truncator and the thing truncated is a ROW
 *                         reference: "2foo"→2 and "1.5"→1 would each fold a DIFFERENT entry than
 *                         the model named while every repair counter stayed at zero.
 */
export declare function sanitizeLlmGroups(groups: unknown, { candidateIds, maxInputsPerProduct }: {
    candidateIds: readonly string[];
    maxInputsPerProduct?: number;
}): SanitizedLlmGroups;
export interface LlmConsolidationPlanProduct {
    key: string;
    intent: string;
    name: string;
    description: string;
    type: "project" | "reference";
    body: string;
    inputIds: string[];
    /** The committed rev of each input AT MINT TIME (id → rev). The replay filter refuses to fold an
     *  input whose committed rev has moved since the model read it — the product's body was written
     *  over the OLD bytes, and superseding the edited entry under it would suppress a newer value
     *  behind stale prose. Absent on plans minted before this field existed: those replay by id
     *  alone, exactly as they always did (the archived-readings compatibility arm). */
    inputRevs?: Record<string, string>;
}
/** design/376-C1 §2.2 — one arm's mint accounting inside an exposure-partitioned plan (usage
 *  "两臂合计+分列": the merged plan's top-level `minting` carries the totals, these rows carry the
 *  per-arm split PLUS each arm's verbatim grouping prompt — the clean arm's prompt bytes are the
 *  C1-G5 audit face: any reader can mechanically verify zero marked-entry bytes reached it). */
export interface LlmConsolidationPlanArm {
    arm: "clean" | "marked";
    candidateCount: number;
    calls: number;
    promptTokens: number;
    completionTokens: number;
    retries: number;
    truncations: number;
    groupingPrompt: string;
}
export interface LlmConsolidationPlan {
    kind: "llm-consolidation-plan";
    contractVersion: string;
    model: string;
    /** The endpoint the mint ran against, when the caller supplied one (audit locator only). */
    baseUrl?: string;
    mintedAt: string;
    maxInputsPerProduct: number;
    candidateCount: number;
    products: LlmConsolidationPlanProduct[];
    /** design/376-C1 D-2 — present ⇔ this plan was minted by {@link mintExposurePartitionedPlan}
     *  (clean/marked candidates partitioned into physically separate call chains). The DRIVE threads
     *  this onto the committed proposal, where it is the engine's per-product-fold attestation.
     *  Absent on every plan minted before the field existed — those replay UN-attested (run-level
     *  fold, the safe default), which is exactly right for prompts composed over the whole library. */
    mintExposure?: "partitioned";
    /** design/376-C1 D-1b — the mint's served-world roster: every candidate actually RENDERED into
     *  some arm's grouping call, with the rev and marked flag it was served at. The driver's
     *  cross-cycle invalidation predicate compares each row against the fresh snapshot
     *  (offered ∧ same rev ∧ same marked, or departed-with-own-fold-evidence) — any other state
     *  means the world the prompts were composed over no longer stands. A single withheld marked
     *  row (markedCount = 1, no marked arm) is deliberately NOT in the roster: no call ever saw it. */
    servedCandidates?: Array<{
        id: string;
        rev: string;
        marked: boolean;
    }>;
    /** design/376-C1 A-5 disclosure — how many marked candidates were withheld from the CLEAN arm's
     *  prompts (= the marked candidate count when the partition engaged). Present ⇔ partitioned
     *  mint over a library with at least one marked row. */
    withheldFromCleanArm?: number;
    minting: {
        calls: number;
        groupingAttempts: number;
        groupingParseRepairs: number;
        parseRepairs: PlanParseRepairs;
        repairs: SanitizedLlmGroups["repairs"];
        writeFailures: Array<{
            key: string;
            members: number;
            reason: string | null;
        }>;
        usage: {
            promptTokens: number;
            completionTokens: number;
        };
        prompts: {
            grouping: string;
            writingTemplate: string;
            systems: {
                grouping: string;
                writing: string;
            };
        };
        transcript: Array<{
            label: string;
            ms: number;
            usage: DistillerChatAnswer["usage"];
            finishReason: string | null;
            model: string | null;
            answer: string;
        }>;
        /** Per-arm split of a partitioned mint (see {@link LlmConsolidationPlanArm}); absent on
         *  single-call-chain plans, INCLUDING the zero-marked degenerate of the partitioned mint
         *  (whose bytes must stay identical to the plain mint's, modulo the attestation echo). */
        arms?: LlmConsolidationPlanArm[];
        /** pd-2 root-elimination A/B — the WRITING CHANNEL this plan was minted under, PRESENT ONLY when non-default,
         *  so a default plan's bytes are unchanged and a reading can attribute its arm from the archive alone. */
        writingChannel?: {
            intent: "grouping" | "none-stated";
            keyFallback: boolean;
        };
    };
}
export type MintLlmConsolidationPlanResult = {
    ok: true;
    plan: LlmConsolidationPlan;
} | {
    ok: false;
    reason: string;
    calls: LlmConsolidationPlan["minting"]["transcript"];
};
/**
 * Mint a fold plan from a live model over one candidate snapshot. `candidates` is the snapshot
 * shape the engine hands the driver (`{ entry, marked }`). Everything the model saw and everything
 * it answered is returned inside the plan so the run can be re-read, re-scored and disputed from
 * the archive alone.
 */
export declare function mintLlmConsolidationPlan({ candidates, chat, model, baseUrl, maxInputsPerProduct, contract, onProgress, armLabel, writingIntent, productKeyFallback, }: {
    candidates: readonly DistillerCandidate[];
    chat: DistillerChatFn;
    /** The requested model id, recorded into the plan (audit anchor). Alias spellings refuse. */
    model: string;
    baseUrl?: string;
    maxInputsPerProduct?: number;
    contract?: LlmDistillerContract;
    onProgress?: ((p: {
        phase: "grouping";
        groups: number;
        claimed: number;
        of: number;
    } | {
        phase: "writing";
        done: number;
        of: number;
    }) => void) | null;
    /** design/376-C1 — the partitioned mint's TRANSCRIPT arm tag (`grouping.clean#1` /
     *  `writing.marked#1:key#1` label forms). Prompt bytes are untouched — this names calls in the
     *  archive only, and stays compatible with `planParseRepairs`' `startsWith("grouping")` phase
     *  split and its `#N` retry grammar. Absent ⇒ the historical label forms, byte-identical. */
    armLabel?: "clean" | "marked";
    /**
     * pd-2 root-elimination A/B (the standing obligation on {@link MEMORY_DISTILLER_CONTRACT_V1}) — the WRITING
     * CHANNEL: which grouping-authored bytes reach the writing pass. `"grouping"` (absent ≡ this, the shipped form)
     * fills `{{INTENT}}` with the grouping pass's own `intent`; `"none-stated"` fills it with `(none stated)` for
     * EVERY group, so the clean arm's authoring prompt carries no grouping-authored intent bytes. Mint TOPOLOGY, not
     * wording: the contract's frozen text is untouched and no version moves. Recorded on the plan
     * (`minting.writingChannel`) whenever non-default.
     */
    writingIntent?: "grouping" | "none-stated";
    /**
     * The channel's second byte path: `true` (absent ≡ this) names a product after the grouping-authored `key` when
     * the writing answer carries no `name`; `false` treats a missing or empty `name` as a FORMAT failure (retried,
     * then a counted write failure — R3), so nothing grouping-authored ever becomes a product's name on this arm.
     */
    productKeyFallback?: boolean;
}): Promise<MintLlmConsolidationPlanResult>;
/** ONE rev sentinel for every face that RECORDS or RE-CHECKS a candidate's rev (the mint roster
 *  below, the driver's cross-cycle roster re-check, the replay's offered map). The snapshot
 *  interface is structural — a host-supplied engine can hand entries whose rev is not a string at
 *  runtime — and the recording and re-checking sides MUST agree on the fallback, or a rev-less
 *  candidate compares unequal to its own roster row and self-invalidates the mint it was just
 *  served into (a paid mint discarded on its own cycle, re-minted and re-discarded forever). */
export declare const normalizedCandidateRev: (rev: unknown) => string;
/**
 * design/376-C1 §2.2 — mint an EXPOSURE-PARTITIONED fold plan: candidates are bisected on the
 * snapshot's per-candidate `marked` flag and each subset is minted through its own physically
 * separate call chain, so no clean product's authoring chain (grouping intent included) is ever
 * served a marked entry's bytes. This is what makes the engine's per-product fold SOUND — the
 * attestation the plan carries (`mintExposure: "partitioned"`) is the freeze's gate, and this
 * function is the single source the product driver and every bench adapter share.
 *
 *  - CLEAN arm: `mintLlmConsolidationPlan` over the clean subset — contract, templates, retries,
 *    sampling all byte-identical to the plain mint (dl-3 恒等; only the candidate-set composition
 *    moves, which is topology, not wording — no prompt-contract version bump, D-10/A-10).
 *  - MARKED arm: the same contract over the marked subset, minted only at `markedCount ≥ 2`
 *    (design C1-B): a single marked row has no group to fold and simply stays where it is — an
 *    index handle row loses no discoverability by standing still. Withheld rows are disclosed
 *    (`withheldFromCleanArm`) and kept OUT of the served roster (no call saw them).
 *  - ZERO-marked degenerate: exactly ONE arm over exactly the full set with the HISTORICAL
 *    transcript labels — byte-identical to the plain mint modulo the attestation echo and the
 *    roster (C1-G1's identity claim).
 *  - Either arm failing fails the WHOLE mint (one mint, one verdict; both arms' calls ride the
 *    failure transcript). This widens nothing: under the run-level mint an adversarial marked row
 *    already sat inside the single grouping call and could fail it whole.
 *  - Cross-arm group structure is impossible by construction: each arm's `sanitizeLlmGroups`
 *    valid map is built over that arm's own 1..N numbering.
 */
export declare function mintExposurePartitionedPlan(args: Parameters<typeof mintLlmConsolidationPlan>[0]): Promise<MintLlmConsolidationPlanResult>;
/**
 * The detector's frozen algorithm parameters — a CONTRACT SIBLING, published beside the readings
 * so a run's verdict can be re-derived from the archive alone, and deliberately NOT a knob: this
 * is a safety gate, and a safety gate with an off switch is a safety gate that gets switched off
 * under deadline pressure. A new parameter set is a NEW `version`, exactly like the prompt
 * contract's own rule.
 */
export interface MemoryDistillerPurityContract {
    version: string;
    /** Unicode normalization applied to BOTH sides before comparison (so a decomposed copy of a
     *  composed original still compares equal). */
    normalization: "NFC";
    /** Case is PRESERVED. Case-folding would widen the detector toward paraphrase matching and start
     *  minting false positives on ordinary prose — this gate answers "were these bytes copied", and
     *  a case change is already outside "verbatim". */
    caseFold: false;
    /** Shingle width, in whitespace-separated words: the comparison unit on every side. */
    shingleWords: number;
    /** A shingle shorter than this many characters is dropped before comparison (the floor that
     *  keeps short, high-frequency phrases out of the evidence set). */
    minShingleChars: number;
}
export declare const MEMORY_DISTILLER_PURITY_CONTRACT_V1: MemoryDistillerPurityContract;
/** One witness: a marked-side-unique shingle found verbatim in a clean product's own bytes. */
export interface CleanArmLeakFinding {
    /** The product's INDEX in `plan.products` — the locator safe to put in a message. Unlike
     *  {@link productKey} it is harness-assigned, so it cannot itself carry a payload. */
    productIndex: number;
    /** The product's key AS THE MODEL WROTE IT (`sanitizeLlmGroups` passes `String(g.key)` through
     *  unbounded). Useful when reading the archive; NEVER safe to interpolate into a notice, an
     *  announcement or a log line that reaches a model context — the key is model-authored text and
     *  can carry the same payload the shingle does. Use {@link productIndex} there. */
    productKey: string;
    field: "name" | "description" | "body";
    /** The offending shingle VERBATIM — the evidence, not a description of it. Same rule as
     *  {@link productKey}: this is for the archive and an operator's own eyes, never for a message
     *  that lands in front of a model. */
    shingle: string;
}
export interface CleanArmLeakVerdict {
    /** False ⇔ the gate had nothing to check (see {@link notEngagedReason}); never a pass claim. */
    engaged: boolean;
    notEngagedReason?: string;
    /** Size of the marked-side-unique evidence set (clean-arm bytes already subtracted). */
    markedUniqueShingles: number;
    /** How many products were scanned (the all-clean-input products of a partitioned plan). */
    scannedProducts: number;
    findings: CleanArmLeakFinding[];
}
/**
 * design/383 §1.3 (S-1) — a VERBATIM-LEAK DETECTOR over a minted exposure-partitioned plan. Pure
 * bytes, zero model calls, run in the driver between the mint and the plan cache.
 *
 * WHAT IT ASKS: did any byte sequence that ONLY the marked arm was shown come back out inside a
 * product that will commit UNMARKED? The prompt-side audit (C1-G5) proves the clean arm's prompt
 * carried no marked bytes; this proves the partition machine itself was not bypassed, mis-wired or
 * side-channelled — a different failure surface, which is why both gates ship.
 *
 * HOW:
 *  1. evidence source = the MARKED arm's verbatim grouping prompt (`minting.arms[marked]`), which
 *     is the exact rendered form the model saw — `name — description\n body`. Reading only entry
 *     bodies would miss a payload parked in a `description`;
 *  2. minus the CLEAN arm's rendered bytes ⇒ the MARKED-SIDE-UNIQUE set. Text both sides legitimately
 *     carry (the shared instruction template, a description a clean sibling also holds) proves
 *     nothing and is subtracted here — this step is what keeps the gate off false positives;
 *  3. scanned surface = the all-clean-input products' `name` / `description` / `body`. "Clean" is
 *     read off the plan's own served roster (no marked input id), which is the same judgment that
 *     decides the product commits without an origin marker. A plan that claims the attestation but
 *     carries NO roster (no plan this driver mints, but a foreign minter could stamp one) has every
 *     product read as clean — including the marked arm's own, which legitimately holds marked
 *     bytes — so it refuses. That is the intended direction: an attestation with no evidence behind
 *     it should not buy a plan the per-product fold;
 *  4. any intersection ⇒ the caller fails the mint WHOLE. A leak falsifies the clean arm's
 *     isolation premise, and the other products of the same mint rest on that same premise.
 *
 * HONEST BOUNDARY (do not let this JSDoc be read as more than it is): this catches VERBATIM
 * copying. It does not catch paraphrase, translation, or a summary of marked content — a complete
 * detector would need a semantic judge, i.e. a model judging a model, which this design forbids.
 * A leak shorter than one shingle (see the contract's `shingleWords`/`minShingleChars`) is below
 * the floor and will not be seen. It is a leak DETECTOR, not a purity PROOF; `engaged: false` is
 * "nothing to check here", never "checked and clean".
 */
export declare function detectCleanArmVerbatimLeak(plan: LlmConsolidationPlan, contract?: MemoryDistillerPurityContract): CleanArmLeakVerdict;
/** The distiller signature the drive consumes: `(candidates, snapshot, scope) -> proposal`.
 *  At runtime only `entry.id` is load-bearing for the replay filter — test/bench stubs that hand
 *  partial entry shapes are tolerated by the reads, the type states the engine's real snapshot.
 *  The drive consumes the proposal's FOLD face only (`products`; `scope` is the drive's own): a
 *  proposal carrying directed `intents` is REFUSED by the drive rather than silently narrowed —
 *  directed update/delete intents go through `commitConsolidationPlan` directly (see the drive's
 *  own doc for why a per-cycle forward is structurally impossible). */
export type ConsolidationDistillFn = (candidates: readonly DistillerCandidate[], snapshot: unknown, scope: string) => ConsolidationProposal | Promise<ConsolidationProposal>;
/**
 * A minted plan replayed as a per-cycle distiller. Per cycle it filters each product's inputs down
 * to the ids the snapshot is still offering — a product whose inputs have all been folded is empty
 * and is the drive's termination signal. `supersede: true` rides on every input: an input
 * attributed without the edge is provenance only, the row would stay active, and the run would
 * read as "consolidation did nothing" for a reason on this side of the seam.
 *
 * This is the RUN-CACHE form the cost model depends on: one mint per run, this filter per cycle —
 * re-minting per cycle would multiply the model bill by the cycle count for the same answer.
 */
export declare function llmPlanDistiller(plan: {
    products?: ReadonlyArray<Pick<LlmConsolidationPlanProduct, "name" | "description" | "type" | "body" | "inputIds"> & {
        inputRevs?: Record<string, string>;
    }>;
    mintExposure?: "partitioned";
} | undefined | null): ConsolidationDistillFn;
export interface FuseSchedule<P> {
    schedule: "next-fit-decreasing";
    picked: P[];
    targets: number;
    ceiling: number;
    deferred: number;
    oversize: Array<{
        name: string;
        inputs: number;
        cap: number;
    }>;
}
/**
 * One cycle's plan selection under the engine's supersession fuse.
 *
 * NEXT-FIT DECREASING: products in descending target count, stop at the first that does not fit.
 * The missing backfill is the point, not an oversight: the ceiling is a FRACTION of the active set
 * and every fold shrinks the active set, so a cycle that spends leftover budget on a small fold —
 * one that would still fit later, at a lower ceiling — permanently lowers the ceiling every
 * remaining large fold has to clear. Measured with the shipped 0.25 cap: a backfilling schedule
 * dead-ends with two large digests permanently unfoldable while the same products converge when
 * the large folds simply go first.
 *
 * `maxProductsPerRun` is honoured here too; a product whose input count exceeds
 * `maxInputsPerProduct` can never be frozen at all and is reported structurally infeasible rather
 * than retried forever.
 */
export declare function scheduleUnderFuse<P extends {
    name?: string;
    inputs?: Array<{
        id: string;
        supersede?: boolean;
    }>;
}>(products: readonly P[] | undefined, ceiling: number, { maxProductsPerRun, maxInputsPerProduct }?: {
    maxProductsPerRun?: number;
    maxInputsPerProduct?: number;
}): FuseSchedule<P>;
/** The engine face the drive consumes (MemoryEngine satisfies it structurally; tests may stub). */
export interface ConsolidationDriveEngine {
    snapshotConsolidationCandidates(scope: string, opts?: {
        force?: {
            requestId: string;
        };
        full?: boolean;
    }): Promise<{
        cycleToken: string;
        watermark?: unknown;
        candidates: Array<{
            entry: MemoryEntry;
            marked: boolean;
        }>;
    }>;
    commitConsolidationPlan(cycleToken: string, proposal: ConsolidationProposal, opts: {
        requestId: string;
    }): Promise<ConsolidationDriveReceipt>;
    reconcileConsolidation(scope: string): Promise<{
        replayed?: {
            planId: string;
            state: string;
        } | null;
    } | undefined | void>;
}
/** The commit receipt fields the drive reads (the engine's receipt carries more). */
export interface ConsolidationDriveReceipt {
    planId?: string;
    state?: string;
    products?: {
        frozen?: number;
        applied?: number | null;
        conflicts?: number | null;
        refused?: Array<{
            index: number;
            reason: string;
        }>;
    };
}
export interface ConsolidationFoldState {
    active: number;
    superseded: number;
    products: number;
}
export interface ConsolidationDriveCycleRow {
    cycle: number;
    activeSetSize: number;
    ceiling: number;
    candidates?: number;
    /** How many of the cycle's served candidates carried a committed external-origin marker — the
     *  engine-served fact the fold law and the instruction hard gate key on (design/376 §2.1: a
     *  reading about the marked treatment must show the snapshot actually saw marked content, and a
     *  zero-marked run must show the zero). Absent only on the row of a snapshot that refused. */
    markedCandidates?: number;
    deferred?: number;
    planId?: string;
    state?: string;
    applied?: number | null;
    conflicts?: number | null;
    refusedProducts?: Array<{
        index: number;
        reason: string;
    }>;
    reconciled?: {
        planId: string;
        state: string;
    } | null;
    note?: string;
    refused?: string;
    recovery?: {
        attempted: boolean;
        ok: boolean;
        replayed: {
            planId: string;
            state: string;
        } | null;
        summary: string;
        report?: unknown;
    };
    attempted: {
        products: number;
        targets: number;
    };
    committed: {
        products: number;
        superseded: number;
    };
    /** `committed`'s two flat mirrors — the drive-report projections read the row under these names
     *  as well as under `committed.*`; all three spellings are one store-delta fact. */
    products: number;
    targets: number;
}
export interface ConsolidationDriveResult {
    /** A STORE fact: did anything actually fold. A refused first cycle answers false. */
    ran: boolean;
    schedule: "next-fit-decreasing";
    cycles: ConsolidationDriveCycleRow[];
    cycleCount: number;
    productsCommitted: number;
    entriesSuperseded: number;
    productsAttempted: number;
    engineRefusedProducts: Array<{
        index: number;
        reason: string;
    }>;
    converged: boolean;
    foldState: {
        before: ConsolidationFoldState;
        after: ConsolidationFoldState;
    };
    stop?: {
        reason: string;
        refused?: boolean;
        stalled?: boolean;
        parked?: boolean;
        exhausted?: boolean;
        planId?: string;
        fuseBlocked?: Array<{
            name: string;
            targets: number;
        }>;
        oversize?: Array<{
            name: string;
            inputs: number;
            cap: number;
        }>;
        recovery?: unknown;
    };
}
/** The engine's refusals carry a `code` and a `reasons` array; a bare `.message` throws half away. */
export declare function codedReason(err: unknown): string;
export interface DriveConsolidationOptions {
    distill: ConsolidationDistillFn;
    /** The fuse denominator AND every published count are store facts, never driver assumptions. */
    readFoldState: () => Promise<ConsolidationFoldState> | ConsolidationFoldState;
    options: {
        supersedeRatioCap: number;
        supersedeAbsoluteFloor: number;
        maxProductsPerRun: number;
        maxInputsPerProduct: number;
    };
    /** The ceiling arithmetic — pass the engine's `supersessionFuseCeiling` (single source). */
    fuseCeilingOf: (activeSetSize: number, opts: {
        supersedeRatioCap: number;
        supersedeAbsoluteFloor: number;
    }) => number;
    requestIdPrefix?: string;
    maxCycles?: number;
    /**
     * The snapshot step, injectable so a HOST scheduling policy (throttle waits, run-scoped force,
     * busy recovery) wraps the verb without forking the loop. Default = the plain full snapshot.
     * Anything it throws takes the loop's refusal arm exactly as the plain call would.
     */
    takeSnapshot?: (cycle: number) => ReturnType<ConsolidationDriveEngine["snapshotConsolidationCandidates"]>;
    /** Observation hook after each cycle row closes against the store (run persistence seat). */
    onCycle?: (row: ConsolidationDriveCycleRow) => void | Promise<void>;
}
/**
 * Drive consolidation to its FIXPOINT: repeat snapshot → commit → reconcile until nothing is left
 * to fold, the fuse can no longer admit the smallest remaining product, or the engine refuses.
 *
 * Why a loop is the honest shape: the supersession fuse caps ONE plan at a fraction of the active
 * set (0.25 shipped), so a library that accumulated for many sessions cannot be folded by a single
 * plan — the engine refuses that by design, and a driver that fired one plan and stopped would be
 * converting a safety property into a failure report.
 *
 * Every snapshot asks `full: true`: snapshots are CHANGE-DRIVEN by default, so cycle 2 of a stable
 * library would answer an EMPTY candidate set and strand most of the library unfolded. `full` is
 * the protocol's own name for "re-distill what is already there", which is exactly what every
 * cycle of a first consolidation is.
 *
 * WHAT WAS PROPOSED IS NEVER WHAT IS COUNTED: every per-cycle figure is a STORE delta —
 * `readFoldState()` before and after each cycle, `committed` is the difference. Receipts are not
 * enough (a `completed` receipt can carry per-product freeze refusals; a `conflict` receipt says
 * how many applied but not which); the store is the only witness that cannot be off by a refusal.
 */
export declare function driveConsolidationToFixpoint(engine: ConsolidationDriveEngine, scope: string, opts: DriveConsolidationOptions): Promise<ConsolidationDriveResult>;
/** A host snapshot policy (the injectable `takeSnapshot`) may classify its own stop as PARKED by
 *  throwing an error carrying this marker — e.g. a busy seat whose reconcile answered a conflict
 *  plan awaiting the host valve. The drive maps it onto the parked stop family. */
export declare const CONSOLIDATION_SNAPSHOT_PARKED: unique symbol;
/**
 * A host ABORT marker: an error the injectable snapshot policy tags with this symbol is NOT an
 * engine refusal — it is the host tearing the run down (a cancelled wait, a shutdown hook). The
 * drive RETHROWS it instead of composing a refusal stop, so the caller's run account stays
 * PENDING and the paid mint survives to the resume — folding a host abort into a terminal
 * `refused` would silently discard a whole mint over a shutdown.
 */
export declare const CONSOLIDATION_DRIVE_ABORT: unique symbol;
