@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,1128 @@
1
+ import { SQUARES } from "chess.js";
2
+ import { REFERENCE_DOMAINS } from "./concept-registry";
3
+ import { EVIDENCE_LICENSE, effectiveLicensesForRecord, licensesBoundedRouteRelevance, licensesSemanticChessGrounding } from "./evidence-licenses";
4
+ import {
5
+ validateAblationEvidenceClosureBundle,
6
+ type AblationEvidenceClosureBundle,
7
+ } from "./ablation-evidence-bundle";
8
+ import { projectAblationEvidenceForModel } from "./ablation-model-facing-evidence";
9
+ import { HypothesisLedger, validateHypothesisAcceptancePolicies } from "./poc-hypotheses";
10
+ import { collectReasoningMoveWitnesses } from "./poc-move-witnesses";
11
+ import { createPocIdentityTimeline, pieceIdentity } from "./poc-piece-refs";
12
+ import { RAW_CHESS_TEXT } from "./poc-semantic-text";
13
+ import { stableHash } from "./stable";
14
+ import type {
15
+ ReasoningClaimKind,
16
+ ReasoningHypothesisProposal,
17
+ ReasoningHypothesisProvenance,
18
+ ReasoningHypothesisRecord,
19
+ ReasoningHypothesisRelationProposal,
20
+ ReasoningHypothesisRelationRecord,
21
+ ReasoningNarrativeSegment,
22
+ } from "./poc-contracts";
23
+ import type { ProofStrength } from "./types";
24
+
25
+ /**
26
+ * Contract used by the direct ablation arms only. It deliberately ends before
27
+ * C's observation review, criticism, terminal review, graph selection,
28
+ * checkpoint, discourse, rendering, or repair stages.
29
+ */
30
+ export const ABLATION_DIRECT_SYNTHESIS_VERSION = "architecture-ablation-direct-synthesis/2" as const;
31
+
32
+ /**
33
+ * Provider-facing schema. Strict structured-output providers require every
34
+ * object field to be required and every object to be closed. The parser below
35
+ * maps explicit nulls and entry-encoded attribute values back to the existing
36
+ * optional/object-valued semantic contract before materialization.
37
+ */
38
+ export const DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA = {
39
+ type: "object", additionalProperties: false,
40
+ required: ["schemaVersion", "hypotheses", "relations", "claims", "demonstrations", "coverage", "limitations", "riskProfile"],
41
+ properties: {
42
+ schemaVersion: { const: ABLATION_DIRECT_SYNTHESIS_VERSION },
43
+ hypotheses: { type: "array", minItems: 1, maxItems: 16, items: { $ref: "#/$defs/hypothesis" } },
44
+ relations: { type: "array", maxItems: 24, items: { $ref: "#/$defs/relation" } },
45
+ claims: { type: "array", minItems: 1, maxItems: 16, items: { $ref: "#/$defs/claim" } },
46
+ demonstrations: { type: ["array", "null"], maxItems: 8, items: { $ref: "#/$defs/demonstration" } },
47
+ coverage: { $ref: "#/$defs/coverage" },
48
+ limitations: { type: "array", maxItems: 24, items: { type: "string", minLength: 1, maxLength: 1000 } },
49
+ riskProfile: { $ref: "#/$defs/riskProfile" },
50
+ },
51
+ $defs: {
52
+ reference: { type: "object", additionalProperties: false, required: ["domain", "id"], properties: { domain: { type: "string", minLength: 1, maxLength: 160 }, id: { type: "string", minLength: 1, maxLength: 200 } } },
53
+ participant: { type: "object", additionalProperties: false, required: ["role", "ref"], properties: { role: { type: "string", minLength: 1, maxLength: 160 }, ref: { $ref: "#/$defs/reference" } } },
54
+ anchor: { type: "object", additionalProperties: false, required: ["participant", "disposition"], properties: { participant: { $ref: "#/$defs/participant" }, disposition: { const: "required" } } },
55
+ attributeEntry: { type: "object", additionalProperties: false, required: ["key", "value"], properties: { key: { type: "string", minLength: 1, maxLength: 160 }, value: { $ref: "#/$defs/attributeValue" } } },
56
+ attributeObject: { type: "object", additionalProperties: false, required: ["entries"], properties: { entries: { type: "array", maxItems: 32, items: { $ref: "#/$defs/attributeEntry" } } } },
57
+ attributeValue: { anyOf: [
58
+ { type: "object", additionalProperties: false, required: ["type", "value"], properties: { type: { const: "string" }, value: { type: "string", maxLength: 1000 } } },
59
+ { type: "object", additionalProperties: false, required: ["type", "value"], properties: { type: { const: "number" }, value: { type: "number" } } },
60
+ { type: "object", additionalProperties: false, required: ["type", "value"], properties: { type: { const: "boolean" }, value: { type: "boolean" } } },
61
+ { type: "object", additionalProperties: false, required: ["type", "value"], properties: { type: { const: "null" }, value: { type: "null" } } },
62
+ { type: "object", additionalProperties: false, required: ["type", "items"], properties: { type: { const: "array" }, items: { type: "array", maxItems: 32, items: { $ref: "#/$defs/attributeValue" } } } },
63
+ { type: "object", additionalProperties: false, required: ["type", "entries"], properties: { type: { const: "object" }, entries: { type: "array", maxItems: 32, items: { $ref: "#/$defs/attributeEntry" } } } },
64
+ ] },
65
+ hypothesis: {
66
+ type: "object", additionalProperties: false,
67
+ required: ["localId", "entityType", "concepts", "owner", "participants", "attributes", "proofStrength", "evidenceIds", "sourceObservationIds", "parentHypothesisIds", "anchorRequirements"],
68
+ properties: {
69
+ localId: { type: "string", minLength: 1, maxLength: 160 }, entityType: { type: "string", minLength: 1, maxLength: 160 },
70
+ concepts: { type: "array", minItems: 1, maxItems: 4, items: { type: "string", minLength: 1, maxLength: 160 } }, owner: { enum: ["w", "b", null] },
71
+ participants: { type: "array", maxItems: 12, items: { $ref: "#/$defs/participant" } }, attributes: { $ref: "#/$defs/attributeObject" },
72
+ proofStrength: { enum: ["observed", "supported", "forced", null] }, evidenceIds: { type: "array", maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } },
73
+ sourceObservationIds: { type: "array", maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } }, parentHypothesisIds: { type: "array", maxItems: 8, items: { type: "string", minLength: 1, maxLength: 200 } }, anchorRequirements: { type: "array", maxItems: 12, items: { $ref: "#/$defs/anchor" } },
74
+ },
75
+ },
76
+ relation: { type: "object", additionalProperties: false, required: ["localId", "relationType", "from", "to", "attributes", "evidenceIds", "sourceObservationIds"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, relationType: { type: "string", minLength: 1, maxLength: 160 }, from: { type: "string", minLength: 1, maxLength: 160 }, to: { type: "string", minLength: 1, maxLength: 160 }, attributes: { $ref: "#/$defs/attributeObject" }, evidenceIds: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } }, sourceObservationIds: { type: "array", maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } } } },
77
+ segment: { anyOf: [
78
+ { type: "object", additionalProperties: false, required: ["type", "text"], properties: { type: { const: "text" }, text: { type: "string", minLength: 1, maxLength: 600 } } },
79
+ { type: "object", additionalProperties: false, required: ["type", "pieceId", "form", "capitalize", "label"], properties: { type: { const: "piece" }, pieceId: { type: "string", minLength: 1, maxLength: 200 }, form: { enum: ["definite", "color-qualified", "square-qualified", "canonical", "bare", "bare-square-qualified", null] }, capitalize: { type: ["boolean", "null"] }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
80
+ { type: "object", additionalProperties: false, required: ["type", "pieceKind", "color", "label"], properties: { type: { const: "piece-kind" }, pieceKind: { enum: ["bishop", "king", "knight", "pawn", "queen", "rook"] }, color: { enum: ["w", "b", null] }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
81
+ { type: "object", additionalProperties: false, required: ["type", "square", "label"], properties: { type: { const: "square" }, square: { type: "string", minLength: 1, maxLength: 2 }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
82
+ { type: "object", additionalProperties: false, required: ["type", "uci", "pieceId", "san", "label"], properties: { type: { const: "move" }, uci: { type: "string", minLength: 1, maxLength: 5 }, pieceId: { type: "string", minLength: 1, maxLength: 200 }, san: { type: "string", minLength: 1, maxLength: 40 }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
83
+ { type: "object", additionalProperties: false, required: ["type", "entityLocalId", "label"], properties: { type: { const: "entity" }, entityLocalId: { type: "string", minLength: 1, maxLength: 160 }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
84
+ { type: "object", additionalProperties: false, required: ["type", "relationLocalId", "fromEntityLocalId", "toEntityLocalId", "label"], properties: { type: { const: "relation" }, relationLocalId: { type: "string", minLength: 1, maxLength: 160 }, fromEntityLocalId: { type: "string", minLength: 1, maxLength: 160 }, toEntityLocalId: { type: "string", minLength: 1, maxLength: 160 }, label: { type: ["string", "null"], minLength: 1, maxLength: 120 } } },
85
+ ] },
86
+ claim: { type: "object", additionalProperties: false, required: ["localId", "kind", "content", "proofStrength", "evidenceIds", "qualifications"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, kind: { enum: ["fact", "interpretation", "consequence", "defense", "uncertainty"] }, content: { type: "array", minItems: 1, maxItems: 32, items: { $ref: "#/$defs/segment" } }, proofStrength: { enum: ["observed", "supported", "forced", null] }, evidenceIds: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } }, qualifications: { type: "array", maxItems: 8, items: { type: "array", minItems: 1, maxItems: 16, items: { $ref: "#/$defs/segment" } } } } },
87
+ demonstration: { anyOf: [
88
+ { type: "object", additionalProperties: false, required: ["localId", "type", "start", "moves", "label", "evidenceIds"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, type: { const: "legal-variation" }, start: { enum: ["before", "after"] }, moves: { type: "array", minItems: 1, maxItems: 16, items: { type: "object", additionalProperties: false, required: ["uci", "pieceId", "san"], properties: { uci: { type: "string", minLength: 1, maxLength: 5 }, pieceId: { type: "string", minLength: 1, maxLength: 200 }, san: { type: "string", minLength: 1, maxLength: 40 } } } }, label: { type: "string", minLength: 1, maxLength: 160 }, evidenceIds: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } } } },
89
+ { type: "object", additionalProperties: false, required: ["localId", "type", "pieceId", "waypoints", "prerequisiteRefs", "label", "evidenceIds"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, type: { const: "conditional-route" }, pieceId: { type: "string", minLength: 1, maxLength: 200 }, waypoints: { type: "array", minItems: 1, maxItems: 16, items: { type: "string", minLength: 1, maxLength: 1000 } }, prerequisiteRefs: { type: "array", minItems: 1, maxItems: 16, items: { $ref: "#/$defs/segment" } }, label: { type: "string", minLength: 1, maxLength: 160 }, evidenceIds: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } } } },
90
+ { type: "object", additionalProperties: false, required: ["localId", "type", "relationLocalId", "fromEntityLocalId", "toEntityLocalId", "label", "evidenceIds"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, type: { const: "static-relationship" }, relationLocalId: { type: "string", minLength: 1, maxLength: 160 }, fromEntityLocalId: { type: "string", minLength: 1, maxLength: 160 }, toEntityLocalId: { type: "string", minLength: 1, maxLength: 160 }, label: { type: "string", minLength: 1, maxLength: 160 }, evidenceIds: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } } } },
91
+ ] },
92
+ coverageLane: { type: "object", additionalProperties: false, required: ["status", "evidenceIds", "unresolved"], properties: { status: { enum: ["complete", "partial", "unavailable"] }, evidenceIds: { type: "array", maxItems: 12, items: { type: "string", minLength: 1, maxLength: 200 } }, unresolved: { type: "array", maxItems: 24, items: { type: "string", minLength: 1, maxLength: 1000 } } } },
93
+ omission: { type: "object", additionalProperties: false, required: ["localId", "reason"], properties: { localId: { type: "string", minLength: 1, maxLength: 160 }, reason: { type: "string", minLength: 1, maxLength: 500 } } },
94
+ coverage: { type: "object", additionalProperties: false, required: ["responseCoverage", "continuationCoverage", "unresolvedEvidenceObligations", "omittedHypotheses", "omittedRelations"], properties: { responseCoverage: { $ref: "#/$defs/coverageLane" }, continuationCoverage: { $ref: "#/$defs/coverageLane" }, unresolvedEvidenceObligations: { type: "array", maxItems: 24, items: { type: "object", additionalProperties: false, required: ["id", "reason"], properties: { id: { type: "string", minLength: 1, maxLength: 160 }, reason: { type: "string", minLength: 1, maxLength: 500 } } } }, omittedHypotheses: { type: "array", maxItems: 24, items: { $ref: "#/$defs/omission" } }, omittedRelations: { type: "array", maxItems: 24, items: { $ref: "#/$defs/omission" } } } },
95
+ riskProfile: { type: "object", additionalProperties: false, required: ["level", "reasons"], properties: { level: { enum: ["low", "medium", "high"] }, reasons: { type: "array", maxItems: 12, items: { type: "string", minLength: 1, maxLength: 1000 } } } },
96
+ },
97
+ } as const;
98
+
99
+ export type DirectSynthesisArm = "A1" | "B" | "B-prime";
100
+
101
+ export type DirectSynthesisSegment = Exclude<ReasoningNarrativeSegment, { type: "move" | "entity" | "relation" }>
102
+ | { type: "move"; uci: string; pieceId: string; san: string; label?: string }
103
+ | { type: "entity"; entityLocalId: string; label?: string }
104
+ | { type: "relation"; relationLocalId: string; fromEntityLocalId: string; toEntityLocalId: string; label?: string };
105
+
106
+ export type DirectSynthesisClaim = {
107
+ localId: string;
108
+ kind: ReasoningClaimKind;
109
+ content: DirectSynthesisSegment[];
110
+ proofStrength: ProofStrength | null;
111
+ evidenceIds: string[];
112
+ qualifications: DirectSynthesisSegment[][];
113
+ };
114
+
115
+ export type DirectSynthesisDemonstration =
116
+ | {
117
+ localId: string;
118
+ type: "legal-variation";
119
+ start: "before" | "after";
120
+ moves: Array<{ uci: string; pieceId: string; san: string }>;
121
+ label: string;
122
+ evidenceIds: string[];
123
+ }
124
+ | {
125
+ localId: string;
126
+ type: "conditional-route";
127
+ pieceId: string;
128
+ waypoints: string[];
129
+ prerequisiteRefs: DirectSynthesisSegment[];
130
+ label: string;
131
+ evidenceIds: string[];
132
+ }
133
+ | {
134
+ localId: string;
135
+ type: "static-relationship";
136
+ relationLocalId: string;
137
+ fromEntityLocalId: string;
138
+ toEntityLocalId: string;
139
+ label: string;
140
+ evidenceIds: string[];
141
+ };
142
+
143
+ export type DirectSynthesisRiskProfile = {
144
+ level: "low" | "medium" | "high";
145
+ reasons: string[];
146
+ };
147
+
148
+ export type DirectSynthesisCoverageStatus = "complete" | "partial" | "unavailable";
149
+
150
+ export type DirectSynthesisCoverage = {
151
+ responseCoverage: { status: DirectSynthesisCoverageStatus; evidenceIds: string[]; unresolved: string[] };
152
+ continuationCoverage: { status: DirectSynthesisCoverageStatus; evidenceIds: string[]; unresolved: string[] };
153
+ unresolvedEvidenceObligations: Array<{ id: string; reason: string }>;
154
+ omittedHypotheses: Array<{ localId: string; reason: string }>;
155
+ omittedRelations: Array<{ localId: string; reason: string }>;
156
+ };
157
+
158
+ /** A local relation handle is transport-only and is discarded after remapping. */
159
+ export type DirectSynthesisRelationProposal = ReasoningHypothesisRelationProposal & { localId: string };
160
+
161
+ /** Model-local handles are permitted only in this inbound proposal. */
162
+ export type DirectSynthesisProposal = {
163
+ schemaVersion: typeof ABLATION_DIRECT_SYNTHESIS_VERSION;
164
+ hypotheses: ReasoningHypothesisProposal[];
165
+ relations: DirectSynthesisRelationProposal[];
166
+ claims: DirectSynthesisClaim[];
167
+ demonstrations?: DirectSynthesisDemonstration[];
168
+ coverage: DirectSynthesisCoverage;
169
+ limitations: string[];
170
+ riskProfile: DirectSynthesisRiskProfile;
171
+ };
172
+
173
+ export type CanonicalDirectSynthesisClaim = Omit<DirectSynthesisClaim, "localId" | "content" | "qualifications"> & {
174
+ id: string;
175
+ content: ReasoningNarrativeSegment[];
176
+ qualifications: ReasoningNarrativeSegment[][];
177
+ };
178
+
179
+ export type CanonicalDirectSynthesisDemonstration =
180
+ | {
181
+ id: string;
182
+ type: "legal-variation";
183
+ start: "before" | "after";
184
+ moves: Array<{ uci: string; pieceId: string; san: string }>;
185
+ label: string;
186
+ evidenceIds: string[];
187
+ }
188
+ | {
189
+ id: string;
190
+ type: "conditional-route";
191
+ pieceId: string;
192
+ waypoints: string[];
193
+ prerequisiteRefs: ReasoningNarrativeSegment[];
194
+ label: string;
195
+ evidenceIds: string[];
196
+ }
197
+ | {
198
+ id: string;
199
+ type: "static-relationship";
200
+ relationId: string;
201
+ fromEntityId: string;
202
+ toEntityId: string;
203
+ relationType: string;
204
+ label: string;
205
+ evidenceIds: string[];
206
+ };
207
+
208
+ /** The Ledger's localId is an input-only helper and is never persisted here. */
209
+ export type CanonicalDirectSynthesisHypothesis = Omit<ReasoningHypothesisRecord, "localId">;
210
+
211
+ /**
212
+ * This artifact is intentionally pre-checkpoint. It contains only canonical
213
+ * Ledger records plus directly validated narrative material; it does not
214
+ * manufacture acceptance, graph selection, or a C-stage checkpoint.
215
+ */
216
+ export type DirectSynthesisArtifact = {
217
+ schemaVersion: typeof ABLATION_DIRECT_SYNTHESIS_VERSION;
218
+ contextHash: string;
219
+ bundleHash: string;
220
+ hypotheses: CanonicalDirectSynthesisHypothesis[];
221
+ relations: ReasoningHypothesisRelationRecord[];
222
+ claims: CanonicalDirectSynthesisClaim[];
223
+ demonstrations: CanonicalDirectSynthesisDemonstration[];
224
+ coverage: Omit<DirectSynthesisCoverage, "omittedHypotheses" | "omittedRelations"> & {
225
+ omittedHypothesisIds: Array<{ hypothesisId: string; reason: string }>;
226
+ omittedRelationIds: Array<{ relationId: string; reason: string }>;
227
+ };
228
+ admission: "pre-checkpoint-proposed";
229
+ productSupport: "not-a-validated-graph-checkpoint-or-product-result";
230
+ limitations: string[];
231
+ riskProfile: DirectSynthesisRiskProfile;
232
+ };
233
+
234
+ export type DirectSynthesisIssue = {
235
+ path: string;
236
+ code: string;
237
+ message: string;
238
+ permittedReferences: string[];
239
+ };
240
+
241
+ export type DirectSynthesisMaterializationResult =
242
+ | { ok: true; metadata: { arm: DirectSynthesisArm }; artifact: DirectSynthesisArtifact }
243
+ | { ok: false; metadata: { arm: DirectSynthesisArm }; code: "direct-synthesis-rejected"; issues: DirectSynthesisIssue[] };
244
+
245
+ export type MaterializeDirectSynthesisInput = {
246
+ arm: DirectSynthesisArm;
247
+ bundle: Readonly<AblationEvidenceClosureBundle>;
248
+ proposal: unknown;
249
+ provenance?: ReasoningHypothesisProvenance;
250
+ };
251
+
252
+ class AdmissionFailure extends Error {
253
+ constructor(readonly issue: DirectSynthesisIssue) {
254
+ super(issue.message);
255
+ }
256
+ }
257
+
258
+ function fail(path: string, code: string, message: string, permittedReferences: string[] = []): never {
259
+ throw new AdmissionFailure({ path, code, message, permittedReferences });
260
+ }
261
+
262
+ function isRecord(value: unknown): value is Record<string, unknown> {
263
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
264
+ }
265
+
266
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[], path: string): void {
267
+ if (Object.keys(value).some((key) => !keys.includes(key))) fail(path, "unsupported_field", "Direct synthesis contains an unsupported field");
268
+ }
269
+
270
+ function nonEmptyString(value: unknown, path: string, max = 1_000): string {
271
+ if (typeof value !== "string" || !value.trim() || value.length > max) fail(path, "invalid_string", "Expected a bounded non-empty string");
272
+ return value.trim();
273
+ }
274
+
275
+ function stringArray(value: unknown, path: string, max: number, allowEmpty = true): string[] {
276
+ if (!Array.isArray(value) || value.length > max || (!allowEmpty && !value.length)) fail(path, "invalid_string_array", "Expected a bounded string array");
277
+ const result = value.map((item, index) => nonEmptyString(item, `${path}[${index}]`));
278
+ if (new Set(result).size !== result.length) fail(path, "duplicate_reference", "References must not be duplicated");
279
+ return result;
280
+ }
281
+
282
+ function proof(value: unknown, path: string): ProofStrength | null {
283
+ if (value === null) return null;
284
+ if (value === "observed" || value === "supported" || value === "forced") return value;
285
+ return fail(path, "invalid_proof_strength", "Proof strength must be observed, supported, forced, or null");
286
+ }
287
+
288
+ function parseProviderAttributeEntries(value: unknown, path: string): Record<string, unknown> {
289
+ if (!Array.isArray(value) || value.length > 32) fail(path, "invalid_attribute_entries", "Attribute entries must be a bounded array");
290
+ const attributes: Record<string, unknown> = {};
291
+ for (const [index, entry] of value.entries()) {
292
+ if (!isRecord(entry)) fail(`${path}[${index}]`, "invalid_attribute_entry", "Attribute entry must be structured");
293
+ exactKeys(entry, ["key", "value"], `${path}[${index}]`);
294
+ const key = nonEmptyString(entry.key, `${path}[${index}].key`, 160);
295
+ if (Object.hasOwn(attributes, key)) fail(`${path}[${index}].key`, "duplicate_attribute_key", "Attribute keys must be unique");
296
+ attributes[key] = parseProviderAttributeValue(entry.value, `${path}[${index}].value`);
297
+ }
298
+ return attributes;
299
+ }
300
+
301
+ function parseProviderAttributeValue(value: unknown, path: string): unknown {
302
+ if (!isRecord(value)) fail(path, "invalid_attribute_value", "Attribute values must use the typed provider transport");
303
+ const type = value.type;
304
+ if (type === "string") {
305
+ exactKeys(value, ["type", "value"], path);
306
+ return nonEmptyString(value.value, `${path}.value`, 1_000);
307
+ }
308
+ if (type === "number") {
309
+ exactKeys(value, ["type", "value"], path);
310
+ if (typeof value.value !== "number" || !Number.isFinite(value.value)) fail(`${path}.value`, "invalid_attribute_number", "Attribute number must be finite");
311
+ return value.value;
312
+ }
313
+ if (type === "boolean") {
314
+ exactKeys(value, ["type", "value"], path);
315
+ if (typeof value.value !== "boolean") fail(`${path}.value`, "invalid_attribute_boolean", "Attribute boolean must be boolean");
316
+ return value.value;
317
+ }
318
+ if (type === "null") {
319
+ exactKeys(value, ["type", "value"], path);
320
+ if (value.value !== null) fail(`${path}.value`, "invalid_attribute_null", "Null attribute must carry null");
321
+ return null;
322
+ }
323
+ if (type === "array") {
324
+ exactKeys(value, ["type", "items"], path);
325
+ if (!Array.isArray(value.items) || value.items.length > 32) fail(`${path}.items`, "invalid_attribute_array", "Attribute array must be bounded");
326
+ return value.items.map((item, index) => parseProviderAttributeValue(item, `${path}.items[${index}]`));
327
+ }
328
+ if (type === "object") {
329
+ exactKeys(value, ["type", "entries"], path);
330
+ return parseProviderAttributeEntries(value.entries, `${path}.entries`);
331
+ }
332
+ return fail(`${path}.type`, "invalid_attribute_type", "Attribute value type is invalid");
333
+ }
334
+
335
+ function parseAttributes(value: unknown, path: string): Record<string, unknown> {
336
+ if (!isRecord(value)) fail(path, "invalid_attributes", "Attributes must be an object");
337
+ // Strict-provider requests use the closed, recursively typed entry transport.
338
+ // Retain the legacy object form for stored fixtures and direct parser callers;
339
+ // provider output is constrained by DIRECT_SYNTHESIS_PROPOSAL_JSON_SCHEMA.
340
+ if (Array.isArray(value.entries)) {
341
+ exactKeys(value, ["entries"], path);
342
+ return parseProviderAttributeEntries(value.entries, `${path}.entries`);
343
+ }
344
+ return value;
345
+ }
346
+
347
+ function assertNoCArtifacts(value: unknown, path = "proposal", topLevel = true): void {
348
+ if (Array.isArray(value)) {
349
+ value.forEach((item, index) => assertNoCArtifacts(item, `${path}[${index}]`, false));
350
+ return;
351
+ }
352
+ if (!isRecord(value)) return;
353
+ const forbiddenTopLevel = new Set([
354
+ "review", "reviews", "critic", "critics", "challenge", "challenges", "terminalReview",
355
+ "answerObligation", "answerObligations", "selectedGraph", "selectedEntities", "selectedRelations",
356
+ "graphRefs", "hypothesisLedgerRevision", "discourse", "discoursePlan", "render", "repair",
357
+ ]);
358
+ if (topLevel) {
359
+ for (const key of Object.keys(value)) if (forbiddenTopLevel.has(key)) {
360
+ fail(`${path}.${key}`, "semantic_c_artifact", "Direct synthesis must not transport staged-C semantic artifacts");
361
+ }
362
+ }
363
+ // Attribute dictionaries remain model-owned semantic meaning. Reject only
364
+ // recognizable C envelopes, never an ordinary attribute merely called
365
+ // "review", "critic", or similar.
366
+ if (Array.isArray(value.selectedEntities) || Array.isArray(value.selectedRelations)
367
+ || typeof value.hypothesisLedgerRevision === "string"
368
+ || (isRecord(value.discoursePlan) && typeof value.discoursePlan.checkpointId === "string")
369
+ || (isRecord(value.reasoningCheckpoint) && typeof value.reasoningCheckpoint.checkpointVersion === "string")) {
370
+ fail(path, "semantic_c_artifact", "Direct synthesis must not transport a staged-C envelope");
371
+ }
372
+ for (const [key, child] of Object.entries(value)) assertNoCArtifacts(child, `${path}.${key}`, false);
373
+ }
374
+
375
+ function parseSegment(value: unknown, path: string): DirectSynthesisSegment {
376
+ if (!isRecord(value)) return fail(path, "invalid_segment", "Narrative segments must be typed objects");
377
+ const type = nonEmptyString(value.type, `${path}.type`, 64);
378
+ if (type === "text") {
379
+ exactKeys(value, ["type", "text"], path);
380
+ const text = nonEmptyString(value.text, `${path}.text`, 600);
381
+ if (RAW_CHESS_TEXT.test(text)) fail(`${path}.text`, "raw_chess_reference", "Concrete chess references must use typed segments");
382
+ return { type: "text", text };
383
+ }
384
+ if (type === "piece") {
385
+ exactKeys(value, ["type", "pieceId", "form", "capitalize", "label"], path);
386
+ const form = value.form;
387
+ if (form !== undefined && form !== null && form !== "definite" && form !== "color-qualified" && form !== "square-qualified" && form !== "canonical" && form !== "bare" && form !== "bare-square-qualified") {
388
+ fail(`${path}.form`, "invalid_piece_form", "Piece reference form is invalid");
389
+ }
390
+ if (value.capitalize !== undefined && value.capitalize !== null && typeof value.capitalize !== "boolean") fail(`${path}.capitalize`, "invalid_piece_capitalization", "Piece capitalization must be boolean");
391
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
392
+ return { type: "piece", pieceId: nonEmptyString(value.pieceId, `${path}.pieceId`, 200), ...(form === undefined || form === null ? {} : { form }), ...(value.capitalize === undefined || value.capitalize === null ? {} : { capitalize: value.capitalize }), ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }) };
393
+ }
394
+ if (type === "piece-kind") {
395
+ exactKeys(value, ["type", "pieceKind", "color", "label"], path);
396
+ const pieceKind = nonEmptyString(value.pieceKind, `${path}.pieceKind`, 20);
397
+ if (!(["bishop", "king", "knight", "pawn", "queen", "rook"] as string[]).includes(pieceKind)) fail(`${path}.pieceKind`, "invalid_piece_kind", "Piece kind is invalid");
398
+ if (value.color !== "w" && value.color !== "b" && value.color !== null) fail(`${path}.color`, "invalid_piece_color", "Piece-kind color is invalid");
399
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
400
+ return { type: "piece-kind", pieceKind: pieceKind as "bishop" | "king" | "knight" | "pawn" | "queen" | "rook", color: value.color, ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }) };
401
+ }
402
+ if (type === "square") {
403
+ exactKeys(value, ["type", "square", "label"], path);
404
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
405
+ return { type: "square", square: nonEmptyString(value.square, `${path}.square`, 2), ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }) };
406
+ }
407
+ if (type === "move") {
408
+ exactKeys(value, ["type", "uci", "pieceId", "san", "label"], path);
409
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
410
+ return { type: "move", uci: nonEmptyString(value.uci, `${path}.uci`, 5), pieceId: nonEmptyString(value.pieceId, `${path}.pieceId`, 200), san: nonEmptyString(value.san, `${path}.san`, 40), ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }) };
411
+ }
412
+ if (type === "entity") {
413
+ exactKeys(value, ["type", "entityLocalId", "label"], path);
414
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
415
+ return { type: "entity", entityLocalId: nonEmptyString(value.entityLocalId, `${path}.entityLocalId`, 160), ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }) };
416
+ }
417
+ if (type === "relation") {
418
+ exactKeys(value, ["type", "relationLocalId", "fromEntityLocalId", "toEntityLocalId", "label"], path);
419
+ if (value.label !== undefined && value.label !== null) nonEmptyString(value.label, `${path}.label`, 120);
420
+ return {
421
+ type: "relation",
422
+ relationLocalId: nonEmptyString(value.relationLocalId, `${path}.relationLocalId`, 160),
423
+ fromEntityLocalId: nonEmptyString(value.fromEntityLocalId, `${path}.fromEntityLocalId`, 160),
424
+ toEntityLocalId: nonEmptyString(value.toEntityLocalId, `${path}.toEntityLocalId`, 160),
425
+ ...(value.label === undefined || value.label === null ? {} : { label: value.label as string }),
426
+ };
427
+ }
428
+ return fail(`${path}.type`, "unknown_segment_type", "Narrative segment type is not supported");
429
+ }
430
+
431
+ function parseClaim(value: unknown, path: string): DirectSynthesisClaim {
432
+ if (!isRecord(value)) return fail(path, "invalid_claim", "Claim must be an object");
433
+ exactKeys(value, ["localId", "kind", "content", "proofStrength", "evidenceIds", "qualifications"], path);
434
+ const kind = value.kind;
435
+ if (kind !== "fact" && kind !== "interpretation" && kind !== "consequence" && kind !== "defense" && kind !== "uncertainty") fail(`${path}.kind`, "invalid_claim_kind", "Claim kind is invalid");
436
+ if (!Array.isArray(value.content) || !value.content.length || value.content.length > 32) fail(`${path}.content`, "invalid_claim_content", "Claim requires bounded typed content");
437
+ if (!Array.isArray(value.qualifications) || value.qualifications.length > 8) fail(`${path}.qualifications`, "invalid_qualifications", "Claim qualifications must be bounded typed segment arrays");
438
+ return {
439
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160),
440
+ kind,
441
+ content: value.content.map((segment, index) => parseSegment(segment, `${path}.content[${index}]`)),
442
+ proofStrength: proof(value.proofStrength, `${path}.proofStrength`),
443
+ evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12, false),
444
+ qualifications: value.qualifications.map((qualification, index) => {
445
+ if (!Array.isArray(qualification) || !qualification.length || qualification.length > 16) fail(`${path}.qualifications[${index}]`, "invalid_qualification", "Each qualification requires typed content");
446
+ return qualification.map((segment, segmentIndex) => parseSegment(segment, `${path}.qualifications[${index}][${segmentIndex}]`));
447
+ }),
448
+ };
449
+ }
450
+
451
+ function parseHypothesis(value: unknown, path: string): ReasoningHypothesisProposal {
452
+ if (!isRecord(value)) return fail(path, "invalid_hypothesis", "Hypothesis must be an object");
453
+ exactKeys(value, ["localId", "entityType", "concepts", "owner", "participants", "attributes", "proofStrength", "evidenceIds", "sourceObservationIds", "parentHypothesisIds", "anchorRequirements"], path);
454
+ if (value.owner !== "w" && value.owner !== "b" && value.owner !== null) fail(`${path}.owner`, "invalid_owner", "Hypothesis owner is invalid");
455
+ if (!Array.isArray(value.participants) || value.participants.length > 12) fail(`${path}.participants`, "invalid_participants", "Hypothesis participants must be bounded");
456
+ const participants = value.participants.map((participant, index) => {
457
+ if (!isRecord(participant) || !isRecord(participant.ref)) fail(`${path}.participants[${index}]`, "invalid_participant", "Participant must have a typed reference");
458
+ exactKeys(participant, ["role", "ref"], `${path}.participants[${index}]`);
459
+ exactKeys(participant.ref, ["domain", "id"], `${path}.participants[${index}].ref`);
460
+ return {
461
+ role: nonEmptyString(participant.role, `${path}.participants[${index}].role`, 160),
462
+ ref: {
463
+ domain: nonEmptyString(participant.ref.domain, `${path}.participants[${index}].ref.domain`, 160),
464
+ id: nonEmptyString(participant.ref.id, `${path}.participants[${index}].ref.id`, 200),
465
+ },
466
+ };
467
+ });
468
+ if (!Array.isArray(value.anchorRequirements) || value.anchorRequirements.length > 12) fail(`${path}.anchorRequirements`, "invalid_anchor_requirements", "Hypothesis anchors must be bounded");
469
+ const anchorRequirements = value.anchorRequirements.map((anchor, index) => {
470
+ if (!isRecord(anchor) || !isRecord(anchor.participant) || !isRecord(anchor.participant.ref)) fail(`${path}.anchorRequirements[${index}]`, "invalid_anchor_requirement", "Anchor requirement is malformed");
471
+ exactKeys(anchor, ["participant", "disposition"], `${path}.anchorRequirements[${index}]`);
472
+ exactKeys(anchor.participant, ["role", "ref"], `${path}.anchorRequirements[${index}].participant`);
473
+ exactKeys(anchor.participant.ref, ["domain", "id"], `${path}.anchorRequirements[${index}].participant.ref`);
474
+ if (anchor.disposition !== "required") fail(`${path}.anchorRequirements[${index}].disposition`, "invalid_anchor_disposition", "Anchor disposition must be required");
475
+ return {
476
+ disposition: "required" as const,
477
+ participant: {
478
+ role: nonEmptyString(anchor.participant.role, `${path}.anchorRequirements[${index}].participant.role`, 160),
479
+ ref: {
480
+ domain: nonEmptyString(anchor.participant.ref.domain, `${path}.anchorRequirements[${index}].participant.ref.domain`, 160),
481
+ id: nonEmptyString(anchor.participant.ref.id, `${path}.anchorRequirements[${index}].participant.ref.id`, 200),
482
+ },
483
+ },
484
+ };
485
+ });
486
+ return {
487
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160),
488
+ entityType: nonEmptyString(value.entityType, `${path}.entityType`, 160),
489
+ concepts: stringArray(value.concepts, `${path}.concepts`, 4, false),
490
+ owner: value.owner,
491
+ participants,
492
+ attributes: parseAttributes(value.attributes, `${path}.attributes`) as ReasoningHypothesisProposal["attributes"],
493
+ proofStrength: proof(value.proofStrength, `${path}.proofStrength`),
494
+ evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12),
495
+ sourceObservationIds: stringArray(value.sourceObservationIds, `${path}.sourceObservationIds`, 12),
496
+ parentHypothesisIds: stringArray(value.parentHypothesisIds, `${path}.parentHypothesisIds`, 8),
497
+ anchorRequirements,
498
+ };
499
+ }
500
+
501
+ function parseRelation(value: unknown, path: string): DirectSynthesisRelationProposal {
502
+ if (!isRecord(value)) return fail(path, "invalid_relation", "Relation must be an object");
503
+ exactKeys(value, ["localId", "relationType", "from", "to", "attributes", "evidenceIds", "sourceObservationIds"], path);
504
+ return {
505
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160),
506
+ relationType: nonEmptyString(value.relationType, `${path}.relationType`, 160),
507
+ from: nonEmptyString(value.from, `${path}.from`, 160),
508
+ to: nonEmptyString(value.to, `${path}.to`, 160),
509
+ attributes: parseAttributes(value.attributes, `${path}.attributes`) as ReasoningHypothesisRelationProposal["attributes"],
510
+ evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12, false),
511
+ sourceObservationIds: stringArray(value.sourceObservationIds, `${path}.sourceObservationIds`, 12),
512
+ };
513
+ }
514
+
515
+ function parseCoverageLane(value: unknown, path: string): DirectSynthesisCoverage["responseCoverage"] {
516
+ if (!isRecord(value)) return fail(path, "invalid_coverage_lane", "Coverage lane must be structured");
517
+ exactKeys(value, ["status", "evidenceIds", "unresolved"], path);
518
+ if (value.status !== "complete" && value.status !== "partial" && value.status !== "unavailable") fail(`${path}.status`, "invalid_coverage_status", "Coverage status is invalid");
519
+ const evidenceIds = stringArray(value.evidenceIds, `${path}.evidenceIds`, 12);
520
+ const unresolved = stringArray(value.unresolved, `${path}.unresolved`, 24);
521
+ if (value.status === "complete" && unresolved.length) fail(`${path}.unresolved`, "complete_coverage_has_unresolved", "Complete coverage cannot retain unresolved obligations");
522
+ return { status: value.status, evidenceIds, unresolved };
523
+ }
524
+
525
+ function parseOmissions(value: unknown, path: string): Array<{ localId: string; reason: string }> {
526
+ if (!Array.isArray(value) || value.length > 24) fail(path, "invalid_omissions", "Omissions must be a bounded array");
527
+ const omissions = value.map((item, index) => {
528
+ if (!isRecord(item)) return fail(`${path}[${index}]`, "invalid_omission", "Omission must be structured");
529
+ exactKeys(item, ["localId", "reason"], `${path}[${index}]`);
530
+ return { localId: nonEmptyString(item.localId, `${path}[${index}].localId`, 160), reason: nonEmptyString(item.reason, `${path}[${index}].reason`, 500) };
531
+ });
532
+ if (new Set(omissions.map((item) => item.localId)).size !== omissions.length) fail(path, "duplicate_omission", "Omitted local IDs must be unique");
533
+ return omissions;
534
+ }
535
+
536
+ function parseCoverage(value: unknown, path: string): DirectSynthesisCoverage {
537
+ if (!isRecord(value)) return fail(path, "invalid_coverage", "Direct synthesis requires structured coverage");
538
+ exactKeys(value, ["responseCoverage", "continuationCoverage", "unresolvedEvidenceObligations", "omittedHypotheses", "omittedRelations"], path);
539
+ if (!Array.isArray(value.unresolvedEvidenceObligations) || value.unresolvedEvidenceObligations.length > 24) fail(`${path}.unresolvedEvidenceObligations`, "invalid_unresolved_obligations", "Unresolved evidence obligations must be bounded");
540
+ const unresolvedEvidenceObligations = value.unresolvedEvidenceObligations.map((item, index) => {
541
+ if (!isRecord(item)) return fail(`${path}.unresolvedEvidenceObligations[${index}]`, "invalid_unresolved_obligation", "Unresolved evidence obligation must be structured");
542
+ exactKeys(item, ["id", "reason"], `${path}.unresolvedEvidenceObligations[${index}]`);
543
+ return { id: nonEmptyString(item.id, `${path}.unresolvedEvidenceObligations[${index}].id`, 160), reason: nonEmptyString(item.reason, `${path}.unresolvedEvidenceObligations[${index}].reason`, 500) };
544
+ });
545
+ if (new Set(unresolvedEvidenceObligations.map((item) => item.id)).size !== unresolvedEvidenceObligations.length) fail(`${path}.unresolvedEvidenceObligations`, "duplicate_unresolved_obligation", "Unresolved evidence obligations must be unique");
546
+ return {
547
+ responseCoverage: parseCoverageLane(value.responseCoverage, `${path}.responseCoverage`),
548
+ continuationCoverage: parseCoverageLane(value.continuationCoverage, `${path}.continuationCoverage`),
549
+ unresolvedEvidenceObligations,
550
+ omittedHypotheses: parseOmissions(value.omittedHypotheses, `${path}.omittedHypotheses`),
551
+ omittedRelations: parseOmissions(value.omittedRelations, `${path}.omittedRelations`),
552
+ };
553
+ }
554
+
555
+ function parseDemonstration(value: unknown, path: string): DirectSynthesisDemonstration {
556
+ if (!isRecord(value)) return fail(path, "invalid_demonstration", "Demonstration must be an object");
557
+ const type = value.type;
558
+ if (type === "legal-variation") {
559
+ exactKeys(value, ["localId", "type", "start", "moves", "label", "evidenceIds"], path);
560
+ if (value.start !== "before" && value.start !== "after") fail(`${path}.start`, "invalid_variation_start", "Variation start must be before or after");
561
+ if (!Array.isArray(value.moves) || !value.moves.length || value.moves.length > 16) fail(`${path}.moves`, "invalid_variation_moves", "Variation requires bounded moves");
562
+ return {
563
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160), type, start: value.start,
564
+ moves: value.moves.map((move, index) => {
565
+ if (!isRecord(move)) return fail(`${path}.moves[${index}]`, "invalid_variation_move", "Variation move must be typed");
566
+ exactKeys(move, ["uci", "pieceId", "san"], `${path}.moves[${index}]`);
567
+ return { uci: nonEmptyString(move.uci, `${path}.moves[${index}].uci`, 5), pieceId: nonEmptyString(move.pieceId, `${path}.moves[${index}].pieceId`, 200), san: nonEmptyString(move.san, `${path}.moves[${index}].san`, 40) };
568
+ }),
569
+ label: nonEmptyString(value.label, `${path}.label`, 160), evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12, false),
570
+ };
571
+ }
572
+ if (type === "conditional-route") {
573
+ exactKeys(value, ["localId", "type", "pieceId", "waypoints", "prerequisiteRefs", "label", "evidenceIds"], path);
574
+ if (!Array.isArray(value.prerequisiteRefs) || !value.prerequisiteRefs.length || value.prerequisiteRefs.length > 16) fail(`${path}.prerequisiteRefs`, "invalid_route_prerequisites", "Route prerequisites require typed references");
575
+ return {
576
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160), type,
577
+ pieceId: nonEmptyString(value.pieceId, `${path}.pieceId`, 200), waypoints: stringArray(value.waypoints, `${path}.waypoints`, 16, false),
578
+ prerequisiteRefs: value.prerequisiteRefs.map((segment, index) => parseSegment(segment, `${path}.prerequisiteRefs[${index}]`)),
579
+ label: nonEmptyString(value.label, `${path}.label`, 160), evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12, false),
580
+ };
581
+ }
582
+ if (type === "static-relationship") {
583
+ exactKeys(value, ["localId", "type", "relationLocalId", "fromEntityLocalId", "toEntityLocalId", "label", "evidenceIds"], path);
584
+ return {
585
+ localId: nonEmptyString(value.localId, `${path}.localId`, 160), type,
586
+ relationLocalId: nonEmptyString(value.relationLocalId, `${path}.relationLocalId`, 160),
587
+ fromEntityLocalId: nonEmptyString(value.fromEntityLocalId, `${path}.fromEntityLocalId`, 160),
588
+ toEntityLocalId: nonEmptyString(value.toEntityLocalId, `${path}.toEntityLocalId`, 160),
589
+ label: nonEmptyString(value.label, `${path}.label`, 160), evidenceIds: stringArray(value.evidenceIds, `${path}.evidenceIds`, 12, false),
590
+ };
591
+ }
592
+ return fail(`${path}.type`, "invalid_demonstration_type", "Demonstration type is invalid");
593
+ }
594
+
595
+ /** Strict parser intended for a later model-runtime adapter. */
596
+ export function parseDirectSynthesisProposal(value: unknown): DirectSynthesisProposal {
597
+ assertNoCArtifacts(value);
598
+ if (!isRecord(value)) return fail("proposal", "invalid_proposal", "Direct synthesis proposal must be an object");
599
+ exactKeys(value, ["schemaVersion", "hypotheses", "relations", "claims", "demonstrations", "coverage", "limitations", "riskProfile"], "proposal");
600
+ if (value.schemaVersion !== ABLATION_DIRECT_SYNTHESIS_VERSION) fail("proposal.schemaVersion", "unsupported_version", "Unsupported direct synthesis schema version");
601
+ if (!Array.isArray(value.hypotheses) || !value.hypotheses.length || value.hypotheses.length > 16) fail("proposal.hypotheses", "invalid_hypotheses", "Direct synthesis requires one bounded hypothesis batch");
602
+ if (!Array.isArray(value.relations) || value.relations.length > 24) fail("proposal.relations", "invalid_relations", "Relations must be a bounded array");
603
+ if (!Array.isArray(value.claims) || !value.claims.length || value.claims.length > 16) fail("proposal.claims", "invalid_claims", "Direct synthesis requires bounded typed claims");
604
+ if (value.demonstrations !== undefined && value.demonstrations !== null && (!Array.isArray(value.demonstrations) || value.demonstrations.length > 8)) fail("proposal.demonstrations", "invalid_demonstrations", "Demonstrations must be a bounded array or null");
605
+ if (!isRecord(value.riskProfile)) fail("proposal.riskProfile", "invalid_risk_profile", "Risk profile must be structured");
606
+ exactKeys(value.riskProfile, ["level", "reasons"], "proposal.riskProfile");
607
+ if (value.riskProfile.level !== "low" && value.riskProfile.level !== "medium" && value.riskProfile.level !== "high") fail("proposal.riskProfile.level", "invalid_risk_level", "Risk level is invalid");
608
+ return {
609
+ schemaVersion: ABLATION_DIRECT_SYNTHESIS_VERSION,
610
+ hypotheses: value.hypotheses.map((hypothesis, index) => parseHypothesis(hypothesis, `proposal.hypotheses[${index}]`)),
611
+ relations: value.relations.map((relation, index) => parseRelation(relation, `proposal.relations[${index}]`)),
612
+ claims: value.claims.map((claim, index) => parseClaim(claim, `proposal.claims[${index}]`)),
613
+ ...(value.demonstrations === undefined || value.demonstrations === null ? {} : { demonstrations: value.demonstrations.map((demonstration, index) => parseDemonstration(demonstration, `proposal.demonstrations[${index}]`)) }),
614
+ coverage: parseCoverage(value.coverage, "proposal.coverage"),
615
+ limitations: stringArray(value.limitations, "proposal.limitations", 24),
616
+ riskProfile: { level: value.riskProfile.level, reasons: stringArray(value.riskProfile.reasons, "proposal.riskProfile.reasons", 12) },
617
+ };
618
+ }
619
+
620
+ function proofRank(value: ProofStrength): number {
621
+ return value === "observed" ? 1 : value === "supported" ? 2 : 3;
622
+ }
623
+
624
+ function assertDistinctLocalIds(proposal: DirectSynthesisProposal): void {
625
+ const localIds = [
626
+ ...proposal.hypotheses.map((item) => item.localId),
627
+ ...proposal.relations.map((item) => item.localId),
628
+ ...proposal.claims.map((item) => item.localId),
629
+ ...(proposal.demonstrations || []).map((item) => item.localId),
630
+ ];
631
+ if (new Set(localIds).size !== localIds.length) fail("proposal", "duplicate_local_id", "Model-local IDs must be unique within one direct synthesis result");
632
+ }
633
+
634
+ function admittedEvidenceIds(bundle: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], path: string) {
635
+ const allowed = new Set(bundle.referenceAllowlists.evidenceIds);
636
+ const records = new Map(bundle.evidence.map((record) => [record.id, record]));
637
+ for (const id of ids) if (!allowed.has(id) || !records.has(id)) fail(path, "unknown_evidence", "Evidence is outside the frozen closure", [...allowed]);
638
+ return ids.map((id) => records.get(id)!);
639
+ }
640
+
641
+ function assertEvidenceProof(bundle: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], path: string, requestedProof: ProofStrength | null) {
642
+ const records = admittedEvidenceIds(bundle, ids, path);
643
+ if (requestedProof && !records.some((record) => record.proofStrength && proofRank(record.proofStrength) >= proofRank(requestedProof))) {
644
+ fail(path, "unsupported_proof_strength", "Claimed proof strength exceeds cited evidence");
645
+ }
646
+ return records;
647
+ }
648
+
649
+ function hasMeaningLicense(record: ReturnType<typeof admittedEvidenceIds>): boolean {
650
+ return record.some((item) => licensesSemanticChessGrounding(effectiveLicensesForRecord(item)));
651
+ }
652
+
653
+ function assertEvidenceSupportsMeaning(bundle: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], path: string, requestedProof: ProofStrength | null): void {
654
+ const records = assertEvidenceProof(bundle, ids, path, requestedProof);
655
+ if (!hasMeaningLicense(records)) fail(path, "underlicensed_semantic_meaning", "Semantic meaning requires centrally licensed position, bounded-engine, or reviewed-semantic evidence; legality and identity alone are insufficient");
656
+ }
657
+
658
+ function assertEvidenceSupportsRouteMeaning(bundle: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], path: string): void {
659
+ const records = admittedEvidenceIds(bundle, ids, path);
660
+ if (!records.some((record) => licensesBoundedRouteRelevance(effectiveLicensesForRecord(record)))) {
661
+ fail(path, "underlicensed_route_meaning", "Conditional routes and plans require bounded continuation, comparison, or response-coverage evidence");
662
+ }
663
+ }
664
+
665
+ function assertEvidenceSupportsClaim(bundle: Readonly<AblationEvidenceClosureBundle>, ids: readonly string[], path: string, kind: ReasoningClaimKind, requestedProof: ProofStrength | null): void {
666
+ const records = assertEvidenceProof(bundle, ids, path, requestedProof);
667
+ const licenses = records.flatMap((record) => effectiveLicensesForRecord(record));
668
+ if (kind === "fact" || kind === "uncertainty") {
669
+ if (!licenses.some((license) => license === EVIDENCE_LICENSE.CHESS_LEGALITY || license === EVIDENCE_LICENSE.CHESS_IDENTITY || license === EVIDENCE_LICENSE.CHESS_POSITION_FACT || license.startsWith("engine."))) {
670
+ fail(path, "underlicensed_fact_claim", "Fact claim lacks exact or bounded-engine evidence");
671
+ }
672
+ return;
673
+ }
674
+ if (kind === "interpretation" || kind === "consequence") {
675
+ if (!hasMeaningLicense(records)) fail(path, "underlicensed_semantic_meaning", "Interpretation/consequence claims cannot be grounded only by legality, identity, or bounded comparison score evidence");
676
+ return;
677
+ }
678
+ if (!licenses.includes(EVIDENCE_LICENSE.ENGINE_BOUNDED_RESPONSE_COVERAGE) && !licenses.includes(EVIDENCE_LICENSE.ENGINE_BOUNDED_CONTINUATION)) {
679
+ fail(path, "underlicensed_defense_claim", "Defense claims require bounded response or continuation evidence");
680
+ }
681
+ }
682
+
683
+ type ReferenceAuthority = {
684
+ pieces: Set<string>;
685
+ squares: Set<string>;
686
+ moveIds: Set<string>;
687
+ moves: Map<string, Set<string>>;
688
+ };
689
+
690
+ function referenceAuthority(bundle: Readonly<AblationEvidenceClosureBundle>): ReferenceAuthority {
691
+ const pieces = new Set(bundle.referenceAllowlists.semanticReferences.filter((item) => item.domain === REFERENCE_DOMAINS.PIECE).map((item) => item.id));
692
+ const squares = new Set(bundle.referenceAllowlists.semanticReferences.filter((item) => item.domain === REFERENCE_DOMAINS.SQUARE).map((item) => item.id));
693
+ const moveIds = new Set(bundle.referenceAllowlists.semanticReferences.filter((item) => item.domain === REFERENCE_DOMAINS.MOVE).map((item) => item.id));
694
+ const moves = new Map<string, Set<string>>();
695
+ for (const witness of bundle.referenceAllowlists.moveWitnesses) {
696
+ const key = `${witness.uci}:${witness.pieceId}:${witness.san}`;
697
+ const evidence = moves.get(key) || new Set<string>();
698
+ evidence.add(witness.evidenceId);
699
+ moves.set(key, evidence);
700
+ }
701
+ return { pieces, squares, moveIds, moves };
702
+ }
703
+
704
+ function addClosureReferences(value: unknown, global: ReferenceAuthority, scoped: { pieces: Set<string>; squares: Set<string> }): void {
705
+ if (typeof value === "string") {
706
+ if (global.pieces.has(value)) scoped.pieces.add(value);
707
+ if (global.squares.has(value)) scoped.squares.add(value);
708
+ return;
709
+ }
710
+ if (Array.isArray(value)) {
711
+ value.forEach((item) => addClosureReferences(item, global, scoped));
712
+ return;
713
+ }
714
+ if (isRecord(value)) Object.values(value).forEach((item) => addClosureReferences(item, global, scoped));
715
+ }
716
+
717
+ function scopedReferenceAuthority(
718
+ bundle: Readonly<AblationEvidenceClosureBundle>,
719
+ evidenceIds: readonly string[],
720
+ semanticParticipants: Array<{ ref: { domain: string; id: string } }> = [],
721
+ ): ReferenceAuthority {
722
+ const global = referenceAuthority(bundle);
723
+ const pieces = new Set<string>();
724
+ const squares = new Set<string>();
725
+ const moves = new Map<string, Set<string>>();
726
+ const addWitness = (witness: { evidenceId: string; uci: string; pieceId: string; san: string }) => {
727
+ if (!global.moveIds.has(witness.uci)) return;
728
+ const key = `${witness.uci}:${witness.pieceId}:${witness.san}`;
729
+ const sources = moves.get(key) || new Set<string>();
730
+ sources.add(witness.evidenceId);
731
+ moves.set(key, sources);
732
+ if (global.pieces.has(witness.pieceId)) pieces.add(witness.pieceId);
733
+ if (global.squares.has(witness.uci.slice(0, 2))) squares.add(witness.uci.slice(0, 2));
734
+ if (global.squares.has(witness.uci.slice(2, 4))) squares.add(witness.uci.slice(2, 4));
735
+ };
736
+ bundle.referenceAllowlists.moveWitnesses.filter((witness) => witness.uci === bundle.context.moveUci).forEach(addWitness);
737
+ const requested = new Set(evidenceIds);
738
+ for (const record of bundle.evidence) {
739
+ if (!requested.has(record.id)) continue;
740
+ addClosureReferences(record.data, global, { pieces, squares });
741
+ collectReasoningMoveWitnesses([record], bundle.context).forEach(addWitness);
742
+ }
743
+ for (const participant of semanticParticipants) {
744
+ if (participant.ref.domain === REFERENCE_DOMAINS.PIECE && global.pieces.has(participant.ref.id)) pieces.add(participant.ref.id);
745
+ if (participant.ref.domain === REFERENCE_DOMAINS.SQUARE && global.squares.has(participant.ref.id)) squares.add(participant.ref.id);
746
+ }
747
+ return { pieces, squares, moveIds: global.moveIds, moves };
748
+ }
749
+
750
+ function assertTypedParticipantReference(
751
+ reference: { domain: string; id: string },
752
+ evidenceIds: readonly string[],
753
+ authority: ReferenceAuthority,
754
+ path: string,
755
+ ): void {
756
+ if (reference.domain === REFERENCE_DOMAINS.PIECE) {
757
+ if (!authority.pieces.has(reference.id)) fail(path, "participant_outside_closure", "Piece participant is outside the frozen semantic-reference allowlist", [...authority.pieces]);
758
+ return;
759
+ }
760
+ if (reference.domain === REFERENCE_DOMAINS.SQUARE) {
761
+ if (!authority.squares.has(reference.id)) fail(path, "participant_outside_closure", "Square participant is outside the frozen semantic-reference allowlist", [...authority.squares]);
762
+ return;
763
+ }
764
+ if (reference.domain === REFERENCE_DOMAINS.MOVE) {
765
+ const witnessed = [...authority.moves.entries()].some(([key, evidence]) => key.startsWith(`${reference.id}:`) && [...evidence].some((id) => evidenceIds.includes(id)));
766
+ if (!authority.moveIds.has(reference.id) || !witnessed) fail(path, "participant_move_unwitnessed", "Move participant lacks a typed allowlist entry or cited exact witness in the frozen closure", [...authority.moves.keys()]);
767
+ return;
768
+ }
769
+ fail(path, "participant_outside_closure", "Participant domain is outside the frozen semantic-reference allowlist", [REFERENCE_DOMAINS.PIECE, REFERENCE_DOMAINS.SQUARE, REFERENCE_DOMAINS.MOVE]);
770
+ }
771
+
772
+ function assertHypothesisReferenceAuthority(
773
+ hypothesis: ReasoningHypothesisProposal,
774
+ evidenceIds: readonly string[],
775
+ authority: ReferenceAuthority,
776
+ path: string,
777
+ ): void {
778
+ hypothesis.participants.forEach((participant, index) => assertTypedParticipantReference(participant.ref, evidenceIds, authority, `${path}.participants[${index}].ref`));
779
+ hypothesis.anchorRequirements.forEach((anchor, index) => assertTypedParticipantReference(anchor.participant.ref, evidenceIds, authority, `${path}.anchorRequirements[${index}].participant.ref`));
780
+ }
781
+
782
+ function hasEvidenceAssociation(itemEvidenceIds: readonly string[], semanticEvidenceIds: readonly string[]): boolean {
783
+ return itemEvidenceIds.some((id) => semanticEvidenceIds.includes(id));
784
+ }
785
+
786
+ function semanticParticipantsForSegments(
787
+ segments: readonly DirectSynthesisSegment[],
788
+ hypothesesByLocalId: ReadonlyMap<string, ReasoningHypothesisProposal>,
789
+ relationsByLocalId: ReadonlyMap<string, DirectSynthesisRelationProposal>,
790
+ ): Array<{ ref: { domain: string; id: string } }> {
791
+ const participants: Array<{ ref: { domain: string; id: string } }> = [];
792
+ const addHypothesis = (localId: string) => {
793
+ const hypothesis = hypothesesByLocalId.get(localId);
794
+ if (hypothesis) participants.push(...hypothesis.participants);
795
+ };
796
+ for (const segment of segments) {
797
+ if (segment.type === "entity") addHypothesis(segment.entityLocalId);
798
+ if (segment.type === "relation") {
799
+ const relation = relationsByLocalId.get(segment.relationLocalId);
800
+ if (relation) {
801
+ addHypothesis(relation.from);
802
+ addHypothesis(relation.to);
803
+ }
804
+ }
805
+ }
806
+ return participants;
807
+ }
808
+
809
+ function remapSegments(
810
+ segments: readonly DirectSynthesisSegment[],
811
+ path: string,
812
+ evidenceIds: readonly string[],
813
+ authority: ReferenceAuthority,
814
+ localToCanonical: Readonly<Record<string, string>>,
815
+ relationByLocalId: ReadonlyMap<string, ReasoningHypothesisRelationRecord>,
816
+ hypothesisByCanonicalId: ReadonlyMap<string, ReasoningHypothesisRecord>,
817
+ semanticParticipants: Array<{ ref: { domain: string; id: string } }>,
818
+ bundle: Readonly<AblationEvidenceClosureBundle>,
819
+ ): ReasoningNarrativeSegment[] {
820
+ const itemAuthority = scopedReferenceAuthority(bundle, evidenceIds, semanticParticipants);
821
+ const evidenceSet = new Set(evidenceIds);
822
+ const timeline = createPocIdentityTimeline(bundle.context);
823
+ return segments.map((segment, index): ReasoningNarrativeSegment => {
824
+ const segmentPath = `${path}[${index}]`;
825
+ if (segment.type === "text") return segment;
826
+ if (segment.type === "piece") {
827
+ if (!authority.pieces.has(segment.pieceId) || (!pieceIdentity(timeline.before, segment.pieceId) && !pieceIdentity(timeline.after, segment.pieceId))) {
828
+ fail(segmentPath, "untyped_piece_reference", "Piece reference is not admitted by the frozen closure", [...authority.pieces]);
829
+ }
830
+ if (!itemAuthority.pieces.has(segment.pieceId)) {
831
+ fail(segmentPath, "item_unscoped_piece_reference", "Piece reference is not grounded by this item's target, cited evidence, or typed semantic binding", [...itemAuthority.pieces]);
832
+ }
833
+ return segment;
834
+ }
835
+ if (segment.type === "piece-kind") {
836
+ const type = ({ bishop: "b", king: "k", knight: "n", pawn: "p", queen: "q", rook: "r" } as const)[segment.pieceKind];
837
+ const authorized = [...itemAuthority.pieces].some((pieceId) => {
838
+ const identity = pieceIdentity(timeline.before, pieceId) || pieceIdentity(timeline.after, pieceId);
839
+ return identity?.type === type && (segment.color === null || identity.color === segment.color);
840
+ });
841
+ if (!authorized) fail(segmentPath, "item_unscoped_piece_kind_reference", "Generic piece reference is not grounded by this item's authority", [...itemAuthority.pieces]);
842
+ return segment;
843
+ }
844
+ if (segment.type === "square") {
845
+ if (!SQUARES.includes(segment.square as typeof SQUARES[number]) || !authority.squares.has(segment.square)) {
846
+ fail(segmentPath, "untyped_square_reference", "Square reference is not admitted by the frozen closure", [...authority.squares]);
847
+ }
848
+ if (!itemAuthority.squares.has(segment.square)) {
849
+ fail(segmentPath, "item_unscoped_square_reference", "Square reference is not grounded by this item's target, cited evidence, or typed semantic binding", [...itemAuthority.squares]);
850
+ }
851
+ return segment;
852
+ }
853
+ if (segment.type === "move") {
854
+ const witnesses = itemAuthority.moves.get(`${segment.uci}:${segment.pieceId}:${segment.san}`);
855
+ if (!itemAuthority.moveIds.has(segment.uci) || !witnesses || ![...witnesses].some((id) => evidenceSet.has(id))) {
856
+ fail(segmentPath, "untyped_move_reference", "Move reference lacks an exact cited evidence witness", [...itemAuthority.moves.keys()]);
857
+ }
858
+ return { type: "move", uci: segment.uci, pieceId: segment.pieceId, san: segment.san, ...(segment.label === undefined ? {} : { label: segment.label }) };
859
+ }
860
+ if (segment.type === "entity") {
861
+ const entityId = localToCanonical[segment.entityLocalId];
862
+ if (!entityId) fail(segmentPath, "unknown_local_entity", "Entity local reference is not declared by this proposal");
863
+ const hypothesis = hypothesisByCanonicalId.get(entityId);
864
+ if (!hypothesis || !hasEvidenceAssociation(evidenceIds, hypothesis.evidenceIds)) {
865
+ fail(segmentPath, "unrelated_semantic_evidence", "Entity segment must cite evidence associated with its canonical hypothesis");
866
+ }
867
+ return { type: "entity", entityId, ...(segment.label === undefined ? {} : { label: segment.label }) };
868
+ }
869
+ const relation = relationByLocalId.get(segment.relationLocalId);
870
+ const fromEntityId = localToCanonical[segment.fromEntityLocalId];
871
+ const toEntityId = localToCanonical[segment.toEntityLocalId];
872
+ if (!relation || !fromEntityId || !toEntityId) fail(segmentPath, "dangling_local_relation", "Relation local reference is not declared by this proposal");
873
+ if (relation.fromHypothesisId !== fromEntityId || relation.toHypothesisId !== toEntityId) {
874
+ fail(segmentPath, "relation_endpoint_mismatch", "Relation segment endpoints do not match the canonical relation");
875
+ }
876
+ if (!hasEvidenceAssociation(evidenceIds, relation.evidenceIds)) {
877
+ fail(segmentPath, "unrelated_semantic_evidence", "Relation segment must cite evidence associated with its canonical relation");
878
+ }
879
+ return { type: "relation", relationId: relation.id, fromEntityId, toEntityId, ...(segment.label === undefined ? {} : { label: segment.label }) };
880
+ });
881
+ }
882
+
883
+ function validateVariation(
884
+ demonstration: Extract<DirectSynthesisDemonstration, { type: "legal-variation" }>,
885
+ bundle: Readonly<AblationEvidenceClosureBundle>,
886
+ path: string,
887
+ ): void {
888
+ const citedEvidence = new Set(demonstration.evidenceIds);
889
+ const witnesses = collectReasoningMoveWitnesses(
890
+ bundle.evidence.filter((record) => citedEvidence.has(record.id)),
891
+ bundle.context,
892
+ );
893
+ const submitted = demonstration.moves.map((move) => `${move.uci}:${move.pieceId}:${move.san}`);
894
+ for (const key of submitted) if (!witnesses.some((witness) => `${witness.uci}:${witness.pieceId}:${witness.san}` === key)) {
895
+ fail(path, "variation_witness_missing", "Variation move lacks an exact cited evidence witness");
896
+ }
897
+ const starts = bundle.evidence.filter((record) => citedEvidence.has(record.id)).flatMap((record) => {
898
+ const replay = record.replay;
899
+ if (!replay) return [];
900
+ const start = replay.fen === bundle.context.fenBefore ? "before" : replay.fen === bundle.context.fenAfter ? "after" : null;
901
+ const moves = Array.isArray(replay.movesUci) ? replay.movesUci : Array.isArray(replay.moves) ? replay.moves : Array.isArray(replay.line) ? replay.line : typeof replay.move === "string" ? [replay.move] : [];
902
+ return start && moves.every((move) => typeof move === "string") ? [{ start, moves }] : [];
903
+ });
904
+ const submittedUci = demonstration.moves.map((move) => move.uci);
905
+ if (!starts.some((candidate) => candidate.start === demonstration.start && submittedUci.every((move, index) => candidate.moves[index] === move))) {
906
+ fail(path, "variation_sequence_not_grounded", "Variation must be an exact prefix of one cited replayable evidence line");
907
+ }
908
+ }
909
+
910
+ function assertCoverageEvidence(bundle: Readonly<AblationEvidenceClosureBundle>, coverage: DirectSynthesisCoverage): void {
911
+ for (const [path, lane] of [["responseCoverage", coverage.responseCoverage], ["continuationCoverage", coverage.continuationCoverage]] as const) {
912
+ admittedEvidenceIds(bundle, lane.evidenceIds, `proposal.coverage.${path}.evidenceIds`);
913
+ }
914
+ }
915
+
916
+ function validateAndMaterializeCoverage(
917
+ coverage: DirectSynthesisCoverage,
918
+ records: readonly ReasoningHypothesisRecord[],
919
+ relations: readonly ReasoningHypothesisRelationRecord[],
920
+ localToCanonical: Readonly<Record<string, string>>,
921
+ relationByLocalId: ReadonlyMap<string, ReasoningHypothesisRelationRecord>,
922
+ claims: readonly CanonicalDirectSynthesisClaim[],
923
+ demonstrations: readonly CanonicalDirectSynthesisDemonstration[],
924
+ ): DirectSynthesisArtifact["coverage"] {
925
+ const omittedHypotheses = coverage.omittedHypotheses.map((item) => {
926
+ const hypothesisId = localToCanonical[item.localId];
927
+ if (!hypothesisId) fail("proposal.coverage.omittedHypotheses", "unknown_omitted_hypothesis", "Omitted hypothesis does not name a local hypothesis");
928
+ return { hypothesisId, reason: item.reason };
929
+ });
930
+ const omittedRelations = coverage.omittedRelations.map((item) => {
931
+ const relation = relationByLocalId.get(item.localId);
932
+ if (!relation) fail("proposal.coverage.omittedRelations", "unknown_omitted_relation", "Omitted relation does not name a local relation");
933
+ return { relationId: relation.id, reason: item.reason };
934
+ });
935
+ const presentedHypothesisIds = new Set<string>();
936
+ const presentedRelationIds = new Set<string>();
937
+ const visitSegments = (segments: readonly ReasoningNarrativeSegment[]) => {
938
+ for (const segment of segments) {
939
+ if (segment.type === "entity") presentedHypothesisIds.add(segment.entityId);
940
+ if (segment.type === "relation") {
941
+ presentedRelationIds.add(segment.relationId);
942
+ presentedHypothesisIds.add(segment.fromEntityId);
943
+ presentedHypothesisIds.add(segment.toEntityId);
944
+ }
945
+ }
946
+ };
947
+ claims.forEach((claim) => { visitSegments(claim.content); claim.qualifications.forEach(visitSegments); });
948
+ demonstrations.forEach((demonstration) => {
949
+ if (demonstration.type === "conditional-route") visitSegments(demonstration.prerequisiteRefs);
950
+ if (demonstration.type === "static-relationship") {
951
+ presentedRelationIds.add(demonstration.relationId);
952
+ presentedHypothesisIds.add(demonstration.fromEntityId);
953
+ presentedHypothesisIds.add(demonstration.toEntityId);
954
+ }
955
+ });
956
+ const omittedHypothesisIds = new Set(omittedHypotheses.map((item) => item.hypothesisId));
957
+ const omittedRelationIds = new Set(omittedRelations.map((item) => item.relationId));
958
+ for (const record of records) {
959
+ if (!presentedHypothesisIds.has(record.id) && !omittedHypothesisIds.has(record.id)) {
960
+ fail("proposal.coverage.omittedHypotheses", "uncommunicated_hypothesis", "Every emitted hypothesis must be typed-presented or explicitly omitted");
961
+ }
962
+ if (presentedHypothesisIds.has(record.id) && omittedHypothesisIds.has(record.id)) {
963
+ fail("proposal.coverage.omittedHypotheses", "presented_and_omitted_hypothesis", "A hypothesis cannot be both presented and omitted");
964
+ }
965
+ }
966
+ for (const relation of relations) {
967
+ if (!presentedRelationIds.has(relation.id) && !omittedRelationIds.has(relation.id)) {
968
+ fail("proposal.coverage.omittedRelations", "uncommunicated_relation", "Every emitted relation must be typed-presented or explicitly omitted");
969
+ }
970
+ if (presentedRelationIds.has(relation.id) && omittedRelationIds.has(relation.id)) {
971
+ fail("proposal.coverage.omittedRelations", "presented_and_omitted_relation", "A relation cannot be both presented and omitted");
972
+ }
973
+ }
974
+ return {
975
+ responseCoverage: { ...coverage.responseCoverage, evidenceIds: [...coverage.responseCoverage.evidenceIds], unresolved: [...coverage.responseCoverage.unresolved] },
976
+ continuationCoverage: { ...coverage.continuationCoverage, evidenceIds: [...coverage.continuationCoverage.evidenceIds], unresolved: [...coverage.continuationCoverage.unresolved] },
977
+ unresolvedEvidenceObligations: coverage.unresolvedEvidenceObligations.map((item) => ({ ...item })),
978
+ omittedHypothesisIds: omittedHypotheses,
979
+ omittedRelationIds: omittedRelations,
980
+ };
981
+ }
982
+
983
+ /**
984
+ * Deterministically validates and materializes a direct A1/B/B-prime result.
985
+ * It returns a typed failure instead of narrowing, repairing, or inventing
986
+ * chess meaning.
987
+ */
988
+ export function materializeDirectSynthesis(input: MaterializeDirectSynthesisInput): DirectSynthesisMaterializationResult {
989
+ const metadata = { arm: input.arm };
990
+ try {
991
+ if (input.arm !== "A1" && input.arm !== "B" && input.arm !== "B-prime") fail("arm", "invalid_arm", "Direct synthesis arm is invalid");
992
+ validateAblationEvidenceClosureBundle(input.bundle);
993
+ // The supplied bundle remains the immutable audit closure. Every direct
994
+ // proposal is admitted against its model-facing projection, so a hidden
995
+ // fact/observation cannot be cited just because it exists for evaluation.
996
+ const sourceBundleHash = input.bundle.bundleHash;
997
+ input = { ...input, bundle: projectAblationEvidenceForModel(input.bundle).bundle };
998
+ const proposal = parseDirectSynthesisProposal(input.proposal);
999
+ assertDistinctLocalIds(proposal);
1000
+ const authority = referenceAuthority(input.bundle);
1001
+ assertCoverageEvidence(input.bundle, proposal.coverage);
1002
+ const hypothesisIds = new Set(proposal.hypotheses.map((item) => item.localId));
1003
+ for (const [index, relation] of proposal.relations.entries()) {
1004
+ if (!hypothesisIds.has(relation.from) || !hypothesisIds.has(relation.to)) fail(`proposal.relations[${index}]`, "dangling_local_relation", "Relation endpoint does not name a local hypothesis");
1005
+ assertEvidenceSupportsMeaning(input.bundle, relation.evidenceIds, `proposal.relations[${index}].evidenceIds`, null);
1006
+ }
1007
+ for (const [index, hypothesis] of proposal.hypotheses.entries()) {
1008
+ assertEvidenceSupportsMeaning(input.bundle, hypothesis.evidenceIds, `proposal.hypotheses[${index}].evidenceIds`, hypothesis.proofStrength);
1009
+ if (hypothesis.entityType === "plan") assertEvidenceSupportsRouteMeaning(input.bundle, hypothesis.evidenceIds, `proposal.hypotheses[${index}].evidenceIds`);
1010
+ assertHypothesisReferenceAuthority(
1011
+ hypothesis,
1012
+ hypothesis.evidenceIds,
1013
+ scopedReferenceAuthority(input.bundle, hypothesis.evidenceIds),
1014
+ `proposal.hypotheses[${index}]`,
1015
+ );
1016
+ for (const observationId of hypothesis.sourceObservationIds) {
1017
+ if (!input.bundle.referenceAllowlists.observationIds.includes(observationId)) fail(`proposal.hypotheses[${index}].sourceObservationIds`, "unknown_observation", "Source observation is outside the frozen closure");
1018
+ }
1019
+ }
1020
+ const observations = {
1021
+ schemaVersion: input.bundle.observations[0]?.schemaVersion || "conceptual-explanation-poc/57",
1022
+ contextHash: input.bundle.contextHash,
1023
+ observations: input.bundle.observations.map((observation) => ({ ...observation, salience: 1, salienceReasons: ["frozen-ablation-closure"], presentedToModel: true })),
1024
+ evidence: input.bundle.evidence,
1025
+ providers: [], limitations: [...input.bundle.coverage.limitations], saliencePolicy: "ablation-direct-frozen-closure/1",
1026
+ };
1027
+ const ledger = new HypothesisLedger(
1028
+ input.bundle.contextHash,
1029
+ observations,
1030
+ new Map(input.bundle.evidence.map((record) => [record.id, record])),
1031
+ proposal.hypotheses.length,
1032
+ input.bundle.context,
1033
+ );
1034
+ const recorded = ledger.record(
1035
+ {
1036
+ hypotheses: proposal.hypotheses,
1037
+ relations: proposal.relations.map((relation) => {
1038
+ const withoutLocalId = { ...relation };
1039
+ Reflect.deleteProperty(withoutLocalId, "localId");
1040
+ return withoutLocalId;
1041
+ }),
1042
+ },
1043
+ input.provenance || { provider: "knights-eye/ablation-direct", version: ABLATION_DIRECT_SYNTHESIS_VERSION, origin: "model" },
1044
+ );
1045
+ const evidenceById = new Map(input.bundle.evidence.map((record) => [record.id, record]));
1046
+ const observationById = new Map(input.bundle.observations.map((observation) => [observation.id, observation]));
1047
+ for (const [index, record] of recorded.records.entries()) {
1048
+ const policyIssues = validateHypothesisAcceptancePolicies(record, evidenceById);
1049
+ if (policyIssues.length) fail(`proposal.hypotheses[${index}]`, "acceptance_policy_rejected", "Direct hypothesis fails a registered production acceptance policy");
1050
+ const usesConditionalRouteAffordance = record.sourceObservationIds.some((id) => observationById.get(id)?.observationType === "conditional-route-affordance");
1051
+ if (usesConditionalRouteAffordance && !record.evidenceIds.some((id) => {
1052
+ const evidence = evidenceById.get(id);
1053
+ return Boolean(evidence && licensesBoundedRouteRelevance(effectiveLicensesForRecord(evidence)));
1054
+ })) {
1055
+ fail(`proposal.hypotheses[${index}].evidenceIds`, "underlicensed_route_meaning", "Hypotheses sourced from conditional-route affordances require separate bounded route-relevance evidence");
1056
+ }
1057
+ }
1058
+ const relationByLocalId = new Map(proposal.relations.map((relation, index) => [relation.localId, recorded.relations[index]] as const));
1059
+ const hypothesisByCanonicalId = new Map(recorded.records.map((record) => [record.id, record]));
1060
+ const hypothesesByLocalId = new Map(proposal.hypotheses.map((hypothesis) => [hypothesis.localId, hypothesis]));
1061
+ const relationsByLocalId = new Map(proposal.relations.map((relation) => [relation.localId, relation]));
1062
+ const claimIds = new Set<string>();
1063
+ const claims = proposal.claims.map((claim, index): CanonicalDirectSynthesisClaim => {
1064
+ if (claimIds.has(claim.localId)) fail(`proposal.claims[${index}].localId`, "duplicate_local_id", "Claim local ID is duplicated");
1065
+ claimIds.add(claim.localId);
1066
+ assertEvidenceSupportsClaim(input.bundle, claim.evidenceIds, `proposal.claims[${index}].evidenceIds`, claim.kind, claim.proofStrength);
1067
+ const semanticParticipants = semanticParticipantsForSegments([...claim.content, ...claim.qualifications.flat()], hypothesesByLocalId, relationsByLocalId);
1068
+ const content = remapSegments(claim.content, `proposal.claims[${index}].content`, claim.evidenceIds, authority, recorded.localToHypothesisIds, relationByLocalId, hypothesisByCanonicalId, semanticParticipants, input.bundle);
1069
+ const qualifications = claim.qualifications.map((segments, qualificationIndex) => remapSegments(segments, `proposal.claims[${index}].qualifications[${qualificationIndex}]`, claim.evidenceIds, authority, recorded.localToHypothesisIds, relationByLocalId, hypothesisByCanonicalId, semanticParticipants, input.bundle));
1070
+ return { id: `direct-claim:${stableHash({ contextHash: input.bundle.contextHash, kind: claim.kind, content, evidenceIds: claim.evidenceIds, qualifications })}`, kind: claim.kind, content, proofStrength: claim.proofStrength, evidenceIds: [...claim.evidenceIds], qualifications };
1071
+ });
1072
+ const demonstrationIds = new Set<string>();
1073
+ const demonstrations = (proposal.demonstrations || []).map((demonstration, index): CanonicalDirectSynthesisDemonstration => {
1074
+ if (demonstrationIds.has(demonstration.localId)) fail(`proposal.demonstrations[${index}].localId`, "duplicate_local_id", "Demonstration local ID is duplicated");
1075
+ demonstrationIds.add(demonstration.localId);
1076
+ if (demonstration.type === "legal-variation") {
1077
+ admittedEvidenceIds(input.bundle, demonstration.evidenceIds, `proposal.demonstrations[${index}].evidenceIds`);
1078
+ validateVariation(demonstration, input.bundle, `proposal.demonstrations[${index}].moves`);
1079
+ return { id: `direct-demonstration:${stableHash({ contextHash: input.bundle.contextHash, type: demonstration.type, start: demonstration.start, moves: demonstration.moves, evidenceIds: demonstration.evidenceIds })}`, type: demonstration.type, start: demonstration.start, moves: demonstration.moves.map((move) => ({ ...move })), label: demonstration.label, evidenceIds: [...demonstration.evidenceIds] };
1080
+ }
1081
+ if (demonstration.type === "static-relationship") {
1082
+ assertEvidenceSupportsMeaning(input.bundle, demonstration.evidenceIds, `proposal.demonstrations[${index}].evidenceIds`, null);
1083
+ const relation = relationByLocalId.get(demonstration.relationLocalId);
1084
+ const fromEntityId = recorded.localToHypothesisIds[demonstration.fromEntityLocalId];
1085
+ const toEntityId = recorded.localToHypothesisIds[demonstration.toEntityLocalId];
1086
+ if (!relation || !fromEntityId || !toEntityId) fail(`proposal.demonstrations[${index}]`, "dangling_local_relation", "Static relationship does not name direct local records");
1087
+ if (relation.fromHypothesisId !== fromEntityId || relation.toHypothesisId !== toEntityId) fail(`proposal.demonstrations[${index}]`, "relation_endpoint_mismatch", "Static relationship endpoints do not match the canonical relation");
1088
+ if (!hasEvidenceAssociation(demonstration.evidenceIds, relation.evidenceIds)) fail(`proposal.demonstrations[${index}].evidenceIds`, "unrelated_semantic_evidence", "Static relationship must cite evidence associated with its canonical relation");
1089
+ return { id: `direct-demonstration:${stableHash({ contextHash: input.bundle.contextHash, type: demonstration.type, relationId: relation.id, fromEntityId, toEntityId, evidenceIds: demonstration.evidenceIds })}`, type: demonstration.type, relationId: relation.id, fromEntityId, toEntityId, relationType: relation.relationType, label: demonstration.label, evidenceIds: [...demonstration.evidenceIds] };
1090
+ }
1091
+ assertEvidenceSupportsRouteMeaning(input.bundle, demonstration.evidenceIds, `proposal.demonstrations[${index}].evidenceIds`);
1092
+ const routeAuthority = scopedReferenceAuthority(input.bundle, demonstration.evidenceIds, semanticParticipantsForSegments(demonstration.prerequisiteRefs, hypothesesByLocalId, relationsByLocalId));
1093
+ if (!routeAuthority.pieces.has(demonstration.pieceId) || demonstration.waypoints.some((square) => !routeAuthority.squares.has(square))) {
1094
+ fail(`proposal.demonstrations[${index}]`, "untyped_route_reference", "Route references are outside the frozen closure");
1095
+ }
1096
+ const prerequisiteRefs = remapSegments(demonstration.prerequisiteRefs, `proposal.demonstrations[${index}].prerequisiteRefs`, demonstration.evidenceIds, authority, recorded.localToHypothesisIds, relationByLocalId, hypothesisByCanonicalId, semanticParticipantsForSegments(demonstration.prerequisiteRefs, hypothesesByLocalId, relationsByLocalId), input.bundle);
1097
+ return { id: `direct-demonstration:${stableHash({ contextHash: input.bundle.contextHash, type: demonstration.type, pieceId: demonstration.pieceId, waypoints: demonstration.waypoints, prerequisiteRefs, evidenceIds: demonstration.evidenceIds })}`, type: demonstration.type, pieceId: demonstration.pieceId, waypoints: [...demonstration.waypoints], prerequisiteRefs, label: demonstration.label, evidenceIds: [...demonstration.evidenceIds] };
1098
+ });
1099
+ const coverage = validateAndMaterializeCoverage(proposal.coverage, recorded.records, recorded.relations, recorded.localToHypothesisIds, relationByLocalId, claims, demonstrations);
1100
+ return {
1101
+ ok: true,
1102
+ metadata,
1103
+ artifact: {
1104
+ schemaVersion: ABLATION_DIRECT_SYNTHESIS_VERSION,
1105
+ contextHash: input.bundle.contextHash,
1106
+ bundleHash: sourceBundleHash,
1107
+ hypotheses: recorded.records.map((record) => {
1108
+ const withoutLocalId = { ...record };
1109
+ Reflect.deleteProperty(withoutLocalId, "localId");
1110
+ return withoutLocalId;
1111
+ }),
1112
+ relations: recorded.relations,
1113
+ claims,
1114
+ demonstrations,
1115
+ coverage,
1116
+ admission: "pre-checkpoint-proposed",
1117
+ productSupport: "not-a-validated-graph-checkpoint-or-product-result",
1118
+ limitations: [...proposal.limitations],
1119
+ riskProfile: { level: proposal.riskProfile.level, reasons: [...proposal.riskProfile.reasons] },
1120
+ },
1121
+ };
1122
+ } catch (error) {
1123
+ const issue = error instanceof AdmissionFailure
1124
+ ? error.issue
1125
+ : { path: "proposal", code: "production_admission_failure", message: "Direct synthesis failed production admission", permittedReferences: [] };
1126
+ return { ok: false, metadata, code: "direct-synthesis-rejected", issues: [issue] };
1127
+ }
1128
+ }