UNPKG

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
/** * 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