@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
@@ -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, mutationGeneric } from 'convex/server';
6
+ import { isUuidV7 } from '@lucern/contracts/ids';
6
7
 
7
8
  // src/contradictions.ts
8
9
  var unsafeApi = 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 internal = unsafeApi;
14
14
  var mutation = mutationGeneric;
@@ -25,6 +25,8 @@ function debugGraphPrimitiveFallback(message, context) {
25
25
  }
26
26
  console.debug(message, context ?? {});
27
27
  }
28
+
29
+ // src/topicScope.ts
28
30
  var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
29
31
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
30
32
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -45,16 +47,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
45
47
  if (!node) {
46
48
  return null;
47
49
  }
48
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
50
+ const scopeKey = canonicalTopicGlobalId(node);
49
51
  if (!scopeKey) {
50
- return null;
52
+ throw new Error(
53
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
54
+ );
51
55
  }
56
+ const metadata = node.metadata ?? {};
52
57
  return {
53
58
  topicId: scopeKey,
54
59
  projectId: asMappedProjectId(node),
55
- source: "topic_node"
60
+ source: "topic_node",
61
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
62
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
56
63
  };
57
64
  }
65
+ function canonicalTopicGlobalId(node) {
66
+ const globalId = normalizeScopeValue(node.globalId);
67
+ if (globalId && isUuidV7(globalId)) {
68
+ return globalId;
69
+ }
70
+ const topicId = normalizeScopeValue(node.topicId);
71
+ return topicId && isUuidV7(topicId) ? topicId : null;
72
+ }
73
+ function requireUuidV7TopicScope(field, value) {
74
+ if (!isUuidV7(value)) {
75
+ throw new Error(
76
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
77
+ );
78
+ }
79
+ }
58
80
  function asMappedProjectId(topic) {
59
81
  if (!topic) {
60
82
  return;
@@ -76,200 +98,25 @@ function normalizeScopeValue(value) {
76
98
  const normalized = value.trim();
77
99
  return normalized.length > 0 ? normalized : void 0;
78
100
  }
79
- function pickPrimaryTopic(candidates) {
80
- return [...candidates].sort((a, b) => {
81
- const depthA = a.depth ?? 9999;
82
- const depthB = b.depth ?? 9999;
83
- if (depthA !== depthB) {
84
- return depthA - depthB;
85
- }
86
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
87
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
88
- if (createdA !== createdB) {
89
- return createdA - createdB;
90
- }
91
- return String(a.name || "").localeCompare(String(b.name || ""));
92
- })[0];
93
- }
94
- async function findTopicsByScopeAlias(ctx, scopeId) {
95
- const query2 = ctx.db.query("topics");
96
- try {
97
- return await query2.withIndex(
98
- "by_graph_scope_project",
99
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
100
- ).collect();
101
- } catch (error) {
102
- debugGraphPrimitiveFallback(
103
- "[topicScope] Failed to resolve scope alias via index",
104
- {
105
- error,
106
- scopeId
107
- }
108
- );
109
- const topics = await query2.collect();
110
- return topics.filter((topic) => {
111
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
112
- const mappedProjectId = asMappedProjectId(topic);
113
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
114
- });
115
- }
116
- }
117
- async function tryResolveHostTopicById(ctx, topicId) {
118
- if (typeof ctx.runQuery !== "function") {
119
- return null;
120
- }
121
- try {
122
- return await ctx.runQuery(api.topics.get, {
123
- id: topicId
124
- }) ?? null;
125
- } catch (error) {
126
- debugGraphPrimitiveFallback(
127
- "[topicScope] Failed to resolve topic by host query",
128
- {
129
- error,
130
- topicId
131
- }
132
- );
133
- return null;
134
- }
135
- }
136
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
137
- if (typeof ctx.runQuery !== "function") {
138
- return null;
139
- }
140
- try {
141
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
142
- projectId: legacyScopeId
143
- }) ?? null;
144
- } catch (error) {
145
- debugGraphPrimitiveFallback(
146
- "[topicScope] Failed to resolve topic by legacy scope",
147
- {
148
- error,
149
- legacyScopeId
150
- }
151
- );
152
- return null;
153
- }
154
- }
155
- async function resolveInheritedWorkspaceScope(ctx, topic) {
156
- const MAX_DEPTH = 10;
157
- let tenantId = normalizeScopeValue(topic.tenantId);
158
- let workspaceId = normalizeScopeValue(topic.workspaceId);
159
- if (tenantId && workspaceId) {
160
- return { tenantId, workspaceId };
161
- }
162
- let current = topic;
163
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
164
- current = await ctx.db.get(current.parentTopicId);
165
- if (!current) {
166
- break;
167
- }
168
- if (!tenantId) {
169
- tenantId = normalizeScopeValue(current.tenantId);
170
- }
171
- if (!workspaceId) {
172
- workspaceId = normalizeScopeValue(current.workspaceId);
173
- }
174
- if (tenantId && workspaceId) {
175
- break;
176
- }
177
- }
178
- return { tenantId, workspaceId };
179
- }
180
101
  async function resolveTopicProjectScope(ctx, args) {
181
102
  if (args.topicId) {
182
103
  return await resolveScopeFromTopicId(ctx, args.topicId);
183
104
  }
184
105
  if (args.projectId) {
185
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
106
+ throw new Error(
107
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
108
+ );
186
109
  }
187
- throw new Error(
188
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
189
- );
110
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
190
111
  }
191
112
  async function resolveScopeFromTopicId(ctx, topicId) {
192
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
193
- if (topic) {
194
- return await buildTopicScope(ctx, topic, "topic");
195
- }
196
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
113
+ const topicGlobalId = String(topicId);
114
+ requireUuidV7TopicScope("topicId", topicGlobalId);
115
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
197
116
  if (nodeScope) {
198
117
  return nodeScope;
199
118
  }
200
- throw new Error(`Topic not found: ${String(topicId)}`);
201
- }
202
- async function resolveTopicDocFromTopicId(ctx, topicId) {
203
- const direct = await tryReadTopicDoc(ctx, topicId, {
204
- failureLog: "[topicScope] Failed to load topic by direct id",
205
- idLogKey: "topicId"
206
- });
207
- if (direct) {
208
- return direct;
209
- }
210
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
211
- if (hostTopic) {
212
- return hostTopic;
213
- }
214
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
215
- }
216
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
217
- const directTopic = await resolveDirectLegacyProjectTopic(
218
- ctx,
219
- legacyProjectId
220
- );
221
- if (directTopic) {
222
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
223
- fallbackProjectId: legacyProjectId
224
- });
225
- }
226
- const primary = pickPrimaryTopic(
227
- await findTopicsByScopeAlias(ctx, legacyProjectId)
228
- );
229
- if (primary) {
230
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
231
- fallbackProjectId: legacyProjectId
232
- });
233
- }
234
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
235
- if (nodeScope) {
236
- return {
237
- ...nodeScope,
238
- projectId: nodeScope.projectId ?? legacyProjectId
239
- };
240
- }
241
- throw new Error(
242
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
243
- );
244
- }
245
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
246
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
247
- failureLog: "[topicScope] Failed to load direct project topic",
248
- idLogKey: "projectId"
249
- });
250
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
251
- }
252
- async function tryReadTopicDoc(ctx, id, log) {
253
- try {
254
- return await ctx.db.get(id);
255
- } catch (error) {
256
- debugGraphPrimitiveFallback(log.failureLog, {
257
- error,
258
- [log.idLogKey]: id
259
- });
260
- return null;
261
- }
262
- }
263
- async function buildTopicScope(ctx, topic, source, options = {}) {
264
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
265
- const mapped = asMappedProjectId(topic);
266
- return {
267
- topicId: topic._id,
268
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
269
- tenantId: inherited.tenantId,
270
- workspaceId: inherited.workspaceId,
271
- source
272
- };
119
+ throw new Error(`Topic not found: ${topicGlobalId}`);
273
120
  }
274
121
  var optionalScopeArgs = {
275
122
  projectId: v.optional(v.string()),
@@ -5,7 +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, mutationGeneric } from 'convex/server';
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
 
@@ -166,6 +166,9 @@ async function validateEntityMetadata(ctx, nodeType, metadata, tenantId) {
166
166
  }
167
167
  function insertEpistemicNode(ctx, doc) {
168
168
  assertUuidV7Identity("epistemicNodes", doc.globalId);
169
+ if (doc.topicId !== void 0 && doc.topicId !== null) {
170
+ assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
171
+ }
169
172
  return ctx.db.insert("epistemicNodes", doc);
170
173
  }
171
174
  async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
@@ -603,8 +606,47 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
603
606
 
604
607
  // src/topicOntologyResolver.ts
605
608
  var MAX_RESOLUTION_DEPTH = 10;
606
- async function loadTopic(ctx, topicId) {
607
- return await ctx.db.get(topicId);
609
+ async function loadTopic(ctx, topicGlobalId) {
610
+ requireUuidV7TopicGlobalId(topicGlobalId);
611
+ const topic = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", topicGlobalId)).first();
612
+ return requireTopicGlobalId(topic);
613
+ }
614
+ function requireUuidV7TopicGlobalId(topicGlobalId) {
615
+ if (!isUuidV7(topicGlobalId)) {
616
+ throw new Error(
617
+ `topic.uuidv7_required: topic ontology resolution requires a UUIDv7 topic globalId, received ${topicGlobalId}.`
618
+ );
619
+ }
620
+ }
621
+ function requireTopicGlobalId(topic) {
622
+ if (!topic) {
623
+ return null;
624
+ }
625
+ if (topic.nodeType !== "topic") {
626
+ return null;
627
+ }
628
+ if (!isUuidV7(topic.globalId)) {
629
+ throw new Error(
630
+ `topic.uuidv7_required: topic node ${String(topic._id)} is missing a UUIDv7 globalId.`
631
+ );
632
+ }
633
+ return topic;
634
+ }
635
+ function metadataString(topic, field) {
636
+ const value = topic.metadata?.[field];
637
+ return typeof value === "string" && value.trim() ? value : null;
638
+ }
639
+ function topicOntologyId(topic) {
640
+ const ontologyId = metadataString(topic, "ontologyId");
641
+ return ontologyId ? ontologyId : null;
642
+ }
643
+ function parentTopicGlobalId(topic) {
644
+ const parent = metadataString(topic, "parentTopicGlobalId");
645
+ if (!parent) {
646
+ return null;
647
+ }
648
+ requireUuidV7TopicGlobalId(parent);
649
+ return parent;
608
650
  }
609
651
  async function loadOntologyDefinition(ctx, ontologyId) {
610
652
  return await ctx.db.get(ontologyId);
@@ -627,25 +669,20 @@ function resolvedTopicOntology(args) {
627
669
  };
628
670
  }
629
671
  async function resolveCurrentTopicOntology(args) {
630
- if (!args.current.ontologyId) {
672
+ const ontologyId = topicOntologyId(args.current);
673
+ if (!ontologyId) {
631
674
  return "continue";
632
675
  }
633
- const ontologyDef = await loadOntologyDefinition(
634
- args.ctx,
635
- args.current.ontologyId
636
- );
676
+ const ontologyDef = await loadOntologyDefinition(args.ctx, ontologyId);
637
677
  if (!ontologyDef || ontologyDef.status === "archived") {
638
- return args.current.parentTopicId ? "continue" : null;
678
+ return parentTopicGlobalId(args.current) ? "continue" : null;
639
679
  }
640
- const published = await loadPublishedOntologyVersions(
641
- args.ctx,
642
- args.current.ontologyId
643
- );
680
+ const published = await loadPublishedOntologyVersions(args.ctx, ontologyId);
644
681
  return resolvedTopicOntology({
645
682
  latestPublished: published[0] ?? null,
646
683
  ontologyDef,
647
- source: args.current._id === args.startTopicId ? "direct" : "inherited",
648
- sourceTopicId: args.current._id
684
+ source: args.current.globalId === args.startTopicGlobalId ? "direct" : "inherited",
685
+ sourceTopicId: args.current.globalId
649
686
  });
650
687
  }
651
688
  async function resolveTopicOntologyInternal(ctx, topicId) {
@@ -653,20 +690,21 @@ async function resolveTopicOntologyInternal(ctx, topicId) {
653
690
  if (!current) {
654
691
  return null;
655
692
  }
656
- const startTopicId = topicId;
693
+ const startTopicGlobalId = topicId;
657
694
  for (let i = 0; i < MAX_RESOLUTION_DEPTH && current; i++) {
658
695
  const resolved = await resolveCurrentTopicOntology({
659
696
  ctx,
660
697
  current,
661
- startTopicId
698
+ startTopicGlobalId
662
699
  });
663
700
  if (resolved !== "continue") {
664
701
  return resolved;
665
702
  }
666
- if (!current.parentTopicId) {
703
+ const parent = parentTopicGlobalId(current);
704
+ if (!parent) {
667
705
  break;
668
706
  }
669
- current = await loadTopic(ctx, current.parentTopicId);
707
+ current = await loadTopic(ctx, parent);
670
708
  }
671
709
  return null;
672
710
  }
@@ -706,16 +744,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
706
744
  if (!node) {
707
745
  return null;
708
746
  }
709
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
747
+ const scopeKey = canonicalTopicGlobalId(node);
710
748
  if (!scopeKey) {
711
- return null;
749
+ throw new Error(
750
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
751
+ );
712
752
  }
753
+ const metadata = node.metadata ?? {};
713
754
  return {
714
755
  topicId: scopeKey,
715
756
  projectId: asMappedProjectId(node),
716
- source: "topic_node"
757
+ source: "topic_node",
758
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
759
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
717
760
  };
718
761
  }
762
+ function canonicalTopicGlobalId(node) {
763
+ const globalId = normalizeScopeValue(node.globalId);
764
+ if (globalId && isUuidV7(globalId)) {
765
+ return globalId;
766
+ }
767
+ const topicId = normalizeScopeValue(node.topicId);
768
+ return topicId && isUuidV7(topicId) ? topicId : null;
769
+ }
770
+ function requireUuidV7TopicScope(field, value) {
771
+ if (!isUuidV7(value)) {
772
+ throw new Error(
773
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
774
+ );
775
+ }
776
+ }
719
777
  function asMappedProjectId(topic) {
720
778
  if (!topic) {
721
779
  return;
@@ -737,200 +795,25 @@ function normalizeScopeValue(value) {
737
795
  const normalized = value.trim();
738
796
  return normalized.length > 0 ? normalized : void 0;
739
797
  }
740
- function pickPrimaryTopic(candidates) {
741
- return [...candidates].sort((a, b) => {
742
- const depthA = a.depth ?? 9999;
743
- const depthB = b.depth ?? 9999;
744
- if (depthA !== depthB) {
745
- return depthA - depthB;
746
- }
747
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
748
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
749
- if (createdA !== createdB) {
750
- return createdA - createdB;
751
- }
752
- return String(a.name || "").localeCompare(String(b.name || ""));
753
- })[0];
754
- }
755
- async function findTopicsByScopeAlias(ctx, scopeId) {
756
- const query2 = ctx.db.query("topics");
757
- try {
758
- return await query2.withIndex(
759
- "by_graph_scope_project",
760
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
761
- ).collect();
762
- } catch (error) {
763
- debugGraphPrimitiveFallback(
764
- "[topicScope] Failed to resolve scope alias via index",
765
- {
766
- error,
767
- scopeId
768
- }
769
- );
770
- const topics = await query2.collect();
771
- return topics.filter((topic) => {
772
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
773
- const mappedProjectId = asMappedProjectId(topic);
774
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
775
- });
776
- }
777
- }
778
- async function tryResolveHostTopicById(ctx, topicId) {
779
- if (typeof ctx.runQuery !== "function") {
780
- return null;
781
- }
782
- try {
783
- return await ctx.runQuery(api.topics.get, {
784
- id: topicId
785
- }) ?? null;
786
- } catch (error) {
787
- debugGraphPrimitiveFallback(
788
- "[topicScope] Failed to resolve topic by host query",
789
- {
790
- error,
791
- topicId
792
- }
793
- );
794
- return null;
795
- }
796
- }
797
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
798
- if (typeof ctx.runQuery !== "function") {
799
- return null;
800
- }
801
- try {
802
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
803
- projectId: legacyScopeId
804
- }) ?? null;
805
- } catch (error) {
806
- debugGraphPrimitiveFallback(
807
- "[topicScope] Failed to resolve topic by legacy scope",
808
- {
809
- error,
810
- legacyScopeId
811
- }
812
- );
813
- return null;
814
- }
815
- }
816
- async function resolveInheritedWorkspaceScope(ctx, topic) {
817
- const MAX_DEPTH = 10;
818
- let tenantId = normalizeScopeValue(topic.tenantId);
819
- let workspaceId = normalizeScopeValue(topic.workspaceId);
820
- if (tenantId && workspaceId) {
821
- return { tenantId, workspaceId };
822
- }
823
- let current = topic;
824
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
825
- current = await ctx.db.get(current.parentTopicId);
826
- if (!current) {
827
- break;
828
- }
829
- if (!tenantId) {
830
- tenantId = normalizeScopeValue(current.tenantId);
831
- }
832
- if (!workspaceId) {
833
- workspaceId = normalizeScopeValue(current.workspaceId);
834
- }
835
- if (tenantId && workspaceId) {
836
- break;
837
- }
838
- }
839
- return { tenantId, workspaceId };
840
- }
841
798
  async function resolveTopicProjectScope(ctx, args) {
842
799
  if (args.topicId) {
843
800
  return await resolveScopeFromTopicId(ctx, args.topicId);
844
801
  }
845
802
  if (args.projectId) {
846
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
803
+ throw new Error(
804
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
805
+ );
847
806
  }
848
- throw new Error(
849
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
850
- );
807
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
851
808
  }
852
809
  async function resolveScopeFromTopicId(ctx, topicId) {
853
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
854
- if (topic) {
855
- return await buildTopicScope(ctx, topic, "topic");
856
- }
857
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
810
+ const topicGlobalId = String(topicId);
811
+ requireUuidV7TopicScope("topicId", topicGlobalId);
812
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
858
813
  if (nodeScope) {
859
814
  return nodeScope;
860
815
  }
861
- throw new Error(`Topic not found: ${String(topicId)}`);
862
- }
863
- async function resolveTopicDocFromTopicId(ctx, topicId) {
864
- const direct = await tryReadTopicDoc(ctx, topicId, {
865
- failureLog: "[topicScope] Failed to load topic by direct id",
866
- idLogKey: "topicId"
867
- });
868
- if (direct) {
869
- return direct;
870
- }
871
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
872
- if (hostTopic) {
873
- return hostTopic;
874
- }
875
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
876
- }
877
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
878
- const directTopic = await resolveDirectLegacyProjectTopic(
879
- ctx,
880
- legacyProjectId
881
- );
882
- if (directTopic) {
883
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
884
- fallbackProjectId: legacyProjectId
885
- });
886
- }
887
- const primary = pickPrimaryTopic(
888
- await findTopicsByScopeAlias(ctx, legacyProjectId)
889
- );
890
- if (primary) {
891
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
892
- fallbackProjectId: legacyProjectId
893
- });
894
- }
895
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
896
- if (nodeScope) {
897
- return {
898
- ...nodeScope,
899
- projectId: nodeScope.projectId ?? legacyProjectId
900
- };
901
- }
902
- throw new Error(
903
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
904
- );
905
- }
906
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
907
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
908
- failureLog: "[topicScope] Failed to load direct project topic",
909
- idLogKey: "projectId"
910
- });
911
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
912
- }
913
- async function tryReadTopicDoc(ctx, id, log) {
914
- try {
915
- return await ctx.db.get(id);
916
- } catch (error) {
917
- debugGraphPrimitiveFallback(log.failureLog, {
918
- error,
919
- [log.idLogKey]: id
920
- });
921
- return null;
922
- }
923
- }
924
- async function buildTopicScope(ctx, topic, source, options = {}) {
925
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
926
- const mapped = asMappedProjectId(topic);
927
- return {
928
- topicId: topic._id,
929
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
930
- tenantId: inherited.tenantId,
931
- workspaceId: inherited.workspaceId,
932
- source
933
- };
816
+ throw new Error(`Topic not found: ${topicGlobalId}`);
934
817
  }
935
818
  var optionalScopeArgs = {
936
819
  projectId: v.optional(v.string()),