@lucern/graph-sync 0.3.0-alpha.10

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/dist/index.js ADDED
@@ -0,0 +1,3249 @@
1
+ import neo4j from 'neo4j-driver';
2
+ import { v } from 'convex/values';
3
+ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
+ import { NODE_TYPE_TO_LABEL, EDGE_TYPE_TO_REL, getNeo4jRelType } from '@lucern/graph-primitives/graphTypes';
5
+ import { componentsGeneric, anyApi, internalActionGeneric, actionGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/neo4jDriver.ts
14
+ var neo4jDriver_exports = {};
15
+ __export(neo4jDriver_exports, {
16
+ COMPLEX_QUERY_TIMEOUT_MS: () => COMPLEX_QUERY_TIMEOUT_MS,
17
+ DEFAULT_QUERY_TIMEOUT_MS: () => DEFAULT_QUERY_TIMEOUT_MS,
18
+ batchUpsertEdges: () => batchUpsertEdges,
19
+ batchUpsertNodes: () => batchUpsertNodes,
20
+ closeDriver: () => closeDriver,
21
+ deleteEdge: () => deleteEdge,
22
+ deleteNode: () => deleteNode,
23
+ getConnectionInfo: () => getConnectionInfo,
24
+ healthCheck: () => healthCheck,
25
+ runBatchTransaction: () => runBatchTransaction,
26
+ runCypher: () => runCypher,
27
+ runWriteTransaction: () => runWriteTransaction,
28
+ upsertEdge: () => upsertEdge,
29
+ upsertNode: () => upsertNode,
30
+ validateLabel: () => validateLabel,
31
+ validateRelType: () => validateRelType
32
+ });
33
+ var VALID_NODE_LABELS = /* @__PURE__ */ new Set([
34
+ // Ontological
35
+ "ValueChain",
36
+ "Function",
37
+ "FinSector",
38
+ "Company",
39
+ "Person",
40
+ "Investor",
41
+ // Epistemic
42
+ "Theme",
43
+ "Belief",
44
+ "Question",
45
+ "Evidence",
46
+ "Source",
47
+ "Decision",
48
+ "Sprint",
49
+ "Claim",
50
+ "Synthesis",
51
+ "Answer"
52
+ ]);
53
+ var VALID_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
54
+ // Cross-layer edges
55
+ "EXTRACTED_FROM",
56
+ "ANSWERS",
57
+ "RESPONDS_TO",
58
+ "INFORMS",
59
+ "QUALIFIES",
60
+ "TESTS",
61
+ "EXPLORES",
62
+ "BASED_ON",
63
+ "RELATES_TO_THESIS",
64
+ "BELONGS_TO",
65
+ "PLAYS_THEME",
66
+ // Same-layer edges
67
+ "SUPERSEDES",
68
+ "SAME_AS",
69
+ "DEPENDS_ON",
70
+ "REINFORCES",
71
+ "PARENT_OF",
72
+ "CHILD_OF",
73
+ "FALSIFIED_BY",
74
+ "EXCLUSIVE_WITH",
75
+ "COLLAPSES_IF",
76
+ "CASCADE_FROM",
77
+ "STRENGTHENED_BY",
78
+ "WEAKENED_BY",
79
+ "ALTERNATIVE_TO",
80
+ "SUBSUMES",
81
+ "VALIDATED_BY",
82
+ "REQUIRED_FOR",
83
+ "PREREQUISITE_FOR",
84
+ "PARALLEL_TO",
85
+ "CORROBORATES",
86
+ "EXTENDS",
87
+ "SAME_SOURCE_AS",
88
+ "SAME_THEME_AS",
89
+ // Ontological
90
+ "EVALUATES",
91
+ "PERSPECTIVE_ON",
92
+ "WORKS_AT",
93
+ "PARTICIPATES_IN",
94
+ "PERFORMS",
95
+ "FUNCTION_IN",
96
+ "IMPACTS",
97
+ "INVESTED_IN",
98
+ "RAISED_FROM",
99
+ "BASED_ON_BELIEF",
100
+ "BASED_ON_QUESTION",
101
+ "BLOCKED_BY_CONTRADICTION",
102
+ "INFORMED_BY_THEME"
103
+ ]);
104
+ function validateLabel(label) {
105
+ if (!VALID_NODE_LABELS.has(label)) {
106
+ throw new Error(
107
+ `[Neo4j Security] Invalid node label: ${label}. Must be one of: ${Array.from(VALID_NODE_LABELS).join(", ")}`
108
+ );
109
+ }
110
+ }
111
+ function validateRelType(relType) {
112
+ if (!VALID_RELATIONSHIP_TYPES.has(relType)) {
113
+ throw new Error(
114
+ `[Neo4j Security] Invalid relationship type: ${relType}. Must be one of: ${Array.from(VALID_RELATIONSHIP_TYPES).join(", ")}`
115
+ );
116
+ }
117
+ }
118
+ var driver = null;
119
+ function getDriver() {
120
+ if (!driver) {
121
+ const uri = process.env.NEO4J_URI;
122
+ const user = process.env.NEO4J_USER;
123
+ const password = process.env.NEO4J_PASSWORD;
124
+ if (!uri || !user || !password) {
125
+ throw new Error(
126
+ "[Neo4j Driver] Missing credentials. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD via `npx convex env set`"
127
+ );
128
+ }
129
+ driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {
130
+ // Connection pool settings
131
+ maxConnectionPoolSize: 50,
132
+ connectionAcquisitionTimeout: 3e4,
133
+ // Logging
134
+ logging: {
135
+ level: "warn",
136
+ logger: (level, message) => console.log(`[Neo4j ${level}] ${message}`)
137
+ }
138
+ });
139
+ }
140
+ return driver;
141
+ }
142
+ var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
143
+ var COMPLEX_QUERY_TIMEOUT_MS = 6e4;
144
+ function toNeo4jParams(params) {
145
+ const result = {};
146
+ for (const [key, value] of Object.entries(params)) {
147
+ if (typeof value === "number" && Number.isInteger(value)) {
148
+ result[key] = neo4j.int(value);
149
+ } else if (Array.isArray(value)) {
150
+ result[key] = value.map(
151
+ (v5) => typeof v5 === "number" && Number.isInteger(v5) ? neo4j.int(v5) : v5
152
+ );
153
+ } else {
154
+ result[key] = value;
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ async function runCypher(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
160
+ const neo4jDriver = getDriver();
161
+ const session = neo4jDriver.session();
162
+ try {
163
+ const neo4jParams = toNeo4jParams(params);
164
+ const result = await session.run(query, neo4jParams, {
165
+ timeout: neo4j.int(timeoutMs)
166
+ });
167
+ return result.records.map((record) => {
168
+ const obj = {};
169
+ for (const key of record.keys) {
170
+ const field = String(key);
171
+ obj[field] = convertNeo4jValue(record.get(field));
172
+ }
173
+ return obj;
174
+ });
175
+ } finally {
176
+ await session.close();
177
+ }
178
+ }
179
+ async function runWriteTransaction(query, params = {}, timeoutMs = DEFAULT_QUERY_TIMEOUT_MS) {
180
+ const neo4jDriver = getDriver();
181
+ const session = neo4jDriver.session();
182
+ try {
183
+ const neo4jParams = toNeo4jParams(params);
184
+ const result = await session.executeWrite(
185
+ async (tx) => {
186
+ return await tx.run(query, neo4jParams);
187
+ },
188
+ { timeout: timeoutMs }
189
+ );
190
+ return result.records.map((record) => {
191
+ const obj = {};
192
+ for (const key of record.keys) {
193
+ const field = String(key);
194
+ obj[field] = convertNeo4jValue(record.get(field));
195
+ }
196
+ return obj;
197
+ });
198
+ } finally {
199
+ await session.close();
200
+ }
201
+ }
202
+ async function runBatchTransaction(queries, timeoutMs = COMPLEX_QUERY_TIMEOUT_MS) {
203
+ const neo4jDriver = getDriver();
204
+ const session = neo4jDriver.session();
205
+ try {
206
+ await session.executeWrite(
207
+ async (tx) => {
208
+ for (const { query, params } of queries) {
209
+ await tx.run(query, params);
210
+ }
211
+ },
212
+ { timeout: timeoutMs }
213
+ );
214
+ } finally {
215
+ await session.close();
216
+ }
217
+ }
218
+ async function upsertNode(label, globalId, properties) {
219
+ validateLabel(label);
220
+ await runWriteTransaction(
221
+ `
222
+ MERGE (n:${label} {globalId: $globalId})
223
+ SET n += $properties
224
+ SET n.updatedAt = timestamp()
225
+ `,
226
+ { globalId, properties }
227
+ );
228
+ }
229
+ async function deleteNode(globalId) {
230
+ await runWriteTransaction(
231
+ `
232
+ MATCH (n {globalId: $globalId})
233
+ DETACH DELETE n
234
+ `,
235
+ { globalId }
236
+ );
237
+ }
238
+ async function batchUpsertNodes(label, nodes) {
239
+ if (nodes.length === 0) {
240
+ return;
241
+ }
242
+ validateLabel(label);
243
+ await runWriteTransaction(
244
+ `
245
+ UNWIND $nodes as node
246
+ MERGE (n:${label} {globalId: node.globalId})
247
+ SET n += node.properties
248
+ SET n.updatedAt = timestamp()
249
+ `,
250
+ { nodes }
251
+ );
252
+ }
253
+ async function upsertEdge(relType, globalId, fromGlobalId, toGlobalId, properties = {}) {
254
+ validateRelType(relType);
255
+ await runWriteTransaction(
256
+ `
257
+ MATCH (from {globalId: $fromGlobalId})
258
+ MATCH (to {globalId: $toGlobalId})
259
+ MERGE (from)-[r:${relType} {globalId: $globalId}]->(to)
260
+ SET r += $properties
261
+ SET r.updatedAt = timestamp()
262
+ `,
263
+ { globalId, fromGlobalId, toGlobalId, properties }
264
+ );
265
+ }
266
+ async function deleteEdge(globalId) {
267
+ await runWriteTransaction(
268
+ `
269
+ MATCH ()-[r {globalId: $globalId}]->()
270
+ DELETE r
271
+ `,
272
+ { globalId }
273
+ );
274
+ }
275
+ async function batchUpsertEdges(edges) {
276
+ if (edges.length === 0) {
277
+ return;
278
+ }
279
+ const byType = /* @__PURE__ */ new Map();
280
+ for (const edge of edges) {
281
+ const existing = byType.get(edge.relType) || [];
282
+ existing.push(edge);
283
+ byType.set(edge.relType, existing);
284
+ }
285
+ const queries = [];
286
+ for (const [relType, typeEdges] of byType) {
287
+ queries.push({
288
+ query: `
289
+ UNWIND $edges as edge
290
+ MATCH (from {globalId: edge.fromGlobalId})
291
+ MATCH (to {globalId: edge.toGlobalId})
292
+ MERGE (from)-[r:${relType} {globalId: edge.globalId}]->(to)
293
+ SET r += edge.properties
294
+ SET r.updatedAt = timestamp()
295
+ `,
296
+ params: {
297
+ edges: typeEdges.map((e) => ({
298
+ globalId: e.globalId,
299
+ fromGlobalId: e.fromGlobalId,
300
+ toGlobalId: e.toGlobalId,
301
+ properties: e.properties || {}
302
+ }))
303
+ }
304
+ });
305
+ }
306
+ await runBatchTransaction(queries);
307
+ }
308
+ async function healthCheck() {
309
+ try {
310
+ const result = await runCypher(
311
+ "MATCH (n) RETURN count(n) as count LIMIT 1"
312
+ );
313
+ return {
314
+ healthy: true,
315
+ nodeCount: result[0]?.count || 0
316
+ };
317
+ } catch (error) {
318
+ return {
319
+ healthy: false,
320
+ error: error instanceof Error ? error.message : "Unknown error"
321
+ };
322
+ }
323
+ }
324
+ function getConnectionInfo() {
325
+ return {
326
+ uri: process.env.NEO4J_URI,
327
+ user: process.env.NEO4J_USER,
328
+ configured: Boolean(
329
+ process.env.NEO4J_URI && process.env.NEO4J_USER && process.env.NEO4J_PASSWORD
330
+ )
331
+ };
332
+ }
333
+ function convertNeo4jValue(value) {
334
+ if (value === null || value === void 0) {
335
+ return null;
336
+ }
337
+ if (neo4j.isInt(value)) {
338
+ return neo4j.integer.toNumber(value);
339
+ }
340
+ if (neo4j.isDate(value) || neo4j.isDateTime(value) || neo4j.isTime(value)) {
341
+ return value.toString();
342
+ }
343
+ if (Array.isArray(value)) {
344
+ return value.map(convertNeo4jValue);
345
+ }
346
+ if (value && typeof value === "object" && "properties" in value) {
347
+ const nodeObj = value;
348
+ const result = {};
349
+ for (const [k, v5] of Object.entries(nodeObj.properties)) {
350
+ result[k] = convertNeo4jValue(v5);
351
+ }
352
+ return result;
353
+ }
354
+ if (typeof value === "object") {
355
+ const result = {};
356
+ for (const [k, v5] of Object.entries(value)) {
357
+ result[k] = convertNeo4jValue(v5);
358
+ }
359
+ return result;
360
+ }
361
+ return value;
362
+ }
363
+ async function closeDriver() {
364
+ if (driver) {
365
+ await driver.close();
366
+ driver = null;
367
+ }
368
+ }
369
+
370
+ // src/neo4jEdgeAPI.ts
371
+ var neo4jEdgeAPI_exports = {};
372
+ __export(neo4jEdgeAPI_exports, {
373
+ DUAL_WRITE_EDGE_TYPES: () => DUAL_WRITE_EDGE_TYPES,
374
+ createEdge: () => createEdge,
375
+ deleteEdge: () => deleteEdge2,
376
+ getEdge: () => getEdge,
377
+ needsDualWrite: () => needsDualWrite,
378
+ retryProjectionByGlobalId: () => retryProjectionByGlobalId,
379
+ updateEdge: () => updateEdge
380
+ });
381
+ componentsGeneric();
382
+ var internal = anyApi;
383
+ var action = actionGeneric;
384
+ var internalAction = internalActionGeneric;
385
+ var internalMutation = internalMutationGeneric;
386
+ var internalQuery = internalQueryGeneric;
387
+
388
+ // src/neo4jEdgeAPI.ts
389
+ var DUAL_WRITE_EDGE_TYPES = [
390
+ "supports",
391
+ "informs",
392
+ "tests",
393
+ "depends_on",
394
+ "derived_from",
395
+ "contains",
396
+ "supersedes",
397
+ "extracted_from",
398
+ "responds_to",
399
+ "based_on",
400
+ "answers",
401
+ "belongs_to",
402
+ "relates_to_thesis",
403
+ "corroborates",
404
+ "extends",
405
+ "same_source_as",
406
+ "same_theme_as",
407
+ "plays_theme",
408
+ "impacts",
409
+ "evaluates",
410
+ "mentioned_in",
411
+ "perspective_on"
412
+ ];
413
+ function needsDualWrite(edgeType) {
414
+ return DUAL_WRITE_EDGE_TYPES.includes(edgeType);
415
+ }
416
+ function normalizeEdgeType(edgeType) {
417
+ const normalized = edgeType.trim().toLowerCase();
418
+ if (!/^[a-z0-9_]+$/u.test(normalized)) {
419
+ throw new Error(`[Neo4j Edge API] Invalid edge type: ${edgeType}`);
420
+ }
421
+ return normalized;
422
+ }
423
+ function resolveRelationshipType(edgeType) {
424
+ const relType = getNeo4jRelType(edgeType);
425
+ if (!/^[A-Z0-9_]+$/u.test(relType)) {
426
+ throw new Error(`[Neo4j Edge API] Invalid relationship type: ${relType}`);
427
+ }
428
+ return relType;
429
+ }
430
+ function readStringProperty(source, key) {
431
+ const value = source?.[key];
432
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
433
+ }
434
+ function readNumberProperty(source, key) {
435
+ const value = source?.[key];
436
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
437
+ }
438
+ function metadataSummary(metadata) {
439
+ if (!metadata) {
440
+ return void 0;
441
+ }
442
+ return Object.entries(metadata).map(([key, value]) => `${key}=${String(value)}`).slice(0, 8).join(" | ");
443
+ }
444
+ async function mirrorEdgeToConvex(ctx, args) {
445
+ await ctx.runMutation(internal.epistemicEdges.mirrorEdgeToConvex, args);
446
+ }
447
+ async function queueEdgeRetry(ctx, args) {
448
+ await ctx.runMutation(internal.neo4jSyncHelpers.queueForRetry, {
449
+ entityType: "edge",
450
+ entityId: args.globalId,
451
+ operation: args.operation,
452
+ error: args.error
453
+ });
454
+ }
455
+ var createEdge = internalAction({
456
+ args: {
457
+ globalId: v.string(),
458
+ fromGlobalId: v.string(),
459
+ toGlobalId: v.string(),
460
+ edgeType: v.string(),
461
+ weight: v.optional(v.number()),
462
+ confidence: v.optional(v.number()),
463
+ context: v.optional(v.string()),
464
+ derivationType: v.optional(v.string()),
465
+ metadata: v.optional(v.any()),
466
+ createdBy: v.string(),
467
+ topicId: v.optional(v.string()),
468
+ tenantId: v.optional(v.string()),
469
+ workspaceId: v.optional(v.string()),
470
+ fromLayer: v.optional(v.string()),
471
+ toLayer: v.optional(v.string()),
472
+ fromNodeType: v.optional(v.string()),
473
+ toNodeType: v.optional(v.string()),
474
+ reasoningMethod: v.optional(v.string()),
475
+ logicalRole: v.optional(v.string()),
476
+ temporalClass: v.optional(v.string()),
477
+ validFrom: v.optional(v.number()),
478
+ validUntil: v.optional(v.number())
479
+ },
480
+ returns: permissiveReturn,
481
+ handler: async (ctx, args) => {
482
+ const connInfo = getConnectionInfo();
483
+ if (!connInfo.configured) {
484
+ throw new Error(
485
+ "[Neo4j Edge API] Neo4j not configured. Set NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD"
486
+ );
487
+ }
488
+ const edgeType = normalizeEdgeType(args.edgeType);
489
+ const relType = resolveRelationshipType(edgeType);
490
+ const metadata = args.metadata && typeof args.metadata === "object" ? args.metadata : void 0;
491
+ const now = Date.now();
492
+ const properties = {
493
+ globalId: args.globalId,
494
+ edgeType,
495
+ weight: args.weight ?? 1,
496
+ confidence: args.confidence ?? readNumberProperty(metadata, "confidence") ?? 1,
497
+ context: args.context ?? metadataSummary(metadata) ?? "",
498
+ derivationType: args.derivationType ?? "",
499
+ createdBy: args.createdBy,
500
+ createdAt: now,
501
+ updatedAt: now,
502
+ topicId: args.topicId ?? readStringProperty(metadata, "topicId") ?? "",
503
+ tenantId: args.tenantId ?? readStringProperty(metadata, "tenantId") ?? "",
504
+ workspaceId: args.workspaceId ?? readStringProperty(metadata, "workspaceId") ?? "",
505
+ fromLayer: args.fromLayer ?? "",
506
+ toLayer: args.toLayer ?? "",
507
+ fromNodeType: args.fromNodeType ?? "",
508
+ toNodeType: args.toNodeType ?? "",
509
+ reasoningMethod: args.reasoningMethod ?? "",
510
+ logicalRole: args.logicalRole ?? "",
511
+ temporalClass: args.temporalClass ?? "structural",
512
+ validFrom: args.validFrom ?? now,
513
+ validUntil: args.validUntil ?? null
514
+ };
515
+ const result = await runWriteTransaction(
516
+ `
517
+ MATCH (from {globalId: $fromGlobalId})
518
+ MATCH (to {globalId: $toGlobalId})
519
+ MERGE (from)-[r:${relType} {globalId: $globalId}]->(to)
520
+ SET r += $properties
521
+ RETURN r.globalId as globalId
522
+ `,
523
+ {
524
+ fromGlobalId: args.fromGlobalId,
525
+ toGlobalId: args.toGlobalId,
526
+ globalId: args.globalId,
527
+ properties
528
+ }
529
+ );
530
+ if (result.length === 0) {
531
+ await queueEdgeRetry(ctx, {
532
+ globalId: args.globalId,
533
+ operation: "upsert",
534
+ error: `Source or target node not yet synced to Neo4j (from: ${args.fromGlobalId}, to: ${args.toGlobalId})`
535
+ });
536
+ if (needsDualWrite(edgeType)) {
537
+ try {
538
+ await mirrorEdgeToConvex(ctx, {
539
+ globalId: args.globalId,
540
+ fromGlobalId: args.fromGlobalId,
541
+ toGlobalId: args.toGlobalId,
542
+ edgeType,
543
+ weight: args.weight,
544
+ confidence: args.confidence,
545
+ context: args.context,
546
+ derivationType: args.derivationType,
547
+ createdBy: args.createdBy,
548
+ topicId: args.topicId,
549
+ fromLayer: args.fromLayer,
550
+ toLayer: args.toLayer,
551
+ fromNodeType: args.fromNodeType,
552
+ toNodeType: args.toNodeType,
553
+ reasoningMethod: args.reasoningMethod,
554
+ logicalRole: args.logicalRole,
555
+ temporalClass: args.temporalClass,
556
+ validFrom: args.validFrom,
557
+ validUntil: args.validUntil
558
+ });
559
+ } catch {
560
+ }
561
+ }
562
+ return {
563
+ success: false,
564
+ globalId: args.globalId,
565
+ edgeType,
566
+ queuedForRetry: true,
567
+ reason: "nodes_not_synced"
568
+ };
569
+ }
570
+ if (needsDualWrite(edgeType)) {
571
+ try {
572
+ await mirrorEdgeToConvex(ctx, {
573
+ globalId: args.globalId,
574
+ fromGlobalId: args.fromGlobalId,
575
+ toGlobalId: args.toGlobalId,
576
+ edgeType,
577
+ weight: args.weight,
578
+ confidence: args.confidence,
579
+ context: args.context,
580
+ derivationType: args.derivationType,
581
+ createdBy: args.createdBy,
582
+ topicId: args.topicId,
583
+ fromLayer: args.fromLayer,
584
+ toLayer: args.toLayer,
585
+ fromNodeType: args.fromNodeType,
586
+ toNodeType: args.toNodeType,
587
+ reasoningMethod: args.reasoningMethod,
588
+ logicalRole: args.logicalRole,
589
+ temporalClass: args.temporalClass,
590
+ validFrom: args.validFrom,
591
+ validUntil: args.validUntil
592
+ });
593
+ } catch (error) {
594
+ await queueEdgeRetry(ctx, {
595
+ globalId: args.globalId,
596
+ operation: "upsert",
597
+ error: `Convex mirror failed: ${error instanceof Error ? error.message : "Unknown error"}`
598
+ });
599
+ }
600
+ }
601
+ return {
602
+ success: true,
603
+ globalId: args.globalId,
604
+ edgeType,
605
+ dualWritten: needsDualWrite(edgeType)
606
+ };
607
+ }
608
+ });
609
+ var deleteEdge2 = internalAction({
610
+ args: {
611
+ globalId: v.string()
612
+ },
613
+ returns: permissiveReturn,
614
+ handler: async (ctx, args) => {
615
+ const connInfo = getConnectionInfo();
616
+ if (!connInfo.configured) {
617
+ throw new Error("[Neo4j Edge API] Neo4j not configured");
618
+ }
619
+ await runWriteTransaction(
620
+ `
621
+ MATCH ()-[r {globalId: $globalId}]->()
622
+ DELETE r
623
+ `,
624
+ { globalId: args.globalId }
625
+ );
626
+ try {
627
+ await ctx.runMutation(internal.epistemicEdges.deleteEdgeFromConvex, {
628
+ globalId: args.globalId
629
+ });
630
+ } catch {
631
+ }
632
+ return { success: true };
633
+ }
634
+ });
635
+ var updateEdge = internalAction({
636
+ args: {
637
+ globalId: v.string(),
638
+ weight: v.optional(v.number()),
639
+ confidence: v.optional(v.number()),
640
+ context: v.optional(v.string()),
641
+ derivationType: v.optional(v.string())
642
+ },
643
+ returns: permissiveReturn,
644
+ handler: async (ctx, args) => {
645
+ const connInfo = getConnectionInfo();
646
+ if (!connInfo.configured) {
647
+ throw new Error("[Neo4j Edge API] Neo4j not configured");
648
+ }
649
+ const updates = { updatedAt: Date.now() };
650
+ if (args.weight !== void 0) updates.weight = args.weight;
651
+ if (args.confidence !== void 0) updates.confidence = args.confidence;
652
+ if (args.context !== void 0) updates.context = args.context;
653
+ if (args.derivationType !== void 0) {
654
+ updates.derivationType = args.derivationType;
655
+ }
656
+ const result = await runWriteTransaction(
657
+ `
658
+ MATCH ()-[r {globalId: $globalId}]->()
659
+ SET r += $updates
660
+ RETURN true as updated, r.edgeType as edgeType
661
+ `,
662
+ { globalId: args.globalId, updates }
663
+ );
664
+ if (result.length === 0) {
665
+ return { success: false, error: "Edge not found" };
666
+ }
667
+ const edgeType = result[0]?.edgeType;
668
+ if (edgeType && needsDualWrite(edgeType)) {
669
+ try {
670
+ await ctx.runMutation(internal.epistemicEdges.updateEdgeInConvex, {
671
+ globalId: args.globalId,
672
+ weight: args.weight,
673
+ confidence: args.confidence,
674
+ context: args.context,
675
+ derivationType: args.derivationType
676
+ });
677
+ } catch {
678
+ await queueEdgeRetry(ctx, {
679
+ globalId: args.globalId,
680
+ operation: "upsert",
681
+ error: "Convex mirror update failed"
682
+ });
683
+ }
684
+ }
685
+ return { success: true, globalId: args.globalId };
686
+ }
687
+ });
688
+ var getEdge = internalAction({
689
+ args: {
690
+ globalId: v.string()
691
+ },
692
+ returns: permissiveReturn,
693
+ handler: async (_ctx, args) => {
694
+ const connInfo = getConnectionInfo();
695
+ if (!connInfo.configured) {
696
+ return null;
697
+ }
698
+ const result = await runCypher(
699
+ `
700
+ MATCH (from)-[r {globalId: $globalId}]->(to)
701
+ RETURN r.globalId as globalId,
702
+ r.edgeType as edgeType,
703
+ from.globalId as fromGlobalId,
704
+ to.globalId as toGlobalId,
705
+ properties(r) as properties
706
+ LIMIT 1
707
+ `,
708
+ { globalId: args.globalId }
709
+ );
710
+ return result[0] ?? null;
711
+ }
712
+ });
713
+ var retryProjectionByGlobalId = internalAction({
714
+ args: {
715
+ globalId: v.string()
716
+ },
717
+ returns: permissiveReturn,
718
+ handler: async (ctx, args) => {
719
+ const connInfo = getConnectionInfo();
720
+ if (!connInfo.configured) {
721
+ return { success: false, error: "[Neo4j Edge API] Neo4j not configured" };
722
+ }
723
+ const result = await runCypher(
724
+ `
725
+ MATCH (from)-[r {globalId: $globalId}]->(to)
726
+ RETURN r.edgeType as edgeType,
727
+ from.globalId as fromGlobalId,
728
+ to.globalId as toGlobalId,
729
+ properties(r) as properties
730
+ LIMIT 1
731
+ `,
732
+ { globalId: args.globalId }
733
+ );
734
+ if (result.length === 0) {
735
+ return {
736
+ success: false,
737
+ error: `[Neo4j Edge API] Edge not found in Neo4j: ${args.globalId}`
738
+ };
739
+ }
740
+ const edge = result[0];
741
+ if (!needsDualWrite(edge.edgeType)) {
742
+ return {
743
+ success: true,
744
+ skipped: true,
745
+ reason: "edge_type_not_projected",
746
+ edgeType: edge.edgeType
747
+ };
748
+ }
749
+ const props = edge.properties || {};
750
+ await mirrorEdgeToConvex(ctx, {
751
+ globalId: args.globalId,
752
+ fromGlobalId: edge.fromGlobalId,
753
+ toGlobalId: edge.toGlobalId,
754
+ edgeType: edge.edgeType,
755
+ weight: readNumberProperty(props, "weight"),
756
+ confidence: readNumberProperty(props, "confidence"),
757
+ context: readStringProperty(props, "context"),
758
+ derivationType: readStringProperty(props, "derivationType"),
759
+ createdBy: readStringProperty(props, "createdBy") ?? "neo4j_projection_retry",
760
+ topicId: readStringProperty(props, "topicId"),
761
+ fromLayer: readStringProperty(props, "fromLayer"),
762
+ toLayer: readStringProperty(props, "toLayer"),
763
+ fromNodeType: readStringProperty(props, "fromNodeType"),
764
+ toNodeType: readStringProperty(props, "toNodeType"),
765
+ reasoningMethod: readStringProperty(props, "reasoningMethod"),
766
+ logicalRole: readStringProperty(props, "logicalRole"),
767
+ temporalClass: readStringProperty(props, "temporalClass"),
768
+ validFrom: readNumberProperty(props, "validFrom"),
769
+ validUntil: readNumberProperty(props, "validUntil")
770
+ });
771
+ return { success: true, globalId: args.globalId, projected: true };
772
+ }
773
+ });
774
+
775
+ // src/neo4jQueries.ts
776
+ var neo4jQueries_exports = {};
777
+ __export(neo4jQueries_exports, {
778
+ findPotentialContradictions: () => findPotentialContradictions,
779
+ getAnchoringBiasDetection: () => getAnchoringBiasDetection,
780
+ getBeliefEvidenceGraph: () => getBeliefEvidenceGraph,
781
+ getBeliefHalfLife: () => getBeliefHalfLife,
782
+ getBeliefsByCompany: () => getBeliefsByCompany,
783
+ getBeliefsByEpistemicStatus: () => getBeliefsByEpistemicStatus,
784
+ getCausalChains: () => getCausalChains,
785
+ getChallengedBeliefs: () => getChallengedBeliefs,
786
+ getCompaniesByTheme: () => getCompaniesByTheme,
787
+ getConfirmationBiasScore: () => getConfirmationBiasScore,
788
+ getConnectedNodesGraph: () => getConnectedNodesGraph,
789
+ getContradictionTensionMap: () => getContradictionTensionMap,
790
+ getCrossThemeBeliefs: () => getCrossThemeBeliefs,
791
+ getEvidenceByCompany: () => getEvidenceByCompany,
792
+ getEvidenceByMethodology: () => getEvidenceByMethodology,
793
+ getEvidenceToBeliefPath: () => getEvidenceToBeliefPath,
794
+ getFalsificationQuestions: () => getFalsificationQuestions,
795
+ getFunctionsByValueChain: () => getFunctionsByValueChain,
796
+ getGraphStats: () => getGraphStats,
797
+ getHighPriorityQuestions: () => getHighPriorityQuestions,
798
+ getKnowledgeFrontier: () => getKnowledgeFrontier,
799
+ getMeetingPrepBrief: () => getMeetingPrepBrief,
800
+ getMinimumFalsificationSet: () => getMinimumFalsificationSet,
801
+ getMissingQuestionDetection: () => getMissingQuestionDetection,
802
+ getNecessaryEvidence: () => getNecessaryEvidence,
803
+ getNodeLineageGraph: () => getNodeLineageGraph,
804
+ getNodeRelationships: () => getNodeRelationships,
805
+ getNonConsensusBeliefs: () => getNonConsensusBeliefs,
806
+ getPeopleByCompany: () => getPeopleByCompany,
807
+ getPeopleByTheme: () => getPeopleByTheme,
808
+ getPortfolioConviction: () => getPortfolioConviction,
809
+ getPredictions: () => getPredictions,
810
+ getProprietaryEvidence: () => getProprietaryEvidence,
811
+ getProprietarySignals: () => getProprietarySignals,
812
+ getQuestionsByTheme: () => getQuestionsByTheme,
813
+ getQuestionsByValueChain: () => getQuestionsByValueChain,
814
+ getReasoningDepthScore: () => getReasoningDepthScore,
815
+ getSemanticBridges: () => getSemanticBridges,
816
+ getSemanticOrphans: () => getSemanticOrphans,
817
+ getSoftContradictions: () => getSoftContradictions,
818
+ getSourceConcentrationRisk: () => getSourceConcentrationRisk,
819
+ getStaleThemes: () => getStaleThemes,
820
+ getSurpriseDetection: () => getSurpriseDetection,
821
+ getThemeBeliefsGraph: () => getThemeBeliefsGraph,
822
+ getThemeStats: () => getThemeStats,
823
+ getThemeSubgraph: () => getThemeSubgraph,
824
+ getThemeValueChainCandidates: () => getThemeValueChainCandidates,
825
+ getThemesImpactingCompany: () => getThemesImpactingCompany,
826
+ getValueChainsByTheme: () => getValueChainsByTheme,
827
+ graphAwareSearch: () => graphAwareSearch,
828
+ queryGraph: () => queryGraph,
829
+ searchAllNodes: () => searchAllNodes,
830
+ semanticSearch: () => semanticSearch
831
+ });
832
+ function toInt(value, defaultValue) {
833
+ if (value === void 0 || value === null) {
834
+ return defaultValue;
835
+ }
836
+ const parsed = Number(value);
837
+ return Number.isFinite(parsed) ? Math.floor(parsed) : defaultValue;
838
+ }
839
+ function isAllowedProxyUrl(urlString) {
840
+ try {
841
+ const url = new URL(urlString);
842
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
843
+ if (!isLocalhost && url.protocol !== "https:") {
844
+ return false;
845
+ }
846
+ if (url.username || url.password) {
847
+ return false;
848
+ }
849
+ const configuredHosts = (process.env.LUCERN_GRAPH_SYNC_ALLOWED_PROXY_HOSTS || "").split(",").map((host) => host.trim().toLowerCase()).filter(Boolean);
850
+ if (configuredHosts.length === 0) {
851
+ return true;
852
+ }
853
+ const hostname = url.hostname.toLowerCase();
854
+ return configuredHosts.some(
855
+ (host) => host.startsWith(".") ? hostname.endsWith(host) : hostname === host
856
+ );
857
+ } catch {
858
+ console.warn("[Neo4j Queries] Rejected malformed proxy URL", urlString);
859
+ return false;
860
+ }
861
+ }
862
+ function createNeo4jQueryTransportSuccess(data) {
863
+ return { success: true, data };
864
+ }
865
+ function createNeo4jQueryTransportFailure(error) {
866
+ return { success: false, error };
867
+ }
868
+ function resolveProxyBaseUrl(apiBaseUrl) {
869
+ const resolved = apiBaseUrl || process.env.LUCERN_GRAPH_SYNC_QUERY_BASE_URL || process.env.NEXT_PUBLIC_APP_URL;
870
+ const normalized = resolved?.trim();
871
+ return normalized ? normalized.replace(/\/+$/u, "") : null;
872
+ }
873
+ function buildProxyEndpoint(proxyBaseUrl) {
874
+ const endpoint = new URL(proxyBaseUrl);
875
+ if (!endpoint.pathname.endsWith("/api/neo4j-query")) {
876
+ endpoint.pathname = `${endpoint.pathname.replace(/\/+$/u, "")}/api/neo4j-query`;
877
+ }
878
+ return endpoint.toString();
879
+ }
880
+ async function callNeo4jQuery(queryName, params, apiBaseUrl) {
881
+ const proxyUrl = resolveProxyBaseUrl(apiBaseUrl);
882
+ if (!proxyUrl) {
883
+ return createNeo4jQueryTransportFailure(
884
+ "Neo4j query proxy URL not configured"
885
+ );
886
+ }
887
+ if (!isAllowedProxyUrl(proxyUrl)) {
888
+ console.error(
889
+ "[Neo4j Queries] Blocked request to disallowed URL:",
890
+ proxyUrl
891
+ );
892
+ return createNeo4jQueryTransportFailure("Invalid proxy URL");
893
+ }
894
+ const syncSecret = process.env.NEO4J_SYNC_SECRET;
895
+ if (!syncSecret) {
896
+ console.error("[Neo4j Queries] NEO4J_SYNC_SECRET not configured");
897
+ return createNeo4jQueryTransportFailure(
898
+ "Neo4j sync secret not configured"
899
+ );
900
+ }
901
+ try {
902
+ const explicitTenant = typeof params.tenantId === "string" ? params.tenantId.trim() : "";
903
+ const scopedTopic = typeof params.topicId === "string" ? params.topicId.trim() : "";
904
+ const legacyProject = typeof params.projectId === "string" ? params.projectId.trim() : "";
905
+ const scopedTopicId = scopedTopic || legacyProject;
906
+ const projectTenant = scopedTopicId ? `project:${scopedTopicId}` : "";
907
+ const defaultTenant = process.env.LUCERN_DEFAULT_TENANT_ID?.trim() || "";
908
+ const tenantId = explicitTenant || projectTenant || defaultTenant;
909
+ if (!tenantId) {
910
+ return createNeo4jQueryTransportFailure(
911
+ "Missing required tenant context (tenantId or topicId)"
912
+ );
913
+ }
914
+ const scopedParams = { ...params, tenantId };
915
+ const headers = {
916
+ "Content-Type": "application/json",
917
+ Authorization: `Bearer ${syncSecret}`
918
+ };
919
+ const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
920
+ if (bypassSecret && apiBaseUrl) {
921
+ headers["x-vercel-protection-bypass"] = bypassSecret;
922
+ }
923
+ const controller = new AbortController();
924
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
925
+ try {
926
+ const response = await fetch(buildProxyEndpoint(proxyUrl), {
927
+ method: "POST",
928
+ headers,
929
+ body: JSON.stringify({ queryName, params: scopedParams }),
930
+ signal: controller.signal
931
+ });
932
+ clearTimeout(timeoutId);
933
+ const result = await response.json();
934
+ if (!response.ok) {
935
+ return createNeo4jQueryTransportFailure(result.error || "Query failed");
936
+ }
937
+ return createNeo4jQueryTransportSuccess(result.data || []);
938
+ } catch (fetchError) {
939
+ clearTimeout(timeoutId);
940
+ if (fetchError instanceof Error && fetchError.name === "AbortError") {
941
+ console.warn("[Neo4j Queries] Request timed out for:", queryName);
942
+ return createNeo4jQueryTransportFailure("Query timed out (10s)");
943
+ }
944
+ throw fetchError;
945
+ }
946
+ } catch (error) {
947
+ console.error("[Neo4j Queries] Error in callNeo4jQuery:", queryName, error);
948
+ return createNeo4jQueryTransportFailure(
949
+ error instanceof Error ? error.message : "Network error connecting to query proxy"
950
+ );
951
+ }
952
+ }
953
+ function withTopicScope(args, params) {
954
+ return args.topicId ? { ...params, topicId: args.topicId } : params;
955
+ }
956
+ var getNodeLineageGraph = action({
957
+ args: {
958
+ globalId: v.string(),
959
+ apiBaseUrl: v.optional(v.string()),
960
+ topicId: v.optional(v.string())
961
+ },
962
+ returns: permissiveReturn,
963
+ handler: async (_ctx, args) => {
964
+ const result = await callNeo4jQuery(
965
+ "nodeLineage",
966
+ withTopicScope(args, {
967
+ globalId: args.globalId
968
+ }),
969
+ args.apiBaseUrl
970
+ );
971
+ if (!result.success) {
972
+ console.error("[Neo4j] Lineage query failed:", result.error);
973
+ return { lineage: [], error: result.error };
974
+ }
975
+ const lineage = result.data?.[0]?.lineage || [];
976
+ return { lineage };
977
+ }
978
+ });
979
+ var getConnectedNodesGraph = action({
980
+ args: {
981
+ globalId: v.string(),
982
+ maxHops: v.optional(v.number()),
983
+ limit: v.optional(v.number()),
984
+ apiBaseUrl: v.optional(v.string()),
985
+ topicId: v.optional(v.string())
986
+ },
987
+ returns: permissiveReturn,
988
+ handler: async (_ctx, args) => {
989
+ const maxHops = Math.min(toInt(args.maxHops, 2), 5);
990
+ const limit = toInt(args.limit, 50);
991
+ const result = await callNeo4jQuery(
992
+ "connectedNodes",
993
+ withTopicScope(args, {
994
+ globalId: args.globalId,
995
+ maxHops,
996
+ limit
997
+ }),
998
+ args.apiBaseUrl
999
+ );
1000
+ if (!result.success) {
1001
+ console.error("[Neo4j] Connected nodes query failed:", result.error);
1002
+ return { nodes: [], error: result.error };
1003
+ }
1004
+ return { nodes: result.data || [] };
1005
+ }
1006
+ });
1007
+ var getThemeBeliefsGraph = action({
1008
+ args: {
1009
+ themeGlobalId: v.string()
1010
+ },
1011
+ returns: permissiveReturn,
1012
+ handler: async (_ctx, args) => {
1013
+ const result = await callNeo4jQuery("themeBeliefs", {
1014
+ themeGlobalId: args.themeGlobalId
1015
+ });
1016
+ if (!result.success) {
1017
+ return { beliefs: [], error: result.error };
1018
+ }
1019
+ return {
1020
+ beliefs: result.data?.map((r) => r.belief) || []
1021
+ };
1022
+ }
1023
+ });
1024
+ var getBeliefEvidenceGraph = action({
1025
+ args: {
1026
+ beliefGlobalId: v.string(),
1027
+ apiBaseUrl: v.optional(v.string()),
1028
+ topicId: v.optional(v.string())
1029
+ },
1030
+ returns: permissiveReturn,
1031
+ handler: async (_ctx, args) => {
1032
+ const result = await callNeo4jQuery(
1033
+ "beliefEvidence",
1034
+ withTopicScope(args, {
1035
+ beliefGlobalId: args.beliefGlobalId
1036
+ }),
1037
+ args.apiBaseUrl
1038
+ );
1039
+ if (!result.success) {
1040
+ return { evidence: [], error: result.error };
1041
+ }
1042
+ return {
1043
+ evidence: result.data?.map((r) => r.evidence) || []
1044
+ };
1045
+ }
1046
+ });
1047
+ var getCrossThemeBeliefs = action({
1048
+ args: {
1049
+ limit: v.optional(v.number()),
1050
+ apiBaseUrl: v.optional(v.string()),
1051
+ topicId: v.optional(v.string())
1052
+ },
1053
+ returns: permissiveReturn,
1054
+ handler: async (_ctx, args) => {
1055
+ const result = await callNeo4jQuery(
1056
+ "crossThemeBeliefs",
1057
+ withTopicScope(args, {
1058
+ limit: toInt(args.limit, 20)
1059
+ }),
1060
+ args.apiBaseUrl
1061
+ );
1062
+ if (!result.success) {
1063
+ return { beliefs: [], error: result.error };
1064
+ }
1065
+ return {
1066
+ beliefs: result.data?.map((r) => r.belief) || []
1067
+ };
1068
+ }
1069
+ });
1070
+ var findPotentialContradictions = action({
1071
+ args: {
1072
+ limit: v.optional(v.number()),
1073
+ apiBaseUrl: v.optional(v.string()),
1074
+ topicId: v.optional(v.string())
1075
+ },
1076
+ returns: permissiveReturn,
1077
+ handler: async (_ctx, args) => {
1078
+ const result = await callNeo4jQuery(
1079
+ "potentialContradictions",
1080
+ withTopicScope(args, {
1081
+ limit: toInt(args.limit, 10)
1082
+ }),
1083
+ args.apiBaseUrl
1084
+ );
1085
+ if (!result.success) {
1086
+ return { contradictions: [], error: result.error };
1087
+ }
1088
+ return {
1089
+ contradictions: result.data?.map(
1090
+ (r) => r.contradiction
1091
+ ) || []
1092
+ };
1093
+ }
1094
+ });
1095
+ var getThemeSubgraph = action({
1096
+ args: {
1097
+ themeGlobalId: v.string(),
1098
+ apiBaseUrl: v.optional(v.string()),
1099
+ topicId: v.optional(v.string())
1100
+ },
1101
+ returns: permissiveReturn,
1102
+ handler: async (_ctx, args) => {
1103
+ const result = await callNeo4jQuery(
1104
+ "themeSubgraph",
1105
+ withTopicScope(args, {
1106
+ themeGlobalId: args.themeGlobalId
1107
+ }),
1108
+ args.apiBaseUrl
1109
+ );
1110
+ if (!result.success) {
1111
+ return { subgraph: null, error: result.error };
1112
+ }
1113
+ return {
1114
+ subgraph: result.data?.[0]?.subgraph || null
1115
+ };
1116
+ }
1117
+ });
1118
+ var getEvidenceToBeliefPath = action({
1119
+ args: {
1120
+ evidenceGlobalId: v.string(),
1121
+ beliefGlobalId: v.string(),
1122
+ apiBaseUrl: v.optional(v.string()),
1123
+ topicId: v.optional(v.string())
1124
+ },
1125
+ returns: permissiveReturn,
1126
+ handler: async (_ctx, args) => {
1127
+ const result = await callNeo4jQuery(
1128
+ "evidenceToBeliefPath",
1129
+ withTopicScope(args, {
1130
+ evidenceGlobalId: args.evidenceGlobalId,
1131
+ beliefGlobalId: args.beliefGlobalId
1132
+ }),
1133
+ args.apiBaseUrl
1134
+ );
1135
+ if (!result.success) {
1136
+ return { path: null, error: result.error };
1137
+ }
1138
+ const pathResult = result.data?.[0];
1139
+ if (!pathResult?.pathResult) {
1140
+ return { path: null, error: "No path found between evidence and belief" };
1141
+ }
1142
+ return {
1143
+ path: {
1144
+ nodes: pathResult.pathResult.nodes || [],
1145
+ relationships: pathResult.pathResult.relationships || [],
1146
+ length: pathResult.pathResult.length || 0
1147
+ }
1148
+ };
1149
+ }
1150
+ });
1151
+ var getThemeStats = action({
1152
+ args: {
1153
+ themeGlobalId: v.string()
1154
+ },
1155
+ returns: permissiveReturn,
1156
+ handler: async (_ctx, args) => {
1157
+ const result = await callNeo4jQuery("themeStats", {
1158
+ themeGlobalId: args.themeGlobalId
1159
+ });
1160
+ if (!result.success) {
1161
+ return { stats: null, error: result.error };
1162
+ }
1163
+ return { stats: result.data?.[0]?.stats || null };
1164
+ }
1165
+ });
1166
+ var getThemeValueChainCandidates = action({
1167
+ args: {
1168
+ limit: v.optional(v.number()),
1169
+ apiBaseUrl: v.optional(v.string()),
1170
+ topicId: v.optional(v.string())
1171
+ },
1172
+ returns: permissiveReturn,
1173
+ handler: async (_ctx, args) => {
1174
+ const result = await callNeo4jQuery(
1175
+ "themeValueChainCandidates",
1176
+ withTopicScope(args, {
1177
+ limit: toInt(args.limit, 100)
1178
+ }),
1179
+ args.apiBaseUrl
1180
+ );
1181
+ if (!result.success) {
1182
+ return { candidates: [], error: result.error };
1183
+ }
1184
+ return {
1185
+ candidates: result.data?.map((r) => r.candidate) || []
1186
+ };
1187
+ }
1188
+ });
1189
+ var getThemesImpactingCompany = action({
1190
+ args: {
1191
+ companyName: v.string(),
1192
+ limit: v.optional(v.number())
1193
+ },
1194
+ returns: permissiveReturn,
1195
+ handler: async (_ctx, args) => {
1196
+ const result = await callNeo4jQuery("themesImpactingCompany", {
1197
+ companyName: args.companyName,
1198
+ limit: toInt(args.limit, 20)
1199
+ });
1200
+ if (!result.success) {
1201
+ return { results: [], error: result.error };
1202
+ }
1203
+ return { results: result.data || [] };
1204
+ }
1205
+ });
1206
+ var getCompaniesByTheme = action({
1207
+ args: {
1208
+ themeName: v.string(),
1209
+ limit: v.optional(v.number())
1210
+ },
1211
+ returns: permissiveReturn,
1212
+ handler: async (_ctx, args) => {
1213
+ const result = await callNeo4jQuery("companiesByTheme", {
1214
+ themeName: args.themeName,
1215
+ limit: toInt(args.limit, 50)
1216
+ });
1217
+ if (!result.success) {
1218
+ return { results: [], error: result.error };
1219
+ }
1220
+ return { results: result.data || [] };
1221
+ }
1222
+ });
1223
+ var getQuestionsByValueChain = action({
1224
+ args: {
1225
+ valueChainName: v.string(),
1226
+ limit: v.optional(v.number())
1227
+ },
1228
+ returns: permissiveReturn,
1229
+ handler: async (_ctx, args) => {
1230
+ const result = await callNeo4jQuery("questionsByValueChain", {
1231
+ valueChainName: args.valueChainName,
1232
+ limit: toInt(args.limit, 50)
1233
+ });
1234
+ if (!result.success) {
1235
+ return { results: [], error: result.error };
1236
+ }
1237
+ return { results: result.data || [] };
1238
+ }
1239
+ });
1240
+ var getQuestionsByTheme = action({
1241
+ args: {
1242
+ themeName: v.string(),
1243
+ limit: v.optional(v.number())
1244
+ },
1245
+ returns: permissiveReturn,
1246
+ handler: async (_ctx, args) => {
1247
+ const result = await callNeo4jQuery("questionsByTheme", {
1248
+ themeName: args.themeName,
1249
+ limit: toInt(args.limit, 50)
1250
+ });
1251
+ if (!result.success) {
1252
+ return { results: [], error: result.error };
1253
+ }
1254
+ return { results: result.data || [] };
1255
+ }
1256
+ });
1257
+ var getPeopleByTheme = action({
1258
+ args: {
1259
+ themeName: v.string(),
1260
+ limit: v.optional(v.number())
1261
+ },
1262
+ returns: permissiveReturn,
1263
+ handler: async (_ctx, args) => {
1264
+ const result = await callNeo4jQuery("peopleByTheme", {
1265
+ themeName: args.themeName,
1266
+ limit: toInt(args.limit, 50)
1267
+ });
1268
+ if (!result.success) {
1269
+ return { results: [], error: result.error };
1270
+ }
1271
+ return { results: result.data || [] };
1272
+ }
1273
+ });
1274
+ var getPeopleByCompany = action({
1275
+ args: {
1276
+ companyName: v.string(),
1277
+ limit: v.optional(v.number())
1278
+ },
1279
+ returns: permissiveReturn,
1280
+ handler: async (_ctx, args) => {
1281
+ const result = await callNeo4jQuery("peopleByCompany", {
1282
+ companyName: args.companyName,
1283
+ limit: toInt(args.limit, 50)
1284
+ });
1285
+ if (!result.success) {
1286
+ return { results: [], error: result.error };
1287
+ }
1288
+ return { results: result.data || [] };
1289
+ }
1290
+ });
1291
+ var getValueChainsByTheme = action({
1292
+ args: {
1293
+ themeName: v.string(),
1294
+ limit: v.optional(v.number())
1295
+ },
1296
+ returns: permissiveReturn,
1297
+ handler: async (_ctx, args) => {
1298
+ const result = await callNeo4jQuery("valueChainsByTheme", {
1299
+ themeName: args.themeName,
1300
+ limit: toInt(args.limit, 20)
1301
+ });
1302
+ if (!result.success) {
1303
+ return { results: [], error: result.error };
1304
+ }
1305
+ return { results: result.data || [] };
1306
+ }
1307
+ });
1308
+ var getFunctionsByValueChain = action({
1309
+ args: {
1310
+ valueChainName: v.string(),
1311
+ limit: v.optional(v.number())
1312
+ },
1313
+ returns: permissiveReturn,
1314
+ handler: async (_ctx, args) => {
1315
+ const result = await callNeo4jQuery("functionsByValueChain", {
1316
+ valueChainName: args.valueChainName,
1317
+ limit: toInt(args.limit, 50)
1318
+ });
1319
+ if (!result.success) {
1320
+ return { results: [], error: result.error };
1321
+ }
1322
+ return { results: result.data || [] };
1323
+ }
1324
+ });
1325
+ var getBeliefsByCompany = action({
1326
+ args: {
1327
+ companyName: v.string(),
1328
+ limit: v.optional(v.number())
1329
+ },
1330
+ returns: permissiveReturn,
1331
+ handler: async (_ctx, args) => {
1332
+ const result = await callNeo4jQuery("beliefsByCompany", {
1333
+ companyName: args.companyName,
1334
+ limit: toInt(args.limit, 30)
1335
+ });
1336
+ if (!result.success) {
1337
+ return { results: [], error: result.error };
1338
+ }
1339
+ return { results: result.data || [] };
1340
+ }
1341
+ });
1342
+ var getEvidenceByCompany = action({
1343
+ args: {
1344
+ companyName: v.string(),
1345
+ limit: v.optional(v.number())
1346
+ },
1347
+ returns: permissiveReturn,
1348
+ handler: async (_ctx, args) => {
1349
+ const result = await callNeo4jQuery("evidenceByCompany", {
1350
+ companyName: args.companyName,
1351
+ limit: toInt(args.limit, 50)
1352
+ });
1353
+ if (!result.success) {
1354
+ return { results: [], error: result.error };
1355
+ }
1356
+ return { results: result.data || [] };
1357
+ }
1358
+ });
1359
+ var searchAllNodes = action({
1360
+ args: {
1361
+ searchText: v.string(),
1362
+ limit: v.optional(v.number())
1363
+ },
1364
+ returns: permissiveReturn,
1365
+ handler: async (_ctx, args) => {
1366
+ const result = await callNeo4jQuery("searchAllNodes", {
1367
+ searchText: args.searchText,
1368
+ limit: toInt(args.limit, 50)
1369
+ });
1370
+ if (!result.success) {
1371
+ return { results: [], error: result.error };
1372
+ }
1373
+ return { results: result.data || [] };
1374
+ }
1375
+ });
1376
+ var getNodeRelationships = action({
1377
+ args: {
1378
+ globalId: v.optional(v.string()),
1379
+ searchText: v.optional(v.string()),
1380
+ limit: v.optional(v.number())
1381
+ },
1382
+ returns: permissiveReturn,
1383
+ handler: async (_ctx, args) => {
1384
+ const result = await callNeo4jQuery("nodeRelationships", {
1385
+ globalId: args.globalId || "",
1386
+ searchText: args.searchText || "",
1387
+ limit: toInt(args.limit, 50)
1388
+ });
1389
+ if (!result.success) {
1390
+ return { results: [], error: result.error };
1391
+ }
1392
+ return { results: result.data || [] };
1393
+ }
1394
+ });
1395
+ var getGraphStats = action({
1396
+ args: {},
1397
+ returns: permissiveReturn,
1398
+ handler: async () => {
1399
+ const result = await callNeo4jQuery("graphStats", {});
1400
+ if (!result.success) {
1401
+ return { stats: [], error: result.error };
1402
+ }
1403
+ return { stats: result.data || [] };
1404
+ }
1405
+ });
1406
+ var queryGraph = action({
1407
+ args: {
1408
+ queryType: v.union(
1409
+ v.literal("themesImpactingCompany"),
1410
+ v.literal("companiesByTheme"),
1411
+ v.literal("questionsByValueChain"),
1412
+ v.literal("questionsByTheme"),
1413
+ v.literal("peopleByTheme"),
1414
+ v.literal("peopleByCompany"),
1415
+ v.literal("valueChainsByTheme"),
1416
+ v.literal("functionsByValueChain"),
1417
+ v.literal("beliefsByCompany"),
1418
+ v.literal("evidenceByCompany"),
1419
+ v.literal("searchAllNodes"),
1420
+ v.literal("nodeRelationships"),
1421
+ v.literal("graphStats")
1422
+ ),
1423
+ params: v.object({
1424
+ companyName: v.optional(v.string()),
1425
+ themeName: v.optional(v.string()),
1426
+ valueChainName: v.optional(v.string()),
1427
+ searchText: v.optional(v.string()),
1428
+ globalId: v.optional(v.string()),
1429
+ limit: v.optional(v.number())
1430
+ }),
1431
+ apiBaseUrl: v.optional(v.string()),
1432
+ topicId: v.optional(v.string())
1433
+ },
1434
+ returns: permissiveReturn,
1435
+ handler: async (_ctx, args) => {
1436
+ const limit = toInt(args.params.limit, 30);
1437
+ const themeRequiredQueries = [
1438
+ "questionsByTheme",
1439
+ "peopleByTheme",
1440
+ "companiesByTheme",
1441
+ "valueChainsByTheme"
1442
+ ];
1443
+ const companyRequiredQueries = [
1444
+ "themesImpactingCompany",
1445
+ "peopleByCompany",
1446
+ "beliefsByCompany",
1447
+ "evidenceByCompany"
1448
+ ];
1449
+ const valueChainRequiredQueries = [
1450
+ "questionsByValueChain",
1451
+ "functionsByValueChain"
1452
+ ];
1453
+ if (themeRequiredQueries.includes(args.queryType) && !args.params.themeName) {
1454
+ return {
1455
+ results: [],
1456
+ error: `Query type '${args.queryType}' requires 'themeName' parameter`
1457
+ };
1458
+ }
1459
+ if (companyRequiredQueries.includes(args.queryType) && !args.params.companyName) {
1460
+ return {
1461
+ results: [],
1462
+ error: `Query type '${args.queryType}' requires 'companyName' parameter`
1463
+ };
1464
+ }
1465
+ if (valueChainRequiredQueries.includes(args.queryType) && !args.params.valueChainName) {
1466
+ return {
1467
+ results: [],
1468
+ error: `Query type '${args.queryType}' requires 'valueChainName' parameter`
1469
+ };
1470
+ }
1471
+ const result = await callNeo4jQuery(
1472
+ args.queryType,
1473
+ withTopicScope(args, {
1474
+ ...args.params,
1475
+ limit
1476
+ }),
1477
+ args.apiBaseUrl
1478
+ );
1479
+ if (!result.success) {
1480
+ return { results: [], error: result.error };
1481
+ }
1482
+ return { results: result.data || [] };
1483
+ }
1484
+ });
1485
+ var getHighPriorityQuestions = action({
1486
+ args: {
1487
+ limit: v.optional(v.number()),
1488
+ apiBaseUrl: v.optional(v.string()),
1489
+ topicId: v.optional(v.string())
1490
+ },
1491
+ returns: permissiveReturn,
1492
+ handler: async (_ctx, args) => {
1493
+ const result = await callNeo4jQuery(
1494
+ "highPriorityQuestions",
1495
+ withTopicScope(args, {
1496
+ limit: toInt(args.limit, 50)
1497
+ }),
1498
+ args.apiBaseUrl
1499
+ );
1500
+ if (!result.success) {
1501
+ return { questions: [], error: result.error };
1502
+ }
1503
+ return { questions: result.data || [] };
1504
+ }
1505
+ });
1506
+ var getEvidenceByMethodology = action({
1507
+ args: {
1508
+ methodology: v.string(),
1509
+ limit: v.optional(v.number()),
1510
+ apiBaseUrl: v.optional(v.string()),
1511
+ topicId: v.optional(v.string())
1512
+ },
1513
+ returns: permissiveReturn,
1514
+ handler: async (_ctx, args) => {
1515
+ const result = await callNeo4jQuery(
1516
+ "evidenceByMethodology",
1517
+ withTopicScope(args, {
1518
+ methodology: args.methodology,
1519
+ limit: toInt(args.limit, 50)
1520
+ }),
1521
+ args.apiBaseUrl
1522
+ );
1523
+ if (!result.success) {
1524
+ return { evidence: [], error: result.error };
1525
+ }
1526
+ return { evidence: result.data || [] };
1527
+ }
1528
+ });
1529
+ var getProprietaryEvidence = action({
1530
+ args: {
1531
+ limit: v.optional(v.number()),
1532
+ apiBaseUrl: v.optional(v.string()),
1533
+ topicId: v.optional(v.string())
1534
+ },
1535
+ returns: permissiveReturn,
1536
+ handler: async (_ctx, args) => {
1537
+ const result = await callNeo4jQuery(
1538
+ "proprietaryEvidence",
1539
+ withTopicScope(args, {
1540
+ limit: toInt(args.limit, 50)
1541
+ }),
1542
+ args.apiBaseUrl
1543
+ );
1544
+ if (!result.success) {
1545
+ return { evidence: [], error: result.error };
1546
+ }
1547
+ return { evidence: result.data || [] };
1548
+ }
1549
+ });
1550
+ var getBeliefsByEpistemicStatus = action({
1551
+ args: {
1552
+ epistemicStatus: v.string(),
1553
+ limit: v.optional(v.number()),
1554
+ apiBaseUrl: v.optional(v.string()),
1555
+ topicId: v.optional(v.string())
1556
+ },
1557
+ returns: permissiveReturn,
1558
+ handler: async (_ctx, args) => {
1559
+ const result = await callNeo4jQuery(
1560
+ "beliefsByEpistemicStatus",
1561
+ withTopicScope(args, {
1562
+ epistemicStatus: args.epistemicStatus,
1563
+ limit: toInt(args.limit, 50)
1564
+ }),
1565
+ args.apiBaseUrl
1566
+ );
1567
+ if (!result.success) {
1568
+ return { beliefs: [], error: result.error };
1569
+ }
1570
+ return { beliefs: result.data || [] };
1571
+ }
1572
+ });
1573
+ var getChallengedBeliefs = action({
1574
+ args: {
1575
+ limit: v.optional(v.number()),
1576
+ apiBaseUrl: v.optional(v.string()),
1577
+ topicId: v.optional(v.string())
1578
+ },
1579
+ returns: permissiveReturn,
1580
+ handler: async (_ctx, args) => {
1581
+ const result = await callNeo4jQuery(
1582
+ "challengedBeliefs",
1583
+ withTopicScope(args, {
1584
+ limit: toInt(args.limit, 30)
1585
+ }),
1586
+ args.apiBaseUrl
1587
+ );
1588
+ if (!result.success) {
1589
+ return { beliefs: [], error: result.error };
1590
+ }
1591
+ return { beliefs: result.data || [] };
1592
+ }
1593
+ });
1594
+ var getPredictions = action({
1595
+ args: {
1596
+ limit: v.optional(v.number()),
1597
+ apiBaseUrl: v.optional(v.string()),
1598
+ topicId: v.optional(v.string())
1599
+ },
1600
+ returns: permissiveReturn,
1601
+ handler: async (_ctx, args) => {
1602
+ const result = await callNeo4jQuery(
1603
+ "predictions",
1604
+ withTopicScope(args, {
1605
+ limit: toInt(args.limit, 50)
1606
+ }),
1607
+ args.apiBaseUrl
1608
+ );
1609
+ if (!result.success) {
1610
+ return { predictions: [], error: result.error };
1611
+ }
1612
+ return { predictions: result.data || [] };
1613
+ }
1614
+ });
1615
+ var getCausalChains = action({
1616
+ args: {
1617
+ limit: v.optional(v.number()),
1618
+ apiBaseUrl: v.optional(v.string()),
1619
+ topicId: v.optional(v.string())
1620
+ },
1621
+ returns: permissiveReturn,
1622
+ handler: async (_ctx, args) => {
1623
+ const result = await callNeo4jQuery(
1624
+ "causalChains",
1625
+ withTopicScope(args, {
1626
+ limit: toInt(args.limit, 50)
1627
+ }),
1628
+ args.apiBaseUrl
1629
+ );
1630
+ if (!result.success) {
1631
+ return { chains: [], error: result.error };
1632
+ }
1633
+ return { chains: result.data || [] };
1634
+ }
1635
+ });
1636
+ var getNecessaryEvidence = action({
1637
+ args: {
1638
+ limit: v.optional(v.number()),
1639
+ apiBaseUrl: v.optional(v.string()),
1640
+ topicId: v.optional(v.string())
1641
+ },
1642
+ returns: permissiveReturn,
1643
+ handler: async (_ctx, args) => {
1644
+ const result = await callNeo4jQuery(
1645
+ "necessaryEvidence",
1646
+ withTopicScope(args, {
1647
+ limit: toInt(args.limit, 50)
1648
+ }),
1649
+ args.apiBaseUrl
1650
+ );
1651
+ if (!result.success) {
1652
+ return { results: [], error: result.error };
1653
+ }
1654
+ return { results: result.data || [] };
1655
+ }
1656
+ });
1657
+ var getFalsificationQuestions = action({
1658
+ args: {
1659
+ limit: v.optional(v.number()),
1660
+ apiBaseUrl: v.optional(v.string()),
1661
+ topicId: v.optional(v.string())
1662
+ },
1663
+ returns: permissiveReturn,
1664
+ handler: async (_ctx, args) => {
1665
+ const result = await callNeo4jQuery(
1666
+ "falsificationQuestions",
1667
+ withTopicScope(args, {
1668
+ limit: toInt(args.limit, 50)
1669
+ }),
1670
+ args.apiBaseUrl
1671
+ );
1672
+ if (!result.success) {
1673
+ return { questions: [], error: result.error };
1674
+ }
1675
+ return { questions: result.data || [] };
1676
+ }
1677
+ });
1678
+ var getConfirmationBiasScore = action({
1679
+ args: {
1680
+ limit: v.optional(v.number()),
1681
+ apiBaseUrl: v.optional(v.string()),
1682
+ topicId: v.optional(v.string())
1683
+ },
1684
+ returns: permissiveReturn,
1685
+ handler: async (_ctx, args) => {
1686
+ const result = await callNeo4jQuery(
1687
+ "confirmationBiasScore",
1688
+ {
1689
+ ...args.topicId ? { topicId: args.topicId } : {},
1690
+ limit: toInt(args.limit, 30)
1691
+ },
1692
+ args.apiBaseUrl
1693
+ );
1694
+ if (!result.success) {
1695
+ return { results: [], error: result.error };
1696
+ }
1697
+ return { results: result.data || [] };
1698
+ }
1699
+ });
1700
+ var getAnchoringBiasDetection = action({
1701
+ args: {
1702
+ limit: v.optional(v.number()),
1703
+ apiBaseUrl: v.optional(v.string()),
1704
+ topicId: v.optional(v.string())
1705
+ },
1706
+ returns: permissiveReturn,
1707
+ handler: async (_ctx, args) => {
1708
+ const result = await callNeo4jQuery(
1709
+ "anchoringBiasDetection",
1710
+ withTopicScope(args, {
1711
+ limit: toInt(args.limit, 30)
1712
+ }),
1713
+ args.apiBaseUrl
1714
+ );
1715
+ if (!result.success) {
1716
+ return { results: [], error: result.error };
1717
+ }
1718
+ return { results: result.data || [] };
1719
+ }
1720
+ });
1721
+ var getSourceConcentrationRisk = action({
1722
+ args: {
1723
+ limit: v.optional(v.number()),
1724
+ apiBaseUrl: v.optional(v.string()),
1725
+ topicId: v.optional(v.string())
1726
+ },
1727
+ returns: permissiveReturn,
1728
+ handler: async (_ctx, args) => {
1729
+ const result = await callNeo4jQuery(
1730
+ "sourceConcentrationRisk",
1731
+ withTopicScope(args, {
1732
+ limit: toInt(args.limit, 30)
1733
+ }),
1734
+ args.apiBaseUrl
1735
+ );
1736
+ if (!result.success) {
1737
+ return { results: [], error: result.error };
1738
+ }
1739
+ return { results: result.data || [] };
1740
+ }
1741
+ });
1742
+ var getMinimumFalsificationSet = action({
1743
+ args: {
1744
+ apiBaseUrl: v.optional(v.string()),
1745
+ topicId: v.optional(v.string())
1746
+ },
1747
+ returns: permissiveReturn,
1748
+ handler: async (_ctx, args) => {
1749
+ const result = await callNeo4jQuery(
1750
+ "minimumFalsificationSet",
1751
+ withTopicScope(args, {}),
1752
+ args.apiBaseUrl
1753
+ );
1754
+ if (!result.success) {
1755
+ return { result: null, error: result.error };
1756
+ }
1757
+ const data = result.data;
1758
+ return { result: Array.isArray(data) && data.length > 0 ? data[0] : null };
1759
+ }
1760
+ });
1761
+ var getContradictionTensionMap = action({
1762
+ args: {
1763
+ limit: v.optional(v.number()),
1764
+ apiBaseUrl: v.optional(v.string()),
1765
+ topicId: v.optional(v.string())
1766
+ },
1767
+ returns: permissiveReturn,
1768
+ handler: async (_ctx, args) => {
1769
+ const result = await callNeo4jQuery(
1770
+ "contradictionTensionMap",
1771
+ withTopicScope(args, {
1772
+ limit: toInt(args.limit, 30)
1773
+ }),
1774
+ args.apiBaseUrl
1775
+ );
1776
+ if (!result.success) {
1777
+ return { results: [], error: result.error };
1778
+ }
1779
+ return { results: result.data || [] };
1780
+ }
1781
+ });
1782
+ var getReasoningDepthScore = action({
1783
+ args: {
1784
+ limit: v.optional(v.number()),
1785
+ apiBaseUrl: v.optional(v.string()),
1786
+ topicId: v.optional(v.string())
1787
+ },
1788
+ returns: permissiveReturn,
1789
+ handler: async (_ctx, args) => {
1790
+ const result = await callNeo4jQuery(
1791
+ "reasoningDepthScore",
1792
+ withTopicScope(args, {
1793
+ limit: toInt(args.limit, 50)
1794
+ }),
1795
+ args.apiBaseUrl
1796
+ );
1797
+ if (!result.success) {
1798
+ return { results: [], error: result.error };
1799
+ }
1800
+ return { results: result.data || [] };
1801
+ }
1802
+ });
1803
+ var getKnowledgeFrontier = action({
1804
+ args: {
1805
+ limit: v.optional(v.number()),
1806
+ apiBaseUrl: v.optional(v.string()),
1807
+ topicId: v.optional(v.string())
1808
+ },
1809
+ returns: permissiveReturn,
1810
+ handler: async (_ctx, args) => {
1811
+ const result = await callNeo4jQuery(
1812
+ "knowledgeFrontier",
1813
+ withTopicScope(args, {
1814
+ limit: toInt(args.limit, 30)
1815
+ }),
1816
+ args.apiBaseUrl
1817
+ );
1818
+ if (!result.success) {
1819
+ return { results: [], error: result.error };
1820
+ }
1821
+ return { results: result.data || [] };
1822
+ }
1823
+ });
1824
+ var getBeliefHalfLife = action({
1825
+ args: {
1826
+ apiBaseUrl: v.optional(v.string()),
1827
+ topicId: v.optional(v.string())
1828
+ },
1829
+ returns: permissiveReturn,
1830
+ handler: async (_ctx, args) => {
1831
+ const result = await callNeo4jQuery(
1832
+ "beliefHalfLife",
1833
+ withTopicScope(args, {}),
1834
+ args.apiBaseUrl
1835
+ );
1836
+ if (!result.success) {
1837
+ return { result: null, error: result.error };
1838
+ }
1839
+ const data = result.data;
1840
+ return { result: Array.isArray(data) && data.length > 0 ? data[0] : null };
1841
+ }
1842
+ });
1843
+ var getMeetingPrepBrief = action({
1844
+ args: {
1845
+ personGlobalId: v.string(),
1846
+ apiBaseUrl: v.optional(v.string()),
1847
+ topicId: v.optional(v.string())
1848
+ },
1849
+ returns: permissiveReturn,
1850
+ handler: async (_ctx, args) => {
1851
+ const result = await callNeo4jQuery(
1852
+ "meetingPrepBrief",
1853
+ withTopicScope(args, {
1854
+ personGlobalId: args.personGlobalId
1855
+ }),
1856
+ args.apiBaseUrl
1857
+ );
1858
+ if (!result.success) {
1859
+ return { brief: null, error: result.error };
1860
+ }
1861
+ const data = result.data;
1862
+ return { brief: Array.isArray(data) && data.length > 0 ? data[0] : null };
1863
+ }
1864
+ });
1865
+ var getProprietarySignals = action({
1866
+ args: {
1867
+ limit: v.optional(v.number()),
1868
+ apiBaseUrl: v.optional(v.string()),
1869
+ topicId: v.optional(v.string())
1870
+ },
1871
+ returns: permissiveReturn,
1872
+ handler: async (_ctx, args) => {
1873
+ const result = await callNeo4jQuery(
1874
+ "proprietarySignals",
1875
+ withTopicScope(args, {
1876
+ limit: toInt(args.limit, 30)
1877
+ }),
1878
+ args.apiBaseUrl
1879
+ );
1880
+ if (!result.success) {
1881
+ return { results: [], error: result.error };
1882
+ }
1883
+ return { results: result.data || [] };
1884
+ }
1885
+ });
1886
+ var getPortfolioConviction = action({
1887
+ args: {
1888
+ limit: v.optional(v.number()),
1889
+ apiBaseUrl: v.optional(v.string()),
1890
+ topicId: v.optional(v.string())
1891
+ },
1892
+ returns: permissiveReturn,
1893
+ handler: async (_ctx, args) => {
1894
+ const result = await callNeo4jQuery(
1895
+ "portfolioConviction",
1896
+ withTopicScope(args, {
1897
+ limit: toInt(args.limit, 50)
1898
+ }),
1899
+ args.apiBaseUrl
1900
+ );
1901
+ if (!result.success) {
1902
+ return { portfolio: [], error: result.error };
1903
+ }
1904
+ return { portfolio: result.data || [] };
1905
+ }
1906
+ });
1907
+ var getStaleThemes = action({
1908
+ args: {
1909
+ limit: v.optional(v.number()),
1910
+ apiBaseUrl: v.optional(v.string()),
1911
+ topicId: v.optional(v.string())
1912
+ },
1913
+ returns: permissiveReturn,
1914
+ handler: async (_ctx, args) => {
1915
+ const result = await callNeo4jQuery(
1916
+ "staleThemes",
1917
+ withTopicScope(args, {
1918
+ limit: toInt(args.limit, 30)
1919
+ }),
1920
+ args.apiBaseUrl
1921
+ );
1922
+ if (!result.success) {
1923
+ return { themes: [], error: result.error };
1924
+ }
1925
+ return { themes: result.data || [] };
1926
+ }
1927
+ });
1928
+ var getMissingQuestionDetection = action({
1929
+ args: {
1930
+ limit: v.optional(v.number()),
1931
+ apiBaseUrl: v.optional(v.string()),
1932
+ topicId: v.optional(v.string())
1933
+ },
1934
+ returns: permissiveReturn,
1935
+ handler: async (_ctx, args) => {
1936
+ const result = await callNeo4jQuery(
1937
+ "missingQuestionDetection",
1938
+ withTopicScope(args, {
1939
+ limit: toInt(args.limit, 30)
1940
+ }),
1941
+ args.apiBaseUrl
1942
+ );
1943
+ if (!result.success) {
1944
+ return { results: [], error: result.error };
1945
+ }
1946
+ return { results: result.data || [] };
1947
+ }
1948
+ });
1949
+ var getSurpriseDetection = action({
1950
+ args: {
1951
+ limit: v.optional(v.number()),
1952
+ apiBaseUrl: v.optional(v.string()),
1953
+ topicId: v.optional(v.string())
1954
+ },
1955
+ returns: permissiveReturn,
1956
+ handler: async (_ctx, args) => {
1957
+ const result = await callNeo4jQuery(
1958
+ "surpriseDetection",
1959
+ withTopicScope(args, {
1960
+ limit: toInt(args.limit, 30)
1961
+ }),
1962
+ args.apiBaseUrl
1963
+ );
1964
+ if (!result.success) {
1965
+ return { results: [], error: result.error };
1966
+ }
1967
+ return { results: result.data || [] };
1968
+ }
1969
+ });
1970
+ var getNonConsensusBeliefs = action({
1971
+ args: {
1972
+ limit: v.optional(v.number()),
1973
+ apiBaseUrl: v.optional(v.string()),
1974
+ topicId: v.optional(v.string())
1975
+ },
1976
+ returns: permissiveReturn,
1977
+ handler: async (_ctx, args) => {
1978
+ const result = await callNeo4jQuery(
1979
+ "nonConsensusBeliefs",
1980
+ withTopicScope(args, {
1981
+ limit: toInt(args.limit, 30)
1982
+ }),
1983
+ args.apiBaseUrl
1984
+ );
1985
+ if (!result.success) {
1986
+ return { results: [], error: result.error };
1987
+ }
1988
+ return { results: result.data || [] };
1989
+ }
1990
+ });
1991
+ var EMBEDDING_DIMENSIONS = 1024;
1992
+ var VALID_INDEX_NAMES = /* @__PURE__ */ new Set([
1993
+ "belief_embedding_index",
1994
+ "question_embedding_index",
1995
+ "evidence_embedding_index",
1996
+ "theme_embedding_index",
1997
+ "source_embedding_index",
1998
+ "synthesis_embedding_index"
1999
+ ]);
2000
+ function clamp(value, min, max) {
2001
+ return Math.max(min, Math.min(max, value));
2002
+ }
2003
+ var semanticSearch = action({
2004
+ args: {
2005
+ embedding: v.array(v.float64()),
2006
+ indexName: v.optional(v.string()),
2007
+ topK: v.optional(v.number()),
2008
+ minScore: v.optional(v.number()),
2009
+ apiBaseUrl: v.optional(v.string()),
2010
+ topicId: v.optional(v.string())
2011
+ },
2012
+ returns: permissiveReturn,
2013
+ handler: async (_ctx, args) => {
2014
+ if (args.embedding.length !== EMBEDDING_DIMENSIONS) {
2015
+ return {
2016
+ results: [],
2017
+ error: `Embedding must be ${EMBEDDING_DIMENSIONS} dimensions, got ${args.embedding.length}`
2018
+ };
2019
+ }
2020
+ const indexName = args.indexName || "belief_embedding_index";
2021
+ if (!VALID_INDEX_NAMES.has(indexName)) {
2022
+ return { results: [], error: `Invalid index name: ${indexName}` };
2023
+ }
2024
+ const result = await callNeo4jQuery(
2025
+ "semanticSearch",
2026
+ {
2027
+ embedding: args.embedding,
2028
+ indexName,
2029
+ topK: toInt(args.topK, 10),
2030
+ minScore: clamp(args.minScore ?? 0.5, 0, 1)
2031
+ },
2032
+ args.apiBaseUrl
2033
+ );
2034
+ if (!result.success) {
2035
+ return { results: [], error: result.error };
2036
+ }
2037
+ return { results: result.data || [] };
2038
+ }
2039
+ });
2040
+ var getSemanticOrphans = action({
2041
+ args: {
2042
+ threshold: v.optional(v.number()),
2043
+ limit: v.optional(v.number()),
2044
+ apiBaseUrl: v.optional(v.string()),
2045
+ topicId: v.optional(v.string())
2046
+ },
2047
+ returns: permissiveReturn,
2048
+ handler: async (_ctx, args) => {
2049
+ const result = await callNeo4jQuery(
2050
+ "semanticOrphans",
2051
+ withTopicScope(args, {
2052
+ threshold: clamp(args.threshold ?? 0.4, 0, 1),
2053
+ limit: toInt(args.limit, 20)
2054
+ }),
2055
+ args.apiBaseUrl
2056
+ );
2057
+ if (!result.success) {
2058
+ return { results: [], error: result.error };
2059
+ }
2060
+ return { results: result.data || [] };
2061
+ }
2062
+ });
2063
+ var getSoftContradictions = action({
2064
+ args: {
2065
+ similarityThreshold: v.optional(v.number()),
2066
+ limit: v.optional(v.number()),
2067
+ apiBaseUrl: v.optional(v.string()),
2068
+ topicId: v.optional(v.string())
2069
+ },
2070
+ returns: permissiveReturn,
2071
+ handler: async (_ctx, args) => {
2072
+ const result = await callNeo4jQuery(
2073
+ "softContradictions",
2074
+ withTopicScope(args, {
2075
+ similarityThreshold: clamp(args.similarityThreshold ?? 0.7, 0, 1),
2076
+ limit: toInt(args.limit, 20)
2077
+ }),
2078
+ args.apiBaseUrl
2079
+ );
2080
+ if (!result.success) {
2081
+ return { results: [], error: result.error };
2082
+ }
2083
+ return { results: result.data || [] };
2084
+ }
2085
+ });
2086
+ var getSemanticBridges = action({
2087
+ args: {
2088
+ similarityThreshold: v.optional(v.number()),
2089
+ limit: v.optional(v.number()),
2090
+ apiBaseUrl: v.optional(v.string()),
2091
+ topicId: v.optional(v.string())
2092
+ },
2093
+ returns: permissiveReturn,
2094
+ handler: async (_ctx, args) => {
2095
+ const result = await callNeo4jQuery(
2096
+ "semanticBridges",
2097
+ withTopicScope(args, {
2098
+ similarityThreshold: clamp(args.similarityThreshold ?? 0.7, 0, 1),
2099
+ limit: toInt(args.limit, 20)
2100
+ }),
2101
+ args.apiBaseUrl
2102
+ );
2103
+ if (!result.success) {
2104
+ return { results: [], error: result.error };
2105
+ }
2106
+ return { results: result.data || [] };
2107
+ }
2108
+ });
2109
+ var graphAwareSearch = action({
2110
+ args: {
2111
+ embedding: v.array(v.float64()),
2112
+ indexName: v.optional(v.string()),
2113
+ topK: v.optional(v.number()),
2114
+ minScore: v.optional(v.number()),
2115
+ apiBaseUrl: v.optional(v.string()),
2116
+ topicId: v.optional(v.string())
2117
+ },
2118
+ returns: permissiveReturn,
2119
+ handler: async (_ctx, args) => {
2120
+ if (args.embedding.length !== EMBEDDING_DIMENSIONS) {
2121
+ return {
2122
+ results: [],
2123
+ error: `Embedding must be ${EMBEDDING_DIMENSIONS} dimensions, got ${args.embedding.length}`
2124
+ };
2125
+ }
2126
+ const indexName = args.indexName || "belief_embedding_index";
2127
+ if (!VALID_INDEX_NAMES.has(indexName)) {
2128
+ return { results: [], error: `Invalid index name: ${indexName}` };
2129
+ }
2130
+ const result = await callNeo4jQuery(
2131
+ "graphAwareSearch",
2132
+ withTopicScope(args, {
2133
+ embedding: args.embedding,
2134
+ indexName,
2135
+ topK: toInt(args.topK, 10),
2136
+ minScore: clamp(args.minScore ?? 0.5, 0, 1)
2137
+ }),
2138
+ args.apiBaseUrl
2139
+ );
2140
+ if (!result.success) {
2141
+ return { results: [], error: result.error };
2142
+ }
2143
+ return { results: result.data || [] };
2144
+ }
2145
+ });
2146
+
2147
+ // src/neo4jQueryRoute.ts
2148
+ var neo4jQueryRoute_exports = {};
2149
+ __export(neo4jQueryRoute_exports, {
2150
+ createNeo4jQueryRouteHandler: () => createNeo4jQueryRouteHandler
2151
+ });
2152
+ function jsonResponse(body, init) {
2153
+ return new Response(JSON.stringify(body), {
2154
+ ...init,
2155
+ headers: {
2156
+ "Content-Type": "application/json",
2157
+ ...init?.headers
2158
+ }
2159
+ });
2160
+ }
2161
+ function readBearerSecret(request) {
2162
+ const authorization = request.headers.get("authorization") ?? "";
2163
+ const match = authorization.match(/^Bearer\s+(.+)$/iu);
2164
+ return match?.[1]?.trim() || null;
2165
+ }
2166
+ function hasTenantContext(params) {
2167
+ const tenantId = params.tenantId;
2168
+ const topicId = params.topicId;
2169
+ const projectId = params.projectId;
2170
+ return [tenantId, topicId, projectId].some(
2171
+ (value) => typeof value === "string" && value.trim().length > 0
2172
+ );
2173
+ }
2174
+ function normalizeConnectedNodesQuery(queryName, params, queries) {
2175
+ if (queryName !== "connectedNodes") {
2176
+ return queryName;
2177
+ }
2178
+ const hops = typeof params.maxHops === "number" && Number.isFinite(params.maxHops) ? Math.min(Math.max(Math.floor(params.maxHops), 1), 5) : 2;
2179
+ const aliased = `connectedNodes${hops}`;
2180
+ return queries[aliased] ? aliased : queryName;
2181
+ }
2182
+ function createNeo4jQueryRouteHandler(options) {
2183
+ return async function handleNeo4jQuery(request) {
2184
+ const expectedSecret = options.syncSecret ?? process.env.NEO4J_SYNC_SECRET?.trim();
2185
+ if (!expectedSecret) {
2186
+ return jsonResponse(
2187
+ { error: "Neo4j sync secret not configured" },
2188
+ { status: 500 }
2189
+ );
2190
+ }
2191
+ if (readBearerSecret(request) !== expectedSecret) {
2192
+ return jsonResponse({ error: "Unauthorized" }, { status: 401 });
2193
+ }
2194
+ let body;
2195
+ try {
2196
+ body = await request.json();
2197
+ } catch {
2198
+ return jsonResponse({ error: "Invalid JSON body" }, { status: 400 });
2199
+ }
2200
+ if (typeof body.queryName !== "string" || body.queryName.length === 0) {
2201
+ return jsonResponse({ error: "Missing queryName" }, { status: 400 });
2202
+ }
2203
+ const params = body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {};
2204
+ if ((options.requireTenantContext ?? true) && !hasTenantContext(params)) {
2205
+ return jsonResponse(
2206
+ { error: "Missing required tenant context" },
2207
+ { status: 400 }
2208
+ );
2209
+ }
2210
+ const queryName = normalizeConnectedNodesQuery(
2211
+ body.queryName,
2212
+ params,
2213
+ options.queries
2214
+ );
2215
+ const query = options.queries[queryName];
2216
+ if (!query) {
2217
+ return jsonResponse(
2218
+ { error: `Unknown query: ${body.queryName}` },
2219
+ { status: 400 }
2220
+ );
2221
+ }
2222
+ try {
2223
+ const data = await runCypher(
2224
+ query.cypher,
2225
+ params,
2226
+ query.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS
2227
+ );
2228
+ return jsonResponse({ data, queryName });
2229
+ } catch (error) {
2230
+ return jsonResponse(
2231
+ {
2232
+ error: error instanceof Error ? error.message : "Neo4j query failed",
2233
+ queryName
2234
+ },
2235
+ { status: 500 }
2236
+ );
2237
+ }
2238
+ };
2239
+ }
2240
+
2241
+ // src/neo4jSync.ts
2242
+ var neo4jSync_exports = {};
2243
+ __export(neo4jSync_exports, {
2244
+ backfillAllToNeo4j: () => backfillAllToNeo4j,
2245
+ checkNeo4jHealth: () => checkNeo4jHealth,
2246
+ processRetryQueue: () => processRetryQueue,
2247
+ resyncAllEdges: () => resyncAllEdges,
2248
+ resyncAllNodes: () => resyncAllNodes,
2249
+ syncAllEdgesToNeo4j: () => syncAllEdgesToNeo4j,
2250
+ syncAllNodesToNeo4j: () => syncAllNodesToNeo4j,
2251
+ syncEdgeToNeo4j: () => syncEdgeToNeo4j,
2252
+ syncEmbeddingToNeo4j: () => syncEmbeddingToNeo4j,
2253
+ syncNodeToNeo4j: () => syncNodeToNeo4j
2254
+ });
2255
+ function buildSyncResponse(entityType, operation, fields) {
2256
+ return {
2257
+ entityType,
2258
+ operation,
2259
+ ...fields
2260
+ };
2261
+ }
2262
+ function buildSyncSuccess(entityType, operation, fields) {
2263
+ return buildSyncResponse(entityType, operation, {
2264
+ success: true,
2265
+ ...fields
2266
+ });
2267
+ }
2268
+ function buildSyncFailure(entityType, operation, error, fields) {
2269
+ return buildSyncResponse(entityType, operation, {
2270
+ success: false,
2271
+ error,
2272
+ ...fields
2273
+ });
2274
+ }
2275
+ function buildNodeProperties(node) {
2276
+ const metadata = node.metadata || {};
2277
+ const pillar = metadata.pillar || metadata.topic || "";
2278
+ const stage = metadata.stage || metadata.beliefStage || "";
2279
+ const criticality = metadata.criticality || "";
2280
+ const synthesizedFrom = metadata.synthesizedFrom || [];
2281
+ const epistemicStatus = node.epistemicStatus || "";
2282
+ const methodology = node.methodology || "";
2283
+ const informationAsymmetry = node.informationAsymmetry || "";
2284
+ const questionType = node.questionType || "";
2285
+ const questionPriority = node.questionPriority || "";
2286
+ const answerQuality = node.answerQuality || "";
2287
+ const reversibility = node.reversibility || "";
2288
+ const predictionMeta = node.predictionMeta;
2289
+ return {
2290
+ convexId: node._id,
2291
+ canonicalText: node.canonicalText || "",
2292
+ title: node.title || "",
2293
+ status: node.status || "active",
2294
+ subtype: node.subtype || "",
2295
+ domain: node.domain || "",
2296
+ confidence: node.confidence || 0,
2297
+ verificationStatus: node.verificationStatus || "unverified",
2298
+ sourceType: node.sourceType || "unknown",
2299
+ createdAt: node.createdAt,
2300
+ updatedAt: node.updatedAt || Date.now(),
2301
+ createdBy: node.createdBy || "",
2302
+ nodeType: node.nodeType,
2303
+ epistemicLayer: node.epistemicLayer || "",
2304
+ // Project and metadata fields
2305
+ projectId: node.projectId || "",
2306
+ tenantId: node.tenantId || "",
2307
+ workspaceId: node.workspaceId || "",
2308
+ pillar,
2309
+ stage,
2310
+ criticality,
2311
+ synthesizedFromCount: synthesizedFrom.length,
2312
+ // Classification fields (Logic Machine)
2313
+ epistemicStatus,
2314
+ methodology,
2315
+ informationAsymmetry,
2316
+ questionType,
2317
+ questionPriority,
2318
+ answerQuality,
2319
+ reversibility,
2320
+ isPrediction: predictionMeta?.isPrediction ? "true" : "false",
2321
+ expectedBy: predictionMeta?.expectedBy ? String(predictionMeta.expectedBy) : ""
2322
+ };
2323
+ }
2324
+ function buildEdgeProperties(edge) {
2325
+ return {
2326
+ convexId: edge._id,
2327
+ weight: edge.weight || 1,
2328
+ confidence: edge.confidence || 0,
2329
+ context: edge.context || "",
2330
+ derivationType: edge.derivationType || "",
2331
+ createdAt: edge.createdAt,
2332
+ createdBy: edge.createdBy || "",
2333
+ edgeType: edge.edgeType,
2334
+ fromLayer: edge.fromLayer || "",
2335
+ toLayer: edge.toLayer || "",
2336
+ projectId: edge.projectId || "",
2337
+ tenantId: edge.tenantId || "",
2338
+ workspaceId: edge.workspaceId || "",
2339
+ // Classification fields (Logic Machine)
2340
+ reasoningMethod: edge.reasoningMethod || "",
2341
+ logicalRole: edge.logicalRole || "",
2342
+ temporalClass: edge.temporalClass || ""
2343
+ };
2344
+ }
2345
+ var syncNodeToNeo4j = internalAction({
2346
+ args: {
2347
+ nodeId: v.id("epistemicNodes"),
2348
+ operation: v.union(v.literal("upsert"), v.literal("delete"))
2349
+ },
2350
+ returns: permissiveReturn,
2351
+ handler: async (ctx, args) => {
2352
+ const connInfo = getConnectionInfo();
2353
+ if (!connInfo.configured) {
2354
+ console.warn(
2355
+ "[Neo4j Sync] Missing Neo4j credentials, skipping sync. Set via `npx convex env set`"
2356
+ );
2357
+ return buildSyncFailure("node", args.operation, "Missing credentials", {
2358
+ skippedReason: "credentials_missing"
2359
+ });
2360
+ }
2361
+ const node = await ctx.runQuery(internal.neo4jSyncHelpers.getNodeForSync, {
2362
+ nodeId: args.nodeId
2363
+ });
2364
+ if (!node) {
2365
+ if (args.operation === "upsert") {
2366
+ return buildSyncFailure("node", args.operation, "Node not found", {
2367
+ skippedReason: "source_node_missing"
2368
+ });
2369
+ }
2370
+ console.log("[Neo4j Sync] Node not found for delete, skipping");
2371
+ return buildSyncSuccess("node", args.operation, {
2372
+ skipped: true,
2373
+ skippedReason: "source_node_missing"
2374
+ });
2375
+ }
2376
+ const label = NODE_TYPE_TO_LABEL[node.nodeType] || "Node";
2377
+ try {
2378
+ if (args.operation === "delete") {
2379
+ await deleteNode(node.globalId);
2380
+ console.log(`[Neo4j Sync] Deleted node ${node.globalId}`);
2381
+ } else {
2382
+ const props = buildNodeProperties(node);
2383
+ const embedding = await ctx.runQuery(
2384
+ internal.neo4jSyncHelpers.getEmbeddingForSync,
2385
+ { nodeId: args.nodeId }
2386
+ );
2387
+ if (embedding) {
2388
+ props.embedding = embedding;
2389
+ }
2390
+ await upsertNode(label, node.globalId, props);
2391
+ console.log(
2392
+ `[Neo4j Sync] Upserted node ${node.globalId} as ${label} with projectId=${node.projectId}` + (embedding ? ` (with ${embedding.length}-dim embedding)` : "")
2393
+ );
2394
+ }
2395
+ await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
2396
+ eventType: args.operation === "delete" ? "node_deleted" : node ? "node_updated" : "node_created",
2397
+ entityId: args.nodeId,
2398
+ entityType: node.nodeType,
2399
+ status: "success"
2400
+ });
2401
+ return buildSyncSuccess("node", args.operation);
2402
+ } catch (error) {
2403
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
2404
+ console.error("[Neo4j Sync] Node sync error:", errorMsg);
2405
+ await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
2406
+ eventType: args.operation === "delete" ? "node_deleted" : "node_updated",
2407
+ entityId: args.nodeId,
2408
+ entityType: node.nodeType,
2409
+ status: "failed",
2410
+ error: errorMsg
2411
+ });
2412
+ await ctx.runMutation(internal.neo4jSyncHelpers.queueForRetry, {
2413
+ entityType: "node",
2414
+ entityId: args.nodeId,
2415
+ operation: args.operation,
2416
+ error: errorMsg
2417
+ });
2418
+ return buildSyncFailure("node", args.operation, errorMsg);
2419
+ }
2420
+ }
2421
+ });
2422
+ var syncEdgeToNeo4j = internalAction({
2423
+ args: {
2424
+ edgeId: v.id("epistemicEdges"),
2425
+ operation: v.union(v.literal("upsert"), v.literal("delete"))
2426
+ },
2427
+ returns: permissiveReturn,
2428
+ handler: async (ctx, args) => {
2429
+ const connInfo = getConnectionInfo();
2430
+ if (!connInfo.configured) {
2431
+ console.warn(
2432
+ "[Neo4j Sync] Missing Neo4j credentials, skipping sync. Set via `npx convex env set`"
2433
+ );
2434
+ return buildSyncFailure("edge", args.operation, "Missing credentials", {
2435
+ skippedReason: "credentials_missing"
2436
+ });
2437
+ }
2438
+ const edge = await ctx.runQuery(internal.neo4jSyncHelpers.getEdgeForSync, {
2439
+ edgeId: args.edgeId
2440
+ });
2441
+ if (!edge) {
2442
+ if (args.operation === "upsert") {
2443
+ return buildSyncFailure("edge", args.operation, "Edge not found", {
2444
+ skippedReason: "source_edge_missing"
2445
+ });
2446
+ }
2447
+ console.log("[Neo4j Sync] Edge not found for delete, skipping");
2448
+ return buildSyncSuccess("edge", args.operation, {
2449
+ skipped: true,
2450
+ skippedReason: "source_edge_missing"
2451
+ });
2452
+ }
2453
+ if (!edge.fromGlobalId || !edge.toGlobalId) {
2454
+ console.warn(
2455
+ "[Neo4j Sync] Edge missing fromGlobalId or toGlobalId, skipping"
2456
+ );
2457
+ return buildSyncFailure(
2458
+ "edge",
2459
+ args.operation,
2460
+ "Edge missing connected node globalIds",
2461
+ {
2462
+ skippedReason: "edge_endpoint_missing"
2463
+ }
2464
+ );
2465
+ }
2466
+ const relType = EDGE_TYPE_TO_REL[edge.edgeType] || edge.edgeType.toUpperCase();
2467
+ try {
2468
+ if (args.operation === "delete") {
2469
+ await deleteEdge(edge.globalId);
2470
+ console.log(`[Neo4j Sync] Deleted edge ${edge.globalId}`);
2471
+ } else {
2472
+ await upsertEdge(
2473
+ relType,
2474
+ edge.globalId,
2475
+ edge.fromGlobalId,
2476
+ edge.toGlobalId,
2477
+ {
2478
+ convexId: args.edgeId,
2479
+ weight: edge.weight || 1,
2480
+ confidence: edge.confidence || 0,
2481
+ context: edge.context || "",
2482
+ derivationType: edge.derivationType || "",
2483
+ createdAt: edge.createdAt,
2484
+ createdBy: edge.createdBy || "",
2485
+ edgeType: edge.edgeType,
2486
+ fromLayer: edge.fromLayer || "",
2487
+ toLayer: edge.toLayer || "",
2488
+ // Classification fields (Logic Machine)
2489
+ reasoningMethod: edge.reasoningMethod || "",
2490
+ logicalRole: edge.logicalRole || "",
2491
+ temporalClass: edge.temporalClass || ""
2492
+ }
2493
+ );
2494
+ console.log(
2495
+ `[Neo4j Sync] Upserted edge ${edge.globalId} as ${relType}`
2496
+ );
2497
+ }
2498
+ await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
2499
+ eventType: args.operation === "delete" ? "edge_deleted" : "edge_created",
2500
+ entityId: args.edgeId,
2501
+ entityType: edge.edgeType,
2502
+ status: "success"
2503
+ });
2504
+ return buildSyncSuccess("edge", args.operation);
2505
+ } catch (error) {
2506
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
2507
+ console.error("[Neo4j Sync] Edge sync error:", errorMsg);
2508
+ await ctx.runMutation(internal.neo4jSyncHelpers.logSyncEvent, {
2509
+ eventType: args.operation === "delete" ? "edge_deleted" : "edge_created",
2510
+ entityId: args.edgeId,
2511
+ entityType: edge.edgeType,
2512
+ status: "failed",
2513
+ error: errorMsg
2514
+ });
2515
+ await ctx.runMutation(internal.neo4jSyncHelpers.queueForRetry, {
2516
+ entityType: "edge",
2517
+ entityId: args.edgeId,
2518
+ operation: args.operation,
2519
+ error: errorMsg
2520
+ });
2521
+ return buildSyncFailure("edge", args.operation, errorMsg);
2522
+ }
2523
+ }
2524
+ });
2525
+ var syncAllNodesToNeo4j = internalAction({
2526
+ args: {
2527
+ batchSize: v.optional(v.number()),
2528
+ cursor: v.optional(v.string())
2529
+ },
2530
+ returns: permissiveReturn,
2531
+ handler: async (ctx, args) => {
2532
+ const batchSize = args.batchSize ?? 100;
2533
+ const result = await ctx.runQuery(
2534
+ internal.neo4jSyncHelpers.getNodeBatchForSync,
2535
+ {
2536
+ limit: batchSize,
2537
+ cursor: args.cursor
2538
+ }
2539
+ );
2540
+ if (result.nodes.length === 0) {
2541
+ return { synced: 0, failed: 0, hasMore: false };
2542
+ }
2543
+ const nodesByLabel = /* @__PURE__ */ new Map();
2544
+ for (const node of result.nodes) {
2545
+ const label = NODE_TYPE_TO_LABEL[node.nodeType] || "Node";
2546
+ if (!nodesByLabel.has(label)) {
2547
+ nodesByLabel.set(label, []);
2548
+ }
2549
+ nodesByLabel.get(label)?.push({
2550
+ globalId: node.globalId,
2551
+ properties: buildNodeProperties(node)
2552
+ });
2553
+ }
2554
+ let synced = 0;
2555
+ let failed = 0;
2556
+ for (const [label, nodes] of nodesByLabel) {
2557
+ try {
2558
+ await batchUpsertNodes(label, nodes);
2559
+ synced += nodes.length;
2560
+ console.log(
2561
+ `[Neo4j Sync] Batch upserted ${nodes.length} ${label} nodes`
2562
+ );
2563
+ } catch (error) {
2564
+ console.error(`[Neo4j Sync] Batch upsert failed for ${label}:`, error);
2565
+ failed += nodes.length;
2566
+ }
2567
+ }
2568
+ return {
2569
+ synced,
2570
+ failed,
2571
+ hasMore: result.hasMore,
2572
+ nextCursor: result.nextCursor
2573
+ };
2574
+ }
2575
+ });
2576
+ var syncAllEdgesToNeo4j = internalAction({
2577
+ args: {
2578
+ batchSize: v.optional(v.number()),
2579
+ cursor: v.optional(v.string())
2580
+ },
2581
+ returns: permissiveReturn,
2582
+ handler: async (ctx, args) => {
2583
+ const batchSize = args.batchSize ?? 100;
2584
+ const result = await ctx.runQuery(
2585
+ internal.neo4jSyncHelpers.getEdgeBatchForSync,
2586
+ {
2587
+ limit: batchSize,
2588
+ cursor: args.cursor
2589
+ }
2590
+ );
2591
+ if (result.edges.length === 0) {
2592
+ return { synced: 0, failed: 0, hasMore: false };
2593
+ }
2594
+ const edgesToSync = [];
2595
+ for (const edge of result.edges) {
2596
+ if (!edge.fromGlobalId || !edge.toGlobalId) {
2597
+ console.warn(
2598
+ `[Neo4j Sync] Skipping edge ${edge.globalId} - missing globalIds`
2599
+ );
2600
+ continue;
2601
+ }
2602
+ const relType = EDGE_TYPE_TO_REL[edge.edgeType] || edge.edgeType.toUpperCase();
2603
+ edgesToSync.push({
2604
+ relType,
2605
+ globalId: edge.globalId,
2606
+ fromGlobalId: edge.fromGlobalId,
2607
+ toGlobalId: edge.toGlobalId,
2608
+ properties: buildEdgeProperties(edge)
2609
+ });
2610
+ }
2611
+ let synced = 0;
2612
+ let failed = 0;
2613
+ try {
2614
+ await batchUpsertEdges(edgesToSync);
2615
+ synced = edgesToSync.length;
2616
+ console.log(`[Neo4j Sync] Batch upserted ${synced} edges`);
2617
+ } catch (error) {
2618
+ console.error("[Neo4j Sync] Batch edge upsert failed:", error);
2619
+ failed = edgesToSync.length;
2620
+ }
2621
+ return {
2622
+ synced,
2623
+ failed,
2624
+ hasMore: result.hasMore,
2625
+ nextCursor: result.nextCursor
2626
+ };
2627
+ }
2628
+ });
2629
+ var backfillAllToNeo4j = internalAction({
2630
+ args: {
2631
+ batchSize: v.optional(v.number())
2632
+ },
2633
+ returns: permissiveReturn,
2634
+ handler: async (ctx, args) => {
2635
+ const batchSize = args.batchSize ?? 100;
2636
+ let totalNodes = 0;
2637
+ let totalEdges = 0;
2638
+ let totalFailed = 0;
2639
+ console.log("[Neo4j Sync] Starting full backfill...");
2640
+ let nodeCursor;
2641
+ do {
2642
+ const result = await ctx.runAction(
2643
+ internal.neo4jSync.syncAllNodesToNeo4j,
2644
+ {
2645
+ batchSize,
2646
+ cursor: nodeCursor
2647
+ }
2648
+ );
2649
+ totalNodes += result.synced;
2650
+ totalFailed += result.failed;
2651
+ nodeCursor = result.hasMore ? result.nextCursor : void 0;
2652
+ console.log(
2653
+ `[Neo4j Sync] Nodes progress: ${totalNodes} synced, ${totalFailed} failed`
2654
+ );
2655
+ } while (nodeCursor);
2656
+ console.log(`[Neo4j Sync] Finished nodes: ${totalNodes} synced`);
2657
+ let edgeCursor;
2658
+ do {
2659
+ const result = await ctx.runAction(
2660
+ internal.neo4jSync.syncAllEdgesToNeo4j,
2661
+ {
2662
+ batchSize,
2663
+ cursor: edgeCursor
2664
+ }
2665
+ );
2666
+ totalEdges += result.synced;
2667
+ totalFailed += result.failed;
2668
+ edgeCursor = result.hasMore ? result.nextCursor : void 0;
2669
+ console.log(
2670
+ `[Neo4j Sync] Edges progress: ${totalEdges} synced, ${totalFailed} failed`
2671
+ );
2672
+ } while (edgeCursor);
2673
+ console.log(
2674
+ `[Neo4j Sync] Backfill complete: ${totalNodes} nodes, ${totalEdges} edges, ${totalFailed} failed`
2675
+ );
2676
+ return {
2677
+ totalNodes,
2678
+ totalEdges,
2679
+ totalFailed
2680
+ };
2681
+ }
2682
+ });
2683
+ var processRetryQueue = internalAction({
2684
+ args: {
2685
+ limit: v.optional(v.number())
2686
+ },
2687
+ returns: permissiveReturn,
2688
+ handler: async (ctx, args) => {
2689
+ const limit = args.limit ?? 10;
2690
+ const pendingItems = await ctx.runQuery(
2691
+ internal.neo4jSyncHelpers.getPendingRetries,
2692
+ { limit }
2693
+ );
2694
+ if (pendingItems.length === 0) {
2695
+ return { processed: 0, succeeded: 0, failed: 0 };
2696
+ }
2697
+ let succeeded = 0;
2698
+ let failed = 0;
2699
+ for (const item of pendingItems) {
2700
+ await ctx.runMutation(internal.neo4jSyncHelpers.updateQueueStatus, {
2701
+ queueId: item._id,
2702
+ status: "in_progress"
2703
+ });
2704
+ let result;
2705
+ if (item.entityType === "node") {
2706
+ result = await ctx.runAction(internal.neo4jSync.syncNodeToNeo4j, {
2707
+ nodeId: item.entityId,
2708
+ operation: item.operation
2709
+ });
2710
+ } else {
2711
+ const resolved = await ctx.runQuery(
2712
+ internal.neo4jSyncHelpers.resolveEdgeRetryTarget,
2713
+ {
2714
+ entityId: item.entityId
2715
+ }
2716
+ );
2717
+ if (resolved.mode === "convex_id" || resolved.mode === "global_id_in_convex") {
2718
+ result = await ctx.runAction(internal.neo4jSync.syncEdgeToNeo4j, {
2719
+ edgeId: resolved.edgeId,
2720
+ operation: item.operation
2721
+ });
2722
+ } else {
2723
+ result = await ctx.runAction(
2724
+ internal.neo4jEdgeAPI.retryProjectionByGlobalId,
2725
+ {
2726
+ globalId: resolved.edgeGlobalId
2727
+ }
2728
+ );
2729
+ }
2730
+ }
2731
+ if (result.success) {
2732
+ await ctx.runMutation(internal.neo4jSyncHelpers.updateQueueStatus, {
2733
+ queueId: item._id,
2734
+ status: "succeeded"
2735
+ });
2736
+ succeeded++;
2737
+ } else {
2738
+ const updated = await ctx.runMutation(
2739
+ internal.neo4jSyncHelpers.incrementAttempts,
2740
+ {
2741
+ queueId: item._id,
2742
+ error: result.error || "Unknown error"
2743
+ }
2744
+ );
2745
+ if (updated.failed) {
2746
+ failed++;
2747
+ }
2748
+ }
2749
+ }
2750
+ return { processed: pendingItems.length, succeeded, failed };
2751
+ }
2752
+ });
2753
+ var syncEmbeddingToNeo4j = internalAction({
2754
+ args: {
2755
+ nodeId: v.id("epistemicNodes")
2756
+ },
2757
+ returns: permissiveReturn,
2758
+ handler: async (ctx, args) => {
2759
+ const connInfo = getConnectionInfo();
2760
+ if (!connInfo.configured) {
2761
+ return buildSyncFailure("embedding", "sync", "Missing credentials", {
2762
+ skippedReason: "credentials_missing"
2763
+ });
2764
+ }
2765
+ const node = await ctx.runQuery(internal.neo4jSyncHelpers.getNodeForSync, {
2766
+ nodeId: args.nodeId
2767
+ });
2768
+ if (!node?.globalId) {
2769
+ return buildSyncFailure("embedding", "sync", "Node not found", {
2770
+ skippedReason: "source_node_missing"
2771
+ });
2772
+ }
2773
+ const embedding = await ctx.runQuery(
2774
+ internal.neo4jSyncHelpers.getEmbeddingForSync,
2775
+ { nodeId: args.nodeId }
2776
+ );
2777
+ if (!embedding) {
2778
+ return buildSyncFailure("embedding", "sync", "Embedding not found", {
2779
+ skippedReason: "embedding_missing"
2780
+ });
2781
+ }
2782
+ try {
2783
+ await runWriteTransaction(
2784
+ "MATCH (n {globalId: $globalId}) SET n.embedding = $embedding",
2785
+ { globalId: node.globalId, embedding }
2786
+ );
2787
+ console.log(
2788
+ `[Neo4j Sync] Synced ${embedding.length}-dim embedding for node ${node.globalId}`
2789
+ );
2790
+ return buildSyncSuccess("embedding", "sync");
2791
+ } catch (error) {
2792
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
2793
+ console.error("[Neo4j Sync] Embedding sync error:", errorMsg);
2794
+ return buildSyncFailure("embedding", "sync", errorMsg);
2795
+ }
2796
+ }
2797
+ });
2798
+ var checkNeo4jHealth = internalAction({
2799
+ args: {},
2800
+ returns: permissiveReturn,
2801
+ handler: async () => {
2802
+ const connInfo = getConnectionInfo();
2803
+ if (!connInfo.configured) {
2804
+ return {
2805
+ healthy: false,
2806
+ error: "Neo4j not configured",
2807
+ uri: connInfo.uri
2808
+ };
2809
+ }
2810
+ const health = await healthCheck();
2811
+ return {
2812
+ ...health,
2813
+ uri: connInfo.uri
2814
+ };
2815
+ }
2816
+ });
2817
+ var resyncAllNodes = internalAction({
2818
+ args: {
2819
+ batchSize: v.optional(v.number()),
2820
+ nodeType: v.optional(v.string()),
2821
+ cursor: v.optional(v.string())
2822
+ },
2823
+ returns: permissiveReturn,
2824
+ handler: async (ctx, args) => {
2825
+ const batchSize = args.batchSize ?? 50;
2826
+ const result = await ctx.runQuery(
2827
+ internal.neo4jSyncHelpers.getAllNodesForResync,
2828
+ {
2829
+ nodeType: args.nodeType,
2830
+ limit: batchSize,
2831
+ cursor: args.cursor
2832
+ }
2833
+ );
2834
+ let synced = 0;
2835
+ let failed = 0;
2836
+ for (const node of result.nodes) {
2837
+ try {
2838
+ const syncResult = await ctx.runAction(
2839
+ internal.neo4jSync.syncNodeToNeo4j,
2840
+ {
2841
+ nodeId: node._id,
2842
+ operation: "upsert"
2843
+ }
2844
+ );
2845
+ if (syncResult.success) {
2846
+ synced++;
2847
+ } else {
2848
+ failed++;
2849
+ }
2850
+ } catch (error) {
2851
+ console.error(`[Neo4j Resync] Failed to sync node ${node._id}:`, error);
2852
+ failed++;
2853
+ }
2854
+ }
2855
+ return {
2856
+ synced,
2857
+ failed,
2858
+ total: result.nodes.length,
2859
+ hasMore: result.hasMore,
2860
+ nextCursor: result.nextCursor
2861
+ };
2862
+ }
2863
+ });
2864
+ var resyncAllEdges = internalAction({
2865
+ args: {
2866
+ batchSize: v.optional(v.number()),
2867
+ cursor: v.optional(v.string())
2868
+ },
2869
+ returns: permissiveReturn,
2870
+ handler: async (ctx, args) => {
2871
+ const batchSize = args.batchSize ?? 50;
2872
+ const result = await ctx.runQuery(
2873
+ internal.neo4jSyncHelpers.getAllEdgesForResync,
2874
+ {
2875
+ limit: batchSize,
2876
+ cursor: args.cursor
2877
+ }
2878
+ );
2879
+ let synced = 0;
2880
+ let failed = 0;
2881
+ for (const edge of result.edges) {
2882
+ try {
2883
+ const syncResult = await ctx.runAction(
2884
+ internal.neo4jSync.syncEdgeToNeo4j,
2885
+ {
2886
+ edgeId: edge._id,
2887
+ operation: "upsert"
2888
+ }
2889
+ );
2890
+ if (syncResult.success) {
2891
+ synced++;
2892
+ } else {
2893
+ failed++;
2894
+ }
2895
+ } catch (error) {
2896
+ console.error(`[Neo4j Resync] Failed to sync edge ${edge._id}:`, error);
2897
+ failed++;
2898
+ }
2899
+ }
2900
+ return {
2901
+ synced,
2902
+ failed,
2903
+ total: result.edges.length,
2904
+ hasMore: result.hasMore,
2905
+ nextCursor: result.nextCursor
2906
+ };
2907
+ }
2908
+ });
2909
+
2910
+ // src/neo4jSyncHelpers.ts
2911
+ var neo4jSyncHelpers_exports = {};
2912
+ __export(neo4jSyncHelpers_exports, {
2913
+ checkSyncHealth: () => checkSyncHealth,
2914
+ getAllEdgesForResync: () => getAllEdgesForResync,
2915
+ getAllNodesForResync: () => getAllNodesForResync,
2916
+ getEdgeBatchForSync: () => getEdgeBatchForSync,
2917
+ getEdgeForSync: () => getEdgeForSync,
2918
+ getEmbeddingForSync: () => getEmbeddingForSync,
2919
+ getNodeBatchForSync: () => getNodeBatchForSync,
2920
+ getNodeForSync: () => getNodeForSync,
2921
+ getPendingRetries: () => getPendingRetries,
2922
+ incrementAttempts: () => incrementAttempts,
2923
+ logSyncEvent: () => logSyncEvent,
2924
+ queueForRetry: () => queueForRetry,
2925
+ resolveEdgeRetryTarget: () => resolveEdgeRetryTarget,
2926
+ updateQueueStatus: () => updateQueueStatus
2927
+ });
2928
+ function logRetryTargetFallback(context, error) {
2929
+ const env = globalThis.process?.env;
2930
+ if (env?.LUCERN_GRAPH_SYNC_DEBUG !== "1") {
2931
+ return;
2932
+ }
2933
+ console.debug("[graph-sync][neo4jSyncHelpers]", context, error);
2934
+ }
2935
+ var logSyncEvent = internalMutation({
2936
+ args: {
2937
+ eventType: v.union(
2938
+ v.literal("node_created"),
2939
+ v.literal("node_updated"),
2940
+ v.literal("node_deleted"),
2941
+ v.literal("edge_created"),
2942
+ v.literal("edge_deleted")
2943
+ ),
2944
+ entityId: v.string(),
2945
+ entityType: v.string(),
2946
+ status: v.union(
2947
+ v.literal("pending"),
2948
+ v.literal("success"),
2949
+ v.literal("failed")
2950
+ ),
2951
+ error: v.optional(v.string())
2952
+ },
2953
+ returns: permissiveReturn,
2954
+ handler: async (_ctx, args) => {
2955
+ console.log(
2956
+ `[Neo4j Sync] ${args.eventType} ${args.entityType}:${args.entityId} - ${args.status}`,
2957
+ args.error || ""
2958
+ );
2959
+ }
2960
+ });
2961
+ var getNodeForSync = internalQuery({
2962
+ args: { nodeId: v.id("epistemicNodes") },
2963
+ returns: permissiveReturn,
2964
+ handler: async (ctx, args) => {
2965
+ return await ctx.db.get(args.nodeId);
2966
+ }
2967
+ });
2968
+ var getEmbeddingForSync = internalQuery({
2969
+ args: { nodeId: v.id("epistemicNodes") },
2970
+ returns: permissiveReturn,
2971
+ handler: async (ctx, args) => {
2972
+ const record = await ctx.db.query("epistemicNodeEmbeddings").withIndex("by_nodeId", (q) => q.eq("nodeId", args.nodeId)).first();
2973
+ return record?.embedding ?? null;
2974
+ }
2975
+ });
2976
+ var getAllNodesForResync = internalQuery({
2977
+ args: {
2978
+ nodeType: v.optional(v.string()),
2979
+ limit: v.optional(v.number()),
2980
+ cursor: v.optional(v.string())
2981
+ },
2982
+ returns: permissiveReturn,
2983
+ handler: async (ctx, args) => {
2984
+ const limit = args.limit ?? 100;
2985
+ const paginationOpts = {
2986
+ numItems: limit,
2987
+ cursor: args.cursor ?? null
2988
+ };
2989
+ if (args.nodeType) {
2990
+ const result2 = await ctx.db.query("epistemicNodes").withIndex(
2991
+ "by_nodeType",
2992
+ (q) => q.eq("nodeType", args.nodeType)
2993
+ ).paginate(paginationOpts);
2994
+ return {
2995
+ nodes: result2.page,
2996
+ hasMore: !result2.isDone,
2997
+ nextCursor: result2.continueCursor
2998
+ };
2999
+ }
3000
+ const result = await ctx.db.query("epistemicNodes").order("asc").paginate(paginationOpts);
3001
+ return {
3002
+ nodes: result.page,
3003
+ hasMore: !result.isDone,
3004
+ nextCursor: result.continueCursor
3005
+ };
3006
+ }
3007
+ });
3008
+ var getEdgeForSync = internalQuery({
3009
+ args: { edgeId: v.id("epistemicEdges") },
3010
+ returns: permissiveReturn,
3011
+ handler: async (ctx, args) => {
3012
+ const edge = await ctx.db.get(args.edgeId);
3013
+ if (!edge) {
3014
+ return null;
3015
+ }
3016
+ const fromNode = edge.fromNodeId ? await ctx.db.get(edge.fromNodeId) : null;
3017
+ const toNode = edge.toNodeId ? await ctx.db.get(edge.toNodeId) : null;
3018
+ return {
3019
+ ...edge,
3020
+ // Cross-graph edges may not have toNodeId/fromNodeId in Convex mirror.
3021
+ // Fall back to denormalized global IDs when node lookup is unavailable.
3022
+ fromGlobalId: fromNode?.globalId || edge.sourceGlobalId,
3023
+ toGlobalId: toNode?.globalId || edge.targetGlobalId
3024
+ };
3025
+ }
3026
+ });
3027
+ var getAllEdgesForResync = internalQuery({
3028
+ args: {
3029
+ limit: v.optional(v.number()),
3030
+ cursor: v.optional(v.string())
3031
+ },
3032
+ returns: permissiveReturn,
3033
+ handler: async (ctx, args) => {
3034
+ const limit = args.limit ?? 100;
3035
+ const paginationOpts = {
3036
+ numItems: limit,
3037
+ cursor: args.cursor ?? null
3038
+ };
3039
+ const result = await ctx.db.query("epistemicEdges").order("asc").paginate(paginationOpts);
3040
+ return {
3041
+ edges: result.page,
3042
+ hasMore: !result.isDone,
3043
+ nextCursor: result.continueCursor
3044
+ };
3045
+ }
3046
+ });
3047
+ var getNodeBatchForSync = internalQuery({
3048
+ args: {
3049
+ limit: v.number(),
3050
+ cursor: v.optional(v.string())
3051
+ },
3052
+ returns: permissiveReturn,
3053
+ handler: async (ctx, args) => {
3054
+ const paginationOpts = {
3055
+ numItems: args.limit,
3056
+ cursor: args.cursor ?? null
3057
+ };
3058
+ const result = await ctx.db.query("epistemicNodes").order("asc").paginate(paginationOpts);
3059
+ return {
3060
+ nodes: result.page,
3061
+ hasMore: !result.isDone,
3062
+ nextCursor: result.continueCursor
3063
+ };
3064
+ }
3065
+ });
3066
+ var getEdgeBatchForSync = internalQuery({
3067
+ args: {
3068
+ limit: v.number(),
3069
+ cursor: v.optional(v.string())
3070
+ },
3071
+ returns: permissiveReturn,
3072
+ handler: async (ctx, args) => {
3073
+ const paginationOpts = {
3074
+ numItems: args.limit,
3075
+ cursor: args.cursor ?? null
3076
+ };
3077
+ const result = await ctx.db.query("epistemicEdges").order("asc").paginate(paginationOpts);
3078
+ const enrichedEdges = await Promise.all(
3079
+ result.page.map(async (edge) => {
3080
+ const fromNode = await ctx.db.get(edge.fromNodeId);
3081
+ const toNode = edge.toNodeId ? await ctx.db.get(edge.toNodeId) : null;
3082
+ return {
3083
+ ...edge,
3084
+ fromGlobalId: fromNode?.globalId || edge.sourceGlobalId,
3085
+ toGlobalId: toNode?.globalId || edge.targetGlobalId
3086
+ };
3087
+ })
3088
+ );
3089
+ return {
3090
+ edges: enrichedEdges,
3091
+ hasMore: !result.isDone,
3092
+ nextCursor: result.continueCursor
3093
+ };
3094
+ }
3095
+ });
3096
+ var resolveEdgeRetryTarget = internalQuery({
3097
+ args: {
3098
+ entityId: v.string()
3099
+ },
3100
+ returns: permissiveReturn,
3101
+ handler: async (ctx, args) => {
3102
+ try {
3103
+ const byId = await ctx.db.get(args.entityId);
3104
+ if (byId && "edgeType" in byId && "fromNodeId" in byId) {
3105
+ return {
3106
+ mode: "convex_id",
3107
+ edgeId: byId._id,
3108
+ edgeGlobalId: byId.globalId
3109
+ };
3110
+ }
3111
+ } catch (error) {
3112
+ logRetryTargetFallback(
3113
+ `direct edge lookup fallback for entityId=${args.entityId}`,
3114
+ error
3115
+ );
3116
+ }
3117
+ const byGlobalId = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.entityId)).first();
3118
+ if (byGlobalId) {
3119
+ return {
3120
+ mode: "global_id_in_convex",
3121
+ edgeId: byGlobalId._id,
3122
+ edgeGlobalId: byGlobalId.globalId
3123
+ };
3124
+ }
3125
+ return {
3126
+ mode: "global_id_only",
3127
+ edgeGlobalId: args.entityId
3128
+ };
3129
+ }
3130
+ });
3131
+ var queueForRetry = internalMutation({
3132
+ args: {
3133
+ entityType: v.union(v.literal("node"), v.literal("edge")),
3134
+ entityId: v.string(),
3135
+ operation: v.union(v.literal("upsert"), v.literal("delete")),
3136
+ error: v.string()
3137
+ },
3138
+ returns: permissiveReturn,
3139
+ handler: async (ctx, args) => {
3140
+ const now = Date.now();
3141
+ const existing = await ctx.db.query("neo4jSyncQueue").withIndex(
3142
+ "by_entity",
3143
+ (q) => q.eq("entityType", args.entityType).eq("entityId", args.entityId)
3144
+ ).first();
3145
+ if (existing) {
3146
+ const attempts = existing.attempts ?? 0;
3147
+ const maxAttempts = existing.maxAttempts ?? 5;
3148
+ await ctx.db.patch(existing._id, {
3149
+ attempts: attempts + 1,
3150
+ maxAttempts,
3151
+ lastAttemptAt: now,
3152
+ lastError: args.error,
3153
+ status: attempts + 1 >= maxAttempts ? "failed" : "pending",
3154
+ updatedAt: now
3155
+ });
3156
+ return { queued: true, updated: true, queueId: existing._id };
3157
+ }
3158
+ const queueId = await ctx.db.insert("neo4jSyncQueue", {
3159
+ entityType: args.entityType,
3160
+ entityId: args.entityId,
3161
+ operation: args.operation,
3162
+ attempts: 1,
3163
+ maxAttempts: 5,
3164
+ lastAttemptAt: now,
3165
+ lastError: args.error,
3166
+ status: "pending",
3167
+ createdAt: now,
3168
+ updatedAt: now
3169
+ });
3170
+ return { queued: true, updated: false, queueId };
3171
+ }
3172
+ });
3173
+ var getPendingRetries = internalQuery({
3174
+ args: {
3175
+ limit: v.number()
3176
+ },
3177
+ returns: permissiveReturn,
3178
+ handler: async (ctx, args) => {
3179
+ return await ctx.db.query("neo4jSyncQueue").withIndex("by_status", (q) => q.eq("status", "pending")).take(args.limit);
3180
+ }
3181
+ });
3182
+ var updateQueueStatus = internalMutation({
3183
+ args: {
3184
+ queueId: v.id("neo4jSyncQueue"),
3185
+ status: v.union(
3186
+ v.literal("pending"),
3187
+ v.literal("in_progress"),
3188
+ v.literal("failed"),
3189
+ v.literal("succeeded")
3190
+ )
3191
+ },
3192
+ returns: permissiveReturn,
3193
+ handler: async (ctx, args) => {
3194
+ await ctx.db.patch(args.queueId, {
3195
+ status: args.status,
3196
+ updatedAt: Date.now()
3197
+ });
3198
+ }
3199
+ });
3200
+ var incrementAttempts = internalMutation({
3201
+ args: {
3202
+ queueId: v.id("neo4jSyncQueue"),
3203
+ error: v.string()
3204
+ },
3205
+ returns: permissiveReturn,
3206
+ handler: async (ctx, args) => {
3207
+ const item = await ctx.db.get(args.queueId);
3208
+ if (!item) {
3209
+ return { failed: false };
3210
+ }
3211
+ const attempts = item.attempts ?? 0;
3212
+ const maxAttempts = item.maxAttempts ?? 5;
3213
+ const newAttempts = attempts + 1;
3214
+ const isFailed = newAttempts >= maxAttempts;
3215
+ await ctx.db.patch(args.queueId, {
3216
+ attempts: newAttempts,
3217
+ maxAttempts,
3218
+ lastAttemptAt: Date.now(),
3219
+ lastError: args.error,
3220
+ status: isFailed ? "failed" : "pending",
3221
+ updatedAt: Date.now()
3222
+ });
3223
+ return { failed: isFailed };
3224
+ }
3225
+ });
3226
+ var checkSyncHealth = internalQuery({
3227
+ args: {},
3228
+ returns: permissiveReturn,
3229
+ handler: async (ctx) => {
3230
+ const now = Date.now();
3231
+ const oneHourAgo = now - 60 * 60 * 1e3;
3232
+ const recentNodes = await ctx.db.query("epistemicNodes").filter((q) => q.gte(q.field("updatedAt"), oneHourAgo)).collect();
3233
+ const recentEdges = await ctx.db.query("epistemicEdges").filter((q) => q.gte(q.field("createdAt"), oneHourAgo)).collect();
3234
+ const pendingRetries = await ctx.db.query("neo4jSyncQueue").withIndex("by_status", (q) => q.eq("status", "pending")).collect();
3235
+ const failedRetries = await ctx.db.query("neo4jSyncQueue").withIndex("by_status", (q) => q.eq("status", "failed")).collect();
3236
+ return {
3237
+ recentNodesUpdated: recentNodes.length,
3238
+ recentEdgesUpdated: recentEdges.length,
3239
+ pendingRetries: pendingRetries.length,
3240
+ failedRetries: failedRetries.length,
3241
+ healthStatus: failedRetries.length > 10 ? "unhealthy" : pendingRetries.length > 50 ? "degraded" : "healthy",
3242
+ checkedAt: now
3243
+ };
3244
+ }
3245
+ });
3246
+
3247
+ export { neo4jDriver_exports as neo4jDriver, neo4jEdgeAPI_exports as neo4jEdgeAPI, neo4jQueries_exports as neo4jQueries, neo4jQueryRoute_exports as neo4jQueryRoute, neo4jSync_exports as neo4jSync, neo4jSyncHelpers_exports as neo4jSyncHelpers };
3248
+ //# sourceMappingURL=index.js.map
3249
+ //# sourceMappingURL=index.js.map