@lucern/graph-primitives 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +13 -12
  2. package/dist/beliefDecay.js +24 -17
  3. package/dist/beliefDecay.js.map +1 -1
  4. package/dist/beliefEvidenceLinks.js +32 -8
  5. package/dist/beliefEvidenceLinks.js.map +1 -1
  6. package/dist/confidencePropagationDispatch.js.map +1 -1
  7. package/dist/contradictions.js +32 -9
  8. package/dist/contradictions.js.map +1 -1
  9. package/dist/convex.d.ts +55 -12
  10. package/dist/convex.js.map +1 -1
  11. package/dist/edgeValidation.d.ts +25 -2
  12. package/dist/edges/index.d.ts +9 -2
  13. package/dist/edges/index.js.map +1 -1
  14. package/dist/edges/propagationTypes.d.ts +2 -3
  15. package/dist/edges/propagationTypes.js.map +1 -1
  16. package/dist/entityBridge.js +10 -3
  17. package/dist/entityBridge.js.map +1 -1
  18. package/dist/entityLifecycle.js +15 -3
  19. package/dist/entityLifecycle.js.map +1 -1
  20. package/dist/epistemicAnswers.js.map +1 -1
  21. package/dist/epistemicBeliefs.admin.d.ts +36 -0
  22. package/dist/epistemicBeliefs.admin.js +745 -0
  23. package/dist/epistemicBeliefs.admin.js.map +1 -0
  24. package/dist/epistemicBeliefs.backfills.d.ts +62 -0
  25. package/dist/epistemicBeliefs.backfills.js +1004 -0
  26. package/dist/epistemicBeliefs.backfills.js.map +1 -0
  27. package/dist/epistemicBeliefs.confidence.d.ts +45 -0
  28. package/dist/epistemicBeliefs.confidence.js +1285 -0
  29. package/dist/epistemicBeliefs.confidence.js.map +1 -0
  30. package/dist/epistemicBeliefs.core.d.ts +35 -0
  31. package/dist/epistemicBeliefs.core.js +1508 -0
  32. package/dist/epistemicBeliefs.core.js.map +1 -0
  33. package/dist/epistemicBeliefs.d.ts +12 -3
  34. package/dist/epistemicBeliefs.helpers.d.ts +168 -0
  35. package/dist/epistemicBeliefs.helpers.js +1060 -0
  36. package/dist/epistemicBeliefs.helpers.js.map +1 -0
  37. package/dist/epistemicBeliefs.internal.d.ts +30 -0
  38. package/dist/epistemicBeliefs.internal.js +1329 -0
  39. package/dist/epistemicBeliefs.internal.js.map +1 -0
  40. package/dist/epistemicBeliefs.js +1196 -1184
  41. package/dist/epistemicBeliefs.js.map +1 -1
  42. package/dist/epistemicBeliefs.lifecycle.d.ts +19 -0
  43. package/dist/epistemicBeliefs.lifecycle.js +1608 -0
  44. package/dist/epistemicBeliefs.lifecycle.js.map +1 -0
  45. package/dist/epistemicBeliefs.links.d.ts +30 -0
  46. package/dist/epistemicBeliefs.links.js +761 -0
  47. package/dist/epistemicBeliefs.links.js.map +1 -0
  48. package/dist/epistemicBeliefs.queries.d.ts +16 -0
  49. package/dist/epistemicBeliefs.queries.js +90 -0
  50. package/dist/epistemicBeliefs.queries.js.map +1 -0
  51. package/dist/epistemicContractHelpers.d.ts +1 -1
  52. package/dist/epistemicContractHelpers.js +1 -1
  53. package/dist/epistemicContracts.d.ts +5 -76
  54. package/dist/epistemicContracts.evaluators.d.ts +36 -0
  55. package/dist/epistemicContracts.evaluators.js +2506 -0
  56. package/dist/epistemicContracts.evaluators.js.map +1 -0
  57. package/dist/epistemicContracts.handlers.d.ts +40 -0
  58. package/dist/epistemicContracts.handlers.js +3029 -0
  59. package/dist/epistemicContracts.handlers.js.map +1 -0
  60. package/dist/epistemicContracts.js +2006 -5281
  61. package/dist/epistemicContracts.js.map +1 -1
  62. package/dist/epistemicContracts.metrics.d.ts +26 -0
  63. package/dist/epistemicContracts.metrics.js +427 -0
  64. package/dist/epistemicContracts.metrics.js.map +1 -0
  65. package/dist/epistemicContracts.types.d.ts +159 -0
  66. package/dist/epistemicContracts.types.js +3 -0
  67. package/dist/epistemicContracts.types.js.map +1 -0
  68. package/dist/epistemicEdgeCreation.d.ts +73 -0
  69. package/dist/epistemicEdgeCreation.js +450 -0
  70. package/dist/epistemicEdgeCreation.js.map +1 -0
  71. package/dist/epistemicEdges-BF-cn4i3.d.ts +43 -0
  72. package/dist/epistemicEdges.d.ts +8 -1
  73. package/dist/epistemicEdges.handlers.d.ts +20 -0
  74. package/dist/epistemicEdges.handlers.js +289 -0
  75. package/dist/epistemicEdges.handlers.js.map +1 -0
  76. package/dist/epistemicEdges.helpers.d.ts +27 -0
  77. package/dist/epistemicEdges.helpers.js +162 -0
  78. package/dist/epistemicEdges.helpers.js.map +1 -0
  79. package/dist/epistemicEdges.js +797 -875
  80. package/dist/epistemicEdges.js.map +1 -1
  81. package/dist/epistemicEdges.mutations.d.ts +39 -0
  82. package/dist/epistemicEdges.mutations.js +1365 -0
  83. package/dist/epistemicEdges.mutations.js.map +1 -0
  84. package/dist/epistemicEdges.queries.d.ts +95 -0
  85. package/dist/epistemicEdges.queries.js +851 -0
  86. package/dist/epistemicEdges.queries.js.map +1 -0
  87. package/dist/epistemicEdges.types.d.ts +32 -0
  88. package/dist/epistemicEdges.types.js +3 -0
  89. package/dist/epistemicEdges.types.js.map +1 -0
  90. package/dist/epistemicEvidence-DvfchNt7.d.ts +46 -0
  91. package/dist/epistemicEvidence.d.ts +5 -2
  92. package/dist/epistemicEvidence.js +801 -807
  93. package/dist/epistemicEvidence.js.map +1 -1
  94. package/dist/epistemicEvidenceHelpers.d.ts +71 -0
  95. package/dist/epistemicEvidenceHelpers.js +769 -0
  96. package/dist/epistemicEvidenceHelpers.js.map +1 -0
  97. package/dist/epistemicEvidenceMutations.d.ts +10 -0
  98. package/dist/epistemicEvidenceMutations.js +1421 -0
  99. package/dist/epistemicEvidenceMutations.js.map +1 -0
  100. package/dist/epistemicEvidenceQueries.d.ts +10 -0
  101. package/dist/epistemicEvidenceQueries.js +1049 -0
  102. package/dist/epistemicEvidenceQueries.js.map +1 -0
  103. package/dist/epistemicHelpers.d.ts +4 -2
  104. package/dist/epistemicHelpers.js +132 -127
  105. package/dist/epistemicHelpers.js.map +1 -1
  106. package/dist/epistemicLayerRules.d.ts +138 -0
  107. package/dist/epistemicLayerRules.js +481 -0
  108. package/dist/epistemicLayerRules.js.map +1 -0
  109. package/dist/epistemicLinking.js +1 -1
  110. package/dist/epistemicLinking.js.map +1 -1
  111. package/dist/epistemicNodeCreation.d.ts +101 -0
  112. package/dist/epistemicNodeCreation.js +709 -0
  113. package/dist/epistemicNodeCreation.js.map +1 -0
  114. package/dist/epistemicNodes-BCQxpYx_.d.ts +54 -0
  115. package/dist/epistemicNodes.d.ts +5 -1
  116. package/dist/epistemicNodes.helpers.d.ts +51 -0
  117. package/dist/epistemicNodes.helpers.js +73 -0
  118. package/dist/epistemicNodes.helpers.js.map +1 -0
  119. package/dist/epistemicNodes.internal.d.ts +34 -0
  120. package/dist/epistemicNodes.internal.js +658 -0
  121. package/dist/epistemicNodes.internal.js.map +1 -0
  122. package/dist/epistemicNodes.js +698 -693
  123. package/dist/epistemicNodes.js.map +1 -1
  124. package/dist/epistemicNodes.mutations.d.ts +34 -0
  125. package/dist/epistemicNodes.mutations.js +1153 -0
  126. package/dist/epistemicNodes.mutations.js.map +1 -0
  127. package/dist/epistemicNodes.queries.d.ts +36 -0
  128. package/dist/epistemicNodes.queries.js +619 -0
  129. package/dist/epistemicNodes.queries.js.map +1 -0
  130. package/dist/epistemicNodes.validators.d.ts +23 -0
  131. package/dist/epistemicNodes.validators.js +105 -0
  132. package/dist/epistemicNodes.validators.js.map +1 -0
  133. package/dist/epistemicQuestions-bwHd2FWE.d.ts +68 -0
  134. package/dist/epistemicQuestions.conviction.d.ts +52 -0
  135. package/dist/epistemicQuestions.conviction.js +1389 -0
  136. package/dist/epistemicQuestions.conviction.js.map +1 -0
  137. package/dist/epistemicQuestions.create.d.ts +29 -0
  138. package/dist/epistemicQuestions.create.js +1300 -0
  139. package/dist/epistemicQuestions.create.js.map +1 -0
  140. package/dist/epistemicQuestions.d.ts +10 -2
  141. package/dist/epistemicQuestions.evidence.d.ts +22 -0
  142. package/dist/epistemicQuestions.evidence.js +929 -0
  143. package/dist/epistemicQuestions.evidence.js.map +1 -0
  144. package/dist/epistemicQuestions.helpers.d.ts +69 -0
  145. package/dist/epistemicQuestions.helpers.js +824 -0
  146. package/dist/epistemicQuestions.helpers.js.map +1 -0
  147. package/dist/epistemicQuestions.js +2435 -2430
  148. package/dist/epistemicQuestions.js.map +1 -1
  149. package/dist/epistemicQuestions.lifecycle.d.ts +24 -0
  150. package/dist/epistemicQuestions.lifecycle.js +838 -0
  151. package/dist/epistemicQuestions.lifecycle.js.map +1 -0
  152. package/dist/epistemicQuestions.queries.d.ts +41 -0
  153. package/dist/epistemicQuestions.queries.js +1013 -0
  154. package/dist/epistemicQuestions.queries.js.map +1 -0
  155. package/dist/epistemicQuestions.sprint.d.ts +22 -0
  156. package/dist/epistemicQuestions.sprint.js +757 -0
  157. package/dist/epistemicQuestions.sprint.js.map +1 -0
  158. package/dist/epistemicQuestions.tail.d.ts +42 -0
  159. package/dist/epistemicQuestions.tail.js +1345 -0
  160. package/dist/epistemicQuestions.tail.js.map +1 -0
  161. package/dist/epistemicSources.js +6 -2
  162. package/dist/epistemicSources.js.map +1 -1
  163. package/dist/evaluators/index.d.ts +2 -2
  164. package/dist/evaluators/index.js +45 -5320
  165. package/dist/evaluators/index.js.map +1 -1
  166. package/dist/evaluators/lintCheckerEvaluator.d.ts +1 -1
  167. package/dist/evaluators/sentryCheckerEvaluator.d.ts +1 -1
  168. package/dist/evaluators/testRunnerEvaluator.d.ts +1 -1
  169. package/dist/evaluators/tscCheckerEvaluator.d.ts +1 -1
  170. package/dist/{graphTypes-CpgIuCdo.d.ts → graphTypes-B8VaIjnl.d.ts} +1 -1
  171. package/dist/graphTypes.d.ts +1 -1
  172. package/dist/{helpers-BYHIk5vU.d.ts → helpers-DNYfg6mo.d.ts} +2 -3
  173. package/dist/helpers.d.ts +2 -2
  174. package/dist/helpers.js.map +1 -1
  175. package/dist/{index-Dq-7R-gi.d.ts → index-C-Kyd7hD.d.ts} +1 -1
  176. package/dist/index.d.ts +160 -14
  177. package/dist/index.js +12291 -13001
  178. package/dist/index.js.map +1 -1
  179. package/dist/logicalRoleInference.js.map +1 -1
  180. package/dist/ontologyApproval.js +1 -1
  181. package/dist/ontologyApproval.js.map +1 -1
  182. package/dist/ontologyDefinitions.js +25 -7
  183. package/dist/ontologyDefinitions.js.map +1 -1
  184. package/dist/ontologyRegistry.js.map +1 -1
  185. package/dist/projectionReconciliation.js.map +1 -1
  186. package/dist/questionEvidenceLinks.js +28 -7
  187. package/dist/questionEvidenceLinks.js.map +1 -1
  188. package/dist/resolvers.js.map +1 -1
  189. package/dist/scopeResolverCompat.js.map +1 -1
  190. package/dist/topicProjectOverlay.js.map +1 -1
  191. package/dist/topicScope.js.map +1 -1
  192. package/dist/workflowBridge.js.map +1 -1
  193. package/dist/workspaceIsolation.js.map +1 -1
  194. package/package.json +4 -5
  195. package/dist/edgeValidation-CeI0wc0r.d.ts +0 -35
  196. package/dist/epistemicBeliefs-DzKjZAeC.d.ts +0 -377
  197. package/dist/epistemicEdges-CvlKnEyy.d.ts +0 -191
  198. package/dist/epistemicEvidence-xw6UUrwh.d.ts +0 -128
  199. package/dist/epistemicHelpers-DevrYgPN.d.ts +0 -329
  200. package/dist/epistemicNodes-DjSUfvyD.d.ts +0 -167
  201. package/dist/epistemicQuestions-B_nUclrH.d.ts +0 -214
  202. package/dist/index-Dct1T70K.d.ts +0 -25
@@ -0,0 +1,1300 @@
1
+ import { v } from 'convex/values';
2
+ import { checkProjectAccess, checkScopeAccess } from '@lucern/access-control/access';
3
+ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
+ import { componentsGeneric, anyApi, mutationGeneric, internalMutationGeneric } from 'convex/server';
5
+ import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
6
+
7
+ // src/epistemicQuestions.create.ts
8
+ var api = anyApi;
9
+ componentsGeneric();
10
+ var internal = anyApi;
11
+ var internalMutation = internalMutationGeneric;
12
+ var mutation = mutationGeneric;
13
+
14
+ // src/debug.ts
15
+ function isGraphPrimitiveDebugEnabled() {
16
+ const env = globalThis.process?.env;
17
+ return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
18
+ }
19
+ function debugGraphPrimitiveFallback(message, context) {
20
+ if (!isGraphPrimitiveDebugEnabled()) {
21
+ return;
22
+ }
23
+ console.debug(message, context ?? {});
24
+ }
25
+
26
+ // src/embeddingTrigger.ts
27
+ async function scheduleEmbeddingGeneration(args) {
28
+ try {
29
+ await args.ctx.scheduler.runAfter(
30
+ 0,
31
+ "embeddingActions:generateEpistemicNodeEmbedding",
32
+ {
33
+ nodeId: args.nodeId,
34
+ projectId: args.projectId ? String(args.projectId) : void 0,
35
+ topicId: args.topicId ? String(args.topicId) : void 0,
36
+ createdBy: args.createdBy,
37
+ nodeType: args.nodeType,
38
+ text: args.text.slice(0, 2e4),
39
+ hasAnswer: args.hasAnswer,
40
+ confidence: args.confidence
41
+ }
42
+ );
43
+ } catch (error) {
44
+ debugGraphPrimitiveFallback(
45
+ "[embeddingTrigger] Failed to schedule embedding generation",
46
+ {
47
+ error,
48
+ nodeId: String(args.nodeId),
49
+ nodeType: args.nodeType
50
+ }
51
+ );
52
+ }
53
+ }
54
+
55
+ // src/globalId.ts
56
+ function generateGlobalId() {
57
+ const bytes = new Uint8Array(16);
58
+ crypto.getRandomValues(bytes);
59
+ bytes[6] = bytes[6] & 15 | 64;
60
+ bytes[8] = bytes[8] & 63 | 128;
61
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
62
+ ""
63
+ );
64
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
65
+ }
66
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
67
+ function asMappedProjectId(topic) {
68
+ if (!topic) {
69
+ return;
70
+ }
71
+ const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
72
+ if (directLegacyProjectId) {
73
+ return directLegacyProjectId;
74
+ }
75
+ const metadata = topic.metadata || {};
76
+ const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
77
+ return candidate ? candidate : void 0;
78
+ }
79
+ function normalizeScopeValue(value) {
80
+ if (typeof value !== "string") {
81
+ return;
82
+ }
83
+ const normalized = value.trim();
84
+ return normalized.length > 0 ? normalized : void 0;
85
+ }
86
+ function pickPrimaryTopic(candidates) {
87
+ return [...candidates].sort((a, b) => {
88
+ const depthA = a.depth ?? 9999;
89
+ const depthB = b.depth ?? 9999;
90
+ if (depthA !== depthB) {
91
+ return depthA - depthB;
92
+ }
93
+ const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
94
+ const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
95
+ if (createdA !== createdB) {
96
+ return createdA - createdB;
97
+ }
98
+ return String(a.name || "").localeCompare(String(b.name || ""));
99
+ })[0];
100
+ }
101
+ async function findTopicsByScopeAlias(ctx, scopeId) {
102
+ try {
103
+ return await ctx.db.query("topics").withIndex(
104
+ "by_graph_scope_project",
105
+ (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
106
+ ).collect();
107
+ } catch (error) {
108
+ debugGraphPrimitiveFallback(
109
+ "[topicScope] Failed to resolve scope alias via index",
110
+ {
111
+ error,
112
+ scopeId
113
+ }
114
+ );
115
+ const topics = await ctx.db.query("topics").collect();
116
+ return topics.filter((topic) => {
117
+ const normalizedGlobalId = normalizeScopeValue(topic.globalId);
118
+ const mappedProjectId = asMappedProjectId(topic);
119
+ return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
120
+ });
121
+ }
122
+ }
123
+ async function tryResolveHostTopicById(ctx, topicId) {
124
+ if (typeof ctx.runQuery !== "function") {
125
+ return null;
126
+ }
127
+ try {
128
+ return await ctx.runQuery(api.topics.get, {
129
+ id: topicId
130
+ }) ?? null;
131
+ } catch (error) {
132
+ debugGraphPrimitiveFallback(
133
+ "[topicScope] Failed to resolve topic by host query",
134
+ {
135
+ error,
136
+ topicId
137
+ }
138
+ );
139
+ return null;
140
+ }
141
+ }
142
+ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
143
+ if (typeof ctx.runQuery !== "function") {
144
+ return null;
145
+ }
146
+ try {
147
+ return await ctx.runQuery(api.topics.getByLegacyScopeId, {
148
+ projectId: legacyScopeId
149
+ }) ?? null;
150
+ } catch (error) {
151
+ debugGraphPrimitiveFallback(
152
+ "[topicScope] Failed to resolve topic by legacy scope",
153
+ {
154
+ error,
155
+ legacyScopeId
156
+ }
157
+ );
158
+ return null;
159
+ }
160
+ }
161
+ async function resolveInheritedWorkspaceScope(ctx, topic) {
162
+ const MAX_DEPTH = 10;
163
+ let tenantId = normalizeScopeValue(topic.tenantId);
164
+ let workspaceId = normalizeScopeValue(topic.workspaceId);
165
+ if (tenantId && workspaceId) {
166
+ return { tenantId, workspaceId };
167
+ }
168
+ let current = topic;
169
+ for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
170
+ current = await ctx.db.get(current.parentTopicId);
171
+ if (!current) break;
172
+ if (!tenantId) {
173
+ tenantId = normalizeScopeValue(current.tenantId);
174
+ }
175
+ if (!workspaceId) {
176
+ workspaceId = normalizeScopeValue(current.workspaceId);
177
+ }
178
+ if (tenantId && workspaceId) break;
179
+ }
180
+ return { tenantId, workspaceId };
181
+ }
182
+ async function resolveTopicProjectScope(ctx, args) {
183
+ if (args.topicId) {
184
+ let topic = null;
185
+ try {
186
+ topic = await ctx.db.get(
187
+ args.topicId
188
+ );
189
+ } catch (error) {
190
+ debugGraphPrimitiveFallback(
191
+ "[topicScope] Failed to load topic by direct id",
192
+ {
193
+ error,
194
+ topicId: args.topicId
195
+ }
196
+ );
197
+ }
198
+ if (!topic) {
199
+ topic = await tryResolveHostTopicById(ctx, String(args.topicId));
200
+ }
201
+ if (!topic) {
202
+ topic = pickPrimaryTopic(
203
+ await findTopicsByScopeAlias(ctx, String(args.topicId))
204
+ ) ?? null;
205
+ }
206
+ if (!topic) {
207
+ throw new Error(`Topic not found: ${String(args.topicId)}`);
208
+ }
209
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
210
+ const mapped = asMappedProjectId(topic);
211
+ if (mapped) {
212
+ return {
213
+ topicId: topic._id,
214
+ projectId: mapped,
215
+ tenantId: inherited.tenantId,
216
+ workspaceId: inherited.workspaceId,
217
+ source: "topic"
218
+ };
219
+ }
220
+ return {
221
+ topicId: topic._id,
222
+ tenantId: inherited.tenantId,
223
+ workspaceId: inherited.workspaceId,
224
+ source: "topic"
225
+ };
226
+ }
227
+ if (args.projectId) {
228
+ let directTopic = null;
229
+ try {
230
+ directTopic = await ctx.db.get(
231
+ args.projectId
232
+ );
233
+ } catch (error) {
234
+ debugGraphPrimitiveFallback(
235
+ "[topicScope] Failed to load direct project topic",
236
+ {
237
+ error,
238
+ projectId: args.projectId
239
+ }
240
+ );
241
+ }
242
+ if (directTopic) {
243
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
244
+ const mapped = asMappedProjectId(directTopic);
245
+ return {
246
+ topicId: directTopic._id,
247
+ projectId: mapped ?? args.projectId,
248
+ tenantId: inherited.tenantId,
249
+ workspaceId: inherited.workspaceId,
250
+ source: "topic_inferred"
251
+ };
252
+ }
253
+ directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
254
+ if (directTopic) {
255
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
256
+ const mapped = asMappedProjectId(directTopic);
257
+ return {
258
+ topicId: directTopic._id,
259
+ projectId: mapped ?? args.projectId,
260
+ tenantId: inherited.tenantId,
261
+ workspaceId: inherited.workspaceId,
262
+ source: "topic_inferred"
263
+ };
264
+ }
265
+ const topics = await findTopicsByScopeAlias(ctx, args.projectId);
266
+ const primary = pickPrimaryTopic(topics);
267
+ if (primary) {
268
+ const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
269
+ return {
270
+ topicId: primary._id,
271
+ projectId: args.projectId,
272
+ tenantId: inherited.tenantId,
273
+ workspaceId: inherited.workspaceId,
274
+ source: "project_mapped_topic"
275
+ };
276
+ }
277
+ throw new Error(
278
+ `Legacy project scope ${String(args.projectId)} has no mapped topic.`
279
+ );
280
+ }
281
+ throw new Error(
282
+ "Missing scope: provide topicId (preferred) or legacy projectId alias."
283
+ );
284
+ }
285
+ var optionalScopeArgs = {
286
+ projectId: v.optional(v.string()),
287
+ topicId: v.optional(v.string())
288
+ };
289
+ function normalizeScopeValue2(value) {
290
+ if (typeof value !== "string") {
291
+ return;
292
+ }
293
+ const normalized = value.trim();
294
+ return normalized.length > 0 ? normalized : void 0;
295
+ }
296
+ function throwWorkspaceIsolationError(args) {
297
+ const error = new Error(args.message);
298
+ error.status = 409;
299
+ error.code = "INVARIANT_VIOLATION";
300
+ error.invariantCode = args.invariantCode;
301
+ error.suggestion = args.suggestion;
302
+ error.details = args.details;
303
+ throw error;
304
+ }
305
+ function assertWorkspaceScopedEpistemicNodeScope(args) {
306
+ const layer = isNodeType(args.nodeType) ? getLayerForNodeType(args.nodeType) : void 0;
307
+ if (layer === "ontological") {
308
+ return;
309
+ }
310
+ const workspaceId = normalizeScopeValue2(args.scope.workspaceId);
311
+ if (workspaceId) {
312
+ return;
313
+ }
314
+ throwWorkspaceIsolationError({
315
+ message: "Workspace-scoped reasoning isolation requires workspaceId on non-ontological node creation.",
316
+ invariantCode: "workspace.scope_required_for_epistemic_nodes",
317
+ suggestion: "Resolve the topic/project scope through a workspace-bound topic before creating epistemic nodes.",
318
+ details: {
319
+ mutationName: args.mutationName,
320
+ nodeType: args.nodeType,
321
+ topicId: args.scope.topicId,
322
+ projectId: args.scope.projectId
323
+ }
324
+ });
325
+ }
326
+ function resolveRuntimePackMutationContext(args) {
327
+ if (!args.runtimeToolName && !args.runtimePackKey && !args.runtimePackInstallScope) {
328
+ return;
329
+ }
330
+ return {
331
+ toolName: args.runtimeToolName,
332
+ packKey: args.runtimePackKey,
333
+ packInstallScope: args.runtimePackInstallScope
334
+ };
335
+ }
336
+ function assertTenantPackWorkspaceMutationAllowed(args) {
337
+ if (!args.runtime?.packKey || args.runtime.packInstallScope !== "tenant") {
338
+ return;
339
+ }
340
+ const targetWorkspaceId = normalizeScopeValue2(args.target.workspaceId);
341
+ const targetLayer = typeof args.target.epistemicLayer === "string" ? args.target.epistemicLayer : void 0;
342
+ if (!targetWorkspaceId || targetLayer === "ontological") {
343
+ return;
344
+ }
345
+ throwWorkspaceIsolationError({
346
+ message: `Tenant-scoped pack "${args.runtime.packKey}" cannot mutate workspace-scoped reasoning state.`,
347
+ invariantCode: "workspace.tenant_pack_reasoning_write_forbidden",
348
+ suggestion: "Use a workspace-scoped pack for workspace-local graph mutations, or route the change through tenant-global canonical entity flows.",
349
+ details: {
350
+ mutationName: args.mutationName,
351
+ toolName: args.runtime.toolName,
352
+ packKey: args.runtime.packKey,
353
+ targetWorkspaceId,
354
+ targetNodeType: args.target.nodeType,
355
+ targetLayer
356
+ }
357
+ });
358
+ }
359
+
360
+ // src/topicProjectOverlay.ts
361
+ var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
362
+ function readNonEmptyString(value) {
363
+ if (typeof value !== "string") {
364
+ return;
365
+ }
366
+ const normalized = value.trim();
367
+ return normalized.length > 0 ? normalized : void 0;
368
+ }
369
+ function readStringArray(value) {
370
+ if (!Array.isArray(value)) {
371
+ return [];
372
+ }
373
+ return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
374
+ }
375
+ function readMetadata(topic) {
376
+ return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
377
+ }
378
+ function readLegacyProjectId(value) {
379
+ if (!value) {
380
+ return;
381
+ }
382
+ return readNonEmptyString(value[LEGACY_SCOPE_FIELD2]);
383
+ }
384
+ function coerceVisibility(value) {
385
+ return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
386
+ }
387
+ function coerceStatus(value) {
388
+ return value === "active" || value === "archived" || value === "watching" ? value : void 0;
389
+ }
390
+ function mapProjectType(topic, metadata) {
391
+ const explicit = readNonEmptyString(metadata.projectType);
392
+ if (explicit) {
393
+ return explicit;
394
+ }
395
+ if (topic.type === "theme") {
396
+ return "thematic";
397
+ }
398
+ return readNonEmptyString(topic.type) || "general";
399
+ }
400
+ function isProjectLikeTopic(topic) {
401
+ const metadata = readMetadata(topic);
402
+ return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
403
+ }
404
+ function isMissingLucernChildComponentError(error) {
405
+ const message = getErrorMessage(error);
406
+ return message.includes(
407
+ 'Child component ComponentName(Identifier("lucern")) not found'
408
+ ) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
409
+ }
410
+ function getErrorMessage(error) {
411
+ if (error instanceof Error) {
412
+ return error.message;
413
+ }
414
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
415
+ return error.message;
416
+ }
417
+ return "unknown error";
418
+ }
419
+ async function resolveTopicDoc(ctx, scopeId) {
420
+ if (ctx?.db && typeof ctx.db.get === "function") {
421
+ try {
422
+ const directTopic = await ctx.db.get(
423
+ scopeId
424
+ );
425
+ if (directTopic) {
426
+ return directTopic;
427
+ }
428
+ } catch (error) {
429
+ debugGraphPrimitiveFallback(
430
+ "[topicProjectOverlay] Failed to resolve topic by direct ID",
431
+ {
432
+ error,
433
+ scopeId
434
+ }
435
+ );
436
+ }
437
+ }
438
+ if (typeof ctx.runQuery !== "function") {
439
+ return null;
440
+ }
441
+ try {
442
+ const topic = await ctx.runQuery(api.topics.get, {
443
+ id: String(scopeId)
444
+ });
445
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
446
+ return topic;
447
+ }
448
+ } catch (error) {
449
+ debugGraphPrimitiveFallback(
450
+ "[topicProjectOverlay] Failed to resolve topic by ID query",
451
+ {
452
+ error,
453
+ scopeId
454
+ }
455
+ );
456
+ }
457
+ try {
458
+ const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
459
+ projectId: String(scopeId)
460
+ });
461
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
462
+ return topic;
463
+ }
464
+ } catch (error) {
465
+ debugGraphPrimitiveFallback(
466
+ "[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
467
+ { error, scopeId }
468
+ );
469
+ }
470
+ return null;
471
+ }
472
+ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
473
+ const metadata = readMetadata(topic);
474
+ const topicId = String(topic._id);
475
+ const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
476
+ const storageProjectId = legacyProjectId || topicId;
477
+ const outwardId = idMode === "topic" ? topicId : storageProjectId;
478
+ const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
479
+ const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
480
+ const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
481
+ const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
482
+ return {
483
+ ...metadata,
484
+ _id: outwardId,
485
+ projectId: outwardId,
486
+ topicId,
487
+ storageProjectId,
488
+ legacyProjectId,
489
+ name: readNonEmptyString(topic.name) || "Untitled Theme",
490
+ type: mapProjectType(topic, metadata),
491
+ description: readNonEmptyString(topic.description),
492
+ ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
493
+ sharedWith: readStringArray(metadata.sharedWith),
494
+ visibility,
495
+ tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
496
+ workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
497
+ status,
498
+ tags: readStringArray(metadata.tags),
499
+ chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
500
+ artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
501
+ lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
502
+ _creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
503
+ createdAt,
504
+ updatedAt
505
+ };
506
+ }
507
+ async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
508
+ const topic = await resolveTopicDoc(ctx, scopeId);
509
+ if (!topic) {
510
+ return null;
511
+ }
512
+ if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
513
+ return null;
514
+ }
515
+ return materializeTopicProjectOverlay(topic, options.idMode);
516
+ }
517
+ async function listTopicProjectOverlays(ctx, options = {}) {
518
+ let allTopics = [];
519
+ if (ctx?.db?.query && typeof ctx.db.query === "function") {
520
+ try {
521
+ allTopics = await ctx.db.query("topics").collect();
522
+ } catch (error) {
523
+ debugGraphPrimitiveFallback(
524
+ "[topicProjectOverlay] Failed to read topics table; falling back to API",
525
+ { error }
526
+ );
527
+ allTopics = [];
528
+ }
529
+ }
530
+ if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
531
+ allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
532
+ }
533
+ return allTopics.filter(
534
+ (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
535
+ ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
536
+ }
537
+ async function patchTopicProjectOverlay(ctx, scopeId, value) {
538
+ const topic = await resolveTopicDoc(ctx, scopeId);
539
+ if (!topic) {
540
+ return null;
541
+ }
542
+ const nextMetadata = { ...readMetadata(topic) };
543
+ const patch = {};
544
+ const topicUpdateArgs = {
545
+ id: String(topic._id)
546
+ };
547
+ for (const [key, rawValue] of Object.entries(value)) {
548
+ switch (key) {
549
+ case "_id":
550
+ case "projectId":
551
+ case "topicId":
552
+ case "legacyProjectId":
553
+ case "storageProjectId":
554
+ break;
555
+ case "name":
556
+ case "description":
557
+ patch[key] = rawValue;
558
+ topicUpdateArgs[key] = rawValue;
559
+ break;
560
+ case "tenantId":
561
+ case "workspaceId":
562
+ case "ownerId":
563
+ throw new Error(
564
+ `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
565
+ );
566
+ case "status": {
567
+ const status = coerceStatus(rawValue);
568
+ if (status) {
569
+ patch.status = status;
570
+ topicUpdateArgs.status = status;
571
+ }
572
+ break;
573
+ }
574
+ case "visibility": {
575
+ const visibility = coerceVisibility(rawValue);
576
+ if (visibility) {
577
+ patch.visibility = visibility;
578
+ topicUpdateArgs.visibility = visibility;
579
+ }
580
+ break;
581
+ }
582
+ case "type": {
583
+ const projectType = readNonEmptyString(rawValue);
584
+ if (projectType) {
585
+ nextMetadata.projectType = projectType;
586
+ } else {
587
+ delete nextMetadata.projectType;
588
+ }
589
+ break;
590
+ }
591
+ case "updatedAt":
592
+ case "createdAt":
593
+ break;
594
+ default:
595
+ if (rawValue === void 0) {
596
+ delete nextMetadata[key];
597
+ } else {
598
+ nextMetadata[key] = rawValue;
599
+ }
600
+ }
601
+ }
602
+ patch.updatedAt = Date.now();
603
+ patch.metadata = nextMetadata;
604
+ topicUpdateArgs.metadata = nextMetadata;
605
+ if (typeof ctx.runMutation === "function") {
606
+ try {
607
+ await ctx.runMutation(api.topics.update, topicUpdateArgs);
608
+ } catch (error) {
609
+ if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
610
+ throw error;
611
+ }
612
+ await ctx.db.patch(String(topic._id), patch);
613
+ }
614
+ } else if (ctx?.db && typeof ctx.db.patch === "function") {
615
+ await ctx.db.patch(String(topic._id), patch);
616
+ } else {
617
+ throw new Error(
618
+ "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
619
+ );
620
+ }
621
+ return materializeTopicProjectOverlay({
622
+ ...topic,
623
+ ...patch,
624
+ metadata: nextMetadata
625
+ });
626
+ }
627
+
628
+ // src/resolvers.ts
629
+ function isMissingLucernChildComponentError2(error) {
630
+ const message = getErrorMessage2(error);
631
+ return message.includes(
632
+ 'Child component ComponentName(Identifier("lucern")) not found'
633
+ ) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
634
+ }
635
+ function getErrorMessage2(error) {
636
+ if (error instanceof Error) {
637
+ return error.message;
638
+ }
639
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
640
+ return error.message;
641
+ }
642
+ return "unknown error";
643
+ }
644
+ function isAdvisoryTopicPatch(value) {
645
+ const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
646
+ const keys = Object.keys(value);
647
+ return keys.length > 0 && keys.every((key) => advisoryKeys.has(key));
648
+ }
649
+ async function patchProjectWithTolerance(ctx, projectId, value) {
650
+ try {
651
+ await patchTopicProjectOverlay(ctx, projectId, value);
652
+ } catch (error) {
653
+ if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
654
+ throw error;
655
+ }
656
+ console.warn(
657
+ "[lucern graph-primitives] Non-fatal advisory topic patch failure",
658
+ {
659
+ projectId,
660
+ keys: Object.keys(value),
661
+ error: getErrorMessage2(error)
662
+ }
663
+ );
664
+ }
665
+ }
666
+ function defaultResolvers() {
667
+ return {
668
+ getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
669
+ idMode: "legacy",
670
+ projectLikeOnly: false
671
+ }),
672
+ patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
673
+ listTopics: (ctx) => listTopicProjectOverlays(ctx, {
674
+ idMode: "legacy"
675
+ }),
676
+ getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
677
+ };
678
+ }
679
+ var resolverOverrides = {};
680
+ function resolveGraphPrimitivesAppResolvers(_ctx) {
681
+ return {
682
+ ...defaultResolvers(),
683
+ ...resolverOverrides
684
+ };
685
+ }
686
+
687
+ // src/epistemicQuestions.helpers.ts
688
+ function generateContentHash(text) {
689
+ const content = `question:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
690
+ let hash = 5381;
691
+ for (let i = 0; i < content.length; i++) {
692
+ hash = (hash << 5) + hash + content.charCodeAt(i);
693
+ hash &= hash;
694
+ }
695
+ return Math.abs(hash).toString(16).padStart(8, "0");
696
+ }
697
+ function buildTestsEdgeGlobalId(fromGlobalId, toGlobalId) {
698
+ return `edge-${fromGlobalId}-tests-${toGlobalId}`;
699
+ }
700
+ async function markProjectGraphDirty(ctx, projectId, topicId) {
701
+ const normalizedProjectId = typeof projectId === "string" && projectId.trim().length > 0 ? projectId : void 0;
702
+ const normalizedTopicId = typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
703
+ if (!normalizedProjectId && !normalizedTopicId) {
704
+ return;
705
+ }
706
+ if (normalizedProjectId) {
707
+ await ctx.scheduler.runAfter(
708
+ 0,
709
+ internal.graphAnalysisCache.markCacheStaleInternal,
710
+ {
711
+ projectId: normalizedProjectId
712
+ }
713
+ );
714
+ }
715
+ if (normalizedTopicId) {
716
+ await ctx.scheduler.runAfter(
717
+ 0,
718
+ internal.graphAnalysisCache.markCacheStaleByTopic,
719
+ {
720
+ topicId: normalizedTopicId
721
+ }
722
+ );
723
+ }
724
+ await resolveGraphPrimitivesAppResolvers().patchProject(
725
+ ctx,
726
+ normalizedTopicId ?? normalizedProjectId,
727
+ {
728
+ lastActivityAt: Date.now()
729
+ }
730
+ );
731
+ }
732
+ function normalizeCategory(category) {
733
+ if (!category) {
734
+ return "other";
735
+ }
736
+ const lower = category.toLowerCase();
737
+ if (lower === "financial") {
738
+ return "financials";
739
+ }
740
+ const validCategories = [
741
+ "market",
742
+ "competition",
743
+ "product",
744
+ "team",
745
+ "financials",
746
+ "deal",
747
+ "risks"
748
+ ];
749
+ return validCategories.find((c) => lower.includes(c)) || "other";
750
+ }
751
+ function normalizeQuestionTopicId(topicId) {
752
+ return typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
753
+ }
754
+ function resolveQuestionScopeId(scope) {
755
+ return normalizeQuestionTopicId(scope.topicId) ?? scope.projectId ?? void 0;
756
+ }
757
+ function buildLinkedWorktreeMetadata(linkedWorktreeId) {
758
+ return linkedWorktreeId ? {
759
+ linkedWorktreeId
760
+ } : {};
761
+ }
762
+
763
+ // src/epistemicQuestions.create.ts
764
+ var create = mutation({
765
+ args: {
766
+ ...optionalScopeArgs,
767
+ question: v.string(),
768
+ category: v.optional(v.string()),
769
+ priority: v.optional(
770
+ v.union(v.literal("high"), v.literal("medium"), v.literal("low"))
771
+ ),
772
+ source: v.optional(v.string()),
773
+ userId: v.string(),
774
+ // Optional linking
775
+ linkedBeliefNodeId: v.optional(v.id("epistemicNodes")),
776
+ testType: v.optional(
777
+ v.union(
778
+ v.literal("validates"),
779
+ v.literal("invalidates"),
780
+ v.literal("clarifies")
781
+ )
782
+ ),
783
+ // Optional metadata
784
+ importance: v.optional(v.number()),
785
+ epistemicUnlock: v.optional(v.string()),
786
+ // === SOURCE QUESTIONS (for key question derivation) ===
787
+ // When a key question is synthesized from raw questions, track the source
788
+ sourceQuestionIds: v.optional(v.array(v.string())),
789
+ // globalIds of source questions
790
+ // === WORKTREE LINKAGE ===
791
+ linkedWorktreeId: v.optional(v.string()),
792
+ // === CLASSIFICATION FIELDS ===
793
+ questionType: v.optional(
794
+ v.union(
795
+ v.literal("validation"),
796
+ v.literal("falsification"),
797
+ v.literal("assumption_probe"),
798
+ v.literal("prediction_test"),
799
+ v.literal("counterfactual"),
800
+ v.literal("discovery"),
801
+ v.literal("clarification"),
802
+ v.literal("comparison"),
803
+ v.literal("causal"),
804
+ v.literal("mechanism"),
805
+ v.literal("general")
806
+ )
807
+ ),
808
+ questionPriority: v.optional(
809
+ v.union(
810
+ v.literal("critical"),
811
+ v.literal("high"),
812
+ v.literal("medium"),
813
+ v.literal("low")
814
+ )
815
+ )
816
+ },
817
+ returns: permissiveReturn,
818
+ handler: async (ctx, args) => {
819
+ const scope = await resolveTopicProjectScope(ctx, {
820
+ topicId: args.topicId,
821
+ projectId: args.projectId
822
+ });
823
+ assertWorkspaceScopedEpistemicNodeScope({
824
+ scope,
825
+ nodeType: "question",
826
+ mutationName: "epistemicQuestions.create"
827
+ });
828
+ if (scope.projectId) {
829
+ await checkProjectAccess(ctx, scope.projectId, args.userId);
830
+ }
831
+ const now = Date.now();
832
+ const globalId = generateGlobalId();
833
+ const contentHash = generateContentHash(args.question);
834
+ const category = normalizeCategory(args.category);
835
+ const nodeId = await ctx.db.insert("epistemicNodes", {
836
+ globalId,
837
+ topicId: scope.topicId,
838
+ projectId: scope.projectId,
839
+ tenantId: scope.tenantId,
840
+ workspaceId: scope.workspaceId,
841
+ nodeType: "question",
842
+ canonicalText: args.question,
843
+ contentHash,
844
+ status: "active",
845
+ epistemicLayer: "L3",
846
+ // L3: Traversal Anchors (Questions)
847
+ sourceType: args.source === "user" ? "human" : "ai_generated",
848
+ // Classification fields
849
+ questionType: args.questionType ?? "general",
850
+ questionPriority: args.questionPriority ?? "medium",
851
+ answerQuality: "unanswered",
852
+ // New questions start unanswered
853
+ createdAt: now,
854
+ updatedAt: now,
855
+ createdBy: args.userId,
856
+ metadata: {
857
+ category,
858
+ priority: args.priority || "medium",
859
+ source: args.source || "ai_suggested",
860
+ questionStatus: "open",
861
+ linkedBeliefNodeId: args.linkedBeliefNodeId,
862
+ ...buildLinkedWorktreeMetadata(args.linkedWorktreeId),
863
+ testType: args.testType,
864
+ importance: args.importance,
865
+ epistemicUnlock: args.epistemicUnlock,
866
+ // Track source questions for key question derivation
867
+ prerequisiteQuestions: args.sourceQuestionIds
868
+ }
869
+ });
870
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
871
+ nodeId,
872
+ operation: "upsert"
873
+ });
874
+ await scheduleEmbeddingGeneration({
875
+ ctx,
876
+ nodeId,
877
+ projectId: scope.projectId,
878
+ topicId: scope.topicId,
879
+ createdBy: args.userId,
880
+ nodeType: "question",
881
+ text: args.question
882
+ });
883
+ if (args.linkedBeliefNodeId) {
884
+ const beliefNode = await ctx.db.get(args.linkedBeliefNodeId);
885
+ if (beliefNode?.globalId) {
886
+ await ctx.scheduler.runAfter(100, internal.neo4jSync.syncNodeToNeo4j, {
887
+ nodeId: args.linkedBeliefNodeId,
888
+ operation: "upsert"
889
+ });
890
+ const edgeGlobalId = buildTestsEdgeGlobalId(
891
+ beliefNode.globalId,
892
+ globalId
893
+ );
894
+ await ctx.scheduler.runAfter(250, internal.neo4jEdgeAPI.createEdge, {
895
+ globalId: edgeGlobalId,
896
+ fromGlobalId: beliefNode.globalId,
897
+ toGlobalId: globalId,
898
+ edgeType: "tests",
899
+ weight: 1,
900
+ context: args.testType || "tests",
901
+ createdBy: args.userId,
902
+ topicId: scope.projectId ? String(scope.projectId) : void 0,
903
+ fromNodeType: "belief",
904
+ toNodeType: "question",
905
+ fromLayer: "L3",
906
+ toLayer: "L3"
907
+ });
908
+ }
909
+ }
910
+ await ctx.db.insert("epistemicAudit", {
911
+ entityType: "question",
912
+ entityId: nodeId,
913
+ changeType: "created",
914
+ changedAt: now,
915
+ changedBy: args.userId,
916
+ isAgent: false,
917
+ projectId: scope.projectId,
918
+ newState: {
919
+ question: args.question,
920
+ category,
921
+ priority: args.priority || "medium"
922
+ }
923
+ });
924
+ if (scope.projectId || scope.topicId) {
925
+ await ctx.scheduler.runAfter(
926
+ 0,
927
+ "embeddingActions:generateEpistemicNodeEmbedding",
928
+ {
929
+ nodeId,
930
+ projectId: scope.projectId,
931
+ topicId: scope.topicId ? String(scope.topicId) : void 0,
932
+ createdBy: args.userId,
933
+ nodeType: "question",
934
+ text: args.question,
935
+ hasAnswer: false
936
+ }
937
+ );
938
+ }
939
+ if (scope.projectId || scope.topicId) {
940
+ await ctx.scheduler.runAfter(
941
+ 2e3,
942
+ // 2 second delay
943
+ internal.nodeClassification.scheduleClassification,
944
+ {
945
+ nodeId,
946
+ nodeType: "question",
947
+ projectId: scope.projectId,
948
+ topicId: normalizeQuestionTopicId(scope.topicId)
949
+ }
950
+ );
951
+ }
952
+ await markProjectGraphDirty(
953
+ ctx,
954
+ scope.projectId,
955
+ normalizeQuestionTopicId(scope.topicId)
956
+ );
957
+ return { nodeId };
958
+ }
959
+ });
960
+ var createBatch = mutation({
961
+ args: {
962
+ ...optionalScopeArgs,
963
+ questions: v.array(
964
+ v.object({
965
+ question: v.string(),
966
+ category: v.optional(v.string()),
967
+ priority: v.optional(
968
+ v.union(v.literal("high"), v.literal("medium"), v.literal("low"))
969
+ ),
970
+ linkedBeliefNodeId: v.optional(v.id("epistemicNodes")),
971
+ testType: v.optional(
972
+ v.union(
973
+ v.literal("validates"),
974
+ v.literal("invalidates"),
975
+ v.literal("clarifies")
976
+ )
977
+ )
978
+ })
979
+ ),
980
+ source: v.optional(v.string()),
981
+ userId: v.string()
982
+ },
983
+ returns: permissiveReturn,
984
+ handler: async (ctx, args) => {
985
+ const scope = await resolveTopicProjectScope(ctx, {
986
+ topicId: args.topicId,
987
+ projectId: args.projectId
988
+ });
989
+ const scopeId = resolveQuestionScopeId(scope);
990
+ if (!scopeId) {
991
+ throw new Error("No scope identifier provided");
992
+ }
993
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
994
+ if (!hasAccess) {
995
+ throw new Error("Access denied");
996
+ }
997
+ const now = Date.now();
998
+ const nodeIds = [];
999
+ for (const q of args.questions) {
1000
+ const globalId = generateGlobalId();
1001
+ const contentHash = generateContentHash(q.question);
1002
+ const category = normalizeCategory(q.category);
1003
+ const nodeId = await ctx.db.insert("epistemicNodes", {
1004
+ globalId,
1005
+ topicId: scope.topicId,
1006
+ projectId: scope.projectId,
1007
+ tenantId: scope.tenantId,
1008
+ workspaceId: scope.workspaceId,
1009
+ nodeType: "question",
1010
+ canonicalText: q.question,
1011
+ contentHash,
1012
+ status: "active",
1013
+ epistemicLayer: "L3",
1014
+ sourceType: args.source === "user" ? "human" : "ai_generated",
1015
+ createdAt: now,
1016
+ updatedAt: now,
1017
+ createdBy: args.userId,
1018
+ metadata: {
1019
+ category,
1020
+ priority: q.priority || "medium",
1021
+ source: args.source || "ai_suggested",
1022
+ questionStatus: "open",
1023
+ linkedBeliefNodeId: q.linkedBeliefNodeId,
1024
+ testType: q.testType
1025
+ }
1026
+ });
1027
+ nodeIds.push(nodeId);
1028
+ await ctx.db.insert("epistemicAudit", {
1029
+ entityType: "question",
1030
+ entityId: String(nodeId),
1031
+ changeType: "created",
1032
+ changedAt: now,
1033
+ changedBy: args.userId,
1034
+ isAgent: false,
1035
+ projectId: scope.projectId,
1036
+ topicId: normalizeQuestionTopicId(scope.topicId),
1037
+ newState: {
1038
+ question: q.question,
1039
+ category,
1040
+ priority: q.priority || "medium",
1041
+ source: args.source || "ai_suggested",
1042
+ linkedBeliefNodeId: q.linkedBeliefNodeId,
1043
+ testType: q.testType
1044
+ },
1045
+ triggeringAction: "epistemicQuestions.createBatch"
1046
+ });
1047
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1048
+ nodeId,
1049
+ operation: "upsert"
1050
+ });
1051
+ if (q.linkedBeliefNodeId) {
1052
+ const beliefNode = await ctx.db.get(q.linkedBeliefNodeId);
1053
+ if (beliefNode) {
1054
+ await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
1055
+ globalId: crypto.randomUUID(),
1056
+ fromGlobalId: beliefNode.globalId,
1057
+ toGlobalId: globalId,
1058
+ edgeType: "tests",
1059
+ weight: 1,
1060
+ createdBy: args.userId,
1061
+ topicId: scope.projectId,
1062
+ fromNodeType: "belief",
1063
+ toNodeType: "question",
1064
+ fromLayer: "L3",
1065
+ toLayer: "L3"
1066
+ });
1067
+ }
1068
+ }
1069
+ }
1070
+ await markProjectGraphDirty(
1071
+ ctx,
1072
+ scope.projectId,
1073
+ normalizeQuestionTopicId(scope.topicId)
1074
+ );
1075
+ return { nodeIds, count: nodeIds.length };
1076
+ }
1077
+ });
1078
+ var internalCreate = internalMutation({
1079
+ args: {
1080
+ ...optionalScopeArgs,
1081
+ question: v.string(),
1082
+ category: v.optional(v.string()),
1083
+ priority: v.optional(v.string()),
1084
+ source: v.optional(v.string()),
1085
+ userId: v.string(),
1086
+ linkedBeliefNodeId: v.optional(v.id("epistemicNodes")),
1087
+ testType: v.optional(v.string()),
1088
+ runtimeToolName: v.optional(v.string()),
1089
+ runtimePackKey: v.optional(v.string()),
1090
+ runtimePackInstallScope: v.optional(
1091
+ v.union(v.literal("tenant"), v.literal("workspace"))
1092
+ )
1093
+ },
1094
+ returns: permissiveReturn,
1095
+ handler: async (ctx, args) => {
1096
+ const now = Date.now();
1097
+ const scope = await resolveTopicProjectScope(ctx, {
1098
+ topicId: args.topicId,
1099
+ projectId: args.projectId
1100
+ });
1101
+ assertWorkspaceScopedEpistemicNodeScope({
1102
+ scope,
1103
+ nodeType: "question",
1104
+ mutationName: "epistemicQuestions.internalCreate"
1105
+ });
1106
+ assertTenantPackWorkspaceMutationAllowed({
1107
+ runtime: resolveRuntimePackMutationContext(args),
1108
+ target: {
1109
+ tenantId: scope.tenantId,
1110
+ workspaceId: scope.workspaceId,
1111
+ nodeType: "question",
1112
+ epistemicLayer: "L3"
1113
+ },
1114
+ mutationName: "epistemicQuestions.internalCreate"
1115
+ });
1116
+ const globalId = generateGlobalId();
1117
+ const contentHash = generateContentHash(args.question);
1118
+ const category = normalizeCategory(args.category);
1119
+ const nodeId = await ctx.db.insert("epistemicNodes", {
1120
+ globalId,
1121
+ topicId: scope.topicId,
1122
+ projectId: scope.projectId,
1123
+ tenantId: scope.tenantId,
1124
+ workspaceId: scope.workspaceId,
1125
+ nodeType: "question",
1126
+ canonicalText: args.question,
1127
+ contentHash,
1128
+ status: "active",
1129
+ epistemicLayer: "L3",
1130
+ sourceType: args.source === "user" ? "human" : "ai_generated",
1131
+ createdAt: now,
1132
+ updatedAt: now,
1133
+ createdBy: args.userId,
1134
+ metadata: {
1135
+ category,
1136
+ priority: args.priority || "medium",
1137
+ source: args.source || "ai_suggested",
1138
+ questionStatus: "open",
1139
+ linkedBeliefNodeId: args.linkedBeliefNodeId,
1140
+ testType: args.testType
1141
+ }
1142
+ });
1143
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1144
+ nodeId,
1145
+ operation: "upsert"
1146
+ });
1147
+ if (args.linkedBeliefNodeId) {
1148
+ const beliefNode = await ctx.db.get(args.linkedBeliefNodeId);
1149
+ if (beliefNode) {
1150
+ await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
1151
+ globalId: crypto.randomUUID(),
1152
+ fromGlobalId: beliefNode.globalId,
1153
+ toGlobalId: globalId,
1154
+ edgeType: "tests",
1155
+ weight: 1,
1156
+ createdBy: args.userId,
1157
+ topicId: scope.projectId ? String(scope.projectId) : void 0,
1158
+ fromNodeType: "belief",
1159
+ toNodeType: "question",
1160
+ fromLayer: "L3",
1161
+ toLayer: "L3"
1162
+ });
1163
+ }
1164
+ }
1165
+ if (scope.projectId || scope.topicId) {
1166
+ await ctx.scheduler.runAfter(
1167
+ 0,
1168
+ "embeddingActions:generateEpistemicNodeEmbedding",
1169
+ {
1170
+ nodeId,
1171
+ projectId: scope.projectId,
1172
+ topicId: scope.topicId ? String(scope.topicId) : void 0,
1173
+ createdBy: args.userId,
1174
+ nodeType: "question",
1175
+ text: args.question,
1176
+ hasAnswer: false
1177
+ }
1178
+ );
1179
+ }
1180
+ await markProjectGraphDirty(
1181
+ ctx,
1182
+ scope.projectId,
1183
+ normalizeQuestionTopicId(scope.topicId)
1184
+ );
1185
+ return { nodeId };
1186
+ }
1187
+ });
1188
+ var addQuestion = mutation({
1189
+ args: {
1190
+ ...optionalScopeArgs,
1191
+ question: v.string(),
1192
+ category: v.optional(v.string()),
1193
+ priority: v.optional(v.string()),
1194
+ source: v.optional(v.string()),
1195
+ userId: v.string(),
1196
+ beliefId: v.optional(v.string()),
1197
+ chatId: v.optional(v.string()),
1198
+ importance: v.optional(v.number()),
1199
+ epistemicUnlock: v.optional(v.string()),
1200
+ metadata: v.optional(v.any()),
1201
+ questionType: v.optional(v.string())
1202
+ },
1203
+ returns: permissiveReturn,
1204
+ handler: async (ctx, args) => {
1205
+ const scope = await resolveTopicProjectScope(ctx, {
1206
+ topicId: args.topicId,
1207
+ projectId: args.projectId
1208
+ });
1209
+ const scopeId = resolveQuestionScopeId(scope);
1210
+ if (!scopeId) {
1211
+ throw new Error("No scope identifier provided");
1212
+ }
1213
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
1214
+ if (!hasAccess) {
1215
+ throw new Error("Access denied");
1216
+ }
1217
+ const now = Date.now();
1218
+ const globalId = generateGlobalId();
1219
+ const contentHash = generateContentHash(args.question);
1220
+ const category = normalizeCategory(args.category);
1221
+ const additionalMetadata = args.metadata && typeof args.metadata === "object" ? args.metadata : {};
1222
+ const nodeId = await ctx.db.insert("epistemicNodes", {
1223
+ globalId,
1224
+ topicId: scope.topicId,
1225
+ projectId: scope.projectId,
1226
+ tenantId: scope.tenantId,
1227
+ workspaceId: scope.workspaceId,
1228
+ nodeType: "question",
1229
+ canonicalText: args.question,
1230
+ contentHash,
1231
+ status: "active",
1232
+ epistemicLayer: "L3",
1233
+ sourceType: args.source === "user" ? "human" : "ai_generated",
1234
+ createdAt: now,
1235
+ updatedAt: now,
1236
+ createdBy: args.userId,
1237
+ metadata: {
1238
+ ...additionalMetadata,
1239
+ category,
1240
+ priority: args.priority || "medium",
1241
+ source: args.source || "ai_suggested",
1242
+ questionStatus: "open",
1243
+ beliefId: args.beliefId,
1244
+ linkedBeliefId: args.beliefId,
1245
+ chatId: args.chatId,
1246
+ importance: args.importance,
1247
+ epistemicUnlock: args.epistemicUnlock
1248
+ }
1249
+ });
1250
+ await ctx.db.insert("epistemicAudit", {
1251
+ entityType: "question",
1252
+ entityId: String(nodeId),
1253
+ changeType: "created",
1254
+ changedAt: now,
1255
+ changedBy: args.userId,
1256
+ isAgent: false,
1257
+ projectId: scope.projectId,
1258
+ topicId: normalizeQuestionTopicId(scope.topicId),
1259
+ newState: {
1260
+ ...additionalMetadata,
1261
+ question: args.question,
1262
+ category,
1263
+ priority: args.priority || "medium",
1264
+ source: args.source || "ai_suggested",
1265
+ beliefId: args.beliefId,
1266
+ chatId: args.chatId,
1267
+ importance: args.importance,
1268
+ epistemicUnlock: args.epistemicUnlock
1269
+ },
1270
+ triggeringAction: "epistemicQuestions.addQuestion"
1271
+ });
1272
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1273
+ nodeId,
1274
+ operation: "upsert"
1275
+ });
1276
+ await ctx.scheduler.runAfter(
1277
+ 0,
1278
+ "embeddingActions:generateEpistemicNodeEmbedding",
1279
+ {
1280
+ nodeId,
1281
+ projectId: scope.projectId,
1282
+ topicId: normalizeQuestionTopicId(scope.topicId),
1283
+ createdBy: args.userId,
1284
+ nodeType: "question",
1285
+ text: args.question,
1286
+ hasAnswer: false
1287
+ }
1288
+ );
1289
+ await markProjectGraphDirty(
1290
+ ctx,
1291
+ scope.projectId,
1292
+ normalizeQuestionTopicId(scope.topicId)
1293
+ );
1294
+ return nodeId;
1295
+ }
1296
+ });
1297
+
1298
+ export { addQuestion, create, createBatch, internalCreate };
1299
+ //# sourceMappingURL=epistemicQuestions.create.js.map
1300
+ //# sourceMappingURL=epistemicQuestions.create.js.map