/**
 * receipt — what the model was actually handed, recorded at the stop where it
 * was handed it.
 *
 * Role:  Fold. This module owns the receipt's SHAPE and the one function that
 *        mints one (`buildReceipt`); every chart's request assembly calls it
 *        and commits the result under the scope key `receipt`, so the record
 *        lands in the call-llm bundle that already exists. Nothing here
 *        executes, reads scope, or emits.
 * Reads: its arguments.
 * Emits: N/A.
 *
 * ── THE FIRST LAW: NO AUTHORITY OMISSIONS ──────────────────────────────────
 * A receipt never names — and never counts — what a caller's ROLE was not
 * allowed to see. Committed state is readable by the trace toolpack's
 * debugging tools, so a receipt carrying `hiddenSkillIds`, or even "3 skills
 * withheld", would turn a permission decision into a leak path: the thing the
 * check exists to hide, restated one layer down where nobody is checking. A
 * reader that needs those ids reads them from the fold (`hiddenSkillIds` is
 * committed state, and reading it is governed where it is read), not from
 * here.
 *
 * ATTENTION omissions are a different fact and MAY ride the receipt:
 * `omittedForAttention` says a budget dropped content before this request was
 * composed. Nobody was refused anything — the request simply did not fit — and
 * a reader chasing "why did it not know that?" needs to see it. Even there the
 * turns are hashed, never quoted, for the same reason everything else on a
 * receipt is.
 *
 * WHO SUPPLIES IT (9.93.0). Measured on 9.88.0 and again on 9.91.0, no chart
 * supplied it, and the reason recorded was the slots: a slot writes its drops
 * to `slotCompositions` inside its own subflow, no boundary bubbles that record
 * out — and, underneath that, the built-in slots never DROP anything
 * (`slots/helpers.ts` · `slotOverflow` answers over-budget with
 * `planAction: 'none'`). That was true and it was not the whole measurement.
 * The agent chart's WINDOW STAGE (`stages/window.ts`) evicts turns for budget
 * on every run whose strategy engages — `context.evicted` fires with
 * `reason: 'budget'` — and it never wrote the field. Since 9.93.0 it does: the
 * window files what left at this iteration's head on an in-memory handle
 * (`window/evictedTurns.ts`), the same seam the compaction meter already
 * crosses, and the call-llm mint hands those turns here. NOT a scope read:
 * a tracked read of `compactions` — absent on every run without a window —
 * would put a phantom context source on every call-llm stage, the defect
 * measured when `slotCompositions` was read for one release.
 *
 * Each hash is the evicted turn's OWN digest — `messageDigestInput`, the same
 * formula `messages.entries[].hash` uses — so a dropped turn on epoch k's
 * receipt pairs by hash with the turn as it was served on an earlier epoch's.
 * That pairing is a law a test can check, and `gap-sentences.test.ts` does.
 *
 * ── THE SECOND LAW: HASHES AND REFERENCES, NEVER BYTES ─────────────────────
 * A receipt is a fingerprint, not a copy. It records that a piece of a given
 * shape and size was in a given position, and nothing about what it said. The
 * bytes are already governed — `recordSystemPrompt` is opt-in for exactly this
 * reason, redaction patterns scrub the committed mirror, and a window strategy
 * decides what survives. A receipt that carried content would quietly reopen
 * all three.
 *
 * ── THE THIRD LAW: HASHES ARE SALTED WITH THE RUN ID ───────────────────────
 * `hash = sha256(runId + '\u001f' + content)`, first 16 hex characters. The
 * salt is what stops a short piece from being fingerprinted ACROSS runs: an
 * unsalted hash of a one-line system prompt, a tool schema, or a two-word user
 * turn is a dictionary lookup away from being read back, and the receipt is
 * committed state that travels in recordings. Salted per run, the same
 * sentence in two runs has two hashes, so the fingerprint answers "is this the
 * same as THAT piece of THIS run?" — which is the only question the
 * conformance law asks — and cannot be matched across runs.
 *
 * Hashes are NOT redacted, and the salt is what limits THEM — not an
 * assumption that something scrubbed them. It does not keep a GUESSABLE value
 * secret from someone holding the recording, which carries the `runId`: a
 * short value can be confirmed by hashing guesses (a named limit of an
 * agent's `redact`, `src/redaction/README.md`). It says nothing about the rest
 * of the recording, and this law must not be read as if it did.
 *
 * WHAT A RECORDING ACTUALLY CONTAINS. An agent run is redacted only when it
 * was covered by a policy — `Agent.create({ redact })`, a composed member's,
 * or one a calling tool handed down. Without one, a secret in a system
 * prompt is in the commit log and in `servedAt(k).system.text`, verbatim.
 * With one, footprintjs scrubs at COMMIT time, so a selected key never enters
 * the commit log, and `getLastSnapshot()` serves the redacted mirror
 * (`servableSnapshot`) — the rebuilt view reads a redacted piece as ABSENT
 * (`time-travel/servedView.ts` never prints a placeholder as if the request had carried
 * it), so the view's hash no longer matches this receipt's (the receipt hashed
 * what the model was served). The same holds for `flowchartAsTool({ redact })` /
 * `runbookAsTool({ redact })` on an inner run. A snapshot taken with
 * `redact: true` also omits `initialState`, so folding it reports
 * `basis: 'log-only'` and `time-travel/servedView.ts` raises `no-fold-base` rather than
 * rebuilding a short view in silence. Treat an unredacted recording as the
 * plaintext it is; the salt protects the fingerprints, and only the
 * fingerprints.
 *
 * ── THE FOURTH LAW: THE RECEIPT IS MINTED AT THE PROVIDER PORT ─────────────
 * Everything on a receipt describes the request AS HANDED TO
 * `LLMProvider.complete` — the port, not the wire. Anything downstream of that
 * boundary is outside what this record can witness: a decorated provider, a
 * vendor adapter's own serializer, a consumer's hand-written `complete`, or the
 * vendor's server-side defaults. `RECEIPT_BOUNDARY` states it in one sentence
 * for a renderer to print, and `time-travel/servedView.ts` · `SERVED_GAPS` names the two
 * places a reader is most likely to mistake it for wire-level proof.
 */
import type { LLMMessage, LLMToolSchema } from '../../../provider-contract/types.js';
import type { ContextRole, ContextSlot, ContextSource } from '../../../events/types.js';
import { type RequestMeasurement } from './requestMeasurement.js';
import type { ReceiptDigests } from './receiptDigests.js';
export type { RequestMeasurement, RequestJsonSize } from './requestMeasurement.js';
/** What {@link receiptPieces} needs from one committed injection record — the
 *  three fields a receipt keeps of a system piece, and none of the rest. */
export interface SystemPieceRecord {
    readonly rawContent?: string;
    readonly slot: ContextSlot;
    readonly source: ContextSource;
}
/**
 * What a digest input records in place of a value JSON cannot express — a
 * cycle, a `BigInt`, anything `stableJson` refuses. It is a MARK, not an empty
 * string: two unserializable values are recorded as unserializable rather than
 * as identical, which is what stops the cache comparison claiming "the strategy
 * changed nothing" about two requests it could not read.
 */
export declare const UNSERIALIZABLE = "\u001F<unserializable>";
/**
 * The boundary every receipt field is true at, in one sentence — exported so a
 * renderer prints the library's own wording instead of inferring a stronger
 * claim from a `null`.
 *
 * WHY IT IS A CONSTANT AND NOT A COMMENT. A reader who sees
 * `cache.transform: 'unchanged'` will conclude "this request was not rewritten"
 * unless something on the screen says otherwise, and the person who reads the
 * screen is rarely the person who read the source. Print it beside the record.
 *
 * ── THE MECHANISM, WHICH THE PRINTED SENTENCE NO LONGER NAMES (9.88.0) ─────
 * `buildReceipt` is called with the request about to be passed to
 * `LLMProvider.complete` — the PORT, this library's last sight of it. Four
 * stages call it since 9.91.0: the agent charts' (`stages/callLLM.ts`),
 * `LLMCall.ts` · `callLLM`, and the two message-API charts' through
 * `messageApiReceipt.ts`. Three things sit downstream of that call and none of them is on the
 * record: a provider decorated by the consumer (`complete()` wrapping
 * `complete()`), a vendor adapter's own serializer, and the vendor's
 * server-side defaults. `test/time-travel/receipt-conformance.test.ts`
 * reproduces the first of those — a decorator that appends a system suffix and
 * a ghost tool after the mint, with `cache.transform` still reading
 * `'unchanged'`, correctly.
 *
 * The sentence below used to say all of that, and it was PRINTED beside a
 * trace by renderers that append it to a gap. A printed sentence names no
 * module, function or call: it says which fields, what they mean here, and
 * what to do — the rule in `test/helpers/gapProseClaims.ts`. So the mechanism
 * lives in this comment, where a maintainer reads it and review catches its
 * rot, and the constant says only the thing a reader must not get wrong.
 *
 * @example
 * ```ts
 * import { RECEIPT_BOUNDARY, receiptAt } from 'agentfootprint';
 *
 * const receipt = receiptAt(agent.getSnapshot()!, 1)!;
 * if (receipt.cache.transform === 'unchanged') {
 *   console.log(`the cache strategy changed nothing. ${RECEIPT_BOUNDARY}`);
 * }
 * ```
 */
export declare const RECEIPT_BOUNDARY: string;
/** The scope key holding the name of the tool a `'tool-forced'` output
 *  strategy put on the wire. `seed` commits it; `time-travel/servedView.ts` · `servedAt`
 *  reads it — so a rebuild can NAME the forced tool without reading the
 *  receipt it is being checked against. */
export declare const FORCED_OUTPUT_TOOL_KEY = "forcedOutputToolName";
/** How many hex characters of the digest a receipt keeps. 16 — 64 bits, which
 *  is far past collision range for the few dozen pieces of one run, and short
 *  enough to read in a terminal. */
export declare const RECEIPT_HASH_CHARS = 16;
/** One piece of the composed system string. */
export interface ReceiptPiece {
    readonly hash: string;
    readonly slot: ContextSlot;
    readonly source: ContextSource;
}
/** One message as it went out. */
export interface ReceiptMessage {
    readonly role: ContextRole;
    readonly hash: string;
    /** The message's own join key when it has one — a tool result's
     *  `toolCallId`. Absent for every other message. */
    readonly key?: string;
}
/** A line that existed on the request only and was never written to history. */
export interface ReceiptRequestOnlyMessage {
    readonly role: ContextRole;
    readonly hash: string;
    /** Which library mechanism composed it. `'staged-refs-nudge'` or `'time-window-line'` (step T6b). */
    readonly reason: string;
}
/**
 * One `cache_control` breakpoint the cache strategy actually APPLIED — three
 * scalars, no bytes.
 *
 * `cache.transformHash` collapses a whole rewrite into one bit ("something
 * changed"), and a digest over the whole prepared request is not comparable
 * across epochs. The question that decides an Anthropic bill is narrower and
 * concrete: *did the breakpoints move between call 3 and call 4?* Two receipts'
 * `markersApplied` answer it by inspection.
 *
 * APPLIED, not offered: `scope.cacheMarkers` holds the CANDIDATES the strategy
 * was given, and a strategy clamps them to what the provider allows. The
 * candidates are on the record; which survived is not, which is why they ride
 * here.
 *
 * @example did the breakpoints move between two turns?
 * ```ts
 * import { receiptAt } from 'agentfootprint';
 *
 * const at = (k: number) =>
 *   JSON.stringify(receiptAt(agent.getSnapshot()!, k)?.cache.markersApplied);
 * at(3) === at(4); // false ⇒ the cached prefix moved, and the bill with it
 * ```
 */
export interface ReceiptCacheMarker {
    readonly field: 'system' | 'tools' | 'messages';
    readonly boundaryIndex: number;
    readonly ttl: 'short' | 'long';
}
/**
 * The sampling knobs the call went out with — scalars and short strings, no
 * bytes, no privacy change.
 *
 * They decide the answer as surely as the prompt does: the same context at
 * `temperature: 0` and at `1.2` is a different call, and "why did this turn
 * ramble?" is unanswerable from a record that kept the prompt and dropped the
 * dial.
 *
 * READ OFF THE PREPARED REQUEST — the object the port is handed, after the
 * cache strategy has had it. This is the one part of a receipt that describes
 * the post-strategy request rather than the pre-strategy one, and deliberately
 * so: a strategy holds the whole composed request and can move a dial, and
 * `params` claims in this very docstring to be what the PORT got.
 * `time-travel/servedView.ts` · `SERVED_GAPS` says the same from the other side —
 * `cache-transform` names every field the log rebuilds pre-strategy, and
 * `params` is not among them.
 *
 * PORT VALUES ONLY — see {@link RECEIPT_BOUNDARY}. An unset `maxTokens` may
 * still become the model's own maximum inside a vendor SDK, and the receipt
 * records that the library sent nothing, never what the vendor decided.
 *
 * @example compare the dial two turns went out on
 * ```ts
 * import { receiptAt } from 'agentfootprint';
 *
 * const snapshot = agent.getSnapshot()!;
 * receiptAt(snapshot, 1)?.params.temperature; // 0.25
 * receiptAt(snapshot, 2)?.params.temperature; // 0.25 — the dial did not move
 * receiptAt(snapshot, 1)?.params.stop;        // undefined: none was sent
 * ```
 */
export interface ReceiptParams {
    readonly temperature?: number;
    readonly maxTokens?: number;
    /** `LLMRequest.thinking.budget` — the reasoning-token ceiling asked for. */
    readonly thinkingBudget?: number;
    readonly stop?: readonly string[];
    /** The forced tool choice, as the port carried it. */
    readonly toolChoice?: {
        readonly type: string;
        readonly name?: string;
    };
    /**
     * The structured-output format the port carried (`LLMRequest.outputFormat`,
     * `.outputSchema(s, { strategy: 'native' })`): its `type`, and the
     * run-salted hash of its schema as sorted-key JSON — so a reader can tell
     * the answer was decoded under a schema, and which one, without the bytes.
     * Absent when the request carried none.
     */
    readonly outputFormat?: {
        readonly type: string;
        readonly schemaHash: string;
    };
}
/** What a budget dropped before the request was composed — the window's
 *  evictions at this iteration's head. */
export interface ReceiptAttentionOmission {
    readonly count: number;
    /** One hash per dropped turn — the turn's own `messages.entries[].hash`, so
     *  it pairs with the receipt that last served it. See the first law for why
     *  not the text. */
    readonly hashes: readonly string[];
}
/**
 * THE RECEIPT. One per composed request, committed at the call-llm stop.
 *
 * @example
 * ```ts
 * import { receiptAt } from 'agentfootprint';
 *
 * // Epochs are the run's OWN iteration numbers and start at 1.
 * const receipt = receiptAt(agent.getSnapshot()!, 1);
 * receipt?.tools.withheld;        // 'wrap-up' on the out-of-budget call
 * receipt?.messages.count;        // how many turns went out
 * receipt?.params.temperature;    // the dial this turn went out on
 * ```
 */
export interface Receipt {
    /**
     * Actual leaf-provider preparation omitted thinking after this port receipt
     * was minted. Original hashes/measurement/cache verdict remain port-level;
     * this is an explicit downstream gap, not proof of what a server accepted.
     */
    readonly replayPreparations?: readonly {
        readonly boundary: 'provider-adapter';
        readonly phase: 'prepared';
        readonly omittedBlocks: number;
    }[];
    /** Counts of the initial prepared canonical request, including full schemas.
     * Older recordings have no measurement. No payload, tokens or retry totals. */
    readonly requestMeasurement?: RequestMeasurement;
    readonly system: {
        readonly hash: string;
        readonly chars: number;
        readonly pieces: readonly ReceiptPiece[];
    };
    readonly messages: {
        readonly count: number;
        readonly entries: readonly ReceiptMessage[];
        readonly requestOnly: readonly ReceiptRequestOnlyMessage[];
    };
    readonly tools: {
        readonly names: readonly string[];
        readonly schemaHashes: Readonly<Record<string, string>>;
        /** The tool the model was forced to answer through, or `null`. */
        readonly forced: string | null;
        /** Why the tool list is empty when it would not otherwise be. */
        readonly withheld: 'wrap-up' | 'evidence-revision' | null;
    };
    readonly cache: {
        /**
         * What the comparison between the request handed TO the cache strategy and
         * the one it handed back could establish. BRANCH ON THIS, never on
         * `transformHash === null`:
         *
         * - `'unchanged'` — the two serialize identically.
         * - `'rewritten'` — they do not, and `transformHash` fingerprints the
         *   result.
         * - `'unknown'`   — one of them could not be serialized at all, so the
         *   receipt refuses to claim either.
         *
         * It scopes to the CACHE STRATEGY and to nothing else — see
         * {@link RECEIPT_BOUNDARY}.
         */
        readonly transform: 'unchanged' | 'rewritten' | 'unknown';
        /**
         * Fingerprint of the request the cache strategy handed back, when it
         * differed from the one it was given; `null` otherwise. Recompute it with
         * {@link transformHashOf}. The value names its scheme: `'chain-v1:…'`
         * (see {@link TRANSFORM_HASH_PREFIX}), or a bare 16-hex value on a receipt
         * minted before the prefix existed, which verifies as
         * `receiptHash(runId, stableJson(request))`.
         */
        readonly transformHash: string | null;
        /** The breakpoints the strategy actually applied, in the order it applied
         *  them — see {@link ReceiptCacheMarker}. Empty when it applied none. */
        readonly markersApplied: readonly ReceiptCacheMarker[];
        /**
         * WHICH strategy stood between assembly and the port, or `null` when none
         * did (9.93.0). The value is the strategy's `name`: for the built-ins,
         * the caching the provider declared (`'breakpoints'`, `'automatic'`,
         * `'none'`); older receipts carry the old registry key (`'anthropic'`,
         * `'openai'`, `'*'`).
         *
         * `null` is a FACT, not an absence: `LLMCall` and the two message-API
         * charts hand the port the request assembly built, with nothing in
         * between, and say so here. `transform` alone could not — `'unchanged'` is
         * the honest verdict both when a strategy returned what it was given and
         * when there was no strategy to return anything — which is why the served
         * view raised `cache-transform` on every view until this field existed.
         * Now it raises that gap only where a strategy could have rewritten the
         * request: where this is not `null`, or where no receipt can say.
         *
         * A receipt minted before 9.93.0 has no key here; a reader treats that as
         * "cannot say", never as `null` — {@link StoredReceipt} is the shape that
         * says so, and the one every reader is handed.
         */
        readonly strategy: string | null;
    };
    /** The sampling knobs the call went out with — see {@link ReceiptParams}. */
    readonly params: ReceiptParams;
    /**
     * What left the window for budget at this iteration's head, before this
     * request was composed — one hash per evicted turn, each the turn's own
     * `messages.entries[].hash` as an earlier receipt served it.
     *
     * Absent when nothing was dropped before this call. Since 9.93.0 that is a
     * claim and not a shrug: the agent chart's window stage is the one shipped
     * mechanism that drops for attention and it files every eviction here; the
     * built-in slots drop nothing; `LLMCall` and the message-API charts have no
     * window. It is named by the `no-receipt-on-chart` entry of `time-travel/servedView.ts` ·
     * `SERVED_GAPS` — the one gap that can lose it, by losing the receipt — and it sat in
     * `UNGAPPED_FIELDS` until this release, when its absence was universal and
     * meant only that nobody had recorded a drop.
     */
    readonly omittedForAttention?: ReceiptAttentionOmission;
    readonly basis: {
        readonly epoch: number;
        readonly runId: string;
        readonly model: string;
        readonly provider: string;
    };
}
/**
 * A receipt as a READER meets it — what `receiptAt` hands back, and the shape
 * every rebuild reads through (9.94.1).
 *
 * {@link Receipt} is what a MINT writes, and a mint writes every container it
 * knows. But a recording is older than the reader that opens it: what a
 * reader is handed is what the MINTING release knew to write, and that has
 * grown — `cache` came with the receipt in 9.88.0 and gained `strategy` in
 * 9.93.0; `omittedForAttention` has always been written only when something
 * was dropped. The narrowing that admits a stored value (`time-travel/servedView.ts` ·
 * `readReceipt`) checks the basis and nothing past it, so this type says what
 * that check leaves open, and the compiler — not a throw on an older
 * recording — is what meets a reader that forgets.
 *
 * THE VINTAGE LAW: a reader reads the receipt it is handed; a missing
 * container is a fact about the vintage, never a throw. Absence reads as
 * "cannot say" — `servedAt` raises `cache-transform` on a receipt with no
 * `cache.strategy` exactly as it does with no receipt at all — never as
 * `null`, and never as a fabricated `cache: {}`. The record is handed back as
 * it was stored.
 *
 * @example a recording minted by 9.92, read by this release
 * ```ts
 * const receipt = receiptAt(olderRecording, 1)!;
 * receipt.cache?.strategy;                              // undefined — the mint predates the key
 * servedAt(olderRecording, 1)!.gaps.map((g) => g.gap);  // includes 'cache-transform': it cannot say
 * ```
 */
export type StoredReceipt = Omit<Receipt, 'cache'> & {
    readonly cache?: Omit<Receipt['cache'], 'strategy'> & {
        readonly strategy?: Receipt['cache']['strategy'];
    };
};
/**
 * The receipt's hash: run-salted SHA-256, first {@link RECEIPT_HASH_CHARS} hex
 * characters. Exported because it is half of the conformance law — a reader
 * that rebuilds a served view proves the rebuild by hashing it the same way.
 *
 * @example
 * ```ts
 * import { receiptAt, receiptHash, servedAt } from 'agentfootprint';
 *
 * const view = servedAt(agent.getSnapshot()!, 1)!;
 * const receipt = receiptAt(agent.getSnapshot()!, 1)!;
 * receiptHash(receipt.basis.runId, view.system.text) === receipt.system.hash; // true
 * ```
 */
export declare function receiptHash(runId: string, content: string): string;
/**
 * The bytes a tool schema's hash covers: the whole `LLMToolSchema` — `name`,
 * `description`, `inputSchema` — as sorted-key JSON, so two schemas that are
 * structurally the same hash the same however they were built, and with a
 * schema JSON cannot express (a cycle, a `BigInt`) marked {@link UNSERIALIZABLE}
 * rather than dropped or thrown on.
 *
 * WHAT TO PASS (9.89.0). The receipt hashes each tool AS HANDED TO THE PORT:
 * `buildReceipt` is given the request's tool list, and that list is the
 * `dynamicToolSchemas` the run committed, plus a forced answer tool when an
 * output strategy adds one. `servedAt(k).tools.schemas[i]` is that committed
 * list read back — the same shape, the same bytes — so a consumer holding a
 * served view already holds the object this function takes. It does not take
 * the served view, a `Tool`, or a `defineTool` definition: those carry an
 * `execute`, `wants` and other fields the model never saw.
 *
 * WHY IT IS EXPORTED. A reader that rebuilds a served view proves the rebuild
 * by hashing it the way the receipt did. `receiptHash` and `messageDigestInput`
 * were exported in 9.88.0 for the system text, the pieces and the messages,
 * and a consumer could verify all of them — and NOT the tools, because the
 * serializer behind `schemaHashes` was internal. Its only options were to copy
 * `stableJson` (a second owner of the rule, which drifts the day the digest
 * gains a field, as the message digest did in 9.88.0) or to leave the schema
 * rows unverified. This is the third digest half of the law, beside its two
 * siblings, and the ONLY spelling of the schema rule: `buildReceipt` calls it
 * too. `stableJson` stays off the root barrel for the same reason — a consumer
 * composing `hash(stableJson(tool))` would be writing the rule a second time.
 *
 * @example verify every schema row of a receipt from outside
 * ```ts
 * import { receiptAt, receiptHash, servedAt, toolDigestInput } from 'agentfootprint';
 *
 * const snapshot = agent.getSnapshot()!;
 * const view = servedAt(snapshot, 1)!;
 * const receipt = receiptAt(snapshot, 1)!;
 * for (const tool of view.tools.schemas) {
 *   receiptHash(receipt.basis.runId, toolDigestInput(tool)) ===
 *     receipt.tools.schemaHashes[tool.name]; // true, for every tool of every epoch
 * }
 * // A forced answer tool is in `schemaHashes` and NOT in `schemas` — its body
 * // is a declared gap (`forced-tool-schema`), so there is no row to check.
 * ```
 */
export declare function toolDigestInput(tool: LLMToolSchema): string;
/**
 * The bytes a message's hash covers, each field behind a separator: its role,
 * its text, its `toolCallId`, its `toolName`, the calls it asked for (id, name,
 * arguments and `providerMeta`) and its `thinkingBlocks`.
 *
 * WHY `toolCallId` (9.88.0). It is the JOIN KEY — `tool_use_id` on Anthropic's
 * wire, `tool_call_id` on OpenAI's — that pairs a tool result to the call that
 * asked for it. Two parallel calls whose results happen to have identical text
 * hashed the SAME without it, so a mis-pairing (the answer to `c1` filed under
 * `c2`) was invisible to the law: exactly the defect a receipt exists to catch.
 *
 * WHY `toolName` TOO (9.88.0). On two shipped providers the id is not the join
 * key at all. `adapters/llm/GeminiProvider.ts` · `toGeminiContents` pairs a
 * `functionResponse` to its call BY NAME and says so in its own comment; the
 * id is optional there and dropped when it is not a real one.
 * `adapters/llm/OllamaProvider.ts` · `toOllamaMessages` puts `tool_name` on
 * the wire and only falls back to the id. So on those providers a receipt that
 * covered `toolCallId` alone still could not see the very mis-pairing the
 * field was added for: two `role:'tool'` messages with identical text and
 * SWAPPED names fingerprinted identically. It rides as its own field behind
 * the separator, beside the id, so a name moving between two results changes
 * both hashes and neither can absorb the other's bytes.
 *
 * WHY `thinkingBlocks` and `providerMeta` (9.88.0). `provider-contract/types.ts` is
 * explicit that a signed thinking block MUST be echoed byte-exact or the API
 * rejects the turn, and `providerMeta` round-trips vendor state (Gemini's
 * `thoughtSignature`, without which the turn after a tool call is refused).
 * Both are things the model receives; a fingerprint that omitted them said two
 * requests were the same when one of them would be rejected. They enter as a
 * `stableJson` FINGERPRINT, so nothing quotable is added — a signature is an
 * opaque token, not content.
 *
 * Still deliberately NOT covered: `injectedBy` and `notDispatched` (both
 * stripped before the request exists) and `ephemeral` (a persistence flag,
 * invisible to the model). Two messages that hash the same are the same thing
 * said to the model.
 */
export declare function messageDigestInput(message: LLMMessage): string;
/** What `buildReceipt` needs from the stage that composed the request. */
export interface BuildReceiptInput {
    readonly runId: string;
    readonly epoch: number;
    readonly model: string;
    readonly provider: string;
    /** The joined system string, exactly as sent. */
    readonly systemText: string;
    /** The pieces it was joined from, in order. */
    readonly systemPieces: readonly {
        readonly text: string;
        readonly slot: ContextSlot;
        readonly source: ContextSource;
    }[];
    /** The messages that came from history, post-strip, in wire order. */
    readonly messages: readonly LLMMessage[];
    /** Lines appended to this request only, each with the mechanism that
     *  composed it. */
    readonly requestOnly: readonly {
        readonly message: LLMMessage;
        readonly reason: string;
    }[];
    /** The tool list as sent — including a forced output tool, if there is one. */
    readonly tools: readonly LLMToolSchema[];
    readonly forced: string | null;
    readonly withheld: 'wrap-up' | 'evidence-revision' | null;
    /** The request handed TO the cache strategy — one half of the cache
     *  comparison, and nothing else is read off it. */
    readonly baseRequest: unknown;
    /**
     * The request it handed back: the object the provider port is given.
     *
     * The other half of the cache comparison, AND where {@link ReceiptParams} is
     * read from. A strategy is handed the whole composed request, so it can move
     * a dial as easily as a marker; reading the knobs off `baseRequest` recorded
     * what assembly PROPOSED and called it what the port was handed, which is
     * the one thing `RECEIPT_BOUNDARY` promises a receipt never does.
     */
    readonly preparedRequest: unknown;
    /** The breakpoints the strategy reported applying (`prepareRequest`'s
     *  `markersApplied`). Absent ⇒ recorded as none. */
    readonly markersApplied?: readonly {
        readonly field: 'system' | 'tools' | 'messages';
        readonly boundaryIndex: number;
        readonly ttl: 'short' | 'long';
    }[];
    /** The `name` of the strategy `preparedRequest` came back from, or
     *  `null` when no strategy stood between assembly and the port — see
     *  {@link Receipt.cache.strategy}. A mint must say which; it may not omit. */
    readonly strategy: string | null;
    /** The turns a budget dropped before this request was composed — the
     *  window's evictions at this iteration's head. Absent or empty ⇒ no key on
     *  the receipt. */
    readonly omittedForAttention?: readonly LLMMessage[];
}
/**
 * The system pieces a receipt records, off the injection records a slot
 * committed — the SURVIVORS of the join, in the order they were joined.
 *
 * ONE OWNER, because there are now four mints (9.91.0): the agent's `call-llm`
 * stage, `LLMCall`'s, and the two message-API charts'. Written out at each of
 * them, this five-line map is four chances to disagree about which record was
 * piece 2 — and `servedAt` proves piece 2 against the receipt's piece 2, so a
 * disagreement here reads as a defect in the record.
 *
 * It calls `contributingPieces`, which is also what the join itself calls, so
 * an empty record cannot be a piece on the receipt and a blank line in the
 * string, or the other way round.
 *
 * @example
 * ```ts
 * receiptPieces([{ rawContent: 'You are a bot.', slot: 'systemPrompt', source: 'static' }]);
 * // [{ text: 'You are a bot.', slot: 'systemPrompt', source: 'static' }]
 * ```
 */
export declare function receiptPieces(records: readonly SystemPieceRecord[]): BuildReceiptInput['systemPieces'];
/**
 * The prefix a chained {@link Receipt.cache.transformHash} carries — the NAME of
 * the scheme that produced it, inside the value, so a verifier can tell the two
 * schemes apart without a second field:
 *
 * - `'chain-v1:<16 hex>'` — {@link transformHashOf}: each message of the
 *   returned request digested on its own and chained in order, then the rest
 *   of the request. Minted since this prefix existed.
 * - a bare `<16 hex>` — the older WHOLE-REQUEST scheme,
 *   `receiptHash(runId, stableJson(request))`. Every receipt minted before the
 *   prefix carries this form, and it still verifies with that formula.
 *
 * WHY THE SCHEME CHANGED. The whole-request digest could not be extended: the
 * strategy's markers sort to the front of the canonical JSON and move on every
 * call, so call k's preimage shared no prefix with call k−1's and every byte of
 * the history was hashed again — measured, the larger half of the receipt's
 * SHA-256 input under a caching provider. A chain over per-message digests is
 * extended by the messages a call added and nothing else.
 */
export declare const TRANSFORM_HASH_PREFIX = "chain-v1:";
/**
 * The fingerprint a receipt records as `cache.transformHash` for the request a
 * cache strategy handed back — recompute it from that request to verify a
 * receipt's claim.
 *
 * The scheme, so a verifier in any language can follow it (every `H` below is
 * {@link receiptHash} with the receipt's `basis.runId`):
 *
 * ```text
 * link₀  = H("agentfootprint.transform.chain.v1")
 * dᵢ     = H(stableJson([messageᵢ]))          — each message, as one array element
 * linkᵢ  = H(linkᵢ₋₁ + "\u001F" + dᵢ)          — in request order
 * head   = stableJson(request without "messages")   (the whole request when it
 *                                                    carries no messages array)
 * hash   = "chain-v1:" + H(linkₙ + "\u001F" + head)
 * ```
 *
 * A value `stableJson` cannot produce is recorded as {@link UNSERIALIZABLE} in
 * its place, never as an empty string. A request that cannot be read at all (a
 * property that throws when read) is `"chain-v1:" + H(link₀ + "\u001F" +
 * UNSERIALIZABLE)` — the function never throws. A receipt minted before the
 * prefix existed carries a bare 16-hex value: verify that one with
 * `receiptHash(runId, stableJson(request))`.
 *
 * @example
 * ```ts
 * import { receiptAt, transformHashOf } from 'agentfootprint';
 *
 * const receipt = receiptAt(snapshot, 3)!;
 * // `prepared` — the request your provider decorator saw at call 3
 * transformHashOf(receipt.basis.runId, prepared) === receipt.cache.transformHash; // true
 * ```
 */
export declare function transformHashOf(runId: string, request: unknown): string;
/**
 * Mint the receipt for one composed request.
 *
 * Pure and total: every branch here is arithmetic over what it was handed, so
 * a receipt cannot fail a call. The one judgement it makes is the cache
 * comparison, and it makes it in three values rather than two: `'unchanged'`
 * exactly when the strategy handed back a request that serializes identically
 * to the one it was given, `'rewritten'` when it did not, and `'unknown'` when
 * either request could not be serialized at all — because "I could not read
 * them" is not the same claim as "they were the same".
 *
 * Every value it produces is true AT THE PROVIDER PORT and nowhere past it —
 * see {@link RECEIPT_BOUNDARY}.
 *
 * `digests` is the run's memo (`receiptDigests.ts`): a loop that mints a
 * receipt per call passes the same one to every call, and each message is then
 * hashed and measured once per run instead of once per call. It changes no
 * byte of the receipt — only how much work produces it.
 */
export declare function buildReceipt(input: BuildReceiptInput, digests?: ReceiptDigests): Receipt;
