@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,1345 @@
1
+ import { v } from 'convex/values';
2
+ import { checkScopeAccess } from '@lucern/access-control/access';
3
+ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
+ import { componentsGeneric, anyApi, queryGeneric, mutationGeneric } from 'convex/server';
5
+ import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
6
+
7
+ // src/epistemicQuestions.tail.ts
8
+ var api = anyApi;
9
+ componentsGeneric();
10
+ var internal = anyApi;
11
+ var mutation = mutationGeneric;
12
+ var query = queryGeneric;
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 formatGraphPrimitiveError(error) {
20
+ if (error instanceof Error) {
21
+ return `${error.name}: ${error.message}`;
22
+ }
23
+ if (typeof error === "string") {
24
+ return error;
25
+ }
26
+ if (error === null) {
27
+ return "null";
28
+ }
29
+ if (error === void 0) {
30
+ return "undefined";
31
+ }
32
+ try {
33
+ return JSON.stringify(error);
34
+ } catch {
35
+ return Object.prototype.toString.call(error);
36
+ }
37
+ }
38
+ function debugGraphPrimitiveFallback(message, context) {
39
+ if (!isGraphPrimitiveDebugEnabled()) {
40
+ return;
41
+ }
42
+ console.debug(message, context ?? {});
43
+ }
44
+
45
+ // src/globalId.ts
46
+ function generateGlobalId() {
47
+ const bytes = new Uint8Array(16);
48
+ crypto.getRandomValues(bytes);
49
+ bytes[6] = bytes[6] & 15 | 64;
50
+ bytes[8] = bytes[8] & 63 | 128;
51
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
52
+ ""
53
+ );
54
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
55
+ }
56
+
57
+ // src/topicProjectOverlay.ts
58
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
59
+ function readNonEmptyString(value) {
60
+ if (typeof value !== "string") {
61
+ return;
62
+ }
63
+ const normalized = value.trim();
64
+ return normalized.length > 0 ? normalized : void 0;
65
+ }
66
+ function readStringArray(value) {
67
+ if (!Array.isArray(value)) {
68
+ return [];
69
+ }
70
+ return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
71
+ }
72
+ function readMetadata(topic) {
73
+ return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
74
+ }
75
+ function readLegacyProjectId(value) {
76
+ if (!value) {
77
+ return;
78
+ }
79
+ return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
80
+ }
81
+ function coerceVisibility(value) {
82
+ return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
83
+ }
84
+ function coerceStatus(value) {
85
+ return value === "active" || value === "archived" || value === "watching" ? value : void 0;
86
+ }
87
+ function mapProjectType(topic, metadata) {
88
+ const explicit = readNonEmptyString(metadata.projectType);
89
+ if (explicit) {
90
+ return explicit;
91
+ }
92
+ if (topic.type === "theme") {
93
+ return "thematic";
94
+ }
95
+ return readNonEmptyString(topic.type) || "general";
96
+ }
97
+ function isProjectLikeTopic(topic) {
98
+ const metadata = readMetadata(topic);
99
+ return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
100
+ }
101
+ function isMissingLucernChildComponentError(error) {
102
+ const message = getErrorMessage(error);
103
+ return message.includes(
104
+ 'Child component ComponentName(Identifier("lucern")) not found'
105
+ ) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
106
+ }
107
+ function getErrorMessage(error) {
108
+ if (error instanceof Error) {
109
+ return error.message;
110
+ }
111
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
112
+ return error.message;
113
+ }
114
+ return "unknown error";
115
+ }
116
+ async function resolveTopicDoc(ctx, scopeId) {
117
+ if (ctx?.db && typeof ctx.db.get === "function") {
118
+ try {
119
+ const directTopic = await ctx.db.get(
120
+ scopeId
121
+ );
122
+ if (directTopic) {
123
+ return directTopic;
124
+ }
125
+ } catch (error) {
126
+ debugGraphPrimitiveFallback(
127
+ "[topicProjectOverlay] Failed to resolve topic by direct ID",
128
+ {
129
+ error,
130
+ scopeId
131
+ }
132
+ );
133
+ }
134
+ }
135
+ if (typeof ctx.runQuery !== "function") {
136
+ return null;
137
+ }
138
+ try {
139
+ const topic = await ctx.runQuery(api.topics.get, {
140
+ id: String(scopeId)
141
+ });
142
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
143
+ return topic;
144
+ }
145
+ } catch (error) {
146
+ debugGraphPrimitiveFallback(
147
+ "[topicProjectOverlay] Failed to resolve topic by ID query",
148
+ {
149
+ error,
150
+ scopeId
151
+ }
152
+ );
153
+ }
154
+ try {
155
+ const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
156
+ projectId: String(scopeId)
157
+ });
158
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
159
+ return topic;
160
+ }
161
+ } catch (error) {
162
+ debugGraphPrimitiveFallback(
163
+ "[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
164
+ { error, scopeId }
165
+ );
166
+ }
167
+ return null;
168
+ }
169
+ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
170
+ const metadata = readMetadata(topic);
171
+ const topicId = String(topic._id);
172
+ const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
173
+ const storageProjectId = legacyProjectId || topicId;
174
+ const outwardId = idMode === "topic" ? topicId : storageProjectId;
175
+ const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
176
+ const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
177
+ const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
178
+ const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
179
+ return {
180
+ ...metadata,
181
+ _id: outwardId,
182
+ projectId: outwardId,
183
+ topicId,
184
+ storageProjectId,
185
+ legacyProjectId,
186
+ name: readNonEmptyString(topic.name) || "Untitled Theme",
187
+ type: mapProjectType(topic, metadata),
188
+ description: readNonEmptyString(topic.description),
189
+ ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
190
+ sharedWith: readStringArray(metadata.sharedWith),
191
+ visibility,
192
+ tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
193
+ workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
194
+ status,
195
+ tags: readStringArray(metadata.tags),
196
+ chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
197
+ artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
198
+ lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
199
+ _creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
200
+ createdAt,
201
+ updatedAt
202
+ };
203
+ }
204
+ async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
205
+ const topic = await resolveTopicDoc(ctx, scopeId);
206
+ if (!topic) {
207
+ return null;
208
+ }
209
+ if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
210
+ return null;
211
+ }
212
+ return materializeTopicProjectOverlay(topic, options.idMode);
213
+ }
214
+ async function listTopicProjectOverlays(ctx, options = {}) {
215
+ let allTopics = [];
216
+ if (ctx?.db?.query && typeof ctx.db.query === "function") {
217
+ try {
218
+ allTopics = await ctx.db.query("topics").collect();
219
+ } catch (error) {
220
+ debugGraphPrimitiveFallback(
221
+ "[topicProjectOverlay] Failed to read topics table; falling back to API",
222
+ { error }
223
+ );
224
+ allTopics = [];
225
+ }
226
+ }
227
+ if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
228
+ allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
229
+ }
230
+ return allTopics.filter(
231
+ (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
232
+ ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
233
+ }
234
+ async function patchTopicProjectOverlay(ctx, scopeId, value) {
235
+ const topic = await resolveTopicDoc(ctx, scopeId);
236
+ if (!topic) {
237
+ return null;
238
+ }
239
+ const nextMetadata = { ...readMetadata(topic) };
240
+ const patch = {};
241
+ const topicUpdateArgs = {
242
+ id: String(topic._id)
243
+ };
244
+ for (const [key, rawValue] of Object.entries(value)) {
245
+ switch (key) {
246
+ case "_id":
247
+ case "projectId":
248
+ case "topicId":
249
+ case "legacyProjectId":
250
+ case "storageProjectId":
251
+ break;
252
+ case "name":
253
+ case "description":
254
+ patch[key] = rawValue;
255
+ topicUpdateArgs[key] = rawValue;
256
+ break;
257
+ case "tenantId":
258
+ case "workspaceId":
259
+ case "ownerId":
260
+ throw new Error(
261
+ `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
262
+ );
263
+ case "status": {
264
+ const status = coerceStatus(rawValue);
265
+ if (status) {
266
+ patch.status = status;
267
+ topicUpdateArgs.status = status;
268
+ }
269
+ break;
270
+ }
271
+ case "visibility": {
272
+ const visibility = coerceVisibility(rawValue);
273
+ if (visibility) {
274
+ patch.visibility = visibility;
275
+ topicUpdateArgs.visibility = visibility;
276
+ }
277
+ break;
278
+ }
279
+ case "type": {
280
+ const projectType = readNonEmptyString(rawValue);
281
+ if (projectType) {
282
+ nextMetadata.projectType = projectType;
283
+ } else {
284
+ delete nextMetadata.projectType;
285
+ }
286
+ break;
287
+ }
288
+ case "updatedAt":
289
+ case "createdAt":
290
+ break;
291
+ default:
292
+ if (rawValue === void 0) {
293
+ delete nextMetadata[key];
294
+ } else {
295
+ nextMetadata[key] = rawValue;
296
+ }
297
+ }
298
+ }
299
+ patch.updatedAt = Date.now();
300
+ patch.metadata = nextMetadata;
301
+ topicUpdateArgs.metadata = nextMetadata;
302
+ if (typeof ctx.runMutation === "function") {
303
+ try {
304
+ await ctx.runMutation(api.topics.update, topicUpdateArgs);
305
+ } catch (error) {
306
+ if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
307
+ throw error;
308
+ }
309
+ await ctx.db.patch(String(topic._id), patch);
310
+ }
311
+ } else if (ctx?.db && typeof ctx.db.patch === "function") {
312
+ await ctx.db.patch(String(topic._id), patch);
313
+ } else {
314
+ throw new Error(
315
+ "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
316
+ );
317
+ }
318
+ return materializeTopicProjectOverlay({
319
+ ...topic,
320
+ ...patch,
321
+ metadata: nextMetadata
322
+ });
323
+ }
324
+
325
+ // src/resolvers.ts
326
+ function isMissingLucernChildComponentError2(error) {
327
+ const message = getErrorMessage2(error);
328
+ return message.includes(
329
+ 'Child component ComponentName(Identifier("lucern")) not found'
330
+ ) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
331
+ }
332
+ function getErrorMessage2(error) {
333
+ if (error instanceof Error) {
334
+ return error.message;
335
+ }
336
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
337
+ return error.message;
338
+ }
339
+ return "unknown error";
340
+ }
341
+ function isAdvisoryTopicPatch(value) {
342
+ const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
343
+ const keys = Object.keys(value);
344
+ return keys.length > 0 && keys.every((key) => advisoryKeys.has(key));
345
+ }
346
+ async function patchProjectWithTolerance(ctx, projectId, value) {
347
+ try {
348
+ await patchTopicProjectOverlay(ctx, projectId, value);
349
+ } catch (error) {
350
+ if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
351
+ throw error;
352
+ }
353
+ console.warn(
354
+ "[lucern graph-primitives] Non-fatal advisory topic patch failure",
355
+ {
356
+ projectId,
357
+ keys: Object.keys(value),
358
+ error: getErrorMessage2(error)
359
+ }
360
+ );
361
+ }
362
+ }
363
+ function defaultResolvers() {
364
+ return {
365
+ getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
366
+ idMode: "legacy",
367
+ projectLikeOnly: false
368
+ }),
369
+ patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
370
+ listTopics: (ctx) => listTopicProjectOverlays(ctx, {
371
+ idMode: "legacy"
372
+ }),
373
+ getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
374
+ };
375
+ }
376
+ var resolverOverrides = {};
377
+ function resolveGraphPrimitivesAppResolvers(_ctx) {
378
+ return {
379
+ ...defaultResolvers(),
380
+ ...resolverOverrides
381
+ };
382
+ }
383
+ var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
384
+ function asMappedProjectId(topic) {
385
+ if (!topic) {
386
+ return;
387
+ }
388
+ const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
389
+ if (directLegacyProjectId) {
390
+ return directLegacyProjectId;
391
+ }
392
+ const metadata = topic.metadata || {};
393
+ const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
394
+ return candidate ? candidate : void 0;
395
+ }
396
+ function normalizeScopeValue(value) {
397
+ if (typeof value !== "string") {
398
+ return;
399
+ }
400
+ const normalized = value.trim();
401
+ return normalized.length > 0 ? normalized : void 0;
402
+ }
403
+ function pickPrimaryTopic(candidates) {
404
+ return [...candidates].sort((a, b) => {
405
+ const depthA = a.depth ?? 9999;
406
+ const depthB = b.depth ?? 9999;
407
+ if (depthA !== depthB) {
408
+ return depthA - depthB;
409
+ }
410
+ const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
411
+ const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
412
+ if (createdA !== createdB) {
413
+ return createdA - createdB;
414
+ }
415
+ return String(a.name || "").localeCompare(String(b.name || ""));
416
+ })[0];
417
+ }
418
+ async function findTopicsByScopeAlias(ctx, scopeId) {
419
+ try {
420
+ return await ctx.db.query("topics").withIndex(
421
+ "by_graph_scope_project",
422
+ (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
423
+ ).collect();
424
+ } catch (error) {
425
+ debugGraphPrimitiveFallback(
426
+ "[topicScope] Failed to resolve scope alias via index",
427
+ {
428
+ error,
429
+ scopeId
430
+ }
431
+ );
432
+ const topics = await ctx.db.query("topics").collect();
433
+ return topics.filter((topic) => {
434
+ const normalizedGlobalId = normalizeScopeValue(topic.globalId);
435
+ const mappedProjectId = asMappedProjectId(topic);
436
+ return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
437
+ });
438
+ }
439
+ }
440
+ async function tryResolveHostTopicById(ctx, topicId) {
441
+ if (typeof ctx.runQuery !== "function") {
442
+ return null;
443
+ }
444
+ try {
445
+ return await ctx.runQuery(api.topics.get, {
446
+ id: topicId
447
+ }) ?? null;
448
+ } catch (error) {
449
+ debugGraphPrimitiveFallback(
450
+ "[topicScope] Failed to resolve topic by host query",
451
+ {
452
+ error,
453
+ topicId
454
+ }
455
+ );
456
+ return null;
457
+ }
458
+ }
459
+ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
460
+ if (typeof ctx.runQuery !== "function") {
461
+ return null;
462
+ }
463
+ try {
464
+ return await ctx.runQuery(api.topics.getByLegacyScopeId, {
465
+ projectId: legacyScopeId
466
+ }) ?? null;
467
+ } catch (error) {
468
+ debugGraphPrimitiveFallback(
469
+ "[topicScope] Failed to resolve topic by legacy scope",
470
+ {
471
+ error,
472
+ legacyScopeId
473
+ }
474
+ );
475
+ return null;
476
+ }
477
+ }
478
+ async function resolveInheritedWorkspaceScope(ctx, topic) {
479
+ const MAX_DEPTH = 10;
480
+ let tenantId = normalizeScopeValue(topic.tenantId);
481
+ let workspaceId = normalizeScopeValue(topic.workspaceId);
482
+ if (tenantId && workspaceId) {
483
+ return { tenantId, workspaceId };
484
+ }
485
+ let current = topic;
486
+ for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
487
+ current = await ctx.db.get(current.parentTopicId);
488
+ if (!current) break;
489
+ if (!tenantId) {
490
+ tenantId = normalizeScopeValue(current.tenantId);
491
+ }
492
+ if (!workspaceId) {
493
+ workspaceId = normalizeScopeValue(current.workspaceId);
494
+ }
495
+ if (tenantId && workspaceId) break;
496
+ }
497
+ return { tenantId, workspaceId };
498
+ }
499
+ async function resolveTopicProjectScope(ctx, args) {
500
+ if (args.topicId) {
501
+ let topic = null;
502
+ try {
503
+ topic = await ctx.db.get(
504
+ args.topicId
505
+ );
506
+ } catch (error) {
507
+ debugGraphPrimitiveFallback(
508
+ "[topicScope] Failed to load topic by direct id",
509
+ {
510
+ error,
511
+ topicId: args.topicId
512
+ }
513
+ );
514
+ }
515
+ if (!topic) {
516
+ topic = await tryResolveHostTopicById(ctx, String(args.topicId));
517
+ }
518
+ if (!topic) {
519
+ topic = pickPrimaryTopic(
520
+ await findTopicsByScopeAlias(ctx, String(args.topicId))
521
+ ) ?? null;
522
+ }
523
+ if (!topic) {
524
+ throw new Error(`Topic not found: ${String(args.topicId)}`);
525
+ }
526
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
527
+ const mapped = asMappedProjectId(topic);
528
+ if (mapped) {
529
+ return {
530
+ topicId: topic._id,
531
+ projectId: mapped,
532
+ tenantId: inherited.tenantId,
533
+ workspaceId: inherited.workspaceId,
534
+ source: "topic"
535
+ };
536
+ }
537
+ return {
538
+ topicId: topic._id,
539
+ tenantId: inherited.tenantId,
540
+ workspaceId: inherited.workspaceId,
541
+ source: "topic"
542
+ };
543
+ }
544
+ if (args.projectId) {
545
+ let directTopic = null;
546
+ try {
547
+ directTopic = await ctx.db.get(
548
+ args.projectId
549
+ );
550
+ } catch (error) {
551
+ debugGraphPrimitiveFallback(
552
+ "[topicScope] Failed to load direct project topic",
553
+ {
554
+ error,
555
+ projectId: args.projectId
556
+ }
557
+ );
558
+ }
559
+ if (directTopic) {
560
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
561
+ const mapped = asMappedProjectId(directTopic);
562
+ return {
563
+ topicId: directTopic._id,
564
+ projectId: mapped ?? args.projectId,
565
+ tenantId: inherited.tenantId,
566
+ workspaceId: inherited.workspaceId,
567
+ source: "topic_inferred"
568
+ };
569
+ }
570
+ directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
571
+ if (directTopic) {
572
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
573
+ const mapped = asMappedProjectId(directTopic);
574
+ return {
575
+ topicId: directTopic._id,
576
+ projectId: mapped ?? args.projectId,
577
+ tenantId: inherited.tenantId,
578
+ workspaceId: inherited.workspaceId,
579
+ source: "topic_inferred"
580
+ };
581
+ }
582
+ const topics = await findTopicsByScopeAlias(ctx, args.projectId);
583
+ const primary = pickPrimaryTopic(topics);
584
+ if (primary) {
585
+ const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
586
+ return {
587
+ topicId: primary._id,
588
+ projectId: args.projectId,
589
+ tenantId: inherited.tenantId,
590
+ workspaceId: inherited.workspaceId,
591
+ source: "project_mapped_topic"
592
+ };
593
+ }
594
+ throw new Error(
595
+ `Legacy project scope ${String(args.projectId)} has no mapped topic.`
596
+ );
597
+ }
598
+ throw new Error(
599
+ "Missing scope: provide topicId (preferred) or legacy projectId alias."
600
+ );
601
+ }
602
+ var optionalScopeArgs = {
603
+ projectId: v.optional(v.string()),
604
+ topicId: v.optional(v.string())
605
+ };
606
+ function normalizeScopeValue2(value) {
607
+ if (typeof value !== "string") {
608
+ return;
609
+ }
610
+ const normalized = value.trim();
611
+ return normalized.length > 0 ? normalized : void 0;
612
+ }
613
+ function throwWorkspaceIsolationError(args) {
614
+ const error = new Error(args.message);
615
+ error.status = 409;
616
+ error.code = "INVARIANT_VIOLATION";
617
+ error.invariantCode = args.invariantCode;
618
+ error.suggestion = args.suggestion;
619
+ error.details = args.details;
620
+ throw error;
621
+ }
622
+ async function resolveNodeScopeForWorkspaceIsolation(ctx, node) {
623
+ const epistemicLayer = typeof node?.epistemicLayer === "string" ? node.epistemicLayer : void 0;
624
+ const resolved = {
625
+ tenantId: normalizeScopeValue2(node?.tenantId),
626
+ workspaceId: normalizeScopeValue2(node?.workspaceId),
627
+ epistemicLayer,
628
+ nodeType: typeof node?.nodeType === "string" ? node.nodeType : void 0
629
+ };
630
+ if (!node) {
631
+ return resolved;
632
+ }
633
+ if (resolved.epistemicLayer === "ontological") {
634
+ return resolved;
635
+ }
636
+ if (resolved.tenantId || resolved.workspaceId) {
637
+ return resolved;
638
+ }
639
+ if (node.topicId) {
640
+ const topicScope = await resolveTopicProjectScope(ctx, {
641
+ topicId: node.topicId
642
+ });
643
+ return {
644
+ ...resolved,
645
+ tenantId: topicScope.tenantId,
646
+ workspaceId: topicScope.workspaceId
647
+ };
648
+ }
649
+ if (node.projectId) {
650
+ const topicScope = await resolveTopicProjectScope(ctx, {
651
+ projectId: String(node.projectId)
652
+ });
653
+ return {
654
+ ...resolved,
655
+ tenantId: topicScope.tenantId,
656
+ workspaceId: topicScope.workspaceId
657
+ };
658
+ }
659
+ return resolved;
660
+ }
661
+ function resolveRuntimePackMutationContext(args) {
662
+ if (!args.runtimeToolName && !args.runtimePackKey && !args.runtimePackInstallScope) {
663
+ return;
664
+ }
665
+ return {
666
+ toolName: args.runtimeToolName,
667
+ packKey: args.runtimePackKey,
668
+ packInstallScope: args.runtimePackInstallScope
669
+ };
670
+ }
671
+ function assertTenantPackWorkspaceMutationAllowed(args) {
672
+ if (!args.runtime?.packKey || args.runtime.packInstallScope !== "tenant") {
673
+ return;
674
+ }
675
+ const targetWorkspaceId = normalizeScopeValue2(args.target.workspaceId);
676
+ const targetLayer = typeof args.target.epistemicLayer === "string" ? args.target.epistemicLayer : void 0;
677
+ if (!targetWorkspaceId || targetLayer === "ontological") {
678
+ return;
679
+ }
680
+ throwWorkspaceIsolationError({
681
+ message: `Tenant-scoped pack "${args.runtime.packKey}" cannot mutate workspace-scoped reasoning state.`,
682
+ invariantCode: "workspace.tenant_pack_reasoning_write_forbidden",
683
+ suggestion: "Use a workspace-scoped pack for workspace-local graph mutations, or route the change through tenant-global canonical entity flows.",
684
+ details: {
685
+ mutationName: args.mutationName,
686
+ toolName: args.runtime.toolName,
687
+ packKey: args.runtime.packKey,
688
+ targetWorkspaceId,
689
+ targetNodeType: args.target.nodeType,
690
+ targetLayer
691
+ }
692
+ });
693
+ }
694
+
695
+ // src/epistemicQuestions.helpers.ts
696
+ function generateContentHash(text) {
697
+ const content = `question:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
698
+ let hash = 5381;
699
+ for (let i = 0; i < content.length; i++) {
700
+ hash = (hash << 5) + hash + content.charCodeAt(i);
701
+ hash &= hash;
702
+ }
703
+ return Math.abs(hash).toString(16).padStart(8, "0");
704
+ }
705
+ async function markProjectGraphDirty(ctx, projectId, topicId) {
706
+ const normalizedProjectId = typeof projectId === "string" && projectId.trim().length > 0 ? projectId : void 0;
707
+ const normalizedTopicId = typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
708
+ if (!normalizedProjectId && !normalizedTopicId) {
709
+ return;
710
+ }
711
+ if (normalizedProjectId) {
712
+ await ctx.scheduler.runAfter(
713
+ 0,
714
+ internal.graphAnalysisCache.markCacheStaleInternal,
715
+ {
716
+ projectId: normalizedProjectId
717
+ }
718
+ );
719
+ }
720
+ if (normalizedTopicId) {
721
+ await ctx.scheduler.runAfter(
722
+ 0,
723
+ internal.graphAnalysisCache.markCacheStaleByTopic,
724
+ {
725
+ topicId: normalizedTopicId
726
+ }
727
+ );
728
+ }
729
+ await resolveGraphPrimitivesAppResolvers().patchProject(
730
+ ctx,
731
+ normalizedTopicId ?? normalizedProjectId,
732
+ {
733
+ lastActivityAt: Date.now()
734
+ }
735
+ );
736
+ }
737
+ function dedupeQuestionNodes(nodes) {
738
+ const seen = /* @__PURE__ */ new Set();
739
+ const deduped = [];
740
+ for (const node of nodes) {
741
+ const id = String(node._id);
742
+ if (seen.has(id)) {
743
+ continue;
744
+ }
745
+ seen.add(id);
746
+ deduped.push(node);
747
+ }
748
+ return deduped;
749
+ }
750
+ function normalizeQuestionTopicId(topicId) {
751
+ return typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
752
+ }
753
+ function resolveQuestionScopeId(scope) {
754
+ return normalizeQuestionTopicId(scope.topicId) ?? scope.projectId ?? void 0;
755
+ }
756
+ async function resolveQuestionScopeOrNull(ctx, args) {
757
+ if (!args.projectId && !args.topicId) {
758
+ return null;
759
+ }
760
+ try {
761
+ return await resolveTopicProjectScope(ctx, {
762
+ projectId: args.projectId ?? void 0,
763
+ topicId: args.topicId ?? void 0
764
+ });
765
+ } catch (error) {
766
+ debugGraphPrimitiveFallback(
767
+ "[epistemicQuestions] Failed to resolve question scope",
768
+ {
769
+ error: formatGraphPrimitiveError(error),
770
+ projectId: args.projectId,
771
+ topicId: args.topicId
772
+ }
773
+ );
774
+ return null;
775
+ }
776
+ }
777
+ async function getQuestionNodesForScope(ctx, scope, args) {
778
+ const fetchNodes = (query2) => query2.collect();
779
+ const topicNodes = await fetchNodes(
780
+ ctx.db.query("epistemicNodes").withIndex(
781
+ "by_topic_type",
782
+ (q) => q.eq("topicId", scope.topicId).eq("nodeType", "question")
783
+ )
784
+ );
785
+ return dedupeQuestionNodes(topicNodes).filter(
786
+ (node) => questionMatchesScope(node, scope)
787
+ );
788
+ }
789
+ function questionMatchesScope(node, scope) {
790
+ return scope.topicId !== void 0 && node.topicId === scope.topicId || scope.projectId !== void 0 && node.projectId === scope.projectId;
791
+ }
792
+ function resolveLinkedWorktreeId(meta) {
793
+ const linkedWorktreeId = meta.linkedWorktreeId;
794
+ return typeof linkedWorktreeId === "string" && linkedWorktreeId.trim() ? linkedWorktreeId : null;
795
+ }
796
+ function flattenQuestionNode(n) {
797
+ const meta = n.metadata || {};
798
+ const linkedWorktreeId = resolveLinkedWorktreeId(meta);
799
+ return {
800
+ ...n,
801
+ question: n.canonicalText || "",
802
+ category: meta.category || "other",
803
+ priority: meta.priority || "medium",
804
+ status: meta.questionStatus || meta.status || "open",
805
+ beliefId: meta.linkedBeliefNodeId || meta.beliefId || null,
806
+ relatedBeliefIds: meta.relatedBeliefIds || [],
807
+ relatedInsightIds: meta.relatedInsightIds || [],
808
+ linkedWorktreeId,
809
+ sprintIndex: meta.sprintIndex || void 0,
810
+ linkedBeliefId: meta.linkedBeliefNodeId || null,
811
+ testType: meta.testType || "validates",
812
+ importance: meta.importance || 5,
813
+ isKeyQuestion: meta.isKeyQuestion || false,
814
+ answer: meta.answer || null,
815
+ convictionStage: meta.convictionStage || null,
816
+ conviction: meta.conviction ?? null,
817
+ source: meta.source || "ai_suggested"
818
+ };
819
+ }
820
+ function flattenInternalQuestionNode(n) {
821
+ const meta = n.metadata || {};
822
+ const flattened = flattenQuestionNode(n);
823
+ return {
824
+ ...flattened,
825
+ _epistemicNodeId: n._id,
826
+ questionType: n.questionType || meta.questionType || flattened.testType,
827
+ linkedBeliefIds: flattened.relatedBeliefIds,
828
+ audienceLabel: n.audienceLabel,
829
+ policyTags: n.policyTags,
830
+ sensitivityTier: n.sensitivityTier,
831
+ exportClass: n.exportClass,
832
+ anonymizationClass: n.anonymizationClass
833
+ };
834
+ }
835
+ var INACTIVE_NODE_STATUSES = /* @__PURE__ */ new Set(["archived", "superseded", "deleted"]);
836
+ var INACTIVE_METADATA_STATUSES = /* @__PURE__ */ new Set([
837
+ "archived",
838
+ "superseded",
839
+ "deleted"
840
+ ]);
841
+ function getQuestionStatusCandidates(node) {
842
+ const meta = node.metadata || {};
843
+ return [meta.questionStatus, meta.status, node.status].filter(
844
+ (value) => typeof value === "string"
845
+ );
846
+ }
847
+ function isActiveQuestionNode(node) {
848
+ if (INACTIVE_NODE_STATUSES.has(node.status)) {
849
+ return false;
850
+ }
851
+ return !getQuestionStatusCandidates(node).some(
852
+ (status) => INACTIVE_METADATA_STATUSES.has(status.toLowerCase())
853
+ );
854
+ }
855
+ function matchesRequestedQuestionStatus(node, requestedStatus) {
856
+ const normalizedStatus = requestedStatus.toLowerCase();
857
+ return getQuestionStatusCandidates(node).some(
858
+ (status) => status.toLowerCase() === normalizedStatus
859
+ );
860
+ }
861
+
862
+ // src/epistemicQuestions.tail.ts
863
+ var list = query({
864
+ args: {
865
+ ...optionalScopeArgs,
866
+ userId: v.string(),
867
+ status: v.optional(v.string())
868
+ },
869
+ returns: permissiveReturn,
870
+ handler: async (ctx, args) => {
871
+ const scope = await resolveQuestionScopeOrNull(ctx, args);
872
+ if (!scope) {
873
+ return [];
874
+ }
875
+ const scopeId = resolveQuestionScopeId(scope);
876
+ if (!scopeId) {
877
+ return [];
878
+ }
879
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
880
+ if (!hasAccess) {
881
+ return [];
882
+ }
883
+ const nodes = await getQuestionNodesForScope(ctx, scope);
884
+ const filtered = args.status ? nodes.filter(
885
+ (node) => matchesRequestedQuestionStatus(node, args.status)
886
+ ) : nodes.filter(isActiveQuestionNode);
887
+ return filtered.map(flattenInternalQuestionNode);
888
+ }
889
+ });
890
+ var getByPillar = query({
891
+ args: {
892
+ ...optionalScopeArgs,
893
+ pillar: v.string(),
894
+ includeAnswered: v.optional(v.boolean()),
895
+ userId: v.string()
896
+ },
897
+ returns: permissiveReturn,
898
+ handler: async (ctx, args) => {
899
+ const scope = await resolveQuestionScopeOrNull(ctx, args);
900
+ if (!scope) {
901
+ return { questions: [], beliefs: [] };
902
+ }
903
+ const scopeId = resolveQuestionScopeId(scope);
904
+ if (!scopeId) {
905
+ return { questions: [], beliefs: [] };
906
+ }
907
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
908
+ if (!hasAccess) {
909
+ return { questions: [], beliefs: [] };
910
+ }
911
+ const allBeliefNodes = await ctx.db.query("epistemicNodes").withIndex(
912
+ "by_topic_type",
913
+ (q) => q.eq("topicId", scope.topicId).eq("nodeType", "belief")
914
+ ).collect();
915
+ const beliefs = allBeliefNodes.filter((n) => {
916
+ const meta = n.metadata || {};
917
+ return (meta.category === args.pillar || meta.topic === args.pillar || meta.pillar === args.pillar) && n.status !== "archived";
918
+ });
919
+ if (beliefs.length === 0) {
920
+ return { questions: [], beliefs: [] };
921
+ }
922
+ const beliefIds = new Set(beliefs.map((b) => String(b._id)));
923
+ const beliefMap = new Map(
924
+ beliefs.map((b) => [String(b._id), b.canonicalText || ""])
925
+ );
926
+ const questionNodes = await getQuestionNodesForScope(ctx, scope);
927
+ const pillarQuestions = questionNodes.filter((q) => {
928
+ const meta = q.metadata || {};
929
+ const linkedId = String(
930
+ meta.linkedBeliefNodeId || meta.linkedBeliefId || meta.beliefId || ""
931
+ );
932
+ if (linkedId && beliefIds.has(linkedId)) {
933
+ return true;
934
+ }
935
+ const relatedIds = meta.relatedBeliefIds || [];
936
+ return relatedIds.some((id) => beliefIds.has(String(id)));
937
+ });
938
+ const includeAnswered = args.includeAnswered ?? true;
939
+ const filteredQuestions = includeAnswered ? pillarQuestions : pillarQuestions.filter((q) => {
940
+ const meta = q.metadata || {};
941
+ return meta.questionStatus !== "answered";
942
+ });
943
+ const enrichedQuestions = filteredQuestions.map((q) => {
944
+ const meta = q.metadata || {};
945
+ const linkedId = String(
946
+ meta.linkedBeliefNodeId || meta.linkedBeliefId || meta.beliefId || ""
947
+ );
948
+ return {
949
+ _id: q._id,
950
+ _creationTime: q._creationTime,
951
+ projectId: q.projectId,
952
+ question: q.canonicalText,
953
+ canonicalText: q.canonicalText,
954
+ category: meta.category || "other",
955
+ priority: meta.priority || "medium",
956
+ status: meta.questionStatus || q.status,
957
+ questionType: q.questionType || meta.questionType || "general",
958
+ beliefId: linkedId || void 0,
959
+ beliefText: linkedId ? beliefMap.get(linkedId) : void 0,
960
+ createdAt: q.createdAt,
961
+ createdBy: q.createdBy,
962
+ updatedAt: q.updatedAt
963
+ };
964
+ });
965
+ const priorityOrder = {
966
+ high: 0,
967
+ medium: 1,
968
+ low: 2
969
+ };
970
+ enrichedQuestions.sort((a, b) => {
971
+ const aPriority = priorityOrder[a.priority || "medium"] ?? 1;
972
+ const bPriority = priorityOrder[b.priority || "medium"] ?? 1;
973
+ if (aPriority !== bPriority) {
974
+ return aPriority - bPriority;
975
+ }
976
+ return (b.createdAt || 0) - (a.createdAt || 0);
977
+ });
978
+ return {
979
+ questions: enrichedQuestions,
980
+ beliefs: beliefs.map((b) => ({
981
+ _id: b._id,
982
+ belief: b.canonicalText,
983
+ pillar: b.metadata?.category || b.metadata?.pillar,
984
+ confidence: b.metadata?.confidence
985
+ }))
986
+ };
987
+ }
988
+ });
989
+ var consolidate = mutation({
990
+ args: {
991
+ ...optionalScopeArgs,
992
+ questionIds: v.array(v.string()),
993
+ // Accept string[] for backward compat
994
+ consolidatedQuestion: v.string(),
995
+ consolidationRationale: v.optional(v.string()),
996
+ linkedBeliefIds: v.optional(v.array(v.string())),
997
+ priority: v.optional(
998
+ v.union(v.literal("high"), v.literal("medium"), v.literal("low"))
999
+ ),
1000
+ sprintId: v.optional(v.string()),
1001
+ consolidatedBy: v.string()
1002
+ },
1003
+ returns: permissiveReturn,
1004
+ handler: async (ctx, args) => {
1005
+ const scope = await resolveTopicProjectScope(ctx, {
1006
+ projectId: args.projectId,
1007
+ topicId: args.topicId
1008
+ });
1009
+ const scopeId = resolveQuestionScopeId(scope);
1010
+ if (!scopeId) {
1011
+ return [];
1012
+ }
1013
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.consolidatedBy);
1014
+ if (!hasAccess) {
1015
+ throw new Error("Access denied");
1016
+ }
1017
+ if (args.questionIds.length < 2) {
1018
+ throw new Error("At least 2 questions required for consolidation");
1019
+ }
1020
+ const originalQuestions = await Promise.all(
1021
+ args.questionIds.map(async (qid) => {
1022
+ try {
1023
+ return await ctx.db.get(qid);
1024
+ } catch (error) {
1025
+ debugGraphPrimitiveFallback(
1026
+ "[epistemicQuestions] Failed to load original question",
1027
+ {
1028
+ error: formatGraphPrimitiveError(error),
1029
+ questionId: qid
1030
+ }
1031
+ );
1032
+ return null;
1033
+ }
1034
+ })
1035
+ );
1036
+ const validQuestions = originalQuestions.filter((q) => {
1037
+ if (!q || q.nodeType !== "question") {
1038
+ return false;
1039
+ }
1040
+ if (scope.topicId) {
1041
+ return String(q.topicId) === String(scope.topicId) || !q.topicId && scope.projectId !== void 0 && String(q.projectId) === String(scope.projectId);
1042
+ }
1043
+ return String(q.projectId) === String(scope.projectId);
1044
+ });
1045
+ if (validQuestions.length !== args.questionIds.length) {
1046
+ throw new Error(
1047
+ "Some questions not found or do not belong to this topic scope"
1048
+ );
1049
+ }
1050
+ const priorityOrder = {
1051
+ high: 0,
1052
+ medium: 1,
1053
+ low: 2
1054
+ };
1055
+ let bestPriority = args.priority;
1056
+ if (!bestPriority) {
1057
+ let bestScore = 2;
1058
+ for (const q of validQuestions) {
1059
+ const meta = q?.metadata || {};
1060
+ const score = priorityOrder[meta.priority || "medium"] ?? 1;
1061
+ if (score < bestScore) {
1062
+ bestScore = score;
1063
+ bestPriority = meta.priority || "medium";
1064
+ }
1065
+ }
1066
+ }
1067
+ const linkedBeliefSet = /* @__PURE__ */ new Set();
1068
+ if (args.linkedBeliefIds) {
1069
+ for (const bid of args.linkedBeliefIds) {
1070
+ linkedBeliefSet.add(String(bid));
1071
+ }
1072
+ } else {
1073
+ for (const q of validQuestions) {
1074
+ const meta = q?.metadata || {};
1075
+ if (meta.linkedBeliefNodeId) {
1076
+ linkedBeliefSet.add(String(meta.linkedBeliefNodeId));
1077
+ }
1078
+ if (meta.linkedBeliefId) {
1079
+ linkedBeliefSet.add(String(meta.linkedBeliefId));
1080
+ }
1081
+ }
1082
+ }
1083
+ let category = "other";
1084
+ if (linkedBeliefSet.size > 0) {
1085
+ const firstBeliefId = Array.from(linkedBeliefSet)[0];
1086
+ try {
1087
+ const belief = await ctx.db.get(firstBeliefId);
1088
+ if (belief) {
1089
+ const bMeta = belief.metadata || {};
1090
+ category = bMeta.category || bMeta.pillar || "other";
1091
+ }
1092
+ } catch (error) {
1093
+ debugGraphPrimitiveFallback(
1094
+ "[epistemicQuestions] Failed to read first linked belief metadata",
1095
+ {
1096
+ error: formatGraphPrimitiveError(error),
1097
+ beliefId: firstBeliefId
1098
+ }
1099
+ );
1100
+ }
1101
+ }
1102
+ const now = Date.now();
1103
+ const globalId = generateGlobalId();
1104
+ const contentHash = generateContentHash(args.consolidatedQuestion);
1105
+ const newNodeId = await ctx.db.insert("epistemicNodes", {
1106
+ globalId,
1107
+ projectId: scope.projectId,
1108
+ topicId: scope.topicId,
1109
+ nodeType: "question",
1110
+ canonicalText: args.consolidatedQuestion,
1111
+ contentHash,
1112
+ status: "active",
1113
+ epistemicLayer: "L3",
1114
+ sourceType: "human",
1115
+ createdAt: now,
1116
+ updatedAt: now,
1117
+ createdBy: args.consolidatedBy,
1118
+ metadata: {
1119
+ category,
1120
+ priority: bestPriority || "medium",
1121
+ source: "consolidated",
1122
+ questionStatus: "open",
1123
+ linkedBeliefNodeId: linkedBeliefSet.size > 0 ? Array.from(linkedBeliefSet)[0] : void 0,
1124
+ consolidationMetadata: {
1125
+ originalQuestionIds: args.questionIds,
1126
+ rationale: args.consolidationRationale,
1127
+ consolidatedAt: now,
1128
+ consolidatedBy: args.consolidatedBy
1129
+ }
1130
+ }
1131
+ });
1132
+ for (const qid of args.questionIds) {
1133
+ try {
1134
+ const node = await ctx.db.get(qid);
1135
+ if (node) {
1136
+ const meta = node.metadata || {};
1137
+ await ctx.db.patch(qid, {
1138
+ status: "archived",
1139
+ updatedAt: now,
1140
+ metadata: {
1141
+ ...meta,
1142
+ questionStatus: "archived",
1143
+ archivedAt: now,
1144
+ archivedReason: `Consolidated into question ${newNodeId}`,
1145
+ consolidatedIntoId: newNodeId
1146
+ }
1147
+ });
1148
+ }
1149
+ } catch (error) {
1150
+ debugGraphPrimitiveFallback(
1151
+ "[epistemicQuestions] Failed to archive linked question",
1152
+ {
1153
+ error: formatGraphPrimitiveError(error),
1154
+ questionId: qid
1155
+ }
1156
+ );
1157
+ }
1158
+ }
1159
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1160
+ nodeId: newNodeId,
1161
+ operation: "upsert"
1162
+ });
1163
+ await markProjectGraphDirty(
1164
+ ctx,
1165
+ scope.projectId,
1166
+ normalizeQuestionTopicId(scope.topicId)
1167
+ );
1168
+ return {
1169
+ newQuestionId: newNodeId,
1170
+ archivedCount: args.questionIds.length,
1171
+ linkedBeliefs: Array.from(linkedBeliefSet)
1172
+ };
1173
+ }
1174
+ });
1175
+ var deleteQuestion = mutation({
1176
+ args: {
1177
+ questionId: v.id("epistemicNodes"),
1178
+ userId: v.string(),
1179
+ runtimeToolName: v.optional(v.string()),
1180
+ runtimePackKey: v.optional(v.string()),
1181
+ runtimePackInstallScope: v.optional(
1182
+ v.union(v.literal("tenant"), v.literal("workspace"))
1183
+ )
1184
+ },
1185
+ returns: permissiveReturn,
1186
+ handler: async (ctx, args) => {
1187
+ const node = await ctx.db.get(args.questionId);
1188
+ if (!node || node.nodeType !== "question") {
1189
+ throw new Error("Question not found");
1190
+ }
1191
+ assertTenantPackWorkspaceMutationAllowed({
1192
+ runtime: resolveRuntimePackMutationContext(args),
1193
+ target: await resolveNodeScopeForWorkspaceIsolation(ctx, node),
1194
+ mutationName: "epistemicQuestions.deleteQuestion"
1195
+ });
1196
+ const now = Date.now();
1197
+ const meta = node.metadata || {};
1198
+ await ctx.db.patch(args.questionId, {
1199
+ status: "archived",
1200
+ updatedAt: now,
1201
+ metadata: {
1202
+ ...meta,
1203
+ questionStatus: "archived",
1204
+ archivedAt: now,
1205
+ archivedBy: args.userId
1206
+ }
1207
+ });
1208
+ await ctx.db.insert("epistemicAudit", {
1209
+ entityType: "question",
1210
+ entityId: args.questionId,
1211
+ changeType: "status_changed",
1212
+ changedAt: now,
1213
+ changedBy: args.userId,
1214
+ isAgent: false,
1215
+ projectId: node.projectId,
1216
+ previousState: {
1217
+ status: meta.questionStatus || node.status
1218
+ },
1219
+ newState: { status: "archived" }
1220
+ });
1221
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1222
+ nodeId: args.questionId,
1223
+ operation: "upsert"
1224
+ });
1225
+ await markProjectGraphDirty(ctx, node.projectId, node.topicId);
1226
+ }
1227
+ });
1228
+ var markAnsweredWithArtifact = mutation({
1229
+ args: {
1230
+ questionId: v.id("epistemicNodes"),
1231
+ artifactId: v.id("finalArtifacts"),
1232
+ answer: v.optional(v.string()),
1233
+ userId: v.string()
1234
+ },
1235
+ returns: permissiveReturn,
1236
+ handler: async (ctx, args) => {
1237
+ const node = await ctx.db.get(args.questionId);
1238
+ if (!node || node.nodeType !== "question") {
1239
+ throw new Error("Question not found");
1240
+ }
1241
+ const artifact = await resolveGraphPrimitivesAppResolvers().getFinalArtifact(ctx, args.artifactId);
1242
+ if (!artifact) {
1243
+ throw new Error("Artifact not found");
1244
+ }
1245
+ const scopeId = resolveQuestionScopeId({
1246
+ projectId: node.projectId,
1247
+ topicId: normalizeQuestionTopicId(node.topicId)
1248
+ });
1249
+ if (scopeId) {
1250
+ const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
1251
+ if (!hasAccess) {
1252
+ throw new Error("Access denied");
1253
+ }
1254
+ }
1255
+ const now = Date.now();
1256
+ const meta = node.metadata || {};
1257
+ await ctx.db.patch(args.questionId, {
1258
+ updatedAt: now,
1259
+ metadata: {
1260
+ ...meta,
1261
+ questionStatus: "answered",
1262
+ answeringArtifactId: args.artifactId,
1263
+ answer: args.answer,
1264
+ answeredAt: now,
1265
+ answeredBy: args.userId
1266
+ }
1267
+ });
1268
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
1269
+ nodeId: args.questionId,
1270
+ operation: "upsert"
1271
+ });
1272
+ await markProjectGraphDirty(ctx, node.projectId, node.topicId);
1273
+ }
1274
+ });
1275
+ var getQuestionClusterPositions = query({
1276
+ args: {
1277
+ ...optionalScopeArgs,
1278
+ userId: v.string()
1279
+ },
1280
+ returns: permissiveReturn,
1281
+ handler: async (ctx, args) => {
1282
+ let scope = null;
1283
+ try {
1284
+ scope = await resolveTopicProjectScope(ctx, {
1285
+ projectId: args.projectId,
1286
+ topicId: args.topicId
1287
+ });
1288
+ } catch (error) {
1289
+ debugGraphPrimitiveFallback(
1290
+ "[epistemicQuestions] Failed to resolve question cluster scope",
1291
+ {
1292
+ error: formatGraphPrimitiveError(error),
1293
+ projectId: args.projectId,
1294
+ topicId: args.topicId
1295
+ }
1296
+ );
1297
+ return {
1298
+ positions: {},
1299
+ counts: {
1300
+ cluster: 0,
1301
+ sprintLinked: 0,
1302
+ baseline: 0,
1303
+ total: 0
1304
+ }
1305
+ };
1306
+ }
1307
+ const questionNodes = await ctx.db.query("epistemicNodes").withIndex(
1308
+ "by_topic_type",
1309
+ (q) => q.eq("topicId", scope.topicId).eq("nodeType", "question")
1310
+ ).collect();
1311
+ const activeQuestionNodes = questionNodes.filter(isActiveQuestionNode);
1312
+ const positions = {};
1313
+ let clusterCount = 0;
1314
+ let sprintLinkedCount = 0;
1315
+ let baselineCount = 0;
1316
+ for (const question of activeQuestionNodes) {
1317
+ const id = question._id.toString();
1318
+ const meta = question.metadata || {};
1319
+ const questionType = typeof question.questionType === "string" ? question.questionType : typeof meta.questionType === "string" ? meta.questionType : void 0;
1320
+ if (questionType === "belief_test" || meta.testType) {
1321
+ positions[id] = "cluster";
1322
+ clusterCount++;
1323
+ } else if (resolveLinkedWorktreeId(meta)) {
1324
+ positions[id] = "sprint_linked";
1325
+ sprintLinkedCount++;
1326
+ } else {
1327
+ positions[id] = "baseline";
1328
+ baselineCount++;
1329
+ }
1330
+ }
1331
+ return {
1332
+ positions,
1333
+ counts: {
1334
+ cluster: clusterCount,
1335
+ sprintLinked: sprintLinkedCount,
1336
+ baseline: baselineCount,
1337
+ total: activeQuestionNodes.length
1338
+ }
1339
+ };
1340
+ }
1341
+ });
1342
+
1343
+ export { consolidate, deleteQuestion, getByPillar, getQuestionClusterPositions, list, markAnsweredWithArtifact };
1344
+ //# sourceMappingURL=epistemicQuestions.tail.js.map
1345
+ //# sourceMappingURL=epistemicQuestions.tail.js.map