UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

121 lines (120 loc) • 5.31 kB
function canon(v, seen = new WeakSet()) { if (v === null) return "null"; if (v === undefined) return "undefined"; const t = typeof v; if (t === "number") return Object.is(v, -0) ? "-0" : String(v); if (t === "bigint") return `${v}n`; if (t === "string") return JSON.stringify(v); if (t === "boolean") return String(v); if (t === "function" || t === "symbol") return `[${t}]`; if (Array.isArray(v)) { if (seen.has(v)) return "[Circular]"; seen.add(v); const s = `[${v.map((e) => canon(e, seen)).join(",")}]`; seen.delete(v); return s; } const o = v; if (seen.has(o)) return "[Circular]"; seen.add(o); const s = `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${canon(o[k], seen)}`).join(",")}}`; seen.delete(o); return s; } const structEq = (a, b) => canon(a) === canon(b); const multisetEq = (a, b) => structEq(a.map((x) => canon(x)).sort(), b.map((x) => canon(x)).sort()); function multisetSubset(out, inp) { const c = new Map(); for (const x of inp) { const k = canon(x); c.set(k, (c.get(k) ?? 0) + 1); } for (const x of out) { const k = canon(x); const n = (c.get(k) ?? 0) - 1; if (n < 0) return false; c.set(k, n); } return true; } const lenOf = (x) => (typeof x === "string" || Array.isArray(x) ? x.length : 0); export function deriveInvariants(contract) { const inv = []; const call = (fn, x) => fn(x); const coll = (x) => (contract.inputCollection ? contract.inputCollection(x) : x); const add = (kind, description, check) => inv.push({ kind, description, check }); if (contract.permutation) add("permutation", "output is a permutation of the input collection", (f, x) => multisetEq(call(f, x), coll(x))); if (contract.lengthPreserving) add("lengthPreserving", "|output| === |input collection|", (f, x) => call(f, x).length === coll(x).length); if (contract.subset) add("subset", "output is a sub-multiset of the input collection", (f, x) => multisetSubset(call(f, x), coll(x))); if (contract.distinctOutput) add("distinctOutput", "output has no duplicate elements", (f, x) => { const o = call(f, x); return new Set(o.map((e) => canon(e))).size === o.length; }); if (contract.idempotent) add("idempotent", "f(f(x)) === f(x)", (f, x) => { const once = call(f, x); return structEq(call(f, once), once); }); if (contract.involution) add("involution", "f(f(x)) === x", (f, x) => structEq(call(f, call(f, x)), x)); if (contract.outputPredicate) add("outputPredicate", "every output element satisfies the contract predicate", (f, x) => call(f, x).every((e) => contract.outputPredicate(e))); if (contract.rangeBound) add("rangeBound", "output is within the contract range", (f, x) => { const o = call(f, x); const [lo, hi] = contract.rangeBound(x); return typeof o === "number" && o >= lo && o <= hi; }); if (contract.nonNegative) add("nonNegative", "output >= 0", (f, x) => call(f, x) >= 0); if (contract.nonEmptyPreserving) add("nonEmptyPreserving", "non-empty input collection => non-empty output", (f, x) => (lenOf(coll(x)) > 0 ? lenOf(call(f, x)) > 0 : true)); if (contract.concatPreserving) add("concatPreserving", "concatenation of output groups === input collection", (f, x) => structEq([].concat(...call(f, x)), coll(x))); if (contract.chunkMaxLen) add("chunkMaxLen", "every output group has length in (0, n]", (f, x) => { const n = contract.chunkMaxLen(x); return call(f, x).every((g) => g.length > 0 && g.length <= n); }); if (contract.lengthEq) add("lengthEq", "|output| === the contract length", (f, x) => call(f, x).length === contract.lengthEq(x)); if (contract.roundtrip) add("roundtrip", "decode(encode(x)) === x", (pair, x) => { const p = pair; return structEq(p.decode(p.encode(x)), x); }); if (contract.totalNoThrow) add("totalNoThrow", "must not throw on a valid input", (f, x) => { try { if (typeof f === "function") f(x); return true; } catch { return false; } }); return inv; } export function checkInvariants(fn, genInput, invariants, opts = {}) { const trials = Math.max(1, Math.floor(opts.trials ?? 200)); const found = new Map(); for (let i = 0; i < trials; i++) { const input = genInput(i); for (const iv of invariants) { if (found.has(iv.kind)) continue; try { if (!iv.check(fn, input)) found.set(iv.kind, { kind: iv.kind, description: iv.description, input }); } catch { try { if (typeof fn === "function") fn(input); found.set(iv.kind, { kind: iv.kind, description: iv.description, input, evaluationThrew: true }); } catch { } } } } const violations = [...found.values()]; return { ok: violations.length === 0, violations, trials }; }