@hraness/kb 0.17.1
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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
validateEvaluationEvidenceRegistry,
|
|
5
|
+
type EvaluationEvidenceRegistry,
|
|
6
|
+
} from "./evaluation-evidence.js";
|
|
7
|
+
import {
|
|
8
|
+
assertVerifiedKnowledgeBaseEvaluationV2,
|
|
9
|
+
type KnowledgeBaseEvaluationV2,
|
|
10
|
+
} from "./evaluation-kb-v2.js";
|
|
11
|
+
import type {
|
|
12
|
+
EvaluationPackingContractV2,
|
|
13
|
+
KnowledgeBaseEvaluationFourReaderBatchV2,
|
|
14
|
+
KnowledgeBaseEvaluationRunnerV2Dependencies,
|
|
15
|
+
} from "./evaluation-kb-runner-v2.js";
|
|
16
|
+
import {
|
|
17
|
+
packKnowledgeBaseEvaluationContextV2,
|
|
18
|
+
type EvaluationReaderTokenizerV2,
|
|
19
|
+
} from "./evaluation-packing-v2.js";
|
|
20
|
+
import {
|
|
21
|
+
assertEvaluationRetrieverLockedV2,
|
|
22
|
+
parseRetrievalEvaluationCorpusV2,
|
|
23
|
+
type EvaluationMeasurementProfileV2,
|
|
24
|
+
type RetrievalEvaluationCorpusV2,
|
|
25
|
+
} from "./evaluation-v2.js";
|
|
26
|
+
|
|
27
|
+
const MAX_CACHE_PREPARATION_BYTES = 64 * 1_024;
|
|
28
|
+
const MAX_CACHE_DEFINITION_BYTES = 1 * 1_024 * 1_024;
|
|
29
|
+
const cacheVerifierBrand: unique symbol = Symbol("knowledge-base-evaluation-cache-verifier-v2");
|
|
30
|
+
const cacheVerifierRegistration = new WeakMap<object, string>();
|
|
31
|
+
const fourReaderOpenerBrand: unique symbol = Symbol("knowledge-base-evaluation-four-reader-opener-v2");
|
|
32
|
+
const fourReaderOpenerRegistration = new WeakMap<object, string>();
|
|
33
|
+
|
|
34
|
+
export type KnowledgeBaseEvaluationCacheVerifierV2 = Readonly<{
|
|
35
|
+
readonly preparation: string;
|
|
36
|
+
readonly definitionSha256: string;
|
|
37
|
+
readonly verify: (signal: AbortSignal) => string | Promise<string>;
|
|
38
|
+
readonly [cacheVerifierBrand]: true;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
export type OpenFreshKnowledgeBaseEvaluationV2 = (
|
|
42
|
+
input: Readonly<{
|
|
43
|
+
readonly suiteSha256: string;
|
|
44
|
+
readonly batchIdentity: RetrievalEvaluationCorpusV2["experiment"]["environment"]["fourReaderBatch"];
|
|
45
|
+
readonly profileId: string;
|
|
46
|
+
readonly repetition: number;
|
|
47
|
+
readonly readerIndex: 0 | 1 | 2 | 3;
|
|
48
|
+
readonly signal: AbortSignal;
|
|
49
|
+
}>,
|
|
50
|
+
) => Promise<KnowledgeBaseEvaluationV2>;
|
|
51
|
+
|
|
52
|
+
export type KnowledgeBaseEvaluationFourReaderOpenerV2 = Readonly<{
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly definitionSha256: string;
|
|
55
|
+
readonly open: OpenFreshKnowledgeBaseEvaluationV2;
|
|
56
|
+
readonly [fourReaderOpenerBrand]: true;
|
|
57
|
+
}>;
|
|
58
|
+
|
|
59
|
+
export type CreateKnowledgeBaseEvaluationRunnerV2DependenciesOptions = Readonly<{
|
|
60
|
+
readonly corpus: RetrievalEvaluationCorpusV2;
|
|
61
|
+
readonly evidenceRegistry: EvaluationEvidenceRegistry;
|
|
62
|
+
readonly tokenizer: EvaluationReaderTokenizerV2;
|
|
63
|
+
readonly measureRetrieverOperation: KnowledgeBaseEvaluationRunnerV2Dependencies["measureRetrieverOperation"];
|
|
64
|
+
readonly fourReaderOpener: KnowledgeBaseEvaluationFourReaderOpenerV2;
|
|
65
|
+
readonly cacheVerifier: KnowledgeBaseEvaluationCacheVerifierV2;
|
|
66
|
+
readonly now?: () => number;
|
|
67
|
+
}>;
|
|
68
|
+
|
|
69
|
+
function sha256(text: string): string {
|
|
70
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cacheRegistrationKey(preparation: string, definitionSha256: string): string {
|
|
74
|
+
return JSON.stringify([preparation, definitionSha256]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fourReaderOpenerRegistrationKey(id: string, definitionSha256: string): string {
|
|
78
|
+
return JSON.stringify([id, definitionSha256]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function boundedDefinition(value: string, label: string, maximumBytes: number): string {
|
|
82
|
+
if (
|
|
83
|
+
typeof value !== "string"
|
|
84
|
+
|| value.length === 0
|
|
85
|
+
|| value.includes("\0")
|
|
86
|
+
|| Buffer.byteLength(value, "utf8") > maximumBytes
|
|
87
|
+
) throw new TypeError(`${label} must be non-empty, NUL-free, and bounded.`);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Register an immutable live verifier for one exact cache preparation and definition. */
|
|
92
|
+
export function createKnowledgeBaseEvaluationCacheVerifierV2(options: Readonly<{
|
|
93
|
+
readonly preparation: string;
|
|
94
|
+
readonly definition: string;
|
|
95
|
+
readonly verify: KnowledgeBaseEvaluationCacheVerifierV2["verify"];
|
|
96
|
+
}>): KnowledgeBaseEvaluationCacheVerifierV2 {
|
|
97
|
+
const preparation = boundedDefinition(
|
|
98
|
+
options.preparation,
|
|
99
|
+
"Cache verifier preparation",
|
|
100
|
+
MAX_CACHE_PREPARATION_BYTES,
|
|
101
|
+
);
|
|
102
|
+
const definition = boundedDefinition(
|
|
103
|
+
options.definition,
|
|
104
|
+
"Cache verifier definition",
|
|
105
|
+
MAX_CACHE_DEFINITION_BYTES,
|
|
106
|
+
);
|
|
107
|
+
if (typeof options.verify !== "function") {
|
|
108
|
+
throw new TypeError("Cache verifier verify must be a function.");
|
|
109
|
+
}
|
|
110
|
+
const verifier: KnowledgeBaseEvaluationCacheVerifierV2 = {
|
|
111
|
+
preparation,
|
|
112
|
+
definitionSha256: sha256(definition),
|
|
113
|
+
verify: options.verify,
|
|
114
|
+
[cacheVerifierBrand]: true,
|
|
115
|
+
};
|
|
116
|
+
Object.defineProperty(verifier, cacheVerifierBrand, {
|
|
117
|
+
configurable: false,
|
|
118
|
+
enumerable: false,
|
|
119
|
+
value: true,
|
|
120
|
+
writable: false,
|
|
121
|
+
});
|
|
122
|
+
Object.freeze(verifier);
|
|
123
|
+
cacheVerifierRegistration.set(
|
|
124
|
+
verifier,
|
|
125
|
+
cacheRegistrationKey(verifier.preparation, verifier.definitionSha256),
|
|
126
|
+
);
|
|
127
|
+
return verifier;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Register one immutable four-reader opener under its exact implementation definition. */
|
|
131
|
+
export function createKnowledgeBaseEvaluationFourReaderOpenerV2(options: Readonly<{
|
|
132
|
+
readonly id: string;
|
|
133
|
+
readonly definition: string;
|
|
134
|
+
readonly open: OpenFreshKnowledgeBaseEvaluationV2;
|
|
135
|
+
}>): KnowledgeBaseEvaluationFourReaderOpenerV2 {
|
|
136
|
+
const id = boundedDefinition(options.id, "Four-reader opener id", 512);
|
|
137
|
+
if (/\r|\n/u.test(id) || id.normalize("NFC") !== id || id.trim() !== id) {
|
|
138
|
+
throw new TypeError("Four-reader opener id must be a trimmed NFC single line.");
|
|
139
|
+
}
|
|
140
|
+
const definition = boundedDefinition(
|
|
141
|
+
options.definition,
|
|
142
|
+
"Four-reader opener definition",
|
|
143
|
+
MAX_CACHE_DEFINITION_BYTES,
|
|
144
|
+
);
|
|
145
|
+
if (typeof options.open !== "function") {
|
|
146
|
+
throw new TypeError("Four-reader opener open must be a function.");
|
|
147
|
+
}
|
|
148
|
+
const opener: KnowledgeBaseEvaluationFourReaderOpenerV2 = {
|
|
149
|
+
id,
|
|
150
|
+
definitionSha256: sha256(definition),
|
|
151
|
+
open: options.open,
|
|
152
|
+
[fourReaderOpenerBrand]: true,
|
|
153
|
+
};
|
|
154
|
+
Object.defineProperty(opener, fourReaderOpenerBrand, {
|
|
155
|
+
configurable: false,
|
|
156
|
+
enumerable: false,
|
|
157
|
+
value: true,
|
|
158
|
+
writable: false,
|
|
159
|
+
});
|
|
160
|
+
Object.freeze(opener);
|
|
161
|
+
fourReaderOpenerRegistration.set(
|
|
162
|
+
opener,
|
|
163
|
+
fourReaderOpenerRegistrationKey(opener.id, opener.definitionSha256),
|
|
164
|
+
);
|
|
165
|
+
return opener;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function assertRegisteredCacheVerifier(
|
|
169
|
+
verifier: KnowledgeBaseEvaluationCacheVerifierV2,
|
|
170
|
+
corpus: RetrievalEvaluationCorpusV2,
|
|
171
|
+
): void {
|
|
172
|
+
if (
|
|
173
|
+
verifier === null
|
|
174
|
+
|| typeof verifier !== "object"
|
|
175
|
+
|| verifier[cacheVerifierBrand] !== true
|
|
176
|
+
|| cacheVerifierRegistration.get(verifier)
|
|
177
|
+
!== cacheRegistrationKey(verifier.preparation, verifier.definitionSha256)
|
|
178
|
+
) throw new TypeError("Evaluation cache verifier is not a registered capability.");
|
|
179
|
+
const sealed = corpus.experiment.environment.cache;
|
|
180
|
+
if (
|
|
181
|
+
verifier.preparation !== sealed.preparation
|
|
182
|
+
|| verifier.definitionSha256 !== sealed.fingerprintSha256
|
|
183
|
+
) {
|
|
184
|
+
throw new TypeError(
|
|
185
|
+
"Evaluation cache verifier does not match the sealed preparation and definition digest.",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function assertRegisteredFourReaderOpener(
|
|
191
|
+
opener: KnowledgeBaseEvaluationFourReaderOpenerV2,
|
|
192
|
+
corpus: RetrievalEvaluationCorpusV2,
|
|
193
|
+
): void {
|
|
194
|
+
if (
|
|
195
|
+
opener === null
|
|
196
|
+
|| typeof opener !== "object"
|
|
197
|
+
|| opener[fourReaderOpenerBrand] !== true
|
|
198
|
+
|| fourReaderOpenerRegistration.get(opener)
|
|
199
|
+
!== fourReaderOpenerRegistrationKey(opener.id, opener.definitionSha256)
|
|
200
|
+
) throw new TypeError("Evaluation four-reader opener is not a registered capability.");
|
|
201
|
+
const sealed = corpus.experiment.environment.fourReaderBatch;
|
|
202
|
+
if (opener.id !== sealed.id || opener.definitionSha256 !== sealed.sha256) {
|
|
203
|
+
throw new TypeError("Evaluation four-reader opener does not match the sealed batch identity and definition digest.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function throwIfAborted(signal: AbortSignal): void {
|
|
208
|
+
if (signal.aborted) throw signal.reason ?? new Error("Evaluation execution was aborted.");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function finiteDuration(value: number): number {
|
|
212
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
213
|
+
throw new TypeError("Evaluation packing duration must be non-negative and finite.");
|
|
214
|
+
}
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function profileFor(
|
|
219
|
+
corpus: RetrievalEvaluationCorpusV2,
|
|
220
|
+
profileId: string,
|
|
221
|
+
): EvaluationMeasurementProfileV2 {
|
|
222
|
+
const profile = corpus.measurementProfiles.find(({ id }) => id === profileId);
|
|
223
|
+
if (profile === undefined) throw new TypeError(`Evaluation profile ${profileId} is not sealed by the corpus.`);
|
|
224
|
+
return profile;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assertRepetition(profile: EvaluationMeasurementProfileV2, repetition: number): void {
|
|
228
|
+
if (!Number.isSafeInteger(repetition) || repetition < 1 || repetition > profile.repetitions) {
|
|
229
|
+
throw new TypeError(`Evaluation repetition is outside profile ${profile.id}.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function assertPackingContract(
|
|
234
|
+
contract: EvaluationPackingContractV2,
|
|
235
|
+
corpus: RetrievalEvaluationCorpusV2,
|
|
236
|
+
): void {
|
|
237
|
+
const sealed = corpus.experiment;
|
|
238
|
+
if (
|
|
239
|
+
contract.suiteSha256 !== corpus.manifest.corpusSha256
|
|
240
|
+
|| contract.tokenizer.id !== sealed.environment.tokenizer.id
|
|
241
|
+
|| contract.tokenizer.sha256 !== sealed.environment.tokenizer.sha256
|
|
242
|
+
|| contract.contextCeilings.utf8Bytes !== sealed.protocol.contextCeilings.utf8Bytes
|
|
243
|
+
|| contract.contextCeilings.readerTokens !== sealed.protocol.contextCeilings.readerTokens
|
|
244
|
+
) throw new TypeError("Packing request does not match the sealed corpus contract.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function closePartialEvaluations(
|
|
248
|
+
evaluations: readonly KnowledgeBaseEvaluationV2[],
|
|
249
|
+
openingFailure: unknown,
|
|
250
|
+
): Promise<never> {
|
|
251
|
+
const settlements = await Promise.allSettled(evaluations.map((evaluation) =>
|
|
252
|
+
Promise.resolve().then(() => Reflect.apply(evaluation.close, evaluation, []))));
|
|
253
|
+
const closeFailures: unknown[] = [];
|
|
254
|
+
for (const result of settlements) {
|
|
255
|
+
if (result.status === "rejected") closeFailures.push(result.reason as unknown);
|
|
256
|
+
}
|
|
257
|
+
if (closeFailures.length > 0) {
|
|
258
|
+
throw new AggregateError(
|
|
259
|
+
[openingFailure, ...closeFailures],
|
|
260
|
+
"Four-reader evaluation opening failed and partial sessions did not close cleanly.",
|
|
261
|
+
{ cause: openingFailure },
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
throw openingFailure;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Bind the complete runner dependency surface to one sealed corpus and live capabilities. */
|
|
268
|
+
export function createKnowledgeBaseEvaluationRunnerV2Dependencies(
|
|
269
|
+
options: CreateKnowledgeBaseEvaluationRunnerV2DependenciesOptions,
|
|
270
|
+
): KnowledgeBaseEvaluationRunnerV2Dependencies {
|
|
271
|
+
const corpus = parseRetrievalEvaluationCorpusV2(options.corpus, { claimPromotion: false });
|
|
272
|
+
validateEvaluationEvidenceRegistry(options.evidenceRegistry);
|
|
273
|
+
if (typeof options.measureRetrieverOperation !== "function") {
|
|
274
|
+
throw new TypeError("Evaluation retriever operation measurer must be a function.");
|
|
275
|
+
}
|
|
276
|
+
if (
|
|
277
|
+
options.tokenizer === null
|
|
278
|
+
|| typeof options.tokenizer !== "object"
|
|
279
|
+
|| options.tokenizer.id !== corpus.experiment.environment.tokenizer.id
|
|
280
|
+
|| options.tokenizer.sha256 !== corpus.experiment.environment.tokenizer.sha256
|
|
281
|
+
) throw new TypeError("Evaluation tokenizer does not match the sealed corpus.");
|
|
282
|
+
assertRegisteredCacheVerifier(options.cacheVerifier, corpus);
|
|
283
|
+
assertRegisteredFourReaderOpener(options.fourReaderOpener, corpus);
|
|
284
|
+
const now = options.now ?? performance.now.bind(performance);
|
|
285
|
+
if (typeof now !== "function") throw new TypeError("Evaluation clock must be a function.");
|
|
286
|
+
const openedEvaluationSessions = new WeakSet<object>();
|
|
287
|
+
const verifyCapability = options.cacheVerifier.verify;
|
|
288
|
+
const fourReaderOpenCapability = options.fourReaderOpener.open;
|
|
289
|
+
const sealedCacheDigest = corpus.experiment.environment.cache.fingerprintSha256;
|
|
290
|
+
|
|
291
|
+
const verifyCacheFingerprint = async (signal: AbortSignal): Promise<string> => {
|
|
292
|
+
throwIfAborted(signal);
|
|
293
|
+
assertRegisteredCacheVerifier(options.cacheVerifier, corpus);
|
|
294
|
+
const observed = await verifyCapability(signal);
|
|
295
|
+
throwIfAborted(signal);
|
|
296
|
+
if (observed !== sealedCacheDigest) {
|
|
297
|
+
throw new TypeError("Live evaluation cache fingerprint drifted from the sealed definition digest.");
|
|
298
|
+
}
|
|
299
|
+
return observed;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const measureRetrieverOperation: KnowledgeBaseEvaluationRunnerV2Dependencies["measureRetrieverOperation"] =
|
|
303
|
+
async (input) => {
|
|
304
|
+
throwIfAborted(input.signal);
|
|
305
|
+
if (input.corpus.manifest.corpusSha256 !== corpus.manifest.corpusSha256) {
|
|
306
|
+
throw new TypeError("Retriever operation measurement does not match the bound sealed corpus.");
|
|
307
|
+
}
|
|
308
|
+
assertEvaluationRetrieverLockedV2(corpus, input.descriptor);
|
|
309
|
+
const profile = profileFor(corpus, input.profile.id);
|
|
310
|
+
if (
|
|
311
|
+
profile.operation !== input.operation
|
|
312
|
+
|| JSON.stringify(profile) !== JSON.stringify(input.profile)
|
|
313
|
+
) throw new TypeError("Retriever operation measurement profile does not match the sealed corpus.");
|
|
314
|
+
assertRepetition(profile, input.repetition);
|
|
315
|
+
return options.measureRetrieverOperation(Object.freeze({
|
|
316
|
+
...input,
|
|
317
|
+
corpus,
|
|
318
|
+
profile,
|
|
319
|
+
}));
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const pack: KnowledgeBaseEvaluationRunnerV2Dependencies["pack"] = async (input) => {
|
|
323
|
+
throwIfAborted(input.signal);
|
|
324
|
+
assertPackingContract(input.contract, corpus);
|
|
325
|
+
assertEvaluationRetrieverLockedV2(corpus, input.descriptor);
|
|
326
|
+
if (input.result.retrieverId !== input.descriptor.id) {
|
|
327
|
+
throw new TypeError("Packing result does not match its sealed retriever descriptor.");
|
|
328
|
+
}
|
|
329
|
+
const profile = profileFor(corpus, input.profileId);
|
|
330
|
+
if (profile.operation !== "packing") {
|
|
331
|
+
throw new TypeError(`Evaluation profile ${profile.id} is not a packing profile.`);
|
|
332
|
+
}
|
|
333
|
+
assertRepetition(profile, input.repetition);
|
|
334
|
+
if (!corpus.queries.some(({ id }) => id === input.queryId)) {
|
|
335
|
+
throw new TypeError(`Evaluation query ${input.queryId} is not sealed by the corpus.`);
|
|
336
|
+
}
|
|
337
|
+
const startedAt = now();
|
|
338
|
+
const packed = await packKnowledgeBaseEvaluationContextV2({
|
|
339
|
+
corpus,
|
|
340
|
+
result: input.result,
|
|
341
|
+
evidenceRegistry: options.evidenceRegistry,
|
|
342
|
+
tokenizer: options.tokenizer,
|
|
343
|
+
});
|
|
344
|
+
throwIfAborted(input.signal);
|
|
345
|
+
const packedBytes = Buffer.from(packed.text, "utf8");
|
|
346
|
+
if (
|
|
347
|
+
packedBytes.byteLength !== packed.utf8Bytes
|
|
348
|
+
|| createHash("sha256").update(packedBytes).digest("hex") !== packed.packedBytesSha256
|
|
349
|
+
) throw new TypeError("Packed-context accounting does not match its exact returned bytes.");
|
|
350
|
+
return Object.freeze({
|
|
351
|
+
durationMs: finiteDuration(now() - startedAt),
|
|
352
|
+
packedContext: Object.freeze({
|
|
353
|
+
utf8Bytes: packed.utf8Bytes,
|
|
354
|
+
readerTokens: packed.readerTokens,
|
|
355
|
+
}),
|
|
356
|
+
includedEvidenceUnitIds: packed.includedEvidenceUnitIds,
|
|
357
|
+
truncated: packed.truncated,
|
|
358
|
+
packedBytesSha256: packed.packedBytesSha256,
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const openFourReaderBatch: KnowledgeBaseEvaluationRunnerV2Dependencies["openFourReaderBatch"] =
|
|
363
|
+
async (input): Promise<KnowledgeBaseEvaluationFourReaderBatchV2> => {
|
|
364
|
+
throwIfAborted(input.signal);
|
|
365
|
+
const profile = profileFor(corpus, input.profileId);
|
|
366
|
+
if (profile.operation !== "four-reader-query" || profile.concurrency !== 4) {
|
|
367
|
+
throw new TypeError(`Evaluation profile ${profile.id} is not a four-reader profile.`);
|
|
368
|
+
}
|
|
369
|
+
assertRepetition(profile, input.repetition);
|
|
370
|
+
const evaluations: KnowledgeBaseEvaluationV2[] = [];
|
|
371
|
+
const currentSessions = new Set<KnowledgeBaseEvaluationV2>();
|
|
372
|
+
try {
|
|
373
|
+
for (let readerIndex = 0; readerIndex < 4; readerIndex += 1) {
|
|
374
|
+
throwIfAborted(input.signal);
|
|
375
|
+
assertRegisteredFourReaderOpener(options.fourReaderOpener, corpus);
|
|
376
|
+
const evaluation = await fourReaderOpenCapability(Object.freeze({
|
|
377
|
+
suiteSha256: corpus.manifest.corpusSha256,
|
|
378
|
+
batchIdentity: corpus.experiment.environment.fourReaderBatch,
|
|
379
|
+
profileId: profile.id,
|
|
380
|
+
repetition: input.repetition,
|
|
381
|
+
readerIndex: readerIndex as 0 | 1 | 2 | 3,
|
|
382
|
+
signal: input.signal,
|
|
383
|
+
}));
|
|
384
|
+
if (currentSessions.has(evaluation) || openedEvaluationSessions.has(evaluation)) {
|
|
385
|
+
throw new TypeError("Four-reader evaluation opener returned a duplicate or reused session.");
|
|
386
|
+
}
|
|
387
|
+
// Ownership transfers when the opener returns. Record it before any
|
|
388
|
+
// brand or shape assertion so a malformed session is still closed.
|
|
389
|
+
evaluations.push(evaluation);
|
|
390
|
+
currentSessions.add(evaluation);
|
|
391
|
+
openedEvaluationSessions.add(evaluation);
|
|
392
|
+
assertVerifiedKnowledgeBaseEvaluationV2(evaluation, corpus);
|
|
393
|
+
}
|
|
394
|
+
throwIfAborted(input.signal);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
return closePartialEvaluations(evaluations, error);
|
|
397
|
+
}
|
|
398
|
+
return Object.freeze({
|
|
399
|
+
id: options.fourReaderOpener.id,
|
|
400
|
+
sha256: options.fourReaderOpener.definitionSha256,
|
|
401
|
+
evaluations: Object.freeze(evaluations),
|
|
402
|
+
verifyCacheFingerprint,
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const verifyWarmCacheFingerprint: KnowledgeBaseEvaluationRunnerV2Dependencies["verifyWarmCacheFingerprint"] =
|
|
407
|
+
async (input) => {
|
|
408
|
+
const profile = profileFor(corpus, input.profileId);
|
|
409
|
+
if (profile.operation !== input.operation) {
|
|
410
|
+
throw new TypeError("Warm cache verification profile does not match the sealed operation.");
|
|
411
|
+
}
|
|
412
|
+
assertRepetition(profile, input.repetition);
|
|
413
|
+
if (input.phase !== "before" && input.phase !== "after") {
|
|
414
|
+
throw new TypeError("Warm cache verification phase is invalid.");
|
|
415
|
+
}
|
|
416
|
+
return verifyCacheFingerprint(input.signal);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
return Object.freeze({
|
|
420
|
+
measureRetrieverOperation,
|
|
421
|
+
pack,
|
|
422
|
+
openFourReaderBatch,
|
|
423
|
+
verifyWarmCacheFingerprint,
|
|
424
|
+
...(options.now === undefined ? {} : { now }),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assertEvaluationRetrieverLockedV2,
|
|
5
|
+
type EvaluationRetrieverDescriptorV2,
|
|
6
|
+
type RetrievalEvaluationCorpusV2,
|
|
7
|
+
} from "./evaluation-v2.js";
|
|
8
|
+
|
|
9
|
+
const MAX_IMPLEMENTATION_SOURCES = 32;
|
|
10
|
+
const MAX_IMPLEMENTATION_SOURCE_BYTES = 4 * 1_024 * 1_024;
|
|
11
|
+
const MAX_IMPLEMENTATION_TOTAL_BYTES = 16 * 1_024 * 1_024;
|
|
12
|
+
const GIT_OBJECT = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
|
|
13
|
+
const artifactBrand: unique symbol = Symbol("verified-evaluation-implementation-v2");
|
|
14
|
+
|
|
15
|
+
export type EvaluationImplementationSourceV2 = Readonly<{
|
|
16
|
+
readonly sourcePath: string;
|
|
17
|
+
readonly bytes: Uint8Array;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export type VerifiedEvaluationImplementationArtifactV2 = Readonly<{
|
|
21
|
+
readonly retrieverId: string;
|
|
22
|
+
readonly repositoryCommit: string;
|
|
23
|
+
readonly implementationSha256: string;
|
|
24
|
+
readonly sourcePaths: readonly string[];
|
|
25
|
+
readonly [artifactBrand]: true;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
function confinedPath(value: string, label: string): string {
|
|
29
|
+
if (
|
|
30
|
+
value === ""
|
|
31
|
+
|| value.normalize("NFC") !== value
|
|
32
|
+
|| /[\0\r\n\\]/u.test(value)
|
|
33
|
+
|| value.startsWith("/")
|
|
34
|
+
|| value.startsWith("./")
|
|
35
|
+
|| /^[a-z]:[\\/]/iu.test(value)
|
|
36
|
+
|| value.split("/").some((part) => part === "" || part === "." || part === "..")
|
|
37
|
+
) throw new TypeError(`${label} must be a canonical confined repository-relative path.`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function canonicalJson(value: unknown): string {
|
|
42
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
|
|
43
|
+
if (typeof value === "number") {
|
|
44
|
+
if (!Number.isFinite(value)) throw new TypeError("Canonical JSON cannot contain a non-finite number.");
|
|
45
|
+
return JSON.stringify(value);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
48
|
+
if (typeof value === "object" && value !== null) {
|
|
49
|
+
const input = value as Readonly<Record<string, unknown>>;
|
|
50
|
+
return `{${Object.keys(input).toSorted().map((key) =>
|
|
51
|
+
`${JSON.stringify(key)}:${canonicalJson(input[key])}`).join(",")}}`;
|
|
52
|
+
}
|
|
53
|
+
throw new TypeError("Canonical JSON accepts only JSON values.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sha256(bytes: Uint8Array): string {
|
|
57
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Hash the exact implementation source manifest used by both authoring and execution. */
|
|
61
|
+
export function evaluationImplementationArtifactSha256V2(
|
|
62
|
+
sources: readonly EvaluationImplementationSourceV2[],
|
|
63
|
+
): string {
|
|
64
|
+
if (sources.length < 1 || sources.length > MAX_IMPLEMENTATION_SOURCES) {
|
|
65
|
+
throw new TypeError(`Implementation sources must contain from 1 through ${MAX_IMPLEMENTATION_SOURCES} files.`);
|
|
66
|
+
}
|
|
67
|
+
let totalBytes = 0;
|
|
68
|
+
const seen = new Set<string>();
|
|
69
|
+
const manifest = sources.map(({ sourcePath: rawSourcePath, bytes }, index) => {
|
|
70
|
+
const sourcePath = confinedPath(rawSourcePath, `implementation sources[${index}].sourcePath`);
|
|
71
|
+
if (seen.has(sourcePath)) throw new TypeError(`Implementation source path ${sourcePath} is repeated.`);
|
|
72
|
+
seen.add(sourcePath);
|
|
73
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength > MAX_IMPLEMENTATION_SOURCE_BYTES) {
|
|
74
|
+
throw new TypeError(
|
|
75
|
+
`Implementation source ${sourcePath} must contain at most ${MAX_IMPLEMENTATION_SOURCE_BYTES} bytes.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
totalBytes += bytes.byteLength;
|
|
79
|
+
if (totalBytes > MAX_IMPLEMENTATION_TOTAL_BYTES) {
|
|
80
|
+
throw new TypeError(`Implementation sources exceed ${MAX_IMPLEMENTATION_TOTAL_BYTES} aggregate bytes.`);
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({ sourcePath, sha256: sha256(bytes) });
|
|
83
|
+
}).toSorted((left, right) => left.sourcePath.localeCompare(right.sourcePath));
|
|
84
|
+
return sha256(Buffer.from(`${canonicalJson(manifest)}\n`, "utf8"));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Prove that the files loaded for execution are from the corpus commit and match
|
|
89
|
+
* the candidate-locked descriptor. The returned branded artifact is the only
|
|
90
|
+
* runtime binding accepted by the KB v2 adapter.
|
|
91
|
+
*/
|
|
92
|
+
export function verifyEvaluationImplementationArtifactV2(options: Readonly<{
|
|
93
|
+
readonly corpus: RetrievalEvaluationCorpusV2;
|
|
94
|
+
readonly descriptor: EvaluationRetrieverDescriptorV2;
|
|
95
|
+
readonly loadedRepositoryCommit: string;
|
|
96
|
+
readonly sources: readonly EvaluationImplementationSourceV2[];
|
|
97
|
+
}>): VerifiedEvaluationImplementationArtifactV2 {
|
|
98
|
+
if (!GIT_OBJECT.test(options.loadedRepositoryCommit)) {
|
|
99
|
+
throw new TypeError("Loaded implementation repository commit must be a lowercase Git object ID.");
|
|
100
|
+
}
|
|
101
|
+
if (options.loadedRepositoryCommit !== options.corpus.frozen.repositoryCommit) {
|
|
102
|
+
throw new TypeError("Loaded implementation is not from the corpus's frozen repository commit.");
|
|
103
|
+
}
|
|
104
|
+
assertEvaluationRetrieverLockedV2(options.corpus, options.descriptor);
|
|
105
|
+
const implementationSha256 = evaluationImplementationArtifactSha256V2(options.sources);
|
|
106
|
+
if (implementationSha256 !== options.descriptor.implementationSha256) {
|
|
107
|
+
throw new TypeError(`Retriever ${options.descriptor.id} implementation bytes do not match its candidate lock.`);
|
|
108
|
+
}
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
retrieverId: options.descriptor.id,
|
|
111
|
+
repositoryCommit: options.loadedRepositoryCommit,
|
|
112
|
+
implementationSha256,
|
|
113
|
+
sourcePaths: Object.freeze(options.sources.map(({ sourcePath }, index) =>
|
|
114
|
+
confinedPath(sourcePath, `implementation sources[${index}].sourcePath`)).toSorted()),
|
|
115
|
+
[artifactBrand]: true as const,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function assertEvaluationImplementationArtifactV2(
|
|
120
|
+
artifact: VerifiedEvaluationImplementationArtifactV2 | undefined,
|
|
121
|
+
corpus: RetrievalEvaluationCorpusV2,
|
|
122
|
+
descriptor: EvaluationRetrieverDescriptorV2,
|
|
123
|
+
): void {
|
|
124
|
+
if (
|
|
125
|
+
artifact?.[artifactBrand] !== true
|
|
126
|
+
|| artifact.retrieverId !== descriptor.id
|
|
127
|
+
|| artifact.repositoryCommit !== corpus.frozen.repositoryCommit
|
|
128
|
+
|| artifact.implementationSha256 !== descriptor.implementationSha256
|
|
129
|
+
) throw new TypeError(`Retriever ${descriptor.id} lacks a verified frozen implementation artifact.`);
|
|
130
|
+
assertEvaluationRetrieverLockedV2(corpus, descriptor);
|
|
131
|
+
}
|