@lucern/graph-primitives 1.0.29 → 1.0.30

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,12 +1,16 @@
1
- import { v } from 'convex/values';
2
1
  import { checkScopeAccess } from '@lucern/access-control/access';
3
2
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
- import { componentsGeneric, anyApi, mutationGeneric, queryGeneric } from 'convex/server';
3
+ import { v } from 'convex/values';
4
+ import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
5
+ import { componentsGeneric, mutationGeneric, queryGeneric } from 'convex/server';
5
6
 
6
7
  // src/epistemicQuestions.conviction.ts
7
- var api = anyApi;
8
+ var unsafeApi = unsafeConvexAnyApi(
9
+ "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
10
+ );
11
+ var api = unsafeApi;
8
12
  componentsGeneric();
9
- var internal = anyApi;
13
+ var internal = unsafeApi;
10
14
  var mutation = mutationGeneric;
11
15
  var query = queryGeneric;
12
16
 
@@ -59,6 +63,10 @@ function readStringArray(value) {
59
63
  function readMetadata(topic) {
60
64
  return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
61
65
  }
66
+ function omitMetadataKey(metadata, key) {
67
+ const { [key]: _omitted, ...rest } = metadata;
68
+ return rest;
69
+ }
62
70
  function readLegacyProjectId(value) {
63
71
  if (!value) {
64
72
  return;
@@ -139,9 +147,12 @@ async function resolveTopicDoc(ctx, scopeId) {
139
147
  );
140
148
  }
141
149
  try {
142
- const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
143
- projectId: String(scopeId)
144
- });
150
+ const topic = await ctx.runQuery(
151
+ api.topics.getByLegacyScopeId,
152
+ {
153
+ projectId: String(scopeId)
154
+ }
155
+ );
145
156
  if (topic?.name !== void 0 && topic?.type !== void 0) {
146
157
  return topic;
147
158
  }
@@ -161,8 +172,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
161
172
  const outwardId = idMode === "topic" ? topicId : storageProjectId;
162
173
  const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
163
174
  const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
164
- const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
165
- const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
175
+ let createdAt = 0;
176
+ if (typeof topic.createdAt === "number") {
177
+ createdAt = topic.createdAt;
178
+ } else if (typeof topic._creationTime === "number") {
179
+ createdAt = topic._creationTime;
180
+ }
181
+ let updatedAt = createdAt;
182
+ if (typeof topic.updatedAt === "number") {
183
+ updatedAt = topic.updatedAt;
184
+ } else if (typeof metadata.updatedAt === "number") {
185
+ updatedAt = metadata.updatedAt;
186
+ }
166
187
  return {
167
188
  ...metadata,
168
189
  _id: outwardId,
@@ -231,90 +252,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
231
252
  if (!topic) {
232
253
  return null;
233
254
  }
234
- const nextMetadata = { ...readMetadata(topic) };
235
- const patch = {};
236
- const topicUpdateArgs = {
237
- id: String(topic._id)
255
+ const plan = buildTopicProjectOverlayPatchPlan(topic, value);
256
+ await applyTopicProjectOverlayPatch(ctx, topic, plan);
257
+ return materializeTopicProjectOverlay({
258
+ ...topic,
259
+ ...plan.patch,
260
+ metadata: plan.nextMetadata
261
+ });
262
+ }
263
+ function buildTopicProjectOverlayPatchPlan(topic, value) {
264
+ const plan = {
265
+ nextMetadata: { ...readMetadata(topic) },
266
+ patch: {},
267
+ topicUpdateArgs: {
268
+ id: String(topic._id)
269
+ }
238
270
  };
239
271
  for (const [key, rawValue] of Object.entries(value)) {
240
- switch (key) {
241
- case "_id":
242
- case "projectId":
243
- case "topicId":
244
- case "legacyProjectId":
245
- case "storageProjectId":
246
- break;
247
- case "name":
248
- case "description":
249
- patch[key] = rawValue;
250
- topicUpdateArgs[key] = rawValue;
251
- break;
252
- case "tenantId":
253
- case "workspaceId":
254
- case "ownerId":
255
- throw new Error(
256
- `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
257
- );
258
- case "status": {
259
- const status = coerceStatus(rawValue);
260
- if (status) {
261
- patch.status = status;
262
- topicUpdateArgs.status = status;
263
- }
264
- break;
265
- }
266
- case "visibility": {
267
- const visibility = coerceVisibility(rawValue);
268
- if (visibility) {
269
- patch.visibility = visibility;
270
- topicUpdateArgs.visibility = visibility;
271
- }
272
- break;
273
- }
274
- case "type": {
275
- const projectType = readNonEmptyString(rawValue);
276
- if (projectType) {
277
- nextMetadata.projectType = projectType;
278
- } else {
279
- delete nextMetadata.projectType;
280
- }
281
- break;
282
- }
283
- case "updatedAt":
284
- case "createdAt":
285
- break;
286
- default:
287
- if (rawValue === void 0) {
288
- delete nextMetadata[key];
289
- } else {
290
- nextMetadata[key] = rawValue;
291
- }
292
- }
272
+ applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
273
+ }
274
+ plan.patch.updatedAt = Date.now();
275
+ plan.patch.metadata = plan.nextMetadata;
276
+ plan.topicUpdateArgs.metadata = plan.nextMetadata;
277
+ return plan;
278
+ }
279
+ function applyTopicProjectOverlayPatchEntry(plan, key, rawValue) {
280
+ switch (key) {
281
+ case "_id":
282
+ case "projectId":
283
+ case "topicId":
284
+ case "legacyProjectId":
285
+ case "storageProjectId":
286
+ case "updatedAt":
287
+ case "createdAt":
288
+ return;
289
+ case "name":
290
+ case "description":
291
+ plan.patch[key] = rawValue;
292
+ plan.topicUpdateArgs[key] = rawValue;
293
+ return;
294
+ case "tenantId":
295
+ case "workspaceId":
296
+ case "ownerId":
297
+ throw new Error(
298
+ `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
299
+ );
300
+ case "status":
301
+ applyTopicStatusPatch(plan, rawValue);
302
+ return;
303
+ case "visibility":
304
+ applyTopicVisibilityPatch(plan, rawValue);
305
+ return;
306
+ case "type":
307
+ applyTopicProjectTypePatch(plan, rawValue);
308
+ return;
309
+ default:
310
+ applyTopicMetadataPatch(plan, key, rawValue);
311
+ }
312
+ }
313
+ function applyTopicStatusPatch(plan, rawValue) {
314
+ const status = coerceStatus(rawValue);
315
+ if (status) {
316
+ plan.patch.status = status;
317
+ plan.topicUpdateArgs.status = status;
318
+ }
319
+ }
320
+ function applyTopicVisibilityPatch(plan, rawValue) {
321
+ const visibility = coerceVisibility(rawValue);
322
+ if (visibility) {
323
+ plan.patch.visibility = visibility;
324
+ plan.topicUpdateArgs.visibility = visibility;
325
+ }
326
+ }
327
+ function applyTopicProjectTypePatch(plan, rawValue) {
328
+ const projectType = readNonEmptyString(rawValue);
329
+ if (projectType) {
330
+ plan.nextMetadata.projectType = projectType;
331
+ return;
293
332
  }
294
- patch.updatedAt = Date.now();
295
- patch.metadata = nextMetadata;
296
- topicUpdateArgs.metadata = nextMetadata;
333
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, "projectType");
334
+ }
335
+ function applyTopicMetadataPatch(plan, key, rawValue) {
336
+ if (rawValue === void 0) {
337
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, key);
338
+ return;
339
+ }
340
+ plan.nextMetadata[key] = rawValue;
341
+ }
342
+ async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
297
343
  if (typeof ctx.runMutation === "function") {
298
344
  try {
299
- await ctx.runMutation(api.topics.update, topicUpdateArgs);
345
+ await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
300
346
  } catch (error) {
301
- if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
347
+ if (!canPatchTopicViaLocalDb(ctx, error)) {
302
348
  throw error;
303
349
  }
304
- await ctx.db.patch(String(topic._id), patch);
350
+ await ctx.db.patch(topic._id, plan.patch);
305
351
  }
306
352
  } else if (ctx?.db && typeof ctx.db.patch === "function") {
307
- await ctx.db.patch(String(topic._id), patch);
353
+ await ctx.db.patch(topic._id, plan.patch);
308
354
  } else {
309
355
  throw new Error(
310
356
  "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
311
357
  );
312
358
  }
313
- return materializeTopicProjectOverlay({
314
- ...topic,
315
- ...patch,
316
- metadata: nextMetadata
317
- });
359
+ }
360
+ function canPatchTopicViaLocalDb(ctx, error) {
361
+ return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
318
362
  }
319
363
 
320
364
  // src/resolvers.ts
@@ -342,7 +386,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
342
386
  try {
343
387
  await patchTopicProjectOverlay(ctx, projectId, value);
344
388
  } catch (error) {
345
- if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
389
+ if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
346
390
  throw error;
347
391
  }
348
392
  console.warn(
@@ -409,13 +453,15 @@ function asMappedProjectId(topic) {
409
453
  if (!topic) {
410
454
  return;
411
455
  }
412
- const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
456
+ const directLegacyProjectId = normalizeScopeValue(
457
+ topic[LEGACY_SCOPE_FIELD2]
458
+ );
413
459
  if (directLegacyProjectId) {
414
460
  return directLegacyProjectId;
415
461
  }
416
462
  const metadata = topic.metadata || {};
417
463
  const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
418
- return candidate ? candidate : void 0;
464
+ return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
419
465
  }
420
466
  function normalizeScopeValue(value) {
421
467
  if (typeof value !== "string") {
@@ -440,8 +486,9 @@ function pickPrimaryTopic(candidates) {
440
486
  })[0];
441
487
  }
442
488
  async function findTopicsByScopeAlias(ctx, scopeId) {
489
+ const query2 = ctx.db.query("topics");
443
490
  try {
444
- return await ctx.db.query("topics").withIndex(
491
+ return await query2.withIndex(
445
492
  "by_graph_scope_project",
446
493
  (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
447
494
  ).collect();
@@ -453,7 +500,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
453
500
  scopeId
454
501
  }
455
502
  );
456
- const topics = await ctx.db.query("topics").collect();
503
+ const topics = await query2.collect();
457
504
  return topics.filter((topic) => {
458
505
  const normalizedGlobalId = normalizeScopeValue(topic.globalId);
459
506
  const mappedProjectId = asMappedProjectId(topic);
@@ -509,137 +556,115 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
509
556
  let current = topic;
510
557
  for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
511
558
  current = await ctx.db.get(current.parentTopicId);
512
- if (!current) break;
559
+ if (!current) {
560
+ break;
561
+ }
513
562
  if (!tenantId) {
514
563
  tenantId = normalizeScopeValue(current.tenantId);
515
564
  }
516
565
  if (!workspaceId) {
517
566
  workspaceId = normalizeScopeValue(current.workspaceId);
518
567
  }
519
- if (tenantId && workspaceId) break;
568
+ if (tenantId && workspaceId) {
569
+ break;
570
+ }
520
571
  }
521
572
  return { tenantId, workspaceId };
522
573
  }
523
574
  async function resolveTopicProjectScope(ctx, args) {
524
575
  if (args.topicId) {
525
- let topic = null;
526
- try {
527
- topic = await ctx.db.get(
528
- args.topicId
529
- );
530
- } catch (error) {
531
- debugGraphPrimitiveFallback(
532
- "[topicScope] Failed to load topic by direct id",
533
- {
534
- error,
535
- topicId: args.topicId
536
- }
537
- );
538
- }
539
- if (!topic) {
540
- topic = await tryResolveHostTopicById(ctx, String(args.topicId));
541
- }
542
- if (!topic) {
543
- topic = pickPrimaryTopic(
544
- await findTopicsByScopeAlias(ctx, String(args.topicId))
545
- ) ?? null;
546
- }
547
- if (!topic) {
548
- const nodeScope = await resolveTopicNodeScopeOrNull(
549
- ctx,
550
- String(args.topicId)
551
- );
552
- if (nodeScope) {
553
- return nodeScope;
554
- }
555
- throw new Error(`Topic not found: ${String(args.topicId)}`);
556
- }
557
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
558
- const mapped = asMappedProjectId(topic);
559
- if (mapped) {
560
- return {
561
- topicId: topic._id,
562
- projectId: mapped,
563
- tenantId: inherited.tenantId,
564
- workspaceId: inherited.workspaceId,
565
- source: "topic"
566
- };
567
- }
568
- return {
569
- topicId: topic._id,
570
- tenantId: inherited.tenantId,
571
- workspaceId: inherited.workspaceId,
572
- source: "topic"
573
- };
576
+ return await resolveScopeFromTopicId(ctx, args.topicId);
574
577
  }
575
578
  if (args.projectId) {
576
- let directTopic = null;
577
- try {
578
- directTopic = await ctx.db.get(
579
- args.projectId
580
- );
581
- } catch (error) {
582
- debugGraphPrimitiveFallback(
583
- "[topicScope] Failed to load direct project topic",
584
- {
585
- error,
586
- projectId: args.projectId
587
- }
588
- );
589
- }
590
- if (directTopic) {
591
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
592
- const mapped = asMappedProjectId(directTopic);
593
- return {
594
- topicId: directTopic._id,
595
- projectId: mapped ?? args.projectId,
596
- tenantId: inherited.tenantId,
597
- workspaceId: inherited.workspaceId,
598
- source: "topic_inferred"
599
- };
600
- }
601
- directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
602
- if (directTopic) {
603
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
604
- const mapped = asMappedProjectId(directTopic);
605
- return {
606
- topicId: directTopic._id,
607
- projectId: mapped ?? args.projectId,
608
- tenantId: inherited.tenantId,
609
- workspaceId: inherited.workspaceId,
610
- source: "topic_inferred"
611
- };
612
- }
613
- const topics = await findTopicsByScopeAlias(ctx, args.projectId);
614
- const primary = pickPrimaryTopic(topics);
615
- if (primary) {
616
- const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
617
- return {
618
- topicId: primary._id,
619
- projectId: args.projectId,
620
- tenantId: inherited.tenantId,
621
- workspaceId: inherited.workspaceId,
622
- source: "project_mapped_topic"
623
- };
624
- }
625
- const nodeScope = await resolveTopicNodeScopeOrNull(
626
- ctx,
627
- String(args.projectId)
628
- );
629
- if (nodeScope) {
630
- return {
631
- ...nodeScope,
632
- projectId: nodeScope.projectId ?? String(args.projectId)
633
- };
634
- }
635
- throw new Error(
636
- `Legacy project scope ${String(args.projectId)} has no mapped topic.`
637
- );
579
+ return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
638
580
  }
639
581
  throw new Error(
640
582
  "Missing scope: provide topicId (preferred) or legacy projectId alias."
641
583
  );
642
584
  }
585
+ async function resolveScopeFromTopicId(ctx, topicId) {
586
+ const topic = await resolveTopicDocFromTopicId(ctx, topicId);
587
+ if (topic) {
588
+ return await buildTopicScope(ctx, topic, "topic");
589
+ }
590
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
591
+ if (nodeScope) {
592
+ return nodeScope;
593
+ }
594
+ throw new Error(`Topic not found: ${String(topicId)}`);
595
+ }
596
+ async function resolveTopicDocFromTopicId(ctx, topicId) {
597
+ const direct = await tryReadTopicDoc(ctx, topicId, {
598
+ failureLog: "[topicScope] Failed to load topic by direct id",
599
+ idLogKey: "topicId"
600
+ });
601
+ if (direct) {
602
+ return direct;
603
+ }
604
+ const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
605
+ if (hostTopic) {
606
+ return hostTopic;
607
+ }
608
+ return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
609
+ }
610
+ async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
611
+ const directTopic = await resolveDirectLegacyProjectTopic(
612
+ ctx,
613
+ legacyProjectId
614
+ );
615
+ if (directTopic) {
616
+ return await buildTopicScope(ctx, directTopic, "topic_inferred", {
617
+ fallbackProjectId: legacyProjectId
618
+ });
619
+ }
620
+ const primary = pickPrimaryTopic(
621
+ await findTopicsByScopeAlias(ctx, legacyProjectId)
622
+ );
623
+ if (primary) {
624
+ return await buildTopicScope(ctx, primary, "project_mapped_topic", {
625
+ fallbackProjectId: legacyProjectId
626
+ });
627
+ }
628
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
629
+ if (nodeScope) {
630
+ return {
631
+ ...nodeScope,
632
+ projectId: nodeScope.projectId ?? legacyProjectId
633
+ };
634
+ }
635
+ throw new Error(
636
+ `Legacy project scope ${legacyProjectId} has no mapped topic.`
637
+ );
638
+ }
639
+ async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
640
+ const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
641
+ failureLog: "[topicScope] Failed to load direct project topic",
642
+ idLogKey: "projectId"
643
+ });
644
+ return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
645
+ }
646
+ async function tryReadTopicDoc(ctx, id, log) {
647
+ try {
648
+ return await ctx.db.get(id);
649
+ } catch (error) {
650
+ debugGraphPrimitiveFallback(log.failureLog, {
651
+ error,
652
+ [log.idLogKey]: id
653
+ });
654
+ return null;
655
+ }
656
+ }
657
+ async function buildTopicScope(ctx, topic, source, options = {}) {
658
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
659
+ const mapped = asMappedProjectId(topic);
660
+ return {
661
+ topicId: topic._id,
662
+ ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
663
+ tenantId: inherited.tenantId,
664
+ workspaceId: inherited.workspaceId,
665
+ source
666
+ };
667
+ }
643
668
  ({
644
669
  projectId: v.optional(v.string()),
645
670
  topicId: v.optional(v.string())
@@ -649,9 +674,14 @@ async function resolveTopicProjectScope(ctx, args) {
649
674
  function generateContentHash(text) {
650
675
  const content = `question:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
651
676
  let hash = 5381;
677
+ const maxSigned32Bit = 2147483647;
678
+ const uint32Size = 4294967296;
652
679
  for (let i = 0; i < content.length; i++) {
653
- hash = (hash << 5) + hash + content.charCodeAt(i);
654
- hash &= hash;
680
+ hash = Math.imul(hash, 33) + content.charCodeAt(i);
681
+ hash %= uint32Size;
682
+ if (hash > maxSigned32Bit) {
683
+ hash -= uint32Size;
684
+ }
655
685
  }
656
686
  return Math.abs(hash).toString(16).padStart(8, "0");
657
687
  }
@@ -659,9 +689,10 @@ function buildTestsEdgeGlobalId(fromGlobalId, toGlobalId) {
659
689
  return `edge-${fromGlobalId}-tests-${toGlobalId}`;
660
690
  }
661
691
  async function markProjectGraphDirty(ctx, projectId, topicId) {
692
+ const markCacheStaleByTopic = internal.graphAnalysisCache.markCacheStaleByTopic;
662
693
  const normalizedProjectId = typeof projectId === "string" && projectId.trim().length > 0 ? projectId : void 0;
663
694
  const normalizedTopicId = typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
664
- if (!normalizedProjectId && !normalizedTopicId) {
695
+ if (!(normalizedProjectId || normalizedTopicId)) {
665
696
  return;
666
697
  }
667
698
  if (normalizedProjectId) {
@@ -674,17 +705,17 @@ async function markProjectGraphDirty(ctx, projectId, topicId) {
674
705
  );
675
706
  }
676
707
  if (normalizedTopicId) {
677
- await ctx.scheduler.runAfter(
678
- 0,
679
- internal.graphAnalysisCache.markCacheStaleByTopic,
680
- {
681
- topicId: normalizedTopicId
682
- }
683
- );
708
+ await ctx.scheduler.runAfter(0, markCacheStaleByTopic, {
709
+ topicId: normalizedTopicId
710
+ });
711
+ }
712
+ const resolvedProjectId = normalizedTopicId ?? normalizedProjectId;
713
+ if (!resolvedProjectId) {
714
+ return;
684
715
  }
685
716
  await resolveGraphPrimitivesAppResolvers().patchProject(
686
717
  ctx,
687
- normalizedTopicId ?? normalizedProjectId,
718
+ resolvedProjectId,
688
719
  {
689
720
  lastActivityAt: Date.now()
690
721
  }
@@ -716,7 +747,7 @@ function logQuestionFallback(message, error, context) {
716
747
  });
717
748
  }
718
749
  async function resolveQuestionScopeOrNull(ctx, args) {
719
- if (!args.projectId && !args.topicId) {
750
+ if (!(args.projectId || args.topicId)) {
720
751
  return null;
721
752
  }
722
753
  try {
@@ -758,6 +789,362 @@ function buildLinkedWorktreeMetadata(linkedWorktreeId) {
758
789
  }
759
790
 
760
791
  // src/epistemicQuestions.conviction.ts
792
+ function readOptionalString(value) {
793
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
794
+ }
795
+ function readConvexId(value) {
796
+ const normalized = readOptionalString(value);
797
+ return normalized ? normalized : null;
798
+ }
799
+ function readRecord(value) {
800
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
801
+ }
802
+ function readStringArray2(value) {
803
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : [];
804
+ }
805
+ function readQuestionNodeRow(value) {
806
+ const record = readRecord(value);
807
+ if (!record) {
808
+ return null;
809
+ }
810
+ const id = readConvexId(record._id);
811
+ const nodeType = readOptionalString(record.nodeType);
812
+ if (!(id && nodeType)) {
813
+ return null;
814
+ }
815
+ const metadata = readRecord(record.metadata) ?? {};
816
+ const node = { _id: id, metadata, nodeType };
817
+ const canonicalText = readOptionalString(record.canonicalText);
818
+ if (canonicalText !== void 0) {
819
+ node.canonicalText = canonicalText;
820
+ }
821
+ const globalId = readOptionalString(record.globalId);
822
+ if (globalId !== void 0) {
823
+ node.globalId = globalId;
824
+ }
825
+ const projectId = readOptionalString(record.projectId);
826
+ if (projectId !== void 0) {
827
+ node.projectId = projectId;
828
+ }
829
+ const sourceType = readOptionalString(record.sourceType);
830
+ if (sourceType !== void 0) {
831
+ node.sourceType = sourceType;
832
+ }
833
+ const topicId = readOptionalString(record.topicId);
834
+ if (topicId !== void 0) {
835
+ node.topicId = topicId;
836
+ }
837
+ return node;
838
+ }
839
+ function requireQuestionNode(value) {
840
+ const node = readQuestionNodeRow(value);
841
+ if (node?.nodeType !== "question") {
842
+ throw new Error("Question not found");
843
+ }
844
+ return node;
845
+ }
846
+ function readBeliefNode(value) {
847
+ const node = readQuestionNodeRow(value);
848
+ return node?.nodeType === "belief" ? node : null;
849
+ }
850
+ function readEvidenceNode(value) {
851
+ const node = readQuestionNodeRow(value);
852
+ return node?.nodeType === "evidence" ? node : null;
853
+ }
854
+ function readQuestionEvidenceLinkRow(value) {
855
+ const record = readRecord(value);
856
+ if (!record) {
857
+ return null;
858
+ }
859
+ const id = readConvexId(record._id);
860
+ if (!id) {
861
+ return null;
862
+ }
863
+ const link = { _id: id };
864
+ const insightId = readConvexId(record.insightId) ?? readOptionalString(record.insightId);
865
+ if (insightId !== void 0 && insightId !== null) {
866
+ link.insightId = insightId;
867
+ }
868
+ return link;
869
+ }
870
+ function readQuestionEdgeRow(value) {
871
+ const record = readRecord(value);
872
+ if (!record) {
873
+ return null;
874
+ }
875
+ const id = readConvexId(record._id);
876
+ if (!id) {
877
+ return null;
878
+ }
879
+ const edge = { _id: id };
880
+ const edgeType = readOptionalString(record.edgeType);
881
+ if (edgeType !== void 0) {
882
+ edge.edgeType = edgeType;
883
+ }
884
+ const globalId = readOptionalString(record.globalId);
885
+ if (globalId !== void 0) {
886
+ edge.globalId = globalId;
887
+ }
888
+ return edge;
889
+ }
890
+ function resolveConvictionCompleteness(explicit, conviction) {
891
+ if (explicit !== void 0) {
892
+ return explicit;
893
+ }
894
+ if (conviction >= 0.8) {
895
+ return "comprehensive";
896
+ }
897
+ if (conviction >= 0.6) {
898
+ return "sufficient";
899
+ }
900
+ if (conviction >= 0.3) {
901
+ return "partial";
902
+ }
903
+ return "unanswered";
904
+ }
905
+ function buildFinalizedConvictionMetadata(args, meta, now, clampedConviction) {
906
+ return {
907
+ ...meta,
908
+ convictionStage: "scored",
909
+ conviction: clampedConviction,
910
+ convictionRationale: args.convictionRationale,
911
+ convictionUpdatedAt: now,
912
+ convictionUpdatedBy: args.userId,
913
+ answer: args.answer,
914
+ answerStatus: "final",
915
+ questionStatus: args.answerCompleteness === "unanswerable" ? "blocked" : "answered",
916
+ answeredAt: now,
917
+ answeredBy: args.userId,
918
+ answerCompleteness: args.answerCompleteness,
919
+ whatWeNeed: args.whatWeNeed
920
+ };
921
+ }
922
+ function resolveLinkedBeliefNodeIdFromMetadata(meta) {
923
+ return readOptionalString(meta.linkedBeliefNodeId) || readOptionalString(meta.linkedBeliefId) || readOptionalString(meta.beliefId);
924
+ }
925
+ async function scheduleEvidenceCreationFromScoredQuestion(ctx, options) {
926
+ if (!(options.projectId || options.topicId)) {
927
+ return;
928
+ }
929
+ const linkedBeliefId = resolveLinkedBeliefNodeIdFromMetadata(
930
+ options.questionMetadata
931
+ );
932
+ if (!linkedBeliefId) {
933
+ return;
934
+ }
935
+ try {
936
+ await ctx.scheduler.runAfter(
937
+ 0,
938
+ internal.epistemicQuestions.createEvidenceFromScoredQuestion,
939
+ {
940
+ questionNodeId: options.questionNodeId,
941
+ questionText: options.questionText,
942
+ answerText: options.answerText,
943
+ beliefId: linkedBeliefId,
944
+ relatedBeliefIds: options.relatedBeliefIds,
945
+ conviction: options.conviction,
946
+ rationale: options.rationale || "",
947
+ projectId: options.projectId,
948
+ topicId: options.topicId ? String(options.topicId) : void 0,
949
+ userId: options.userId
950
+ }
951
+ );
952
+ } catch (error) {
953
+ logQuestionFallback(
954
+ "[epistemicQuestions] Failed to schedule evidence creation from scored question",
955
+ error,
956
+ {
957
+ questionId: options.questionNodeId,
958
+ beliefId: linkedBeliefId,
959
+ projectId: options.projectId
960
+ }
961
+ );
962
+ console.error(
963
+ "[finalizeConviction] Failed to schedule evidence creation:",
964
+ error
965
+ );
966
+ }
967
+ }
968
+ async function resolveBeliefNodeForBeliefLink(ctx, beliefId) {
969
+ try {
970
+ return readBeliefNode(await ctx.db.get(beliefId));
971
+ } catch (error) {
972
+ debugGraphPrimitiveFallback(
973
+ "[epistemicQuestions] Failed to resolve belief node directly",
974
+ {
975
+ error: formatGraphPrimitiveError(error),
976
+ beliefId
977
+ }
978
+ );
979
+ try {
980
+ return readBeliefNode(await ctx.db.get(beliefId));
981
+ } catch (legacyError) {
982
+ debugGraphPrimitiveFallback(
983
+ "[epistemicQuestions] Failed to resolve legacy belief node",
984
+ {
985
+ error: formatGraphPrimitiveError(legacyError),
986
+ beliefId
987
+ }
988
+ );
989
+ return null;
990
+ }
991
+ }
992
+ }
993
+ function buildBeliefLinkMetadata(questionMetadata, args) {
994
+ const currentBeliefs = readStringArray2(questionMetadata.relatedBeliefIds);
995
+ const beliefId = String(args.beliefId);
996
+ const relatedBeliefs = currentBeliefs.includes(beliefId) ? currentBeliefs : [...currentBeliefs, beliefId];
997
+ const metaUpdates = {
998
+ ...questionMetadata,
999
+ relatedBeliefIds: relatedBeliefs
1000
+ };
1001
+ if (args.isPrimaryBelief || args.testType) {
1002
+ metaUpdates.linkedBeliefNodeId = args.beliefId;
1003
+ metaUpdates.linkedBeliefId = args.beliefId;
1004
+ metaUpdates.questionType = "belief_test";
1005
+ if (args.testType) {
1006
+ metaUpdates.testType = args.testType;
1007
+ }
1008
+ if (args.answerImpact) {
1009
+ metaUpdates.answerImpact = args.answerImpact;
1010
+ }
1011
+ }
1012
+ return metaUpdates;
1013
+ }
1014
+ async function syncBeliefTestEdge(ctx, questionNode, beliefNode, args) {
1015
+ try {
1016
+ const beliefGlobalId = beliefNode.globalId;
1017
+ if (!(beliefGlobalId && questionNode.globalId)) {
1018
+ return;
1019
+ }
1020
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1021
+ nodeId: questionNode._id,
1022
+ operation: "upsert"
1023
+ });
1024
+ await ctx.scheduler.runAfter(100, internal.neo4jSync.syncNodeToNeo4j, {
1025
+ nodeId: beliefNode._id,
1026
+ operation: "upsert"
1027
+ });
1028
+ const edgeGlobalId = buildTestsEdgeGlobalId(
1029
+ questionNode.globalId,
1030
+ beliefGlobalId
1031
+ );
1032
+ await ctx.scheduler.runAfter(250, internal.neo4jEdgeAPI.createEdge, {
1033
+ globalId: edgeGlobalId,
1034
+ fromGlobalId: questionNode.globalId,
1035
+ toGlobalId: beliefGlobalId,
1036
+ edgeType: "tests",
1037
+ context: args.testType || "tests",
1038
+ topicId: normalizeQuestionTopicId(questionNode.topicId) ?? questionNode.projectId,
1039
+ createdBy: args.userId,
1040
+ fromNodeType: "question",
1041
+ toNodeType: "belief",
1042
+ fromLayer: "L3",
1043
+ toLayer: "L3"
1044
+ });
1045
+ } catch (error) {
1046
+ logQuestionFallback(
1047
+ "[epistemicQuestions] Failed to create tests edge",
1048
+ error,
1049
+ {
1050
+ questionId: questionNode._id,
1051
+ beliefId: args.beliefId
1052
+ }
1053
+ );
1054
+ console.error("[linkToBelief] Failed to create tests edge:", error);
1055
+ }
1056
+ }
1057
+ async function assertQuestionScopeAccess(ctx, questionNode, userId) {
1058
+ const questionScopeId = resolveQuestionScopeId({
1059
+ projectId: questionNode.projectId,
1060
+ topicId: normalizeQuestionTopicId(questionNode.topicId)
1061
+ });
1062
+ if (!questionScopeId) {
1063
+ return;
1064
+ }
1065
+ const hasAccess = await checkScopeAccess(ctx, questionScopeId, userId);
1066
+ if (!hasAccess) {
1067
+ throw new Error("Access denied");
1068
+ }
1069
+ }
1070
+ async function removeQuestionEvidenceLinks(ctx, args) {
1071
+ try {
1072
+ const links = await ctx.db.query("questionEvidenceLinks").withIndex("by_questionId", (q) => q.eq("questionId", args.questionId)).collect();
1073
+ for (const rawLink of links) {
1074
+ const link = readQuestionEvidenceLinkRow(rawLink);
1075
+ if (link && String(link.insightId) === String(args.insightId)) {
1076
+ await ctx.db.delete(link._id);
1077
+ }
1078
+ }
1079
+ } catch (error) {
1080
+ logQuestionFallback(
1081
+ "[epistemicQuestions] Failed to remove questionEvidenceLink",
1082
+ error,
1083
+ {
1084
+ questionId: args.questionId,
1085
+ insightId: args.insightId
1086
+ }
1087
+ );
1088
+ console.error(
1089
+ "[unlinkInsight] Failed to remove questionEvidenceLink:",
1090
+ error
1091
+ );
1092
+ }
1093
+ }
1094
+ function buildEvidenceQuestionEndpointPairs(evidenceNode, questionNode) {
1095
+ return [String(evidenceNode._id), evidenceNode.globalId].filter(
1096
+ (endpoint) => typeof endpoint === "string" && endpoint.length > 0
1097
+ ).flatMap((fromNodeId) => [
1098
+ { fromNodeId, toNodeId: String(questionNode._id) },
1099
+ ...questionNode.globalId ? [{ fromNodeId, toNodeId: questionNode.globalId }] : []
1100
+ ]);
1101
+ }
1102
+ async function collectEdgesForEndpointPairs(ctx, endpointPairs) {
1103
+ const edgeGroups = await Promise.all(
1104
+ endpointPairs.map(
1105
+ (endpoint) => ctx.db.query("epistemicEdges").withIndex(
1106
+ "by_from_to",
1107
+ (q) => q.eq("fromNodeId", endpoint.fromNodeId).eq("toNodeId", endpoint.toNodeId)
1108
+ ).collect()
1109
+ )
1110
+ );
1111
+ return edgeGroups.flat();
1112
+ }
1113
+ async function removeDerivedEvidenceEdges(ctx, args) {
1114
+ try {
1115
+ const evidenceNode = readEvidenceNode(await ctx.db.get(args.insightId));
1116
+ if (!evidenceNode) {
1117
+ return;
1118
+ }
1119
+ const endpointPairs = buildEvidenceQuestionEndpointPairs(
1120
+ evidenceNode,
1121
+ args.questionNode
1122
+ );
1123
+ const rawEdges = await collectEdgesForEndpointPairs(ctx, endpointPairs);
1124
+ for (const rawEdge of rawEdges) {
1125
+ const edge = readQuestionEdgeRow(rawEdge);
1126
+ if (!(edge && edge.edgeType === "derived_from")) {
1127
+ continue;
1128
+ }
1129
+ if (edge.globalId) {
1130
+ await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
1131
+ globalId: edge.globalId
1132
+ });
1133
+ }
1134
+ await ctx.db.delete(edge._id);
1135
+ }
1136
+ } catch (error) {
1137
+ logQuestionFallback(
1138
+ "[epistemicQuestions] Failed to remove derived edge",
1139
+ error,
1140
+ {
1141
+ questionId: args.questionId,
1142
+ insightId: args.insightId
1143
+ }
1144
+ );
1145
+ console.error("[unlinkInsight] Failed to remove edge:", error);
1146
+ }
1147
+ }
761
1148
  var advanceToConviction = mutation({
762
1149
  args: {
763
1150
  questionId: v.id("epistemicNodes"),
@@ -767,11 +1154,8 @@ var advanceToConviction = mutation({
767
1154
  },
768
1155
  returns: permissiveReturn,
769
1156
  handler: async (ctx, args) => {
770
- const node = await ctx.db.get(args.questionId);
771
- if (!node || node.nodeType !== "question") {
772
- throw new Error("Question not found");
773
- }
774
- const meta = node.metadata || {};
1157
+ const node = requireQuestionNode(await ctx.db.get(args.questionId));
1158
+ const meta = node.metadata;
775
1159
  if (meta.convictionStage === "in_conviction" || meta.convictionStage === "scored") {
776
1160
  throw new Error("Question is already in conviction stage");
777
1161
  }
@@ -837,12 +1221,9 @@ var updateConviction = mutation({
837
1221
  },
838
1222
  returns: permissiveReturn,
839
1223
  handler: async (ctx, args) => {
840
- const node = await ctx.db.get(args.questionId);
841
- if (!node || node.nodeType !== "question") {
842
- throw new Error("Question not found");
843
- }
1224
+ const node = requireQuestionNode(await ctx.db.get(args.questionId));
844
1225
  const now = Date.now();
845
- const meta = node.metadata || {};
1226
+ const meta = node.metadata;
846
1227
  const updates = { ...meta };
847
1228
  if (args.conviction !== void 0) {
848
1229
  updates.conviction = Math.max(0, Math.min(1, args.conviction));
@@ -887,35 +1268,28 @@ var finalizeConviction = mutation({
887
1268
  },
888
1269
  returns: permissiveReturn,
889
1270
  handler: async (ctx, args) => {
890
- const node = await ctx.db.get(args.questionId);
891
- if (!node || node.nodeType !== "question") {
892
- throw new Error("Question not found");
893
- }
1271
+ const node = requireQuestionNode(await ctx.db.get(args.questionId));
894
1272
  const now = Date.now();
895
1273
  const clampedConviction = Math.max(0, Math.min(1, args.conviction));
896
- const isUnanswerable = args.answerCompleteness === "unanswerable";
897
- const meta = node.metadata || {};
898
- let completeness = args.answerCompleteness;
899
- if (!completeness) {
900
- completeness = clampedConviction >= 0.8 ? "comprehensive" : clampedConviction >= 0.6 ? "sufficient" : clampedConviction >= 0.3 ? "partial" : "unanswered";
901
- }
1274
+ const completeness = resolveConvictionCompleteness(
1275
+ args.answerCompleteness,
1276
+ clampedConviction
1277
+ );
1278
+ const meta = node.metadata;
902
1279
  await ctx.db.patch(args.questionId, {
903
1280
  updatedAt: now,
904
- metadata: {
905
- ...meta,
906
- convictionStage: "scored",
907
- conviction: clampedConviction,
908
- convictionRationale: args.convictionRationale,
909
- convictionUpdatedAt: now,
910
- convictionUpdatedBy: args.userId,
911
- answer: args.answer,
912
- answerStatus: "final",
913
- questionStatus: isUnanswerable ? "blocked" : "answered",
914
- answeredAt: now,
915
- answeredBy: args.userId,
916
- answerCompleteness: completeness,
917
- whatWeNeed: args.whatWeNeed
918
- }
1281
+ metadata: buildFinalizedConvictionMetadata(
1282
+ {
1283
+ userId: args.userId,
1284
+ answer: args.answer,
1285
+ convictionRationale: args.convictionRationale,
1286
+ answerCompleteness: completeness,
1287
+ whatWeNeed: args.whatWeNeed
1288
+ },
1289
+ meta,
1290
+ now,
1291
+ clampedConviction
1292
+ )
919
1293
  });
920
1294
  try {
921
1295
  await ctx.db.insert("epistemicAudit", {
@@ -946,43 +1320,18 @@ var finalizeConviction = mutation({
946
1320
  );
947
1321
  console.error("[EpistemicAudit] Failed to log finalizeConviction:", e);
948
1322
  }
949
- if (node.projectId || node.topicId) {
950
- const beliefId = meta.linkedBeliefNodeId || meta.linkedBeliefId || meta.beliefId;
951
- if (beliefId) {
952
- try {
953
- await ctx.scheduler.runAfter(
954
- 0,
955
- internal.epistemicQuestions.createEvidenceFromScoredQuestion,
956
- {
957
- questionNodeId: args.questionId,
958
- questionText: node.canonicalText || "",
959
- answerText: args.answer,
960
- beliefId,
961
- relatedBeliefIds: meta.relatedBeliefIds || [],
962
- conviction: clampedConviction,
963
- rationale: args.convictionRationale || "",
964
- projectId: node.projectId,
965
- topicId: normalizeQuestionTopicId(node.topicId),
966
- userId: args.userId
967
- }
968
- );
969
- } catch (e) {
970
- logQuestionFallback(
971
- "[epistemicQuestions] Failed to schedule evidence creation from scored question",
972
- e,
973
- {
974
- questionId: args.questionId,
975
- beliefId,
976
- projectId: node.projectId
977
- }
978
- );
979
- console.error(
980
- "[finalizeConviction] Failed to schedule evidence creation:",
981
- e
982
- );
983
- }
984
- }
985
- }
1323
+ await scheduleEvidenceCreationFromScoredQuestion(ctx, {
1324
+ questionNodeId: args.questionId,
1325
+ questionText: node.canonicalText ?? "",
1326
+ answerText: args.answer,
1327
+ questionMetadata: meta,
1328
+ conviction: clampedConviction,
1329
+ rationale: args.convictionRationale || "",
1330
+ projectId: node.projectId,
1331
+ topicId: normalizeQuestionTopicId(node.topicId),
1332
+ userId: args.userId,
1333
+ relatedBeliefIds: readStringArray2(meta.relatedBeliefIds)
1334
+ });
986
1335
  await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
987
1336
  nodeId: args.questionId,
988
1337
  operation: "upsert"
@@ -1004,7 +1353,9 @@ var getByBeliefWithAccess = query({
1004
1353
  handler: async (ctx, args) => {
1005
1354
  let beliefNode = null;
1006
1355
  try {
1007
- beliefNode = await ctx.db.get(args.beliefId);
1356
+ beliefNode = readBeliefNode(
1357
+ await ctx.db.get(args.beliefId)
1358
+ );
1008
1359
  } catch (error) {
1009
1360
  debugGraphPrimitiveFallback(
1010
1361
  "[epistemicQuestions] Failed to resolve belief node directly",
@@ -1014,7 +1365,9 @@ var getByBeliefWithAccess = query({
1014
1365
  }
1015
1366
  );
1016
1367
  try {
1017
- beliefNode = await ctx.db.get(args.beliefId);
1368
+ beliefNode = readBeliefNode(
1369
+ await ctx.db.get(args.beliefId)
1370
+ );
1018
1371
  } catch (legacyError) {
1019
1372
  debugGraphPrimitiveFallback(
1020
1373
  "[epistemicQuestions] Failed to resolve legacy belief node",
@@ -1034,7 +1387,7 @@ var getByBeliefWithAccess = query({
1034
1387
  topicId: normalizeQuestionTopicId(beliefNode.topicId)
1035
1388
  });
1036
1389
  const scopeId = scope ? resolveQuestionScopeId(scope) : void 0;
1037
- if (!scope || !scopeId) {
1390
+ if (!(scope && scopeId)) {
1038
1391
  return [];
1039
1392
  }
1040
1393
  const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
@@ -1044,7 +1397,7 @@ var getByBeliefWithAccess = query({
1044
1397
  const questionNodes = await getQuestionNodesForScope(ctx, scope);
1045
1398
  const beliefIdStr = String(args.beliefId);
1046
1399
  return questionNodes.filter((q) => {
1047
- const meta = q.metadata || {};
1400
+ const meta = readRecord(q.metadata) ?? {};
1048
1401
  if (String(meta.linkedBeliefNodeId || "") === beliefIdStr) {
1049
1402
  return true;
1050
1403
  }
@@ -1054,7 +1407,7 @@ var getByBeliefWithAccess = query({
1054
1407
  if (String(meta.beliefId || "") === beliefIdStr) {
1055
1408
  return true;
1056
1409
  }
1057
- const relatedIds = meta.relatedBeliefIds || [];
1410
+ const relatedIds = readStringArray2(meta.relatedBeliefIds);
1058
1411
  if (relatedIds.some((id) => String(id) === beliefIdStr)) {
1059
1412
  return true;
1060
1413
  }
@@ -1089,12 +1442,9 @@ var updateQuestion = mutation({
1089
1442
  },
1090
1443
  returns: permissiveReturn,
1091
1444
  handler: async (ctx, args) => {
1092
- const node = await ctx.db.get(args.questionId);
1093
- if (!node || node.nodeType !== "question") {
1094
- throw new Error("Question not found");
1095
- }
1445
+ const node = requireQuestionNode(await ctx.db.get(args.questionId));
1096
1446
  const now = Date.now();
1097
- const meta = node.metadata || {};
1447
+ const meta = node.metadata;
1098
1448
  const metaUpdates = { ...meta };
1099
1449
  if (args.category) {
1100
1450
  const category = args.category === "financial" ? "financials" : args.category;
@@ -1145,115 +1495,27 @@ var linkToBelief = mutation({
1145
1495
  },
1146
1496
  returns: permissiveReturn,
1147
1497
  handler: async (ctx, args) => {
1148
- const questionNode = await ctx.db.get(args.questionId);
1149
- if (!questionNode || questionNode.nodeType !== "question") {
1150
- throw new Error("Question not found");
1151
- }
1152
- const questionScopeId = resolveQuestionScopeId({
1153
- projectId: questionNode.projectId,
1154
- topicId: normalizeQuestionTopicId(questionNode.topicId)
1155
- });
1156
- if (questionScopeId) {
1157
- const hasAccess = await checkScopeAccess(
1158
- ctx,
1159
- questionScopeId,
1160
- args.userId
1161
- );
1162
- if (!hasAccess) {
1163
- throw new Error("Access denied");
1164
- }
1165
- }
1166
- let beliefNode = null;
1167
- try {
1168
- beliefNode = await ctx.db.get(args.beliefId);
1169
- } catch (error) {
1170
- debugGraphPrimitiveFallback(
1171
- "[epistemicQuestions] Failed to resolve belief node directly",
1172
- {
1173
- error: formatGraphPrimitiveError(error),
1174
- beliefId: args.beliefId
1175
- }
1176
- );
1177
- try {
1178
- beliefNode = await ctx.db.get(args.beliefId);
1179
- } catch (legacyError) {
1180
- debugGraphPrimitiveFallback(
1181
- "[epistemicQuestions] Failed to resolve legacy belief node",
1182
- {
1183
- error: formatGraphPrimitiveError(legacyError),
1184
- beliefId: args.beliefId
1185
- }
1186
- );
1187
- throw new Error("Belief not found");
1188
- }
1189
- }
1498
+ const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
1499
+ await assertQuestionScopeAccess(ctx, questionNode, args.userId);
1500
+ const beliefNode = await resolveBeliefNodeForBeliefLink(ctx, args.beliefId);
1190
1501
  if (!beliefNode) {
1191
1502
  throw new Error("Belief not found");
1192
1503
  }
1193
1504
  const now = Date.now();
1194
- const meta = questionNode.metadata || {};
1195
- const currentBeliefs = meta.relatedBeliefIds || [];
1196
- const beliefIdStr = String(args.beliefId);
1197
- const newRelated = currentBeliefs.includes(beliefIdStr) ? currentBeliefs : [...currentBeliefs, beliefIdStr];
1198
- const metaUpdates = {
1199
- ...meta,
1200
- relatedBeliefIds: newRelated
1505
+ const meta = questionNode.metadata;
1506
+ const linkArgs = {
1507
+ beliefId: args.beliefId,
1508
+ userId: args.userId,
1509
+ testType: args.testType,
1510
+ answerImpact: args.answerImpact,
1511
+ isPrimaryBelief: args.isPrimaryBelief
1201
1512
  };
1202
- if (args.isPrimaryBelief || args.testType) {
1203
- metaUpdates.linkedBeliefNodeId = args.beliefId;
1204
- metaUpdates.linkedBeliefId = args.beliefId;
1205
- metaUpdates.questionType = "belief_test";
1206
- if (args.testType) {
1207
- metaUpdates.testType = args.testType;
1208
- }
1209
- if (args.answerImpact) {
1210
- metaUpdates.answerImpact = args.answerImpact;
1211
- }
1212
- }
1513
+ const metaUpdates = buildBeliefLinkMetadata(meta, linkArgs);
1213
1514
  await ctx.db.patch(args.questionId, {
1214
1515
  updatedAt: now,
1215
1516
  metadata: metaUpdates
1216
1517
  });
1217
- try {
1218
- const beliefGlobalId = beliefNode.globalId;
1219
- if (beliefGlobalId && questionNode.globalId) {
1220
- await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1221
- nodeId: args.questionId,
1222
- operation: "upsert"
1223
- });
1224
- await ctx.scheduler.runAfter(100, internal.neo4jSync.syncNodeToNeo4j, {
1225
- nodeId: args.beliefId,
1226
- operation: "upsert"
1227
- });
1228
- const edgeGlobalId = buildTestsEdgeGlobalId(
1229
- questionNode.globalId,
1230
- beliefGlobalId
1231
- );
1232
- await ctx.scheduler.runAfter(250, internal.neo4jEdgeAPI.createEdge, {
1233
- globalId: edgeGlobalId,
1234
- fromGlobalId: questionNode.globalId,
1235
- toGlobalId: beliefGlobalId,
1236
- edgeType: "tests",
1237
- context: args.testType || "tests",
1238
- topicId: questionNode.projectId ? String(questionNode.projectId) : void 0,
1239
- createdBy: args.userId,
1240
- fromNodeType: "question",
1241
- toNodeType: "belief",
1242
- fromLayer: "L3",
1243
- toLayer: "L3"
1244
- });
1245
- }
1246
- } catch (e) {
1247
- logQuestionFallback(
1248
- "[epistemicQuestions] Failed to create tests edge",
1249
- e,
1250
- {
1251
- questionId: args.questionId,
1252
- beliefId: args.beliefId
1253
- }
1254
- );
1255
- console.error("[linkToBelief] Failed to create tests edge:", e);
1256
- }
1518
+ await syncBeliefTestEdge(ctx, questionNode, beliefNode, linkArgs);
1257
1519
  await markProjectGraphDirty(
1258
1520
  ctx,
1259
1521
  questionNode.projectId,
@@ -1273,31 +1535,15 @@ var linkToInsight = mutation({
1273
1535
  },
1274
1536
  returns: permissiveReturn,
1275
1537
  handler: async (ctx, args) => {
1276
- const questionNode = await ctx.db.get(args.questionId);
1277
- if (!questionNode || questionNode.nodeType !== "question") {
1278
- throw new Error("Question not found");
1279
- }
1280
- const insight = await ctx.db.get(args.insightId);
1538
+ const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
1539
+ const insight = readQuestionNodeRow(await ctx.db.get(args.insightId));
1281
1540
  if (!insight) {
1282
1541
  throw new Error("Insight not found");
1283
1542
  }
1284
- const questionScopeId = resolveQuestionScopeId({
1285
- projectId: questionNode.projectId,
1286
- topicId: normalizeQuestionTopicId(questionNode.topicId)
1287
- });
1288
- if (questionScopeId) {
1289
- const hasAccess = await checkScopeAccess(
1290
- ctx,
1291
- questionScopeId,
1292
- args.userId
1293
- );
1294
- if (!hasAccess) {
1295
- throw new Error("Access denied");
1296
- }
1297
- }
1543
+ await assertQuestionScopeAccess(ctx, questionNode, args.userId);
1298
1544
  const now = Date.now();
1299
- const meta = questionNode.metadata || {};
1300
- const currentInsights = meta.relatedInsightIds || [];
1545
+ const meta = questionNode.metadata;
1546
+ const currentInsights = readStringArray2(meta.relatedInsightIds);
1301
1547
  const insightIdStr = String(args.insightId);
1302
1548
  if (!currentInsights.includes(insightIdStr)) {
1303
1549
  await ctx.db.patch(args.questionId, {
@@ -1312,8 +1558,8 @@ var linkToInsight = mutation({
1312
1558
  "by_questionId",
1313
1559
  (q) => q.eq("questionId", args.questionId)
1314
1560
  ).collect();
1315
- const duplicate = existingLinks.find(
1316
- (link) => String(link.insightId) === String(args.insightId)
1561
+ const duplicate = existingLinks.map(readQuestionEvidenceLinkRow).find(
1562
+ (link) => link && String(link.insightId) === String(args.insightId)
1317
1563
  );
1318
1564
  if (!duplicate) {
1319
1565
  await ctx.db.insert("questionEvidenceLinks", {
@@ -1348,27 +1594,11 @@ var unlinkInsight = mutation({
1348
1594
  },
1349
1595
  returns: permissiveReturn,
1350
1596
  handler: async (ctx, args) => {
1351
- const questionNode = await ctx.db.get(args.questionId);
1352
- if (!questionNode || questionNode.nodeType !== "question") {
1353
- throw new Error("Question not found");
1354
- }
1355
- const questionScopeId = resolveQuestionScopeId({
1356
- projectId: questionNode.projectId,
1357
- topicId: normalizeQuestionTopicId(questionNode.topicId)
1358
- });
1359
- if (questionScopeId) {
1360
- const hasAccess = await checkScopeAccess(
1361
- ctx,
1362
- questionScopeId,
1363
- args.userId
1364
- );
1365
- if (!hasAccess) {
1366
- throw new Error("Access denied");
1367
- }
1368
- }
1597
+ const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
1598
+ await assertQuestionScopeAccess(ctx, questionNode, args.userId);
1369
1599
  const now = Date.now();
1370
- const meta = questionNode.metadata || {};
1371
- const currentInsights = meta.relatedInsightIds || [];
1600
+ const meta = questionNode.metadata;
1601
+ const currentInsights = readStringArray2(meta.relatedInsightIds);
1372
1602
  const newInsights = currentInsights.filter(
1373
1603
  (id) => String(id) !== String(args.insightId)
1374
1604
  );
@@ -1379,57 +1609,8 @@ var unlinkInsight = mutation({
1379
1609
  relatedInsightIds: newInsights
1380
1610
  }
1381
1611
  });
1382
- try {
1383
- const links = await ctx.db.query("questionEvidenceLinks").withIndex(
1384
- "by_questionId",
1385
- (q) => q.eq("questionId", args.questionId)
1386
- ).collect();
1387
- for (const link of links) {
1388
- if (String(link.insightId) === String(args.insightId)) {
1389
- await ctx.db.delete(link._id);
1390
- }
1391
- }
1392
- } catch (e) {
1393
- logQuestionFallback(
1394
- "[epistemicQuestions] Failed to remove questionEvidenceLink",
1395
- e,
1396
- {
1397
- questionId: args.questionId,
1398
- insightId: args.insightId
1399
- }
1400
- );
1401
- console.error(
1402
- "[unlinkInsight] Failed to remove questionEvidenceLink:",
1403
- e
1404
- );
1405
- }
1406
- try {
1407
- const evidenceNode = await ctx.db.get(args.insightId);
1408
- if (evidenceNode && evidenceNode.nodeType === "evidence") {
1409
- const edges = await ctx.db.query("epistemicEdges").withIndex(
1410
- "by_from_to",
1411
- (q) => q.eq("fromNodeId", evidenceNode._id).eq("toNodeId", args.questionId)
1412
- ).collect();
1413
- for (const edge of edges) {
1414
- if (edge.edgeType === "derived_from") {
1415
- await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
1416
- globalId: edge.globalId
1417
- });
1418
- await ctx.db.delete(edge._id);
1419
- }
1420
- }
1421
- }
1422
- } catch (e) {
1423
- logQuestionFallback(
1424
- "[epistemicQuestions] Failed to remove derived edge",
1425
- e,
1426
- {
1427
- questionId: args.questionId,
1428
- insightId: args.insightId
1429
- }
1430
- );
1431
- console.error("[unlinkInsight] Failed to remove edge:", e);
1432
- }
1612
+ await removeQuestionEvidenceLinks(ctx, args);
1613
+ await removeDerivedEvidenceEdges(ctx, { ...args, questionNode });
1433
1614
  await markProjectGraphDirty(
1434
1615
  ctx,
1435
1616
  questionNode.projectId,