@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,513 @@
1
+ import { Chess, SQUARES } from "chess.js";
2
+ import {
3
+ createAblationEvidenceClosureBundle,
4
+ enrichAblationEvidenceClosureBundle,
5
+ type AblationEvidenceClosureBundle,
6
+ type AblationEvidenceClosureCoverage,
7
+ type AblationEvidenceReferenceAllowlists,
8
+ } from "./ablation-evidence-bundle";
9
+ import { REFERENCE_DOMAINS } from "./concept-registry";
10
+ import { chessFactRecordLicenses, factProofStrength, type ChessFactRecord } from "./fact-provider";
11
+ import { deriveAdmittedCaptureResponseAvailabilityFact } from "./mechanical-fact-provider";
12
+ import { collectReasoningMoveWitnesses } from "./poc-move-witnesses";
13
+ import { createPocIdentityTimeline } from "./poc-piece-refs";
14
+ import {
15
+ REASONING_POC_VERSION,
16
+ reasoningContextHash,
17
+ validateReasoningPositionContext,
18
+ type ReasoningEvidenceRecord,
19
+ type ReasoningObservation,
20
+ type ReasoningObservationSet,
21
+ type ReasoningPositionContext,
22
+ type ReasoningRankedObservationSet,
23
+ type ReasoningToolName,
24
+ type ReasoningToolResult,
25
+ } from "./poc-contracts";
26
+ import { stableJson } from "./stable";
27
+ import type { SemanticValue } from "./types";
28
+
29
+ /**
30
+ * The capture boundary is deliberately narrower than a full reasoning trace:
31
+ * it accepts only the completed evidence-acquisition phase. No model-created
32
+ * hypothesis, review, graph, or answer state can cross this boundary.
33
+ */
34
+ export const ABLATION_EVIDENCE_CAPTURE_VERSION = "architecture-ablation-evidence-capture/1" as const;
35
+ export const CAPTURE_RESPONSE_FACT_KIND = "move.capture_response_availability_after" as const;
36
+ export const COMPLETED_TOOL_REPLAY_ADMISSION_KEY = "ablationCompletedToolReplayAdmission" as const;
37
+ const COMPLETED_TOOL_REPLAY_ADMISSION_VERSION = "architecture-ablation-completed-tool-replay-admission/1" as const;
38
+
39
+ const PRESEMANTIC_TOOL_NAMES = new Set<ReasoningToolName>([
40
+ "finish_evidence_collection",
41
+ "inspect_position",
42
+ "analyze_move",
43
+ "explore_continuations",
44
+ "compare_moves",
45
+ "enumerate_defenses",
46
+ "test_line",
47
+ ]);
48
+ const UCI = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
49
+
50
+ export type AblationEvidenceCaptureInput = {
51
+ context: ReasoningPositionContext;
52
+ question: string;
53
+ /** Provider output before salience; retained to make membership auditable. */
54
+ observations: ReasoningObservationSet;
55
+ /** The exact output of the production salience policy for `observations`. */
56
+ rankedObservations: ReasoningRankedObservationSet;
57
+ /** Already completed fact/probe calls from the evidence phase. */
58
+ admittedToolResults?: readonly ReasoningToolResult[];
59
+ /**
60
+ * Retain exact fact records that the supplied salience result explicitly
61
+ * presents. This is for consumers whose model packet is defined by that
62
+ * production presentation decision; the original 2x2 baseline remains
63
+ * response-fact-withheld by default.
64
+ */
65
+ retainPresentedExactFacts?: boolean;
66
+ /** Generic acquisition keeps every admitted record discoverable; frozen 2x2 defaults stay unchanged. */
67
+ retainAllExactFacts?: boolean;
68
+ };
69
+
70
+ type AdmittedReplayLine = {
71
+ evidenceId: string;
72
+ start: "before" | "after";
73
+ movesUci: string[];
74
+ };
75
+
76
+ type CompletedToolReplayAdmission = {
77
+ schemaVersion: typeof COMPLETED_TOOL_REPLAY_ADMISSION_VERSION;
78
+ tool: ReasoningToolName;
79
+ callId: string;
80
+ };
81
+
82
+ function isRecord(value: unknown): value is Record<string, unknown> {
83
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
84
+ }
85
+
86
+ function hasCallerSuppliedReplayAdmission(record: ReasoningEvidenceRecord): boolean {
87
+ return isRecord(record.replay) && COMPLETED_TOOL_REPLAY_ADMISSION_KEY in record.replay;
88
+ }
89
+
90
+ function withCompletedToolReplayAdmission(
91
+ record: ReasoningEvidenceRecord,
92
+ result: Pick<ReasoningToolResult, "tool" | "callId">,
93
+ ): ReasoningEvidenceRecord {
94
+ if (!isRecord(record.replay)) return clone(record);
95
+ if (hasCallerSuppliedReplayAdmission(record)) {
96
+ throw new Error(`Ablation evidence ${record.id} cannot supply its own completed-tool replay admission`);
97
+ }
98
+ const admission: CompletedToolReplayAdmission = {
99
+ schemaVersion: COMPLETED_TOOL_REPLAY_ADMISSION_VERSION,
100
+ tool: result.tool,
101
+ callId: result.callId,
102
+ };
103
+ return {
104
+ ...clone(record),
105
+ replay: {
106
+ ...clone(record.replay),
107
+ [COMPLETED_TOOL_REPLAY_ADMISSION_KEY]: admission,
108
+ },
109
+ };
110
+ }
111
+
112
+ function completedToolReplayAdmission(value: unknown): CompletedToolReplayAdmission | null {
113
+ if (!isRecord(value)) return null;
114
+ const admission = value[COMPLETED_TOOL_REPLAY_ADMISSION_KEY];
115
+ if (!isRecord(admission)
116
+ || Object.keys(admission).sort().join(",") !== "callId,schemaVersion,tool"
117
+ || admission.schemaVersion !== COMPLETED_TOOL_REPLAY_ADMISSION_VERSION
118
+ || typeof admission.callId !== "string" || !admission.callId.trim() || admission.callId.length > 500
119
+ || typeof admission.tool !== "string" || !PRESEMANTIC_TOOL_NAMES.has(admission.tool as ReasoningToolName)) {
120
+ return null;
121
+ }
122
+ return admission as CompletedToolReplayAdmission;
123
+ }
124
+
125
+ function clone<T>(value: T): T {
126
+ return structuredClone(value);
127
+ }
128
+
129
+ function sortedUnique(values: readonly string[]): string[] {
130
+ if (values.some((value) => typeof value !== "string" || !value.trim())) throw new Error("Expected non-empty deterministic identifiers");
131
+ return [...new Set(values)].sort();
132
+ }
133
+
134
+ function responseEvidence(record: ReasoningEvidenceRecord): boolean {
135
+ return isRecord(record.data) && record.data.kind === CAPTURE_RESPONSE_FACT_KIND;
136
+ }
137
+
138
+ function responseObservation(observation: ReasoningObservation): boolean {
139
+ return observation.observationType === `chess-fact:${CAPTURE_RESPONSE_FACT_KIND}`
140
+ || observation.attributes.factKind === CAPTURE_RESPONSE_FACT_KIND;
141
+ }
142
+
143
+ function assertNoSemanticState(value: unknown, path = "capture input"): void {
144
+ if (Array.isArray(value)) return void value.forEach((item, index) => assertNoSemanticState(item, `${path}[${index}]`));
145
+ if (!isRecord(value)) return;
146
+ for (const [key, child] of Object.entries(value)) {
147
+ if (["hypotheses", "relations", "selectedEntities", "selectedRelations", "dispositions", "answer", "claims", "evaluatorAnchor", "idealProse"].includes(key)) {
148
+ throw new Error(`Ablation evidence capture cannot admit semantic state at ${path}.${key}`);
149
+ }
150
+ assertNoSemanticState(child, `${path}.${key}`);
151
+ }
152
+ }
153
+
154
+ function assertContextAndQuestion(context: ReasoningPositionContext, question: string): { context: ReasoningPositionContext; contextHash: string } {
155
+ const checked = validateReasoningPositionContext(clone(context));
156
+ if (typeof question !== "string" || !question.trim() || question.trim().length > 4_000) throw new Error("Ablation evidence capture requires a bounded verbatim question");
157
+ return { context: checked, contextHash: reasoningContextHash(checked) };
158
+ }
159
+
160
+ function assertObservationSets(
161
+ raw: ReasoningObservationSet,
162
+ ranked: ReasoningRankedObservationSet,
163
+ contextHash: string,
164
+ ): ReasoningRankedObservationSet["observations"] {
165
+ assertNoSemanticState(raw);
166
+ assertNoSemanticState(ranked);
167
+ if (raw.schemaVersion !== REASONING_POC_VERSION || ranked.schemaVersion !== REASONING_POC_VERSION
168
+ || raw.contextHash !== contextHash || ranked.contextHash !== contextHash
169
+ || !Array.isArray(raw.observations) || !Array.isArray(ranked.observations)
170
+ || !Array.isArray(raw.evidence) || !Array.isArray(ranked.evidence)
171
+ || !ranked.saliencePolicy?.trim()) {
172
+ throw new Error("Ablation evidence capture received malformed raw or ranked observations");
173
+ }
174
+ const rawById = new Map(raw.observations.map((observation) => [observation.id, observation]));
175
+ if (rawById.size !== raw.observations.length || ranked.observations.length !== raw.observations.length) {
176
+ throw new Error("Ablation evidence capture requires a one-to-one raw/ranked observation set");
177
+ }
178
+ for (const observation of ranked.observations) {
179
+ const unranked = rawById.get(observation.id);
180
+ if (!unranked || observation.contextHash !== contextHash || unranked.contextHash !== contextHash
181
+ || typeof observation.salience !== "number" || !Number.isFinite(observation.salience)
182
+ || observation.salience < 0 || observation.salience > 1 || !Array.isArray(observation.salienceReasons)
183
+ || typeof observation.presentedToModel !== "boolean") {
184
+ throw new Error("Ablation evidence capture received a malformed ranked observation");
185
+ }
186
+ const { salience: _salience, salienceReasons: _reasons, presentedToModel: _presented, ...base } = observation;
187
+ void _salience; void _reasons; void _presented;
188
+ if (stableJson(base) !== stableJson(unranked)) {
189
+ throw new Error(`Ablation salience altered observation meaning or membership: ${observation.id}`);
190
+ }
191
+ }
192
+ return ranked.observations.map((observation) => clone(observation));
193
+ }
194
+
195
+ function admittedEvidence(
196
+ raw: ReasoningObservationSet,
197
+ results: readonly ReasoningToolResult[],
198
+ contextHash: string,
199
+ ): { evidence: ReasoningEvidenceRecord[]; toolNames: ReasoningToolName[]; limitations: string[] } {
200
+ const evidence = new Map<string, ReasoningEvidenceRecord>();
201
+ const append = (record: ReasoningEvidenceRecord) => {
202
+ if (!record || record.schemaVersion !== REASONING_POC_VERSION || record.contextHash !== contextHash
203
+ || !record.id?.trim() || !["context", "fact", "probe"].includes(record.kind)) {
204
+ throw new Error("Ablation evidence capture received non-presemantic or cross-context evidence");
205
+ }
206
+ assertNoSemanticState(record, `evidence:${record.id}`);
207
+ const previous = evidence.get(record.id);
208
+ if (previous && stableJson(previous) !== stableJson(record)) throw new Error(`Ablation evidence capture has conflicting admitted evidence ${record.id}`);
209
+ evidence.set(record.id, clone(record));
210
+ };
211
+ raw.evidence.forEach((record) => {
212
+ if (hasCallerSuppliedReplayAdmission(record)) {
213
+ throw new Error(`Provider evidence ${record.id} cannot self-authorize a completed-tool replay`);
214
+ }
215
+ append(record);
216
+ });
217
+ const toolNames = new Set<ReasoningToolName>();
218
+ const limitations = new Set<string>(raw.limitations || []);
219
+ for (const result of results) {
220
+ assertNoSemanticState(result, `tool-result:${result.callId || "unknown"}`);
221
+ if (result.schemaVersion !== REASONING_POC_VERSION || result.contextHash !== contextHash
222
+ || !result.callId?.trim() || result.callId.length > 500 || !PRESEMANTIC_TOOL_NAMES.has(result.tool)
223
+ || !["supported", "insufficient-evidence", "unsupported"].includes(result.support)
224
+ || !Array.isArray(result.evidence) || !Array.isArray(result.limitations)
225
+ || !Number.isFinite(result.durationMs) || result.durationMs < 0) {
226
+ throw new Error("Ablation evidence capture can admit only completed presemantic fact/probe tool results");
227
+ }
228
+ toolNames.add(result.tool);
229
+ result.evidence.forEach((record) => append(withCompletedToolReplayAdmission(record, result)));
230
+ result.limitations.forEach((limitation) => {
231
+ if (typeof limitation !== "string" || !limitation.trim()) throw new Error("Ablation evidence capture tool limitation must be a non-empty string");
232
+ limitations.add(limitation);
233
+ });
234
+ }
235
+ return { evidence: [...evidence.values()].sort((left, right) => left.id.localeCompare(right.id)), toolNames: [...toolNames].sort() as ReasoningToolName[], limitations: [...limitations].sort() };
236
+ }
237
+
238
+ function responseLinkedObservation(observation: ReasoningObservation, responseIds: Set<string>): boolean {
239
+ return observation.factEvidenceIds.some((id) => responseIds.has(id))
240
+ || (responseObservation(observation) && observation.factEvidenceIds.length === 0);
241
+ }
242
+
243
+ function referenceAllowlists(
244
+ context: ReasoningPositionContext,
245
+ observations: readonly ReasoningObservation[],
246
+ evidence: readonly ReasoningEvidenceRecord[],
247
+ toolNames: readonly ReasoningToolName[],
248
+ ): AblationEvidenceReferenceAllowlists {
249
+ const timeline = createPocIdentityTimeline(context);
250
+ const knownPieces = new Set([...Object.keys(timeline.before.pieces), ...Object.keys(timeline.after.pieces)]);
251
+ const witnesses = collectReasoningMoveWitnesses(evidence, context);
252
+ const knownMoves = new Set([...context.historyUci, ...(context.kind === "position" ? [] : [context.moveUci]), ...witnesses.map((witness) => witness.uci)]);
253
+ const references = new Map<string, { domain: "piece" | "square" | "move"; id: string }>();
254
+ for (const observation of observations) {
255
+ for (const participant of observation.participants) {
256
+ const ref = participant.ref;
257
+ if (ref.domain === REFERENCE_DOMAINS.PIECE) {
258
+ if (!knownPieces.has(ref.id)) throw new Error(`Ablation capture references an unknown stable piece ${ref.id}`);
259
+ } else if (ref.domain === REFERENCE_DOMAINS.SQUARE) {
260
+ if (!SQUARES.includes(ref.id as typeof SQUARES[number])) throw new Error(`Ablation capture references an unknown square ${ref.id}`);
261
+ } else if (ref.domain === REFERENCE_DOMAINS.MOVE) {
262
+ if (!knownMoves.has(ref.id)) throw new Error(`Ablation capture references a move absent from context/evidence ${ref.id}`);
263
+ } else {
264
+ throw new Error(`Ablation capture references unsupported semantic domain ${String(ref.domain)}`);
265
+ }
266
+ references.set(`${ref.domain}:${ref.id}`, { domain: ref.domain, id: ref.id });
267
+ }
268
+ }
269
+ if (context.kind !== "position") references.set(`move:${context.moveUci}`, { domain: "move", id: context.moveUci });
270
+ return {
271
+ evidenceIds: evidence.map((record) => record.id),
272
+ observationIds: observations.map((observation) => observation.id),
273
+ toolNames: [...toolNames].sort(),
274
+ semanticReferences: [...references.values()].sort((left, right) => `${left.domain}:${left.id}`.localeCompare(`${right.domain}:${right.id}`)),
275
+ moveWitnesses: witnesses,
276
+ };
277
+ }
278
+
279
+ function originalCoverage(
280
+ allObservations: readonly ReasoningObservation[],
281
+ allEvidence: readonly ReasoningEvidenceRecord[],
282
+ withheldObservations: readonly ReasoningObservation[],
283
+ withheldEvidence: readonly ReasoningEvidenceRecord[],
284
+ inheritedLimitations: readonly string[],
285
+ ): AblationEvidenceClosureCoverage {
286
+ return {
287
+ complete: false,
288
+ covered: sortedUnique([
289
+ "pre-salience-observation-membership",
290
+ "production-ranked-observation-membership",
291
+ "admitted-presemantic-evidence",
292
+ ...allObservations.map((observation) => `observation:${observation.id}`),
293
+ ...allEvidence.map((record) => `evidence:${record.id}`),
294
+ ]),
295
+ omitted: [
296
+ ...withheldObservations.map((observation) => ({ domain: "observation" as const, id: observation.id, reason: "withheld" as const })),
297
+ ...withheldEvidence.map((record) => ({ domain: "evidence" as const, id: record.id, reason: "withheld" as const })),
298
+ ].sort((left, right) => `${left.domain}:${left.id}`.localeCompare(`${right.domain}:${right.id}`)),
299
+ unavailable: [],
300
+ limitations: sortedUnique([
301
+ "This immutable closure contains only supplied, already-admitted presemantic evidence; no evidence discovery occurred during capture.",
302
+ ...(withheldEvidence.length ? ["Response-availability facts are deliberately withheld from the original 2x2 baseline."] : []),
303
+ ...inheritedLimitations,
304
+ ]),
305
+ };
306
+ }
307
+
308
+ /** Freeze the evidence-acquisition state without allowing semantic artifacts across the boundary. */
309
+ export function captureAblationOriginalEvidenceBundle(input: AblationEvidenceCaptureInput): Readonly<AblationEvidenceClosureBundle> {
310
+ const { context, contextHash } = assertContextAndQuestion(input.context, input.question);
311
+ const ranked = assertObservationSets(input.observations, input.rankedObservations, contextHash);
312
+ const admitted = admittedEvidence(input.observations, input.admittedToolResults || [], contextHash);
313
+ const presentedExactFactEvidenceIds = new Set(input.retainPresentedExactFacts
314
+ ? ranked.filter((observation) => observation.presentedToModel && observation.exactness === "exact")
315
+ .flatMap((observation) => observation.factEvidenceIds)
316
+ : []);
317
+ const withheldEvidence = admitted.evidence.filter((record) => (
318
+ responseEvidence(record) && !input.retainAllExactFacts && !presentedExactFactEvidenceIds.has(record.id)
319
+ ));
320
+ const withheldEvidenceIds = new Set(withheldEvidence.map((record) => record.id));
321
+ const withheldObservations = ranked.filter((observation) => (
322
+ responseLinkedObservation(observation, withheldEvidenceIds)
323
+ ));
324
+ const evidence = admitted.evidence.filter((record) => !withheldEvidenceIds.has(record.id));
325
+ const observations = ranked.filter((observation) => !responseLinkedObservation(observation, withheldEvidenceIds));
326
+ const allowedEvidenceIds = new Set(evidence.map((record) => record.id));
327
+ if (observations.some((observation) => observation.factEvidenceIds.some((id) => !allowedEvidenceIds.has(id)))) {
328
+ throw new Error("Ablation original closure cannot retain an observation whose response evidence was withheld");
329
+ }
330
+ return createAblationEvidenceClosureBundle({
331
+ context,
332
+ question: input.question,
333
+ observations,
334
+ evidence,
335
+ referenceAllowlists: referenceAllowlists(context, observations, evidence, admitted.toolNames),
336
+ coverage: originalCoverage(observations, evidence, withheldObservations, withheldEvidence, admitted.limitations),
337
+ });
338
+ }
339
+
340
+ function phaseForReplayFen(context: ReasoningPositionContext, value: unknown): "before" | "after" | null {
341
+ if (typeof value !== "string") return null;
342
+ try {
343
+ const fen = new Chess(value).fen();
344
+ if (fen === new Chess(context.fenBefore).fen()) return "before";
345
+ if (fen === new Chess(context.fenAfter).fen()) return "after";
346
+ } catch {
347
+ return null;
348
+ }
349
+ return null;
350
+ }
351
+
352
+ function explicitReplayLines(context: ReasoningPositionContext, evidence: readonly ReasoningEvidenceRecord[]): AdmittedReplayLine[] {
353
+ const lines = new Map<string, AdmittedReplayLine>();
354
+ for (const record of evidence) {
355
+ // Only an admitted evidence-phase tool result may introduce a path beyond
356
+ // the target. Provider fact replays are verification witnesses, not an
357
+ // authorization to mine every provider record for a new continuation.
358
+ const admission = completedToolReplayAdmission(record.replay);
359
+ if ((record.kind !== "probe" && !admission) || !isRecord(record.replay)) continue;
360
+ const start = phaseForReplayFen(context, record.replay.fen ?? record.replay.fenAfter);
361
+ if (!start) continue;
362
+ const supplied: unknown[] = [];
363
+ if (Array.isArray(record.replay.movesUci)) supplied.push(record.replay.movesUci);
364
+ // `test_line` calls its exact legal witness `moves`. It is admissible only
365
+ // with the capture-owned completed-tool marker above; provider fact replay
366
+ // fields never authorize continuation mining.
367
+ if (admission && Array.isArray(record.replay.moves)) supplied.push(record.replay.moves);
368
+ if (Array.isArray(record.replay.linesUci)) supplied.push(...record.replay.linesUci);
369
+ for (const candidate of supplied) {
370
+ if (!Array.isArray(candidate) || !candidate.length || candidate.some((move) => typeof move !== "string" || !UCI.test(move))) {
371
+ throw new Error(`Admitted evidence ${record.id} has a malformed replay line`);
372
+ }
373
+ const movesUci = candidate as string[];
374
+ const key = `${record.id}:${start}:${movesUci.join(",")}`;
375
+ lines.set(key, { evidenceId: record.id, start, movesUci: [...movesUci] });
376
+ }
377
+ }
378
+ return [...lines.values()].sort((left, right) => `${left.evidenceId}:${left.start}:${left.movesUci.join(",")}`.localeCompare(`${right.evidenceId}:${right.start}:${right.movesUci.join(",")}`));
379
+ }
380
+
381
+ function finalMoveIsCapture(context: ReasoningPositionContext, line: Pick<AdmittedReplayLine, "start" | "movesUci">): boolean {
382
+ const board = new Chess(line.start === "before" ? context.fenBefore : context.fenAfter);
383
+ for (const [index, uci] of line.movesUci.entries()) {
384
+ const move = board.moves({ verbose: true }).find((candidate) => `${candidate.from}${candidate.to}${candidate.promotion || ""}` === uci);
385
+ if (!move) throw new Error(`Admitted evidence replay is illegal at ply ${index}: ${uci}`);
386
+ if (index === line.movesUci.length - 1) return Boolean(move.captured);
387
+ board.move({ from: move.from, to: move.to, promotion: move.promotion });
388
+ }
389
+ return false;
390
+ }
391
+
392
+ function factAsEvidence(fact: ChessFactRecord, derivedFromEvidenceIds: readonly string[]): ReasoningEvidenceRecord {
393
+ const evidenceId = `evidence:${fact.id}`;
394
+ return {
395
+ schemaVersion: REASONING_POC_VERSION,
396
+ id: evidenceId,
397
+ contextHash: fact.contextHash,
398
+ kind: "fact",
399
+ source: `${fact.provenance.provider}@${fact.provenance.version}`,
400
+ proofStrength: factProofStrength(fact.authority, fact.coverage),
401
+ licenses: chessFactRecordLicenses(fact),
402
+ summary: fact.summary,
403
+ data: {
404
+ factId: fact.id,
405
+ kind: fact.kind,
406
+ phase: fact.phase,
407
+ authority: fact.authority,
408
+ participants: fact.participants as unknown as SemanticValue,
409
+ data: fact.data,
410
+ coverage: fact.coverage as unknown as SemanticValue,
411
+ provenance: fact.provenance as unknown as SemanticValue,
412
+ derivedFromEvidenceIds: [...derivedFromEvidenceIds].sort(),
413
+ captureVersion: ABLATION_EVIDENCE_CAPTURE_VERSION,
414
+ },
415
+ coverage: {
416
+ complete: fact.coverage.exhaustive && fact.coverage.unresolved.length === 0,
417
+ covered: [...fact.coverage.covered],
418
+ unresolved: [...fact.coverage.unresolved],
419
+ },
420
+ replay: fact.replay,
421
+ };
422
+ }
423
+
424
+ function factAsObservation(fact: ChessFactRecord, evidenceId: string, derivedFromEvidenceIds: readonly string[]): ReasoningObservation {
425
+ return {
426
+ schemaVersion: REASONING_POC_VERSION,
427
+ id: `observation:${fact.id}`,
428
+ contextHash: fact.contextHash,
429
+ observationType: `chess-fact:${fact.kind}`,
430
+ participants: fact.participants.map((participant) => ({ ...participant, ref: { ...participant.ref } })),
431
+ attributes: {
432
+ factId: fact.id,
433
+ factKind: fact.kind,
434
+ phase: fact.phase,
435
+ authority: fact.authority,
436
+ data: fact.data,
437
+ coverage: fact.coverage as unknown as SemanticValue,
438
+ derivedFromEvidenceIds: [...derivedFromEvidenceIds].sort(),
439
+ captureVersion: ABLATION_EVIDENCE_CAPTURE_VERSION,
440
+ },
441
+ exactness: fact.authority === "heuristic-affordance" ? "heuristic" : "exact",
442
+ provenance: {
443
+ provider: fact.provenance.provider,
444
+ version: fact.provenance.version,
445
+ trust: fact.authority === "bounded-engine" ? "external" : "deterministic",
446
+ methods: [fact.provenance.method, ABLATION_EVIDENCE_CAPTURE_VERSION],
447
+ },
448
+ factEvidenceIds: [evidenceId],
449
+ probeRecipes: [],
450
+ };
451
+ }
452
+
453
+ /**
454
+ * Derive the opt-in response-availability cell strictly from the immutable
455
+ * original closure. `null` means that the target/its already-admitted lines
456
+ * contain no capture, so the 2x2 experiment has no enriched cell to run.
457
+ */
458
+ export function enrichAblationEvidenceBundleWithAdmittedCaptureResponses(
459
+ original: Readonly<AblationEvidenceClosureBundle>,
460
+ ): Readonly<AblationEvidenceClosureBundle> | null {
461
+ if (original.lineage.kind !== "original") throw new Error("Capture-response enrichment requires an original evidence bundle");
462
+ const candidates: AdmittedReplayLine[] = [];
463
+ const targetLine: AdmittedReplayLine = { evidenceId: "context:target-move", start: "before", movesUci: [original.context.moveUci] };
464
+ if (original.context.kind !== "position" && finalMoveIsCapture(original.context, targetLine)) candidates.push(targetLine);
465
+ for (const line of explicitReplayLines(original.context, original.evidence)) {
466
+ if (finalMoveIsCapture(original.context, line)) candidates.push(line);
467
+ }
468
+ const byFactId = new Map<string, { fact: ChessFactRecord; sources: Set<string> }>();
469
+ for (const candidate of candidates) {
470
+ const fact = deriveAdmittedCaptureResponseAvailabilityFact({
471
+ context: original.context,
472
+ contextHash: original.contextHash,
473
+ start: candidate.start,
474
+ movesUci: candidate.movesUci,
475
+ });
476
+ const existing = byFactId.get(fact.id);
477
+ if (existing) existing.sources.add(candidate.evidenceId);
478
+ else byFactId.set(fact.id, { fact, sources: new Set([candidate.evidenceId]) });
479
+ }
480
+ const additions = [...byFactId.values()].sort((left, right) => left.fact.id.localeCompare(right.fact.id));
481
+ if (!additions.length) return null;
482
+ const evidence = additions.map(({ fact, sources }) => factAsEvidence(fact, [...sources]));
483
+ const observations = additions.map(({ fact, sources }) => factAsObservation(fact, `evidence:${fact.id}`, [...sources]));
484
+ const combinedEvidence = [...original.evidence, ...evidence];
485
+ const combinedObservations = [...original.observations, ...observations];
486
+ return enrichAblationEvidenceClosureBundle(original, {
487
+ evidence,
488
+ observations,
489
+ referenceAllowlists: referenceAllowlists(
490
+ original.context,
491
+ combinedObservations,
492
+ combinedEvidence,
493
+ original.referenceAllowlists.toolNames,
494
+ ),
495
+ coverage: {
496
+ complete: false,
497
+ covered: sortedUnique([
498
+ ...original.coverage.covered,
499
+ "response-availability-from-admitted-captures",
500
+ ...evidence.map((record) => `evidence:${record.id}`),
501
+ ]),
502
+ omitted: original.coverage.omitted.filter((item) => (
503
+ !evidence.some((record) => record.id === item.id)
504
+ && !observations.some((observation) => observation.id === item.id)
505
+ )),
506
+ unavailable: [...original.coverage.unavailable],
507
+ limitations: sortedUnique([
508
+ ...original.coverage.limitations,
509
+ "Response availability is exhaustive only for each already-admitted capture's immediate same-square recaptures; it does not discover a continuation or establish exchange quality.",
510
+ ]),
511
+ },
512
+ });
513
+ }