@lucern/graph-primitives 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/dist/{beliefDecay-DZ6tkLYq.d.ts → beliefDecay-BmkEk5OJ.d.ts} +3 -3
  2. package/dist/beliefDecay.d.ts +1 -1
  3. package/dist/beliefDecay.js +448 -314
  4. package/dist/beliefDecay.js.map +1 -1
  5. package/dist/{beliefEvidenceLinks-CWOXxxJg.d.ts → beliefEvidenceLinks-BzfjON_6.d.ts} +13 -13
  6. package/dist/beliefEvidenceLinks.d.ts +1 -1
  7. package/dist/beliefEvidenceLinks.js +843 -624
  8. package/dist/beliefEvidenceLinks.js.map +1 -1
  9. package/dist/beliefEvidenceLinks.operational.d.ts +7 -5
  10. package/dist/beliefEvidenceLinks.operational.js +91 -18
  11. package/dist/beliefEvidenceLinks.operational.js.map +1 -1
  12. package/dist/beliefLifecycle.js.map +1 -1
  13. package/dist/confidencePropagationDispatch.d.ts +28 -27
  14. package/dist/confidencePropagationDispatch.js +157 -99
  15. package/dist/confidencePropagationDispatch.js.map +1 -1
  16. package/dist/{contradictions-51VLsESq.d.ts → contradictions-BATPuZTL.d.ts} +10 -10
  17. package/dist/contradictions.d.ts +1 -1
  18. package/dist/contradictions.js +395 -225
  19. package/dist/contradictions.js.map +1 -1
  20. package/dist/convex.d.ts +65 -30
  21. package/dist/convex.js +7 -3
  22. package/dist/convex.js.map +1 -1
  23. package/dist/debug.js.map +1 -1
  24. package/dist/edgeValidation.js +293 -85
  25. package/dist/edgeValidation.js.map +1 -1
  26. package/dist/edges/contains.d.ts +1 -1
  27. package/dist/edges/contains.js.map +1 -1
  28. package/dist/edges/contradicts.d.ts +1 -1
  29. package/dist/edges/contradicts.js.map +1 -1
  30. package/dist/edges/{dependsOn.d.ts → depends-on.d.ts} +1 -1
  31. package/dist/edges/{dependsOn.js → depends-on.js} +4 -4
  32. package/dist/edges/depends-on.js.map +1 -0
  33. package/dist/edges/{derivedFrom.d.ts → derived-from.d.ts} +1 -1
  34. package/dist/edges/{derivedFrom.js → derived-from.js} +3 -3
  35. package/dist/edges/derived-from.js.map +1 -0
  36. package/dist/edges/elaborates.d.ts +1 -1
  37. package/dist/edges/elaborates.js.map +1 -1
  38. package/dist/edges/index.d.ts +7 -3
  39. package/dist/edges/index.js +7 -4
  40. package/dist/edges/index.js.map +1 -1
  41. package/dist/edges/informs.d.ts +1 -1
  42. package/dist/edges/informs.js.map +1 -1
  43. package/dist/edges/{propagationTypes.d.ts → propagation-types.d.ts} +14 -14
  44. package/dist/edges/{propagationTypes.js → propagation-types.js} +3 -3
  45. package/dist/edges/propagation-types.js.map +1 -0
  46. package/dist/edges/refutes.d.ts +1 -1
  47. package/dist/edges/refutes.js.map +1 -1
  48. package/dist/edges/supports.d.ts +1 -1
  49. package/dist/edges/supports.js.map +1 -1
  50. package/dist/edges/tests.d.ts +1 -1
  51. package/dist/edges/tests.js.map +1 -1
  52. package/dist/edges/utils.d.ts +1 -1
  53. package/dist/edges/utils.js.map +1 -1
  54. package/dist/embeddingTrigger.d.ts +14 -6
  55. package/dist/embeddingTrigger.js +11 -14
  56. package/dist/embeddingTrigger.js.map +1 -1
  57. package/dist/{entityBridge-DMaKooYn.d.ts → entityBridge-BhVDM3pc.d.ts} +5 -5
  58. package/dist/entityBridge.d.ts +1 -1
  59. package/dist/entityBridge.js +602 -225
  60. package/dist/entityBridge.js.map +1 -1
  61. package/dist/entityCanonicalMatch.d.ts +14 -12
  62. package/dist/entityCanonicalMatch.js.map +1 -1
  63. package/dist/{entityLifecycle-CvgSK5FV.d.ts → entityLifecycle-BsfCz9pS.d.ts} +5 -9
  64. package/dist/entityLifecycle.d.ts +1 -1
  65. package/dist/entityLifecycle.js +854 -480
  66. package/dist/entityLifecycle.js.map +1 -1
  67. package/dist/{entityValidation-KLZ_Xl2D.d.ts → entityValidation-B1yNEHJx.d.ts} +7 -6
  68. package/dist/entityValidation.d.ts +3 -1
  69. package/dist/entityValidation.js +60 -8
  70. package/dist/entityValidation.js.map +1 -1
  71. package/dist/{epistemicAnswers-C5ib4z6_.d.ts → epistemicAnswers-f47YMu9U.d.ts} +6 -6
  72. package/dist/epistemicAnswers.d.ts +1 -1
  73. package/dist/epistemicAnswers.js +587 -545
  74. package/dist/epistemicAnswers.js.map +1 -1
  75. package/dist/epistemicBeliefs.admin.d.ts +8 -8
  76. package/dist/epistemicBeliefs.admin.js +365 -166
  77. package/dist/epistemicBeliefs.admin.js.map +1 -1
  78. package/dist/epistemicBeliefs.backfills.d.ts +8 -8
  79. package/dist/epistemicBeliefs.backfills.js +655 -289
  80. package/dist/epistemicBeliefs.backfills.js.map +1 -1
  81. package/dist/epistemicBeliefs.confidence.d.ts +19 -15
  82. package/dist/epistemicBeliefs.confidence.js +633 -386
  83. package/dist/epistemicBeliefs.confidence.js.map +1 -1
  84. package/dist/epistemicBeliefs.core.d.ts +6 -6
  85. package/dist/epistemicBeliefs.core.js +717 -371
  86. package/dist/epistemicBeliefs.core.js.map +1 -1
  87. package/dist/epistemicBeliefs.d.ts +11 -9
  88. package/dist/epistemicBeliefs.forkEvidence.d.ts +2 -0
  89. package/dist/epistemicBeliefs.forkEvidence.js +8 -8
  90. package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
  91. package/dist/epistemicBeliefs.helpers.d.ts +68 -49
  92. package/dist/epistemicBeliefs.helpers.js +358 -211
  93. package/dist/epistemicBeliefs.helpers.js.map +1 -1
  94. package/dist/epistemicBeliefs.internal.d.ts +5 -5
  95. package/dist/epistemicBeliefs.internal.js +1248 -1026
  96. package/dist/epistemicBeliefs.internal.js.map +1 -1
  97. package/dist/epistemicBeliefs.js +4942 -3590
  98. package/dist/epistemicBeliefs.js.map +1 -1
  99. package/dist/epistemicBeliefs.lifecycle.d.ts +5 -5
  100. package/dist/epistemicBeliefs.lifecycle.js +1138 -781
  101. package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
  102. package/dist/epistemicBeliefs.links.d.ts +7 -7
  103. package/dist/epistemicBeliefs.links.js +404 -267
  104. package/dist/epistemicBeliefs.links.js.map +1 -1
  105. package/dist/epistemicBeliefs.queries.d.ts +4 -4
  106. package/dist/epistemicBeliefs.queries.js +175 -20
  107. package/dist/epistemicBeliefs.queries.js.map +1 -1
  108. package/dist/epistemicBeliefs.topicAnchor.d.ts +6 -4
  109. package/dist/epistemicBeliefs.topicAnchor.js +12 -5
  110. package/dist/epistemicBeliefs.topicAnchor.js.map +1 -1
  111. package/dist/epistemicContracts.d.ts +28 -3
  112. package/dist/epistemicContracts.evaluators.d.ts +2 -0
  113. package/dist/epistemicContracts.evaluators.js +1062 -576
  114. package/dist/epistemicContracts.evaluators.js.map +1 -1
  115. package/dist/epistemicContracts.handlers.d.ts +15 -32
  116. package/dist/epistemicContracts.handlers.js +1829 -1351
  117. package/dist/epistemicContracts.handlers.js.map +1 -1
  118. package/dist/epistemicContracts.js +1131 -636
  119. package/dist/epistemicContracts.js.map +1 -1
  120. package/dist/epistemicContracts.metrics.d.ts +2 -0
  121. package/dist/epistemicContracts.metrics.js +375 -158
  122. package/dist/epistemicContracts.metrics.js.map +1 -1
  123. package/dist/epistemicContracts.types.d.ts +87 -81
  124. package/dist/epistemicEdgeCreation.d.ts +2 -0
  125. package/dist/epistemicEdgeCreation.js +87 -16
  126. package/dist/epistemicEdgeCreation.js.map +1 -1
  127. package/dist/{epistemicEdges-BF-cn4i3.d.ts → epistemicEdges-BGBh0QSP.d.ts} +4 -7
  128. package/dist/epistemicEdges.d.ts +6 -5
  129. package/dist/epistemicEdges.handlers.d.ts +3 -3
  130. package/dist/epistemicEdges.handlers.js +129 -24
  131. package/dist/epistemicEdges.handlers.js.map +1 -1
  132. package/dist/epistemicEdges.helpers.d.ts +6 -4
  133. package/dist/epistemicEdges.helpers.js +37 -2
  134. package/dist/epistemicEdges.helpers.js.map +1 -1
  135. package/dist/epistemicEdges.js +1966 -1202
  136. package/dist/epistemicEdges.js.map +1 -1
  137. package/dist/epistemicEdges.mutations.d.ts +7 -7
  138. package/dist/epistemicEdges.mutations.js +956 -579
  139. package/dist/epistemicEdges.mutations.js.map +1 -1
  140. package/dist/epistemicEdges.queries.d.ts +16 -16
  141. package/dist/epistemicEdges.queries.js +639 -367
  142. package/dist/epistemicEdges.queries.js.map +1 -1
  143. package/dist/epistemicEdges.types.d.ts +10 -8
  144. package/dist/epistemicEvidence.d.ts +4 -1
  145. package/dist/epistemicEvidence.js +933 -532
  146. package/dist/epistemicEvidence.js.map +1 -1
  147. package/dist/epistemicEvidenceHelpers.d.ts +26 -10
  148. package/dist/epistemicEvidenceHelpers.js +239 -200
  149. package/dist/epistemicEvidenceHelpers.js.map +1 -1
  150. package/dist/epistemicEvidenceMutations.d.ts +8 -8
  151. package/dist/epistemicEvidenceMutations.js +840 -692
  152. package/dist/epistemicEvidenceMutations.js.map +1 -1
  153. package/dist/epistemicEvidenceQueries.d.ts +8 -8
  154. package/dist/epistemicEvidenceQueries.js +514 -238
  155. package/dist/epistemicEvidenceQueries.js.map +1 -1
  156. package/dist/epistemicHelpers.d.ts +4 -2
  157. package/dist/epistemicHelpers.js +308 -134
  158. package/dist/epistemicHelpers.js.map +1 -1
  159. package/dist/epistemicInsert.d.ts +16 -4
  160. package/dist/epistemicInsert.js +6 -3
  161. package/dist/epistemicInsert.js.map +1 -1
  162. package/dist/epistemicLayerRules.d.ts +10 -8
  163. package/dist/epistemicLayerRules.js +1 -5
  164. package/dist/epistemicLayerRules.js.map +1 -1
  165. package/dist/{epistemicLinking-CfE00tHJ.d.ts → epistemicLinking-CsCDv2cN.d.ts} +3 -3
  166. package/dist/epistemicLinking.d.ts +1 -1
  167. package/dist/epistemicLinking.js +177 -100
  168. package/dist/epistemicLinking.js.map +1 -1
  169. package/dist/epistemicNodeCreation.d.ts +2 -0
  170. package/dist/epistemicNodeCreation.js +203 -40
  171. package/dist/epistemicNodeCreation.js.map +1 -1
  172. package/dist/{epistemicNodes-BCQxpYx_.d.ts → epistemicNodes-CokAgBHg.d.ts} +3 -3
  173. package/dist/epistemicNodes.d.ts +3 -3
  174. package/dist/epistemicNodes.helpers.d.ts +24 -15
  175. package/dist/epistemicNodes.helpers.js.map +1 -1
  176. package/dist/epistemicNodes.internal.d.ts +6 -6
  177. package/dist/epistemicNodes.internal.js +389 -319
  178. package/dist/epistemicNodes.internal.js.map +1 -1
  179. package/dist/epistemicNodes.js +700 -504
  180. package/dist/epistemicNodes.js.map +1 -1
  181. package/dist/epistemicNodes.mutations.d.ts +6 -6
  182. package/dist/epistemicNodes.mutations.js +560 -463
  183. package/dist/epistemicNodes.mutations.js.map +1 -1
  184. package/dist/epistemicNodes.queries.d.ts +8 -8
  185. package/dist/epistemicNodes.queries.js +311 -314
  186. package/dist/epistemicNodes.queries.js.map +1 -1
  187. package/dist/epistemicNodes.validators.d.ts +2 -2
  188. package/dist/epistemicNodes.validators.js.map +1 -1
  189. package/dist/epistemicQuestions.conviction.d.ts +8 -8
  190. package/dist/epistemicQuestions.conviction.js +665 -484
  191. package/dist/epistemicQuestions.conviction.js.map +1 -1
  192. package/dist/epistemicQuestions.create.d.ts +4 -4
  193. package/dist/epistemicQuestions.create.js +640 -612
  194. package/dist/epistemicQuestions.create.js.map +1 -1
  195. package/dist/epistemicQuestions.d.ts +8 -5
  196. package/dist/epistemicQuestions.evidence.d.ts +2 -2
  197. package/dist/epistemicQuestions.evidence.js +475 -383
  198. package/dist/epistemicQuestions.evidence.js.map +1 -1
  199. package/dist/epistemicQuestions.helpers.d.ts +125 -24
  200. package/dist/epistemicQuestions.helpers.js +240 -209
  201. package/dist/epistemicQuestions.helpers.js.map +1 -1
  202. package/dist/epistemicQuestions.js +3474 -2823
  203. package/dist/epistemicQuestions.js.map +1 -1
  204. package/dist/epistemicQuestions.lifecycle.d.ts +2 -2
  205. package/dist/epistemicQuestions.lifecycle.js +607 -546
  206. package/dist/epistemicQuestions.lifecycle.js.map +1 -1
  207. package/dist/epistemicQuestions.queries.d.ts +12 -7
  208. package/dist/epistemicQuestions.queries.js +305 -244
  209. package/dist/epistemicQuestions.queries.js.map +1 -1
  210. package/dist/epistemicQuestions.sprint.d.ts +2 -2
  211. package/dist/epistemicQuestions.sprint.js +600 -394
  212. package/dist/epistemicQuestions.sprint.js.map +1 -1
  213. package/dist/epistemicQuestions.tail.d.ts +6 -6
  214. package/dist/epistemicQuestions.tail.js +572 -433
  215. package/dist/epistemicQuestions.tail.js.map +1 -1
  216. package/dist/{epistemicSources-dlKj58Jp.d.ts → epistemicSources-DQtaEkWs.d.ts} +4 -4
  217. package/dist/epistemicSources.d.ts +1 -1
  218. package/dist/epistemicSources.js +351 -311
  219. package/dist/epistemicSources.js.map +1 -1
  220. package/dist/evaluators/index.d.ts +8 -6
  221. package/dist/evaluators/index.js +399 -167
  222. package/dist/evaluators/index.js.map +1 -1
  223. package/dist/evaluators/lint-checker-evaluator.d.ts +16 -0
  224. package/dist/evaluators/{lintCheckerEvaluator.js → lint-checker-evaluator.js} +10 -5
  225. package/dist/evaluators/lint-checker-evaluator.js.map +1 -0
  226. package/dist/evaluators/{sentryCheckerEvaluator.d.ts → sentry-checker-evaluator.d.ts} +7 -2
  227. package/dist/evaluators/{sentryCheckerEvaluator.js → sentry-checker-evaluator.js} +3 -3
  228. package/dist/evaluators/sentry-checker-evaluator.js.map +1 -0
  229. package/dist/evaluators/shared.d.ts +2 -2
  230. package/dist/evaluators/shared.js +3 -1
  231. package/dist/evaluators/shared.js.map +1 -1
  232. package/dist/evaluators/{testRunnerEvaluator.d.ts → test-runner-evaluator.d.ts} +6 -1
  233. package/dist/evaluators/{testRunnerEvaluator.js → test-runner-evaluator.js} +6 -4
  234. package/dist/evaluators/test-runner-evaluator.js.map +1 -0
  235. package/dist/evaluators/tsc-checker-evaluator.d.ts +16 -0
  236. package/dist/evaluators/{tscCheckerEvaluator.js → tsc-checker-evaluator.js} +10 -5
  237. package/dist/evaluators/tsc-checker-evaluator.js.map +1 -0
  238. package/dist/graphTypes.js +6 -2
  239. package/dist/graphTypes.js.map +1 -1
  240. package/dist/helpers.d.ts +2 -0
  241. package/dist/helpers.js +313 -93
  242. package/dist/helpers.js.map +1 -1
  243. package/dist/{index-C-Kyd7hD.d.ts → index-DZxyC9Pb.d.ts} +7 -6
  244. package/dist/index.d.ts +86 -83
  245. package/dist/index.js +16914 -11760
  246. package/dist/index.js.map +1 -1
  247. package/dist/invariantEnforcement.d.ts +3 -3
  248. package/dist/invariantEnforcement.js.map +1 -1
  249. package/dist/logicalRoleInference.d.ts +2 -0
  250. package/dist/logicalRoleInference.js +1 -1
  251. package/dist/logicalRoleInference.js.map +1 -1
  252. package/dist/matcherFeedbackUtils.d.ts +2 -2
  253. package/dist/matcherFeedbackUtils.js.map +1 -1
  254. package/dist/{ontology-matching-C6rrz2VP.d.ts → ontology-matching-C-mYFrir.d.ts} +16 -16
  255. package/dist/ontology-matching.d.ts +1 -1
  256. package/dist/{ontologyApproval-CFYmqKmk.d.ts → ontologyApproval-BVt0feJi.d.ts} +10 -10
  257. package/dist/ontologyApproval.d.ts +1 -1
  258. package/dist/ontologyApproval.js +7 -1
  259. package/dist/ontologyApproval.js.map +1 -1
  260. package/dist/ontologyDefinitions.d.ts +14 -24
  261. package/dist/ontologyDefinitions.js +269 -34
  262. package/dist/ontologyDefinitions.js.map +1 -1
  263. package/dist/ontologyHelpers.d.ts +13 -13
  264. package/dist/ontologyHelpers.js.map +1 -1
  265. package/dist/{ontologyRegistry-B67rPJ16.d.ts → ontologyRegistry-CljS-ENv.d.ts} +2 -2
  266. package/dist/ontologyRegistry.d.ts +1 -1
  267. package/dist/ontologyRegistry.js +34 -6
  268. package/dist/ontologyRegistry.js.map +1 -1
  269. package/dist/{projectionReconciliation-jww2fBI0.d.ts → projectionReconciliation-DnrSgHSQ.d.ts} +4 -4
  270. package/dist/projectionReconciliation.d.ts +1 -1
  271. package/dist/projectionReconciliation.js +57 -10
  272. package/dist/projectionReconciliation.js.map +1 -1
  273. package/dist/{projectionStaleness-CmdbpjVK.d.ts → projectionStaleness-C8ImQ2zP.d.ts} +17 -17
  274. package/dist/projectionStaleness.d.ts +1 -1
  275. package/dist/projectionStaleness.js +8 -2
  276. package/dist/projectionStaleness.js.map +1 -1
  277. package/dist/proof-attestation.json +1 -1
  278. package/dist/{questionEvidenceLinks-DFlyPpAj.d.ts → questionEvidenceLinks-_nPRa-LY.d.ts} +10 -10
  279. package/dist/questionEvidenceLinks.d.ts +1 -1
  280. package/dist/questionEvidenceLinks.js +564 -347
  281. package/dist/questionEvidenceLinks.js.map +1 -1
  282. package/dist/{resolverTypes-CC8Ea2E2.d.ts → resolverTypes-BOXPxLET.d.ts} +8 -7
  283. package/dist/resolverTypes.d.ts +4 -2
  284. package/dist/{resolvers-Br1a6eLV.d.ts → resolvers-B1TIBmRO.d.ts} +3 -1
  285. package/dist/resolvers.d.ts +5 -3
  286. package/dist/resolvers.js +121 -77
  287. package/dist/resolvers.js.map +1 -1
  288. package/dist/scopeResolverCompat.d.ts +10 -7
  289. package/dist/scopeResolverCompat.js +106 -123
  290. package/dist/scopeResolverCompat.js.map +1 -1
  291. package/dist/{text-matching-DNg4M5Wd.d.ts → text-matching-DzFooju6.d.ts} +7 -7
  292. package/dist/text-matching.d.ts +1 -1
  293. package/dist/topicOntologyResolver.d.ts +22 -21
  294. package/dist/topicOntologyResolver.js +54 -32
  295. package/dist/topicOntologyResolver.js.map +1 -1
  296. package/dist/topicProjectOverlay.d.ts +30 -20
  297. package/dist/topicProjectOverlay.js +120 -76
  298. package/dist/topicProjectOverlay.js.map +1 -1
  299. package/dist/{topicScope-7zhyeGl7.d.ts → topicScope-DJVa0mLa.d.ts} +22 -7
  300. package/dist/topicScope.d.ts +3 -1
  301. package/dist/topicScope.js +104 -119
  302. package/dist/topicScope.js.map +1 -1
  303. package/dist/workflowBridge.d.ts +26 -15
  304. package/dist/workflowBridge.js +140 -144
  305. package/dist/workflowBridge.js.map +1 -1
  306. package/dist/workspaceIsolation.d.ts +14 -12
  307. package/dist/workspaceIsolation.js +108 -122
  308. package/dist/workspaceIsolation.js.map +1 -1
  309. package/package.json +4 -4
  310. package/dist/edges/dependsOn.js.map +0 -1
  311. package/dist/edges/derivedFrom.js.map +0 -1
  312. package/dist/edges/propagationTypes.js.map +0 -1
  313. package/dist/evaluators/lintCheckerEvaluator.d.ts +0 -11
  314. package/dist/evaluators/lintCheckerEvaluator.js.map +0 -1
  315. package/dist/evaluators/sentryCheckerEvaluator.js.map +0 -1
  316. package/dist/evaluators/testRunnerEvaluator.js.map +0 -1
  317. package/dist/evaluators/tscCheckerEvaluator.d.ts +0 -11
  318. package/dist/evaluators/tscCheckerEvaluator.js.map +0 -1
  319. package/dist/{epistemicQuestions-bwHd2FWE.d.ts → epistemicQuestions-Do1fhYm5.d.ts} +4 -4
@@ -1,29 +1,52 @@
1
- import { v } from 'convex/values';
2
- import { checkScopeAccess, requireScopeWriteAccess } from '@lucern/access-control/access';
1
+ import { REASONING_METHODS, assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
3
2
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
- import { componentsGeneric, anyApi, queryGeneric, mutationGeneric, internalMutationGeneric } from 'convex/server';
3
+ import { v } from 'convex/values';
4
+ import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
5
+ import { componentsGeneric, internalMutationGeneric, mutationGeneric, queryGeneric } from 'convex/server';
5
6
  import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
6
- import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
7
+ import { requireScopeWriteAccess, checkScopeAccess } from '@lucern/access-control/access';
7
8
 
8
- // src/epistemicEdges.queries.ts
9
-
10
- // src/debug.ts
11
- function isGraphPrimitiveDebugEnabled() {
12
- const env = globalThis.process?.env;
13
- return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
14
- }
15
- function debugGraphPrimitiveFallback(message, context) {
16
- if (!isGraphPrimitiveDebugEnabled()) {
17
- return;
18
- }
19
- console.debug(message, context ?? {});
20
- }
21
- var api = anyApi;
9
+ // src/epistemicEdges.handlers.ts
10
+ var unsafeApi = unsafeConvexAnyApi(
11
+ "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
12
+ );
13
+ var api = unsafeApi;
22
14
  componentsGeneric();
23
- var internal = anyApi;
15
+ var internal = unsafeApi;
24
16
  var internalMutation = internalMutationGeneric;
25
17
  var mutation = mutationGeneric;
26
18
  var query = queryGeneric;
19
+ function readOptionalString(value) {
20
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
21
+ }
22
+ function readConvexId(value) {
23
+ const normalized = readOptionalString(value);
24
+ return normalized;
25
+ }
26
+ function readRecord(value) {
27
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
28
+ }
29
+ function readWorkspaceScopedEdge(value) {
30
+ const record = readRecord(value);
31
+ if (!record) {
32
+ return null;
33
+ }
34
+ const id = readConvexId(record._id);
35
+ const edgeType = readOptionalString(record.edgeType);
36
+ if (!(id && edgeType)) {
37
+ return null;
38
+ }
39
+ const edge = { _id: id, edgeType };
40
+ const projectId = readOptionalString(record.projectId);
41
+ if (projectId !== void 0) {
42
+ edge.projectId = projectId;
43
+ }
44
+ const topicId = readOptionalString(record.topicId);
45
+ if (topicId !== void 0) {
46
+ edge.topicId = topicId;
47
+ }
48
+ return edge;
49
+ }
27
50
  var epistemicLayerValidator = v.union(
28
51
  v.literal("L4"),
29
52
  v.literal("L3"),
@@ -146,7 +169,7 @@ function buildEdgeMirrorWriteResult(edgeId, existed) {
146
169
  };
147
170
  }
148
171
  function edgeMatchesWorkspaceReasoningScope(edge, scope) {
149
- return scope.topicId !== void 0 && edge.topicId === scope.topicId || scope.projectId !== void 0 && edge.projectId === scope.projectId;
172
+ return scope.topicId !== void 0 && edge.topicId === String(scope.topicId) || scope.projectId !== void 0 && (edge.projectId === scope.projectId || edge.topicId === scope.projectId);
150
173
  }
151
174
  async function collectScopedEdges(ctx, scope, scanLimit) {
152
175
  const queries = [];
@@ -169,7 +192,11 @@ async function collectScopedEdges(ctx, scope, scanLimit) {
169
192
  const seen = /* @__PURE__ */ new Set();
170
193
  const deduped = [];
171
194
  const flattened = (await Promise.all(queries)).flat();
172
- for (const edge of flattened) {
195
+ for (const value of flattened) {
196
+ const edge = readWorkspaceScopedEdge(value);
197
+ if (!edge) {
198
+ continue;
199
+ }
173
200
  const key = String(edge._id);
174
201
  if (seen.has(key)) {
175
202
  continue;
@@ -179,457 +206,115 @@ async function collectScopedEdges(ctx, scope, scanLimit) {
179
206
  }
180
207
  return deduped;
181
208
  }
182
-
183
- // src/graphTypes.ts
184
- var EDGE_TYPE_TO_REL = {
185
- // === THE SIX CANONICAL EPISTEMIC EDGE TYPES ===
186
- supports: "SUPPORTS",
187
- // L3↔L3: belief bears on belief (weight -1 to +1)
188
- informs: "INFORMS",
189
- // L2→L3: evidence bears on belief
190
- depends_on: "DEPENDS_ON",
191
- // L3→L3, Q→Q: structural gate
192
- derived_from: "DERIVED_FROM",
193
- // Any→Any: provenance chain (fork, synthesis, extraction, answer)
194
- contains: "CONTAINS",
195
- // Any→Any: hierarchy, scoping, membership
196
- tests: "TESTS",
197
- // Q→L3: question interrogates belief
198
- // === L4 DECISION EDGES (derived_from with derivationType=decision) ===
199
- // Kept as separate Neo4j relationship types for backward compat with L4 queries.
200
- // New code should use derived_from + metadata.
201
- based_on_belief: "BASED_ON_BELIEF",
202
- based_on_question: "BASED_ON_QUESTION",
203
- blocked_by_contradiction: "BLOCKED_BY_CONTRADICTION",
204
- informed_by_theme: "INFORMED_BY_THEME",
205
- // === ONTOLOGICAL EDGES (tenant-extensible, managed by ontology system) ===
206
- works_at: "WORKS_AT",
207
- invested_in: "INVESTED_IN",
208
- competes_with: "COMPETES_WITH",
209
- participates_in: "PARTICIPATES_IN",
210
- founded_by: "FOUNDED_BY",
211
- evaluates: "EVALUATES",
212
- performs: "PERFORMS",
213
- function_in: "FUNCTION_IN",
214
- impacts: "IMPACTS",
215
- raised_from: "RAISED_FROM",
216
- mentioned_in: "MENTIONED_IN",
217
- perspective_on: "PERSPECTIVE_ON",
218
- about_entity: "ABOUT_ENTITY",
219
- entity_referenced_in: "ENTITY_REFERENCED_IN"
220
- };
221
- function getNodeLayer(nodeType) {
222
- const L4_TYPES = ["decision"];
223
- const L3_TYPES = ["belief", "question", "theme", "deal"];
224
- const L2_TYPES = ["claim", "evidence", "synthesis", "answer"];
225
- const L1_TYPES = ["atomic_fact", "excerpt", "source"];
226
- const ONTOLOGICAL_TYPES = [
227
- "company",
228
- "person",
229
- "investor",
230
- "function",
231
- "value_chain"
232
- ];
233
- const ORGANIZATIONAL_TYPES = ["topic"];
234
- if (L4_TYPES.includes(nodeType)) {
235
- return "L4";
236
- }
237
- if (L3_TYPES.includes(nodeType)) {
238
- return "L3";
239
- }
240
- if (L2_TYPES.includes(nodeType)) {
241
- return "L2";
209
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
210
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
211
+ const node = await ctx.db.query("epistemicNodes").withIndex(
212
+ "by_globalId",
213
+ (q) => q.eq("globalId", endpoint)
214
+ ).first();
215
+ if (!node) {
216
+ throw new Error(
217
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
218
+ );
242
219
  }
243
- if (L1_TYPES.includes(nodeType)) {
244
- return "L1";
220
+ }
221
+ async function insertEpistemicEdge(ctx, doc) {
222
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
223
+ assertStorageEdgeVocabulary(doc.edgeType);
224
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
225
+ throw new Error(
226
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
227
+ );
245
228
  }
246
- if (ONTOLOGICAL_TYPES.includes(nodeType)) {
247
- return "ontological";
229
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
230
+ throw new Error(
231
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
232
+ );
248
233
  }
249
- if (ORGANIZATIONAL_TYPES.includes(nodeType)) {
250
- return "organizational";
234
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
235
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
236
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
237
+ assertEdgePolicyAllowed(
238
+ edgePolicyManifest,
239
+ doc.edgeType,
240
+ {
241
+ kind: "epistemic_node",
242
+ nodeId: doc.fromNodeId,
243
+ nodeType: doc.fromNodeType
244
+ },
245
+ {
246
+ kind: "epistemic_node",
247
+ nodeId: doc.toNodeId,
248
+ nodeType: doc.toNodeType
249
+ }
250
+ );
251
251
  }
252
- console.warn(`[GraphTypes] Unknown nodeType "${nodeType}", defaulting to L2`);
253
- return "L2";
254
- }
255
- var CANONICAL_EPISTEMIC_TYPES = /* @__PURE__ */ new Set([
256
- "supports",
257
- "informs",
258
- "depends_on",
259
- "derived_from",
260
- "contains",
261
- "tests"
262
- ]);
263
- function isDeprecatedEdgeType(edgeType) {
264
- if (CANONICAL_EPISTEMIC_TYPES.has(edgeType)) return false;
265
- if (edgeType in EDGE_TYPE_TO_REL) return false;
266
- return true;
252
+ return ctx.db.insert("epistemicEdges", doc);
267
253
  }
268
254
 
269
- // src/edgeValidation.ts
270
- function getLayerDepth(layer) {
271
- switch (layer) {
272
- case "L4":
273
- return 4;
274
- case "L3":
275
- return 3;
276
- case "L2":
277
- return 2;
278
- case "L1":
279
- return 1;
280
- case "ontological":
281
- return 0;
282
- default:
283
- return -1;
284
- }
255
+ // src/debug.ts
256
+ function isGraphPrimitiveDebugEnabled() {
257
+ const env = globalThis.process?.env;
258
+ return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
285
259
  }
286
- function isValidLayerConnection(fromLayer, toLayer) {
287
- if (fromLayer === toLayer) {
288
- return true;
289
- }
290
- if (fromLayer === "ontological" && toLayer === "ontological") {
291
- return true;
292
- }
293
- if (fromLayer === "ontological" && (toLayer === "L3" || toLayer === "L2" || toLayer === "L1")) {
294
- return true;
295
- }
296
- if ((fromLayer === "L3" || fromLayer === "L2" || fromLayer === "L1") && toLayer === "ontological") {
297
- return true;
298
- }
299
- if (fromLayer === "L2" && toLayer === "L1") {
300
- return true;
301
- }
302
- if (fromLayer === "L2" && toLayer === "L3") {
303
- return true;
304
- }
305
- if (fromLayer === "L3" && toLayer === "L2") {
306
- return true;
307
- }
308
- if (fromLayer === "L3" && toLayer === "L4") {
309
- return true;
260
+ function debugGraphPrimitiveFallback(message, context) {
261
+ if (!isGraphPrimitiveDebugEnabled()) {
262
+ return;
310
263
  }
311
- if (fromLayer === "L4" && toLayer === "L3") {
312
- return true;
264
+ console.debug(message, context ?? {});
265
+ }
266
+
267
+ // src/topicScope.ts
268
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
269
+ async function resolveTopicNodeScopeOrNull(ctx, ref) {
270
+ if (!ctx?.db || typeof ctx.db.query !== "function") {
271
+ return null;
313
272
  }
314
- if (fromLayer === "L3" && toLayer === "organizational") {
315
- return true;
273
+ let node = null;
274
+ try {
275
+ const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
276
+ if (byGlobalId && byGlobalId.nodeType === "topic") {
277
+ node = byGlobalId;
278
+ }
279
+ } catch (error) {
280
+ debugGraphPrimitiveFallback(
281
+ "[topicScope] topic-node scope lookup by globalId failed",
282
+ { error, ref }
283
+ );
316
284
  }
317
- if (fromLayer === "organizational" && toLayer === "L3") {
318
- return true;
285
+ if (!node) {
286
+ return null;
319
287
  }
320
- const fromDepth = getLayerDepth(fromLayer);
321
- const toDepth = getLayerDepth(toLayer);
322
- if (Math.abs(fromDepth - toDepth) > 1) {
323
- return false;
288
+ const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
289
+ if (!scopeKey) {
290
+ return null;
324
291
  }
325
- return true;
292
+ return {
293
+ topicId: scopeKey,
294
+ projectId: asMappedProjectId(node),
295
+ source: "topic_node"
296
+ };
326
297
  }
327
- function shouldContinueTraversal(currentLayer, targetLayer, options) {
328
- const currentDepth = getLayerDepth(currentLayer);
329
- const targetDepth = getLayerDepth(targetLayer);
330
- if (options.minLayer !== void 0 && targetDepth < options.minLayer) {
331
- return false;
298
+ function asMappedProjectId(topic) {
299
+ if (!topic) {
300
+ return;
332
301
  }
333
- if (options.maxLayer !== void 0 && targetDepth > options.maxLayer) {
334
- return false;
302
+ const directLegacyProjectId = normalizeScopeValue(
303
+ topic[LEGACY_SCOPE_FIELD]
304
+ );
305
+ if (directLegacyProjectId) {
306
+ return directLegacyProjectId;
335
307
  }
336
- switch (options.mode) {
337
- case "anchor_down":
338
- return targetDepth <= currentDepth;
339
- case "anchor_up":
340
- return targetDepth >= currentDepth;
341
- case "same_layer":
342
- return targetDepth === currentDepth;
343
- case "decision_trace":
344
- return currentDepth === 4 ? targetDepth === 3 : targetDepth === currentDepth;
345
- default:
346
- return true;
308
+ const metadata = topic.metadata || {};
309
+ const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
310
+ return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
311
+ }
312
+ function normalizeScopeValue(value) {
313
+ if (typeof value !== "string") {
314
+ return;
347
315
  }
348
- }
349
- function getDefaultMinLayer(mode) {
350
- switch (mode) {
351
- case "anchor_down":
352
- return 1;
353
- case "anchor_up":
354
- return 2;
355
- case "same_layer":
356
- return 1;
357
- case "decision_trace":
358
- return 3;
359
- default:
360
- return 1;
361
- }
362
- }
363
- var EDGE_LAYER_RULES = {
364
- // === 6 Canonical Epistemic Types ===
365
- supports: {
366
- from: ["L3"],
367
- to: ["L3"],
368
- description: "Belief bears on Belief (weight: +1 supports, -1 contradicts)"
369
- },
370
- informs: {
371
- from: ["L2"],
372
- to: ["L3"],
373
- description: "Evidence -> Belief (L2 -> L3)"
374
- },
375
- depends_on: {
376
- from: ["L3"],
377
- to: ["L3"],
378
- description: "Belief B requires Belief A (structural gate)"
379
- },
380
- derived_from: {
381
- from: ["L2", "L3", "L4"],
382
- to: ["L1", "L2", "L3"],
383
- description: "A was produced from B (provenance chain)"
384
- },
385
- contains: {
386
- from: ["L3", "L4", "ontological"],
387
- to: ["L2", "L3", "ontological"],
388
- description: "A scopes/aggregates B (hierarchy)"
389
- },
390
- tests: {
391
- from: ["L3"],
392
- to: ["L3"],
393
- description: "Question -> Belief (L3 -> L3)"
394
- },
395
- // === Structural / Lifecycle ===
396
- supersedes: {
397
- from: ["L3"],
398
- to: ["L3"],
399
- description: "NewNode -> OldNode (fork lineage)"
400
- },
401
- responds_to: {
402
- from: ["L2", "L3"],
403
- to: ["L3"],
404
- description: "Answer -> Question (L2/L3 -> L3)"
405
- },
406
- belongs_to: {
407
- from: ["L2", "L3", "ontological"],
408
- to: ["L3"],
409
- description: "Membership (migrating to contains)"
410
- },
411
- relates_to_thesis: {
412
- from: ["L3"],
413
- to: ["L3"],
414
- description: "Belief -> Theme (L3 -> L3)"
415
- },
416
- // === Ontological (entity-entity or entity-epistemic bridge) ===
417
- works_at: {
418
- from: ["ontological"],
419
- to: ["ontological"],
420
- description: "Person -> Company"
421
- },
422
- invested_in: {
423
- from: ["ontological"],
424
- to: ["ontological"],
425
- description: "Investor -> Company"
426
- },
427
- competes_with: {
428
- from: ["ontological"],
429
- to: ["ontological"],
430
- description: "Company -> Company"
431
- },
432
- participates_in: {
433
- from: ["ontological"],
434
- to: ["ontological"],
435
- description: "Company -> ValueChain"
436
- },
437
- founded_by: {
438
- from: ["ontological"],
439
- to: ["ontological"],
440
- description: "Company -> Person"
441
- },
442
- evaluates: {
443
- from: ["ontological"],
444
- to: ["ontological"],
445
- description: "Deal -> Company"
446
- },
447
- performs: {
448
- from: ["ontological"],
449
- to: ["ontological"],
450
- description: "Company -> Function"
451
- },
452
- function_in: {
453
- from: ["ontological"],
454
- to: ["ontological"],
455
- description: "Function -> ValueChain"
456
- },
457
- impacts: {
458
- from: ["ontological", "L3"],
459
- to: ["ontological"],
460
- description: "Theme/Entity -> ValueChain"
461
- },
462
- raised_from: {
463
- from: ["ontological"],
464
- to: ["ontological"],
465
- description: "Company -> Investor"
466
- },
467
- mentioned_in: {
468
- from: ["ontological"],
469
- to: ["L1", "L2"],
470
- description: "Entity -> Source/Evidence"
471
- },
472
- perspective_on: {
473
- from: ["ontological"],
474
- to: ["L3"],
475
- description: "Person -> Belief/Theme"
476
- },
477
- plays_theme: {
478
- from: ["ontological"],
479
- to: ["L3"],
480
- description: "Deal -> Theme"
481
- },
482
- // C2-RR.4 — storage migration alias + the remaining 55 reachable edge types.
483
- // Mirrors the kernel rule table
484
- // (packages/reasoning-kernel/src/adapters/lib/edgeValidation.ts) so this
485
- // graph-primitives write path admits the same public edge vocabulary. See the
486
- // kernel file for the per-group rationale.
487
- extracted_from: {
488
- from: ["L2", "L3", "L4"],
489
- to: ["L1", "L2", "L3"],
490
- description: "Legacy storage migration alias for derived_from; public writes use derived_from"
491
- },
492
- qualifies: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief qualifies a Belief" },
493
- contradicts: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief contradicts a Belief" },
494
- reinforces: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief reinforces a Belief" },
495
- corroborates: { from: ["L2", "L3"], to: ["L3"], description: "Independent evidence/belief corroborates a Belief" },
496
- strengthened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is strengthened by Evidence/Belief" },
497
- weakened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is weakened by Evidence/Belief" },
498
- validated_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is validated by Evidence/Belief" },
499
- falsified_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is falsified by Evidence/Belief" },
500
- amplifies: { from: ["L3"], to: ["L3"], description: "Belief amplifies another Belief" },
501
- precondition_for: { from: ["L3"], to: ["L3"], description: "Belief is a precondition for another Belief" },
502
- prerequisite_for: { from: ["L3"], to: ["L3"], description: "Belief is a prerequisite for another Belief" },
503
- required_for: { from: ["L3"], to: ["L3"], description: "Belief is required for another Belief" },
504
- in_tension_with: { from: ["L3"], to: ["L3"], description: "Belief is in tension with another Belief" },
505
- mutually_exclusive: { from: ["L3"], to: ["L3"], description: "Beliefs cannot both hold" },
506
- exclusive_with: { from: ["L3"], to: ["L3"], description: "Belief is exclusive with another Belief" },
507
- alternative_to: { from: ["L3"], to: ["L3"], description: "Belief is an alternative to another Belief" },
508
- subsumes: { from: ["L3"], to: ["L3"], description: "Belief subsumes a narrower Belief" },
509
- extends: { from: ["L3"], to: ["L3"], description: "Belief extends another Belief" },
510
- refines: { from: ["L3"], to: ["L3"], description: "Belief refines another Belief" },
511
- implements: { from: ["L3"], to: ["L3"], description: "Belief implements an abstract Belief" },
512
- violates: { from: ["L3"], to: ["L3"], description: "Belief violates a constraint Belief" },
513
- assumes: { from: ["L3"], to: ["L3"], description: "Belief assumes another Belief" },
514
- would_predict: { from: ["L3"], to: ["L3"], description: "Belief would predict another Belief" },
515
- analogous_to: { from: ["L3"], to: ["L3"], description: "Belief is analogous to another Belief" },
516
- independent_of: { from: ["L3"], to: ["L3"], description: "Belief is independent of another Belief" },
517
- correlates_with: { from: ["L3"], to: ["L3"], description: "Belief correlates with another Belief" },
518
- co_changes_with: { from: ["L3"], to: ["L3"], description: "Belief co-changes with another Belief" },
519
- counterfactual_of: { from: ["L3"], to: ["L3"], description: "Belief is a counterfactual of another Belief" },
520
- parallel_to: { from: ["L3"], to: ["L3"], description: "Belief runs parallel to another Belief" },
521
- cascade_from: { from: ["L3"], to: ["L3"], description: "Cascade from an upstream Belief" },
522
- cascade_to: { from: ["L3"], to: ["L3"], description: "Cascade to a downstream Belief" },
523
- collapses_if: { from: ["L3"], to: ["L3"], description: "Belief collapses if another fails" },
524
- branches_from: { from: ["L3"], to: ["L3"], description: "Belief branches from an ancestor Belief" },
525
- same_as: { from: ["L2", "L3", "ontological"], to: ["L2", "L3", "ontological"], description: "Two nodes are the same entity" },
526
- answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief answers a Question" },
527
- partially_answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief partially answers a Question" },
528
- explores: { from: ["L3"], to: ["L3"], description: "Question explores a Belief/Theme" },
529
- informed_by_theme: { from: ["L3"], to: ["L3"], description: "Belief is informed by a Theme" },
530
- same_theme_as: { from: ["L3"], to: ["L3"], description: "Two Beliefs share a Theme" },
531
- based_on: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief/Question" },
532
- based_on_belief: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief" },
533
- based_on_question: { from: ["L4"], to: ["L3"], description: "Decision is based on a Question" },
534
- blocked_by_contradiction: { from: ["L4"], to: ["L3"], description: "Decision is blocked by a Contradiction" },
535
- parent_of: { from: ["L3", "L4", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "A is the parent of B" },
536
- child_of: { from: ["L2", "L3", "ontological", "organizational"], to: ["L3", "L4", "ontological", "organizational"], description: "A is the child of B" },
537
- scoped_by: { from: ["L2", "L3"], to: ["L3", "organizational"], description: "Object is scoped by a Topic/Theme" },
538
- cites: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Evidence/Belief cites a Source/Excerpt" },
539
- summarizes: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Synthesis/Evidence summarizes a Source/Evidence" },
540
- same_source_as: { from: ["L1", "L2"], to: ["L1", "L2"], description: "Two nodes derive from the same Source" },
541
- migrating_from: { from: ["L2", "L3", "L4"], to: ["L1", "L2", "L3"], description: "Migration lineage: from an ancestor" },
542
- migrating_to: { from: ["L1", "L2", "L3"], to: ["L2", "L3", "L4"], description: "Migration lineage: to a successor" },
543
- about_entity: { from: ["L2", "L3"], to: ["ontological"], description: "Belief/Evidence is about an Entity" },
544
- entity_referenced_in: { from: ["ontological"], to: ["L1", "L2"], description: "Entity is referenced in a Source/Evidence" },
545
- related_to: { from: ["L2", "L3", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "Lateral adjacency; no confidence pressure" },
546
- blocks: { from: ["L3", "L4"], to: ["L3", "L4"], description: "A blocks B (structural gate)" }
547
- };
548
- function validateEdgeLayers(edgeType, fromLayer, toLayer) {
549
- const rules = EDGE_LAYER_RULES[edgeType];
550
- if (!rules) {
551
- console.warn(
552
- `[EdgeValidation] Unknown edge type: ${edgeType}, allowing by default`
553
- );
554
- return { valid: true };
555
- }
556
- if (edgeType === "supersedes") {
557
- if (fromLayer !== toLayer) {
558
- return {
559
- valid: false,
560
- reason: `${edgeType} edges must be between nodes of the same layer. Got ${fromLayer} -> ${toLayer}`
561
- };
562
- }
563
- return { valid: true };
564
- }
565
- if (!rules.from.includes(fromLayer)) {
566
- return {
567
- valid: false,
568
- reason: `Edge type '${edgeType}' does not allow source layer ${fromLayer}. Allowed: ${rules.from.join(", ")}`
569
- };
570
- }
571
- if (!rules.to.includes(toLayer)) {
572
- return {
573
- valid: false,
574
- reason: `Edge type '${edgeType}' does not allow target layer ${toLayer}. Allowed: ${rules.to.join(", ")}`
575
- };
576
- }
577
- if (!isValidLayerConnection(fromLayer, toLayer)) {
578
- return {
579
- valid: false,
580
- reason: `Layer transition ${fromLayer} -> ${toLayer} is not permitted`
581
- };
582
- }
583
- return { valid: true };
584
- }
585
- var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
586
- async function resolveTopicNodeScopeOrNull(ctx, ref) {
587
- if (!ctx?.db || typeof ctx.db.query !== "function") {
588
- return null;
589
- }
590
- let node = null;
591
- try {
592
- const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
593
- if (byGlobalId && byGlobalId.nodeType === "topic") {
594
- node = byGlobalId;
595
- }
596
- } catch (error) {
597
- debugGraphPrimitiveFallback(
598
- "[topicScope] topic-node scope lookup by globalId failed",
599
- { error, ref }
600
- );
601
- }
602
- if (!node) {
603
- return null;
604
- }
605
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
606
- if (!scopeKey) {
607
- return null;
608
- }
609
- return {
610
- topicId: scopeKey,
611
- projectId: asMappedProjectId(node),
612
- source: "topic_node"
613
- };
614
- }
615
- function asMappedProjectId(topic) {
616
- if (!topic) {
617
- return;
618
- }
619
- const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
620
- if (directLegacyProjectId) {
621
- return directLegacyProjectId;
622
- }
623
- const metadata = topic.metadata || {};
624
- const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
625
- return candidate ? candidate : void 0;
626
- }
627
- function normalizeScopeValue(value) {
628
- if (typeof value !== "string") {
629
- return;
630
- }
631
- const normalized = value.trim();
632
- return normalized.length > 0 ? normalized : void 0;
316
+ const normalized = value.trim();
317
+ return normalized.length > 0 ? normalized : void 0;
633
318
  }
634
319
  function pickPrimaryTopic(candidates) {
635
320
  return [...candidates].sort((a, b) => {
@@ -647,8 +332,9 @@ function pickPrimaryTopic(candidates) {
647
332
  })[0];
648
333
  }
649
334
  async function findTopicsByScopeAlias(ctx, scopeId) {
335
+ const query2 = ctx.db.query("topics");
650
336
  try {
651
- return await ctx.db.query("topics").withIndex(
337
+ return await query2.withIndex(
652
338
  "by_graph_scope_project",
653
339
  (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
654
340
  ).collect();
@@ -660,7 +346,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
660
346
  scopeId
661
347
  }
662
348
  );
663
- const topics = await ctx.db.query("topics").collect();
349
+ const topics = await query2.collect();
664
350
  return topics.filter((topic) => {
665
351
  const normalizedGlobalId = normalizeScopeValue(topic.globalId);
666
352
  const mappedProjectId = asMappedProjectId(topic);
@@ -716,527 +402,997 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
716
402
  let current = topic;
717
403
  for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
718
404
  current = await ctx.db.get(current.parentTopicId);
719
- if (!current) break;
405
+ if (!current) {
406
+ break;
407
+ }
720
408
  if (!tenantId) {
721
409
  tenantId = normalizeScopeValue(current.tenantId);
722
410
  }
723
411
  if (!workspaceId) {
724
412
  workspaceId = normalizeScopeValue(current.workspaceId);
725
413
  }
726
- if (tenantId && workspaceId) break;
414
+ if (tenantId && workspaceId) {
415
+ break;
416
+ }
727
417
  }
728
418
  return { tenantId, workspaceId };
729
419
  }
730
420
  async function resolveTopicProjectScope(ctx, args) {
731
421
  if (args.topicId) {
732
- let topic = null;
733
- try {
734
- topic = await ctx.db.get(
735
- args.topicId
736
- );
737
- } catch (error) {
738
- debugGraphPrimitiveFallback(
739
- "[topicScope] Failed to load topic by direct id",
740
- {
741
- error,
742
- topicId: args.topicId
743
- }
744
- );
745
- }
746
- if (!topic) {
747
- topic = await tryResolveHostTopicById(ctx, String(args.topicId));
748
- }
749
- if (!topic) {
750
- topic = pickPrimaryTopic(
751
- await findTopicsByScopeAlias(ctx, String(args.topicId))
752
- ) ?? null;
753
- }
754
- if (!topic) {
755
- const nodeScope = await resolveTopicNodeScopeOrNull(
756
- ctx,
757
- String(args.topicId)
758
- );
759
- if (nodeScope) {
760
- return nodeScope;
761
- }
762
- throw new Error(`Topic not found: ${String(args.topicId)}`);
763
- }
764
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
765
- const mapped = asMappedProjectId(topic);
766
- if (mapped) {
767
- return {
768
- topicId: topic._id,
769
- projectId: mapped,
770
- tenantId: inherited.tenantId,
771
- workspaceId: inherited.workspaceId,
772
- source: "topic"
773
- };
774
- }
775
- return {
776
- topicId: topic._id,
777
- tenantId: inherited.tenantId,
778
- workspaceId: inherited.workspaceId,
779
- source: "topic"
780
- };
422
+ return await resolveScopeFromTopicId(ctx, args.topicId);
781
423
  }
782
424
  if (args.projectId) {
783
- let directTopic = null;
784
- try {
785
- directTopic = await ctx.db.get(
786
- args.projectId
787
- );
788
- } catch (error) {
789
- debugGraphPrimitiveFallback(
790
- "[topicScope] Failed to load direct project topic",
791
- {
792
- error,
793
- projectId: args.projectId
794
- }
795
- );
796
- }
797
- if (directTopic) {
798
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
799
- const mapped = asMappedProjectId(directTopic);
800
- return {
801
- topicId: directTopic._id,
802
- projectId: mapped ?? args.projectId,
803
- tenantId: inherited.tenantId,
804
- workspaceId: inherited.workspaceId,
805
- source: "topic_inferred"
806
- };
807
- }
808
- directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
809
- if (directTopic) {
810
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
811
- const mapped = asMappedProjectId(directTopic);
812
- return {
813
- topicId: directTopic._id,
814
- projectId: mapped ?? args.projectId,
815
- tenantId: inherited.tenantId,
816
- workspaceId: inherited.workspaceId,
817
- source: "topic_inferred"
818
- };
819
- }
820
- const topics = await findTopicsByScopeAlias(ctx, args.projectId);
821
- const primary = pickPrimaryTopic(topics);
822
- if (primary) {
823
- const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
824
- return {
825
- topicId: primary._id,
826
- projectId: args.projectId,
827
- tenantId: inherited.tenantId,
828
- workspaceId: inherited.workspaceId,
829
- source: "project_mapped_topic"
830
- };
831
- }
832
- const nodeScope = await resolveTopicNodeScopeOrNull(
833
- ctx,
834
- String(args.projectId)
835
- );
836
- if (nodeScope) {
837
- return {
838
- ...nodeScope,
839
- projectId: nodeScope.projectId ?? String(args.projectId)
840
- };
841
- }
842
- throw new Error(
843
- `Legacy project scope ${String(args.projectId)} has no mapped topic.`
844
- );
425
+ return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
845
426
  }
846
427
  throw new Error(
847
428
  "Missing scope: provide topicId (preferred) or legacy projectId alias."
848
429
  );
849
430
  }
431
+ async function resolveScopeFromTopicId(ctx, topicId) {
432
+ const topic = await resolveTopicDocFromTopicId(ctx, topicId);
433
+ if (topic) {
434
+ return await buildTopicScope(ctx, topic, "topic");
435
+ }
436
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
437
+ if (nodeScope) {
438
+ return nodeScope;
439
+ }
440
+ throw new Error(`Topic not found: ${String(topicId)}`);
441
+ }
442
+ async function resolveTopicDocFromTopicId(ctx, topicId) {
443
+ const direct = await tryReadTopicDoc(ctx, topicId, {
444
+ failureLog: "[topicScope] Failed to load topic by direct id",
445
+ idLogKey: "topicId"
446
+ });
447
+ if (direct) {
448
+ return direct;
449
+ }
450
+ const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
451
+ if (hostTopic) {
452
+ return hostTopic;
453
+ }
454
+ return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
455
+ }
456
+ async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
457
+ const directTopic = await resolveDirectLegacyProjectTopic(
458
+ ctx,
459
+ legacyProjectId
460
+ );
461
+ if (directTopic) {
462
+ return await buildTopicScope(ctx, directTopic, "topic_inferred", {
463
+ fallbackProjectId: legacyProjectId
464
+ });
465
+ }
466
+ const primary = pickPrimaryTopic(
467
+ await findTopicsByScopeAlias(ctx, legacyProjectId)
468
+ );
469
+ if (primary) {
470
+ return await buildTopicScope(ctx, primary, "project_mapped_topic", {
471
+ fallbackProjectId: legacyProjectId
472
+ });
473
+ }
474
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
475
+ if (nodeScope) {
476
+ return {
477
+ ...nodeScope,
478
+ projectId: nodeScope.projectId ?? legacyProjectId
479
+ };
480
+ }
481
+ throw new Error(
482
+ `Legacy project scope ${legacyProjectId} has no mapped topic.`
483
+ );
484
+ }
485
+ async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
486
+ const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
487
+ failureLog: "[topicScope] Failed to load direct project topic",
488
+ idLogKey: "projectId"
489
+ });
490
+ return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
491
+ }
492
+ async function tryReadTopicDoc(ctx, id, log) {
493
+ try {
494
+ return await ctx.db.get(id);
495
+ } catch (error) {
496
+ debugGraphPrimitiveFallback(log.failureLog, {
497
+ error,
498
+ [log.idLogKey]: id
499
+ });
500
+ return null;
501
+ }
502
+ }
503
+ async function buildTopicScope(ctx, topic, source, options = {}) {
504
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
505
+ const mapped = asMappedProjectId(topic);
506
+ return {
507
+ topicId: topic._id,
508
+ ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
509
+ tenantId: inherited.tenantId,
510
+ workspaceId: inherited.workspaceId,
511
+ source
512
+ };
513
+ }
850
514
  var optionalScopeArgs = {
851
515
  projectId: v.optional(v.string()),
852
516
  topicId: v.optional(v.string())
853
517
  };
854
518
 
855
- // src/epistemicEdges.queries.ts
856
- var get = query({
857
- args: { edgeId: v.id("epistemicEdges") },
858
- returns: permissiveReturn,
859
- handler: async (ctx, args) => {
860
- return await ctx.db.get(args.edgeId);
519
+ // src/epistemicEdges.handlers.ts
520
+ var EDGE_CONSTRAINTS = /* @__PURE__ */ new Set([
521
+ "alternative",
522
+ "counterfactual",
523
+ "xor"
524
+ ]);
525
+ var EDGE_PROPAGATION_MODES = /* @__PURE__ */ new Set([
526
+ "continuous",
527
+ "threshold"
528
+ ]);
529
+ var EDGE_LOGICAL_ROLES = /* @__PURE__ */ new Set([
530
+ "contributory",
531
+ "corroborative",
532
+ "necessary",
533
+ "necessary_sufficient",
534
+ "sufficient"
535
+ ]);
536
+ var EDGE_TEMPORAL_CLASSES = /* @__PURE__ */ new Set([
537
+ "cyclical",
538
+ "point_in_time",
539
+ "structural",
540
+ "transient"
541
+ ]);
542
+ var EPISTEMIC_LAYERS = /* @__PURE__ */ new Set([
543
+ "L4",
544
+ "L3",
545
+ "L2",
546
+ "L1",
547
+ "ontological",
548
+ "organizational"
549
+ ]);
550
+ var REASONING_METHOD_SET = new Set(REASONING_METHODS);
551
+ function readOptionalString2(value) {
552
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
553
+ }
554
+ function readConvexId2(value) {
555
+ const normalized = readOptionalString2(value);
556
+ return normalized ? normalized : null;
557
+ }
558
+ function readRecord2(value) {
559
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
560
+ }
561
+ function readEdgeMirrorNodeRow(value) {
562
+ const record = readRecord2(value);
563
+ if (!record) {
564
+ return null;
861
565
  }
862
- });
863
- var getByGlobalId = query({
864
- args: { globalId: v.string() },
865
- returns: permissiveReturn,
866
- handler: async (ctx, args) => {
867
- return await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
566
+ const globalId = readOptionalString2(record.globalId);
567
+ return globalId ? { globalId } : null;
568
+ }
569
+ function requireEdgeId(value, context) {
570
+ const id = readConvexId2(value);
571
+ if (!id) {
572
+ throw new Error(`${context} requires a stored epistemic edge id.`);
868
573
  }
869
- });
870
- var getFromNode = query({
871
- args: {
872
- fromNodeId: v.id("epistemicNodes"),
873
- edgeType: v.optional(edgeTypeValidator)
874
- },
875
- returns: permissiveReturn,
876
- handler: async (ctx, args) => {
877
- const { edgeType } = args;
878
- if (edgeType) {
879
- return await ctx.db.query("epistemicEdges").withIndex(
880
- "by_from_type",
881
- (q) => q.eq("fromNodeId", args.fromNodeId).eq("edgeType", edgeType)
882
- ).collect();
883
- }
884
- return await ctx.db.query("epistemicEdges").withIndex("by_from", (q) => q.eq("fromNodeId", args.fromNodeId)).collect();
574
+ return id;
575
+ }
576
+ function readOptionalEnum(value, allowed, fieldName) {
577
+ if (value === void 0) {
578
+ return;
885
579
  }
886
- });
887
- var getToNode = query({
888
- args: {
889
- toNodeId: v.id("epistemicNodes"),
890
- edgeType: v.optional(edgeTypeValidator)
891
- },
892
- returns: permissiveReturn,
893
- handler: async (ctx, args) => {
894
- const { edgeType } = args;
895
- if (edgeType) {
896
- return await ctx.db.query("epistemicEdges").withIndex(
897
- "by_to_type",
898
- (q) => q.eq("toNodeId", args.toNodeId).eq("edgeType", edgeType)
899
- ).collect();
900
- }
901
- return await ctx.db.query("epistemicEdges").withIndex("by_to", (q) => q.eq("toNodeId", args.toNodeId)).collect();
580
+ if (allowed.has(value)) {
581
+ return value;
902
582
  }
903
- });
904
- var getBySourceNode = query({
583
+ throw new Error(`${fieldName} received unsupported value "${value}".`);
584
+ }
585
+ var mirrorEdgeToConvex = internalMutation({
905
586
  args: {
906
- sourceNodeId: v.string(),
907
- edgeType: v.optional(v.string())
587
+ globalId: v.string(),
588
+ fromGlobalId: v.string(),
589
+ toGlobalId: v.string(),
590
+ edgeType: v.string(),
591
+ weight: v.optional(v.number()),
592
+ confidence: v.optional(v.number()),
593
+ context: v.optional(v.string()),
594
+ derivationType: v.optional(v.string()),
595
+ createdBy: v.string(),
596
+ ...optionalScopeArgs,
597
+ fromLayer: v.optional(v.string()),
598
+ toLayer: v.optional(v.string()),
599
+ fromNodeType: v.optional(v.string()),
600
+ toNodeType: v.optional(v.string()),
601
+ reasoningMethod: v.optional(v.string()),
602
+ logicalRole: v.optional(v.string()),
603
+ temporalClass: v.optional(v.string()),
604
+ validFrom: v.optional(v.number()),
605
+ validUntil: v.optional(v.number()),
606
+ constraint: v.optional(v.string()),
607
+ propagation: v.optional(v.string()),
608
+ blocking: v.optional(v.boolean()),
609
+ implicit: v.optional(v.boolean()),
610
+ conditionalA: v.optional(subjectiveOpinionValidator),
611
+ conditionalNotA: v.optional(subjectiveOpinionValidator)
908
612
  },
909
613
  returns: permissiveReturn,
910
614
  handler: async (ctx, args) => {
911
- const isConvexId = args.sourceNodeId.length === 32 || args.sourceNodeId.includes("epistemicNodes");
912
- if (isConvexId) {
913
- const query2 = args.edgeType ? ctx.db.query("epistemicEdges").withIndex(
914
- "by_from_type",
915
- (q) => q.eq("fromNodeId", args.sourceNodeId).eq("edgeType", args.edgeType)
916
- ) : ctx.db.query("epistemicEdges").withIndex(
917
- "by_from",
918
- (q) => q.eq("fromNodeId", args.sourceNodeId)
615
+ const fromNode = readEdgeMirrorNodeRow(
616
+ await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", args.fromGlobalId)).first()
617
+ );
618
+ if (!fromNode) {
619
+ console.log(
620
+ `[Dual-Write] Skipping mirror - source node not in Convex: ${args.fromGlobalId}`
919
621
  );
920
- return await query2.collect();
921
- }
922
- const byGlobalId = await ctx.db.query("epistemicEdges").withIndex(
923
- "by_source_global_id",
924
- (q) => q.eq("sourceGlobalId", args.sourceNodeId)
925
- ).collect();
926
- if (args.edgeType) {
927
- return byGlobalId.filter((edge) => edge.edgeType === args.edgeType);
622
+ return buildEdgeMirrorSkippedResult();
928
623
  }
929
- return byGlobalId;
930
- }
931
- });
932
- var getByTargetNode = query({
933
- args: {
934
- targetNodeId: v.string(),
935
- edgeType: v.optional(v.string())
936
- },
937
- returns: permissiveReturn,
938
- handler: async (ctx, args) => {
939
- const isConvexId = args.targetNodeId.length === 32 || args.targetNodeId.includes("epistemicNodes");
940
- if (isConvexId) {
941
- const query2 = args.edgeType ? ctx.db.query("epistemicEdges").withIndex(
942
- "by_to_type",
943
- (q) => q.eq("toNodeId", args.targetNodeId).eq("edgeType", args.edgeType)
944
- ) : ctx.db.query("epistemicEdges").withIndex(
945
- "by_to",
946
- (q) => q.eq("toNodeId", args.targetNodeId)
624
+ const toNode = readEdgeMirrorNodeRow(
625
+ await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", args.toGlobalId)).first()
626
+ );
627
+ const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
628
+ if (existing) {
629
+ console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);
630
+ return buildEdgeMirrorWriteResult(
631
+ requireEdgeId(existing._id, "Edge mirror write result"),
632
+ true
947
633
  );
948
- return await query2.collect();
949
634
  }
950
- const byGlobalId = await ctx.db.query("epistemicEdges").withIndex(
951
- "by_target_global_id",
952
- (q) => q.eq("targetGlobalId", args.targetNodeId)
953
- ).collect();
954
- if (args.edgeType) {
955
- return byGlobalId.filter((edge) => edge.edgeType === args.edgeType);
635
+ if (!toNode) {
636
+ console.log(
637
+ `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`
638
+ );
639
+ return buildEdgeMirrorSkippedResult();
956
640
  }
957
- return byGlobalId;
641
+ const now = Date.now();
642
+ const edgeId = await insertEpistemicEdge(ctx, {
643
+ globalId: args.globalId,
644
+ // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex
645
+ // doc ids. fromNode/toNode are resolved by_globalId above, so their
646
+ // stored globalId equals args.from/toGlobalId; persisting the doc _id here
647
+ // was the "mixed _id/globalId" defect.
648
+ fromNodeId: fromNode.globalId,
649
+ toNodeId: toNode.globalId,
650
+ sourceGlobalId: args.fromGlobalId,
651
+ targetGlobalId: args.toGlobalId,
652
+ edgeType: args.edgeType,
653
+ weight: args.weight,
654
+ confidence: args.confidence,
655
+ context: args.context,
656
+ derivationType: args.derivationType,
657
+ constraint: readOptionalEnum(
658
+ args.constraint,
659
+ EDGE_CONSTRAINTS,
660
+ "constraint"
661
+ ),
662
+ propagation: readOptionalEnum(
663
+ args.propagation,
664
+ EDGE_PROPAGATION_MODES,
665
+ "propagation"
666
+ ),
667
+ blocking: args.blocking,
668
+ implicit: args.implicit,
669
+ conditionalA: args.conditionalA,
670
+ conditionalNotA: args.conditionalNotA,
671
+ createdBy: args.createdBy,
672
+ createdAt: now,
673
+ updatedAt: now,
674
+ topicId: args.projectId,
675
+ fromLayer: readOptionalEnum(
676
+ args.fromLayer,
677
+ EPISTEMIC_LAYERS,
678
+ "fromLayer"
679
+ ),
680
+ toLayer: readOptionalEnum(args.toLayer, EPISTEMIC_LAYERS, "toLayer"),
681
+ fromNodeType: args.fromNodeType,
682
+ toNodeType: args.toNodeType,
683
+ reasoningMethod: readOptionalEnum(
684
+ args.reasoningMethod,
685
+ REASONING_METHOD_SET,
686
+ "reasoningMethod"
687
+ ),
688
+ logicalRole: readOptionalEnum(
689
+ args.logicalRole,
690
+ EDGE_LOGICAL_ROLES,
691
+ "logicalRole"
692
+ ),
693
+ temporalClass: readOptionalEnum(
694
+ args.temporalClass,
695
+ EDGE_TEMPORAL_CLASSES,
696
+ "temporalClass"
697
+ ),
698
+ validFrom: args.validFrom,
699
+ validUntil: args.validUntil
700
+ });
701
+ console.log(
702
+ `[Dual-Write] Mirrored edge to Convex: ${args.globalId} (${args.edgeType})`
703
+ );
704
+ return buildEdgeMirrorWriteResult(edgeId, false);
958
705
  }
959
706
  });
960
- var getBetween = query({
707
+ var deleteEdgeFromConvex = internalMutation({
961
708
  args: {
962
- fromNodeId: v.id("epistemicNodes"),
963
- toNodeId: v.id("epistemicNodes"),
964
- edgeType: v.optional(edgeTypeValidator)
709
+ globalId: v.string()
965
710
  },
966
711
  returns: permissiveReturn,
967
712
  handler: async (ctx, args) => {
968
- const edges = await ctx.db.query("epistemicEdges").withIndex(
969
- "by_from_to",
970
- (q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
971
- ).collect();
972
- if (args.edgeType) {
973
- return edges.filter((e) => e.edgeType === args.edgeType);
713
+ const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
714
+ if (!existing) {
715
+ return buildEdgeMirrorMissingResult();
974
716
  }
975
- return edges;
976
- }
977
- });
978
- var getByNodes = query({
979
- args: {
980
- fromNodeId: v.id("epistemicNodes"),
981
- toNodeId: v.id("epistemicNodes")
982
- },
983
- returns: permissiveReturn,
984
- handler: async (ctx, args) => {
985
- return await ctx.db.query("epistemicEdges").withIndex(
986
- "by_from_to",
987
- (q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
988
- ).first();
717
+ await ctx.db.delete(
718
+ requireEdgeId(existing._id, "Delete mirrored edge from Convex")
719
+ );
720
+ console.log(`[Dual-Write] Deleted edge from Convex: ${args.globalId}`);
721
+ return buildEdgeStatusSuccessResult();
989
722
  }
990
723
  });
991
- var getByProjectAndType = query({
724
+ var updateEdgeInConvex = internalMutation({
992
725
  args: {
993
- ...optionalScopeArgs,
994
- edgeType: edgeTypeValidator
726
+ globalId: v.string(),
727
+ weight: v.optional(v.number()),
728
+ confidence: v.optional(v.number()),
729
+ context: v.optional(v.string()),
730
+ derivationType: v.optional(v.string()),
731
+ constraint: v.optional(v.string()),
732
+ propagation: v.optional(v.string()),
733
+ blocking: v.optional(v.boolean()),
734
+ implicit: v.optional(v.boolean()),
735
+ conditionalA: v.optional(subjectiveOpinionValidator),
736
+ conditionalNotA: v.optional(subjectiveOpinionValidator)
995
737
  },
996
738
  returns: permissiveReturn,
997
739
  handler: async (ctx, args) => {
998
- if (!args.projectId && !args.topicId) {
999
- return [];
740
+ const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
741
+ if (!existing) {
742
+ return buildEdgeMirrorMissingResult();
1000
743
  }
1001
- let scope;
1002
- try {
1003
- scope = await resolveTopicProjectScope(ctx, {
1004
- projectId: args.projectId,
1005
- topicId: args.topicId
1006
- });
1007
- } catch (error) {
1008
- debugGraphPrimitiveFallback(
1009
- "[epistemicEdges] Failed to resolve getAll scope",
1010
- {
1011
- error,
1012
- projectId: args.projectId,
1013
- topicId: args.topicId
1014
- }
1015
- );
1016
- return [];
744
+ const updates = {
745
+ updatedAt: Date.now()
746
+ };
747
+ if (args.weight !== void 0) {
748
+ updates.weight = args.weight;
1017
749
  }
1018
- const projectEdges = await collectScopedEdges(ctx, scope, 5e3);
1019
- return projectEdges.filter(
1020
- (e) => e.edgeType === args.edgeType && edgeMatchesWorkspaceReasoningScope(e, scope)
1021
- ).slice(0, 5e3);
1022
- }
1023
- });
1024
- var getByProject = query({
1025
- args: {
1026
- ...optionalScopeArgs,
1027
- userId: v.optional(v.string()),
1028
- limit: v.optional(v.number())
1029
- },
1030
- returns: permissiveReturn,
1031
- handler: async (ctx, args) => {
1032
- if (!args.projectId && !args.topicId) {
1033
- return [];
750
+ if (args.confidence !== void 0) {
751
+ updates.confidence = args.confidence;
1034
752
  }
1035
- let scope;
1036
- try {
1037
- scope = await resolveTopicProjectScope(ctx, {
1038
- projectId: args.projectId,
1039
- topicId: args.topicId
1040
- });
1041
- } catch (error) {
1042
- debugGraphPrimitiveFallback(
1043
- "[epistemicEdges] Failed to resolve getByProject scope",
1044
- {
1045
- error,
1046
- projectId: args.projectId,
1047
- topicId: args.topicId
1048
- }
1049
- );
1050
- return [];
753
+ if (args.context !== void 0) {
754
+ updates.context = args.context;
1051
755
  }
1052
- if (args.userId) {
1053
- const hasAccess = await checkScopeAccess(
1054
- ctx,
1055
- String(scope.topicId ?? scope.projectId),
1056
- args.userId
1057
- );
1058
- if (!hasAccess) {
1059
- return [];
1060
- }
756
+ if (args.derivationType !== void 0) {
757
+ updates.derivationType = args.derivationType;
1061
758
  }
1062
- const pageSize = Math.max(1, Math.min(Math.floor(args.limit ?? 500), 2e3));
1063
- const edges = await collectScopedEdges(
1064
- ctx,
1065
- scope,
1066
- Math.min(pageSize * 3, 6e3)
759
+ if (args.constraint !== void 0) {
760
+ updates.constraint = args.constraint;
761
+ }
762
+ if (args.propagation !== void 0) {
763
+ updates.propagation = args.propagation;
764
+ }
765
+ if (args.blocking !== void 0) {
766
+ updates.blocking = args.blocking;
767
+ }
768
+ if (args.implicit !== void 0) {
769
+ updates.implicit = args.implicit;
770
+ }
771
+ if (args.conditionalA !== void 0) {
772
+ updates.conditionalA = args.conditionalA;
773
+ }
774
+ if (args.conditionalNotA !== void 0) {
775
+ updates.conditionalNotA = args.conditionalNotA;
776
+ }
777
+ await ctx.db.patch(
778
+ requireEdgeId(existing._id, "Update mirrored edge in Convex"),
779
+ updates
1067
780
  );
1068
- return edges.filter((edge) => edgeMatchesWorkspaceReasoningScope(edge, scope)).slice(0, pageSize);
781
+ console.log(`[Dual-Write] Updated edge in Convex: ${args.globalId}`);
782
+ return buildEdgeStatusSuccessResult();
1069
783
  }
1070
784
  });
1071
- var listAll = query({
1072
- args: {
1073
- limit: v.optional(v.number())
785
+
786
+ // src/graphTypes.ts
787
+ var EDGE_TYPE_TO_REL = {
788
+ // === THE SIX CANONICAL EPISTEMIC EDGE TYPES ===
789
+ supports: "SUPPORTS",
790
+ // L3↔L3: belief bears on belief (weight -1 to +1)
791
+ informs: "INFORMS",
792
+ // L2→L3: evidence bears on belief
793
+ depends_on: "DEPENDS_ON",
794
+ // L3→L3, Q→Q: structural gate
795
+ derived_from: "DERIVED_FROM",
796
+ // Any→Any: provenance chain (fork, synthesis, extraction, answer)
797
+ contains: "CONTAINS",
798
+ // Any→Any: hierarchy, scoping, membership
799
+ tests: "TESTS",
800
+ // Q→L3: question interrogates belief
801
+ // === L4 DECISION EDGES (derived_from with derivationType=decision) ===
802
+ // Kept as separate Neo4j relationship types for backward compat with L4 queries.
803
+ // New code should use derived_from + metadata.
804
+ based_on_belief: "BASED_ON_BELIEF",
805
+ based_on_question: "BASED_ON_QUESTION",
806
+ blocked_by_contradiction: "BLOCKED_BY_CONTRADICTION",
807
+ informed_by_theme: "INFORMED_BY_THEME",
808
+ // === ONTOLOGICAL EDGES (tenant-extensible, managed by ontology system) ===
809
+ works_at: "WORKS_AT",
810
+ invested_in: "INVESTED_IN",
811
+ competes_with: "COMPETES_WITH",
812
+ participates_in: "PARTICIPATES_IN",
813
+ founded_by: "FOUNDED_BY",
814
+ evaluates: "EVALUATES",
815
+ performs: "PERFORMS",
816
+ function_in: "FUNCTION_IN",
817
+ impacts: "IMPACTS",
818
+ raised_from: "RAISED_FROM",
819
+ mentioned_in: "MENTIONED_IN",
820
+ perspective_on: "PERSPECTIVE_ON",
821
+ about_entity: "ABOUT_ENTITY",
822
+ entity_referenced_in: "ENTITY_REFERENCED_IN"
823
+ };
824
+ function getNodeLayer(nodeType) {
825
+ const L4_TYPES = ["decision"];
826
+ const L3_TYPES = ["belief", "question", "theme", "deal"];
827
+ const L2_TYPES = ["claim", "evidence", "synthesis", "answer"];
828
+ const L1_TYPES = ["atomic_fact", "excerpt", "source"];
829
+ const ONTOLOGICAL_TYPES = [
830
+ "company",
831
+ "person",
832
+ "investor",
833
+ "function",
834
+ "value_chain"
835
+ ];
836
+ const ORGANIZATIONAL_TYPES = ["topic"];
837
+ if (L4_TYPES.includes(nodeType)) {
838
+ return "L4";
839
+ }
840
+ if (L3_TYPES.includes(nodeType)) {
841
+ return "L3";
842
+ }
843
+ if (L2_TYPES.includes(nodeType)) {
844
+ return "L2";
845
+ }
846
+ if (L1_TYPES.includes(nodeType)) {
847
+ return "L1";
848
+ }
849
+ if (ONTOLOGICAL_TYPES.includes(nodeType)) {
850
+ return "ontological";
851
+ }
852
+ if (ORGANIZATIONAL_TYPES.includes(nodeType)) {
853
+ return "organizational";
854
+ }
855
+ console.warn(`[GraphTypes] Unknown nodeType "${nodeType}", defaulting to L2`);
856
+ return "L2";
857
+ }
858
+ var CANONICAL_EPISTEMIC_TYPES = /* @__PURE__ */ new Set([
859
+ "supports",
860
+ "informs",
861
+ "depends_on",
862
+ "derived_from",
863
+ "contains",
864
+ "tests"
865
+ ]);
866
+ function isDeprecatedEdgeType(edgeType) {
867
+ if (CANONICAL_EPISTEMIC_TYPES.has(edgeType)) {
868
+ return false;
869
+ }
870
+ if (edgeType in EDGE_TYPE_TO_REL) {
871
+ return false;
872
+ }
873
+ return true;
874
+ }
875
+
876
+ // src/edgeValidation.ts
877
+ var VALID_LAYER_CONNECTIONS = {
878
+ ontological: /* @__PURE__ */ new Set(["ontological", "L1", "L2", "L3"]),
879
+ L1: /* @__PURE__ */ new Set(["L1", "L2"]),
880
+ L2: /* @__PURE__ */ new Set(["L1", "L2", "L3", "ontological"]),
881
+ L3: /* @__PURE__ */ new Set(["L2", "L3", "L4", "ontological", "organizational"]),
882
+ L4: /* @__PURE__ */ new Set(["L3", "L4"]),
883
+ organizational: /* @__PURE__ */ new Set(["organizational", "L3"])
884
+ };
885
+ function getLayerDepth(layer) {
886
+ switch (layer) {
887
+ case "L4":
888
+ return 4;
889
+ case "L3":
890
+ return 3;
891
+ case "L2":
892
+ return 2;
893
+ case "L1":
894
+ return 1;
895
+ case "ontological":
896
+ return 0;
897
+ default:
898
+ return -1;
899
+ }
900
+ }
901
+ function isValidLayerConnection(fromLayer, toLayer) {
902
+ if (!VALID_LAYER_CONNECTIONS[fromLayer].has(toLayer)) {
903
+ return false;
904
+ }
905
+ if (fromLayer === "ontological" || toLayer === "ontological") {
906
+ return true;
907
+ }
908
+ if (fromLayer === "organizational" || toLayer === "organizational") {
909
+ return true;
910
+ }
911
+ const fromDepth = getLayerDepth(fromLayer);
912
+ const toDepth = getLayerDepth(toLayer);
913
+ if (Math.abs(fromDepth - toDepth) > 1) {
914
+ return false;
915
+ }
916
+ return true;
917
+ }
918
+ function shouldContinueTraversal(currentLayer, targetLayer, options) {
919
+ const currentDepth = getLayerDepth(currentLayer);
920
+ const targetDepth = getLayerDepth(targetLayer);
921
+ if (options.minLayer !== void 0 && targetDepth < options.minLayer) {
922
+ return false;
923
+ }
924
+ if (options.maxLayer !== void 0 && targetDepth > options.maxLayer) {
925
+ return false;
926
+ }
927
+ switch (options.mode) {
928
+ case "anchor_down":
929
+ return targetDepth <= currentDepth;
930
+ case "anchor_up":
931
+ return targetDepth >= currentDepth;
932
+ case "same_layer":
933
+ return targetDepth === currentDepth;
934
+ case "decision_trace":
935
+ return currentDepth === 4 ? targetDepth === 3 : targetDepth === currentDepth;
936
+ default:
937
+ return true;
938
+ }
939
+ }
940
+ function getDefaultMinLayer(mode) {
941
+ switch (mode) {
942
+ case "anchor_down":
943
+ return 1;
944
+ case "anchor_up":
945
+ return 2;
946
+ case "same_layer":
947
+ return 1;
948
+ case "decision_trace":
949
+ return 3;
950
+ default:
951
+ return 1;
952
+ }
953
+ }
954
+ var EDGE_LAYER_RULES = {
955
+ // === 6 Canonical Epistemic Types ===
956
+ supports: {
957
+ from: ["L3"],
958
+ to: ["L3"],
959
+ description: "Belief bears on Belief (weight: +1 supports, -1 contradicts)"
960
+ },
961
+ informs: {
962
+ from: ["L2"],
963
+ to: ["L3"],
964
+ description: "Evidence -> Belief (L2 -> L3)"
965
+ },
966
+ depends_on: {
967
+ from: ["L3"],
968
+ to: ["L3"],
969
+ description: "Belief B requires Belief A (structural gate)"
970
+ },
971
+ derived_from: {
972
+ from: ["L2", "L3", "L4"],
973
+ to: ["L1", "L2", "L3"],
974
+ description: "A was produced from B (provenance chain)"
975
+ },
976
+ contains: {
977
+ from: ["L3", "L4", "ontological"],
978
+ to: ["L2", "L3", "ontological"],
979
+ description: "A scopes/aggregates B (hierarchy)"
980
+ },
981
+ tests: {
982
+ from: ["L3"],
983
+ to: ["L3"],
984
+ description: "Question -> Belief (L3 -> L3)"
985
+ },
986
+ // === Structural / Lifecycle ===
987
+ supersedes: {
988
+ from: ["L3"],
989
+ to: ["L3"],
990
+ description: "NewNode -> OldNode (fork lineage)"
991
+ },
992
+ responds_to: {
993
+ from: ["L2", "L3"],
994
+ to: ["L3"],
995
+ description: "Answer -> Question (L2/L3 -> L3)"
996
+ },
997
+ belongs_to: {
998
+ from: ["L2", "L3", "ontological"],
999
+ to: ["L3"],
1000
+ description: "Membership (migrating to contains)"
1001
+ },
1002
+ relates_to_thesis: {
1003
+ from: ["L3"],
1004
+ to: ["L3"],
1005
+ description: "Belief -> Theme (L3 -> L3)"
1006
+ },
1007
+ // === Ontological (entity-entity or entity-epistemic bridge) ===
1008
+ works_at: {
1009
+ from: ["ontological"],
1010
+ to: ["ontological"],
1011
+ description: "Person -> Company"
1012
+ },
1013
+ invested_in: {
1014
+ from: ["ontological"],
1015
+ to: ["ontological"],
1016
+ description: "Investor -> Company"
1017
+ },
1018
+ competes_with: {
1019
+ from: ["ontological"],
1020
+ to: ["ontological"],
1021
+ description: "Company -> Company"
1022
+ },
1023
+ participates_in: {
1024
+ from: ["ontological"],
1025
+ to: ["ontological"],
1026
+ description: "Company -> ValueChain"
1027
+ },
1028
+ founded_by: {
1029
+ from: ["ontological"],
1030
+ to: ["ontological"],
1031
+ description: "Company -> Person"
1032
+ },
1033
+ evaluates: {
1034
+ from: ["ontological"],
1035
+ to: ["ontological"],
1036
+ description: "Deal -> Company"
1037
+ },
1038
+ performs: {
1039
+ from: ["ontological"],
1040
+ to: ["ontological"],
1041
+ description: "Company -> Function"
1042
+ },
1043
+ function_in: {
1044
+ from: ["ontological"],
1045
+ to: ["ontological"],
1046
+ description: "Function -> ValueChain"
1047
+ },
1048
+ impacts: {
1049
+ from: ["ontological", "L3"],
1050
+ to: ["ontological"],
1051
+ description: "Theme/Entity -> ValueChain"
1052
+ },
1053
+ raised_from: {
1054
+ from: ["ontological"],
1055
+ to: ["ontological"],
1056
+ description: "Company -> Investor"
1057
+ },
1058
+ mentioned_in: {
1059
+ from: ["ontological"],
1060
+ to: ["L1", "L2"],
1061
+ description: "Entity -> Source/Evidence"
1062
+ },
1063
+ perspective_on: {
1064
+ from: ["ontological"],
1065
+ to: ["L3"],
1066
+ description: "Person -> Belief/Theme"
1067
+ },
1068
+ plays_theme: {
1069
+ from: ["ontological"],
1070
+ to: ["L3"],
1071
+ description: "Deal -> Theme"
1072
+ },
1073
+ // C2-RR.4 — storage migration alias + the remaining 55 reachable edge types.
1074
+ // Mirrors the kernel rule table
1075
+ // (packages/reasoning-kernel/src/adapters/lib/edgeValidation.ts) so this
1076
+ // graph-primitives write path admits the same public edge vocabulary. See the
1077
+ // kernel file for the per-group rationale.
1078
+ extracted_from: {
1079
+ from: ["L2", "L3", "L4"],
1080
+ to: ["L1", "L2", "L3"],
1081
+ description: "Legacy storage migration alias for derived_from; public writes use derived_from"
1082
+ },
1083
+ qualifies: {
1084
+ from: ["L2", "L3"],
1085
+ to: ["L3"],
1086
+ description: "Evidence/Belief qualifies a Belief"
1087
+ },
1088
+ contradicts: {
1089
+ from: ["L2", "L3"],
1090
+ to: ["L3"],
1091
+ description: "Evidence/Belief contradicts a Belief"
1092
+ },
1093
+ reinforces: {
1094
+ from: ["L2", "L3"],
1095
+ to: ["L3"],
1096
+ description: "Evidence/Belief reinforces a Belief"
1097
+ },
1098
+ corroborates: {
1099
+ from: ["L2", "L3"],
1100
+ to: ["L3"],
1101
+ description: "Independent evidence/belief corroborates a Belief"
1102
+ },
1103
+ strengthened_by: {
1104
+ from: ["L3"],
1105
+ to: ["L2", "L3"],
1106
+ description: "Belief is strengthened by Evidence/Belief"
1107
+ },
1108
+ weakened_by: {
1109
+ from: ["L3"],
1110
+ to: ["L2", "L3"],
1111
+ description: "Belief is weakened by Evidence/Belief"
1112
+ },
1113
+ validated_by: {
1114
+ from: ["L3"],
1115
+ to: ["L2", "L3"],
1116
+ description: "Belief is validated by Evidence/Belief"
1117
+ },
1118
+ falsified_by: {
1119
+ from: ["L3"],
1120
+ to: ["L2", "L3"],
1121
+ description: "Belief is falsified by Evidence/Belief"
1122
+ },
1123
+ amplifies: {
1124
+ from: ["L3"],
1125
+ to: ["L3"],
1126
+ description: "Belief amplifies another Belief"
1127
+ },
1128
+ precondition_for: {
1129
+ from: ["L3"],
1130
+ to: ["L3"],
1131
+ description: "Belief is a precondition for another Belief"
1132
+ },
1133
+ prerequisite_for: {
1134
+ from: ["L3"],
1135
+ to: ["L3"],
1136
+ description: "Belief is a prerequisite for another Belief"
1137
+ },
1138
+ required_for: {
1139
+ from: ["L3"],
1140
+ to: ["L3"],
1141
+ description: "Belief is required for another Belief"
1142
+ },
1143
+ in_tension_with: {
1144
+ from: ["L3"],
1145
+ to: ["L3"],
1146
+ description: "Belief is in tension with another Belief"
1147
+ },
1148
+ mutually_exclusive: {
1149
+ from: ["L3"],
1150
+ to: ["L3"],
1151
+ description: "Beliefs cannot both hold"
1152
+ },
1153
+ exclusive_with: {
1154
+ from: ["L3"],
1155
+ to: ["L3"],
1156
+ description: "Belief is exclusive with another Belief"
1157
+ },
1158
+ alternative_to: {
1159
+ from: ["L3"],
1160
+ to: ["L3"],
1161
+ description: "Belief is an alternative to another Belief"
1162
+ },
1163
+ subsumes: {
1164
+ from: ["L3"],
1165
+ to: ["L3"],
1166
+ description: "Belief subsumes a narrower Belief"
1167
+ },
1168
+ extends: {
1169
+ from: ["L3"],
1170
+ to: ["L3"],
1171
+ description: "Belief extends another Belief"
1172
+ },
1173
+ refines: {
1174
+ from: ["L3"],
1175
+ to: ["L3"],
1176
+ description: "Belief refines another Belief"
1177
+ },
1178
+ implements: {
1179
+ from: ["L3"],
1180
+ to: ["L3"],
1181
+ description: "Belief implements an abstract Belief"
1182
+ },
1183
+ violates: {
1184
+ from: ["L3"],
1185
+ to: ["L3"],
1186
+ description: "Belief violates a constraint Belief"
1187
+ },
1188
+ assumes: {
1189
+ from: ["L3"],
1190
+ to: ["L3"],
1191
+ description: "Belief assumes another Belief"
1192
+ },
1193
+ would_predict: {
1194
+ from: ["L3"],
1195
+ to: ["L3"],
1196
+ description: "Belief would predict another Belief"
1197
+ },
1198
+ analogous_to: {
1199
+ from: ["L3"],
1200
+ to: ["L3"],
1201
+ description: "Belief is analogous to another Belief"
1202
+ },
1203
+ independent_of: {
1204
+ from: ["L3"],
1205
+ to: ["L3"],
1206
+ description: "Belief is independent of another Belief"
1207
+ },
1208
+ correlates_with: {
1209
+ from: ["L3"],
1210
+ to: ["L3"],
1211
+ description: "Belief correlates with another Belief"
1212
+ },
1213
+ co_changes_with: {
1214
+ from: ["L3"],
1215
+ to: ["L3"],
1216
+ description: "Belief co-changes with another Belief"
1217
+ },
1218
+ counterfactual_of: {
1219
+ from: ["L3"],
1220
+ to: ["L3"],
1221
+ description: "Belief is a counterfactual of another Belief"
1222
+ },
1223
+ parallel_to: {
1224
+ from: ["L3"],
1225
+ to: ["L3"],
1226
+ description: "Belief runs parallel to another Belief"
1227
+ },
1228
+ cascade_from: {
1229
+ from: ["L3"],
1230
+ to: ["L3"],
1231
+ description: "Cascade from an upstream Belief"
1232
+ },
1233
+ cascade_to: {
1234
+ from: ["L3"],
1235
+ to: ["L3"],
1236
+ description: "Cascade to a downstream Belief"
1237
+ },
1238
+ collapses_if: {
1239
+ from: ["L3"],
1240
+ to: ["L3"],
1241
+ description: "Belief collapses if another fails"
1242
+ },
1243
+ branches_from: {
1244
+ from: ["L3"],
1245
+ to: ["L3"],
1246
+ description: "Belief branches from an ancestor Belief"
1247
+ },
1248
+ same_as: {
1249
+ from: ["L2", "L3", "ontological"],
1250
+ to: ["L2", "L3", "ontological"],
1251
+ description: "Two nodes are the same entity"
1074
1252
  },
1075
- returns: permissiveReturn,
1076
- handler: async (ctx, args) => {
1077
- const pageSize = Math.max(
1078
- 1,
1079
- Math.min(Math.floor(args.limit ?? 5e3), 1e4)
1080
- );
1081
- return await ctx.db.query("epistemicEdges").order("desc").take(pageSize);
1082
- }
1083
- });
1084
- var findContradictions = query({
1085
- args: {
1086
- nodeId: v.id("epistemicNodes")
1253
+ answers: {
1254
+ from: ["L2", "L3"],
1255
+ to: ["L3"],
1256
+ description: "Answer/Belief answers a Question"
1087
1257
  },
1088
- returns: permissiveReturn,
1089
- handler: async (ctx, args) => {
1090
- const edges = await ctx.db.query("epistemicEdges").withIndex(
1091
- "by_to_type",
1092
- (q) => q.eq("toNodeId", args.nodeId).eq("edgeType", "informs")
1093
- ).collect();
1094
- return edges.filter((e) => (e.weight ?? 0) < 0);
1095
- }
1096
- });
1097
- var findSupport = query({
1098
- args: {
1099
- nodeId: v.id("epistemicNodes")
1258
+ partially_answers: {
1259
+ from: ["L2", "L3"],
1260
+ to: ["L3"],
1261
+ description: "Answer/Belief partially answers a Question"
1100
1262
  },
1101
- returns: permissiveReturn,
1102
- handler: async (ctx, args) => {
1103
- const edges = await ctx.db.query("epistemicEdges").withIndex(
1104
- "by_to_type",
1105
- (q) => q.eq("toNodeId", args.nodeId).eq("edgeType", "informs")
1106
- ).collect();
1107
- return edges.filter((e) => (e.weight ?? 0) >= 0);
1108
- }
1109
- });
1110
- var getLineage = query({
1111
- args: {
1112
- nodeId: v.id("epistemicNodes"),
1113
- maxDepth: v.optional(v.number()),
1114
- // Phase 2D: Layer-aware traversal options
1115
- minLayer: v.optional(v.number()),
1116
- // 1=L1, 2=L2, 3=L3, 4=L4
1117
- maxLayer: v.optional(v.number()),
1118
- mode: v.optional(
1119
- v.union(
1120
- v.literal("anchor_down"),
1121
- v.literal("anchor_up"),
1122
- v.literal("same_layer"),
1123
- v.literal("decision_trace")
1124
- )
1125
- )
1263
+ explores: {
1264
+ from: ["L3"],
1265
+ to: ["L3"],
1266
+ description: "Question explores a Belief/Theme"
1126
1267
  },
1127
- returns: permissiveReturn,
1128
- handler: async (ctx, args) => {
1129
- const maxDepth = args.maxDepth ?? 10;
1130
- const mode = args.mode ?? "anchor_down";
1131
- const minLayer = args.minLayer ?? getDefaultMinLayer(mode);
1132
- const maxLayer = args.maxLayer ?? 4;
1133
- const lineage = [];
1134
- const visited = /* @__PURE__ */ new Set();
1135
- const startNode = await ctx.db.get(args.nodeId);
1136
- if (!startNode) {
1137
- return lineage;
1138
- }
1139
- const startLayer = startNode.epistemicLayer || getNodeLayer(startNode.nodeType);
1140
- const queue = [{ nodeId: args.nodeId, depth: 0, currentLayer: startLayer }];
1141
- while (queue.length > 0) {
1142
- const current = queue.shift();
1143
- if (!current || current.depth >= maxDepth) {
1144
- continue;
1145
- }
1146
- const nodeIdStr = current.nodeId.toString();
1147
- if (visited.has(nodeIdStr)) {
1148
- continue;
1149
- }
1150
- visited.add(nodeIdStr);
1151
- const edges = await ctx.db.query("epistemicEdges").withIndex("by_from", (q) => q.eq("fromNodeId", current.nodeId)).collect();
1152
- const lineageEdges = edges.filter((e) => e.edgeType === "derived_from");
1153
- for (const edge of lineageEdges) {
1154
- if (!edge.toNodeId) {
1155
- continue;
1156
- }
1157
- const targetNode = await ctx.db.get(edge.toNodeId);
1158
- if (!targetNode || visited.has(edge.toNodeId.toString())) {
1159
- continue;
1160
- }
1161
- const targetLayer = edge.toLayer || targetNode.epistemicLayer || getNodeLayer(targetNode.nodeType);
1162
- const shouldContinue = shouldContinueTraversal(
1163
- current.currentLayer,
1164
- targetLayer,
1165
- { mode, minLayer, maxLayer }
1166
- );
1167
- if (!shouldContinue) {
1168
- continue;
1169
- }
1170
- lineage.push({
1171
- node: targetNode,
1172
- depth: current.depth + 1,
1173
- edgeType: edge.edgeType,
1174
- layer: targetLayer
1175
- });
1176
- queue.push({
1177
- nodeId: edge.toNodeId,
1178
- depth: current.depth + 1,
1179
- currentLayer: targetLayer
1180
- });
1181
- }
1182
- }
1183
- return lineage;
1184
- }
1185
- });
1186
- var getEvidenceForBelief = query({
1187
- args: {
1188
- beliefNodeId: v.id("epistemicNodes")
1268
+ informed_by_theme: {
1269
+ from: ["L3"],
1270
+ to: ["L3"],
1271
+ description: "Belief is informed by a Theme"
1189
1272
  },
1190
- returns: permissiveReturn,
1191
- handler: async (ctx, args) => {
1192
- const informsEdges = await ctx.db.query("epistemicEdges").withIndex(
1193
- "by_to_type",
1194
- (q) => q.eq("toNodeId", args.beliefNodeId).eq("edgeType", "informs")
1195
- ).collect();
1196
- const supportEdges = informsEdges.filter((e) => (e.weight ?? 0) >= 0);
1197
- const contradictEdges = informsEdges.filter((e) => (e.weight ?? 0) < 0);
1198
- const supportingEvidence = await Promise.all(
1199
- supportEdges.map(async (edge) => ({
1200
- edge,
1201
- node: await ctx.db.get(edge.fromNodeId)
1202
- }))
1203
- );
1204
- const contradictingEvidence = await Promise.all(
1205
- contradictEdges.map(async (edge) => ({
1206
- edge,
1207
- node: await ctx.db.get(edge.fromNodeId)
1208
- }))
1209
- );
1210
- const filteredSupporting = supportingEvidence.filter((e) => e.node !== null);
1211
- const filteredContradicting = contradictingEvidence.filter(
1212
- (e) => e.node !== null
1273
+ same_theme_as: {
1274
+ from: ["L3"],
1275
+ to: ["L3"],
1276
+ description: "Two Beliefs share a Theme"
1277
+ },
1278
+ based_on: {
1279
+ from: ["L4"],
1280
+ to: ["L3"],
1281
+ description: "Decision is based on a Belief/Question"
1282
+ },
1283
+ based_on_belief: {
1284
+ from: ["L4"],
1285
+ to: ["L3"],
1286
+ description: "Decision is based on a Belief"
1287
+ },
1288
+ based_on_question: {
1289
+ from: ["L4"],
1290
+ to: ["L3"],
1291
+ description: "Decision is based on a Question"
1292
+ },
1293
+ blocked_by_contradiction: {
1294
+ from: ["L4"],
1295
+ to: ["L3"],
1296
+ description: "Decision is blocked by a Contradiction"
1297
+ },
1298
+ parent_of: {
1299
+ from: ["L3", "L4", "ontological", "organizational"],
1300
+ to: ["L2", "L3", "ontological", "organizational"],
1301
+ description: "A is the parent of B"
1302
+ },
1303
+ child_of: {
1304
+ from: ["L2", "L3", "ontological", "organizational"],
1305
+ to: ["L3", "L4", "ontological", "organizational"],
1306
+ description: "A is the child of B"
1307
+ },
1308
+ scoped_by: {
1309
+ from: ["L2", "L3"],
1310
+ to: ["L3", "organizational"],
1311
+ description: "Object is scoped by a Topic/Theme"
1312
+ },
1313
+ cites: {
1314
+ from: ["L2", "L3"],
1315
+ to: ["L1", "L2"],
1316
+ description: "Evidence/Belief cites a Source/Excerpt"
1317
+ },
1318
+ summarizes: {
1319
+ from: ["L2", "L3"],
1320
+ to: ["L1", "L2"],
1321
+ description: "Synthesis/Evidence summarizes a Source/Evidence"
1322
+ },
1323
+ same_source_as: {
1324
+ from: ["L1", "L2"],
1325
+ to: ["L1", "L2"],
1326
+ description: "Two nodes derive from the same Source"
1327
+ },
1328
+ migrating_from: {
1329
+ from: ["L2", "L3", "L4"],
1330
+ to: ["L1", "L2", "L3"],
1331
+ description: "Migration lineage: from an ancestor"
1332
+ },
1333
+ migrating_to: {
1334
+ from: ["L1", "L2", "L3"],
1335
+ to: ["L2", "L3", "L4"],
1336
+ description: "Migration lineage: to a successor"
1337
+ },
1338
+ about_entity: {
1339
+ from: ["L2", "L3"],
1340
+ to: ["ontological"],
1341
+ description: "Belief/Evidence is about an Entity"
1342
+ },
1343
+ entity_referenced_in: {
1344
+ from: ["ontological"],
1345
+ to: ["L1", "L2"],
1346
+ description: "Entity is referenced in a Source/Evidence"
1347
+ },
1348
+ related_to: {
1349
+ from: ["L2", "L3", "ontological", "organizational"],
1350
+ to: ["L2", "L3", "ontological", "organizational"],
1351
+ description: "Lateral adjacency; no confidence pressure"
1352
+ },
1353
+ blocks: {
1354
+ from: ["L3", "L4"],
1355
+ to: ["L3", "L4"],
1356
+ description: "A blocks B (structural gate)"
1357
+ }
1358
+ };
1359
+ function validateEdgeLayers(edgeType, fromLayer, toLayer) {
1360
+ const rules = EDGE_LAYER_RULES[edgeType];
1361
+ if (!rules) {
1362
+ console.warn(
1363
+ `[EdgeValidation] Unknown edge type: ${edgeType}, allowing by default`
1213
1364
  );
1365
+ return { valid: true };
1366
+ }
1367
+ if (edgeType === "supersedes") {
1368
+ if (fromLayer !== toLayer) {
1369
+ return {
1370
+ valid: false,
1371
+ reason: `${edgeType} edges must be between nodes of the same layer. Got ${fromLayer} -> ${toLayer}`
1372
+ };
1373
+ }
1374
+ return { valid: true };
1375
+ }
1376
+ if (!rules.from.includes(fromLayer)) {
1214
1377
  return {
1215
- supporting: filteredSupporting,
1216
- contradicting: filteredContradicting,
1217
- supportCount: filteredSupporting.length,
1218
- contradictCount: filteredContradicting.length
1378
+ valid: false,
1379
+ reason: `Edge type '${edgeType}' does not allow source layer ${fromLayer}. Allowed: ${rules.from.join(", ")}`
1219
1380
  };
1220
1381
  }
1221
- });
1222
- var getClusterEdges = query({
1223
- args: {
1224
- beliefIds: v.array(v.string())
1225
- },
1226
- returns: permissiveReturn,
1227
- handler: async (ctx, args) => {
1228
- if (args.beliefIds.length === 0) {
1229
- return [];
1230
- }
1231
- const beliefIdSet = new Set(args.beliefIds);
1232
- const allEdges = await ctx.db.query("epistemicEdges").collect();
1233
- return allEdges.filter((edge) => {
1234
- const fromInCluster = beliefIdSet.has(String(edge.fromNodeId)) || edge.sourceGlobalId && beliefIdSet.has(edge.sourceGlobalId);
1235
- const toInCluster = beliefIdSet.has(String(edge.toNodeId)) || edge.targetGlobalId && beliefIdSet.has(edge.targetGlobalId);
1236
- return fromInCluster && toInCluster;
1237
- });
1382
+ if (!rules.to.includes(toLayer)) {
1383
+ return {
1384
+ valid: false,
1385
+ reason: `Edge type '${edgeType}' does not allow target layer ${toLayer}. Allowed: ${rules.to.join(", ")}`
1386
+ };
1238
1387
  }
1239
- });
1388
+ if (!isValidLayerConnection(fromLayer, toLayer)) {
1389
+ return {
1390
+ valid: false,
1391
+ reason: `Layer transition ${fromLayer} -> ${toLayer} is not permitted`
1392
+ };
1393
+ }
1394
+ return { valid: true };
1395
+ }
1240
1396
 
1241
1397
  // src/topicProjectOverlay.ts
1242
1398
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
@@ -1256,6 +1412,10 @@ function readStringArray(value) {
1256
1412
  function readMetadata(topic) {
1257
1413
  return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
1258
1414
  }
1415
+ function omitMetadataKey(metadata, key) {
1416
+ const { [key]: _omitted, ...rest } = metadata;
1417
+ return rest;
1418
+ }
1259
1419
  function readLegacyProjectId(value) {
1260
1420
  if (!value) {
1261
1421
  return;
@@ -1336,9 +1496,12 @@ async function resolveTopicDoc(ctx, scopeId) {
1336
1496
  );
1337
1497
  }
1338
1498
  try {
1339
- const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
1340
- projectId: String(scopeId)
1341
- });
1499
+ const topic = await ctx.runQuery(
1500
+ api.topics.getByLegacyScopeId,
1501
+ {
1502
+ projectId: String(scopeId)
1503
+ }
1504
+ );
1342
1505
  if (topic?.name !== void 0 && topic?.type !== void 0) {
1343
1506
  return topic;
1344
1507
  }
@@ -1358,8 +1521,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
1358
1521
  const outwardId = idMode === "topic" ? topicId : storageProjectId;
1359
1522
  const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
1360
1523
  const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
1361
- const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
1362
- const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
1524
+ let createdAt = 0;
1525
+ if (typeof topic.createdAt === "number") {
1526
+ createdAt = topic.createdAt;
1527
+ } else if (typeof topic._creationTime === "number") {
1528
+ createdAt = topic._creationTime;
1529
+ }
1530
+ let updatedAt = createdAt;
1531
+ if (typeof topic.updatedAt === "number") {
1532
+ updatedAt = topic.updatedAt;
1533
+ } else if (typeof metadata.updatedAt === "number") {
1534
+ updatedAt = metadata.updatedAt;
1535
+ }
1363
1536
  return {
1364
1537
  ...metadata,
1365
1538
  _id: outwardId,
@@ -1428,90 +1601,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
1428
1601
  if (!topic) {
1429
1602
  return null;
1430
1603
  }
1431
- const nextMetadata = { ...readMetadata(topic) };
1432
- const patch = {};
1433
- const topicUpdateArgs = {
1434
- id: String(topic._id)
1604
+ const plan = buildTopicProjectOverlayPatchPlan(topic, value);
1605
+ await applyTopicProjectOverlayPatch(ctx, topic, plan);
1606
+ return materializeTopicProjectOverlay({
1607
+ ...topic,
1608
+ ...plan.patch,
1609
+ metadata: plan.nextMetadata
1610
+ });
1611
+ }
1612
+ function buildTopicProjectOverlayPatchPlan(topic, value) {
1613
+ const plan = {
1614
+ nextMetadata: { ...readMetadata(topic) },
1615
+ patch: {},
1616
+ topicUpdateArgs: {
1617
+ id: String(topic._id)
1618
+ }
1435
1619
  };
1436
1620
  for (const [key, rawValue] of Object.entries(value)) {
1437
- switch (key) {
1438
- case "_id":
1439
- case "projectId":
1440
- case "topicId":
1441
- case "legacyProjectId":
1442
- case "storageProjectId":
1443
- break;
1444
- case "name":
1445
- case "description":
1446
- patch[key] = rawValue;
1447
- topicUpdateArgs[key] = rawValue;
1448
- break;
1449
- case "tenantId":
1450
- case "workspaceId":
1451
- case "ownerId":
1452
- throw new Error(
1453
- `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
1454
- );
1455
- case "status": {
1456
- const status = coerceStatus(rawValue);
1457
- if (status) {
1458
- patch.status = status;
1459
- topicUpdateArgs.status = status;
1460
- }
1461
- break;
1462
- }
1463
- case "visibility": {
1464
- const visibility = coerceVisibility(rawValue);
1465
- if (visibility) {
1466
- patch.visibility = visibility;
1467
- topicUpdateArgs.visibility = visibility;
1468
- }
1469
- break;
1470
- }
1471
- case "type": {
1472
- const projectType = readNonEmptyString(rawValue);
1473
- if (projectType) {
1474
- nextMetadata.projectType = projectType;
1475
- } else {
1476
- delete nextMetadata.projectType;
1477
- }
1478
- break;
1479
- }
1480
- case "updatedAt":
1481
- case "createdAt":
1482
- break;
1483
- default:
1484
- if (rawValue === void 0) {
1485
- delete nextMetadata[key];
1486
- } else {
1487
- nextMetadata[key] = rawValue;
1488
- }
1489
- }
1621
+ applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
1622
+ }
1623
+ plan.patch.updatedAt = Date.now();
1624
+ plan.patch.metadata = plan.nextMetadata;
1625
+ plan.topicUpdateArgs.metadata = plan.nextMetadata;
1626
+ return plan;
1627
+ }
1628
+ function applyTopicProjectOverlayPatchEntry(plan, key, rawValue) {
1629
+ switch (key) {
1630
+ case "_id":
1631
+ case "projectId":
1632
+ case "topicId":
1633
+ case "legacyProjectId":
1634
+ case "storageProjectId":
1635
+ case "updatedAt":
1636
+ case "createdAt":
1637
+ return;
1638
+ case "name":
1639
+ case "description":
1640
+ plan.patch[key] = rawValue;
1641
+ plan.topicUpdateArgs[key] = rawValue;
1642
+ return;
1643
+ case "tenantId":
1644
+ case "workspaceId":
1645
+ case "ownerId":
1646
+ throw new Error(
1647
+ `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
1648
+ );
1649
+ case "status":
1650
+ applyTopicStatusPatch(plan, rawValue);
1651
+ return;
1652
+ case "visibility":
1653
+ applyTopicVisibilityPatch(plan, rawValue);
1654
+ return;
1655
+ case "type":
1656
+ applyTopicProjectTypePatch(plan, rawValue);
1657
+ return;
1658
+ default:
1659
+ applyTopicMetadataPatch(plan, key, rawValue);
1660
+ }
1661
+ }
1662
+ function applyTopicStatusPatch(plan, rawValue) {
1663
+ const status = coerceStatus(rawValue);
1664
+ if (status) {
1665
+ plan.patch.status = status;
1666
+ plan.topicUpdateArgs.status = status;
1667
+ }
1668
+ }
1669
+ function applyTopicVisibilityPatch(plan, rawValue) {
1670
+ const visibility = coerceVisibility(rawValue);
1671
+ if (visibility) {
1672
+ plan.patch.visibility = visibility;
1673
+ plan.topicUpdateArgs.visibility = visibility;
1674
+ }
1675
+ }
1676
+ function applyTopicProjectTypePatch(plan, rawValue) {
1677
+ const projectType = readNonEmptyString(rawValue);
1678
+ if (projectType) {
1679
+ plan.nextMetadata.projectType = projectType;
1680
+ return;
1490
1681
  }
1491
- patch.updatedAt = Date.now();
1492
- patch.metadata = nextMetadata;
1493
- topicUpdateArgs.metadata = nextMetadata;
1682
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, "projectType");
1683
+ }
1684
+ function applyTopicMetadataPatch(plan, key, rawValue) {
1685
+ if (rawValue === void 0) {
1686
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, key);
1687
+ return;
1688
+ }
1689
+ plan.nextMetadata[key] = rawValue;
1690
+ }
1691
+ async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
1494
1692
  if (typeof ctx.runMutation === "function") {
1495
1693
  try {
1496
- await ctx.runMutation(api.topics.update, topicUpdateArgs);
1694
+ await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
1497
1695
  } catch (error) {
1498
- if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
1696
+ if (!canPatchTopicViaLocalDb(ctx, error)) {
1499
1697
  throw error;
1500
1698
  }
1501
- await ctx.db.patch(String(topic._id), patch);
1699
+ await ctx.db.patch(topic._id, plan.patch);
1502
1700
  }
1503
1701
  } else if (ctx?.db && typeof ctx.db.patch === "function") {
1504
- await ctx.db.patch(String(topic._id), patch);
1702
+ await ctx.db.patch(topic._id, plan.patch);
1505
1703
  } else {
1506
1704
  throw new Error(
1507
1705
  "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
1508
1706
  );
1509
1707
  }
1510
- return materializeTopicProjectOverlay({
1511
- ...topic,
1512
- ...patch,
1513
- metadata: nextMetadata
1514
- });
1708
+ }
1709
+ function canPatchTopicViaLocalDb(ctx, error) {
1710
+ return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
1515
1711
  }
1516
1712
 
1517
1713
  // src/resolvers.ts
@@ -1539,7 +1735,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
1539
1735
  try {
1540
1736
  await patchTopicProjectOverlay(ctx, projectId, value);
1541
1737
  } catch (error) {
1542
- if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
1738
+ if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
1543
1739
  throw error;
1544
1740
  }
1545
1741
  console.warn(
@@ -1572,8 +1768,140 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
1572
1768
  ...resolverOverrides
1573
1769
  };
1574
1770
  }
1575
-
1576
- // src/epistemicEdges.mutations.ts
1771
+
1772
+ // src/epistemicEdges.mutations.ts
1773
+ var EPISTEMIC_LAYERS2 = /* @__PURE__ */ new Set([
1774
+ "L4",
1775
+ "L3",
1776
+ "L2",
1777
+ "L1",
1778
+ "ontological",
1779
+ "organizational"
1780
+ ]);
1781
+ function readOptionalString3(value) {
1782
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1783
+ }
1784
+ function readOptionalNumber(value) {
1785
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1786
+ }
1787
+ function readConvexId3(value) {
1788
+ const normalized = readOptionalString3(value);
1789
+ return normalized;
1790
+ }
1791
+ function readRecord3(value) {
1792
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1793
+ }
1794
+ function readEpistemicLayer(value) {
1795
+ const layer = readOptionalString3(value);
1796
+ return layer && EPISTEMIC_LAYERS2.has(layer) ? layer : void 0;
1797
+ }
1798
+ function readEdgeNodeRow(value) {
1799
+ const record = readRecord3(value);
1800
+ if (!record) {
1801
+ return null;
1802
+ }
1803
+ const id = readConvexId3(record._id);
1804
+ const globalId = readOptionalString3(record.globalId);
1805
+ const nodeType = readOptionalString3(record.nodeType);
1806
+ if (!(id && globalId && nodeType)) {
1807
+ return null;
1808
+ }
1809
+ const node = { _id: id, globalId, nodeType };
1810
+ const epistemicLayer = readEpistemicLayer(record.epistemicLayer);
1811
+ if (epistemicLayer !== void 0) {
1812
+ node.epistemicLayer = epistemicLayer;
1813
+ }
1814
+ const projectId = readOptionalString3(record.projectId);
1815
+ if (projectId !== void 0) {
1816
+ node.projectId = projectId;
1817
+ }
1818
+ return node;
1819
+ }
1820
+ function requireEdgeNodeRow(value, context) {
1821
+ const node = readEdgeNodeRow(value);
1822
+ if (!node) {
1823
+ throw new Error(`${context} requires a canonical epistemic node row.`);
1824
+ }
1825
+ return node;
1826
+ }
1827
+ function readEdgeRow(value) {
1828
+ const record = readRecord3(value);
1829
+ if (!record) {
1830
+ return null;
1831
+ }
1832
+ const id = readConvexId3(record._id);
1833
+ const globalId = readOptionalString3(record.globalId);
1834
+ const edgeType = readOptionalString3(record.edgeType);
1835
+ if (!(id && globalId && edgeType)) {
1836
+ return null;
1837
+ }
1838
+ const edge = { _id: id, globalId, edgeType };
1839
+ const createdBy = readOptionalString3(record.createdBy);
1840
+ if (createdBy !== void 0) {
1841
+ edge.createdBy = createdBy;
1842
+ }
1843
+ const fromNodeId = readOptionalString3(record.fromNodeId);
1844
+ if (fromNodeId !== void 0) {
1845
+ edge.fromNodeId = fromNodeId;
1846
+ }
1847
+ const projectId = readOptionalString3(record.projectId);
1848
+ if (projectId !== void 0) {
1849
+ edge.projectId = projectId;
1850
+ }
1851
+ const toNodeId = readOptionalString3(record.toNodeId);
1852
+ if (toNodeId !== void 0) {
1853
+ edge.toNodeId = toNodeId;
1854
+ }
1855
+ const confidence = readOptionalNumber(record.confidence);
1856
+ if (confidence !== void 0) {
1857
+ edge.confidence = confidence;
1858
+ }
1859
+ const weight = readOptionalNumber(record.weight);
1860
+ if (weight !== void 0) {
1861
+ edge.weight = weight;
1862
+ }
1863
+ if ("context" in record) {
1864
+ edge.context = record.context;
1865
+ }
1866
+ if ("description" in record) {
1867
+ edge.description = record.description;
1868
+ }
1869
+ return edge;
1870
+ }
1871
+ function readEdgeRows(values) {
1872
+ return values.flatMap((value) => {
1873
+ const edge = readEdgeRow(value);
1874
+ return edge ? [edge] : [];
1875
+ });
1876
+ }
1877
+ function nodeEndpointRefs(nodeId, node) {
1878
+ const refs = /* @__PURE__ */ new Set([String(nodeId)]);
1879
+ if (node?.globalId) {
1880
+ refs.add(node.globalId);
1881
+ }
1882
+ return [...refs];
1883
+ }
1884
+ async function collectEdgesBetween(ctx, fromRefs, toRefs) {
1885
+ const seen = /* @__PURE__ */ new Set();
1886
+ const edges = [];
1887
+ for (const fromRef of fromRefs) {
1888
+ for (const toRef of toRefs) {
1889
+ const rows = await ctx.db.query("epistemicEdges").withIndex(
1890
+ "by_from_to",
1891
+ (q) => q.eq("fromNodeId", fromRef).eq("toNodeId", toRef)
1892
+ ).collect();
1893
+ for (const edge of readEdgeRows(rows)) {
1894
+ const key = String(edge._id);
1895
+ if (seen.has(key)) {
1896
+ continue;
1897
+ }
1898
+ seen.add(key);
1899
+ edges.push(edge);
1900
+ }
1901
+ }
1902
+ }
1903
+ return edges;
1904
+ }
1577
1905
  var create = mutation({
1578
1906
  args: {
1579
1907
  globalId: v.string(),
@@ -1590,11 +1918,14 @@ var create = mutation({
1590
1918
  },
1591
1919
  returns: permissiveReturn,
1592
1920
  handler: async (ctx, args) => {
1593
- const fromNode = await ctx.db.get(args.fromNodeId);
1594
- const toNode = await ctx.db.get(args.toNodeId);
1595
- if (!fromNode || !toNode) {
1596
- throw new Error("One or both nodes not found");
1597
- }
1921
+ const fromNode = requireEdgeNodeRow(
1922
+ await ctx.db.get(args.fromNodeId),
1923
+ "Create edge source"
1924
+ );
1925
+ const toNode = requireEdgeNodeRow(
1926
+ await ctx.db.get(args.toNodeId),
1927
+ "Create edge target"
1928
+ );
1598
1929
  const resolvedScope = args.topicId || args.projectId ? await resolveTopicProjectScope(ctx, {
1599
1930
  topicId: args.topicId,
1600
1931
  projectId: args.projectId
@@ -1603,8 +1934,8 @@ var create = mutation({
1603
1934
  if (resolvedProjectId) {
1604
1935
  await requireScopeWriteAccess(ctx, resolvedProjectId, args.createdBy);
1605
1936
  }
1606
- const fromLayer = fromNode.epistemicLayer || getNodeLayer(fromNode.nodeType);
1607
- const toLayer = toNode.epistemicLayer || getNodeLayer(toNode.nodeType);
1937
+ const fromLayer = fromNode.epistemicLayer ?? getNodeLayer(fromNode.nodeType);
1938
+ const toLayer = toNode.epistemicLayer ?? getNodeLayer(toNode.nodeType);
1608
1939
  if (!args.skipLayerValidation) {
1609
1940
  const validation = validateEdgeLayers(args.edgeType, fromLayer, toLayer);
1610
1941
  if (!validation.valid) {
@@ -1618,10 +1949,11 @@ var create = mutation({
1618
1949
  `FORBIDDEN: Edge type '${args.edgeType}' has been removed from StackOS schema.`
1619
1950
  );
1620
1951
  }
1621
- const existing = await ctx.db.query("epistemicEdges").withIndex(
1622
- "by_from_to",
1623
- (q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
1624
- ).collect();
1952
+ const existing = await collectEdgesBetween(
1953
+ ctx,
1954
+ nodeEndpointRefs(args.fromNodeId, fromNode),
1955
+ nodeEndpointRefs(args.toNodeId, toNode)
1956
+ );
1625
1957
  const duplicateEdge = existing.find((e) => e.edgeType === args.edgeType);
1626
1958
  if (duplicateEdge) {
1627
1959
  return { edgeId: duplicateEdge._id, isDuplicate: true };
@@ -1698,7 +2030,7 @@ var update = mutation({
1698
2030
  returns: permissiveReturn,
1699
2031
  handler: async (ctx, args) => {
1700
2032
  const { edgeId, userId, ...updates } = args;
1701
- const edge = await ctx.db.get(edgeId);
2033
+ const edge = readEdgeRow(await ctx.db.get(edgeId));
1702
2034
  if (!edge) {
1703
2035
  throw new Error("Edge not found");
1704
2036
  }
@@ -1739,7 +2071,7 @@ var remove = mutation({
1739
2071
  },
1740
2072
  returns: permissiveReturn,
1741
2073
  handler: async (ctx, args) => {
1742
- const edge = await ctx.db.get(args.edgeId);
2074
+ const edge = readEdgeRow(await ctx.db.get(args.edgeId));
1743
2075
  if (!edge) {
1744
2076
  return buildEdgeNotFoundResult();
1745
2077
  }
@@ -1785,10 +2117,13 @@ var removeBetween = mutation({
1785
2117
  },
1786
2118
  returns: permissiveReturn,
1787
2119
  handler: async (ctx, args) => {
1788
- const edges = await ctx.db.query("epistemicEdges").withIndex(
1789
- "by_from_to",
1790
- (q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
1791
- ).collect();
2120
+ const fromNode = readEdgeNodeRow(await ctx.db.get(args.fromNodeId));
2121
+ const toNode = readEdgeNodeRow(await ctx.db.get(args.toNodeId));
2122
+ const edges = await collectEdgesBetween(
2123
+ ctx,
2124
+ nodeEndpointRefs(args.fromNodeId, fromNode),
2125
+ nodeEndpointRefs(args.toNodeId, toNode)
2126
+ );
1792
2127
  let deleted = 0;
1793
2128
  for (const edge of edges) {
1794
2129
  if (!args.edgeType || edge.edgeType === args.edgeType) {
@@ -1825,19 +2160,23 @@ var batchCreate = mutation({
1825
2160
  const results = [];
1826
2161
  const errors = [];
1827
2162
  for (const edge of args.edges) {
1828
- const fromNode = await ctx.db.get(edge.fromNodeId);
1829
- const toNode = await ctx.db.get(edge.toNodeId);
1830
- if (!fromNode || !toNode) {
2163
+ const fromNode = readEdgeNodeRow(await ctx.db.get(edge.fromNodeId));
2164
+ const toNode = readEdgeNodeRow(await ctx.db.get(edge.toNodeId));
2165
+ if (!(fromNode && toNode)) {
1831
2166
  errors.push({
1832
2167
  globalId: edge.globalId,
1833
2168
  error: "One or both nodes not found"
1834
2169
  });
1835
2170
  continue;
1836
2171
  }
1837
- const fromLayer = fromNode.epistemicLayer || getNodeLayer(fromNode.nodeType);
1838
- const toLayer = toNode.epistemicLayer || getNodeLayer(toNode.nodeType);
2172
+ const fromLayer = fromNode.epistemicLayer ?? getNodeLayer(fromNode.nodeType);
2173
+ const toLayer = toNode.epistemicLayer ?? getNodeLayer(toNode.nodeType);
1839
2174
  if (!args.skipLayerValidation) {
1840
- const validation = validateEdgeLayers(edge.edgeType, fromLayer, toLayer);
2175
+ const validation = validateEdgeLayers(
2176
+ edge.edgeType,
2177
+ fromLayer,
2178
+ toLayer
2179
+ );
1841
2180
  if (!validation.valid) {
1842
2181
  errors.push({
1843
2182
  globalId: edge.globalId,
@@ -1872,9 +2211,7 @@ var batchCreate = mutation({
1872
2211
  results.push({ globalId: edge.globalId, edgeGlobalId: edge.globalId });
1873
2212
  }
1874
2213
  const projectIds = new Set(
1875
- args.edges.flatMap(
1876
- (edge) => typeof edge.projectId === "string" ? [edge.projectId] : []
1877
- )
2214
+ args.edges.flatMap((edge) => edge.projectId ? [edge.projectId] : [])
1878
2215
  );
1879
2216
  for (const pid of projectIds) {
1880
2217
  if (pid) {
@@ -1895,8 +2232,11 @@ var cleanupDeprecatedEdges = mutation({
1895
2232
  handler: async (ctx, args) => {
1896
2233
  const DEPRECATED_TYPES = ["contradicts"];
1897
2234
  const scopeId = args.topicId || args.projectId;
1898
- const allEdges = scopeId ? await ctx.db.query("epistemicEdges").withIndex("by_topic", (q) => q.eq("topicId", scopeId)).collect() : [];
1899
- const deprecatedEdges = allEdges.filter(
2235
+ const allEdges = scopeId ? await ctx.db.query("epistemicEdges").withIndex(
2236
+ "by_topic",
2237
+ (q) => q.eq("topicId", scopeId)
2238
+ ).collect() : [];
2239
+ const deprecatedEdges = readEdgeRows(allEdges).filter(
1900
2240
  (edge) => DEPRECATED_TYPES.includes(edge.edgeType)
1901
2241
  );
1902
2242
  if (args.dryRun) {
@@ -1939,15 +2279,15 @@ var deleteEdges = mutation({
1939
2279
  const deleted = [];
1940
2280
  const notFound = [];
1941
2281
  for (const edgeId of args.edgeIds) {
1942
- const edge = await ctx.db.get(edgeId);
2282
+ const edge = readEdgeRow(await ctx.db.get(edgeId));
1943
2283
  if (edge) {
1944
2284
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
1945
2285
  globalId: edge.globalId
1946
2286
  });
1947
2287
  await ctx.db.delete(edgeId);
1948
- deleted.push(edgeId);
2288
+ deleted.push(String(edgeId));
1949
2289
  } else {
1950
- notFound.push(edgeId);
2290
+ notFound.push(String(edgeId));
1951
2291
  }
1952
2292
  }
1953
2293
  return {
@@ -1957,219 +2297,643 @@ var deleteEdges = mutation({
1957
2297
  };
1958
2298
  }
1959
2299
  });
1960
- async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
1961
- assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
1962
- const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
1963
- if (!node) {
1964
- throw new Error(
1965
- `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
2300
+ var DERIVED_FROM_EDGE_TYPE = "derived_from";
2301
+ var EPISTEMIC_LAYERS3 = /* @__PURE__ */ new Set([
2302
+ "L4",
2303
+ "L3",
2304
+ "L2",
2305
+ "L1",
2306
+ "ontological",
2307
+ "organizational"
2308
+ ]);
2309
+ function readOptionalString4(value) {
2310
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
2311
+ }
2312
+ function readOptionalNumber2(value) {
2313
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2314
+ }
2315
+ function readConvexId4(value) {
2316
+ const normalized = readOptionalString4(value);
2317
+ return normalized;
2318
+ }
2319
+ function readRecord4(value) {
2320
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2321
+ }
2322
+ function readEpistemicLayer2(value) {
2323
+ const layer = readOptionalString4(value);
2324
+ return layer && EPISTEMIC_LAYERS3.has(layer) ? layer : void 0;
2325
+ }
2326
+ function readEdgeNodeRow2(value) {
2327
+ const record = readRecord4(value);
2328
+ if (!record) {
2329
+ return null;
2330
+ }
2331
+ const id = readConvexId4(record._id);
2332
+ const nodeType = readOptionalString4(record.nodeType);
2333
+ if (!(id && nodeType)) {
2334
+ return null;
2335
+ }
2336
+ const node = { ...record, _id: id, nodeType };
2337
+ const globalId = readOptionalString4(record.globalId);
2338
+ if (globalId !== void 0) {
2339
+ node.globalId = globalId;
2340
+ }
2341
+ const epistemicLayer = readEpistemicLayer2(record.epistemicLayer);
2342
+ if (epistemicLayer !== void 0) {
2343
+ node.epistemicLayer = epistemicLayer;
2344
+ }
2345
+ return node;
2346
+ }
2347
+ function readEdgeRow2(value) {
2348
+ const record = readRecord4(value);
2349
+ if (!record) {
2350
+ return null;
2351
+ }
2352
+ const id = readConvexId4(record._id);
2353
+ const edgeType = readOptionalString4(record.edgeType);
2354
+ if (!(id && edgeType)) {
2355
+ return null;
2356
+ }
2357
+ const edge = { ...record, _id: id, edgeType };
2358
+ const globalId = readOptionalString4(record.globalId);
2359
+ if (globalId !== void 0) {
2360
+ edge.globalId = globalId;
2361
+ }
2362
+ const fromNodeId = readOptionalString4(record.fromNodeId);
2363
+ if (fromNodeId !== void 0) {
2364
+ edge.fromNodeId = fromNodeId;
2365
+ }
2366
+ const toNodeId = readOptionalString4(record.toNodeId);
2367
+ if (toNodeId !== void 0) {
2368
+ edge.toNodeId = toNodeId;
2369
+ }
2370
+ const sourceGlobalId = readOptionalString4(record.sourceGlobalId);
2371
+ if (sourceGlobalId !== void 0) {
2372
+ edge.sourceGlobalId = sourceGlobalId;
2373
+ }
2374
+ const targetGlobalId = readOptionalString4(record.targetGlobalId);
2375
+ if (targetGlobalId !== void 0) {
2376
+ edge.targetGlobalId = targetGlobalId;
2377
+ }
2378
+ const fromGlobalId = readOptionalString4(record.fromGlobalId);
2379
+ if (fromGlobalId !== void 0) {
2380
+ edge.fromGlobalId = fromGlobalId;
2381
+ }
2382
+ const toGlobalId = readOptionalString4(record.toGlobalId);
2383
+ if (toGlobalId !== void 0) {
2384
+ edge.toGlobalId = toGlobalId;
2385
+ }
2386
+ const toLayer = readEpistemicLayer2(record.toLayer);
2387
+ if (toLayer !== void 0) {
2388
+ edge.toLayer = toLayer;
2389
+ }
2390
+ const weight = readOptionalNumber2(record.weight);
2391
+ if (weight !== void 0) {
2392
+ edge.weight = weight;
2393
+ }
2394
+ return edge;
2395
+ }
2396
+ function readEdgeRows2(values) {
2397
+ return values.flatMap((value) => {
2398
+ const edge = readEdgeRow2(value);
2399
+ return edge ? [edge] : [];
2400
+ });
2401
+ }
2402
+ function dedupeEdges(edges) {
2403
+ const seen = /* @__PURE__ */ new Set();
2404
+ const deduped = [];
2405
+ for (const edge of edges) {
2406
+ const key = String(edge._id);
2407
+ if (seen.has(key)) {
2408
+ continue;
2409
+ }
2410
+ seen.add(key);
2411
+ deduped.push(edge);
2412
+ }
2413
+ return deduped;
2414
+ }
2415
+ function filterEdgesByType(edges, edgeType) {
2416
+ return edgeType ? edges.filter((edge) => edge.edgeType === edgeType) : [...edges];
2417
+ }
2418
+ function nodeEndpointRefs2(node) {
2419
+ const refs = /* @__PURE__ */ new Set([String(node._id)]);
2420
+ if (node.globalId) {
2421
+ refs.add(node.globalId);
2422
+ }
2423
+ return [...refs];
2424
+ }
2425
+ function edgeSourceRefs(edge) {
2426
+ return [edge.fromNodeId, edge.sourceGlobalId, edge.fromGlobalId].flatMap(
2427
+ (value) => value ? [value] : []
2428
+ );
2429
+ }
2430
+ function edgeTargetRefs(edge) {
2431
+ return [edge.toNodeId, edge.targetGlobalId, edge.toGlobalId].flatMap(
2432
+ (value) => value ? [value] : []
2433
+ );
2434
+ }
2435
+ function primaryEdgeSourceRef(edge) {
2436
+ return edge.sourceGlobalId ?? edge.fromGlobalId ?? edge.fromNodeId;
2437
+ }
2438
+ function primaryEdgeTargetRef(edge) {
2439
+ return edge.targetGlobalId ?? edge.toGlobalId ?? edge.toNodeId;
2440
+ }
2441
+ function hasVisitedNode(node, visited) {
2442
+ return nodeEndpointRefs2(node).some((ref) => visited.has(ref));
2443
+ }
2444
+ function markVisitedNode(node, visited) {
2445
+ for (const ref of nodeEndpointRefs2(node)) {
2446
+ visited.add(ref);
2447
+ }
2448
+ }
2449
+ async function resolveNodeRefSoft(ctx, nodeRef) {
2450
+ const normalizedId = ctx.db.normalizeId?.("epistemicNodes", nodeRef) ?? readConvexId4(nodeRef);
2451
+ if (normalizedId) {
2452
+ try {
2453
+ const direct = readEdgeNodeRow2(await ctx.db.get(normalizedId));
2454
+ if (direct) {
2455
+ return direct;
2456
+ }
2457
+ } catch (error) {
2458
+ debugGraphPrimitiveFallback(
2459
+ "[epistemicEdges] Direct node-ref lookup failed",
2460
+ { error, nodeRef }
2461
+ );
2462
+ }
2463
+ }
2464
+ return readEdgeNodeRow2(
2465
+ await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", nodeRef)).first()
2466
+ );
2467
+ }
2468
+ async function collectNodeReferenceIds(ctx, nodeRef) {
2469
+ const refs = /* @__PURE__ */ new Set([nodeRef]);
2470
+ const node = await resolveNodeRefSoft(ctx, nodeRef);
2471
+ if (node) {
2472
+ for (const ref of nodeEndpointRefs2(node)) {
2473
+ refs.add(ref);
2474
+ }
2475
+ }
2476
+ return [...refs];
2477
+ }
2478
+ async function collectEdgesFromRefs(ctx, refs, edgeType) {
2479
+ const reads = [];
2480
+ for (const ref of refs) {
2481
+ reads.push(
2482
+ edgeType ? ctx.db.query("epistemicEdges").withIndex(
2483
+ "by_from_type",
2484
+ (q) => q.eq("fromNodeId", ref).eq("edgeType", edgeType)
2485
+ ).collect() : ctx.db.query("epistemicEdges").withIndex("by_from", (q) => q.eq("fromNodeId", ref)).collect(),
2486
+ ctx.db.query("epistemicEdges").withIndex("by_source_global_id", (q) => q.eq("sourceGlobalId", ref)).collect()
2487
+ );
2488
+ }
2489
+ const edges = readEdgeRows2((await Promise.all(reads)).flat());
2490
+ return dedupeEdges(filterEdgesByType(edges, edgeType));
2491
+ }
2492
+ async function collectEdgesToRefs(ctx, refs, edgeType) {
2493
+ const reads = [];
2494
+ for (const ref of refs) {
2495
+ reads.push(
2496
+ edgeType ? ctx.db.query("epistemicEdges").withIndex(
2497
+ "by_to_type",
2498
+ (q) => q.eq("toNodeId", ref).eq("edgeType", edgeType)
2499
+ ).collect() : ctx.db.query("epistemicEdges").withIndex("by_to", (q) => q.eq("toNodeId", ref)).collect(),
2500
+ ctx.db.query("epistemicEdges").withIndex("by_target_global_id", (q) => q.eq("targetGlobalId", ref)).collect()
2501
+ );
2502
+ }
2503
+ const edges = readEdgeRows2((await Promise.all(reads)).flat());
2504
+ return dedupeEdges(filterEdgesByType(edges, edgeType));
2505
+ }
2506
+ async function collectEdgesFromNodeRef(ctx, nodeRef, edgeType) {
2507
+ return collectEdgesFromRefs(
2508
+ ctx,
2509
+ await collectNodeReferenceIds(ctx, nodeRef),
2510
+ edgeType
2511
+ );
2512
+ }
2513
+ async function collectEdgesToNodeRef(ctx, nodeRef, edgeType) {
2514
+ return collectEdgesToRefs(
2515
+ ctx,
2516
+ await collectNodeReferenceIds(ctx, nodeRef),
2517
+ edgeType
2518
+ );
2519
+ }
2520
+ async function collectEdgesBetweenRefs(ctx, fromRefs, toRefs) {
2521
+ const reads = [];
2522
+ for (const fromRef of fromRefs) {
2523
+ for (const toRef of toRefs) {
2524
+ reads.push(
2525
+ ctx.db.query("epistemicEdges").withIndex(
2526
+ "by_from_to",
2527
+ (q) => q.eq("fromNodeId", fromRef).eq("toNodeId", toRef)
2528
+ ).collect()
2529
+ );
2530
+ }
2531
+ reads.push(
2532
+ ctx.db.query("epistemicEdges").withIndex(
2533
+ "by_source_global_id",
2534
+ (q) => q.eq("sourceGlobalId", fromRef)
2535
+ ).collect()
2536
+ );
2537
+ }
2538
+ for (const toRef of toRefs) {
2539
+ reads.push(
2540
+ ctx.db.query("epistemicEdges").withIndex("by_target_global_id", (q) => q.eq("targetGlobalId", toRef)).collect()
2541
+ );
2542
+ }
2543
+ const fromRefSet = new Set(fromRefs);
2544
+ const toRefSet = new Set(toRefs);
2545
+ const edges = readEdgeRows2((await Promise.all(reads)).flat()).filter(
2546
+ (edge) => edgeSourceRefs(edge).some((ref) => fromRefSet.has(ref)) && edgeTargetRefs(edge).some((ref) => toRefSet.has(ref))
2547
+ );
2548
+ return dedupeEdges(edges);
2549
+ }
2550
+ async function collectEdgesBetweenNodeRefs(ctx, fromNodeRef, toNodeRef) {
2551
+ const [fromRefs, toRefs] = await Promise.all([
2552
+ collectNodeReferenceIds(ctx, fromNodeRef),
2553
+ collectNodeReferenceIds(ctx, toNodeRef)
2554
+ ]);
2555
+ return collectEdgesBetweenRefs(ctx, fromRefs, toRefs);
2556
+ }
2557
+ function buildLineageTraversalConfig(args) {
2558
+ const mode = args.mode ?? "anchor_down";
2559
+ return {
2560
+ maxDepth: args.maxDepth ?? 10,
2561
+ mode,
2562
+ minLayer: args.minLayer ?? getDefaultMinLayer(mode),
2563
+ maxLayer: args.maxLayer ?? 4
2564
+ };
2565
+ }
2566
+ function buildLineageQueueSeed(node) {
2567
+ return {
2568
+ node,
2569
+ depth: 0,
2570
+ currentLayer: node.epistemicLayer ?? getNodeLayer(node.nodeType)
2571
+ };
2572
+ }
2573
+ function shouldSkipLineageNode(item, state, visited) {
2574
+ if (item.depth >= state.maxDepth) {
2575
+ return true;
2576
+ }
2577
+ return hasVisitedNode(item.node, visited);
2578
+ }
2579
+ function resolveLineageTargetLayer(edge, targetNode) {
2580
+ return edge.toLayer ?? targetNode.epistemicLayer ?? getNodeLayer(targetNode.nodeType);
2581
+ }
2582
+ function collectDerivedFromEdges(ctx, node) {
2583
+ return collectEdgesFromRefs(
2584
+ ctx,
2585
+ nodeEndpointRefs2(node),
2586
+ DERIVED_FROM_EDGE_TYPE
2587
+ );
2588
+ }
2589
+ function shouldAdvanceLineage(currentLayer, targetLayer, state) {
2590
+ return shouldContinueTraversal(currentLayer, targetLayer, {
2591
+ mode: state.mode,
2592
+ minLayer: state.minLayer,
2593
+ maxLayer: state.maxLayer
2594
+ });
2595
+ }
2596
+ async function appendLineageEdges(ctx, current, state, lineage, queue, visited) {
2597
+ const lineageEdges = await collectDerivedFromEdges(ctx, current.node);
2598
+ for (const edge of lineageEdges) {
2599
+ const targetRef = primaryEdgeTargetRef(edge);
2600
+ if (!targetRef) {
2601
+ continue;
2602
+ }
2603
+ const targetNode = await resolveNodeRefSoft(ctx, targetRef);
2604
+ if (!targetNode || hasVisitedNode(targetNode, visited)) {
2605
+ continue;
2606
+ }
2607
+ const targetLayer = resolveLineageTargetLayer(edge, targetNode);
2608
+ if (!shouldAdvanceLineage(current.currentLayer, targetLayer, state)) {
2609
+ continue;
2610
+ }
2611
+ const nextDepth = current.depth + 1;
2612
+ lineage.push({
2613
+ node: targetNode,
2614
+ depth: nextDepth,
2615
+ edgeType: edge.edgeType,
2616
+ layer: targetLayer
2617
+ });
2618
+ queue.push({
2619
+ node: targetNode,
2620
+ depth: nextDepth,
2621
+ currentLayer: targetLayer
2622
+ });
2623
+ }
2624
+ }
2625
+ var get = query({
2626
+ args: { edgeId: v.id("epistemicEdges") },
2627
+ returns: permissiveReturn,
2628
+ handler: async (ctx, args) => await ctx.db.get(args.edgeId)
2629
+ });
2630
+ var getByGlobalId = query({
2631
+ args: { globalId: v.string() },
2632
+ returns: permissiveReturn,
2633
+ handler: async (ctx, args) => await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first()
2634
+ });
2635
+ var getFromNode = query({
2636
+ args: {
2637
+ fromNodeId: v.id("epistemicNodes"),
2638
+ edgeType: v.optional(edgeTypeValidator)
2639
+ },
2640
+ returns: permissiveReturn,
2641
+ handler: async (ctx, args) => await collectEdgesFromNodeRef(ctx, String(args.fromNodeId), args.edgeType)
2642
+ });
2643
+ var getToNode = query({
2644
+ args: {
2645
+ toNodeId: v.id("epistemicNodes"),
2646
+ edgeType: v.optional(edgeTypeValidator)
2647
+ },
2648
+ returns: permissiveReturn,
2649
+ handler: async (ctx, args) => await collectEdgesToNodeRef(ctx, String(args.toNodeId), args.edgeType)
2650
+ });
2651
+ var getBySourceNode = query({
2652
+ args: {
2653
+ sourceNodeId: v.string(),
2654
+ edgeType: v.optional(v.string())
2655
+ },
2656
+ returns: permissiveReturn,
2657
+ handler: async (ctx, args) => await collectEdgesFromNodeRef(ctx, args.sourceNodeId, args.edgeType)
2658
+ });
2659
+ var getByTargetNode = query({
2660
+ args: {
2661
+ targetNodeId: v.string(),
2662
+ edgeType: v.optional(v.string())
2663
+ },
2664
+ returns: permissiveReturn,
2665
+ handler: async (ctx, args) => await collectEdgesToNodeRef(ctx, args.targetNodeId, args.edgeType)
2666
+ });
2667
+ var getBetween = query({
2668
+ args: {
2669
+ fromNodeId: v.id("epistemicNodes"),
2670
+ toNodeId: v.id("epistemicNodes"),
2671
+ edgeType: v.optional(edgeTypeValidator)
2672
+ },
2673
+ returns: permissiveReturn,
2674
+ handler: async (ctx, args) => filterEdgesByType(
2675
+ await collectEdgesBetweenNodeRefs(
2676
+ ctx,
2677
+ String(args.fromNodeId),
2678
+ String(args.toNodeId)
2679
+ ),
2680
+ args.edgeType
2681
+ )
2682
+ });
2683
+ var getByNodes = query({
2684
+ args: {
2685
+ fromNodeId: v.id("epistemicNodes"),
2686
+ toNodeId: v.id("epistemicNodes")
2687
+ },
2688
+ returns: permissiveReturn,
2689
+ handler: async (ctx, args) => (await collectEdgesBetweenNodeRefs(
2690
+ ctx,
2691
+ String(args.fromNodeId),
2692
+ String(args.toNodeId)
2693
+ ))[0] ?? null
2694
+ });
2695
+ var getByProjectAndType = query({
2696
+ args: {
2697
+ ...optionalScopeArgs,
2698
+ edgeType: edgeTypeValidator
2699
+ },
2700
+ returns: permissiveReturn,
2701
+ handler: async (ctx, args) => {
2702
+ if (!(args.projectId || args.topicId)) {
2703
+ return [];
2704
+ }
2705
+ let scope;
2706
+ try {
2707
+ scope = await resolveTopicProjectScope(ctx, {
2708
+ projectId: args.projectId,
2709
+ topicId: args.topicId
2710
+ });
2711
+ } catch (error) {
2712
+ debugGraphPrimitiveFallback(
2713
+ "[epistemicEdges] Failed to resolve getAll scope",
2714
+ {
2715
+ error,
2716
+ projectId: args.projectId,
2717
+ topicId: args.topicId
2718
+ }
2719
+ );
2720
+ return [];
2721
+ }
2722
+ const projectEdges = await collectScopedEdges(ctx, scope, 5e3);
2723
+ return projectEdges.filter(
2724
+ (e) => e.edgeType === args.edgeType && edgeMatchesWorkspaceReasoningScope(e, scope)
2725
+ ).slice(0, 5e3);
2726
+ }
2727
+ });
2728
+ var getByProject = query({
2729
+ args: {
2730
+ ...optionalScopeArgs,
2731
+ userId: v.optional(v.string()),
2732
+ limit: v.optional(v.number())
2733
+ },
2734
+ returns: permissiveReturn,
2735
+ handler: async (ctx, args) => {
2736
+ if (!(args.projectId || args.topicId)) {
2737
+ return [];
2738
+ }
2739
+ let scope;
2740
+ try {
2741
+ scope = await resolveTopicProjectScope(ctx, {
2742
+ projectId: args.projectId,
2743
+ topicId: args.topicId
2744
+ });
2745
+ } catch (error) {
2746
+ debugGraphPrimitiveFallback(
2747
+ "[epistemicEdges] Failed to resolve getByProject scope",
2748
+ {
2749
+ error,
2750
+ projectId: args.projectId,
2751
+ topicId: args.topicId
2752
+ }
2753
+ );
2754
+ return [];
2755
+ }
2756
+ if (args.userId) {
2757
+ const hasAccess = await checkScopeAccess(
2758
+ ctx,
2759
+ String(scope.topicId ?? scope.projectId),
2760
+ args.userId
2761
+ );
2762
+ if (!hasAccess) {
2763
+ return [];
2764
+ }
2765
+ }
2766
+ const pageSize = Math.max(1, Math.min(Math.floor(args.limit ?? 500), 2e3));
2767
+ const edges = await collectScopedEdges(
2768
+ ctx,
2769
+ scope,
2770
+ Math.min(pageSize * 3, 6e3)
1966
2771
  );
2772
+ return edges.filter((edge) => edgeMatchesWorkspaceReasoningScope(edge, scope)).slice(0, pageSize);
1967
2773
  }
1968
- }
1969
- async function insertEpistemicEdge(ctx, doc) {
1970
- assertUuidV7Identity("epistemicEdges", doc.globalId);
1971
- assertStorageEdgeVocabulary(doc.edgeType);
1972
- if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
1973
- throw new Error(
1974
- "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
2774
+ });
2775
+ var listAll = query({
2776
+ args: {
2777
+ limit: v.optional(v.number())
2778
+ },
2779
+ returns: permissiveReturn,
2780
+ handler: async (ctx, args) => {
2781
+ const pageSize = Math.max(
2782
+ 1,
2783
+ Math.min(Math.floor(args.limit ?? 5e3), 1e4)
1975
2784
  );
2785
+ return await ctx.db.query("epistemicEdges").order("desc").take(pageSize);
1976
2786
  }
1977
- if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
1978
- throw new Error(
1979
- "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
2787
+ });
2788
+ var findContradictions = query({
2789
+ args: {
2790
+ nodeId: v.id("epistemicNodes")
2791
+ },
2792
+ returns: permissiveReturn,
2793
+ handler: async (ctx, args) => {
2794
+ const edges = await collectEdgesToNodeRef(
2795
+ ctx,
2796
+ String(args.nodeId),
2797
+ "informs"
1980
2798
  );
2799
+ return edges.filter((e) => (e.weight ?? 0) < 0);
1981
2800
  }
1982
- await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
1983
- await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
1984
- if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
1985
- assertEdgePolicyAllowed(
1986
- edgePolicyManifest,
1987
- doc.edgeType,
1988
- {
1989
- kind: "epistemic_node",
1990
- nodeId: doc.fromNodeId,
1991
- nodeType: doc.fromNodeType
1992
- },
1993
- {
1994
- kind: "epistemic_node",
1995
- nodeId: doc.toNodeId,
1996
- nodeType: doc.toNodeType
1997
- }
2801
+ });
2802
+ var findSupport = query({
2803
+ args: {
2804
+ nodeId: v.id("epistemicNodes")
2805
+ },
2806
+ returns: permissiveReturn,
2807
+ handler: async (ctx, args) => {
2808
+ const edges = await collectEdgesToNodeRef(
2809
+ ctx,
2810
+ String(args.nodeId),
2811
+ "informs"
1998
2812
  );
2813
+ return edges.filter((e) => (e.weight ?? 0) >= 0);
1999
2814
  }
2000
- return ctx.db.insert("epistemicEdges", doc);
2001
- }
2002
-
2003
- // src/epistemicEdges.handlers.ts
2004
- var mirrorEdgeToConvex = internalMutation({
2815
+ });
2816
+ var getLineage = query({
2005
2817
  args: {
2006
- globalId: v.string(),
2007
- fromGlobalId: v.string(),
2008
- toGlobalId: v.string(),
2009
- edgeType: v.string(),
2010
- weight: v.optional(v.number()),
2011
- confidence: v.optional(v.number()),
2012
- context: v.optional(v.string()),
2013
- derivationType: v.optional(v.string()),
2014
- createdBy: v.string(),
2015
- ...optionalScopeArgs,
2016
- fromLayer: v.optional(v.string()),
2017
- toLayer: v.optional(v.string()),
2018
- fromNodeType: v.optional(v.string()),
2019
- toNodeType: v.optional(v.string()),
2020
- reasoningMethod: v.optional(v.string()),
2021
- logicalRole: v.optional(v.string()),
2022
- temporalClass: v.optional(v.string()),
2023
- validFrom: v.optional(v.number()),
2024
- validUntil: v.optional(v.number()),
2025
- constraint: v.optional(v.string()),
2026
- propagation: v.optional(v.string()),
2027
- blocking: v.optional(v.boolean()),
2028
- implicit: v.optional(v.boolean()),
2029
- conditionalA: v.optional(subjectiveOpinionValidator),
2030
- conditionalNotA: v.optional(subjectiveOpinionValidator)
2818
+ nodeId: v.id("epistemicNodes"),
2819
+ maxDepth: v.optional(v.number()),
2820
+ // Phase 2D: Layer-aware traversal options
2821
+ minLayer: v.optional(v.number()),
2822
+ // 1=L1, 2=L2, 3=L3, 4=L4
2823
+ maxLayer: v.optional(v.number()),
2824
+ mode: v.optional(
2825
+ v.union(
2826
+ v.literal("anchor_down"),
2827
+ v.literal("anchor_up"),
2828
+ v.literal("same_layer"),
2829
+ v.literal("decision_trace")
2830
+ )
2831
+ )
2031
2832
  },
2032
2833
  returns: permissiveReturn,
2033
2834
  handler: async (ctx, args) => {
2034
- const fromNode = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", args.fromGlobalId)).first();
2035
- if (!fromNode) {
2036
- console.log(
2037
- `[Dual-Write] Skipping mirror - source node not in Convex: ${args.fromGlobalId}`
2038
- );
2039
- return buildEdgeMirrorSkippedResult();
2040
- }
2041
- const toNode = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", args.toGlobalId)).first();
2042
- const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
2043
- if (existing) {
2044
- console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);
2045
- return buildEdgeMirrorWriteResult(existing._id, true);
2835
+ const traversalState = buildLineageTraversalConfig(args);
2836
+ const lineage = [];
2837
+ const queue = [];
2838
+ const visited = /* @__PURE__ */ new Set();
2839
+ const startNode = readEdgeNodeRow2(await ctx.db.get(args.nodeId));
2840
+ if (!startNode) {
2841
+ return lineage;
2046
2842
  }
2047
- if (!toNode) {
2048
- console.log(
2049
- `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`
2843
+ queue.push(buildLineageQueueSeed(startNode));
2844
+ while (queue.length > 0) {
2845
+ const current = queue.shift();
2846
+ if (!current || shouldSkipLineageNode(current, traversalState, visited)) {
2847
+ continue;
2848
+ }
2849
+ markVisitedNode(current.node, visited);
2850
+ await appendLineageEdges(
2851
+ ctx,
2852
+ current,
2853
+ traversalState,
2854
+ lineage,
2855
+ queue,
2856
+ visited
2050
2857
  );
2051
- return buildEdgeMirrorSkippedResult();
2052
2858
  }
2053
- const now = Date.now();
2054
- const edgeId = await insertEpistemicEdge(ctx, {
2055
- globalId: args.globalId,
2056
- // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex
2057
- // doc ids. fromNode/toNode are resolved by_globalId above, so their
2058
- // stored globalId equals args.from/toGlobalId; persisting the doc _id here
2059
- // was the "mixed _id/globalId" defect.
2060
- fromNodeId: fromNode.globalId,
2061
- toNodeId: toNode.globalId,
2062
- sourceGlobalId: args.fromGlobalId,
2063
- targetGlobalId: args.toGlobalId,
2064
- // Preserve the canonical string value even when the local schema lags.
2065
- edgeType: args.edgeType,
2066
- weight: args.weight,
2067
- confidence: args.confidence,
2068
- context: args.context,
2069
- derivationType: args.derivationType,
2070
- constraint: args.constraint,
2071
- propagation: args.propagation,
2072
- blocking: args.blocking,
2073
- implicit: args.implicit,
2074
- conditionalA: args.conditionalA,
2075
- conditionalNotA: args.conditionalNotA,
2076
- createdBy: args.createdBy,
2077
- createdAt: now,
2078
- updatedAt: now,
2079
- topicId: args.projectId,
2080
- fromLayer: args.fromLayer,
2081
- toLayer: args.toLayer,
2082
- fromNodeType: args.fromNodeType,
2083
- toNodeType: args.toNodeType,
2084
- reasoningMethod: args.reasoningMethod,
2085
- logicalRole: args.logicalRole,
2086
- temporalClass: args.temporalClass,
2087
- validFrom: args.validFrom,
2088
- validUntil: args.validUntil
2089
- });
2090
- console.log(
2091
- `[Dual-Write] Mirrored edge to Convex: ${args.globalId} (${args.edgeType})`
2092
- );
2093
- return buildEdgeMirrorWriteResult(edgeId, false);
2859
+ return lineage;
2094
2860
  }
2095
2861
  });
2096
- var deleteEdgeFromConvex = internalMutation({
2862
+ var getEvidenceForBelief = query({
2097
2863
  args: {
2098
- globalId: v.string()
2864
+ beliefNodeId: v.id("epistemicNodes")
2099
2865
  },
2100
2866
  returns: permissiveReturn,
2101
2867
  handler: async (ctx, args) => {
2102
- const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
2103
- if (!existing) {
2104
- return buildEdgeMirrorMissingResult();
2105
- }
2106
- await ctx.db.delete(existing._id);
2107
- console.log(`[Dual-Write] Deleted edge from Convex: ${args.globalId}`);
2108
- return buildEdgeStatusSuccessResult();
2868
+ const informsEdges = await collectEdgesToNodeRef(
2869
+ ctx,
2870
+ String(args.beliefNodeId),
2871
+ "informs"
2872
+ );
2873
+ const supportEdges = informsEdges.filter((e) => (e.weight ?? 0) >= 0);
2874
+ const contradictEdges = informsEdges.filter((e) => (e.weight ?? 0) < 0);
2875
+ const supportingEvidence = await Promise.all(
2876
+ supportEdges.map(async (edge) => {
2877
+ const sourceRef = primaryEdgeSourceRef(edge);
2878
+ return {
2879
+ edge,
2880
+ node: sourceRef ? await resolveNodeRefSoft(ctx, sourceRef) : null
2881
+ };
2882
+ })
2883
+ );
2884
+ const contradictingEvidence = await Promise.all(
2885
+ contradictEdges.map(async (edge) => {
2886
+ const sourceRef = primaryEdgeSourceRef(edge);
2887
+ return {
2888
+ edge,
2889
+ node: sourceRef ? await resolveNodeRefSoft(ctx, sourceRef) : null
2890
+ };
2891
+ })
2892
+ );
2893
+ const filteredSupporting = supportingEvidence.filter(
2894
+ (e) => e.node !== null
2895
+ );
2896
+ const filteredContradicting = contradictingEvidence.filter(
2897
+ (e) => e.node !== null
2898
+ );
2899
+ return {
2900
+ supporting: filteredSupporting,
2901
+ contradicting: filteredContradicting,
2902
+ supportCount: filteredSupporting.length,
2903
+ contradictCount: filteredContradicting.length
2904
+ };
2109
2905
  }
2110
2906
  });
2111
- var updateEdgeInConvex = internalMutation({
2907
+ var getClusterEdges = query({
2112
2908
  args: {
2113
- globalId: v.string(),
2114
- weight: v.optional(v.number()),
2115
- confidence: v.optional(v.number()),
2116
- context: v.optional(v.string()),
2117
- derivationType: v.optional(v.string()),
2118
- constraint: v.optional(v.string()),
2119
- propagation: v.optional(v.string()),
2120
- blocking: v.optional(v.boolean()),
2121
- implicit: v.optional(v.boolean()),
2122
- conditionalA: v.optional(subjectiveOpinionValidator),
2123
- conditionalNotA: v.optional(subjectiveOpinionValidator)
2909
+ beliefIds: v.array(v.string())
2124
2910
  },
2125
2911
  returns: permissiveReturn,
2126
2912
  handler: async (ctx, args) => {
2127
- const existing = await ctx.db.query("epistemicEdges").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
2128
- if (!existing) {
2129
- return buildEdgeMirrorMissingResult();
2130
- }
2131
- const updates = {
2132
- updatedAt: Date.now()
2133
- };
2134
- if (args.weight !== void 0) {
2135
- updates.weight = args.weight;
2136
- }
2137
- if (args.confidence !== void 0) {
2138
- updates.confidence = args.confidence;
2139
- }
2140
- if (args.context !== void 0) {
2141
- updates.context = args.context;
2142
- }
2143
- if (args.derivationType !== void 0) {
2144
- updates.derivationType = args.derivationType;
2145
- }
2146
- if (args.constraint !== void 0) {
2147
- updates.constraint = args.constraint;
2148
- }
2149
- if (args.propagation !== void 0) {
2150
- updates.propagation = args.propagation;
2151
- }
2152
- if (args.blocking !== void 0) {
2153
- updates.blocking = args.blocking;
2154
- }
2155
- if (args.implicit !== void 0) {
2156
- updates.implicit = args.implicit;
2157
- }
2158
- if (args.conditionalA !== void 0) {
2159
- updates.conditionalA = args.conditionalA;
2913
+ if (args.beliefIds.length === 0) {
2914
+ return [];
2160
2915
  }
2161
- if (args.conditionalNotA !== void 0) {
2162
- updates.conditionalNotA = args.conditionalNotA;
2916
+ const beliefIdSet = /* @__PURE__ */ new Set();
2917
+ for (const beliefId of args.beliefIds) {
2918
+ for (const ref of await collectNodeReferenceIds(ctx, beliefId)) {
2919
+ beliefIdSet.add(ref);
2920
+ }
2163
2921
  }
2164
- await ctx.db.patch(existing._id, updates);
2165
- console.log(`[Dual-Write] Updated edge in Convex: ${args.globalId}`);
2166
- return buildEdgeStatusSuccessResult();
2922
+ const allEdges = readEdgeRows2(
2923
+ await ctx.db.query("epistemicEdges").collect()
2924
+ );
2925
+ return allEdges.filter((edge) => {
2926
+ const fromInCluster = edgeSourceRefs(edge).some(
2927
+ (ref) => beliefIdSet.has(ref)
2928
+ );
2929
+ const toInCluster = edgeTargetRefs(edge).some(
2930
+ (ref) => beliefIdSet.has(ref)
2931
+ );
2932
+ return fromInCluster && toInCluster;
2933
+ });
2167
2934
  }
2168
2935
  });
2169
2936
 
2170
- // src/epistemicEdges.ts
2171
- var getByTopic = getByProject;
2172
-
2173
- export { batchCreate, cleanupDeprecatedEdges, create, deleteEdgeFromConvex, deleteEdges, edgeTypeValidator, epistemicLayerValidator, findContradictions, findSupport, get, getBetween, getByGlobalId, getByNodes, getByProject, getByProjectAndType, getBySourceNode, getByTargetNode, getByTopic, getClusterEdges, getEvidenceForBelief, getFromNode, getLineage, getToNode, listAll, mirrorEdgeToConvex, remove, removeBetween, update, updateEdgeInConvex };
2937
+ export { batchCreate, cleanupDeprecatedEdges, create, deleteEdgeFromConvex, deleteEdges, edgeTypeValidator, epistemicLayerValidator, findContradictions, findSupport, get, getBetween, getByGlobalId, getByNodes, getByProject, getByProjectAndType, getBySourceNode, getByTargetNode, getByProject as getByTopic, getClusterEdges, getEvidenceForBelief, getFromNode, getLineage, getToNode, listAll, mirrorEdgeToConvex, remove, removeBetween, update, updateEdgeInConvex };
2174
2938
  //# sourceMappingURL=epistemicEdges.js.map
2175
2939
  //# sourceMappingURL=epistemicEdges.js.map