@knights-eye-chess/ai-analysis 0.0.0-stage → 0.1.0-dev.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +235 -0
- package/LICENSING.md +26 -0
- package/NOTICE +5 -0
- package/NPM-PUBLISHING.md +24 -0
- package/README.md +141 -2
- package/SOURCE-ORIGIN.json +16 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/build.mjs +22 -0
- package/dist/answer-pipeline.test.mjs +375 -0
- package/dist/chunk-PLKNJRPF.js +10662 -0
- package/dist/chunk-XQDZJOYD.js +2515 -0
- package/dist/index.js +146 -0
- package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
- package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
- package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
- package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
- package/dist/owner-tests/position-preparation.test.mjs +625 -0
- package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
- package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
- package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
- package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
- package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
- package/dist/research.js +2274 -0
- package/dist/runtime-closure.json +103 -0
- package/dist/shared-runtime-closure.json +39 -0
- package/dist/shared.js +152 -0
- package/dist/types/ai-request-failure.d.ts +14 -0
- package/dist/types/ai-request-limits.d.ts +4 -0
- package/dist/types/ai-routing-diagnostics.d.ts +14 -0
- package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
- package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
- package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
- package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
- package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
- package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
- package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
- package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
- package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
- package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
- package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
- package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
- package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
- package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
- package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
- package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
- package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
- package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
- package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
- package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
- package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
- package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
- package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
- package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
- package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
- package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
- package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
- package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
- package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
- package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
- package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
- package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
- package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
- package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
- package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
- package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
- package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
- package/dist/types/chess-reasoning/material-format.d.ts +4 -0
- package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
- package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
- package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
- package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
- package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
- package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
- package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
- package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
- package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
- package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
- package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
- package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
- package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
- package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
- package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
- package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
- package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
- package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
- package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
- package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
- package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
- package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
- package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
- package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
- package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
- package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
- package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
- package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
- package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
- package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
- package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
- package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
- package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
- package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
- package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
- package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
- package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
- package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
- package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
- package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
- package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
- package/dist/types/chess-reasoning/stable.d.ts +2 -0
- package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
- package/dist/types/chess-reasoning/types.d.ts +180 -0
- package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
- package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
- package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
- package/dist/types/player-perspective.d.ts +16 -0
- package/dist/types/shared-exports.d.ts +22 -0
- package/docs/API.md +82 -0
- package/docs/OWNERSHIP-ADOPTION.md +35 -0
- package/docs/OWNERSHIP-DRIFT.json +55 -0
- package/docs/RESEARCH-API.md +28 -0
- package/licenses/third-party/INVENTORY.json +202 -0
- package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
- package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
- package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
- package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
- package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
- package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
- package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
- package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
- package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
- package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
- package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
- package/npm-release.json +5 -0
- package/package.json +72 -4
- package/scripts/check-release-tooling.mjs +22 -0
- package/scripts/pack.mjs +68 -0
- package/scripts/publish-reviewed.mjs +72 -0
- package/scripts/release-tooling.json +13 -0
- package/scripts/update-dependency.mjs +44 -0
- package/scripts/verify-release.mjs +52 -0
- package/src/ai-request-failure.ts +34 -0
- package/src/ai-request-limits.ts +4 -0
- package/src/ai-routing-diagnostics.ts +33 -0
- package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
- package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
- package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
- package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
- package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
- package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
- package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
- package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
- package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
- package/src/chess-reasoning/answer-host-contracts.ts +10 -0
- package/src/chess-reasoning/answer-model-connector.ts +83 -0
- package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
- package/src/chess-reasoning/answer-pipeline.ts +195 -0
- package/src/chess-reasoning/answer-reference-validation.ts +124 -0
- package/src/chess-reasoning/answer-tool-host.ts +10 -0
- package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
- package/src/chess-reasoning/branch-expansion.ts +80 -0
- package/src/chess-reasoning/causal-claim-checks.ts +256 -0
- package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
- package/src/chess-reasoning/classification-question-target.ts +30 -0
- package/src/chess-reasoning/classification-reassessment.ts +34 -0
- package/src/chess-reasoning/compact-search-preparation.ts +125 -0
- package/src/chess-reasoning/concept-registry.ts +275 -0
- package/src/chess-reasoning/conversation-context.ts +29 -0
- package/src/chess-reasoning/engine-search-failure.ts +11 -0
- package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
- package/src/chess-reasoning/evidence-licenses.ts +166 -0
- package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
- package/src/chess-reasoning/existing-analysis-context.ts +544 -0
- package/src/chess-reasoning/fact-observation-provider.ts +134 -0
- package/src/chess-reasoning/fact-provider.ts +250 -0
- package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
- package/src/chess-reasoning/historical-prose.ts +116 -0
- package/src/chess-reasoning/idea-graph.ts +224 -0
- package/src/chess-reasoning/initial-search-plan.ts +115 -0
- package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
- package/src/chess-reasoning/matched-comparison.ts +40 -0
- package/src/chess-reasoning/material-accounting.ts +55 -0
- package/src/chess-reasoning/material-format.ts +47 -0
- package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
- package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
- package/src/chess-reasoning/mechanism-presentation.ts +94 -0
- package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
- package/src/chess-reasoning/native-answer-connector.ts +13 -0
- package/src/chess-reasoning/opening-source-contracts.ts +47 -0
- package/src/chess-reasoning/opening-source-credits.ts +53 -0
- package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
- package/src/chess-reasoning/piece-identities.ts +159 -0
- package/src/chess-reasoning/poc-contracts.ts +1097 -0
- package/src/chess-reasoning/poc-hypotheses.ts +759 -0
- package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
- package/src/chess-reasoning/poc-observations.ts +611 -0
- package/src/chess-reasoning/poc-piece-refs.ts +62 -0
- package/src/chess-reasoning/poc-plan-model.ts +345 -0
- package/src/chess-reasoning/poc-semantic-text.ts +10 -0
- package/src/chess-reasoning/poc-tools.ts +978 -0
- package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
- package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
- package/src/chess-reasoning/position-analysis-scope.ts +20 -0
- package/src/chess-reasoning/preparation-consolidation.ts +91 -0
- package/src/chess-reasoning/production-observation-provider.ts +41 -0
- package/src/chess-reasoning/question-routing.ts +332 -0
- package/src/chess-reasoning/recorded-main-line.ts +55 -0
- package/src/chess-reasoning/recorded-position-tools.ts +78 -0
- package/src/chess-reasoning/recorded-reference-context.ts +44 -0
- package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
- package/src/chess-reasoning/reference-limits.ts +2 -0
- package/src/chess-reasoning/research-exports.ts +197 -0
- package/src/chess-reasoning/saved-score-presentation.ts +6 -0
- package/src/chess-reasoning/score-comparison-notices.ts +115 -0
- package/src/chess-reasoning/server-simple-policy.ts +75 -0
- package/src/chess-reasoning/simple-agent-config.ts +58 -0
- package/src/chess-reasoning/simple-board-analysis.ts +57 -0
- package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
- package/src/chess-reasoning/simple-completion.ts +37 -0
- package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
- package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
- package/src/chess-reasoning/simple-evidence-view.ts +69 -0
- package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
- package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
- package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
- package/src/chess-reasoning/simple-prose-references.ts +528 -0
- package/src/chess-reasoning/simple-runtime-version.ts +2 -0
- package/src/chess-reasoning/stable.ts +18 -0
- package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
- package/src/chess-reasoning/types.ts +195 -0
- package/src/chess-reasoning/visible-support-lines.ts +44 -0
- package/src/chess-reasoning/wikibooks-index.ts +56 -0
- package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
- package/src/player-perspective.ts +41 -0
- package/src/shared-exports.ts +23 -0
- package/test/answer-pipeline.test.mjs +375 -0
- package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
- package/test/owner/mechanism-encoding.test.mjs +78 -0
- package/test/owner/move-mechanism-facts.test.mjs +105 -0
- package/test/owner/pipeline-host-preparation.test.mjs +115 -0
- package/test/owner/position-preparation.test.mjs +22 -0
- package/test/owner/preparation-consolidation.test.mjs +74 -0
- package/test/owner/prose-reference-admission.test.mjs +26 -0
- package/test/owner/score-comparison-notices.test.mjs +116 -0
- package/test/owner/simple-engine-presentation.test.mjs +50 -0
- package/test/owner/tool-input-loop-guard.test.mjs +25 -0
- package/test-types.mts +23 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHESS_FACT_REQUEST_VERSION,
|
|
3
|
+
chessFactRecordLicenses,
|
|
4
|
+
factProofStrength,
|
|
5
|
+
type ChessFactProvider,
|
|
6
|
+
type ChessFactRequest,
|
|
7
|
+
} from "./fact-provider";
|
|
8
|
+
import {
|
|
9
|
+
REASONING_POC_VERSION,
|
|
10
|
+
reasoningContextHash,
|
|
11
|
+
type ObservationProvider,
|
|
12
|
+
type ReasoningEvidenceRecord,
|
|
13
|
+
type ReasoningObservation,
|
|
14
|
+
type ReasoningObservationSet,
|
|
15
|
+
type ReasoningPositionContext,
|
|
16
|
+
type ReasoningRunControls,
|
|
17
|
+
} from "./poc-contracts";
|
|
18
|
+
import { stableHash } from "./stable";
|
|
19
|
+
import type { SemanticValue } from "./types";
|
|
20
|
+
|
|
21
|
+
export const FACT_OBSERVATION_ADAPTER_VERSION = "chess-fact-observation-adapter/1" as const;
|
|
22
|
+
|
|
23
|
+
export type ChessFactObservationProviderOptions = {
|
|
24
|
+
maxRecords?: number;
|
|
25
|
+
maxBytes?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function providerTrust(authority: import("./fact-provider").ChessFactAuthority): "deterministic" | "learned" | "external" {
|
|
29
|
+
if (authority === "bounded-engine") return "external";
|
|
30
|
+
return "deterministic";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ChessFactObservationProvider implements ObservationProvider {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly version: string;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private readonly provider: ChessFactProvider,
|
|
39
|
+
private readonly options: ChessFactObservationProviderOptions = {},
|
|
40
|
+
) {
|
|
41
|
+
this.id = `knights-eye/fact-observation:${provider.id}`;
|
|
42
|
+
this.version = stableHash({ adapter: FACT_OBSERVATION_ADAPTER_VERSION, provider: provider.id, version: provider.version });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async observe(context: ReasoningPositionContext, controls: ReasoningRunControls, signal?: AbortSignal): Promise<ReasoningObservationSet> {
|
|
46
|
+
const contextHash = reasoningContextHash(context);
|
|
47
|
+
const request: ChessFactRequest = {
|
|
48
|
+
schemaVersion: CHESS_FACT_REQUEST_VERSION,
|
|
49
|
+
requestId: `reasoning-facts:${stableHash({ contextHash, provider: this.provider.id, version: this.provider.version })}`,
|
|
50
|
+
context,
|
|
51
|
+
requestedKinds: [],
|
|
52
|
+
budgets: {
|
|
53
|
+
maxRecords: Math.max(1, Math.min(controls.maxObservationRecords, this.options.maxRecords ?? controls.maxObservationRecords)),
|
|
54
|
+
maxBytes: Math.max(1_024, Math.min(controls.maxProviderOutputChars, this.options.maxBytes ?? controls.maxProviderOutputChars)),
|
|
55
|
+
maxWallTimeMs: Math.max(1, Math.min(300_000, controls.maxWallTimeMs)),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const result = await this.provider.collect(request, signal);
|
|
59
|
+
if (result.contextHash !== contextHash) throw new Error("Chess fact provider returned stale context");
|
|
60
|
+
const observations: ReasoningObservation[] = [];
|
|
61
|
+
const evidence: ReasoningEvidenceRecord[] = [];
|
|
62
|
+
for (const record of result.records) {
|
|
63
|
+
const evidenceId = `evidence:${record.id}`;
|
|
64
|
+
evidence.push({
|
|
65
|
+
schemaVersion: REASONING_POC_VERSION,
|
|
66
|
+
id: evidenceId,
|
|
67
|
+
contextHash,
|
|
68
|
+
kind: "fact",
|
|
69
|
+
source: `${record.provenance.provider}@${record.provenance.version}`,
|
|
70
|
+
proofStrength: factProofStrength(record.authority, record.coverage),
|
|
71
|
+
licenses: chessFactRecordLicenses(record),
|
|
72
|
+
summary: record.summary,
|
|
73
|
+
data: {
|
|
74
|
+
factId: record.id,
|
|
75
|
+
kind: record.kind,
|
|
76
|
+
phase: record.phase,
|
|
77
|
+
authority: record.authority,
|
|
78
|
+
participants: record.participants as unknown as SemanticValue,
|
|
79
|
+
data: record.data,
|
|
80
|
+
coverage: record.coverage as unknown as SemanticValue,
|
|
81
|
+
provenance: record.provenance as unknown as SemanticValue,
|
|
82
|
+
},
|
|
83
|
+
coverage: {
|
|
84
|
+
complete: record.coverage.exhaustive && record.coverage.unresolved.length === 0,
|
|
85
|
+
covered: [...record.coverage.covered],
|
|
86
|
+
unresolved: [...record.coverage.unresolved],
|
|
87
|
+
},
|
|
88
|
+
replay: record.replay,
|
|
89
|
+
});
|
|
90
|
+
observations.push({
|
|
91
|
+
schemaVersion: REASONING_POC_VERSION,
|
|
92
|
+
id: `observation:${record.id}`,
|
|
93
|
+
contextHash,
|
|
94
|
+
observationType: `chess-fact:${record.kind}`,
|
|
95
|
+
participants: record.participants.map((participant) => ({ ...participant, ref: { ...participant.ref } })),
|
|
96
|
+
attributes: {
|
|
97
|
+
factId: record.id,
|
|
98
|
+
factKind: record.kind,
|
|
99
|
+
phase: record.phase,
|
|
100
|
+
authority: record.authority,
|
|
101
|
+
data: record.data,
|
|
102
|
+
coverage: record.coverage as unknown as SemanticValue,
|
|
103
|
+
},
|
|
104
|
+
exactness: record.authority === "heuristic-affordance" ? "heuristic" : "exact",
|
|
105
|
+
provenance: {
|
|
106
|
+
provider: record.provenance.provider,
|
|
107
|
+
version: record.provenance.version,
|
|
108
|
+
trust: providerTrust(record.authority),
|
|
109
|
+
methods: [record.provenance.method, FACT_OBSERVATION_ADAPTER_VERSION],
|
|
110
|
+
},
|
|
111
|
+
factEvidenceIds: [evidenceId],
|
|
112
|
+
probeRecipes: [],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const capabilities = result.capabilities.map((capability) => capability.kind).sort();
|
|
116
|
+
return {
|
|
117
|
+
schemaVersion: REASONING_POC_VERSION,
|
|
118
|
+
contextHash,
|
|
119
|
+
observations,
|
|
120
|
+
evidence,
|
|
121
|
+
providers: [
|
|
122
|
+
{ provider: this.id, version: this.version },
|
|
123
|
+
{ provider: this.provider.id, version: this.provider.version },
|
|
124
|
+
],
|
|
125
|
+
limitations: [
|
|
126
|
+
...result.limitations,
|
|
127
|
+
`Chess fact capabilities advertised for model-directed expansion: ${capabilities.join(", ") || "none"}.`,
|
|
128
|
+
...(result.omitted.length ? [`${result.omitted.length} fact record(s) were explicitly omitted by request or budget.`] : []),
|
|
129
|
+
...(result.unavailableKinds.length ? [`Unavailable fact kinds: ${result.unavailableKinds.map((item) => item.kind).join(", ")}.`] : []),
|
|
130
|
+
"The adapter preserves fact authority and does not create semantic concepts, plans, roles, or classifications.",
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { ReasoningPositionContext } from "./poc-contracts";
|
|
2
|
+
import { effectiveEvidenceLicenses, validateEvidenceLicenseClaims, type EvidenceLicense } from "./evidence-licenses";
|
|
3
|
+
import type { ProofStrength, SemanticParticipant, SemanticValue } from "./types";
|
|
4
|
+
|
|
5
|
+
export const CHESS_FACT_SCHEMA_VERSION = "chess-facts/1" as const;
|
|
6
|
+
export const CHESS_FACT_REQUEST_VERSION = "chess-fact-request/1" as const;
|
|
7
|
+
export const CHESS_FACT_RESULT_VERSION = "chess-fact-result/1" as const;
|
|
8
|
+
|
|
9
|
+
export type ChessFactAuthority =
|
|
10
|
+
| "legal-exact"
|
|
11
|
+
| "deterministic-derived"
|
|
12
|
+
| "heuristic-affordance"
|
|
13
|
+
| "bounded-engine";
|
|
14
|
+
|
|
15
|
+
const PROOF_RANK: Readonly<Record<ProofStrength, number>> = Object.freeze({
|
|
16
|
+
observed: 1,
|
|
17
|
+
supported: 2,
|
|
18
|
+
forced: 3,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type ChessFactPhase = "before" | "after" | "transition" | "path";
|
|
22
|
+
|
|
23
|
+
export type ChessFactCoverage = {
|
|
24
|
+
domain: string;
|
|
25
|
+
exhaustive: boolean;
|
|
26
|
+
covered: string[];
|
|
27
|
+
unresolved: string[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ChessFactEngineProvenance = {
|
|
31
|
+
name: string;
|
|
32
|
+
build: string;
|
|
33
|
+
network: string | null;
|
|
34
|
+
depth: number;
|
|
35
|
+
threads: 1;
|
|
36
|
+
multiPv: number;
|
|
37
|
+
rootMoves: string[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ChessFactRecord = {
|
|
41
|
+
schemaVersion: typeof CHESS_FACT_SCHEMA_VERSION;
|
|
42
|
+
id: string;
|
|
43
|
+
contextHash: string;
|
|
44
|
+
kind: string;
|
|
45
|
+
phase: ChessFactPhase;
|
|
46
|
+
authority: ChessFactAuthority;
|
|
47
|
+
licenses?: EvidenceLicense[];
|
|
48
|
+
summary: string;
|
|
49
|
+
participants: SemanticParticipant[];
|
|
50
|
+
data: Record<string, SemanticValue>;
|
|
51
|
+
coverage: ChessFactCoverage;
|
|
52
|
+
provenance: {
|
|
53
|
+
provider: string;
|
|
54
|
+
version: string;
|
|
55
|
+
method: string;
|
|
56
|
+
derivedFromIds: string[];
|
|
57
|
+
engine?: ChessFactEngineProvenance;
|
|
58
|
+
};
|
|
59
|
+
replay: Record<string, SemanticValue> | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type ChessFactCapability = {
|
|
63
|
+
kind: string;
|
|
64
|
+
phases: ChessFactPhase[];
|
|
65
|
+
authorities: ChessFactAuthority[];
|
|
66
|
+
coverageDomains: string[];
|
|
67
|
+
expandable: boolean;
|
|
68
|
+
limitations: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type ChessFactRequest = {
|
|
72
|
+
schemaVersion: typeof CHESS_FACT_REQUEST_VERSION;
|
|
73
|
+
requestId: string;
|
|
74
|
+
context: ReasoningPositionContext;
|
|
75
|
+
requestedKinds: string[];
|
|
76
|
+
budgets: {
|
|
77
|
+
maxRecords: number;
|
|
78
|
+
maxBytes: number;
|
|
79
|
+
maxWallTimeMs: number;
|
|
80
|
+
};
|
|
81
|
+
engineLimits?: {
|
|
82
|
+
depth: number;
|
|
83
|
+
threads: 1;
|
|
84
|
+
multiPv: number;
|
|
85
|
+
rootMoves: string[];
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type ChessFactOmission = {
|
|
90
|
+
kind: string;
|
|
91
|
+
reason: "budget" | "not-requested";
|
|
92
|
+
recordId: string | null;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type ChessFactResult = {
|
|
96
|
+
schemaVersion: typeof CHESS_FACT_RESULT_VERSION;
|
|
97
|
+
contextHash: string;
|
|
98
|
+
records: ChessFactRecord[];
|
|
99
|
+
capabilities: ChessFactCapability[];
|
|
100
|
+
omitted: ChessFactOmission[];
|
|
101
|
+
unavailableKinds: Array<{ kind: string; reason: string }>;
|
|
102
|
+
limitations: string[];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export interface ChessFactProvider {
|
|
106
|
+
readonly id: string;
|
|
107
|
+
readonly version: string;
|
|
108
|
+
capabilities(): readonly ChessFactCapability[];
|
|
109
|
+
collect(request: ChessFactRequest, signal?: AbortSignal): Promise<ChessFactResult> | ChessFactResult;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const MECHANICAL_KIND = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$/;
|
|
113
|
+
const INTERPRETIVE_KIND_TOKEN = /(?:^|[._-])(plan|idea|purpose|brilliant|great|interesting|best|good|okay|dubious|mistake|miss|blunder|decisive|interposition|readiness)(?:$|[._-])/;
|
|
114
|
+
const UCI_MOVE = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
115
|
+
const SQUARE = /^[a-h][1-8]$/;
|
|
116
|
+
|
|
117
|
+
function validateMechanicalKind(kind: string): void {
|
|
118
|
+
if (!MECHANICAL_KIND.test(kind)) throw new Error(`Chess fact kind is not mechanical: ${kind}`);
|
|
119
|
+
if (INTERPRETIVE_KIND_TOKEN.test(kind)) throw new Error(`Chess fact kind leaks interpretation or policy: ${kind}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function validateChessFactRequest(request: ChessFactRequest): ChessFactRequest {
|
|
123
|
+
if (request.schemaVersion !== CHESS_FACT_REQUEST_VERSION) throw new Error("Unsupported chess fact request version");
|
|
124
|
+
if (!request.requestId || request.requestId.length > 200) throw new Error("Chess fact requestId must contain 1..200 characters");
|
|
125
|
+
if (!Number.isInteger(request.budgets.maxRecords) || request.budgets.maxRecords < 1 || request.budgets.maxRecords > 512) {
|
|
126
|
+
throw new Error("Chess fact maxRecords must be an integer in 1..512");
|
|
127
|
+
}
|
|
128
|
+
if (!Number.isInteger(request.budgets.maxBytes) || request.budgets.maxBytes < 1_024 || request.budgets.maxBytes > 2_000_000) {
|
|
129
|
+
throw new Error("Chess fact maxBytes must be an integer in 1024..2000000");
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isInteger(request.budgets.maxWallTimeMs) || request.budgets.maxWallTimeMs < 1 || request.budgets.maxWallTimeMs > 300_000) {
|
|
132
|
+
throw new Error("Chess fact maxWallTimeMs must be an integer in 1..300000");
|
|
133
|
+
}
|
|
134
|
+
for (const kind of request.requestedKinds) validateMechanicalKind(kind);
|
|
135
|
+
if (new Set(request.requestedKinds).size !== request.requestedKinds.length) throw new Error("Chess fact requestedKinds must be unique");
|
|
136
|
+
if (request.engineLimits) {
|
|
137
|
+
if (request.engineLimits.threads !== 1) throw new Error("Reproducible chess fact engine requests require threads=1");
|
|
138
|
+
if (!Number.isInteger(request.engineLimits.depth) || request.engineLimits.depth < 1 || request.engineLimits.depth > 64) throw new Error("Chess fact engine depth must be in 1..64");
|
|
139
|
+
if (!Number.isInteger(request.engineLimits.multiPv) || request.engineLimits.multiPv < 1 || request.engineLimits.multiPv > 16) throw new Error("Chess fact engine multiPv must be in 1..16");
|
|
140
|
+
if (request.engineLimits.rootMoves.some((move) => !UCI_MOVE.test(move))) throw new Error("Chess fact engine root moves must be UCI moves");
|
|
141
|
+
}
|
|
142
|
+
return request;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function validateChessFactRecord(record: ChessFactRecord): ChessFactRecord {
|
|
146
|
+
if (record.schemaVersion !== CHESS_FACT_SCHEMA_VERSION) throw new Error("Unsupported chess fact record version");
|
|
147
|
+
if (!record.id || !record.contextHash) throw new Error("Chess fact record requires stable identity and context hash");
|
|
148
|
+
validateMechanicalKind(record.kind);
|
|
149
|
+
if (!new Set<ChessFactAuthority>(["legal-exact", "deterministic-derived", "heuristic-affordance", "bounded-engine"]).has(record.authority)) throw new Error("Unsupported chess fact authority");
|
|
150
|
+
if (!new Set<ChessFactPhase>(["before", "after", "transition", "path"]).has(record.phase)) throw new Error("Unsupported chess fact phase");
|
|
151
|
+
if (!record.summary.trim() || record.summary.length > 1_000) throw new Error("Chess fact summary must contain 1..1000 characters");
|
|
152
|
+
if (!record.coverage.domain) throw new Error("Chess fact coverage domain is required");
|
|
153
|
+
if (!record.coverage.exhaustive && record.coverage.unresolved.length === 0) throw new Error("Non-exhaustive chess fact coverage must state unresolved domain members");
|
|
154
|
+
if (record.coverage.covered.some((item) => !item) || record.coverage.unresolved.some((item) => !item)) throw new Error("Chess fact coverage entries must be nonempty");
|
|
155
|
+
if (!record.provenance.provider || !record.provenance.version || !record.provenance.method) throw new Error("Chess fact provenance is incomplete");
|
|
156
|
+
if (new Set(record.provenance.derivedFromIds).size !== record.provenance.derivedFromIds.length) throw new Error("Chess fact derivedFromIds must be unique");
|
|
157
|
+
if (Object.keys(record).some((key) => /^is[A-Z]/.test(key))) throw new Error("Chess fact records cannot add concept-specific boolean flags");
|
|
158
|
+
const licenseInput = {
|
|
159
|
+
kind: "fact",
|
|
160
|
+
source: `${record.provenance.provider}/${record.provenance.method}`,
|
|
161
|
+
authority: record.authority,
|
|
162
|
+
proofStrength: factProofStrength(record.authority, record.coverage),
|
|
163
|
+
data: { ...record.data, authority: record.authority, phase: record.phase, factKind: record.kind },
|
|
164
|
+
coverage: { complete: record.coverage.exhaustive, covered: record.coverage.covered, unresolved: record.coverage.unresolved },
|
|
165
|
+
replay: record.replay,
|
|
166
|
+
};
|
|
167
|
+
if (record.licenses !== undefined) validateEvidenceLicenseClaims(licenseInput, record.licenses);
|
|
168
|
+
if (record.authority === "bounded-engine") {
|
|
169
|
+
const engine = record.provenance.engine;
|
|
170
|
+
if (!engine) throw new Error("Bounded-engine fact requires engine provenance");
|
|
171
|
+
if (engine.threads !== 1) throw new Error("Bounded-engine fact provenance requires threads=1");
|
|
172
|
+
if (!Number.isInteger(engine.depth) || engine.depth < 1 || engine.depth > 64) throw new Error("Bounded-engine fact provenance requires fixed depth");
|
|
173
|
+
if (!Number.isInteger(engine.multiPv) || engine.multiPv < 1 || engine.multiPv > 16) throw new Error("Bounded-engine fact provenance requires bounded MultiPV");
|
|
174
|
+
if (!engine.name || !engine.build) throw new Error("Bounded-engine fact provenance requires engine name and build");
|
|
175
|
+
if (engine.rootMoves.some((move) => !UCI_MOVE.test(move))) throw new Error("Bounded-engine root moves must be UCI moves");
|
|
176
|
+
} else if (record.provenance.engine) {
|
|
177
|
+
throw new Error("Only bounded-engine facts may carry engine provenance");
|
|
178
|
+
}
|
|
179
|
+
const pieceIds = new Set(record.participants.filter((item) => item.ref.domain === "piece").map((item) => item.ref.id));
|
|
180
|
+
if (pieceIds.size) {
|
|
181
|
+
const locations = Array.isArray(record.data.pieceLocations) ? record.data.pieceLocations : [];
|
|
182
|
+
const located = new Set(locations.flatMap((value) => {
|
|
183
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
184
|
+
const pieceId = (value as Record<string, SemanticValue>).pieceId;
|
|
185
|
+
const currentSquare = (value as Record<string, SemanticValue>).currentSquare;
|
|
186
|
+
return typeof pieceId === "string" && typeof currentSquare === "string" && SQUARE.test(currentSquare) ? [pieceId] : [];
|
|
187
|
+
}));
|
|
188
|
+
for (const pieceId of pieceIds) if (!located.has(pieceId)) throw new Error(`Piece participant lacks currentSquare data: ${pieceId}`);
|
|
189
|
+
}
|
|
190
|
+
return record;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function chessFactRecordLicenses(record: ChessFactRecord): EvidenceLicense[] {
|
|
194
|
+
return effectiveEvidenceLicenses({
|
|
195
|
+
kind: "fact",
|
|
196
|
+
source: `${record.provenance.provider}/${record.provenance.method}`,
|
|
197
|
+
authority: record.authority,
|
|
198
|
+
proofStrength: factProofStrength(record.authority, record.coverage),
|
|
199
|
+
data: { ...record.data, authority: record.authority, phase: record.phase, factKind: record.kind },
|
|
200
|
+
coverage: { complete: record.coverage.exhaustive, covered: record.coverage.covered, unresolved: record.coverage.unresolved },
|
|
201
|
+
replay: record.replay,
|
|
202
|
+
licenses: record.licenses,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Central authority-to-proof policy for every fact-provider boundary.
|
|
208
|
+
*
|
|
209
|
+
* `forced` is intentionally absent: fact records cannot establish it. Forced
|
|
210
|
+
* proof belongs to the rules-complete probe/coverage validators.
|
|
211
|
+
*/
|
|
212
|
+
export function maximumFactProofStrength(
|
|
213
|
+
authority: ChessFactAuthority,
|
|
214
|
+
coverage?: Pick<ChessFactCoverage, "exhaustive" | "unresolved"> | null,
|
|
215
|
+
): ProofStrength | null {
|
|
216
|
+
if (authority === "heuristic-affordance") return null;
|
|
217
|
+
if (authority === "legal-exact" || authority === "deterministic-derived") return "observed";
|
|
218
|
+
return coverage?.exhaustive && coverage.unresolved.length === 0 ? "supported" : "observed";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function factProofStrength(
|
|
222
|
+
authority: ChessFactAuthority,
|
|
223
|
+
coverage?: Pick<ChessFactCoverage, "exhaustive" | "unresolved"> | null,
|
|
224
|
+
): ProofStrength | null {
|
|
225
|
+
return maximumFactProofStrength(authority, coverage);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function assertFactProofWithinAuthority(input: {
|
|
229
|
+
authority: ChessFactAuthority;
|
|
230
|
+
proofStrength: ProofStrength | null;
|
|
231
|
+
coverage?: Pick<ChessFactCoverage, "exhaustive" | "unresolved"> | null;
|
|
232
|
+
label?: string;
|
|
233
|
+
}): void {
|
|
234
|
+
const maximum = maximumFactProofStrength(input.authority, input.coverage);
|
|
235
|
+
if (input.proofStrength === null) return;
|
|
236
|
+
if (maximum === null || PROOF_RANK[input.proofStrength] > PROOF_RANK[maximum]) {
|
|
237
|
+
throw new Error(`${input.label || "Fact evidence"} overstates ${input.authority} authority as ${input.proofStrength}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function isChessFactAuthority(value: unknown): value is ChessFactAuthority {
|
|
242
|
+
return value === "legal-exact"
|
|
243
|
+
|| value === "deterministic-derived"
|
|
244
|
+
|| value === "heuristic-affordance"
|
|
245
|
+
|| value === "bounded-engine";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function factCoverageSupportsAbsence(record: ChessFactRecord, domain: string): boolean {
|
|
249
|
+
return record.coverage.domain === domain && record.coverage.exhaustive && record.coverage.unresolved.length === 0;
|
|
250
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Chess } from 'chess.js';
|
|
2
|
+
import type { PositionAnalysisLine } from './position-analysis-contracts';
|
|
3
|
+
import { reasoningContextHash, type ReasoningPositionContext } from './poc-contracts';
|
|
4
|
+
|
|
5
|
+
/** Host-only input, created from this answer's acquired engine results, never a saved snapshot. */
|
|
6
|
+
export type FreshEnginePvWitness = {
|
|
7
|
+
version: 'fresh-engine-pv-witness/1'; acquisition: 'same-answer-engine'; acquisitionId: number;
|
|
8
|
+
evidenceId: string; contextHash: string; phase: 'before' | 'after'; fen: string;
|
|
9
|
+
engineBuild: string; command: string; requestedDepth: number; multiPv: number; rootMoves: string[];
|
|
10
|
+
authority: 'bounded-engine'; licenses: ['engine.bounded-continuation'];
|
|
11
|
+
lines: Pick<PositionAnalysisLine, 'rootMove' | 'pv' | 'depth' | 'scoreProvenance' | 'scoreCp' | 'mateInMoves'>[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FreshEnginePvReferenceEvidence = {
|
|
15
|
+
acquisitionId: number; evidenceId: string; rootFen: string; rootMove: string;
|
|
16
|
+
depth: number; requestedDepth: number; engineBuild: string; command: string;
|
|
17
|
+
scoreCp: number | null; mateInMoves: number | null;
|
|
18
|
+
scoreProvenance: NonNullable<PositionAnalysisLine['scoreProvenance']>;
|
|
19
|
+
conditional: true;
|
|
20
|
+
scoreInterpretation: 'Root-search score only; the continuation is conditional, not forced and not a score for a later position.';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type FreshEnginePvReferenceLine = {
|
|
24
|
+
start: 'before' | 'after'; ucis: string[]; evidenceIds: string[]; authority: string;
|
|
25
|
+
engineEvidence: FreshEnginePvReferenceEvidence[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const MAX_FRESH_ENGINE_WITNESS_ACQUISITIONS = 64;
|
|
29
|
+
export type FreshEnginePvRejectionReason =
|
|
30
|
+
| 'invalid-acquisition' | 'missing-authority-license' | 'context-mismatch' | 'root-mismatch'
|
|
31
|
+
| 'invalid-search-scope' | 'root-set-mismatch' | 'depth-mismatch' | 'provenance-mismatch'
|
|
32
|
+
| 'score-mismatch' | 'invalid-pv' | 'illegal-pv' | 'audit-capacity-exceeded';
|
|
33
|
+
export type FreshEnginePvAdmissionAudit = {
|
|
34
|
+
version: 'fresh-engine-pv-admission/1'; inputAcquisitions: number; inspectedAcquisitions: number;
|
|
35
|
+
acceptedAcquisitions: number; rejectedAcquisitions: number; acceptedLines: number; omittedAcquisitions: number;
|
|
36
|
+
acceptedAcquisitionIds: number[]; rejectedAcquisitionIds: (number | null)[];
|
|
37
|
+
rejections: { acquisitionId: number | null; reason: FreshEnginePvRejectionReason }[];
|
|
38
|
+
rejectionCounts: Partial<Record<FreshEnginePvRejectionReason, number>>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const UCI = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
42
|
+
const positiveInteger = (value: unknown): value is number => Number.isSafeInteger(value) && Number(value) > 0;
|
|
43
|
+
|
|
44
|
+
function rejectionReason(context: ReasoningPositionContext, hash: string, witness: FreshEnginePvWitness): FreshEnginePvRejectionReason | null {
|
|
45
|
+
if (!witness || witness.version !== 'fresh-engine-pv-witness/1' || witness.acquisition !== 'same-answer-engine'
|
|
46
|
+
|| !positiveInteger(witness.acquisitionId) || typeof witness.evidenceId !== 'string' || !witness.evidenceId
|
|
47
|
+
|| witness.evidenceId.length > 200) return 'invalid-acquisition';
|
|
48
|
+
if (witness.authority !== 'bounded-engine' || !Array.isArray(witness.licenses)
|
|
49
|
+
|| witness.licenses.length !== 1 || witness.licenses[0] !== 'engine.bounded-continuation') return 'missing-authority-license';
|
|
50
|
+
if (witness.contextHash !== hash) return 'context-mismatch';
|
|
51
|
+
if (!['before', 'after'].includes(witness.phase)
|
|
52
|
+
|| witness.fen !== (witness.phase === 'before' ? context.fenBefore : context.fenAfter)) return 'root-mismatch';
|
|
53
|
+
if (typeof witness.engineBuild !== 'string' || !witness.engineBuild || typeof witness.command !== 'string' || !witness.command
|
|
54
|
+
|| !positiveInteger(witness.requestedDepth) || !positiveInteger(witness.multiPv) || witness.multiPv > 32
|
|
55
|
+
|| !Array.isArray(witness.lines) || witness.lines.length !== witness.multiPv
|
|
56
|
+
|| !Array.isArray(witness.rootMoves) || witness.rootMoves.length > 32
|
|
57
|
+
|| witness.rootMoves.some(root => typeof root !== 'string' || !UCI.test(root))
|
|
58
|
+
|| new Set(witness.rootMoves).size !== witness.rootMoves.length) return 'invalid-search-scope';
|
|
59
|
+
if (witness.lines.some(line => !line || typeof line.rootMove !== 'string')) return 'invalid-pv';
|
|
60
|
+
const roots = witness.lines.map(line => line.rootMove);
|
|
61
|
+
if (new Set(roots).size !== roots.length || witness.rootMoves.some(root => !roots.includes(root))
|
|
62
|
+
|| (witness.rootMoves.length > 0 && roots.some(root => !witness.rootMoves.includes(root)))) return 'root-set-mismatch';
|
|
63
|
+
// The acquisition is atomic: a malformed sibling or full tail rejects every line.
|
|
64
|
+
for (const line of witness.lines) {
|
|
65
|
+
if (!positiveInteger(line.depth) || line.depth < witness.requestedDepth) return 'depth-mismatch';
|
|
66
|
+
const provenance = line.scoreProvenance;
|
|
67
|
+
if (!provenance || provenance.kind !== 'root-search' || provenance.fen !== witness.fen
|
|
68
|
+
|| provenance.engineBuild !== witness.engineBuild
|
|
69
|
+
|| (provenance.command !== undefined && provenance.command !== witness.command)
|
|
70
|
+
|| provenance.originatingDepth !== line.depth || provenance.reportedDepth !== line.depth) return 'provenance-mismatch';
|
|
71
|
+
if (!((Number.isFinite(line.scoreCp) && line.mateInMoves === null)
|
|
72
|
+
|| (line.scoreCp === null && Number.isSafeInteger(line.mateInMoves) && line.mateInMoves !== 0))) return 'score-mismatch';
|
|
73
|
+
if (!Array.isArray(line.pv) || !line.pv.length || line.pv.length > 256
|
|
74
|
+
|| line.pv.some(uci => typeof uci !== 'string' || !UCI.test(uci)) || line.pv[0] !== line.rootMove) return 'invalid-pv';
|
|
75
|
+
try {
|
|
76
|
+
const board = new Chess(witness.fen);
|
|
77
|
+
for (const uci of line.pv) board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || undefined });
|
|
78
|
+
} catch { return 'illegal-pv'; }
|
|
79
|
+
try { structuredClone(provenance); } catch { return 'provenance-mismatch'; }
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Bounded, safe reason codes; no raw error message, board, path or private source in the audit. */
|
|
85
|
+
export function admitFreshEnginePvWitnesses(context: ReasoningPositionContext, witnesses: readonly FreshEnginePvWitness[]): {
|
|
86
|
+
lines: FreshEnginePvReferenceLine[]; audit: FreshEnginePvAdmissionAudit;
|
|
87
|
+
} {
|
|
88
|
+
const lines: FreshEnginePvReferenceLine[] = [];
|
|
89
|
+
const inspected = witnesses.slice(0, MAX_FRESH_ENGINE_WITNESS_ACQUISITIONS);
|
|
90
|
+
const audit: FreshEnginePvAdmissionAudit = {
|
|
91
|
+
version: 'fresh-engine-pv-admission/1', inputAcquisitions: witnesses.length, inspectedAcquisitions: inspected.length,
|
|
92
|
+
acceptedAcquisitions: 0, rejectedAcquisitions: 0, acceptedLines: 0,
|
|
93
|
+
omittedAcquisitions: witnesses.length - inspected.length, acceptedAcquisitionIds: [], rejectedAcquisitionIds: [],
|
|
94
|
+
rejections: [], rejectionCounts: {},
|
|
95
|
+
};
|
|
96
|
+
const hash = reasoningContextHash(context);
|
|
97
|
+
for (const witness of inspected) {
|
|
98
|
+
const reason = rejectionReason(context, hash, witness);
|
|
99
|
+
if (reason) {
|
|
100
|
+
const acquisitionId = positiveInteger(witness?.acquisitionId) ? witness.acquisitionId : null;
|
|
101
|
+
audit.rejections.push({ acquisitionId, reason });
|
|
102
|
+
audit.rejectedAcquisitionIds.push(acquisitionId);
|
|
103
|
+
audit.rejectionCounts[reason] = (audit.rejectionCounts[reason] ?? 0) + 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
audit.acceptedAcquisitionIds.push(witness.acquisitionId);
|
|
107
|
+
for (const line of witness.lines) {
|
|
108
|
+
lines.push({ start: context.kind === 'position' ? 'after' : witness.phase, ucis: [...line.pv],
|
|
109
|
+
evidenceIds: [witness.evidenceId], authority: 'Bounded engine line', engineEvidence: [{
|
|
110
|
+
acquisitionId: witness.acquisitionId, evidenceId: witness.evidenceId, rootFen: witness.fen,
|
|
111
|
+
rootMove: line.rootMove, depth: line.depth, requestedDepth: witness.requestedDepth,
|
|
112
|
+
engineBuild: witness.engineBuild, command: witness.command,
|
|
113
|
+
scoreCp: line.scoreCp, mateInMoves: line.mateInMoves, scoreProvenance: structuredClone(line.scoreProvenance!),
|
|
114
|
+
conditional: true, scoreInterpretation: 'Root-search score only; the continuation is conditional, not forced and not a score for a later position.',
|
|
115
|
+
}] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (audit.omittedAcquisitions) audit.rejectionCounts['audit-capacity-exceeded'] = audit.omittedAcquisitions;
|
|
119
|
+
audit.acceptedAcquisitions = audit.acceptedAcquisitionIds.length;
|
|
120
|
+
audit.rejectedAcquisitions = audit.rejections.length + audit.omittedAcquisitions;
|
|
121
|
+
audit.acceptedLines = lines.length;
|
|
122
|
+
return { lines, audit };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Presentation witness admission. Scores retain their original root scope, without entailment claims. */
|
|
126
|
+
export function freshEnginePvReferenceLines(context: ReasoningPositionContext, witnesses: readonly FreshEnginePvWitness[]): FreshEnginePvReferenceLine[] {
|
|
127
|
+
return admitFreshEnginePvWitnesses(context, witnesses).lines;
|
|
128
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { effectiveLicensesForRecord } from './evidence-licenses';
|
|
2
|
+
import { Chess } from 'chess.js';
|
|
3
|
+
import { historyPositionContext, recordedEvidenceContext, recordedDetailsMatch } from './recorded-reference-context';
|
|
4
|
+
import { reasoningContextHash, type ReasoningPositionContext, type ReasoningEvidenceRecord } from './poc-contracts';
|
|
5
|
+
export type RecordedReferenceLine = {startFen:string; moves:readonly {uci:string;san:string}[]};
|
|
6
|
+
|
|
7
|
+
type Root = {context:ReasoningPositionContext; records:ReasoningEvidenceRecord[]};
|
|
8
|
+
/** Exact frozen witnesses for game:<recorded ply>:<local UCI path> links. */
|
|
9
|
+
export function historicalProseRoots(selected:ReasoningPositionContext, records:readonly ReasoningEvidenceRecord[], line?:RecordedReferenceLine) {
|
|
10
|
+
const roots = new Map<number,Root>();
|
|
11
|
+
const paths: Array<{ply:number;path:string[];sans:string[];numbers:string[]}> = [];
|
|
12
|
+
const addPath = (ply:number,context:ReasoningPositionContext,path:string[]) => {
|
|
13
|
+
if (!path.length || path.length > 256) return;
|
|
14
|
+
const board = new Chess(context.fenAfter), sans:string[]=[], numbers:string[]=[];
|
|
15
|
+
try { for (const uci of path) {
|
|
16
|
+
const f=board.fen().split(' '); numbers.push(`${f[5]}${f[1]==='w'?'.':'...'}`);
|
|
17
|
+
sans.push(board.move({from:uci.slice(0,2),to:uci.slice(2,4),promotion:uci[4]}).san);
|
|
18
|
+
} paths.push({ply,path,sans,numbers}); } catch { /* Invalid whole path is not evidence. */ }
|
|
19
|
+
};
|
|
20
|
+
let recorded:string[]|undefined;
|
|
21
|
+
const recordedNumbers:string[]=[];
|
|
22
|
+
if (line && line.startFen === (selected.historyStartFen ?? new Chess().fen()) && line.moves.length <= 512) {
|
|
23
|
+
try {
|
|
24
|
+
const board = new Chess(line.startFen);
|
|
25
|
+
recorded=line.moves.map(m=>m.uci);
|
|
26
|
+
let before=board.fen();
|
|
27
|
+
for(let ply=0;ply<recorded.length;ply++) {
|
|
28
|
+
const f=board.fen().split(' '); recordedNumbers.push(`${f[5]}${f[1]==='w'?'.':'...'}`);
|
|
29
|
+
const context:ReasoningPositionContext=ply===0 ? historyPositionContext(line.startFen,[]) : {
|
|
30
|
+
nodeId:'recorded-reference',historyStartFen:line.startFen,historyUci:recorded.slice(0,ply-1),
|
|
31
|
+
fenBefore:before,fenAfter:board.fen(),moveUci:recorded[ply-1],moveSan:line.moves[ply-1].san};
|
|
32
|
+
roots.set(ply,{context,records:[]});
|
|
33
|
+
before=board.fen();
|
|
34
|
+
const uci=recorded[ply];
|
|
35
|
+
if(board.move({from:uci.slice(0,2),to:uci.slice(2,4),promotion:uci[4]}).san!==line.moves[ply].san)throw Error('Invalid recorded SAN');
|
|
36
|
+
}
|
|
37
|
+
} catch { recorded=undefined; roots.clear(); paths.length=0; }
|
|
38
|
+
}
|
|
39
|
+
for (const e of records) {
|
|
40
|
+
if (e.contextHash !== reasoningContextHash(selected) || !e.data.recordedPosition) continue;
|
|
41
|
+
try {
|
|
42
|
+
const p=e.data.recordedPosition as {ply:number;historyUci:string[]};
|
|
43
|
+
const context=recordedEvidenceContext(selected,p);
|
|
44
|
+
if (recorded && p.historyUci.some((uci,i)=>recorded![i]!==uci)) continue;
|
|
45
|
+
const root=roots.get(p.ply) ?? {context,records:[]};
|
|
46
|
+
if(root.context.fenAfter!==context.fenAfter || JSON.stringify(root.context.historyUci)!==JSON.stringify(context.historyUci))continue;
|
|
47
|
+
effectiveLicensesForRecord(e);
|
|
48
|
+
const data=e.data.details as ReasoningEvidenceRecord['data'];
|
|
49
|
+
if(!recordedDetailsMatch(context,data))continue;
|
|
50
|
+
const unwrapped={...e,contextHash:reasoningContextHash(root.context),data};
|
|
51
|
+
root.records.push(unwrapped); roots.set(p.ply,root);
|
|
52
|
+
if(Array.isArray(data.lines))for(const candidate of data.lines) {
|
|
53
|
+
const prefix=Array.isArray(data.played)?data.played.map(m=>(m as {uci:string}).uci):[];
|
|
54
|
+
const pv=(candidate as {pv?:string[]})?.pv;
|
|
55
|
+
if(Array.isArray(pv))addPath(p.ply,context,[...prefix,...pv]);
|
|
56
|
+
}
|
|
57
|
+
if(Array.isArray(data.played))addPath(p.ply,context,data.played.map(m=>(m as {uci:string}).uci));
|
|
58
|
+
} catch { /* Malformed scope never becomes a link. */ }
|
|
59
|
+
}
|
|
60
|
+
// Only numbered notation starts a procedural match. Unnumbered/square prose
|
|
61
|
+
// is never guessed. A complete contiguous sequence must have one exact path.
|
|
62
|
+
const san='(?:O-O(?:-O)?|[KQRBN](?:[a-h1-8])?x?[a-h][1-8]|[a-h](?:x[a-h])?[1-8](?:=[QRBN])?)[+#]?[!?]*';
|
|
63
|
+
const sequence=new RegExp(`(?<![\\w])\\d+\\s*(?:\\.{3}|\\.|…)\\s*${san}(?:\\s+(?:\\d+\\s*(?:\\.{3}|\\.|…)\\s*)?${san})*`,'g');
|
|
64
|
+
const token=new RegExp(`(?:(\\d+)\\s*(\\.{3}|\\.|…)\\s*)?(${san})`,'g');
|
|
65
|
+
const annotate=(text:string)=>text.replace(sequence,(match,offset)=>{
|
|
66
|
+
// Leave authored Markdown links untouched.
|
|
67
|
+
const left=text.slice(0,offset), right=text.slice(offset+match.length);
|
|
68
|
+
if(left.lastIndexOf('[')>left.lastIndexOf(']') || /^\]\(/.test(right))return match;
|
|
69
|
+
const moves=[...match.matchAll(token)];
|
|
70
|
+
const candidates=new Map<string,{ply:number;path:string[]}>();
|
|
71
|
+
for(const witness of paths) for(let from=0;from<witness.path.length;from++) {
|
|
72
|
+
if(from+moves.length>witness.path.length)continue;
|
|
73
|
+
if(!moves.every((m,i)=>m[3].replace(/[!?]/g,'')===witness.sans[from+i]
|
|
74
|
+
&& (!m[1] || `${Number(m[1])}${m[2]==='.'?'.':'...'}`===witness.numbers[from+i])))continue;
|
|
75
|
+
const path=witness.path.slice(0,from+moves.length),root=roots.get(witness.ply)!;
|
|
76
|
+
const history=[...root.context.historyUci,...(root.context.kind==='position'?[]:[root.context.moveUci]),...path];
|
|
77
|
+
candidates.set(history.join(','),{ply:witness.ply,path});
|
|
78
|
+
}
|
|
79
|
+
// Consecutive recorded moves may span the one-move witnesses above.
|
|
80
|
+
if(recorded)for(let ply=0;ply<recorded.length;ply++) {
|
|
81
|
+
const root=roots.get(ply);if(!root || ply+moves.length>recorded.length)continue;
|
|
82
|
+
const path=recorded.slice(ply,ply+moves.length);
|
|
83
|
+
if(moves.every((m,i)=>m[3].replace(/[!?]/g,'')===line!.moves[ply+i].san && (!m[1]||`${Number(m[1])}${m[2]==='.'?'.':'...'}`===recordedNumbers[ply+i])))candidates.set(recorded.slice(0,ply+moves.length).join(','),{ply,path});
|
|
84
|
+
}
|
|
85
|
+
if(candidates.size!==1)return match;
|
|
86
|
+
const c=[...candidates.values()][0];
|
|
87
|
+
return `[${match}](game:${c.ply}:${c.path.join(',')})`;
|
|
88
|
+
});
|
|
89
|
+
const witnessedSuffixes=(ply:number,path:string[]) => {
|
|
90
|
+
const target=roots.get(ply);
|
|
91
|
+
if(!recorded || !target || !path.length) return [];
|
|
92
|
+
const suffixes: Array<{root:Root;path:string[]}> = [];
|
|
93
|
+
const seen=new Set<string>();
|
|
94
|
+
for(const witness of paths) {
|
|
95
|
+
if(witness.ply>=ply)continue;
|
|
96
|
+
const omitted=recorded.slice(witness.ply,ply);
|
|
97
|
+
if(omitted.length!==ply-witness.ply || witness.path.length<omitted.length+path.length
|
|
98
|
+
|| !omitted.every((uci,i)=>witness.path[i]===uci)
|
|
99
|
+
|| !path.every((uci,i)=>witness.path[omitted.length+i]===uci))continue;
|
|
100
|
+
const root=roots.get(witness.ply);
|
|
101
|
+
if(!root?.records.length)continue;
|
|
102
|
+
// The ordered record, not a repeated FEN elsewhere in the game, fixes
|
|
103
|
+
// the omitted prefix and the later root. A source line only proposes a
|
|
104
|
+
// candidate; the caller must still admit its complete evidence witness.
|
|
105
|
+
const board=new Chess(root.context.fenAfter);
|
|
106
|
+
try {
|
|
107
|
+
for(const uci of omitted)board.move({from:uci.slice(0,2),to:uci.slice(2,4),promotion:uci[4]});
|
|
108
|
+
if(board.fen()!==target.context.fenAfter)continue;
|
|
109
|
+
} catch {continue;}
|
|
110
|
+
const fullPath=[...omitted,...path],key=`${witness.ply}:${fullPath.join(',')}`;
|
|
111
|
+
if(!seen.has(key)){seen.add(key);suffixes.push({root,path:fullPath});}
|
|
112
|
+
}
|
|
113
|
+
return suffixes.sort((a,b)=>a.path.length-b.path.length);
|
|
114
|
+
};
|
|
115
|
+
return {roots,annotate,recorded,witnessedSuffixes};
|
|
116
|
+
}
|