@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.
Files changed (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,452 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import {
4
+ validateEvaluationEvidenceRegistry,
5
+ type EvaluationEvidenceUnit,
6
+ type EvaluationEvidenceRegistry,
7
+ } from "./evaluation-evidence.js";
8
+ import type { KnowledgeBaseEvaluationRetrieverResultV2 } from "./evaluation-kb-v2.js";
9
+ import type { RetrievalEvaluationCorpusV2 } from "./evaluation-v2.js";
10
+
11
+ const UTF8_BYTE_TOKENIZER_DEFINITION = "hraness/kb evaluation reader tokenizer: one token per UTF-8 byte; v1\n";
12
+ const MAX_TOKENIZER_DEFINITION_BYTES = 1 * 1_024 * 1_024;
13
+ const MAX_TOKENIZER_ID_BYTES = 512;
14
+ const tokenizerBrand: unique symbol = Symbol("registered-evaluation-reader-tokenizer-v2");
15
+ const registeredTokenizers = new WeakSet<object>();
16
+ const registeredTokenizerById = new Map<string, EvaluationReaderTokenizerV2>();
17
+
18
+ export type EvaluationReaderTokenizerV2 = Readonly<{
19
+ readonly id: string;
20
+ readonly sha256: string;
21
+ readonly count: (text: string) => number | Promise<number>;
22
+ readonly [tokenizerBrand]: true;
23
+ }>;
24
+
25
+ /** Register one immutable tokenizer capability under an exact definition digest. */
26
+ export function createEvaluationReaderTokenizerV2(options: Readonly<{
27
+ readonly id: string;
28
+ readonly definition: string;
29
+ readonly count: EvaluationReaderTokenizerV2["count"];
30
+ }>): EvaluationReaderTokenizerV2 {
31
+ if (
32
+ typeof options.id !== "string"
33
+ || options.id.length === 0
34
+ || /[\0\r\n]/u.test(options.id)
35
+ || Buffer.byteLength(options.id, "utf8") > MAX_TOKENIZER_ID_BYTES
36
+ ) throw new TypeError("Evaluation reader tokenizer id must be a non-empty bounded single line.");
37
+ if (
38
+ typeof options.definition !== "string"
39
+ || options.definition.length === 0
40
+ || Buffer.byteLength(options.definition, "utf8") > MAX_TOKENIZER_DEFINITION_BYTES
41
+ ) throw new TypeError("Evaluation reader tokenizer definition must be non-empty and bounded.");
42
+ if (typeof options.count !== "function") {
43
+ throw new TypeError("Evaluation reader tokenizer count must be a function.");
44
+ }
45
+ if (registeredTokenizerById.has(options.id)) {
46
+ throw new TypeError(`Evaluation reader tokenizer id ${options.id} is already registered.`);
47
+ }
48
+ const tokenizer: EvaluationReaderTokenizerV2 = {
49
+ id: options.id,
50
+ sha256: createHash("sha256").update(options.definition, "utf8").digest("hex"),
51
+ count: options.count,
52
+ [tokenizerBrand]: true,
53
+ };
54
+ Object.defineProperty(tokenizer, tokenizerBrand, {
55
+ configurable: false,
56
+ enumerable: false,
57
+ value: true,
58
+ writable: false,
59
+ });
60
+ Object.freeze(tokenizer);
61
+ registeredTokenizers.add(tokenizer);
62
+ registeredTokenizerById.set(tokenizer.id, tokenizer);
63
+ return tokenizer;
64
+ }
65
+
66
+ export const utf8ByteEvaluationReaderTokenizerV2 = createEvaluationReaderTokenizerV2({
67
+ id: "utf8-byte-tokenizer-v1",
68
+ definition: UTF8_BYTE_TOKENIZER_DEFINITION,
69
+ count: (text: string) => Buffer.byteLength(text, "utf8"),
70
+ });
71
+
72
+ export type PackedKnowledgeBaseEvaluationContextV2 = Readonly<{
73
+ readonly text: string;
74
+ readonly utf8Bytes: number;
75
+ readonly readerTokens: number;
76
+ readonly includedEvidenceUnitIds: readonly string[];
77
+ readonly truncated: boolean;
78
+ readonly packedBytesSha256: string;
79
+ }>;
80
+
81
+ type VerifiedPackingEvidenceV2 = Readonly<{
82
+ readonly evidenceUnitId: string;
83
+ readonly documentId: string;
84
+ readonly sourceFamilyId: string;
85
+ readonly sourceClass: string;
86
+ readonly trustClass: string;
87
+ readonly sourcePath: string;
88
+ readonly lineRange: Readonly<{ readonly start: number; readonly end: number }>;
89
+ readonly headingPath: readonly string[];
90
+ readonly sourcePage?: number;
91
+ readonly text: string;
92
+ }>;
93
+
94
+ function equalStrings(left: readonly string[], right: readonly string[]): boolean {
95
+ return left.length === right.length && left.every((value, index) => value === right[index]);
96
+ }
97
+
98
+ function verifyBinding(
99
+ binding: KnowledgeBaseEvaluationRetrieverResultV2["evidenceUnits"][number],
100
+ corpusUnit: RetrievalEvaluationCorpusV2["evidenceUnits"][number],
101
+ live: EvaluationEvidenceUnit,
102
+ family: RetrievalEvaluationCorpusV2["sourceFamilies"][number],
103
+ document: RetrievalEvaluationCorpusV2["documents"][number],
104
+ ): VerifiedPackingEvidenceV2 {
105
+ const locator = binding.locator;
106
+ if (
107
+ binding.evidenceUnitId !== corpusUnit.id
108
+ || binding.registryUnitId !== live.id
109
+ || corpusUnit.id !== live.id
110
+ || binding.documentId !== corpusUnit.documentId
111
+ || corpusUnit.documentId !== document.id
112
+ || document.id !== live.documentId
113
+ || binding.sourceFamilyId !== corpusUnit.sourceFamilyId
114
+ || corpusUnit.sourceFamilyId !== family.id
115
+ || document.sourceFamilyId !== family.id
116
+ || binding.sourceClass !== family.sourceClass
117
+ || binding.trustClass !== corpusUnit.trustClass
118
+ || corpusUnit.trustClass !== document.trustClass
119
+ || document.trustClass !== family.trustClass
120
+ || family.trustClass !== live.trustClass
121
+ || binding.trustClass !== live.trustClass
122
+ || locator.evidenceUnitId !== binding.evidenceUnitId
123
+ || locator.sourceFamilyId !== family.id
124
+ || locator.sourceClass !== family.sourceClass
125
+ || locator.trustClass !== live.trustClass
126
+ || corpusUnit.sourcePath !== live.sourcePath
127
+ || locator.sourcePath !== live.sourcePath
128
+ || corpusUnit.lineRange.start !== live.lineRange.start
129
+ || corpusUnit.lineRange.end !== live.lineRange.end
130
+ || locator.lineRange.start !== live.lineRange.start
131
+ || locator.lineRange.end !== live.lineRange.end
132
+ || !equalStrings(corpusUnit.headingPath, live.headingAncestry)
133
+ || !equalStrings(locator.headingPath, live.headingAncestry)
134
+ || corpusUnit.sourcePage !== live.pdfPage
135
+ || locator.sourcePage !== live.pdfPage
136
+ ) {
137
+ throw new TypeError(
138
+ `Packed evidence binding ${binding.evidenceUnitId} does not exactly match its live registry metadata.`,
139
+ );
140
+ }
141
+ return Object.freeze({
142
+ evidenceUnitId: live.id,
143
+ documentId: live.documentId,
144
+ sourceFamilyId: family.id,
145
+ sourceClass: family.sourceClass,
146
+ trustClass: live.trustClass,
147
+ sourcePath: live.sourcePath,
148
+ lineRange: Object.freeze({ start: live.lineRange.start, end: live.lineRange.end }),
149
+ headingPath: Object.freeze([...live.headingAncestry]),
150
+ ...(live.pdfPage === undefined ? {} : { sourcePage: live.pdfPage }),
151
+ text: live.text,
152
+ });
153
+ }
154
+
155
+ function assertDecisionProvenance(
156
+ decision: KnowledgeBaseEvaluationRetrieverResultV2["trace"]["candidateDecisions"][number],
157
+ verifiedById: ReadonlyMap<string, VerifiedPackingEvidenceV2>,
158
+ ): void {
159
+ if (new Set(decision.evidenceUnitIds).size !== decision.evidenceUnitIds.length) {
160
+ throw new TypeError("Evaluation candidate decision repeats an evidence-unit binding.");
161
+ }
162
+ if (decision.provenance.length !== decision.evidenceUnitIds.length) {
163
+ throw new TypeError("Evaluation candidate decision provenance does not exactly cover its evidence units.");
164
+ }
165
+ for (const [index, evidenceUnitId] of decision.evidenceUnitIds.entries()) {
166
+ const verified = verifiedById.get(evidenceUnitId);
167
+ if (verified === undefined) {
168
+ throw new TypeError(`Evaluation candidate decision names unbound evidence unit ${evidenceUnitId}.`);
169
+ }
170
+ const locator = decision.provenance[index];
171
+ if (
172
+ locator === undefined
173
+ || (decision.documentId !== verified.documentId && decision.laneId !== "graph")
174
+ || locator.evidenceUnitId !== verified.evidenceUnitId
175
+ || locator.sourceFamilyId !== verified.sourceFamilyId
176
+ || locator.sourceClass !== verified.sourceClass
177
+ || locator.trustClass !== verified.trustClass
178
+ || locator.sourcePath !== verified.sourcePath
179
+ || locator.lineRange.start !== verified.lineRange.start
180
+ || locator.lineRange.end !== verified.lineRange.end
181
+ || !equalStrings(locator.headingPath, verified.headingPath)
182
+ || locator.sourcePage !== verified.sourcePage
183
+ ) {
184
+ throw new TypeError(
185
+ `Evaluation candidate decision provenance for ${evidenceUnitId} does not match verified live evidence.`,
186
+ );
187
+ }
188
+ }
189
+ }
190
+
191
+ function locatorMatchesVerified(
192
+ locator: KnowledgeBaseEvaluationRetrieverResultV2["trace"]["candidateDecisions"][number]["provenance"][number],
193
+ verified: VerifiedPackingEvidenceV2,
194
+ ): boolean {
195
+ return locator.evidenceUnitId === verified.evidenceUnitId
196
+ && locator.sourceFamilyId === verified.sourceFamilyId
197
+ && locator.sourceClass === verified.sourceClass
198
+ && locator.trustClass === verified.trustClass
199
+ && locator.sourcePath === verified.sourcePath
200
+ && locator.lineRange.start === verified.lineRange.start
201
+ && locator.lineRange.end === verified.lineRange.end
202
+ && equalStrings(locator.headingPath, verified.headingPath)
203
+ && locator.sourcePage === verified.sourcePage;
204
+ }
205
+
206
+ function assertRankedCandidateProvenance(
207
+ candidate: KnowledgeBaseEvaluationRetrieverResultV2["candidates"][number],
208
+ decisions: KnowledgeBaseEvaluationRetrieverResultV2["trace"]["candidateDecisions"],
209
+ verifiedById: ReadonlyMap<string, VerifiedPackingEvidenceV2>,
210
+ ): void {
211
+ if (
212
+ new Set(candidate.evidenceUnitIds).size !== candidate.evidenceUnitIds.length
213
+ || candidate.provenance.length !== candidate.evidenceUnitIds.length
214
+ ) throw new TypeError("Ranked evaluation candidate provenance must uniquely cover its evidence units.");
215
+ const candidateDecisionEvidence = new Set(decisions
216
+ .filter(({ documentId }) => documentId === candidate.documentId)
217
+ .flatMap(({ evidenceUnitIds }) => evidenceUnitIds));
218
+ if (
219
+ candidateDecisionEvidence.size !== candidate.evidenceUnitIds.length
220
+ || candidate.evidenceUnitIds.some((id) => !candidateDecisionEvidence.has(id))
221
+ ) {
222
+ throw new TypeError(
223
+ `Ranked evaluation candidate ${candidate.documentId} must aggregate exactly its lane-decision evidence.`,
224
+ );
225
+ }
226
+ for (const [index, evidenceUnitId] of candidate.evidenceUnitIds.entries()) {
227
+ const verified = verifiedById.get(evidenceUnitId);
228
+ const locator = candidate.provenance[index];
229
+ if (verified === undefined || locator === undefined || !locatorMatchesVerified(locator, verified)) {
230
+ throw new TypeError(
231
+ `Ranked evaluation candidate provenance for ${evidenceUnitId} does not match verified live evidence.`,
232
+ );
233
+ }
234
+ if (candidate.documentId !== verified.documentId) {
235
+ const graphDecision = decisions.some((decision) =>
236
+ decision.documentId === candidate.documentId
237
+ && decision.laneId === "graph"
238
+ && decision.evidenceUnitIds.some((id, evidenceIndex) =>
239
+ id === evidenceUnitId
240
+ && decision.provenance[evidenceIndex] !== undefined
241
+ && locatorMatchesVerified(decision.provenance[evidenceIndex], verified)));
242
+ if (!graphDecision) {
243
+ throw new TypeError(
244
+ `Ranked evaluation candidate ${candidate.documentId} has cross-document evidence without a graph decision.`,
245
+ );
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ function safeTokenCount(value: number, label: string): number {
252
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} returned an invalid token count.`);
253
+ return value;
254
+ }
255
+
256
+ function block(options: Readonly<{
257
+ readonly evidenceUnitId: string;
258
+ readonly sourcePath: string;
259
+ readonly lineStart: number;
260
+ readonly lineEnd: number;
261
+ readonly sourcePage?: number;
262
+ readonly sourceClass: string;
263
+ readonly trustClass: string;
264
+ readonly text: string;
265
+ }>): string {
266
+ const page = options.sourcePage === undefined ? "" : ` page=${options.sourcePage}`;
267
+ return [
268
+ `[${options.sourcePath}:${options.lineStart}-${options.lineEnd}${page} evidence=${options.evidenceUnitId} source=${options.sourceClass} trust=${options.trustClass}]`,
269
+ options.text,
270
+ "",
271
+ ].join("\n");
272
+ }
273
+
274
+ /** Pack only accepted, registry-bound evidence in final output order under both sealed ceilings. */
275
+ export async function packKnowledgeBaseEvaluationContextV2(options: Readonly<{
276
+ readonly corpus: Readonly<{
277
+ readonly documents: RetrievalEvaluationCorpusV2["documents"];
278
+ readonly evidenceUnits: RetrievalEvaluationCorpusV2["evidenceUnits"];
279
+ readonly sourceFamilies: RetrievalEvaluationCorpusV2["sourceFamilies"];
280
+ readonly experiment: Readonly<{
281
+ readonly environment: Pick<RetrievalEvaluationCorpusV2["experiment"]["environment"], "tokenizer">;
282
+ readonly protocol: Pick<RetrievalEvaluationCorpusV2["experiment"]["protocol"], "contextCeilings">;
283
+ }>;
284
+ }>;
285
+ readonly result: KnowledgeBaseEvaluationRetrieverResultV2;
286
+ readonly evidenceRegistry: EvaluationEvidenceRegistry;
287
+ readonly tokenizer: EvaluationReaderTokenizerV2;
288
+ }>): Promise<PackedKnowledgeBaseEvaluationContextV2> {
289
+ if (
290
+ options.tokenizer === null
291
+ || typeof options.tokenizer !== "object"
292
+ || !registeredTokenizers.has(options.tokenizer)
293
+ || registeredTokenizerById.get(options.tokenizer.id) !== options.tokenizer
294
+ || options.tokenizer[tokenizerBrand] !== true
295
+ ) throw new TypeError("Packed-context tokenizer is not a registered tokenizer capability.");
296
+ if (
297
+ options.tokenizer.id !== options.corpus.experiment.environment.tokenizer.id
298
+ || options.tokenizer.sha256 !== options.corpus.experiment.environment.tokenizer.sha256
299
+ ) throw new TypeError("Packed-context tokenizer does not match the sealed evaluation environment.");
300
+ const registry = options.evidenceRegistry;
301
+ const registryIds = new Set<string>();
302
+ for (const unit of registry.units) {
303
+ if (registryIds.has(unit.id)) throw new TypeError(`Live evidence registry repeats unit ${unit.id}.`);
304
+ registryIds.add(unit.id);
305
+ }
306
+ validateEvaluationEvidenceRegistry(registry);
307
+ const registryById = new Map(registry.units.map((unit) => [unit.id, unit]));
308
+ const familyById = new Map<string, RetrievalEvaluationCorpusV2["sourceFamilies"][number]>();
309
+ for (const family of options.corpus.sourceFamilies) {
310
+ if (familyById.has(family.id)) throw new TypeError(`Evaluation corpus repeats source family ${family.id}.`);
311
+ familyById.set(family.id, family);
312
+ }
313
+ const documentById = new Map<string, RetrievalEvaluationCorpusV2["documents"][number]>();
314
+ for (const document of options.corpus.documents) {
315
+ if (documentById.has(document.id)) throw new TypeError(`Evaluation corpus repeats document ${document.id}.`);
316
+ documentById.set(document.id, document);
317
+ }
318
+ const corpusUnitById = new Map<string, RetrievalEvaluationCorpusV2["evidenceUnits"][number]>();
319
+ for (const unit of options.corpus.evidenceUnits) {
320
+ if (corpusUnitById.has(unit.id)) throw new TypeError(`Evaluation corpus repeats evidence unit ${unit.id}.`);
321
+ corpusUnitById.set(unit.id, unit);
322
+ }
323
+ const verifiedByCorpusId = new Map<string, VerifiedPackingEvidenceV2>();
324
+ const boundRegistryIds = new Set<string>();
325
+ for (const binding of options.result.evidenceUnits) {
326
+ if (verifiedByCorpusId.has(binding.evidenceUnitId)) {
327
+ throw new TypeError(`Result evidence repeats corpus unit ${binding.evidenceUnitId}.`);
328
+ }
329
+ if (boundRegistryIds.has(binding.registryUnitId)) {
330
+ throw new TypeError(`Result evidence repeats live registry binding ${binding.registryUnitId}.`);
331
+ }
332
+ const live = registryById.get(binding.registryUnitId);
333
+ if (live === undefined) {
334
+ throw new TypeError(`Packed evidence unit ${binding.evidenceUnitId} names a missing live registry unit.`);
335
+ }
336
+ const corpusUnit = corpusUnitById.get(binding.evidenceUnitId);
337
+ if (corpusUnit === undefined) {
338
+ throw new TypeError(`Packed evidence unit ${binding.evidenceUnitId} names a missing corpus evidence unit.`);
339
+ }
340
+ const family = familyById.get(corpusUnit.sourceFamilyId);
341
+ const document = documentById.get(corpusUnit.documentId);
342
+ if (family === undefined || document === undefined) {
343
+ throw new TypeError(`Packed evidence unit ${binding.evidenceUnitId} has an unresolved corpus provenance chain.`);
344
+ }
345
+ verifiedByCorpusId.set(
346
+ binding.evidenceUnitId,
347
+ verifyBinding(binding, corpusUnit, live, family, document),
348
+ );
349
+ boundRegistryIds.add(binding.registryUnitId);
350
+ }
351
+ for (const decision of options.result.trace.candidateDecisions) {
352
+ assertDecisionProvenance(decision, verifiedByCorpusId);
353
+ }
354
+ const accepted = options.result.trace.candidateDecisions
355
+ .filter(({ disposition }) => disposition === "accepted")
356
+ .toSorted((left, right) => (left.outputRank ?? Number.MAX_SAFE_INTEGER)
357
+ - (right.outputRank ?? Number.MAX_SAFE_INTEGER));
358
+ const outputRanks = accepted.map(({ outputRank }) => outputRank);
359
+ if (
360
+ new Set(outputRanks).size !== outputRanks.length
361
+ || outputRanks.some((rank, index) => rank !== index + 1)
362
+ ) throw new TypeError("Accepted evaluation candidates must use unique contiguous output ranks.");
363
+ if (
364
+ options.result.candidates.length !== accepted.length
365
+ || options.result.candidates.some((candidate, index) =>
366
+ candidate.rank !== index + 1
367
+ || candidate.documentId !== accepted[index]?.documentId
368
+ || accepted[index]?.outputRank !== candidate.rank)
369
+ ) {
370
+ throw new TypeError("Ranked evaluation candidates must match accepted output decisions exactly.");
371
+ }
372
+ const orderedIds: string[] = [];
373
+ const seen = new Set<string>();
374
+ for (const candidate of options.result.candidates) {
375
+ assertRankedCandidateProvenance(
376
+ candidate,
377
+ options.result.trace.candidateDecisions,
378
+ verifiedByCorpusId,
379
+ );
380
+ if (candidate.evidenceUnitIds.length === 0) {
381
+ throw new TypeError("Accepted evaluation candidates must carry ranked evidence provenance before packing.");
382
+ }
383
+ for (const evidenceUnitId of candidate.evidenceUnitIds) {
384
+ if (!seen.has(evidenceUnitId)) orderedIds.push(evidenceUnitId);
385
+ seen.add(evidenceUnitId);
386
+ }
387
+ }
388
+
389
+ const byteCeiling = options.corpus.experiment.protocol.contextCeilings.utf8Bytes;
390
+ const tokenCeiling = options.corpus.experiment.protocol.contextCeilings.readerTokens;
391
+ if (!Number.isSafeInteger(byteCeiling) || byteCeiling < 1) {
392
+ throw new TypeError("Packed-context UTF-8 byte ceiling must be a positive safe integer.");
393
+ }
394
+ if (!Number.isSafeInteger(tokenCeiling) || tokenCeiling < 1) {
395
+ throw new TypeError("Packed-context reader-token ceiling must be a positive safe integer.");
396
+ }
397
+ let text = "";
398
+ const includedEvidenceUnitIds: string[] = [];
399
+ let utf8Bytes = 0;
400
+ let readerTokens = safeTokenCount(await options.tokenizer.count(""), "Packed-context tokenizer");
401
+ if (readerTokens !== 0) {
402
+ throw new TypeError("Packed-context tokenizer must count an empty context as zero tokens.");
403
+ }
404
+ let truncated = false;
405
+ for (const evidenceUnitId of orderedIds) {
406
+ const verified = verifiedByCorpusId.get(evidenceUnitId);
407
+ if (verified === undefined) throw new TypeError(`Packed evidence unit ${evidenceUnitId} lacks a live registry binding.`);
408
+ const content = block({
409
+ evidenceUnitId: verified.evidenceUnitId,
410
+ sourcePath: verified.sourcePath,
411
+ lineStart: verified.lineRange.start,
412
+ lineEnd: verified.lineRange.end,
413
+ ...(verified.sourcePage === undefined ? {} : { sourcePage: verified.sourcePage }),
414
+ sourceClass: verified.sourceClass,
415
+ trustClass: verified.trustClass,
416
+ text: verified.text,
417
+ });
418
+ const prospectiveText = `${text}${content}`;
419
+ const prospectiveBytes = Buffer.byteLength(prospectiveText, "utf8");
420
+ const prospectiveTokens = safeTokenCount(
421
+ await options.tokenizer.count(prospectiveText),
422
+ "Packed-context tokenizer",
423
+ );
424
+ if (prospectiveBytes > byteCeiling || prospectiveTokens > tokenCeiling) {
425
+ truncated = true;
426
+ break;
427
+ }
428
+ text = prospectiveText;
429
+ includedEvidenceUnitIds.push(evidenceUnitId);
430
+ utf8Bytes = prospectiveBytes;
431
+ readerTokens = prospectiveTokens;
432
+ }
433
+ const finalReaderTokens = safeTokenCount(
434
+ await options.tokenizer.count(text),
435
+ "Packed-context tokenizer",
436
+ );
437
+ if (finalReaderTokens !== readerTokens) {
438
+ throw new TypeError("Packed-context tokenizer returned a nondeterministic final count.");
439
+ }
440
+ const packedBytes = Buffer.from(text, "utf8");
441
+ if (packedBytes.byteLength !== utf8Bytes) {
442
+ throw new TypeError("Packed-context UTF-8 byte accounting drifted from the exact final bytes.");
443
+ }
444
+ return Object.freeze({
445
+ text,
446
+ utf8Bytes,
447
+ readerTokens,
448
+ includedEvidenceUnitIds: Object.freeze(includedEvidenceUnitIds),
449
+ truncated,
450
+ packedBytesSha256: createHash("sha256").update(packedBytes).digest("hex"),
451
+ });
452
+ }
@@ -0,0 +1,28 @@
1
+ /** Remove machine-local user and temporary roots from persisted evaluation evidence. */
2
+ export function redactEvaluationMachinePaths(value: string): string {
3
+ return value
4
+ .replace(/file:\/\/\/(?:Users|home)\/[^/\\\s"'`]+/giu, "file://<home>")
5
+ .replace(/file:\/\/\/root(?=\/|$)/giu, "file://<home>")
6
+ .replace(/file:\/\/\/(?:private\/tmp|tmp|var\/tmp)(?=\/|$)/giu, "file://<temporary>")
7
+ .replace(
8
+ /file:\/\/\/(?:private\/)?var\/folders\/[^/\\\s"'`]+\/[^/\\\s"'`]+\/[A-Z](?=\/|$)/giu,
9
+ "file://<temporary>",
10
+ )
11
+ .replace(
12
+ /(^|[^A-Za-z0-9:/])\/(?:Users|home)\/[^/\\\s"'`]+/gu,
13
+ "$1<home>",
14
+ )
15
+ .replace(/(^|[^A-Za-z0-9:/])\/root(?=\/|$)/gu, "$1<home>")
16
+ .replace(
17
+ /(^|[^A-Za-z0-9:/])\/(?:private\/)?var\/folders\/[^/\\\s"'`]+\/[^/\\\s"'`]+\/[A-Z](?=\/|$)/gu,
18
+ "$1<temporary>",
19
+ )
20
+ .replace(
21
+ /(^|[^A-Za-z0-9:/])\/(?:private\/tmp|tmp|var\/tmp)(?=\/|$)/gu,
22
+ "$1<temporary>",
23
+ )
24
+ .replace(
25
+ /(^|[^A-Za-z0-9])(?:[A-Za-z]:[\\/]Users[\\/][^\\/\s"'`]+)/giu,
26
+ "$1<home>",
27
+ );
28
+ }