UNPKG

agentfootprint

Version:

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

111 lines • 5.89 kB
"use strict"; /** * identity/encode — one identity field → one unambiguous key segment. * * **This module is the tenant boundary.** Every durable store in the library * addresses rows by a string composed from `{ tenant, principal, * conversationId }`. If two DIFFERENT tuples can spell the SAME string, one * scope reads, overwrites and erases another scope's data — no race, no * misconfiguration, just arithmetic. So the encoding here is **injective** * (different tuples ⇒ different strings, always), and that property is * load-bearing rather than tidy: it is the whole isolation guarantee. * * Two things could collapse two tuples into one string, and each has a rule: * * • **the separator.** `/` is what joins the fields, so a value containing * one would otherwise donate a field boundary: tenant `acme/hr` and * principal `alice` spelled the same string as tenant `acme` and * principal `hr/alice`. Real ids hit this — a JWT `sub` is often a URI. * `/` is therefore escaped to `%2F`, and `%` — the escape's own * introducer — is escaped FIRST to `%25`, or a value that merely reads * like an escape (`a%2Fb`) would land on the value that produced it * (`a/b`) and we would have moved the collision rather than closed it. * * • **the absence marker.** A field that was not given still needs a * segment, and that segment is `_`. A tenant *named* `_` is a different * scope from *no tenant*, so a present value that spells the marker is * escaped to `%5F`. `%5F` is unreachable any other way (every `%` in an * encoded value is already escaped), which is what makes it safe to * reserve. * * **What deliberately does NOT change.** A value containing neither `/` nor * `%`, and which is not exactly `_`, is returned BYTE FOR BYTE. That is not a * nicety — it is what makes this fix deployable: every existing artifact and * memory row written under a well-behaved id keeps its address, so there is no * migration. The only tuples that change key are the ones that were sharing a * key with somebody else, and those were already broken. * * Absence has exactly ONE spelling: `undefined`, `null` and `''` all mean "not * given". An empty string is not a name, and treating it as one would create a * second anonymous scope rather than distinguish anything real. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.decodePresentIdentityField = exports.encodeIdentityField = exports.distinctFromAbsent = exports.IDENTITY_ABSENT = void 0; /** The one segment that means "this field was not given". */ exports.IDENTITY_ABSENT = '_'; /** * The segment for a present value that literally spells {@link IDENTITY_ABSENT}. * * Reserved, and reachable ONLY from the value `_`: any escaper used with * {@link distinctFromAbsent} escapes `%` itself, so no other input can produce * these three bytes. */ const ABSENT_LOOKALIKE = '%5F'; /** * Keep a PRESENT value from spelling the absence marker. * * For encoders that already do their own escaping (the artifact path * segments use `encodeURIComponent`) and only need the absence law applied on * top. * * **Precondition:** `escaped` comes from an escaper that escapes `%`. Without * that, a caller-supplied `%5F` would collide with an escaped `_` and this * function would be hiding a collision instead of closing one. */ function distinctFromAbsent(escaped) { return escaped === exports.IDENTITY_ABSENT ? ABSENT_LOOKALIKE : escaped; } exports.distinctFromAbsent = distinctFromAbsent; /** * Encode ONE identity field as a key segment that cannot be confused with any * other field's, or with absence. * * @param raw the field as the caller gave it; `undefined`/`null`/`''` are * absence. * @returns a segment containing no `/`, never equal to another field's segment * unless the fields were equal. * @throws TypeError when a present field is not a string. The value is NOT * named in the message — this function is the identity boundary, and * an error string is the one place a tenant id must never travel. */ function encodeIdentityField(raw) { if (raw === undefined || raw === null || raw === '') return exports.IDENTITY_ABSENT; if (typeof raw !== 'string') { throw new TypeError('[identity] a MemoryIdentity field must be a string or omitted. A non-string was ' + 'given, and it is not quoted here on purpose: this value addresses a tenant, and an ' + 'error message is the wrong place for one. Convert it at the call site so the id ' + 'that reaches storage is the id you meant.'); } // '%' FIRST — it introduces every escape below, so it has to be inert before // any are emitted. Reversing these two lines re-opens the collision. const escaped = raw.replace(/%/g, '%25').replace(/\//g, '%2F'); return distinctFromAbsent(escaped); } exports.encodeIdentityField = encodeIdentityField; /** * Read a required present segment with the encoder's canonical spelling. * One-pass decoding matters: an escaped percent must not acquire a second * meaning as a slash escape. Absence and alternative spellings are refused. * Internal restored-data readers share this rule; errors never quote an id. */ function decodePresentIdentityField(encoded) { if (typeof encoded !== 'string' || encoded === exports.IDENTITY_ABSENT || encoded.length === 0) throw new TypeError('Invalid present identity segment.'); const raw = encoded.replace(/%25|%2F|%5F/g, (escape) => escape === '%25' ? '%' : escape === '%2F' ? '/' : '_'); if (encodeIdentityField(raw) !== encoded) throw new TypeError('Noncanonical present identity segment.'); return raw; } exports.decodePresentIdentityField = decodePresentIdentityField; //# sourceMappingURL=encode.js.map