@lucern/graph-primitives 1.0.50 → 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 +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
@@ -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, mutationGeneric } from 'convex/server';
6
- import { generateGlobalId, assertUuidV7Identity } from '@lucern/contracts/ids';
6
+ import { generateGlobalId, 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 '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
@@ -50,6 +50,9 @@ function debugGraphPrimitiveFallback(message, context) {
50
50
  }
51
51
  function insertEpistemicNode(ctx, doc) {
52
52
  assertUuidV7Identity("epistemicNodes", doc.globalId);
53
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
54
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
55
+ }
53
56
  return ctx.db.insert("epistemicNodes", doc);
54
57
  }
55
58
 
@@ -427,6 +430,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
427
430
  ...resolverOverrides
428
431
  };
429
432
  }
433
+
434
+ // src/topicScope.ts
430
435
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
431
436
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
432
437
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -447,16 +452,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
447
452
  if (!node) {
448
453
  return null;
449
454
  }
450
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
455
+ const scopeKey = canonicalTopicGlobalId(node);
451
456
  if (!scopeKey) {
452
- return null;
457
+ throw new Error(
458
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
459
+ );
453
460
  }
461
+ const metadata = node.metadata ?? {};
454
462
  return {
455
463
  topicId: scopeKey,
456
464
  projectId: asMappedProjectId(node),
457
- source: "topic_node"
465
+ source: "topic_node",
466
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
467
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
458
468
  };
459
469
  }
470
+ function canonicalTopicGlobalId(node) {
471
+ const globalId = normalizeScopeValue(node.globalId);
472
+ if (globalId && isUuidV7(globalId)) {
473
+ return globalId;
474
+ }
475
+ const topicId = normalizeScopeValue(node.topicId);
476
+ return topicId && isUuidV7(topicId) ? topicId : null;
477
+ }
478
+ function requireUuidV7TopicScope(field, value) {
479
+ if (!isUuidV7(value)) {
480
+ throw new Error(
481
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
482
+ );
483
+ }
484
+ }
460
485
  function asMappedProjectId(topic) {
461
486
  if (!topic) {
462
487
  return;
@@ -478,200 +503,25 @@ function normalizeScopeValue(value) {
478
503
  const normalized = value.trim();
479
504
  return normalized.length > 0 ? normalized : void 0;
480
505
  }
481
- function pickPrimaryTopic(candidates) {
482
- return [...candidates].sort((a, b) => {
483
- const depthA = a.depth ?? 9999;
484
- const depthB = b.depth ?? 9999;
485
- if (depthA !== depthB) {
486
- return depthA - depthB;
487
- }
488
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
489
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
490
- if (createdA !== createdB) {
491
- return createdA - createdB;
492
- }
493
- return String(a.name || "").localeCompare(String(b.name || ""));
494
- })[0];
495
- }
496
- async function findTopicsByScopeAlias(ctx, scopeId) {
497
- const query2 = ctx.db.query("topics");
498
- try {
499
- return await query2.withIndex(
500
- "by_graph_scope_project",
501
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
502
- ).collect();
503
- } catch (error) {
504
- debugGraphPrimitiveFallback(
505
- "[topicScope] Failed to resolve scope alias via index",
506
- {
507
- error,
508
- scopeId
509
- }
510
- );
511
- const topics = await query2.collect();
512
- return topics.filter((topic) => {
513
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
514
- const mappedProjectId = asMappedProjectId(topic);
515
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
516
- });
517
- }
518
- }
519
- async function tryResolveHostTopicById(ctx, topicId) {
520
- if (typeof ctx.runQuery !== "function") {
521
- return null;
522
- }
523
- try {
524
- return await ctx.runQuery(api.topics.get, {
525
- id: topicId
526
- }) ?? null;
527
- } catch (error) {
528
- debugGraphPrimitiveFallback(
529
- "[topicScope] Failed to resolve topic by host query",
530
- {
531
- error,
532
- topicId
533
- }
534
- );
535
- return null;
536
- }
537
- }
538
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
539
- if (typeof ctx.runQuery !== "function") {
540
- return null;
541
- }
542
- try {
543
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
544
- projectId: legacyScopeId
545
- }) ?? null;
546
- } catch (error) {
547
- debugGraphPrimitiveFallback(
548
- "[topicScope] Failed to resolve topic by legacy scope",
549
- {
550
- error,
551
- legacyScopeId
552
- }
553
- );
554
- return null;
555
- }
556
- }
557
- async function resolveInheritedWorkspaceScope(ctx, topic) {
558
- const MAX_DEPTH = 10;
559
- let tenantId = normalizeScopeValue(topic.tenantId);
560
- let workspaceId = normalizeScopeValue(topic.workspaceId);
561
- if (tenantId && workspaceId) {
562
- return { tenantId, workspaceId };
563
- }
564
- let current = topic;
565
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
566
- current = await ctx.db.get(current.parentTopicId);
567
- if (!current) {
568
- break;
569
- }
570
- if (!tenantId) {
571
- tenantId = normalizeScopeValue(current.tenantId);
572
- }
573
- if (!workspaceId) {
574
- workspaceId = normalizeScopeValue(current.workspaceId);
575
- }
576
- if (tenantId && workspaceId) {
577
- break;
578
- }
579
- }
580
- return { tenantId, workspaceId };
581
- }
582
506
  async function resolveTopicProjectScope(ctx, args) {
583
507
  if (args.topicId) {
584
508
  return await resolveScopeFromTopicId(ctx, args.topicId);
585
509
  }
586
510
  if (args.projectId) {
587
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
511
+ throw new Error(
512
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
513
+ );
588
514
  }
589
- throw new Error(
590
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
591
- );
515
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
592
516
  }
593
517
  async function resolveScopeFromTopicId(ctx, topicId) {
594
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
595
- if (topic) {
596
- return await buildTopicScope(ctx, topic, "topic");
597
- }
598
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
518
+ const topicGlobalId = String(topicId);
519
+ requireUuidV7TopicScope("topicId", topicGlobalId);
520
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
599
521
  if (nodeScope) {
600
522
  return nodeScope;
601
523
  }
602
- throw new Error(`Topic not found: ${String(topicId)}`);
603
- }
604
- async function resolveTopicDocFromTopicId(ctx, topicId) {
605
- const direct = await tryReadTopicDoc(ctx, topicId, {
606
- failureLog: "[topicScope] Failed to load topic by direct id",
607
- idLogKey: "topicId"
608
- });
609
- if (direct) {
610
- return direct;
611
- }
612
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
613
- if (hostTopic) {
614
- return hostTopic;
615
- }
616
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
617
- }
618
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
619
- const directTopic = await resolveDirectLegacyProjectTopic(
620
- ctx,
621
- legacyProjectId
622
- );
623
- if (directTopic) {
624
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
625
- fallbackProjectId: legacyProjectId
626
- });
627
- }
628
- const primary = pickPrimaryTopic(
629
- await findTopicsByScopeAlias(ctx, legacyProjectId)
630
- );
631
- if (primary) {
632
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
633
- fallbackProjectId: legacyProjectId
634
- });
635
- }
636
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
637
- if (nodeScope) {
638
- return {
639
- ...nodeScope,
640
- projectId: nodeScope.projectId ?? legacyProjectId
641
- };
642
- }
643
- throw new Error(
644
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
645
- );
646
- }
647
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
648
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
649
- failureLog: "[topicScope] Failed to load direct project topic",
650
- idLogKey: "projectId"
651
- });
652
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
653
- }
654
- async function tryReadTopicDoc(ctx, id, log) {
655
- try {
656
- return await ctx.db.get(id);
657
- } catch (error) {
658
- debugGraphPrimitiveFallback(log.failureLog, {
659
- error,
660
- [log.idLogKey]: id
661
- });
662
- return null;
663
- }
664
- }
665
- async function buildTopicScope(ctx, topic, source, options = {}) {
666
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
667
- const mapped = asMappedProjectId(topic);
668
- return {
669
- topicId: topic._id,
670
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
671
- tenantId: inherited.tenantId,
672
- workspaceId: inherited.workspaceId,
673
- source
674
- };
524
+ throw new Error(`Topic not found: ${topicGlobalId}`);
675
525
  }
676
526
  var optionalScopeArgs = {
677
527
  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, mutationGeneric, queryGeneric } from 'convex/server';
6
- import { generateGlobalId, assertUuidV7Identity } from '@lucern/contracts/ids';
6
+ import { generateGlobalId, 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 mutation = mutationGeneric;
@@ -57,6 +56,9 @@ async function scheduleEmbeddingGeneration(args) {
57
56
  }
58
57
  function insertEpistemicNode(ctx, doc) {
59
58
  assertUuidV7Identity("epistemicNodes", doc.globalId);
59
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
60
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
61
+ }
60
62
  return ctx.db.insert("epistemicNodes", doc);
61
63
  }
62
64
  var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
@@ -79,16 +81,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
79
81
  if (!node) {
80
82
  return null;
81
83
  }
82
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
84
+ const scopeKey = canonicalTopicGlobalId(node);
83
85
  if (!scopeKey) {
84
- return null;
86
+ throw new Error(
87
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
88
+ );
85
89
  }
90
+ const metadata = node.metadata ?? {};
86
91
  return {
87
92
  topicId: scopeKey,
88
93
  projectId: asMappedProjectId(node),
89
- source: "topic_node"
94
+ source: "topic_node",
95
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
96
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
90
97
  };
91
98
  }
99
+ function canonicalTopicGlobalId(node) {
100
+ const globalId = normalizeScopeValue(node.globalId);
101
+ if (globalId && isUuidV7(globalId)) {
102
+ return globalId;
103
+ }
104
+ const topicId = normalizeScopeValue(node.topicId);
105
+ return topicId && isUuidV7(topicId) ? topicId : null;
106
+ }
107
+ function requireUuidV7TopicScope(field, value) {
108
+ if (!isUuidV7(value)) {
109
+ throw new Error(
110
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
111
+ );
112
+ }
113
+ }
92
114
  function asMappedProjectId(topic) {
93
115
  if (!topic) {
94
116
  return;
@@ -110,200 +132,25 @@ function normalizeScopeValue(value) {
110
132
  const normalized = value.trim();
111
133
  return normalized.length > 0 ? normalized : void 0;
112
134
  }
113
- function pickPrimaryTopic(candidates) {
114
- return [...candidates].sort((a, b) => {
115
- const depthA = a.depth ?? 9999;
116
- const depthB = b.depth ?? 9999;
117
- if (depthA !== depthB) {
118
- return depthA - depthB;
119
- }
120
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
121
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
122
- if (createdA !== createdB) {
123
- return createdA - createdB;
124
- }
125
- return String(a.name || "").localeCompare(String(b.name || ""));
126
- })[0];
127
- }
128
- async function findTopicsByScopeAlias(ctx, scopeId) {
129
- const query2 = ctx.db.query("topics");
130
- try {
131
- return await query2.withIndex(
132
- "by_graph_scope_project",
133
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
134
- ).collect();
135
- } catch (error) {
136
- debugGraphPrimitiveFallback(
137
- "[topicScope] Failed to resolve scope alias via index",
138
- {
139
- error,
140
- scopeId
141
- }
142
- );
143
- const topics = await query2.collect();
144
- return topics.filter((topic) => {
145
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
146
- const mappedProjectId = asMappedProjectId(topic);
147
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
148
- });
149
- }
150
- }
151
- async function tryResolveHostTopicById(ctx, topicId) {
152
- if (typeof ctx.runQuery !== "function") {
153
- return null;
154
- }
155
- try {
156
- return await ctx.runQuery(api.topics.get, {
157
- id: topicId
158
- }) ?? null;
159
- } catch (error) {
160
- debugGraphPrimitiveFallback(
161
- "[topicScope] Failed to resolve topic by host query",
162
- {
163
- error,
164
- topicId
165
- }
166
- );
167
- return null;
168
- }
169
- }
170
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
171
- if (typeof ctx.runQuery !== "function") {
172
- return null;
173
- }
174
- try {
175
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
176
- projectId: legacyScopeId
177
- }) ?? null;
178
- } catch (error) {
179
- debugGraphPrimitiveFallback(
180
- "[topicScope] Failed to resolve topic by legacy scope",
181
- {
182
- error,
183
- legacyScopeId
184
- }
185
- );
186
- return null;
187
- }
188
- }
189
- async function resolveInheritedWorkspaceScope(ctx, topic) {
190
- const MAX_DEPTH = 10;
191
- let tenantId = normalizeScopeValue(topic.tenantId);
192
- let workspaceId = normalizeScopeValue(topic.workspaceId);
193
- if (tenantId && workspaceId) {
194
- return { tenantId, workspaceId };
195
- }
196
- let current = topic;
197
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
198
- current = await ctx.db.get(current.parentTopicId);
199
- if (!current) {
200
- break;
201
- }
202
- if (!tenantId) {
203
- tenantId = normalizeScopeValue(current.tenantId);
204
- }
205
- if (!workspaceId) {
206
- workspaceId = normalizeScopeValue(current.workspaceId);
207
- }
208
- if (tenantId && workspaceId) {
209
- break;
210
- }
211
- }
212
- return { tenantId, workspaceId };
213
- }
214
135
  async function resolveTopicProjectScope(ctx, args) {
215
136
  if (args.topicId) {
216
137
  return await resolveScopeFromTopicId(ctx, args.topicId);
217
138
  }
218
139
  if (args.projectId) {
219
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
140
+ throw new Error(
141
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
142
+ );
220
143
  }
221
- throw new Error(
222
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
223
- );
144
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
224
145
  }
225
146
  async function resolveScopeFromTopicId(ctx, topicId) {
226
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
227
- if (topic) {
228
- return await buildTopicScope(ctx, topic, "topic");
229
- }
230
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
147
+ const topicGlobalId = String(topicId);
148
+ requireUuidV7TopicScope("topicId", topicGlobalId);
149
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
231
150
  if (nodeScope) {
232
151
  return nodeScope;
233
152
  }
234
- throw new Error(`Topic not found: ${String(topicId)}`);
235
- }
236
- async function resolveTopicDocFromTopicId(ctx, topicId) {
237
- const direct = await tryReadTopicDoc(ctx, topicId, {
238
- failureLog: "[topicScope] Failed to load topic by direct id",
239
- idLogKey: "topicId"
240
- });
241
- if (direct) {
242
- return direct;
243
- }
244
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
245
- if (hostTopic) {
246
- return hostTopic;
247
- }
248
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
249
- }
250
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
251
- const directTopic = await resolveDirectLegacyProjectTopic(
252
- ctx,
253
- legacyProjectId
254
- );
255
- if (directTopic) {
256
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
257
- fallbackProjectId: legacyProjectId
258
- });
259
- }
260
- const primary = pickPrimaryTopic(
261
- await findTopicsByScopeAlias(ctx, legacyProjectId)
262
- );
263
- if (primary) {
264
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
265
- fallbackProjectId: legacyProjectId
266
- });
267
- }
268
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
269
- if (nodeScope) {
270
- return {
271
- ...nodeScope,
272
- projectId: nodeScope.projectId ?? legacyProjectId
273
- };
274
- }
275
- throw new Error(
276
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
277
- );
278
- }
279
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
280
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
281
- failureLog: "[topicScope] Failed to load direct project topic",
282
- idLogKey: "projectId"
283
- });
284
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
285
- }
286
- async function tryReadTopicDoc(ctx, id, log) {
287
- try {
288
- return await ctx.db.get(id);
289
- } catch (error) {
290
- debugGraphPrimitiveFallback(log.failureLog, {
291
- error,
292
- [log.idLogKey]: id
293
- });
294
- return null;
295
- }
296
- }
297
- async function buildTopicScope(ctx, topic, source, options = {}) {
298
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
299
- const mapped = asMappedProjectId(topic);
300
- return {
301
- topicId: topic._id,
302
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
303
- tenantId: inherited.tenantId,
304
- workspaceId: inherited.workspaceId,
305
- source
306
- };
153
+ throw new Error(`Topic not found: ${topicGlobalId}`);
307
154
  }
308
155
  ({
309
156
  projectId: v.optional(v.string()),
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export { q as questionEvidenceLinks } from './questionEvidenceLinks-_nPRa-LY.js'
35
35
  export { r as resolvers } from './resolvers-B1TIBmRO.js';
36
36
  export { r as resolverTypes } from './resolverTypes-BOXPxLET.js';
37
37
  export { t as textMatching } from './text-matching-DzFooju6.js';
38
- export { t as topicScope } from './topicScope-DJVa0mLa.js';
38
+ export { t as topicScope } from './topicScope-CL1IVOmv.js';
39
39
  import './convex.js';
40
40
  import '@lucern/access-control/convex';
41
41
  import '@lucern/contracts/convex/unsafeAnyApi';