/**
 * UserRegistryReplicatedStore — the SIXTH concrete consumer of the HLC replicated-store
 * foundation (WS2.6a) and the SECOND PII kind (after WS2.3 relationships). It layers the
 * `user-record` replicated kind onto the generic substrate (ReplicatedRecordEnvelope /
 * UnionReader / ConflictStore / RollbackUnmerge / ReplicationBudget / StoreSnapshot) so that
 * a user the agent knows on machine A is known on machine B — ONE user registry, not
 * one-per-machine.
 *
 * It is the literal analog of `RelationshipsReplicatedStore.ts` (the WS2.3 PII reference
 * consumer): a UserProfile is a registered principal (the multi-user identity the
 * UserManager resolves an inbound message to), so it carries directly-identifying PII and
 * REUSES the WS2.3 PII machinery (type-clamp, disclosure-min projection, channel-set
 * recordKey, tombstones, flag-coherence) rather than reinventing or downgrading it. THIS IS
 * PURE LOGIC. No fs, no Date directly, no network. It defines:
 *
 *   A. The `user-record` store schema — a STRICT typed validator that TYPE-CLAMPS every
 *      known field (`createdAt` ISO-8601-or-absent, `telegramUserId` a finite number,
 *      `channels[]`/`permissions[]`/free text length-bounded + jailed). The schema is a
 *      DISCRIMINATED UNION on `op` — an `op:'put'` VALUE schema AND an `op:'delete'`
 *      TOMBSTONE schema coexist under the one kind, so a tombstone is never marked invalid
 *      by the value schema.
 *
 *   B. The disclosure-minimized PROJECTION — `buildUserRecordData` emits ONLY the enumerated
 *      resolution + merge-relevant fields, NEVER the raw on-disk blob and NEVER the local
 *      `userId` `id`. `recordKey` is the cross-machine IDENTITY SURFACE, derived
 *      deterministically from the SORTED channel-set ("type:identifier" pairs) — the SAME
 *      identity model as relationships (a user IS their channel identifiers, mirroring
 *      UserManager.channelIndex) — never the per-machine `userId` (VM-A and VM-B mint
 *      different ids for the same human; a UUID-keyed record could never collide them).
 *
 *   C. The TOMBSTONE builder — `buildUserTombstoneData` emits an `op:'delete'` record
 *      `{ recordKey, op, hlc, origin, deletedAt }` so a removeUser propagates as a positive
 *      signal across an offline-then-rejoining peer instead of a record absence. CRITICAL:
 *      the UserManager.removeUser() path MUST emit a tombstone, else a peer re-replicates the
 *      locally-removed user forever (resurrection).
 *
 *   D. The union-aware read — `mergeUnionToUsers` collapses a `Map<recordKey, UnionResult>`
 *      into the merged user view. Users are HIGH-impact at the REPLICATION layer (a concurrent
 *      divergent edit to the SAME channel-set identity goes through APPEND-BOTH-AND-FLAG —
 *      both versions surface, never a silent clobber; auto-merging two divergent profiles
 *      could fuse two distinct humans). The CONSUMER READ path is ADVISORY: a replicated user
 *      record is a HINT about what my OTHER machines know — NEVER my authoritative answer to
 *      "who is this inbound sender?" (identity RESOLUTION of an inbound principal is
 *      LOCAL-ONLY, mirroring REQ-M14 — the local channelIndex is always authoritative). The
 *      read NEVER writes a foreign record into the local store.
 *
 *   E. Foreign-record render safety — `renderForeignUserContext` wraps a replicated record in
 *      an explicit `<replicated-untrusted-data origin="…">` envelope and sanitizes EVERY
 *      rendered field. There is no "trusted because machine-set" render slot for a foreign
 *      record.
 *
 * DECIDED FORKS (build prompt, recorded verbatim in the PR ELI16):
 *   1. recordKey = sha256 of the SORTED channel-set ("type:identifier" pairs), NEVER the
 *      local `userId` (cross-machine identity surface — see deriveUserRecordKey).
 *   2. Impact tier = HIGH at the REPLICATION layer (append-both-and-flag), ADVISORY at the
 *      READ layer (a replicated user is a hint, never the authoritative inbound-resolution).
 *   3. disclosure-min: strip the local `userId` — a peer's local id is meaningless + a mild
 *      correlation leak; the channel-set IS the cross-machine identity.
 *
 * SAFETY POSTURE: MECHANISM, dark by default. Nothing here blocks a user-initiated action.
 * The local `userId` is NEVER part of the replicated schema and is stripped from every emitted
 * projection (disclosure minimization).
 */
import type { UserProfile, UserChannel } from './types.js';
import type { StoreFieldSchema, ReplicatedEnvelope } from './ReplicatedRecordEnvelope.js';
import type { ImpactTier, OriginRecord, UnionResult } from './UnionReader.js';
import type { ReplicatedKindBounds } from './ReplicationBudget.js';
import type { HlcTimestamp } from './HybridLogicalClock.js';
/** The stateSync config sub-key + advert suffix for this store (e.g.
 *  `multiMachine.stateSync.userRegistry.enabled`). Equal to the advert flag key
 *  `stateSyncReceive['userRegistry']`. */
export declare const USER_STORE_KEY = "userRegistry";
/** The JournalKind string this store rides — the DUAL-REGISTRY's dynamic half.
 *  MUST also be present in CoherenceJournal.JOURNAL_KINDS (the static half), or the
 *  store advertises receive=true yet serves/applies/pulls nothing. */
export declare const USER_RECORD_KIND = "user-record";
/**
 * Users are HIGH-impact at the REPLICATION layer (fork #2): a concurrent divergent VALUE
 * edit to the SAME channel-set identity surface from different origins goes through
 * APPEND-BOTH-AND-FLAG — both versions preserved, ONE deduped conflict, never a silent
 * overwrite (auto-merging two divergent profiles could fuse two distinct humans). The READ
 * path (mergeUnionToUsers) is ADVISORY — a replicated user record is a HINT about what my
 * OTHER machines know, NEVER my authoritative answer to "who is this inbound sender?".
 */
export declare const USER_IMPACT_TIER: ImpactTier;
/** Mirrors a reasonable per-user channel count (UserManager has no hard cap; this bounds a
 *  hostile peer's flood). */
export declare const MAX_CHANNELS = 50;
/** Per-free-text-string clamp for name / each channel identifier / each permission / each
 *  free-text profile field. */
export declare const MAX_FREETEXT_LENGTH = 2000;
/** A channel `type` / permission is a short slug. */
export declare const MAX_SLUG_LENGTH = 128;
/** Permissions cap. */
export declare const MAX_PERMISSIONS = 50;
/**
 * Per-kind replication bounds. The user registry is FEW + bounded (a handful of registered
 * principals), so the per-store retention mirrors the pref-record / learning-record siblings
 * (a small window with a few archives). NEVER `rotateKeep: 0` (rotate-but-never-delete would
 * be a compliance defect for any PII kind, REQ-D1). The rate cap COALESCES (latest state per
 * recordKey per interval) so a churny upsert loop does not flood the stream.
 */
export declare const USER_RECORD_BOUNDS: ReplicatedKindBounds;
/**
 * Per-entry size cap RAISED to 64KB for this PII kind. The default
 * APPLIER_MAX_ENTRY_BYTES = 8KB could be smaller than a fat profile (many channels + a long
 * bio), so under it the highest-PII records would never replicate AND would wedge the stream.
 * 64KB is provably above the disclosure-minimized projection's maximum (50 channels ×
 * (128 + 2000) ≈ 106KB worst-case shows why we additionally enforce a HARD post-projection
 * ceiling): a record that STILL exceeds 64KB after projection is REJECTED with a named error
 * (never silent-truncate, never suspect-wedge). See assertProjectionUnderCap.
 */
export declare const USER_MAX_ENTRY_BYTES: number;
/**
 * The store-specific field names the `user-record` VALUE schema OWNS (the unknown-field
 * counter's allowlist). The local `userId` `id` is DELIBERATELY ABSENT — it is per-machine
 * and never replicated (fork #1/#3: the recordKey keys on the channel-set identity surface,
 * not the id). `recordKey`/`hlc`/`op`/`origin`/`observed` are reserved envelope fields, never
 * store fields.
 */
export declare const USER_STORE_KNOWN_FIELDS: ReadonlyArray<string>;
/** The tombstone's store-owned fields beyond the reserved envelope set. `deletedAt`
 *  is the only store field a delete carries. */
export declare const USER_TOMBSTONE_KNOWN_FIELDS: ReadonlyArray<string>;
/** Is `v` a valid ISO-8601 date string (and ONLY a date — no smuggled markup)? A string
 *  Date.parse rejects, or that contains an injection char (`<`, `>`, `"`), is not a clean ISO
 *  date. */
export declare function isIso8601(v: unknown): v is string;
/**
 * The `user-record` store schema — a DISCRIMINATED UNION on `op`. Strict typed validation on
 * top of the envelope: reject free text beyond the known fields, TYPE-CLAMP every known field
 * (createdAt ISO-8601, telegramUserId finite number, channels/permissions/free text
 * length-clamped + jailed) so markup cannot smuggle through a render slot that bypasses
 * sanitize(). Returns the validated store-specific object (known fields only), or null to
 * reject the WHOLE record. PURE (no I/O, no mutation of `raw`).
 *
 * The envelope validator has ALREADY validated `op` ∈ {put,delete} before calling this. We
 * branch on it so a tombstone `{recordKey, op:'delete', hlc, origin, deletedAt}` passes (only
 * `deletedAt` is a legal store field for a delete) WITHOUT being marked invalid by the rich
 * VALUE schema.
 */
export declare const userRecordStoreSchema: StoreFieldSchema;
/**
 * Normalize one channel into its stable uid form `type:identifier` (lowercased type, trimmed
 * identifier). This is the SAME `${type}:${identifier}` key UserManager.channelIndex uses to
 * resolve a user across platforms.
 */
export declare function channelUid(channel: UserChannel): string;
/**
 * Derive the cross-machine-stable recordKey for a user (fork #1). A user is "the same" across
 * machines by their CHANNEL SET (mirroring UserManager.channelIndex), NOT by the per-machine
 * `userId` — VM-A and VM-B mint different ids for the same human, so an id-keyed record could
 * never collide them (exactly the relationship-UUID trap WS2.3 solved with the channel-set
 * key).
 *
 * The key is a deterministic, collision-resistant hash of the SORTED, de-duplicated
 * channel-uids: `sha256(sorted(channelUids).join('\n'))`, hex-truncated to 32 chars (the same
 * shape UnionReader.conflictId uses). Sorting makes it order-independent (the two machines
 * converge to the SAME key for the same channel set); the hash makes it a bounded,
 * non-path-shaped string (the envelope's recordKey jail accepts it). A user with NO channels
 * (a degenerate local-only record) is NOT replicable — it has no cross-machine identity
 * surface — and is reported as null so the caller skips emission (it can never collide a
 * stranger by an empty key).
 *
 * COLLISION SAFETY: two DIFFERENT users share a key ONLY if they share the EXACT same full
 * channel set — which IS the UserManager's own definition of "the same user" (the channelIndex
 * maps one user per channel-uid and refuses a cross-user collision). SPLIT-IDENTITY SAFETY: the
 * same user derives the SAME key on both machines IFF both hold the same channel set; when the
 * sets differ (one machine learned an extra channel) the keys differ — correct (they are not
 * yet provably the same user on the machine missing the channel).
 */
export declare function deriveUserRecordKey(channels: ReadonlyArray<UserChannel>): string | null;
/** The `data` object a `user-record` journal entry carries. */
export type UserRecordData = Record<string, unknown>;
/** Input to buildUserRecordData: the record to emit, the freshly-ticked hlc, this machine's
 *  origin id, and the observed-witness (the hlc already merged for THIS recordKey before
 *  writing, or absent). */
export interface BuildUserRecordInput {
    record: UserProfile;
    hlc: HlcTimestamp;
    origin: string;
    observed?: HlcTimestamp;
}
/** The named error a record-over-cap surfaces: not silent-truncate, not suspect-wedge. */
export declare class UserRecordTooLargeError extends Error {
    readonly recordKey: string;
    readonly bytes: number;
    constructor(recordKey: string, bytes: number);
}
/**
 * Build the disclosure-minimized `user-record` envelope `data` for an `op:'put'` (fork #3).
 * Emits ONLY the enumerated resolution + merge-relevant fields — NEVER the raw on-disk blob,
 * NEVER the local `userId` id. recordKey = the derived channel-set identity surface (fork #1).
 *
 * Returns null when the record has NO channels (no cross-machine identity surface ⇒ not
 * replicable — the caller skips emission). Throws UserRecordTooLargeError when the projection
 * STILL exceeds the 64KB per-entry cap (a NAMED, surfaced rejection — never silent-truncate).
 */
export declare function buildUserRecordData(input: BuildUserRecordInput): UserRecordData | null;
/** Throw UserRecordTooLargeError if the projected data serializes over the per-entry cap. The
 *  cap is set so a legal disclosure-minimized record can never reach it; this is the
 *  belt-and-suspenders named rejection. */
export declare function assertProjectionUnderCap(recordKey: string, data: UserRecordData): void;
/** Input to buildUserTombstoneData: the channel set of the deleted user (to derive the
 *  recordKey identity surface), the freshly-ticked hlc, the origin, and the deletedAt
 *  timestamp. */
export interface BuildUserTombstoneInput {
    channels: ReadonlyArray<UserChannel>;
    hlc: HlcTimestamp;
    origin: string;
    deletedAt: string;
    observed?: HlcTimestamp;
}
/**
 * Build an `op:'delete'` TOMBSTONE `data` for a user removal. recordKey = the SAME channel-set
 * identity surface the value records key on, so the tombstone reaches the same human's record
 * on every machine even though the local ids differ. Returns null when the user has no channels
 * (no identity surface to tombstone).
 *
 * CRITICAL: the UserManager.removeUser() path MUST call this, else a peer re-replicates the
 * locally-removed user forever (resurrection). The delete-resurrection guard lives in the merge
 * (a later `delete` hlc wins over an earlier `put`).
 */
export declare function buildUserTombstoneData(input: BuildUserTombstoneInput): UserRecordData | null;
/** A merged user view entry: the projected record fields PLUS its origin machine id (so a
 *  foreign record is rendered inside the untrusted-data envelope). READ-ONLY — NEVER written
 *  back into the local store. */
export interface MergedUserView {
    recordKey: string;
    origin: string;
    /** The validated, type-clamped projection fields (the receive-side schema already ran on
     *  apply; here `data` is that validated portion). */
    data: Record<string, unknown>;
    /** True when this view entry is one of ≥2 concurrent variants of an OPEN conflict
     *  (append-both — both surface; the read NEVER suppresses a usable view AND NEVER blocks on
     *  the unresolved conflict). */
    conflicted: boolean;
}
/**
 * Collapse a `Map<recordKey, UnionResult>` into the merged user view.
 * HIGH-impact-at-replication / ADVISORY-at-read contract (fork #2):
 *   - A resolved single value ⇒ that one view entry.
 *   - An OPEN concurrent conflict ⇒ BOTH (all) `put` variants as separate entries (append-both
 *     — both surface as ADVISORY hints; the read NEVER suppresses a usable view AND NEVER
 *     BLOCKS waiting on operator resolution — a replicated user is a hint, not authority). A
 *     `delete` variant contributes nothing to display.
 *   - A delete-resolved key (every origin's latest is a tombstone) ⇒ nothing (the
 *     delete-resurrection guard: a later delete wins over an earlier put).
 * The read is READ-ONLY: a replicated record NEVER clobbers a divergent local record — the
 * local store files are never written here.
 */
export declare function mergeUnionToUsers(union: Map<string, UnionResult>): MergedUserView[];
/**
 * Render a FOREIGN (replicated) user record into a session-context block, wrapped in an
 * explicit `<replicated-untrusted-data origin="…">` envelope so the session model treats it as
 * a PEER'S claim to re-ground against, never a directive AND never the authoritative
 * inbound-resolution answer. EVERY rendered field is escaped — there is no "trusted because
 * machine-set" slot. A null `data.name` (a malformed view) yields null.
 */
export declare function renderForeignUserContext(view: MergedUserView): string | null;
/**
 * Build an OriginRecord for the OWN user registry (the single-origin materialization the union
 * reader merges against peer replicas). recordKey = derived channel-set identity surface; the
 * envelope carries a SYNTHETIC own-origin HLC stamp derived deterministically from the
 * record's createdAt (physical) so the own record has a well-formed, stable position relative
 * to peer records. Returns null for a channel-less record (no identity surface). The local
 * `userId` is NEVER carried into the replicated namespace (fork #1/#3).
 */
export declare function userToOriginRecord(record: UserProfile, origin: string): OriginRecord | null;
/** The ReplicatedKindRegistry registration for the `user-record` store. server.ts registers
 *  this onto the shared registry; the dual-registry coupling test asserts `kind` is also
 *  present in JOURNAL_KINDS. */
export declare const USER_KIND_REGISTRATION: {
    readonly kind: "user-record";
    readonly store: "userRegistry";
    readonly schema: StoreFieldSchema;
};
/** Convenience: the store's contributing journal kinds (for rollback-unmerge's
 *  kindsForStore('userRegistry') wiring). */
export declare function userContributingKinds(): string[];
/** The store's impact tier resolver, for ReplicatedStoreReader.tierOf. Returns HIGH for the
 *  `userRegistry` store (and HIGH for any unknown store — the conservative
 *  append-both-and-flag direction, never a silent clobber). */
export declare function userTierOf(_store: string): ImpactTier;
/** Re-export the envelope type for callers building/applying user-record envelopes. */
export type { ReplicatedEnvelope };
//# sourceMappingURL=UserRegistryReplicatedStore.d.ts.map