@lucern/graph-primitives 0.3.0-alpha.0 → 0.3.0-alpha.10

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 (141) hide show
  1. package/dist/{beliefDecay-Q_26RTc-.d.ts → beliefDecay-DZ6tkLYq.d.ts} +1 -1
  2. package/dist/beliefDecay.d.ts +1 -1
  3. package/dist/beliefDecay.js +188 -1144
  4. package/dist/beliefDecay.js.map +1 -1
  5. package/dist/{beliefEvidenceLinks-42FlR48t.d.ts → beliefEvidenceLinks-CWOXxxJg.d.ts} +1 -1
  6. package/dist/beliefEvidenceLinks.d.ts +1 -1
  7. package/dist/beliefEvidenceLinks.js +186 -871
  8. package/dist/beliefEvidenceLinks.js.map +1 -1
  9. package/dist/{beliefLifecycle-C-AehZgF.d.ts → beliefLifecycle-y8WLXqQj.d.ts} +1 -1
  10. package/dist/beliefLifecycle.d.ts +1 -1
  11. package/dist/confidencePropagationDispatch.d.ts +4 -4
  12. package/dist/confidencePropagationDispatch.js +31 -311
  13. package/dist/confidencePropagationDispatch.js.map +1 -1
  14. package/dist/{contradictions-Hdwl7zid.d.ts → contradictions-51VLsESq.d.ts} +1 -1
  15. package/dist/contradictions.d.ts +1 -1
  16. package/dist/contradictions.js +67 -800
  17. package/dist/contradictions.js.map +1 -1
  18. package/dist/debug.d.ts +4 -0
  19. package/dist/debug.js +34 -0
  20. package/dist/debug.js.map +1 -0
  21. package/dist/edges/contradicts.js +1 -122
  22. package/dist/edges/contradicts.js.map +1 -1
  23. package/dist/edges/dependsOn.js +14 -172
  24. package/dist/edges/dependsOn.js.map +1 -1
  25. package/dist/edges/elaborates.js +1 -49
  26. package/dist/edges/elaborates.js.map +1 -1
  27. package/dist/edges/index.js +15 -280
  28. package/dist/edges/index.js.map +1 -1
  29. package/dist/edges/informs.js +2 -65
  30. package/dist/edges/informs.js.map +1 -1
  31. package/dist/edges/propagationTypes.d.ts +2 -2
  32. package/dist/edges/propagationTypes.js.map +1 -1
  33. package/dist/edges/refutes.js +2 -65
  34. package/dist/edges/refutes.js.map +1 -1
  35. package/dist/edges/supports.js +1 -122
  36. package/dist/edges/supports.js.map +1 -1
  37. package/dist/edges/utils.d.ts +7 -7
  38. package/dist/edges/utils.js +2 -133
  39. package/dist/edges/utils.js.map +1 -1
  40. package/dist/embeddingTrigger.js +21 -1
  41. package/dist/embeddingTrigger.js.map +1 -1
  42. package/dist/entityBridge.js +3 -18
  43. package/dist/entityBridge.js.map +1 -1
  44. package/dist/{entityLifecycle-BkhRJ-XI.d.ts → entityLifecycle-CvgSK5FV.d.ts} +1 -1
  45. package/dist/entityLifecycle.d.ts +1 -1
  46. package/dist/entityLifecycle.js +193 -892
  47. package/dist/entityLifecycle.js.map +1 -1
  48. package/dist/{epistemicAnswers-DSP1slZ9.d.ts → epistemicAnswers-C5ib4z6_.d.ts} +1 -1
  49. package/dist/epistemicAnswers.d.ts +1 -1
  50. package/dist/epistemicAnswers.js +73 -810
  51. package/dist/epistemicAnswers.js.map +1 -1
  52. package/dist/{epistemicBeliefs-DtFVTp-k.d.ts → epistemicBeliefs-DzKjZAeC.d.ts} +3 -3
  53. package/dist/epistemicBeliefs.d.ts +2 -2
  54. package/dist/epistemicBeliefs.js +404 -1698
  55. package/dist/epistemicBeliefs.js.map +1 -1
  56. package/dist/epistemicContractHelpers.js +1 -318
  57. package/dist/epistemicContractHelpers.js.map +1 -1
  58. package/dist/epistemicContracts.d.ts +1 -1
  59. package/dist/epistemicContracts.js +417 -1980
  60. package/dist/epistemicContracts.js.map +1 -1
  61. package/dist/{epistemicEdges-DcA8ErUG.d.ts → epistemicEdges-CD5vxmlH.d.ts} +3 -3
  62. package/dist/epistemicEdges.d.ts +1 -1
  63. package/dist/epistemicEdges.js +248 -919
  64. package/dist/epistemicEdges.js.map +1 -1
  65. package/dist/{epistemicEvidence-Bo638XDP.d.ts → epistemicEvidence-xw6UUrwh.d.ts} +1 -1
  66. package/dist/epistemicEvidence.d.ts +1 -1
  67. package/dist/epistemicEvidence.js +229 -1087
  68. package/dist/epistemicEvidence.js.map +1 -1
  69. package/dist/{epistemicHelpers-Bd9xbaib.d.ts → epistemicHelpers-DevrYgPN.d.ts} +1 -1
  70. package/dist/epistemicHelpers.d.ts +1 -1
  71. package/dist/{epistemicLinking-CyeLOIzN.d.ts → epistemicLinking-CfE00tHJ.d.ts} +1 -1
  72. package/dist/epistemicLinking.d.ts +1 -1
  73. package/dist/epistemicLinking.js +3 -786
  74. package/dist/epistemicLinking.js.map +1 -1
  75. package/dist/{epistemicNodes-BpD6Koud.d.ts → epistemicNodes-NBrPW7fk.d.ts} +2 -2
  76. package/dist/epistemicNodes.d.ts +1 -1
  77. package/dist/epistemicNodes.js +172 -899
  78. package/dist/epistemicNodes.js.map +1 -1
  79. package/dist/{epistemicQuestions-CmEeY6zQ.d.ts → epistemicQuestions-B_nUclrH.d.ts} +1 -1
  80. package/dist/epistemicQuestions.d.ts +1 -1
  81. package/dist/epistemicQuestions.js +369 -1125
  82. package/dist/epistemicQuestions.js.map +1 -1
  83. package/dist/{epistemicSources-ZazxHOK1.d.ts → epistemicSources-dlKj58Jp.d.ts} +1 -1
  84. package/dist/epistemicSources.d.ts +1 -1
  85. package/dist/epistemicSources.js +86 -886
  86. package/dist/epistemicSources.js.map +1 -1
  87. package/dist/evaluators/index.js +417 -1980
  88. package/dist/evaluators/index.js.map +1 -1
  89. package/dist/evaluators/lintCheckerEvaluator.js.map +1 -1
  90. package/dist/evaluators/sentryCheckerEvaluator.js.map +1 -1
  91. package/dist/evaluators/shared.js +20 -1
  92. package/dist/evaluators/shared.js.map +1 -1
  93. package/dist/evaluators/testRunnerEvaluator.js +20 -1
  94. package/dist/evaluators/testRunnerEvaluator.js.map +1 -1
  95. package/dist/evaluators/tscCheckerEvaluator.js.map +1 -1
  96. package/dist/index.d.ts +20 -20
  97. package/dist/index.js +965 -3004
  98. package/dist/index.js.map +1 -1
  99. package/dist/{ontology-matching-Buhu23ss.d.ts → ontology-matching-C6rrz2VP.d.ts} +1 -1
  100. package/dist/ontology-matching.d.ts +1 -1
  101. package/dist/ontology-matching.js +1 -344
  102. package/dist/ontology-matching.js.map +1 -1
  103. package/dist/{ontologyApproval-Ba0Jjk1k.d.ts → ontologyApproval-CFYmqKmk.d.ts} +1 -1
  104. package/dist/ontologyApproval.d.ts +1 -1
  105. package/dist/ontologyApproval.js +1 -13
  106. package/dist/ontologyApproval.js.map +1 -1
  107. package/dist/ontologyDefinitions.js +6 -20
  108. package/dist/ontologyDefinitions.js.map +1 -1
  109. package/dist/ontologyHelpers.d.ts +1 -1
  110. package/dist/ontologyHelpers.js +4 -3
  111. package/dist/ontologyHelpers.js.map +1 -1
  112. package/dist/ontologyRegistry.js +2 -17
  113. package/dist/ontologyRegistry.js.map +1 -1
  114. package/dist/{projectionReconciliation-CxrXYGaB.d.ts → projectionReconciliation-jww2fBI0.d.ts} +1 -1
  115. package/dist/projectionReconciliation.d.ts +1 -1
  116. package/dist/projectionReconciliation.js +16 -37
  117. package/dist/projectionReconciliation.js.map +1 -1
  118. package/dist/{projectionStaleness-CAdpIsaW.d.ts → projectionStaleness-CmdbpjVK.d.ts} +1 -1
  119. package/dist/projectionStaleness.d.ts +1 -1
  120. package/dist/{questionEvidenceLinks-BdQD0TkM.d.ts → questionEvidenceLinks-DFlyPpAj.d.ts} +1 -1
  121. package/dist/questionEvidenceLinks.d.ts +1 -1
  122. package/dist/questionEvidenceLinks.js +199 -881
  123. package/dist/questionEvidenceLinks.js.map +1 -1
  124. package/dist/resolvers.js +86 -37
  125. package/dist/resolvers.js.map +1 -1
  126. package/dist/scopeResolverCompat.js +64 -7
  127. package/dist/scopeResolverCompat.js.map +1 -1
  128. package/dist/{text-matching-CMn2WnVD.d.ts → text-matching-DNg4M5Wd.d.ts} +1 -1
  129. package/dist/text-matching.d.ts +1 -1
  130. package/dist/text-matching.js +1 -244
  131. package/dist/text-matching.js.map +1 -1
  132. package/dist/topicProjectOverlay.js +56 -13
  133. package/dist/topicProjectOverlay.js.map +1 -1
  134. package/dist/topicScope.js +55 -6
  135. package/dist/topicScope.js.map +1 -1
  136. package/dist/workflowBridge.d.ts +27 -0
  137. package/dist/workflowBridge.js +352 -0
  138. package/dist/workflowBridge.js.map +1 -0
  139. package/dist/workspaceIsolation.js +56 -57
  140. package/dist/workspaceIsolation.js.map +1 -1
  141. package/package.json +6 -5
@@ -1,820 +1,30 @@
1
1
  import { v } from 'convex/values';
2
- import { componentsGeneric, queryGeneric, mutationGeneric, anyApi, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
2
+ import { checkScopeAccess, requireProjectAccess } from '@lucern/access-control/access';
3
+ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
+ import { componentsGeneric, anyApi, queryGeneric, mutationGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
5
+ import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
3
6
 
4
7
  // src/epistemicNodes.ts
5
8
  var api = anyApi;
6
9
  componentsGeneric();
7
-
8
- // ../access-control/src/topicProjectOverlay.ts
9
- var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
10
- function readNonEmptyString(value) {
11
- if (typeof value !== "string") {
12
- return;
13
- }
14
- const normalized = value.trim();
15
- return normalized.length > 0 ? normalized : void 0;
16
- }
17
- function readStringArray(value) {
18
- if (!Array.isArray(value)) {
19
- return [];
20
- }
21
- return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
22
- }
23
- function readMetadata(topic) {
24
- return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
25
- }
26
- function readLegacyProjectId(value) {
27
- if (!value) {
28
- return;
29
- }
30
- return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
31
- }
32
- function coerceVisibility(value) {
33
- return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
34
- }
35
- function coerceStatus(value) {
36
- return value === "active" || value === "archived" || value === "watching" ? value : void 0;
37
- }
38
- function mapProjectType(topic, metadata) {
39
- const explicit = readNonEmptyString(metadata.projectType);
40
- if (explicit) {
41
- return explicit;
42
- }
43
- if (topic.type === "theme") {
44
- return "thematic";
45
- }
46
- return readNonEmptyString(topic.type) || "general";
47
- }
48
- function isProjectLikeTopic(topic) {
49
- const metadata = readMetadata(topic);
50
- return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
51
- }
52
- async function resolveTopicDoc(ctx, scopeId) {
53
- if (ctx?.db && typeof ctx.db.get === "function") {
54
- try {
55
- const directTopic = await ctx.db.get(scopeId);
56
- if (directTopic) {
57
- return directTopic;
58
- }
59
- } catch {
60
- }
61
- }
62
- if (typeof ctx.runQuery !== "function") {
63
- return null;
64
- }
65
- try {
66
- const topic = await ctx.runQuery(api.topics.get, {
67
- id: String(scopeId)
68
- });
69
- if (topic?.name !== void 0 && topic?.type !== void 0) {
70
- return topic;
71
- }
72
- } catch {
73
- }
74
- try {
75
- const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
76
- projectId: String(scopeId)
77
- });
78
- if (topic?.name !== void 0 && topic?.type !== void 0) {
79
- return topic;
80
- }
81
- } catch {
82
- }
83
- return null;
84
- }
85
- function materializeTopicProjectOverlay(topic, idMode = "legacy") {
86
- const metadata = readMetadata(topic);
87
- const topicId = String(topic._id);
88
- const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
89
- const storageProjectId = legacyProjectId || topicId;
90
- const outwardId = idMode === "topic" ? topicId : storageProjectId;
91
- const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
92
- const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
93
- const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
94
- const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
95
- return {
96
- ...metadata,
97
- _id: outwardId,
98
- projectId: outwardId,
99
- topicId,
100
- storageProjectId,
101
- legacyProjectId,
102
- name: readNonEmptyString(topic.name) || "Untitled Theme",
103
- type: mapProjectType(topic, metadata),
104
- description: readNonEmptyString(topic.description),
105
- ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
106
- sharedWith: readStringArray(metadata.sharedWith),
107
- visibility,
108
- tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
109
- workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
110
- status,
111
- tags: readStringArray(metadata.tags),
112
- chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
113
- artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
114
- lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
115
- _creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
116
- createdAt,
117
- updatedAt
118
- };
119
- }
120
- async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
121
- const topic = await resolveTopicDoc(ctx, scopeId);
122
- if (!topic) {
123
- return null;
124
- }
125
- if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
126
- return null;
127
- }
128
- return materializeTopicProjectOverlay(topic, options.idMode);
129
- }
130
- async function listTopicProjectOverlays(ctx, options = {}) {
131
- let allTopics = [];
132
- if (ctx?.db?.query && typeof ctx.db.query === "function") {
133
- try {
134
- allTopics = await ctx.db.query("topics").collect();
135
- } catch {
136
- allTopics = [];
137
- }
138
- }
139
- if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
140
- allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
141
- }
142
- return allTopics.filter(
143
- (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
144
- ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
145
- }
146
-
147
- // ../access-control/src/projectGrantsBridge.ts
148
- var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
149
- function normalizeString(value) {
150
- if (typeof value !== "string") {
151
- return;
152
- }
153
- const trimmed = value.trim();
154
- return trimmed.length > 0 ? trimmed : void 0;
155
- }
156
- async function resolveGrantScopeIds(ctx, args) {
157
- const topicId = normalizeString(args.topicId);
158
- const projectId = normalizeString(args.projectId);
159
- for (const scopeId of [topicId, projectId]) {
160
- if (!scopeId) {
161
- continue;
162
- }
163
- try {
164
- const overlay = await resolveTopicProjectOverlay(ctx, scopeId, {
165
- idMode: "legacy",
166
- projectLikeOnly: false
167
- });
168
- if (overlay) {
169
- return {
170
- topicId: normalizeString(overlay.topicId) ?? topicId,
171
- projectId: normalizeString(overlay.projectId) ?? projectId ?? scopeId
172
- };
173
- }
174
- } catch {
175
- }
176
- }
177
- return { topicId, projectId };
178
- }
179
- async function normalizeProjectGrantRow(ctx, row) {
180
- const scope = await resolveGrantScopeIds(ctx, {
181
- topicId: row.topicId,
182
- projectId: row.projectId
183
- });
184
- return {
185
- ...row,
186
- ...scope.topicId ? { topicId: scope.topicId } : {},
187
- ...scope.projectId ?? scope.topicId ? { projectId: scope.projectId ?? scope.topicId } : {}
188
- };
189
- }
190
- async function normalizeProjectGrantRows(ctx, rows) {
191
- return await Promise.all(rows.map((row) => normalizeProjectGrantRow(ctx, row)));
192
- }
193
- async function listProjectGrantsByPrincipal(ctx, principalId) {
194
- const rows = await Promise.all(
195
- PROJECT_GRANT_STATUSES.map(
196
- (status) => ctx.db.query("projectGrants").withIndex(
197
- "by_principal_status",
198
- (q) => q.eq("principalId", principalId).eq("status", status)
199
- ).collect()
200
- )
201
- );
202
- return await normalizeProjectGrantRows(ctx, rows.flat());
203
- }
204
- async function listProjectGrantsByGroup(ctx, groupId) {
205
- const rows = await Promise.all(
206
- PROJECT_GRANT_STATUSES.map(
207
- (status) => ctx.db.query("projectGrants").withIndex(
208
- "by_group_status",
209
- (q) => q.eq("groupId", groupId).eq("status", status)
210
- ).collect()
211
- )
212
- );
213
- return await normalizeProjectGrantRows(ctx, rows.flat());
214
- }
215
- function buildScopeMatchers(inputScopeId, resolved) {
216
- return new Set(
217
- [inputScopeId, resolved.topicId, resolved.projectId].map((value) => normalizeString(value)).filter((value) => Boolean(value))
218
- );
219
- }
220
- function matchesResolvedScope(row, scopeIds) {
221
- const rowTopicId = normalizeString(row.topicId);
222
- const rowProjectId = normalizeString(row.projectId);
223
- return rowTopicId !== void 0 && scopeIds.has(rowTopicId) || rowProjectId !== void 0 && scopeIds.has(rowProjectId);
224
- }
225
- async function bridgeListProjectGrantsByTopicAndPrincipal(ctx, topicId, principalId) {
226
- const resolved = await resolveGrantScopeIds(ctx, { topicId });
227
- const scopeIds = buildScopeMatchers(topicId, resolved);
228
- const rows = await listProjectGrantsByPrincipal(ctx, principalId);
229
- return rows.filter((row) => matchesResolvedScope(row, scopeIds));
230
- }
231
- async function bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId) {
232
- const resolved = await resolveGrantScopeIds(ctx, { topicId });
233
- const scopeIds = buildScopeMatchers(topicId, resolved);
234
- const rows = await listProjectGrantsByGroup(ctx, groupId);
235
- return rows.filter((row) => matchesResolvedScope(row, scopeIds));
236
- }
237
- async function bridgeListProjectGrantsByPrincipalStatus(ctx, principalId, status) {
238
- const rows = await listProjectGrantsByPrincipal(ctx, principalId);
239
- return rows.filter((row) => row.status === status);
240
- }
241
- async function bridgeListProjectGrantsByGroupStatus(ctx, groupId, status) {
242
- const rows = await listProjectGrantsByGroup(ctx, groupId);
243
- return rows.filter((row) => row.status === status);
244
- }
245
- async function bridgeInsertProjectGrant(ctx, value) {
246
- const resolved = await resolveGrantScopeIds(ctx, value);
247
- return await ctx.db.insert("projectGrants", {
248
- ...value,
249
- ...resolved.topicId ? { topicId: resolved.topicId } : {},
250
- ...resolved.projectId ?? resolved.topicId ? { projectId: resolved.projectId ?? resolved.topicId } : {}
251
- });
252
- }
253
-
254
- // ../access-control/src/resolvers.ts
255
- async function findUserByClerkId(ctx, clerkId) {
256
- const normalizedClerkId = clerkId.trim();
257
- if (!normalizedClerkId) {
258
- return null;
259
- }
260
- if (typeof ctx.runQuery === "function") {
261
- try {
262
- const bridgedUser = await ctx.runQuery(api.users.getUserByClerkId, {
263
- clerkId: normalizedClerkId
264
- });
265
- if (bridgedUser) {
266
- return bridgedUser;
267
- }
268
- } catch {
269
- }
270
- }
271
- try {
272
- const users = await ctx.db.query("users").collect();
273
- return users.find((user) => String(user.clerkId ?? "") === normalizedClerkId) ?? null;
274
- } catch {
275
- return null;
276
- }
277
- }
278
- async function findUserByPrincipalId(ctx, principalId) {
279
- const normalizedPrincipalId = principalId.trim();
280
- if (!normalizedPrincipalId) {
281
- return null;
282
- }
283
- try {
284
- const users = await ctx.db.query("users").collect();
285
- return users.find(
286
- (user) => String(user.defaultPrincipalId ?? "") === normalizedPrincipalId
287
- ) ?? null;
288
- } catch {
289
- return null;
290
- }
291
- }
292
- async function findAgentByPrincipalId(ctx, principalId) {
293
- const normalizedPrincipalId = principalId.trim();
294
- if (!normalizedPrincipalId) {
295
- return null;
296
- }
297
- if (typeof ctx.runQuery === "function") {
298
- try {
299
- const bridgedAgent = await ctx.runQuery(
300
- api.agents.getAgentByPrincipalId,
301
- {
302
- principalId: normalizedPrincipalId
303
- }
304
- );
305
- if (bridgedAgent) {
306
- return bridgedAgent;
307
- }
308
- } catch {
309
- }
310
- }
311
- try {
312
- const agents = await ctx.db.query("agents").collect();
313
- return agents.find(
314
- (agent) => String(agent.principalId ?? "") === normalizedPrincipalId
315
- ) ?? null;
316
- } catch {
317
- return null;
318
- }
319
- }
320
- function defaultResolvers() {
321
- return {
322
- async getProject(ctx, topicId) {
323
- return await resolveTopicProjectOverlay(ctx, topicId, {
324
- idMode: "legacy",
325
- projectLikeOnly: false
326
- });
327
- },
328
- async listTopics(ctx) {
329
- return await listTopicProjectOverlays(ctx, { idMode: "legacy" });
330
- },
331
- async listTopicsByOwner(ctx, ownerId) {
332
- const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
333
- return topics.filter((topic) => topic.ownerId === ownerId);
334
- },
335
- async listTopicsByVisibility(ctx, visibility) {
336
- const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
337
- return topics.filter((topic) => topic.visibility === visibility);
338
- },
339
- async listProjectGrantsByProjectAndPrincipal(ctx, topicId, principalId) {
340
- return await bridgeListProjectGrantsByTopicAndPrincipal(
341
- ctx,
342
- topicId,
343
- principalId
344
- );
345
- },
346
- async listProjectGrantsByProjectAndGroup(ctx, topicId, groupId) {
347
- return await bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId);
348
- },
349
- async listProjectGrantsByPrincipalStatus(ctx, principalId, status) {
350
- return await bridgeListProjectGrantsByPrincipalStatus(
351
- ctx,
352
- principalId,
353
- status
354
- );
355
- },
356
- async listProjectGrantsByGroupStatus(ctx, groupId, status) {
357
- return await bridgeListProjectGrantsByGroupStatus(ctx, groupId, status);
358
- },
359
- async insertProjectGrant(ctx, value) {
360
- return await bridgeInsertProjectGrant(ctx, value);
361
- },
362
- async getAgentByPrincipalId(ctx, principalId) {
363
- return await findAgentByPrincipalId(ctx, principalId);
364
- },
365
- async getUserByClerkId(ctx, clerkId) {
366
- return await findUserByClerkId(ctx, clerkId);
367
- },
368
- async getUserByPrincipalId(ctx, principalId) {
369
- return await findUserByPrincipalId(ctx, principalId);
370
- }
371
- };
372
- }
373
- var resolverOverrides = {};
374
- function resolveAccessControlAppResolvers(_ctx) {
375
- return {
376
- ...defaultResolvers(),
377
- ...resolverOverrides
378
- };
379
- }
380
-
381
- // ../access-control/src/principalContext.ts
382
- function requireCanonicalResolvedUser(user, clerkId) {
383
- const resolved = user;
384
- if (!resolved) {
385
- throw new Error(
386
- `[AccessControl] Canonical user identity required for ${clerkId}. Sync users.upsertUser before user-bound access checks.`
387
- );
388
- }
389
- const { mcRole, defaultTenantId, defaultWorkspaceId, defaultPrincipalId } = resolved;
390
- if (mcRole !== "platform_admin" && mcRole !== "tenant_admin" && mcRole !== "workspace_admin" && mcRole !== "editor" && mcRole !== "viewer" && mcRole !== "auditor" && mcRole !== "service_agent") {
391
- throw new Error(
392
- `[AccessControl] Canonical MC role required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
393
- );
394
- }
395
- if (typeof defaultTenantId !== "string" || defaultTenantId.trim().length === 0) {
396
- throw new Error(
397
- `[AccessControl] Canonical home tenant required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
398
- );
399
- }
400
- if (typeof defaultWorkspaceId !== "string" || defaultWorkspaceId.trim().length === 0) {
401
- throw new Error(
402
- `[AccessControl] Canonical home workspace required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
403
- );
404
- }
405
- if (typeof defaultPrincipalId !== "string" || defaultPrincipalId.trim().length === 0) {
406
- throw new Error(
407
- `[AccessControl] Canonical federated principal required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
408
- );
409
- }
410
- return {
411
- mcRole,
412
- defaultTenantId: defaultTenantId.trim(),
413
- defaultWorkspaceId: defaultWorkspaceId.trim(),
414
- defaultPrincipalId: defaultPrincipalId.trim()
415
- };
416
- }
417
- function isPrincipalIdInput(value) {
418
- return value.startsWith("user:") || value.startsWith("group:") || value.startsWith("service:") || value.startsWith("agent:") || value.startsWith("external_viewer:");
419
- }
420
- async function resolveCanonicalUserRecord(ctx, actorId) {
421
- const normalizedActorId = actorId.trim();
422
- const clerkId = isPrincipalIdInput(normalizedActorId) && normalizedActorId.startsWith("user:") ? normalizedActorId.slice("user:".length) : normalizedActorId;
423
- const resolvers = resolveAccessControlAppResolvers();
424
- const resolvedByClerkId = await resolvers.getUserByClerkId(ctx, clerkId);
425
- if (resolvedByClerkId) {
426
- return {
427
- resolvedUser: resolvedByClerkId,
428
- clerkId,
429
- contextClerkId: clerkId
430
- };
431
- }
432
- const resolvedByPrincipalId = await resolvers.getUserByPrincipalId(
433
- ctx,
434
- normalizedActorId
435
- );
436
- return {
437
- resolvedUser: resolvedByPrincipalId ?? null,
438
- clerkId,
439
- contextClerkId: normalizedActorId.startsWith("user:") && clerkId.length > 0 ? clerkId : normalizedActorId
440
- };
441
- }
442
- function uniqRoles(roles) {
443
- const roleSet = /* @__PURE__ */ new Set();
444
- for (const role of roles) {
445
- if (role === "platform_admin" || role === "tenant_admin" || role === "workspace_admin" || role === "editor" || role === "viewer" || role === "auditor" || role === "service_agent") {
446
- roleSet.add(role);
447
- }
448
- }
449
- return [...roleSet];
450
- }
451
- function normalizeGroupIds(value) {
452
- if (!Array.isArray(value)) {
453
- return [];
454
- }
455
- return [...new Set(
456
- value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean)
457
- )];
458
- }
459
- function requireServiceAgentUser(user, actorId) {
460
- const canonicalUser = requireCanonicalResolvedUser(user, actorId);
461
- if (canonicalUser.mcRole !== "service_agent") {
462
- throw new Error(
463
- `[AccessControl] Canonical service_agent identity required for ${actorId}. Sync users.upsertUser before agent-bound access checks.`
464
- );
465
- }
466
- return canonicalUser;
467
- }
468
- function requireCanonicalResolvedAgent(agent, actorId) {
469
- const resolved = agent;
470
- if (!resolved) {
471
- throw new Error(
472
- `[AccessControl] Agent "${actorId}" not found in agents or users table.`
473
- );
474
- }
475
- if (typeof resolved.principalId !== "string" || resolved.principalId.trim().length === 0) {
476
- throw new Error(
477
- `[AccessControl] Canonical agent principalId required for ${actorId}.`
478
- );
479
- }
480
- if (typeof resolved.tenantId !== "string" || resolved.tenantId.trim().length === 0) {
481
- throw new Error(
482
- `[AccessControl] Canonical home tenant required for ${actorId}.`
483
- );
484
- }
485
- if (typeof resolved.workspaceId !== "string" || resolved.workspaceId.trim().length === 0) {
486
- throw new Error(
487
- `[AccessControl] Canonical home workspace required for ${actorId}.`
488
- );
489
- }
490
- return {
491
- principalId: resolved.principalId.trim(),
492
- tenantId: resolved.tenantId.trim(),
493
- workspaceId: resolved.workspaceId.trim(),
494
- roles: uniqRoles(Array.isArray(resolved.roles) ? resolved.roles : []) ?? ["service_agent"],
495
- groupIds: normalizeGroupIds(resolved.groupIds)
496
- };
497
- }
498
- async function resolvePrincipalContext(ctx, actorId) {
499
- if (actorId.startsWith("agent:")) {
500
- const resolvers = resolveAccessControlAppResolvers();
501
- const resolvedAgent = await resolvers.getAgentByPrincipalId(ctx, actorId);
502
- if (resolvedAgent) {
503
- const agent = requireCanonicalResolvedAgent(
504
- resolvedAgent,
505
- actorId
506
- );
507
- return {
508
- principalId: agent.principalId,
509
- principalType: "service",
510
- clerkId: actorId,
511
- tenantId: agent.tenantId,
512
- workspaceId: agent.workspaceId,
513
- roles: agent.roles.length > 0 ? agent.roles : ["service_agent"],
514
- groupIds: agent.groupIds,
515
- isPlatformAdmin: false,
516
- isTenantAdmin: false,
517
- isWorkspaceAdmin: false,
518
- isSystemFallback: false
519
- };
520
- }
521
- const resolvedUser2 = await resolvers.getUserByClerkId(
522
- ctx,
523
- actorId
524
- );
525
- if (!resolvedUser2) {
526
- throw new Error(
527
- `[AccessControl] Agent "${actorId}" not found in agents or users table.`
528
- );
529
- }
530
- const user2 = requireServiceAgentUser(
531
- resolvedUser2,
532
- actorId
533
- );
534
- console.warn(
535
- `[AccessControl] Deprecated legacy service-agent fallback for ${actorId}; migrate this principal into identity.agents.`
536
- );
537
- return {
538
- principalId: user2.defaultPrincipalId,
539
- principalType: "service",
540
- clerkId: actorId,
541
- tenantId: user2.defaultTenantId,
542
- workspaceId: user2.defaultWorkspaceId,
543
- roles: ["service_agent"],
544
- groupIds: normalizeGroupIds(resolvedUser2?.principalGroupIds),
545
- isPlatformAdmin: false,
546
- isTenantAdmin: false,
547
- isWorkspaceAdmin: false,
548
- isSystemFallback: false
549
- };
550
- }
551
- const {
552
- resolvedUser,
553
- contextClerkId
554
- } = await resolveCanonicalUserRecord(ctx, actorId);
555
- const user = requireCanonicalResolvedUser(
556
- resolvedUser,
557
- contextClerkId
558
- );
559
- if (!user.defaultPrincipalId) {
560
- throw new Error(
561
- `[AccessControl] Canonical federated principal required for ${contextClerkId}. Re-sync Master Control identity before user-bound access checks.`
562
- );
563
- }
564
- if (user.mcRole === "service_agent") {
565
- return {
566
- principalId: user.defaultPrincipalId,
567
- principalType: "service",
568
- clerkId: contextClerkId,
569
- tenantId: user.defaultTenantId,
570
- workspaceId: user.defaultWorkspaceId,
571
- roles: ["service_agent"],
572
- groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
573
- isPlatformAdmin: false,
574
- isTenantAdmin: false,
575
- isWorkspaceAdmin: false,
576
- isSystemFallback: false
577
- };
578
- }
579
- const principalId = user.defaultPrincipalId;
580
- const effectiveRole = user.mcRole;
581
- const roles = effectiveRole === "platform_admin" ? ["platform_admin", "tenant_admin"] : effectiveRole === "tenant_admin" ? ["tenant_admin"] : [effectiveRole];
582
- const tenantId = user.defaultTenantId;
583
- const workspaceId = user.defaultWorkspaceId;
584
- const isPlatformAdmin = effectiveRole === "platform_admin";
585
- return {
586
- principalId,
587
- principalType: "user",
588
- clerkId: contextClerkId,
589
- tenantId,
590
- workspaceId,
591
- roles: uniqRoles(roles),
592
- groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
593
- isPlatformAdmin,
594
- isTenantAdmin: isPlatformAdmin || effectiveRole === "tenant_admin",
595
- isWorkspaceAdmin: isPlatformAdmin || effectiveRole === "tenant_admin" || effectiveRole === "workspace_admin",
596
- isSystemFallback: false
597
- };
598
- }
599
-
600
- // ../access-control/src/access.ts
601
- function isTopicInPrincipalTenant(topic, principalTenantId) {
602
- if (!topic.tenantId) {
603
- return false;
604
- }
605
- if (!principalTenantId) {
606
- return false;
607
- }
608
- return String(topic.tenantId) === String(principalTenantId);
609
- }
610
- function isTopicInPrincipalWorkspace(topic, principalWorkspaceId) {
611
- if (!topic.workspaceId) {
612
- return false;
613
- }
614
- if (!principalWorkspaceId) {
615
- return false;
616
- }
617
- return String(topic.workspaceId) === String(principalWorkspaceId);
618
- }
619
- function isLegacyUnscopedTopic(topic) {
620
- return !topic.tenantId || !topic.workspaceId;
621
- }
622
- function isGrantScopeAlignedToTopic(topic, grant) {
623
- if (topic.tenantId && grant.tenantId && String(topic.tenantId) !== String(grant.tenantId)) {
624
- return false;
625
- }
626
- if (topic.workspaceId && grant.workspaceId && String(topic.workspaceId) !== String(grant.workspaceId)) {
627
- return false;
628
- }
629
- return true;
630
- }
631
- function isGrantSourceAllowedForVisibility(visibility, source) {
632
- if (source !== "external_share") {
633
- return true;
634
- }
635
- return visibility === "external" || visibility === "public";
636
- }
637
- function isGrantActive(grant) {
638
- if (grant.status !== "active") {
639
- return false;
640
- }
641
- if (grant.expiresAt !== void 0 && grant.expiresAt <= Date.now()) {
642
- return false;
643
- }
644
- return true;
645
- }
646
- async function hasPrincipalGrant(ctx, args) {
647
- const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndPrincipal(
648
- ctx,
649
- args.topic._id,
650
- args.principalId
651
- );
652
- if (grants.some(
653
- (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
654
- args.topic.visibility,
655
- grant.source
656
- ) && (!args.principalIsExternal || args.topic.visibility === "public" || grant.source === "external_share")
657
- )) {
658
- return true;
659
- }
660
- return false;
661
- }
662
- async function hasGroupGrant(ctx, args) {
663
- if (args.groupIds.length === 0) {
664
- return false;
665
- }
666
- for (const groupId of args.groupIds) {
667
- const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndGroup(ctx, args.topic._id, groupId);
668
- if (grants.some(
669
- (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
670
- args.topic.visibility,
671
- grant.source
672
- )
673
- )) {
674
- return true;
675
- }
676
- }
677
- return false;
678
- }
679
- function isExternalPrincipal(_ctx, _args) {
680
- return false;
681
- }
682
- async function evaluateTopicAccessDetailed(ctx, args) {
683
- if (args.legacyUserId) {
684
- return {
685
- hasAccess: true,
686
- isAdmin: false,
687
- isOwner: false,
688
- isShared: false,
689
- hasGrant: true,
690
- isFirmVisible: true,
691
- isExternalVisible: false,
692
- isPublicVisible: false,
693
- isTenantScopeMatch: true,
694
- isWorkspaceScopeMatch: true,
695
- isPrincipalExternal: false
696
- };
697
- }
698
- const topic = await resolveAccessControlAppResolvers().getProject(
699
- ctx,
700
- args.topicId
701
- );
702
- if (!topic) {
703
- return {
704
- hasAccess: false,
705
- isAdmin: false,
706
- isOwner: false,
707
- isShared: false,
708
- hasGrant: false,
709
- isFirmVisible: false,
710
- isExternalVisible: false,
711
- isPublicVisible: false,
712
- isTenantScopeMatch: false,
713
- isWorkspaceScopeMatch: false,
714
- isPrincipalExternal: false
715
- };
716
- }
717
- const { principalContext, legacyUserId } = args;
718
- const userIsAdmin = principalContext.isPlatformAdmin;
719
- const isOwner = topic.ownerId === legacyUserId;
720
- const isShared = (topic.sharedWith ?? []).includes(legacyUserId);
721
- const principalIsExternal = await isExternalPrincipal(ctx, {
722
- groupIds: principalContext.groupIds,
723
- topicTenantId: topic.tenantId,
724
- topicWorkspaceId: topic.workspaceId
725
- });
726
- const hasPrincipalGrantResult = await hasPrincipalGrant(ctx, {
727
- topic,
728
- principalId: principalContext.principalId,
729
- principalIsExternal
730
- });
731
- const hasGroupGrantResult = await hasGroupGrant(ctx, {
732
- topic,
733
- groupIds: principalContext.groupIds
734
- });
735
- const hasGrant = isShared || hasPrincipalGrantResult || hasGroupGrantResult;
736
- const legacyUnscoped = isLegacyUnscopedTopic(topic);
737
- const tenantScopeMatch = isTopicInPrincipalTenant(
738
- topic,
739
- principalContext.tenantId
740
- );
741
- const workspaceScopeMatch = isTopicInPrincipalWorkspace(
742
- topic,
743
- principalContext.workspaceId
744
- );
745
- const isPublicVisible = topic.visibility === "public";
746
- const isFirmVisible = topic.visibility === "firm" && !legacyUnscoped && tenantScopeMatch && workspaceScopeMatch && !principalIsExternal;
747
- const hasScopedGrant = hasGrant && (legacyUnscoped || tenantScopeMatch && workspaceScopeMatch);
748
- const isExternalVisible = topic.visibility === "external" && hasScopedGrant;
749
- const hasAccess = userIsAdmin || isOwner || hasScopedGrant || isPublicVisible || isFirmVisible;
750
- return {
751
- hasAccess,
752
- isAdmin: userIsAdmin,
753
- isOwner,
754
- isShared,
755
- hasGrant,
756
- isFirmVisible,
757
- isExternalVisible,
758
- isPublicVisible,
759
- isTenantScopeMatch: tenantScopeMatch,
760
- isWorkspaceScopeMatch: workspaceScopeMatch,
761
- isPrincipalExternal: principalIsExternal
762
- };
763
- }
764
- async function checkTopicAccessDetailed(ctx, topicId, userId) {
765
- const principalContext = await resolvePrincipalContext(ctx, userId);
766
- return evaluateTopicAccessDetailed(ctx, {
767
- topicId,
768
- legacyUserId: userId,
769
- principalContext
770
- });
771
- }
772
- async function checkTopicAccess(ctx, topicId, userId) {
773
- const result = await checkTopicAccessDetailed(ctx, topicId, userId);
774
- return result.hasAccess;
775
- }
776
- async function checkScopeAccess(ctx, scopeId, userId) {
777
- try {
778
- const topic = await ctx.db.get(scopeId);
779
- if (topic && topic.name !== void 0 && topic.type !== void 0) {
780
- return true;
781
- }
782
- } catch {
783
- }
784
- try {
785
- return await checkTopicAccess(ctx, scopeId, userId);
786
- } catch {
787
- return false;
788
- }
789
- }
790
- async function requireTopicAccess(ctx, topicId, userId) {
791
- const hasAccess = await checkTopicAccess(ctx, topicId, userId);
792
- if (!hasAccess) {
793
- throw new Error(
794
- "Access denied: You don't have permission to access this topic"
795
- );
796
- }
797
- }
798
- var requireProjectAccess = requireTopicAccess;
799
- var permissiveReturn = v.optional(v.any());
800
- var looseJsonObject = v.record(v.string(), v.any());
801
- var looseJsonArray = v.array(v.any());
802
- v.union(
803
- v.string(),
804
- v.number(),
805
- v.boolean(),
806
- v.null(),
807
- looseJsonObject,
808
- looseJsonArray
809
- );
810
- var api2 = anyApi;
811
- componentsGeneric();
812
10
  var internal = anyApi;
813
11
  var internalMutation = internalMutationGeneric;
814
12
  var internalQuery = internalQueryGeneric;
815
13
  var mutation = mutationGeneric;
816
14
  var query = queryGeneric;
817
15
 
16
+ // src/debug.ts
17
+ function isGraphPrimitiveDebugEnabled() {
18
+ const env = globalThis.process?.env;
19
+ return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
20
+ }
21
+ function debugGraphPrimitiveFallback(message, context) {
22
+ if (!isGraphPrimitiveDebugEnabled()) {
23
+ return;
24
+ }
25
+ console.debug(message, context ?? {});
26
+ }
27
+
818
28
  // src/graphTypes.ts
819
29
  function getNodeLayer(nodeType) {
820
30
  const L4_TYPES = ["decision"];
@@ -1078,17 +288,17 @@ function assertOntologicalNodeSupersedeAllowed(args) {
1078
288
  }
1079
289
  });
1080
290
  }
1081
- var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
291
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
1082
292
  function asMappedProjectId(topic) {
1083
293
  if (!topic) {
1084
294
  return;
1085
295
  }
1086
- const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
296
+ const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
1087
297
  if (directLegacyProjectId) {
1088
298
  return directLegacyProjectId;
1089
299
  }
1090
300
  const metadata = topic.metadata || {};
1091
- const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
301
+ const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
1092
302
  return candidate ? candidate : void 0;
1093
303
  }
1094
304
  function normalizeScopeValue(value) {
@@ -1117,9 +327,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
1117
327
  try {
1118
328
  return await ctx.db.query("topics").withIndex(
1119
329
  "by_graph_scope_project",
1120
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
330
+ (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
1121
331
  ).collect();
1122
- } catch {
332
+ } catch (error) {
333
+ debugGraphPrimitiveFallback(
334
+ "[topicScope] Failed to resolve scope alias via index",
335
+ {
336
+ error,
337
+ scopeId
338
+ }
339
+ );
1123
340
  const topics = await ctx.db.query("topics").collect();
1124
341
  return topics.filter((topic) => {
1125
342
  const normalizedGlobalId = normalizeScopeValue(topic.globalId);
@@ -1133,10 +350,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
1133
350
  return null;
1134
351
  }
1135
352
  try {
1136
- return await ctx.runQuery(api2.topics.get, {
353
+ return await ctx.runQuery(api.topics.get, {
1137
354
  id: topicId
1138
355
  }) ?? null;
1139
- } catch {
356
+ } catch (error) {
357
+ debugGraphPrimitiveFallback(
358
+ "[topicScope] Failed to resolve topic by host query",
359
+ {
360
+ error,
361
+ topicId
362
+ }
363
+ );
1140
364
  return null;
1141
365
  }
1142
366
  }
@@ -1145,10 +369,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
1145
369
  return null;
1146
370
  }
1147
371
  try {
1148
- return await ctx.runQuery(api2.topics.getByLegacyScopeId, {
372
+ return await ctx.runQuery(api.topics.getByLegacyScopeId, {
1149
373
  projectId: legacyScopeId
1150
374
  }) ?? null;
1151
- } catch {
375
+ } catch (error) {
376
+ debugGraphPrimitiveFallback(
377
+ "[topicScope] Failed to resolve topic by legacy scope",
378
+ {
379
+ error,
380
+ legacyScopeId
381
+ }
382
+ );
1152
383
  return null;
1153
384
  }
1154
385
  }
@@ -1177,8 +408,17 @@ async function resolveTopicProjectScope(ctx, args) {
1177
408
  if (args.topicId) {
1178
409
  let topic = null;
1179
410
  try {
1180
- topic = await ctx.db.get(args.topicId);
1181
- } catch {
411
+ topic = await ctx.db.get(
412
+ args.topicId
413
+ );
414
+ } catch (error) {
415
+ debugGraphPrimitiveFallback(
416
+ "[topicScope] Failed to load topic by direct id",
417
+ {
418
+ error,
419
+ topicId: args.topicId
420
+ }
421
+ );
1182
422
  }
1183
423
  if (!topic) {
1184
424
  topic = await tryResolveHostTopicById(ctx, String(args.topicId));
@@ -1215,7 +455,14 @@ async function resolveTopicProjectScope(ctx, args) {
1215
455
  directTopic = await ctx.db.get(
1216
456
  args.projectId
1217
457
  );
1218
- } catch {
458
+ } catch (error) {
459
+ debugGraphPrimitiveFallback(
460
+ "[topicScope] Failed to load direct project topic",
461
+ {
462
+ error,
463
+ projectId: args.projectId
464
+ }
465
+ );
1219
466
  }
1220
467
  if (directTopic) {
1221
468
  const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
@@ -1264,61 +511,6 @@ var optionalScopeArgs = {
1264
511
  projectId: v.optional(v.string()),
1265
512
  topicId: v.optional(v.string())
1266
513
  };
1267
-
1268
- // ../../packages/contracts/src/schema-helpers/spine/tables/epistemicNodes.ts
1269
- var NODE_TYPES = [
1270
- "decision",
1271
- "belief",
1272
- "question",
1273
- "theme",
1274
- "deal",
1275
- "topic",
1276
- "claim",
1277
- "evidence",
1278
- "synthesis",
1279
- "answer",
1280
- "atomic_fact",
1281
- "excerpt",
1282
- "source",
1283
- "company",
1284
- "person",
1285
- "investor",
1286
- "function",
1287
- "value_chain"
1288
- ];
1289
- function isNodeType(value) {
1290
- return NODE_TYPES.includes(value);
1291
- }
1292
- function getLayerForNodeType(type) {
1293
- switch (type) {
1294
- case "decision":
1295
- return "L4";
1296
- case "belief":
1297
- case "question":
1298
- case "theme":
1299
- case "deal":
1300
- return "L3";
1301
- case "claim":
1302
- case "evidence":
1303
- case "synthesis":
1304
- case "answer":
1305
- return "L2";
1306
- case "atomic_fact":
1307
- case "excerpt":
1308
- case "source":
1309
- return "L1";
1310
- case "topic":
1311
- return "organizational";
1312
- case "company":
1313
- case "person":
1314
- case "investor":
1315
- case "function":
1316
- case "value_chain":
1317
- return "ontological";
1318
- }
1319
- }
1320
-
1321
- // src/workspaceIsolation.ts
1322
514
  function normalizeScopeValue2(value) {
1323
515
  if (typeof value !== "string") {
1324
516
  return;
@@ -1529,7 +721,15 @@ var getByProjectAndType = query({
1529
721
  projectId: args.projectId,
1530
722
  topicId: args.topicId
1531
723
  });
1532
- } catch {
724
+ } catch (error) {
725
+ debugGraphPrimitiveFallback(
726
+ "[epistemicNodes] Failed to resolve getByProjectAndType scope",
727
+ {
728
+ error,
729
+ projectId: args.projectId,
730
+ topicId: args.topicId
731
+ }
732
+ );
1533
733
  return [];
1534
734
  }
1535
735
  const pageSize = clampNodeLimit(args.limit);
@@ -1566,7 +766,15 @@ var getByProjectAndTypeLite = query({
1566
766
  projectId: args.projectId,
1567
767
  topicId: args.topicId
1568
768
  });
1569
- } catch {
769
+ } catch (error) {
770
+ debugGraphPrimitiveFallback(
771
+ "[epistemicNodes] Failed to resolve getByNodeType scope",
772
+ {
773
+ error,
774
+ projectId: args.projectId,
775
+ topicId: args.topicId
776
+ }
777
+ );
1570
778
  return [];
1571
779
  }
1572
780
  const pageSize = clampNodeLimit(args.limit);
@@ -1598,6 +806,27 @@ function clampNodeLimit(limit, fallback = DEFAULT_NODE_PAGE_SIZE) {
1598
806
  }
1599
807
  return Math.max(1, Math.min(Math.floor(limit), MAX_NODE_PAGE_SIZE));
1600
808
  }
809
+ function buildNodeStatusSuccessResult() {
810
+ return { success: true };
811
+ }
812
+ function buildNodeArchivedResult() {
813
+ return {
814
+ success: true,
815
+ effectiveStatus: "archived"
816
+ };
817
+ }
818
+ function buildNodeNotFoundResult() {
819
+ const result = {};
820
+ result.success = false;
821
+ result.error = "Node not found";
822
+ return result;
823
+ }
824
+ function buildNodeDeletedResult(deletedEdgeCount) {
825
+ return {
826
+ success: true,
827
+ deletedEdgeCount
828
+ };
829
+ }
1601
830
  function dedupeWorkspaceNodes(nodes) {
1602
831
  const seen = /* @__PURE__ */ new Set();
1603
832
  const deduped = [];
@@ -1629,14 +858,13 @@ async function collectScopedNodes(ctx, scope, args) {
1629
858
  args.nodeType ? ctx.db.query("epistemicNodes").withIndex(
1630
859
  "by_topic_type",
1631
860
  (q) => q.eq("topicId", scope.projectId).eq("nodeType", args.nodeType)
1632
- ).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex(
1633
- "by_topic",
1634
- (q) => q.eq("topicId", scope.projectId)
1635
- ).order("desc").take(args.scanLimit)
861
+ ).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex("by_topic", (q) => q.eq("topicId", scope.projectId)).order("desc").take(args.scanLimit)
1636
862
  );
1637
863
  }
1638
864
  const combined = dedupeWorkspaceNodes((await Promise.all(queries)).flat());
1639
- return combined.filter((node) => nodeMatchesWorkspaceReasoningScope(node, scope));
865
+ return combined.filter(
866
+ (node) => nodeMatchesWorkspaceReasoningScope(node, scope)
867
+ );
1640
868
  }
1641
869
  var getByProject = query({
1642
870
  args: {
@@ -1658,7 +886,15 @@ var getByProject = query({
1658
886
  projectId: args.projectId,
1659
887
  topicId: args.topicId
1660
888
  });
1661
- } catch {
889
+ } catch (error) {
890
+ debugGraphPrimitiveFallback(
891
+ "[epistemicNodes] Failed to resolve list scope",
892
+ {
893
+ error,
894
+ projectId: args.projectId,
895
+ topicId: args.topicId
896
+ }
897
+ );
1662
898
  return [];
1663
899
  }
1664
900
  if (args.userId) {
@@ -1712,7 +948,15 @@ var search = query({
1712
948
  projectId: args.projectId,
1713
949
  topicId: args.topicId
1714
950
  });
1715
- } catch {
951
+ } catch (error) {
952
+ debugGraphPrimitiveFallback(
953
+ "[epistemicNodes] Failed to resolve search scope",
954
+ {
955
+ error,
956
+ projectId: args.projectId,
957
+ topicId: args.topicId
958
+ }
959
+ );
1716
960
  return [];
1717
961
  }
1718
962
  }
@@ -1939,7 +1183,7 @@ var update = mutation({
1939
1183
  nodeId,
1940
1184
  operation: "upsert"
1941
1185
  });
1942
- return { success: true };
1186
+ return buildNodeStatusSuccessResult();
1943
1187
  }
1944
1188
  });
1945
1189
  var supersede = mutation({
@@ -2061,7 +1305,7 @@ var archive = mutation({
2061
1305
  projectId: node.projectId
2062
1306
  });
2063
1307
  }
2064
- return { success: true, effectiveStatus: "archived" };
1308
+ return buildNodeArchivedResult();
2065
1309
  }
2066
1310
  });
2067
1311
  var hardDelete = internalMutation({
@@ -2075,7 +1319,7 @@ var hardDelete = internalMutation({
2075
1319
  handler: async (ctx, args) => {
2076
1320
  const node = await ctx.db.get(args.nodeId);
2077
1321
  if (!node) {
2078
- return { success: false, error: "Node not found" };
1322
+ return buildNodeNotFoundResult();
2079
1323
  }
2080
1324
  assertBeliefNodeHardDeleteAllowed({
2081
1325
  node,
@@ -2111,10 +1355,7 @@ var hardDelete = internalMutation({
2111
1355
  await ctx.db.delete(edge._id);
2112
1356
  }
2113
1357
  await ctx.db.delete(args.nodeId);
2114
- return {
2115
- success: true,
2116
- deletedEdgeCount: uniqueEdges.size
2117
- };
1358
+ return buildNodeDeletedResult(uniqueEdges.size);
2118
1359
  }
2119
1360
  });
2120
1361
  var verify = mutation({
@@ -2161,7 +1402,7 @@ var verify = mutation({
2161
1402
  projectId: node.projectId,
2162
1403
  triggeringAction: "epistemicNodes.verify"
2163
1404
  });
2164
- return { success: true };
1405
+ return buildNodeStatusSuccessResult();
2165
1406
  }
2166
1407
  });
2167
1408
  var batchCreate = mutation({
@@ -2199,6 +1440,27 @@ var batchCreate = mutation({
2199
1440
  },
2200
1441
  returns: permissiveReturn,
2201
1442
  handler: async (ctx, args) => {
1443
+ const resolveNodeScope = async (node) => {
1444
+ if (!(node.topicId || node.projectId)) {
1445
+ return void 0;
1446
+ }
1447
+ try {
1448
+ return await resolveTopicProjectScope(ctx, {
1449
+ topicId: node.topicId,
1450
+ projectId: node.projectId
1451
+ });
1452
+ } catch (error) {
1453
+ debugGraphPrimitiveFallback(
1454
+ "[epistemicNodes] Failed to resolve scope for batch create node",
1455
+ {
1456
+ error,
1457
+ topicId: node.topicId,
1458
+ projectId: node.projectId
1459
+ }
1460
+ );
1461
+ return void 0;
1462
+ }
1463
+ };
2202
1464
  const now = Date.now();
2203
1465
  const results = [];
2204
1466
  for (const node of args.nodes) {
@@ -2207,10 +1469,7 @@ var batchCreate = mutation({
2207
1469
  mutationName: "epistemicNodes.batchCreate"
2208
1470
  });
2209
1471
  const epistemicLayer = getNodeLayer(node.nodeType);
2210
- const resolvedScope = node.topicId || node.projectId ? await resolveTopicProjectScope(ctx, {
2211
- topicId: node.topicId,
2212
- projectId: node.projectId
2213
- }).catch(() => void 0) : void 0;
1472
+ const resolvedScope = await resolveNodeScope(node);
2214
1473
  const nodeId = await ctx.db.insert("epistemicNodes", {
2215
1474
  ...node,
2216
1475
  epistemicLayer,
@@ -2292,10 +1551,24 @@ var createInternal = internalMutation({
2292
1551
  returns: permissiveReturn,
2293
1552
  handler: async (ctx, args) => {
2294
1553
  const now = Date.now();
2295
- const resolvedScope = args.topicId || args.projectId ? await resolveTopicProjectScope(ctx, {
2296
- topicId: args.topicId,
2297
- projectId: args.projectId
2298
- }).catch(() => void 0) : void 0;
1554
+ const resolvedScope = args.topicId || args.projectId ? await (async () => {
1555
+ try {
1556
+ return await resolveTopicProjectScope(ctx, {
1557
+ topicId: args.topicId,
1558
+ projectId: args.projectId
1559
+ });
1560
+ } catch (error) {
1561
+ debugGraphPrimitiveFallback(
1562
+ "[epistemicNodes] Failed to resolve create scope",
1563
+ {
1564
+ error,
1565
+ topicId: args.topicId,
1566
+ projectId: args.projectId
1567
+ }
1568
+ );
1569
+ return void 0;
1570
+ }
1571
+ })() : void 0;
2299
1572
  const contentHash = args.contentHash || `${args.nodeType}:${args.canonicalText}`.slice(0, 64);
2300
1573
  const epistemicLayer = args.epistemicLayer || getNodeLayer(args.nodeType);
2301
1574
  const nodeId = await ctx.db.insert("epistemicNodes", {
@@ -2362,7 +1635,7 @@ var updateSyncStatus = internalMutation({
2362
1635
  updates.syncError = args.syncError;
2363
1636
  }
2364
1637
  await ctx.db.patch(args.nodeId, updates);
2365
- return { success: true };
1638
+ return buildNodeStatusSuccessResult();
2366
1639
  }
2367
1640
  });
2368
1641
  var backfillTopicId = internalMutation({