UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

1,107 lines • 59.5 kB
"use strict";
/**
 * hosting/errors — the refusals, authored once so every adapter refuses in the
 * same words.
 *
 * A refusal that varies by adapter is a refusal nobody can write a test or a
 * runbook against. These six carry a stable `code`, name WHO refused, and say
 * what the caller should do instead. Adapters map the codes onto whatever their
 * transport uses to say "no" — that mapping is the adapter's business and lives
 * in the adapter, never here.
 *
 * Note what is NOT here: a run that paused. That is unfinished work rather than
 * a refusal, and it leaves through `reply.awaiting(...)` — its own terminal —
 * not through an error dressed up as one.
 *
 * `requireCapability` is the last refusal and the only one that is a
 * programming mistake rather than a runtime condition, so it throws a plain
 * `Error`: nothing branches on "I forgot to feature-detect", it just needs to
 * say so loudly and name the adapter it is talking about.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireCapability = exports.InvalidSessionIdError = exports.HostNotAllowedError = exports.OriginNotAllowedError = exports.UnsupportedMediaTypeError = exports.WireRequestRefusal = exports.RequestTooLargeError = exports.SessionsNotCarriedError = exports.SessionOwnershipConflictError = exports.SessionNotFoundError = exports.SessionRetentionUnavailableError = exports.SessionIndexUnavailableError = exports.SessionOpNeedsIdentityError = exports.AdmissionRefusedError = exports.VerifierUnavailableError = exports.IdentityNotVerifiedError = exports.UnreadableEnvelopeError = exports.RequestArtifactsRevokedError = exports.TurnArtifactsExpiredError = exports.ArtifactNotCarriedError = exports.RecordingTooLargeForAccountError = exports.ArtifactOpsBusyError = exports.ArtifactNotFoundError = exports.NoArtifactStoreError = exports.ArtifactSessionRequiredError = exports.InvalidWireOpError = exports.FrameTooLargeError = exports.ConversationClosedError = exports.NoPendingAskError = exports.AwaitingDecisionError = exports.PauseNotCarriedError = exports.ConcurrentRunError = exports.HostClosedError = void 0;
const storedPreview_js_1 = require("../lib/storedPreview.js");
/**
 * Thrown when a request arrives at a host that is shutting down or shut down.
 *
 * `close()` lets in-flight work finish and refuses everything after it; this is
 * what "everything after it" receives.
 */
class HostClosedError extends Error {
    code = 'ERR_HOST_CLOSED';
    /** Which adapter refused. */
    hostName;
    constructor(hostName) {
        super(`[hosting] the '${hostName}' host is closed and is not accepting new requests. ` +
            `In-flight requests were allowed to finish; this one arrived after close() was called. ` +
            `Serve again on a fresh host to accept new work.`);
        this.name = 'HostClosedError';
        this.hostName = hostName;
    }
}
exports.HostClosedError = HostClosedError;
/**
 * Thrown when a request arrives for a session that already has a run in flight
 * and the policy is `'reject'`.
 *
 * The refusal is about the SESSION, not about load: two turns of one
 * conversation racing each other would each answer from the state the other is
 * about to replace. A request for any OTHER session is never refused — it
 * simply waits.
 */
class ConcurrentRunError extends Error {
    code = 'ERR_CONCURRENT_RUN';
    /** The session that already has a run going. */
    sessionId;
    /** The run that is already going, when it has announced itself. */
    activeRunId;
    constructor(sessionId, activeRunId) {
        super(`[hosting] session '${sessionId}' already has a run in flight` +
            (activeRunId ? ` (run '${activeRunId}')` : '') +
            `. Refusing rather than running two turns of one conversation at once — ` +
            `they would each answer from state the other is about to replace. ` +
            `Wait for the active run, or build the standing agent with ` +
            `onConcurrentInvoke: 'enqueue' to queue this turn behind it instead.`);
        this.name = 'ConcurrentRunError';
        this.sessionId = sessionId;
        if (activeRunId !== undefined)
            this.activeRunId = activeRunId;
    }
}
exports.ConcurrentRunError = ConcurrentRunError;
/**
 * Raised when a run paused to ask a person something and there is **nowhere to
 * keep it**.
 *
 * **The run did not fail.** A pause is unfinished work: the agent stopped to ask
 * and is waiting for an answer. Since 7.19 a paused run is stored as
 * `'flowchart-v1'` and continued by a later request carrying a decision — so the
 * one case left where a pause genuinely cannot be carried is a request with no
 * session id. There is no session to store it under, and therefore no later
 * request that could ever answer it.
 *
 * The other half of the old meaning — "the reply cannot carry a pause" — is
 * gone: {@link HostReply.awaiting} carries it now. An adapter that has not
 * implemented that terminal still gets its pause STORED (the store is not the
 * transport's business) and this refusal on the wire, naming the session it can
 * be answered on.
 */
class PauseNotCarriedError extends Error {
    code = 'ERR_PAUSE_NOT_CARRIED';
    /** The tool that asked, when the run recorded which one it was. */
    toolName;
    /** The session the paused run was stored under, when there was one. */
    sessionId;
    /** Whether the paused run was stored. `false` means it is gone. */
    stored;
    constructor(toolName, sessionId, stored = false) {
        super(`[hosting] the run paused to ask a person about ` +
            (toolName ? `'${toolName}'` : 'a tool') +
            `. The run did not fail — it is unfinished, waiting on an answer. ` +
            (stored
                ? `It IS stored: send another request for session '${String(sessionId)}' carrying ` +
                    `a 'decision' to continue it. This reply could not describe the question ` +
                    `because the host it arrived on does not implement reply.awaiting(); read the ` +
                    `pending ask from the session store, or serve on a host that has it.`
                : `Nothing was written: ` +
                    (sessionId === undefined
                        ? `this request carried no session id, so there is nowhere to store a paused ` +
                            `run and no later request that could ever answer it. Send a sessionId, or `
                        : `session '${sessionId}' still holds what it held before this request. `) +
                    `carry the pause yourself with agent.run() / agent.resume().`));
        this.name = 'PauseNotCarriedError';
        if (toolName !== undefined)
            this.toolName = toolName;
        if (sessionId !== undefined)
            this.sessionId = sessionId;
        this.stored = stored;
    }
}
exports.PauseNotCarriedError = PauseNotCarriedError;
/**
 * Thrown when a new message arrives for a session whose run is waiting on a
 * person's decision.
 *
 * The message is NOT run and the pause is NOT discarded — those are the two ways
 * this could have gone wrong. Answering the message would step over an
 * outstanding consent gate; dropping the paused run to make room for the message
 * would throw away work a person was asked about. So the request is refused, the
 * pending question is named, and the session sits exactly where it was.
 *
 * Answer it by sending the same session a request carrying
 * {@link HostRequest.decision}. A pause NO answer can reach any more — stored
 * before requests were named, its call changed after the ask, or an ask made
 * under rules the agent no longer runs — is not held this way: the door closes
 * it and runs the message (`answeredPause.ts`).
 */
class AwaitingDecisionError extends Error {
    code = 'ERR_AWAITING_DECISION';
    /** The session that is waiting. */
    sessionId;
    constructor(sessionId, pending) {
        const asked = pending.question ?? pending.ask?.question ?? pending.checkIn?.evidence.willDo ?? 'a decision';
        super(`[hosting] session '${sessionId}' is waiting on a person: ` +
            (pending.tool ? `'${pending.tool}' asked "${asked}"` : `"${asked}"`) +
            `. This message was NOT run and the paused run was NOT discarded — answering a ` +
            `new message would step over the question, and dropping the question to answer ` +
            `the message would throw away work somebody was asked to approve. ` +
            `Send this session a request carrying 'decision' (checkInApproved({ request, by }) / ` +
            `checkInDeclined(...) for a check-in or a middleware ask — \`request\` is the ` +
            `pending ask's checkIn or ask) to continue the run.`);
        this.name = 'AwaitingDecisionError';
        this.sessionId = sessionId;
        Object.defineProperty(this, 'pending', {
            value: pending,
            enumerable: false,
            writable: false,
            configurable: false,
        });
    }
}
exports.AwaitingDecisionError = AwaitingDecisionError;
/**
 * Thrown when a request carries a decision for a session that is not waiting on
 * one.
 *
 * Usually a duplicate delivery: the run was already continued, or already
 * answered, and the same decision arrived twice. Running it as an ordinary
 * message would put a raw approval into the conversation as if the user had
 * typed it, so it is refused by name instead.
 */
class NoPendingAskError extends Error {
    code = 'ERR_NO_PENDING_ASK';
    /** The session the decision was addressed to. */
    sessionId;
    constructor(sessionId) {
        super(`[hosting] this request carries a 'decision' but session '${sessionId}' is not ` +
            `waiting on one — nothing is paused. The run it answered has most likely already ` +
            `been continued (a duplicate delivery). Refusing rather than treating an approval ` +
            `as if a person had typed it into the conversation. Send it as 'input' if that is ` +
            `genuinely what you meant.`);
        this.name = 'NoPendingAskError';
        this.sessionId = sessionId;
    }
}
exports.NoPendingAskError = NoPendingAskError;
/**
 * Thrown when something is sent down a conversation that has already ended.
 *
 * A closed channel that accepts frames and drops them is the worst of the three
 * options: the sender believes the far side got it, the far side never did, and
 * nothing anywhere says so. Refusing by name is the only version of this that
 * leaves a trace.
 *
 * `onClose` is how you avoid meeting this — subscribe, and stop sending.
 */
class ConversationClosedError extends Error {
    code = 'ERR_CONVERSATION_CLOSED';
    /** Which adapter's door this was. */
    hostName;
    /** The session the conversation claimed, when it claimed one. */
    sessionId;
    constructor(hostName, sessionId) {
        super(`[hosting] this conversation on the '${hostName}' host has ended` +
            (sessionId !== undefined ? ` (session '${sessionId}')` : '') +
            `, so there is nowhere for that frame to go. Refusing rather than accepting it ` +
            `and dropping it — a send that silently goes nowhere looks identical to a send ` +
            `that worked. Subscribe with onClose(...) and stop sending when it fires, or open ` +
            `a new conversation.`);
        this.name = 'ConversationClosedError';
        this.hostName = hostName;
        if (sessionId !== undefined)
            this.sessionId = sessionId;
    }
}
exports.ConversationClosedError = ConversationClosedError;
/**
 * Thrown when a frame is bigger than the ceiling the adapter DECLARED.
 *
 * This is the other half of {@link ConversationLimits} — a declared ceiling
 * nothing enforces is a number in a doc comment. The port neither chunks nor
 * truncates, on purpose: how a message is split, numbered and reassembled is
 * the consumer's protocol question, and answering it inside the adapter would
 * answer it for every consumer at once. So the ceiling is visible, the refusal
 * names it, and the splitting happens above the port where the protocol lives.
 */
class FrameTooLargeError extends Error {
    code = 'ERR_FRAME_TOO_LARGE';
    /** Which adapter's door refused. */
    hostName;
    /** How big the frame was, in bytes of UTF-8. */
    bytes;
    /** The declared ceiling it crossed. */
    maxFrameBytes;
    constructor(hostName, bytes, maxFrameBytes) {
        super(`[hosting] this frame is ${bytes} bytes and the '${hostName}' host declares a ` +
            `maxFrameBytes of ${maxFrameBytes}. Refusing rather than truncating it or splitting ` +
            `it for you: how a message is chunked, numbered and put back together is your ` +
            `protocol's question, and an adapter that answered it would answer it for every ` +
            `consumer at once. Read host.conversationLimits and chunk above the port.`);
        this.name = 'FrameTooLargeError';
        this.hostName = hostName;
        this.bytes = bytes;
        this.maxFrameBytes = maxFrameBytes;
    }
}
exports.FrameTooLargeError = FrameTooLargeError;
/**
 * Thrown when a request body names a wire operation this host does not speak,
 * or names one and leaves out what it needs (an artifact op without a `ref`).
 *
 * The law it enforces: **a body that named an `op` never falls through to a
 * model turn.** A caller who typo'd `'artifact-head'` and silently got a
 * conversation turn — with the ref as garbage input — would be told nothing
 * and billed anyway, which is the accepted-and-silently-wrong failure this
 * refusal exists to prevent. Adapters answer it as that request's 400: the
 * request is what is wrong, and the same caller can send the right shape.
 */
class InvalidWireOpError extends Error {
    code = 'ERR_INVALID_WIRE_OP';
    constructor(detail) {
        super(`[hosting] ${detail}`);
        this.name = 'InvalidWireOpError';
    }
}
exports.InvalidWireOpError = InvalidWireOpError;
function artifactOpSpelling(op) {
    if (op === 'account')
        return 'answer-account';
    if (op === 'put' || op === 'delete' || op === 'list')
        return `artifacts.${op}(...)`;
    return `artifact-${op}`;
}
/**
 * Thrown when an artifact operation arrives with no session id.
 *
 * Artifact resolution is governed by the requesting session's identity —
 * a ref is redeemed under exactly the scope the run's tools minted it in, and
 * a request that names no session presents no scope to resolve under. (A run
 * served without a session scopes its artifacts to its own runId, which no
 * later request can name — so there is genuinely nothing this request could
 * ever redeem.) There is deliberately no bare-ref mode: an id alone opens
 * nothing, ever.
 */
class ArtifactSessionRequiredError extends Error {
    code = 'ERR_ARTIFACT_SESSION_REQUIRED';
    constructor(op) {
        super(`[hosting] ${artifactOpSpelling(op)} needs the session whose run minted the ref: refs resolve ` +
            `under the requesting session's identity-composed scope, and this request named no ` +
            `session. Send sessionId the same way the conversation's own requests do — in the ` +
            `body, the session header, or the session cookie. There is no bare-ref mode: a ref ` +
            `alone opens nothing.`);
        this.name = 'ArtifactSessionRequiredError';
    }
}
exports.ArtifactSessionRequiredError = ArtifactSessionRequiredError;
/**
 * Thrown when an artifact operation arrives and the agent serving this
 * session has no artifact store attached.
 *
 * The teaching refusal of the fail-closed capability (`ctx.artifacts` with no
 * store), spoken at the hosting door: it names the attach rather than
 * answering "not found" — a deployment gap and a missing ref are different
 * facts, and only one of them is the operator's to fix.
 */
class NoArtifactStoreError extends Error {
    code = 'ERR_NO_ARTIFACT_STORE';
    constructor(op) {
        super(`[hosting] ${artifactOpSpelling(op)} has no artifact store behind it: the agent serving this ` +
            `session was built without one, so no ref could ever resolve here. Pass ` +
            `\`artifacts\` to Agent.create({ ..., artifacts }) — inMemoryArtifacts() for an ` +
            `in-process store, fileArtifacts({ directory }) or sqliteArtifacts({ file }) for ` +
            `one that survives a restart — and the wire operations answer from it.`);
        this.name = 'NoArtifactStoreError';
    }
}
exports.NoArtifactStoreError = NoArtifactStoreError;
/**
 * Thrown when a ref does not resolve for the requesting session — missing,
 * expired, or minted under a scope this session's identity does not compose.
 *
 * **Deliberately one shape for all three.** Distinguishing "never existed"
 * from "another session's" would let a caller probe scopes it does not own;
 * the store already answers a wrong scope with "no data" rather than a
 * cross-tenant error, and this refusal keeps that ambiguity on the wire.
 * The screen renders a stated absence in place — the `present` result's
 * description snapshot exists exactly so an expired pane can still say what
 * is gone.
 */
class ArtifactNotFoundError extends Error {
    code = 'ERR_ARTIFACT_NOT_FOUND';
    /** The ref that did not resolve. */
    ref;
    constructor(ref) {
        super(`[hosting] artifact '${ref}' does not resolve for this session — missing, expired, ` +
            `swept, or never stored under this session's scope. Render its stated absence in ` +
            `place (a present(...) result carries a description snapshot for exactly this ` +
            `moment); re-run to regenerate the data.`);
        this.name = 'ArtifactNotFoundError';
        this.ref = ref;
    }
}
exports.ArtifactNotFoundError = ArtifactNotFoundError;
/**
 * Refused when one session already has as many artifact operations in flight
 * as the host allows (`standingAgent({ artifactOpsPerSession })`, default 8) —
 * `artifact-head`, `artifact-get` and `answer-account` counted together.
 *
 * Those ops are lane-free and turn admission does not see them, so without a
 * bound one session could keep the event loop every session shares busy with
 * payload reads. The refusal is for THIS session only (every other session is
 * served), it names no session id, and the same request succeeds once one of
 * the session's own requests has finished.
 */
class ArtifactOpsBusyError extends Error {
    code = 'ERR_ARTIFACT_OPS_BUSY';
    /** The per-session bound that was met. */
    limit;
    constructor(op, limit) {
        super(`[hosting] ${artifactOpSpelling(op)} was refused: the requesting session already has ${limit} ` +
            `artifact operations in flight, which is as many as this host allows at once. Retry ` +
            `when one has finished — redeem a few at a time — or raise ` +
            `standingAgent({ artifactOpsPerSession }).`);
        this.name = 'ArtifactOpsBusyError';
        this.limit = limit;
    }
}
exports.ArtifactOpsBusyError = ArtifactOpsBusyError;
/**
 * Refused when an `answer-account` request names a recording larger than the
 * host's ceiling (`standingAgent({ answerAccounts: { maxRecordingBytes } })`,
 * default 16 MiB).
 *
 * The account is computed on the event loop that serves every session, so its
 * worst single parse is bounded by a number the operator chose — and a
 * recording over it is refused BEFORE a byte of its payload is read (the size
 * comes from the ticket's `bytes`). Named rather than folded into the one
 * not-found: it reaches only a caller who already passed the ownership check,
 * so it tells them nothing about anybody else's records, and "too large to
 * explain here" is a different fact from "not available" — the operator can
 * raise the ceiling, and a person should not be told their answer's record is
 * gone when it is not.
 *
 * The message carries the ceiling and nothing from the recording.
 */
class RecordingTooLargeForAccountError extends Error {
    code = 'ERR_RECORDING_TOO_LARGE_FOR_ACCOUNT';
    /** The ceiling the recording is over, in bytes. */
    maxRecordingBytes;
    constructor(maxRecordingBytes) {
        super(`[hosting] this answer's recording is larger than the ${maxRecordingBytes} bytes this ` +
            `host explains, so no account was computed and none of it was read. The operator ` +
            `can raise the ceiling with standingAgent({ answerAccounts: { maxRecordingBytes } }); ` +
            `the recording itself is still redeemable with 'artifact-get'.`);
        this.name = 'RecordingTooLargeForAccountError';
        this.maxRecordingBytes = maxRecordingBytes;
    }
}
exports.RecordingTooLargeForAccountError = RecordingTooLargeForAccountError;
/**
 * Raised when an artifact RESOLVED and the reply cannot describe it — a host
 * without {@link HostReply.artifact}, or an `HttpWire` dialect without an
 * `artifact` body shape.
 *
 * The `PauseNotCarriedError` shape, for the other optional terminal: the
 * resolution itself succeeded and nothing about the store is wrong — it is
 * THIS REPLY that has no vocabulary for the answer. Named rather than
 * improvised, because an adapter inventing a body shape on the spot would be
 * a body no client was written against.
 */
class ArtifactNotCarriedError extends Error {
    code = 'ERR_ARTIFACT_NOT_CARRIED';
    /** The ref that resolved but could not be delivered. */
    ref;
    constructor(ref, hostName) {
        super(`[hosting] artifact '${ref}' resolved, but ` +
            (hostName !== undefined
                ? `the '${hostName}' host's wire has no artifact() body shape, `
                : `this host does not implement reply.artifact(), `) +
            `so the result cannot be described on this wire. Nothing is wrong with the ` +
            `artifact — serve on an adapter that carries artifact results (nodeHost does), or ` +
            `add the artifact body shape to the dialect.`);
        this.name = 'ArtifactNotCarriedError';
        this.ref = ref;
    }
}
exports.ArtifactNotCarriedError = ArtifactNotCarriedError;
/**
 * Refused when host code uses a turn's artifact hand-over
 * (`HostReply.turnArtifacts`) after that turn ended.
 *
 * The hand-over is live only while the hook runs: the composer awaits the
 * hook while the session's lane is held, waits for every operation the hook
 * started, then revokes the binding before the reply ends. A verb called
 * after that is refused by name rather than performed. The footprintjs
 * `ScopeFacade · assertLive` law, one layer up: a handle held past its stage
 * is refused, because a write made outside its turn lands on whatever run the
 * instance is serving by then — in the shared shape, another person's — and
 * makes the record lie.
 *
 * The fix is always the same: file INSIDE the hook (`await
 * turn.artifacts.put(...)`), and carry the ticket onward from there.
 */
class TurnArtifactsExpiredError extends Error {
    code = 'ERR_TURN_ARTIFACTS_EXPIRED';
    /** Which verb was called after the turn ended. */
    op;
    constructor(op) {
        super(`[hosting] turnArtifacts.${op}(...) was called after its turn ended. The hand-over ` +
            `is live only while reply.turnArtifacts runs: the composer awaits the hook, waits ` +
            `for every operation it started, then revokes the binding before the reply ends — ` +
            `a write made later would land on whatever run the agent is serving by then. File ` +
            `inside the hook (await turn.artifacts.${op}(...)) and carry the result onward from ` +
            `there.`);
        this.name = 'TurnArtifactsExpiredError';
        this.op = op;
    }
}
exports.TurnArtifactsExpiredError = TurnArtifactsExpiredError;
/**
 * Thrown by a verb of a binding `handle.artifactsForRequest` returned, called
 * after the binding was revoked: the instance it was bound to was retired
 * (evicted from the pool) or the composer was closed.
 *
 * The same law as {@link TurnArtifactsExpiredError}: a binding never outlives
 * the instance whose store and record it writes to — a write after that would
 * land on a stopped agent, and its fact on a record nobody holds. The
 * rejection is created already handled, so a floating late call cannot crash
 * the process. Ask the handle again for a fresh binding.
 */
class RequestArtifactsRevokedError extends Error {
    code = 'ERR_REQUEST_ARTIFACTS_REVOKED';
    /** Which verb was called after the revocation. */
    op;
    constructor(op) {
        super(`[hosting] artifacts.${op}(...) was called on a binding from ` +
            `handle.artifactsForRequest after it was revoked: the agent instance it was bound to ` +
            `was retired from the pool, or the host was closed. Call ` +
            `handle.artifactsForRequest(request) again for a binding to the instance serving the ` +
            `session now.`);
        this.name = 'RequestArtifactsRevokedError';
        this.op = op;
    }
}
exports.RequestArtifactsRevokedError = RequestArtifactsRevokedError;
/**
 * An already-computed preview, so {@link UnreadableEnvelopeError.withSession}
 * can copy a refusal without being handed the stored bytes a second time. Not
 * exported: nothing outside this file should be able to hand-write a preview.
 */
class StoredPreview {
    text;
    constructor(text) {
        this.text = text;
    }
}
/**
 * Thrown when a store hands back something that is **present but unreadable**
 * where a `CheckpointEnvelope` should be.
 *
 * The law, in the words of the field report that bought it:
 *
 * > *An unreadable stored conversation and an absent one are different facts,
 * > and only one of them is safe to answer with a fresh start.*
 *
 * Absent is ordinary — a new session has no conversation, and answering it
 * fresh is exactly right. Unreadable is not ordinary: a conversation EXISTS,
 * somebody is in the middle of it, and starting fresh over the top of it looks
 * identical to the happy path from the outside. Nobody finds that until a
 * deployment boundary hands a user a stranger's blank slate. So this refuses,
 * loudly, naming the session — the one thing a silent `undefined` could never
 * do.
 *
 * It extends `TypeError` because that is what it always was: the refusal gained
 * a name, a `code` and the session it is about, but a caller who was already
 * catching a `TypeError` from a reader keeps working.
 *
 * `storedPreview` quotes at most 64 characters (`STORED_PREVIEW_LIMIT`, in
 * `lib/storedPreview` — one cap, shared by every adapter that has to describe
 * bytes it could not read) — enough to recognise a mangled encoding at a
 * glance, never the conversation itself.
 */
class UnreadableEnvelopeError extends TypeError {
    code = 'ERR_UNREADABLE_ENVELOPE';
    /** The session those bytes were stored under, when the refuser knows it. */
    sessionId;
    /** A short, deliberately truncated rendering of what the store handed back. */
    storedPreview;
    constructor(stored, sessionId) {
        const preview = stored instanceof StoredPreview ? stored.text : (0, storedPreview_js_1.previewStored)(stored);
        super(`[hosting] ` +
            (sessionId === undefined
                ? `a stored session is present but unreadable`
                : `session '${sessionId}' has a STORED conversation this runtime cannot read`) +
            `. An unreadable stored conversation and an absent one are different facts, and ` +
            `only one of them is safe to answer with a fresh start — so this refuses instead ` +
            `of starting over on top of a conversation that exists. ` +
            `What the store handed back looks like: ${preview}. ` +
            `Only a short prefix is ever quoted here — the rest of those bytes is the ` +
            `conversation. ` +
            `Expected { format, data, savedAt } as written by toEnvelope() / toPausedEnvelope(). ` +
            `A store that keeps its own encoding must decode back to that object before ` +
            `handing it over.`);
        this.name = 'UnreadableEnvelopeError';
        if (sessionId !== undefined)
            this.sessionId = sessionId;
        this.storedPreview = preview;
    }
    /**
     * The same refusal, naming the session — for a reader that knew the bytes
     * were unreadable but not whose conversation they were.
     *
     * Returns a copy rather than mutating: an error already thrown past somebody
     * is a fact about a moment, and editing it under them is how two stack traces
     * end up disagreeing about what happened.
     */
    withSession(sessionId) {
        if (this.sessionId !== undefined)
            return this;
        const named = new UnreadableEnvelopeError(new StoredPreview(this.storedPreview), sessionId);
        named.cause = this;
        return named;
    }
}
exports.UnreadableEnvelopeError = UnreadableEnvelopeError;
/**
 * The caller could not be identified, and this door was configured to insist.
 *
 * **The message never contains the token.** Not truncated, not hashed, not "the
 * first eight characters". A bearer token in a refusal is a bearer token in the
 * reply body, in the log line, in the trace and in every sink attached — one
 * echo and the credential has been published. What travels is
 * {@link IdentityFailureClass} and the sentence that tells the caller what to
 * do about it.
 */
class IdentityNotVerifiedError extends Error {
    code = 'ERR_IDENTITY_NOT_VERIFIED';
    /** What went wrong, in the one vocabulary. Never the token. */
    failure;
    /** Did the request also NAME a user it could not prove? The impersonation
     *  shape, kept as a fact so a sink can count it separately. */
    claimedUser;
    constructor(failure, claimedUser) {
        super(`[hosting] the caller was not identified: ${sentenceFor(failure, claimedUser)}`);
        this.name = 'IdentityNotVerifiedError';
        this.failure = failure;
        this.claimedUser = claimedUser;
    }
}
exports.IdentityNotVerifiedError = IdentityNotVerifiedError;
/** The teaching half of {@link IdentityNotVerifiedError}, per class. */
function sentenceFor(failure, claimedUser) {
    switch (failure) {
        case 'no-token':
            return claimedUser
                ? `this request named a user but presented no credential, and this host verifies ` +
                    `identity. A user id in a header is a string anyone can send — it is accepted only ` +
                    `beside a token that proves it. Send 'Authorization: Bearer <token>', or drop the ` +
                    `user id and call anonymously (allowed only when the host was built with ` +
                    `identity: { verify, allowAnonymous: true }).`
                : `this host verifies identity and the request presented no credential. Send ` +
                    `'Authorization: Bearer <token>'. If some callers here are genuinely anonymous, ` +
                    `build the host with identity: { verify, allowAnonymous: true } — anonymous ` +
                    `requests may then carry no user id at all.`;
        case 'expired':
            return (`the presented token has expired, or the sign-in it named has ended. Refresh the ` +
                `token at your identity provider (or sign in again) and retry; nothing is wrong with ` +
                `this host.`);
        case 'two-credentials':
            return (`the request presented a token AND a sign-in. One credential per request: which of ` +
                `the two to believe is not a question this door answers. Send one of them.`);
        case 'not-yet-valid':
            return (`the presented token is not valid yet (its 'nbf' is in the future). That is ` +
                `usually clock skew between the issuer and this host rather than a bad token.`);
        case 'wrong-audience':
            return (`the presented token was minted for a different audience — it is a valid token ` +
                `for somebody else's API. Request one for this API's audience.`);
        case 'wrong-issuer':
            return (`the presented token came from an issuer this host does not accept. Check which ` +
                `identity provider the host was configured with.`);
        case 'claimed-another-user':
            return (`the presented token identifies one user and the request named a different one. ` +
                `One of those two facts is untrue and this door cannot know which, so it serves ` +
                `neither. Send the user id the token proves, or send none.`);
        case 'unverifiable':
            return (`the presented credential could not be verified — the signature did not check ` +
                `out, no key matched it, or it is not a token this host reads. Deliberately one ` +
                `answer for all three: naming which would tell whoever is probing what to fix.`);
        case 'not-a-user-token':
            return (`the presented token is valid but does not stand for a person: it is an ` +
                `application's own token, or it lacks the scope this API requires of a person's ` +
                `token. Sign in as a person through a client this API lists, and request this ` +
                `API's scope.`);
        case 'wrong-client':
            return (`the presented token was obtained by a client this host does not accept, or it ` +
                `does not say which client obtained it. Request the token through one of the ` +
                `clients this API was configured to list.`);
        case 'roles-unknown':
            return (`the presented token does not carry the caller's roles: its roles claim was ` +
                `replaced by a pointer to somewhere else (the group-overage shape, above about 200 ` +
                `groups). Unknown is not none, so the caller is refused rather than served with no ` +
                `roles. The deployment should send app roles (the 'roles' claim) instead of groups.`);
    }
}
/**
 * The verifier itself could not do its job — its key set was unreachable, its
 * introspection endpoint timed out.
 *
 * A DIFFERENT fact from a token that failed, and the difference is who is at
 * fault: this is the deployment's outage, not the caller's bad credential, and
 * answering it with a 401 would send every client off to re-authenticate
 * against an identity provider that is already down. It maps to 503.
 */
class VerifierUnavailableError extends Error {
    code = 'ERR_IDENTITY_VERIFIER_UNAVAILABLE';
    /** Which verifier could not answer — its name, never its configuration. */
    verifier;
    constructor(verifier, detail) {
        super(`[hosting] identity could not be verified because the '${verifier}' verifier could not ` +
            `answer${detail !== undefined ? ` (${detail})` : ''}. This is an outage on this side, ` +
            `not a bad credential — the caller's token was never judged. Retry once the identity ` +
            `provider is reachable.`);
        this.name = 'VerifierUnavailableError';
        this.verifier = verifier;
    }
}
exports.VerifierUnavailableError = VerifierUnavailableError;
// ─── Admission (9.26.0) ──────────────────────────────────────────────
/**
 * An admission policy refused this request before any work started.
 *
 * The policy authors the sentence — this class only carries it — because the
 * limit and its reset are the operator's facts, not the library's. What this
 * class guarantees is the SHAPE: a refusal that names a limit is a refusal a
 * client can act on, and one that says "denied" is a support ticket.
 */
class AdmissionRefusedError extends Error {
    code = 'ERR_ADMISSION_REFUSED';
    /** WHO was refused, when the request identified anybody. Never a token. */
    userId;
    constructor(reason, userId) {
        super(`[hosting] this request was not admitted: ${reason}`);
        this.name = 'AdmissionRefusedError';
        if (userId !== undefined)
            this.userId = userId;
    }
}
exports.AdmissionRefusedError = AdmissionRefusedError;
// ─── Session history ops (9.26.0) ────────────────────────────────────
/**
 * A session-history operation arrived at a door that does not verify identity.
 *
 * Listing a person's conversations is answering "which sessions belong to
 * you?", and a door that reads WHO from an unverified header would answer that
 * for anyone who guesses a user id — enumeration with a friendly interface.
 * So the ops are refused outright rather than served under a claimed name.
 */
class SessionOpNeedsIdentityError extends Error {
    code = 'ERR_SESSION_OP_NEEDS_IDENTITY';
    /** The op that was refused. */
    op;
    constructor(op) {
        super(`[hosting] '${op}' needs a VERIFIED caller and this host does not verify identity. ` +
            `Listing or reading a person's conversations under a user id nobody proved is ` +
            `enumeration: anyone who guesses an id reads somebody's transcript. Build the host ` +
            `with identity: { verify: jwksIdentity({ jwksUrl, issuer, audience }).verify } — or ` +
            `any IdentityVerifier — and these ops answer for the caller the token proves.`);
        this.name = 'SessionOpNeedsIdentityError';
        this.op = op;
    }
}
exports.SessionOpNeedsIdentityError = SessionOpNeedsIdentityError;
/**
 * A session-history operation arrived at a session store that keeps no
 * owner index.
 *
 * The two members are OPTIONAL on the port on purpose — most stores are a
 * key/value map and owe nobody a secondary index — so this refusal names the
 * store's limitation instead of pretending the answer is "you have no
 * sessions". An empty list and an unanswerable question are different facts,
 * and only one of them is safe to render as "nothing here".
 */
class SessionIndexUnavailableError extends Error {
    code = 'ERR_SESSION_INDEX_UNAVAILABLE';
    op;
    constructor(op, missing) {
        super(`[hosting] '${op}' needs the session store to answer '${missing}', and the store this ` +
            `host was given does not implement it. That is a limitation of the store, not an ` +
            `empty result — reporting "no sessions" here would be an answer nobody could ` +
            `distinguish from the truth. memorySessions() and sqliteSessions({ file }) both ` +
            `implement it; a custom SessionLifecycle can add ${missing} additively.`);
        this.name = 'SessionIndexUnavailableError';
        this.op = op;
    }
}
exports.SessionIndexUnavailableError = SessionIndexUnavailableError;
/**
 * Retention was asked of a session store that has no answer for it (9.42.0).
 *
 * `retention` is OPTIONAL on the port for the same reason the two index
 * members are — most stores are a key/value map and owe nobody a way to expire
 * one — so this refusal names the store's limitation rather than doing
 * nothing quietly. That distinction is the whole point here: an unanswerable
 * question and a sweep that deleted nothing look identical from a cron job's
 * exit code, and a deployment can run for a year believing conversations are
 * being expired because a call returned without complaining.
 *
 * It names which stores DO implement it, because the answer to "my store
 * cannot do this" is usually "then use one that can", and a refusal that makes
 * somebody go and find that out is a refusal that gets caught and ignored. The
 * two it names by hand are the two that ship in this folder; the cloud
 * adapters are pointed at rather than listed, because a hand-maintained list
 * of them inside a PORT is both a vendor name where none belongs and a list
 * that goes stale the release after somebody adds one.
 *
 * No identity material, like every refusal here: retention is about a store,
 * and nothing about who was signed in belongs in it.
 */
class SessionRetentionUnavailableError extends Error {
    code = 'ERR_SESSION_RETENTION_UNAVAILABLE';
    /** What was being attempted, in the caller's words. */
    purpose;
    constructor(purpose) {
        super(`[hosting] ${purpose} needs the session store to answer 'retention()', and the store ` +
            `this was given does not implement it. That is a limitation of the store, not a ` +
            `conversation that has already been expired — a sweep that quietly did nothing is ` +
            `indistinguishable from retention that is working, which is how a deployment keeps ` +
            `every conversation it ever had for a year. memorySessions() and ` +
            `sqliteSessions({ file }) both implement it, as does every cloud session adapter in ` +
            `this package whose backend can express an expiry — each states which in its own ` +
            `docs. A custom SessionLifecycle can add retention() additively, with nothing else ` +
            `changed: an absent member refuses like this one, it never silently keeps data.`);
        this.name = 'SessionRetentionUnavailableError';
        this.purpose = purpose;
    }
}
exports.SessionRetentionUnavailableError = SessionRetentionUnavailableError;
/**
 * The ONE answer for a session the caller may not have — read OR continued.
 *
 * A session that does not exist, one that belongs to somebody else, and one
 * whose stored conversation names no owner all end here, byte-identical but
 * for the id the caller already knew. Told apart, they would be an oracle for
 * which session ids are real — the same law
 * {@link ArtifactNotFoundError} follows, for the same reason.
 *
 * Raised by BOTH doors that open a session at a verifying host: the two
 * session-history ops, and an ordinary turn that named somebody else's session
 * (9.26.0). One refusal, so the answer cannot depend on which door a caller
 * knocked at — the mistake this class of hole is always made of.
 */
class SessionNotFoundError extends Error {
    code = 'ERR_SESSION_NOT_FOUND';
    /** The id that was asked for — the caller's own string, nothing learned. */
    sessionId;
    constructor(sessionId) {
        super(`[hosting] session '${sessionId}' is not one you can open. Deliberately one answer for ` +
            `"no such session", "somebody else's session" and "a session nobody signed for": ` +
            `distinguishing them would tell a caller which ids are real. The ids you can read — ` +
            `and continue — are exactly the ones session-list hands back.`);
        this.name = 'SessionNotFoundError';
        this.sessionId = sessionId;
    }
}
exports.SessionNotFoundError = SessionNotFoundError;
/**
 * A `persist` would have left the ownership index naming one person and the
 * stored conversation naming another (9.36.1).
 *
 * ── The split brain this exists to prevent ──────────────────────────────────
 * The write-once rule protects the INDEX and said nothing about the PAYLOAD.
 * Two writers signing the same fresh session therefore produced a store where
 * `ownerOf(session)` answered the FIRST writer and the stored envelope carried
 * the SECOND writer's whole conversation — and the envelope carries its own
 * identity. The first writer lists the session, opens it, and reads somebody
 * else's conversation. That is not a race in one backend: it was reproduced in
 * a live trial and then found, by inspection, in every store implementing this
 * contract, because the flaw was in the contract rather than in any of them.
 *
 * So the rule gained its missing half: **a different non-empty identity is
 * refused, and an absent one is allowed.** A leaner turn claims nobody and
 * cannot contradict anybody — the contract explicitly blesses it — while a
 * turn signed by somebody else is a contradiction, and a store that stored it
 * would be a store where ownership is decided by who wrote last.
 *
 * ── What this refusal will not tell you ─────────────────────────────────────
 * Neither name. Not the owner, not the caller, not a line of either
 * conversation. An error is read by whoever provoked it, and a refusal that
 * names the person who owns a session is an oracle for who is signed in — the
 * same law {@link SessionNotFoundError} and {@link ArtifactNotFoundError}
 * follow. The session id is here because it is the caller's own string, which
 * is the one thing the message teaches nobody anything by repeating.
 */
class SessionOwnershipConflictError extends Error {
    code = 'ERR_SESSION_OWNERSHIP_CONFLICT';
    /** The id that was written to — the caller's own string, nothing learned. */
    sessionId;
    constructor(sessionId) {
        super(`[hosting] refusing to persist session '${sessionId}': it already belongs to a ` +
            `different signed-in person than the one this conversation is signed by. ` +
            `Ownership is established by the FIRST turn that signs for a conversation and no ` +
            `later write moves it, so storing this envelope would leave the ownership index ` +
            `naming one person and the stored conversation naming another — and the person the ` +
            `index names would open it and read the other one's conversation. ` +
            `Neither name appears in this message on purpose. ` +
            `What to do: write this turn under a NEW session id, or continue the existing one ` +
            `under the identity that already owns it. Configuring 'identity' on standingAgent ` +
            `refuses a foreign turn at the door, before it ever reaches the store.`);
        this.name = 'SessionOwnershipConflictError';
        this.sessionId = sessionId;
    }
}
exports.SessionOwnershipConflictError = SessionOwnershipConflictError;
/**
 * A session-history result resolved and the reply cannot describe it.
 *
 * The {@link ArtifactNotCarriedError} shape, for the other new terminal.
 */
class SessionsNotCarriedError extends Error {
    code = 'ERR_SESSIONS_NOT_CARRIED';
    op;
    constructor(op, hostName) {
        super(`[hosting] '${op}' resolved, but ` +
            (hostName !== undefined
                ? `the '${hostName}' host's wire has no sessions() body shape, `
                : `this host does not implement reply.sessions(), `) +
            `so the result cannot be described on this wire. Nothing is wrong with the session ` +
            `store — serve on an adapter that carries session results (nodeHost does), or add ` +
            `the sessions body shape to the dialect.`);
        this.name = 'SessionsNotCarriedError';
        this.op = op;
    }
}
exports.SessionsNotCarriedError = SessionsNotCarriedError;
/**
 * Assert that a host can do something, and throw a corrective error naming the
 * adapter when it cannot.
 *
 * This is the feature-detection law with teeth: capabilities are read, never
 * assumed, and asking for one that is absent tells you which adapter you are
 * actually holding rather than failing quietly somewhere downstream.
 *
 * Takes either port, because both declare the same two facts — who they are and
 * what they can do — and a caller holding a conversation-only host is entitled
 * to the same answer as one holding a request host.
 *
 * @example
 *   requireCapability(host, 'streaming'); // throws unless this host streams
 *
 *   // or branch instead of insisting:
 *   if (host.capabilities.includes('streaming')) { ... }
 */
/**
 * Thrown when a request body exceeds the ceiling the host was given.
 *
 * A body is memory this process pays for while somebody else fills it, so a
 * host with a ceiling stops reading at it rather than discovering the limit as
 * an out-of-memory kill. The read is abandoned at the byte that crossed the
 * line — nothing further is buffered, and the bytes already read are dropped.
 *
 * There is no default ceiling: see `HttpHostOptions.maxBodyBytes` for why the
 * absence is deliberate and what it costs.
 */
class RequestTooLargeError extends Error {
    limitBytes;
    code = 'ERR_REQUEST_TOO_LARGE';
    constructor(limitBytes, hostName) {
        super(`[hosting] the '${hostName}' host refuses a request body over ${String(limitBytes)} ` +
            `bytes. Raise maxBodyBytes if this deployment genuinely carries larger requests.`);
        this.limitBytes = limitBytes;
        this.name = 'RequestTooLargeError';
    }
}
exports.RequestTooLargeError = RequestTooLargeError;
/**
 * A dialect's refusal of THIS request — its own status, its own code.
 *
 * `readRequest` is a wire's one chance to look at a body before a turn is paid
 * for, and some dialects can tell from the shape alone that they cannot serve
 * it: an input item kind this dialect does not carry, a field whose type is
 * wrong. Throwing this says so with a status the caller can act on, instead of
 * a 500 that reads as "the server broke" for a request the caller could fix.
 *
 * The distinction it keeps: this is the REQUEST being wrong. A dialect that
 * throws anything else from `readRequest` still gets that request's 500, which
 * is the honest answer when the dialect itself is what failed.
 *
 * @example  A dialect refusing an input kind it does not carry
 *   throw new WireRequestRefusal(
 *     'unsupported_input',
 *     'image input is not carried by this dialect',
 *   );
 */
class WireRequestRefusal extends Error {
    code;
    status;
    constructor(code, detail, 
    /** HTTP status for this refusal. Default 400 — the request is what is wrong. */
    // eslint-disable-next-line @typescript-eslint/no-inferrable-types -- Keep the public parameter-property type explicit in the generated declaration.
    status = 400) {
        super(`[hosting] ${detail}`);
        this.code = code;
        this.status = status;
        this.name = 'WireRequestRefusal';
    }
}
exports.WireRequestRefusal = WireRequestRefusal;
// ─── The door guard ──────────────────────────────────────────────────
//
// Four refusals the HOST makes before any handler sees a request — see
// `doorGuard.ts` for the rules and `docs/design/2026-09-door-hardening.md` for
// the threat. Each carries its own HTTP `status`, because the conversation door
// answers a pre-101 socket by hand and an application route calling the same
// guard answers with its own response writer; a status only `httpHost` could
// look up would leave both of them guessing.
//
// **None of them ever repeats what the caller sent.** Not the content type,
// not the Origin, not the Host, not one character of a session id — the
// refusal names the rule and the option that changes it, nothing else. A
// session id in a refusal is a session id in every log line and trace span the
// refusal reaches, which is the exposure the fourth one exists to close.
/**
 * A request that changes something did not say `content-type: application/json`.
 *
 * The door is a JSON API, and that is only half the reason. The other half is
 * the web: a page on ANY site can make a browser send `text/plain`,
 * `application/x-www-form-urlencoded` or `multipart/form-data` — or no content
 * type at all — without asking the server first (a CORS "simple" request). A
 * browser must ask first (a preflight, which this host never approves) before
 * it sends `application/json` across origins. Requiring it is what turns a
 * forged request into one the browser refuses to send.
 */
class UnsupportedMediaTypeError extends Error {
    code = 'ERR_UNSUPPORTED_MEDIA_TYPE';
    /** 415 Unsupported Media Type. */
    status = 415;
    constructor(hostName) {
        super(`[hosting] the '${hostName}' host takes JSON: a request that changes anything must say ` +
            `content-type: application/json. Other content types are refused because a web page on ` +
            `any site can make a browser send them without asking first — this is the cross-site ` +
            `request defence, not a parser preference. Send the header; an integration that ` +
            `genuinely cannot is named on the host with requireJsonContentType: false.`);
        this.name = 'UnsupportedMediaTypeError';
    }
}
exports.UnsupportedMediaTypeError = UnsupportedMediaTypeError;
/**
 * A browser request came from a page whose origin this door does not allow —
 * or from an opaque origin (`Origin: null`), which any sandboxed frame on any
 * site can produce and which is therefore never allowed — or the browser said
 * outright that it was sent cross-site.
 *
 * Only BROWSERS send `Origin` (on every request that changes something, and on
 * every WebSocket handshake), so a request without one is a script or a
 * server — unless the same request carries the browser's own `Sec-Fetch-Site:
 * cross-site`, which is how a stripped `Origin` still gets caught.
 */
class OriginNotAllowedError extends Error {
    code = 'ERR_ORIGIN_NOT_ALLOWED';
    /** 403 Forbidden. */
    status = 403;
    /** Which browser fact refused it. */
    rule;
    constructor(hostName, rule) {
        super(`[hosting] the '${hostName}' host refuses a request sent by a web page from an origin it ` +
            `does not allow. ` +
            (rule === 'listed'
                ? `Only the origins listed in allowedOrigins may drive it from a browser — add the ` +
                    `page's origin there if it should.`
                : rule === 'same-host'
                    ? `With allowedOrigins unset, a page may drive it only from this host's own name ` +
                        `(the request's Host, or X-Forwarded-Host from a proxy). Serve the page from this ` +
                        `host, or list its origin in allowedOrigins.`
                    : `The browser marked it cross-site (Sec-Fetch-Site) and its Origin is not one ` +
                        `allowedOrigins lists. If this is your own page, a reverse proxy in front of this ` +
                        `host is stripping or rewriting headers: it must forward Origin and Sec-Fetch-* ` +
                        `unchanged. Otherwise list the page's origin in allowedOrigins.`));
        this.name = 'OriginNotAllowedError';
        this.rule = rule;
    }
}
exports.OriginNotAllowedError = OriginNotAllowedError;
/**
 * A request named a Host this door was not configured to answer for.
 *
 * The DNS-rebinding defence: a page on an attacker's domain re-points its OWN
 * name at this server's address, and from then on the browser treats it as
 * same-origin with the door — so every other check passes, and the Host header
 * (which no page can set) is the one fact that still names the attacker.
 * 421 is the status HTTP defines for exactly this (RFC 9110 §15.5.20: a target
 * "that does not match an origin for which the server has been configured").
 */
class HostNotAllowedError extends Error {
    code = 'ERR_HOST_NOT_ALLOWED';
    /** 421 Misdirected Request. */
    status = 421;
    /**
     * Where the list came from: `'listed'` — the deployment's own
     * `allowedHosts`; `'loopback-default'` — nobody set one, and the door is
     * bound to a loopback address, so it answers the loopback names only.
     */
    rule;
    constructor(hostName, rule = 'listed') {
        super(rule === 'loopback-default'
            ? `[hosting] the '${hostName}' host is bound to a loopback address, so with allowedHosts ` +
                `unset it answers only localhost, 127.0.0.1 and [::1] — the defence against a page ` +
                `on another domain re-pointing its own name at this machine (DNS rebinding). Behind ` +
                `a reverse proxy that forwards the public name in Host (nginx ` +
                `proxy_set_header Host $host), list that name in allowedHosts.`
            : `[hosting] the '${hostName}' host is not configured to answer for the name this ` +
                `request was sent to. It answers only the names in allowedHosts — the defence ` +
                `against a page on another domain re-pointing its own name at this server (DNS ` +
                `rebinding). Reach it by one of its configured names, or add this one to ` +
                `allowedHosts.`);
        this.name = 'HostNotAllowedError';
        this.rule = rule;
    }
}
exports.HostNotAllowedError = HostNotAllowedError;
/**
 * A session id the door will not carry: empty, longer than the ceiling, or
 * holding anything but visible ASCII (0x21–0x7E).
 *
 * A session id is caller data that travels everywhere the conversation does —
 * the store's key, every log line, every exported trace span of every turn.
 * Unbounded, one caller with no credentials could make every span of a turn
 * carry megabytes; with a control, a line separator or a bidi override, it
 * could forge or disguise a line in any log that prints it; with non-ASCII,
 * the same id read from a header (latin1) and from a body (UTF-8) would be two
 * different conversations. So it is bounded at the door, before any of that
 * happens. The refusal says how long the id was, never what it said.
 */
class InvalidSessionIdError extends Error {
    code = 'ERR_INVALID_SESSION_ID';
    /** 400 Bad Request — the caller can send a valid id and continue. */
    status = 400;
    /** Which rule the id broke. Never the id. */
    reason;
    constructor(reason, hostName, length, limit) {
        super(reason === 'too-long'
            ? `[hosting] the '${hostName}' host refuses a session id of ${String(length)} ` +
                `characters; the ceiling is ${String(limit)}. A session id travels into every ` +
                `stored row, log line and trace span of the conversation, so it is bounded at the ` +
                `door. Send a shorter one — a UUID is 36.`
            : reason === 'empty'
                ? `[hosting] the '${hostName}' host refuses an empty session id: every caller that ` +
                    `sent one would share a single conversation. Send an id, or leave the field out ` +
                    `to have no session.`
                : `[hosting] the '${hostName}' host refuses a session id containing a character ` +
                    `outside visible ASCII (0x21–0x7E). A session id is printed wherever the ` +
                    `conversation is logged and read from headers and bodies alike, so it is limited ` +
                    `to characters every one of them spells the same way — a UUID fits.`);
        this.name = 'InvalidSessionIdError';
        this.reason = reason;
    }
}
exports.InvalidSessionIdError = InvalidSessionIdError;
function requireCapability(host, capability) {
    if (host.capabilities.includes(capability))
        return;
    const has = host.capabilities.length > 0 ? host.capabilities.join(', ') : 'none';
    throw new Error(`[hosting] the '${host.name}' host does not support '${capability}'. ` +
        `It reports: ${has}. Feature-detect with ` +
        `host.capabilities.includes('${capability}') and fall back, or serve on a host ` +
        `that has it — capabilities are read from the adapter, never assumed from its name.`);
}
exports.requireCapability = requireCapability;
//# sourceMappingURL=errors.js.map