agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
201 lines (200 loc) • 10.9 kB
TypeScript
/**
* time/windows — the turn's windows, read from the record (time design
* § 5.6): the window the person settled in the time ask (`answered`), a
* `model` reader's one reading waiting for confirmation
* (`derived-from-reading`) and the clock's `control` window; the quotes still
* waiting on the person; and what the served sentence names
* (`readerWindowsOf`).
*
* Pattern: Walker over recorded rows — never over words, never the resolver.
* Role: the record half of `bind.ts`, split out BY FILE for the synchronous
* doors: the Tools mount's `inputMapper` (`agent/buildAgentChart.ts` ·
* `timeWindowsArg`) and the evidence gate's lineage read it before
* any `import()` can run, and a bundler places a whole file on the
* synchronous graph — so this file imports only the leaves and the
* row readers, never `convert.ts` or `resolve.ts` (the optional-family
* law, `test/trace-toolpack/browserGraph.test.ts`). `bind.ts`
* re-exports every public name.
* Emits: N/A (pure).
*
* @example
* ```ts
* readerWindowsOf(ledger);
* // { now: '2026-10-09T15:40:00Z', windows: [{ source: 'answered', quote: 'yesterday', … }] }
* ```
*/
import type { WindowToConvert } from './convert.js';
import type { DurationText } from './duration.js';
import type { InstantText } from './instant.js';
import { type TimeRange } from './range.js';
import type { TimeRefusal } from './periodForm.js';
import { type ClockRow, type TimeAnswerRow, type TimeReadingRow } from './rows.js';
import type { ZoneName } from './zone.js';
/**
* Who a window is: the window the person settled in the time ask
* (`answered`), a `model` reader's unconfirmed reading (`derived-from-reading`),
* or a UI control. `said` — a `rule` reading filed as the person's words — is
* no longer filed (the owner's decision "Always confirm", time design TQ29);
* it stays in the union so a record an earlier version filed still reads.
*/
export type WindowSource = 'said' | 'derived-from-reading' | 'answered' | 'control';
/** One window of the turn — a mention that resolved to one window, or the `control` window. */
export interface TurnWindow {
readonly source: WindowSource;
/** The `time-reading` row's mention index — absent on the `control` window. */
readonly mention?: number;
/** The person's words it was read from — absent on the `control` window. */
readonly quote?: string;
readonly range: TimeRange;
/** The look-back it was said as, when it was one. */
readonly lookback?: DurationText;
/** The zone the person meant, else the clock's. */
readonly zone: ZoneName;
/** On an `answered` window: whether the person picked the offered reading (`confirmed`) or wrote their own (`edited`). */
readonly answer?: TimeAnswerRow['how'];
}
/** The turn's windows, and how many mentions the turn holds in all (resolved or not). */
export interface TurnWindows {
readonly windows: readonly TurnWindow[];
/** Every mention with a quote, plus the `control` window — the "exactly one" count. */
readonly mentions: number;
/**
* Per mention still OPEN for the person (they have not answered it), the
* ranges of the readings left — what a tool's facts can already rule out
* (§ 6.3: every reading outside a tool's `direction` → nothing is asked, the
* call is refused). EMPTY for a mention the library holds no reading of yet
* — a zone to name first (`open` on `zone`), or words it could not read
* (`by: 'none'`): nothing to rule out, and the person is still asked
* (`isOpenForPerson`). Absent when no mention is open.
*/
readonly open?: readonly (readonly TimeRange[])[];
}
/**
* The look-back the library OFFERED for a reading, when the person's answer is
* exactly it (`last 2 hours` confirmed as offered) — the window then stays a
* look-back from now. `undefined` for any other answer (an edit, a range).
*/
export declare function offeredLookback(row: TimeReadingRow | undefined, range: TimeRange): DurationText | undefined;
/**
* What a turn window hands the conversion — its range and, when it was said
* (and answered) as a look-back, that look-back. THE one input every door
* that writes a window into a tool's arguments builds (the fill, the time
* ask's answer, the served line), so the value served to the model and the
* value the tool is handed are one spelling (time follow-ups, packet "gaps":
* the served line spelled `15:59:59Z` where the ask's fill handed `16:00:00Z`).
*/
export declare function windowToConvert(window: {
readonly range: TimeRange;
readonly lookback?: DurationText;
}): WindowToConvert;
/**
* Whether a mention the person has not answered still waits on THEM — the
* one owner of "a time the person wrote that the library holds no settled
* window for" (the lazy ask, the served line's pending half and the window
* plan all ask it): a reading still `open` (readings to confirm, a zone to
* name), and words the library holds NO reading of (`by: 'none'` — words the
* reader could not read, or no candidate). The person said a time either way,
* so a tool's default never stands in for it (time follow-ups, packet "gaps":
* an unreadable phrase to a tool whose rule assumed its period ran that
* default silently). A `model` reading's one window is not open: it fills as
* a reading. A refused mention (its quote was not in the text) is not the
* person's words.
*/
export declare function isOpenForPerson(row: TimeReadingRow): boolean;
/**
* This turn's windows, read from the record: per mention, the window the
* person settled in the time ask (`answered`, its latest `time-answer` row),
* else a `model` reader's one window waiting for confirmation
* (`derived-from-reading`); and the clock's `control` window. A `rule`
* reading the person has not answered, and words the library could not read,
* are counted and are OPEN ({@link isOpenForPerson}).
*/
export declare function turnWindowsOf(readings: readonly TimeReadingRow[], clock: ClockRow | undefined, answers?: readonly TimeAnswerRow[]): TurnWindows;
/** The windows of the person's words this turn, with the turn's clock — what the served sentence names. */
export interface ReaderWindows {
readonly now: InstantText;
/** Each settled mention's window (`answered`, or a `model` reader's), in mention order — never the `control` window. */
readonly windows: readonly TurnWindow[];
/**
* The window the person set in the app's time control this turn (the run's
* `time.window`, recorded on the `clock` row, `source: 'control'`) — served
* on its own sentence, so an app never writes its own prompt text for it.
* Absent when the run set none.
*/
readonly control?: TurnWindow;
/**
* The quote of each mention the library holds only as a PROPOSAL — a `rule`
* reading the person has not answered yet (its choice still `open`: a zone
* to name, readings to confirm) — in mention order. The served sentence
* names them as not confirmed, so a model that would write its own window
* leaves the period out and the time ask confirms it (step T6b bench,
* `bench/time/`). Absent when none is pending.
*/
readonly pending?: readonly string[];
/**
* The quotes in `pending` the library holds NO reading of yet: the words
* name a zone it cannot resolve (an abbreviation outside the app's map), so
* the time ask asks for the zone and pre-fills nothing (the choice `open`
* on `zone`, no candidates). The served line says so instead of promising a
* form that shows a reading (T6b paid run: `field-pst`). Absent when none.
*/
readonly pendingZones?: readonly string[];
/**
* The quotes in `pending` the library could not READ at all (`by: 'none'`:
* words outside the reader's set, or no candidate): the time ask asks the
* person which time they meant, with nothing pre-filled. The served line
* says so instead of promising a reading. Absent when none.
*/
readonly pendingUnread?: readonly string[];
/**
* A mention is pending AND a call of this turn already ran on a window the
* model wrote into it (a `call-window` row `how: 'model'` — § 7.3: it runs
* as sent, unconfirmed). The served line then names that limit for the
* answer instead of the move that would have asked the person. Absent
* otherwise.
*/
readonly ranUnconfirmed?: true;
/**
* The person's windows a tool REFUSED this turn, before dispatch (a
* `call-window` row `how: 'refused'`): the quote, the tool and the reason
* code — a window of the person's the call carried (`person.mention`), or
* the turn's one open reading, refused in every reading (a row with no
* range). The served line turns each into the conclusion an answer states,
* so a refused window is never asked for again as though a call could read
* it. A window the model wrote is not the person's and is not named. Absent
* when none was refused.
*/
readonly refused?: readonly RefusedWindow[];
/**
* The turn's clock zone is UNKNOWN (`zoneSource: 'unknown'`, G15): the
* control window is spelled in UTC and the served line names the person's
* zone as not known. Absent otherwise.
*/
readonly zoneUnknown?: true;
}
/** One window of the person's a tool refused before dispatch — what the served line concludes. */
export interface RefusedWindow {
readonly quote: string;
readonly toolName: string;
readonly refused: TimeRefusal;
/** On a `dst-gap`: the argument whose wall time the zone skips. */
readonly argument?: string;
}
/**
* The quotes of this turn's mentions still waiting on the person
* ({@link isOpenForPerson}), not answered in the time ask: a reading to
* confirm, a zone to name, or words the library could not read (asked with
* nothing pre-filled). A `model` reading's one window is served as a reading;
* a refused mention is not the person's words.
*/
export declare function pendingQuotesOf(readings: readonly TimeReadingRow[], answers?: readonly TimeAnswerRow[]): readonly string[];
/**
* The latest turn's windows of the person's words, read off the ledger (its
* last `clock` row and that turn's `time-reading` and `time-answer` rows) —
* `undefined` when the turn has no clock, no settled mention, none pending
* and no `control` window. A `rule` reading is named as a window only once
* the person answered it in the time ask; before that its quote is
* `pending`. The `control` window is no reading: it rides `control`, never
* `windows`.
*/
export declare function readerWindowsOf(ledger: readonly unknown[] | undefined): ReaderWindows | undefined;