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