@knights-eye-chess/ai-analysis 0.0.0-stage → 0.1.0-dev.14

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 (258) hide show
  1. package/LICENSE +235 -0
  2. package/LICENSING.md +26 -0
  3. package/NOTICE +5 -0
  4. package/NPM-PUBLISHING.md +24 -0
  5. package/README.md +141 -2
  6. package/SOURCE-ORIGIN.json +16 -0
  7. package/THIRD_PARTY_NOTICES.md +18 -0
  8. package/build.mjs +22 -0
  9. package/dist/answer-pipeline.test.mjs +375 -0
  10. package/dist/chunk-PLKNJRPF.js +10662 -0
  11. package/dist/chunk-XQDZJOYD.js +2515 -0
  12. package/dist/index.js +146 -0
  13. package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
  14. package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
  15. package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
  16. package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
  17. package/dist/owner-tests/position-preparation.test.mjs +625 -0
  18. package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
  19. package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
  20. package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
  21. package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
  22. package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
  23. package/dist/research.js +2274 -0
  24. package/dist/runtime-closure.json +103 -0
  25. package/dist/shared-runtime-closure.json +39 -0
  26. package/dist/shared.js +152 -0
  27. package/dist/types/ai-request-failure.d.ts +14 -0
  28. package/dist/types/ai-request-limits.d.ts +4 -0
  29. package/dist/types/ai-routing-diagnostics.d.ts +14 -0
  30. package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
  31. package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
  32. package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
  33. package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
  34. package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
  35. package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
  36. package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
  37. package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
  38. package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
  39. package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
  40. package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
  41. package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
  42. package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
  43. package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
  44. package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
  45. package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
  46. package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
  47. package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
  48. package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
  49. package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
  50. package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
  51. package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
  52. package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
  53. package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
  54. package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
  55. package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
  56. package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
  57. package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
  58. package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
  59. package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
  60. package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
  61. package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
  62. package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
  63. package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
  64. package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
  65. package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
  66. package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
  67. package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
  68. package/dist/types/chess-reasoning/material-format.d.ts +4 -0
  69. package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
  70. package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
  71. package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
  72. package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
  73. package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
  74. package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
  75. package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
  76. package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
  77. package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
  78. package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
  79. package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
  80. package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
  81. package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
  82. package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
  83. package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
  84. package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
  85. package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
  86. package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
  87. package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
  88. package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
  89. package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
  90. package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
  91. package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
  92. package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
  93. package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
  94. package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
  95. package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
  96. package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
  97. package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
  98. package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
  99. package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
  100. package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
  101. package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
  102. package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
  103. package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
  104. package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
  105. package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
  106. package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
  107. package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
  108. package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
  109. package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
  110. package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
  111. package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
  112. package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
  113. package/dist/types/chess-reasoning/stable.d.ts +2 -0
  114. package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
  115. package/dist/types/chess-reasoning/types.d.ts +180 -0
  116. package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
  117. package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
  118. package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
  119. package/dist/types/player-perspective.d.ts +16 -0
  120. package/dist/types/shared-exports.d.ts +22 -0
  121. package/docs/API.md +82 -0
  122. package/docs/OWNERSHIP-ADOPTION.md +35 -0
  123. package/docs/OWNERSHIP-DRIFT.json +55 -0
  124. package/docs/RESEARCH-API.md +28 -0
  125. package/licenses/third-party/INVENTORY.json +202 -0
  126. package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
  127. package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
  128. package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
  129. package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
  130. package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
  131. package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
  132. package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
  133. package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
  134. package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
  135. package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
  136. package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
  137. package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
  138. package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
  139. package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
  140. package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
  141. package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
  142. package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
  143. package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
  144. package/npm-release.json +5 -0
  145. package/package.json +72 -4
  146. package/scripts/check-release-tooling.mjs +22 -0
  147. package/scripts/pack.mjs +68 -0
  148. package/scripts/publish-reviewed.mjs +72 -0
  149. package/scripts/release-tooling.json +13 -0
  150. package/scripts/update-dependency.mjs +44 -0
  151. package/scripts/verify-release.mjs +52 -0
  152. package/src/ai-request-failure.ts +34 -0
  153. package/src/ai-request-limits.ts +4 -0
  154. package/src/ai-routing-diagnostics.ts +33 -0
  155. package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
  156. package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
  157. package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
  158. package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
  159. package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
  160. package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
  161. package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
  162. package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
  163. package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
  164. package/src/chess-reasoning/answer-host-contracts.ts +10 -0
  165. package/src/chess-reasoning/answer-model-connector.ts +83 -0
  166. package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
  167. package/src/chess-reasoning/answer-pipeline.ts +195 -0
  168. package/src/chess-reasoning/answer-reference-validation.ts +124 -0
  169. package/src/chess-reasoning/answer-tool-host.ts +10 -0
  170. package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
  171. package/src/chess-reasoning/branch-expansion.ts +80 -0
  172. package/src/chess-reasoning/causal-claim-checks.ts +256 -0
  173. package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
  174. package/src/chess-reasoning/classification-question-target.ts +30 -0
  175. package/src/chess-reasoning/classification-reassessment.ts +34 -0
  176. package/src/chess-reasoning/compact-search-preparation.ts +125 -0
  177. package/src/chess-reasoning/concept-registry.ts +275 -0
  178. package/src/chess-reasoning/conversation-context.ts +29 -0
  179. package/src/chess-reasoning/engine-search-failure.ts +11 -0
  180. package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
  181. package/src/chess-reasoning/evidence-licenses.ts +166 -0
  182. package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
  183. package/src/chess-reasoning/existing-analysis-context.ts +544 -0
  184. package/src/chess-reasoning/fact-observation-provider.ts +134 -0
  185. package/src/chess-reasoning/fact-provider.ts +250 -0
  186. package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
  187. package/src/chess-reasoning/historical-prose.ts +116 -0
  188. package/src/chess-reasoning/idea-graph.ts +224 -0
  189. package/src/chess-reasoning/initial-search-plan.ts +115 -0
  190. package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
  191. package/src/chess-reasoning/matched-comparison.ts +40 -0
  192. package/src/chess-reasoning/material-accounting.ts +55 -0
  193. package/src/chess-reasoning/material-format.ts +47 -0
  194. package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
  195. package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
  196. package/src/chess-reasoning/mechanism-presentation.ts +94 -0
  197. package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
  198. package/src/chess-reasoning/native-answer-connector.ts +13 -0
  199. package/src/chess-reasoning/opening-source-contracts.ts +47 -0
  200. package/src/chess-reasoning/opening-source-credits.ts +53 -0
  201. package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
  202. package/src/chess-reasoning/piece-identities.ts +159 -0
  203. package/src/chess-reasoning/poc-contracts.ts +1097 -0
  204. package/src/chess-reasoning/poc-hypotheses.ts +759 -0
  205. package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
  206. package/src/chess-reasoning/poc-observations.ts +611 -0
  207. package/src/chess-reasoning/poc-piece-refs.ts +62 -0
  208. package/src/chess-reasoning/poc-plan-model.ts +345 -0
  209. package/src/chess-reasoning/poc-semantic-text.ts +10 -0
  210. package/src/chess-reasoning/poc-tools.ts +978 -0
  211. package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
  212. package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
  213. package/src/chess-reasoning/position-analysis-scope.ts +20 -0
  214. package/src/chess-reasoning/preparation-consolidation.ts +91 -0
  215. package/src/chess-reasoning/production-observation-provider.ts +41 -0
  216. package/src/chess-reasoning/question-routing.ts +332 -0
  217. package/src/chess-reasoning/recorded-main-line.ts +55 -0
  218. package/src/chess-reasoning/recorded-position-tools.ts +78 -0
  219. package/src/chess-reasoning/recorded-reference-context.ts +44 -0
  220. package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
  221. package/src/chess-reasoning/reference-limits.ts +2 -0
  222. package/src/chess-reasoning/research-exports.ts +197 -0
  223. package/src/chess-reasoning/saved-score-presentation.ts +6 -0
  224. package/src/chess-reasoning/score-comparison-notices.ts +115 -0
  225. package/src/chess-reasoning/server-simple-policy.ts +75 -0
  226. package/src/chess-reasoning/simple-agent-config.ts +58 -0
  227. package/src/chess-reasoning/simple-board-analysis.ts +57 -0
  228. package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
  229. package/src/chess-reasoning/simple-completion.ts +37 -0
  230. package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
  231. package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
  232. package/src/chess-reasoning/simple-evidence-view.ts +69 -0
  233. package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
  234. package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
  235. package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
  236. package/src/chess-reasoning/simple-prose-references.ts +528 -0
  237. package/src/chess-reasoning/simple-runtime-version.ts +2 -0
  238. package/src/chess-reasoning/stable.ts +18 -0
  239. package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
  240. package/src/chess-reasoning/types.ts +195 -0
  241. package/src/chess-reasoning/visible-support-lines.ts +44 -0
  242. package/src/chess-reasoning/wikibooks-index.ts +56 -0
  243. package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
  244. package/src/player-perspective.ts +41 -0
  245. package/src/shared-exports.ts +23 -0
  246. package/test/answer-pipeline.test.mjs +375 -0
  247. package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
  248. package/test/owner/mechanism-encoding.test.mjs +78 -0
  249. package/test/owner/move-mechanism-facts.test.mjs +105 -0
  250. package/test/owner/pipeline-host-preparation.test.mjs +115 -0
  251. package/test/owner/position-preparation.test.mjs +22 -0
  252. package/test/owner/preparation-consolidation.test.mjs +74 -0
  253. package/test/owner/prose-reference-admission.test.mjs +26 -0
  254. package/test/owner/score-comparison-notices.test.mjs +116 -0
  255. package/test/owner/simple-engine-presentation.test.mjs +50 -0
  256. package/test/owner/tool-input-loop-guard.test.mjs +25 -0
  257. package/test-types.mts +23 -0
  258. package/tsconfig.json +22 -0
@@ -0,0 +1,924 @@
1
+ import { Output, generateText, jsonSchema, type LanguageModel, type LanguageModelCallOptions } from "ai";
2
+ import {
3
+ DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA,
4
+ materializeDirectSynthesis,
5
+ type DirectSynthesisArtifact,
6
+ type DirectSynthesisIssue,
7
+ } from "./ablation-direct-synthesis";
8
+ import {
9
+ ablationHistoryThroughTargetHash,
10
+ createAblationProbeTelemetry,
11
+ enrichAblationEvidenceClosureBundle,
12
+ validateAblationEvidenceClosureBundle,
13
+ type AblationCalledProbe,
14
+ type AblationEvidenceClosureBundle,
15
+ type AblationEvidenceClosureCoverage,
16
+ type AblationProbeTelemetry,
17
+ } from "./ablation-evidence-bundle";
18
+ import { projectAblationEvidenceForModel } from "./ablation-model-facing-evidence";
19
+ import { effectiveLicensesForRecord } from "./evidence-licenses";
20
+ import { collectReasoningMoveWitnesses } from "./poc-move-witnesses";
21
+ import type {
22
+ ReasoningObservation,
23
+ ReasoningToolDescriptor,
24
+ ReasoningToolName,
25
+ ReasoningToolResult,
26
+ } from "./poc-contracts";
27
+ import { REASONING_POC_VERSION } from "./poc-contracts";
28
+ import { stableJson } from "./stable";
29
+ import {
30
+ AblationTransportCorrectionError,
31
+ assertTransportOnlyDirectCorrection,
32
+ isTransportCorrectableDirectSynthesisIssues,
33
+ } from "./ablation-transport-correction";
34
+
35
+ export { AblationTransportCorrectionError, assertTransportOnlyDirectCorrection } from "./ablation-transport-correction";
36
+ /** @deprecated Use AblationTransportCorrectionError from the shared module. */
37
+ export { AblationTransportCorrectionError as AdaptiveTransportCorrectionError } from "./ablation-transport-correction";
38
+
39
+ /**
40
+ * Adaptive direct arms remain deliberately outside the staged-C review chain.
41
+ * The evidence-loop boundary is injectable so the browser and Node adapters can
42
+ * drive the same episode without giving this experiment its own tool runtime.
43
+ */
44
+ export const ABLATION_ADAPTIVE_RUNTIME_VERSION = "architecture-ablation-adaptive-runtime/2" as const;
45
+
46
+ export const ABLATION_B_SYSTEM = `You are Knight's Eye in adaptive direct arm B. You may use only the supplied bounded fact and probe tools to acquire evidence about the selected move. Work iteratively, stop as soon as the available evidence is sufficient, and do not infer purpose, safety, forcedness, or a plan merely from a legal move. The verbatim user question is authoritative. You receive no later game history, evaluator answer keys, graph, critic, review, checkpoint, or render state.`;
47
+
48
+ export const ABLATION_B_DIRECT_SYSTEM = `You are Knight's Eye in adaptive direct arm B. Return exactly one object matching the direct-synthesis schema over the frozen evidence closure. This is a pre-checkpoint proposal, not a supported product answer. Cite only allowed evidence and observations. Evidence licenses are strict ceilings: legal sequence evidence establishes legality and identity, and bounded engine evidence establishes only the recorded bounded line, score, comparison, or response coverage. Do not claim intent, a forced plan, or strategic relevance unless the evidence itself licenses the bounded claim.`;
49
+
50
+ export const ABLATION_B_PRIME_CRITIC_SYSTEM = `You are the single bounded semantic reconsideration for Knight's Eye architecture-ablation arm B-prime. You have no tools and may not acquire evidence. Given a frozen direct proposal, explicit generic trigger values, and the same frozen evidence closure, decide whether the proposal can be retained or needs one corrected replacement. A correction may only use existing IDs and evidence licenses; it may narrow, qualify, or remove unsupported meaning but may not add evidence, broaden authority, change the user question, or invoke another critic. Return exactly the required object.`;
51
+
52
+ export const ABLATION_B_PRIME_TRIGGER_VALUES = Object.freeze([
53
+ "claim.forced-best-unique-safe-winning-principal-purpose",
54
+ "coverage.requested-defense-or-continuation-incomplete",
55
+ "evidence.conflict",
56
+ "claim.sacrifice-or-long-multistep",
57
+ "plan.legal-affordance-only",
58
+ "model.high-uncertainty",
59
+ "validation.semantic-reconsideration-required",
60
+ ] as const);
61
+
62
+ export type AblationAdaptiveArm = "B" | "B-prime";
63
+ export type AblationBPrimeTrigger = typeof ABLATION_B_PRIME_TRIGGER_VALUES[number];
64
+
65
+ export type AdaptiveCallUsage = {
66
+ inputTokens: number | null;
67
+ outputTokens: number | null;
68
+ latencyMs: number | null;
69
+ cache: { hits: number; misses: number };
70
+ cost: number | null;
71
+ };
72
+
73
+ export type AdaptiveEvidenceToolRound = {
74
+ /** Monotonic across both returned and failed tool executions. */
75
+ attempt: number;
76
+ callId: string;
77
+ requestId: string;
78
+ tool: ReasoningToolName;
79
+ input: Record<string, unknown>;
80
+ selectionRationale: string;
81
+ modelStatedRationale: string | null;
82
+ result: ReasoningToolResult;
83
+ usage: AdaptiveCallUsage;
84
+ };
85
+
86
+ /**
87
+ * A requested tool is still a telemetry event when its host execution fails.
88
+ * Keep this transport-only record separate from a valid tool result: inventing
89
+ * a result/evidence record for a failure would make the closure dishonest.
90
+ */
91
+ export type AdaptiveEvidenceToolFailure = {
92
+ attempt: number;
93
+ callId: string;
94
+ requestId: string;
95
+ tool: ReasoningToolName;
96
+ input: Record<string, unknown>;
97
+ selectionRationale: string;
98
+ modelStatedRationale: string | null;
99
+ failureCode: "tool-execution-failed";
100
+ usage: AdaptiveCallUsage;
101
+ };
102
+
103
+ export type AdaptiveEvidenceStoppingReason =
104
+ | "model-finished"
105
+ | "tool-budget-exhausted"
106
+ | "probe-budget-exhausted"
107
+ | "model-decision-budget-exhausted";
108
+
109
+ export type AdaptiveEvidenceLoopResult = {
110
+ /** Only already-executed presemantic fact/probe calls may cross this boundary. */
111
+ rounds: readonly AdaptiveEvidenceToolRound[];
112
+ /** Requested executions that started but did not return an admissible result. */
113
+ failedToolCalls: readonly AdaptiveEvidenceToolFailure[];
114
+ /** Exact descriptors advertised to the model for this bounded episode. */
115
+ availableTools: readonly ReasoningToolDescriptor[];
116
+ /** Fact/probe evidence actually retained by the existing salience policy. */
117
+ saliencePresentedFactIds: readonly string[];
118
+ addedObservations?: readonly ReasoningObservation[];
119
+ coverage: AblationEvidenceClosureCoverage;
120
+ stopping: { reason: AdaptiveEvidenceStoppingReason; rationale: string; unusedRelevantTools: ReasoningToolName[] };
121
+ /** Counts provider calls made by the injected model-owned loop. */
122
+ modelCalls: number;
123
+ usage: AdaptiveCallUsage & { corrections: number };
124
+ };
125
+
126
+ export type AdaptiveEvidenceLoopInput = {
127
+ arm: AblationAdaptiveArm;
128
+ system: string;
129
+ /** The first provider-visible bytes of every loop call must be this prefix. */
130
+ firstProviderVisiblePrefix: string;
131
+ /** Call once for every additional provider attempt/retry after the first. */
132
+ nextProviderVisiblePrefix: () => string;
133
+ bundle: Readonly<AblationEvidenceClosureBundle>;
134
+ tools: readonly ReasoningToolDescriptor[];
135
+ maxToolCalls: number;
136
+ maxProbeCalls: number;
137
+ maxModelSteps: number;
138
+ signal?: AbortSignal;
139
+ };
140
+
141
+ /**
142
+ * Adapters own tool-call transport and may use ToolLoopAgent. This contract
143
+ * makes its evidence output auditable while forbidding it from adding semantic
144
+ * state or bypassing production evidence records.
145
+ */
146
+ export interface AdaptiveEvidenceLoop {
147
+ run(input: AdaptiveEvidenceLoopInput): Promise<AdaptiveEvidenceLoopResult>;
148
+ }
149
+
150
+ export type AblationAdaptiveCallDiagnostic = {
151
+ stage: "B-evidence" | "B-direct" | "B-prime-critic" | "B-transport-correction";
152
+ nonce: string;
153
+ inputBytes: number;
154
+ outputBytes: number | null;
155
+ latencyMs: number | null;
156
+ inputTokens: number | null;
157
+ outputTokens: number | null;
158
+ cacheTokens: number | null;
159
+ cost: number | null;
160
+ correctionCount: number;
161
+ outcome: "succeeded" | "failed" | "skipped" | "aborted";
162
+ failureCode: string | null;
163
+ };
164
+
165
+ export type AblationAdaptiveRuntimeOptions = {
166
+ model: LanguageModel;
167
+ /**
168
+ * Optional isolated B-prime critic transport. The original adaptive model
169
+ * and reasoning setting remain the backwards-compatible default for every
170
+ * stage unless the bounded critic explicitly supplies its own profile.
171
+ */
172
+ criticModel?: LanguageModel;
173
+ evidenceLoop: AdaptiveEvidenceLoop;
174
+ /** The exact fact/probe descriptor set exposed to the model-owned loop. */
175
+ evidenceTools: readonly ReasoningToolDescriptor[];
176
+ /** Fresh, opaque, unique values supplied by the benchmark runner. */
177
+ benchmarkNonceForCall: (stage: "evidence" | "direct" | "critic" | "transport-correction", ordinal: number) => string;
178
+ modelName?: string;
179
+ providerRoute?: string;
180
+ reasoning?: NonNullable<LanguageModelCallOptions["reasoning"]>;
181
+ criticReasoning?: NonNullable<LanguageModelCallOptions["reasoning"]>;
182
+ temperature?: number;
183
+ maxOutputTokens: number;
184
+ /** One precommitted wall-time envelope for the complete B/B′ episode. */
185
+ maxWallTimeMs: number;
186
+ maxToolCalls: number;
187
+ maxProbeCalls: number;
188
+ maxModelSteps: number;
189
+ triggerPolicy?: (input: AdaptiveTriggerPolicyInput) => readonly AblationBPrimeTrigger[];
190
+ };
191
+
192
+ export type AdaptiveTriggerPolicyInput = {
193
+ bundle: Readonly<AblationEvidenceClosureBundle>;
194
+ proposal: unknown;
195
+ artifact: DirectSynthesisArtifact | null;
196
+ initialIssues: readonly DirectSynthesisIssue[];
197
+ };
198
+
199
+ export type AblationAdaptiveFailure = {
200
+ code: "adaptive-evidence-loop-rejected" | "direct-synthesis-rejected" | "transport-correction-rejected" | "critic-rejected" | "provider-or-structured-output" | "adaptive-timeout" | "adaptive-aborted";
201
+ issues?: DirectSynthesisIssue[];
202
+ };
203
+
204
+ /** B/B′ deliberately start from the frozen initial-exact cell, never a prior enrichment. */
205
+ export class AdaptiveAblationPreconditionError extends Error {
206
+ readonly code = "starting-bundle-must-be-original" as const;
207
+ constructor() {
208
+ super("Adaptive ablation B/B-prime must start from an original initial-exact evidence bundle");
209
+ this.name = "AdaptiveAblationPreconditionError";
210
+ }
211
+ }
212
+
213
+ export type AblationAdaptiveResult = {
214
+ arm: AblationAdaptiveArm;
215
+ ok: boolean;
216
+ bundle: Readonly<AblationEvidenceClosureBundle>;
217
+ artifact?: DirectSynthesisArtifact;
218
+ telemetry: Readonly<AblationProbeTelemetry>;
219
+ triggers: readonly AblationBPrimeTrigger[];
220
+ diagnostics: AblationAdaptiveCallDiagnostic[];
221
+ failure?: AblationAdaptiveFailure;
222
+ };
223
+
224
+ type CriticDecision = {
225
+ schemaVersion: typeof ABLATION_ADAPTIVE_RUNTIME_VERSION;
226
+ decision: "retain" | "replace";
227
+ rationale: string;
228
+ replacement?: unknown;
229
+ };
230
+
231
+ const CRITIC_SCHEMA = {
232
+ type: "object", additionalProperties: false,
233
+ required: ["schemaVersion", "decision", "rationale"],
234
+ properties: {
235
+ schemaVersion: { const: ABLATION_ADAPTIVE_RUNTIME_VERSION },
236
+ decision: { enum: ["retain", "replace"] },
237
+ rationale: { type: "string", minLength: 1, maxLength: 2_000 },
238
+ replacement: DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA,
239
+ },
240
+ } as const;
241
+
242
+ const PRESEMANTIC_TOOLS = new Set<ReasoningToolName>([
243
+ "inspect_position", "analyze_move", "explore_continuations", "compare_moves", "enumerate_defenses", "test_line",
244
+ ]);
245
+ const FORBIDDEN_ADAPTIVE_TELEMETRY_KEYS = new Set([
246
+ "evaluatorAnchor", "evaluatorAnchors", "idealProse", "answerKey", "laterMoves",
247
+ ]);
248
+
249
+ function bytes(value: unknown): number {
250
+ return new TextEncoder().encode(typeof value === "string" ? value : JSON.stringify(value)).length;
251
+ }
252
+
253
+ function nonEmptyNonce(value: string): string {
254
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(value)) throw new Error("Adaptive ablation calls require a bounded supplied benchmark nonce");
255
+ return value;
256
+ }
257
+
258
+ function firstPrefix(nonce: string, body: string): string {
259
+ return `[Benchmark cache-isolation nonce: ${nonce}. Ignore this opaque identifier; it has no chess, workflow, or output meaning.]\n\n${body}`;
260
+ }
261
+
262
+ function sum(values: readonly (number | null)[]): number | null {
263
+ return values.every((value) => value === null) ? null : values.reduce<number>((total, value) => total + (value || 0), 0);
264
+ }
265
+
266
+ function sameRecord(left: unknown, right: unknown): boolean {
267
+ return stableJson(left) === stableJson(right);
268
+ }
269
+
270
+ function assertNoEvaluatorTelemetryPayload(value: unknown, path = "adaptive loop telemetry"): void {
271
+ if (Array.isArray(value)) {
272
+ value.forEach((item, index) => assertNoEvaluatorTelemetryPayload(item, `${path}[${index}]`));
273
+ return;
274
+ }
275
+ if (!value || typeof value !== "object") return;
276
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
277
+ if (FORBIDDEN_ADAPTIVE_TELEMETRY_KEYS.has(key)) throw new Error(`Adaptive loop telemetry contains evaluator-only payload at ${path}.${key}`);
278
+ assertNoEvaluatorTelemetryPayload(child, `${path}.${key}`);
279
+ }
280
+ }
281
+
282
+ type AdaptiveAbortCode = "adaptive-timeout" | "adaptive-aborted";
283
+
284
+ type AdaptiveAbortScope = {
285
+ signal: AbortSignal;
286
+ code(): AdaptiveAbortCode | null;
287
+ dispose(): void;
288
+ };
289
+
290
+ function createAdaptiveAbortScope(external: AbortSignal | undefined, maxWallTimeMs: number): AdaptiveAbortScope {
291
+ const controller = new AbortController();
292
+ let stopped: AdaptiveAbortCode | null = null;
293
+ const abortExternally = () => {
294
+ if (stopped) return;
295
+ stopped = "adaptive-aborted";
296
+ controller.abort(new Error("Adaptive ablation episode cancelled"));
297
+ };
298
+ if (external?.aborted) abortExternally();
299
+ else external?.addEventListener("abort", abortExternally, { once: true });
300
+ const timer = setTimeout(() => {
301
+ if (stopped) return;
302
+ stopped = "adaptive-timeout";
303
+ controller.abort(new Error("Adaptive ablation episode exceeded its wall-time budget"));
304
+ }, maxWallTimeMs);
305
+ return {
306
+ signal: controller.signal,
307
+ code: () => stopped,
308
+ dispose: () => {
309
+ clearTimeout(timer);
310
+ external?.removeEventListener("abort", abortExternally);
311
+ },
312
+ };
313
+ }
314
+
315
+ function throwIfAdaptiveStopped(scope: AdaptiveAbortScope): void {
316
+ const code = scope.code();
317
+ if (code) throw Object.assign(new Error("Adaptive ablation episode stopped"), { adaptiveAbortCode: code });
318
+ }
319
+
320
+ function adaptiveAbortCode(scope: AdaptiveAbortScope, error?: unknown): AdaptiveAbortCode | null {
321
+ const explicit = error && typeof error === "object" ? (error as { adaptiveAbortCode?: unknown }).adaptiveAbortCode : null;
322
+ return explicit === "adaptive-timeout" || explicit === "adaptive-aborted" ? explicit : scope.code();
323
+ }
324
+
325
+ async function abortable<T>(scope: AdaptiveAbortScope, work: Promise<T>): Promise<T> {
326
+ // `work` may have observed the signal while the call expression was being
327
+ // evaluated. Consume that late provider/loop rejection before returning the
328
+ // sanitized episode status; otherwise it becomes an unhandled rejection.
329
+ if (scope.signal.aborted) {
330
+ void work.catch(() => undefined);
331
+ throwIfAdaptiveStopped(scope);
332
+ }
333
+ return await new Promise<T>((resolve, reject) => {
334
+ const abort = () => reject(Object.assign(new Error("Adaptive ablation episode stopped"), { adaptiveAbortCode: scope.code() || "adaptive-aborted" }));
335
+ scope.signal.addEventListener("abort", abort, { once: true });
336
+ work.then(
337
+ (value) => { scope.signal.removeEventListener("abort", abort); resolve(value); },
338
+ (error) => { scope.signal.removeEventListener("abort", abort); reject(error); },
339
+ );
340
+ });
341
+ }
342
+
343
+ export function deriveAblationBPrimeTriggers(input: AdaptiveTriggerPolicyInput): AblationBPrimeTrigger[] {
344
+ const triggers = new Set<AblationBPrimeTrigger>();
345
+ // Trigger policy intentionally reads only structured semantic payloads. It
346
+ // must never rediscover words in an evidence summary or scrape a raw prompt.
347
+ const claimText = input.artifact?.claims.flatMap((claim) => [
348
+ ...claim.content.filter((segment) => segment.type === "text").map((segment) => segment.text),
349
+ ...claim.qualifications.flatMap((qualification) => qualification.filter((segment) => segment.type === "text").map((segment) => segment.text)),
350
+ ]).join(" ").toLowerCase() || "";
351
+ const riskReasons = input.artifact?.riskProfile.reasons.join(" ").toLowerCase() || "";
352
+ if (/\b(forced|best|unique|safe|winning|principal purpose)\b/.test(claimText)) triggers.add("claim.forced-best-unique-safe-winning-principal-purpose");
353
+ if (input.artifact && (input.artifact.coverage.responseCoverage.status !== "complete" || input.artifact.coverage.continuationCoverage.status !== "complete")) {
354
+ triggers.add("coverage.requested-defense-or-continuation-incomplete");
355
+ }
356
+ if (input.artifact && input.artifact.coverage.unresolvedEvidenceObligations.length > 0 && /\b(always|only|must|cannot)\b/.test(claimText)) triggers.add("evidence.conflict");
357
+ if (/\b(sacrifice|sac|long[- ]term|multi[- ]step)\b/.test(claimText)) triggers.add("claim.sacrifice-or-long-multistep");
358
+ const planHypotheses = input.artifact?.hypotheses.filter((hypothesis) => hypothesis.entityType === "plan" || hypothesis.concepts.some((concept) => concept.startsWith("plan."))) || [];
359
+ if (planHypotheses.length && input.artifact && !planHypotheses.some((hypothesis) => hypothesis.evidenceIds.some((id) => {
360
+ const evidence = input.bundle.evidence.find((record) => record.id === id);
361
+ return evidence ? effectiveLicensesForRecord(evidence).some((license) => license.startsWith("engine.bounded-")) : false;
362
+ }))) {
363
+ triggers.add("plan.legal-affordance-only");
364
+ }
365
+ if (input.artifact?.riskProfile.level === "high" || /\b(uncertain|unclear|unknown|insufficient)\b/.test(riskReasons)) triggers.add("model.high-uncertainty");
366
+ if (input.initialIssues.length) triggers.add("validation.semantic-reconsideration-required");
367
+ return [...triggers].sort() as AblationBPrimeTrigger[];
368
+ }
369
+
370
+ function exactTriggers(values: readonly AblationBPrimeTrigger[]): AblationBPrimeTrigger[] {
371
+ if (values.some((value) => !ABLATION_B_PRIME_TRIGGER_VALUES.includes(value))) throw new Error("Adaptive B-prime trigger policy returned an unsupported trigger");
372
+ return [...new Set(values)].sort() as AblationBPrimeTrigger[];
373
+ }
374
+
375
+ function normalizedDescriptors(values: readonly ReasoningToolDescriptor[]): ReasoningToolDescriptor[] {
376
+ return [...values].map((value) => structuredClone(value)).sort((left, right) => left.name.localeCompare(right.name));
377
+ }
378
+
379
+ function validateLoopResult(
380
+ original: Readonly<AblationEvidenceClosureBundle>,
381
+ result: AdaptiveEvidenceLoopResult,
382
+ limits: Pick<AblationAdaptiveRuntimeOptions, "maxToolCalls" | "maxProbeCalls" | "maxModelSteps">,
383
+ advertisedTools: readonly ReasoningToolDescriptor[],
384
+ ): ReasoningToolDescriptor[] {
385
+ if (!Number.isInteger(result.modelCalls) || result.modelCalls < 0 || result.modelCalls > limits.maxModelSteps || !Array.isArray(result.rounds) || !Array.isArray(result.failedToolCalls)
386
+ || result.rounds.length + result.failedToolCalls.length > limits.maxToolCalls) {
387
+ throw new Error("Adaptive evidence loop exceeded its model or tool budget");
388
+ }
389
+ if (!result.coverage || !result.stopping?.rationale?.trim() || !Array.isArray(result.stopping.unusedRelevantTools)
390
+ || !["model-finished", "tool-budget-exhausted", "probe-budget-exhausted", "model-decision-budget-exhausted"].includes(result.stopping.reason)) {
391
+ throw new Error("Adaptive evidence loop omitted explicit stopping telemetry");
392
+ }
393
+ if (!Array.isArray(result.coverage.covered) || !Array.isArray(result.coverage.omitted) || !Array.isArray(result.coverage.unavailable) || !Array.isArray(result.coverage.limitations)) {
394
+ throw new Error("Adaptive evidence loop omitted explicit coverage telemetry");
395
+ }
396
+ if (!Array.isArray(result.saliencePresentedFactIds) || result.saliencePresentedFactIds.some((id) => typeof id !== "string" || !id.trim())) throw new Error("Adaptive evidence loop omitted salience membership");
397
+ const descriptors = normalizedDescriptors(result.availableTools);
398
+ if (sameRecord(descriptors, normalizedDescriptors(advertisedTools)) === false) throw new Error("Adaptive evidence loop changed the advertised tool set");
399
+ const descriptorByName = new Map(descriptors.map((item) => [item.name, item]));
400
+ const calls = new Set<string>();
401
+ const attempts = new Set<number>();
402
+ let probes = 0;
403
+ for (const round of result.rounds) {
404
+ if (!Number.isInteger(round.attempt) || round.attempt < 1 || attempts.has(round.attempt)
405
+ || !round.callId?.trim() || calls.has(round.callId) || !round.requestId?.trim() || !PRESEMANTIC_TOOLS.has(round.tool)) {
406
+ throw new Error("Adaptive evidence loop returned an invalid tool round");
407
+ }
408
+ attempts.add(round.attempt);
409
+ calls.add(round.callId);
410
+ assertNoEvaluatorTelemetryPayload(round.input, `adaptive tool round ${round.callId}.input`);
411
+ assertNoEvaluatorTelemetryPayload(round.result, `adaptive tool round ${round.callId}.result`);
412
+ if (!descriptorByName.has(round.tool) || round.result.schemaVersion !== REASONING_POC_VERSION || round.result.callId !== round.callId || round.result.tool !== round.tool || round.result.contextHash !== original.contextHash) throw new Error("Adaptive evidence loop returned a cross-context or unadvertised tool result");
413
+ if (round.tool !== "inspect_position" && round.tool !== "analyze_move") probes += 1;
414
+ if (!round.selectionRationale?.trim() || !round.result || !Array.isArray(round.result.evidence)) throw new Error("Adaptive evidence loop round lacks its model-owned selection rationale");
415
+ }
416
+ for (const failed of result.failedToolCalls) {
417
+ if (!Number.isInteger(failed.attempt) || failed.attempt < 1 || attempts.has(failed.attempt)
418
+ || !failed.callId?.trim() || calls.has(failed.callId) || !failed.requestId?.trim() || !PRESEMANTIC_TOOLS.has(failed.tool)
419
+ || !descriptorByName.has(failed.tool) || !failed.selectionRationale?.trim() || !failed.input || typeof failed.input !== "object" || Array.isArray(failed.input)
420
+ || failed.failureCode !== "tool-execution-failed") {
421
+ throw new Error("Adaptive evidence loop returned malformed failed-tool telemetry");
422
+ }
423
+ attempts.add(failed.attempt);
424
+ calls.add(failed.callId);
425
+ assertNoEvaluatorTelemetryPayload(failed.input, `adaptive failed tool ${failed.callId}.input`);
426
+ if (failed.tool !== "inspect_position" && failed.tool !== "analyze_move") probes += 1;
427
+ }
428
+ if (probes > limits.maxProbeCalls) throw new Error("Adaptive evidence loop exceeded its probe budget");
429
+ if (result.stopping.unusedRelevantTools.some((tool) => !descriptorByName.has(tool) || result.rounds.some((round) => round.tool === tool) || result.failedToolCalls.some((failed) => failed.tool === tool))) {
430
+ throw new Error("Adaptive evidence loop reported invalid unused tools");
431
+ }
432
+ const attemptedToolCalls = result.rounds.length + result.failedToolCalls.length;
433
+ if ((result.stopping.reason === "tool-budget-exhausted" && attemptedToolCalls !== limits.maxToolCalls)
434
+ || (result.stopping.reason === "model-decision-budget-exhausted" && result.modelCalls !== limits.maxModelSteps)
435
+ || (result.stopping.reason === "probe-budget-exhausted" && (probes < limits.maxProbeCalls || descriptors.some((descriptor) => descriptor.name === "inspect_position" || descriptor.name === "analyze_move")))) {
436
+ throw new Error("Adaptive evidence loop stopping reason does not match its recorded budget state");
437
+ }
438
+ const modelFacingIds = new Set(projectAblationEvidenceForModel(original).visibleEvidenceIds);
439
+ for (const round of result.rounds) for (const evidence of round.result.evidence) modelFacingIds.add(evidence.id);
440
+ if (result.saliencePresentedFactIds.some((id) => !modelFacingIds.has(id))) throw new Error("Adaptive evidence loop presented evidence that was never available to the model");
441
+ return descriptors;
442
+ }
443
+
444
+ function enrichedBundle(
445
+ original: Readonly<AblationEvidenceClosureBundle>,
446
+ result: AdaptiveEvidenceLoopResult,
447
+ ): Readonly<AblationEvidenceClosureBundle> {
448
+ const additions = result.rounds.flatMap((round) => round.result.evidence);
449
+ if (!additions.length) return original;
450
+ const allEvidence = [...original.evidence, ...additions];
451
+ const witnesses = collectReasoningMoveWitnesses(allEvidence, original.context);
452
+ const addedReferences = witnesses.flatMap((witness) => [
453
+ { domain: "move" as const, id: witness.uci },
454
+ { domain: "piece" as const, id: witness.pieceId },
455
+ ]);
456
+ const oldReferences = original.referenceAllowlists.semanticReferences;
457
+ const references = [...new Map([...oldReferences, ...addedReferences].map((value) => [`${value.domain}:${value.id}`, value] as const)).values()];
458
+ return enrichAblationEvidenceClosureBundle(original, {
459
+ observations: [...(result.addedObservations || [])],
460
+ evidence: additions,
461
+ coverage: mergedCoverage(original.coverage, result.coverage),
462
+ referenceAllowlists: {
463
+ evidenceIds: allEvidence.map((record) => record.id),
464
+ observationIds: [...original.observations, ...(result.addedObservations || [])].map((observation) => observation.id),
465
+ toolNames: [...new Set([...original.referenceAllowlists.toolNames, ...result.rounds.map((round) => round.tool)])].sort(),
466
+ semanticReferences: references,
467
+ moveWitnesses: witnesses,
468
+ },
469
+ });
470
+ }
471
+
472
+ function mergedCoverage(
473
+ original: AblationEvidenceClosureCoverage,
474
+ acquired: AblationEvidenceClosureCoverage,
475
+ ): AblationEvidenceClosureCoverage {
476
+ const union = <T,>(items: readonly T[], key: (item: T) => string) => [...new Map(items.map((item) => [key(item), item] as const)).values()];
477
+ const omitted = union([...original.omitted, ...acquired.omitted], (item) => `${item.domain}:${item.id}`)
478
+ .sort((left, right) => `${left.domain}:${left.id}`.localeCompare(`${right.domain}:${right.id}`));
479
+ const unavailable = union([...original.unavailable, ...acquired.unavailable], (item) => item.id)
480
+ .sort((left, right) => left.id.localeCompare(right.id));
481
+ return {
482
+ // An acquisition pass can add coverage but cannot erase inherited scope
483
+ // limits or turn an incomplete starting cell into an exhaustive closure.
484
+ complete: original.complete && acquired.complete && !omitted.length && !unavailable.length,
485
+ covered: [...new Set([...original.covered, ...acquired.covered])].sort(),
486
+ omitted,
487
+ unavailable,
488
+ limitations: [...new Set([...original.limitations, ...acquired.limitations])].sort(),
489
+ };
490
+ }
491
+
492
+ function probeTelemetry(
493
+ bundle: Readonly<AblationEvidenceClosureBundle>,
494
+ descriptors: readonly ReasoningToolDescriptor[],
495
+ result: AdaptiveEvidenceLoopResult,
496
+ artifact: DirectSynthesisArtifact | undefined,
497
+ diagnostics: readonly AblationAdaptiveCallDiagnostic[],
498
+ limits: Pick<AblationAdaptiveRuntimeOptions, "maxToolCalls" | "maxProbeCalls">,
499
+ ): Readonly<AblationProbeTelemetry> {
500
+ const modelView = projectAblationEvidenceForModel(bundle);
501
+ const modelBundle = modelView.bundle;
502
+ const hypothesisIdsForEvidence = (evidenceIds: readonly string[]) => artifact
503
+ ? artifact.hypotheses.filter((hypothesis) => hypothesis.evidenceIds.some((id) => evidenceIds.includes(id))).map((hypothesis) => hypothesis.id).sort()
504
+ : [];
505
+ const priorEvidenceIds = new Set(modelView.visibleEvidenceIds.filter((id) => bundle.lineage.kind !== "enriched" || !bundle.lineage.acquiredEvidenceIds.includes(id)));
506
+ const beforeProbeEvidenceIds = new Set(priorEvidenceIds);
507
+ const effectForRound = (
508
+ round: AdaptiveEvidenceToolRound,
509
+ evidenceIds: readonly string[],
510
+ hypotheses: readonly string[],
511
+ hypothesesWithCompleteBasis: readonly string[],
512
+ ): "materially-changed" | "supported" | "rejected" | "no-change" => {
513
+ if (!evidenceIds.length || !hypotheses.length) return "no-change";
514
+ if (round.result.support === "unsupported") return "rejected";
515
+ const complete = artifact!.hypotheses.filter((hypothesis) => hypothesesWithCompleteBasis.includes(hypothesis.id));
516
+ // This is the narrow case where the telemetry can prove a before/after
517
+ // difference from recorded citations: the whole cited basis is now
518
+ // present, and none of that basis was in the pre-probe closure. A cited
519
+ // result that is still awaiting another cited result remains support, not
520
+ // a no-op; citations do not establish causal necessity by themselves.
521
+ return complete.some((hypothesis) => hypothesis.evidenceIds.every((id) => !beforeProbeEvidenceIds.has(id)))
522
+ ? "materially-changed"
523
+ : "supported";
524
+ };
525
+ const calls: Array<{ attempt: number; call: AblationCalledProbe }> = [...result.rounds].sort((left, right) => left.attempt - right.attempt).map((round, callIndex) => {
526
+ const evidenceIds = round.result.evidence.map((record) => record.id).filter((id) => modelBundle.referenceAllowlists.evidenceIds.includes(id));
527
+ const candidateHypotheses = hypothesisIdsForEvidence(evidenceIds);
528
+ const availableAfterThisRound = new Set([...priorEvidenceIds, ...evidenceIds]);
529
+ const hypothesesWithCompleteBasis = artifact
530
+ ? artifact.hypotheses.filter((hypothesis) => candidateHypotheses.includes(hypothesis.id) && hypothesis.evidenceIds.every((id) => availableAfterThisRound.has(id))).map((hypothesis) => hypothesis.id).sort()
531
+ : [];
532
+ // Every admitted final citation is semantic consumption. A later cited
533
+ // result may be needed before the final basis is complete, but that does
534
+ // not turn this earlier supporting result into a no-change event.
535
+ const hypotheses = candidateHypotheses;
536
+ const admitted = evidenceIds.length > 0;
537
+ const consumed = admitted && hypotheses.length > 0;
538
+ const descriptor = descriptors.find((item) => item.name === round.tool)!;
539
+ const lineRecord = round.result.evidence.find((record) => Array.isArray(record.data.lines));
540
+ const coverage = lineRecord?.coverage || { complete: false, covered: [], unresolved: [] };
541
+ const effect = artifact ? effectForRound(round, evidenceIds, hypotheses, hypothesesWithCompleteBasis) : "no-change";
542
+ for (const evidenceId of evidenceIds) priorEvidenceIds.add(evidenceId);
543
+ return { attempt: round.attempt, call: {
544
+ callId: round.callId,
545
+ requestId: round.requestId,
546
+ tool: round.tool,
547
+ request: { descriptor, input: structuredClone(round.input), selectionRationale: round.selectionRationale },
548
+ target: { fen: bundle.context.fenAfter, historyThroughTargetHash: ablationHistoryThroughTargetHash(bundle.context) },
549
+ permittedLimits: { maxToolCalls: limits.maxToolCalls, maxProbeCalls: limits.maxProbeCalls, engine: null },
550
+ approval: {
551
+ approved: true,
552
+ budgetReason: "within-frozen-adaptive-budget",
553
+ remainingToolCalls: Math.max(0, limits.maxToolCalls - callIndex - 1),
554
+ remainingProbeCalls: Math.max(0, limits.maxProbeCalls - result.rounds.slice(0, callIndex + 1).filter((item) => item.tool !== "inspect_position" && item.tool !== "analyze_move").length),
555
+ },
556
+ status: { started: true, finished: "returned" },
557
+ returned: { lines: Array.isArray(lineRecord?.data.lines) ? lineRecord!.data.lines.length : 0, coverage },
558
+ admission: { status: admitted ? "admitted" : "not-produced", reason: admitted ? "admitted-into-frozen-direct-closure" : "tool-returned-no-admissible-evidence" },
559
+ admittedEvidence: evidenceIds.map((evidenceId) => ({ evidenceId, licenses: effectiveLicensesForRecord(modelBundle.evidence.find((record) => record.id === evidenceId)!) })),
560
+ presentation: { presentedToModel: admitted, reason: admitted ? "frozen-direct-submission-context" : "no-admitted-evidence" },
561
+ semanticConsumption: { status: consumed ? "consumed" : "not-consumed", hypothesisIds: hypotheses },
562
+ modelStatedRationale: round.modelStatedRationale,
563
+ effect,
564
+ hypothesisIds: hypotheses,
565
+ usage: { ...round.usage, corrections: 0 },
566
+ } };
567
+ });
568
+ for (const failed of result.failedToolCalls) {
569
+ const descriptor = descriptors.find((item) => item.name === failed.tool)!;
570
+ calls.push({ attempt: failed.attempt, call: {
571
+ callId: failed.callId,
572
+ requestId: failed.requestId,
573
+ tool: failed.tool,
574
+ request: { descriptor, input: structuredClone(failed.input), selectionRationale: failed.selectionRationale },
575
+ target: { fen: bundle.context.fenAfter, historyThroughTargetHash: ablationHistoryThroughTargetHash(bundle.context) },
576
+ permittedLimits: { maxToolCalls: limits.maxToolCalls, maxProbeCalls: limits.maxProbeCalls, engine: null },
577
+ approval: { approved: true, budgetReason: "within-frozen-adaptive-budget", remainingToolCalls: Math.max(0, limits.maxToolCalls - failed.attempt), remainingProbeCalls: Math.max(0, limits.maxProbeCalls - result.rounds.filter((round) => round.attempt <= failed.attempt && round.tool !== "inspect_position" && round.tool !== "analyze_move").length) },
578
+ status: { started: true, finished: "failed" },
579
+ returned: null,
580
+ admission: { status: "not-produced", reason: failed.failureCode },
581
+ admittedEvidence: [],
582
+ presentation: { presentedToModel: false, reason: failed.failureCode },
583
+ semanticConsumption: { status: "not-reached", hypothesisIds: [] },
584
+ modelStatedRationale: failed.modelStatedRationale,
585
+ effect: "failure",
586
+ hypothesisIds: [],
587
+ usage: { ...failed.usage, corrections: 0 },
588
+ } });
589
+ }
590
+ const orderedCalls = calls.sort((left, right) => left.attempt - right.attempt).map((item) => item.call);
591
+ const modelDiagnostics = diagnostics.filter((item) => item.stage !== "B-evidence");
592
+ const acquiredEvidenceIds = bundle.lineage.kind === "enriched" ? bundle.lineage.acquiredEvidenceIds : [];
593
+ return createAblationProbeTelemetry(bundle, {
594
+ beforeProbeEvidenceIds: modelView.visibleEvidenceIds.filter((id) => !acquiredEvidenceIds.includes(id)),
595
+ postSaliencePresentedFactIds: [...result.saliencePresentedFactIds].filter((id) => modelView.visibleEvidenceIds.includes(id)),
596
+ availableTools: [...descriptors],
597
+ calledProbes: orderedCalls,
598
+ missedAnchorAttributions: [],
599
+ stopping: { rationale: result.stopping.rationale, unusedRelevantTools: result.stopping.unusedRelevantTools },
600
+ usage: {
601
+ modelCalls: result.modelCalls + modelDiagnostics.filter((item) => item.outcome !== "skipped").length,
602
+ toolCalls: orderedCalls.length,
603
+ // `result.usage` is the loop's complete evidence-decision accounting.
604
+ // Round usage is only the tool execution view and must not replace it.
605
+ inputTokens: sum([result.usage.inputTokens, ...modelDiagnostics.map((item) => item.inputTokens)]),
606
+ outputTokens: sum([result.usage.outputTokens, ...modelDiagnostics.map((item) => item.outputTokens)]),
607
+ latencyMs: sum([result.usage.latencyMs, ...modelDiagnostics.map((item) => item.latencyMs)]),
608
+ cache: { hits: result.usage.cache.hits, misses: result.usage.cache.misses },
609
+ cost: sum([result.usage.cost, ...modelDiagnostics.map((item) => item.cost)]),
610
+ corrections: result.usage.corrections + modelDiagnostics.reduce((total, item) => total + item.correctionCount, 0),
611
+ },
612
+ });
613
+ }
614
+
615
+ /**
616
+ * C-plus consumes exactly B's model-owned evidence episode, then hands its
617
+ * admitted closure to the frozen C chain. Keeping this adapter here prevents
618
+ * a second, subtly different evidence-to-bundle conversion in Gate 2.
619
+ */
620
+ export function prepareAblationAdaptiveEvidenceForStagedReference(
621
+ original: Readonly<AblationEvidenceClosureBundle>,
622
+ loop: AdaptiveEvidenceLoopResult,
623
+ evidenceTools: readonly ReasoningToolDescriptor[],
624
+ limits: Pick<AblationAdaptiveRuntimeOptions, "maxToolCalls" | "maxProbeCalls" | "maxModelSteps">,
625
+ ): Readonly<{ bundle: Readonly<AblationEvidenceClosureBundle>; telemetry: Readonly<AblationProbeTelemetry> }> {
626
+ validateAblationEvidenceClosureBundle(original);
627
+ if (original.lineage.kind !== "original") throw new AdaptiveAblationPreconditionError();
628
+ const descriptors = validateLoopResult(original, loop, limits, evidenceTools);
629
+ const bundle = enrichedBundle(original, loop);
630
+ return Object.freeze({ bundle, telemetry: probeTelemetry(bundle, descriptors, loop, undefined, [], limits) });
631
+ }
632
+
633
+ function providerDiagnostic(stage: AblationAdaptiveCallDiagnostic["stage"], nonce: string, input: unknown): AblationAdaptiveCallDiagnostic {
634
+ return { stage, nonce, inputBytes: bytes(input), outputBytes: null, latencyMs: null, inputTokens: null, outputTokens: null, cacheTokens: null, cost: null, correctionCount: 0, outcome: "failed", failureCode: null };
635
+ }
636
+
637
+ function abortedAfterValidLoop(
638
+ scope: AdaptiveAbortScope,
639
+ arm: AblationAdaptiveArm,
640
+ bundle: Readonly<AblationEvidenceClosureBundle>,
641
+ descriptors: readonly ReasoningToolDescriptor[],
642
+ loop: AdaptiveEvidenceLoopResult,
643
+ diagnostics: readonly AblationAdaptiveCallDiagnostic[],
644
+ options: AblationAdaptiveRuntimeOptions,
645
+ triggers: readonly AblationBPrimeTrigger[] = [],
646
+ ): AblationAdaptiveResult {
647
+ const code = scope.code();
648
+ if (!code) throw new Error("Adaptive abort result requires a stopped episode");
649
+ return {
650
+ arm,
651
+ ok: false,
652
+ bundle,
653
+ telemetry: probeTelemetry(bundle, descriptors, loop, undefined, diagnostics, options),
654
+ triggers,
655
+ diagnostics: [...diagnostics],
656
+ failure: { code },
657
+ };
658
+ }
659
+
660
+ function finishDiagnostic(diagnostic: AblationAdaptiveCallDiagnostic, result: Awaited<ReturnType<typeof generateText>>): void {
661
+ diagnostic.outputBytes = bytes(result.output ?? result.text);
662
+ const input = result.usage.inputTokens as unknown;
663
+ const output = result.usage.outputTokens as unknown;
664
+ diagnostic.inputTokens = typeof input === "number" ? input : input && typeof input === "object" && typeof (input as { total?: unknown }).total === "number" ? (input as { total: number }).total : null;
665
+ diagnostic.outputTokens = typeof output === "number" ? output : output && typeof output === "object" && typeof (output as { total?: unknown }).total === "number" ? (output as { total: number }).total : null;
666
+ diagnostic.latencyMs = result.steps.at(-1)?.performance.responseTimeMs ?? null;
667
+ diagnostic.outcome = "succeeded";
668
+ }
669
+
670
+ async function directCall(
671
+ options: AblationAdaptiveRuntimeOptions,
672
+ abort: AdaptiveAbortScope,
673
+ arm: AblationAdaptiveArm,
674
+ bundle: Readonly<AblationEvidenceClosureBundle>,
675
+ nonce: string,
676
+ stage: "B-direct" | "B-transport-correction",
677
+ correction: DirectSynthesisIssue[] | null,
678
+ ): Promise<{ proposal: unknown; diagnostic: AblationAdaptiveCallDiagnostic }> {
679
+ throwIfAdaptiveStopped(abort);
680
+ const modelView = projectAblationEvidenceForModel(bundle);
681
+ const modelFacingBundle = modelView.bundle;
682
+ const prompt = correction
683
+ ? JSON.stringify({ sourceBundleHash: modelView.sourceBundleHash, modelFacingEvidenceViewVersion: modelView.schemaVersion, modelFacingEvidenceViewHash: modelView.viewHash, bundle: modelFacingBundle, correction: { issues: correction, instruction: "Return a complete replacement. Fix only the listed transport/reference/schema paths; do not add evidence, observations, concepts, claims, relations, or demonstrations. For raw_chess_reference in a qualification text segment, retain the qualification's bounded meaning but replace only that raw text with a narrower generic text phrase that omits the unsupported concrete reference. Preserve all item IDs, evidence IDs, scope, and every other semantic association." } })
684
+ : JSON.stringify({ schemaVersion: ABLATION_ADAPTIVE_RUNTIME_VERSION, arm, sourceBundleHash: modelView.sourceBundleHash, modelFacingEvidenceViewVersion: modelView.schemaVersion, modelFacingEvidenceViewHash: modelView.viewHash, bundle: modelFacingBundle });
685
+ const system = firstPrefix(nonce, ABLATION_B_DIRECT_SYSTEM);
686
+ const diagnostic = providerDiagnostic(stage, nonce, { system, prompt, schema: DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA });
687
+ const started = performance.now();
688
+ try {
689
+ const result = await abortable(abort, generateText({
690
+ model: options.model,
691
+ system,
692
+ prompt,
693
+ output: Output.object({ schema: jsonSchema<Record<string, unknown>>(DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA as never), name: "knights_eye_ablation_adaptive_direct", description: "Pre-checkpoint direct synthesis proposal." }),
694
+ abortSignal: abort.signal,
695
+ maxOutputTokens: options.maxOutputTokens,
696
+ maxRetries: 0,
697
+ reasoning: options.reasoning || "provider-default",
698
+ ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
699
+ }));
700
+ diagnostic.latencyMs = Math.round(performance.now() - started);
701
+ finishDiagnostic(diagnostic, result);
702
+ if (correction) diagnostic.correctionCount = 1;
703
+ return { proposal: result.output, diagnostic };
704
+ } catch (error) {
705
+ diagnostic.latencyMs = Math.round(performance.now() - started);
706
+ const code = adaptiveAbortCode(abort, error);
707
+ diagnostic.outcome = code ? "aborted" : "failed";
708
+ diagnostic.failureCode = code || "provider-or-structured-output";
709
+ throw Object.assign(error instanceof Error ? error : new Error("Adaptive direct provider failure"), { adaptiveDiagnostic: diagnostic });
710
+ }
711
+ }
712
+
713
+
714
+ function ensureUniqueNonce(used: Set<string>, options: AblationAdaptiveRuntimeOptions, stage: Parameters<AblationAdaptiveRuntimeOptions["benchmarkNonceForCall"]>[0]): string {
715
+ const nonce = nonEmptyNonce(options.benchmarkNonceForCall(stage, used.size));
716
+ if (used.has(nonce)) throw new Error("Every adaptive provider call requires a distinct benchmark nonce");
717
+ used.add(nonce);
718
+ return nonce;
719
+ }
720
+
721
+ function criticPayload(bundle: Readonly<AblationEvidenceClosureBundle>, proposal: unknown, triggers: readonly AblationBPrimeTrigger[], initialIssues: readonly DirectSynthesisIssue[]): Record<string, unknown> {
722
+ const modelView = projectAblationEvidenceForModel(bundle);
723
+ return {
724
+ schemaVersion: ABLATION_ADAPTIVE_RUNTIME_VERSION,
725
+ sourceBundleHash: modelView.sourceBundleHash,
726
+ modelFacingEvidenceViewVersion: modelView.schemaVersion,
727
+ modelFacingEvidenceViewHash: modelView.viewHash,
728
+ bundle: modelView.bundle,
729
+ proposal,
730
+ triggers,
731
+ validationIssues: initialIssues,
732
+ };
733
+ }
734
+
735
+ function assertCriticDecision(value: unknown): asserts value is CriticDecision {
736
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("B-prime critic returned no object");
737
+ const record = value as Record<string, unknown>;
738
+ if (record.schemaVersion !== ABLATION_ADAPTIVE_RUNTIME_VERSION || (record.decision !== "retain" && record.decision !== "replace") || typeof record.rationale !== "string" || !record.rationale.trim()) throw new Error("B-prime critic returned an invalid decision");
739
+ if (record.decision === "replace" && !("replacement" in record)) throw new Error("B-prime critic replacement is missing");
740
+ if (record.decision === "retain" && "replacement" in record) throw new Error("B-prime critic retain decision cannot rewrite the proposal");
741
+ }
742
+
743
+ export async function runAblationAdaptiveDirect(
744
+ options: AblationAdaptiveRuntimeOptions,
745
+ arm: AblationAdaptiveArm,
746
+ original: Readonly<AblationEvidenceClosureBundle>,
747
+ signal?: AbortSignal,
748
+ ): Promise<AblationAdaptiveResult> {
749
+ if (arm !== "B" && arm !== "B-prime") throw new Error("Adaptive ablation arm must be B or B-prime");
750
+ if (!Number.isInteger(options.maxToolCalls) || options.maxToolCalls < 0 || !Number.isInteger(options.maxProbeCalls) || options.maxProbeCalls < 0 || !Number.isInteger(options.maxModelSteps) || options.maxModelSteps < 1 || !Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens < 1 || !Number.isInteger(options.maxWallTimeMs) || options.maxWallTimeMs < 1) throw new Error("Adaptive ablation runtime requires bounded positive budgets and a positive wall-time budget");
751
+ validateAblationEvidenceClosureBundle(original);
752
+ if (original.lineage.kind !== "original") throw new AdaptiveAblationPreconditionError();
753
+ const abort = createAdaptiveAbortScope(signal, options.maxWallTimeMs);
754
+ try {
755
+ const diagnostics: AblationAdaptiveCallDiagnostic[] = [];
756
+ const usedNonces = new Set<string>();
757
+ const evidenceNonce = ensureUniqueNonce(usedNonces, options, "evidence");
758
+ const originalModelView = projectAblationEvidenceForModel(original);
759
+ const evidenceDiagnostic = providerDiagnostic("B-evidence", evidenceNonce, {
760
+ system: firstPrefix(evidenceNonce, ABLATION_B_SYSTEM),
761
+ sourceBundleHash: originalModelView.sourceBundleHash,
762
+ modelFacingEvidenceViewVersion: originalModelView.schemaVersion,
763
+ modelFacingEvidenceViewHash: originalModelView.viewHash,
764
+ bundle: originalModelView.bundle,
765
+ });
766
+ if (abort.code()) {
767
+ evidenceDiagnostic.outcome = "aborted";
768
+ evidenceDiagnostic.failureCode = abort.code();
769
+ diagnostics.push(evidenceDiagnostic);
770
+ const telemetry = createAblationProbeTelemetry(original, {
771
+ beforeProbeEvidenceIds: [...originalModelView.visibleEvidenceIds],
772
+ postSaliencePresentedFactIds: [...originalModelView.visibleEvidenceIds],
773
+ availableTools: [],
774
+ calledProbes: [],
775
+ missedAnchorAttributions: [],
776
+ stopping: { rationale: "Adaptive ablation stopped before evidence collection began.", unusedRelevantTools: [] },
777
+ usage: { modelCalls: 0, toolCalls: 0, inputTokens: null, outputTokens: null, latencyMs: null, cache: { hits: 0, misses: 0 }, cost: null, corrections: 0 },
778
+ });
779
+ return { arm, ok: false, bundle: original, telemetry, triggers: [], diagnostics, failure: { code: abort.code()! } };
780
+ }
781
+ let loop: AdaptiveEvidenceLoopResult;
782
+ let descriptors: ReasoningToolDescriptor[];
783
+ let bundle: Readonly<AblationEvidenceClosureBundle> = original;
784
+ try {
785
+ loop = await abortable(abort, options.evidenceLoop.run({
786
+ arm,
787
+ system: firstPrefix(evidenceNonce, ABLATION_B_SYSTEM),
788
+ firstProviderVisiblePrefix: `[Benchmark cache-isolation nonce: ${evidenceNonce}. Ignore this opaque identifier; it has no chess, workflow, or output meaning.]`,
789
+ nextProviderVisiblePrefix: () => {
790
+ const nonce = ensureUniqueNonce(usedNonces, options, "evidence");
791
+ return `[Benchmark cache-isolation nonce: ${nonce}. Ignore this opaque identifier; it has no chess, workflow, or output meaning.]`;
792
+ },
793
+ bundle: original,
794
+ tools: options.evidenceTools,
795
+ maxToolCalls: options.maxToolCalls,
796
+ maxProbeCalls: options.maxProbeCalls,
797
+ maxModelSteps: options.maxModelSteps,
798
+ signal: abort.signal,
799
+ }));
800
+ descriptors = validateLoopResult(original, loop, options, options.evidenceTools);
801
+ bundle = enrichedBundle(original, loop);
802
+ evidenceDiagnostic.outcome = "succeeded";
803
+ evidenceDiagnostic.latencyMs = loop.usage.latencyMs;
804
+ evidenceDiagnostic.inputTokens = loop.usage.inputTokens;
805
+ evidenceDiagnostic.outputTokens = loop.usage.outputTokens;
806
+ evidenceDiagnostic.cost = loop.usage.cost;
807
+ } catch (error) {
808
+ const code = adaptiveAbortCode(abort, error);
809
+ evidenceDiagnostic.failureCode = code || "adaptive-evidence-loop-rejected";
810
+ evidenceDiagnostic.outcome = code ? "aborted" : "failed";
811
+ diagnostics.push(evidenceDiagnostic);
812
+ const emptyLoop: AdaptiveEvidenceLoopResult = { rounds: [], failedToolCalls: [], availableTools: [], saliencePresentedFactIds: [], coverage: original.coverage, stopping: { reason: "model-finished", rationale: "Adaptive evidence loop failed before a valid closure was available.", unusedRelevantTools: [] }, modelCalls: 0, usage: { inputTokens: null, outputTokens: null, latencyMs: null, cache: { hits: 0, misses: 0 }, cost: null, corrections: 0 } };
813
+ const telemetry = createAblationProbeTelemetry(original, { beforeProbeEvidenceIds: [...originalModelView.visibleEvidenceIds], postSaliencePresentedFactIds: [...originalModelView.visibleEvidenceIds], availableTools: [], calledProbes: [], missedAnchorAttributions: [], stopping: { rationale: emptyLoop.stopping.rationale, unusedRelevantTools: emptyLoop.stopping.unusedRelevantTools }, usage: { modelCalls: 0, toolCalls: 0, inputTokens: null, outputTokens: null, latencyMs: null, cache: { hits: 0, misses: 0 }, cost: null, corrections: 0 } });
814
+ return { arm, ok: false, bundle: original, telemetry, triggers: [], diagnostics, failure: { code: code || "adaptive-evidence-loop-rejected" } };
815
+ }
816
+ diagnostics.push(evidenceDiagnostic);
817
+ if (abort.code()) return abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options);
818
+
819
+ let proposal: unknown;
820
+ let initialIssues: DirectSynthesisIssue[] = [];
821
+ let artifact: DirectSynthesisArtifact | undefined;
822
+ let transportCorrectionAttempted = false;
823
+ try {
824
+ const direct = await directCall(options, abort, arm, bundle, ensureUniqueNonce(usedNonces, options, "direct"), "B-direct", null);
825
+ diagnostics.push(direct.diagnostic);
826
+ proposal = direct.proposal;
827
+ const admission = materializeDirectSynthesis({ arm, bundle, proposal, provenance: { provider: `knights-eye/ablation-${arm}`, version: ABLATION_ADAPTIVE_RUNTIME_VERSION, origin: "model" } });
828
+ if (admission.ok) artifact = admission.artifact;
829
+ else initialIssues = admission.issues;
830
+ } catch (error) {
831
+ const diagnostic = (error as { adaptiveDiagnostic?: AblationAdaptiveCallDiagnostic }).adaptiveDiagnostic;
832
+ if (diagnostic) diagnostics.push(diagnostic);
833
+ const telemetry = probeTelemetry(bundle, descriptors, loop, undefined, diagnostics, options);
834
+ const code = adaptiveAbortCode(abort, error);
835
+ return code
836
+ ? abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options)
837
+ : { arm, ok: false, bundle, telemetry, triggers: [], diagnostics, failure: { code: "provider-or-structured-output" } };
838
+ }
839
+ if (abort.code()) return abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options);
840
+
841
+ // A single explicit transport correction is permitted only for machine-level
842
+ // proposal admission defects. It is neither a semantic review nor recursion.
843
+ if (!artifact && isTransportCorrectableDirectSynthesisIssues(initialIssues)) {
844
+ try {
845
+ const corrected = await directCall(options, abort, arm, bundle, ensureUniqueNonce(usedNonces, options, "transport-correction"), "B-transport-correction", initialIssues);
846
+ diagnostics.push(corrected.diagnostic);
847
+ transportCorrectionAttempted = true;
848
+ assertTransportOnlyDirectCorrection(proposal, corrected.proposal, initialIssues);
849
+ proposal = corrected.proposal;
850
+ const admission = materializeDirectSynthesis({ arm, bundle, proposal, provenance: { provider: `knights-eye/ablation-${arm}`, version: ABLATION_ADAPTIVE_RUNTIME_VERSION, origin: "model" } });
851
+ if (admission.ok) artifact = admission.artifact;
852
+ else {
853
+ initialIssues = admission.issues;
854
+ diagnostics[diagnostics.length - 1] = { ...diagnostics.at(-1)!, outcome: "failed", failureCode: "transport-correction-rejected" };
855
+ }
856
+ } catch (error) {
857
+ const diagnostic = (error as { adaptiveDiagnostic?: AblationAdaptiveCallDiagnostic }).adaptiveDiagnostic;
858
+ if (diagnostic) diagnostics.push(diagnostic);
859
+ else if (diagnostics.at(-1)?.stage === "B-transport-correction") {
860
+ diagnostics[diagnostics.length - 1] = { ...diagnostics.at(-1)!, outcome: "failed", failureCode: error instanceof AblationTransportCorrectionError ? error.code : "transport-correction-rejected" };
861
+ }
862
+ if (adaptiveAbortCode(abort, error)) return abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options);
863
+ }
864
+ }
865
+ if (abort.code()) return abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options);
866
+
867
+ let triggers: AblationBPrimeTrigger[] = [];
868
+ if (arm === "B-prime") {
869
+ triggers = exactTriggers((options.triggerPolicy || deriveAblationBPrimeTriggers)({ bundle, proposal, artifact: artifact || null, initialIssues }));
870
+ if (triggers.length && (artifact || initialIssues.length)) {
871
+ const nonce = ensureUniqueNonce(usedNonces, options, "critic");
872
+ const payload = criticPayload(bundle, proposal, triggers, initialIssues);
873
+ const diagnostic = providerDiagnostic("B-prime-critic", nonce, { system: firstPrefix(nonce, ABLATION_B_PRIME_CRITIC_SYSTEM), payload, schema: CRITIC_SCHEMA });
874
+ const started = performance.now();
875
+ try {
876
+ throwIfAdaptiveStopped(abort);
877
+ const result = await abortable(abort, generateText({
878
+ model: options.criticModel || options.model,
879
+ system: firstPrefix(nonce, ABLATION_B_PRIME_CRITIC_SYSTEM),
880
+ prompt: JSON.stringify(payload),
881
+ output: Output.object({ schema: jsonSchema<Record<string, unknown>>(CRITIC_SCHEMA as never), name: "knights_eye_ablation_b_prime_critic", description: "One bounded tool-free semantic reconsideration." }),
882
+ abortSignal: abort.signal,
883
+ maxOutputTokens: options.maxOutputTokens,
884
+ maxRetries: 0,
885
+ reasoning: options.criticReasoning || options.reasoning || "provider-default",
886
+ ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
887
+ }));
888
+ diagnostic.latencyMs = Math.round(performance.now() - started);
889
+ finishDiagnostic(diagnostic, result);
890
+ assertCriticDecision(result.output);
891
+ if (result.output.decision === "replace") {
892
+ const reconsidered = materializeDirectSynthesis({ arm, bundle, proposal: result.output.replacement, provenance: { provider: "knights-eye/ablation-b-prime-critic", version: ABLATION_ADAPTIVE_RUNTIME_VERSION, origin: "model" } });
893
+ if (!reconsidered.ok) {
894
+ diagnostic.outcome = "failed";
895
+ diagnostic.failureCode = "critic-rejected";
896
+ artifact = undefined;
897
+ initialIssues = reconsidered.issues;
898
+ } else artifact = reconsidered.artifact;
899
+ } else if (!artifact) {
900
+ diagnostic.outcome = "failed";
901
+ diagnostic.failureCode = "critic-rejected";
902
+ }
903
+ } catch (error) {
904
+ diagnostic.latencyMs = Math.round(performance.now() - started);
905
+ const code = adaptiveAbortCode(abort, error);
906
+ diagnostic.outcome = code ? "aborted" : "failed";
907
+ diagnostic.failureCode = code || "critic-rejected";
908
+ artifact = undefined;
909
+ }
910
+ diagnostics.push(diagnostic);
911
+ if (abort.code()) return abortedAfterValidLoop(abort, arm, bundle, descriptors, loop, diagnostics, options, triggers);
912
+ } else {
913
+ diagnostics.push({ ...providerDiagnostic("B-prime-critic", "skipped", {}), outcome: "skipped" });
914
+ }
915
+ }
916
+ const telemetry = probeTelemetry(bundle, descriptors, loop, artifact, diagnostics, options);
917
+ if (!artifact) {
918
+ return { arm, ok: false, bundle, telemetry, triggers, diagnostics, failure: { code: transportCorrectionAttempted ? "transport-correction-rejected" : "direct-synthesis-rejected", issues: initialIssues } };
919
+ }
920
+ return { arm, ok: true, bundle, artifact, telemetry, triggers, diagnostics };
921
+ } finally {
922
+ abort.dispose();
923
+ }
924
+ }