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

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 +28 -0
  5. package/README.md +139 -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 +85 -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,670 @@
1
+ import { Output, generateText, jsonSchema, type LanguageModel, type LanguageModelCallOptions } from "ai";
2
+ import { effectiveLicensesForRecord, licensesBoundedRouteRelevance, licensesSemanticChessGrounding } from "./evidence-licenses";
3
+ import type { LeanDirectEvidenceSelector } from "./evidence-selector-contract";
4
+ import {
5
+ projectAblationEvidenceForModel,
6
+ compactLeanEvidencePacket,
7
+ } from "./ablation-model-facing-evidence";
8
+ import {
9
+ createAblationEvidenceClosureBundle,
10
+ validateAblationEvidenceClosureBundle,
11
+ type AblationEvidenceClosureBundle,
12
+ } from "./ablation-evidence-bundle";
13
+ import { bytes, emitObservableTrace, numericUsage, type ObservableLlmTraceSink } from "./ai-sdk-runtime-support";
14
+ import { collectReasoningMoveWitnesses } from "./poc-move-witnesses";
15
+ import { stableHash } from "./stable";
16
+
17
+ /**
18
+ * A deliberately small development diagnostic. It is not a product stage and
19
+ * does not create hypotheses, a graph, a checkpoint, or an answer repair
20
+ * loop. Its purpose is to test whether the model can synthesize a useful,
21
+ * evidence-cited answer when the evidence transport is not the bottleneck.
22
+ */
23
+ export const ABLATION_LEAN_DIRECT_RUNTIME_VERSION = "architecture-ablation-lean-direct-runtime/8" as const;
24
+ export const ABLATION_LEAN_DIRECT_PROPOSAL_VERSION = "architecture-ablation-lean-direct-proposal/3" as const;
25
+ export const ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION = "architecture-ablation-lean-direct-evidence-view/2" as const;
26
+
27
+ /**
28
+ * The direct arm normally sees the production salience projection. It also
29
+ * admits at most one already-captured bounded continuation/comparison record,
30
+ * plus observations that cite it. This is deterministic transport selection,
31
+ * not an engine call or a chess-purpose heuristic: it prevents a previously
32
+ * available discriminating witness from being silently withheld before the
33
+ * sole model call.
34
+ */
35
+ export type LeanDirectEvidenceView = Readonly<{
36
+ schemaVersion: typeof ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION;
37
+ sourceBundleHash: string;
38
+ bundle: Readonly<AblationEvidenceClosureBundle>;
39
+ salienceEvidenceIds: readonly string[];
40
+ requiredEvidenceIds: readonly string[];
41
+ supplementalBoundedEvidenceIds: readonly string[];
42
+ /** Present only for the compact fixture-declared packet, never model-authored semantics. */
43
+ selectedSelectorIds: readonly string[];
44
+ viewHash: string;
45
+ }>;
46
+
47
+ export const ablationLeanDirectSystem = `You are Knight's Eye in a private development diagnostic. Return exactly one object matching the supplied schema. This is one compact synthesis call: there are no tools, hypothesis ledger, critic, graph selection, checkpoint, discourse stage, rendering stage, repair call, or hidden answer key.
48
+
49
+ Answer the user's verbatim chess question naturally and concisely. Before drafting, silently compare the evidence-backed causal chains. Lead with the smallest chain that directly answers the question and explains the move's most decision-relevant change to an option, resource, constraint, threat, or comparison. When exact structured evidence gives the mechanism for an unavailable or legal response--for example a blocking occupant or ray, a pin, or a defender constraint--state that mechanism before any incidental attack, square, or material side effect. Then include only the supporting links needed to show why it works, its practical consequence, or a necessary qualification. Deliberately omit exact but incidental facts: do not enumerate records merely because they are available or use them to make the answer look complete. A factual observation is not automatically a strategic explanation: do not invent purpose, force, safety, or a continuation beyond the cited evidence. Bounded engine evidence establishes only its recorded line, comparison, score, or response coverage. Exact legal facts establish only what they state. If the available evidence does not settle a point, say so in limitations instead of guessing.
50
+
51
+ Use only the supplied evidence IDs. Cite evidence for every claim. Each claim is plain concise prose; do not encode moves, pieces, squares, or other references as structured fields. The answer remains an unvalidated diagnostic proposal, never a supported Knight's Eye result.`;
52
+
53
+ const CLAIM_KINDS = new Set(["fact", "interpretation", "consequence", "defense", "uncertainty", "comparison"]);
54
+
55
+ export const LEAN_DIRECT_PROPOSAL_JSON_SCHEMA = {
56
+ type: "object", additionalProperties: false,
57
+ required: ["schemaVersion", "claims", "limitations", "coverage"],
58
+ properties: {
59
+ schemaVersion: { const: ABLATION_LEAN_DIRECT_PROPOSAL_VERSION },
60
+ claims: {
61
+ type: "array", minItems: 1, maxItems: 4,
62
+ items: {
63
+ type: "object", additionalProperties: false,
64
+ required: ["text", "kind", "evidenceIds"],
65
+ properties: {
66
+ text: { type: "string", minLength: 1, maxLength: 700 },
67
+ kind: { enum: ["fact", "interpretation", "consequence", "defense", "uncertainty", "comparison"] },
68
+ evidenceIds: { type: "array", minItems: 1, maxItems: 8, items: { type: "string", minLength: 1, maxLength: 200 } },
69
+ },
70
+ },
71
+ },
72
+ limitations: { type: "array", maxItems: 4, items: { type: "string", minLength: 1, maxLength: 400 } },
73
+ coverage: {
74
+ type: "object", additionalProperties: false,
75
+ required: ["status", "unresolved"],
76
+ properties: {
77
+ status: { enum: ["complete", "partial", "unavailable"] },
78
+ unresolved: { type: "array", maxItems: 4, items: { type: "string", minLength: 1, maxLength: 400 } },
79
+ },
80
+ },
81
+ },
82
+ } as const;
83
+
84
+ export type LeanDirectClaim = {
85
+ text: string;
86
+ kind: "fact" | "interpretation" | "consequence" | "defense" | "uncertainty" | "comparison";
87
+ evidenceIds: string[];
88
+ };
89
+
90
+ export type LeanDirectArtifact = {
91
+ schemaVersion: typeof ABLATION_LEAN_DIRECT_PROPOSAL_VERSION;
92
+ sourceBundleHash: string;
93
+ modelFacingEvidenceViewHash: string;
94
+ claims: LeanDirectClaim[];
95
+ limitations: string[];
96
+ coverage: { status: "complete" | "partial" | "unavailable"; unresolved: string[] };
97
+ admission: "diagnostic-proposed";
98
+ productSupport: "not-a-validated-graph-checkpoint-or-product-result";
99
+ };
100
+
101
+ export type LeanDirectFailure = {
102
+ kind: "provider-or-structured-output" | "lean-contract-rejected" | "aborted" | "wall-time";
103
+ code: string;
104
+ issues?: Array<{ path: string; code: string }>;
105
+ };
106
+
107
+ export type LeanDirectCallDiagnostic = {
108
+ stage: "lean-direct" | "lean-acquisition";
109
+ runtime: typeof ABLATION_LEAN_DIRECT_RUNTIME_VERSION;
110
+ model: string;
111
+ route: string;
112
+ effort: string;
113
+ temperature: number | null;
114
+ bundleHash: string;
115
+ inputBytes: number;
116
+ schemaBytes: number;
117
+ outputBytes: number;
118
+ finishReason: string | null;
119
+ latencyMs: number | null;
120
+ inputTokens: number | null;
121
+ outputTokens: number | null;
122
+ cost: number | null;
123
+ cacheTokens: number | null;
124
+ outcome: "succeeded" | "failed" | "aborted";
125
+ failureCode: string | null;
126
+ };
127
+
128
+ export type LeanDirectRuntimeOptions = {
129
+ model: LanguageModel;
130
+ modelName?: string;
131
+ providerRoute?: string;
132
+ reasoning?: NonNullable<LanguageModelCallOptions["reasoning"]>;
133
+ temperature?: number;
134
+ maxOutputTokens: number;
135
+ maxWallTimeMs: number;
136
+ benchmarkNonce?: string | null;
137
+ /** Deterministically acquired, fixture-scoped evidence that must reach this sole synthesis call. */
138
+ requiredEvidenceIds?: readonly string[];
139
+ /**
140
+ * Fixture-declared fact/observation selectors for a deliberately compact
141
+ * evidence packet. Absent selectors retain the generic salience projection
142
+ * used by non-fixture diagnostics.
143
+ */
144
+ evidenceSelectors?: readonly LeanDirectEvidenceSelector[];
145
+ /** Generic acquisition: only records actually presented or retrieved in this episode. */
146
+ modelEvidenceIds?: readonly string[];
147
+ episodeCoverage?: AblationEvidenceClosureBundle["coverage"];
148
+ consumeProviderMetadata?: () => readonly { cost?: number | null; cacheTokens?: number | null; model?: string | null; route?: string | null; effort?: string | null }[];
149
+ observableTraceSink?: ObservableLlmTraceSink;
150
+ };
151
+
152
+ export type LeanDirectResult =
153
+ | { ok: true; artifact: LeanDirectArtifact; diagnostics: LeanDirectCallDiagnostic[]; modelView: LeanDirectEvidenceView }
154
+ | { ok: false; diagnostics: LeanDirectCallDiagnostic[]; failure: LeanDirectFailure; modelView: LeanDirectEvidenceView };
155
+
156
+ function isRecord(value: unknown): value is Record<string, unknown> {
157
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
158
+ }
159
+
160
+ function nonEmpty(value: unknown, path: string, max = 600): string {
161
+ if (typeof value !== "string" || !value.trim() || value.length > max) throw new LeanAdmissionError(path, "invalid-string");
162
+ return value;
163
+ }
164
+
165
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[], path: string): void {
166
+ if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value))) throw new LeanAdmissionError(path, "invalid-shape");
167
+ }
168
+
169
+ class LeanAdmissionError extends Error {
170
+ constructor(readonly path: string, readonly code: string) { super(`${path}:${code}`); }
171
+ }
172
+
173
+ function modelName(options: LeanDirectRuntimeOptions): string {
174
+ if (options.modelName) return options.modelName;
175
+ return typeof options.model !== "string" && "modelId" in options.model && typeof options.model.modelId === "string" ? options.model.modelId : "unspecified";
176
+ }
177
+
178
+ function effectiveSystem(nonce: string | null | undefined): string {
179
+ if (!nonce) return ablationLeanDirectSystem;
180
+ if (typeof nonce !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(nonce)) throw new Error("Lean direct requires a bounded opaque benchmark nonce");
181
+ return `[Benchmark cache-isolation nonce: ${nonce}. Ignore this opaque identifier; it has no chess, workflow, or output meaning.]\n\n${ablationLeanDirectSystem}`;
182
+ }
183
+
184
+ function now(): number { return typeof performance !== "undefined" ? performance.now() : Date.now(); }
185
+
186
+ function abortContext(signal: AbortSignal | undefined, maxWallTimeMs: number) {
187
+ if (!Number.isFinite(maxWallTimeMs) || maxWallTimeMs < 1) throw new Error("Lean direct requires a positive wall-time budget");
188
+ const controller = new AbortController();
189
+ let timedOut = false;
190
+ const cancel = () => controller.abort(signal?.reason || new Error("Lean direct cancelled"));
191
+ if (signal?.aborted) cancel(); else signal?.addEventListener("abort", cancel, { once: true });
192
+ const timeout = setTimeout(() => { timedOut = true; controller.abort(new Error("Lean direct timed out")); }, maxWallTimeMs);
193
+ return { signal: controller.signal, timedOut: () => timedOut, dispose: () => { clearTimeout(timeout); signal?.removeEventListener("abort", cancel); } };
194
+ }
195
+
196
+ function classify(error: unknown, timedOut: boolean, externallyAborted: boolean): LeanDirectFailure {
197
+ if (timedOut) return { kind: "wall-time", code: "wall-time-budget" };
198
+ if (externallyAborted) return { kind: "aborted", code: "aborted" };
199
+ const message = error instanceof Error ? `${error.name} ${error.message}` : String(error || "");
200
+ if (/schema/i.test(message)) return { kind: "provider-or-structured-output", code: "structured-output-schema" };
201
+ if (/parse|json/i.test(message)) return { kind: "provider-or-structured-output", code: "structured-output-parse" };
202
+ if (/empty|content|output/i.test(message)) return { kind: "provider-or-structured-output", code: "structured-output-missing" };
203
+ return { kind: "provider-or-structured-output", code: "provider-runtime" };
204
+ }
205
+
206
+ function factKind(observation: Readonly<AblationEvidenceClosureBundle>["observations"][number]): string | null {
207
+ const attributes = observation.attributes;
208
+ const kind = attributes && typeof attributes === "object" && !Array.isArray(attributes) ? attributes.factKind : null;
209
+ return typeof kind === "string" ? kind : null;
210
+ }
211
+
212
+ function selectorMatches(selector: LeanDirectEvidenceSelector, observation: Readonly<AblationEvidenceClosureBundle>["observations"][number]): boolean {
213
+ const matchesFact = Boolean(selector.factKinds?.includes(factKind(observation) || ""));
214
+ const matchesType = Boolean(selector.observationTypes?.includes(observation.observationType));
215
+ return matchesFact || matchesType;
216
+ }
217
+
218
+ const SQUARE = /^[a-h][1-8]$/;
219
+ const UCI = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
220
+ const COMPACT_AUDIT_LIST_LIMIT = 8;
221
+ const LEAN_CAUSAL_FOCUS_LIMIT = 3;
222
+
223
+ function hasStructuredField(value: unknown, matcher: RegExp): boolean {
224
+ if (Array.isArray(value)) return value.some((item) => hasStructuredField(item, matcher));
225
+ if (!value || typeof value !== "object") return false;
226
+ return Object.entries(value as Record<string, unknown>).some(([key, child]) => matcher.test(key) || hasStructuredField(child, matcher));
227
+ }
228
+
229
+ /**
230
+ * This is transport ranking, not a chess conclusion: response constraints
231
+ * and their exact mechanisms carry more explanatory information than broad
232
+ * geometry deltas. The order is independent of any fixture, question, or
233
+ * model-authored answer.
234
+ */
235
+ function causalFocusRank(
236
+ observation: Readonly<AblationEvidenceClosureBundle>["observations"][number],
237
+ evidenceById: ReadonlyMap<string, AblationEvidenceClosureBundle["evidence"][number]>,
238
+ ): number {
239
+ const kind = factKind(observation);
240
+ const evidence = observation.factEvidenceIds.map((id) => evidenceById.get(id)).filter((item): item is AblationEvidenceClosureBundle["evidence"][number] => Boolean(item));
241
+ const mechanismFields = /(?:block|occup|ray|pin|defend|constraint)/i;
242
+ const hasMechanism = evidence.some((item) => hasStructuredField(item.data, mechanismFields));
243
+ switch (kind) {
244
+ case "move.capture_response_availability_after": return hasMechanism ? 500 : 470;
245
+ case "piece.absolute_pin": return 460;
246
+ case "move.legal_recaptures_after": return 440;
247
+ case "move.blocks_check_line":
248
+ case "move.unblocks_line":
249
+ case "king.checkers_after":
250
+ case "king.legal_moves": return 400;
251
+ case "move.legal_transition": return 360;
252
+ case "piece.attack_delta":
253
+ case "square.attacker_delta":
254
+ case "square.attackers_delta_inventory": return 120;
255
+ default: return 240;
256
+ }
257
+ }
258
+
259
+ function presentationSalience(observation: Readonly<AblationEvidenceClosureBundle>["observations"][number]): number {
260
+ const value = (observation as { salience?: unknown }).salience;
261
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
262
+ }
263
+
264
+ function focusedLeanProjection(
265
+ source: Readonly<AblationEvidenceClosureBundle>,
266
+ alwaysIncludedEvidenceIds: readonly string[],
267
+ ): { bundle: Readonly<AblationEvidenceClosureBundle>; selectedEvidenceIds: string[]; selectedObservationIds: string[] } {
268
+ const evidenceById = new Map(source.evidence.map((item) => [item.id, item]));
269
+ const alwaysIncluded = new Set(alwaysIncludedEvidenceIds);
270
+ const requiredObservations = source.observations.filter((observation) => observation.factEvidenceIds.some((id) => alwaysIncluded.has(id)));
271
+ const selectedObservationIds = new Set(requiredObservations.map((observation) => observation.id));
272
+ const ranked = source.observations
273
+ .filter((observation) => !selectedObservationIds.has(observation.id))
274
+ .sort((left, right) => (
275
+ causalFocusRank(right, evidenceById) - causalFocusRank(left, evidenceById)
276
+ || presentationSalience(right) - presentationSalience(left)
277
+ || left.id.localeCompare(right.id)
278
+ ));
279
+ for (const observation of ranked.slice(0, LEAN_CAUSAL_FOCUS_LIMIT)) selectedObservationIds.add(observation.id);
280
+ const observations = source.observations.filter((observation) => selectedObservationIds.has(observation.id));
281
+ const selectedEvidenceIds = new Set(alwaysIncluded);
282
+ observations.forEach((observation) => observation.factEvidenceIds.forEach((id) => selectedEvidenceIds.add(id)));
283
+ if ([...selectedEvidenceIds].some((id) => !evidenceById.has(id))) throw new Error("Lean direct causal focus references unadmitted evidence");
284
+ const evidence = source.evidence.filter((item) => selectedEvidenceIds.has(item.id));
285
+ const sourceOmissions = source.coverage.omitted.slice(0, COMPACT_AUDIT_LIST_LIMIT);
286
+ const sourceUnavailable = source.coverage.unavailable.slice(0, COMPACT_AUDIT_LIST_LIMIT);
287
+ const omittedCount = source.observations.length - observations.length + source.evidence.length - evidence.length;
288
+ return {
289
+ bundle: createAblationEvidenceClosureBundle({
290
+ context: source.context,
291
+ question: source.question,
292
+ observations,
293
+ evidence,
294
+ referenceAllowlists: referenceAllowlistsForSelection(source, observations, evidence),
295
+ coverage: {
296
+ complete: false,
297
+ covered: evidence.map((item) => item.id),
298
+ omitted: sourceOmissions,
299
+ unavailable: sourceUnavailable,
300
+ limitations: [
301
+ ...source.coverage.limitations.slice(0, COMPACT_AUDIT_LIST_LIMIT),
302
+ "This Lean diagnostic uses a deterministic causal-focus projection of presented exact evidence plus required bounded evidence.",
303
+ `${omittedCount} lower-information or non-required record(s) are retained only in the immutable source-bundle audit, not this model packet.`,
304
+ ...(source.coverage.omitted.length > sourceOmissions.length ? [`${source.coverage.omitted.length - sourceOmissions.length} additional source omission record(s) remain in source-bundle audit only.`] : []),
305
+ ...(source.coverage.unavailable.length > sourceUnavailable.length ? [`${source.coverage.unavailable.length - sourceUnavailable.length} additional unavailable-domain record(s) remain in source-bundle audit only.`] : []),
306
+ ],
307
+ },
308
+ }),
309
+ selectedEvidenceIds: evidence.map((item) => item.id).sort(),
310
+ selectedObservationIds: observations.map((item) => item.id).sort(),
311
+ };
312
+ }
313
+
314
+ function stringsIn(value: unknown, values: Set<string> = new Set()): Set<string> {
315
+ if (typeof value === "string") values.add(value);
316
+ else if (Array.isArray(value)) value.forEach((item) => stringsIn(item, values));
317
+ else if (value && typeof value === "object") Object.values(value as Record<string, unknown>).forEach((item) => stringsIn(item, values));
318
+ return values;
319
+ }
320
+
321
+ function referenceAllowlistsForSelection(
322
+ source: Readonly<AblationEvidenceClosureBundle>,
323
+ observations: readonly AblationEvidenceClosureBundle["observations"][number][],
324
+ evidence: readonly AblationEvidenceClosureBundle["evidence"][number][],
325
+ ) {
326
+ const witnesses = collectReasoningMoveWitnesses(evidence, source.context);
327
+ const groundedMoves = new Set([
328
+ ...source.context.historyUci,
329
+ source.context.moveUci,
330
+ ...witnesses.map((witness) => witness.uci),
331
+ ]);
332
+ const supportedStrings = new Set<string>();
333
+ observations.forEach((observation) => stringsIn({ participants: observation.participants, attributes: observation.attributes }, supportedStrings));
334
+ evidence.forEach((item) => stringsIn({ data: item.data, replay: item.replay }, supportedStrings));
335
+ for (const witness of witnesses) {
336
+ supportedStrings.add(witness.uci);
337
+ supportedStrings.add(witness.pieceId);
338
+ supportedStrings.add(witness.uci.slice(0, 2));
339
+ supportedStrings.add(witness.uci.slice(2, 4));
340
+ }
341
+ const semanticReferences = source.referenceAllowlists.semanticReferences.filter((reference) => (
342
+ // A UCI-shaped string in a retained record is not itself a replayable
343
+ // chess reference. A compact projection may have dropped the record that
344
+ // supplied its witness, so moves must still be grounded by the retained
345
+ // context or retained evidence witnesses.
346
+ (reference.domain !== "move" || groundedMoves.has(reference.id))
347
+ && (
348
+ (reference.domain === "move" && reference.id === source.context.moveUci)
349
+ || observations.some((observation) => observation.participants.some((participant) => participant.ref.domain === reference.domain && participant.ref.id === reference.id))
350
+ || supportedStrings.has(reference.id)
351
+ )
352
+ ));
353
+ const explicitWitnessReferences = witnesses.flatMap((witness) => [
354
+ { domain: "move" as const, id: witness.uci },
355
+ { domain: "piece" as const, id: witness.pieceId },
356
+ { domain: "square" as const, id: witness.uci.slice(0, 2) },
357
+ { domain: "square" as const, id: witness.uci.slice(2, 4) },
358
+ ]);
359
+ const exactSquareReferences = [...supportedStrings]
360
+ .filter((value) => SQUARE.test(value))
361
+ .map((id) => ({ domain: "square" as const, id }));
362
+ const exactMoveReferences = [...supportedStrings]
363
+ .filter((value) => UCI.test(value) && witnesses.some((witness) => witness.uci === value))
364
+ .map((id) => ({ domain: "move" as const, id }));
365
+ return {
366
+ evidenceIds: evidence.map((item) => item.id),
367
+ observationIds: observations.map((item) => item.id),
368
+ toolNames: source.referenceAllowlists.toolNames,
369
+ semanticReferences: [...semanticReferences, ...explicitWitnessReferences, ...exactSquareReferences, ...exactMoveReferences]
370
+ .filter((reference, index, all) => all.findIndex((candidate) => candidate.domain === reference.domain && candidate.id === reference.id) === index),
371
+ moveWitnesses: witnesses,
372
+ };
373
+ }
374
+
375
+ function compactFixtureProjection(
376
+ source: Readonly<AblationEvidenceClosureBundle>,
377
+ requiredEvidenceIds: readonly string[],
378
+ selectors: readonly LeanDirectEvidenceSelector[],
379
+ ): { bundle: Readonly<AblationEvidenceClosureBundle>; selectedEvidenceIds: string[]; selectedObservationIds: string[]; selectorIds: string[] } {
380
+ const selectorIds = selectors.map((selector) => selector.id);
381
+ if (!selectors.length || selectorIds.some((id) => typeof id !== "string" || !id.trim()) || new Set(selectorIds).size !== selectorIds.length) {
382
+ throw new Error("Lean direct compact evidence selection requires uniquely named selectors");
383
+ }
384
+ for (const selector of selectors) {
385
+ if ((!selector.factKinds?.length && !selector.observationTypes?.length)
386
+ || selector.factKinds?.some((kind) => typeof kind !== "string" || !kind.trim())
387
+ || selector.observationTypes?.some((type) => typeof type !== "string" || !type.trim())) {
388
+ throw new Error(`Lean direct selector ${selector.id} must declare bounded fact or observation names`);
389
+ }
390
+ }
391
+ const evidenceById = new Map(source.evidence.map((item) => [item.id, item]));
392
+ const selectedObservations = source.observations.filter((observation) => selectors.some((selector) => selectorMatches(selector, observation)));
393
+ for (const selector of selectors) {
394
+ if (!selectedObservations.some((observation) => selectorMatches(selector, observation))) {
395
+ throw new Error(`Lean direct compact evidence selector ${selector.id} found no observation`);
396
+ }
397
+ }
398
+ const selectedEvidenceIds = new Set<string>(requiredEvidenceIds);
399
+ for (const observation of selectedObservations) observation.factEvidenceIds.forEach((id) => selectedEvidenceIds.add(id));
400
+ if ([...selectedEvidenceIds].some((id) => !evidenceById.has(id))) throw new Error("Lean direct compact evidence selection references unadmitted evidence");
401
+ const evidence = source.evidence.filter((item) => selectedEvidenceIds.has(item.id));
402
+ const sourceOmissions = source.coverage.omitted.slice(0, COMPACT_AUDIT_LIST_LIMIT);
403
+ const sourceUnavailable = source.coverage.unavailable.slice(0, COMPACT_AUDIT_LIST_LIMIT);
404
+ const omittedCount = source.observations.length - selectedObservations.length + source.evidence.length - evidence.length;
405
+ const bundle = createAblationEvidenceClosureBundle({
406
+ context: source.context,
407
+ question: source.question,
408
+ observations: selectedObservations,
409
+ evidence,
410
+ referenceAllowlists: referenceAllowlistsForSelection(source, selectedObservations, evidence),
411
+ coverage: {
412
+ complete: false,
413
+ covered: evidence.map((item) => item.id),
414
+ omitted: sourceOmissions,
415
+ unavailable: sourceUnavailable,
416
+ limitations: [
417
+ ...source.coverage.limitations.slice(0, COMPACT_AUDIT_LIST_LIMIT),
418
+ "This Lean diagnostic uses only fixture-declared, pre-semantic evidence selectors plus its required witness.",
419
+ `${omittedCount} non-selected pre-semantic record(s) are retained only in the immutable source-bundle audit, not this model packet.`,
420
+ ...(source.coverage.omitted.length > sourceOmissions.length ? [`${source.coverage.omitted.length - sourceOmissions.length} additional source omission record(s) remain in source-bundle audit only.`] : []),
421
+ ...(source.coverage.unavailable.length > sourceUnavailable.length ? [`${source.coverage.unavailable.length - sourceUnavailable.length} additional unavailable-domain record(s) remain in source-bundle audit only.`] : []),
422
+ ],
423
+ },
424
+ });
425
+ return {
426
+ bundle,
427
+ selectedEvidenceIds: evidence.map((item) => item.id).sort(),
428
+ selectedObservationIds: selectedObservations.map((item) => item.id).sort(),
429
+ selectorIds: [...selectorIds].sort(),
430
+ };
431
+ }
432
+
433
+ export function projectLeanDirectEvidence(
434
+ bundle: Readonly<AblationEvidenceClosureBundle>,
435
+ requiredEvidenceIds: readonly string[] = [],
436
+ evidenceSelectors: readonly LeanDirectEvidenceSelector[] = [],
437
+ ): LeanDirectEvidenceView {
438
+ validateAblationEvidenceClosureBundle(bundle);
439
+ const required = [...new Set(requiredEvidenceIds)].sort();
440
+ if (required.some((id) => typeof id !== "string" || !id.trim())) throw new Error("Lean direct required evidence IDs must be bounded non-empty strings");
441
+ const sourceEvidence = new Map(bundle.evidence.map((item) => [item.id, item]));
442
+ if (required.some((id) => !sourceEvidence.has(id))) throw new Error("Lean direct required evidence is not in the frozen bundle");
443
+ if (evidenceSelectors.length) {
444
+ const compact = compactFixtureProjection(bundle, required, evidenceSelectors);
445
+ const focused = focusedLeanProjection(compact.bundle, required);
446
+ return Object.freeze({
447
+ schemaVersion: ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION,
448
+ sourceBundleHash: bundle.bundleHash,
449
+ bundle: focused.bundle,
450
+ salienceEvidenceIds: Object.freeze(focused.selectedEvidenceIds),
451
+ requiredEvidenceIds: Object.freeze(required),
452
+ supplementalBoundedEvidenceIds: Object.freeze([]),
453
+ selectedSelectorIds: Object.freeze(compact.selectorIds),
454
+ viewHash: stableHash({
455
+ schemaVersion: ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION,
456
+ sourceBundleHash: bundle.bundleHash,
457
+ projectedBundleHash: focused.bundle.bundleHash,
458
+ selectedEvidenceIds: focused.selectedEvidenceIds,
459
+ selectedObservationIds: focused.selectedObservationIds,
460
+ requiredEvidenceIds: required,
461
+ selectedSelectorIds: compact.selectorIds,
462
+ }),
463
+ });
464
+ }
465
+ const salience = projectAblationEvidenceForModel(bundle);
466
+ const salienceEvidenceIds = new Set(salience.bundle.evidence.map((item) => item.id));
467
+ const requiredSupplemental = required.flatMap((id) => {
468
+ const record = sourceEvidence.get(id);
469
+ return record && !salienceEvidenceIds.has(id) ? [record] : [];
470
+ });
471
+ const boundedSupplemental = bundle.evidence
472
+ .filter((item) => !salienceEvidenceIds.has(item.id) && !required.includes(item.id) && licensesBoundedRouteRelevance(effectiveLicensesForRecord(item)))
473
+ .sort((left, right) => left.id.localeCompare(right.id))
474
+ .slice(0, 1);
475
+ const supplemental = [...requiredSupplemental, ...boundedSupplemental];
476
+ const supplementalIds = new Set(supplemental.map((item) => item.id));
477
+ const observations = [
478
+ ...salience.bundle.observations,
479
+ ...bundle.observations.filter((item) => !salience.bundle.observations.some((visible) => visible.id === item.id)
480
+ && item.factEvidenceIds.some((id) => supplementalIds.has(id))),
481
+ ];
482
+ const evidence = [...salience.bundle.evidence, ...supplemental];
483
+ const projected = createAblationEvidenceClosureBundle({
484
+ context: bundle.context,
485
+ question: bundle.question,
486
+ observations,
487
+ evidence,
488
+ referenceAllowlists: referenceAllowlistsForSelection(bundle, observations, evidence),
489
+ coverage: {
490
+ ...salience.bundle.coverage,
491
+ limitations: [
492
+ ...salience.bundle.coverage.limitations,
493
+ ...(supplemental.length ? ["One already-captured bounded continuation/comparison record was included for this one-call diagnostic."] : []),
494
+ ],
495
+ },
496
+ });
497
+ const supplementalBoundedEvidenceIds = boundedSupplemental.map((item) => item.id);
498
+ const focused = focusedLeanProjection(projected, [...required, ...supplementalBoundedEvidenceIds]);
499
+ return Object.freeze({
500
+ schemaVersion: ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION,
501
+ sourceBundleHash: bundle.bundleHash,
502
+ bundle: focused.bundle,
503
+ salienceEvidenceIds: Object.freeze(focused.selectedEvidenceIds),
504
+ requiredEvidenceIds: Object.freeze(required),
505
+ supplementalBoundedEvidenceIds: Object.freeze(supplementalBoundedEvidenceIds),
506
+ selectedSelectorIds: Object.freeze([]),
507
+ viewHash: stableHash({
508
+ schemaVersion: ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION,
509
+ sourceBundleHash: bundle.bundleHash,
510
+ projectedBundleHash: focused.bundle.bundleHash,
511
+ salienceEvidenceIds: focused.selectedEvidenceIds,
512
+ requiredEvidenceIds: required,
513
+ supplementalBoundedEvidenceIds,
514
+ }),
515
+ });
516
+ }
517
+
518
+ /** Exact episode membership; no salience reranking or fixture selection at synthesis. */
519
+ export function projectLeanEpisodeEvidence(source: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], coverage = source.coverage): LeanDirectEvidenceView {
520
+ validateAblationEvidenceClosureBundle(source);
521
+ const selected = new Set(ids);
522
+ const evidence = source.evidence.filter((item) => selected.has(item.id));
523
+ if (evidence.length !== selected.size) throw new Error("Lean episode references unknown evidence");
524
+ const observations = source.observations.filter((item) => item.factEvidenceIds.every((id) => selected.has(id)));
525
+ const bundle = createAblationEvidenceClosureBundle({ context: source.context, question: source.question, observations, evidence,
526
+ referenceAllowlists: referenceAllowlistsForSelection(source, observations, evidence),
527
+ coverage: { ...coverage, complete: false, covered: [...selected],
528
+ limitations: [...coverage.limitations, `${source.evidence.length - evidence.length} retained records were not retrieved; absence from this packet is not absence on the board.`] },
529
+ });
530
+ return Object.freeze({ schemaVersion: ABLATION_LEAN_DIRECT_EVIDENCE_VIEW_VERSION, sourceBundleHash: source.bundleHash,
531
+ bundle, salienceEvidenceIds: [...selected], requiredEvidenceIds: [], supplementalBoundedEvidenceIds: [], selectedSelectorIds: [],
532
+ viewHash: stableHash({ source: source.bundleHash, selected: [...selected].sort(), bundle: bundle.bundleHash }) });
533
+ }
534
+
535
+ function modelPrompt(view: LeanDirectEvidenceView, compact = false) {
536
+ return {
537
+ schemaVersion: ABLATION_LEAN_DIRECT_RUNTIME_VERSION,
538
+ proposalVersion: ABLATION_LEAN_DIRECT_PROPOSAL_VERSION,
539
+ sourceBundleHash: view.sourceBundleHash,
540
+ modelFacingEvidenceViewHash: view.viewHash,
541
+ frozenEvidence: compact ? compactLeanEvidencePacket(view.bundle) : view.bundle,
542
+ };
543
+ }
544
+
545
+ /** Deterministic admission checks evidence identity and source ceilings; it never interprets generated prose. */
546
+ export function materializeLeanDirectProposal(view: LeanDirectEvidenceView, proposal: unknown): LeanDirectArtifact {
547
+ validateAblationEvidenceClosureBundle(view.bundle);
548
+ if (!isRecord(proposal)) throw new LeanAdmissionError("proposal", "invalid-shape");
549
+ exactKeys(proposal, ["schemaVersion", "claims", "limitations", "coverage"], "proposal");
550
+ if (proposal.schemaVersion !== ABLATION_LEAN_DIRECT_PROPOSAL_VERSION) throw new LeanAdmissionError("proposal.schemaVersion", "unsupported-version");
551
+ if (!Array.isArray(proposal.claims) || !proposal.claims.length || proposal.claims.length > 4) throw new LeanAdmissionError("proposal.claims", "invalid-claims");
552
+ const evidence = new Map(view.bundle.evidence.map((item) => [item.id, item]));
553
+ const allowedEvidenceIds = new Set(view.bundle.referenceAllowlists.evidenceIds);
554
+ const claims = proposal.claims.map((raw, index) => {
555
+ const path = `proposal.claims[${index}]`;
556
+ if (!isRecord(raw)) throw new LeanAdmissionError(path, "invalid-claim");
557
+ exactKeys(raw, ["text", "kind", "evidenceIds"], path);
558
+ const text = nonEmpty(raw.text, `${path}.text`, 700);
559
+ if (typeof raw.kind !== "string" || !CLAIM_KINDS.has(raw.kind)) throw new LeanAdmissionError(`${path}.kind`, "invalid-kind");
560
+ if (!Array.isArray(raw.evidenceIds) || !raw.evidenceIds.length || raw.evidenceIds.length > 8) throw new LeanAdmissionError(`${path}.evidenceIds`, "invalid-evidence");
561
+ const evidenceIds = raw.evidenceIds.map((value, evidenceIndex) => nonEmpty(value, `${path}.evidenceIds[${evidenceIndex}]`, 200));
562
+ if (new Set(evidenceIds).size !== evidenceIds.length) throw new LeanAdmissionError(`${path}.evidenceIds`, "duplicate-evidence");
563
+ const cited = evidenceIds.map((id) => allowedEvidenceIds.has(id) ? evidence.get(id) : undefined).filter((item): item is NonNullable<typeof item> => Boolean(item));
564
+ if (cited.length !== evidenceIds.length) throw new LeanAdmissionError(`${path}.evidenceIds`, "unknown-evidence");
565
+ if (!cited.some((item) => licensesSemanticChessGrounding(effectiveLicensesForRecord(item)))) {
566
+ throw new LeanAdmissionError(`${path}.evidenceIds`, "evidence-license-insufficient");
567
+ }
568
+ return {
569
+ text,
570
+ kind: raw.kind as LeanDirectClaim["kind"],
571
+ evidenceIds,
572
+ };
573
+ });
574
+ if (!Array.isArray(proposal.limitations) || proposal.limitations.length > 4) throw new LeanAdmissionError("proposal.limitations", "invalid-limitations");
575
+ const limitations = proposal.limitations.map((value, index) => nonEmpty(value, `proposal.limitations[${index}]`, 400));
576
+ if (!isRecord(proposal.coverage)) throw new LeanAdmissionError("proposal.coverage", "invalid-coverage");
577
+ exactKeys(proposal.coverage, ["status", "unresolved"], "proposal.coverage");
578
+ if (proposal.coverage.status !== "complete" && proposal.coverage.status !== "partial" && proposal.coverage.status !== "unavailable") throw new LeanAdmissionError("proposal.coverage.status", "invalid-coverage");
579
+ if (!Array.isArray(proposal.coverage.unresolved) || proposal.coverage.unresolved.length > 4) throw new LeanAdmissionError("proposal.coverage.unresolved", "invalid-coverage");
580
+ const unresolved = proposal.coverage.unresolved.map((value, index) => nonEmpty(value, `proposal.coverage.unresolved[${index}]`, 400));
581
+ return Object.freeze({
582
+ schemaVersion: ABLATION_LEAN_DIRECT_PROPOSAL_VERSION,
583
+ sourceBundleHash: view.sourceBundleHash,
584
+ modelFacingEvidenceViewHash: view.viewHash,
585
+ claims,
586
+ limitations,
587
+ coverage: { status: proposal.coverage.status as LeanDirectArtifact["coverage"]["status"], unresolved },
588
+ admission: "diagnostic-proposed",
589
+ productSupport: "not-a-validated-graph-checkpoint-or-product-result",
590
+ });
591
+ }
592
+
593
+ export async function runLeanDirect(
594
+ options: LeanDirectRuntimeOptions,
595
+ bundle: Readonly<AblationEvidenceClosureBundle>,
596
+ signal?: AbortSignal,
597
+ ): Promise<LeanDirectResult> {
598
+ if (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens < 1) throw new Error("Lean direct requires a positive output-token budget");
599
+ validateAblationEvidenceClosureBundle(bundle);
600
+ const modelView = options.modelEvidenceIds ? projectLeanEpisodeEvidence(bundle, options.modelEvidenceIds, options.episodeCoverage) : projectLeanDirectEvidence(bundle, options.requiredEvidenceIds, options.evidenceSelectors);
601
+ const prompt = JSON.stringify(modelPrompt(modelView, Boolean(options.modelEvidenceIds)));
602
+ const system = effectiveSystem(options.benchmarkNonce) + (options.modelEvidenceIds
603
+ ? "\nScope and licensing: before/after means immediately around the target move, not after later moves in a witness. Do not carry a position relation into a later line unless it persists there. A legality-only witness or attention affordance alone cannot ground a strategic consequence. Legal recaptures are unranked possibilities, not evidence that recapturing is profitable or preferred. Nominal arithmetic is not exchange safety. Cite the evidence that establishes the claimed relation, not merely a nearby legal sequence."
604
+ : "");
605
+ const diagnostic: LeanDirectCallDiagnostic = {
606
+ stage: "lean-direct", runtime: ABLATION_LEAN_DIRECT_RUNTIME_VERSION, model: modelName(options), route: options.providerRoute || "unspecified",
607
+ effort: String(options.reasoning || "provider-default"), temperature: options.temperature ?? null, bundleHash: bundle.bundleHash,
608
+ inputBytes: bytes({ system, prompt }), schemaBytes: bytes(LEAN_DIRECT_PROPOSAL_JSON_SCHEMA), outputBytes: 0, finishReason: null,
609
+ latencyMs: null, inputTokens: null, outputTokens: null, cost: null, cacheTokens: null, outcome: "failed", failureCode: null,
610
+ };
611
+ emitObservableTrace(options.observableTraceSink, {
612
+ stage: "ablation-lean-direct", direction: "input", model: diagnostic.model, providerRoute: diagnostic.route, reasoningEffort: diagnostic.effort,
613
+ payload: { system, prompt, responseSchema: LEAN_DIRECT_PROPOSAL_JSON_SCHEMA, tools: [], maxOutputTokens: options.maxOutputTokens, maxWallTimeMs: options.maxWallTimeMs, temperature: options.temperature ?? null, benchmarkNonce: options.benchmarkNonce ?? null },
614
+ });
615
+ const abort = abortContext(signal, options.maxWallTimeMs);
616
+ const started = now();
617
+ try {
618
+ const generated = await generateText({
619
+ model: options.model,
620
+ system,
621
+ prompt,
622
+ output: Output.object({
623
+ schema: jsonSchema<Record<string, unknown>>(LEAN_DIRECT_PROPOSAL_JSON_SCHEMA as unknown as Parameters<typeof jsonSchema<Record<string, unknown>>>[0]),
624
+ name: "knights_eye_lean_direct_answer",
625
+ description: "A one-call evidence-cited chess-answer diagnostic.",
626
+ }),
627
+ abortSignal: abort.signal,
628
+ maxOutputTokens: options.maxOutputTokens,
629
+ maxRetries: 0,
630
+ reasoning: options.reasoning || "provider-default",
631
+ ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
632
+ onStepFinish(step) {
633
+ const metadata = options.consumeProviderMetadata?.().at(-1);
634
+ diagnostic.model = metadata?.model || step.model.modelId || diagnostic.model;
635
+ diagnostic.route = metadata?.route || diagnostic.route;
636
+ diagnostic.effort = metadata?.effort || diagnostic.effort;
637
+ diagnostic.finishReason = String(step.finishReason || "");
638
+ diagnostic.latencyMs = step.performance.responseTimeMs ?? null;
639
+ diagnostic.inputTokens = numericUsage(step.usage.inputTokens);
640
+ diagnostic.outputTokens = numericUsage(step.usage.outputTokens);
641
+ diagnostic.cost = typeof metadata?.cost === "number" && Number.isFinite(metadata.cost) ? metadata.cost : null;
642
+ diagnostic.cacheTokens = typeof metadata?.cacheTokens === "number" && Number.isFinite(metadata.cacheTokens) ? metadata.cacheTokens : null;
643
+ },
644
+ });
645
+ if (abort.signal.aborted) throw abort.signal.reason;
646
+ diagnostic.latencyMs ??= Math.round(now() - started);
647
+ diagnostic.outputBytes = bytes(generated.output);
648
+ let artifact: LeanDirectArtifact;
649
+ try { artifact = materializeLeanDirectProposal(modelView, generated.output); }
650
+ catch (error) {
651
+ const issue = error instanceof LeanAdmissionError ? [{ path: error.path, code: error.code }] : [{ path: "proposal", code: "invalid" }];
652
+ const failure: LeanDirectFailure = { kind: "lean-contract-rejected", code: "lean-direct-rejected", issues: issue };
653
+ diagnostic.outcome = "failed"; diagnostic.failureCode = failure.code;
654
+ emitObservableTrace(options.observableTraceSink, { stage: "ablation-lean-direct", direction: "output", model: diagnostic.model, providerRoute: diagnostic.route, reasoningEffort: diagnostic.effort, payload: { structuredOutput: generated.output, materialization: { status: "rejected", issues: issue } } });
655
+ return { ok: false, diagnostics: [diagnostic], failure, modelView };
656
+ }
657
+ diagnostic.outcome = "succeeded";
658
+ emitObservableTrace(options.observableTraceSink, { stage: "ablation-lean-direct", direction: "output", model: diagnostic.model, providerRoute: diagnostic.route, reasoningEffort: diagnostic.effort, payload: { text: generated.text, structuredOutput: generated.output, finishReason: generated.finishReason, usage: generated.usage, materialization: { status: "mechanically-admitted", checked: ["shape", "evidence-id-existence", "record-licensing"], unchecked: ["prose-entailment", "embedded-chess-references", "factual-and-conceptual-correctness"] } } });
659
+ return { ok: true, artifact, diagnostics: [diagnostic], modelView };
660
+ } catch (error) {
661
+ const failure = classify(error, abort.timedOut(), Boolean(signal?.aborted && !abort.timedOut()));
662
+ diagnostic.latencyMs ??= Math.round(now() - started);
663
+ diagnostic.outcome = failure.kind === "aborted" || failure.kind === "wall-time" ? "aborted" : "failed";
664
+ diagnostic.failureCode = failure.code;
665
+ emitObservableTrace(options.observableTraceSink, { stage: "ablation-lean-direct", direction: "error", model: diagnostic.model, providerRoute: diagnostic.route, reasoningEffort: diagnostic.effort, payload: { failure: { kind: failure.kind, code: failure.code } } });
666
+ return { ok: false, diagnostics: [diagnostic], failure, modelView };
667
+ } finally {
668
+ abort.dispose();
669
+ }
670
+ }