@lucern/graph-primitives 1.0.48 → 1.0.52

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 +2 -2
  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
@@ -5,7 +5,7 @@ import { v } from 'convex/values';
5
5
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
6
6
  import { componentsGeneric, mutationGeneric, internalMutationGeneric } from 'convex/server';
7
7
  import '@lucern/access-control/audience';
8
- import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
8
+ import { generateGlobalId, assertUuidV7Identity, assertUuidV7Reference, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint, isUuidV7 } from '@lucern/contracts/ids';
9
9
  import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
10
10
  import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
11
11
  import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
@@ -432,6 +432,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
432
432
  ...resolverOverrides
433
433
  };
434
434
  }
435
+
436
+ // src/topicScope.ts
435
437
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
436
438
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
437
439
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -452,16 +454,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
452
454
  if (!node) {
453
455
  return null;
454
456
  }
455
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
457
+ const scopeKey = canonicalTopicGlobalId(node);
456
458
  if (!scopeKey) {
457
- return null;
459
+ throw new Error(
460
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
461
+ );
458
462
  }
463
+ const metadata = node.metadata ?? {};
459
464
  return {
460
465
  topicId: scopeKey,
461
466
  projectId: asMappedProjectId(node),
462
- source: "topic_node"
467
+ source: "topic_node",
468
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
469
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
463
470
  };
464
471
  }
472
+ function canonicalTopicGlobalId(node) {
473
+ const globalId = normalizeScopeValue(node.globalId);
474
+ if (globalId && isUuidV7(globalId)) {
475
+ return globalId;
476
+ }
477
+ const topicId = normalizeScopeValue(node.topicId);
478
+ return topicId && isUuidV7(topicId) ? topicId : null;
479
+ }
480
+ function requireUuidV7TopicScope(field, value) {
481
+ if (!isUuidV7(value)) {
482
+ throw new Error(
483
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
484
+ );
485
+ }
486
+ }
465
487
  function asMappedProjectId(topic) {
466
488
  if (!topic) {
467
489
  return;
@@ -483,200 +505,25 @@ function normalizeScopeValue(value) {
483
505
  const normalized = value.trim();
484
506
  return normalized.length > 0 ? normalized : void 0;
485
507
  }
486
- function pickPrimaryTopic(candidates) {
487
- return [...candidates].sort((a, b) => {
488
- const depthA = a.depth ?? 9999;
489
- const depthB = b.depth ?? 9999;
490
- if (depthA !== depthB) {
491
- return depthA - depthB;
492
- }
493
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
494
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
495
- if (createdA !== createdB) {
496
- return createdA - createdB;
497
- }
498
- return String(a.name || "").localeCompare(String(b.name || ""));
499
- })[0];
500
- }
501
- async function findTopicsByScopeAlias(ctx, scopeId) {
502
- const query = ctx.db.query("topics");
503
- try {
504
- return await query.withIndex(
505
- "by_graph_scope_project",
506
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
507
- ).collect();
508
- } catch (error) {
509
- debugGraphPrimitiveFallback(
510
- "[topicScope] Failed to resolve scope alias via index",
511
- {
512
- error,
513
- scopeId
514
- }
515
- );
516
- const topics = await query.collect();
517
- return topics.filter((topic) => {
518
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
519
- const mappedProjectId = asMappedProjectId(topic);
520
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
521
- });
522
- }
523
- }
524
- async function tryResolveHostTopicById(ctx, topicId) {
525
- if (typeof ctx.runQuery !== "function") {
526
- return null;
527
- }
528
- try {
529
- return await ctx.runQuery(api.topics.get, {
530
- id: topicId
531
- }) ?? null;
532
- } catch (error) {
533
- debugGraphPrimitiveFallback(
534
- "[topicScope] Failed to resolve topic by host query",
535
- {
536
- error,
537
- topicId
538
- }
539
- );
540
- return null;
541
- }
542
- }
543
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
544
- if (typeof ctx.runQuery !== "function") {
545
- return null;
546
- }
547
- try {
548
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
549
- projectId: legacyScopeId
550
- }) ?? null;
551
- } catch (error) {
552
- debugGraphPrimitiveFallback(
553
- "[topicScope] Failed to resolve topic by legacy scope",
554
- {
555
- error,
556
- legacyScopeId
557
- }
558
- );
559
- return null;
560
- }
561
- }
562
- async function resolveInheritedWorkspaceScope(ctx, topic) {
563
- const MAX_DEPTH = 10;
564
- let tenantId = normalizeScopeValue(topic.tenantId);
565
- let workspaceId = normalizeScopeValue(topic.workspaceId);
566
- if (tenantId && workspaceId) {
567
- return { tenantId, workspaceId };
568
- }
569
- let current = topic;
570
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
571
- current = await ctx.db.get(current.parentTopicId);
572
- if (!current) {
573
- break;
574
- }
575
- if (!tenantId) {
576
- tenantId = normalizeScopeValue(current.tenantId);
577
- }
578
- if (!workspaceId) {
579
- workspaceId = normalizeScopeValue(current.workspaceId);
580
- }
581
- if (tenantId && workspaceId) {
582
- break;
583
- }
584
- }
585
- return { tenantId, workspaceId };
586
- }
587
508
  async function resolveTopicProjectScope(ctx, args) {
588
509
  if (args.topicId) {
589
510
  return await resolveScopeFromTopicId(ctx, args.topicId);
590
511
  }
591
512
  if (args.projectId) {
592
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
513
+ throw new Error(
514
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
515
+ );
593
516
  }
594
- throw new Error(
595
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
596
- );
517
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
597
518
  }
598
519
  async function resolveScopeFromTopicId(ctx, topicId) {
599
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
600
- if (topic) {
601
- return await buildTopicScope(ctx, topic, "topic");
602
- }
603
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
520
+ const topicGlobalId = String(topicId);
521
+ requireUuidV7TopicScope("topicId", topicGlobalId);
522
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
604
523
  if (nodeScope) {
605
524
  return nodeScope;
606
525
  }
607
- throw new Error(`Topic not found: ${String(topicId)}`);
608
- }
609
- async function resolveTopicDocFromTopicId(ctx, topicId) {
610
- const direct = await tryReadTopicDoc(ctx, topicId, {
611
- failureLog: "[topicScope] Failed to load topic by direct id",
612
- idLogKey: "topicId"
613
- });
614
- if (direct) {
615
- return direct;
616
- }
617
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
618
- if (hostTopic) {
619
- return hostTopic;
620
- }
621
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
622
- }
623
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
624
- const directTopic = await resolveDirectLegacyProjectTopic(
625
- ctx,
626
- legacyProjectId
627
- );
628
- if (directTopic) {
629
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
630
- fallbackProjectId: legacyProjectId
631
- });
632
- }
633
- const primary = pickPrimaryTopic(
634
- await findTopicsByScopeAlias(ctx, legacyProjectId)
635
- );
636
- if (primary) {
637
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
638
- fallbackProjectId: legacyProjectId
639
- });
640
- }
641
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
642
- if (nodeScope) {
643
- return {
644
- ...nodeScope,
645
- projectId: nodeScope.projectId ?? legacyProjectId
646
- };
647
- }
648
- throw new Error(
649
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
650
- );
651
- }
652
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
653
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
654
- failureLog: "[topicScope] Failed to load direct project topic",
655
- idLogKey: "projectId"
656
- });
657
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
658
- }
659
- async function tryReadTopicDoc(ctx, id, log) {
660
- try {
661
- return await ctx.db.get(id);
662
- } catch (error) {
663
- debugGraphPrimitiveFallback(log.failureLog, {
664
- error,
665
- [log.idLogKey]: id
666
- });
667
- return null;
668
- }
669
- }
670
- async function buildTopicScope(ctx, topic, source, options = {}) {
671
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
672
- const mapped = asMappedProjectId(topic);
673
- return {
674
- topicId: topic._id,
675
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
676
- tenantId: inherited.tenantId,
677
- workspaceId: inherited.workspaceId,
678
- source
679
- };
526
+ throw new Error(`Topic not found: ${topicGlobalId}`);
680
527
  }
681
528
  var optionalScopeArgs = {
682
529
  projectId: v.optional(v.string()),
@@ -775,6 +622,9 @@ async function resolveEvidenceScopeOrNull(ctx, args) {
775
622
  }
776
623
  function insertEpistemicNode(ctx, doc) {
777
624
  assertUuidV7Identity("epistemicNodes", doc.globalId);
625
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
626
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
627
+ }
778
628
  return ctx.db.insert("epistemicNodes", doc);
779
629
  }
780
630
  async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
@@ -5,6 +5,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
5
5
  import { v } from 'convex/values';
6
6
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
7
7
  import { componentsGeneric, queryGeneric, internalQueryGeneric } from 'convex/server';
8
+ import { isUuidV7 } from '@lucern/contracts/ids';
8
9
  import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
9
10
 
10
11
  // src/epistemicEvidenceQueries.ts
@@ -402,6 +403,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
402
403
  ...resolverOverrides
403
404
  };
404
405
  }
406
+
407
+ // src/topicScope.ts
405
408
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
406
409
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
407
410
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -422,16 +425,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
422
425
  if (!node) {
423
426
  return null;
424
427
  }
425
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
428
+ const scopeKey = canonicalTopicGlobalId(node);
426
429
  if (!scopeKey) {
427
- return null;
430
+ throw new Error(
431
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
432
+ );
428
433
  }
434
+ const metadata = node.metadata ?? {};
429
435
  return {
430
436
  topicId: scopeKey,
431
437
  projectId: asMappedProjectId(node),
432
- source: "topic_node"
438
+ source: "topic_node",
439
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
440
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
433
441
  };
434
442
  }
443
+ function canonicalTopicGlobalId(node) {
444
+ const globalId = normalizeScopeValue(node.globalId);
445
+ if (globalId && isUuidV7(globalId)) {
446
+ return globalId;
447
+ }
448
+ const topicId = normalizeScopeValue(node.topicId);
449
+ return topicId && isUuidV7(topicId) ? topicId : null;
450
+ }
451
+ function requireUuidV7TopicScope(field, value) {
452
+ if (!isUuidV7(value)) {
453
+ throw new Error(
454
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
455
+ );
456
+ }
457
+ }
435
458
  function asMappedProjectId(topic) {
436
459
  if (!topic) {
437
460
  return;
@@ -453,200 +476,25 @@ function normalizeScopeValue(value) {
453
476
  const normalized = value.trim();
454
477
  return normalized.length > 0 ? normalized : void 0;
455
478
  }
456
- function pickPrimaryTopic(candidates) {
457
- return [...candidates].sort((a, b) => {
458
- const depthA = a.depth ?? 9999;
459
- const depthB = b.depth ?? 9999;
460
- if (depthA !== depthB) {
461
- return depthA - depthB;
462
- }
463
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
464
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
465
- if (createdA !== createdB) {
466
- return createdA - createdB;
467
- }
468
- return String(a.name || "").localeCompare(String(b.name || ""));
469
- })[0];
470
- }
471
- async function findTopicsByScopeAlias(ctx, scopeId) {
472
- const query2 = ctx.db.query("topics");
473
- try {
474
- return await query2.withIndex(
475
- "by_graph_scope_project",
476
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
477
- ).collect();
478
- } catch (error) {
479
- debugGraphPrimitiveFallback(
480
- "[topicScope] Failed to resolve scope alias via index",
481
- {
482
- error,
483
- scopeId
484
- }
485
- );
486
- const topics = await query2.collect();
487
- return topics.filter((topic) => {
488
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
489
- const mappedProjectId = asMappedProjectId(topic);
490
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
491
- });
492
- }
493
- }
494
- async function tryResolveHostTopicById(ctx, topicId) {
495
- if (typeof ctx.runQuery !== "function") {
496
- return null;
497
- }
498
- try {
499
- return await ctx.runQuery(api.topics.get, {
500
- id: topicId
501
- }) ?? null;
502
- } catch (error) {
503
- debugGraphPrimitiveFallback(
504
- "[topicScope] Failed to resolve topic by host query",
505
- {
506
- error,
507
- topicId
508
- }
509
- );
510
- return null;
511
- }
512
- }
513
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
514
- if (typeof ctx.runQuery !== "function") {
515
- return null;
516
- }
517
- try {
518
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
519
- projectId: legacyScopeId
520
- }) ?? null;
521
- } catch (error) {
522
- debugGraphPrimitiveFallback(
523
- "[topicScope] Failed to resolve topic by legacy scope",
524
- {
525
- error,
526
- legacyScopeId
527
- }
528
- );
529
- return null;
530
- }
531
- }
532
- async function resolveInheritedWorkspaceScope(ctx, topic) {
533
- const MAX_DEPTH = 10;
534
- let tenantId = normalizeScopeValue(topic.tenantId);
535
- let workspaceId = normalizeScopeValue(topic.workspaceId);
536
- if (tenantId && workspaceId) {
537
- return { tenantId, workspaceId };
538
- }
539
- let current = topic;
540
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
541
- current = await ctx.db.get(current.parentTopicId);
542
- if (!current) {
543
- break;
544
- }
545
- if (!tenantId) {
546
- tenantId = normalizeScopeValue(current.tenantId);
547
- }
548
- if (!workspaceId) {
549
- workspaceId = normalizeScopeValue(current.workspaceId);
550
- }
551
- if (tenantId && workspaceId) {
552
- break;
553
- }
554
- }
555
- return { tenantId, workspaceId };
556
- }
557
479
  async function resolveTopicProjectScope(ctx, args) {
558
480
  if (args.topicId) {
559
481
  return await resolveScopeFromTopicId(ctx, args.topicId);
560
482
  }
561
483
  if (args.projectId) {
562
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
484
+ throw new Error(
485
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
486
+ );
563
487
  }
564
- throw new Error(
565
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
566
- );
488
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
567
489
  }
568
490
  async function resolveScopeFromTopicId(ctx, topicId) {
569
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
570
- if (topic) {
571
- return await buildTopicScope(ctx, topic, "topic");
572
- }
573
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
491
+ const topicGlobalId = String(topicId);
492
+ requireUuidV7TopicScope("topicId", topicGlobalId);
493
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
574
494
  if (nodeScope) {
575
495
  return nodeScope;
576
496
  }
577
- throw new Error(`Topic not found: ${String(topicId)}`);
578
- }
579
- async function resolveTopicDocFromTopicId(ctx, topicId) {
580
- const direct = await tryReadTopicDoc(ctx, topicId, {
581
- failureLog: "[topicScope] Failed to load topic by direct id",
582
- idLogKey: "topicId"
583
- });
584
- if (direct) {
585
- return direct;
586
- }
587
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
588
- if (hostTopic) {
589
- return hostTopic;
590
- }
591
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
592
- }
593
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
594
- const directTopic = await resolveDirectLegacyProjectTopic(
595
- ctx,
596
- legacyProjectId
597
- );
598
- if (directTopic) {
599
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
600
- fallbackProjectId: legacyProjectId
601
- });
602
- }
603
- const primary = pickPrimaryTopic(
604
- await findTopicsByScopeAlias(ctx, legacyProjectId)
605
- );
606
- if (primary) {
607
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
608
- fallbackProjectId: legacyProjectId
609
- });
610
- }
611
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
612
- if (nodeScope) {
613
- return {
614
- ...nodeScope,
615
- projectId: nodeScope.projectId ?? legacyProjectId
616
- };
617
- }
618
- throw new Error(
619
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
620
- );
621
- }
622
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
623
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
624
- failureLog: "[topicScope] Failed to load direct project topic",
625
- idLogKey: "projectId"
626
- });
627
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
628
- }
629
- async function tryReadTopicDoc(ctx, id, log) {
630
- try {
631
- return await ctx.db.get(id);
632
- } catch (error) {
633
- debugGraphPrimitiveFallback(log.failureLog, {
634
- error,
635
- [log.idLogKey]: id
636
- });
637
- return null;
638
- }
639
- }
640
- async function buildTopicScope(ctx, topic, source, options = {}) {
641
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
642
- const mapped = asMappedProjectId(topic);
643
- return {
644
- topicId: topic._id,
645
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
646
- tenantId: inherited.tenantId,
647
- workspaceId: inherited.workspaceId,
648
- source
649
- };
497
+ throw new Error(`Topic not found: ${topicGlobalId}`);
650
498
  }
651
499
  var optionalScopeArgs = {
652
500
  projectId: v.optional(v.string()),
@@ -1,6 +1,6 @@
1
1
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
2
2
  import { componentsGeneric } from 'convex/server';
3
- import { generateGlobalId, assertUuidV7Identity } from '@lucern/contracts/ids';
3
+ import { generateGlobalId, assertUuidV7Identity, assertUuidV7Reference } from '@lucern/contracts/ids';
4
4
  import '@lucern/contracts/manifests/edge-policy-manifest';
5
5
  import '@lucern/contracts/manifests/edge-policy-manifest.data';
6
6
 
@@ -653,6 +653,9 @@ function normalizeLegacyNodeType(legacyType) {
653
653
  }
654
654
  function insertEpistemicNode(ctx, doc) {
655
655
  assertUuidV7Identity("epistemicNodes", doc.globalId);
656
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
657
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
658
+ }
656
659
  return ctx.db.insert("epistemicNodes", doc);
657
660
  }
658
661
 
@@ -1,10 +1,13 @@
1
- import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
1
+ import { assertUuidV7Identity, assertUuidV7Reference, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
2
2
  import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
3
3
  import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
4
4
 
5
5
  // src/epistemicInsert.ts
6
6
  function insertEpistemicNode(ctx, doc) {
7
7
  assertUuidV7Identity("epistemicNodes", doc.globalId);
8
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
9
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
10
+ }
8
11
  return ctx.db.insert("epistemicNodes", doc);
9
12
  }
10
13
  async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
@@ -1,6 +1,6 @@
1
1
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
2
2
  import { componentsGeneric } from 'convex/server';
3
- import { generateGlobalId, assertUuidV7Identity } from '@lucern/contracts/ids';
3
+ import { generateGlobalId, assertUuidV7Identity, assertUuidV7Reference } from '@lucern/contracts/ids';
4
4
  import '@lucern/contracts/manifests/edge-policy-manifest';
5
5
  import '@lucern/contracts/manifests/edge-policy-manifest.data';
6
6
 
@@ -435,6 +435,9 @@ async function createEpistemicEdge(ctx, params) {
435
435
  }
436
436
  function insertEpistemicNode(ctx, doc) {
437
437
  assertUuidV7Identity("epistemicNodes", doc.globalId);
438
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
439
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
440
+ }
438
441
  return ctx.db.insert("epistemicNodes", doc);
439
442
  }
440
443
 
@@ -1,5 +1,5 @@
1
1
  import { Id, QueryCtx } from './convex.js';
2
- import { r as resolveTopicProjectScope } from './topicScope-DJVa0mLa.js';
2
+ import { r as resolveTopicProjectScope } from './topicScope-CL1IVOmv.js';
3
3
  import '@lucern/access-control/convex';
4
4
  import '@lucern/contracts/convex/unsafeAnyApi';
5
5
  import 'convex/values';