/**
 * hosting/ingressRecord — what the door decided, for the requests that never
 * became a run.
 *
 *   await standingAgent({
 *     agent, sessions, host,
 *     identity: { verify },
 *     admission: turnsPerHour({ limit: 60 }),
 *     onIngressDecision: (record) => myAuditSink.write(record),
 *   });
 *
 * ── The gap this closes, named exactly ───────────────────────────────────────
 * `auditExport()` is a record of RUNS. A 401 out of `identity.verify` and a 429
 * out of `admission.decide` both happen before a run exists — no agent has been
 * built, no observer is attached, no typed event is emitted — so neither is in
 * the bundle. Two independent review rounds wrote that down as the same finding,
 * and the honest consequence is the sharp one: **an empty bundle is not evidence
 * that nobody was turned away.** It is evidence that nobody ran.
 *
 * This is the seam where the turned-away go. One record per request the composer
 * decided, handed to a sink you own, at the moment the reply reaches its
 * terminal.
 *
 * ── What it is NOT, said before anything else ────────────────────────────────
 * **It does not join the audit hash chain.** Nothing here is hashed, chained,
 * sequenced against `auditExport()`'s records, or verifiable by
 * `verifyAuditBundle`. It is a STREAM you chain into your own sink. Saying
 * otherwise would make this fix the exact failure it exists to close — a
 * mechanism that looks like evidence and is not. If you need ingress decisions
 * inside a tamper-evident chain, write them into the same store your audit
 * bundle lands in and chain them there, where the sequence is yours to define.
 *
 * ── Secrets never travel ─────────────────────────────────────────────────────
 * A record carries the CLASS of what happened and the identifiers the caller
 * already knew. Never the bearer token, never a header, never a claim set,
 * never an error's message — the `sdkFailure` law, applied to the front door.
 * `errorName` and `errorCode` are this package's own vocabulary; a message may
 * carry an SDK's text, an operator's sentence or a store's detail, so no message
 * is copied. The one identity field is `userId`, and it is the id the token
 * PROVED — never one a request merely claimed.
 *
 * ── What the composer can decide, and what it never sees ─────────────────────
 * Every ingress exit of `standingAgent` funnels through one reply terminal, so
 * all of these land here — including the ones a consumer's own `verify` and
 * `decide` never observe:
 *
 *   - a request with no `Authorization: Bearer …` at all (`verify` is never
 *     called, so nothing you wrote could have logged it);
 *   - a verifier that could not answer (503) as distinct from a bad credential;
 *   - an admission refusal, and also an admission ALLOW — the census is what
 *     makes an absence readable;
 *   - a turn or a transcript naming somebody else's session (the 404 that is
 *     deliberately indistinguishable from "no such session");
 *   - a session op at a door with no verifier, a store with no owner index, a
 *     concurrent-run refusal, an artifact ref that did not resolve;
 *   - and a request that was SERVED.
 *
 * The HOST's own decisions land here too, though no handler ever saw them: a
 * request its door guard turned away (a content type any page can send, a
 * foreign Origin, a Host it was not configured for — `'cross-site-refused'`)
 * and a session id over the bound (`'refused'`), at the request door and the
 * conversation door alike. They arrive through the host's `onRefusal`, which
 * the composer subscribes to whenever a sink is set, and they carry the same
 * classes and nothing more — door `'request'` or `'conversation'`, because a
 * forged request is refused before its body says which of the three doors it
 * wanted.
 *
 * What it does not carry, stated rather than implied: a body the TRANSPORT
 * refused as MALFORMED — unparseable JSON, a body over `maxBodyBytes`, or an
 * `op` this host's wire grammar does not speak (`InvalidWireOpError`, answered
 * 400 by `httpHost` inside its own request reader). That is a broken request
 * rather than a decision about a caller, and the composer does not claim it.
 * Your HTTP access log has it.
 *
 * Pattern: Observer at a composition boundary. Zero-cost when unset — with no
 * sink configured nothing is built, nothing is wrapped, and the reply object the
 * handler uses is the host's own, to the byte.
 */
import { type VerifiedIdentity } from './identityVerification.js';
import { type IdentityFailureClass } from './errors.js';
import type { ArtifactHandOverFailedPayload } from '../events/payloads.js';
import type { HostRefusal, HostReply, HostRequest } from './types.js';
/**
 * Which door this request knocked at.
 *
 *  - `'turn'`, `'session-op'`, `'artifact'` — the composer's three doors, for
 *    every request the host handed on.
 *  - `'request'` — the host's one-exchange door, for a request the HOST refused
 *    before the composer saw it (the door guard; the session-id bound). Which
 *    of the three it was headed for is deliberately not recorded: a forged
 *    request is refused on its headers, and its body is never parsed.
 *  - `'conversation'` — a conversation upgrade the host refused before the 101.
 *    Recorded here because it is the same host's door and the same census,
 *    though the composer itself never serves conversations.
 */
export type IngressDoor = 'turn' | 'session-op' | 'artifact' | 'request' | 'conversation';
/**
 * How the door answered — coarse on purpose, because this is the field a
 * dashboard groups by. `errorCode` carries the precision beside it.
 *
 *  - `'served'` — the reply reached a DELIVERING terminal: an answer, a
 *    question a person must answer, a transcript, a listing, an artifact.
 *
 *    Read it as **delivered**, not as *admitted*. The record is filed at the
 *    terminal the reply actually reached, and a failure is not a delivery — so
 *    a request the door admitted whose RUN, store or provider then broke ends
 *    at `reply.fail` and is filed `'failed'` (or `'refused'`, when the error
 *    names itself with an `ERR_…` code), carrying that error's class. It is
 *    NOT `'served'`. To count what the door let through, count everything that
 *    is not one of the door's own refusals — `'identity-refused'`,
 *    `'verifier-unavailable'`, `'admission-refused'`, `'session-refused'` — or
 *    read `admission`, which is the verdict a policy actually returned and is
 *    present on a refused record and a broken one alike.
 *  - `'identity-refused'` — 401. The credential did not identify anybody, or
 *    none arrived at a door that insists.
 *  - `'verifier-unavailable'` — 503. The verifier could not do its job; the
 *    caller's token was never judged and this is not their fault.
 *  - `'admission-refused'` — 429. A policy said no before any work started.
 *  - `'session-refused'` — the one indistinguishable not-found: a session that
 *    does not exist, belongs to somebody else, or names no owner.
 *  - `'cross-site-refused'` — the host's door guard turned it away before any
 *    handler saw it: a content type a page on any site can send (415), a
 *    browser Origin this door does not allow (403), a Host it was not
 *    configured for (421). `errorCode` says which. Not proof of an attack — a
 *    script that forgot `content-type: application/json` lands here too — but
 *    the shape every forged request has, counted where it can be seen.
 *  - `'refused'` — every other refusal the door made by name (a session op with
 *    no verifier configured, a store with no owner index, a concurrent run, an
 *    artifact ref that did not resolve, a pause the wire could not carry, a
 *    session id over the ceiling).
 *  - `'failed'` — the request ended in an error that is not one of the door's
 *    own refusals. Usually the run, the store or the provider — which is why
 *    an admitted request that then broke lands HERE and not in `'served'`.
 */
export type IngressOutcome = 'served' | 'identity-refused' | 'verifier-unavailable' | 'admission-refused' | 'session-refused' | 'cross-site-refused' | 'refused' | 'failed';
/** What an admission policy answered, when one was consulted. */
export type IngressAdmissionVerdict = 'allow' | 'queue' | 'refuse';
/**
 * One decision the door made, as plain data.
 *
 * Every field is either a class of what happened or an identifier the caller
 * already had. Nothing here is a secret, and nothing here is a message.
 */
export interface IngressRecord {
    /** When the request left the door, `Date.now()`. */
    readonly at: number;
    /** Which door it knocked at. */
    readonly door: IngressDoor;
    /** The wire op it named, in the spelling a body uses. Absent for a turn. */
    readonly op?: string;
    /** How the door answered. */
    readonly outcome: IngressOutcome;
    /** The refusal's own `code` (`'ERR_IDENTITY_NOT_VERIFIED'`, …). Never a message. */
    readonly errorCode?: string;
    /** The error's class name, for a failure this package did not author. */
    readonly errorName?: string;
    /** WHICH token check failed — the whole vocabulary a refusal may say about a
     *  credential, and never one character of the credential. */
    readonly identityFailure?: IdentityFailureClass;
    /** `true` when the refused request also NAMED a user it could not prove —
     *  the impersonation shape, kept separate so a sink can count it. */
    readonly claimedUser?: boolean;
    /** The id the token PROVED, when one was proven. Never a claimed one. */
    readonly userId?: string;
    /** The conversation this request named, when it named one. */
    readonly sessionId?: string;
    /** Whether a bearer credential was PRESENT. Never its value. */
    readonly bearerPresent: boolean;
    /** What the admission policy answered, when one was consulted. */
    readonly admission?: IngressAdmissionVerdict;
    /**
     * This turn's artifact hand-over (`HostReply.turnArtifacts`) did not go
     * cleanly — the FIRST failure of it: the host's hook threw, an operation it
     * started failed, it outran `turnArtifactsTimeoutMs`, or the caller hung up
     * first. The reply was delivered regardless — the hand-over never decides the
     * terminal, so `outcome` still says what the caller got. The per-request
     * census of what `agentfootprint.artifacts.hand_over_failed` reports on the
     * serving agent's stream, in the same shape: class only, never the message.
     */
    readonly turnArtifactsFailure?: ArtifactHandOverFailedPayload;
}
/**
 * Where records go — {@link StandingAgentBaseOptions.onIngressDecision}.
 *
 * Called once per request, synchronously, at the moment the reply reaches its
 * terminal. A sink that throws is contained (a broken log must not turn a served
 * request into a failed one) and reported once on `console.warn`, because a
 * silently-dropped security record is the failure this whole seam exists to
 * close. Do the slow part yourself: buffer, batch, and never `await` the network
 * inside it.
 */
export type IngressSink = (record: IngressRecord) => void;
/**
 * The per-request bookkeeping: the facts known at the door, the facts learned
 * on the way in, and the ONE record that leaves.
 *
 * Built only when a sink is configured. `settle` is idempotent — the first
 * terminal wins, and a reply that somehow reaches two of them (a host that
 * completes and then fails) still produces exactly one record, because two
 * records for one request would be a census that over-counts.
 */
export interface IngressNote {
    /** Wrap the host's reply so its first terminal files the record. */
    watch(reply: HostReply): HostReply;
    /** The verifier proved somebody — record the proven id, never a claimed one. */
    identified(verified: VerifiedIdentity | undefined): void;
    /** An admission policy answered. */
    admitted(verdict: IngressAdmissionVerdict): void;
    /** The turn's artifact hand-over failed — the first failure is recorded. */
    turnArtifactsFailed(failure: ArtifactHandOverFailedPayload): void;
    /** File the record for a request that left without any terminal at all. */
    settle(): void;
}
/**
 * File the record for a request the HOST refused before the composer saw it —
 * what `standingAgent` hands to the host's `onRefusal` when a sink is set.
 *
 * Built from the refusal's class and the fact of a credential, exactly as a
 * composer refusal is: no message, no header, no session id (a session id the
 * bound refused is the one thing this record must never carry), no user —
 * nothing was verified, because nothing got that far.
 *
 * @internal — the option is public, this bookkeeping is not.
 */
export declare function recordHostRefusal(refusal: HostRefusal, sink: IngressSink): void;
/**
 * Begin one request's ingress record.
 *
 * @internal — the option is public, this bookkeeping is not.
 */
export declare function beginIngress(request: HostRequest, sink: IngressSink): IngressNote;
