/**
 * Search-scope honesty — the note that turns "nothing found" from an assertion
 * about the WORLD into a statement about where we actually looked.
 *
 * Why this module exists (incident 2026-08-07). Two agents lost a working day
 * to the same silent failure: a message was written into a shared room, and an
 * unscoped `memory_list_recent` answered "Nothing new since your watermark."
 * The write was fine, the index was fine, and the read was fine — an UNSCOPED
 * read simply never covers rooms.
 *
 * That is not a filter oversight; it is how the store is built. A room is a
 * separate storage tenant (core ADR-019): with no `domain`, the request runs
 * against the caller's OWN org, and room atoms live in the room's org. So the
 * unscoped feed cannot see them and never could.
 *
 * The defect is therefore not the scoping — it is the WORDING. "Nothing new"
 * is a claim about absence, and absence is only knowable within the scope you
 * searched. An empty result from an unscoped read must therefore say what it did
 * not cover: which rooms went unsearched and how to read them, that every room
 * it has is archived and reachable by no path, or that it could not find out.
 * Silence is correct in exactly one case — the caller has no rooms, so nothing
 * was missed — and that case is a distinct state below rather than the falsy
 * spelling of the other three.
 *
 * Kept out of index.ts so the copy can be unit-tested without booting the
 * stdio transport, matching render.ts and teaching.ts.
 */
/**
 * How to NAME the scope inside a sentence, so the sentence is true on its own
 * rather than corrected by a paragraph underneath it.
 *
 * "Nothing new since your watermark." was left untouched by the first two
 * passes of this release — the very sentence this module's header names as the
 * lie that cost two agents a day. A note was appended to it instead, so the
 * assembled answer read as two voices: one asserting you were caught up, the
 * next saying that assertion was meaningless. The sibling branch in memory_read
 * had it right all along by naming its filters inside the sentence (review,
 * 2026-08-08).
 *
 * The name inside that sentence then went through safeInline, which made the
 * sentence false in the case that matters most: a read on `" engineering"` — the
 * padded second store this release is about — answered `Nothing in
 * "engineering"`, naming a DIFFERENT store, and a whitespace-only scope printed
 * as `""`, i.e. as no scope at all. It is now the exact literal (src/names.ts),
 * and when a name cannot be reproduced the sentence names nothing rather than
 * naming something else.
 *
 * A room address stays unnamed ("that room") — unchanged, and deliberately: the
 * address is already in the caller's hand, and this sentence has no need to
 * print another principal's display string. Where a room's NAME is printed at
 * all ({@link liveRoomsNote} below, the room tools in index.ts) it goes through
 * `roomNamePhrase` (src/names.ts) — exactly, or declared unprintable — never
 * through the lossy sanitiser, which renamed "проект" to "(unnamed room)".
 *
 * Lives here rather than in index.ts so the sentence can be unit-tested: this is
 * the module about saying where we looked, and index.ts opens a stdio transport
 * on import.
 */
export declare function scopeLabel(searched: string | undefined): string;
/**
 * "Nothing new since your watermark" is also what you get for a watermark in
 * the FUTURE — a mixed-up timezone or a bad relative-date calculation reads
 * exactly like a clean bill of health (dogfood, 2026-08-07). If the caller's
 * `since` is ahead of now, say so and show the current time; that is the whole
 * diagnosis, and the caller cannot reach it from "nothing new".
 *
 * The explaining clause claims a fact about TIME, never about any store. It
 * used to read "nothing has been written after it YET" — an absence claim over
 * EVERY store, printed directly above the scope disclosure admitting a room
 * went unsearched, and premised on an admittedly-approximate client clock
 * (review, 2026-08-08). The window is empty because it asks for entries newer
 * than a moment that has not happened; what any store holds is not this note's
 * to assert.
 *
 * `nowMs` is injected so the note is testable without freezing the clock.
 */
export declare function futureSinceNote(since: string | undefined, nowMs: number): string;
/**
 * The disclosure for the {@link NamedScope} arm `no-such-domain`: the caller
 * scoped a read to a domain that is not in `/memory/stats.domains`.
 *
 * WHAT IT DOES, which is not what it was called. Until 0.8.1 this was
 * `nearestDomainNote`, and the name outlived the behaviour twice over: the
 * "nearest"/prefix guess it was named for was deleted as unsound (see the body),
 * and even before that it was never a nearest-neighbour search. What remains is
 * two branches, and neither is a proximity search:
 *
 *   1. an EXACT case-insensitive twin, when one exists in the known list and
 *      both names can be printed reproducibly — the casing slip that silently
 *      forks a namespace, which is the one miss a reader can act on
 *      (dogfood, 2026-08-07);
 *   2. otherwise a NAME-FREE statement that no store OF THE CALLER'S OWN
 *      carries that exact name — bounded to its evidence: `/memory/stats.domains`
 *      covers the caller's own org bucket only, so rooms and other principals'
 *      stores are invisible to it and the sentence may not claim them (review,
 *      2026-08-08; the destructive sibling in index.ts already said "in your
 *      own store"). It is always safe to say and must never go quiet — silence
 *      here is byte-identical to "the store is there, your query merely
 *      missed".
 *
 * Deliberately conservative: only case differs. A whitespace or zero-width twin
 * (`" engineering"` beside `"engineering"`) is NOT diagnosed here — extending the
 * rule is a behaviour decision, and `memory_stats` closes that loop by printing
 * both names exactly. Recorded in CHANGELOG's "Known and NOT fixed here".
 */
export declare function noSuchDomainNote(domain: string, knownDomains: readonly string[]): string;
/** The subset of a room record anything here reads. Every field is normalised
 *  to a string-or-absent by {@link asRoom} before it is stored. */
export interface RoomSummary {
    room_id?: string;
    name?: string;
    address?: string;
    role?: string;
    scope?: string;
    archived?: boolean;
}
/**
 * Why a probe could not answer. Both are "we do not know", and they are kept
 * apart because the reader's next move differs: a fetch failure is worth
 * retrying, a shape this client cannot read is not.
 */
export type UncheckedReason = "fetch-failed" | "unrecognised-shape";
/**
 * What we know about the shared rooms an UNSCOPED read did not cover.
 *
 * `note` is the disclosure such a read must append, and it is present on
 * exactly the states that have something to disclose. Other consumers of the
 * same payload (memory_list_rooms) read the facts and ignore it.
 */
export type RoomScope = {
    /** The probe answered, and the caller has no rooms at all. Nothing was
     *  missed, so there is nothing to disclose — hence no `note` field. */
    readonly state: "none";
} | {
    /** At least one room can still be read by passing its address. */
    readonly state: "live";
    /** Every room in the list, live and archived — for memory_list_rooms. */
    readonly rooms: readonly RoomSummary[];
    readonly live: readonly RoomSummary[];
    readonly archived: number;
    readonly note: string;
} | {
    /** Rooms exist and NONE of them can be read: every one is archived. */
    readonly state: "archived-only";
    readonly rooms: readonly RoomSummary[];
    readonly archived: number;
    readonly note: string;
} | {
    /** We could not look. Not evidence in either direction. */
    readonly state: "unknown";
    readonly reason: UncheckedReason;
    readonly note: string;
};
/** What we know about the ONE store a scoped read named. */
export type NamedScope = {
    /** The scope exists and we are in it, so an empty result is a real miss. */
    readonly state: "present";
    readonly name: string;
} | {
    /** No store with that byte-exact name in the caller's own bucket. */
    readonly state: "no-such-domain";
    readonly name: string;
    readonly note: string;
} | {
    /** That address is not among the rooms this client can see. */
    readonly state: "no-such-room";
    readonly name: string;
    readonly note: string;
} | {
    /** We could not look — so nothing may be concluded about the name. */
    readonly state: "unchecked";
    readonly name: string;
    readonly probed: "domains" | "rooms";
    readonly reason: UncheckedReason;
    readonly note: string;
};
/**
 * Where a read looked, and what we know about what it therefore did not cover.
 *
 * The two halves are ONE value on purpose. A scoped read must not probe the room
 * list (a room address is checked against the rooms, a domain against the
 * domains — CodeRabbit #65), and an unscoped read must always probe it; as two
 * parameters those invariants were merely unwritten, and both were broken at
 * different points in this release. Here a `named` scope has no room knowledge
 * to be wrong about, and an `own-domains` scope cannot exist without it.
 */
export type ReadScope = {
    readonly kind: "named";
    readonly named: NamedScope;
} | {
    readonly kind: "own-domains";
    readonly rooms: RoomScope;
};
/** The disclosure for either kind of scope — "" only where the type says there
 *  is genuinely nothing to disclose. */
export declare function readScopeNote(scope: ReadScope): string;
/**
 * Classify a `/memory/rooms` body. Total: every payload maps to exactly one
 * state, and a payload that is not a list of rooms maps to `unknown` — never to
 * `none`, which is the substitution that turned a contract violation into "you
 * have no rooms".
 */
export declare function classifyRooms(payload: unknown, sanitize: (s: string | undefined | null) => string): RoomScope;
/** Fetch the caller's rooms and classify them. Never throws — a failure is a
 *  state, not an exception, because every caller has to render it. */
export declare function probeRoomScope(fetchRooms: () => Promise<unknown>, sanitize: (s: string | undefined | null) => string): Promise<RoomScope>;
/**
 * Build the {@link ReadScope} for one read: where it looked, and what we know
 * about what it therefore did not cover. One GET per call — the room list for
 * an unscoped read or an `xroom:` address, `/memory/stats` for a plain domain
 * — and callers reach it on zero-result paths only. That is MORE probing than
 * 0.8.0 did, not "exactly as before" (an earlier version of this comment):
 * 0.8.0 made no probe at all on a domain-scoped, room-scoped or filtered read
 * and none anywhere in the feed; only the plain unscoped read probed stats.
 * The added reads are disclosed in the 0.8.1 CHANGELOG entry, rate cost
 * included.
 *
 * `searched` must be the value that went over the wire — `searchedScope(domain)`
 * — so that the diagnosis and the request can never describe different stores.
 * A falsy value takes the own-domains path because that is what the request did:
 * core filters on `domain is not None`, and `searchedScope` drops `""`.
 */
export declare function probeReadScope(searched: string | undefined, fetchStats: () => Promise<unknown>, fetchRooms: () => Promise<unknown>, sanitize: (s: string | undefined | null) => string): Promise<ReadScope>;
