@hraness/wordcell 0.20.0-bootstrap.1 → 0.21.0
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/README.md +29 -15
- package/dist/agent-context.js +1 -0
- package/dist/agent-guide-audit.js +1 -0
- package/dist/attachments.js +1 -0
- package/dist/authoring.js +1 -0
- package/dist/authority-4sp9d552.js +663 -0
- package/dist/benchmark.js +1 -0
- package/dist/capture.js +1 -0
- package/dist/cli.js +3781 -32
- package/dist/clip/acquire.js +1 -0
- package/dist/clip/args.js +1 -0
- package/dist/clip/bounded-byte-buffer.js +1 -0
- package/dist/clip/browser-profiles.js +1 -0
- package/dist/clip/bundle-reader.js +1 -0
- package/dist/clip/cli.js +1 -0
- package/dist/clip/cookies.js +1 -0
- package/dist/clip/doctor.js +1 -0
- package/dist/clip/jobs.js +1 -0
- package/dist/clip/network-proxy.js +1 -0
- package/dist/clip/network.js +1 -0
- package/dist/clip/persist.js +1 -0
- package/dist/clip/refresh.js +1 -0
- package/dist/clip/terminal.js +1 -0
- package/dist/evaluation-builder.js +13 -5
- package/dist/evaluation-kb.js +11 -4
- package/dist/evaluation.js +1 -0
- package/dist/git.js +1 -0
- package/dist/graph-authority.js +29 -0
- package/dist/graph-percolation.js +16 -0
- package/dist/graph.js +1 -0
- package/dist/index-0k2x4nn9.js +393 -0
- package/dist/{index-n05s3wsb.js → index-115b07ap.js} +156 -541
- package/dist/index-11621h23.js +160 -0
- package/dist/{index-tkpxp48t.js → index-51bap0mq.js} +2 -2
- package/dist/{index-8a08v6x8.js → index-59ssmqqm.js} +1 -1
- package/dist/index-bcfn9xah.js +472 -0
- package/dist/index-bgfzwt4h.js +51 -0
- package/dist/{index-bs0yf4pp.js → index-pbr6rv47.js} +2 -2
- package/dist/{index-m7et4rz4.js → index-vdw5yjfa.js} +61 -9
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +61 -34
- package/dist/navigation.js +1 -0
- package/dist/pdf.js +1 -0
- package/dist/percolate.js +1 -0
- package/dist/portfolio.js +11 -4
- package/dist/query.js +1 -0
- package/dist/repository-memory.js +1 -0
- package/dist/sdk.js +10 -3
- package/dist/search-rules.js +1 -0
- package/dist/search.js +1 -0
- package/dist/semantic.js +3 -1
- package/dist/source-inbox.js +1 -0
- package/dist/untrusted-content.js +1 -0
- package/dist/url-intelligence.js +1 -0
- package/dist/workflow.js +1 -0
- package/dist/workflows/decision-context.js +11 -4
- package/dist/workflows/explain-change.js +1 -0
- package/dist/workflows/index.js +11 -4
- package/dist/workflows/plan-radar.js +1 -0
- package/package.json +24 -6
- package/skills/wordcell/SKILL.md +2 -2
- package/skills/wordcell/references/percolate.md +4 -0
- package/skills/wordcell/references/query.md +6 -0
- package/src/cli-program.ts +45 -2
- package/src/graph-authority-model.ts +116 -0
- package/src/graph-authority.ts +219 -0
- package/src/graph-cache-filesystem.ts +4 -0
- package/src/graph-cli.ts +96 -0
- package/src/graph-facts.ts +206 -0
- package/src/graph-percolation.ts +39 -0
- package/src/graph-query.ts +122 -0
- package/src/index.ts +2 -0
- package/src/oh/authority.ts +193 -0
- package/src/oh/programs.ts +99 -0
- package/src/oh/schema.ts +40 -0
- package/src/oh/snapshot.ts +114 -0
- package/src/oh/validation.ts +61 -0
- package/src/sdk.ts +51 -6
- package/dist/index-sy0zety7.js +0 -3630
- package/dist/kb-alias.js +0 -55
- package/src/kb-alias.ts +0 -14
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
GRAPH_LIMITS,
|
|
4
|
+
GraphAuthorityError,
|
|
5
|
+
validateGraphQueryRequest
|
|
6
|
+
} from "./index-11621h23.js";
|
|
7
|
+
import {
|
|
8
|
+
validateRepositoryScopeSelection
|
|
9
|
+
} from "./index-06c9ctr6.js";
|
|
10
|
+
import"./index-5vwpzb5a.js";
|
|
11
|
+
import {
|
|
12
|
+
isCanonicalNoteId,
|
|
13
|
+
isCanonicalRelationPredicate
|
|
14
|
+
} from "./index-ekpwvbra.js";
|
|
15
|
+
import"./index-z1w83f81.js";
|
|
16
|
+
|
|
17
|
+
// src/oh/authority.ts
|
|
18
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
19
|
+
import { isProxy as isProxy2 } from "util/types";
|
|
20
|
+
import { canonicalJson as canonicalJson4, canonicalSha256 as canonicalSha2562 } from "@hraness/oh";
|
|
21
|
+
import {
|
|
22
|
+
createOhProjectionDatasetV1,
|
|
23
|
+
createOhProjectionFactV1,
|
|
24
|
+
createOhProjectionSnapshotV1,
|
|
25
|
+
evaluateOhProjectionV1,
|
|
26
|
+
parseOhProjectionResultV1
|
|
27
|
+
} from "@hraness/oh/projection";
|
|
28
|
+
import { createOhSqliteStoreAuthorityV1 } from "@hraness/oh/sqlite";
|
|
29
|
+
import { createOhStoreProfileV1, OH_WORKING_STORE_PROFILE_V1 } from "@hraness/oh/store";
|
|
30
|
+
|
|
31
|
+
// src/oh/programs.ts
|
|
32
|
+
import {
|
|
33
|
+
createOhProjectionLiteralV1 as literal,
|
|
34
|
+
createOhProjectionQueryV1,
|
|
35
|
+
createOhProjectionRulePackV1,
|
|
36
|
+
createOhProjectionRuleV1,
|
|
37
|
+
ohProjectionConstantV1 as constant,
|
|
38
|
+
ohProjectionVariableV1 as variable
|
|
39
|
+
} from "@hraness/oh/projection";
|
|
40
|
+
|
|
41
|
+
// src/oh/validation.ts
|
|
42
|
+
import { isProxy } from "util/types";
|
|
43
|
+
import { canonicalJson } from "@hraness/oh";
|
|
44
|
+
function detachedData(input, maximumBytes) {
|
|
45
|
+
const seen = new Set;
|
|
46
|
+
let nodes = 0, bytes = 0;
|
|
47
|
+
function visit(value2, depth) {
|
|
48
|
+
nodes += 1;
|
|
49
|
+
if (nodes > 1e6 || depth > 192)
|
|
50
|
+
throw new GraphAuthorityError("budget", "Graph input exceeds structural bounds.");
|
|
51
|
+
if (value2 === null || typeof value2 === "boolean") {
|
|
52
|
+
bytes += 5;
|
|
53
|
+
return value2;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value2 === "string") {
|
|
56
|
+
bytes += Buffer.byteLength(value2) + 2;
|
|
57
|
+
if (bytes > maximumBytes)
|
|
58
|
+
throw new GraphAuthorityError("budget", "Graph input exceeds its byte bound.");
|
|
59
|
+
return value2;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value2 === "number" && Number.isFinite(value2)) {
|
|
62
|
+
bytes += 32;
|
|
63
|
+
return value2;
|
|
64
|
+
}
|
|
65
|
+
if (typeof value2 !== "object" || value2 === null || isProxy(value2) || seen.has(value2))
|
|
66
|
+
throw new GraphAuthorityError("invalid-input", "Graph input must be acyclic plain data.");
|
|
67
|
+
seen.add(value2);
|
|
68
|
+
const array = Array.isArray(value2);
|
|
69
|
+
if (!array && Object.getPrototypeOf(value2) !== Object.prototype && Object.getPrototypeOf(value2) !== null)
|
|
70
|
+
throw new GraphAuthorityError("invalid-input", "Graph input must contain plain objects.");
|
|
71
|
+
const keys = Reflect.ownKeys(value2);
|
|
72
|
+
if (keys.length > 200000 || keys.some((key) => typeof key !== "string"))
|
|
73
|
+
throw new GraphAuthorityError("budget", "Graph input has too many fields.");
|
|
74
|
+
const output = Object.create(null);
|
|
75
|
+
if (array && keys.length !== value2.length + 1)
|
|
76
|
+
throw new GraphAuthorityError("invalid-input", "Graph arrays must be dense.");
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
if (array && key === "length")
|
|
79
|
+
continue;
|
|
80
|
+
if (array && (!/^(0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value2.length))
|
|
81
|
+
throw new GraphAuthorityError("invalid-input", "Graph array has extra fields.");
|
|
82
|
+
const descriptor = Object.getOwnPropertyDescriptor(value2, key);
|
|
83
|
+
if (!descriptor.enumerable || !("value" in descriptor))
|
|
84
|
+
throw new GraphAuthorityError("invalid-input", "Graph input accessors are forbidden.");
|
|
85
|
+
bytes += Buffer.byteLength(key) + 4;
|
|
86
|
+
if (bytes > maximumBytes)
|
|
87
|
+
throw new GraphAuthorityError("budget", "Graph input exceeds its byte bound.");
|
|
88
|
+
output[key] = visit(descriptor.value, depth + 1);
|
|
89
|
+
}
|
|
90
|
+
seen.delete(value2);
|
|
91
|
+
return array ? Array.from({ length: value2.length }, (_, index) => output[String(index)]) : output;
|
|
92
|
+
}
|
|
93
|
+
const value = visit(input, 0);
|
|
94
|
+
try {
|
|
95
|
+
const encoded = canonicalJson(value);
|
|
96
|
+
if (Buffer.byteLength(encoded) > maximumBytes)
|
|
97
|
+
throw new GraphAuthorityError("budget", "Graph input exceeds its canonical byte bound.");
|
|
98
|
+
return JSON.parse(encoded);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof GraphAuthorityError)
|
|
101
|
+
throw error;
|
|
102
|
+
throw new GraphAuthorityError("invalid-input", "Graph input is not canonical JSON data.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function exactKeys(value, required, optional = []) {
|
|
106
|
+
const keys = Object.keys(value);
|
|
107
|
+
if (required.some((key) => !keys.includes(key)) || keys.some((key) => !required.includes(key) && !optional.includes(key)))
|
|
108
|
+
throw new GraphAuthorityError("invalid-input", "Graph input has missing or unexpected fields.");
|
|
109
|
+
}
|
|
110
|
+
function deepFreeze(value) {
|
|
111
|
+
if (value !== null && typeof value === "object") {
|
|
112
|
+
for (const item of Object.values(value))
|
|
113
|
+
deepFreeze(item);
|
|
114
|
+
Object.freeze(value);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/oh/programs.ts
|
|
120
|
+
var DEFAULT_GRAPH_QUERY_LIMITS = Object.freeze({
|
|
121
|
+
rows: 100,
|
|
122
|
+
workUnits: 1e6,
|
|
123
|
+
derivedTuples: 50000,
|
|
124
|
+
rounds: 32,
|
|
125
|
+
proofDepth: 32,
|
|
126
|
+
proofNodes: 256,
|
|
127
|
+
totalProofNodes: 8192,
|
|
128
|
+
resultBytes: 1048576
|
|
129
|
+
});
|
|
130
|
+
function normalizeGraphQueryRequest(input) {
|
|
131
|
+
const request = validateGraphQueryRequest(detachedData(input, 64 * 1024));
|
|
132
|
+
const depth = request.program === "reachability" || request.program === "relation-closure" ? { depth: request.depth ?? 3 } : {};
|
|
133
|
+
return Object.freeze({
|
|
134
|
+
...request,
|
|
135
|
+
...depth,
|
|
136
|
+
limits: Object.freeze({ ...DEFAULT_GRAPH_QUERY_LIMITS, ...request.limits })
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
var atom = (relation, ...terms) => literal({ relation, terms });
|
|
140
|
+
function compileGraphProgram(request) {
|
|
141
|
+
const rules = [];
|
|
142
|
+
const add = (ruleId, head, ...body) => {
|
|
143
|
+
rules.push(createOhProjectionRuleV1({ ruleId: `wordcell.${ruleId}`, head, body }));
|
|
144
|
+
};
|
|
145
|
+
const a = variable("source"), b = variable("target"), line = variable("line"), predicate = variable("predicate");
|
|
146
|
+
let columns;
|
|
147
|
+
let where;
|
|
148
|
+
switch (request.program) {
|
|
149
|
+
case "backlinks": {
|
|
150
|
+
add("backlink.link", atom("wordcell.answer", a, b, line, constant("link"), constant("")), atom("wordcell.link", a, b, line));
|
|
151
|
+
add("backlink.relation", atom("wordcell.answer", a, b, line, constant("relation"), predicate), atom("wordcell.relation", a, b, predicate, line));
|
|
152
|
+
columns = ["source", "target", "line", "kind", "predicate"];
|
|
153
|
+
where = [atom("wordcell.answer", a, b, line, variable("kind"), predicate), atom("wordcell.selected", b)];
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "reachability":
|
|
157
|
+
case "relation-closure": {
|
|
158
|
+
const relation = request.program === "relation-closure";
|
|
159
|
+
const edge = "wordcell.edge";
|
|
160
|
+
if (relation)
|
|
161
|
+
add("edge.relation", atom(edge, a, b), atom("wordcell.relation", a, b, constant(request.predicate), line));
|
|
162
|
+
else {
|
|
163
|
+
add("edge.link", atom(edge, a, b), atom("wordcell.link", a, b, line));
|
|
164
|
+
add("edge.relation", atom(edge, a, b), atom("wordcell.relation", a, b, predicate, line));
|
|
165
|
+
}
|
|
166
|
+
const source = constant(request.note);
|
|
167
|
+
add("walk.1", atom("wordcell.walk.1", b), atom(edge, source, b));
|
|
168
|
+
for (let depth = 1;depth <= (request.depth ?? 3); depth += 1) {
|
|
169
|
+
if (depth > 1)
|
|
170
|
+
add(`walk.${depth}`, atom(`wordcell.walk.${depth}`, b), atom(`wordcell.walk.${depth - 1}`, a), atom(edge, a, b));
|
|
171
|
+
add(`walk.answer.${depth}`, atom("wordcell.answer", source, b, constant(depth)), atom(`wordcell.walk.${depth}`, b));
|
|
172
|
+
}
|
|
173
|
+
columns = ["source", "target", "depth"];
|
|
174
|
+
where = [atom("wordcell.answer", a, b, variable("depth"))];
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "scope-route": {
|
|
178
|
+
add("scope", atom("wordcell.answer", a, constant(request.scope)), atom("wordcell.scope", a, constant(request.scope)));
|
|
179
|
+
columns = ["note", "scope"];
|
|
180
|
+
where = [atom("wordcell.answer", variable("note"), variable("scope"))];
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "shared-tags": {
|
|
184
|
+
const note = variable("note"), other = variable("other"), tag = variable("tag");
|
|
185
|
+
add("shared.tags", atom("wordcell.answer", note, other, tag), atom("wordcell.selected", note), atom("wordcell.tag", note, tag), atom("wordcell.tag", other, tag), atom("wordcell.peer", other));
|
|
186
|
+
columns = ["note", "other", "tag"];
|
|
187
|
+
where = [atom("wordcell.answer", note, other, tag)];
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case "shared-concepts": {
|
|
191
|
+
const note = variable("note"), other = variable("other"), concept = variable("concept");
|
|
192
|
+
add("concept.link", atom("wordcell.concept-edge", a, b), atom("wordcell.link", a, b, line), atom("wordcell.concept", b));
|
|
193
|
+
add("concept.relation", atom("wordcell.concept-edge", a, b), atom("wordcell.relation", a, b, predicate, line), atom("wordcell.concept", b));
|
|
194
|
+
add("concept.reverse-link", atom("wordcell.concept-edge", a, b), atom("wordcell.link", b, a, line), atom("wordcell.concept", b));
|
|
195
|
+
add("concept.reverse-relation", atom("wordcell.concept-edge", a, b), atom("wordcell.relation", b, a, predicate, line), atom("wordcell.concept", b));
|
|
196
|
+
add("shared.concepts", atom("wordcell.answer", note, other, concept), atom("wordcell.selected", note), atom("wordcell.ordinary", note), atom("wordcell.concept-edge", note, concept), atom("wordcell.concept-edge", other, concept), atom("wordcell.peer", other), atom("wordcell.ordinary", other));
|
|
197
|
+
columns = ["note", "other", "concept"];
|
|
198
|
+
where = [atom("wordcell.answer", note, other, concept)];
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
columns,
|
|
204
|
+
query: createOhProjectionQueryV1({ queryId: `wordcell.${request.program}`, find: columns, where, limit: request.limits?.rows ?? DEFAULT_GRAPH_QUERY_LIMITS.rows }),
|
|
205
|
+
rulePack: createOhProjectionRulePackV1({ rulePackId: `wordcell.${request.program}`, rulePackRevision: 1, rules })
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/oh/snapshot.ts
|
|
210
|
+
import { createHash } from "crypto";
|
|
211
|
+
import { canonicalJson as canonicalJson2, canonicalSha256, createKnowledgeGraphRecordV1 } from "@hraness/oh";
|
|
212
|
+
var digest = (value) => typeof value === "string" && /^[0-9a-f]{64}$/u.test(value);
|
|
213
|
+
var fail = (message) => {
|
|
214
|
+
throw new GraphAuthorityError("invalid-input", message);
|
|
215
|
+
};
|
|
216
|
+
var nonempty = (value) => typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= GRAPH_LIMITS.atomBytes;
|
|
217
|
+
var path = (value) => nonempty(value) && !value.startsWith("/") && !value.includes("\\") && !value.split("/").some((part) => part === "" || part === "." || part === "..");
|
|
218
|
+
var GRAPH_EXTRACTOR_PROFILE = Object.freeze({
|
|
219
|
+
schemaVersion: 1,
|
|
220
|
+
id: "wordcell.graph-facts.v1",
|
|
221
|
+
relations: {
|
|
222
|
+
note: ["id", "path", "title", "type"],
|
|
223
|
+
link: ["source", "target", "line"],
|
|
224
|
+
relation: ["source", "target", "predicate", "line"],
|
|
225
|
+
"external-relation": ["source", "uri", "predicate", "line"],
|
|
226
|
+
tag: ["id", "tag"],
|
|
227
|
+
scope: ["id", "scope"],
|
|
228
|
+
concept: ["id"]
|
|
229
|
+
},
|
|
230
|
+
selectors: "selected is the requested note; peers are all other notes; ordinary excludes type concept; scope is an exact constant",
|
|
231
|
+
semantics: "positive walks of lengths 1..depth; shared concepts connect ordinary notes to concepts through explicit links or typed relations in either direction"
|
|
232
|
+
});
|
|
233
|
+
var GRAPH_EXTRACTOR_SHA256 = canonicalSha256(GRAPH_EXTRACTOR_PROFILE);
|
|
234
|
+
var GRAPH_MANIFEST_KEY = "view:wordcell/manifest";
|
|
235
|
+
function parseGraphSnapshot(input) {
|
|
236
|
+
const snapshot = detachedData(input, GRAPH_LIMITS.sourceBytes);
|
|
237
|
+
if (snapshot === null || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
238
|
+
fail("Invalid graph snapshot.");
|
|
239
|
+
exactKeys(snapshot, ["schemaVersion", "vaultIdentity", "revision", "catalogNoteId", "sourceDigests", "records", "factCount"]);
|
|
240
|
+
if (snapshot.schemaVersion !== 1 || !digest(snapshot.vaultIdentity) || !digest(snapshot.revision) || !nonempty(snapshot.catalogNoteId) || !Array.isArray(snapshot.records) || snapshot.records.length > GRAPH_LIMITS.notes || !Array.isArray(snapshot.sourceDigests) || snapshot.sourceDigests.length > GRAPH_LIMITS.notes + 1 || !Number.isSafeInteger(snapshot.factCount) || snapshot.factCount < 0 || snapshot.factCount > GRAPH_LIMITS.facts)
|
|
241
|
+
fail("Invalid graph snapshot identity or bounds.");
|
|
242
|
+
const sourceDigests = new Map;
|
|
243
|
+
let lastPath = "";
|
|
244
|
+
for (const source of snapshot.sourceDigests) {
|
|
245
|
+
if (source === null || typeof source !== "object" || Array.isArray(source))
|
|
246
|
+
fail("Invalid graph source digest.");
|
|
247
|
+
exactKeys(source, ["path", "contentSha256"]);
|
|
248
|
+
if (!path(source.path) || source.path <= lastPath || !digest(source.contentSha256))
|
|
249
|
+
fail("Graph source digests must be sorted unique paths and hashes.");
|
|
250
|
+
sourceDigests.set(source.path, source.contentSha256);
|
|
251
|
+
lastPath = source.path;
|
|
252
|
+
}
|
|
253
|
+
const ids = new Set, paths = new Set;
|
|
254
|
+
let lastKey = "", count = 0;
|
|
255
|
+
for (const record of snapshot.records) {
|
|
256
|
+
if (record === null || typeof record !== "object" || Array.isArray(record))
|
|
257
|
+
fail("Invalid graph source record.");
|
|
258
|
+
exactKeys(record, ["key", "id", "path", "documentId", "contentSha256", "facts"]);
|
|
259
|
+
if (!nonempty(record.id) || !isCanonicalNoteId(record.id) || record.path !== `${record.id}.md` || !path(record.path) || !digest(record.contentSha256) || record.documentId !== null && !nonempty(record.documentId) || !Array.isArray(record.facts) || ids.has(record.id) || paths.has(record.path) || record.id === snapshot.catalogNoteId)
|
|
260
|
+
fail("Invalid or duplicated graph source identity.");
|
|
261
|
+
const identity = record.documentId === null ? `path:${record.id}` : `document:${record.documentId}`;
|
|
262
|
+
const expectedKey = `edition:note/${createHash("sha256").update(identity).digest("hex")}`;
|
|
263
|
+
if (record.key !== expectedKey || record.key <= lastKey || sourceDigests.get(record.path) !== record.contentSha256)
|
|
264
|
+
fail("Graph source key, order or digest differs.");
|
|
265
|
+
lastKey = record.key;
|
|
266
|
+
ids.add(record.id);
|
|
267
|
+
paths.add(record.path);
|
|
268
|
+
let lastFact = "", noteFacts = 0;
|
|
269
|
+
for (const fact of record.facts) {
|
|
270
|
+
if (fact === null || typeof fact !== "object" || Array.isArray(fact))
|
|
271
|
+
fail("Invalid graph fact.");
|
|
272
|
+
exactKeys(fact, ["relation", "tuple"]);
|
|
273
|
+
if (!Array.isArray(fact.tuple) || fact.tuple[0] !== record.id)
|
|
274
|
+
fail("Graph facts must belong to their source note.");
|
|
275
|
+
const key = canonicalJson2([fact.relation, fact.tuple]);
|
|
276
|
+
if (key <= lastFact)
|
|
277
|
+
fail("Graph facts must be sorted and unique.");
|
|
278
|
+
lastFact = key;
|
|
279
|
+
for (const value of fact.tuple)
|
|
280
|
+
if (Buffer.byteLength(canonicalJson2(value)) > GRAPH_LIMITS.atomBytes)
|
|
281
|
+
fail("Graph atom exceeds its byte bound.");
|
|
282
|
+
const tuple = fact.tuple;
|
|
283
|
+
switch (fact.relation) {
|
|
284
|
+
case "wordcell.note":
|
|
285
|
+
if (tuple.length !== 4 || tuple[1] !== record.path || typeof tuple[2] !== "string" || typeof tuple[3] !== "string")
|
|
286
|
+
fail("Invalid note fact.");
|
|
287
|
+
noteFacts += 1;
|
|
288
|
+
break;
|
|
289
|
+
case "wordcell.link":
|
|
290
|
+
if (tuple.length !== 3 || !nonempty(tuple[1]) || !Number.isSafeInteger(tuple[2]) || tuple[2] < 0)
|
|
291
|
+
fail("Invalid link fact.");
|
|
292
|
+
break;
|
|
293
|
+
case "wordcell.relation":
|
|
294
|
+
case "wordcell.external-relation":
|
|
295
|
+
if (tuple.length !== 4 || !nonempty(tuple[1]) || !nonempty(tuple[2]) || !Number.isSafeInteger(tuple[3]) || tuple[3] < 0)
|
|
296
|
+
fail("Invalid relation fact.");
|
|
297
|
+
if (!isCanonicalRelationPredicate(tuple[2]))
|
|
298
|
+
fail("Invalid relation predicate.");
|
|
299
|
+
if (fact.relation === "wordcell.external-relation" && !tuple[1].startsWith("kb://"))
|
|
300
|
+
fail("Invalid external relation target.");
|
|
301
|
+
break;
|
|
302
|
+
case "wordcell.tag":
|
|
303
|
+
case "wordcell.scope":
|
|
304
|
+
if (tuple.length !== 2 || !nonempty(tuple[1]))
|
|
305
|
+
fail("Invalid tag or scope fact.");
|
|
306
|
+
if (fact.relation === "wordcell.scope") {
|
|
307
|
+
try {
|
|
308
|
+
validateRepositoryScopeSelection([tuple[1]]);
|
|
309
|
+
} catch {
|
|
310
|
+
fail("Invalid repository scope fact.");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case "wordcell.concept":
|
|
315
|
+
if (tuple.length !== 1)
|
|
316
|
+
fail("Invalid concept fact.");
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
fail("Unknown graph fact relation.");
|
|
320
|
+
}
|
|
321
|
+
count += 1;
|
|
322
|
+
if (count > GRAPH_LIMITS.facts)
|
|
323
|
+
fail("Graph fact count exceeds its limit.");
|
|
324
|
+
}
|
|
325
|
+
if (noteFacts !== 1)
|
|
326
|
+
fail("Every graph record must contain exactly one note fact.");
|
|
327
|
+
const type = record.facts.find((fact) => fact.relation === "wordcell.note").tuple[3];
|
|
328
|
+
if (record.facts.some((fact) => fact.relation === "wordcell.concept") !== (type.normalize("NFC").toLocaleLowerCase("en-US") === "concept"))
|
|
329
|
+
fail("Concept facts must match authored note type.");
|
|
330
|
+
}
|
|
331
|
+
for (const record of snapshot.records)
|
|
332
|
+
for (const fact of record.facts) {
|
|
333
|
+
if ((fact.relation === "wordcell.link" || fact.relation === "wordcell.relation") && !ids.has(fact.tuple[1]))
|
|
334
|
+
fail("Internal graph fact target is absent.");
|
|
335
|
+
}
|
|
336
|
+
if ([...sourceDigests.keys()].some((source) => !paths.has(source) && source !== `${snapshot.catalogNoteId}.md`))
|
|
337
|
+
fail("Graph source inventory contains an unrepresented non-catalog note.");
|
|
338
|
+
if (count !== snapshot.factCount || canonicalSha256({
|
|
339
|
+
schemaVersion: 1,
|
|
340
|
+
vaultIdentity: snapshot.vaultIdentity,
|
|
341
|
+
catalogNoteId: snapshot.catalogNoteId,
|
|
342
|
+
sourceDigests: snapshot.sourceDigests,
|
|
343
|
+
records: snapshot.records
|
|
344
|
+
}) !== snapshot.revision)
|
|
345
|
+
fail("Graph snapshot revision or fact count differs.");
|
|
346
|
+
return deepFreeze(snapshot);
|
|
347
|
+
}
|
|
348
|
+
function sourceGraphRecord(record) {
|
|
349
|
+
return createKnowledgeGraphRecordV1({
|
|
350
|
+
key: record.key,
|
|
351
|
+
kind: "edition",
|
|
352
|
+
dependencies: [],
|
|
353
|
+
v: 1,
|
|
354
|
+
value: JSON.parse(canonicalJson2(record))
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function manifestGraphRecord(snapshot) {
|
|
358
|
+
return createKnowledgeGraphRecordV1({
|
|
359
|
+
key: GRAPH_MANIFEST_KEY,
|
|
360
|
+
kind: "view",
|
|
361
|
+
dependencies: [],
|
|
362
|
+
v: 1,
|
|
363
|
+
value: JSON.parse(canonicalJson2({
|
|
364
|
+
schemaVersion: 1,
|
|
365
|
+
vaultIdentity: snapshot.vaultIdentity,
|
|
366
|
+
revision: snapshot.revision,
|
|
367
|
+
catalogNoteId: snapshot.catalogNoteId,
|
|
368
|
+
sourceDigests: snapshot.sourceDigests,
|
|
369
|
+
records: snapshot.records.length,
|
|
370
|
+
facts: snapshot.factCount,
|
|
371
|
+
extractorSha256: GRAPH_EXTRACTOR_SHA256
|
|
372
|
+
}))
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/oh/schema.ts
|
|
377
|
+
import { Database } from "bun:sqlite";
|
|
378
|
+
import { canonicalJson as canonicalJson3 } from "@hraness/oh";
|
|
379
|
+
import { applyOhSqliteMigrations } from "@hraness/oh/sqlite";
|
|
380
|
+
var MAXIMUM_SCHEMA_ENTRIES = 256;
|
|
381
|
+
var MAXIMUM_SCHEMA_SQL_LENGTH = 16384;
|
|
382
|
+
var expectedSchema;
|
|
383
|
+
function schemaEntries(database) {
|
|
384
|
+
const entries = database.query(`SELECT type,
|
|
385
|
+
substr(name, 1, 257) AS name, length(name) AS nameLength,
|
|
386
|
+
substr(tbl_name, 1, 257) AS tbl_name, length(tbl_name) AS tableNameLength,
|
|
387
|
+
substr(sql, 1, ${MAXIMUM_SCHEMA_SQL_LENGTH + 1}) AS sql, length(sql) AS sqlLength
|
|
388
|
+
FROM main.sqlite_schema LIMIT ${MAXIMUM_SCHEMA_ENTRIES + 1}`).all();
|
|
389
|
+
if (entries.length > MAXIMUM_SCHEMA_ENTRIES || entries.some((entry) => entry.nameLength > 256 || entry.tableNameLength > 256 || (entry.sqlLength ?? 0) > MAXIMUM_SCHEMA_SQL_LENGTH)) {
|
|
390
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache schema exceeds its metadata bounds.");
|
|
391
|
+
}
|
|
392
|
+
return entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.type < b.type ? -1 : 1);
|
|
393
|
+
}
|
|
394
|
+
function validateOhCacheSchema(database) {
|
|
395
|
+
const entries = schemaEntries(database);
|
|
396
|
+
if (entries.length === 0)
|
|
397
|
+
return false;
|
|
398
|
+
if (expectedSchema === undefined) {
|
|
399
|
+
const trusted = new Database(":memory:", { strict: true });
|
|
400
|
+
try {
|
|
401
|
+
applyOhSqliteMigrations(trusted);
|
|
402
|
+
expectedSchema = canonicalJson3(schemaEntries(trusted));
|
|
403
|
+
} finally {
|
|
404
|
+
trusted.close();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (canonicalJson3(entries) !== expectedSchema)
|
|
408
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache schema differs from the pinned Oh schema; rebuild it from Markdown.");
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/oh/authority.ts
|
|
413
|
+
var MAXIMUM_CACHE_OPERATIONS = 1024;
|
|
414
|
+
function cacheError(error) {
|
|
415
|
+
return error instanceof GraphAuthorityError ? error : new GraphAuthorityError("corrupt-cache", "The graph cache failed Oh integrity verification; rebuild it from Markdown.");
|
|
416
|
+
}
|
|
417
|
+
async function openOhGraphAdapter(input, options = {}) {
|
|
418
|
+
const snapshot = parseGraphSnapshot(input);
|
|
419
|
+
if (options === null || typeof options !== "object" || isProxy2(options) || Object.getPrototypeOf(options) !== Object.prototype && Object.getPrototypeOf(options) !== null || Reflect.ownKeys(options).some((key) => typeof key !== "string" || !Object.getOwnPropertyDescriptor(options, key)?.enumerable || !("value" in Object.getOwnPropertyDescriptor(options, key))))
|
|
420
|
+
throw new GraphAuthorityError("invalid-input", "Graph adapter options must be plain data fields.");
|
|
421
|
+
exactKeys(options, [], ["databasePath", "databaseBytes", "writable"]);
|
|
422
|
+
if (options.databasePath !== undefined && options.databaseBytes !== undefined || options.databasePath !== undefined && (typeof options.databasePath !== "string" || options.databasePath.length === 0 || options.writable !== true) || options.databaseBytes !== undefined && (!(options.databaseBytes instanceof Uint8Array) || options.databaseBytes.byteLength < 1 || options.databaseBytes.byteLength > GRAPH_LIMITS.sourceBytes || options.writable === true) || options.writable !== undefined && typeof options.writable !== "boolean")
|
|
423
|
+
throw new GraphAuthorityError("invalid-input", "Use writable staging paths or read-only cache bytes, never both.");
|
|
424
|
+
const readonly = options.databaseBytes !== undefined;
|
|
425
|
+
let records, expected;
|
|
426
|
+
try {
|
|
427
|
+
records = snapshot.records.map(sourceGraphRecord);
|
|
428
|
+
expected = [...records, manifestGraphRecord(snapshot)].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (error instanceof RangeError)
|
|
431
|
+
throw new GraphAuthorityError("budget", "A graph source record exceeds the Oh record byte limit.");
|
|
432
|
+
throw new GraphAuthorityError("invalid-input", "Graph source records are not valid Oh records.");
|
|
433
|
+
}
|
|
434
|
+
const profile = createOhStoreProfileV1({
|
|
435
|
+
applicationProfileSha256: canonicalSha2562({ vaultIdentity: snapshot.vaultIdentity, extractorSha256: GRAPH_EXTRACTOR_SHA256 }),
|
|
436
|
+
capabilities: OH_WORKING_STORE_PROFILE_V1.capabilities,
|
|
437
|
+
profileId: "wordcell.graph.working.v1",
|
|
438
|
+
profileKind: "working",
|
|
439
|
+
v: 1
|
|
440
|
+
});
|
|
441
|
+
const spaceId = "wordcell.graph";
|
|
442
|
+
let database;
|
|
443
|
+
let store;
|
|
444
|
+
let closed = false;
|
|
445
|
+
try {
|
|
446
|
+
let mapProof = function(proof, budget) {
|
|
447
|
+
const reserve = (value) => {
|
|
448
|
+
budget.remaining -= Buffer.byteLength(canonicalJson4(value)) + 16;
|
|
449
|
+
if (budget.remaining < 0)
|
|
450
|
+
throw new GraphAuthorityError("budget", "Graph proof source labels exceed the outward result byte limit.");
|
|
451
|
+
};
|
|
452
|
+
if (proof.kind === "truncated") {
|
|
453
|
+
const result2 = { kind: proof.kind, relation: proof.relation, tuple: proof.tuple, reason: proof.reason };
|
|
454
|
+
reserve(result2);
|
|
455
|
+
return result2;
|
|
456
|
+
}
|
|
457
|
+
if (proof.kind === "derived") {
|
|
458
|
+
const result2 = {
|
|
459
|
+
kind: proof.kind,
|
|
460
|
+
relation: proof.relation,
|
|
461
|
+
tuple: proof.tuple,
|
|
462
|
+
ruleId: proof.ruleId,
|
|
463
|
+
ruleSha256: proof.ruleSha256,
|
|
464
|
+
premisesTruncated: proof.premisesTruncated
|
|
465
|
+
};
|
|
466
|
+
reserve(result2);
|
|
467
|
+
return { ...result2, premises: proof.premises.map((premise) => mapProof(premise, budget)) };
|
|
468
|
+
}
|
|
469
|
+
const result = { kind: "fact", relation: proof.relation, tuple: proof.tuple, sources: proof.sources.map((reference) => {
|
|
470
|
+
const bound = recordMap.get(reference.key);
|
|
471
|
+
if (bound === undefined || bound.record.recordSha256 !== reference.recordSha256)
|
|
472
|
+
throw new GraphAuthorityError("corrupt-cache", "Projection proof contains an unbound source record.");
|
|
473
|
+
return {
|
|
474
|
+
key: reference.key,
|
|
475
|
+
recordSha256: reference.recordSha256,
|
|
476
|
+
noteId: bound.source.id,
|
|
477
|
+
path: bound.source.path,
|
|
478
|
+
contentSha256: bound.source.contentSha256
|
|
479
|
+
};
|
|
480
|
+
}) };
|
|
481
|
+
reserve(result);
|
|
482
|
+
return result;
|
|
483
|
+
};
|
|
484
|
+
database = readonly ? Database2.deserialize(new Uint8Array(options.databaseBytes), { strict: true }) : new Database2(options.databasePath ?? ":memory:", { create: true, strict: true });
|
|
485
|
+
database.exec("PRAGMA trusted_schema = OFF");
|
|
486
|
+
const hasSchema = validateOhCacheSchema(database);
|
|
487
|
+
if (hasSchema) {
|
|
488
|
+
const spaces = database.query("SELECT space_id FROM oh_spaces LIMIT 2").all();
|
|
489
|
+
const bindings = database.query("SELECT binding_json FROM oh_space_bindings LIMIT 2").all();
|
|
490
|
+
if (spaces.length !== 1 || spaces[0]?.space_id !== spaceId || bindings.length !== 1)
|
|
491
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache does not contain one bound Wordcell space.");
|
|
492
|
+
const binding = JSON.parse(bindings[0].binding_json);
|
|
493
|
+
if (binding.profile?.profileSha256 !== profile.profileSha256)
|
|
494
|
+
throw new GraphAuthorityError("stale", "Graph cache belongs to a different vault or extractor profile.");
|
|
495
|
+
const count = database.query(`SELECT count(*) AS count FROM (SELECT 1 FROM oh_operations LIMIT ${MAXIMUM_CACHE_OPERATIONS + 1})`).get()?.count;
|
|
496
|
+
if (!Number.isSafeInteger(count) || count > MAXIMUM_CACHE_OPERATIONS)
|
|
497
|
+
throw new GraphAuthorityError("budget", "Graph cache history exceeds its replay bound; run graph rebuild --fresh.");
|
|
498
|
+
} else if (readonly)
|
|
499
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache has no Oh authority schema.");
|
|
500
|
+
database.exec("PRAGMA foreign_keys = ON; PRAGMA journal_mode = DELETE; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000");
|
|
501
|
+
store = createOhSqliteStoreAuthorityV1({ database, profile, realmId: `wordcell:${snapshot.vaultIdentity}`, spaceId }).store;
|
|
502
|
+
await store.verify();
|
|
503
|
+
const current = await store.snapshot({ maximumRecords: GRAPH_LIMITS.notes + 1 });
|
|
504
|
+
if (canonicalJson4(current.records) !== canonicalJson4(expected)) {
|
|
505
|
+
if (readonly)
|
|
506
|
+
throw new GraphAuthorityError("stale", "Graph cache does not match the current Markdown snapshot.");
|
|
507
|
+
if (current.head.sequence >= MAXIMUM_CACHE_OPERATIONS)
|
|
508
|
+
throw new GraphAuthorityError("budget", "Graph cache history is full; run graph rebuild --fresh.");
|
|
509
|
+
if (current.records.some((record) => record.key !== GRAPH_MANIFEST_KEY && !/^edition:note\/[0-9a-f]{64}$/u.test(record.key)))
|
|
510
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache contains foreign records.");
|
|
511
|
+
const prior = new Map(current.records.map((record) => [record.key, record]));
|
|
512
|
+
const next = new Map(expected.map((record) => [record.key, record]));
|
|
513
|
+
const changes = [];
|
|
514
|
+
for (const record of expected)
|
|
515
|
+
if (prior.get(record.key)?.recordSha256 !== record.recordSha256)
|
|
516
|
+
changes.push({ kind: "put", record, v: 1 });
|
|
517
|
+
for (const record of current.records)
|
|
518
|
+
if (!next.has(record.key))
|
|
519
|
+
changes.push({ kind: "tombstone", key: record.key, priorSha256: record.recordSha256, v: 1 });
|
|
520
|
+
await store.commit({
|
|
521
|
+
actorId: "wordcell.graph",
|
|
522
|
+
changes,
|
|
523
|
+
expectedHead: current.head,
|
|
524
|
+
...options.databasePath === undefined ? { instant: "1970-01-01T00:00:00.000Z" } : {},
|
|
525
|
+
operationId: `graph:${snapshot.revision}:${current.head.sequence}`,
|
|
526
|
+
maximumOperationBytes: GRAPH_LIMITS.sourceBytes
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const selectedStore = store;
|
|
530
|
+
const materialized = await selectedStore.snapshot({ maximumRecords: GRAPH_LIMITS.notes + 1 });
|
|
531
|
+
if (canonicalJson4(materialized.records) !== canonicalJson4(expected))
|
|
532
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph reconciliation did not retain exact source records.");
|
|
533
|
+
const projectionSnapshot = createOhProjectionSnapshotV1({ ...materialized, spaceId });
|
|
534
|
+
const recordMap = new Map(records.map((record, index) => [record.key, { record, source: snapshot.records[index] }]));
|
|
535
|
+
const assertOpen = () => {
|
|
536
|
+
if (closed)
|
|
537
|
+
throw new GraphAuthorityError("closed", "The graph authority is closed.");
|
|
538
|
+
};
|
|
539
|
+
const assertCurrent = async () => {
|
|
540
|
+
assertOpen();
|
|
541
|
+
if (canonicalJson4(await selectedStore.head()) !== canonicalJson4(materialized.head))
|
|
542
|
+
throw new GraphAuthorityError("stale", "Graph authority head changed after opening.");
|
|
543
|
+
};
|
|
544
|
+
async function query(value) {
|
|
545
|
+
assertOpen();
|
|
546
|
+
const request = normalizeGraphQueryRequest(value);
|
|
547
|
+
await assertCurrent();
|
|
548
|
+
const limits = request.limits;
|
|
549
|
+
const program = compileGraphProgram(request);
|
|
550
|
+
const facts = snapshot.records.flatMap((source, index) => {
|
|
551
|
+
const record = records[index];
|
|
552
|
+
const sources = [{ key: record.key, recordSha256: record.recordSha256, v: 1 }];
|
|
553
|
+
const items = source.facts.map((fact) => createOhProjectionFactV1({ ...fact, sources }));
|
|
554
|
+
if (request.program !== "scope-route" && source.id === request.note)
|
|
555
|
+
items.push(createOhProjectionFactV1({ relation: "wordcell.selected", tuple: [source.id], sources }));
|
|
556
|
+
if ((request.program === "shared-tags" || request.program === "shared-concepts") && source.id !== request.note)
|
|
557
|
+
items.push(createOhProjectionFactV1({ relation: "wordcell.peer", tuple: [source.id], sources }));
|
|
558
|
+
if (request.program === "shared-concepts" && !source.facts.some((fact) => fact.relation === "wordcell.concept"))
|
|
559
|
+
items.push(createOhProjectionFactV1({ relation: "wordcell.ordinary", tuple: [source.id], sources }));
|
|
560
|
+
return items;
|
|
561
|
+
});
|
|
562
|
+
try {
|
|
563
|
+
const dataset = createOhProjectionDatasetV1({ extractorSha256: GRAPH_EXTRACTOR_SHA256, factPackId: "wordcell.graph-facts", factPackRevision: 1, facts, snapshot: projectionSnapshot });
|
|
564
|
+
const result = evaluateOhProjectionV1({ ...program, dataset, snapshot: projectionSnapshot, options: {
|
|
565
|
+
maximumDerivedTuples: limits.derivedTuples,
|
|
566
|
+
maximumRounds: limits.rounds,
|
|
567
|
+
maximumWorkUnits: limits.workUnits,
|
|
568
|
+
maximumProofDepth: limits.proofDepth,
|
|
569
|
+
maximumProofNodes: limits.proofNodes,
|
|
570
|
+
maximumTotalProofNodes: limits.totalProofNodes,
|
|
571
|
+
maximumResultBytes: limits.resultBytes
|
|
572
|
+
} });
|
|
573
|
+
if (parseOhProjectionResultV1(result, result.identity.projectionSha256) === null)
|
|
574
|
+
throw new GraphAuthorityError("corrupt-cache", "Oh returned an invalid bounded projection result.");
|
|
575
|
+
await assertCurrent();
|
|
576
|
+
const proofBudget = { remaining: limits.resultBytes };
|
|
577
|
+
const output = {
|
|
578
|
+
schemaVersion: 1,
|
|
579
|
+
authority: "derived",
|
|
580
|
+
vaultIdentity: snapshot.vaultIdentity,
|
|
581
|
+
revision: snapshot.revision,
|
|
582
|
+
projectionSha256: result.identity.projectionSha256,
|
|
583
|
+
request,
|
|
584
|
+
columns: program.columns,
|
|
585
|
+
rows: result.rows.map((row) => ({
|
|
586
|
+
values: row.values,
|
|
587
|
+
proofs: row.proofs.map((proof) => mapProof(proof, proofBudget)),
|
|
588
|
+
proofsTruncated: row.proofsTruncated,
|
|
589
|
+
supportCount: row.supportCount
|
|
590
|
+
})),
|
|
591
|
+
limits,
|
|
592
|
+
truncated: result.stats.truncated,
|
|
593
|
+
proofsTruncated: result.stats.proofsTruncated,
|
|
594
|
+
truncationReasons: result.stats.truncationReasons,
|
|
595
|
+
stats: {
|
|
596
|
+
baseFacts: result.stats.baseFacts,
|
|
597
|
+
derivedFacts: result.stats.derivedFacts,
|
|
598
|
+
workUnits: result.stats.workUnits,
|
|
599
|
+
rounds: result.stats.rounds,
|
|
600
|
+
proofNodes: result.stats.proofNodes,
|
|
601
|
+
queryMatches: result.stats.queryMatches
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
if (Buffer.byteLength(canonicalJson4(output)) > limits.resultBytes)
|
|
605
|
+
throw new GraphAuthorityError("budget", "Graph result with source labels exceeds the outward result byte limit.");
|
|
606
|
+
return deepFreeze(output);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error instanceof GraphAuthorityError)
|
|
609
|
+
throw error;
|
|
610
|
+
if (error instanceof RangeError)
|
|
611
|
+
throw new GraphAuthorityError("budget", error.message);
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return Object.freeze({
|
|
616
|
+
query,
|
|
617
|
+
verifyResult: async (value) => {
|
|
618
|
+
assertOpen();
|
|
619
|
+
try {
|
|
620
|
+
const result = detachedData(value, GRAPH_LIMITS.resultBytes * 4);
|
|
621
|
+
if (result === null || typeof result !== "object" || Array.isArray(result) || result.vaultIdentity !== snapshot.vaultIdentity || result.revision !== snapshot.revision)
|
|
622
|
+
return false;
|
|
623
|
+
const verified = await query(result.request);
|
|
624
|
+
return canonicalJson4(result) === canonicalJson4(verified);
|
|
625
|
+
} catch {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
verify: async () => {
|
|
630
|
+
await assertCurrent();
|
|
631
|
+
await selectedStore.verify();
|
|
632
|
+
const actual = await selectedStore.snapshot({ maximumRecords: GRAPH_LIMITS.notes + 1 });
|
|
633
|
+
if (canonicalJson4(actual.records) !== canonicalJson4(expected))
|
|
634
|
+
throw new GraphAuthorityError("corrupt-cache", "Graph cache differs from its exact snapshot.");
|
|
635
|
+
return deepFreeze({
|
|
636
|
+
schemaVersion: 1,
|
|
637
|
+
status: "verified",
|
|
638
|
+
vaultIdentity: snapshot.vaultIdentity,
|
|
639
|
+
revision: snapshot.revision,
|
|
640
|
+
records: snapshot.records.length,
|
|
641
|
+
facts: snapshot.factCount
|
|
642
|
+
});
|
|
643
|
+
},
|
|
644
|
+
close: async () => {
|
|
645
|
+
if (!closed) {
|
|
646
|
+
closed = true;
|
|
647
|
+
await selectedStore.close();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
} catch (error) {
|
|
652
|
+
try {
|
|
653
|
+
if (store !== undefined)
|
|
654
|
+
await store.close();
|
|
655
|
+
else
|
|
656
|
+
database?.close();
|
|
657
|
+
} catch {}
|
|
658
|
+
throw cacheError(error);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
export {
|
|
662
|
+
openOhGraphAdapter
|
|
663
|
+
};
|