/**
 * design/179 §8 / design/389 — the DURABLE partition of the permission-rule store (the OR-Set CRDT that
 * holds the `user` and `project` sources), its backend write face, the pure fold/join law every backend
 * shares, and the removal entry.
 *
 * The host-visible STORE is `permission-rule-provider.ts` (`PermissionRuleStore` — one read query over
 * the four source partitions). What lives here is the partition a backend implements: `list()` over one
 * owner's bucket, the writer handle, and the delta semantics. A backend author implements
 * {@link DurableRulePartitionProvider}; the engine composes it into the store with
 * `createPermissionRuleStoreProvider`.
 *
 * ## Two faces, deliberately unequal
 *
 * A deployment sees a READ face: `list()`, scoped to one verified principal by a factory. The write face
 * is a BACKEND CONTRACT (ruled 2026-08-10: exported so an out-of-repo store twin builds against the same
 * definitions instead of mirroring them), not a host write API — the only way a rule enters the store
 * through the ENGINE is the redemption path (`permission-rule-consent.ts`), which requires an approved
 * durable approval record; that invariant lives in the engine's wiring and is pinned by the
 * writer-caller registry test, not in type visibility.
 * `expectedRev` is concurrency control, not authorization, so "hold a put API and skip the ticket" is not
 * a shape that exists here rather than a rule someone must remember.
 *
 * That claim is scoped to the API. A standard in-process deployment shares filesystem credentials with the
 * user, so it can edit a file backend's file directly — the same trust model the settings file it mirrors
 * has. The checksum detects corruption, not an editor who recomputes it. This is stated, not defended
 * against, in v1: the file backend's trust is host = user, no more and no less.
 *
 * ## Principal scoping is a factory boundary, not a parameter
 *
 * `forPrincipal` returns a store already anchored to one identity, so `list()` has no cross-tenant
 * argument to pass wrong. An unauthenticated task (`principal === undefined`) resolves to ZERO rules
 * rather than to a shared bucket — fail-closed on a loosening face means fewer allows, never a shared one.
 *
 * ## Delete is a tombstone, and the tombstone IS the operation's identity
 *
 * Removal takes the observed-remove form: the tombstone carries every add dot the deleting replica could
 * see at delete time, and that observation is FIXED when the call constructs it. An OCC retry inside that
 * one live call re-submits the SAME tombstone — re-snapshotting would suck a concurrently landed add into
 * `removedDots` and swallow a legitimate re-add. The fixing is scoped to one live call on purpose: there
 * is no cross-process or cross-restart continuation of a delete in v1, so calling the entry again after a
 * restart is a NEW delete intent (a new observation, a new tombstone) — which is the correct reading of a
 * person pressing delete a second time.
 */
import type { PersistedRule, RuleAdd, RuleDot, RuleScope, RuleTombstone } from "./permission-rule-model.js";
import { type RuleBehavior } from "./permission-rule-model.js";
import type { RuleSyncDropReason, RuleQuarantineReason } from "./governance-codes.js";
import type { StoreDurability, StoreFidelity } from "./checkpoint-store.js";
/** One store read: the rules as persisted, their tombstones, and the revision they were read at. */
export interface StoredRules {
    /** Rules with their FULL add sets — tombstoned dots already removed by the backend on read. */
    rules: PersistedRule[];
    tombstones: RuleTombstone[];
    /** Monotonic revision of this store, for optimistic concurrency on every write. */
    rev: number;
    /** Integrity fingerprint the backend computed, when it has one. Corruption detection only. */
    checksum?: string;
}
/**
 * The durable partition's read face: read-only, already anchored to a single owner. Rows carry the two
 * durable scopes only (`global` = the `user` source, `project` = the `project` source); a session row
 * never lives here (design/382 §4.3 — the durable two-member face).
 *
 * A backend that fails to read must not silently report an empty set as if it were an empty store — it
 * throws, and the store's caller turns that into zero rules PLUS a loud disclosure. Both outcomes deny
 * more than they allow; only one of them is honest about why.
 */
export interface DurableRulePartition {
    list(): Promise<StoredRules>;
    /**
     * design/182 §5.2/§8.3 — the quarantine area, for introspection. Rows a sync round moved out of the
     * live view (fence arm, server rejection, validator rejection): bytes kept, never silently deleted,
     * re-enterable only through a new consent. Optional and additive — a backend without sync has none.
     */
    quarantined?(): Promise<QuarantinedRuleAdd[]>;
    /** Declared durability; an undeclared backend reads fail-closed as `"process-local"`. */
    readonly durability?: StoreDurability;
    /** Declared serialization fidelity, same doctrine as the other store families. */
    readonly fidelity?: StoreFidelity;
}
/**
 * design/182 §3.3/§4.5 — who a rule bucket belongs to, as a STRUCTURAL position.
 *
 * Identity absence is spelled as a distinct union member, never as a sentinel string flowing through
 * `forPrincipal` — every consumer of a principal string is an identity surface, and a made-up name on an
 * identity surface is indistinguishable from a real one downstream. Cloud sync and cloud tickets accept
 * ONLY the `principal` kind: syncing is an authenticated act, and a local-owner bucket has no cloud home
 * until it is adopted (`adoptFilePermissionRuleStore`).
 */
export type RuleOwner = {
    kind: "principal";
    principal: string;
} | {
    kind: "local-owner";
};
/** Do two owners name the same bucket? */
export declare function sameRuleOwner(a: RuleOwner, b: RuleOwner): boolean;
/** Resolves one owner's durable partition. `undefined` ⇒ a partition that reports zero rules. This is
 *  the BACKEND contract (file, in-memory, an out-of-repo SQL twin); the engine never consumes it
 *  directly — `createPermissionRuleStoreProvider({ durable })` composes it into the unified store. */
export interface DurableRulePartitionProvider {
    forPrincipal(principal: string | undefined): DurableRulePartition;
    /**
     * design/182 §4.5 (F-011) — the identity-less LOCAL bucket, for a deployment that explicitly declared
     * local-owner rules. Optional and additive: a provider without it simply has no local-owner form.
     * A file backend resolves a FIXED file name (never a principal-hash path), and after an adoption
     * completed it resolves the ADOPTED principal's bucket forever — the retired bucket never revives.
     */
    forLocalOwner?(): DurableRulePartition;
}
/** The outcome of one accepted write. `sync` is present only on a `sync-join` delta — the landing
 *  report the disclosure layer reads (design/182 §8.1: quarantine/fence details ride the PutResult). */
export interface PutResult {
    rev: number;
    sync?: RuleSyncLandingReport;
}
/**
 * What one `apply` answers. `conflict` is the durable partition's compare-and-set losing; `refused` is
 * the SESSION partition's epoch fence speaking (design/382 §4.3, carried through the unified store's
 * routing writer): a `redemption-add` whose scope names a session that has ended lands nothing — the
 * redemption reports the member `refused`, never a silent success and never a resurrected row. A
 * durable backend never answers `refused` (it holds no session partition); the member exists on the
 * union so the ONE write path the consent protocol takes has one result vocabulary.
 */
export type RuleWriteOutcome = PutResult | {
    conflict: true;
    rev: number;
} | {
    refused: "session_ended";
    rev: number;
};
/**
 * What a caller may conclude from one {@link RuleWriteOutcome} — the arms told apart ONCE, here.
 *
 * `unreadable` is the arm the union cannot spell: an `apply` answer is DATA (a foreign backend, a
 * deserialized reply, a twin compiled against another generation), so "it was not a conflict and not a
 * refusal" is not a proof that it landed.
 */
export type RuleWriteReading = {
    kind: "landed";
    rev: number;
    sync?: RuleSyncLandingReport; /** The landing stands; its report does not. */
    reportUnreadable?: string;
} | {
    kind: "conflict";
    rev: number;
} | {
    kind: "refused";
    refused: string;
}
/** The store ANSWERED, and nothing in the answer says what happened. Never a landing, never a
 *  confirmed nothing: the write may have committed and lost its acknowledgement, so the reading a
 *  caller renders from this arm is the indeterminate one and the reconciliation is `list()`. */
 | {
    kind: "unreadable";
    why: string;
};
/**
 * A revision is a COUNT the store advances, so this is the whole of what one may be. Zero is a revision:
 * a session-scope add lands in a partition that has none of its own and the landing reports the durable
 * revision unchanged, which is `0` for a durable bucket nothing has written yet.
 *
 * Exported because the acceptance kit judges the same thing about a backend's answer, and two copies of
 * "what a revision is" is exactly the drift the contract-sentence discipline exists to stop.
 */
export declare function isRuleRevision(rev: unknown): rev is number;
export declare function readRuleWriteOutcome(res: RuleWriteOutcome): RuleWriteReading;
/** Authorization accompanying an add: the redemption that produced it. Carrying the record id makes the
 *  add's logical operation identity checkable at the backend, not just at the caller. A v1 caller
 *  carries `principal`; the local-owner path (design/182 §4.5) carries `owner` instead — never both
 *  disagreeing, never a sentinel string. */
export interface RedemptionAuthorization {
    recordId: string;
    principal?: string;
    owner?: RuleOwner;
}
/** The add half of the write union: one redeemed approval becoming one dot on one (rule, scope). */
export interface RuleAddDelta {
    kind: "redemption-add";
    /** The identity's behavior member (a redemption lands one dot on one (behavior, text, scope)). */
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    tool: PersistedRule["tool"];
    match: PersistedRule["match"];
    command: string;
    add: RuleAdd;
    redemption: RedemptionAuthorization;
}
/**
 * The TIGHTENING add — the true dual of {@link RuleDeleteDelta}, and the reason it is a separate arm
 * rather than a `redemption-add` with the authorization left out: a delta named for a redemption that
 * carries no redemption is a name that lies. This arm carries NO authorization block at all, exactly as
 * `tighten-delete` does, because it needs none: a host may narrow on a person's behalf, it may not
 * widen — so `behavior` excludes `allow` IN THE TYPE and the widening direction cannot reach this door.
 * Minting a standing approval still takes a person's consent record, which is what `redemption-add` is.
 *
 * The QUARANTINE fence ({@link assertRedemptionNotQuarantined}) applies to `redemption-add` only, and
 * not because it was forgotten here: the fence exists to stop a REPLAYED approval record from reviving
 * an add its dot was quarantined for. A tightening add has no record and is never replayed — its dot is
 * minted once, at the call — so there is no replay for the fence to catch.
 */
export interface RuleTightenAddDelta {
    kind: "tighten-add";
    /** The tightening members only. `allow` is not in the type: see this interface's doc. */
    behavior: Exclude<RuleBehavior, "allow">;
    rule: string;
    scope: RuleScope;
    tool: PersistedRule["tool"];
    match: PersistedRule["match"];
    command: string;
    add: RuleAdd;
}
/** The delete half: appending one tombstone, and nothing else. */
export interface RuleDeleteDelta {
    kind: "tighten-delete";
    tombstone: RuleTombstone;
}
/**
 * design/182 §8.1 — one whole sync round landing as ONE delta.
 *
 * "Partial landing" is unspellable at the write layer: either the whole join lands or none of it does,
 * and replaying the same join is idempotent. The delta cannot express "remove a local row" through the
 * join (a join only ever unions) — that is what the explicit `quarantine` member is for, so a
 * server-rejected or validator-rejected LOCAL row moves out of the live view in the SAME optimistic
 * write as the join, never silently and never in a second racing write.
 */
export interface RuleSyncJoinDelta {
    kind: "sync-join";
    /** The validated inbound state (the client's §4.3 partitioning already applied). */
    inbound: RuleSyncState;
    /** The server's collected (already-recycled) frontier. Absent in v2.0 — the fence arms stay dormant. */
    gcFrontier?: RuleSyncFrontier;
    /** The observation vector to persist on a CLEAN landing (absent ⇒ the vector does not advance). */
    observedVector?: RuleSyncFrontier;
    /**
     * Explicit local-row quarantine instructions. Each dot must name an add present in the current raw
     * state (an already-quarantined or unknown dot is an idempotent no-op); an instruction may never touch
     * a tombstone. Quarantined rows keep their bytes, leave the live view and the sync state, and are
     * disclosed — they can re-enter only through a NEW consent (a new ask, a new record, a new dot).
     */
    quarantine?: Array<{
        behavior: RuleBehavior;
        rule: string;
        scope: RuleScope;
        dots: RuleDot[];
        reason: RuleQuarantineReason;
    }>;
}
/**
 * What a write may say. Authorization discriminates on the DELTA SHAPE, not on a full snapshot: only the
 * add arm can introduce a dot, and the delete arm carries a tombstone and no adds. A backend additionally
 * REFUSES at runtime any delete that would introduce a new add dot — structure and runtime check together,
 * so "pick the delete arm and carry across an add" is neither expressible nor accepted. The `sync-join` arm's
 * safety does not rest on hiding the type (ruled 2026-08-10: the backend contract IS exported for
 * out-of-repo store twins) — it rests on every inbound record inside it passing the single validator
 * again AT THE BACKEND: the fifth door of design/179 §4's validator list closes here, not at the
 * calling layer, and closes identically for every caller.
 */
export type RuleWriteDelta = RuleAddDelta | RuleTightenAddDelta | RuleDeleteDelta | RuleSyncJoinDelta;
/**
 * The refusal every backend owes a delta whose `kind` is outside {@link RuleWriteDelta} — minted here so
 * the two bundled backends (and an out-of-repo twin) answer an unnamed operation with ONE sentence
 * instead of each guessing.
 *
 * The parameter is `never` on purpose: the only way to reach it is from a `default` arm the type system
 * has already proved unreachable, so the factory doubles as the compile-time fence (a fifth arm added to
 * the union stops satisfying `never` and reds at every backend's switch, naming the missing word). The
 * runtime half is not decoration — a delta arrives as DATA (a foreign caller, a replayed record, a
 * deserialized join), and the alternative to throwing is folding an unnamed kind as whichever arm
 * an if/else chain happens to end with: a write nobody defined, landed under a name nobody checked.
 */
export declare function unknownRuleWriteDeltaError(delta: never): Error;
/**
 * The backend write face. Exported as part of the BACKEND CONTRACT (ruled 2026-08-10) so an
 * out-of-repo store implementation hangs the same face the file backend does, instead of mirroring the
 * types. The consent boundary is unchanged by the export: the ENGINE reaches a writer only through the
 * consent protocol's redemption (and the sync client's join) — a property of the engine's wiring —
 * and a deployment always owned its own storage bytes, so type visibility grants nothing new.
 */
export interface PermissionRuleWriter {
    /** Mint the next dot for this replica. Counters need only be unique and monotonic, so a dot minted for
     *  an operation that then fails is simply never used. */
    nextDot(): Promise<RuleDot>;
    apply(delta: RuleWriteDelta, opts: {
        expectedRev: number;
    }): Promise<RuleWriteOutcome>;
    /**
     * design/182 §4.2 — the RAW state a sync round exchanges: adds with tombstoned dots NOT pre-filtered
     * (list() filters; a join must not), plus the replica identity, the minted-counter high water and the
     * persisted observation vector. Core-private like the rest of the writer: the raw view rides the sync
     * client, never a host API.
     */
    readRaw(): Promise<RawRuleSyncState>;
}
/** The writer's raw view of one store — everything one sync round needs to know about this replica. */
export interface RawRuleSyncState {
    /** This replica's actor id — the wire `replica` field. A replica identity, NEVER a person identity. */
    actor: string;
    /** The highest counter this replica has minted — the own-actor forgery fence's local truth. */
    counter: number;
    rev: number;
    rules: PersistedRule[];
    tombstones: RuleTombstone[];
    /** The persisted observation vector (absent = never completed a clean round). */
    observedVector?: RuleSyncFrontier;
    quarantined?: QuarantinedRuleAdd[];
}
/**
 * The handle a writable backend hangs its write face on (exported with the backend contract, ruled 2026-08-10;
 * the consent boundary lives in the engine's wiring, not in this key's visibility).
 */
export declare const PERMISSION_RULE_WRITER = "__semaPermissionRuleWriter";
/** A partition that also carries the backend write face. */
export interface WritableDurableRulePartition extends DurableRulePartition {
    readonly [PERMISSION_RULE_WRITER]: PermissionRuleWriter;
}
/** The writer of a store or partition, or `undefined` when the backend is read-only from the engine's
 *  side. Duck-typed on the handle, so it answers for the unified `PermissionRuleStore` (whose writer
 *  routes by scope) exactly as for a bare durable partition. */
export declare function writerOf(store: DurableRulePartition | import("./permission-rule-provider.js").PermissionRuleStore): PermissionRuleWriter | undefined;
/** Do two scopes name the same place? Three members (design/382 §4.1): global, project-by-root,
 *  session-by-sessionId. */
export declare function sameScope(a: RuleScope, b: RuleScope): boolean;
/** Do two rows/tombstones/quarantine rows name the SAME logical rule — the (behavior, text, scope)
 *  triple? Every identity comparison in this module goes through this one predicate: a comparison
 *  that forgot the behavior member would let a delete aimed at an allow take the same-text deny. */
export declare function sameRuleIdentity(a: {
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
}, b: {
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
}): boolean;
/**
 * Apply tombstones to a raw persisted set: an add survives iff its dot appears in no tombstone for the
 * same (behavior, rule, scope). The criterion is dot IDENTITY — single dots across replicas carry no order to
 * compare — and an add the deleting replica never observed survives (add-wins), because behind every add
 * there is one real human approval.
 */
export declare function applyTombstones(rules: readonly PersistedRule[], tombstones: readonly RuleTombstone[]): PersistedRule[];
/** Fold one delta into a raw rule set, returning the new set. Pure — the backends share it so the file and
 *  in-memory forms cannot drift in what a delta MEANS. */
export declare function foldDelta(rules: readonly PersistedRule[], delta: RuleAddDelta | RuleTightenAddDelta): PersistedRule[];
/** Every add dot present in a rule set. */
export declare function addDotsOf(rules: readonly PersistedRule[]): RuleDot[];
/**
 * The runtime half of the delete-arm authorization, shared by every backend so the two cannot drift on
 * what a delta MEANS.
 *
 * The union already makes "pick the delete arm and also add something" unspellable in TypeScript, and a
 * union does not constrain a JavaScript caller's payload — so the check is executed, not merely intended.
 * Every backend calls this before appending a tombstone.
 */
export declare function assertDeleteDeltaCarriesNoAdd(delta: RuleDeleteDelta): void;
/**
 * design/382 §4.3 — the DURABLE-scope gate of the two direct write arms, shared by every backend
 * (the `assertDeleteDeltaCarriesNoAdd` doctrine: the check is executed, not merely intended, because a
 * union does not constrain a JavaScript caller's payload).
 *
 * A `redemption-add` whose scope — or a `tighten-delete` whose tombstone's scope — is a session scope
 * refuses LOUDLY with the typed code `unsupported.session_scope_store`: a session authorization's home
 * is the session's own overlay, and a session TOMBSTONE is equally unspellable here — session rows are
 * not individually deletable (they die with their session, design/382 §10), so a tombstone naming one
 * is either a foreign writer's fabrication or a caller wiring fault, and both deserve the loud arm.
 * The third arm (`sync-join`) is NOT gated here: its session rows are per-row DROPPED AND DISCLOSED by
 * {@link screenRuleSyncState} (a peer's damage must not veto a whole round the way a local caller's
 * bug must), and a session-scoped QUARANTINE instruction throws inside {@link applySyncJoin} itself.
 *
 * The session member is the LOUD, typed arm; the rest of the face is executed too. A scope that is
 * neither member in well-formed shape — `{kind:"project", root:""}` above all, which `pathWithinRoot`
 * turns into base `/` so the row crosses every project boundary — is a caller wiring fault, refused
 * with the untyped loud arm the sibling {@link assertDeleteDeltaCarriesNoAdd} uses (no closed-set code
 * names it: it is nobody's supported request). The sync arm already refuses the same shape inbound
 * ({@link applySyncJoin} runs {@link isValidDurableScope} on every joined row), so this closes the
 * matching direct-write door rather than inventing a new rule.
 *
 * Exported with the backend contract for the same OPEN-SET duty {@link isValidDurableScope} names:
 * any lane — host-side import doors included — that writes rows into a persisted store carries the
 * durable two-member face.
 */
export declare function assertWriteDeltaScopeDurable(delta: RuleAddDelta | RuleTightenAddDelta | RuleDeleteDelta): void;
/**
 * The state one sync round exchanges: raw adds (tombstoned dots NOT pre-filtered) plus tombstones.
 * The live view is always DERIVED (`applyTombstones`); the join below never deletes anything.
 * The server-side twin MUST use the same join (consume the export, or pass
 * `permissionRuleSyncContract` against its own reimplementation).
 */
export interface RuleSyncState {
    rules: PersistedRule[];
    tombstones: RuleTombstone[];
}
/**
 * An actor → counter vector. A dot is at-or-below a frontier ⟺ `dot.counter <= frontier[dot.actor]`.
 * Used both as the client's observation vector and as the server's collected-frontier watermark —
 * two quantities with strictly separated jobs (§5.1): only the COLLECTED watermark (monotone,
 * never lowered by a registering or returning replica) may drive the fence arm.
 *
 * SQL twin note (server half): persist this as a JSON blob column — the vector is read and written
 * whole, never queried per-actor; a row-per-actor table would invite partial updates the semantics
 * cannot express. Epoch-millisecond columns around it follow the `_ms` suffix convention; the OCC
 * counter column is named `rev` (never `version` — that word is reserved for format/schema versions).
 */
export type RuleSyncFrontier = Record<string, number>;
/** One quarantined row: bytes preserved, out of the live view and out of sync, disclosed.
 *  SQL twin note: `add` (dot + provenance) is one immutable value minted at approval time — persist it
 *  as a JSON blob; `rule`/`scope`/`reason`/`atMs` are query axes and belong in structured columns
 *  (`at_ms` on the SQL side). */
export interface QuarantinedRuleAdd {
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    add: RuleAdd;
    reason: RuleQuarantineReason;
    atMs: number;
}
/** One dropped/quarantined disclosure line. `reason` is a closed, typed code (RULE_SYNC_DROP_CODES in
 *  governance-codes.ts) — "silently dropped" must be machine-checkable, so free text is not a member. */
export interface RuleSyncDrop {
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    dot: RuleDot;
    reason: RuleSyncDropReason;
}
/** What one landed sync-join reports back for disclosure (rides {@link PutResult}). */
export interface RuleSyncLandingReport {
    /** Inbound records the backend refused (the peer still holds them — no data loss on this side). */
    droppedInbound: RuleSyncDrop[];
    /** Local rows moved into the quarantine area by this landing (instruction, fence, or validator). */
    quarantined: RuleSyncDrop[];
    /** Tombstones (with their covered adds) recycled below the frontier. Hygiene, not semantics. */
    collectedTombstones: number;
    /** Inbound adds withheld this round because an inbound tombstone was rejected (§4.3 tighten-only:
     *  a dropped tombstone may have covered them — they re-arrive once the peer's state is clean). */
    withheldAdds: number;
}
/**
 * design/382 §4.3 — the CONSENT face's scope validity: the union's full three members, their shapes
 * executed (a project scope with an empty root would admit every cwd, wider than global, through
 * `pathWithinRoot`'s base construction; an empty sessionId would be a session nobody can name).
 * Read by the consent protocol's entries (prepare's explicit-scope gate, the record integrity walk),
 * and exported for the server's consent-projection twin. NEVER by a durable entrance — that face is
 * {@link isValidDurableScope}, and keeping the two as separate named tables is the design's point:
 * a session row leaking toward the store must meet an explicit refusal at every door, not a shared
 * table someone widened for the other face's sake.
 */
export declare function isValidConsentScope(scope: RuleScope): boolean;
/**
 * design/382 §4.3 — the DURABLE face's scope validity: the two members a persisted row may carry,
 * `{global, project}`. A session scope is a WELL-FORMED consent scope that is structurally not a
 * durable one — session authorizations live in the session's own overlay and die with it; letting one
 * into the store (or the sync wire, or the at-rest bytes) would give it exactly the afterlife the
 * dimension is defined not to have. Exported for the server's rule-sync validator (same face, same
 * two members) and for any host-side lane that writes rows into a persisted store — the OPEN-SET
 * duty (design/382 §10): every such entrance, present or future, carries this face.
 */
export declare function isValidDurableScope(scope: RuleScope): boolean;
/** Is `dot` at or below the frontier? Absent actor ⇒ not below (a frontier never covers what it never saw). */
export declare function dotAtOrBelowFrontier(dot: RuleDot, frontier: RuleSyncFrontier): boolean;
/**
 * design/182 §5.1 — the state vector: per actor, the max counter over every add dot, every tombstone's
 * `deletedBy` and every tombstone's `removedDots`. "How far has this state seen that actor mint."
 */
export declare function ruleSyncVector(state: RuleSyncState): RuleSyncFrontier;
/** Pointwise max of two frontiers — how an observation vector advances (monotone, never down). */
export declare function joinFrontiers(a: RuleSyncFrontier, b: RuleSyncFrontier): RuleSyncFrontier;
/**
 * design/182 §3.1 pre-normalization — re-project the denormalized `tool`/`match`/`command` fields of one
 * persisted rule from its `rule` TEXT (the single source of truth) through the one shared validator.
 *
 * A record whose text the validator refuses, or whose stored fields disagree with the re-projection, is
 * damage or forgery — it is REFUSED (fail-closed), never repaired toward either side: picking a side
 * would break the join's commutativity, and "take the wider" would widen a matching surface nobody
 * approved. Returns the normalized record or the refusal reason.
 */
export declare function normalizePersistedRule(r: PersistedRule): {
    rule: PersistedRule;
} | {
    reject: Extract<RuleSyncDropReason, "invalid_rule_text" | "metadata_mismatch">;
};
/** The normalization half exposed for the DISCLOSURE layer: which records a join would refuse, and why.
 *
 *  design/382 §4.3 — this screen carries the SESSION member of the durable face on every JOIN leg
 *  (inbound state, local state, both sides of the pure join): a rule or tombstone row carrying a
 *  session scope is dropped and reported (`session_scope_not_durable`), never landed and never allowed
 *  to veto the round the way a malformed tombstone does. Dropping a session TOMBSTONE cannot widen:
 *  tombstones remove adds only under the SAME (rule, scope), and every session-scope add is dropped by
 *  this same screen — the #176 "its cover is not load-bearing" argument, one scope over.
 *
 *  The face's OTHER member — a durable scope that is not well-formed, `{kind:"project", root:""}`
 *  above all — is executed on the INBOUND legs by {@link applySyncJoin} (its tombstone pre-loop and its
 *  rule arm both run {@link isValidDurableScope}) and at the direct write arms by
 *  {@link assertWriteDeltaScopeDurable}, not here. Stated so the boundary is readable: a LOCAL row of
 *  that shape — reachable only by editing the at-rest bytes, or from a store written before the write
 *  arm executed that axis — passes this screen and stays live. */
export declare function screenRuleSyncState(state: RuleSyncState): {
    state: RuleSyncState;
    rejected: RuleSyncDrop[];
};
/**
 * design/182 §3.1 — the pure state join. Idempotent, commutative, associative; both sides monotone
 * (a join only ever unions, it never deletes). The output is CANONICALLY ORDERED so equal states are
 * deep-equal, which is what lets the contract suite assert the three laws structurally.
 *
 * - rules: grouped by (behavior, rule, scope); a group's adds are the dot-identity union (provenance rides each
 *   add; two payloads under ONE dot — unreachable in a healthy protocol — resolve by canonical-json
 *   byte order, determinism only, detection belongs to the screening layer);
 * - tombstones: grouped by (behavior, rule, scope, deletedBy); same identity with differing `removedDots` takes
 *   the UNION (any one-sided pick breaks commutativity and can drop an observed removal — the union is
 *   the only convergent answer, and it only ever tightens);
 * - both inputs pass {@link screenRuleSyncState} first (refused records are dropped deterministically;
 *   callers wanting the refusal DETAILS run the screen themselves — this function stays a pure join).
 */
export declare function joinRuleStates(a: RuleSyncState, b: RuleSyncState): RuleSyncState;
/**
 * design/182 §5.1/§5.2 — the paired-collection arm: a tombstone is collectable ⟺ its `removedDots` are
 * ALL at-or-below the frontier AND its `deletedBy` is too; collection removes the tombstone together
 * with the adds it covers, atomically (collect only the tombstone and the next join resurrects the add;
 * collect only the add and the leftover tombstone is idle but harmless — so it is always the pair).
 */
export declare function collectBelowFrontier(state: RuleSyncState, frontier: RuleSyncFrontier): {
    state: RuleSyncState;
    collectedTombstones: number;
};
/** The current raw facts a sync-join folds into — what {@link applySyncJoin} takes and returns. */
export interface RuleSyncJoinBase {
    actor: string;
    counter: number;
    rules: PersistedRule[];
    tombstones: RuleTombstone[];
    quarantined: QuarantinedRuleAdd[];
    observedVector?: RuleSyncFrontier;
}
/**
 * design/182 §8.1 — the ONE landing pipeline every backend shares (the `foldDelta` doctrine: the file
 * and in-memory forms must not drift on what a sync-join MEANS). Pure; the backend persists the result
 * in a single optimistic write. Steps:
 *
 *   ① screen the inbound state at the backend (the single validator's fifth door closes HERE — the
 *     backend does not trust the calling layer): text/metadata refusals drop the inbound record;
 *     the own-actor forgery fence (M14) drops any inbound add or tombstone claiming THIS replica's
 *     actor with a counter above what this replica has minted; an inbound tombstone with an empty
 *     `removedDots` is a protocol violation and THROWS (the client's tighten-only partitioning must
 *     have withheld the round's adds — a backend landing them anyway would loosen);
 *   ② join (unions only, never deletes);
 *   ③ with a `gcFrontier`: paired collection, then the fence arm — a live LOCAL add at-or-below the
 *     collected frontier and ABSENT from the inbound state is the residue of an already-recycled
 *     tombstone: moved to quarantine (never silently dropped), disclosed;
 *   ④ execute the explicit quarantine instructions (idempotent per dot; touching a tombstone throws);
 *   ⑤ screen LOCAL rows too — a locally damaged row (file edit) moves to quarantine rather than
 *     riding every future round;
 *   ⑥ persist the observation vector the delta carries (the client only sends one on a clean round).
 */
export declare function applySyncJoin(cur: RuleSyncJoinBase, delta: RuleSyncJoinDelta, nowMs: number): {
    next: RuleSyncJoinBase;
    report: RuleSyncLandingReport;
};
/**
 * The redemption-side quarantine fence, shared by every backend (design/182 §5.2 review round 2):
 * a quarantined row's contract is "re-entry only through a NEW consent — a new ask, a new record, a
 * NEW dot". Replaying an already-redeemed approval record re-submits its RECORDED dot; once that
 * add has been quarantined, folding the replay back into `rules` would resurrect it without anyone
 * consenting again (and for `below_gc_frontier`, resurrect an approval whose deletion tombstone was
 * already recycled). Any quarantined row sharing the delta's dot — under ANY (rule, scope) — refuses
 * the write loudly: dots are minted once, so a replayed dot that quarantine pinned anywhere is the
 * resurrection shape regardless of which tuple it claims now.
 */
export declare function assertRedemptionNotQuarantined(quarantined: readonly QuarantinedRuleAdd[], delta: RuleAddDelta): void;
/** Integrity fingerprint over a store's own facts. Corruption detection, not tamper-proofing. */
export declare function ruleStoreChecksum(payload: unknown): Promise<string>;
/**
 * What a removal may say about the rule identity it just tombstoned. THREE words, not a boolean, for the
 * same reason {@link RuleAddLanding} has `committed: "no" | "unknown"`: the answer comes from a read-back,
 * and a read-back can fail. `yes` and `no` are things the store said; `unknown` is the honest word for a
 * look that did not happen — reporting `no` there would render "we could not see it" as "it is not there",
 * which is the one sentence this domain refuses to say anywhere else ("we cannot see the rules" is never
 * "there are none").
 */
export declare const REMOVAL_LIVENESS_WORDS: readonly ["yes", "no", "unknown"];
export type RemovalLiveness = (typeof REMOVAL_LIVENESS_WORDS)[number];
/**
 * What a terminal write FAILURE may claim about the bytes — the failure half of the honesty
 * {@link RemovalLiveness} keeps on the success half, spelled ONCE and read by every write entry here.
 *
 * `"no"` = the store ITSELF answered: a face that cannot write at all, a target the type face refuses,
 * a read that failed before any write, a refused preflight, an epoch seal, a compare-and-set that lost
 * every attempt. Each of those is the store saying nothing landed, so saying it is a fact.
 * `"unknown"` = the apply THREW, which proves nothing either way (a backend can commit and then lose
 * its acknowledgement), or the read-back that would have settled it could not be taken.
 *
 * The word exists because the caller's two branches — retry, or reconcile with `list()` — are not
 * derivable from the failure PROSE. A consumer reading the strength out of the sentence is parsing a
 * message written for a person, and every such reader has to be updated by hand when the sentence is.
 */
export declare const RULE_WRITE_COMMITTED_WORDS: readonly ["no", "unknown"];
export type RuleWriteCommitted = (typeof RULE_WRITE_COMMITTED_WORDS)[number];
/**
 * The result of a removal.
 *
 * `conflict` is not a member: an OCC conflict is retried internally to a terminal state, so a caller never
 * has to write the retry loop that must not re-snapshot.
 */
export type RemoveResult = 
/** The tombstone landed and none of the observed dots survive. `stillLive` reports whether an approval
 *  that landed DURING this call keeps the rule alive anyway (add-wins) — a real outcome with a name,
 *  not an anomaly — or {@link RemovalLiveness} `unknown` when the read-back that answers it could not be
 *  taken. A consumer reading `unknown` as "removed" has re-introduced the very over-claim the third word
 *  exists to prevent: the tombstone landed, the rule's liveness is UNOBSERVED, reconcile with `list()`. */
{
    status: "removed";
    rev: number;
    stillLive: RemovalLiveness;
}
/** Nothing to remove — including a repeated delete. No empty tombstone is minted. `stillLive` is the
 *  SAME read-back the `removed` arm reports, taken at the same point in the call ({@link
 *  readRuleLiveness}): "there was nothing to remove" is a statement about the snapshot this call read,
 *  and an approval landing after it leaves the rule standing. Both 200-shaped arms therefore carry the
 *  word, from one source. It is a LATER OBSERVATION, not an atomic absence: `rev` is the revision the
 *  decision was taken at, the word is what a second look saw, and the store may change again right
 *  after — `yes` beside a `no-op` is the honest pair, not a contradiction. */
 | {
    status: "no-op";
    rev: number;
    stillLive: RemovalLiveness;
}
/** Terminal failure, in one of two strengths — word for word the dual of {@link AddResult}'s, and
 *  `committed` is the word for it ({@link RuleWriteCommitted}), so the strength is READ, not parsed out
 *  of the sentence. When the STORE answered every attempt (the compare-and-set simply lost), nothing
 *  landed: `committed: "no"`, and the read-back confirms it in the text. When the apply THREW, a missing
 *  tombstone cannot tell a write that never landed from one whose evidence a paired collection erased
 *  (`collectBelowFrontier` recycles a tombstone together with the adds it covered), so that outcome is
 *  `committed: "unknown"`, reported INDETERMINATE, and the caller reconciles with `list()` rather than
 *  retrying blind. When the read-back itself failed, the word is the one the apply earned — a throw
 *  leaves it `unknown`, an answered compare-and-set stays `no`, because an unreadable store cannot
 *  un-answer what the store already said. */
 | {
    status: "failed";
    error: string;
    committed: RuleWriteCommitted;
};
/**
 * Remove a persisted rule.
 *
 * Removal is the TIGHTENING direction, so it needs no approval-record ceremony: a host may narrow on a
 * user's behalf, it may not widen. The asymmetry is the point — minting takes a human's click, revoking
 * does not.
 */
export declare function removePersistedRule(opts: {
    /** The identity's behavior member — `removePersistedRule({ behavior: "allow", … })` for a standing
     *  approval, `"deny"`/`"ask"` for a standing refusal/question. Required: the same text can stand in
     *  more than one behavior, and a removal that named only the text would delete one nobody asked about. */
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    /** Whose bucket. A bare string stays the principal shorthand (unchanged callers); the structural
     *  {@link RuleOwner} form adds the local-owner bucket (downstream request, 2026-08-10 — design/182 §4.5's
     *  `forLocalOwner()` face existed, but removal could not name it, so a local-owner rule was
     *  unrevokable through this entry). Same observed-remove/add-wins/stillLive semantics either way. */
    principal: string | RuleOwner;
    provider: import("./permission-rule-provider.js").PermissionRuleStoreProvider;
}): Promise<RemoveResult>;
/**
 * What the ONE add-write path answered. `landed` carries the revision the add committed at; every other
 * terminal state is a single refusal string, which each door renders in its own vocabulary (the consent
 * protocol says `refused`, the record-less entry says `failed`) — one mechanism, two spellings, never
 * two mechanisms.
 */
export type RuleAddLanding = {
    landed: true;
    rev: number;
}
/** `committed` is what the caller may CLAIM afterwards. `"no"` means the store itself answered — a
 *  read that failed before any write, a refused preflight, an epoch seal, or a compare-and-set that
 *  lost every attempt: each of those is the store saying nothing landed. `"unknown"` means the apply
 *  THREW, which proves nothing either way: a backend can commit and then lose its acknowledgement. */
 | {
    landed: false;
    reason: string;
    committed: RuleWriteCommitted;
};
/**
 * Land ONE add — the terminal write both doors take (the consent protocol's redemption and the
 * record-less tightening entry below). Optimistic: every attempt re-reads the store, anchors the
 * compare-and-set to that revision, and retries a conflict without rebuilding the add (the dot is the
 * add's identity and is minted once, by the caller, so a retry can never mint a second).
 *
 * The scope routing is the writer's ({@link writerOf}): a session-scope add lands in the session
 * partition, everything else in the durable one — one path, so the two doors cannot disagree about
 * where a rule goes.
 */
export declare function landRuleAdd(args: {
    store: import("./permission-rule-provider.js").PermissionRuleStore;
    writer: PermissionRuleWriter;
    /** Either add arm, whole. The kinds are dispatched EXHAUSTIVELY below, so a third add arm cannot be
     *  added to the union without this function being told what it means. */
    delta: RuleAddDelta | RuleTightenAddDelta;
    /** Judged on EVERY attempt's snapshot — the very revision the write below is anchored to, so a fact
     *  that changed in between conflicts the write and is re-judged. A returned string refuses. */
    preflight?: (snapshot: import("./permission-rule-provider.js").EffectivePermissionRules) => Promise<string | undefined>;
}): Promise<RuleAddLanding>;
/**
 * The result of an add — the dual of {@link RemoveResult}, word for word where the two mean the same
 * thing. `conflict` is not a member for the same reason: the retry loop is inside.
 */
export type AddResult = 
/** The add landed; `dot` is its audit identity (the same value a removal's tombstone will name).
 *  Deliberately WITHOUT a `stillLive` twin of {@link RemoveResult}: a removal's twin reports the
 *  semilattice's own add-wins resolution, a structural outcome of the write; a fresh dot has no such
 *  resolution to report, and a removal that lands afterwards is an ordinary later event `list()`
 *  answers. */
{
    status: "added";
    rev: number;
    dot: RuleDot;
}
/** An equivalent rule is already live in this bucket — the instruction is already in effect, and no
 *  second dot is minted. Repeating the call is a no-op, exactly as repeating a delete is. */
 | {
    status: "no-op";
    rev: number;
}
/** Terminal failure, in one of two strengths the `error` text distinguishes. When the STORE answered
 *  (a read that failed before any write, a refused preflight, an epoch seal, a compare-and-set that
 *  lost every attempt) nothing landed and the text says so plainly. When the apply THREW, the write
 *  may have committed and lost its acknowledgement: the read-back asks the raw face, where an add
 *  survives the tombstone a concurrent removal laid on it — but a sync collection can erase even
 *  that, so a dot it cannot find is reported INDETERMINATE rather than as a confirmed nothing, and
 *  the caller reconciles with `list()` instead of retrying blind (a blind retry mints a fresh dot and
 *  can reinstate a restriction somebody had just removed). `committed` ({@link RuleWriteCommitted}) is
 *  which of the two this is, as a WORD — the landing's own answer, passed through rather than re-derived,
 *  so this entry and the consent protocol's door cannot disagree about a write they both took. */
 | {
    status: "failed";
    error: string;
    committed: RuleWriteCommitted;
};
/**
 * Add a persisted rule in ONE step, with no approval record.
 *
 * The dual of {@link removePersistedRule}, and bounded by the same asymmetry: a host may narrow on a
 * person's behalf, it may not widen. So this entry writes the TIGHTENING direction only — `deny` and
 * `ask` — and `allow` is not spellable here at all (minting a standing approval still takes a person's
 * consent record: prepare → confirm → redeem). A JS caller that spells it anyway meets a loud refusal.
 *
 * The provenance is MINTED HERE as `user`: this entry takes no origin argument, so a host cannot claim
 * a rule came from an import or a starter set. Before it existed, the only way to land a rule was to
 * borrow the approval-record legs, which stamped the borrowed record's kind onto a write nobody had
 * approved — a second, unverified story about where a rule came from.
 */
export declare function addPersistedRule(opts: {
    /** The identity's behavior member. `allow` is deliberately not in the type — see the entry's doc. */
    behavior: Exclude<RuleBehavior, "allow">;
    rule: string;
    scope: RuleScope;
    /** Whose bucket — the same two spellings {@link removePersistedRule} takes: a bare string is the
     *  principal shorthand, the structural {@link RuleOwner} adds the local-owner bucket. */
    principal: string | RuleOwner;
    provider: import("./permission-rule-provider.js").PermissionRuleStoreProvider;
}): Promise<AddResult>;
export declare function errText(err: unknown): string;
/** A durable partition that always reports zero rules — what an unauthenticated principal resolves to. */
export declare const EMPTY_DURABLE_RULE_PARTITION: DurableRulePartition;
/**
 * In-memory backend — the reference implementation of the delta semantics, and the one the tests drive.
 * `process-local` by declaration: it makes no durability promise it cannot keep.
 */
export declare class InMemoryDurableRulePartition implements WritableDurableRulePartition {
    private readonly actor;
    private readonly now;
    readonly durability: StoreDurability;
    readonly fidelity: StoreFidelity;
    private rules;
    private tombstones;
    private quarantinedRows;
    private observedVector;
    private rev;
    private counter;
    constructor(actor?: string, now?: () => number);
    list(): Promise<StoredRules>;
    quarantined(): Promise<QuarantinedRuleAdd[]>;
    readonly [PERMISSION_RULE_WRITER]: PermissionRuleWriter;
}
