@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,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
|
+
}
|