/**
 * LearningsReplicatedStore — the THIRD concrete consumer of the HLC replicated-store
 * foundation (WS2.2) and the SECOND memory-family kind (after WS2.3 relationships).
 * It layers the `learning-record` replicated kind onto the generic substrate
 * (ReplicatedRecordEnvelope / UnionReader / ConflictStore / RollbackUnmerge /
 * ReplicationBudget / StoreSnapshot) so that a lesson the agent learned on machine
 * A is known on machine B — ONE learning registry, not one-per-machine.
 *
 * It is the literal analog of `RelationshipsReplicatedStore.ts` (the WS2.3 PII
 * reference consumer). A learning is a LESSON, not a person, so it is lower-PII than a
 * relationship — but its `description`/`source` CAN reference people, content, or
 * platforms, so it REUSES the WS2.3 PII machinery (type-clamp, disclosure-min
 * projection, tombstones, flag-coherence) rather than reinventing or downgrading it.
 * THIS IS PURE LOGIC. No fs, no Date directly, no network. It defines:
 *
 *   A. The `learning-record` store schema — a STRICT typed validator that
 *      TYPE-CLAMPS every known field: `source.discoveredAt` ISO-8601-only, `applied`
 *      a boolean, `tags[]`/free text length-clamped. 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 — `buildLearningRecordData` emits ONLY the
 *      enumerated merge-relevant fields, NEVER the raw on-disk blob and NEVER the
 *      local `LRN-NNN` id. `recordKey` is the cross-machine IDENTITY SURFACE, derived
 *      deterministically from the stable content (normalize(title) + normalize(category)
 *      + (source.contentId || source.discoveredAt)) — never the per-machine,
 *      sequentially-assigned `LRN-NNN` id (the cross-machine-UNSTABLE id, exactly the
 *      relationship-UUID trap WS2.3 solved with the channel-set key). The SAME lesson
 *      learned on two machines collapses to ONE record.
 *
 *   C. The TOMBSTONE builder — `buildLearningTombstoneData` emits an `op:'delete'`
 *      record `{ recordKey, op, hlc, origin, deletedAt }` so a removal/prune propagates
 *      as a positive signal across an offline-then-rejoining peer instead of a record
 *      absence. CRITICAL: the EvolutionManager prune-over-maxLearnings path MUST emit
 *      a tombstone per pruned learning, else a peer re-replicates the locally-pruned
 *      learning forever (resurrection).
 *
 *   D. The union-aware read — `mergeUnionToLearnings` collapses a
 *      `Map<recordKey, UnionResult>` into the merged learning view. Learnings are
 *      HIGH-impact at the REPLICATION layer (a concurrent divergent edit to the SAME
 *      recordKey goes through APPEND-BOTH-AND-FLAG — both versions surface, never a
 *      silent clobber). The CONSUMER READ path is ADVISORY: it injects BOTH variants
 *      of an open conflict as guidance — a learning is guidance, not authority — and
 *      NEVER blocks on an unresolved conflict. The read NEVER writes a foreign record
 *      into the local store (read-only union).
 *
 *   E. Foreign-record render safety — `renderForeignLearningContext` 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 (Echo, 2026-06-13 — recorded verbatim in the PR ELI16):
 *   1. recordKey = a content fingerprint, NEVER the local `LRN-NNN` id (cross-machine
 *      identity surface — see deriveLearningRecordKey).
 *   2. Impact tier = HIGH at the REPLICATION layer (append-both-and-flag), ADVISORY at
 *      the READ layer (both variants injected as hints, never blocking) — see
 *      mergeUnionToLearnings + LEARNING_IMPACT_TIER.
 *   3. `applied`/`appliedTo` are LOCAL-merge fields, replicated but last-writer-witness
 *      wins; a concurrent applied-vs-unapplied divergence rides the SAME append-both-
 *      and-flag path (NOT a special CRDT merge) — the single conflict path.
 *
 * SAFETY POSTURE: MECHANISM, dark by default. Nothing here blocks a user-initiated
 * action. The local `LRN-NNN` id is NEVER part of the replicated schema and is stripped
 * from every emitted projection (disclosure minimization).
 */
import type { LearningEntry, LearningSource } 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.learnings.enabled`). Equal to the advert flag key
 *  `stateSyncReceive['learnings']`. */
export declare const LEARNING_STORE_KEY = "learnings";
/** 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 LEARNING_RECORD_KIND = "learning-record";
/**
 * Learnings are HIGH-impact at the REPLICATION layer (fork #2): a concurrent
 * divergent VALUE edit to the SAME recordKey from different origins goes through
 * APPEND-BOTH-AND-FLAG — both versions preserved, ONE deduped conflict, never a
 * silent overwrite. The READ path (mergeUnionToLearnings) is ADVISORY — both variants
 * surface as guidance hints, the read never blocks on an open conflict — a learning is
 * guidance, not authority. Operator resolution via POST /state/resolve-conflict is
 * OPTIONAL cleanup that collapses the flag, never a gate on the hint.
 */
export declare const LEARNING_IMPACT_TIER: ImpactTier;
/** A learning `description` can be long (a full lesson write-up). Clamp on receive. */
export declare const MAX_DESCRIPTION_LENGTH = 20000;
/** Per-free-text-string clamp for title / category / evolutionRelevance / appliedTo /
 *  each tag / each source sub-field. */
export declare const MAX_FREETEXT_LENGTH = 2000;
/** A category is a short slug. */
export declare const MAX_CATEGORY_LENGTH = 128;
/** Tags cap (mirrors a reasonable per-learning tag count). */
export declare const MAX_TAGS = 50;
/**
 * Per-kind replication bounds. The learnings store is FEW + bounded (the
 * EvolutionManager prunes to maxLearnings=500), so the per-store retention mirrors the
 * pref-record store (a small window with a few archives). NEVER `rotateKeep: 0`
 * (rotate-but-never-delete would be a compliance defect for any memory-family kind).
 * The rate cap COALESCES (latest state per recordKey per interval) so a churny
 * apply/markApplied loop does not flood the stream.
 */
export declare const LEARNING_RECORD_BOUNDS: ReplicatedKindBounds;
/**
 * Per-entry size cap RAISED to 64KB for this kind. The default
 * APPLIER_MAX_ENTRY_BYTES = 8KB is SMALLER than a fat learning (a 20K description
 * alone exceeds it), so under it the longest learnings would never replicate AND
 * would wedge the stream. 64KB is provably above the disclosure-minimized
 * projection's maximum: description(20k) + 50 tags×2k(100k) is the dominant term, but
 * tags are SHORT slugs in practice and EACH free-text is clamped to 2k — 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 LEARNING_MAX_ENTRY_BYTES: number;
/**
 * The store-specific field names the `learning-record` VALUE schema OWNS (the
 * unknown-field counter's allowlist). The local `LRN-NNN` id is DELIBERATELY ABSENT
 * — it is per-machine + sequential and never replicated (the recordKey keys on the
 * content fingerprint, not the id). `recordKey`/`hlc`/`op`/`origin`/`observed` are
 * reserved envelope fields, never store fields.
 */
export declare const LEARNING_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 LEARNING_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 `learning-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 (discoveredAt ISO-8601, applied boolean, tags string[],
 * description/free text length-clamped) 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 learningRecordStoreSchema: StoreFieldSchema;
/** Normalize a string for the content fingerprint: trim + lowercase + collapse
 *  internal whitespace, so trivial formatting differences across machines do not
 *  split the same lesson into two records. */
export declare function normalizeForKey(v: string): string;
/**
 * Derive the cross-machine-stable recordKey for a learning (fork #1). A lesson is "the
 * same" across machines by its CONTENT, NOT by the per-machine, sequentially-assigned
 * `LRN-NNN` id — VM-A and VM-B mint different LRN ids for the same lesson, 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:
 *   sha256(normalize(title) + '\x1f' + normalize(category) + '\x1f' + (source.contentId || source.discoveredAt))
 * hex-truncated to 32 chars (the same shape UnionReader.conflictId uses). The `\x1f`
 * (unit separator) is an un-typeable delimiter so two lessons cannot collide by
 * straddling the field boundary (e.g. title "a" + category "b" vs title "a b").
 *
 * `source.contentId` (a post/thread id) is the stronger disambiguator when present —
 * two distinct lessons from different content never collide even with the same
 * title+category; when ABSENT we fall back to `source.discoveredAt`. Returns null when
 * title OR category is empty (a degenerate record with no stable identity surface — the
 * caller skips emission; it can never collide a stranger by an empty key).
 *
 * COLLISION SAFETY: two DIFFERENT lessons share a key ONLY if they share the EXACT same
 * normalized title AND category AND (contentId || discoveredAt) — which IS the
 * definition of "the same lesson". SPLIT-IDENTITY SAFETY: the same lesson derives the
 * SAME key on both machines IFF both hold the same title/category/content anchor; the
 * normalization absorbs trivial formatting drift.
 */
export declare function deriveLearningRecordKey(title: string, category: string, source: LearningSource): string | null;
/** The `data` object a `learning-record` journal entry carries. */
export type LearningRecordData = Record<string, unknown>;
/** Input to buildLearningRecordData: 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 BuildLearningRecordInput {
    record: LearningEntry;
    hlc: HlcTimestamp;
    origin: string;
    observed?: HlcTimestamp;
}
/** The named error a record-over-cap surfaces: not silent-truncate, not suspect-wedge. */
export declare class LearningRecordTooLargeError extends Error {
    readonly recordKey: string;
    readonly bytes: number;
    constructor(recordKey: string, bytes: number);
}
/**
 * Build the disclosure-minimized `learning-record` envelope `data` for an `op:'put'`.
 * Emits ONLY the enumerated projection — NEVER the raw on-disk blob, NEVER the local
 * `LRN-NNN` id. recordKey = the derived content-fingerprint identity surface (fork #1).
 *
 * Returns null when the record has no stable identity surface (empty title/category ⇒
 * deriveLearningRecordKey null — the caller skips emission). Throws
 * LearningRecordTooLargeError when the projection STILL exceeds the 64KB per-entry cap
 * (a NAMED, surfaced rejection — never silent-truncate).
 */
export declare function buildLearningRecordData(input: BuildLearningRecordInput): LearningRecordData | null;
/** Throw LearningRecordTooLargeError 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: LearningRecordData): void;
/** Input to buildLearningTombstoneData: the title/category/source of the deleted
 *  learning (to derive the recordKey identity surface), the freshly-ticked hlc, the
 *  origin, and the deletedAt timestamp. */
export interface BuildLearningTombstoneInput {
    title: string;
    category: string;
    source: LearningSource;
    hlc: HlcTimestamp;
    origin: string;
    deletedAt: string;
    observed?: HlcTimestamp;
}
/**
 * Build an `op:'delete'` TOMBSTONE `data` for a learning removal/prune. recordKey = the
 * SAME content-fingerprint identity surface the value records key on, so the tombstone
 * reaches the same lesson's record on every machine even though the local LRN ids
 * differ. Returns null when title/category are empty (no identity surface to tombstone).
 *
 * CRITICAL (fork-adjacent): the EvolutionManager prune-over-maxLearnings path MUST call
 * this for each pruned learning, else a peer re-replicates the locally-pruned learning
 * forever (resurrection). The delete-resurrection guard lives in the merge (a later
 * `delete` hlc wins over an earlier `put`).
 */
export declare function buildLearningTombstoneData(input: BuildLearningTombstoneInput): LearningRecordData | null;
/** A merged learning 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 MergedLearningView {
    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 as advisory hints; the read NEVER suppresses a usable
     *  view AND NEVER blocks on the unresolved conflict). */
    conflicted: boolean;
}
/**
 * Collapse a `Map<recordKey, UnionResult>` into the merged learning 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 guidance; the read NEVER suppresses a
 *     usable view AND NEVER BLOCKS waiting on operator resolution — a learning is
 *     guidance, 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 mergeUnionToLearnings(union: Map<string, UnionResult>): MergedLearningView[];
/**
 * Render a FOREIGN (replicated) learning 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 learning to re-ground against, never a directive. EVERY rendered
 * field is escaped — there is no "trusted because machine-set" slot. A null `data.title`
 * (a malformed view) yields null.
 */
export declare function renderForeignLearningContext(view: MergedLearningView): string | null;
/**
 * Build an OriginRecord for the OWN learning store (the single-origin materialization
 * the union reader merges against peer replicas). recordKey = derived content-
 * fingerprint identity surface; the envelope carries a SYNTHETIC own-origin HLC stamp
 * derived deterministically from the source.discoveredAt (physical) so the own record
 * has a well-formed, stable position relative to peer records. Returns null for a
 * degenerate record (no identity surface). The local `LRN-NNN` id is NEVER carried into
 * the replicated namespace.
 */
export declare function learningToOriginRecord(record: LearningEntry, origin: string): OriginRecord | null;
/** The ReplicatedKindRegistry registration for the `learning-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 LEARNING_KIND_REGISTRATION: {
    readonly kind: "learning-record";
    readonly store: "learnings";
    readonly schema: StoreFieldSchema;
};
/** Convenience: the store's contributing journal kinds (for rollback-unmerge's
 *  kindsForStore('learnings') wiring). */
export declare function learningContributingKinds(): string[];
/** The store's impact tier resolver, for ReplicatedStoreReader.tierOf. Returns HIGH for
 *  the `learnings` store (and HIGH for any unknown store — the conservative
 *  append-both-and-flag direction, never a silent clobber). */
export declare function learningTierOf(_store: string): ImpactTier;
/** Re-export the envelope type for callers building/applying learning-record envelopes. */
export type { ReplicatedEnvelope };
//# sourceMappingURL=LearningsReplicatedStore.d.ts.map