UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

76 lines (75 loc) • 4.11 kB
/** * frozenCopyOf — a callback's copy of a value, frozen all the way down. * * Role: Support. A leaf: it reads its argument and nothing else. * Reads: its argument. * Emits: one dev-mode warning per kind of callback ({@link warnFrozenWrite}). * * The boundary between a call's arguments and the app code that looks at them * before the call runs (a `checkIn` predicate, the evidence assembler, the * permission checker, a `tool_start` listener). App callbacks are trusted * code, and trusted code can still be buggy: a predicate that writes into the * object it was handed would otherwise write into the arguments the tool then * runs with — after a person approved them, or after the predicate itself * answered "no check-in needed". So the callback is handed a copy * (`structuredClone`) that the tool never runs with, frozen at every depth: a * write to it throws in strict code and is ignored in sloppy code, and either * way the arguments that run are untouched. * * A copy freezing SEALS ({@link isSealed}) cannot change, so ONE is shared by * every callback and event that sees a call before it runs ({@link frozenView}): * a large argument is copied once, not once per callback. That is every JSON * value — all a model ever proposes. A copy holding a container `Object.freeze` * cannot seal — a `Map` or `Set` (their own `set`/`add`/`delete`), a `Date` * (its setters), an `ArrayBuffer` or a typed array or view (their bytes) — is * NOT sealed: one callback could still change what the next one judges, so * such a copy is never shared and each callback is handed its own. (Refusing * such arguments instead would break a tool an app hands a `Date` or a `Map` * through its own provider or middleware; a copy per callback costs only those * calls their extra copies.) A `SharedArrayBuffer` is shared, not copied, by * `structuredClone` — its bytes would be every copy's and the tool's — so each * one (and every view over one) is copied into a plain buffer first. * * `undefined` when no copy can be made (a function, a symbol, a foreign proxy * — what `structuredClone` refuses). The caller decides what that means, and * fails closed: a consent gate trips, a permission check denies. * * Never throws. */ export declare function frozenCopyOf<T>(value: T): T | undefined; /** * A frozen, detached projection of `value` for an event that must carry * SOMETHING when no copy can be made ({@link frozenCopyOf} gave `undefined`): * what JSON writes down of it (a function is dropped), or a frozen empty * object when JSON cannot write it either. Never a live object of `value`. */ export declare function frozenProjectionOf<T>(value: T): T; /** * Whether `value` is (part of) a copy {@link frozenCopyOf} made that freezing * sealed — nothing in it can change, so it may be shared between callbacks. * `false` for a copy holding a container freezing cannot seal (a `Map`, a * `Set`, a `Date`, an `ArrayBuffer`, a typed array or view), and for any value * this module did not copy. */ export declare function isSealed(value: unknown): boolean; /** * `value` itself when it is already (part of) a sealed copy ({@link isSealed}), * so sharing it is safe — else a new copy, which is the caller's alone when it * holds a container freezing cannot seal. */ export declare function frozenView<T>(value: T): T | undefined; /** * Whether `error` is what strict code throws on a write to a frozen object — * the engines' own words for assigning a read-only property, adding to an * object that is not extensible, deleting or redefining a property. */ export declare function isFrozenWriteError(error: unknown): boolean; /** * In dev mode, once per kind of callback: say that `who` wrote into the frozen * copy it was handed, and what that did (`consequence`). A no-op for any other * error, outside dev mode, and after the first warning for `who`. * * @example * warnFrozenWrite('permission checker', err, 'the call was denied'); */ export declare function warnFrozenWrite(who: string, error: unknown, consequence: string): void;