import type { SessionTreeEntry } from "../harness/types.js";
/**
 * session-sync — the SHARED content digest for a session entry log.
 *
 * ## The gap this closes
 * `classifySyncRelationshipByIds` compares only the SET of entry ids. Entry ids are uuidv7, not content
 * addresses, so "same ids, different payload" is structurally ordinary — and the wire accepts whatever
 * entries a client sends. The measured consequences were: an `identical` 200 returned while the destination
 * still holds different text; a `fast_forward` classified "clean append, safe to apply" while it destroys
 * corrections on the destination without the 409 that promise depends on.
 *
 * ## Why RE-computing a canonical form on both sides is safe HERE (and is not, elsewhere)
 * There is a load-bearing ruling against exactly this pattern — design/80 r3, "绝不让两个运行时各算
 * canonical-JSON" — which is why `boundInputHash` is minted once and treated as opaque by every consumer,
 * and why `canonicalize` is not exported from the package index. Read at the source rather than through its
 * paraphrase in `canonical-json.ts`, that ruling rests on TWO legs, and only one of them still stands:
 *
 *  1. **There was no vetted canonical serializer.** The verbatim reasoning is "deps 只 typebox+MCP、core 无
 *     canonical/stable JSON serializer、JS JSON.stringify 非规范". The remedy prescribed in the same
 *     sentence was "ship 一个 vetted canonical serializer" — which shipped as `canonical-json.ts`. This leg
 *     described the state of the codebase at the time, and it no longer holds.
 *  2. **The blast radius of a false mismatch**: "false-mismatch 会 fail-closed 拒掉合法审批". A serializer
 *     divergence on the approval path DENIES a legitimate human decision. This leg stands, and it is the
 *     one that decides whether the ruling transfers to a new use.
 *
 * Here the blast radius is inverted, and deliberately so:
 *  - digests EQUAL   ⇒ short-circuit as `identical`;
 *  - digests DIFFER  ⇒ **do not short-circuit** — run the full sync path.
 *
 * So a serializer divergence between two core versions costs extra work (a sync that would have short-
 * circuited now runs), never a wrong answer and never a refusal. That asymmetry is the whole reason this
 * function may be exported while `canonicalize` itself stays internal. **Consumers must preserve it**: a
 * digest mismatch may never be turned into an error, a 409, or a refusal to sync — only into "compare
 * properly instead of trusting the ids".
 *
 * ## Versioning
 * `test/session-log-digest.test.ts` freezes the digest of a fixture that exercises every value shape the
 * serializer distinguishes. If a change moves a frozen value, the documented response is to ADD a row for a
 * new {@link SESSION_LOG_DIGEST_SCHEME} — never to edit a frozen value in place, because peers on different
 * core versions compare these strings to each other and the old row records what was on the wire.
 *
 * A scheme mismatch is reported as "not comparable", which — by the rule above — degrades to the full sync
 * path, exactly like a value mismatch.
 *
 * ## What is hashed, and why not the engine's canonical serializer
 * v1 hashed `canonicalize` (the approval-binding serializer). An falsification review of the shipped v1 found
 * that choice unsound for a WIRE digest, on four counts that all trace to the same root — that serializer is
 * built for a single runtime comparing a value to ITSELF, not for two runtimes comparing what they hold:
 *
 *  - It distinguishes `{a: undefined}` from `{}` by design. On a wire there is no such distinction, and core
 *    itself produces own-keys-set-to-undefined routinely (clearing a label; a custom entry with no data). So
 *    the SAME log digested different on an in-memory backend than after a JSON round-trip — measured end to
 *    end — and the short-circuit this whole mechanism exists for would simply never have fired.
 *  - Its number token has no terminator, so a numeric value can run into the following key: two genuinely
 *    different payloads produced one canonical form (demonstrated).
 *  - It collapses every host object to `o0:{}` (any two Dates, any two Maps) and everything past a depth
 *    limit to one sentinel — acceptable when the alternative is refusing an approval, not acceptable when the
 *    consequence is "these two logs are the same, skip the sync".
 *  - Lone surrogates became U+FFFD at the UTF-8 hashing step, so a payload that survived one byte round-trip
 *    on one peer collided with the original on the other.
 *
 * v2 hashes a **stable JSON serialization of the JSON-representable form** instead: each entry goes through
 * `JSON.stringify`/`parse` (so both peers digest the same thing the wire carries), then a key-sorted
 * serializer. JSON's own grammar is unambiguous — strings are quoted, numbers are delimited — so the
 * adhesion above cannot occur; `JSON.stringify` escapes lone surrogates as `\uXXXX`; depth is unlimited; and
 * a Date/Map has already become its wire form before hashing. It also means this module no longer shares a
 * serializer with the approval path at all, which removes the tension with that path's ruling at the root
 * rather than arguing about its blast radius.
 *
 * ## What is covered
 * Every field of every entry that survives to JSON, in the order given — id, parentId, type, timestamp and
 * the whole payload. Key ORDER within an entry is not (that is what the sort is for); entry order is (a
 * reordered log is a different log — one of the three measured failure shapes).
 */
export declare const SESSION_LOG_DIGEST_SCHEME = "sema-log-v3";
/** Digest of ONE entry (same scheme and rules as {@link sessionLogDigest}; useful for per-entry reporting). */
export declare function sessionEntryDigest(entry: SessionTreeEntry): string;
/**
 * Digest of an ORDERED entry log. Two logs with the same digest hold the same entries, in the same order,
 * with the same payloads and the same parent structure — as far as JSON can express them.
 *
 * Length-prefixing each entry's serialized form keeps concatenation unambiguous (without it, two adjacent
 * entries could be re-partitioned into a different pair with the same bytes). The domain tag (`log` vs
 * `entry`) keeps the two digest families from ever being equal by accident.
 */
export declare function sessionLogDigest(entries: readonly SessionTreeEntry[] | null | undefined): string;
/**
 * Are two digests comparable at all? False for a different scheme version, an absent value, or anything that
 * is not a well-formed digest string.
 *
 * Every rejection path returns `false` rather than throwing. That is load-bearing: this function sits on the
 * classification path, and the contract it serves says a digest problem must degrade to "do the full sync",
 * never to an error. v1 guarded only `undefined` — but JSON has no `undefined`, so "the peer sent no digest"
 * arrives as `null`, which threw. v1 also derived the scheme with `slice(0, indexOf(":sha256:"))`, and a
 * missing separator yields -1, so any string shaped like the scheme plus one character passed as a valid
 * digest of that scheme — two peers holding the same truncated non-digest would have compared equal.
 */
export declare function sessionLogDigestsComparable(a: string | null | undefined, b: string | null | undefined): boolean;
