/**
 * artifacts/scopePath — the ONE owner of "a scope becomes path segments".
 *
 * Every durable adapter partitions its storage by the scope tuple, and every
 * one of them takes CALLER STRINGS (`tenant`, `principal`, `conversationId`)
 * and turns them into names inside a hierarchy: directories on a filesystem,
 * key prefixes in an object store. That is a traversal surface, and it must
 * be closed the same way in all of them — one encoding, one place, so a
 * hostile tenant cannot be inert on disk and a navigation hop in a bucket.
 *
 * The law, unchanged from the directory adapter that first stated it
 * (9.21.0), now shared rather than copied:
 *
 *   • encode from the FIELD, never from a joined namespace — a tenant
 *     containing `/` must not become two segments;
 *   • `encodeURIComponent` leaves `.` alone, so dots are encoded too:
 *     a tenant of literally `'..'` would otherwise survive as a real
 *     parent-directory hop (`%2E%2E` is a NAME);
 *   • an empty or absent field collapses to `_`, mirroring
 *     `identityNamespace`'s stable layout, so "no tenant" is one shape and
 *     not two — and a field whose VALUE is `_` is escaped away from that
 *     marker, so absence and the tenant named `_` are not one directory.
 *
 * After this, `..`, `/` and `\` arrive as data and land as literals. The
 * adapters assert `isArtifactRef` on the last segment anyway, because
 * "cannot happen" is a claim, not a defence.
 *
 * The encoding is INJECTIVE, per field and therefore per scope: two different
 * scopes never address one directory or one key prefix. `encodeURIComponent`
 * supplies that for every character (it escapes `%`, its own introducer, so
 * an already-escaped-looking value cannot land on the value that produced
 * it); {@link distinctFromAbsent} supplies it for the absence marker, which
 * `encodeURIComponent` passes through untouched because `_` is unreserved.
 *
 * ── Injective is not enough: the NAME has to be case-unambiguous too ─────────
 * Two different strings are two different names only where the namer agrees.
 * macOS/APFS and Windows/NTFS are **case-insensitive by default**, so a scope
 * encoding that is injective as a string can still put two tenants in one
 * directory — which is what this file did until 9.44.0. A tenant of `Acme` and
 * a tenant of `acme` encoded to two distinct segments and landed in one folder,
 * and since the ref is a content address that both scopes then resolve, the
 * neighbour could read, LIST and delete. Reproduced on a stock Mac; the store
 * conformance battery passed all 106 cases in the same process, because it had
 * pairs for separators, absence markers and pre-escaped values, and no pair
 * that differed only in case.
 *
 * So an uppercase ASCII letter is escaped BEFORE `encodeURIComponent`, leaving
 * an output whose every letter is lowercase except the hex digits inside
 * `%XX` escapes, which are always uppercase and can never be preceded by a
 * lone `%` in any other way. No two distinct outputs can differ by case alone,
 * so folding them is not something a filesystem is able to do.
 *
 * The escape is `~`, doubled when it appears in the input — the same
 * escape-the-escape shape `identityNamespace` uses (9.40.0) and for the same
 * reason: it makes a decoder a left inverse, which is what "injective" means
 * when you have to prove it rather than assert it. `~` survives
 * `encodeURIComponent` untouched because it is unreserved.
 *
 * **What re-keys.** Only a scope field containing an uppercase ASCII letter
 * moves. Every all-lowercase field — including one carrying `/`, spaces, dots
 * or non-ASCII — encodes to exactly the bytes it did before, so the ordinary
 * deployment migrates nothing. A field that DID contain an uppercase letter was
 * sharing a directory with its case variants on two of the three major
 * platforms, which is the condition being fixed.
 */
import type { ArtifactScope } from './types.js';
/** One RAW tuple field → one inert path/key segment. */
export declare function scopeSegment(raw: string | undefined): string;
/** The three encoded segments of a scope, in the fixed tenant/principal/
 *  conversation order every adapter lays out. */
export declare function scopeSegments(scope: ArtifactScope): readonly [string, string, string];
/**
 * The object-key prefix for one scope: `[<root>/]<tenant>/<principal>/<conversation>/`.
 *
 * Trailing slash included on purpose — it is what makes a prefix listing
 * exact. Without it, scope `c` would also list scope `c2`'s keys, which is
 * the isolation bug this whole module exists to prevent.
 */
export declare function scopeKeyPrefix(scope: ArtifactScope, root?: string): string;
/**
 * Validate an operator-supplied key ROOT at construction (the prefix a bucket
 * is shared under). It is not caller data — an operator writes it — so it is
 * not encoded; it is CHECKED, and refused where it would be ambiguous.
 *
 * @param adapter the factory name, for the refusal's first word.
 * @param root    the raw option, or undefined.
 * @returns the normalized root (no leading/trailing `/`), or undefined.
 */
export declare function normalizeKeyRoot(adapter: string, root: string | undefined): string | undefined;
//# sourceMappingURL=scopePath.d.ts.map