/**
 * ReplicatedRecordEnvelope — the GENERIC substrate for replicated-store journal
 * kinds (WS2 replicated-store foundation, Component 2 / Build-order step 2).
 *
 * Spec: docs/specs/multi-machine-replicated-store-foundation.md §4 (the
 * replicated-record envelope, flag-gated + flag-coherence-gated emission), §10
 * (config / advert / awareness), §13 build-order item 2, §14 (safety posture).
 *
 * This module is the REUSABLE machinery the concrete stores (WS2.1 preferences,
 * relationships, learnings, …) layer a kind onto. It adds NO concrete store
 * kind itself — the registry ships EMPTY. It provides exactly four pieces:
 *
 *   A. The replicated-record envelope type + a parameterizable STRICT validator
 *      (`validateReplicatedEnvelope`) that mirrors the CoherenceJournal typed-
 *      schema discipline: reject free text, drop unknown fields (counted), jail
 *      any path-shaped field, validate `hlc` + (when present) `observed` as
 *      well-formed HlcTimestamps. ABSENT `observed` is LEGAL ⇒ "no prior
 *      witness" ⇒ flag-on-conflict (the safe direction, §4 / §7.2). The validated
 *      envelope fields are AUTHORITATIVE: a store schema can never override a
 *      reserved envelope field on the reconstructed `data` (reserved keys the
 *      store returns are stripped + counted), and the path-jail is REUSABLE
 *      machinery for store fields too — a store declares `pathSensitiveFields`
 *      for auto-jailing, or calls the exported `jailStoreStringField` helper.
 *      Reserved-key collisions in a store's `knownFields` are rejected at
 *      registration time (a wiring-time programmer error), not trusted to a
 *      store's self-check.
 *
 *   B. A registry of replicated kinds (`ReplicatedKindRegistry`) — the generic
 *      registration mechanism a concrete store calls to register its kind +
 *      store-specific field schema. Initially empty (no concrete kind).
 *
 *   C. Flag-gated emission (`isStoreEmissionEnabled`) — a replicated kind is
 *      emitted ONLY when `multiMachine.stateSync.<store>.enabled` is true
 *      (default FALSE). When off, strict no-op.
 *
 *   D. Flag-coherence-gated emission (`shouldEmitToPeer`,
 *      `checkPoolFlagCoherence`) — a replicated kind is exchanged with a peer ONLY
 *      when that peer's `seamlessnessFlags` advertises the matching
 *      `stateSync.<store>` capability. The applier "silently drops unknown
 *      kinds" (forward-compat). The per-peer decision is correct for N peers
 *      (never assumes exactly 2); the boot-time pool-flag-coherence check
 *      coalesces ALL mixed-flag peers into ONE surfaced result.
 *
 * TRANSPORT IS PULL — `shouldEmitToPeer` is intentionally UNWIRED in Step 2.
 * The real journal-sync transport is RECEIVER-DRIVEN PULL: `PeerPresencePuller`
 * iterates the SENDER's advert (`CoherenceJournal.getOwnAdvert()`, over the static
 * `JOURNAL_KINDS` const) and pulls each kind it is behind on; serve + apply both
 * gate on `JOURNAL_KINDS`. There is NO push-forward step. The named "emit a new
 * kind to an OLD peer → silently dropped" mode therefore manifests as "an old
 * peer NEVER PULLS a kind absent from its own JOURNAL_KINDS" (nothing requested ⇒
 * nothing dropped). On this PULL transport the flag-coherence gate's role is the
 * boot-time pool surface (`checkPoolFlagCoherence`, wired in server.ts) PLUS the
 * serve/pull-decision gate the WS2.1 consumer PR will consult once a concrete
 * kind exists — that is where `shouldEmitToPeer` will be called. In Step 2 it is
 * a pure decision with no concrete kind to serve, hence intentionally unwired
 * (NOT an unintegrated push gate).
 *
 * DUAL-REGISTRY COUPLING — a replicated kind MUST be added to BOTH registries.
 * The new `ReplicatedKindRegistry` (read by the gate + the stateSyncReceive
 * advert) and the static `JOURNAL_KINDS` (in CoherenceJournal.ts, gating serve +
 * apply + enumerated by getOwnAdvert) are TWO parallel kind registries. A WS2.1
 * PR that registers into `ReplicatedKindRegistry` but forgets `JOURNAL_KINDS`
 * yields a store that advertises receive=true yet serves/applies/pulls nothing —
 * a SILENT no-replication. The wiring-integrity test asserts every replicated
 * kind is present in BOTH (a CI ratchet, not a memory item).
 *
 * SAFETY POSTURE (§14): this is MECHANISM, dark by default. NO gate here blocks
 * a user-initiated action. The only refusals are at the RECEIVE door (the
 * validator rejects malformed data) and the EMISSION door (don't forward a kind
 * to a non-advertising peer). Both protect data; neither blocks the user. A
 * single-machine install (no advertising peers) is a strict no-op — emission is
 * gated on having a peer advertising the matching flag.
 *
 * Purity: this module imports ONLY the HLC primitive + node:path (for the
 * path-shape jail's separator detection). No fs, no network, no Date. Every
 * function is a pure function of its inputs.
 */
import { type HlcTimestamp } from './HybridLogicalClock.js';
/** The op a replicated record carries (§4). A delete is a tombstone, never a
 *  physical removal, so the last-writer-wins merge can resolve a delete↔put
 *  race deterministically. */
export type ReplicatedOp = 'put' | 'delete';
/**
 * The fields EVERY replicated store's journal `data` carries, on top of its
 * store-specific fields (§4):
 *  - `recordKey` — the store's primary key (e.g. a preference id). A non-empty,
 *    length-capped string; never path-shaped (jailed).
 *  - `hlc` — the HlcTimestamp from `clock.tick()` at AUTHOR time. The
 *    load-bearing total order the merge uses.
 *  - `op` — 'put' | 'delete'.
 *  - `origin` — the author machine id. Equal to `entry.machine`, kept EXPLICIT
 *    so the reader/un-merge does not have to infer it (§4).
 *  - `observed` — the single HlcTimestamp the author had ALREADY merged for THIS
 *    recordKey before writing, or ABSENT if none. The last-writer-witness the
 *    sound concurrency detector needs (§7.2): ONE bounded HLC, NOT a per-key
 *    vector. Absent ⇒ "no prior witness" ⇒ flag-on-conflict (the safe direction).
 */
export interface ReplicatedEnvelope {
    recordKey: string;
    hlc: HlcTimestamp;
    op: ReplicatedOp;
    origin: string;
    observed?: HlcTimestamp;
}
/** The envelope's own field names — the union of all reserved keys a store may
 *  not reuse for a store-specific field. Exported AND ENFORCED:
 *  ReplicatedKindRegistry.register() throws if a schema's `knownFields` claims any
 *  of these, and validateReplicatedEnvelope strips any reserved key a store's
 *  validate() returns (counting it as a dropped field) so the validated envelope
 *  fields are the sole authority for the reconstructed `data`. */
export declare const RESERVED_ENVELOPE_FIELDS: ReadonlyArray<string>;
/** Caps (mirror the CoherenceJournal length-cap discipline — a value over the
 *  cap is rejected, never truncated-and-accepted, so a record is whole or not). */
export declare const MAX_RECORD_KEY_LENGTH = 512;
export declare const MAX_ORIGIN_LENGTH = 256;
/**
 * The store-specific schema a concrete store (WS2.1) supplies ON TOP of the
 * envelope. `validate(raw, ctx)` receives the store-specific portion of `data`
 * (the envelope fields already stripped + validated) and must return the
 * validated, known-fields-only object, or null to REJECT the whole record. It
 * MUST NOT mutate `raw`. `knownFields` is the allowlist the envelope validator
 * uses to count dropped unknown fields across BOTH the envelope and the store
 * portion — a store reports the field names it owns here.
 *
 * Parameterizable by design: the envelope validator is generic; the store-
 * specific discipline (which extra fields, which enums) is the store's to define.
 */
export interface StoreFieldSchema {
    /** The store-specific field names this schema owns (for unknown-field counting). */
    knownFields: ReadonlyArray<string>;
    /**
     * The SUBSET of `knownFields` that are path-sensitive — string fields that must
     * never hold a path (a `/`, `\`, `..`/`.` segment, or absolute path). The
     * envelope validator AUTO-JAILS every declared path-sensitive field BEFORE
     * calling the store's `validate()`: if any is a path-shaped string the WHOLE
     * record is rejected (`store-field-path-shaped`) and `bumpJailReject` fires —
     * the SAME jail discipline the envelope applies to `recordKey`/`origin`,
     * reusable machinery instead of per-store willpower (Structure > Willpower). A
     * field not declared here is the store's own responsibility (use
     * `jailStoreStringField` inside `validate()` to opt in imperatively). Optional;
     * absent ⇒ no auto-jailed store fields.
     */
    pathSensitiveFields?: ReadonlyArray<string>;
    /**
     * Validate the store-specific fields. Receives the FULL raw data object (so a
     * store may cross-validate a store field against `recordKey` if it wants) and
     * a context object. Returns the validated store-specific fields (known fields
     * only), or null to reject the whole record. Must be PURE (no I/O, no mutation).
     *
     * NOTE: any RESERVED_ENVELOPE_FIELDS key in the returned object is STRIPPED by
     * the envelope validator (and counted as a dropped field) — the validated
     * envelope fields are authoritative and a store can never override them. A
     * store MUST jail its own path-sensitive string fields (declare them in
     * `pathSensitiveFields` for auto-jailing, or call `jailStoreStringField`).
     */
    validate(raw: Readonly<Record<string, unknown>>, ctx: StoreValidateContext): Record<string, unknown> | null;
}
/** Context handed to a store schema's validate() — counters it may bump. */
export interface StoreValidateContext {
    /** Increment when the store drops an unknown/duplicate field (counted). */
    countDroppedField(): void;
    /** Increment when the store rejects a path-shaped string field via the jail
     *  (wired to the SAME jailRejects counter CoherenceJournal surfaces). Bumped
     *  automatically for a declared `pathSensitiveFields` reject; a store may also
     *  call it from `jailStoreStringField` for an imperative jail. */
    countJailReject(): void;
}
/**
 * Reusable store-field path-jail (the §4 "jail any path-shaped field" rule, made
 * reusable machinery instead of per-store re-implementation). Returns the value
 * unchanged when it is a NON-path-shaped string; returns null (and bumps the
 * shared jail counter) when it is path-shaped, so a store's validate() can reject
 * the whole record. A non-string is returned unchanged (the store does its own
 * type check) — this helper enforces the PATH discipline only.
 *
 * Usage inside a store's validate():
 *   const fp = jailStoreStringField(raw.filePath, ctx);
 *   if (fp === null) return null; // path-shaped → reject whole record
 */
export declare function jailStoreStringField(value: unknown, ctx: StoreValidateContext): unknown;
/** A counters bag the caller passes to the validator so degradation is counted
 *  by the SAME counters CoherenceJournal already surfaces (schemaRejects /
 *  droppedFields / jailRejects). The validator NEVER throws on bad data — it
 *  bumps a counter and returns a rejection; throwing is reserved for a
 *  programmer error (a kind that was never registered). */
export interface EnvelopeValidationCounters {
    /** A record dropped for failing the typed schema (any reason). */
    bumpSchemaReject(): void;
    /** An unknown field dropped from an otherwise-valid record. */
    bumpDroppedField(): void;
    /** A path-shaped field rejected by the jail (also a schema reject). */
    bumpJailReject(): void;
}
/** The result of validateReplicatedEnvelope. */
export type EnvelopeValidationResult = {
    ok: true;
    envelope: ReplicatedEnvelope;
    storeFields: Record<string, unknown>;
    data: Record<string, unknown>;
} | {
    ok: false;
    reason: EnvelopeRejectReason;
};
/** Why a record was rejected (for the typed result + tests). Never free text. */
export type EnvelopeRejectReason = 'not-an-object' | 'bad-record-key' | 'record-key-path-shaped' | 'bad-hlc' | 'bad-op' | 'bad-origin' | 'bad-observed' | 'store-field-path-shaped' | 'store-schema-rejected';
/**
 * Is a string PATH-SHAPED? (The §4 "jail any path-shaped field" rule, applied
 * to the envelope's string fields — recordKey/origin — so a path can never
 * smuggle in as a primary key.) A value is path-shaped if it contains a path
 * separator, a `..` traversal segment, or is an absolute path. This is a
 * STRUCTURAL reject (the field has no business holding a path), not a
 * filesystem jail — the envelope carries identifiers, never artifact paths.
 */
export declare function isPathShaped(value: string): boolean;
/**
 * The strict envelope validator (§4). Mirrors the CoherenceJournal typed-schema
 * discipline:
 *   - rejects a non-object,
 *   - validates recordKey (non-empty, length-capped, NOT path-shaped → jail),
 *   - validates hlc as a well-formed HlcTimestamp,
 *   - validates op ∈ {'put','delete'},
 *   - validates origin (non-empty, length-capped, NOT path-shaped),
 *   - validates observed when present (well-formed HlcTimestamp); ABSENT is LEGAL,
 *   - delegates store-specific fields to the supplied StoreFieldSchema,
 *   - DROPS every field not in (RESERVED_ENVELOPE_FIELDS ∪ schema.knownFields),
 *     counting each drop.
 *
 * Returns a typed result. NEVER throws on bad DATA — it bumps a counter and
 * returns `{ ok: false }`. The reconstructed `data` object on success is the
 * validated store fields + the validated envelope fields, in a deterministic
 * shape (only known fields, observed omitted when absent), ready to serialize as
 * the journal entry's `data`. The validated envelope fields are AUTHORITATIVE:
 * any RESERVED_ENVELOPE_FIELDS key the store's validate() returns is stripped
 * (and counted as a dropped field) before merge, so a store can NEVER override a
 * load-bearing envelope field (op/recordKey/hlc/origin/observed) on `data` — the
 * on-disk `data.op/recordKey/hlc/origin/observed` always equal the validated
 * `envelope.*`. `storeFields` on the result is likewise reserved-key-stripped.
 */
export declare function validateReplicatedEnvelope(raw: unknown, schema: StoreFieldSchema, counters: EnvelopeValidationCounters): EnvelopeValidationResult;
/**
 * One registered replicated kind. The `kind` is the JournalKind string (e.g.
 * 'pref-record' — registered later by WS2.1, NOT here). `store` is the stateSync
 * config sub-key + the advert flag suffix (e.g. 'pref'); ONE store may own one
 * kind. `schema` is the store-specific field schema layered on the envelope.
 */
export interface ReplicatedKindRegistration {
    /** The JournalKind string for this replicated store's stream. */
    kind: string;
    /** The stateSync config sub-key / advert suffix (e.g. 'pref'). */
    store: string;
    /** The store-specific field schema validated on top of the envelope. */
    schema: StoreFieldSchema;
}
/**
 * A registry of replicated kinds. Ships EMPTY — Step 2 adds the GENERIC
 * registration mechanism; the first concrete kind is registered by WS2.1.
 *
 * Why a registry (not a hardcoded union extension): the spec mandates "add the
 * GENERIC registration mechanism" and "do NOT hardcode the future concrete
 * kinds into the union in a way that forces them to exist now". A registry lets
 * each store register independently at wiring time; an unregistered/never-
 * emitted kind is simply absent on read (the reader filters by kind), and the
 * applier already drops unknown kinds forward-compat — so an old peer that does
 * not know a kind poisons nothing.
 */
export declare class ReplicatedKindRegistry {
    private readonly byKind;
    private readonly byStore;
    /**
     * Register a replicated kind. Idempotent for an IDENTICAL re-registration of
     * the same (kind, store); THROWS on a CONFLICTING registration (a different
     * store claiming an already-claimed kind, or a store re-registered with a
     * different kind) — a registration conflict is a programmer error at wiring
     * time, surfaced loudly, never a silent overwrite.
     */
    register(reg: ReplicatedKindRegistration): void;
    /** Is this kind a registered replicated kind? */
    isReplicatedKind(kind: string): boolean;
    /** The registration for a kind, or undefined if unregistered. */
    getByKind(kind: string): ReplicatedKindRegistration | undefined;
    /** The registration for a store, or undefined if unregistered. */
    getByStore(store: string): ReplicatedKindRegistration | undefined;
    /** All registered kinds (the JournalKind strings). Empty until a store registers. */
    kinds(): string[];
    /** All registered store keys. Empty until a store registers. */
    stores(): string[];
    /** Count of registered kinds (0 in the Step-2 substrate-only state). */
    get size(): number;
}
/** A per-store stateSync flag block (the minimal shape this module reads). */
export interface StoreStateSyncFlags {
    enabled?: boolean;
    dryRun?: boolean;
}
/** The stateSync config block as a map of store-key → per-store flags, plus the
 *  foundation-level knobs. Only the per-store `enabled` is read by the emission
 *  gate; the knobs are validated by validateStateSyncInvariants (stateSyncConfig.ts). */
export type StateSyncStores = Record<string, StoreStateSyncFlags>;
/**
 * Is emission ENABLED for a store? (§4 flag-gated emission.) True ONLY when
 * `stateSync[store].enabled === true` (default false). When off, the store
 * NEVER emits its kind — a strict no-op. Nullish/absent ⇒ false.
 */
export declare function isStoreEmissionEnabled(stores: StateSyncStores | undefined, store: string): boolean;
/**
 * The seamlessnessFlags subset this module needs from a peer's advert: the
 * per-store stateSync receive booleans. ABSENT (older peer or feature dark) =
 * non-participant for that store (the conservative side — a sender never
 * forwards a store's kind to a peer that cannot durably receive it). Keyed by
 * the SAME `store` key the registry + config use, so a store added later needs
 * no change here.
 */
export interface PeerStateSyncAdvert {
    /** The peer's machine id (for the coalesced coherence surface). */
    machineId: string;
    /** Whether the peer is currently online (an offline peer is not a forward target). */
    online?: boolean;
    /** Per-store receive capability: stateSyncReceive[store] === true ⇒ advertises it. */
    stateSyncReceive?: Record<string, boolean>;
}
/** Does THIS peer advertise the matching stateSync.<store> receive capability? */
export declare function peerAdvertisesStore(peer: PeerStateSyncAdvert, store: string): boolean;
/**
 * The per-peer emission decision (§4 flag-coherence gating). Emit a replicated
 * kind to a peer ONLY when:
 *   - emission is enabled for the store locally (isStoreEmissionEnabled), AND
 *   - the peer is online, AND
 *   - the peer advertises the matching stateSync.<store> capability.
 * Withhold otherwise — emitting to a non-advertising peer is the NAMED data-loss
 * skew mode (the applier silently drops the unknown kind). Returns a typed
 * decision so the caller can surface a withhold.
 *
 * This is PER-PEER and correct for N peers — the caller iterates peers and calls
 * this once each; there is no "exactly 2" assumption anywhere.
 */
export declare function shouldEmitToPeer(stores: StateSyncStores | undefined, store: string, peer: PeerStateSyncAdvert): {
    emit: boolean;
    reason: 'emit' | 'store-disabled' | 'peer-offline' | 'peer-not-advertising';
};
/** Per-store coherence: which online peers advertise this store, which don't. */
export interface StoreFlagCoherence {
    store: string;
    /** Online peers advertising the store's capability. */
    advertising: string[];
    /** Online peers NOT advertising it (would silently drop our kind). */
    notAdvertising: string[];
    /** True iff the store is locally enabled AND there is ≥1 online non-advertiser
     *  alongside ≥1 advertiser (or local emission) — a genuine MIXED state worth
     *  surfacing once. A pool where everyone advertises (or no one does, with the
     *  store locally off) is coherent and not surfaced. */
    mixed: boolean;
}
/** The boot-time pool-flag-coherence result. ONE result, coalescing ALL stores
 *  + ALL peers — never per-peer-per-tick. `mixedStores` lists exactly the stores
 *  in a mixed state; empty ⇒ fully coherent (nothing to surface). */
export interface PoolFlagCoherenceResult {
    /** Per-store breakdown for every locally-enabled store. */
    stores: StoreFlagCoherence[];
    /** Stores in a MIXED state (the only ones worth a surfaced notice). */
    mixedStores: string[];
    /** A single, deterministic, content-free summary line per mixed store, for the
     *  coalesced surface (never free text from peers — only ids + counts). */
    summary: string[];
}
/**
 * The boot-time pool-flag-coherence check (§4: "A boot-time pool-flag-coherence
 * check surfaces (ONCE) any mixed state"). Iterates ALL registered replicated
 * stores × ALL advertising peers and computes, per store, the advertising vs
 * non-advertising online peers. A store is MIXED when it is locally enabled AND
 * has at least one online peer that does NOT advertise it (that peer would
 * silently drop our kind) alongside at least one online peer that DOES (or there
 * is anyone at all to forward to). The result is ONE coalesced object — the
 * caller surfaces it once (e.g. one Attention item / one log line), never one
 * per peer per tick. Correct for N peers.
 *
 * PURE: no I/O, no surfacing — it computes the verdict; the caller decides how
 * to surface (and dedupes across boots).
 */
export declare function checkPoolFlagCoherence(registry: ReplicatedKindRegistry, stores: StateSyncStores | undefined, peers: ReadonlyArray<PeerStateSyncAdvert>): PoolFlagCoherenceResult;
//# sourceMappingURL=ReplicatedRecordEnvelope.d.ts.map