agentfootprint 9.94.2 → 9.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/CLAUDE.md +2 -0
  3. package/dist/answer-validation/evidence.js +121 -0
  4. package/dist/answer-validation/evidence.js.map +1 -0
  5. package/dist/answer-validation/index.js +10 -0
  6. package/dist/answer-validation/index.js.map +1 -0
  7. package/dist/answer-validation/types.js +24 -0
  8. package/dist/answer-validation/types.js.map +1 -0
  9. package/dist/answer-validation/validate.js +326 -0
  10. package/dist/answer-validation/validate.js.map +1 -0
  11. package/dist/cache/CacheGateDecider.js +7 -1
  12. package/dist/cache/CacheGateDecider.js.map +1 -1
  13. package/dist/core/Agent.js +56 -2
  14. package/dist/core/Agent.js.map +1 -1
  15. package/dist/core/agent/AgentBuilder.js +27 -1
  16. package/dist/core/agent/AgentBuilder.js.map +1 -1
  17. package/dist/core/agent/buildAgentChart.js +17 -2
  18. package/dist/core/agent/buildAgentChart.js.map +1 -1
  19. package/dist/core/agent/buildDynamicAgentChart.js +15 -2
  20. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  21. package/dist/core/agent/outputEnforcement.js.map +1 -1
  22. package/dist/core/agent/stages/answerValidation.js +57 -0
  23. package/dist/core/agent/stages/answerValidation.js.map +1 -0
  24. package/dist/core/agent/stages/breakFinal.js +11 -1
  25. package/dist/core/agent/stages/breakFinal.js.map +1 -1
  26. package/dist/core/agent/stages/callLLM.js +7 -5
  27. package/dist/core/agent/stages/callLLM.js.map +1 -1
  28. package/dist/core/agent/stages/prepareFinal.js +35 -2
  29. package/dist/core/agent/stages/prepareFinal.js.map +1 -1
  30. package/dist/core/agent/stages/route.js +9 -5
  31. package/dist/core/agent/stages/route.js.map +1 -1
  32. package/dist/core/agent/stages/toolCalls.js +7 -1
  33. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  34. package/dist/esm/answer-validation/evidence.d.ts +12 -0
  35. package/dist/esm/answer-validation/evidence.js +117 -0
  36. package/dist/esm/answer-validation/evidence.js.map +1 -0
  37. package/dist/esm/answer-validation/index.d.ts +3 -0
  38. package/dist/esm/answer-validation/index.js +4 -0
  39. package/dist/esm/answer-validation/index.js.map +1 -0
  40. package/dist/esm/answer-validation/types.d.ts +94 -0
  41. package/dist/esm/answer-validation/types.js +20 -0
  42. package/dist/esm/answer-validation/types.js.map +1 -0
  43. package/dist/esm/answer-validation/validate.d.ts +15 -0
  44. package/dist/esm/answer-validation/validate.js +321 -0
  45. package/dist/esm/answer-validation/validate.js.map +1 -0
  46. package/dist/esm/cache/CacheGateDecider.js +7 -1
  47. package/dist/esm/cache/CacheGateDecider.js.map +1 -1
  48. package/dist/esm/core/Agent.d.ts +6 -1
  49. package/dist/esm/core/Agent.js +56 -2
  50. package/dist/esm/core/Agent.js.map +1 -1
  51. package/dist/esm/core/agent/AgentBuilder.d.ts +11 -0
  52. package/dist/esm/core/agent/AgentBuilder.js +27 -1
  53. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  54. package/dist/esm/core/agent/buildAgentChart.d.ts +2 -0
  55. package/dist/esm/core/agent/buildAgentChart.js +19 -4
  56. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  57. package/dist/esm/core/agent/buildDynamicAgentChart.js +17 -4
  58. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  59. package/dist/esm/core/agent/outputEnforcement.d.ts +2 -0
  60. package/dist/esm/core/agent/outputEnforcement.js.map +1 -1
  61. package/dist/esm/core/agent/stages/answerValidation.d.ts +10 -0
  62. package/dist/esm/core/agent/stages/answerValidation.js +53 -0
  63. package/dist/esm/core/agent/stages/answerValidation.js.map +1 -0
  64. package/dist/esm/core/agent/stages/breakFinal.d.ts +6 -0
  65. package/dist/esm/core/agent/stages/breakFinal.js +9 -0
  66. package/dist/esm/core/agent/stages/breakFinal.js.map +1 -1
  67. package/dist/esm/core/agent/stages/callLLM.d.ts +2 -0
  68. package/dist/esm/core/agent/stages/callLLM.js +7 -5
  69. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  70. package/dist/esm/core/agent/stages/prepareFinal.d.ts +7 -0
  71. package/dist/esm/core/agent/stages/prepareFinal.js +33 -1
  72. package/dist/esm/core/agent/stages/prepareFinal.js.map +1 -1
  73. package/dist/esm/core/agent/stages/route.js +9 -5
  74. package/dist/esm/core/agent/stages/route.js.map +1 -1
  75. package/dist/esm/core/agent/stages/toolCalls.js +7 -1
  76. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  77. package/dist/esm/core/agent/types.d.ts +6 -0
  78. package/dist/esm/index.d.ts +1 -0
  79. package/dist/esm/index.js +2 -0
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/index.js +4 -1
  82. package/dist/index.js.map +1 -1
  83. package/dist/types/answer-validation/evidence.d.ts +13 -0
  84. package/dist/types/answer-validation/evidence.d.ts.map +1 -0
  85. package/dist/types/answer-validation/index.d.ts +4 -0
  86. package/dist/types/answer-validation/index.d.ts.map +1 -0
  87. package/dist/types/answer-validation/types.d.ts +95 -0
  88. package/dist/types/answer-validation/types.d.ts.map +1 -0
  89. package/dist/types/answer-validation/validate.d.ts +16 -0
  90. package/dist/types/answer-validation/validate.d.ts.map +1 -0
  91. package/dist/types/cache/CacheGateDecider.d.ts.map +1 -1
  92. package/dist/types/core/Agent.d.ts +6 -1
  93. package/dist/types/core/Agent.d.ts.map +1 -1
  94. package/dist/types/core/agent/AgentBuilder.d.ts +11 -0
  95. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  96. package/dist/types/core/agent/buildAgentChart.d.ts +2 -0
  97. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  98. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  99. package/dist/types/core/agent/outputEnforcement.d.ts +2 -0
  100. package/dist/types/core/agent/outputEnforcement.d.ts.map +1 -1
  101. package/dist/types/core/agent/stages/answerValidation.d.ts +11 -0
  102. package/dist/types/core/agent/stages/answerValidation.d.ts.map +1 -0
  103. package/dist/types/core/agent/stages/breakFinal.d.ts +6 -0
  104. package/dist/types/core/agent/stages/breakFinal.d.ts.map +1 -1
  105. package/dist/types/core/agent/stages/callLLM.d.ts +2 -0
  106. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  107. package/dist/types/core/agent/stages/prepareFinal.d.ts +7 -0
  108. package/dist/types/core/agent/stages/prepareFinal.d.ts.map +1 -1
  109. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  110. package/dist/types/core/agent/types.d.ts +6 -0
  111. package/dist/types/core/agent/types.d.ts.map +1 -1
  112. package/dist/types/index.d.ts +1 -0
  113. package/dist/types/index.d.ts.map +1 -1
  114. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [9.95.0] - 2026-09-12
9
+
10
+ ### Added
11
+
12
+ - Opt-in `.answerValidation()` checks schema-parsed JSON against host-owned
13
+ evidence before final answer delivery. Enforce mode refuses failed or
14
+ unverified checks; observe mode records them without refusing. Scoped,
15
+ read-only artifact resolution has per-validation read, byte and time limits.
16
+ Reports separate actual checks from unavailable evidence and artifact reads.
17
+ - Configured agents withhold draft tokens and deliver the checked canonical
18
+ value consistently through `run`, `runTyped`, final events and memory.
19
+ Output fallback and coverage suffix combinations are explicitly refused in
20
+ this version; automatic repair and free-text entailment are outside scope.
21
+
22
+ ## [9.94.4] - 2026-09-12
23
+
24
+ ### Documentation
25
+
26
+ - Propose optional answer validation against referenced evidence in
27
+ [proposal 013](docs/proposals/013-answer-validation.md), with executable tests
28
+ of current claim-checking and output-enforcement boundaries. This is a
29
+ feature request; it introduces no runtime API or validation guarantee.
30
+
31
+ ## [9.94.3] - 2026-09-12
32
+
33
+ ### Fixed — two behaviours that moved with footprintjs 9.22.0+ and 9.24.0, found by running the suite on them
34
+
35
+ The lockfile had held footprintjs at 9.21.1, so nothing here had run on the
36
+ 9.22–9.23 line. Running the suite on the 9.24.0 build found two defects on
37
+ OUR side; both are fixed so that agentfootprint is correct on every
38
+ footprintjs the range admits (`^9.21.1`).
39
+
40
+ - **`skill.step_advanced` named the step AFTER the one that completed** on
41
+ footprintjs ≥ 9.22.0 (and `skip_step`'s pointer moved one too far). The
42
+ step boundary read `pointerOf(scope.stepPointer)` — since 9.22.0 an array
43
+ element read through the scope is a handle that reads LIVE by path — then
44
+ replaced the pointer and only then emitted `ptr.step`, which by then was
45
+ the new value. It reads the pointer as a VALUE now
46
+ (`scope.$getValue('stepPointer')`, the committed object, which never
47
+ changes once replaced). Three `skill-steps` tests were red on 9.22.0,
48
+ 9.22.1, 9.23.0 and 9.23.3 (bisected); green on all of them now.
49
+ - **A phantom skill churn switched caching off.** `detectSkillChurn`
50
+ counted every slot that was not `undefined`. The parent writes
51
+ `skillHistory` with `undefined` for "no skill yet"; footprintjs < 9.24.0
52
+ round-tripped that array through JSON on the scope write, so the gate saw
53
+ `null`, counted it as a third skill, and took the `skip-caching` branch on
54
+ the first turn after two real skills — every run with two skills read in
55
+ sequence lost its cache markers from iteration 4. The gate counts only
56
+ strings now. One byte-identity reference (`agent-shared-tool-reference`)
57
+ had recorded the phantom (`cacheMarkers: []` at the iteration-4
58
+ merge-back) and is regenerated with the delta on record in the test
59
+ header; it is green on both the lockfile's 9.21.1 and on 9.24.0, so what
60
+ it pins is the gate, not the substrate. Pinned:
61
+ `test/cache/CacheGateDecider.test.ts` ("a `null` slot is no skill too").
62
+
63
+ ### Changed — the suite runs on the footprintjs consumers get
64
+
65
+ - The dev pin moves to footprintjs `^9.24.0` (the lockfile had held 9.21.1
66
+ since 9.85; the peer range stays `^9.21.1`, and the two fixes above make
67
+ this library correct across it). 10,541 tests green on 9.24.0.
68
+
69
+ ### Tests — recorded-not-built entry 10, the quantifier half, for its three named clauses
70
+
71
+ - `gap-sentences.test.ts`: `no-fold-base`'s "reads as empty rather than as
72
+ unknown" walks the catalogue's whole field list (a reading table; a field
73
+ the table cannot read fails by name — the list also names `epoch`, never
74
+ touched before); `cache-transform`'s "that gap is the stronger claim" is
75
+ measured for every (gap, shared field) pairing that occurs beside it on
76
+ every fixture view, intact and damaged, against that gap's own claim (an
77
+ unmeasured kind fails by name; "stronger" is *may be short*, ≤ — on a fresh
78
+ run with its base stripped the rebuild recovers everything);
79
+ `forced-tool-schema`'s "the tool list is complete" runs on both chart
80
+ shapes. The design page marks the three built; the general statement — a
81
+ clause not named there has its quantifier read by a person — stands.
82
+
8
83
  ## [9.94.2] - 2026-09-12
9
84
 
10
85
  ### Fixed
package/CLAUDE.md CHANGED
@@ -17,6 +17,7 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
17
17
 
18
18
  | If you are about to build… | It is | Where | Since |
19
19
  |---|---|---|---|
20
+ | checking a typed final answer against scoped evidence before streaming or memory delivery — optional host-authored checks with enforce/observe modes, canonical JSON and bounded read-only artifacts | `answerValidation` + `AnswerValidationError` | `src/answer-validation/` | 9.95.0 |
20
21
  | turning a written operational procedure (a runbook, a triage playbook) into ONE agent tool whose every answer is EVIDENCE — coverage folded up from the inner tools it calls, rule name+version, verdict rows with GENERATED meanings, and the recorded walk as an artifact ticket, never bytes | `runbookAsTool` — dials: `procedure` (factory, invoked per call with `ctx.tools`) + `resultKind` (`'verdict/*'` arms the rowset projection; anything else ships spine + the chart's `report`) + `rules {name, version}` (default absent ⇒ `rule_version: 'undeclared'`) + `verdicts {decider, maxRows}` (default 50; `verdict_meanings` = statically declared branches + this run's rule labels + the DEFAULT branch's label, which reaches evidence only when the chart calls `decide(s, rules, {branch, label})` — fp ≥9.16.1, the default is chosen by NO rule so nothing else can name it, and inside a generated fan-out branch the static walk is blind too; an undeclared or blank label stays ABSENT from the map, never invented from a branch id, and there is deliberately NO caller-supplied meanings map) + `presentation` (`'prose'` default = ship `table` + `VERDICT_RENDER_NOTE` "output it VERBATIM"; `'panel'` = the HOST renders the rowset, so NO `table` key at all + `PANEL_RENDER_NOTE` "the rows are already on the reader's screen — do not reproduce them"; the rowset half is byte-identical across modes, `table` stays RESERVED in both, an unknown value THROWS at definition) + `walk {cap, recording}` (cap default 500; over-cap ⇒ control-flow projection, declared · `recording: true` or `{label, maxBytes}`, default OFF — ALSO files the inner chart's own `{snapshot, events, structure}` under `recording/run` and puts its ref on the spine as `walk.recording_ref`, because the ROW projection cannot be drawn; snapshot read from the REDACTED mirror so one `redact` means the same for both; over `maxBytes` (`DEFAULT_RECORDING_MAX_BYTES` = 5,000,000) it is REFUSED not truncated; every absence — no store / over size / unserializable / store threw — is STATED in `walk.recording_note`, and the four `recording_*` fields are absent entirely when the dial is off) + `composedOf` (drift-checked at agent BUILD) + kept `recorders`/`keepRecord`/`keepRecordLimit`/`redact`; reserved state keys `verdicts`/`coverage`/`report` (the `report` bag lands BESIDE the spine, never over it — a report field spelling `af_coverage`/`af_provenance`/`rule_version`/`walk`/`report_note` or a live projection key is discarded and NAMED in `result.report_note`; precedence is explicit in `report.ts`, never spread order); inner `absent()` passes through VERBATIM unless the call said `allowAbsent`; walk kind `recording/chart-walk` + `walk_segment` discriminant | `src/core/runbook/` | 9.76.0, recording 9.79.0 |
21
22
  | speaking MCP from a BROWSER — the SDK is browser-clean (its client + streamableHttp bundle at `platform:'browser'` with zero `node:` edges and never pull in `client/stdio.js`); the one barrier was that `lazyRequire` gets CALLED there, so the fix is to let the caller supply what the loader would have found | `mcpClient({ sdk })` (`McpSdk` = the two SDK modules you imported statically; the library STILL builds the transport, so headers/fetch/gateway vending/`retryOnThrottle`/`_meta` all keep working) · `mcpClient({ connection })` (`McpConnection` = listTools/callTool/close, deliberately NO `connect` — you already did; the only arm that reaches the SDK's `jsonSchemaValidator`, i.e. CSP) + `McpConnectionOptions`; `refuseConflictingOptions` REFUSES at construction every option a transport would have consumed, naming where it moved (a knob naming a behaviour that no longer happens is the defect class); `transportUrl` resolves a relative `url` against `globalThis.location.href` (absolute takes the identical branch, Node refuses by name); `sdkLoadFailure` classifies the seven load sites so a browser is never told to install a package it has (resolution failure ⇒ byte-identical historical message); `retryingFetch` + `ThrottleFetch` promoted out of `@internal` so the connection arm keeps its 429 handling. stdio keeps `lazyRequire` FOREVER — it spawns a subprocess. ZERO packaging change: no subpath, no `browser` condition (TypeScript is blind to it), peer stays optional (no literal dynamic `import()`). Fenced by `test/lib/mcp/browserGraph.test.ts` | `src/lib/mcp/` | 9.81.0 |
22
23
  | calling ANOTHER registered tool from inside a tool's `execute` — composition over the agent's own dispatch map instead of importing the module and building a second query stack | `agentToolDispatch` + `ctx.tools` (ToolDispatch has/call) — sees static and skill-carried tools, NEVER ToolProvider-delivered ones (no build-time list, the 9.72.0 caveat); inner calls get the outer facts with hasArtifacts false and a derived toolCallId, `needs` resolved fail-closed non-interactively, `checkIn` and `wants` tools refused by name, no nested dispatch; declare ingredients via `composedOf` (and `gates` for a pausing procedure) — both travel MCP `_meta` | `src/core/agent/toolDispatch.ts` | 9.76.0 |
@@ -85,6 +86,7 @@ Entry points — FIFTEEN doors, and `package.json` `exports` is exhaustive, so a
85
86
 
86
87
  | src/ | one job |
87
88
  |---|---|
89
+ | answer-validation/ | optional final-answer contract, scoped bounded artifact reads, canonical JSON and explicit check dispositions; the Agent route owns invocation and final capture owns delivery |
88
90
  | core/ | primitives: Agent.ts (ReAct runner), LLMCall.ts, RunnerBase.ts (dispatcher + attach + enable.*), tools.ts (defineTool), pause.ts, runCheckpoint.ts |
89
91
  | core/agent/evidence/ | (9.35.0) the evidence gate — `.namesAndNumbersFromEvidence()`. Pure + deterministic BY LAW (no model, no embedding: a guard that needed a bigger model to police a smaller one inverts the library's thesis). normalize (one spelling per value, both sides) → extract (which tokens are DATA — conservative, justified clause by clause) → evidenceIndex (STRUCTURAL walk of `role:'tool'` results; the exempt corpus is user message + user/system turns + `systemPromptInjections`, MINUS the library's own frames — see frames.ts, the laundering bug) → gate (resolve/judge/sentences) → errors (`UnsupportedValuesError`). Fabrication detector, NOT a correctness judge: a false claim built from real values passes |
90
92
  | core/agent/coverage/ | the two FIELD-INVENTED result primitives. `absent()` = an absence that names its own coverage (checked / not checked / cannot cover) and says a retry returns the same; `coverage(result, …)` = the ledger of what a clean verdict does NOT rule out. Both RECOGNIZED (reserved `af_absent`/`af_coverage` keys, the effects-envelope strictness law), not conventions. Pure; the moving parts are `declareCoverage` in stages/toolCalls.ts, the whitelist in evidence/evidenceIndex.ts, and `prepareFinalWithLimitsStage` |
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAnswerEvidenceResolver = void 0;
4
+ /** Bounded reads over the existing store. No new store, scope, or ref grammar. */
5
+ const naming_js_1 = require("../artifacts/naming.js");
6
+ const payload_js_1 = require("../artifacts/payload.js");
7
+ const types_js_1 = require("../artifacts/types.js");
8
+ /** @internal Run-local read session. Closing invalidates even retained readers. */
9
+ function createAnswerEvidenceResolver(store, scope, limits, signal) {
10
+ const boundScope = Object.freeze({ ...scope });
11
+ const cache = new Map();
12
+ const resolved = new Set();
13
+ let reads = 0;
14
+ let pendingReads = 0;
15
+ let reservedBytes = 0;
16
+ let closed = false;
17
+ let failure;
18
+ const unavailable = (reason) => {
19
+ failure ??= reason;
20
+ return { ok: false, reason };
21
+ };
22
+ const isClosed = () => closed || signal.aborted;
23
+ const load = async (ref, kind) => {
24
+ if (!store)
25
+ return unavailable('no-store');
26
+ try {
27
+ const described = await store.head(boundScope, ref);
28
+ if (isClosed())
29
+ return unavailable('closed');
30
+ if (described === null)
31
+ return unavailable('missing-or-expired');
32
+ // Memory stores may return their own metadata object: snapshot before the
33
+ // next await, and never hand that live value to an author callback.
34
+ const head = structuredClone(described);
35
+ if (head.ref !== ref || !Number.isSafeInteger(head.bytes) || head.bytes < 0)
36
+ return unavailable('invalid-record');
37
+ if (head.kind !== kind)
38
+ return unavailable('kind-mismatch');
39
+ if (head.bytes > limits.maxBytes - reservedBytes)
40
+ return unavailable('max-bytes');
41
+ reservedBytes += head.bytes; // Reserve before await: parallel reads share this budget.
42
+ const record = await store.get(boundScope, ref);
43
+ if (isClosed())
44
+ return unavailable('closed');
45
+ if (record === null)
46
+ return unavailable('missing-or-expired');
47
+ const meta = structuredClone(record.meta);
48
+ if (meta.ref !== ref ||
49
+ meta.kind !== kind ||
50
+ meta.bytes !== head.bytes ||
51
+ meta.digest !== head.digest ||
52
+ meta.createdAt !== head.createdAt ||
53
+ meta.expiresAt !== head.expiresAt ||
54
+ meta.mediaType !== head.mediaType)
55
+ return unavailable('invalid-record');
56
+ // get is the store's verifying read, including its own expiry clock.
57
+ // A nonconforming adapter may allocate too much inside get; the port has
58
+ // no bounded-read verb. Never deliver an understated parcel as accepted.
59
+ const bytes = (0, payload_js_1.canonicalPayloadBytes)(record.data);
60
+ if (bytes.byteLength !== meta.bytes)
61
+ return unavailable('invalid-record');
62
+ if (bytes.byteLength > limits.maxBytes)
63
+ return unavailable('max-bytes');
64
+ const data = (0, payload_js_1.decodeCanonicalPayload)((0, payload_js_1.payloadShapeOf)(record.data), bytes.slice());
65
+ if (isClosed())
66
+ return unavailable('closed');
67
+ resolved.add(ref);
68
+ return { ok: true, record: { meta, data } };
69
+ }
70
+ catch (error) {
71
+ if (isClosed())
72
+ return unavailable('closed');
73
+ return unavailable(error instanceof types_js_1.ArtifactIntegrityError ? 'digest-mismatch' : 'store-error');
74
+ }
75
+ };
76
+ const resolver = Object.freeze({
77
+ async resolve(ref, options) {
78
+ if (isClosed())
79
+ return unavailable('closed');
80
+ if (!(0, naming_js_1.isArtifactRef)(ref) || options === null || typeof options !== 'object')
81
+ return unavailable('invalid-input');
82
+ const descriptor = Object.getOwnPropertyDescriptor(options, 'kind');
83
+ if (!descriptor ||
84
+ !('value' in descriptor) ||
85
+ typeof descriptor.value !== 'string' ||
86
+ descriptor.value.trim() === '' ||
87
+ descriptor.value.length > 256)
88
+ return unavailable('invalid-input');
89
+ const kind = descriptor.value;
90
+ const key = JSON.stringify([ref, kind]);
91
+ let pending = cache.get(key);
92
+ if (!pending) {
93
+ if (reads >= limits.maxReads)
94
+ return unavailable('max-reads');
95
+ reads++;
96
+ pendingReads++;
97
+ pending = load(ref, kind).finally(() => {
98
+ pendingReads--;
99
+ });
100
+ cache.set(key, pending);
101
+ }
102
+ const result = await pending;
103
+ if (isClosed())
104
+ return unavailable('closed');
105
+ // One callback cannot mutate a cached parcel subsequently used by another
106
+ // check. Metadata and data are detached on every successful delivery.
107
+ return result.ok ? { ok: true, record: structuredClone(result.record) } : { ...result };
108
+ },
109
+ });
110
+ return {
111
+ resolver,
112
+ resolvedRefs: () => [...resolved],
113
+ failureReason: () => failure ?? (pendingReads > 0 ? 'closed' : undefined),
114
+ close() {
115
+ closed = true;
116
+ cache.clear();
117
+ },
118
+ };
119
+ }
120
+ exports.createAnswerEvidenceResolver = createAnswerEvidenceResolver;
121
+ //# sourceMappingURL=evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/answer-validation/evidence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeAnswerValidation = exports.resolveAnswerValidation = exports.AnswerValidationError = void 0;
4
+ /** Explicit, bounded validation of schema-accepted JSON answers. */
5
+ var types_js_1 = require("./types.js");
6
+ Object.defineProperty(exports, "AnswerValidationError", { enumerable: true, get: function () { return types_js_1.AnswerValidationError; } });
7
+ var validate_js_1 = require("./validate.js");
8
+ Object.defineProperty(exports, "resolveAnswerValidation", { enumerable: true, get: function () { return validate_js_1.resolveAnswerValidation; } });
9
+ Object.defineProperty(exports, "executeAnswerValidation", { enumerable: true, get: function () { return validate_js_1.executeAnswerValidation; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/answer-validation/index.ts"],"names":[],"mappings":";;;AAAA,oEAAoE;AACpE,uCAYoB;AAXlB,iHAAA,qBAAqB,OAAA;AAYvB,6CAAiF;AAAxE,sHAAA,uBAAuB,OAAA;AAAE,sHAAA,uBAAuB,OAAA"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnswerValidationError = void 0;
4
+ /** An enforcing host refused delivery; diagnostic metadata only, never content. */
5
+ class AnswerValidationError extends Error {
6
+ code = 'ERR_ANSWER_VALIDATION';
7
+ report;
8
+ constructor(report) {
9
+ super(`Answer validation '${report.validatorId}' did not pass (${report.status}).`);
10
+ this.name = 'AnswerValidationError';
11
+ this.report = Object.freeze({
12
+ ...report,
13
+ checks: Object.freeze(report.checks.map((check) => Object.freeze({
14
+ ...check,
15
+ ...(check.evidenceRefs !== undefined && {
16
+ evidenceRefs: Object.freeze([...check.evidenceRefs]),
17
+ }),
18
+ }))),
19
+ resolvedRefs: Object.freeze([...report.resolvedRefs]),
20
+ });
21
+ }
22
+ }
23
+ exports.AnswerValidationError = AnswerValidationError;
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/answer-validation/types.ts"],"names":[],"mappings":";;;AA6GA,mFAAmF;AACnF,MAAa,qBAAsB,SAAQ,KAAK;IACrC,IAAI,GAAG,uBAAgC,CAAC;IACxC,MAAM,CAAyB;IAExC,YAAY,MAA8B;QACxC,KAAK,CAAC,sBAAsB,MAAM,CAAC,WAAW,mBAAmB,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QACpF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC1B,GAAG,MAAM;YACT,MAAM,EAAE,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1B,MAAM,CAAC,MAAM,CAAC;gBACZ,GAAG,KAAK;gBACR,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI;oBACtC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC;iBACrD,CAAC;aACH,CAAC,CACH,CACF;YACD,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;SACtD,CAAC,CAAC;IACL,CAAC;CACF;AAtBD,sDAsBC"}
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeAnswerValidation = exports.resolveAnswerValidation = void 0;
4
+ /** One explicit JSON-answer validation; the runner owns when to invoke it. */
5
+ const outputSchema_js_1 = require("../core/outputSchema.js");
6
+ const payload_js_1 = require("../artifacts/payload.js");
7
+ const evidence_js_1 = require("./evidence.js");
8
+ const DEFAULT_LIMITS = Object.freeze({
9
+ maxReads: 16,
10
+ maxBytes: 4 * 1024 * 1024,
11
+ maxChecks: 100,
12
+ maxCandidateBytes: 1024 * 1024,
13
+ timeoutMs: 5000,
14
+ });
15
+ const encoder = new TextEncoder();
16
+ const dispositions = new Set(['checked-pass', 'checked-fail', 'not-applicable', 'unreachable']);
17
+ function ownData(value, allowed) {
18
+ if (value === null ||
19
+ typeof value !== 'object' ||
20
+ Array.isArray(value) ||
21
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
22
+ throw new Error('Expected a plain data object.');
23
+ const result = Object.create(null);
24
+ for (const key of Reflect.ownKeys(value)) {
25
+ if (typeof key !== 'string' || (allowed && !allowed.includes(key)))
26
+ throw new Error('Unknown property.');
27
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
28
+ if (!descriptor?.enumerable || !('value' in descriptor))
29
+ throw new Error('Expected an enumerable data property.');
30
+ Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
31
+ }
32
+ return result;
33
+ }
34
+ function text(value, max) {
35
+ if (typeof value !== 'string' || value.trim() === '' || value.length > max)
36
+ throw new Error('Expected bounded non-empty text.');
37
+ return value;
38
+ }
39
+ /** Validate and snapshot an authored contract once, before running an agent. */
40
+ function resolveAnswerValidation(options) {
41
+ const source = ownData(options, ['id', 'version', 'mode', 'limits', 'validate']);
42
+ const id = text(source.id, 128);
43
+ const version = text(source.version, 128);
44
+ const mode = source.mode === undefined ? 'enforce' : source.mode;
45
+ if (mode !== 'enforce' && mode !== 'observe')
46
+ throw new Error('answerValidation.mode must be enforce or observe.');
47
+ if (typeof source.validate !== 'function')
48
+ throw new Error('answerValidation.validate must be a function.');
49
+ const validate = source.validate;
50
+ const supplied = source.limits === undefined ? {} : ownData(source.limits, Object.keys(DEFAULT_LIMITS));
51
+ const limits = { ...DEFAULT_LIMITS };
52
+ for (const key of Object.keys(DEFAULT_LIMITS)) {
53
+ if (Object.hasOwn(supplied, key)) {
54
+ const value = supplied[key];
55
+ if (typeof value !== 'number' ||
56
+ !Number.isSafeInteger(value) ||
57
+ value <= 0 ||
58
+ (key === 'timeoutMs' && value > 2147483647))
59
+ throw new Error(`answerValidation.limits.${key} must be a positive bounded integer.`);
60
+ limits[key] = value;
61
+ }
62
+ }
63
+ return Object.freeze({
64
+ id,
65
+ version,
66
+ mode,
67
+ limits: Object.freeze(limits),
68
+ validate: (candidate, context) => validate(candidate, context),
69
+ });
70
+ }
71
+ exports.resolveAnswerValidation = resolveAnswerValidation;
72
+ /** JSON copy without invoking toJSON/getters or silently dropping data. */
73
+ function jsonCopy(value, maxBytes, freeze) {
74
+ let budget = maxBytes;
75
+ const ancestors = new Set();
76
+ const spend = (bytes) => {
77
+ budget -= bytes;
78
+ if (budget < 0)
79
+ throw new Error('JSON byte limit.');
80
+ };
81
+ const string = (item) => {
82
+ if (item.length > budget)
83
+ throw new Error('JSON byte limit.');
84
+ spend(encoder.encode(JSON.stringify(item)).byteLength);
85
+ };
86
+ const copy = (item, depth) => {
87
+ if (depth > 100)
88
+ throw new Error('JSON nesting limit.');
89
+ if (item === null) {
90
+ spend(4);
91
+ return null;
92
+ }
93
+ if (typeof item === 'string') {
94
+ string(item);
95
+ return item;
96
+ }
97
+ if (typeof item === 'boolean') {
98
+ spend(item ? 4 : 5);
99
+ return item;
100
+ }
101
+ if (typeof item === 'number') {
102
+ if (!Number.isFinite(item) || Object.is(item, -0))
103
+ throw new Error('Non-lossless JSON number.');
104
+ spend(JSON.stringify(item).length);
105
+ return item;
106
+ }
107
+ if (typeof item !== 'object')
108
+ throw new Error('Non-JSON value.');
109
+ if (ancestors.has(item))
110
+ throw new Error('Cyclic JSON value.');
111
+ ancestors.add(item);
112
+ spend(2);
113
+ let output;
114
+ if (Array.isArray(item)) {
115
+ if (Object.getPrototypeOf(item) !== Array.prototype)
116
+ throw new Error('Non-JSON array prototype.');
117
+ if (item.length > budget + 1)
118
+ throw new Error('JSON byte limit.');
119
+ const keys = Reflect.ownKeys(item);
120
+ if (keys.length !== item.length + 1)
121
+ throw new Error('Sparse or decorated array.');
122
+ const result = [];
123
+ for (let index = 0; index < item.length; index++) {
124
+ const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
125
+ if (!descriptor?.enumerable || !('value' in descriptor))
126
+ throw new Error('Sparse or accessor array.');
127
+ if (index)
128
+ spend(1);
129
+ result.push(copy(descriptor.value, depth + 1));
130
+ }
131
+ output = result;
132
+ }
133
+ else {
134
+ const fields = ownData(item);
135
+ const result = {};
136
+ let index = 0;
137
+ for (const key of Object.keys(fields)) {
138
+ if (index++)
139
+ spend(1);
140
+ string(key);
141
+ spend(1);
142
+ Object.defineProperty(result, key, {
143
+ value: copy(fields[key], depth + 1),
144
+ enumerable: true,
145
+ writable: true,
146
+ configurable: true,
147
+ });
148
+ }
149
+ output = result;
150
+ }
151
+ ancestors.delete(item);
152
+ return freeze ? Object.freeze(output) : output;
153
+ };
154
+ return copy(value, 0);
155
+ }
156
+ function readChecks(value, config, resolved) {
157
+ // Bound before copying. The copy rejects getters, class instances, cycles,
158
+ // decorated arrays and values JSON would otherwise silently discard.
159
+ const source = ownData(value, ['checks']);
160
+ if (!Array.isArray(source.checks) || source.checks.length > config.limits.maxChecks)
161
+ throw new Error('Invalid checks.');
162
+ const detached = jsonCopy(source.checks, config.limits.maxChecks * 4096, false);
163
+ const ids = new Set();
164
+ const refs = new Set(resolved);
165
+ return detached.map((raw) => {
166
+ const check = ownData(raw, ['id', 'disposition', 'reason', 'path', 'evidenceRefs']);
167
+ const id = text(check.id, 128);
168
+ if (ids.has(id) ||
169
+ typeof check.disposition !== 'string' ||
170
+ !dispositions.has(check.disposition))
171
+ throw new Error('Invalid check identity or disposition.');
172
+ ids.add(id);
173
+ const reason = check.reason === undefined ? undefined : text(check.reason, 1024);
174
+ const path = check.path;
175
+ if (path !== undefined && (typeof path !== 'string' || path.length > 256))
176
+ throw new Error('Invalid check path.');
177
+ let evidenceRefs;
178
+ if (check.evidenceRefs !== undefined) {
179
+ if (!Array.isArray(check.evidenceRefs) || check.evidenceRefs.length > config.limits.maxReads)
180
+ throw new Error('Invalid evidence references.');
181
+ evidenceRefs = check.evidenceRefs.map((ref) => {
182
+ if (typeof ref !== 'string' || !refs.has(ref))
183
+ throw new Error('Unresolved evidence reference.');
184
+ return ref;
185
+ });
186
+ if (new Set(evidenceRefs).size !== evidenceRefs.length)
187
+ throw new Error('Duplicate evidence reference.');
188
+ }
189
+ return Object.freeze({
190
+ id,
191
+ disposition: check.disposition,
192
+ ...(reason !== undefined && { reason }),
193
+ ...(path !== undefined && { path }),
194
+ ...(evidenceRefs !== undefined && { evidenceRefs: Object.freeze(evidenceRefs) }),
195
+ });
196
+ });
197
+ }
198
+ /**
199
+ * Validate once. Content is present only when delivery is permitted by this
200
+ * contract. It is the exact JSON string whose detached value was checked;
201
+ * callers must not reparse through a transforming schema or rewrite afterward.
202
+ */
203
+ async function executeAnswerValidation(raw, parser, config, store, scope, signal) {
204
+ const controller = new AbortController();
205
+ const evidence = (0, evidence_js_1.createAnswerEvidenceResolver)(store, scope, config.limits, controller.signal);
206
+ let reason;
207
+ let schemaAccepted = false;
208
+ let canonical;
209
+ let candidateDigest;
210
+ let checks = [];
211
+ let active = true;
212
+ let stop;
213
+ const abort = () => {
214
+ reason = 'aborted';
215
+ controller.abort();
216
+ stop?.();
217
+ };
218
+ const timedOut = () => {
219
+ reason = 'timeout';
220
+ controller.abort();
221
+ stop?.();
222
+ };
223
+ const deadline = Date.now() + config.limits.timeoutMs;
224
+ const timer = setTimeout(timedOut, config.limits.timeoutMs);
225
+ signal?.addEventListener('abort', abort, { once: true });
226
+ if (signal?.aborted)
227
+ abort();
228
+ try {
229
+ const interrupted = new Promise((resolve) => {
230
+ stop = resolve;
231
+ if (controller.signal.aborted)
232
+ resolve();
233
+ });
234
+ const work = async () => {
235
+ if (controller.signal.aborted)
236
+ return;
237
+ let candidate;
238
+ try {
239
+ if (raw.length > config.limits.maxCandidateBytes ||
240
+ encoder.encode(raw).byteLength > config.limits.maxCandidateBytes) {
241
+ reason = 'candidate-limit';
242
+ return;
243
+ }
244
+ candidate = (0, outputSchema_js_1.applyOutputSchema)(raw, parser);
245
+ }
246
+ catch {
247
+ reason = 'schema-rejected';
248
+ return;
249
+ }
250
+ try {
251
+ candidate = jsonCopy(candidate, config.limits.maxCandidateBytes, true);
252
+ canonical = JSON.stringify(candidate);
253
+ schemaAccepted = true;
254
+ }
255
+ catch {
256
+ reason = 'non-json-candidate';
257
+ return;
258
+ }
259
+ if (Date.now() >= deadline) {
260
+ timedOut();
261
+ return;
262
+ }
263
+ try {
264
+ const digest = await (0, payload_js_1.computeArtifactDigest)(canonical);
265
+ if (!active || controller.signal.aborted)
266
+ return;
267
+ candidateDigest = digest;
268
+ const returned = await config.validate(candidate, Object.freeze({ signal: controller.signal, artifacts: evidence.resolver }));
269
+ if (!active || controller.signal.aborted)
270
+ return;
271
+ if (Date.now() >= deadline) {
272
+ timedOut();
273
+ return;
274
+ }
275
+ try {
276
+ checks = readChecks(returned, config, evidence.resolvedRefs());
277
+ }
278
+ catch {
279
+ reason = 'invalid-validator-result';
280
+ }
281
+ }
282
+ catch {
283
+ if (active && !controller.signal.aborted)
284
+ reason = 'validator-error';
285
+ }
286
+ };
287
+ await Promise.race([work(), interrupted]);
288
+ }
289
+ finally {
290
+ active = false;
291
+ clearTimeout(timer);
292
+ signal?.removeEventListener('abort', abort);
293
+ evidence.close();
294
+ controller.abort();
295
+ }
296
+ const failed = checks.filter((check) => check.disposition === 'checked-fail').length;
297
+ const checked = failed + checks.filter((check) => check.disposition === 'checked-pass').length;
298
+ const unreachable = checks.filter((check) => check.disposition === 'unreachable').length;
299
+ const notApplicable = checks.filter((check) => check.disposition === 'not-applicable').length;
300
+ if (!reason && evidence.failureReason())
301
+ reason = `evidence-${evidence.failureReason()}`;
302
+ if (!reason && checked === 0)
303
+ reason = 'no-comparable-checks';
304
+ if (!reason && unreachable > 0)
305
+ reason = 'unreachable-checks';
306
+ const status = reason ? 'unverified' : failed > 0 ? 'failed' : 'passed';
307
+ const report = Object.freeze({
308
+ validatorId: config.id,
309
+ validatorVersion: config.version,
310
+ mode: config.mode,
311
+ status,
312
+ checked,
313
+ failed,
314
+ unreachable,
315
+ notApplicable,
316
+ checks: Object.freeze(checks),
317
+ schemaAccepted,
318
+ ...(reason !== undefined && { reason }),
319
+ ...(candidateDigest !== undefined && { candidateDigest }),
320
+ resolvedRefs: Object.freeze(evidence.resolvedRefs()),
321
+ });
322
+ const content = status === 'passed' || config.mode === 'observe' ? canonical ?? raw : undefined;
323
+ return { report, ...(content !== undefined && { content }) };
324
+ }
325
+ exports.executeAnswerValidation = executeAnswerValidation;
326
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/answer-validation/validate.ts"],"names":[],"mappings":"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"}
@@ -61,7 +61,13 @@ function detectSkillChurn(history, windowSize = exports.SKILL_CHURN_WINDOW, thre
61
61
  const recent = history.slice(-windowSize);
62
62
  const uniqueSkills = new Set();
63
63
  for (const s of recent) {
64
- if (s !== undefined)
64
+ // A skill id is a string; anything else is "no skill on this turn". The
65
+ // slot is `undefined` when the run had no skill yet — and it was `null`
66
+ // in every record written before footprintjs 9.24.0, whose scope
67
+ // round-tripped the array through JSON. That `null` counted here as a
68
+ // skill and turned caching OFF on the first turn after two real skills
69
+ // (a phantom churn; found by the 9.24.0 byte-identity run).
70
+ if (typeof s === 'string')
65
71
  uniqueSkills.add(s);
66
72
  }
67
73
  return uniqueSkills.size >= threshold;