/**
 * design/182 §4 — the cloud-sync client for persisted allow rules.
 *
 * One round = one RTT, full state both directions. The data model is already a state CRDT (OR-Set:
 * per-add dots + observed-remove tombstones), so sync is the JOIN of two states — no cursors, no
 * baselines, no three-way reconcile, and the whole cursor-retention/prefill/echo family of defenses the
 * memory sync face carries is structurally absent here (a registered, deliberate divergence). What IS
 * inherited from that face is the contract style: an injected transport (core bundles no fetch), the
 * transport running BEFORE any local write (a throw aborts the round with zero local effects), a
 * picked-not-spread response parse, and a pure join both ends share.
 *
 * The safety polarity is the opposite of memory's: a memory sync bug loses data; a rule sync bug
 * WIDENS what runs without asking. Every ambiguous call here therefore resolves toward the tightening
 * side — the tighten-only partial application (§4.3), the frontier fence (§5.2), and the rule that a
 * LOCAL row only ever leaves the live view through an explicit, disclosed quarantine instruction.
 *
 * ## Trust domain (stated, not defended against)
 *
 * Connecting a principal's store to a sync endpoint merges that principal's devices, the server and
 * every device's transport into ONE consent trust domain: any compromised member can mint persisting
 * allow rules for the whole domain (an injected add rides the next full-state exchange everywhere).
 * Per-add provenance keeps the minting replica auditable, resurrection/landing disclosures keep changes
 * visible, and the single validator still refuses every illegal SHAPE from the wire — but the
 * "one human click behind every add" guarantee is domain-level, not device-level, until the
 * privilege-separation ticket adds device-bound signatures. Deployments should connect knowing which
 * level they are buying.
 */
import type { PersistedRule, RuleBehavior, RuleDot, RuleRejectCode, RuleScope, RuleTombstone } from "./permission-rule-model.js";
import type { PermissionRuleStoreProvider } from "./permission-rule-provider.js";
import type { RuleOwner, RuleSyncDrop, RuleSyncFrontier, RuleSyncState } from "./permission-rule-store.js";
import { type TracerHook } from "./trace.js";
/** The injected HTTP seam — core never bundles a fetch. The deployment owns base URL, auth, TLS and
 *  retries; a non-2xx / network failure should THROW (the round then aborts with zero local effects —
 *  the transport runs before any local write). */
export type PermissionRuleSyncTransport = (path: string, body: unknown) => Promise<unknown>;
/** The one wire path of this protocol. */
export declare const PERMISSION_RULE_SYNC_PATH = "/v1/permission-rules/sync";
/** The wire request: the local raw state, whole. `replica` is this store's actor id — an observation /
 *  frontier-accounting fact, NEVER an identity input; `principal` is an envelope echo — authentication
 *  lives on the transport's authenticated channel, and the server must never trust this field. */
export interface RuleSyncRequestBody {
    principal: string;
    replica: string;
    state: RuleSyncState;
    observedVector?: RuleSyncFrontier;
}
/** The wire response (server twin: join its persisted state with the request state, answer the whole
 *  merged form). `gcFrontier` is the COLLECTED watermark (§5.1) — absent in v2.0 by protocol. */
export interface RuleSyncResponseBody {
    principal: string;
    merged: RuleSyncState;
    gcFrontier?: RuleSyncFrontier;
    /** Server-side refusals of rows we sent. `behavior` is part of the row identity the twin names. */
    dropped?: Array<{
        behavior: import("./permission-rule-model.js").RuleBehavior;
        rule: string;
        scope: RuleScope;
        dot: RuleDot;
        reason: string;
    }>;
}
export interface PermissionRuleSyncResult {
    /** True ⟺ the round landed with zero drops, zero quarantines and zero warnings. */
    ok: boolean;
    /** Local set sizes at push time (full-state protocol: the wire carries the whole set). */
    pushed: {
        addDots: number;
        tombstones: number;
    };
    /** What the landing added relative to the local pre-state. */
    landed: {
        newAdds: number;
        newTombstones: number;
    };
    /** (behavior, rule, scope) identities that went removed → live in this round — add-wins made visible (§4.6). */
    resurrected: Array<{
        behavior: import("./permission-rule-model.js").RuleBehavior;
        rule: string;
        scope: RuleScope;
        byDot: RuleDot;
    }>;
    /** Inbound refusals and local quarantine moves, typed (closed reason set, governance-codes family). */
    dropped: RuleSyncDrop[];
    /** The store revision after landing (the pre-round revision when nothing landed). */
    rev: number;
    /** Honest disclosures for tolerated protocol irregularities (withheld adds, unshapeable entries…). */
    warnings?: string[];
}
/** A typed, DISCRIMINABLE refusal: a local-owner bucket has no cloud home until it is adopted. Distinct
 *  by `code` from a missing principal, so "not signed in" and "this bucket cannot sync" never collapse
 *  into one indistinguishable failure shape. */
export declare const LOCAL_OWNER_UNSYNCABLE_CODE = "permission_rules.local_owner_unsyncable";
/**
 * Run one sync round for one principal's bucket.
 *
 * Order of operations is the contract: read raw → transport (no local write yet; a throw aborts with
 * zero local effects) → validate the response (picked-not-spread; principal echo mismatch throws) →
 * partition tighten-only (§4.3) → land the WHOLE round as ONE `sync-join` delta under optimistic
 * concurrency — a conflict re-reads and RE-JOINS (the join's commutativity/idempotence makes the
 * recompute correct by construction; adds and tombstones that landed during the RTT survive it).
 */
export declare function syncPermissionRules(opts: {
    provider: PermissionRuleStoreProvider;
    principal: string;
    /** Accepted only as `kind: "principal"` (and it must agree with `principal`). A local-owner value is
     *  refused with the typed {@link LOCAL_OWNER_UNSYNCABLE_CODE} error — see design/182 §4.5. */
    owner?: RuleOwner;
    transport: PermissionRuleSyncTransport;
    /** Injected clock — observation timestamps only, never adjudication input. */
    now?: () => number;
    /** design/182 §4.6 — optional trace sink for the round's DISCLOSURES: one
     *  `permission.rule_sync_resurrected` per removed→live transition and one
     *  `permission.rule_sync_dropped` per refused/quarantined row. The same facts are already on the
     *  returned result; this is the operator channel for a deployment that drives sync outside a task
     *  (where there is no `RunnerDeps.tracer` in scope) and does not read the result itself. Absent ⇒ no
     *  events, and the round is byte-identical. */
    tracer?: TracerHook;
}): Promise<PermissionRuleSyncResult>;
interface ParsedRuleSyncResponse {
    principal: string;
    merged: {
        rules: PersistedRule[];
        tombstones: Array<RuleTombstone | undefined>;
    };
    gcFrontier?: RuleSyncFrontier;
    dropped: Array<{
        behavior: RuleBehavior;
        rule: string;
        scope: RuleScope;
        dot: RuleDot;
        reason: string;
    }>;
    /** Tombstones whose SHAPE is whole but whose rule TEXT the current validator refuses — the third
     *  outcome, kept OUT of `merged.tombstones` so it does not read as malformed. See {@link
     *  parseRuleSyncResponse} for why this one does not withhold the round's adds (backlog #176). */
    textRefusedTombstones: Array<{
        behavior: RuleBehavior;
        rule: string;
        scope: RuleScope;
        dot: RuleDot;
        code: RuleRejectCode;
    }>;
    /** design/382 §4.3 — SESSION-scope tombstones, dropped at the parse as per-row refusals (never
     *  `undefined`-malformed): a session row can never land regardless of its dots, so classifying a
     *  DAMAGED one as generic-malformed would withhold every valid inbound add on every round — one
     *  peer row poisoning sync forever, the exact shape the per-row drop arm exists to avoid. */
    sessionScopeTombstones: Array<{
        behavior: RuleBehavior;
        rule: string;
        scope: RuleScope;
        dot: RuleDot;
    }>;
    warnings: string[];
}
/**
 * Parse one wire response. Envelope violations (missing/mistyped principal, echo mismatch, no merged
 * state, an unparseable frontier) THROW — a protocol error aborts the round with zero local effects.
 * ENTRY-level violations degrade per §4.3: an unshapeable rule/add is skipped with a warning (it never
 * reaches the local disk); an unshapeable tombstone is surfaced as `undefined` so the caller's
 * tighten-only partitioning withholds the round's adds. Unknown keys never flow anywhere — every field
 * below is PICKED off the response, the object itself is discarded.
 *
 * A tombstone has THREE outcomes, not two (backlog #176): sound; malformed (bad shape, or a spelling
 * that parses but is not canonical — the withholding arm); and shape-whole-but-TEXT-refused, reported
 * on {@link ParsedRuleSyncResponse.textRefusedTombstones}. The third is a legacy row a tightened
 * validator no longer admits — permanent, so treating it as the second would stall inbound adds forever.
 */
export declare function parseRuleSyncResponse(raw: unknown, expectedPrincipal: string): ParsedRuleSyncResponse;
export {};
