@curatelabs/graphforge-agent-skills 0.5.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/LICENSE +202 -0
- package/NOTICE +18 -0
- package/README.md +169 -0
- package/adapter/index.js +422 -0
- package/bin/graphforge-agent-skills.js +29 -0
- package/compatibility.json +14 -0
- package/package.json +67 -0
- package/schemas/README.md +27 -0
- package/schemas/input-envelope-v1.json +45 -0
- package/schemas/output-envelope-v1.json +76 -0
- package/schemas/skill-manifest-v1.json +41 -0
- package/schemas/validator.js +233 -0
- package/skills/README.md +10 -0
- package/skills/bootstrap/manifest.json +9 -0
- package/skills/build-knowledge/manifest.json +13 -0
- package/workflows/index.js +1392 -0
|
@@ -0,0 +1,1392 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AgentAdapterError,
|
|
5
|
+
normalizeGraphForgeError,
|
|
6
|
+
openProject,
|
|
7
|
+
requireCapabilities,
|
|
8
|
+
tableToJson,
|
|
9
|
+
uuidToString,
|
|
10
|
+
validateProjectPath,
|
|
11
|
+
normalizeWriteOptions,
|
|
12
|
+
} from "../adapter/index.js";
|
|
13
|
+
|
|
14
|
+
const BOOTSTRAP_QUERY =
|
|
15
|
+
"MATCH (n:GraphForgeBootstrap {key: 'agent-skills/v1'}) RETURN n.node_uuid AS node_uuid";
|
|
16
|
+
|
|
17
|
+
export async function bootstrapProject({
|
|
18
|
+
GraphForge,
|
|
19
|
+
tableFromIPC,
|
|
20
|
+
path,
|
|
21
|
+
cwd,
|
|
22
|
+
ontologyMode = "exploratory",
|
|
23
|
+
ontologyPath,
|
|
24
|
+
writeOptions,
|
|
25
|
+
}) {
|
|
26
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
27
|
+
if (!["exploratory", "advisory", "strict"].includes(ontologyMode)) {
|
|
28
|
+
throw new AgentAdapterError(
|
|
29
|
+
"GF_AGENT_BOOTSTRAP_CONFIGURATION",
|
|
30
|
+
"ontology mode must be exploratory, advisory, or strict",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const projectPath = await validateProjectPath({ path, cwd });
|
|
34
|
+
let graph;
|
|
35
|
+
try {
|
|
36
|
+
await mkdir(projectPath, { recursive: true }).catch((error) => {
|
|
37
|
+
if (error?.code !== "EEXIST") throw error;
|
|
38
|
+
});
|
|
39
|
+
await validateProjectPath({ path: projectPath });
|
|
40
|
+
const normalizedWriteOptions = normalizeWriteOptions(writeOptions);
|
|
41
|
+
graph = new GraphForge(projectPath, normalizedWriteOptions);
|
|
42
|
+
if (ontologyMode === "advisory" && graph.ontologyMode === "exploratory") {
|
|
43
|
+
if (typeof ontologyPath !== "string" || ontologyPath.length === 0) {
|
|
44
|
+
throw new AgentAdapterError(
|
|
45
|
+
"GF_AGENT_BOOTSTRAP_CONFIGURATION",
|
|
46
|
+
"advisory bootstrap requires an explicit ontology path",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const safeOntologyPath = await validateProjectPath({
|
|
50
|
+
path: ontologyPath,
|
|
51
|
+
cwd,
|
|
52
|
+
});
|
|
53
|
+
graph.loadOntology(safeOntologyPath);
|
|
54
|
+
}
|
|
55
|
+
if (graph.ontologyMode !== ontologyMode) {
|
|
56
|
+
throw new AgentAdapterError(
|
|
57
|
+
"GF_AGENT_ONTOLOGY_MODE_CONFLICT",
|
|
58
|
+
"the project ontology mode does not match the requested mode",
|
|
59
|
+
{ actual_mode: graph.ontologyMode, requested_mode: ontologyMode },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const capabilities = decode(tableFromIPC, await graph.projectCapabilities());
|
|
63
|
+
requireCapabilities(capabilityMap(capabilities), { graph: 1 });
|
|
64
|
+
const before = decode(tableFromIPC, await graph.execute(BOOTSTRAP_QUERY));
|
|
65
|
+
if (before.length > 1) {
|
|
66
|
+
throw new AgentAdapterError(
|
|
67
|
+
"GF_AGENT_BOOTSTRAP_CONFLICT",
|
|
68
|
+
"multiple bootstrap markers exist",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const created = before.length === 0;
|
|
72
|
+
const markerUuid = created
|
|
73
|
+
? uuidToString(graph.addNode("GraphForgeBootstrap", { key: "agent-skills/v1" }).uuid)
|
|
74
|
+
: uuidToString(before[0].node_uuid);
|
|
75
|
+
graph.close();
|
|
76
|
+
graph = undefined;
|
|
77
|
+
|
|
78
|
+
const reopened = await openProject({
|
|
79
|
+
GraphForge,
|
|
80
|
+
path: projectPath,
|
|
81
|
+
requiredCapabilities: { graph: 1 },
|
|
82
|
+
tableFromIPC,
|
|
83
|
+
writeOptions: normalizedWriteOptions,
|
|
84
|
+
});
|
|
85
|
+
graph = reopened.graph;
|
|
86
|
+
const verified = decode(tableFromIPC, await graph.execute(BOOTSTRAP_QUERY));
|
|
87
|
+
if (verified.length !== 1 || uuidToString(verified[0].node_uuid) !== markerUuid) {
|
|
88
|
+
throw new AgentAdapterError(
|
|
89
|
+
"GF_AGENT_BOOTSTRAP_VERIFY_FAILED",
|
|
90
|
+
"the reopened project did not return the bootstrap marker",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
capabilities: reopened.capabilities,
|
|
95
|
+
created,
|
|
96
|
+
marker_uuid: markerUuid,
|
|
97
|
+
ontology_mode: graph.ontologyMode,
|
|
98
|
+
rows: verified,
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw normalizeGraphForgeError(error);
|
|
102
|
+
} finally {
|
|
103
|
+
graph?.close?.();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function buildKnowledge({ GraphForge, tableFromIPC, path, input, writeOptions }) {
|
|
108
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
109
|
+
validateBuildInput(input);
|
|
110
|
+
let graph;
|
|
111
|
+
try {
|
|
112
|
+
const opened = await openProject({
|
|
113
|
+
GraphForge,
|
|
114
|
+
path,
|
|
115
|
+
requiredCapabilities: { graph: 1 },
|
|
116
|
+
tableFromIPC,
|
|
117
|
+
writeOptions,
|
|
118
|
+
});
|
|
119
|
+
graph = opened.graph;
|
|
120
|
+
for (const capabilityId of requiredCapabilitiesFor(input)) {
|
|
121
|
+
await graph.enableCapability({
|
|
122
|
+
actorUuid: input.actor_uuid,
|
|
123
|
+
capabilityId,
|
|
124
|
+
capabilityVersion: 1,
|
|
125
|
+
operationUuid: input.capability_operation_uuids[capabilityId],
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const capabilityRows = decode(tableFromIPC, await graph.projectCapabilities());
|
|
129
|
+
|
|
130
|
+
const handles = new Map();
|
|
131
|
+
const nodes = input.nodes.map((node) => {
|
|
132
|
+
const handle = graph.addNode(node.label, node.properties ?? {});
|
|
133
|
+
handles.set(node.key, handle);
|
|
134
|
+
return { key: node.key, uuid: uuidToString(handle.uuid) };
|
|
135
|
+
});
|
|
136
|
+
const edges = input.edges.map((edge) => {
|
|
137
|
+
const source = handles.get(edge.source_key);
|
|
138
|
+
const target = handles.get(edge.target_key);
|
|
139
|
+
if (!source || !target) {
|
|
140
|
+
throw new AgentAdapterError(
|
|
141
|
+
"GF_AGENT_BUILD_REFERENCE_MISSING",
|
|
142
|
+
"edge endpoints must reference nodes in the same request",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const handle = graph.addEdge(source, edge.type, target, edge.properties ?? {});
|
|
146
|
+
return { key: edge.key, uuid: uuidToString(handle.uuid) };
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const graphRefs = input.assertion.graph_refs.map((reference) => ({
|
|
150
|
+
graphKind: reference.graph_kind,
|
|
151
|
+
graphUuid: graphUuid(reference, nodes, edges),
|
|
152
|
+
ordinal: reference.ordinal,
|
|
153
|
+
role: reference.role,
|
|
154
|
+
}));
|
|
155
|
+
const assertionRequest = {
|
|
156
|
+
actorUuid: input.actor_uuid,
|
|
157
|
+
assertionUuid: input.assertion.assertion_uuid,
|
|
158
|
+
claim: input.assertion.claim,
|
|
159
|
+
graphRefs,
|
|
160
|
+
operationUuid: input.assertion.operation_uuid,
|
|
161
|
+
};
|
|
162
|
+
const assertionRows = decode(
|
|
163
|
+
tableFromIPC,
|
|
164
|
+
await graph.createAssertionWithEvidence({
|
|
165
|
+
...assertionRequest,
|
|
166
|
+
evidence: input.evidence.map((evidence) => ({
|
|
167
|
+
evidenceUuid: evidence.evidence_uuid,
|
|
168
|
+
role: evidence.role,
|
|
169
|
+
sourceKind: evidence.source_kind,
|
|
170
|
+
sourceUuid: graphSourceUuid(evidence, nodes, edges),
|
|
171
|
+
weight: evidence.weight,
|
|
172
|
+
})),
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
const assertionProvenance = uuidToString(assertionRows[0].provenance_uuid);
|
|
176
|
+
|
|
177
|
+
const confidenceRows = decode(
|
|
178
|
+
tableFromIPC,
|
|
179
|
+
await graph.assessConfidence({
|
|
180
|
+
actorUuid: input.actor_uuid,
|
|
181
|
+
assertionUuid: input.assertion.assertion_uuid,
|
|
182
|
+
confidenceUuid: input.confidence.confidence_uuid,
|
|
183
|
+
operationUuid: input.confidence.operation_uuid,
|
|
184
|
+
policy: input.confidence.policy,
|
|
185
|
+
value: input.confidence.value,
|
|
186
|
+
inputConfidenceUuids: input.confidence.input_confidence_uuids,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
const reasoningRows = input.reasoning
|
|
190
|
+
? decode(
|
|
191
|
+
tableFromIPC,
|
|
192
|
+
await graph.recordReasoning({
|
|
193
|
+
actorUuid: input.actor_uuid,
|
|
194
|
+
assertionUuid: input.assertion.assertion_uuid,
|
|
195
|
+
content: Buffer.from(input.reasoning.content, "utf8"),
|
|
196
|
+
contentFormat: input.reasoning.content_format,
|
|
197
|
+
kind: input.reasoning.kind,
|
|
198
|
+
operationUuid: input.reasoning.operation_uuid,
|
|
199
|
+
provenanceUuid: assertionProvenance,
|
|
200
|
+
reasoningUuid: input.reasoning.reasoning_uuid,
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
: [];
|
|
204
|
+
const statusRows = input.status
|
|
205
|
+
? decode(
|
|
206
|
+
tableFromIPC,
|
|
207
|
+
await graph.recordAssertionStatus({
|
|
208
|
+
actorUuid: input.actor_uuid,
|
|
209
|
+
assertionUuid: input.assertion.assertion_uuid,
|
|
210
|
+
confidenceUuid: input.confidence.confidence_uuid,
|
|
211
|
+
operationUuid: input.status.operation_uuid,
|
|
212
|
+
provenanceUuid: assertionProvenance,
|
|
213
|
+
reasoningUuid: input.reasoning?.reasoning_uuid,
|
|
214
|
+
status: input.status.status,
|
|
215
|
+
statusEventUuid: input.status.status_event_uuid,
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
: [];
|
|
219
|
+
return {
|
|
220
|
+
assertion: assertionRows,
|
|
221
|
+
capabilities: capabilityRows,
|
|
222
|
+
confidence: confidenceRows,
|
|
223
|
+
edges,
|
|
224
|
+
evidence_count: input.evidence.length,
|
|
225
|
+
nodes,
|
|
226
|
+
reasoning: reasoningRows,
|
|
227
|
+
status: statusRows,
|
|
228
|
+
};
|
|
229
|
+
} catch (error) {
|
|
230
|
+
throw normalizeGraphForgeError(error);
|
|
231
|
+
} finally {
|
|
232
|
+
graph?.close?.();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resolve one caller-addressed belief subject without inventing a selection.
|
|
238
|
+
*
|
|
239
|
+
* Rust remains authoritative for subject selection, the transaction snapshot,
|
|
240
|
+
* valid-time intersection, ambiguity policy, and graph projection. This
|
|
241
|
+
* workflow only decodes the canonical native evidence and exposes the opaque
|
|
242
|
+
* projection for a later recorded invocation.
|
|
243
|
+
*/
|
|
244
|
+
export async function resolveBeliefSubject({
|
|
245
|
+
GraphForge,
|
|
246
|
+
tableFromIPC,
|
|
247
|
+
path,
|
|
248
|
+
input,
|
|
249
|
+
writeOptions,
|
|
250
|
+
}) {
|
|
251
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
252
|
+
const request = validateBeliefSubjectInput(input);
|
|
253
|
+
let graph;
|
|
254
|
+
try {
|
|
255
|
+
const opened = await openProject({
|
|
256
|
+
GraphForge,
|
|
257
|
+
path,
|
|
258
|
+
requiredCapabilities: { epistemic: 1, graph: 1, knowledge: 1 },
|
|
259
|
+
tableFromIPC,
|
|
260
|
+
writeOptions,
|
|
261
|
+
});
|
|
262
|
+
graph = opened.graph;
|
|
263
|
+
const resolved = await graph.resolveBeliefSubject({
|
|
264
|
+
...request.subject,
|
|
265
|
+
transactionCutoffMicros: request.transactionCutoffMicros,
|
|
266
|
+
validTimeMicros: request.validTimeMicros,
|
|
267
|
+
policy: request.nativePolicy,
|
|
268
|
+
});
|
|
269
|
+
const evidence = decode(tableFromIPC, resolved.evidence);
|
|
270
|
+
const assertions = evidence
|
|
271
|
+
.filter((row) => row.entity_kind === "assertion")
|
|
272
|
+
.map(assertionRecord);
|
|
273
|
+
const hypothesisGroups = evidence
|
|
274
|
+
.filter((row) => row.entity_kind === "hypothesis_group")
|
|
275
|
+
.map(hypothesisRecord);
|
|
276
|
+
const subjectSources = [...new Set(evidence.flatMap((row) => row.source_record_uuids))].sort();
|
|
277
|
+
const projection = resolved.projection;
|
|
278
|
+
let subject;
|
|
279
|
+
if (request.subject.assertionUuid) {
|
|
280
|
+
subject = { assertion_uuid: request.subject.assertionUuid, kind: "assertion" };
|
|
281
|
+
} else {
|
|
282
|
+
const addressedGroups = hypothesisGroups.filter(
|
|
283
|
+
(row) => row.question_key === request.subject.hypothesisQuestionKey,
|
|
284
|
+
);
|
|
285
|
+
if (addressedGroups.length !== 1) {
|
|
286
|
+
throw new AgentAdapterError(
|
|
287
|
+
"GF_AGENT_BELIEF_EVIDENCE_INVALID",
|
|
288
|
+
"native belief-subject evidence must contain exactly one addressed hypothesis group",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
subject = {
|
|
292
|
+
group_uuid: addressedGroups[0].group_uuid,
|
|
293
|
+
hypothesis_question_key: request.subject.hypothesisQuestionKey,
|
|
294
|
+
kind: "hypothesis_question",
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
assertions,
|
|
299
|
+
contract_version: 1,
|
|
300
|
+
hypothesis_groups: hypothesisGroups,
|
|
301
|
+
policy: request.outputPolicy,
|
|
302
|
+
projection,
|
|
303
|
+
projection_evidence: {
|
|
304
|
+
graph_content_fingerprint: projection.graphContentFingerprint,
|
|
305
|
+
policy_fingerprint: projection.policyFingerprint,
|
|
306
|
+
snapshot_fingerprint: projection.snapshotFingerprint,
|
|
307
|
+
source_generation_uuid: uuidToString(projection.sourceGenerationUuid),
|
|
308
|
+
source_record_uuids: [...projection.sourceRecordUuids].map(uuidToString).sort(),
|
|
309
|
+
valid_time_fingerprint: projection.validTimeFingerprint ?? null,
|
|
310
|
+
},
|
|
311
|
+
subject,
|
|
312
|
+
subject_source_record_uuids: subjectSources,
|
|
313
|
+
transaction_cutoff_micros: String(request.transactionCutoffMicros),
|
|
314
|
+
valid_time_micros:
|
|
315
|
+
request.validTimeMicros === undefined ? null : String(request.validTimeMicros),
|
|
316
|
+
};
|
|
317
|
+
} catch (error) {
|
|
318
|
+
throw normalizeGraphForgeError(error);
|
|
319
|
+
} finally {
|
|
320
|
+
graph?.close?.();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const DEFAULT_NARRATION_RECORD_BUDGET = 1024;
|
|
325
|
+
const DEFAULT_NARRATION_PAGE_LIMIT = 100;
|
|
326
|
+
const NEXT_PAGE_TOKEN_KEY = "graphforge.next_page_token";
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Narrate every public record relevant to one resolved belief subject.
|
|
330
|
+
*
|
|
331
|
+
* Rust remains authoritative for history content and pagination. This workflow
|
|
332
|
+
* only walks the shipped Node list surfaces for the resolved assertion set,
|
|
333
|
+
* fails closed when the caller budget is exhausted, and returns UUID-addressed
|
|
334
|
+
* descriptors for broader project-level collections instead of truncating them.
|
|
335
|
+
*/
|
|
336
|
+
export async function narrateBeliefRecords({
|
|
337
|
+
GraphForge,
|
|
338
|
+
tableFromIPC,
|
|
339
|
+
path,
|
|
340
|
+
input,
|
|
341
|
+
writeOptions,
|
|
342
|
+
}) {
|
|
343
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
344
|
+
const resolved = await resolveBeliefSubject({
|
|
345
|
+
GraphForge,
|
|
346
|
+
tableFromIPC,
|
|
347
|
+
path,
|
|
348
|
+
input,
|
|
349
|
+
writeOptions,
|
|
350
|
+
});
|
|
351
|
+
const budget = narrationBudget(input?.record_budget);
|
|
352
|
+
const pageLimit = narrationPageLimit(input?.page_limit);
|
|
353
|
+
const counter = { budget, remaining: budget };
|
|
354
|
+
let graph;
|
|
355
|
+
try {
|
|
356
|
+
const opened = await openProject({
|
|
357
|
+
GraphForge,
|
|
358
|
+
path,
|
|
359
|
+
requiredCapabilities: { epistemic: 1, graph: 1, knowledge: 1 },
|
|
360
|
+
tableFromIPC,
|
|
361
|
+
writeOptions,
|
|
362
|
+
});
|
|
363
|
+
graph = opened.graph;
|
|
364
|
+
const assertionUuids = resolved.assertions.map((row) => row.assertion_uuid);
|
|
365
|
+
const groupUuids = resolved.hypothesis_groups.map((row) => row.group_uuid);
|
|
366
|
+
const validTimeEnabled =
|
|
367
|
+
opened.capabilities?.valid_time?.status === "supported" &&
|
|
368
|
+
opened.capabilities?.valid_time?.version === 1;
|
|
369
|
+
const records = {
|
|
370
|
+
assertion_graph_refs: [],
|
|
371
|
+
assertion_status: [],
|
|
372
|
+
assertion_supersessions: [],
|
|
373
|
+
assertion_validity: [],
|
|
374
|
+
assertions: [],
|
|
375
|
+
confidence_assessments: [],
|
|
376
|
+
confidence_inputs: [],
|
|
377
|
+
evidence_links: [],
|
|
378
|
+
hypothesis_groups: [],
|
|
379
|
+
hypothesis_membership: [],
|
|
380
|
+
hypothesis_selection: [],
|
|
381
|
+
provenance: [],
|
|
382
|
+
reasoning: [],
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
for (const assertionUuid of assertionUuids) {
|
|
386
|
+
const assertionRows = decode(tableFromIPC, await graph.assertion(assertionUuid));
|
|
387
|
+
appendUnique(records.assertions, assertionRows, "assertion_uuid", counter);
|
|
388
|
+
await collectPaged(records.assertion_graph_refs, counter, async (after) =>
|
|
389
|
+
pageDecode(
|
|
390
|
+
tableFromIPC,
|
|
391
|
+
await graph.assertionGraphRefs(assertionUuid, { after, limit: pageLimit }),
|
|
392
|
+
),
|
|
393
|
+
);
|
|
394
|
+
await collectPaged(records.assertion_status, counter, async (after) =>
|
|
395
|
+
pageDecode(
|
|
396
|
+
tableFromIPC,
|
|
397
|
+
await graph.listAssertionStatus({ after, assertionUuid, limit: pageLimit }),
|
|
398
|
+
),
|
|
399
|
+
);
|
|
400
|
+
// valid_time@1 is optional; skip when the project has not enabled it.
|
|
401
|
+
if (validTimeEnabled) {
|
|
402
|
+
await collectPaged(records.assertion_validity, counter, async (after) =>
|
|
403
|
+
pageDecode(
|
|
404
|
+
tableFromIPC,
|
|
405
|
+
await graph.listAssertionValidity({ after, assertionUuid, limit: pageLimit }),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
await collectPaged(records.assertion_supersessions, counter, async (after) =>
|
|
410
|
+
pageDecode(
|
|
411
|
+
tableFromIPC,
|
|
412
|
+
await graph.listAssertionSupersessions({
|
|
413
|
+
after,
|
|
414
|
+
limit: pageLimit,
|
|
415
|
+
priorAssertionUuid: assertionUuid,
|
|
416
|
+
}),
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
await collectPaged(records.assertion_supersessions, counter, async (after) =>
|
|
420
|
+
pageDecode(
|
|
421
|
+
tableFromIPC,
|
|
422
|
+
await graph.listAssertionSupersessions({
|
|
423
|
+
after,
|
|
424
|
+
limit: pageLimit,
|
|
425
|
+
replacementAssertionUuid: assertionUuid,
|
|
426
|
+
}),
|
|
427
|
+
),
|
|
428
|
+
);
|
|
429
|
+
const confidenceBefore = records.confidence_assessments.length;
|
|
430
|
+
await collectPaged(
|
|
431
|
+
records.confidence_assessments,
|
|
432
|
+
counter,
|
|
433
|
+
async (after) =>
|
|
434
|
+
pageDecode(
|
|
435
|
+
tableFromIPC,
|
|
436
|
+
await graph.listConfidenceAssessments({
|
|
437
|
+
after,
|
|
438
|
+
assertionUuid,
|
|
439
|
+
limit: pageLimit,
|
|
440
|
+
}),
|
|
441
|
+
),
|
|
442
|
+
"confidence_uuid",
|
|
443
|
+
);
|
|
444
|
+
const confidenceUuids = [
|
|
445
|
+
...new Set(
|
|
446
|
+
records.confidence_assessments.slice(confidenceBefore).map((row) => row.confidence_uuid),
|
|
447
|
+
),
|
|
448
|
+
].sort();
|
|
449
|
+
for (const confidenceUuid of confidenceUuids) {
|
|
450
|
+
await collectPaged(records.confidence_inputs, counter, async (after) =>
|
|
451
|
+
pageDecode(
|
|
452
|
+
tableFromIPC,
|
|
453
|
+
await graph.confidenceInputs(confidenceUuid, {
|
|
454
|
+
after,
|
|
455
|
+
limit: pageLimit,
|
|
456
|
+
}),
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
await collectPaged(records.evidence_links, counter, async (after) =>
|
|
461
|
+
pageDecode(
|
|
462
|
+
tableFromIPC,
|
|
463
|
+
await graph.listEvidenceLinks({ after, assertionUuid, limit: pageLimit }),
|
|
464
|
+
),
|
|
465
|
+
);
|
|
466
|
+
await collectPaged(records.reasoning, counter, async (after) =>
|
|
467
|
+
pageDecode(
|
|
468
|
+
tableFromIPC,
|
|
469
|
+
await graph.listReasoning({ after, assertionUuid, limit: pageLimit }),
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
await collectPaged(records.provenance, counter, async (after) =>
|
|
473
|
+
pageDecode(
|
|
474
|
+
tableFromIPC,
|
|
475
|
+
await graph.listProvenanceHistory({
|
|
476
|
+
after,
|
|
477
|
+
limit: pageLimit,
|
|
478
|
+
subjectUuid: assertionUuid,
|
|
479
|
+
}),
|
|
480
|
+
),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (const group of resolved.hypothesis_groups) {
|
|
485
|
+
await collectPaged(records.hypothesis_groups, counter, async (after) =>
|
|
486
|
+
pageDecode(
|
|
487
|
+
tableFromIPC,
|
|
488
|
+
await graph.listHypothesisGroups({
|
|
489
|
+
after,
|
|
490
|
+
limit: pageLimit,
|
|
491
|
+
questionKey: group.question_key,
|
|
492
|
+
}),
|
|
493
|
+
),
|
|
494
|
+
);
|
|
495
|
+
await collectPaged(records.hypothesis_membership, counter, async (after) =>
|
|
496
|
+
pageDecode(
|
|
497
|
+
tableFromIPC,
|
|
498
|
+
await graph.listHypothesisMembership({
|
|
499
|
+
after,
|
|
500
|
+
groupUuid: group.group_uuid,
|
|
501
|
+
limit: pageLimit,
|
|
502
|
+
}),
|
|
503
|
+
),
|
|
504
|
+
);
|
|
505
|
+
await collectPaged(records.hypothesis_selection, counter, async (after) =>
|
|
506
|
+
pageDecode(
|
|
507
|
+
tableFromIPC,
|
|
508
|
+
await graph.listHypothesisSelection({
|
|
509
|
+
after,
|
|
510
|
+
groupUuid: group.group_uuid,
|
|
511
|
+
limit: pageLimit,
|
|
512
|
+
}),
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
for (const family of Object.values(records)) {
|
|
518
|
+
family.sort(compareCanonicalRows);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
contract_version: 1,
|
|
523
|
+
page_limit: pageLimit,
|
|
524
|
+
policy: resolved.policy,
|
|
525
|
+
projection: resolved.projection,
|
|
526
|
+
projection_descriptors: projectDescriptors(pageLimit),
|
|
527
|
+
projection_evidence: resolved.projection_evidence,
|
|
528
|
+
record_budget: budget,
|
|
529
|
+
records,
|
|
530
|
+
scoped_assertion_uuids: [...assertionUuids].sort(),
|
|
531
|
+
scoped_hypothesis_group_uuids: [...groupUuids].sort(),
|
|
532
|
+
subject: resolved.subject,
|
|
533
|
+
subject_source_record_uuids: resolved.subject_source_record_uuids,
|
|
534
|
+
transaction_cutoff_micros: resolved.transaction_cutoff_micros,
|
|
535
|
+
valid_time_micros: resolved.valid_time_micros,
|
|
536
|
+
};
|
|
537
|
+
} catch (error) {
|
|
538
|
+
throw normalizeGraphForgeError(error);
|
|
539
|
+
} finally {
|
|
540
|
+
graph?.close?.();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Optionally dispatch one caller-prepared neutral M18 analysis on a resolved
|
|
546
|
+
* belief projection, preserving completed M20 runs when M21 attachment fails.
|
|
547
|
+
*/
|
|
548
|
+
export async function dispatchRecordedNeutralAnalysis({
|
|
549
|
+
GraphForge,
|
|
550
|
+
tableFromIPC,
|
|
551
|
+
path,
|
|
552
|
+
input,
|
|
553
|
+
writeOptions,
|
|
554
|
+
}) {
|
|
555
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
556
|
+
const request = validateRecordedAnalysisInput(input);
|
|
557
|
+
let graph;
|
|
558
|
+
try {
|
|
559
|
+
const opened = await openProject({
|
|
560
|
+
GraphForge,
|
|
561
|
+
path,
|
|
562
|
+
requiredCapabilities: { epistemic: 1, graph: 1, knowledge: 1 },
|
|
563
|
+
tableFromIPC,
|
|
564
|
+
writeOptions,
|
|
565
|
+
});
|
|
566
|
+
graph = opened.graph;
|
|
567
|
+
const recorded = await graph.invokeResolvedRecorded(request.projection, {
|
|
568
|
+
actorUuid: request.actorUuid,
|
|
569
|
+
attachmentUuid: request.attachmentUuid,
|
|
570
|
+
descriptor: request.descriptor,
|
|
571
|
+
operationUuid: request.operationUuid,
|
|
572
|
+
runUuid: request.runUuid,
|
|
573
|
+
signal: request.signal,
|
|
574
|
+
});
|
|
575
|
+
const runRows = decode(tableFromIPC, await graph.algorithmRun(recorded.runUuid));
|
|
576
|
+
const eventRows = decode(tableFromIPC, await graph.algorithmRunEvents(recorded.runUuid));
|
|
577
|
+
return {
|
|
578
|
+
attachment: recorded.attachment ? decode(tableFromIPC, recorded.attachment) : [],
|
|
579
|
+
attachment_error_code: recorded.attachmentErrorCode ?? null,
|
|
580
|
+
attachment_state: recorded.attachmentState,
|
|
581
|
+
attachment_uuid: recorded.attachmentUuid,
|
|
582
|
+
contract_version: 1,
|
|
583
|
+
descriptor_algorithm: request.descriptor.algorithm,
|
|
584
|
+
descriptor_fingerprint: request.descriptor.fingerprint,
|
|
585
|
+
descriptor_verb: request.descriptor.verb,
|
|
586
|
+
result: decode(tableFromIPC, recorded.result),
|
|
587
|
+
run: runRows,
|
|
588
|
+
run_events: eventRows,
|
|
589
|
+
run_uuid: recorded.runUuid,
|
|
590
|
+
};
|
|
591
|
+
} catch (error) {
|
|
592
|
+
throw normalizeGraphForgeError(error);
|
|
593
|
+
} finally {
|
|
594
|
+
graph?.close?.();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const EXPLORE_MODES = new Set(["neighborhood", "traversal", "path", "reachability"]);
|
|
599
|
+
const RANDOM_WALK_ALGORITHMS = new Set(["random_walk"]);
|
|
600
|
+
const MAX_EXPLORE_RESULT_LIMIT = 10_000;
|
|
601
|
+
const MAX_EXPLORE_DEPTH = 10_000;
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Bounded graph exploration through the public Node paths facade.
|
|
605
|
+
*
|
|
606
|
+
* Rust owns traversal/path/reachability semantics. This workflow only validates
|
|
607
|
+
* finite agent bounds, dispatches the selected public paths algorithm, and
|
|
608
|
+
* returns UUID-addressed summaries with complete Arrow/JSON linkage.
|
|
609
|
+
*/
|
|
610
|
+
export async function exploreGraph({ GraphForge, tableFromIPC, path, input, writeOptions }) {
|
|
611
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
612
|
+
const request = validateExploreInput(input);
|
|
613
|
+
let graph;
|
|
614
|
+
try {
|
|
615
|
+
const opened = await openProject({
|
|
616
|
+
GraphForge,
|
|
617
|
+
path,
|
|
618
|
+
requiredCapabilities: { graph: 1 },
|
|
619
|
+
tableFromIPC,
|
|
620
|
+
writeOptions,
|
|
621
|
+
});
|
|
622
|
+
graph = opened.graph;
|
|
623
|
+
if (request.signal?.aborted) {
|
|
624
|
+
throw new AgentAdapterError("GF_CANCELLED", "explore request was cancelled");
|
|
625
|
+
}
|
|
626
|
+
const ipc = invokeExplore(graph, request);
|
|
627
|
+
if (request.signal?.aborted) {
|
|
628
|
+
throw new AgentAdapterError("GF_CANCELLED", "explore request was cancelled");
|
|
629
|
+
}
|
|
630
|
+
const table = Number.isInteger(ipc?.numRows) ? ipc : tableFromIPC(ipc);
|
|
631
|
+
const rows = tableToJson(table);
|
|
632
|
+
const truncated = rows.length > request.resultLimit;
|
|
633
|
+
const summary = truncated ? rows.slice(0, request.resultLimit) : rows;
|
|
634
|
+
return {
|
|
635
|
+
algorithm: request.algorithm,
|
|
636
|
+
contract_version: 1,
|
|
637
|
+
directed: request.directed,
|
|
638
|
+
mode: request.mode,
|
|
639
|
+
result: rows,
|
|
640
|
+
result_limit: request.resultLimit,
|
|
641
|
+
start_uuids: request.startUuids,
|
|
642
|
+
summary,
|
|
643
|
+
target_uuid: request.targetUuid,
|
|
644
|
+
truncated,
|
|
645
|
+
via: request.via,
|
|
646
|
+
walk_length: request.walkLength,
|
|
647
|
+
};
|
|
648
|
+
} catch (error) {
|
|
649
|
+
throw normalizeGraphForgeError(error);
|
|
650
|
+
} finally {
|
|
651
|
+
graph?.close?.();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function validateExploreInput(input) {
|
|
656
|
+
if (!input || typeof input !== "object") {
|
|
657
|
+
throw new AgentAdapterError(
|
|
658
|
+
"GF_AGENT_EXPLORE_CONFIGURATION",
|
|
659
|
+
"explore requires a bounded input object",
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const mode = input.mode;
|
|
663
|
+
if (!EXPLORE_MODES.has(mode)) {
|
|
664
|
+
throw new AgentAdapterError(
|
|
665
|
+
"GF_AGENT_EXPLORE_MODE_REQUIRED",
|
|
666
|
+
"mode must be neighborhood, traversal, path, or reachability",
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
const resultLimit = input.result_limit;
|
|
670
|
+
if (
|
|
671
|
+
!Number.isSafeInteger(resultLimit) ||
|
|
672
|
+
resultLimit < 1 ||
|
|
673
|
+
resultLimit > MAX_EXPLORE_RESULT_LIMIT
|
|
674
|
+
) {
|
|
675
|
+
throw new AgentAdapterError(
|
|
676
|
+
"GF_AGENT_EXPLORE_BOUNDS_REQUIRED",
|
|
677
|
+
"result_limit must be a safe integer in 1..=10000",
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const startUuids = normalizeExploreUuids(input.start_uuids, "start_uuids");
|
|
681
|
+
if (startUuids.length === 0) {
|
|
682
|
+
throw new AgentAdapterError(
|
|
683
|
+
"GF_AGENT_EXPLORE_START_REQUIRED",
|
|
684
|
+
"explore requires one or more start UUIDs",
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
let targetUuid = null;
|
|
688
|
+
if (mode === "path") {
|
|
689
|
+
if (input.target_uuid === undefined || input.target_uuid === null) {
|
|
690
|
+
throw new AgentAdapterError(
|
|
691
|
+
"GF_AGENT_EXPLORE_TARGET_REQUIRED",
|
|
692
|
+
"path mode requires an explicit target UUID",
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
targetUuid = uuidToString(input.target_uuid);
|
|
696
|
+
} else if (input.target_uuid !== undefined && input.target_uuid !== null) {
|
|
697
|
+
targetUuid = uuidToString(input.target_uuid);
|
|
698
|
+
}
|
|
699
|
+
let walkLength;
|
|
700
|
+
if (mode === "neighborhood" || mode === "traversal") {
|
|
701
|
+
const depth = input.depth ?? input.walk_length;
|
|
702
|
+
if (!Number.isSafeInteger(depth) || depth < 1 || depth > MAX_EXPLORE_DEPTH) {
|
|
703
|
+
throw new AgentAdapterError(
|
|
704
|
+
"GF_AGENT_EXPLORE_BOUNDS_REQUIRED",
|
|
705
|
+
"neighborhood and traversal require a finite depth in 1..=10000",
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
walkLength = mode === "neighborhood" ? Math.min(depth, 1) : depth;
|
|
709
|
+
} else if (input.walk_length !== undefined && input.walk_length !== null) {
|
|
710
|
+
if (
|
|
711
|
+
!Number.isSafeInteger(input.walk_length) ||
|
|
712
|
+
input.walk_length < 1 ||
|
|
713
|
+
input.walk_length > MAX_EXPLORE_DEPTH
|
|
714
|
+
) {
|
|
715
|
+
throw new AgentAdapterError(
|
|
716
|
+
"GF_AGENT_EXPLORE_BOUNDS_REQUIRED",
|
|
717
|
+
"walk_length must be a safe integer in 1..=10000 when provided",
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
walkLength = input.walk_length;
|
|
721
|
+
}
|
|
722
|
+
const algorithm =
|
|
723
|
+
mode === "neighborhood" || mode === "traversal"
|
|
724
|
+
? (input.algorithm ?? "bfs")
|
|
725
|
+
: mode === "path"
|
|
726
|
+
? (input.algorithm ?? "dijkstra")
|
|
727
|
+
: (input.algorithm ?? "transitive_closure");
|
|
728
|
+
if (typeof algorithm !== "string" || algorithm.length === 0 || algorithm.length > 4096) {
|
|
729
|
+
throw new AgentAdapterError(
|
|
730
|
+
"GF_AGENT_EXPLORE_ALGORITHM_REQUIRED",
|
|
731
|
+
"algorithm must be a non-empty public paths catalog value",
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const via =
|
|
735
|
+
input.via === undefined || input.via === null
|
|
736
|
+
? undefined
|
|
737
|
+
: typeof input.via === "string" && input.via.length > 0 && input.via.length <= 4096
|
|
738
|
+
? input.via
|
|
739
|
+
: (() => {
|
|
740
|
+
throw new AgentAdapterError(
|
|
741
|
+
"GF_AGENT_EXPLORE_CONFIGURATION",
|
|
742
|
+
"via must be a bounded non-empty string when provided",
|
|
743
|
+
);
|
|
744
|
+
})();
|
|
745
|
+
const directed = input.directed === undefined ? true : Boolean(input.directed);
|
|
746
|
+
return {
|
|
747
|
+
algorithm,
|
|
748
|
+
directed,
|
|
749
|
+
mode,
|
|
750
|
+
resultLimit,
|
|
751
|
+
signal: input.signal,
|
|
752
|
+
startUuids,
|
|
753
|
+
targetUuid,
|
|
754
|
+
via,
|
|
755
|
+
walkLength,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function normalizeExploreUuids(value, field) {
|
|
760
|
+
if (!Array.isArray(value)) {
|
|
761
|
+
throw new AgentAdapterError(
|
|
762
|
+
"GF_AGENT_EXPLORE_START_REQUIRED",
|
|
763
|
+
`${field} must be an array of UUIDs`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (value.length > 1024) {
|
|
767
|
+
throw new AgentAdapterError(
|
|
768
|
+
"GF_AGENT_EXPLORE_BOUNDS_REQUIRED",
|
|
769
|
+
`${field} exceeds the 1024-entry explore budget`,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
return [...new Set(value.map((item) => uuidToString(item)))].sort();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function invokeExplore(graph, request) {
|
|
776
|
+
const source = request.startUuids[0];
|
|
777
|
+
const target = request.targetUuid ?? undefined;
|
|
778
|
+
// Only random-walk catalog algorithms accept walkLength. Neighborhood/traversal
|
|
779
|
+
// keep walk_length in the skill response for agent bounds, but must not pass it
|
|
780
|
+
// into bfs/dijkstra/etc. (native validation rejects random-walk options there).
|
|
781
|
+
const walkLength = RANDOM_WALK_ALGORITHMS.has(request.algorithm) ? request.walkLength : undefined;
|
|
782
|
+
if (typeof graph.preparePathsInvocation === "function") {
|
|
783
|
+
const descriptor = graph.preparePathsInvocation(
|
|
784
|
+
source,
|
|
785
|
+
target,
|
|
786
|
+
request.algorithm,
|
|
787
|
+
request.via,
|
|
788
|
+
request.directed,
|
|
789
|
+
undefined,
|
|
790
|
+
undefined,
|
|
791
|
+
undefined,
|
|
792
|
+
walkLength,
|
|
793
|
+
);
|
|
794
|
+
return graph.invokeDescriptor(descriptor);
|
|
795
|
+
}
|
|
796
|
+
return graph.paths(
|
|
797
|
+
source,
|
|
798
|
+
target,
|
|
799
|
+
request.algorithm,
|
|
800
|
+
request.via,
|
|
801
|
+
request.directed,
|
|
802
|
+
undefined,
|
|
803
|
+
undefined,
|
|
804
|
+
undefined,
|
|
805
|
+
walkLength,
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const RETRIEVE_SURFACES = new Set(["find", "rank", "cluster", "paths", "analyze", "similar"]);
|
|
810
|
+
const MAX_RETRIEVE_RESULT_LIMIT = 10_000;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Bounded retrieve/analyze over public M19 find and live M18 descriptor families.
|
|
814
|
+
*
|
|
815
|
+
* Caller-selected modes and descriptor fields pass through unchanged. Rust owns
|
|
816
|
+
* algorithm/search semantics; this workflow only enforces finite bounds and
|
|
817
|
+
* agent-legible truncation while opening the graph capability alone.
|
|
818
|
+
*/
|
|
819
|
+
export async function retrieveAnalyze({ GraphForge, tableFromIPC, path, input, writeOptions }) {
|
|
820
|
+
configuredSurfaces(GraphForge, tableFromIPC);
|
|
821
|
+
const request = validateRetrieveInput(input);
|
|
822
|
+
let graph;
|
|
823
|
+
try {
|
|
824
|
+
const opened = await openProject({
|
|
825
|
+
GraphForge,
|
|
826
|
+
path,
|
|
827
|
+
requiredCapabilities: { graph: 1 },
|
|
828
|
+
tableFromIPC,
|
|
829
|
+
writeOptions,
|
|
830
|
+
});
|
|
831
|
+
graph = opened.graph;
|
|
832
|
+
if (request.signal?.aborted) {
|
|
833
|
+
throw new AgentAdapterError("GF_CANCELLED", "retrieve request was cancelled");
|
|
834
|
+
}
|
|
835
|
+
const ipc = invokeRetrieve(graph, request);
|
|
836
|
+
if (request.signal?.aborted) {
|
|
837
|
+
throw new AgentAdapterError("GF_CANCELLED", "retrieve request was cancelled");
|
|
838
|
+
}
|
|
839
|
+
const table = Number.isInteger(ipc?.numRows) ? ipc : tableFromIPC(ipc);
|
|
840
|
+
const rows = tableToJson(table);
|
|
841
|
+
const truncated = rows.length > request.resultLimit;
|
|
842
|
+
const summary = truncated ? rows.slice(0, request.resultLimit) : rows;
|
|
843
|
+
return {
|
|
844
|
+
contract_version: 1,
|
|
845
|
+
empty: rows.length === 0,
|
|
846
|
+
result: rows,
|
|
847
|
+
result_limit: request.resultLimit,
|
|
848
|
+
summary,
|
|
849
|
+
surface: request.surface,
|
|
850
|
+
truncated,
|
|
851
|
+
...request.echo,
|
|
852
|
+
};
|
|
853
|
+
} catch (error) {
|
|
854
|
+
throw normalizeGraphForgeError(error);
|
|
855
|
+
} finally {
|
|
856
|
+
graph?.close?.();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function validateRetrieveInput(input) {
|
|
861
|
+
if (!input || typeof input !== "object") {
|
|
862
|
+
throw new AgentAdapterError(
|
|
863
|
+
"GF_AGENT_RETRIEVE_CONFIGURATION",
|
|
864
|
+
"retrieve/analyze requires a bounded input object",
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const surface = input.surface;
|
|
868
|
+
if (!RETRIEVE_SURFACES.has(surface)) {
|
|
869
|
+
throw new AgentAdapterError(
|
|
870
|
+
"GF_AGENT_RETRIEVE_SURFACE_REQUIRED",
|
|
871
|
+
"surface must be find, rank, cluster, paths, analyze, or similar",
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const resultLimit = input.result_limit;
|
|
875
|
+
if (
|
|
876
|
+
!Number.isSafeInteger(resultLimit) ||
|
|
877
|
+
resultLimit < 1 ||
|
|
878
|
+
resultLimit > MAX_RETRIEVE_RESULT_LIMIT
|
|
879
|
+
) {
|
|
880
|
+
throw new AgentAdapterError(
|
|
881
|
+
"GF_AGENT_RETRIEVE_BOUNDS_REQUIRED",
|
|
882
|
+
"result_limit must be a safe integer in 1..=10000",
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
if (surface === "find") {
|
|
886
|
+
const hasText = typeof input.query === "string" && input.query.length > 0;
|
|
887
|
+
const hasVector = Array.isArray(input.vector) && input.vector.length > 0;
|
|
888
|
+
const hasSemantic = typeof input.semantic_query === "string" && input.semantic_query.length > 0;
|
|
889
|
+
const hasSimilar = input.similar_to !== undefined && input.similar_to !== null;
|
|
890
|
+
if (!hasText && !hasVector && !hasSemantic && !hasSimilar) {
|
|
891
|
+
throw new AgentAdapterError(
|
|
892
|
+
"GF_AGENT_RETRIEVE_FIND_REQUIRED",
|
|
893
|
+
"find requires query, vector, semantic_query, and/or similar_to",
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
echo: {
|
|
898
|
+
find: {
|
|
899
|
+
force_stale: Boolean(input.force_stale),
|
|
900
|
+
label: input.label ?? null,
|
|
901
|
+
query: input.query ?? null,
|
|
902
|
+
semantic_query: input.semantic_query ?? null,
|
|
903
|
+
similar_to: input.similar_to ?? null,
|
|
904
|
+
space: input.space ?? null,
|
|
905
|
+
vector: hasVector ? [...input.vector] : null,
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
find: {
|
|
909
|
+
forceStale: Boolean(input.force_stale),
|
|
910
|
+
label: input.label,
|
|
911
|
+
query: input.query,
|
|
912
|
+
semanticQuery: input.semantic_query,
|
|
913
|
+
similarTo: input.similar_to,
|
|
914
|
+
space: input.space,
|
|
915
|
+
vector: hasVector ? input.vector : undefined,
|
|
916
|
+
},
|
|
917
|
+
resultLimit,
|
|
918
|
+
signal: input.signal,
|
|
919
|
+
surface,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (typeof input.algorithm !== "string" || input.algorithm.length === 0) {
|
|
923
|
+
throw new AgentAdapterError(
|
|
924
|
+
"GF_AGENT_RETRIEVE_ALGORITHM_REQUIRED",
|
|
925
|
+
"M18 surfaces require an explicit algorithm catalog value",
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
if (surface !== "analyze" && (typeof input.label !== "string" || input.label.length === 0)) {
|
|
929
|
+
if (surface !== "paths") {
|
|
930
|
+
throw new AgentAdapterError(
|
|
931
|
+
"GF_AGENT_RETRIEVE_LABEL_REQUIRED",
|
|
932
|
+
"rank, cluster, and similar require an explicit label",
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
echo: {
|
|
938
|
+
m18: {
|
|
939
|
+
algorithm: input.algorithm,
|
|
940
|
+
directed: input.directed === undefined ? null : Boolean(input.directed),
|
|
941
|
+
label: input.label ?? null,
|
|
942
|
+
source: input.source ?? null,
|
|
943
|
+
target: input.target ?? null,
|
|
944
|
+
vector_property: input.vector_property ?? null,
|
|
945
|
+
via: input.via ?? null,
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
m18: {
|
|
949
|
+
algorithm: input.algorithm,
|
|
950
|
+
directed: input.directed,
|
|
951
|
+
label: input.label,
|
|
952
|
+
source: input.source,
|
|
953
|
+
target: input.target,
|
|
954
|
+
vectorProperty: input.vector_property,
|
|
955
|
+
via: input.via,
|
|
956
|
+
},
|
|
957
|
+
resultLimit,
|
|
958
|
+
signal: input.signal,
|
|
959
|
+
surface,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function invokeRetrieve(graph, request) {
|
|
964
|
+
if (request.surface === "find") {
|
|
965
|
+
return graph.find(
|
|
966
|
+
request.find.query,
|
|
967
|
+
request.find.label,
|
|
968
|
+
request.find.vector,
|
|
969
|
+
request.find.similarTo,
|
|
970
|
+
request.find.semanticQuery,
|
|
971
|
+
request.resultLimit,
|
|
972
|
+
request.find.space,
|
|
973
|
+
request.find.forceStale,
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
const { m18, surface } = request;
|
|
977
|
+
if (surface === "rank") {
|
|
978
|
+
if (typeof graph.prepareRankInvocation === "function") {
|
|
979
|
+
return graph.invokeDescriptor(
|
|
980
|
+
graph.prepareRankInvocation(m18.label, m18.algorithm, m18.via, m18.directed),
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
return graph.rank(m18.label, m18.algorithm, m18.via, m18.directed);
|
|
984
|
+
}
|
|
985
|
+
if (surface === "cluster") {
|
|
986
|
+
if (typeof graph.prepareClusterInvocation === "function") {
|
|
987
|
+
return graph.invokeDescriptor(
|
|
988
|
+
graph.prepareClusterInvocation(
|
|
989
|
+
m18.label,
|
|
990
|
+
m18.algorithm,
|
|
991
|
+
m18.via,
|
|
992
|
+
m18.directed,
|
|
993
|
+
m18.vectorProperty,
|
|
994
|
+
),
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
return graph.cluster(m18.label, m18.algorithm, m18.via, m18.directed, m18.vectorProperty);
|
|
998
|
+
}
|
|
999
|
+
if (surface === "paths") {
|
|
1000
|
+
if (typeof graph.preparePathsInvocation === "function") {
|
|
1001
|
+
return graph.invokeDescriptor(
|
|
1002
|
+
graph.preparePathsInvocation(m18.source, m18.target, m18.algorithm, m18.via, m18.directed),
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
return graph.paths(m18.source, m18.target, m18.algorithm, m18.via, m18.directed);
|
|
1006
|
+
}
|
|
1007
|
+
if (surface === "analyze") {
|
|
1008
|
+
if (typeof graph.prepareAnalyzeInvocation === "function") {
|
|
1009
|
+
return graph.invokeDescriptor(
|
|
1010
|
+
graph.prepareAnalyzeInvocation(m18.algorithm, m18.label, m18.via, m18.directed),
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
return graph.analyze(m18.algorithm, m18.label, m18.via, m18.directed);
|
|
1014
|
+
}
|
|
1015
|
+
if (typeof graph.prepareSimilarInvocation === "function") {
|
|
1016
|
+
return graph.invokeDescriptor(
|
|
1017
|
+
graph.prepareSimilarInvocation(
|
|
1018
|
+
m18.label,
|
|
1019
|
+
m18.algorithm,
|
|
1020
|
+
request.resultLimit,
|
|
1021
|
+
m18.vectorProperty,
|
|
1022
|
+
m18.via,
|
|
1023
|
+
),
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
return graph.similar(m18.label, m18.algorithm, request.resultLimit, m18.vectorProperty, m18.via);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function configuredSurfaces(GraphForge, tableFromIPC) {
|
|
1030
|
+
if (typeof GraphForge !== "function" || typeof tableFromIPC !== "function") {
|
|
1031
|
+
throw new AgentAdapterError(
|
|
1032
|
+
"GF_AGENT_ADAPTER_CONFIGURATION",
|
|
1033
|
+
"GraphForge and tableFromIPC shipped surfaces are required",
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function narrationBudget(value) {
|
|
1039
|
+
if (value === undefined || value === null) return DEFAULT_NARRATION_RECORD_BUDGET;
|
|
1040
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1041
|
+
throw beliefInputError(
|
|
1042
|
+
"GF_AGENT_BELIEF_BUDGET_REQUIRED",
|
|
1043
|
+
"record_budget must be a positive safe integer",
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
return value;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function narrationPageLimit(value) {
|
|
1050
|
+
if (value === undefined || value === null) return DEFAULT_NARRATION_PAGE_LIMIT;
|
|
1051
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 10_000) {
|
|
1052
|
+
throw beliefInputError(
|
|
1053
|
+
"GF_AGENT_BELIEF_PAGE_LIMIT_REQUIRED",
|
|
1054
|
+
"page_limit must be a safe integer in 1..=10000",
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
return value;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function validateRecordedAnalysisInput(input) {
|
|
1061
|
+
if (!input || typeof input !== "object") {
|
|
1062
|
+
throw beliefInputError(
|
|
1063
|
+
"GF_AGENT_ANALYSIS_CONFIGURATION",
|
|
1064
|
+
"recorded analysis requires a bounded input object",
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
if (!input.projection || typeof input.projection !== "object") {
|
|
1068
|
+
throw beliefInputError(
|
|
1069
|
+
"GF_AGENT_ANALYSIS_PROJECTION_REQUIRED",
|
|
1070
|
+
"recorded analysis requires the opaque resolved projection",
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const descriptor = input.descriptor;
|
|
1074
|
+
if (
|
|
1075
|
+
!descriptor ||
|
|
1076
|
+
typeof descriptor !== "object" ||
|
|
1077
|
+
typeof descriptor.algorithm !== "string" ||
|
|
1078
|
+
typeof descriptor.fingerprint !== "string" ||
|
|
1079
|
+
typeof descriptor.verb !== "string"
|
|
1080
|
+
) {
|
|
1081
|
+
throw beliefInputError(
|
|
1082
|
+
"GF_AGENT_ANALYSIS_DESCRIPTOR_REQUIRED",
|
|
1083
|
+
"recorded analysis requires the caller-prepared InvocationDescriptor",
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
actorUuid:
|
|
1088
|
+
input.actor_uuid === undefined || input.actor_uuid === null
|
|
1089
|
+
? undefined
|
|
1090
|
+
: uuidToString(input.actor_uuid),
|
|
1091
|
+
attachmentUuid: uuidToString(input.attachment_uuid),
|
|
1092
|
+
descriptor,
|
|
1093
|
+
operationUuid: uuidToString(input.operation_uuid),
|
|
1094
|
+
projection: input.projection,
|
|
1095
|
+
runUuid: uuidToString(input.run_uuid),
|
|
1096
|
+
signal: input.signal,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function pageDecode(tableFromIPC, ipc) {
|
|
1101
|
+
const table = Number.isInteger(ipc?.numRows) ? ipc : tableFromIPC(ipc);
|
|
1102
|
+
return {
|
|
1103
|
+
next: nextPageToken(table),
|
|
1104
|
+
rows: tableToJson(table),
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function nextPageToken(table) {
|
|
1109
|
+
const metadata = table?.schema?.metadata;
|
|
1110
|
+
if (!metadata) return null;
|
|
1111
|
+
if (typeof metadata.get === "function") {
|
|
1112
|
+
return metadata.get(NEXT_PAGE_TOKEN_KEY) ?? null;
|
|
1113
|
+
}
|
|
1114
|
+
return metadata[NEXT_PAGE_TOKEN_KEY] ?? null;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function collectPaged(target, counter, fetchPage, identityKey) {
|
|
1118
|
+
let after;
|
|
1119
|
+
for (;;) {
|
|
1120
|
+
const page = await fetchPage(after);
|
|
1121
|
+
appendUnique(target, page.rows, identityKey, counter);
|
|
1122
|
+
if (!page.next) return;
|
|
1123
|
+
after = page.next;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function appendUnique(target, rows, identityKey, counter, alreadyCounted = false) {
|
|
1128
|
+
const seen = new Set(target.map((row) => rowIdentity(row, identityKey)));
|
|
1129
|
+
for (const row of rows) {
|
|
1130
|
+
const identity = rowIdentity(row, identityKey);
|
|
1131
|
+
if (seen.has(identity)) continue;
|
|
1132
|
+
if (!alreadyCounted) {
|
|
1133
|
+
if (counter.remaining <= 0) {
|
|
1134
|
+
throw beliefInputError(
|
|
1135
|
+
"GF_AGENT_BELIEF_RECORD_BUDGET_EXCEEDED",
|
|
1136
|
+
"scoped belief narration exceeded the caller record budget",
|
|
1137
|
+
{ record_budget: counter.budget },
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
counter.remaining -= 1;
|
|
1141
|
+
}
|
|
1142
|
+
seen.add(identity);
|
|
1143
|
+
target.push(row);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function rowIdentity(row, identityKey) {
|
|
1148
|
+
if (identityKey && Object.hasOwn(row, identityKey)) {
|
|
1149
|
+
return `${identityKey}:${String(row[identityKey])}`;
|
|
1150
|
+
}
|
|
1151
|
+
return JSON.stringify(row);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function compareCanonicalRows(left, right) {
|
|
1155
|
+
const leftJson = JSON.stringify(left);
|
|
1156
|
+
const rightJson = JSON.stringify(right);
|
|
1157
|
+
return leftJson < rightJson ? -1 : leftJson > rightJson ? 1 : 0;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function projectDescriptors(pageLimit) {
|
|
1161
|
+
return [
|
|
1162
|
+
{ api: "listAssertions", collection: "assertions", page_limit: pageLimit },
|
|
1163
|
+
{
|
|
1164
|
+
api: "listConfidenceAssessments",
|
|
1165
|
+
collection: "confidence_assessments",
|
|
1166
|
+
page_limit: pageLimit,
|
|
1167
|
+
},
|
|
1168
|
+
{ api: "listEvidenceLinks", collection: "evidence_links", page_limit: pageLimit },
|
|
1169
|
+
{ api: "listReasoning", collection: "reasoning", page_limit: pageLimit },
|
|
1170
|
+
{
|
|
1171
|
+
api: "listAssertionStatus",
|
|
1172
|
+
collection: "assertion_status",
|
|
1173
|
+
page_limit: pageLimit,
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
api: "listAssertionValidity",
|
|
1177
|
+
collection: "assertion_validity",
|
|
1178
|
+
page_limit: pageLimit,
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
api: "listAssertionSupersessions",
|
|
1182
|
+
collection: "assertion_supersessions",
|
|
1183
|
+
page_limit: pageLimit,
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
api: "listHypothesisGroups",
|
|
1187
|
+
collection: "hypothesis_groups",
|
|
1188
|
+
page_limit: pageLimit,
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
api: "listHypothesisMembership",
|
|
1192
|
+
collection: "hypothesis_membership",
|
|
1193
|
+
page_limit: pageLimit,
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
api: "listHypothesisSelection",
|
|
1197
|
+
collection: "hypothesis_selection",
|
|
1198
|
+
page_limit: pageLimit,
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
api: "listProvenanceHistory",
|
|
1202
|
+
collection: "provenance",
|
|
1203
|
+
page_limit: pageLimit,
|
|
1204
|
+
},
|
|
1205
|
+
];
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function validateBeliefSubjectInput(input) {
|
|
1209
|
+
if (!input || typeof input !== "object") {
|
|
1210
|
+
throw beliefInputError(
|
|
1211
|
+
"GF_AGENT_BELIEF_CONFIGURATION",
|
|
1212
|
+
"belief resolution requires a bounded input object",
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
const assertionUuid = input.subject?.assertion_uuid;
|
|
1216
|
+
const questionKey = input.subject?.hypothesis_question_key;
|
|
1217
|
+
if ((assertionUuid === undefined) === (questionKey === undefined)) {
|
|
1218
|
+
throw beliefInputError(
|
|
1219
|
+
"GF_AGENT_BELIEF_SUBJECT_REQUIRED",
|
|
1220
|
+
"provide exactly one assertion UUID or hypothesis question key",
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
let subject;
|
|
1224
|
+
if (assertionUuid !== undefined) {
|
|
1225
|
+
subject = { assertionUuid: uuidToString(assertionUuid) };
|
|
1226
|
+
} else if (
|
|
1227
|
+
typeof questionKey === "string" &&
|
|
1228
|
+
questionKey.length > 0 &&
|
|
1229
|
+
questionKey.length <= 4096
|
|
1230
|
+
) {
|
|
1231
|
+
subject = { hypothesisQuestionKey: questionKey };
|
|
1232
|
+
} else {
|
|
1233
|
+
throw beliefInputError(
|
|
1234
|
+
"GF_AGENT_BELIEF_SUBJECT_REQUIRED",
|
|
1235
|
+
"provide exactly one assertion UUID or hypothesis question key",
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const cutoff = input.transaction_cutoff_micros;
|
|
1239
|
+
const validTime = input.valid_time_micros;
|
|
1240
|
+
if (
|
|
1241
|
+
!Number.isSafeInteger(cutoff) ||
|
|
1242
|
+
(![undefined, null].includes(validTime) && !Number.isSafeInteger(validTime))
|
|
1243
|
+
) {
|
|
1244
|
+
throw beliefInputError(
|
|
1245
|
+
"GF_AGENT_BELIEF_TIME_REQUIRED",
|
|
1246
|
+
"transaction cutoff and optional valid time must be safe integer microseconds",
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
const policy = input.policy;
|
|
1250
|
+
if (
|
|
1251
|
+
!policy ||
|
|
1252
|
+
policy.version !== 1 ||
|
|
1253
|
+
!Array.isArray(policy.included_statuses) ||
|
|
1254
|
+
typeof policy.statusless !== "string" ||
|
|
1255
|
+
typeof policy.supersession_branches !== "string" ||
|
|
1256
|
+
typeof policy.hypotheses !== "string"
|
|
1257
|
+
) {
|
|
1258
|
+
throw beliefInputError(
|
|
1259
|
+
"GF_AGENT_BELIEF_POLICY_REQUIRED",
|
|
1260
|
+
"a complete graphforge-belief-projection/1 policy is required",
|
|
1261
|
+
{ required_policy_version: 1 },
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
const includedStatuses = policy.included_statuses.map((status) => {
|
|
1265
|
+
if (typeof status !== "string" || status.length === 0 || status.length > 4096) {
|
|
1266
|
+
throw beliefInputError(
|
|
1267
|
+
"GF_AGENT_BELIEF_POLICY_REQUIRED",
|
|
1268
|
+
"a complete graphforge-belief-projection/1 policy is required",
|
|
1269
|
+
{ required_policy_version: 1 },
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
return status;
|
|
1273
|
+
});
|
|
1274
|
+
return {
|
|
1275
|
+
nativePolicy: {
|
|
1276
|
+
hypotheses: policy.hypotheses,
|
|
1277
|
+
includedStatuses,
|
|
1278
|
+
statusless: policy.statusless,
|
|
1279
|
+
supersessionBranches: policy.supersession_branches,
|
|
1280
|
+
},
|
|
1281
|
+
outputPolicy: {
|
|
1282
|
+
hypotheses: policy.hypotheses,
|
|
1283
|
+
included_statuses: [...includedStatuses].sort(),
|
|
1284
|
+
statusless: policy.statusless,
|
|
1285
|
+
supersession_branches: policy.supersession_branches,
|
|
1286
|
+
version: 1,
|
|
1287
|
+
},
|
|
1288
|
+
subject,
|
|
1289
|
+
transactionCutoffMicros: cutoff,
|
|
1290
|
+
validTimeMicros: validTime ?? undefined,
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function beliefInputError(code, message, details) {
|
|
1295
|
+
return new AgentAdapterError(code, message, details);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function assertionRecord(row) {
|
|
1299
|
+
return {
|
|
1300
|
+
assertion_uuid: row.assertion_uuid,
|
|
1301
|
+
reasoning_history_uuids: row.reasoning_history_uuids,
|
|
1302
|
+
reasoning_leaf_uuids: row.reasoning_leaf_uuids,
|
|
1303
|
+
source_record_uuids: row.source_record_uuids,
|
|
1304
|
+
status: row.status,
|
|
1305
|
+
status_event_uuid: row.status_event_uuid,
|
|
1306
|
+
superseded_by_assertion_uuids: row.superseded_by_assertion_uuids,
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function hypothesisRecord(row) {
|
|
1311
|
+
return {
|
|
1312
|
+
current_member_assertion_uuids: row.current_member_assertion_uuids,
|
|
1313
|
+
group_uuid: row.group_uuid,
|
|
1314
|
+
question_key: row.question_key,
|
|
1315
|
+
selected_assertion_uuid: row.selected_assertion_uuid,
|
|
1316
|
+
source_record_uuids: row.source_record_uuids,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function decode(tableFromIPC, ipc) {
|
|
1321
|
+
return tableToJson(tableFromIPC(ipc));
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function capabilityMap(rows) {
|
|
1325
|
+
return Object.fromEntries(
|
|
1326
|
+
rows.map((row) => [
|
|
1327
|
+
row.capability_id,
|
|
1328
|
+
{ status: row.status ?? "supported", version: Number(row.capability_version) },
|
|
1329
|
+
]),
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function validateBuildInput(input) {
|
|
1334
|
+
if (
|
|
1335
|
+
!input ||
|
|
1336
|
+
!Array.isArray(input.nodes) ||
|
|
1337
|
+
!Array.isArray(input.edges) ||
|
|
1338
|
+
!Array.isArray(input.evidence) ||
|
|
1339
|
+
!input.assertion ||
|
|
1340
|
+
!Array.isArray(input.assertion.graph_refs) ||
|
|
1341
|
+
!input.confidence ||
|
|
1342
|
+
input.evidence.length === 0 ||
|
|
1343
|
+
!input.capability_operation_uuids
|
|
1344
|
+
) {
|
|
1345
|
+
throw new AgentAdapterError(
|
|
1346
|
+
"GF_AGENT_BUILD_CONFIGURATION",
|
|
1347
|
+
"build-knowledge requires nodes, edges, nonempty evidence, assertion, confidence, and capability operation IDs",
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
const keys = input.nodes.map(({ key }) => key);
|
|
1351
|
+
if (new Set(keys).size !== keys.length) {
|
|
1352
|
+
throw new AgentAdapterError("GF_AGENT_BUILD_CONFLICT", "node keys must be unique");
|
|
1353
|
+
}
|
|
1354
|
+
const edgeKeys = input.edges.map(({ key }) => key);
|
|
1355
|
+
if (new Set(edgeKeys).size !== edgeKeys.length) {
|
|
1356
|
+
throw new AgentAdapterError("GF_AGENT_BUILD_CONFLICT", "edge keys must be unique");
|
|
1357
|
+
}
|
|
1358
|
+
for (const capability of requiredCapabilitiesFor(input)) {
|
|
1359
|
+
if (typeof input.capability_operation_uuids[capability] !== "string") {
|
|
1360
|
+
throw new AgentAdapterError(
|
|
1361
|
+
"GF_AGENT_BUILD_CONFIGURATION",
|
|
1362
|
+
"every requested capability requires an operation UUID",
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function requiredCapabilitiesFor(input) {
|
|
1369
|
+
return ["provenance", "knowledge", ...(input.status || input.reasoning ? ["epistemic"] : [])];
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function graphUuid(reference, nodes, edges) {
|
|
1373
|
+
const rows = reference.graph_kind === "node" ? nodes : edges;
|
|
1374
|
+
const match = rows.find(({ key }) => key === reference.key);
|
|
1375
|
+
if (!match) {
|
|
1376
|
+
throw new AgentAdapterError(
|
|
1377
|
+
"GF_AGENT_BUILD_REFERENCE_MISSING",
|
|
1378
|
+
"assertion references must identify records in the same request",
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
return match.uuid;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function graphSourceUuid(evidence, nodes, edges) {
|
|
1385
|
+
if (evidence.source_kind === "graph_node") {
|
|
1386
|
+
return graphUuid({ graph_kind: "node", key: evidence.source_key }, nodes, edges);
|
|
1387
|
+
}
|
|
1388
|
+
if (evidence.source_kind === "graph_edge") {
|
|
1389
|
+
return graphUuid({ graph_kind: "edge", key: evidence.source_key }, nodes, edges);
|
|
1390
|
+
}
|
|
1391
|
+
return evidence.source_uuid;
|
|
1392
|
+
}
|