/**
 * design/179 §4/§7/§13 — the ONE consent protocol every rule takes to enter the store through the engine.
 *
 * Single card, settings import and starter batch all reduce to the same three steps:
 *
 *   durable approval record (pending)  →  authenticated confirmation transfer (approved)  →  principal-bound redemption (redeemed)
 *
 * and only the third step reaches the core-private writer. A host holding a record id cannot redeem it
 * while it is pending, cannot redeem another principal's record, and cannot redeem free text — the
 * redemption face takes a ticket and nothing else, so there is no field to write a rule of one's own into.
 *
 * The record is DURABLE, not an in-memory ticket: a safety ask can park, and the approval that redeems it
 * may arrive in another process at another time. An in-memory ticket loses exactly that case.
 *
 * ## Crash order, and why replay is safe
 *
 * The record moves to `redeemed` — carrying the dot it minted — BEFORE the writer applies. A crash in
 * between leaves a redeemed record whose rule is not in the store; replaying the redemption re-applies the
 * SAME delta with the SAME dot, which the store folds idempotently. One redemption mints exactly one dot,
 * for as many replays as it takes. Deliberately re-adding a deleted rule is a different act: a new ask, a
 * new record, a new dot.
 *
 * ## The boundary this does NOT claim
 *
 * The confirmation transfer is an in-process call. A deployment must reach it only from its authenticated
 * approval channel, and a deployment that shares filesystem credentials with the user can circumvent all of
 * this by editing a file backend's file. That is the settings-file trust model, stated rather than
 * defended against: for a file backend, host = user, no more and no less.
 */
import { type EditedRuleBreadthWarning, type UncoveredSegmentDetail, type RuleBehavior, type RuleOffer, type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
import type { RuleOwner } from "./permission-rule-store.js";
import { type PermissionRuleStoreProvider } from "./permission-rule-provider.js";
/** One candidate rule inside an approval record: what it says (`behavior`), the exact text and where it
 *  would apply. A card mints `allow` candidates only (a card is one person's yes); the settings import
 *  carries all three behaviors, each entry under the list it was read from. */
export interface RuleCandidate {
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
}
/** What the approval record is for — it decides the provenance the minted adds carry. */
export type RuleApprovalKind = "card" | "import" | "starter";
/**
 * design/375 §4.1 — one "don't ask again" OPTION as the record stores it: references into the
 * record's `candidates` by index, never rule text of its own. The display triple (rule, match,
 * command) is deliberately NOT here — it is derived at projection time from the referenced
 * candidate's text ({@link ruleOffersOfRecord}), so there is no stored copy to drift from the rule
 * that would actually persist.
 *
 *  · `kind: "single"` — one candidate covering the whole command.
 *  · `kind: "batch"` — a CONJUNCTION batch: confirming it is one yes to ALL of `candidates` at
 *    once, with no per-member sub-selection. On a CARD record the batch carries two MINT-TIME
 *    facts that cannot be re-derived from candidate texts and therefore must be stored:
 *    `segments` (each member's source segment, index-aligned with `candidates`),
 *    `uncoveredSegments` (the honest surplus count the wire offer disclosed, judged on the
 *    prepare-time coverage snapshot) and — since `schema: 3` (design/382 §3.5) — `uncoveredDetail`,
 *    one reason row per counted segment in segment order: WHY each stayed uncovered is a mint-time
 *    fact that cannot be re-derived from candidate texts, so it must be stored or a crash/reload
 *    rebuild would render a card that says less than the first render did (record-is-what-you-see).
 *    REQUIRED on a card batch, all three: "optional, validated when present" is the
 *    silent-absence-passes shape this record form already refused once, and it is not re-imported
 *    for the new seat. On an import/starter record there is no compound command
 *    and no coverage snapshot, so all three fields must be ABSENT — honest absence, never an
 *    empty-string stand-in. The read-side integrity checks refuse either direction's violation.
 *
 * Cardinality: a batch references 1..N candidates where N is the record's own candidate count.
 * The wire offer's 1..5 is the CARD-lane minting cap — a card record inherits it by being minted
 * there — while an import batch of twenty is a legal record; the two contracts are deliberately
 * separate and neither speaks in the other's name.
 */
export type RuleOffer2 = {
    readonly kind: "single";
    readonly candidate: number;
} | {
    readonly kind: "batch";
    readonly candidates: readonly number[];
    readonly segments?: readonly string[];
    readonly uncoveredSegments?: number;
    readonly uncoveredDetail?: readonly UncoveredSegmentDetail[];
};
/** A durable record of one pending/settled human decision about minting rules.
 *
 * Identity (design/182 §4.5, additive): a v1 record carries `principal` and nothing else — it stays
 * valid forever and projects at runtime to a principal-kind {@link RuleOwner} (zero migration; pending
 * and redeemed-but-unlanded records redeem across the upgrade unchanged). Only the local-owner path
 * mints records with the `owner` field and NO principal. A record carrying both, disagreeing, is
 * contradictory and unredeemable. */
export interface RuleApprovalRecord {
    id: string;
    principal?: string;
    /** Structural owner (additive). Absent on every record a v1 tree minted. */
    owner?: RuleOwner;
    /**
     * design/375 §4.5 — the record-form version stamp. A stored row WITHOUT it — or carrying an older
     * number — predates this form; such a row confirms and redeems nothing (refused loudly,
     * `record_schema_stale`) and is never read through a compatibility shim: pending approvals are
     * short-lived, and re-triggering the command draws a fresh card. Stores hand pre-form rows back
     * as {@link StaleRuleApprovalRecord}. `3` (design/382 §3.5, B8/B7): the card batch's
     * `uncoveredDetail` seat became REQUIRED and `edited.warnings` became storable — a `schema: 2`
     * row is read back stale exactly like the pre-stamp rows before it (the short-lived-pending
     * bargain, unchanged). `4`: every candidate carries its `behavior` (the settings import now carries
     * the deny/ask lists too) — a `schema: 3` row is stale by the same equality gate.
     */
    schema: 4;
    kind: RuleApprovalKind;
    state: "pending" | "approved" | "redeemed";
    candidates: RuleCandidate[];
    /**
     * The card's OPTION structure over `candidates` (design/375 §4.1). The flat candidate list stays
     * the redemption/replay anchor — tickets, `redeemedDots` and the store writes all key on
     * candidate indices, unchanged — while confirmation keys on OFFER indices into this array.
     * Import/starter records are the whole-list normalization: one batch offer over every candidate,
     * so all three entries speak one protocol. Offers are pairwise DISJOINT in membership (an
     * unchosen offer's members must stay unredeemable, which overlapping offers could not
     * guarantee); the mint sites satisfy that by construction and the read side refuses violations.
     */
    offers: RuleOffer2[];
    createdAt: string;
    /** The ask this record was drawn from, for reconciliation. Advisory metadata; never adjudication input.
     *  `boundInputHash` is ALSO the card-edit binding anchor: an edited-candidate confirmation must echo
     *  it back, so a record minted without one refuses edits (there is nothing to bind the edit to). */
    toolCallId?: string;
    boundInputHash?: string;
    /**
     * The adjudicated command a CARD record was drawn for — the same post-rewrite bytes the ask carried
     * (the form `boundInputHash` digests). Additive: absent on batch records and on every card a prior
     * version minted. It is the edit gate's coverage input ("the edited rule must still admit THIS
     * command"), so a record without one refuses edited candidates rather than guessing; the engine-
     * candidate paths never read it.
     */
    command?: string;
    /** Monotonic revision of THIS record, bumped by every accepted transition. The compare-and-set key:
     *  comparing state alone cannot separate two different writes that both leave the state unchanged. */
    rev: number;
    /**
     * Which OFFER the person actually chose, recorded by the confirmation transfer (design/375 §4.3;
     * takes the seat the per-candidate selection held before offers existed).
     *
     * A card offers alternatives of different breadth and a human picks ONE. Without this, confirming
     * the card would authorize every option on it: a host could redeem the narrow ticket, then later
     * the wider one, and the second rule would exist without anyone ever having agreed to it.
     * Choosing a BATCH offer is a conjunction — one yes to ALL of its member candidates at once —
     * so every member becomes redeemable and every non-member stays unredeemable forever. Absent
     * exactly while the record is pending.
     */
    selectedOffer?: number;
    /**
     * The person-EDITED candidate this record carries, if any — full provenance for the one candidate
     * whose text was authored at the card rather than derived by the engine. `index` names the appended
     * row in `candidates` (whose `rule` holds the CANONICAL spelling); `text` keeps the raw input bytes
     * exactly as submitted (the idempotency primary key — a client retrying a lost response resends the
     * same bytes); `at` is when the edit landed. Present ⇒ the record's chosen offer is the single
     * offer minted over this candidate (the append, the offer and the choice are one CAS write).
     * Absent on every card settled through an engine candidate.
     *
     * `warnings` (design/382 §3.3-5) — the BREADTH warning codes the text gates raised on this edit,
     * written in the same CAS that settles it: "the width was named before the yes" becomes a fact the
     * record itself carries, auditable after any crash or upgrade. Optional; present only when a
     * warning was raised, and when present it is an array from the closed code set (the read-side
     * integrity gate refuses anything else). Codes only — the sentences are derivable from the
     * canonical rule text and are deliberately not stored (no second copy to drift).
     */
    edited?: {
        index: number;
        text: string;
        at: string;
        warnings?: readonly EditedRuleBreadthWarning["code"][];
    };
    /** Dots already minted for this record, keyed by candidate index — the replay anchor. */
    redeemedDots?: Record<number, RuleDot>;
}
/**
 * design/375 §4.5 — the minimal ENVELOPE a store hands back for a durable row that predates the
 * CURRENT (`schema: 4`) record form. A typed read cannot honestly return such a row as a
 * {@link RuleApprovalRecord}, and dropping it at the store layer would misreport "stale" as
 * corruption — so the store contract is this variant instead: `staleSchema: true` plus the
 * IDENTITY fields, which are spelled the same way in every version and therefore safe to read.
 * The identity fields exist so the owner gate can still run, and it runs FIRST: a non-owner
 * asking about a stale row is answered exactly like a non-owner asking about any row (existence
 * is never confirmed); the stale disclosure is for the record's own owner only. Nothing else
 * about the row is surfaced.
 */
export interface StaleRuleApprovalRecord {
    readonly staleSchema: true;
    readonly id: string;
    readonly principal?: string;
    readonly owner?: RuleOwner;
}
/**
 * The durable home of approval records. A separate record type on purpose: it is not a checkpoint row and
 * must not evolve with one.
 */
export interface RuleApprovalRecordStore {
    /** A row whose `schema` is missing or unknown MUST come back as {@link StaleRuleApprovalRecord}
     *  (identity fields only) — never thrown away as a parse failure, never widened into a full
     *  record the current form cannot vouch for. */
    get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
    /**
     * Compare-and-set on the record's REVISION, not on its state.
     *
     * State alone is not a compare-and-set key: a batch's second candidate transitions redeemed → redeemed,
     * so two concurrent retries would both find the state they expected, both mint a dot, and both write —
     * one silently overwriting the other's `redeemedDots`, which is how one approval becomes two adds and a
     * deleted rule comes back to life. `next.rev` must be `expectRev + 1`.
     */
    cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
    create(record: RuleApprovalRecord): Promise<void>;
}
/** Everything the consent lane needs wired. */
export interface RuleConsentDeps {
    provider: PermissionRuleStoreProvider;
    approvals: RuleApprovalRecordStore;
    /** Injectable clock/id for deterministic tests; defaults are the real ones. */
    now?: () => Date;
    newId?: () => string;
    /**
     * Deployment lever for the card-edit face: whether `confirmRuleApproval` accepts a FRESH
     * `editedCandidate`. Absent or `false` = OFF (the default — the edit face widens what a fabricated
     * confirmation could mint, from "one of the engine's bounded candidates" to "any same-head rule
     * passing the coverage gate", so it is opt-in). Any other non-boolean value is a configuration
     * mistake and refuses LOUDLY at the read — never silently mapped to a default. Replaying an edit a
     * record already settled is a pure record read and does not consult this switch: the minting already
     * happened, and withholding the receipt helps no one.
     */
    cardEdits?: boolean;
    /**
     * design/382 §2.4-6 — the deployment's sensitive-read negative control for the directoryRead MINT
     * floor, same-source with its compiled read-deny judge (`(d) => readDeny.matchPath(d) !== null` is
     * the canonical wiring). Consulted only when a card would PROPOSE a directory authorization from a
     * cd segment — never by the explicit-consent doors (hand-written, import), which carry their own
     * preview and validation. Absent ⇒ the engine's built-in default deny table stands in (refusal-only).
     */
    deniesDirectoryRead?: (directory: string) => boolean;
}
/** In-memory approval records — the test backend and the reference CAS semantics, the stale-row
 *  envelope read included. */
export declare class InMemoryRuleApprovalRecordStore implements RuleApprovalRecordStore {
    private readonly rows;
    get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
    create(record: RuleApprovalRecord): Promise<void>;
    cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
    /** Test/inspection helper — how many records exist. The principal negative pins read this. */
    size(): number;
}
/**
 * design/375 §4.1 — project a record's stored offers into the wire {@link RuleOffer} shape: the
 * AUTHORITATIVE card render source (record-is-what-you-see; the ask-time projection is advisory
 * and may be coverage-narrower). The display triple (rule, match, command) is derived HERE, at
 * projection time, by parsing each referenced candidate's stored text — never stored beside it,
 * never accepted from a caller — so what a card shows is definitionally the rule that would
 * persist; there is no second copy to corrupt. A batch offer's `segment`s and
 * `uncoveredSegments` come from the mint-time facts the record carries.
 *
 * Card records only, in effect: an import/starter record's whole-list batch carries no segment
 * metadata (its person-facing form is the import/starter PREVIEW, not an offer card), and asking
 * for its card projection throws. A candidate text that no longer parses throws too — that is
 * store damage on a row the validator once accepted, and rendering a wrong rule under a "don't
 * ask again" label is the one failure a refusal always beats.
 */
export declare function ruleOffersOfRecord(rec: Pick<RuleApprovalRecord, "candidates" | "offers">): readonly RuleOffer[];
/** An opaque, single-purpose redemption ticket. Its only content is which record and which candidate. */
export type RuleTicket = string;
export declare function mintRuleTicket(recordId: string, candidateIndex: number): RuleTicket;
/**
 * Create the pending record behind ONE approval card, and the tickets its options would redeem.
 *
 * The candidates are DERIVED here, from the call, by the same function that produced the options shown on
 * the card — they are not an argument. That is the difference between the stated invariant and a wish:
 * while this took a candidate list, a surface could display an approval for `git status` and build a
 * record containing `Bash(rm:*)`, and every later step would faithfully honor the record. There is now no
 * field in which to write a rule of one's own, on this entry as on the redemption entry.
 *
 * `toolCallId` and `boundInputHash` are recorded so an auditor can tie the record back to the exact ask,
 * and a command the lane cannot speak for yields no record and no tickets at all.
 *
 * What this does NOT claim: that the command named here is the command that was displayed. A surface that
 * shows one thing and says another is the same-layer trust §5 already states plainly — in an in-process
 * deployment the host IS the user. What it closes is the gap where the rule TEXT never came from the
 * engine at all.
 */
export declare function prepareCardApproval(opts: {
    principal?: string;
    /** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
    owner?: RuleOwner;
    /** The tool whose ask this card belongs to. Only the shell tool the rule lane speaks for is accepted. */
    toolName: string;
    /** The command as adjudicated — the same bytes the ask carried. */
    command: string;
    /** The ask's call id and argument digest, recorded for reconciliation. */
    toolCallId?: string;
    boundInputHash?: string;
    /**
     * Where a redeemed rule would apply — any of the three consent dimensions (design/382 §4.1).
     *
     * design/382 §4.4 (#490②, BREAKING B2) — the DEFAULT is no longer global:
     *  · present ⇒ used as given (the explicit channel; all three members legal, garbage refused loudly);
     *  · absent with `cwd` present ⇒ `{ kind: "project", root: cwd }`. Root = the adjudicated call's
     *    cwd is a deliberately NARROW default — the engine is a library and repository semantics are
     *    host business; a host wanting the true project root passes an explicit scope. The narrow cost
     *    is more asks (a nested-directory rule does not cover siblings), the safe direction;
     *  · both absent ⇒ a LOUD refusal (`config.missing_scope`): silent-global was the #490② defect,
     *    silent-project has no root to anchor, and "where does this consent land" is not guessable.
     */
    scope?: RuleScope;
    /**
     * design/382 §4.3 — the SESSION IDENTITY of the adjudicated call, threaded by the caller from the
     * original call context exactly like `cwd` (never inferred from the process). It is the coverage
     * read's third eligibility axis: with it, a standing session rule of that session counts as
     * coverage; without it, no session rule covers anything (fail-closed). Independent of `scope` on
     * purpose — `scope` names where a NEW consent would land, this names where the CALL is running.
     */
    sessionId?: string;
    /**
     * The working directory of the ADJUDICATED CALL — the same task root the gate's lane judged
     * with — threaded by the caller from the original call context. Never inferred from `scope`
     * (a rule's landing scope is not where the call ran; nested project roots make the two
     * coverage answers diverge) and never from the process. Absent, a project-scope rule covers
     * nothing (the shared predicate's fail-closed arm), so segments lean UNCOVERED and the batch
     * leans wide — the over-offer direction, which redemption's per-rule validation keeps safe.
     */
    cwd?: string;
    /**
     * design/382 §2.4 (adversarial-review r2) — the EXECUTION working directory of the adjudicated
     * call: the live tracked cwd the shell will resolve a relative `cd ./x` against, threaded by the
     * caller exactly like `cwd` (the runner's ask-projection seat threads its `handsCwdRef.current`;
     * a host preparing cards for its own calls threads whatever base those calls execute under).
     * ONLY the relative-cd resolution reads it; scope eligibility and the derived project scope stay
     * on `cwd`. Absent ⇒ `cwd` is the base (the single-value deployment, where the two coincide).
     * Present and DIFFERENT, it is what keeps this durable consent path and the advisory ask-time
     * projection minting the SAME directory member — two faces reading one world.
     */
    execCwd?: string;
    deps: RuleConsentDeps;
}): Promise<{
    approvalId: string;
    offers: readonly RuleOffer[];
    tickets: RuleTicket[];
    candidates: RuleCandidate[];
} | undefined>;
/**
 * The authenticated confirmation transfer: pending → approved.
 *
 * A deployment must reach this ONLY from the channel that actually carries a human decision — the same
 * channel an approval card's yes travels. Nothing below this line can tell an authentic confirmation from
 * a fabricated one, which is why the boundary is stated here rather than checked here.
 */
export declare function confirmRuleApproval(opts: {
    approvalId: string;
    principal?: string;
    /** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
    owner?: RuleOwner;
    /**
     * The index of the OFFER the person chose — into the record's `offers`, the same order
     * `prepareCardApproval` returned them (design/375 §4.3). A card presents alternatives of
     * different breadth, so "they said yes" is not an answer on its own — "they said yes to THIS
     * one" is. A single offer authorizes its one candidate; a BATCH offer is a conjunction, one yes
     * to ALL of its member candidates at once, with no per-member sub-selection. May be omitted
     * exactly when the record carries ONE offer (a single-element choice space has no ambiguity —
     * the import/starter whole-list batch lands here); a multi-offer record with no selection
     * refuses `selection_missing`. Mutually exclusive with `editedCandidate`.
     */
    selectedOffer?: number;
    /**
     * The person-EDITED rule text for this card, travelling on the SAME authenticated confirmation
     * channel as a choice among the engine's candidates (never the un-authenticated prepare entry, which
     * keeps refusing caller candidates). `text` is the rule as authored; `boundInputHash` must echo the
     * record's own bound-input digest — the submitter's proof of "I am editing the card that showed THIS
     * command", an in-process mis-binding fence (a caller holding only a leaked approvalId cannot spell
     * it), not a cryptographic one. The engine validates the text through the one shared validator,
     * requires it to still ADMIT the adjudicated command, appends it as a new candidate and binds the
     * selection to it, returning the minted ticket. Mutually exclusive with `selectedOffer`.
     */
    editedCandidate?: {
        text: string;
        boundInputHash: string;
    };
    deps: RuleConsentDeps;
}): Promise<ConfirmResult>;
/** Why a confirmation did not land. A closed set so a host can branch (re-present, re-fetch, give up).
 *  The two `record_*` members past `record_not_found` are the durable-row gates (design/375 §4.5/§4.7),
 *  disclosed to the record's own owner only — a non-owner gets `record_not_found` for every row:
 *   - `"record_schema_stale"` — the record predates the current (`schema: 4`) form; the decision it carried
 *     is void, and the recovery path is re-triggering the command for a fresh card (no
 *     compatibility read, no migration — `detail.message` says so);
 *   - `"record_malformed"` — the record's offer structure failed the read-side integrity checks
 *     (store damage); `detail.message` names the breakage.
 *  The three `edit_*` members are the card-edit face's own refusals:
 *   - `"edit_disabled"` — the deployment has not opted into card edits (`RuleConsentDeps.cardEdits`);
 *   - `"edit_binding_mismatch"` — the confirmation does not echo the record's bound-input digest
 *     (missing echo, a record minted without one, or a different card). Refused BEFORE anything else,
 *     settled replays included, and never returns a minted ticket;
 *   - `"edit_rejected"` — the edited text failed a gate (validator refusal, coverage, record shape);
 *     `detail` carries the specifics. */
export type ConfirmRefusalReason = "record_not_found" | "record_schema_stale" | "record_malformed" | "selection_missing" | "selection_invalid" | "selection_mismatch" | "not_pending" | "conflict" | "edit_disabled" | "edit_binding_mismatch" | "edit_rejected";
/** The confirmation outcome: landed, or refused with a named reason.
 *
 *  `mintedCandidate` (additive) is present exactly when an EDITED candidate settled this confirmation —
 *  fresh mint and idempotent replay alike (a client retrying a lost response gets the same index, the
 *  same canonical rule text and the same deterministically re-minted ticket, never a push toward a
 *  second card). `rule` is the CANONICAL spelling, which may differ from the submitted bytes (spelling
 *  normalization); a surface echoes it back so the person sees the form that will actually persist.
 *
 *  `detail` (additive, refusal arm) rides `edit_rejected`: `code` is the shared validator's refusal
 *  code when the validator is what refused, absent when another gate did; `message` always says why. */
export type ConfirmResult = {
    ok: true;
    mintedCandidate?: {
        index: number;
        rule: string;
        ticket: RuleTicket;
    };
} | {
    ok: false;
    reason: ConfirmRefusalReason;
    detail?: {
        code?: RuleRejectCode;
        message: string;
    };
};
/**
 * What the TEXT×COMMAND gates answered. `canonicalRule` is the spelling that would actually persist,
 * which may differ from the submitted bytes (spelling normalization). On the refusal arm, `code` is the
 * shared validator's own refusal code when the validator is what refused, and is ABSENT when the
 * coverage gate refused — the same presence rule the confirmation's `edit_rejected` detail carries,
 * because they are the same values from the same body.
 */
export type EditedRuleTextPrecheck = {
    ok: true;
    canonicalRule: string;
    /**
     * design/382 §3.3-5 — BREADTH notes about the accepted rule (additive; absent = nothing to
     * say). A warning is never a refusal: the muscle-memory wide forms are legal (§3.4), and this
     * seat is what makes accepting one an informed act. Rendering them beside the input line is
     * the surface's duty (the engine cannot force a display); what the ENGINE guarantees is the
     * durable half — a confirmation through the edit arm writes the hit codes into the record
     * (`edited.warnings`), so "the width was named before the yes" is an auditable fact. The
     * confirmation result deliberately does NOT repeat them (one fact, one loud place).
     */
    warnings?: readonly EditedRuleBreadthWarning[];
} | {
    ok: false;
    code?: RuleRejectCode;
    message: string;
};
/**
 * Would this edited rule text pass the card-edit face's text gates for this command? A pure function,
 * for a surface that wants to answer while the person is still typing instead of after a round trip.
 *
 * It is the SAME body the confirmation arm runs (`checkEditedRuleText`), which is the whole point: a
 * boundary that re-implements the check — or calls `parseRuleText` directly — installs a SECOND,
 * STRICTER judge that refuses spellings the edit face accepts (the muscle-memory `Bash(adb *)` form is
 * exactly such a case: the validator alone refuses it as a wildcard; the edit face normalizes it first).
 *
 * TWO THINGS IT DOES NOT SAY, and a surface's wording has to respect both:
 *  · `ok` means SUBMITTABLE, not "the confirmation will succeed". The record-level gates — the binding
 *    echo, the record's owner and state, scope inheritance, the deployment switch — are not inputs here
 *    and are re-decided by `confirmRuleApproval` against the record.
 *  · `canonicalRule` may differ from the bytes passed in (spelling normalization). It is the spelling
 *    that would persist, so an inline reviewer should show THAT form rather than echoing the input.
 *
 * The two arguments are different kinds of thing, and are treated differently on purpose: `text` is a
 * PERSON's input, so every malformed spelling of it is an answer (a refusal), never a throw. `command`
 * is the CALLER's context — the adjudicated command the card was drawn for — and an absent or non-string
 * one is a caller bug: answering "your rule does not admit the empty command" would read to the person
 * as a verdict on what they typed, so it refuses loudly instead of misattributing the fault.
 *
 * ONE RECORD SHAPE A CALLER MUST NOT ASK ABOUT: a card carrying no adjudicated command at all (minted
 * before the field existed, or by a store that dropped it). That is a RECORD-level refusal, and this
 * function is the text half — passing its absent command here is asking a question with no answer, and
 * it throws rather than inventing one. A host holding such a record already knows the outcome without
 * asking: `confirmRuleApproval` refuses the edit with a message that says coverage cannot be verified,
 * so the surface's move is to not offer the edit box at all.
 */
export declare function precheckEditedRuleText(text: string, command: string): EditedRuleTextPrecheck;
/** What a redemption produced. `alreadyRedeemed` marks the replay path — the same dot, no second rule.
 *
 *  `rev` is the DURABLE store's revision. A session-scope redemption (design/382 §4.3) lands in the
 *  session overlay and does not move it: its `rev` is a best-effort read of the store's current
 *  revision (0 when the store could not be read — the landing is in the overlay either way, and the
 *  durable revision is reporting, not the landing's identity). */
export type RedeemResult = {
    status: "redeemed";
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    dot: RuleDot;
    rev: number;
    alreadyRedeemed: boolean;
} | {
    status: "refused";
    reason: string;
};
/**
 * Redeem one ticket into one persisted rule.
 *
 * Takes a ticket and a verified principal, and nothing else. There is deliberately no field carrying rule
 * text: what gets persisted is what the engine put in the record when it drew the card, so a host cannot
 * turn a click on `git status` into a rule for something else.
 */
export declare function redeemRuleTicket(opts: {
    ticket: RuleTicket;
    principal?: string;
    owner?: RuleOwner;
    deps: RuleConsentDeps;
}): Promise<RedeemResult>;
/** The three user-editable settings layers this version reads. */
export type ImportedSettingsLayer = "userSettings" | "projectSettings" | "localSettings";
/** One layer to read. `root` is the canonical directory a project-layer rule is scoped to — taken from
 *  where the FILE actually lives, since the local layer is not always at a repository root. */
export interface CcImportLayer {
    layer: ImportedSettingsLayer;
    path: string;
    root: string;
    readFile: (path: string) => Promise<string | undefined>;
}
/**
 * What the import WOULD do, shown before anyone confirms.
 *
 * `uncovered` is a fixed-key record, not a list: everything this version does not import is named in the
 * TYPE, so an empty list, a missing member or a duplicate is not expressible. A preview that reported only
 * what it read would be claiming completeness it does not have.
 */
export interface ImportPreview {
    candidates: RuleCandidate[];
    skipped: Array<{
        rule: string;
        reason: string;
    }>;
    /**
     * ALWAYS EMPTY in this version — a compatibility seat, kept on the wire shape so an existing reader
     * keeps finding the key (removing a wire field would break readers for no gain).
     *
     * The seat once carried the entries whose MATCH FORM was rewritten on the way in (the space-star
     * suggestion form of the other product translated to this lane's colon-star form, one row per
     * translated candidate with its `scope`). That translation is retired: the space-star wildcard is
     * now a first-class match form of this lane's own grammar, so an entry's match form imports as the
     * file states it — `Bash(npm run *)` lands as `Bash(npm run *)`, never as a colon-star — and nothing
     * is ever pushed here. What `candidates` carries is the grammar's CANONICAL re-projection of each
     * entry (the parser folds spacing: `Bash(npm<TAB>run *)` lands as `Bash(npm run *)`); that is the
     * ordinary normalization every rule text in this lane undergoes, the same one the store applies,
     * and not a translation this seat reports. An entry whose star placement has no rule form is
     * refused into `skipped` under `unsupported.wildcard`, named as the file writes it. A reader
     * rendering a "rewritten spellings" column from this seat renders an empty column, correctly.
     */
    translated: Array<{
        from: string;
        to: string;
        scope: RuleScope;
    }>;
    layers: Array<{
        path: string;
        layer: ImportedSettingsLayer;
        found: boolean;
    }>;
    /** The two SETTINGS LAYERS this version does not read at all. The three permission lists of the layers it
     *  does read — allow, deny AND ask — are all imported (each candidate carries its `behavior`), so there
     *  is no longer a "left in place" count beside them: an entry that did not become a candidate is in
     *  `skipped` with its reason. */
    uncovered: {
        flagSettings: "not-imported-v1";
        policySettings: "not-imported-v1";
    };
}
/**
 * One member row of a batch redemption's FULL table (design/375 §4.6) — what actually happened to
 * each candidate of the chosen offer, keyed by its ORIGINAL candidate index. `candidateIndex` is
 * the member's identity: the same rule text can be two candidates in two scopes with two different
 * outcomes, so the bare text is not a key. A successful row always carries its `dot` and the
 * independent `alreadyRedeemed` axis (the single-leg replay marker: a dot-recorded-but-unlanded
 * retry is `alreadyRedeemed: true` AND `persisted`; a rule that pre-existed in the store is
 * `false` AND `deduped` — the two axes answer different questions and neither implies the other).
 * A refused row always carries its `reason`. `deduped` counts as LANDED: an equivalent rule
 * already standing means the consent is already in effect.
 *
 * design/382 §4.3 — on a SESSION-scope member the two landed words mean the same thing about a
 * different home: landed in / already in the session's OVERLAY (a session rule's home store), never
 * the persisted store. The words state that the landing happened; DURABILITY is what the member's own
 * `scope` says, so a consumer never has to guess it off the status (deliberately no fourth status
 * value — the closed set names landing outcomes, not storage classes).
 */
export type RedeemedBatchMember = {
    readonly candidateIndex: number;
    readonly behavior: RuleBehavior;
    readonly rule: string;
    readonly scope: RuleScope;
    readonly status: "persisted" | "deduped";
    readonly alreadyRedeemed: boolean;
    readonly dot: RuleDot;
} | {
    readonly candidateIndex: number;
    readonly behavior: RuleBehavior;
    readonly rule: string;
    readonly scope: RuleScope;
    readonly status: "refused";
    readonly reason: string;
};
/**
 * Read the THREE permission lists (allow, deny, ask) of the user-editable settings layers and produce a
 * preview plus a PENDING approval record. Nothing is stored until someone confirms that record and the
 * batch is redeemed. A preview with zero importable candidates returns NO `approvalId` — there is nothing
 * to confirm, and the absence says so (design/375 §4.1).
 *
 * Every list is read through the ONE validator under its own behavior: a deny/ask entry takes the
 * tightening floor (`Bash(node:*)` as a deny is a legitimately wide tightening and imports), an allow
 * entry the allow floor. A content-form deny/ask so imported is the person's own standing instruction
 * and adjudicates in the persisted-rule lane beside their allows — deny > ask > allow — instead of
 * being escorted by a shell into a bare tool-name ask that widens `Bash(rm -r:*)` into "every Bash call
 * asks". An entry that does not import (a spelling with no rule form, a tool the lane does not speak
 * for, a non-string) is DISCLOSED in `skipped` with its reason and stays in the settings file.
 */
export declare function prepareCcImport(opts: {
    layers: CcImportLayer[];
    principal?: string;
    owner?: RuleOwner;
    deps: RuleConsentDeps;
}): Promise<{
    preview: ImportPreview;
    approvalId?: string;
}>;
/**
 * The factory-reviewed starter set, offered when there is nothing to import.
 *
 * Every entry is an EXACT read-only command whose behavior does not depend on repository or environment
 * configuration. The version-control family is deliberately absent: repository configuration can make a
 * subcommand invoke an external helper, which is the same reason this repository already declined a
 * subcommand allowlist for it. Nothing here takes effect until someone confirms the batch.
 */
export declare const STARTER_RULES: readonly string[];
/** Prepare the starter batch as a pending approval record. Nothing takes effect without confirmation;
 *  an unconfirmed deployment simply starts with an empty rule set. */
export declare function prepareStarterBatch(opts: {
    principal?: string;
    owner?: RuleOwner;
    deps: RuleConsentDeps;
}): Promise<{
    preview: RuleCandidate[];
    approvalId: string;
}>;
/**
 * Redeem the CHOSEN offer of a confirmed record, member by member — reporting what actually landed
 * rather than what was previewed (dedup, concurrency and redemption-time validation can all move a
 * member between the two moments).
 *
 * design/375 §4.1/§4.6 — this is the one-yes walk for every record kind: an import/starter record's
 * whole-list batch, a card's chosen batch (each per-segment rule lands as its own candidate), or a
 * card's chosen single (a one-member walk). Members OUTSIDE the chosen offer are simply not walked
 * — and stay individually unredeemable through the ticket entry, same guarantee, one predicate.
 *
 * The walk contract, spelled out because each clause is load-bearing:
 *  · every member goes through the SAME single-ticket redemption leg — dot CAS'd into the record
 *    BEFORE the store write (a crash in between leaves a replayable intent), concurrent-loser
 *    dot reuse included; the walk adds no concurrency primitive of its own;
 *  · a refused member does NOT fold the batch: "all or nothing" is the CONFIRMATION's semantics
 *    (one yes covered the whole offer), not the landing's transaction shape — the walk continues
 *    and the member's row says refused, with the reason;
 *  · resuming a partial walk IS re-walking: already-landed members answer `alreadyRedeemed: true`
 *    and reuse their recorded dots (two concurrent walkers converge on one dot set, zero double
 *    mints), unlanded members mint on;
 *  · the returned table is COMPLETE — exactly one row per member of the chosen offer, in the
 *    offer's member order, no extras — so a caller's aggregate is an honest projection: all rows
 *    landed (`persisted` or `deduped`, both count as the consent being in effect) is full
 *    success, and ANY refused row must project to a non-success aggregate, never to a silent
 *    partial one.
 */
export declare function redeemRuleBatch(opts: {
    approvalId: string;
    principal?: string;
    owner?: RuleOwner;
    deps: RuleConsentDeps;
}): Promise<{
    members: readonly RedeemedBatchMember[];
    rev: number;
} | {
    status: "refused";
    reason: string;
}>;
