agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
121 lines • 5.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAnswerEvidenceResolver = void 0;
/** Bounded reads over the existing store. No new store, scope, or ref grammar. */
const naming_js_1 = require("../artifacts/naming.js");
const payload_js_1 = require("../artifacts/payload.js");
const types_js_1 = require("../artifacts/types.js");
/** @internal Run-local read session. Closing invalidates even retained readers. */
function createAnswerEvidenceResolver(store, scope, limits, signal) {
const boundScope = Object.freeze({ ...scope });
const cache = new Map();
const resolved = new Set();
let reads = 0;
let pendingReads = 0;
let reservedBytes = 0;
let closed = false;
let failure;
const unavailable = (reason) => {
failure ??= reason;
return { ok: false, reason };
};
const isClosed = () => closed || signal.aborted;
const load = async (ref, kind) => {
if (!store)
return unavailable('no-store');
try {
const described = await store.head(boundScope, ref);
if (isClosed())
return unavailable('closed');
if (described === null)
return unavailable('missing-or-expired');
// Memory stores may return their own metadata object: snapshot before the
// next await, and never hand that live value to an author callback.
const head = structuredClone(described);
if (head.ref !== ref || !Number.isSafeInteger(head.bytes) || head.bytes < 0)
return unavailable('invalid-record');
if (head.kind !== kind)
return unavailable('kind-mismatch');
if (head.bytes > limits.maxBytes - reservedBytes)
return unavailable('max-bytes');
reservedBytes += head.bytes; // Reserve before await: parallel reads share this budget.
const record = await store.get(boundScope, ref);
if (isClosed())
return unavailable('closed');
if (record === null)
return unavailable('missing-or-expired');
const meta = structuredClone(record.meta);
if (meta.ref !== ref ||
meta.kind !== kind ||
meta.bytes !== head.bytes ||
meta.digest !== head.digest ||
meta.createdAt !== head.createdAt ||
meta.expiresAt !== head.expiresAt ||
meta.mediaType !== head.mediaType)
return unavailable('invalid-record');
// get is the store's verifying read, including its own expiry clock.
// A nonconforming adapter may allocate too much inside get; the port has
// no bounded-read verb. Never deliver an understated parcel as accepted.
const bytes = (0, payload_js_1.canonicalPayloadBytes)(record.data);
if (bytes.byteLength !== meta.bytes)
return unavailable('invalid-record');
if (bytes.byteLength > limits.maxBytes)
return unavailable('max-bytes');
const data = (0, payload_js_1.decodeCanonicalPayload)((0, payload_js_1.payloadShapeOf)(record.data), bytes.slice());
if (isClosed())
return unavailable('closed');
resolved.add(ref);
return { ok: true, record: { meta, data } };
}
catch (error) {
if (isClosed())
return unavailable('closed');
return unavailable(error instanceof types_js_1.ArtifactIntegrityError ? 'digest-mismatch' : 'store-error');
}
};
const resolver = Object.freeze({
async resolve(ref, options) {
if (isClosed())
return unavailable('closed');
if (!(0, naming_js_1.isArtifactRef)(ref) || options === null || typeof options !== 'object')
return unavailable('invalid-input');
const descriptor = Object.getOwnPropertyDescriptor(options, 'kind');
if (!descriptor ||
!('value' in descriptor) ||
typeof descriptor.value !== 'string' ||
descriptor.value.trim() === '' ||
descriptor.value.length > 256)
return unavailable('invalid-input');
const kind = descriptor.value;
const key = JSON.stringify([ref, kind]);
let pending = cache.get(key);
if (!pending) {
if (reads >= limits.maxReads)
return unavailable('max-reads');
reads++;
pendingReads++;
pending = load(ref, kind).finally(() => {
pendingReads--;
});
cache.set(key, pending);
}
const result = await pending;
if (isClosed())
return unavailable('closed');
// One callback cannot mutate a cached parcel subsequently used by another
// check. Metadata and data are detached on every successful delivery.
return result.ok ? { ok: true, record: structuredClone(result.record) } : { ...result };
},
});
return {
resolver,
resolvedRefs: () => [...resolved],
failureReason: () => failure ?? (pendingReads > 0 ? 'closed' : undefined),
close() {
closed = true;
cache.clear();
},
};
}
exports.createAnswerEvidenceResolver = createAnswerEvidenceResolver;
//# sourceMappingURL=evidence.js.map