/**
 * RelationshipsReplicatedStore — the SECOND concrete consumer of the HLC
 * replicated-store foundation (WS2.3) and the FIRST PII kind. It layers the
 * `relationship-record` replicated kind onto the generic substrate
 * (ReplicatedRecordEnvelope / UnionReader / ConflictStore / RollbackUnmerge /
 * ReplicationBudget / StoreSnapshot) so that a person the agent knows on machine
 * A is known on machine B — ONE relationship graph, not one-per-machine.
 *
 * It is the literal analog of `PreferencesReplicatedStore.ts` (the WS2.1 reference
 * consumer), with the PII-specific hardening the security spec
 * (`ws23-relationships-userregistry-security.md`) demands. THIS IS PURE LOGIC. No
 * fs, no Date directly, no network. It defines:
 *
 *   A. The `relationship-record` store schema — a STRICT typed validator that
 *      TYPE-CLAMPS every known field (REQ-M3): ISO-8601-only dates, finite-number
 *      counts, length-clamped free text. A foreign record is fully attacker-
 *      controlled (§2.3 injection), so the clamp is the defense that makes markup
 *      un-smuggleable through a field that bypasses sanitize() on render. The
 *      schema is a DISCRIMINATED UNION on `op` (gap #8): an `op:'put'` VALUE schema
 *      AND an `op:'delete'` TOMBSTONE schema coexist under the one kind, so a
 *      tombstone is never marked invalid/suspect by the value schema (REQ-D6).
 *
 *   B. The disclosure-minimized PROJECTION (REQ-M4) — `buildRelationshipRecordData`
 *      emits ONLY the enumerated resolution + merge-relevant fields, NEVER the raw
 *      on-disk blob and NEVER the local UUID `id`. `recordKey` is the cross-machine
 *      IDENTITY SURFACE (REQ-D17), derived deterministically from the sorted
 *      channel-uids (a person is "the same" across machines by their channels,
 *      mirroring resolveByChannel) — never the per-machine `randomUUID()` id.
 *
 *   C. The TOMBSTONE builder — `buildRelationshipTombstoneData` emits an
 *      `op:'delete'` record `{ recordKey, op, hlc, origin, deletedAt }` (§4.2) so a
 *      delete/erasure propagates as a positive signal across an offline-then-
 *      rejoining peer (§4.3) instead of a record absence that cannot distinguish
 *      "deleted" from "never replicated".
 *
 *   D. The union-aware read — `mergeUnionToRelationships` collapses a
 *      `Map<recordKey, UnionResult>` (from ReplicatedStoreReader.readAll('relationships'))
 *      into the merged relationship view. HIGH-impact (preferences/relationships):
 *      an OPEN concurrent conflict is APPEND-BOTH-AND-FLAG — both versions surface,
 *      never a silent clobber; the read NEVER writes a foreign record into the local
 *      store (REQ-M7 read-only union).
 *
 *   E. Foreign-record render safety — `renderForeignRelationshipContext` wraps a
 *      replicated record in an explicit `<replicated-untrusted-data origin="…">`
 *      envelope (§2.3) and sanitizes EVERY rendered field (the ISO-8601/finite-
 *      number type-clamps on apply already make the date/count fields injection-
 *      safe; the free-text fields are escaped here). There is no "trusted because
 *      machine-set" render slot for a foreign record.
 *
 * SAFETY POSTURE (§14): MECHANISM, dark by default. Nothing here blocks a
 * user-initiated action. The local UUID `id`, the local `channelIndex`/`nameIndex`,
 * and any local-only bookkeeping are NEVER part of the replicated schema and are
 * stripped from every emitted projection (REQ-M4 disclosure minimization).
 */
import type { RelationshipRecord, 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.relationships.enabled`). Equal to the advert flag key
 *  `stateSyncReceive['relationships']`. */
export declare const RELATIONSHIP_STORE_KEY = "relationships";
/** 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 (§4 callout). */
export declare const RELATIONSHIP_RECORD_KIND = "relationship-record";
/**
 * Relationships are HIGH-impact (security spec §5 REQ-M9 / parent §WS2): a
 * concurrent divergent VALUE edit to the SAME 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 people could fuse
 * two distinct humans). The CONSUMER read path injects BOTH variants — see
 * mergeUnionToRelationships.
 */
export declare const RELATIONSHIP_IMPACT_TIER: ImpactTier;
/** Mirrors RelationshipManager.MAX_NOTES_LENGTH. */
export declare const MAX_NOTES_LENGTH = 10000;
/** Mirrors RelationshipManager.MAX_CHANNELS. */
export declare const MAX_CHANNELS = 50;
/** Themes cap (RelationshipManager keeps themes ≤ 20). */
export declare const MAX_THEMES = 20;
/** Recent-interactions cap fallback (config `maxRecentInteractions`; the projection
 *  clamps to this hard ceiling so a hostile peer can't inflate the count). */
export declare const MAX_RECENT_INTERACTIONS = 50;
/** Per-free-text-string clamp for name / arcSummary / communicationStyle / category /
 *  each tag / each interaction summary / each channel identifier. */
export declare const MAX_FREETEXT_LENGTH = 2000;
/** A channel `type` is a short slug. */
export declare const MAX_CHANNEL_TYPE_LENGTH = 64;
/** Tags cap. */
export declare const MAX_TAGS = 50;
/**
 * Per-kind replication bounds (§8 / REQ-D1). A PII store is NEVER `rotateKeep: 0`
 * (rotate but never delete) — unbounded PII history is a compliance defect. The
 * relationship store is chatty (recordInteraction fires on every message), so the
 * rate cap COALESCES (latest state per recordKey per interval, REQ-M12). The
 * per-kind maxFileBytes here is the journal-level fallback; the aggregate replicated
 * PII byte ceiling (REQ-D2, 64 MiB) caps the cross-(peer,kind) footprint.
 */
export declare const RELATIONSHIP_RECORD_BOUNDS: ReplicatedKindBounds;
/**
 * Per-entry size cap RAISED to 64KB for this PII kind (REQ-M3, gap #10). The
 * default APPLIER_MAX_ENTRY_BYTES = 8KB is SMALLER than a fat relationship
 * (MAX_NOTES_LENGTH=10_000 alone, PLUS bounded recentInteractions + MAX_CHANNELS),
 * 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:
 *   name(2k) + notes(10k) + 20 themes×2k(40k) is already the dominant term, but
 *   themes are SHORT topic slugs in practice and EACH free-text is clamped to 2k —
 *   the realistic max (notes 10k + 50 channels×(64+2000) ≈ 103k 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 (not silent-
 *   truncate, not suspect-wedge). See assertProjectionUnderCap.
 */
export declare const RELATIONSHIP_MAX_ENTRY_BYTES: number;
/**
 * The store-specific field names the `relationship-record` VALUE schema OWNS (the
 * unknown-field counter's allowlist). The local UUID `id` is DELIBERATELY ABSENT
 * — it is per-machine and never replicated (REQ-D17 keys on the identity surface,
 * not the id). `recordKey`/`hlc`/`op`/`origin`/`observed` are reserved envelope
 * fields, never store fields.
 */
export declare const RELATIONSHIP_STORE_KNOWN_FIELDS: ReadonlyArray<string>;
/** The tombstone's store-owned fields beyond the reserved envelope set (REQ-D6).
 *  `deletedAt` is the only store field a delete carries. */
export declare const RELATIONSHIP_TOMBSTONE_KNOWN_FIELDS: ReadonlyArray<string>;
/** Is `v` a valid ISO-8601 date string (and ONLY a date — no trailing markup)? A
 *  string that Date.parse rejects, or that round-trips to a different string, is not
 *  a clean ISO date and is rejected. */
export declare function isIso8601(v: unknown): v is string;
/**
 * The `relationship-record` store schema (§4 / REQ-M3) — a DISCRIMINATED UNION on
 * `op` (gap #8). Strict typed validation on top of the envelope: reject free text
 * beyond the known fields, TYPE-CLAMP every known field, validate the date/number
 * fields 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 `op` discriminator: 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 (REQ-D6).
 */
export declare const relationshipRecordStoreSchema: StoreFieldSchema;
/**
 * Normalize one channel into its stable uid form `type:identifier` (lowercased
 * type, trimmed identifier). This is the SAME `${type}:${identifier}` key
 * RelationshipManager.channelIndex uses to collide a person across platforms.
 */
export declare function channelUid(channel: UserChannel): string;
/**
 * Derive the cross-machine-stable recordKey for a relationship (REQ-D17). A person
 * is "the same" across machines by their CHANNEL SET (mirroring resolveByChannel),
 * NOT by the per-machine `randomUUID()` id — VM-A and VM-B mint different UUIDs for
 * the same human, so a UUID-keyed record could never collide them.
 *
 * 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 relationship 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 people share a key ONLY if they share the EXACT
 * same full channel set — which is the manager's own definition of "the same
 * person" (resolveByChannel returns one record per channel-uid). SPLIT-IDENTITY
 * SAFETY: the same person derives the SAME key on both machines IFF both hold the
 * same channel set; when their channel sets differ (one machine learned an extra
 * channel) the keys differ — which is correct (they are not yet provably the same
 * person on the machine missing the channel), and the union/erasure intersection
 * logic (REQ-D17) reconciles them by channel-uid overlap, never by key equality.
 */
export declare function deriveRelationshipRecordKey(channels: ReadonlyArray<UserChannel>): string | null;
/** The `data` object a `relationship-record` journal entry carries. */
export type RelationshipRecordData = Record<string, unknown>;
/** Input to buildRelationshipRecordData: 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 — §7.2). */
export interface BuildRelationshipRecordInput {
    record: RelationshipRecord;
    hlc: HlcTimestamp;
    origin: string;
    /** The HLC already merged for THIS recordKey before writing, or absent (§7.2). */
    observed?: HlcTimestamp;
}
/** The named error a record-over-cap surfaces (REQ-M3): not silent-truncate, not
 *  suspect-wedge. */
export declare class RelationshipRecordTooLargeError extends Error {
    readonly recordKey: string;
    readonly bytes: number;
    constructor(recordKey: string, bytes: number);
}
/**
 * Build the disclosure-minimized `relationship-record` envelope `data` for an
 * `op:'put'` (REQ-M4). Emits ONLY the enumerated resolution + merge-relevant
 * fields — NEVER the raw on-disk blob, NEVER the local UUID `id`. recordKey = the
 * derived channel-set identity surface (REQ-D17).
 *
 * Returns null when the record has NO channels (no cross-machine identity surface ⇒
 * not replicable — the caller skips emission). Throws RelationshipRecordTooLargeError
 * when the projection STILL exceeds the 64KB per-entry cap (a record that big is a
 * NAMED, surfaced rejection, REQ-M3 gap #10 — never silent-truncate).
 */
export declare function buildRelationshipRecordData(input: BuildRelationshipRecordInput): RelationshipRecordData | null;
/** Throw RelationshipRecordTooLargeError if the projected data serializes over the
 *  per-entry cap (REQ-M3). 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: RelationshipRecordData): void;
/** Input to buildRelationshipTombstoneData: the channel set of the deleted person
 *  (to derive the recordKey identity surface), the freshly-ticked hlc, the origin,
 *  and the deletedAt timestamp. */
export interface BuildRelationshipTombstoneInput {
    channels: ReadonlyArray<UserChannel>;
    hlc: HlcTimestamp;
    origin: string;
    deletedAt: string;
    observed?: HlcTimestamp;
}
/**
 * Build an `op:'delete'` TOMBSTONE `data` for a relationship erasure (§4.2 REQ-D4).
 * 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
 * UUIDs differ. Returns null when the person has no channels (no identity surface to
 * tombstone). The exact field set is `{ recordKey, op:'delete', hlc, origin,
 * deletedAt }` (the REQ-D6 tombstone schema branch).
 */
export declare function buildRelationshipTombstoneData(input: BuildRelationshipTombstoneInput): RelationshipRecordData | null;
/** A merged relationship view entry: the projected record fields PLUS its origin
 *  machine id (so a foreign record is rendered inside the untrusted-data envelope).
 *  This is READ-ONLY — it is NEVER written back into the local store (REQ-M7). */
export interface MergedRelationshipView {
    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). */
    conflicted: boolean;
}
/**
 * Collapse a `Map<recordKey, UnionResult>` (from
 * ReplicatedStoreReader.readAll('relationships')) into the merged relationship view.
 * HIGH-impact contract (REQ-M9):
 *   - A resolved single value ⇒ that one view entry.
 *   - An OPEN concurrent conflict ⇒ BOTH (all) `put` variants as separate entries
 *     (append-both — the read NEVER suppresses a usable view waiting on operator
 *     resolution). A `delete` variant contributes nothing to display.
 *   - A delete-resolved key (every origin's latest is a tombstone) ⇒ nothing.
 * The read is READ-ONLY: a replicated record NEVER clobbers a divergent local
 * record (REQ-M7) — the local store files are never written here.
 */
export declare function mergeUnionToRelationships(union: Map<string, UnionResult>): MergedRelationshipView[];
/**
 * Render a FOREIGN (replicated) relationship record into a session-context block,
 * wrapped in an explicit `<replicated-untrusted-data origin="…">` envelope (§2.3)
 * so the session model treats it as a PEER'S CLAIM to re-ground against, never a
 * directive. EVERY rendered field is escaped — there is no "trusted because machine-
 * set" slot. (The date/count fields were already ISO-8601/finite-number type-clamped
 * on apply, so they cannot carry markup; we escape defensively regardless.)
 *
 * This is the foreign-record analog of RelationshipManager.getContextForPerson — a
 * caller asking "what do my OTHER machines know about this person" gets this block,
 * NEVER the local-authoritative context. A null `data.name` (a malformed view) yields
 * null.
 */
export declare function renderForeignRelationshipContext(view: MergedRelationshipView): string | null;
/**
 * Build an OriginRecord for the OWN relationship store (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 lastInteraction (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 UUID `id`
 * is NEVER carried into the replicated namespace (REQ-M4 / REQ-D17).
 */
export declare function relationshipToOriginRecord(record: RelationshipRecord, origin: string): OriginRecord | null;
/** The ReplicatedKindRegistry registration for the `relationship-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 RELATIONSHIP_KIND_REGISTRATION: {
    readonly kind: "relationship-record";
    readonly store: "relationships";
    readonly schema: StoreFieldSchema;
};
/** Convenience: the store's contributing journal kinds (for rollback-unmerge's
 *  kindsForStore('relationships') wiring). */
export declare function relationshipContributingKinds(): string[];
/** The store's impact tier resolver, for ReplicatedStoreReader.tierOf. Returns HIGH
 *  for the `relationships` store (and HIGH for any unknown store — the conservative
 *  append-both-and-flag direction, never a silent clobber). */
export declare function relationshipTierOf(_store: string): ImpactTier;
/** Re-export the envelope type for callers building/applying relationship-record envelopes. */
export type { ReplicatedEnvelope };
//# sourceMappingURL=RelationshipsReplicatedStore.d.ts.map