/**
 * emptiness — the ONE reader of "what came back?" for a finished tool result.
 *
 * Pattern: typed routes over two inputs — the value the MODEL read, and the
 *          door the RECORD says the call returned — and never a guess.
 * Role:    core/ layer, pure. It imports the recognizers only
 *          (`semantics/coverage/recognize.ts`; the artifact ref grammar and the
 *          placement ticket's guard, `artifacts/naming.ts`,
 *          `artifacts/placement.ts`), never a mint, so a post-hoc reader
 *          loads it without the tool-name checks and refusal sentences.
 * Callers: the `/observe` answer account (`answer-account/facts/calls.ts`
 *          for this run's calls, `answer-account/facts/inView.ts` for an
 *          earlier answer's result) and the standing fold
 *          (`core/agent/assessment/assess.ts` · `assessAnswer`). One rule, so
 *          the person's account and the answer's standing read the same bytes
 *          the same way; they differ only where they hold a different door
 *          (the list under "The door decides" below).
 * Emits:   N/A.
 *
 * ## The routes, in order
 *
 * | What the record says came back | Reading | Rows counted by |
 * |---|---|---|
 * | an absence — bare, inside a `coverage()`, or the delivered status `'absent'` | `declared-absent` | — |
 * | a `describedResult()` with data | `non-empty`, with a count per kind | library |
 * | a `describedResult()` with only `clarify` | `clarify` — a question handed back, no data | — |
 * | a `coverage()` envelope | its wrapped `result`, read by these same routes, marked `bounded`; an empty wrapped rowset reads `declared-absent` | library or app |
 * | a bare top-level array | `undeclared-empty` or `non-empty` | library |
 * | the library's placement ticket (`placedToolResult`) — the whole result is in the store | `unknown`, said: the ticket counts bytes, never rows | — |
 * | an object whose key the app declared in `rowsAt` | `undeclared-empty` or `non-empty` | app |
 * | an object whose `rowsAt` rows went to the store — the dataset ticket left in their place | the ticket's whole-number `rows`: `undeclared-empty` (0) or `non-empty`; no count → `unknown`, said | app |
 * | an object with no list and no ticket at the declared `rowsAt` | `unknown`, said: the declared key holds no list | — |
 * | anything else | `unknown` — the record cannot read it | — |
 *
 * ## Rows that travel by reference
 *
 * A projection may move a rowset out of the value the model reads and leave a
 * TICKET to the artifact store in its place — `withDatasetArtifacts` /
 * `stageDatasetArtifacts` stage the rows, and a projection (or an after-tool
 * `allow(replacement)`) keeps the ticket and drops the rows. The library's
 * spelling of that ticket, the one this reader counts:
 *
 * ```ts
 * { …, datasets: { rows: { ref: 'art_…', kind: 'dataset/rows', rows: 2, sourceField: 'rows' } },
 *      dataset: { ref: 'art_…', kind: 'dataset/rows', rows: 2, sourceField: 'rows' } } // the principal one
 * ```
 *
 * `ref` is a store ref (`isArtifactRef`), `rows` the whole-number row count,
 * `sourceField` the result key the rows came from. The ticket is read only for
 * the key the APP declared (`rowsAt`), only when that key is gone from the
 * value (moved, not merely beside it), and only when the ticket names it —
 * keyed under `datasets[rowsAt]` (a `sourceField`, when present, agreeing) or
 * the principal `dataset` with `sourceField: rowsAt`. Two declarations meet
 * there: the app's key and the ticket's field. A ticket with no whole-number
 * `rows` is never guessed: whether the result was empty cannot be told, and
 * the reading says why (`rowsUnread`). The library's own placement ticket
 * (`artifacts/placement.ts`) puts the WHOLE result in the store and counts
 * bytes, so it reads the same way. Take 4 of the demo video: a ticketed
 * `pscale_client_health` (2 rows, `sourceField: 'rows'`, the app's `rowsAt:
 * 'rows'`) read as "its shape is not declared", and the account's tone stayed
 * unknown.
 *
 * ## The door decides, when the record holds one
 *
 * A marker in the bytes is not a declaration: an envelope a tool returned as
 * JSON TEXT (an `mcpClient` in its default text mode) reaches the model and
 * the history byte-for-byte like a recognized one, but the run never
 * recognized it — no delivered status, no coverage row, no limits block. So
 * when the caller holds the call's door (`EmptinessContext.door` — this run's
 * calls, read from the coverage rows or events), the door alone says whether
 * an absence or a boundary was declared, and a marker the door does not vouch
 * for is read as plain data. Only when the caller holds NO door for the result
 * is the door read off the value itself, by the rule the run's own recognizer
 * applies (`declaredByValue`, mirroring `semantics/coverage/read.ts` ·
 * `readCoverageResult`) — the only evidence left, and the same answer the run
 * would have filed for an object. Which caller holds a door:
 *
 * - the answer account, for this run's calls — its EVENTS are the door
 *   (`answer-account/facts/calls.ts`), and a call's `tool_end` is always
 *   in the record, so a JSON-text envelope the run never recognized reads as
 *   data in "It found";
 * - the standing fold, only for a call that has a committed coverage row
 *   (`core/agent/assessment/assess.ts` · `readTurnResults`). Committed state
 *   can LOSE a row the run filed (a history restored by `resumeOnError` does
 *   not carry `coverageDeclared`; a trimmed recording drops it), so for a call
 *   with no row the fold passes no door and the envelope in the committed
 *   history is read — never read as silence. For a JSON-text envelope the run
 *   never recognized (an `mcpClient` in text mode) the fold then says more
 *   than "It found" does: it may over-report, it never hides;
 * - nobody, for an earlier answer's result (`facts/inView.ts`).
 *
 * Recognizing JSON-text envelopes at run time belongs at the execute boundary,
 * with one owner, not in a reader.
 */
/**
 * Whether the result the model read came back empty, and who says so.
 *
 * - `declared-absent` — the tool declared that nothing matched (an absence, or
 *   an empty rowset inside a declared boundary);
 * - `undeclared-empty` — an empty rowset that did not say what it searched;
 * - `non-empty` — rows, or described data, came back;
 * - `clarify` — a described result handed back a question and no data;
 * - `unknown` — the record cannot read it (a shape nobody declared, a
 *   decorated or non-JSON string, an error text).
 */
export type Emptiness = 'declared-absent' | 'undeclared-empty' | 'non-empty' | 'clarify' | 'unknown';
/** A described result's data, counted per kind by the library from the recorded envelope. */
export interface DescribedCounts {
    readonly facts?: number;
    readonly series?: number;
    readonly edges?: number;
}
/** What the RECORD says one call returned. */
export interface ReturnedDoor {
    /** The call returned an absence — bare, inside a `coverage()`, or with the delivered status `'absent'`. */
    readonly absent: boolean;
    /** The call declared a `coverage()` boundary (a `ledger` coverage row / a `tools.coverage_declared` event). */
    readonly bounded: boolean;
    /**
     * The `describedResult()` envelope as the record keeps it —
     * the `tools.semantics_declared` event's `semantics`. The model read only its
     * projection, which carries no marker, so without this the reader cannot
     * tell a described result from any other object. Committed state does not
     * keep it (the projection replaces it at the dispatch door), so the standing
     * fold never has it.
     */
    readonly described?: unknown;
}
export interface EmptinessContext {
    /** Where the APP says an object result of this tool keeps its rows — a top-level key. */
    readonly rowsAt?: string;
    /**
     * What the record says the call returned. ABSENT when the record holds no
     * door for this result — an earlier answer's result, whose run is not in
     * this record, or (for the standing fold) a call with no committed coverage
     * row — and the strict recognizers then read the value itself
     * (`declaredByValue`).
     */
    readonly door?: ReturnedDoor;
    /**
     * For a reader of a record SERVED under a redaction policy (an agent's
     * `redact`): whether a value is the policy's placeholder. The app's rows key
     * holding one reads as kept out (`rowsUnread: 'redacted'`), never as "no
     * list". Absent — the live run, or a record no policy covered — every
     * value is read as the value it is.
     */
    readonly keptOut?: (value: unknown) => boolean;
}
export interface EmptinessReading {
    readonly emptiness: Emptiness;
    /** Rows counted in the value the model read (a counted rowset only). */
    readonly rows?: number;
    /** `app` when the count rests on the app's declared `rowsAt`. */
    readonly source?: 'library' | 'app';
    /** The rows key the reading used (the app's `rowsAt`), when it used one. */
    readonly rowsAt?: string;
    /**
     * Where in the value read the count was taken (path segments; `[]` the value
     * itself; `result` for each declared boundary read through): the rowset, or
     * the `rows` of the dataset ticket standing for it.
     */
    readonly countedAt?: readonly string[];
    /** A described result's data, per kind. */
    readonly described?: DescribedCounts;
    /** Read through a declared `coverage()` boundary. */
    readonly bounded?: true;
    /** An object result with no declared shape — whether it was empty cannot be told. */
    readonly undeclaredShape: boolean;
    /**
     * Why the rows could not be counted when the shape IS known — so a reader
     * never says "not declared" of a declared key:
     * - `no-list` — the app declared `rowsAt`, and the value holds neither a list
     *   there nor a ticket standing for it;
     * - `uncounted-ticket` — the rows went to the artifact store and the ticket
     *   left in their place carries no whole-number count (a dataset ticket
     *   without `rows`, or the library's placement ticket, which counts bytes);
     * - `redacted` — the result itself is kept out of the record: a redaction
     *   policy (an agent's `redact`) left its placeholder where the value was,
     *   so nothing about the rows can be read. Set by the answer account's call
     *   reader (`answer-account/facts/calls.ts`), which never hands a
     *   placeholder to this reader as if it were a result.
     */
    readonly rowsUnread?: 'no-list' | 'uncounted-ticket' | 'redacted';
}
/**
 * The ONE rule for an app's declared rows key: a non-empty TOP-LEVEL key of the
 * object result — no `/` and no `.`, so it can never be read as a path. Returns
 * what is wrong with a value (`'empty'`, `'nested'`), or `undefined` when it
 * keeps the rule. Both readers' declarations are checked by it (the answer
 * account's `answer-account/declarations.ts` · `validateDeclarations`, the
 * standing fold's `core/agent/assessment/assess.ts` · `assessAnswer`), so the
 * two cannot accept different keys.
 */
export declare function rowsAtProblem(rowsAt: unknown): 'empty' | 'nested' | undefined;
/** JSON text that is an object or array is read as data; anything else as found. */
export declare function parseMaybeJson(value: unknown): unknown;
/** What a value's OWN envelope declares — see {@link declaredByValue}. */
export interface ValueDeclaration {
    /** An absence — bare, or directly inside a `coverage()` boundary. */
    readonly absent: boolean;
    /** A `coverage()` boundary. */
    readonly bounded: boolean;
    /**
     * Ground the envelope says it did not check or can never cover — a
     * non-empty `not_checked` / `cannot_cover`, on the boundary or on the
     * absence it wraps.
     */
    readonly gap: boolean;
}
/**
 * What a value's own envelope declares, read by the strict recognizers only and
 * by the rule the RUN's recognizer files rows for a value it is handed
 * (`semantics/coverage/read.ts` · `readCoverageResult`): a bare absence, or a boundary
 * with an absence directly inside it, and the gaps either lists. `undefined`
 * when the value is neither envelope. JSON text is parsed once, at the top.
 *
 * For a reader whose record holds no row for the call: an earlier answer's
 * result (its run is not in this record), or a result whose row was never
 * filed or was lost on the way here — a history restored by `resumeOnError`
 * carries no `coverageDeclared`, and a trimmed recording may drop it. The
 * envelope in the bytes is then the only evidence left, and reading it can
 * only ADD a declaration, never hide one.
 */
export declare function declaredByValue(value: unknown): ValueDeclaration | undefined;
/**
 * Read one finished tool result. Typed routes only — see the header's table.
 *
 * @param value   the value the MODEL read — a history message's `content`, or
 *                `tool_end.modelResult ?? result`. JSON text is parsed once, at
 *                this top level (the library's own serialization boundary);
 *                strings inside the value are the tool's and are never parsed.
 * @param context the app's `rowsAt` for this tool, and the door the record
 *                holds for this call (absent for an earlier answer's result).
 */
export declare function readEmptiness(value: unknown, context?: EmptinessContext): EmptinessReading;
