/**
 * epochs — where one epoch's committed pieces live, in either chart shape.
 *
 * Role:  Fold. It reads a finished run's recording and answers one question:
 *        "for iteration k, which commit log holds the call, at which index,
 *        and against which fold base?" It executes nothing, records nothing
 *        and stores nothing beside the log.
 * Reads: a recording (`commitLog`, `initialState`, `subflowResults`), through
 *        footprintjs's own id parsers and this folder's `keyedFold.ts`.
 * Emits: N/A.
 *
 * ── WHY THIS FILE EXISTS ───────────────────────────────────────────────────
 * An agent's iteration — an EPOCH — commits to a different place depending on
 * how the chart was built. Under `reactMode: 'dynamic'` the `call-llm` stage is
 * a parent-level stage and its bundle is in the run's own log. Under
 * `'dynamic-grouped'` each turn runs inside an `sf-llm-call` subflow, which has
 * its own isolated runtime and its own log, retained per iteration under
 * `subflowResults['sf-llm-call#k'].treeContext.history`.
 *
 * That fork was written out THREE times — `contextLedger.ts` had a private
 * `llmCallMountKeys`, `trajectory.ts` had a byte-twin of it, and a rebuild of
 * what the model was served would have been the third. Three copies of "which
 * log is this key in?" is three chances to disagree about which iteration a
 * value belonged to, on the one question the whole family exists to answer.
 * So the fork lives here once, and every reader asks it.
 *
 * ── WHAT AN EPOCH NUMBER IS ────────────────────────────────────────────────
 * The committed `iteration` value in effect at the call — the same number
 * `callLLM` put on the receipt's `basis.epoch` and on every `iteration_start`
 * event. It starts at 1. It is READ, not counted: a run that pauses and
 * resumes, or one whose first turn is not its first stage, still names its
 * epochs the way the run itself named them.
 *
 * ── EVERY READ FOLDS FROM THE BASE ─────────────────────────────────────────
 * A location carries the FOLD SOURCE its log was recorded against, not just
 * the log, and every read below goes through `keyedFold.ts`. A resumed run is
 * a fresh executor seeded from `checkpoint.sharedState`, so most of what the
 * call read is base rather than log; a fold that could not see the base
 * answered "absent" for the whole pre-pause world and said nothing about it.
 * `EpochLocation.basis` is that fold's own verdict, carried out to the reader
 * so a recording that travelled without its base declares a gap instead of a
 * confident empty view.
 */
import type { CommitBundle } from 'foottrace';
import { type FoldBasis, type FoldSourceLike } from './keyedFold.js';
/** The shape a recording has to have for any of this to work — a snapshot, or
 *  anything that hands one over. */
export interface RecordingLike {
    readonly commitLog?: readonly CommitBundle[];
    /** The log's fold base (`RuntimeSnapshot.initialState`, footprintjs 9.17+).
     *  Absent on a recording that travelled without it — see
     *  {@link EpochLocation.basis}. */
    readonly initialState?: Record<string, unknown>;
    readonly executionTree?: unknown;
    readonly subflowResults?: Record<string, unknown>;
}
/** One epoch, located: which log holds its call, where in that log, and what
 *  that log folds against. */
export interface EpochLocation {
    /**
     * The committed `iteration` at the call. 1-based, and READ rather than
     * counted — a run that pauses and resumes still names its epochs the way the
     * run itself named them.
     *
     * ONE fallback, and it is a count: when the fold cannot read `iteration` at
     * all (a recording whose base did not travel, on a run that never re-`set`
     * it), this is the location's position in run order instead. The tell is
     * {@link EpochLocation.basis} / {@link EpochLocation.runBasis} reading
     * `'log-only'`, which is also what makes `servedView.ts` raise
     * `no-fold-base`. A view must have some number to be addressed by; this one
     * is the honest second choice, not a claim about the record.
     */
    readonly epoch: number;
    /** The `call-llm` stage's `runtimeStageId` — the llm-turn stop's own id. */
    readonly callRuntimeStageId: string;
    /** The log the call committed to: the run's own under `'dynamic'`, the
     *  turn's inner history under `'dynamic-grouped'`. */
    readonly log: readonly CommitBundle[];
    /** The call's ARRAY index in {@link EpochLocation.log}. */
    readonly callIdx: number;
    /** The RUN's log, always. Run constants (a build-time fact seeded once) are
     *  read here, because a grouped turn's inner log never sees them unless the
     *  boundary happens to carry them. */
    readonly runLog: readonly CommitBundle[];
    /** The `sf-llm-call` mount this epoch was projected from — set under
     *  `'dynamic-grouped'` only, and the tell that `callIdx` is inner-relative. */
    readonly subflowScope?: string;
    /**
     * The fold source {@link EpochLocation.log} belongs to — the log PLUS the
     * base it was recorded against. Reads go through it, never through the bare
     * array, so a value seeded before the run (every key of a resumed run) folds
     * from where it actually came from.
     */
    readonly source: FoldSourceLike;
    /** The same, for {@link EpochLocation.runLog}. */
    readonly runSource: FoldSourceLike;
    /**
     * How {@link EpochLocation.source} folds. `'log-only'` means the recording
     * travelled without its fold base, so anything the log never `set` reads as
     * absent — `servedView.ts` turns that into a declared gap rather than an
     * empty view.
     */
    readonly basis: FoldBasis;
    /** The same, for {@link EpochLocation.runSource}. */
    readonly runBasis: FoldBasis;
    /** `false` when this recording carries no RUN log at all — a subtree handed
     *  in on its own. Every run constant is then unreadable, which
     *  `servedView.ts` declares rather than reads as absent. */
    readonly hasRunLog: boolean;
}
/**
 * A snapshot from whatever the caller had: a runner (`Agent`, `LLMCall`), or
 * the snapshot itself. Duck-typed on purpose — a recording read back from JSON
 * is a plain object and must work exactly like a live one.
 */
export declare function recordingOf(source: unknown): RecordingLike | undefined;
/**
 * The `sf-llm-call` mount keys in `subflowResults`, in loop order.
 *
 * Non-empty ⇒ the run is GROUPED and every turn's pieces are in its own inner
 * log. Empty ⇒ flat, and the run's own log holds everything.
 *
 * @example
 * ```ts
 * llmCallMountKeys(snapshot.subflowResults); // ['sf-llm-call#4', 'sf-llm-call#9']
 * ```
 */
export declare function llmCallMountKeys(subflowResults: Record<string, unknown> | undefined): string[];
/**
 * Every epoch in a recording, in run order — the ONE owner of the flat /
 * grouped fork.
 *
 * Returns `[]` for a recording with no LLM call in it at all (an empty log, a
 * chart with no `call-llm` stage). That is a different fact from "the epoch you
 * asked for is not here", which is what {@link epochAt} says with `undefined`.
 *
 * MEMOIZED on the recording object, because the cost of locating epochs is a
 * pass over the log and a per-epoch reader asks for one epoch at a time: before
 * this, scrubbing a 600-turn run epoch by epoch relocated every epoch on every
 * call and took 20.8 s. Holding the snapshot and asking it repeatedly is the
 * shape a reader's UI actually has, so the memo is keyed on exactly that
 * object; a caller that hands in a runner and gets a fresh snapshot each time
 * pays the pass each time, which is the cost of asking a different question.
 *
 * FROZEN, because it is memoized: the array and each location on it are the
 * very objects the next caller gets, so an edit here would be an edit to
 * everybody's answer. The same law `keyedFold.ts` · `freezeDeep` states for a
 * folded value. What is NOT frozen is what a location POINTS AT — `log`,
 * `source`, `runSource` are the caller's own recording, and this file does not
 * get to lock down an object it was merely handed.
 *
 * @example
 * ```ts
 * import { epochLocations } from 'agentfootprint';
 *
 * epochLocations(agent.getSnapshot()!).map((e) => [e.epoch, e.callRuntimeStageId]);
 * // dynamic:         [[1, 'call-llm#12'], [2, 'call-llm#31']]
 * // dynamic-grouped: [[1, 'sf-llm-call/call-llm#9'], [2, 'sf-llm-call/call-llm#28']]
 * ```
 */
export declare function epochLocations(source: unknown): readonly EpochLocation[];
/**
 * One epoch by number, or `undefined` when the run has no such iteration.
 *
 * @example
 * ```ts
 * epochAt(agent.getSnapshot()!, 2)?.callRuntimeStageId; // 'call-llm#31'
 * ```
 */
export declare function epochAt(source: unknown, epoch: number): EpochLocation | undefined;
/**
 * The value of `key` as the epoch's call READ it — folded to just BEFORE the
 * call's own bundle, so a key the call itself writes back (`history` gains the
 * assistant turn; `receipt` is minted) reads as it was on the way in.
 *
 * Folded from the run's base, not from the log alone: on a resumed run the
 * call read values the log never wrote, and reading those as absent is the
 * fiction `keyedFold.ts` exists to end. A call that is its log's FIRST commit
 * (a grouped turn, or the first stage after a resume) therefore folds to the
 * base rather than to nothing.
 *
 * @example
 * ```ts
 * const epoch = epochAt(snapshot, 1)!;
 * (readAtCall(epoch, 'history') as unknown[]).length; // the turns that went out
 * ```
 */
export declare function readAtCall(location: EpochLocation, key: string): unknown;
/**
 * The value of `key` the epoch's call itself COMMITTED — folded through the
 * call's own bundle. The receipt is read this way, and only the receipt: it is
 * the one value the call writes that describes the call.
 */
export declare function readAfterCall(location: EpochLocation, key: string): unknown;
/**
 * A RUN CONSTANT: a build-time fact `seed` put on the record once, read from
 * the run's own log.
 *
 * It is read from the run log rather than the epoch's log because that is where
 * `seed` runs in both chart shapes. Under `'dynamic-grouped'` the turn's inner
 * log only ever holds what the boundary's `inputMapper` carried in, and a
 * constant nobody inside the turn reads is not carried — so asking the inner
 * log would answer "absent" for a fact the run plainly recorded.
 *
 * `undefined` has TWO causes and a caller must not blur them: the run never
 * recorded this constant, or this recording carries no run log to read
 * (`EpochLocation.hasRunLog` is `false` — a subtree handed in on its own).
 * `servedView.ts` branches on that flag and declares a gap for the second.
 */
export declare function readRunConstant(location: EpochLocation, key: string): unknown;
