agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
114 lines (113 loc) • 5.83 kB
TypeScript
/**
* time/forms — every spelling of a time the answer may write, split by
* WHO produced it (time design § 9.5, step T7).
*
* Pattern: a pure function over recorded values — the person's text, or one
* window the record holds — never over the answer. The evidence gate
* asks it (`evidence/evidenceIndex.ts` · `exemptFromRun`,
* `stages/timeLineage.ts` · `timeLineageOf`) and never keeps a time table of
* its own.
* Role: L0 leaf (the time layer). Imports `windows.ts`, `rows.ts`,
* `resolve.ts`, `textForms.ts` (the text rule, split out so the
* evidence gate's synchronous corpus never loads the resolver),
* `instant.ts` and `zone.ts` only. Loaded under `.time()` only,
* through `import()` (`stages/timeLineage.ts`).
* Emits: N/A.
*
* ## The two lists
*
* - **`said`** — spellings of the parts the PERSON said, at the grain they
* said them: "8 AM" said at grain hour is also `8:00` and `08:00` — the
* same instant at that grain, equality and not a new fact. They count as
* the person's words (the gate exempts them).
* - **`derived`** — everything the LIBRARY produced from a reading: an
* implied year, the zone's abbreviation in effect (`PDT` for a said `PST`),
* offsets, UTC and epoch spellings, the end-of-grain `08:41`, a look-back's
* duration, the window's length ("6:25 to 6:45" is `20-minute` as said and
* `21-minute` as read to the end of its grain — arithmetic over the ends,
* never a part anyone wrote), every part of a `model` reading, every part
* of a PROPOSAL — a `rule` reading the time ask offers that the person has
* not answered yet (`06:25:00` for "6:25"; its implied year) — and the
* turn's CLOCK, at the grain it is served (G16: "This turn's time: …" on
* every request under `.time()`): the minute of `now` in the person's zone
* (UTC under an unknown zone) — `7:08`, `07:08`, `7:08am`, the date, the
* year, the zone and its offset. Never a time the clock does not produce
* (`7:09`, or the clock's seconds). And a source's HELD span the served
* time line names in the person's zone (take 3): each end's wall spellings
* there and in UTC. The gate files an answer
* value found only here with the lineage `derived-from-reading`: it can
* support "not sure", never "known", and it is never called invented.
*
* A time value no recorded reading produced is in neither list and still
* fails the gate. Library text is never evidence: without `derived`, a model
* echoing the served time line back would read as the person's words.
*/
import { type TimeForms } from './textForms.js';
import { type TurnWindow } from './windows.js';
import { type TimeCandidate } from './resolve.js';
import { type ClockRow } from './rows.js';
import { type ZoneName } from './zone.js';
export type { TimeForms };
/** One window the record holds, as the spellings read it. */
export interface FormsWindow extends Pick<TurnWindow, 'source' | 'range' | 'zone' | 'lookback'> {
/**
* The reading this window IS, when the person confirmed a `rule` reading
* of their words — which parts they WROTE, and how the range was read.
* Absent: a window they typed in the ask, a UI control, a `model` reading.
*/
readonly reading?: Pick<TimeCandidate, 'said' | 'notes' | 'grain'>;
/** On an `answered` window: the person picked the offered reading, or wrote their own. */
readonly answer?: TurnWindow['answer'];
}
/** The turn's clock as the record keeps it (`rows.ts` · `ClockRow`) — what the served time line names. */
export type FormsClock = Pick<ClockRow, 'now' | 'zone'>;
/**
* A source's held span as the served time line names it (`coverage/timeLimits.ts`
* · `heldLine`): the instants the tool declared, and the person's zone the
* line renders them in.
*/
export interface FormsHeld {
readonly from: string;
readonly to: string;
readonly zone: ZoneName;
}
/** What {@link timeFormsOf} spells: words the person or the app wrote, one recorded window, the turn's clock, or a source's held span. */
export type TimeFormsSource = {
readonly text: string;
} | {
readonly window: FormsWindow;
} | {
readonly clock: FormsClock;
} | {
readonly held: FormsHeld;
};
/**
* The spellings of one source's times, split by lineage (§ 9.5).
*
* A `text` — the person's message, their typed answer, the app's prompt —
* has only `said` spellings, and each is a SPELLING, never an
* interpretation: an ISO date → its year, month and day; a 12-hour reading
* (`8 Am`) → its 24-hour, padded and glued forms; a 24-hour reading
* (`20:00`) → the same set. Slash dates are never read (their order is a
* locale) and durations never (a quantity is not a time of day).
*
* A `window` — the turn's recorded window — splits by who produced each
* part: the person's said parts at grain, or everything of a window they
* typed or set in a UI, are `said`; the rest is `derived`. A `model`
* reading's window (`derived-from-reading`) has no `said` spelling at all.
*
* @example
* ```ts
* timeFormsOf({ text: 'what connected 8 Am to 8:40 AM PST' }).said;
* // ['8:00', '08:00', '8:00am', '8:40', '08:40', '8:40am']
* timeFormsOf({ text: '2026-10-09' }).said; // ['2026', '10', '9']
* timeFormsOf({ text: 'took 2h' }); // { said: [], derived: [] }
* ```
*/
export declare function timeFormsOf(source: TimeFormsSource): TimeForms;
/**
* Every recorded window of the LATEST turn on the ledger (its `clock` row's
* turn), with the reading each one is — what the gate spells. Empty when no
* clock was filed (an agent without `.time()`).
*/
export declare function turnFormsWindowsOf(ledger: readonly unknown[] | undefined): readonly FormsWindow[];