/**
 * firestoreSessions — conversations in Firestore, so a fleet shares them and
 * nobody runs a database.
 *
 * The ladder this rung sits on is already in the package. `memorySessions()`
 * loses everything on restart and says so. `sqliteSessions()` survives a restart
 * on ONE machine and says so. `agentEngineSessions()` is a fleet store, but only
 * for people who already own a Vertex reasoning engine. Firestore is the row for
 * everyone else on this column: a serverless document database with no instance
 * to size, no connection pool to tune, and a free tier — the plainest "many
 * containers, one conversation" answer Google has.
 *
 * ── The shape, said plainly ─────────────────────────────────────────────────
 * One collection. One document per session. Seven fields:
 *
 *   sessionId · format · savedAt · envelope · owner · messageCount · expiresAt
 *
 * That is the SQLite table, moved. It is deliberately the same shape, because
 * the two stores implement the same port under the same laws, and a reader who
 * has understood one should not have to learn a second model to audit the other.
 *
 * The envelope rides as a JSON **string**, not as a nested map, and that is a
 * decision rather than laziness. A `CheckpointEnvelope` carries arbitrary
 * conversation JSON: keys chosen by a model's tool call, values that may be
 * `undefined`, arrays inside arrays. Firestore refuses all three — a field name
 * may not contain a dot, a tilde, a star, a slash, a bracket or a backtick;
 * `undefined` throws unless the client was
 * built with `ignoreUndefinedProperties`, and a directly nested array is not a
 * representable value. Serialising once at the edge makes every one of those a
 * non-event, at the cost of not being able to query INSIDE a conversation —
 * which no caller of this port has ever asked to do.
 *
 * ── Why the document id is a hash ───────────────────────────────────────────
 * A `sessionId` in this library is OPAQUE. It may be a UUID, an upstream
 * gateway's correlation id, a path-shaped tenant key, or a unicode string a
 * person typed. Firestore document names have rules: no `/`, not `.` or `..`,
 * not matching `__.*__`, and at most 1500 bytes. A session id that broke any of
 * them would fail at the wire on the one turn it mattered — or worse, two ids
 * that differ only past a truncation point would silently become ONE
 * conversation.
 *
 * So the document name is `sha256(domain + NUL + sessionId)` in hex — 64
 * characters, always legal, injective for every input anybody will ever have.
 * The raw id is stored in the `sessionId` FIELD, so a listing can hand it back
 * and a console reader can still see whose document they are looking at.
 *
 * **This is not encryption, and it is important not to read it as any.** The
 * conversation itself is stored in the clear; the hash is an addressing scheme,
 * not a confidentiality control. Anyone who can read the collection can read
 * every conversation in it, and the `sessionId` field beside the hash spells out
 * the id the hash was made from. Two things it DOES cost an operator, stated
 * because they are discovered at the worst moment otherwise:
 *
 *   • you cannot look a session up in the Firestore console by typing its raw
 *     id — you have to query `sessionId == '…'`, or hash it yourself;
 *   • a document name carries no information a human can sort or scan by.
 *
 * Encryption at rest is Google's (always on, and configurable with CMEK).
 * Access control is IAM's. Neither is this adapter's, and neither is implied by
 * the hash.
 *
 * ── The composite index, and the error you get without it ───────────────────
 * `listByUser` runs one server-side query — an equality on `owner`, ordered by
 * `savedAt` descending, paged with a real Firestore cursor. Firestore's
 * automatic single-field indexes do NOT serve that shape: an equality filter on
 * one field ordered by another needs a COMPOSITE index, and until it exists the
 * query fails with gRPC status 9, `FAILED_PRECONDITION`.
 *
 * The index, exactly:
 *
 *   collection group : <your collection>   (default: agentfootprint_sessions)
 *   fields           : owner    Ascending
 *                      savedAt  Descending
 *                      __name__ Descending
 *
 * `__name__` is Firestore's document-name field. It is the tiebreaker this
 * adapter orders by explicitly (see {@link FirestoreSessions.listByUser}), and
 * an index's trailing `__name__` takes the direction of the last ordered field —
 * so a console-generated index for `owner ASC, savedAt DESC` is the right one.
 *
 *   gcloud firestore indexes composite create \
 *     --collection-group=agentfootprint_sessions \
 *     --field-config=field-path=owner,order=ascending \
 *     --field-config=field-path=savedAt,order=descending \
 *     --database='(default)'
 *
 * `--database` is spelled out rather than left to gcloud's default, and it must
 * match the `database` this store was built with. gcloud assumes `(default)` when
 * the flag is absent, so an operator on a NAMED database who follows a command
 * without it creates the index somewhere else and gets the identical failure
 * back, with nothing to suggest why.
 *
 * When the index is missing this adapter raises {@link FirestoreIndexMissingError},
 * which prints that same line with your collection and database already filled
 * in, and names those fields rather than restating the service's message — see
 * {@link firestoreFailure} for why no Google text is ever echoed here.
 *
 * ── Retention: a SECOND timestamp, not a converted one (9.42.0) ─────────────
 * A native Firestore TTL policy deletes a document when a field of type
 * **Timestamp** is in the past. `savedAt` is a NUMBER — epoch milliseconds —
 * so until this release an operator could not point a policy at this
 * collection at all without a library change. The fix is a second field,
 * `expiresAt`, written as a `Date` (the client stores one as a Timestamp) on
 * every persist, holding `savedAt + expireAfterMs`. Give the store no
 * `expireAfterMs` and the field is written as `null`, which a TTL policy
 * ignores, and nothing about this release changes.
 *
 * **`savedAt` was NOT converted, and that is the load-bearing decision.** It is
 * the ordering key of the composite index, the field the listing sorts by, and
 * the first half of every cursor this store has ever minted (`<savedAt>:<docId>`,
 * parsed with `parseFloat` and handed back to `startAfter` as a number).
 * Converting it would have meant a new index, a new cursor grammar, and every
 * pagination token issued by an earlier release becoming unreadable — but the
 * decisive one is quieter: **Firestore's value ordering sorts by TYPE first.**
 * Every number sorts before every timestamp. A collection holding old
 * number-`savedAt` documents beside new timestamp-`savedAt` ones would list
 * them in two blocks, all of one type then all of the other, with a person's
 * conversations silently out of order by the type of the field rather than by
 * when they were written. A second field costs one small write per turn and
 * has no such shadow.
 *
 * So the migration consequence, plainly:
 *
 *   • **Every document already stored stays readable and listable.** Nothing
 *     about `savedAt`, the index, the cursor or the query changed, and a
 *     document written by 9.33–9.41 is read by this release unchanged.
 *   • **Old documents do not start expiring.** They carry no `expiresAt`, and
 *     a TTL policy ignores a document whose field is missing. A conversation
 *     that is still being used gains one on its next turn; a conversation
 *     nobody ever writes to again keeps living. If those matter, delete them
 *     by hand once (a query on `savedAt` plus `forget`, or a `gcloud`
 *     bulk-delete) — this adapter will not walk your collection behind your
 *     back.
 *   • **The policy is still yours to create.** `retention().enableWith` prints
 *     the exact command with your collection and database in it.
 *
 * ── The ceiling, since a store should name its own ──────────────────────────
 * A Firestore document is capped at 1 MiB. A conversation whose stored envelope
 * approaches that is refused BY NAME before the write
 * ({@link EnvelopeTooLargeError}) rather than being sent and rejected as an
 * opaque `INVALID_ARGUMENT`. Nothing is ever truncated: half a conversation
 * stored as if it were whole is the failure this whole file exists to avoid.
 * Compaction (the builder's `.compaction({ … })`, or `.window()`) is the
 * answer, and the refusal says so. Named from the builder deliberately: there
 * is no `agent.compact()` to call, and a doc comment ships into the emitted
 * `.d.ts`, so a method named here that does not exist is a method somebody
 * types on hover.
 *
 * ── The laws it inherits rather than re-implements ──────────────────────────
 * `checkEnvelope` runs on the way OUT and on the way IN, so an envelope whose
 * `format` this runtime does not know is refused by name, and a session that is
 * PRESENT but unreadable is refused by name too. Only a session that was never
 * written hydrates as `undefined`. A conversation that exists and cannot be read
 * must never be answered with a fresh start — from the outside that is
 * indistinguishable from a brand-new user.
 *
 * Ownership is DERIVED from the stored envelope and established ONCE. See
 * `persist` below: SQLite gets that from `COALESCE(sessions.owner,
 * excluded.owner)`, Firestore has no such thing, and a `set({ merge: true })`
 * would let the last writer win — which is exactly the bug, not a workaround
 * for it. So the write is a transaction.
 *
 * ── How much of this is verified ────────────────────────────────────────────
 * **Field-validated except the ownership refusal (2026-08).** An independent
 * field trial ran THIS adapter against a real Firestore and exercised seven of
 * its eight areas live: the round trip, the server-side indexed listing with
 * its document-name tiebreak and real cursor, the missing-index refusal, the
 * hashed document name, the size ceiling, ownership derived from the envelope,
 * and `forget`.
 *
 * The eighth is named because under-claiming and over-claiming are the same
 * defect: **the refusal of a turn signed by somebody else** — the
 * `resolveSessionOwner` conflict this file's `persist` raises out of the
 * transaction — was NOT exercised, because it did not exist when the trial
 * ran. It is held by tests here, including the contested-write case of the
 * shared conformance battery, and by nothing in the field. Two writers racing
 * for one fresh session against a real Firestore, with the real client's
 * transaction retry underneath, is exactly the shape a double models by
 * assumption; the sibling Vertex adapter's equivalent was found in the field
 * and not in a test suite.
 *
 * This section said "NOT field-validated. Nothing here has been run against a
 * live Firestore" for two releases after that stopped being true. Under-claiming
 * is the safe direction and it is still the same defect class the trials keep
 * reporting: a status that does not track the evidence is a status nobody can
 * use, whichever way it is wrong.
 *
 * The pin is the `firestoreSessions` row of `GOOGLE_SURFACE_PINS` in
 * `test/adapters/google/googlePin.ts`, asserted by
 * `test/adapters/google/google-surface-pin.test.ts`. Its 18 members were
 * hand-verified against a real `@google-cloud/firestore` 9.0.0 install in a
 * scratch project OUTSIDE this repository: seventeen read off
 * `types/firestore.d.ts` before a line of this file was written, and
 * `DocumentSnapshot.id` verified afterwards, when a review found the row had
 * pinned `DocumentReference.id` — real, but a member this adapter never reads —
 * in place of the one the cursor actually reads.
 *
 * That package is deliberately NOT installed here. It depends on
 * `@opentelemetry/api`, so installing it hoists that package to the repository
 * root and disarms `test/observability-providers/otel.test.ts`, which proves
 * `otelObservability()` refuses BY NAME when `@opentelemetry/api` is absent.
 * The consequence has to be said plainly: **the reality assertion — "every
 * pinned member really exists on the real package" — SKIPS in this repository.**
 * It runs in full for anyone who installs `@google-cloud/firestore` locally.
 *
 * So what is machine-checked in CI is the SHAPE pin, not the reality pin: this
 * adapter dispatches exactly the members the row names and no others, every run,
 * everywhere. That the row spells those members the way Google does is held by a
 * hand check against a real install, not by a test that runs here.
 *
 * The DESIGN was informed, before any of that, by an EARLIER and separate trial
 * of a DIFFERENT Firestore
 * session adapter, which ran against a real Firestore and passed eight ownership
 * and history checks — and whose own report named the defect this adapter does
 * not reproduce: that adapter read every document for one owner, sorted them in
 * the client, and applied an offset cursor. That works until one person has a
 * lot of conversations, and then it costs a full read of all of them per page.
 * What the trial proves is that the ownership and history SEMANTICS survive a
 * real service; it proves nothing about this file's query, cursor, transaction
 * or index, because that adapter had none of them.
 */
import type { SessionExpiryPolicy, SessionLifecycle } from '../../hosting/types.js';
/** One document as it came back. `firestore.d.ts` line 1605. */
export interface FirestoreDocumentSnapshotLike {
    /** A PROPERTY, not a method — `readonly exists: boolean`. */
    readonly exists: boolean;
    readonly id: string;
    /** `undefined` when the document does not exist. */
    data(): Record<string, unknown> | undefined;
}
/** The answer to a query. `firestore.d.ts` line 2163. */
export interface FirestoreQuerySnapshotLike {
    /**
     * An ARRAY, already materialised. Not an async iterable, not a stream — the
     * SDK has `Query.stream()` for that and this adapter does not use it. A
     * double that hands back an async iterable here would be testing a client
     * nobody ships.
     */
    readonly docs: readonly FirestoreDocumentSnapshotLike[];
}
/** The query builder. Every method is SYNCHRONOUS and returns a NEW query
 *  (the SDK documents the immutability explicitly); only `get` is async.
 *  `firestore.d.ts` line 1714. */
export interface FirestoreQueryLike {
    where(fieldPath: string, opStr: string, value: unknown): FirestoreQueryLike;
    orderBy(fieldPath: string | unknown, directionStr?: 'asc' | 'desc'): FirestoreQueryLike;
    startAfter(...fieldValues: unknown[]): FirestoreQueryLike;
    limit(limit: number): FirestoreQueryLike;
    get(): Promise<FirestoreQuerySnapshotLike>;
}
/** A handle on one document. `firestore.d.ts` line 1436. */
export interface FirestoreDocumentReferenceLike {
    readonly id: string;
    get(): Promise<FirestoreDocumentSnapshotLike>;
    /** Resolves to a `WriteResult`, which this adapter does not read. */
    delete(): Promise<unknown>;
}
/** A collection is a Query that can also mint document handles.
 *  `firestore.d.ts` line 2305. */
export interface FirestoreCollectionLike extends FirestoreQueryLike {
    doc(documentPath: string): FirestoreDocumentReferenceLike;
}
/**
 * The transaction handle. `firestore.d.ts` line 801.
 *
 * The asymmetry is the thing to get right in a double: `get` is ASYNC and
 * answers a snapshot, while `set` is SYNCHRONOUS and answers the transaction
 * itself for chaining. A double whose `set` returned a promise would let an
 * adapter that forgot to sequence its writes pass a test it should fail.
 */
export interface FirestoreTransactionLike {
    get(documentRef: FirestoreDocumentReferenceLike): Promise<FirestoreDocumentSnapshotLike>;
    set(documentRef: FirestoreDocumentReferenceLike, data: Record<string, unknown>): FirestoreTransactionLike;
}
/** One connected database. `firestore.d.ts` line 554. */
export interface FirestoreLike {
    collection(collectionPath: string): FirestoreCollectionLike;
    runTransaction<T>(updateFunction: (transaction: FirestoreTransactionLike) => Promise<T>): Promise<T>;
    /** Closes the client's gRPC channels. See {@link FirestoreSessions.close}. */
    terminate(): Promise<void>;
}
/**
 * The two exports this adapter needs from the package.
 *
 * `FieldPath.documentId()` is a STATIC that returns a sentinel; it is how a
 * query orders by document name, and there is no string spelling of it that the
 * client will accept in `orderBy`.
 */
export interface FirestoreConstructorLike {
    new (settings?: Record<string, unknown>): FirestoreLike;
}
/** The module's shape, as this adapter loads it. */
export interface FirestoreSdkModule {
    readonly Firestore: FirestoreConstructorLike;
    readonly FieldPath: {
        documentId(): unknown;
    };
}
/** Options for {@link firestoreSessions}. */
export interface FirestoreSessionsOptions {
    /**
     * The Google Cloud project. Omit and the client reads it from the ambient
     * environment the same way every Google client does (`GCLOUD_PROJECT`, or the
     * Application Default Credentials).
     */
    readonly project?: string;
    /**
     * Which Firestore database in that project. Omit for `'(default)'`.
     *
     * Worth setting deliberately: a project can hold several databases, and
     * "conversations went to the wrong one" looks exactly like "conversations
     * were lost".
     */
    readonly database?: string;
    /**
     * The collection sessions live in. Default
     * {@link DEFAULT_SESSION_COLLECTION}.
     *
     * A top-level collection name, not a path — this adapter does not nest
     * sessions under another document, because a store that required a parent
     * would be making a decision about your data model that the port never asked
     * for.
     */
    readonly collection?: string;
    /**
     * A Firestore client you already built.
     *
     * Most applications that reach for this adapter already have one, and two
     * clients in one process means two sets of gRPC channels for no benefit. When
     * you pass one, this store never terminates it — see
     * {@link FirestoreSessions.close}.
     *
     * Passing this together with `project` or `database` is refused rather than
     * silently ignored: those settings belong to whoever CONSTRUCTED the client,
     * and accepting them here would let a caller believe they had switched
     * databases.
     *
     * It does NOT remove the need for the package to be installed:
     * `FieldPath.documentId()` is a static on the module, and there is no string
     * spelling of `__name__` the client accepts in `orderBy`. In practice that
     * costs nothing — anyone holding a client already has the package — but it is
     * stated rather than discovered, because "I passed my own client, why is it
     * still loading the module?" is a fair question to have answered here.
     */
    readonly firestore?: FirestoreLike;
    /**
     * How long after its last turn a conversation should expire, in
     * milliseconds. Omit and nothing expires (9.42.0).
     *
     * Setting it makes every write stamp {@link EXPIRES_AT_FIELD} —
     * `savedAt + expireAfterMs`, as a timestamp — which is the field a **native
     * Firestore TTL policy** reads. The policy itself is yours to enable, once,
     * with the command {@link FirestoreSessions.retention} hands you; this store
     * writes what it reads and never deletes a document on a clock of its own.
     * Both halves are needed and neither is silent about the other: with no
     * policy the field is inert data, and with no `expireAfterMs` the policy has
     * nothing to act on.
     *
     * **The clock is the conversation's own `savedAt`, not this process's.** So
     * the expiry is IDLE time: every turn pushes it out, and a conversation dies
     * `expireAfterMs` after somebody last spoke, which is what a session
     * retention rule almost always means. It also makes the stamp a pure
     * function of the envelope — the same conversation stamps the same instant
     * on any machine, whatever its clock says.
     *
     * **It does not reach backwards.** Documents written before this was
     * configured carry no expiry field, and a TTL policy ignores a document
     * whose field is missing — so old conversations expire only once they are
     * written again. See the module header for what to do about the ones that
     * never will be.
     *
     * Named `expireAfterMs` rather than `ttlMs` deliberately, even though the
     * artifact stores in this package spell their dial the second way: theirs is
     * stamped once at mint and measured from creation, and this one is measured
     * from the LAST turn and moves on every write. One word for two clocks would
     * be the cheaper name and the more expensive mistake.
     */
    readonly expireAfterMs?: number;
    /**
     * @internal Test seam only — the `@google-cloud/firestore` module, injected.
     * Lets the suite exercise every path (including the refusals) without a
     * credential or a network. Not public API, and not a place to plug in another
     * Firestore driver.
     */
    readonly _sdk?: FirestoreSdkModule;
}
/** Where sessions live when the caller names no collection. */
export declare const DEFAULT_SESSION_COLLECTION = "agentfootprint_sessions";
/**
 * The document field a native TTL policy is configured against (9.42.0).
 *
 * A SECOND field beside `savedAt`, never a change to it — see the module
 * header for why converting the ordering key would have re-sorted every
 * conversation already in the collection.
 *
 * Exported because an operator has to type this name into the console or the
 * `gcloud` line, and a field name that lives only inside a string literal is a
 * field name somebody will mistype at 2am.
 */
export declare const EXPIRES_AT_FIELD = "expiresAt";
/**
 * The one step that turns the TTL policy on, with this store's own collection
 * and database already in it.
 *
 * One spelling, shared by the retention answer and the documentation that
 * quotes it, for the same reason the missing-index refusal prints the whole
 * `gcloud` line: the person reading it can fix this in sixty seconds, and only
 * if nobody makes them go and look up the flags first.
 *
 * `--database` is always printed, including for `(default)`, where gcloud
 * would have assumed it — a project may hold several databases, and a policy
 * created on the wrong one leaves conversations living forever with nothing to
 * suggest why. Single-quoted, because `(default)` is bare parentheses and a
 * syntax error in every shell this will be pasted into.
 */
export declare function ttlPolicyCommand(collection: string, database?: string): string;
/**
 * Firestore's hard ceiling on one document, in bytes. Not ours — the service's.
 *
 * @see https://cloud.google.com/firestore/quotas
 */
export declare const FIRESTORE_MAX_DOCUMENT_BYTES = 1048576;
/**
 * The largest stored envelope this adapter will attempt, in bytes.
 *
 * Below the real ceiling by a margin, because the document also carries five
 * other fields, their NAMES, and the document's own path — all of which count
 * toward Firestore's total. Refusing a little early with a sentence that says
 * what to do beats sending a 1,048,570-byte envelope and getting back an
 * `INVALID_ARGUMENT` that names nothing.
 */
export declare const FIRESTORE_MAX_ENVELOPE_BYTES: number;
/**
 * A session store in Firestore.
 *
 * A {@link SessionLifecycle} plus the three things a real store owns beyond the
 * port — forgetting, closing, and telling you where it is writing — because the
 * port deliberately asks for two methods and leaves the rest to whoever
 * implements it.
 */
export interface FirestoreSessions extends SessionLifecycle {
    /** The collection these sessions live in. Useful in an incident. */
    readonly collection: string;
    /** Forget one session. A session that was never there is not an error. */
    forget(sessionId: string): Promise<void>;
    /**
     * How conversations here stop existing: **the backend does it**, on a native
     * TTL policy an operator enables once, against {@link EXPIRES_AT_FIELD}
     * (9.42.0).
     *
     * Narrowed from the port's optional member to a required one, and to the one
     * arm this store can be: a caller holding a `FirestoreSessions` needs no
     * feature check and no branch, and gets a compile error rather than a
     * runtime surprise if they reach for a sweep that does not exist here.
     *
     * `active` says whether this store was built with `expireAfterMs` and is
     * therefore stamping anything; `enableWith` is the exact command, with this
     * store's collection and database already filled in.
     */
    retention(): SessionExpiryPolicy;
    /**
     * The document name one session id maps to — the sha-256 above.
     *
     * Exposed because the mapping is one-way and an operator in an incident needs
     * it: this is the string to paste into the Firestore console to find one
     * conversation. It is a pure function, it touches nothing, and it works on a
     * closed store.
     */
    documentIdFor(sessionId: string): string;
    /**
     * Stop using this store, and release the client's gRPC channels.
     *
     * **Async, unlike the other stores' `close()`, and that is not an
     * inconsistency for its own sake.** Terminating a Firestore client closes
     * channels; a Node process holding an open channel does not exit. A `void`
     * return here would be a promise this adapter could not keep, so the shape
     * says what actually happens and a shutdown hook can await it.
     *
     * Idempotent, and **final** — reading or writing afterwards refuses by name
     * rather than quietly reconnecting, because a store that reopened behind you
     * would hide a shutdown-ordering bug instead of surfacing it.
     *
     * A client you passed in yourself is NOT terminated: this store did not open
     * it and does not get to decide when the rest of your application stops using
     * it. Nothing is torn down on Google's side either — the documents outlive the
     * process, which is the entire reason to use a managed store.
     */
    close(): Promise<void>;
}
/**
 * Raised when the query `listByUser` needs has no composite index yet.
 *
 * Its own class rather than a generic failure, because this is the ONE
 * Firestore error an operator can fix in sixty seconds — and the only way they
 * will know that is if the message says which index, on which collection, in
 * which DATABASE, in which order. See the module header for the `gcloud` line.
 *
 * The database is named and the `--database` flag is always printed, including
 * for `(default)`, where gcloud would have assumed it anyway. That is deliberate:
 * a project may hold several Firestore databases, and an operator on a
 * non-default one who follows a command with no `--database` creates the index on
 * `(default)` and gets this identical error back. A refusal that teaches the
 * wrong fix is worse than a bare failure, and one always-present flag costs
 * nothing to be right.
 *
 * The service's own message carries a one-click creation link and is
 * deliberately NOT echoed: it restates the failing query, and the failing query
 * contains a user id. See {@link firestoreFailure}.
 */
export declare class FirestoreIndexMissingError extends Error {
    readonly code: "ERR_FIRESTORE_INDEX_MISSING";
    /** The collection whose index is missing. */
    readonly collection: string;
    /**
     * The database it lives in, or `undefined` when this store did not build the
     * client and therefore cannot know — see the message for what to do then.
     */
    readonly database: string | undefined;
    constructor(collection: string, database?: string);
}
/**
 * Raised when a conversation is too big to be one Firestore document.
 *
 * Refused BEFORE the write, so the failure names the conversation and the fix
 * rather than arriving as an opaque `INVALID_ARGUMENT` from the wire. Nothing is
 * truncated on the way past: a conversation half-stored as if it were whole is
 * the exact failure this file's other laws exist to prevent.
 */
export declare class EnvelopeTooLargeError extends Error {
    readonly code: "ERR_ENVELOPE_TOO_LARGE";
    /** The session that could not be stored. */
    readonly sessionId: string;
    /** How big its serialized envelope was. */
    readonly bytes: number;
    constructor(sessionId: string, bytes: number);
}
/**
 * Conversations in Firestore — a fleet-shared session store with no instance to
 * run.
 *
 * **Status: field-validated except the ownership refusal (2026-08).** An
 * independent field trial ran this adapter against a real Firestore and
 * exercised seven of its eight areas live — round trip, indexed and cursored
 * listing, the missing-index refusal, the hashed document name, the size
 * ceiling, derived ownership and `forget`. The eighth, the refusal of a turn
 * signed by somebody ELSE, was added after the trial and is held by tests
 * here and by nothing in the field. Every SDK member it calls was read off a
 * real install of `@google-cloud/firestore` 9.0.0 and hand-verified there; the
 * test that re-checks those names against the real package SKIPS in this
 * repository, because the package is deliberately not installed here. What
 * runs in CI is the dispatch pin. See the module header for the full account.
 *
 * @throws FirestoreIndexMissingError from `listByUser` until the composite index
 *   exists — see the module header for the exact index.
 * @throws EnvelopeTooLargeError from `persist` for a conversation above
 *   {@link FIRESTORE_MAX_ENVELOPE_BYTES}. Never truncated.
 *
 * @example  Conversations that expire 30 days after their last turn
 *   const sessions = firestoreSessions({
 *     project: 'my-project',
 *     expireAfterMs: 30 * 24 * 60 * 60 * 1000,
 *   });
 *   // Then, ONCE, as an operator — the store prints the exact command:
 *   console.log(sessions.retention().enableWith);
 *
 * @example  A standing agent whose conversations are shared across instances
 *   import { standingAgent, nodeHost } from 'agentfootprint/hosting';
 *   import { firestoreSessions } from 'agentfootprint/hosting';
 *
 *   const sessions = firestoreSessions({ project: 'my-project' });
 *   const handle = await standingAgent({
 *     agentFactory: () => buildAgent(),
 *     host: nodeHost({ port: 8080 }),
 *     sessions,
 *   });
 *   process.on('SIGTERM', () => void handle.close().then(() => sessions.close()));
 *
 * @example  Reusing the Firestore client the application already has
 *   const sessions = firestoreSessions({ firestore: db, collection: 'chat_sessions' });
 *   // close() will NOT terminate `db` — this store did not open it.
 */
export declare function firestoreSessions(options?: FirestoreSessionsOptions): FirestoreSessions;
/**
 * The document name for one session id — `sha256(domain ‖ NUL ‖ id)` in hex.
 *
 * A module-level pure function rather than a closure, so the same mapping is
 * available to the store, to a test, and to an operator who needs it in a REPL.
 * The NUL separator is what stops `domain + "a" + "bc"` and `domain + "ab" + "c"`
 * from being the same input; a session id may legally contain anything else.
 *
 * See the module header for why this is an ADDRESSING scheme and not, in any
 * sense, encryption.
 */
export declare function documentIdFor(sessionId: string): string;
/**
 * The gRPC status of a failed call, as a NAME, wherever the client put it.
 *
 * Two spellings are accepted because two layers report it differently: the gax
 * layer sets a numeric `code`, and some wrappers carry the name as a string. A
 * classifier that read only one of them would quietly stop classifying the day
 * the client is upgraded.
 */
export declare function grpcStatusOf(err: unknown): string | undefined;
/** Is this the service saying "that query has no index"? */
export declare function isFailedPrecondition(err: unknown): boolean;
/**
 * Re-raise a failed Firestore call **without its text** — the same law the AWS
 * and Vertex columns follow, re-aimed at a gRPC error.
 *
 * What comes through is the part that is both safe and actionable: which
 * operation failed, which collection it was on, and the gRPC status name. What
 * does not is the SDK's message, because a Firestore error restates the failing
 * request — a document path, a filter value, a field — and those carry a user id
 * and a whole conversation's state. An error thrown from an adapter reaches the
 * model as a tool result AND rides the event stream to every sink attached to
 * the agent.
 *
 * No credential is ever named. **The original is deliberately not attached as
 * `cause`** — a cause travels with the error into every serializer that walks
 * own properties, which would undo all of this in one `JSON.stringify`.
 */
export declare function firestoreFailure(operation: string, collection: string, err: unknown): Error;
//# sourceMappingURL=firestoreSessions.d.ts.map