agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
224 lines (223 loc) • 10.9 kB
TypeScript
/**
* time/periodForm — a tool's period FORMS as DECLARED (time design § 7.1):
* the shapes, the sugar, each form's own rules, a declared value's spelling,
* and the tool's facts against a range.
*
* Pattern: the declaration half of `convert.ts`, split from the conversions so
* the SYNCHRONOUS doors — `defineTool({ period })`
* (`tools/declare.ts`), a time answer's check at the resume
* door (`ask.ts` · `checkTimeAnswer`) and the checkpoint door's
* refusal codes (`rows.ts`) — reach it without the conversion
* engine, which only an armed agent loads (`bind.ts`, `drift.ts`,
* reached through `import()`: the optional-family law of docs-next's
* site budget). `convert.ts` re-exports everything public here.
* Role: L0 leaf (the time layer). Imports `instant.ts`, `duration.ts`
* and `zone.ts` only.
* Emits: N/A.
*
* @example
* ```ts
* const [form] = sugarForms({ argument: 'window', spelling: 'iso-range' });
* formIssue(form); // undefined — a well-formed form
* parsesUnderForm('2026-10-09T08:00:00-07:00..2026-10-09T08:40:59-07:00', form, 'window'); // true
* ```
*/
import { type DurationText } from './duration.js';
import { type InstantText } from './instant.js';
import { type Edge, type TimeRange } from './range.js';
import { type WallTime } from './zone.js';
/** The single-argument spellings — today's three, and `wall-range` (wall times plus a zone argument). */
export type PeriodSpelling = 'lookback' | 'signed-lookback' | 'iso-range' | 'wall-range';
/** The spellings, in the order the docs list them. */
export declare const PERIOD_SPELLINGS: readonly PeriodSpelling[];
/** How one bound is written: an instant with its offset, epoch milliseconds or seconds, a calendar date, a wall time. */
export type BoundAs = 'iso' | 'epoch-ms' | 'epoch-s' | 'date' | 'wall';
export declare const BOUND_AS: readonly BoundAs[];
/**
* One bound of a two-argument period. A `from` bound is inclusive (its `edge`,
* when written, can only say so); a `to` bound MUST say its edge
* ({@link ToBound}) — TQ18: the library never guesses a tool's edge, only the
* sugar defaults (`inclusive`).
*/
export interface Bound {
readonly argument: string;
readonly as: BoundAs;
readonly edge?: Edge;
}
/** The end bound of a `bounds` form — its edge is declared, never defaulted (TQ18). */
export interface ToBound extends Bound {
readonly edge: Edge;
}
/** The argument a wall or date form reads its zone from. */
export interface ZoneArgument {
readonly argument: string;
}
/**
* Every shape a tool's period can take — one `TimeRange` onto one or more
* arguments. Any form may carry its own `maxRange` — the widest window THAT
* form reads at once — when the tool's forms differ (bounds capped at a day,
* a look-back with no cap): it overrides the period's `maxRange` for that
* form only ({@link formMaxRange}, the one owner).
*/
export type PeriodForm = {
readonly kind: 'bounds';
readonly from: Bound;
readonly to: ToBound;
readonly zone?: ZoneArgument;
readonly maxRange?: DurationText;
} | {
readonly kind: 'joined';
readonly argument: string;
readonly as: BoundAs;
readonly joiner: '..' | '/';
readonly edge?: Edge;
readonly zone?: ZoneArgument;
readonly maxRange?: DurationText;
} | {
readonly kind: 'object';
readonly argument: string;
readonly keys: {
readonly from: string;
readonly to: string;
};
readonly as: BoundAs;
/** Declared, never defaulted (TQ18). */
readonly edge: Edge;
readonly zone?: ZoneArgument;
readonly maxRange?: DurationText;
} | {
readonly kind: 'day';
readonly argument: string;
readonly zone?: ZoneArgument;
readonly maxRange?: DurationText;
} | {
readonly kind: 'lookback';
readonly argument: string;
readonly signed: boolean;
/** A unit set ⊆ `smhdw`; absent → today's `mhdw`. */
readonly units?: string;
readonly maxRange?: DurationText;
};
export declare const FORM_KINDS: readonly PeriodForm['kind'][];
/** Which side of now a source can hold. */
export type PeriodDirection = 'past' | 'future' | 'any';
/** Facts about the source — never policy (§ 7.1). Durations are in `smhdw`. */
export interface PeriodFacts {
readonly direction?: PeriodDirection;
readonly retention?: DurationText;
readonly maxRange?: DurationText;
readonly granularity?: DurationText;
readonly filtersToAsked?: boolean;
}
/** The unit set of a fact's duration (`retention`, `maxRange`, `granularity`). */
export declare const FACT_UNITS = "smhdw";
/** The step a look-back ending "at now" may miss by when no `granularity` is declared (§ 7.4). */
export declare const DEFAULT_GRANULARITY_MS = 60000;
/** The form one single-argument spelling means (the module table). */
export declare function sugarForm(argument: string, spelling: PeriodSpelling, zoneArgument?: string): PeriodForm;
/** The sugar and general halves of a period declaration, as `sugarForms` reads them. */
export interface PeriodShapes {
readonly argument?: string;
readonly spelling?: PeriodSpelling;
readonly accepts?: readonly PeriodSpelling[];
readonly zoneArgument?: string;
readonly forms?: readonly PeriodForm[];
}
/**
* Every form a declaration names, in preference order: `forms` as written,
* else the sugar (`accepts`, else `spelling`). A declaration that names an
* argument and no spelling has no form — today's "which argument", nothing
* the library can convert into.
*/
export declare function sugarForms(period: PeriodShapes): readonly PeriodForm[];
type Plain = Record<string, unknown>;
export declare const isPlain: (value: unknown) => value is Plain;
/**
* Why one form's own shape is not a form, or `undefined` when it is one. The
* schema half (does the argument exist, is it the right type, does it carry a
* rule) is `tools/declare.ts`'s — this is the shape alone.
*/
export declare function formIssue(form: unknown): string | undefined;
/** Whether a form reads wall-clock values and so needs a zone (`wall`, `date`, a `day`). */
export declare function needsZone(form: PeriodForm): boolean;
/** One argument a form names, and what the schema must say of it. */
export interface FormArgument {
readonly argument: string;
/** `bound`: carries a value of the window; `zone`: carries the zone; `object`: an object of both bounds. */
readonly role: 'bound' | 'zone' | 'object';
/** The JSON Schema type the property must have: a number for an epoch, else a string. */
readonly type: 'number' | 'string' | 'object';
/** For a `bound` of a `bounds` form — the bound it carries. */
readonly bound?: Bound;
}
/** Every argument a form names, the zone argument last. */
export declare function formArguments(form: PeriodForm): readonly FormArgument[];
/** The argument a form is known by — the results layer's join key (its first bound). */
export declare function primaryArgument(form: PeriodForm): string;
/** A wall time written with no offset, and the step its spelling is precise to. */
export declare function wallOf(text: unknown): {
readonly wall: WallTime;
readonly stepMs: number;
} | undefined;
/** A calendar date `YYYY-MM-DD`, day-checked. */
export declare function dateOf(text: unknown): {
readonly year: number;
readonly month: number;
readonly day: number;
} | undefined;
/** Whether a single bound value is written the way `as` declares. */
export declare function isBoundValue(value: unknown, as: BoundAs): boolean;
export declare const epochFromText: (text: string) => number | undefined;
/**
* Whether a declared value (an `assume` default, an `ask` choice) of
* `argument` is written the way `form` spells that argument. A format check:
* no clock, no conversion. `true` for an argument the form does not name.
*/
export declare function parsesUnderForm(value: unknown, form: PeriodForm, argument: string): boolean;
/** Why a range breaks one of the tool's facts — a code; the sentence is the catalog's. */
export type PeriodFactProblem = 'time-future' | 'time-past' | 'beyond-retention' | 'over-max-range';
/**
* Which of the tool's declared facts a half-open range breaks, against the
* turn's clock — or `undefined`. `past`: a range that starts after now;
* `future`: one that ends before now; `retention`: only a range WHOLLY older
* than `now − retention` (a partial overlap is the result's to judge);
* `maxRange`: a range that REACHES further than it (`range.ts` · `reachMs`:
* first instant to last, so a look-back exactly `maxRange` long fits — an
* inclusive ceiling). A fact the tool does not declare is not checked.
*/
export declare function periodFactProblem(range: TimeRange, facts: PeriodFacts, now: InstantText): PeriodFactProblem | undefined;
/**
* The widest window ONE form reads at once: its own `maxRange`, else the
* period's — the one owner every door asks (the conversion, a sent window's
* check, the answer to a period argument). `undefined`: no cap for that form.
*
* @example
* ```ts
* formMaxRange({ kind: 'bounds', from, to, maxRange: '24h' }, { maxRange: '7d' }); // '24h'
* formMaxRange({ kind: 'lookback', argument: 'w', signed: false }, { maxRange: '7d' }); // '7d'
* ```
*/
export declare function formMaxRange(form: PeriodForm, facts: PeriodFacts | undefined): DurationText | undefined;
/** The facts ONE form is judged against: the period's, with the form's own `maxRange` in place. */
export declare function formFacts(form: PeriodForm, facts: PeriodFacts): PeriodFacts;
/**
* The facts the TOOL is judged against before any form is chosen (a window
* of the person's to fill, a time ask's choices): `maxRange` is the widest
* any form reads — none when one form reads with no cap — so a window is
* refused `over-max-range` only when NO form can read it. The period's facts
* as declared when no form declares its own `maxRange`.
*/
export declare function toolFacts(forms: readonly PeriodForm[], facts: PeriodFacts): PeriodFacts;
/**
* Why a call is refused before dispatch (§ 7.2, step T5b): one of the tool's
* facts ({@link PeriodFactProblem}), a range across days to a tool that reads
* one day (`multi-day`), a wall time the zone skips (`dst-gap`), or a window
* of the person's that no form the tool declares can read, exactly or by
* reading a wider one (`no-form-holds` — a look-back ends at now, so a window
* still running is out of its reach, and a covering look-back wider than
* `maxRange` is one the tool will not read). The tool's own rule never stands
* in for the person's window: its default would read a time nobody asked about.
*/
export type TimeRefusal = PeriodFactProblem | 'multi-day' | 'dst-gap' | 'no-form-holds';
export declare const TIME_REFUSALS: readonly TimeRefusal[];
export {};