agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
196 lines • 11.2 kB
TypeScript
/**
* semantics/envelope — minting, recognizing and projecting the semantic
* tool-result envelope (9.53.0).
*
* Pattern: minted by a helper, RECOGNIZED by the framework (the `absent()` /
* tool-effects precedent). A return shape the framework does not
* understand is a convention, and a convention cannot ride the
* record, feed a UI, or be refused by a build gate.
* Role: lib/ layer, pure. The dispatch loop calls `readSemantics` at the
* execute boundary; `describedResult()` is what a tool author writes;
* `checkSemantics` (check.ts) judges the same shapes offline.
* Emits: N/A (the caller emits `agentfootprint.tools.semantics_declared`).
*
* ## Two views of one envelope — the design decision, stated
*
* The MODEL reads a compact, rendering-free projection
* ({@link semanticsForModel}): the data (`series`/`facts`/`edges`), the
* caveats that must travel with it (`grain`, `provenance`), the composed
* `not_covered` prose, a non-null `clarify`, and the static note. Dropped
* from the model's view: the `af_semantics` marker (machine-only), `render`
* (a UI hint — the tool never renders, and a model parroting rendering
* directives is noise), the three-list `coverage` detail (it rides the
* coverage channel and the record; the model reads the composed
* `not_covered` lines instead), and a `clarify: null` (a stated non-question
* is a fact for the record, not something the model acts on).
*
* The RECORD gets everything: the full envelope — render, coverage,
* marker and all — lands on the `tools.semantics_declared` event BEFORE the
* result ceiling is measured, so grain and provenance survive to recordings
* and UIs even when the content itself is refused as oversized. The
* `coverage` field is additionally declared through the SAME channel the
* `coverage()` primitive uses (`tools.coverage_declared`, tracked state, the
* final-answer limits block) — absorbed, never duplicated.
*
* ## One rule set for authoring and recognition
*
* The mint refuses a declaration this vocabulary cannot honor at the
* CALL SITE (the `absent()` law) — so a minted envelope is honest by
* construction: series carry their grain, data carries its provenance,
* counter-looking aggregations state `is_counter`. `semanticIssues()` judges
* the RENDERED shape — the same rules over a value somebody may have built
* by hand — and is what recognition and the `check:semantics` gate both
* stand on. Recognition is STRICT (the zero-cost guarantee): a marker-
* bearing value with any issue is NOT recognized — it keeps its bytes on
* the data path (dev-warned, and named field-by-field by the gate), because
* this library does not half-apply a shape it cannot fully honor.
*
* ## One authoring door, one unchanged wire
*
* `describedResult()` declares camelCase names, then `mintSemantics` applies
* the same rule set used to recognize stored snake_case envelopes. Validation
* and record reading keep one owner; old wire records never need an adapter.
*/
import { type DeclaredPeriod } from './coverage/period.js';
import type { Coverage } from './coverage/types.js';
import { type DescribedResultDeclaration, type SemanticCoverage, type ToolSemantics } from './types.js';
/** The codes an envelope can be faulted with — shared by recognition (any
* issue ⇒ not recognized) and the `check:semantics` gate (issues become
* findings under these same names). */
export type SemanticIssueCode = 'malformed-semantics' | 'series-without-grain' | 'counter-aggregation-unstated' | 'data-without-provenance';
/** One fault, naming the field so a refusal can teach and a gate can point. */
export interface SemanticIssue {
readonly code: SemanticIssueCode;
/** The offending / missing field, dot-pathed ('grain.is_counter'). */
readonly field: string;
readonly message: string;
}
/** Whole-token match against {@link COUNTER_AGGREGATION_WORDS}, singular or
* plural, case-insensitive — 'sum' and 'Counts' look like counters,
* 'summary' does not. */
export declare function isCounterLookingAggregation(aggregation: string): boolean;
/**
* The keys a {@link SemanticClarify} has — spelled the same in declarations
* and recorded envelopes — tied to the type in BOTH directions.
*/
export declare const CLARIFY_DECLARATION_KEYS: readonly string[];
/**
* The six fields whose WIRE name is snake_case — the only names a declaration
* door may spell differently. Everything else (`interval`, `source`,
* `default`, the data rows, the eight top-level fields) is one word on both
* sides.
*/
export type SpelledField = 'is_counter' | 'measured_at' | 'age_seconds' | 'source_export_date' | 'filter_note' | 'chart_hint';
/** How one door's author spells each {@link SpelledField}. */
export type Spelling = Readonly<Record<SpelledField, string>>;
/** The wire's own spelling — what `semanticIssues`, recognition, the gate
* speak. */
export declare const WIRE_SPELLING: Spelling;
/** The objects inside a declaration whose keys a door checks by name. */
export type DeclaredObject = 'grain' | 'provenance' | 'coverage' | 'clarify' | 'render';
/** The objects inside a declaration a door copies into their wire spelling. */
export type RespelledObject = 'grain' | 'provenance' | 'render';
/**
* How the authoring door spells its declaration and translates it to the
* wire. The rule set is shared with record recognition; only field spelling
* differs between authoring refusals and stored-record findings.
*/
export interface DeclarationDoor {
/** The helper's name, for the one refusal that names the call itself. */
readonly name: string;
/** The top-level keys a declaration may carry. */
readonly declarationKeys: readonly string[];
/** The keys that would hand-write the DERIVED `not_covered` prose list. */
readonly derivedKeys: readonly string[];
/** Each object a declaration may carry, with the keys it has in this
* door's spelling — refused BEFORE anything is copied. */
readonly objectKeys: ReadonlyArray<readonly [field: DeclaredObject, known: readonly string[]]>;
/** Copy one declared object into the wire's spelling, keeping key order.
* What it does with a value that is not a plain object is the door's own
* call: the rule set names whatever reaches the candidate. */
readonly toWire: (field: RespelledObject, value: unknown) => unknown;
/** How this door's author spells the six snake_case wire fields. */
readonly spelling: Spelling;
}
/** Compose the `not_covered` prose lines FROM coverage — the one derivation,
* used by the mint and by the drift check, so the two can never disagree. */
export declare function composeNotCovered(coverage: SemanticCoverage): readonly string[];
/**
* THE provenance rule — one present `provenance` object, in the wire's
* spelling, judged; every fault, each naming its field the way `s` spells it.
* `issuesIn` asks it for a described result, and `coverage/absent.ts` asks it
* for an absence's `provenance` (honesty step 7b), so the two doors cannot
* disagree about what a well-formed source and time is: `measured_at` and
* `source` required, `age_seconds` a finite number ≥ 0, `source_export_date`
* a non-empty string, no other key.
*/
export declare function provenanceIssues(provenance: unknown, s: Spelling): SemanticIssue[];
/**
* Judge one RENDERED envelope shape against the whole rule set. Empty = a
* well-formed envelope this library can honor. Non-empty = the faults, each
* naming its field.
*
* Called with values that carry the marker; on anything else it reports the
* missing marker rather than guessing.
*/
export declare function semanticIssues(value: unknown): readonly SemanticIssue[];
/**
* The ONE mint behind `describedResult()`. Refuses (throws, at the call site — the `absent()` law) any
* declaration this vocabulary cannot honor, in the door's own words; returns
* the rendered envelope otherwise.
*
* In order: the declaration must be an object; a hand-written `not_covered`
* is refused as derived; every key the declaration and its objects carry must
* be one the door reads (a casing slip names the spelling meant); coverage is
* normalized; the candidate is built in the WIRE spelling; and the whole rule
* set judges it, naming each field as the door's author spelled it.
*/
export declare function mintSemantics(decl: DescribedResultDeclaration, door: DeclarationDoor): ToolSemantics;
/**
* Recognize (or decline to recognize) a value as a semantic envelope —
* STRICT, and the strictness is the zero-cost guarantee. Only a plain object
* whose `af_semantics` is exactly `true` AND that passes the whole rule set
* qualifies; every other value any tool has ever returned takes the path it
* always took, byte for byte.
*
* `undefined` means "not an envelope this library can honor" — a marker-
* bearing value with faults stays DATA (never half-applied); the dispatch
* loop dev-warns it and `check:semantics` names every fault.
*/
export declare function readSemantics(value: unknown): ToolSemantics | undefined;
/**
* Name what is wrong with a value that CARRIES the marker but was not
* recognized. `undefined` for values without the marker (they are data, not
* near-misses) and for well-formed envelopes. Diagnosis only — never changes
* what any value does.
*/
export declare function explainSemantics(value: unknown): readonly SemanticIssue[] | undefined;
/**
* The MODEL's view of one recognized envelope — compact and rendering-free.
*
* Keeps: the data (`series`/`facts`/`edges`), the caveats that must travel
* with it (`grain`, `provenance`), the composed `not_covered` prose, a
* non-null `clarify`, and the static note. Drops: the marker, `render`
* (UI hint), the three-list `coverage` detail (rides the coverage channel
* and the record), and a `clarify: null`. Shallow-copied so the history
* entry is not the object the tool still holds.
*
* The period is served as the tool declared it — plus, when the store did not
* hold all of the time the read asked about, the verdict word inside it and
* that word's one clause after the note (`coverage/period.ts` ·
* `servedPeriod`; honesty step 7b, bench round 1). A `covered` period and an
* envelope with none are served byte for byte as before.
*/
export declare function semanticsForModel(sem: ToolSemantics): Record<string, unknown>;
/**
* The period a RECOGNIZED envelope declared, in the record's camelCase form
* (honesty step 7b) — `undefined` when it declared none. Recognition already
* held the period to the rule set (a fault keeps the whole envelope data), so
* this is a read, never a judgement.
*/
export declare function periodOfSemantics(sem: ToolSemantics): DeclaredPeriod | undefined;
/** The envelope's coverage in the normalized three-list shape the coverage
* machinery reads — how `readCoverageResult` absorbs a semantic envelope's
* boundary into the one coverage channel. */
export declare function coverageOfSemantics(sem: ToolSemantics): Coverage;
//# sourceMappingURL=envelope.d.ts.map