agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
66 lines (65 loc) • 3.54 kB
TypeScript
/**
* requestSealing — a host hands each run the credential that arrived with ITS
* request, sealed for that run alone.
*
* `requestContext({ secrets })` (`agentfootprint/security`) seals a request's
* secrets; `requestCredentials` vends them to the tools that declare a `needs`
* for them. A host that runs the agent for you (`standingAgent`,
* `streamHandler`) cannot know which header holds the credential, so it asks
* the app — once per request, with the request's headers — through the
* {@link RequestSealer} option `sealRequest`, and passes the handle to that
* request's `run()` / `resume()` and nowhere else. The sealer is handed the
* headers ONLY: nothing about who is calling, so it can never be tempted to
* look a stored credential up by a name the caller could have claimed.
*
* THE RULES, in one funnel ({@link requestSealing}):
* - asked only for a request that runs, after the caller is bound and every
* refusal is behind it — a refused request never seals anything;
* - the answer is a handle `requestContext` built, or `undefined` (no
* request: a tool that needs one fails closed, by name);
* - ONE HANDLE, ONE RUN, PROCESS-WIDE: a handle any host was handed before
* is refused, so a sealer that caches one — even one shared by two doors
* — cannot hand one caller's credential to another caller's run;
* - a sealer that throws refuses the request in FIXED words
* (`ERR_REQUEST_NOT_SEALED`, 400). Its own message is never sent: it may
* quote the value it could not read.
* The handle never enters a frame, a stored session or a checkpoint; the
* headers never leave the sealer.
*
* Pattern: guarded strategy call. Role: hosting, run input.
*/
import { type RequestContext } from '../identity/requestContext.js';
/** What a host hands the app's {@link RequestSealer} — one request's headers, and nothing else. */
export interface RequestSealFacts {
/**
* The request's headers, names lower-cased — where a per-request credential
* travels (a gateway's encrypted token, a session-bound key). Untrusted, as
* every header is: what a sealer reads here is the CALLER's credential for
* the caller's own run, never proof of who the caller is — so seal what the
* request CARRIES, and never look a stored credential up by a header.
*/
readonly headers: Readonly<Record<string, string>>;
}
/**
* The app's port that seals one request's credential for its run: return
* `requestContext({ secrets })`, or `undefined` for a request that carries
* none. Called once per request that runs; return a NEW handle every time.
*
* @example
* ```ts
* sealRequest: ({ headers }) =>
* headers['x-crm-token'] === undefined
* ? undefined
* : requestContext({ secrets: { crmToken: headers['x-crm-token'] } }),
* ```
*/
export type RequestSealer = (facts: RequestSealFacts) => RequestContext | undefined | Promise<RequestContext | undefined>;
/** The code a request refused by its sealer carries — the status is 400. */
export declare const REQUEST_NOT_SEALED = "ERR_REQUEST_NOT_SEALED";
/**
* The one funnel a host seals through: the app's sealer, checked. `undefined`
* when no sealer was given — the host passes no request, exactly as before.
*
* @throws TypeError at construction when `sealer` is not a function.
*/
export declare function requestSealing(sealer: RequestSealer | undefined, door: string): ((facts: RequestSealFacts) => Promise<RequestContext | undefined>) | undefined;