agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
229 lines • 12.7 kB
JavaScript
;
/**
* The disposition ledger's run lifecycle — registration, the dev canary,
* and nothing else.
*
* Pattern: two small functions bracketing a run; the ledger stays the one
* accumulator, this file only decides WHO is registered and runs
* the canary.
* Role: a check is registered exactly when its preconditions exist on
* THIS agent (wire: always — every run calls the model;
* compose-invariant: a mount kernel is configured; dangling: at
* least one tool declared `argumentsFrom`, which arms BOTH the
* compose-seam dangling-reference check and the choice-seam
* unsupported-argument check off the same declaration;
* empty-lookup: that same declaration AND the operator's
* `noticeEmptyLookups` dial — two halves, because an advisory that
* armed itself off a declaration made for something else would not
* be opt-in at all; column-types: `Tool.resultColumns` AND the
* operator's `checkColumnTypes` dial off `'off'` — the same two
* halves, arming BOTH `column-type-mismatch` and `missing-column`
* off the one declaration; prior-turn-evidence: the operator's
* `noticePriorTurnEvidence` dial AND an armed evidence gate, the
* same two halves — the gate is what decides which tokens in an
* answer are values at all, so without it there is nothing whose
* provenance could be reported).
* Registration is what makes silence auditable — an unregistered
* check is honest
* absence, a registered one that never notes is the wiring rot
* `assertAlive` exists to name.
*
* THE CANARY (dev posture): each registered check's PURE function is run
* once against a synthetic, deliberately contradictory fixture. A check
* that cannot catch its own canary is dead — it runs but cannot fire —
* which is theorem (ii); theorem (i), the unhooked pipeline, is what the
* real per-encounter notes prove. Canary findings never surface as events
* and never touch the real counts (`noteSynthetic` quarantine).
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.beginIntegrityRun = void 0;
const ledger_js_1 = require("./ledger.js");
const check_js_1 = require("../invariant-violation/check.js");
const wire_js_1 = require("../invariant-violation/wire.js");
const check_js_2 = require("../dangling-reference/check.js");
const check_js_3 = require("../unsupported-argument/check.js");
const check_js_4 = require("../unsupported-claim/check.js");
const check_js_5 = require("../empty-lookup/check.js");
const check_js_6 = require("../column-types/check.js");
const check_js_7 = require("../prior-turn-evidence/check.js");
/**
* Start one run's ledger: register the present checks and, in dev posture,
* prove each can still catch its own synthetic defect.
*/
function beginIntegrityRun(present, posture) {
const ledger = (0, ledger_js_1.dispositionLedger)();
if (present.wire)
ledger.register('invariant-violation', 'wire');
// An UNARMED check is a row, not a silence.
//
// Every optional check is registered whether or not this run armed it, and
// one the app never opted into is noted `not-applicable` immediately —
// which is the literal truth: the check looked for its subject (a maps
// plan, a tool declaring `argumentsFrom`, a claims contract) and this run
// has none. Before this, an unarmed check filed NO row at all, and the
// always-on `wire` row sat there green beside it; read casually — which is
// exactly how the library's own reference agent got read for weeks — a run
// that checked nothing was indistinguishable from a run where everything
// passed.
//
// Per check, deliberately, rather than one all-or-nothing block for the
// case where the app armed none of them. The common shape in the field is
// PARTIAL: a consumer declares `argumentsFrom`, arms the two checks that
// field carries, and never learns that the rest never ran. An
// all-or-nothing block goes quiet
// the moment one check is armed, which is precisely when the remaining
// blind spots start to matter.
//
// This reuses the ledger's existing vocabulary rather than adding a flag or
// a second channel, so the rows travel through `report()`, the
// `agentfootprint.integrity.disposition` event and `find_context_errors`'s
// partial-coverage headline with no change at any consumer. And
// `notApplicable` counts as touched in `assertAlive`'s theorem, so an
// unarmed check can never itself trip `CheckerDeadError`: not opting in is
// not wiring rot, and the two must stay tellable apart.
const armed = (kind, seam, isArmed) => {
ledger.register(kind, seam);
if (!isArmed)
ledger.note(kind, seam, 'not-applicable');
};
armed('invariant-violation', 'compose', present.composeInvariant === true);
armed('dangling-reference', 'compose', present.dangling === true);
armed('unsupported-argument', 'choice', present.dangling === true);
armed('unsupported-claim', 'claim', present.claim === true);
armed('empty-lookup', 'write', present.emptyLookup === true);
armed('column-type-mismatch', 'write', present.columnTypes === true);
armed('missing-column', 'write', present.columnTypes === true);
armed('prior-turn-evidence', 'claim', present.priorTurnEvidence === true);
if (posture !== 'dev')
return ledger;
// The canaries — one known-bad fixture per REGISTERED check, through the
// REAL pure function. Epoch -1 marks them as no run iteration's business.
//
// Registered, note, and no longer only armed. The four optional checks now
// file a row whether or not this run had a subject for them, and a
// `not-applicable` row with no canary beside it is the exact ambiguity this
// whole change set exists to remove: a checker that has ROTTED and a checker
// that simply had nothing to look at produce an identical row. The canary is
// the only thing that tells them apart, and it costs a pure function call on
// a fixture.
//
// The alternative — canary only what the app armed — reads reasonable and is
// wrong for the same reason a green report from a check that never ran is
// decoration: the moment somebody DOES arm `.claims()`, they inherit whatever
// state that checker rotted into while nobody was looking, and nothing in the
// record will say how long it had been dead. Dev posture is a library-health
// instrument, so it proves the machinery, not this run's subset of it.
if (present.wire) {
ledger.noteSynthetic('invariant-violation', 'wire', 'minted');
const caught = (0, wire_js_1.wireViolationsOf)({ names: ['canary_tool'], provenance: 'canary: composed frame' }, { names: ['canary_tool', 'canary_ghost'], provenance: 'canary: wire' }, -1);
if (caught.length > 0)
ledger.noteSynthetic('invariant-violation', 'wire', 'caught');
}
{
ledger.noteSynthetic('invariant-violation', 'compose', 'minted');
const caught = (0, check_js_1.invariantViolationsOf)({ mapId: 'canary-map', standing: 'parked', iteration: -1, ownedToolNames: ['canary_tool'] }, { names: ['canary_tool'], provenance: 'canary: serving' });
if (caught.length > 0)
ledger.noteSynthetic('invariant-violation', 'compose', 'caught');
}
{
ledger.noteSynthetic('dangling-reference', 'compose', 'minted');
const caught = (0, check_js_2.danglingReferencesOf)([{ name: 'canary_tool', argumentsFrom: ['canary_ground'] }], new Set(['canary_ground']), new Set(), -1);
if (caught.length > 0)
ledger.noteSynthetic('dangling-reference', 'compose', 'caught');
}
{
// The choice-seam canary: an ARMED call carrying a value the corpus does
// not contain. The fixture's corpus deliberately says nothing that could
// match, so a check that still finds nothing is a check that cannot fire.
ledger.noteSynthetic('unsupported-argument', 'choice', 'minted');
const caught = (0, check_js_3.unsupportedArgumentsOf)([
{
toolName: 'canary_tool',
toolCallId: 'canary',
args: { id: 'canary-fabricated-id' },
argumentsFrom: ['canary_ground'],
},
], { grounded: ['canary: this frame served no identifiers'], assistant: [] }, -1);
if (caught.findings.length > 0) {
ledger.noteSynthetic('unsupported-argument', 'choice', 'caught');
}
}
{
ledger.noteSynthetic('unsupported-claim', 'claim', 'minted');
const caught = (0, check_js_4.unsupportedClaimsOf)([{ answerField: 'canary', entity: 'canary-entity', field: 'canary-field' }], [
{
entity: 'canary-entity',
field: 'canary-field',
value: 'settled',
toolName: 'canary',
toolCallId: 'canary',
iteration: -1,
},
], { canary: 'claimed-otherwise' }, -1);
if (caught.findings.length > 0)
ledger.noteSynthetic('unsupported-claim', 'claim', 'caught');
}
{
// The write-seam canary (9.77.0): a lookup whose argument the fixture's
// PRODUCER really did serve, answering with a zero-row rowset. Both
// halves of the join are deliberately load-bearing — remove the ground
// and the value is not the run's own; make the rowset non-empty and
// there is nothing to notice — so a check that finds nothing here has
// lost one of the two facts it exists to pair.
ledger.noteSynthetic('empty-lookup', 'write', 'minted');
const caught = (0, check_js_5.emptyLookupOf)({
toolName: 'canary_tool',
toolCallId: 'canary',
args: { id: 'canary-grounded-id' },
argumentsFrom: ['canary_ground'],
reading: (0, check_js_5.readLookupResult)([], false),
}, [{ toolName: 'canary_ground', text: 'canary: served canary-grounded-id' }], -1);
if (caught.findings.length > 0)
ledger.noteSynthetic('empty-lookup', 'write', 'caught');
}
{
// The column-type canaries (9.78.0). ONE fixture, deliberately, because
// one rowset carries both defects at once and that is the honest shape of
// the field failure: a numeric column arriving as text AND a declared
// column nobody delivered, in the same result. Each check reads its own
// findings out of the shared verdict, so a canary that catches one and
// not the other names exactly which half died.
ledger.noteSynthetic('column-type-mismatch', 'write', 'minted');
ledger.noteSynthetic('missing-column', 'write', 'minted');
const caught = (0, check_js_6.columnTypesOf)({
toolName: 'canary_tool',
toolCallId: 'canary',
columns: { canary_measure: 'number', canary_absent: 'string' },
reading: (0, check_js_6.readRowset)([{ canary_measure: '1240' }]),
mode: 'warn',
}, -1);
if (caught.findings.some((f) => f.kind === 'column-type-mismatch')) {
ledger.noteSynthetic('column-type-mismatch', 'write', 'caught');
}
if (caught.findings.some((f) => f.kind === 'missing-column')) {
ledger.noteSynthetic('missing-column', 'write', 'caught');
}
}
{
// The claim-seam recency canary (9.83.0): an answer whose only grounded
// values were last served two turns ago, on a turn that fetched nothing.
// Both halves of the reading are load-bearing — give it one value from
// this turn and the claim is falsified, drop the boundary
// (`currentTurn: 0`) and there is no "before" to be — so a check that
// finds nothing here has lost one of the two facts it exists to pair.
ledger.noteSynthetic('prior-turn-evidence', 'claim', 'minted');
const caught = (0, check_js_7.priorTurnEvidenceOf)({
fromThisTurn: 0,
fromPriorTurns: 3,
latestPriorTurn: 1,
currentTurn: 3,
toolResultsThisTurn: 0,
indexTruncated: false,
}, -1);
if (caught.findings.length > 0)
ledger.noteSynthetic('prior-turn-evidence', 'claim', 'caught');
}
return ledger;
}
exports.beginIntegrityRun = beginIntegrityRun;
//# sourceMappingURL=lifecycle.js.map