@lucern/graph-primitives 1.0.50 → 1.0.53

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 (67) hide show
  1. package/dist/beliefDecay.js +34 -186
  2. package/dist/beliefEvidenceLinks.js +32 -187
  3. package/dist/contradictions.js +34 -187
  4. package/dist/entityLifecycle.js +88 -205
  5. package/dist/epistemicAnswers.js +35 -187
  6. package/dist/epistemicBeliefs.admin.js +34 -187
  7. package/dist/epistemicBeliefs.backfills.js +34 -188
  8. package/dist/epistemicBeliefs.confidence.d.ts +1 -1
  9. package/dist/epistemicBeliefs.confidence.js +32 -188
  10. package/dist/epistemicBeliefs.core.js +37 -187
  11. package/dist/epistemicBeliefs.d.ts +1 -1
  12. package/dist/epistemicBeliefs.helpers.d.ts +1 -1
  13. package/dist/epistemicBeliefs.helpers.js +34 -186
  14. package/dist/epistemicBeliefs.internal.js +37 -187
  15. package/dist/epistemicBeliefs.js +37 -187
  16. package/dist/epistemicBeliefs.lifecycle.js +32 -188
  17. package/dist/epistemicBeliefs.links.js +34 -188
  18. package/dist/epistemicContracts.evaluators.js +34 -188
  19. package/dist/epistemicContracts.handlers.js +34 -188
  20. package/dist/epistemicContracts.js +34 -188
  21. package/dist/epistemicEdges.d.ts +1 -1
  22. package/dist/epistemicEdges.helpers.d.ts +1 -1
  23. package/dist/epistemicEdges.js +32 -187
  24. package/dist/epistemicEdges.mutations.js +34 -186
  25. package/dist/epistemicEdges.queries.js +35 -188
  26. package/dist/epistemicEdges.types.d.ts +1 -1
  27. package/dist/epistemicEvidence.d.ts +1 -1
  28. package/dist/epistemicEvidence.js +37 -187
  29. package/dist/epistemicEvidenceHelpers.d.ts +1 -1
  30. package/dist/epistemicEvidenceHelpers.js +34 -186
  31. package/dist/epistemicEvidenceMutations.js +37 -187
  32. package/dist/epistemicEvidenceQueries.js +34 -186
  33. package/dist/epistemicHelpers.js +4 -1
  34. package/dist/epistemicInsert.js +4 -1
  35. package/dist/epistemicNodeCreation.js +4 -1
  36. package/dist/epistemicNodes.helpers.d.ts +1 -1
  37. package/dist/epistemicNodes.internal.js +35 -188
  38. package/dist/epistemicNodes.js +37 -188
  39. package/dist/epistemicNodes.mutations.js +35 -188
  40. package/dist/epistemicNodes.queries.js +35 -188
  41. package/dist/epistemicQuestions.conviction.js +34 -186
  42. package/dist/epistemicQuestions.create.js +37 -187
  43. package/dist/epistemicQuestions.d.ts +1 -1
  44. package/dist/epistemicQuestions.evidence.js +37 -187
  45. package/dist/epistemicQuestions.helpers.d.ts +1 -1
  46. package/dist/epistemicQuestions.helpers.js +34 -186
  47. package/dist/epistemicQuestions.js +37 -187
  48. package/dist/epistemicQuestions.lifecycle.js +34 -186
  49. package/dist/epistemicQuestions.queries.js +34 -186
  50. package/dist/epistemicQuestions.sprint.js +35 -188
  51. package/dist/epistemicQuestions.tail.js +37 -187
  52. package/dist/epistemicSources.js +35 -188
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +98 -213
  55. package/dist/proof-attestation.json +1 -1
  56. package/dist/questionEvidenceLinks.js +34 -187
  57. package/dist/scopeResolverCompat.d.ts +1 -1
  58. package/dist/scopeResolverCompat.js +32 -193
  59. package/dist/topicOntologyResolver.d.ts +3 -3
  60. package/dist/topicOntologyResolver.js +57 -18
  61. package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
  62. package/dist/topicScope.d.ts +1 -1
  63. package/dist/topicScope.js +32 -193
  64. package/dist/workflowBridge.js +32 -193
  65. package/dist/workspaceIsolation.d.ts +1 -1
  66. package/dist/workspaceIsolation.js +32 -193
  67. package/package.json +4 -4
@@ -2,7 +2,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
2
2
  import { v } from 'convex/values';
3
3
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
4
4
  import { componentsGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
5
- import { assertUuidV7Identity } from '@lucern/contracts/ids';
5
+ import { assertUuidV7Identity, assertUuidV7Reference, isUuidV7 } from '@lucern/contracts/ids';
6
6
  import '@lucern/contracts/manifests/edge-policy-manifest';
7
7
  import '@lucern/contracts/manifests/edge-policy-manifest.data';
8
8
 
@@ -10,7 +10,6 @@ import '@lucern/contracts/manifests/edge-policy-manifest.data';
10
10
  var unsafeApi = unsafeConvexAnyApi(
11
11
  "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
12
12
  );
13
- var api = unsafeApi;
14
13
  componentsGeneric();
15
14
  var internal = unsafeApi;
16
15
  var internalMutation = internalMutationGeneric;
@@ -29,6 +28,9 @@ function debugGraphPrimitiveFallback(message, context) {
29
28
  }
30
29
  function insertEpistemicNode(ctx, doc) {
31
30
  assertUuidV7Identity("epistemicNodes", doc.globalId);
31
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
32
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
33
+ }
32
34
  return ctx.db.insert("epistemicNodes", doc);
33
35
  }
34
36
 
@@ -241,16 +243,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
241
243
  if (!node) {
242
244
  return null;
243
245
  }
244
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
246
+ const scopeKey = canonicalTopicGlobalId(node);
245
247
  if (!scopeKey) {
246
- return null;
248
+ throw new Error(
249
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
250
+ );
247
251
  }
252
+ const metadata = node.metadata ?? {};
248
253
  return {
249
254
  topicId: scopeKey,
250
255
  projectId: asMappedProjectId(node),
251
- source: "topic_node"
256
+ source: "topic_node",
257
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
258
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
252
259
  };
253
260
  }
261
+ function canonicalTopicGlobalId(node) {
262
+ const globalId = normalizeScopeValue(node.globalId);
263
+ if (globalId && isUuidV7(globalId)) {
264
+ return globalId;
265
+ }
266
+ const topicId = normalizeScopeValue(node.topicId);
267
+ return topicId && isUuidV7(topicId) ? topicId : null;
268
+ }
269
+ function requireUuidV7TopicScope(field, value) {
270
+ if (!isUuidV7(value)) {
271
+ throw new Error(
272
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
273
+ );
274
+ }
275
+ }
254
276
  function asMappedProjectId(topic) {
255
277
  if (!topic) {
256
278
  return;
@@ -272,200 +294,25 @@ function normalizeScopeValue(value) {
272
294
  const normalized = value.trim();
273
295
  return normalized.length > 0 ? normalized : void 0;
274
296
  }
275
- function pickPrimaryTopic(candidates) {
276
- return [...candidates].sort((a, b) => {
277
- const depthA = a.depth ?? 9999;
278
- const depthB = b.depth ?? 9999;
279
- if (depthA !== depthB) {
280
- return depthA - depthB;
281
- }
282
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
283
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
284
- if (createdA !== createdB) {
285
- return createdA - createdB;
286
- }
287
- return String(a.name || "").localeCompare(String(b.name || ""));
288
- })[0];
289
- }
290
- async function findTopicsByScopeAlias(ctx, scopeId) {
291
- const query = ctx.db.query("topics");
292
- try {
293
- return await query.withIndex(
294
- "by_graph_scope_project",
295
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
296
- ).collect();
297
- } catch (error) {
298
- debugGraphPrimitiveFallback(
299
- "[topicScope] Failed to resolve scope alias via index",
300
- {
301
- error,
302
- scopeId
303
- }
304
- );
305
- const topics = await query.collect();
306
- return topics.filter((topic) => {
307
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
308
- const mappedProjectId = asMappedProjectId(topic);
309
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
310
- });
311
- }
312
- }
313
- async function tryResolveHostTopicById(ctx, topicId) {
314
- if (typeof ctx.runQuery !== "function") {
315
- return null;
316
- }
317
- try {
318
- return await ctx.runQuery(api.topics.get, {
319
- id: topicId
320
- }) ?? null;
321
- } catch (error) {
322
- debugGraphPrimitiveFallback(
323
- "[topicScope] Failed to resolve topic by host query",
324
- {
325
- error,
326
- topicId
327
- }
328
- );
329
- return null;
330
- }
331
- }
332
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
333
- if (typeof ctx.runQuery !== "function") {
334
- return null;
335
- }
336
- try {
337
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
338
- projectId: legacyScopeId
339
- }) ?? null;
340
- } catch (error) {
341
- debugGraphPrimitiveFallback(
342
- "[topicScope] Failed to resolve topic by legacy scope",
343
- {
344
- error,
345
- legacyScopeId
346
- }
347
- );
348
- return null;
349
- }
350
- }
351
- async function resolveInheritedWorkspaceScope(ctx, topic) {
352
- const MAX_DEPTH = 10;
353
- let tenantId = normalizeScopeValue(topic.tenantId);
354
- let workspaceId = normalizeScopeValue(topic.workspaceId);
355
- if (tenantId && workspaceId) {
356
- return { tenantId, workspaceId };
357
- }
358
- let current = topic;
359
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
360
- current = await ctx.db.get(current.parentTopicId);
361
- if (!current) {
362
- break;
363
- }
364
- if (!tenantId) {
365
- tenantId = normalizeScopeValue(current.tenantId);
366
- }
367
- if (!workspaceId) {
368
- workspaceId = normalizeScopeValue(current.workspaceId);
369
- }
370
- if (tenantId && workspaceId) {
371
- break;
372
- }
373
- }
374
- return { tenantId, workspaceId };
375
- }
376
297
  async function resolveTopicProjectScope(ctx, args) {
377
298
  if (args.topicId) {
378
299
  return await resolveScopeFromTopicId(ctx, args.topicId);
379
300
  }
380
301
  if (args.projectId) {
381
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
302
+ throw new Error(
303
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
304
+ );
382
305
  }
383
- throw new Error(
384
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
385
- );
306
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
386
307
  }
387
308
  async function resolveScopeFromTopicId(ctx, topicId) {
388
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
389
- if (topic) {
390
- return await buildTopicScope(ctx, topic, "topic");
391
- }
392
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
309
+ const topicGlobalId = String(topicId);
310
+ requireUuidV7TopicScope("topicId", topicGlobalId);
311
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
393
312
  if (nodeScope) {
394
313
  return nodeScope;
395
314
  }
396
- throw new Error(`Topic not found: ${String(topicId)}`);
397
- }
398
- async function resolveTopicDocFromTopicId(ctx, topicId) {
399
- const direct = await tryReadTopicDoc(ctx, topicId, {
400
- failureLog: "[topicScope] Failed to load topic by direct id",
401
- idLogKey: "topicId"
402
- });
403
- if (direct) {
404
- return direct;
405
- }
406
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
407
- if (hostTopic) {
408
- return hostTopic;
409
- }
410
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
411
- }
412
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
413
- const directTopic = await resolveDirectLegacyProjectTopic(
414
- ctx,
415
- legacyProjectId
416
- );
417
- if (directTopic) {
418
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
419
- fallbackProjectId: legacyProjectId
420
- });
421
- }
422
- const primary = pickPrimaryTopic(
423
- await findTopicsByScopeAlias(ctx, legacyProjectId)
424
- );
425
- if (primary) {
426
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
427
- fallbackProjectId: legacyProjectId
428
- });
429
- }
430
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
431
- if (nodeScope) {
432
- return {
433
- ...nodeScope,
434
- projectId: nodeScope.projectId ?? legacyProjectId
435
- };
436
- }
437
- throw new Error(
438
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
439
- );
440
- }
441
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
442
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
443
- failureLog: "[topicScope] Failed to load direct project topic",
444
- idLogKey: "projectId"
445
- });
446
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
447
- }
448
- async function tryReadTopicDoc(ctx, id, log) {
449
- try {
450
- return await ctx.db.get(id);
451
- } catch (error) {
452
- debugGraphPrimitiveFallback(log.failureLog, {
453
- error,
454
- [log.idLogKey]: id
455
- });
456
- return null;
457
- }
458
- }
459
- async function buildTopicScope(ctx, topic, source, options = {}) {
460
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
461
- const mapped = asMappedProjectId(topic);
462
- return {
463
- topicId: topic._id,
464
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
465
- tenantId: inherited.tenantId,
466
- workspaceId: inherited.workspaceId,
467
- source
468
- };
315
+ throw new Error(`Topic not found: ${topicGlobalId}`);
469
316
  }
470
317
  var optionalScopeArgs = {
471
318
  projectId: v.optional(v.string()),
@@ -3,7 +3,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
3
3
  import { v } from 'convex/values';
4
4
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
5
5
  import { componentsGeneric, queryGeneric, internalMutationGeneric, internalQueryGeneric, mutationGeneric } from 'convex/server';
6
- import { assertUuidV7Identity } from '@lucern/contracts/ids';
6
+ import { assertUuidV7Identity, assertUuidV7Reference, isUuidV7 } from '@lucern/contracts/ids';
7
7
  import '@lucern/contracts/manifests/edge-policy-manifest';
8
8
  import '@lucern/contracts/manifests/edge-policy-manifest.data';
9
9
  import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
@@ -12,7 +12,6 @@ import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helper
12
12
  var unsafeApi = unsafeConvexAnyApi(
13
13
  "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
14
14
  );
15
- var api = unsafeApi;
16
15
  componentsGeneric();
17
16
  var internal = unsafeApi;
18
17
  var internalMutation = internalMutationGeneric;
@@ -199,6 +198,8 @@ var verificationStatusValidator = v.union(
199
198
  v.literal("contradicted"),
200
199
  v.literal("outdated")
201
200
  );
201
+
202
+ // src/topicScope.ts
202
203
  var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
203
204
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
204
205
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -219,16 +220,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
219
220
  if (!node) {
220
221
  return null;
221
222
  }
222
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
223
+ const scopeKey = canonicalTopicGlobalId(node);
223
224
  if (!scopeKey) {
224
- return null;
225
+ throw new Error(
226
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
227
+ );
225
228
  }
229
+ const metadata = node.metadata ?? {};
226
230
  return {
227
231
  topicId: scopeKey,
228
232
  projectId: asMappedProjectId(node),
229
- source: "topic_node"
233
+ source: "topic_node",
234
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
235
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
230
236
  };
231
237
  }
238
+ function canonicalTopicGlobalId(node) {
239
+ const globalId = normalizeScopeValue(node.globalId);
240
+ if (globalId && isUuidV7(globalId)) {
241
+ return globalId;
242
+ }
243
+ const topicId = normalizeScopeValue(node.topicId);
244
+ return topicId && isUuidV7(topicId) ? topicId : null;
245
+ }
246
+ function requireUuidV7TopicScope(field, value) {
247
+ if (!isUuidV7(value)) {
248
+ throw new Error(
249
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
250
+ );
251
+ }
252
+ }
232
253
  function asMappedProjectId(topic) {
233
254
  if (!topic) {
234
255
  return;
@@ -250,200 +271,25 @@ function normalizeScopeValue(value) {
250
271
  const normalized = value.trim();
251
272
  return normalized.length > 0 ? normalized : void 0;
252
273
  }
253
- function pickPrimaryTopic(candidates) {
254
- return [...candidates].sort((a, b) => {
255
- const depthA = a.depth ?? 9999;
256
- const depthB = b.depth ?? 9999;
257
- if (depthA !== depthB) {
258
- return depthA - depthB;
259
- }
260
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
261
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
262
- if (createdA !== createdB) {
263
- return createdA - createdB;
264
- }
265
- return String(a.name || "").localeCompare(String(b.name || ""));
266
- })[0];
267
- }
268
- async function findTopicsByScopeAlias(ctx, scopeId) {
269
- const query2 = ctx.db.query("topics");
270
- try {
271
- return await query2.withIndex(
272
- "by_graph_scope_project",
273
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
274
- ).collect();
275
- } catch (error) {
276
- debugGraphPrimitiveFallback(
277
- "[topicScope] Failed to resolve scope alias via index",
278
- {
279
- error,
280
- scopeId
281
- }
282
- );
283
- const topics = await query2.collect();
284
- return topics.filter((topic) => {
285
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
286
- const mappedProjectId = asMappedProjectId(topic);
287
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
288
- });
289
- }
290
- }
291
- async function tryResolveHostTopicById(ctx, topicId) {
292
- if (typeof ctx.runQuery !== "function") {
293
- return null;
294
- }
295
- try {
296
- return await ctx.runQuery(api.topics.get, {
297
- id: topicId
298
- }) ?? null;
299
- } catch (error) {
300
- debugGraphPrimitiveFallback(
301
- "[topicScope] Failed to resolve topic by host query",
302
- {
303
- error,
304
- topicId
305
- }
306
- );
307
- return null;
308
- }
309
- }
310
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
311
- if (typeof ctx.runQuery !== "function") {
312
- return null;
313
- }
314
- try {
315
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
316
- projectId: legacyScopeId
317
- }) ?? null;
318
- } catch (error) {
319
- debugGraphPrimitiveFallback(
320
- "[topicScope] Failed to resolve topic by legacy scope",
321
- {
322
- error,
323
- legacyScopeId
324
- }
325
- );
326
- return null;
327
- }
328
- }
329
- async function resolveInheritedWorkspaceScope(ctx, topic) {
330
- const MAX_DEPTH = 10;
331
- let tenantId = normalizeScopeValue(topic.tenantId);
332
- let workspaceId = normalizeScopeValue(topic.workspaceId);
333
- if (tenantId && workspaceId) {
334
- return { tenantId, workspaceId };
335
- }
336
- let current = topic;
337
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
338
- current = await ctx.db.get(current.parentTopicId);
339
- if (!current) {
340
- break;
341
- }
342
- if (!tenantId) {
343
- tenantId = normalizeScopeValue(current.tenantId);
344
- }
345
- if (!workspaceId) {
346
- workspaceId = normalizeScopeValue(current.workspaceId);
347
- }
348
- if (tenantId && workspaceId) {
349
- break;
350
- }
351
- }
352
- return { tenantId, workspaceId };
353
- }
354
274
  async function resolveTopicProjectScope(ctx, args) {
355
275
  if (args.topicId) {
356
276
  return await resolveScopeFromTopicId(ctx, args.topicId);
357
277
  }
358
278
  if (args.projectId) {
359
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
279
+ throw new Error(
280
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
281
+ );
360
282
  }
361
- throw new Error(
362
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
363
- );
283
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
364
284
  }
365
285
  async function resolveScopeFromTopicId(ctx, topicId) {
366
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
367
- if (topic) {
368
- return await buildTopicScope(ctx, topic, "topic");
369
- }
370
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
286
+ const topicGlobalId = String(topicId);
287
+ requireUuidV7TopicScope("topicId", topicGlobalId);
288
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
371
289
  if (nodeScope) {
372
290
  return nodeScope;
373
291
  }
374
- throw new Error(`Topic not found: ${String(topicId)}`);
375
- }
376
- async function resolveTopicDocFromTopicId(ctx, topicId) {
377
- const direct = await tryReadTopicDoc(ctx, topicId, {
378
- failureLog: "[topicScope] Failed to load topic by direct id",
379
- idLogKey: "topicId"
380
- });
381
- if (direct) {
382
- return direct;
383
- }
384
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
385
- if (hostTopic) {
386
- return hostTopic;
387
- }
388
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
389
- }
390
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
391
- const directTopic = await resolveDirectLegacyProjectTopic(
392
- ctx,
393
- legacyProjectId
394
- );
395
- if (directTopic) {
396
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
397
- fallbackProjectId: legacyProjectId
398
- });
399
- }
400
- const primary = pickPrimaryTopic(
401
- await findTopicsByScopeAlias(ctx, legacyProjectId)
402
- );
403
- if (primary) {
404
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
405
- fallbackProjectId: legacyProjectId
406
- });
407
- }
408
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
409
- if (nodeScope) {
410
- return {
411
- ...nodeScope,
412
- projectId: nodeScope.projectId ?? legacyProjectId
413
- };
414
- }
415
- throw new Error(
416
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
417
- );
418
- }
419
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
420
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
421
- failureLog: "[topicScope] Failed to load direct project topic",
422
- idLogKey: "projectId"
423
- });
424
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
425
- }
426
- async function tryReadTopicDoc(ctx, id, log) {
427
- try {
428
- return await ctx.db.get(id);
429
- } catch (error) {
430
- debugGraphPrimitiveFallback(log.failureLog, {
431
- error,
432
- [log.idLogKey]: id
433
- });
434
- return null;
435
- }
436
- }
437
- async function buildTopicScope(ctx, topic, source, options = {}) {
438
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
439
- const mapped = asMappedProjectId(topic);
440
- return {
441
- topicId: topic._id,
442
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
443
- tenantId: inherited.tenantId,
444
- workspaceId: inherited.workspaceId,
445
- source
446
- };
292
+ throw new Error(`Topic not found: ${topicGlobalId}`);
447
293
  }
448
294
  var optionalScopeArgs = {
449
295
  projectId: v.optional(v.string()),
@@ -687,6 +533,9 @@ var search = query({
687
533
  });
688
534
  function insertEpistemicNode(ctx, doc) {
689
535
  assertUuidV7Identity("epistemicNodes", doc.globalId);
536
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
537
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
538
+ }
690
539
  return ctx.db.insert("epistemicNodes", doc);
691
540
  }
692
541