@lucern/graph-primitives 1.0.28 → 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 +398 -228
  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 +857 -515
  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 +366 -203
  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 -308
  80. package/dist/epistemicBeliefs.backfills.js.map +1 -1
  81. package/dist/epistemicBeliefs.confidence.d.ts +19 -14
  82. package/dist/epistemicBeliefs.confidence.js +634 -423
  83. package/dist/epistemicBeliefs.confidence.js.map +1 -1
  84. package/dist/epistemicBeliefs.core.d.ts +6 -6
  85. package/dist/epistemicBeliefs.core.js +719 -411
  86. package/dist/epistemicBeliefs.core.js.map +1 -1
  87. package/dist/epistemicBeliefs.d.ts +11 -8
  88. package/dist/epistemicBeliefs.forkEvidence.d.ts +2 -0
  89. package/dist/epistemicBeliefs.forkEvidence.js +8 -28
  90. package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
  91. package/dist/epistemicBeliefs.helpers.d.ts +69 -74
  92. package/dist/epistemicBeliefs.helpers.js +359 -248
  93. package/dist/epistemicBeliefs.helpers.js.map +1 -1
  94. package/dist/epistemicBeliefs.internal.d.ts +5 -5
  95. package/dist/epistemicBeliefs.internal.js +1246 -1044
  96. package/dist/epistemicBeliefs.internal.js.map +1 -1
  97. package/dist/epistemicBeliefs.js +4922 -3608
  98. package/dist/epistemicBeliefs.js.map +1 -1
  99. package/dist/epistemicBeliefs.lifecycle.d.ts +5 -5
  100. package/dist/epistemicBeliefs.lifecycle.js +1137 -818
  101. package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
  102. package/dist/epistemicBeliefs.links.d.ts +7 -7
  103. package/dist/epistemicBeliefs.links.js +408 -307
  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 +1063 -613
  114. package/dist/epistemicContracts.evaluators.js.map +1 -1
  115. package/dist/epistemicContracts.handlers.d.ts +15 -32
  116. package/dist/epistemicContracts.handlers.js +2086 -1644
  117. package/dist/epistemicContracts.handlers.js.map +1 -1
  118. package/dist/epistemicContracts.js +1131 -672
  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 +1969 -1205
  136. package/dist/epistemicEdges.js.map +1 -1
  137. package/dist/epistemicEdges.mutations.d.ts +7 -7
  138. package/dist/epistemicEdges.mutations.js +960 -583
  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 +937 -536
  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 +844 -696
  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 +704 -508
  180. package/dist/epistemicNodes.js.map +1 -1
  181. package/dist/epistemicNodes.mutations.d.ts +6 -6
  182. package/dist/epistemicNodes.mutations.js +564 -467
  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 +352 -312
  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 +87 -83
  245. package/dist/index.js +15677 -10594
  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,38 +1,125 @@
1
- import { v, ConvexError } from 'convex/values';
2
- import { checkProjectAccess } from '@lucern/access-control/access';
1
+ import { requireScopeWriteAccess } from '@lucern/access-control/access';
3
2
  import { getCurrentUserId } from '@lucern/access-control/auth';
3
+ import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
4
4
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
5
- import { componentsGeneric, anyApi, queryGeneric, mutationGeneric } from 'convex/server';
6
- import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
5
+ import { v } from 'convex/values';
6
+ import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
7
+ import { componentsGeneric, queryGeneric, mutationGeneric } from 'convex/server';
7
8
  import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
9
+ import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
8
10
 
9
11
  // src/entityLifecycle.ts
10
- var api = anyApi;
12
+ var unsafeApi = unsafeConvexAnyApi(
13
+ "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
14
+ );
15
+ var api = unsafeApi;
11
16
  componentsGeneric();
12
- var internal = anyApi;
17
+ var internal = unsafeApi;
13
18
  var mutation = mutationGeneric;
14
19
  var query = queryGeneric;
15
20
 
21
+ // src/entityCanonicalMatch.ts
22
+ function normalizeCanonicalEntityText(value) {
23
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
24
+ }
25
+ function buildEntityTitle(canonicalText) {
26
+ return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
27
+ }
28
+ function matchesCanonicalEntityRecord(node, args) {
29
+ if (node.epistemicLayer !== "ontological") {
30
+ return false;
31
+ }
32
+ if (node.workspaceId !== void 0) {
33
+ return false;
34
+ }
35
+ if (args.nodeType && node.nodeType !== args.nodeType) {
36
+ return false;
37
+ }
38
+ if (args.contentHash && node.contentHash !== args.contentHash) {
39
+ return false;
40
+ }
41
+ const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
42
+ if (rowTenantId !== args.tenantId) {
43
+ return false;
44
+ }
45
+ if (args.includeArchived) {
46
+ return node.status !== "deleted";
47
+ }
48
+ return node.status === "active";
49
+ }
50
+
16
51
  // src/entityValidation.ts
52
+ function isObjectLike(value) {
53
+ return value !== null && typeof value === "object";
54
+ }
55
+ function isEntitySchemaConfigField(value) {
56
+ if (!isObjectLike(value)) {
57
+ return false;
58
+ }
59
+ const required = value.required;
60
+ const type = value.type;
61
+ return typeof required === "boolean" && (type === "string" || type === "number" || type === "boolean");
62
+ }
63
+ function readEntitySchema(value) {
64
+ if (!isObjectLike(value)) {
65
+ return {};
66
+ }
67
+ const schema = {};
68
+ for (const [fieldName, fieldValue] of Object.entries(value)) {
69
+ if (!isEntitySchemaConfigField(fieldValue)) {
70
+ continue;
71
+ }
72
+ schema[fieldName] = fieldValue;
73
+ }
74
+ return schema;
75
+ }
76
+ function readSchemaEnumConfigRow(row) {
77
+ if (!isObjectLike(row)) {
78
+ return null;
79
+ }
80
+ const value = row.value;
81
+ if (typeof value !== "string") {
82
+ return null;
83
+ }
84
+ return {
85
+ value,
86
+ status: typeof row.status === "string" ? row.status : void 0,
87
+ metadata: isObjectLike(row.metadata) ? row.metadata : void 0
88
+ };
89
+ }
90
+ function readEntitySchemaFromConfig(row) {
91
+ const parsed = readSchemaEnumConfigRow(row);
92
+ if (parsed?.status !== "active" || !parsed?.metadata) {
93
+ return null;
94
+ }
95
+ const schema = parsed.metadata.schema;
96
+ if (!schema) {
97
+ return null;
98
+ }
99
+ return readEntitySchema(schema);
100
+ }
17
101
  async function getEntityTypeSchema(ctx, nodeType, tenantId) {
18
102
  if (tenantId) {
19
- const tenantEntry = await ctx.db.query("schemaEnumConfig").withIndex(
103
+ const tenantEntries = await ctx.db.query("schemaEnumConfig").withIndex(
20
104
  "by_tenant_category",
21
105
  (q) => q.eq("tenantId", tenantId).eq("category", "entity_type")
22
106
  ).collect();
23
- const tenantMatch = tenantEntry.find(
24
- (e) => e.value === nodeType && e.status === "active"
25
- );
26
- if (tenantMatch?.metadata?.schema) {
27
- return tenantMatch.metadata.schema;
107
+ const tenantMatch = tenantEntries.find((entry) => {
108
+ const parsed = readSchemaEnumConfigRow(entry);
109
+ return parsed !== null && parsed.value === nodeType && parsed.status === "active";
110
+ });
111
+ const tenantSchema = readEntitySchemaFromConfig(tenantMatch);
112
+ if (tenantSchema !== null) {
113
+ return tenantSchema;
28
114
  }
29
115
  }
30
116
  const platformEntry = await ctx.db.query("schemaEnumConfig").withIndex(
31
117
  "by_category_value",
32
118
  (q) => q.eq("category", "entity_type").eq("value", nodeType)
33
119
  ).first();
34
- if (platformEntry?.metadata?.schema && platformEntry.status === "active") {
35
- return platformEntry.metadata.schema;
120
+ const platformSchema = readEntitySchemaFromConfig(platformEntry);
121
+ if (platformSchema !== null) {
122
+ return platformSchema;
36
123
  }
37
124
  return null;
38
125
  }
@@ -76,99 +163,54 @@ async function validateEntityMetadata(ctx, nodeType, metadata, tenantId) {
76
163
  errors
77
164
  };
78
165
  }
79
-
80
- // src/entityCanonicalMatch.ts
81
- function normalizeCanonicalEntityText(value) {
82
- return value.trim().toLowerCase().replace(/\s+/g, " ");
83
- }
84
- function buildEntityTitle(canonicalText) {
85
- return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
86
- }
87
- function matchesCanonicalEntityRecord(node, args) {
88
- if (node.epistemicLayer !== "ontological") {
89
- return false;
90
- }
91
- if (node.workspaceId !== void 0) {
92
- return false;
93
- }
94
- if (args.nodeType && node.nodeType !== args.nodeType) {
95
- return false;
96
- }
97
- if (args.contentHash && node.contentHash !== args.contentHash) {
98
- return false;
99
- }
100
- const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
101
- if (rowTenantId !== args.tenantId) {
102
- return false;
103
- }
104
- if (args.includeArchived) {
105
- return node.status !== "deleted";
106
- }
107
- return node.status === "active";
166
+ function insertEpistemicNode(ctx, doc) {
167
+ assertUuidV7Identity("epistemicNodes", doc.globalId);
168
+ return ctx.db.insert("epistemicNodes", doc);
108
169
  }
109
-
110
- // src/topicOntologyResolver.ts
111
- var MAX_RESOLUTION_DEPTH = 10;
112
- async function resolveTopicOntologyInternal(ctx, topicId) {
113
- let current = await ctx.db.get(topicId);
114
- if (!current) {
115
- return null;
116
- }
117
- const startTopicId = topicId;
118
- for (let i = 0; i < MAX_RESOLUTION_DEPTH && current; i++) {
119
- if (current.ontologyId) {
120
- const ontologyDef = await ctx.db.get(
121
- current.ontologyId
122
- );
123
- if (!ontologyDef || ontologyDef.status === "archived") {
124
- if (current.parentTopicId) {
125
- current = await ctx.db.get(
126
- current.parentTopicId
127
- );
128
- continue;
129
- }
130
- return null;
131
- }
132
- const versions = await ctx.db.query("ontologyVersions").withIndex(
133
- "by_ontologyId",
134
- (q) => q.eq("ontologyId", current?.ontologyId)
135
- ).collect();
136
- const published = versions.filter((v3) => v3.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
137
- const latestPublished = published[0] ?? null;
138
- return {
139
- ontologyId: ontologyDef._id,
140
- ontologyKey: ontologyDef.ontologyKey,
141
- ontologyName: ontologyDef.name,
142
- tier: ontologyDef.tier,
143
- source: current._id === startTopicId ? "direct" : "inherited",
144
- sourceTopicId: current._id,
145
- validEntityTypes: latestPublished ? latestPublished.entityTypes.map((et) => et.value) : [],
146
- validEdgeTypes: latestPublished ? latestPublished.edgeTypes.map((et) => et.value) : [],
147
- publishedVersion: latestPublished
148
- };
149
- }
150
- if (!current.parentTopicId) {
151
- break;
152
- }
153
- current = await ctx.db.get(current.parentTopicId);
170
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
171
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
172
+ const node = await ctx.db.query("epistemicNodes").withIndex(
173
+ "by_globalId",
174
+ (q) => q.eq("globalId", endpoint)
175
+ ).first();
176
+ if (!node) {
177
+ throw new Error(
178
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
179
+ );
154
180
  }
155
- return null;
156
181
  }
157
- async function validateEntityTypeForTopic(ctx, topicId, nodeType) {
158
- const resolved = await resolveTopicOntologyInternal(ctx, topicId);
159
- if (!resolved) {
160
- return { valid: true };
182
+ async function insertEpistemicEdge(ctx, doc) {
183
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
184
+ assertStorageEdgeVocabulary(doc.edgeType);
185
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
186
+ throw new Error(
187
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
188
+ );
161
189
  }
162
- if (resolved.validEntityTypes.length === 0) {
163
- return { valid: true };
190
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
191
+ throw new Error(
192
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
193
+ );
164
194
  }
165
- if (resolved.validEntityTypes.includes(nodeType)) {
166
- return { valid: true };
195
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
196
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
197
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
198
+ assertEdgePolicyAllowed(
199
+ edgePolicyManifest,
200
+ doc.edgeType,
201
+ {
202
+ kind: "epistemic_node",
203
+ nodeId: doc.fromNodeId,
204
+ nodeType: doc.fromNodeType
205
+ },
206
+ {
207
+ kind: "epistemic_node",
208
+ nodeId: doc.toNodeId,
209
+ nodeType: doc.toNodeType
210
+ }
211
+ );
167
212
  }
168
- return {
169
- valid: false,
170
- error: `Entity type "${nodeType}" is not defined in the ontology "${resolved.ontologyKey}" (${resolved.ontologyName}). Valid entity types: ${resolved.validEntityTypes.join(", ")}. Source: ${resolved.source} from topic ${resolved.sourceTopicId}.`
171
- };
213
+ return ctx.db.insert("epistemicEdges", doc);
172
214
  }
173
215
 
174
216
  // src/debug.ts
@@ -201,6 +243,10 @@ function readStringArray(value) {
201
243
  function readMetadata(topic) {
202
244
  return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
203
245
  }
246
+ function omitMetadataKey(metadata, key) {
247
+ const { [key]: _omitted, ...rest } = metadata;
248
+ return rest;
249
+ }
204
250
  function readLegacyProjectId(value) {
205
251
  if (!value) {
206
252
  return;
@@ -281,9 +327,12 @@ async function resolveTopicDoc(ctx, scopeId) {
281
327
  );
282
328
  }
283
329
  try {
284
- const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
285
- projectId: String(scopeId)
286
- });
330
+ const topic = await ctx.runQuery(
331
+ api.topics.getByLegacyScopeId,
332
+ {
333
+ projectId: String(scopeId)
334
+ }
335
+ );
287
336
  if (topic?.name !== void 0 && topic?.type !== void 0) {
288
337
  return topic;
289
338
  }
@@ -303,8 +352,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
303
352
  const outwardId = idMode === "topic" ? topicId : storageProjectId;
304
353
  const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
305
354
  const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
306
- const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
307
- const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
355
+ let createdAt = 0;
356
+ if (typeof topic.createdAt === "number") {
357
+ createdAt = topic.createdAt;
358
+ } else if (typeof topic._creationTime === "number") {
359
+ createdAt = topic._creationTime;
360
+ }
361
+ let updatedAt = createdAt;
362
+ if (typeof topic.updatedAt === "number") {
363
+ updatedAt = topic.updatedAt;
364
+ } else if (typeof metadata.updatedAt === "number") {
365
+ updatedAt = metadata.updatedAt;
366
+ }
308
367
  return {
309
368
  ...metadata,
310
369
  _id: outwardId,
@@ -373,90 +432,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
373
432
  if (!topic) {
374
433
  return null;
375
434
  }
376
- const nextMetadata = { ...readMetadata(topic) };
377
- const patch = {};
378
- const topicUpdateArgs = {
379
- id: String(topic._id)
435
+ const plan = buildTopicProjectOverlayPatchPlan(topic, value);
436
+ await applyTopicProjectOverlayPatch(ctx, topic, plan);
437
+ return materializeTopicProjectOverlay({
438
+ ...topic,
439
+ ...plan.patch,
440
+ metadata: plan.nextMetadata
441
+ });
442
+ }
443
+ function buildTopicProjectOverlayPatchPlan(topic, value) {
444
+ const plan = {
445
+ nextMetadata: { ...readMetadata(topic) },
446
+ patch: {},
447
+ topicUpdateArgs: {
448
+ id: String(topic._id)
449
+ }
380
450
  };
381
451
  for (const [key, rawValue] of Object.entries(value)) {
382
- switch (key) {
383
- case "_id":
384
- case "projectId":
385
- case "topicId":
386
- case "legacyProjectId":
387
- case "storageProjectId":
388
- break;
389
- case "name":
390
- case "description":
391
- patch[key] = rawValue;
392
- topicUpdateArgs[key] = rawValue;
393
- break;
394
- case "tenantId":
395
- case "workspaceId":
396
- case "ownerId":
397
- throw new Error(
398
- `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
399
- );
400
- case "status": {
401
- const status = coerceStatus(rawValue);
402
- if (status) {
403
- patch.status = status;
404
- topicUpdateArgs.status = status;
405
- }
406
- break;
407
- }
408
- case "visibility": {
409
- const visibility = coerceVisibility(rawValue);
410
- if (visibility) {
411
- patch.visibility = visibility;
412
- topicUpdateArgs.visibility = visibility;
413
- }
414
- break;
415
- }
416
- case "type": {
417
- const projectType = readNonEmptyString(rawValue);
418
- if (projectType) {
419
- nextMetadata.projectType = projectType;
420
- } else {
421
- delete nextMetadata.projectType;
422
- }
423
- break;
424
- }
425
- case "updatedAt":
426
- case "createdAt":
427
- break;
428
- default:
429
- if (rawValue === void 0) {
430
- delete nextMetadata[key];
431
- } else {
432
- nextMetadata[key] = rawValue;
433
- }
434
- }
452
+ applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
435
453
  }
436
- patch.updatedAt = Date.now();
437
- patch.metadata = nextMetadata;
438
- topicUpdateArgs.metadata = nextMetadata;
454
+ plan.patch.updatedAt = Date.now();
455
+ plan.patch.metadata = plan.nextMetadata;
456
+ plan.topicUpdateArgs.metadata = plan.nextMetadata;
457
+ return plan;
458
+ }
459
+ function applyTopicProjectOverlayPatchEntry(plan, key, rawValue) {
460
+ switch (key) {
461
+ case "_id":
462
+ case "projectId":
463
+ case "topicId":
464
+ case "legacyProjectId":
465
+ case "storageProjectId":
466
+ case "updatedAt":
467
+ case "createdAt":
468
+ return;
469
+ case "name":
470
+ case "description":
471
+ plan.patch[key] = rawValue;
472
+ plan.topicUpdateArgs[key] = rawValue;
473
+ return;
474
+ case "tenantId":
475
+ case "workspaceId":
476
+ case "ownerId":
477
+ throw new Error(
478
+ `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
479
+ );
480
+ case "status":
481
+ applyTopicStatusPatch(plan, rawValue);
482
+ return;
483
+ case "visibility":
484
+ applyTopicVisibilityPatch(plan, rawValue);
485
+ return;
486
+ case "type":
487
+ applyTopicProjectTypePatch(plan, rawValue);
488
+ return;
489
+ default:
490
+ applyTopicMetadataPatch(plan, key, rawValue);
491
+ }
492
+ }
493
+ function applyTopicStatusPatch(plan, rawValue) {
494
+ const status = coerceStatus(rawValue);
495
+ if (status) {
496
+ plan.patch.status = status;
497
+ plan.topicUpdateArgs.status = status;
498
+ }
499
+ }
500
+ function applyTopicVisibilityPatch(plan, rawValue) {
501
+ const visibility = coerceVisibility(rawValue);
502
+ if (visibility) {
503
+ plan.patch.visibility = visibility;
504
+ plan.topicUpdateArgs.visibility = visibility;
505
+ }
506
+ }
507
+ function applyTopicProjectTypePatch(plan, rawValue) {
508
+ const projectType = readNonEmptyString(rawValue);
509
+ if (projectType) {
510
+ plan.nextMetadata.projectType = projectType;
511
+ return;
512
+ }
513
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, "projectType");
514
+ }
515
+ function applyTopicMetadataPatch(plan, key, rawValue) {
516
+ if (rawValue === void 0) {
517
+ plan.nextMetadata = omitMetadataKey(plan.nextMetadata, key);
518
+ return;
519
+ }
520
+ plan.nextMetadata[key] = rawValue;
521
+ }
522
+ async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
439
523
  if (typeof ctx.runMutation === "function") {
440
524
  try {
441
- await ctx.runMutation(api.topics.update, topicUpdateArgs);
525
+ await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
442
526
  } catch (error) {
443
- if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
527
+ if (!canPatchTopicViaLocalDb(ctx, error)) {
444
528
  throw error;
445
529
  }
446
- await ctx.db.patch(String(topic._id), patch);
530
+ await ctx.db.patch(topic._id, plan.patch);
447
531
  }
448
532
  } else if (ctx?.db && typeof ctx.db.patch === "function") {
449
- await ctx.db.patch(String(topic._id), patch);
533
+ await ctx.db.patch(topic._id, plan.patch);
450
534
  } else {
451
535
  throw new Error(
452
536
  "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
453
537
  );
454
538
  }
455
- return materializeTopicProjectOverlay({
456
- ...topic,
457
- ...patch,
458
- metadata: nextMetadata
459
- });
539
+ }
540
+ function canPatchTopicViaLocalDb(ctx, error) {
541
+ return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
460
542
  }
461
543
 
462
544
  // src/resolvers.ts
@@ -484,7 +566,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
484
566
  try {
485
567
  await patchTopicProjectOverlay(ctx, projectId, value);
486
568
  } catch (error) {
487
- if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
569
+ if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
488
570
  throw error;
489
571
  }
490
572
  console.warn(
@@ -517,6 +599,92 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
517
599
  ...resolverOverrides
518
600
  };
519
601
  }
602
+
603
+ // src/topicOntologyResolver.ts
604
+ var MAX_RESOLUTION_DEPTH = 10;
605
+ async function loadTopic(ctx, topicId) {
606
+ return await ctx.db.get(topicId);
607
+ }
608
+ async function loadOntologyDefinition(ctx, ontologyId) {
609
+ return await ctx.db.get(ontologyId);
610
+ }
611
+ async function loadPublishedOntologyVersions(ctx, ontologyId) {
612
+ const versions = await ctx.db.query("ontologyVersions").withIndex("by_ontologyId", (q) => q.eq("ontologyId", ontologyId)).collect();
613
+ return versions.filter((version) => version.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
614
+ }
615
+ function resolvedTopicOntology(args) {
616
+ return {
617
+ ontologyId: args.ontologyDef._id,
618
+ ontologyKey: args.ontologyDef.ontologyKey,
619
+ ontologyName: args.ontologyDef.name,
620
+ publishedVersion: args.latestPublished,
621
+ source: args.source,
622
+ sourceTopicId: args.sourceTopicId,
623
+ tier: args.ontologyDef.tier,
624
+ validEdgeTypes: args.latestPublished?.edgeTypes.map((edgeType) => edgeType.value) ?? [],
625
+ validEntityTypes: args.latestPublished?.entityTypes.map((entityType) => entityType.value) ?? []
626
+ };
627
+ }
628
+ async function resolveCurrentTopicOntology(args) {
629
+ if (!args.current.ontologyId) {
630
+ return "continue";
631
+ }
632
+ const ontologyDef = await loadOntologyDefinition(
633
+ args.ctx,
634
+ args.current.ontologyId
635
+ );
636
+ if (!ontologyDef || ontologyDef.status === "archived") {
637
+ return args.current.parentTopicId ? "continue" : null;
638
+ }
639
+ const published = await loadPublishedOntologyVersions(
640
+ args.ctx,
641
+ args.current.ontologyId
642
+ );
643
+ return resolvedTopicOntology({
644
+ latestPublished: published[0] ?? null,
645
+ ontologyDef,
646
+ source: args.current._id === args.startTopicId ? "direct" : "inherited",
647
+ sourceTopicId: args.current._id
648
+ });
649
+ }
650
+ async function resolveTopicOntologyInternal(ctx, topicId) {
651
+ let current = await loadTopic(ctx, topicId);
652
+ if (!current) {
653
+ return null;
654
+ }
655
+ const startTopicId = topicId;
656
+ for (let i = 0; i < MAX_RESOLUTION_DEPTH && current; i++) {
657
+ const resolved = await resolveCurrentTopicOntology({
658
+ ctx,
659
+ current,
660
+ startTopicId
661
+ });
662
+ if (resolved !== "continue") {
663
+ return resolved;
664
+ }
665
+ if (!current.parentTopicId) {
666
+ break;
667
+ }
668
+ current = await loadTopic(ctx, current.parentTopicId);
669
+ }
670
+ return null;
671
+ }
672
+ async function validateEntityTypeForTopic(ctx, topicId, nodeType) {
673
+ const resolved = await resolveTopicOntologyInternal(ctx, topicId);
674
+ if (!resolved) {
675
+ return { valid: true };
676
+ }
677
+ if (resolved.validEntityTypes.length === 0) {
678
+ return { valid: true };
679
+ }
680
+ if (resolved.validEntityTypes.includes(nodeType)) {
681
+ return { valid: true };
682
+ }
683
+ return {
684
+ valid: false,
685
+ error: `Entity type "${nodeType}" is not defined in the ontology "${resolved.ontologyKey}" (${resolved.ontologyName}). Valid entity types: ${resolved.validEntityTypes.join(", ")}. Source: ${resolved.source} from topic ${resolved.sourceTopicId}.`
686
+ };
687
+ }
520
688
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
521
689
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
522
690
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -551,13 +719,15 @@ function asMappedProjectId(topic) {
551
719
  if (!topic) {
552
720
  return;
553
721
  }
554
- const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
722
+ const directLegacyProjectId = normalizeScopeValue(
723
+ topic[LEGACY_SCOPE_FIELD2]
724
+ );
555
725
  if (directLegacyProjectId) {
556
726
  return directLegacyProjectId;
557
727
  }
558
728
  const metadata = topic.metadata || {};
559
729
  const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
560
- return candidate ? candidate : void 0;
730
+ return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
561
731
  }
562
732
  function normalizeScopeValue(value) {
563
733
  if (typeof value !== "string") {
@@ -582,8 +752,9 @@ function pickPrimaryTopic(candidates) {
582
752
  })[0];
583
753
  }
584
754
  async function findTopicsByScopeAlias(ctx, scopeId) {
755
+ const query2 = ctx.db.query("topics");
585
756
  try {
586
- return await ctx.db.query("topics").withIndex(
757
+ return await query2.withIndex(
587
758
  "by_graph_scope_project",
588
759
  (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
589
760
  ).collect();
@@ -595,7 +766,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
595
766
  scopeId
596
767
  }
597
768
  );
598
- const topics = await ctx.db.query("topics").collect();
769
+ const topics = await query2.collect();
599
770
  return topics.filter((topic) => {
600
771
  const normalizedGlobalId = normalizeScopeValue(topic.globalId);
601
772
  const mappedProjectId = asMappedProjectId(topic);
@@ -651,209 +822,121 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
651
822
  let current = topic;
652
823
  for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
653
824
  current = await ctx.db.get(current.parentTopicId);
654
- if (!current) break;
825
+ if (!current) {
826
+ break;
827
+ }
655
828
  if (!tenantId) {
656
829
  tenantId = normalizeScopeValue(current.tenantId);
657
830
  }
658
831
  if (!workspaceId) {
659
832
  workspaceId = normalizeScopeValue(current.workspaceId);
660
833
  }
661
- if (tenantId && workspaceId) break;
834
+ if (tenantId && workspaceId) {
835
+ break;
836
+ }
662
837
  }
663
838
  return { tenantId, workspaceId };
664
839
  }
665
840
  async function resolveTopicProjectScope(ctx, args) {
666
841
  if (args.topicId) {
667
- let topic = null;
668
- try {
669
- topic = await ctx.db.get(
670
- args.topicId
671
- );
672
- } catch (error) {
673
- debugGraphPrimitiveFallback(
674
- "[topicScope] Failed to load topic by direct id",
675
- {
676
- error,
677
- topicId: args.topicId
678
- }
679
- );
680
- }
681
- if (!topic) {
682
- topic = await tryResolveHostTopicById(ctx, String(args.topicId));
683
- }
684
- if (!topic) {
685
- topic = pickPrimaryTopic(
686
- await findTopicsByScopeAlias(ctx, String(args.topicId))
687
- ) ?? null;
688
- }
689
- if (!topic) {
690
- const nodeScope = await resolveTopicNodeScopeOrNull(
691
- ctx,
692
- String(args.topicId)
693
- );
694
- if (nodeScope) {
695
- return nodeScope;
696
- }
697
- throw new Error(`Topic not found: ${String(args.topicId)}`);
698
- }
699
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
700
- const mapped = asMappedProjectId(topic);
701
- if (mapped) {
702
- return {
703
- topicId: topic._id,
704
- projectId: mapped,
705
- tenantId: inherited.tenantId,
706
- workspaceId: inherited.workspaceId,
707
- source: "topic"
708
- };
709
- }
710
- return {
711
- topicId: topic._id,
712
- tenantId: inherited.tenantId,
713
- workspaceId: inherited.workspaceId,
714
- source: "topic"
715
- };
842
+ return await resolveScopeFromTopicId(ctx, args.topicId);
716
843
  }
717
844
  if (args.projectId) {
718
- let directTopic = null;
719
- try {
720
- directTopic = await ctx.db.get(
721
- args.projectId
722
- );
723
- } catch (error) {
724
- debugGraphPrimitiveFallback(
725
- "[topicScope] Failed to load direct project topic",
726
- {
727
- error,
728
- projectId: args.projectId
729
- }
730
- );
731
- }
732
- if (directTopic) {
733
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
734
- const mapped = asMappedProjectId(directTopic);
735
- return {
736
- topicId: directTopic._id,
737
- projectId: mapped ?? args.projectId,
738
- tenantId: inherited.tenantId,
739
- workspaceId: inherited.workspaceId,
740
- source: "topic_inferred"
741
- };
742
- }
743
- directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
744
- if (directTopic) {
745
- const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
746
- const mapped = asMappedProjectId(directTopic);
747
- return {
748
- topicId: directTopic._id,
749
- projectId: mapped ?? args.projectId,
750
- tenantId: inherited.tenantId,
751
- workspaceId: inherited.workspaceId,
752
- source: "topic_inferred"
753
- };
754
- }
755
- const topics = await findTopicsByScopeAlias(ctx, args.projectId);
756
- const primary = pickPrimaryTopic(topics);
757
- if (primary) {
758
- const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
759
- return {
760
- topicId: primary._id,
761
- projectId: args.projectId,
762
- tenantId: inherited.tenantId,
763
- workspaceId: inherited.workspaceId,
764
- source: "project_mapped_topic"
765
- };
766
- }
767
- const nodeScope = await resolveTopicNodeScopeOrNull(
768
- ctx,
769
- String(args.projectId)
770
- );
771
- if (nodeScope) {
772
- return {
773
- ...nodeScope,
774
- projectId: nodeScope.projectId ?? String(args.projectId)
775
- };
776
- }
777
- throw new Error(
778
- `Legacy project scope ${String(args.projectId)} has no mapped topic.`
779
- );
845
+ return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
780
846
  }
781
847
  throw new Error(
782
848
  "Missing scope: provide topicId (preferred) or legacy projectId alias."
783
849
  );
784
850
  }
785
- var optionalScopeArgs = {
786
- projectId: v.optional(v.string()),
787
- topicId: v.optional(v.string())
788
- };
789
- async function insertEpistemicNode(ctx, doc) {
790
- assertUuidV7Identity("epistemicNodes", doc.globalId);
791
- return ctx.db.insert("epistemicNodes", doc);
851
+ async function resolveScopeFromTopicId(ctx, topicId) {
852
+ const topic = await resolveTopicDocFromTopicId(ctx, topicId);
853
+ if (topic) {
854
+ return await buildTopicScope(ctx, topic, "topic");
855
+ }
856
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
857
+ if (nodeScope) {
858
+ return nodeScope;
859
+ }
860
+ throw new Error(`Topic not found: ${String(topicId)}`);
792
861
  }
793
- async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
794
- assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
795
- const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
796
- if (!node) {
797
- throw new Error(
798
- `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
799
- );
862
+ async function resolveTopicDocFromTopicId(ctx, topicId) {
863
+ const direct = await tryReadTopicDoc(ctx, topicId, {
864
+ failureLog: "[topicScope] Failed to load topic by direct id",
865
+ idLogKey: "topicId"
866
+ });
867
+ if (direct) {
868
+ return direct;
869
+ }
870
+ const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
871
+ if (hostTopic) {
872
+ return hostTopic;
800
873
  }
874
+ return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
801
875
  }
802
- async function insertEpistemicEdge(ctx, doc) {
803
- assertUuidV7Identity("epistemicEdges", doc.globalId);
804
- assertStorageEdgeVocabulary(doc.edgeType);
805
- if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
806
- throw new Error(
807
- "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
808
- );
876
+ async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
877
+ const directTopic = await resolveDirectLegacyProjectTopic(
878
+ ctx,
879
+ legacyProjectId
880
+ );
881
+ if (directTopic) {
882
+ return await buildTopicScope(ctx, directTopic, "topic_inferred", {
883
+ fallbackProjectId: legacyProjectId
884
+ });
809
885
  }
810
- if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
811
- throw new Error(
812
- "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
813
- );
886
+ const primary = pickPrimaryTopic(
887
+ await findTopicsByScopeAlias(ctx, legacyProjectId)
888
+ );
889
+ if (primary) {
890
+ return await buildTopicScope(ctx, primary, "project_mapped_topic", {
891
+ fallbackProjectId: legacyProjectId
892
+ });
814
893
  }
815
- await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
816
- await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
817
- if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
818
- assertEdgePolicyAllowed(
819
- edgePolicyManifest,
820
- doc.edgeType,
821
- {
822
- kind: "epistemic_node",
823
- nodeId: doc.fromNodeId,
824
- nodeType: doc.fromNodeType
825
- },
826
- {
827
- kind: "epistemic_node",
828
- nodeId: doc.toNodeId,
829
- nodeType: doc.toNodeType
830
- }
831
- );
894
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
895
+ if (nodeScope) {
896
+ return {
897
+ ...nodeScope,
898
+ projectId: nodeScope.projectId ?? legacyProjectId
899
+ };
832
900
  }
833
- return ctx.db.insert("epistemicEdges", doc);
901
+ throw new Error(
902
+ `Legacy project scope ${legacyProjectId} has no mapped topic.`
903
+ );
834
904
  }
835
-
836
- // src/entityLifecycle.ts
837
- function throwStructuredMutationError(args) {
838
- const data = {
839
- structuredMutationError: true,
840
- message: args.message,
841
- status: args.status,
842
- code: args.code,
843
- invariantCode: args.invariantCode,
844
- suggestion: args.suggestion,
845
- details: args.details
905
+ async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
906
+ const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
907
+ failureLog: "[topicScope] Failed to load direct project topic",
908
+ idLogKey: "projectId"
909
+ });
910
+ return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
911
+ }
912
+ async function tryReadTopicDoc(ctx, id, log) {
913
+ try {
914
+ return await ctx.db.get(id);
915
+ } catch (error) {
916
+ debugGraphPrimitiveFallback(log.failureLog, {
917
+ error,
918
+ [log.idLogKey]: id
919
+ });
920
+ return null;
921
+ }
922
+ }
923
+ async function buildTopicScope(ctx, topic, source, options = {}) {
924
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
925
+ const mapped = asMappedProjectId(topic);
926
+ return {
927
+ topicId: topic._id,
928
+ ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
929
+ tenantId: inherited.tenantId,
930
+ workspaceId: inherited.workspaceId,
931
+ source
846
932
  };
847
- const error = new ConvexError(
848
- data
849
- );
850
- error.status = args.status;
851
- error.code = args.code;
852
- error.invariantCode = args.invariantCode;
853
- error.suggestion = args.suggestion;
854
- error.details = args.details;
855
- throw error;
856
933
  }
934
+ var optionalScopeArgs = {
935
+ projectId: v.optional(v.string()),
936
+ topicId: v.optional(v.string())
937
+ };
938
+
939
+ // src/entityLifecycle.ts
857
940
  async function requireAuthenticatedUserId(ctx) {
858
941
  const userId = await getCurrentUserId(ctx);
859
942
  if (!userId) {
@@ -867,19 +950,6 @@ async function requireAuthenticatedUserId(ctx) {
867
950
  }
868
951
  return userId;
869
952
  }
870
- async function requireProjectWriteAccess(ctx, projectId, userId) {
871
- const hasAccess = await checkProjectAccess(ctx, projectId, userId);
872
- if (!hasAccess) {
873
- throwStructuredMutationError({
874
- message: `Project write access denied for topic ${projectId}.`,
875
- status: 403,
876
- code: "PROJECT_ACCESS_DENIED",
877
- invariantCode: "policy.scope_required",
878
- suggestion: "The acting principal lacks project-write access to this topic. Request a topic grant (or, if the principal created this topic, run the creator-grant backfill) and retry.",
879
- details: { topicId: projectId, principalId: userId }
880
- });
881
- }
882
- }
883
953
  function generateContentHash(nodeType, text) {
884
954
  const content = `${nodeType}:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
885
955
  let hash = 5381;
@@ -900,14 +970,227 @@ var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
900
970
  var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
901
971
  ONTOLOGICAL_NODE_TYPE_SET
902
972
  );
973
+ function isRecord(value) {
974
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
975
+ }
976
+ function readConvexId(value) {
977
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
978
+ }
979
+ function readOptionalString(value) {
980
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
981
+ }
982
+ function readRecord(value) {
983
+ return isRecord(value) ? value : void 0;
984
+ }
985
+ function readStringArray2(value) {
986
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : void 0;
987
+ }
988
+ function readEntityUpdateMetadata(value) {
989
+ if (!isRecord(value)) {
990
+ return;
991
+ }
992
+ const metadata = {};
993
+ const crunchbase = readOptionalString(value.crunchbase);
994
+ const linkedin = readOptionalString(value.linkedin);
995
+ const pitchbook = readOptionalString(value.pitchbook);
996
+ const twitter = readOptionalString(value.twitter);
997
+ const website = readOptionalString(value.website);
998
+ if (crunchbase !== void 0) {
999
+ metadata.crunchbase = crunchbase;
1000
+ }
1001
+ if (linkedin !== void 0) {
1002
+ metadata.linkedin = linkedin;
1003
+ }
1004
+ if (pitchbook !== void 0) {
1005
+ metadata.pitchbook = pitchbook;
1006
+ }
1007
+ if (twitter !== void 0) {
1008
+ metadata.twitter = twitter;
1009
+ }
1010
+ if (website !== void 0) {
1011
+ metadata.website = website;
1012
+ }
1013
+ return metadata;
1014
+ }
1015
+ function readCanonicalEntityRecord(value) {
1016
+ if (!isRecord(value)) {
1017
+ return null;
1018
+ }
1019
+ const id = readConvexId(value._id);
1020
+ if (!id) {
1021
+ return null;
1022
+ }
1023
+ const record = { _id: id };
1024
+ const canonicalText = readOptionalString(value.canonicalText);
1025
+ const contentHash = readOptionalString(value.contentHash);
1026
+ const domain = readOptionalString(value.domain);
1027
+ const epistemicLayer = readOptionalString(value.epistemicLayer);
1028
+ const externalIds = readRecord(value.externalIds);
1029
+ const globalId = readOptionalString(value.globalId);
1030
+ const metadata = readRecord(value.metadata);
1031
+ const nodeType = readOptionalString(value.nodeType);
1032
+ const projectId = readOptionalString(value.projectId);
1033
+ const status = readOptionalString(value.status);
1034
+ const subtype = readOptionalString(value.subtype);
1035
+ const tags = readStringArray2(value.tags);
1036
+ const tenantId = readOptionalString(value.tenantId);
1037
+ const title = readOptionalString(value.title);
1038
+ const verificationStatus = readOptionalString(value.verificationStatus);
1039
+ const workspaceId = readOptionalString(value.workspaceId);
1040
+ if (canonicalText !== void 0) {
1041
+ record.canonicalText = canonicalText;
1042
+ }
1043
+ if (contentHash !== void 0) {
1044
+ record.contentHash = contentHash;
1045
+ }
1046
+ if (domain !== void 0) {
1047
+ record.domain = domain;
1048
+ }
1049
+ if (epistemicLayer !== void 0) {
1050
+ record.epistemicLayer = epistemicLayer;
1051
+ }
1052
+ if (externalIds !== void 0) {
1053
+ record.externalIds = externalIds;
1054
+ }
1055
+ if (globalId !== void 0) {
1056
+ record.globalId = globalId;
1057
+ }
1058
+ if (metadata !== void 0) {
1059
+ record.metadata = metadata;
1060
+ }
1061
+ if (nodeType !== void 0) {
1062
+ record.nodeType = nodeType;
1063
+ }
1064
+ if (projectId !== void 0) {
1065
+ record.projectId = projectId;
1066
+ }
1067
+ if (status !== void 0) {
1068
+ record.status = status;
1069
+ }
1070
+ if (subtype !== void 0) {
1071
+ record.subtype = subtype;
1072
+ }
1073
+ if (tags !== void 0) {
1074
+ record.tags = tags;
1075
+ }
1076
+ if (tenantId !== void 0) {
1077
+ record.tenantId = tenantId;
1078
+ }
1079
+ if (title !== void 0) {
1080
+ record.title = title;
1081
+ }
1082
+ if (verificationStatus !== void 0) {
1083
+ record.verificationStatus = verificationStatus;
1084
+ }
1085
+ if (workspaceId !== void 0) {
1086
+ record.workspaceId = workspaceId;
1087
+ }
1088
+ return record;
1089
+ }
1090
+ function readEntityEdgeRecord(value) {
1091
+ if (!isRecord(value)) {
1092
+ return null;
1093
+ }
1094
+ const id = readConvexId(value._id);
1095
+ if (!id) {
1096
+ return null;
1097
+ }
1098
+ const edge = { _id: id };
1099
+ const fromNodeId = readConvexId(value.fromNodeId);
1100
+ const toNodeId = readConvexId(value.toNodeId);
1101
+ if (fromNodeId !== void 0) {
1102
+ edge.fromNodeId = fromNodeId;
1103
+ }
1104
+ if (toNodeId !== void 0) {
1105
+ edge.toNodeId = toNodeId;
1106
+ }
1107
+ return edge;
1108
+ }
1109
+ function readRowList(values, reader) {
1110
+ return values.flatMap((value) => {
1111
+ const row = reader(value);
1112
+ return row ? [row] : [];
1113
+ });
1114
+ }
1115
+ function readScopeTopicDoc(value) {
1116
+ if (!isRecord(value)) {
1117
+ return null;
1118
+ }
1119
+ const id = readConvexId(value._id);
1120
+ if (!id) {
1121
+ return null;
1122
+ }
1123
+ const topic = { _id: id };
1124
+ const tenantId = readOptionalString(value.tenantId);
1125
+ const workspaceId = readOptionalString(value.workspaceId);
1126
+ if (tenantId !== void 0) {
1127
+ topic.tenantId = tenantId;
1128
+ }
1129
+ if (workspaceId !== void 0) {
1130
+ topic.workspaceId = workspaceId;
1131
+ }
1132
+ return topic;
1133
+ }
1134
+ function readScopeProjectDoc(value) {
1135
+ if (!isRecord(value)) {
1136
+ return null;
1137
+ }
1138
+ const id = readOptionalString(value._id);
1139
+ if (!id) {
1140
+ return null;
1141
+ }
1142
+ const project = { _id: id };
1143
+ const tenantId = readOptionalString(value.tenantId);
1144
+ const workspaceId = readOptionalString(value.workspaceId);
1145
+ if (tenantId !== void 0) {
1146
+ project.tenantId = tenantId;
1147
+ }
1148
+ if (workspaceId !== void 0) {
1149
+ project.workspaceId = workspaceId;
1150
+ }
1151
+ return project;
1152
+ }
1153
+ function readEntityUpdateNode(value) {
1154
+ const node = readCanonicalEntityRecord(value);
1155
+ const nodeType = node?.nodeType;
1156
+ if (!(node?.canonicalText && nodeType && isOntologicalNodeType(nodeType))) {
1157
+ return null;
1158
+ }
1159
+ return {
1160
+ _id: node._id,
1161
+ canonicalText: node.canonicalText,
1162
+ domain: node.domain,
1163
+ externalIds: readEntityUpdateMetadata(node.externalIds),
1164
+ globalId: node.globalId,
1165
+ metadata: node.metadata,
1166
+ nodeType,
1167
+ projectId: node.projectId,
1168
+ status: node.status,
1169
+ subtype: node.subtype,
1170
+ tags: node.tags,
1171
+ tenantId: node.tenantId,
1172
+ title: node.title,
1173
+ verificationStatus: node.verificationStatus
1174
+ };
1175
+ }
903
1176
  async function resolveCanonicalEntityScope(ctx, args) {
904
1177
  const scope = await resolveTopicProjectScope(ctx, args);
905
- const topic = scope.topicId ? await ctx.db.get(scope.topicId) ?? null : null;
906
- const project = scope.projectId !== void 0 ? await resolveGraphPrimitivesAppResolvers().getProject(
907
- ctx,
908
- scope.projectId
909
- ) ?? null : null;
910
- const tenantId = typeof topic?.tenantId === "string" && topic.tenantId.trim().length > 0 ? topic.tenantId.trim() : typeof project?.tenantId === "string" && project.tenantId.trim().length > 0 ? project.tenantId.trim() : void 0;
1178
+ const topic = scope.topicId ? readScopeTopicDoc(await ctx.db.get(scope.topicId)) : null;
1179
+ const project = scope.projectId === void 0 ? null : readScopeProjectDoc(
1180
+ await resolveGraphPrimitivesAppResolvers().getProject(
1181
+ ctx,
1182
+ scope.projectId
1183
+ )
1184
+ );
1185
+ let tenantId;
1186
+ if (typeof topic?.tenantId === "string") {
1187
+ const trimmedTopicTenantId = topic.tenantId.trim();
1188
+ tenantId = trimmedTopicTenantId.length > 0 ? trimmedTopicTenantId : void 0;
1189
+ }
1190
+ if (tenantId === void 0 && typeof project?.tenantId === "string") {
1191
+ const trimmedProjectTenantId = project.tenantId.trim();
1192
+ tenantId = trimmedProjectTenantId.length > 0 ? trimmedProjectTenantId : void 0;
1193
+ }
911
1194
  return {
912
1195
  ...scope,
913
1196
  tenantId,
@@ -917,10 +1200,13 @@ async function resolveCanonicalEntityScope(ctx, args) {
917
1200
  }
918
1201
  async function findExistingCanonicalEntity(ctx, args) {
919
1202
  const contentHash = generateContentHash(args.nodeType, args.canonicalText);
920
- const candidates = await ctx.db.query("epistemicNodes").withIndex(
921
- "by_contentHash",
922
- (q) => q.eq("contentHash", contentHash)
923
- ).collect();
1203
+ const candidates = readRowList(
1204
+ await ctx.db.query("epistemicNodes").withIndex(
1205
+ "by_contentHash",
1206
+ (q) => q.eq("contentHash", contentHash)
1207
+ ).collect(),
1208
+ readCanonicalEntityRecord
1209
+ );
924
1210
  return candidates.find(
925
1211
  (node) => matchesCanonicalEntityRecord(node, {
926
1212
  nodeType: args.nodeType,
@@ -930,13 +1216,29 @@ async function findExistingCanonicalEntity(ctx, args) {
930
1216
  ) ?? null;
931
1217
  }
932
1218
  async function listCanonicalEntitiesForScope(ctx, args) {
933
- const baseRows = args.tenantId !== void 0 ? await ctx.db.query("epistemicNodes").withIndex(
934
- "by_tenantId",
935
- (q) => q.eq("tenantId", args.tenantId)
936
- ).collect() : args.nodeType ? await ctx.db.query("epistemicNodes").withIndex(
937
- "by_nodeType",
938
- (q) => q.eq("nodeType", args.nodeType)
939
- ).collect() : await ctx.db.query("epistemicNodes").collect();
1219
+ let baseRows = readRowList(
1220
+ await ctx.db.query("epistemicNodes").collect(),
1221
+ readCanonicalEntityRecord
1222
+ );
1223
+ if (args.tenantId === void 0) {
1224
+ if (args.nodeType !== void 0) {
1225
+ baseRows = readRowList(
1226
+ await ctx.db.query("epistemicNodes").withIndex(
1227
+ "by_nodeType",
1228
+ (q) => q.eq("nodeType", args.nodeType)
1229
+ ).collect(),
1230
+ readCanonicalEntityRecord
1231
+ );
1232
+ }
1233
+ } else {
1234
+ baseRows = readRowList(
1235
+ await ctx.db.query("epistemicNodes").withIndex(
1236
+ "by_tenantId",
1237
+ (q) => q.eq("tenantId", args.tenantId)
1238
+ ).collect(),
1239
+ readCanonicalEntityRecord
1240
+ );
1241
+ }
940
1242
  const normalizedSearch = typeof args.searchTerm === "string" && args.searchTerm.trim().length > 0 ? normalizeCanonicalEntityText(args.searchTerm) : void 0;
941
1243
  return baseRows.filter(
942
1244
  (node) => matchesCanonicalEntityRecord(node, {
@@ -1027,11 +1329,7 @@ var createEntity = mutation({
1027
1329
  projectId: args.projectId
1028
1330
  });
1029
1331
  if (scope.projectId) {
1030
- await requireProjectWriteAccess(
1031
- ctx,
1032
- scope.projectId,
1033
- authenticatedUserId
1034
- );
1332
+ await requireScopeWriteAccess(ctx, scope.projectId, authenticatedUserId);
1035
1333
  }
1036
1334
  if (scope.topicId) {
1037
1335
  const ontologyValidation = await validateEntityTypeForTopic(
@@ -1050,7 +1348,7 @@ var createEntity = mutation({
1050
1348
  });
1051
1349
  }
1052
1350
  }
1053
- const metadata = args.metadata || {};
1351
+ const metadata = readRecord(args.metadata) ?? {};
1054
1352
  const validation = await validateEntityMetadata(
1055
1353
  ctx,
1056
1354
  args.nodeType,
@@ -1125,6 +1423,100 @@ var createEntity = mutation({
1125
1423
  return { nodeId, globalId: entityGlobalId, isDuplicate: false };
1126
1424
  }
1127
1425
  });
1426
+ function normalizeEntityMetadataPatch(metadata) {
1427
+ return metadata === null || metadata === void 0 ? {} : metadata;
1428
+ }
1429
+ function resolveScopeProjectId(node) {
1430
+ return typeof node.projectId === "string" && node.projectId.trim().length > 0 ? node.projectId : void 0;
1431
+ }
1432
+ function prepareEntityAttributePlan(node, args) {
1433
+ const existingMetadata = node.metadata ?? {};
1434
+ const mergedMetadata = {
1435
+ ...existingMetadata,
1436
+ ...normalizeEntityMetadataPatch(args.metadata)
1437
+ };
1438
+ if (args.subtype !== void 0) {
1439
+ mergedMetadata.subtype = args.subtype;
1440
+ }
1441
+ const nextCanonicalText = args.canonicalText === void 0 ? node.canonicalText : args.canonicalText.trim();
1442
+ if (nextCanonicalText.length === 0) {
1443
+ throwStructuredMutationError({
1444
+ message: "canonicalText cannot be empty.",
1445
+ status: 400,
1446
+ code: "INVALID_REQUEST",
1447
+ invariantCode: "entity.update_requires_entity_lifecycle"
1448
+ });
1449
+ }
1450
+ return {
1451
+ nextCanonicalText,
1452
+ existingMetadata,
1453
+ mergedMetadata
1454
+ };
1455
+ }
1456
+ async function loadEntityForUpdate(ctx, nodeId) {
1457
+ const node = readEntityUpdateNode(await ctx.db.get(nodeId));
1458
+ if (!node) {
1459
+ throwStructuredMutationError({
1460
+ message: "Entity not found.",
1461
+ status: 404,
1462
+ code: "NOT_FOUND",
1463
+ details: { nodeId }
1464
+ });
1465
+ }
1466
+ return node;
1467
+ }
1468
+ async function assertCanonicalEntityUnique(ctx, node, nodeId, canonicalText) {
1469
+ const duplicate = await findExistingCanonicalEntity(ctx, {
1470
+ nodeType: node.nodeType,
1471
+ canonicalText,
1472
+ tenantId: typeof node.tenantId === "string" ? node.tenantId : void 0
1473
+ });
1474
+ if (duplicate && String(duplicate._id) !== String(nodeId)) {
1475
+ throwStructuredMutationError({
1476
+ message: "A canonical entity with this name already exists in the tenant scope.",
1477
+ status: 409,
1478
+ code: "CONFLICT",
1479
+ invariantCode: "entity.canonical_text_unique_per_tenant",
1480
+ suggestion: "Merge the duplicate entity instead of renaming this node onto an existing canonical record.",
1481
+ details: {
1482
+ nodeId,
1483
+ duplicateNodeId: duplicate._id,
1484
+ canonicalText
1485
+ }
1486
+ });
1487
+ }
1488
+ }
1489
+ function buildEntityAttributeUpdates(node, args, plan) {
1490
+ const updates = {
1491
+ metadata: plan.mergedMetadata
1492
+ };
1493
+ if (args.canonicalText !== void 0) {
1494
+ updates.canonicalText = plan.nextCanonicalText;
1495
+ updates.contentHash = generateContentHash(
1496
+ node.nodeType,
1497
+ plan.nextCanonicalText
1498
+ );
1499
+ updates.title = args.title === void 0 ? buildEntityTitle(plan.nextCanonicalText) : args.title;
1500
+ } else if (args.title !== void 0) {
1501
+ updates.title = args.title;
1502
+ }
1503
+ if (args.subtype !== void 0) {
1504
+ updates.subtype = args.subtype;
1505
+ }
1506
+ if (args.domain !== void 0) {
1507
+ updates.domain = args.domain;
1508
+ }
1509
+ if (args.tags !== void 0) {
1510
+ updates.tags = args.tags;
1511
+ }
1512
+ if (args.verificationStatus !== void 0) {
1513
+ updates.verificationStatus = args.verificationStatus;
1514
+ }
1515
+ if (args.externalIds !== void 0) {
1516
+ updates.externalIds = args.externalIds;
1517
+ }
1518
+ return updates;
1519
+ }
1128
1520
  var updateEntityAttributes = mutation({
1129
1521
  args: {
1130
1522
  nodeId: v.id("epistemicNodes"),
@@ -1150,68 +1542,33 @@ var updateEntityAttributes = mutation({
1150
1542
  handler: async (ctx, args) => {
1151
1543
  const authenticatedUserId = await requireAuthenticatedUserId(ctx);
1152
1544
  const now = Date.now();
1153
- const node = await ctx.db.get(args.nodeId);
1154
- if (!node) {
1155
- throwStructuredMutationError({
1156
- message: "Entity not found.",
1157
- status: 404,
1158
- code: "NOT_FOUND",
1159
- details: { nodeId: args.nodeId }
1160
- });
1161
- }
1162
- if (!isOntologicalNodeType(node.nodeType)) {
1163
- throwStructuredMutationError({
1164
- message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief/question APIs for epistemic nodes.`,
1165
- status: 400,
1166
- code: "LIFECYCLE_VIOLATION",
1167
- invariantCode: "entity.type_check",
1168
- suggestion: "Only ontological entities can be updated via updateEntityAttributes."
1169
- });
1170
- }
1171
- const scopeProjectId = typeof node.projectId === "string" && node.projectId.trim().length > 0 ? node.projectId : void 0;
1172
- if (scopeProjectId) {
1173
- await requireProjectWriteAccess(ctx, scopeProjectId, authenticatedUserId);
1174
- }
1175
- const existingMetadata = node.metadata || {};
1176
- const newMetadataFields = args.metadata !== void 0 ? args.metadata || {} : {};
1177
- const mergedMetadata = { ...existingMetadata, ...newMetadataFields };
1178
- if (args.subtype !== void 0) {
1179
- mergedMetadata.subtype = args.subtype;
1180
- }
1181
- if (args.canonicalText !== void 0 && args.canonicalText.trim().length === 0) {
1182
- throwStructuredMutationError({
1183
- message: "canonicalText cannot be empty.",
1184
- status: 400,
1185
- code: "INVALID_REQUEST",
1186
- invariantCode: "entity.update_requires_entity_lifecycle"
1187
- });
1545
+ const node = await loadEntityForUpdate(ctx, args.nodeId);
1546
+ const scopeProjectId = resolveScopeProjectId(node);
1547
+ if (scopeProjectId !== void 0) {
1548
+ await requireScopeWriteAccess(ctx, scopeProjectId, authenticatedUserId);
1188
1549
  }
1189
- const nextCanonicalText = typeof args.canonicalText === "string" && args.canonicalText.trim().length > 0 ? args.canonicalText.trim() : node.canonicalText;
1550
+ const plan = prepareEntityAttributePlan(node, {
1551
+ canonicalText: args.canonicalText,
1552
+ metadata: args.metadata,
1553
+ subtype: args.subtype,
1554
+ domain: args.domain,
1555
+ title: args.title,
1556
+ tags: args.tags,
1557
+ verificationStatus: args.verificationStatus,
1558
+ externalIds: args.externalIds
1559
+ });
1190
1560
  if (args.canonicalText !== void 0) {
1191
- const duplicate = await findExistingCanonicalEntity(ctx, {
1192
- nodeType: node.nodeType,
1193
- canonicalText: nextCanonicalText,
1194
- tenantId: typeof node.tenantId === "string" ? node.tenantId : void 0
1195
- });
1196
- if (duplicate && String(duplicate._id) !== String(args.nodeId)) {
1197
- throwStructuredMutationError({
1198
- message: "A canonical entity with this name already exists in the tenant scope.",
1199
- status: 409,
1200
- code: "CONFLICT",
1201
- invariantCode: "entity.canonical_text_unique_per_tenant",
1202
- suggestion: "Merge the duplicate entity instead of renaming this node onto an existing canonical record.",
1203
- details: {
1204
- nodeId: args.nodeId,
1205
- duplicateNodeId: duplicate._id,
1206
- canonicalText: nextCanonicalText
1207
- }
1208
- });
1209
- }
1561
+ await assertCanonicalEntityUnique(
1562
+ ctx,
1563
+ node,
1564
+ args.nodeId,
1565
+ plan.nextCanonicalText
1566
+ );
1210
1567
  }
1211
1568
  const validation = await validateEntityMetadata(
1212
1569
  ctx,
1213
1570
  node.nodeType,
1214
- mergedMetadata,
1571
+ plan.mergedMetadata,
1215
1572
  typeof node.tenantId === "string" ? node.tenantId : void 0
1216
1573
  );
1217
1574
  if (!validation.valid) {
@@ -1224,31 +1581,22 @@ var updateEntityAttributes = mutation({
1224
1581
  });
1225
1582
  }
1226
1583
  const updates = {
1227
- metadata: mergedMetadata,
1584
+ ...buildEntityAttributeUpdates(
1585
+ node,
1586
+ {
1587
+ canonicalText: args.canonicalText,
1588
+ title: args.title,
1589
+ metadata: args.metadata,
1590
+ subtype: args.subtype,
1591
+ domain: args.domain,
1592
+ tags: args.tags,
1593
+ verificationStatus: args.verificationStatus,
1594
+ externalIds: args.externalIds
1595
+ },
1596
+ plan
1597
+ ),
1228
1598
  updatedAt: now
1229
1599
  };
1230
- if (args.canonicalText !== void 0) {
1231
- updates.canonicalText = nextCanonicalText;
1232
- updates.contentHash = generateContentHash(node.nodeType, nextCanonicalText);
1233
- updates.title = args.title !== void 0 ? args.title : buildEntityTitle(nextCanonicalText);
1234
- } else if (args.title !== void 0) {
1235
- updates.title = args.title;
1236
- }
1237
- if (args.subtype !== void 0) {
1238
- updates.subtype = args.subtype;
1239
- }
1240
- if (args.domain !== void 0) {
1241
- updates.domain = args.domain;
1242
- }
1243
- if (args.tags !== void 0) {
1244
- updates.tags = args.tags;
1245
- }
1246
- if (args.verificationStatus !== void 0) {
1247
- updates.verificationStatus = args.verificationStatus;
1248
- }
1249
- if (args.externalIds !== void 0) {
1250
- updates.externalIds = args.externalIds;
1251
- }
1252
1600
  await ctx.db.patch(args.nodeId, updates);
1253
1601
  await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1254
1602
  nodeId: args.nodeId,
@@ -1269,7 +1617,7 @@ var updateEntityAttributes = mutation({
1269
1617
  tags: node.tags,
1270
1618
  verificationStatus: node.verificationStatus,
1271
1619
  externalIds: node.externalIds,
1272
- metadata: existingMetadata
1620
+ metadata: plan.existingMetadata
1273
1621
  },
1274
1622
  newState: updates,
1275
1623
  projectId: scopeProjectId
@@ -1286,9 +1634,13 @@ var mergeEntities = mutation({
1286
1634
  handler: async (ctx, args) => {
1287
1635
  const authenticatedUserId = await requireAuthenticatedUserId(ctx);
1288
1636
  const now = Date.now();
1289
- const canonical = await ctx.db.get(args.canonicalNodeId);
1290
- const duplicate = await ctx.db.get(args.duplicateNodeId);
1291
- if (!canonical || !duplicate) {
1637
+ const canonical = readEntityUpdateNode(
1638
+ await ctx.db.get(args.canonicalNodeId)
1639
+ );
1640
+ const duplicate = readEntityUpdateNode(
1641
+ await ctx.db.get(args.duplicateNodeId)
1642
+ );
1643
+ if (!(canonical && duplicate)) {
1292
1644
  throwStructuredMutationError({
1293
1645
  message: "One or both entity nodes not found.",
1294
1646
  status: 404,
@@ -1299,14 +1651,6 @@ var mergeEntities = mutation({
1299
1651
  }
1300
1652
  });
1301
1653
  }
1302
- if (!isOntologicalNodeType(canonical.nodeType) || !isOntologicalNodeType(duplicate.nodeType)) {
1303
- throwStructuredMutationError({
1304
- message: "Both nodes must be ontological entities to merge.",
1305
- status: 400,
1306
- code: "LIFECYCLE_VIOLATION",
1307
- invariantCode: "entity.type_check"
1308
- });
1309
- }
1310
1654
  if (args.canonicalNodeId === args.duplicateNodeId) {
1311
1655
  throwStructuredMutationError({
1312
1656
  message: "Cannot merge an entity with itself.",
@@ -1317,6 +1661,18 @@ var mergeEntities = mutation({
1317
1661
  details: { canonicalNodeId: args.canonicalNodeId }
1318
1662
  });
1319
1663
  }
1664
+ if (!(canonical.globalId && duplicate.globalId)) {
1665
+ throwStructuredMutationError({
1666
+ message: "Both entity nodes must have globalId values to merge.",
1667
+ status: 400,
1668
+ code: "LIFECYCLE_VIOLATION",
1669
+ invariantCode: "entity.global_id_required",
1670
+ details: {
1671
+ canonicalNodeId: args.canonicalNodeId,
1672
+ duplicateNodeId: args.duplicateNodeId
1673
+ }
1674
+ });
1675
+ }
1320
1676
  if (canonical.nodeType !== duplicate.nodeType) {
1321
1677
  throwStructuredMutationError({
1322
1678
  message: `Cannot merge different entity types: ${canonical.nodeType} vs ${duplicate.nodeType}.`,
@@ -1325,7 +1681,7 @@ var mergeEntities = mutation({
1325
1681
  });
1326
1682
  }
1327
1683
  if (canonical.projectId) {
1328
- await requireProjectWriteAccess(
1684
+ await requireScopeWriteAccess(
1329
1685
  ctx,
1330
1686
  canonical.projectId,
1331
1687
  authenticatedUserId
@@ -1346,10 +1702,13 @@ var mergeEntities = mutation({
1346
1702
  createdAt: now,
1347
1703
  updatedAt: now
1348
1704
  });
1349
- const outgoingEdges = await ctx.db.query("epistemicEdges").withIndex(
1350
- "by_from",
1351
- (q) => q.eq("fromNodeId", args.duplicateNodeId)
1352
- ).collect();
1705
+ const outgoingEdges = readRowList(
1706
+ await ctx.db.query("epistemicEdges").withIndex(
1707
+ "by_from",
1708
+ (q) => q.eq("fromNodeId", args.duplicateNodeId)
1709
+ ).collect(),
1710
+ readEntityEdgeRecord
1711
+ );
1353
1712
  for (const edge of outgoingEdges) {
1354
1713
  if (edge.toNodeId === args.canonicalNodeId) {
1355
1714
  continue;
@@ -1360,10 +1719,13 @@ var mergeEntities = mutation({
1360
1719
  updatedAt: now
1361
1720
  });
1362
1721
  }
1363
- const incomingEdges = await ctx.db.query("epistemicEdges").withIndex(
1364
- "by_to",
1365
- (q) => q.eq("toNodeId", args.duplicateNodeId)
1366
- ).collect();
1722
+ const incomingEdges = readRowList(
1723
+ await ctx.db.query("epistemicEdges").withIndex(
1724
+ "by_to",
1725
+ (q) => q.eq("toNodeId", args.duplicateNodeId)
1726
+ ).collect(),
1727
+ readEntityEdgeRecord
1728
+ );
1367
1729
  for (const edge of incomingEdges) {
1368
1730
  if (edge.fromNodeId === args.canonicalNodeId) {
1369
1731
  continue;
@@ -1374,7 +1736,7 @@ var mergeEntities = mutation({
1374
1736
  updatedAt: now
1375
1737
  });
1376
1738
  }
1377
- const duplicateMetadata = duplicate.metadata || {};
1739
+ const duplicateMetadata = duplicate.metadata ?? {};
1378
1740
  await ctx.db.patch(args.duplicateNodeId, {
1379
1741
  status: "superseded",
1380
1742
  metadata: {
@@ -1439,29 +1801,9 @@ var archiveEntity = mutation({
1439
1801
  handler: async (ctx, args) => {
1440
1802
  const authenticatedUserId = await requireAuthenticatedUserId(ctx);
1441
1803
  const now = Date.now();
1442
- const node = await ctx.db.get(args.nodeId);
1443
- if (!node) {
1444
- throwStructuredMutationError({
1445
- message: "Entity not found.",
1446
- status: 404,
1447
- code: "NOT_FOUND",
1448
- details: { nodeId: args.nodeId }
1449
- });
1450
- }
1451
- if (!isOntologicalNodeType(node.nodeType)) {
1452
- throwStructuredMutationError({
1453
- message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief archive APIs for epistemic nodes.`,
1454
- status: 400,
1455
- code: "LIFECYCLE_VIOLATION",
1456
- invariantCode: "entity.type_check"
1457
- });
1458
- }
1804
+ const node = await loadEntityForUpdate(ctx, args.nodeId);
1459
1805
  if (node.projectId) {
1460
- await requireProjectWriteAccess(
1461
- ctx,
1462
- node.projectId,
1463
- authenticatedUserId
1464
- );
1806
+ await requireScopeWriteAccess(ctx, node.projectId, authenticatedUserId);
1465
1807
  }
1466
1808
  await ctx.db.patch(args.nodeId, {
1467
1809
  status: "archived",