UNPKG

agentfootprint

Version:

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

83 lines • 4.23 kB
"use strict"; /** * hosting/signin/audit — the sign-in door's audit trail: one record per * sign-in outcome, handed to a sink the app owns AND written as one log line. * * const door = signInDoor({ * passwords, store, publicUrl, production, * onAudit: (record) => securityLog.write(record), // typed, optional * // log: (line) => logger.info(line), // default: console.info * }); * * ── Why a door option and not a typed event ───────────────────────────────── * Every typed event carries `EventMeta`, whose `runId` and `runtimeStageId` * are REQUIRED, and is emitted by one agent's dispatcher. A sign-in is never a * run: no agent exists when a person types their password. The library's own * answer for door facts that happen before any run is a SINK at the door — * `standingAgent({ onIngressDecision })` (ingressRecord.ts) — and this is the * same shape, owned by the one place every sign-in outcome is decided. * * ── What a record carries, and what it never does ─────────────────────────── * Classes and identifiers only: the outcome, a reason class, the strategy, the * ACCOUNT the typed name reaches (the checker's `budgetKey` — `alice`, * `CORP\alice` and `alice@corp.example` are all `alice`), the id the strategy * PROVED, the client address after trusted-proxy resolution, the time, and on * `directory-password` the AD sub-code with its name. Never the password, * never a cookie or its key, never a token, never an error's message. * * ── It is not the audit hash chain ────────────────────────────────────────── * Like the ingress record it is a STREAM into your own sink, not hashed, not * sequenced and not verifiable by `verifyAuditBundle`. Chain it in your own * store if you need tamper evidence. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.signInAuditTrail = exports.signInAuditLine = exports.SIGN_IN_AUDIT_PREFIX = void 0; /** The prefix of the door's audit log line; the rest is the record as JSON. */ exports.SIGN_IN_AUDIT_PREFIX = '[identity] sign-in'; /** * The record as ONE log line: the prefix, then a JSON object with `time` (ISO * 8601, UTC) in place of `at`. JSON so a name holding a quote or a space can * never forge a second field, and so a log pipeline parses it without a * grammar of its own. */ function signInAuditLine(record) { const { at, ...rest } = record; const json = JSON.stringify({ time: new Date(at).toISOString(), ...rest }); // JSON leaves U+2028/U+2029 and the bidi controls raw; a viewer may break a // line at the first or reorder the text after the second. Escaped, the line // still parses to the same record. return `${exports.SIGN_IN_AUDIT_PREFIX} ${json.replace(LINE_UNSAFE, escapeChar)}`; } exports.signInAuditLine = signInAuditLine; /** Line and paragraph separators, and the bidi embedding/override/isolate controls. */ const LINE_UNSAFE = /[\u2028\u2029\u202a-\u202e\u2066-\u2069]/g; function escapeChar(ch) { return `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`; } function signInAuditTrail(options) { let warned = false; const contained = (where, run) => { try { run(); } catch (err) { if (warned) return; warned = true; options.warn(`[hosting] signInDoor: the ${where} threw and a sign-in record was dropped. Sign-in is ` + `unaffected, but this door's audit trail is not being recorded. Reported once per ` + `door. Cause: ${err?.name ?? 'unknown'}`); } }; return { file(fields) { const record = { at: options.now(), ...fields }; const sink = options.sink; if (sink !== undefined) contained('onAudit sink', () => sink(record)); contained('audit log', () => options.log(signInAuditLine(record))); }, }; } exports.signInAuditTrail = signInAuditTrail; //# sourceMappingURL=audit.js.map