@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
@@ -1,6 +1,6 @@
1
1
  import { checkScopeAccess } from '@lucern/access-control/access';
2
2
  import { normalizeTupleContradictionPolicy, readOpinionFromRecord, mkOpinion, confidenceFromSL } from '@lucern/confidence';
3
- import { generateUuidV7 } from '@lucern/contracts/ids';
3
+ import { generateUuidV7, isUuidV7 } from '@lucern/contracts/ids';
4
4
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
5
5
  import { v } from 'convex/values';
6
6
  import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
@@ -14,7 +14,6 @@ import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
14
14
  var unsafeApi = unsafeConvexAnyApi(
15
15
  "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
16
16
  );
17
- var api = unsafeApi;
18
17
  componentsGeneric();
19
18
  var internal = unsafeApi;
20
19
  var internalMutation = internalMutationGeneric;
@@ -143,6 +142,8 @@ function resolveBeliefLifecycleStatus(opts) {
143
142
  function isPreValidationBeliefStatus(status) {
144
143
  return status === "assumption" || status === "hypothesis";
145
144
  }
145
+
146
+ // src/topicScope.ts
146
147
  var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
147
148
  async function resolveTopicNodeScopeOrNull(ctx, ref) {
148
149
  if (!ctx?.db || typeof ctx.db.query !== "function") {
@@ -163,16 +164,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
163
164
  if (!node) {
164
165
  return null;
165
166
  }
166
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
167
+ const scopeKey = canonicalTopicGlobalId(node);
167
168
  if (!scopeKey) {
168
- return null;
169
+ throw new Error(
170
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
171
+ );
169
172
  }
173
+ const metadata = node.metadata ?? {};
170
174
  return {
171
175
  topicId: scopeKey,
172
176
  projectId: asMappedProjectId(node),
173
- source: "topic_node"
177
+ source: "topic_node",
178
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
179
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
174
180
  };
175
181
  }
182
+ function canonicalTopicGlobalId(node) {
183
+ const globalId = normalizeScopeValue(node.globalId);
184
+ if (globalId && isUuidV7(globalId)) {
185
+ return globalId;
186
+ }
187
+ const topicId2 = normalizeScopeValue(node.topicId);
188
+ return topicId2 && isUuidV7(topicId2) ? topicId2 : null;
189
+ }
190
+ function requireUuidV7TopicScope(field, value) {
191
+ if (!isUuidV7(value)) {
192
+ throw new Error(
193
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
194
+ );
195
+ }
196
+ }
176
197
  function asMappedProjectId(topic) {
177
198
  if (!topic) {
178
199
  return;
@@ -194,200 +215,25 @@ function normalizeScopeValue(value) {
194
215
  const normalized = value.trim();
195
216
  return normalized.length > 0 ? normalized : void 0;
196
217
  }
197
- function pickPrimaryTopic(candidates) {
198
- return [...candidates].sort((a, b) => {
199
- const depthA = a.depth ?? 9999;
200
- const depthB = b.depth ?? 9999;
201
- if (depthA !== depthB) {
202
- return depthA - depthB;
203
- }
204
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
205
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
206
- if (createdA !== createdB) {
207
- return createdA - createdB;
208
- }
209
- return String(a.name || "").localeCompare(String(b.name || ""));
210
- })[0];
211
- }
212
- async function findTopicsByScopeAlias(ctx, scopeId) {
213
- const query2 = ctx.db.query("topics");
214
- try {
215
- return await query2.withIndex(
216
- "by_graph_scope_project",
217
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
218
- ).collect();
219
- } catch (error) {
220
- debugGraphPrimitiveFallback(
221
- "[topicScope] Failed to resolve scope alias via index",
222
- {
223
- error,
224
- scopeId
225
- }
226
- );
227
- const topics = await query2.collect();
228
- return topics.filter((topic) => {
229
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
230
- const mappedProjectId = asMappedProjectId(topic);
231
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
232
- });
233
- }
234
- }
235
- async function tryResolveHostTopicById(ctx, topicId2) {
236
- if (typeof ctx.runQuery !== "function") {
237
- return null;
238
- }
239
- try {
240
- return await ctx.runQuery(api.topics.get, {
241
- id: topicId2
242
- }) ?? null;
243
- } catch (error) {
244
- debugGraphPrimitiveFallback(
245
- "[topicScope] Failed to resolve topic by host query",
246
- {
247
- error,
248
- topicId: topicId2
249
- }
250
- );
251
- return null;
252
- }
253
- }
254
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
255
- if (typeof ctx.runQuery !== "function") {
256
- return null;
257
- }
258
- try {
259
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
260
- projectId: legacyScopeId
261
- }) ?? null;
262
- } catch (error) {
263
- debugGraphPrimitiveFallback(
264
- "[topicScope] Failed to resolve topic by legacy scope",
265
- {
266
- error,
267
- legacyScopeId
268
- }
269
- );
270
- return null;
271
- }
272
- }
273
- async function resolveInheritedWorkspaceScope(ctx, topic) {
274
- const MAX_DEPTH = 10;
275
- let tenantId = normalizeScopeValue(topic.tenantId);
276
- let workspaceId = normalizeScopeValue(topic.workspaceId);
277
- if (tenantId && workspaceId) {
278
- return { tenantId, workspaceId };
279
- }
280
- let current = topic;
281
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
282
- current = await ctx.db.get(current.parentTopicId);
283
- if (!current) {
284
- break;
285
- }
286
- if (!tenantId) {
287
- tenantId = normalizeScopeValue(current.tenantId);
288
- }
289
- if (!workspaceId) {
290
- workspaceId = normalizeScopeValue(current.workspaceId);
291
- }
292
- if (tenantId && workspaceId) {
293
- break;
294
- }
295
- }
296
- return { tenantId, workspaceId };
297
- }
298
218
  async function resolveTopicProjectScope(ctx, args) {
299
219
  if (args.topicId) {
300
220
  return await resolveScopeFromTopicId(ctx, args.topicId);
301
221
  }
302
222
  if (args.projectId) {
303
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
223
+ throw new Error(
224
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
225
+ );
304
226
  }
305
- throw new Error(
306
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
307
- );
227
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
308
228
  }
309
229
  async function resolveScopeFromTopicId(ctx, topicId2) {
310
- const topic = await resolveTopicDocFromTopicId(ctx, topicId2);
311
- if (topic) {
312
- return await buildTopicScope(ctx, topic, "topic");
313
- }
314
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId2));
230
+ const topicGlobalId = String(topicId2);
231
+ requireUuidV7TopicScope("topicId", topicGlobalId);
232
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
315
233
  if (nodeScope) {
316
234
  return nodeScope;
317
235
  }
318
- throw new Error(`Topic not found: ${String(topicId2)}`);
319
- }
320
- async function resolveTopicDocFromTopicId(ctx, topicId2) {
321
- const direct = await tryReadTopicDoc(ctx, topicId2, {
322
- failureLog: "[topicScope] Failed to load topic by direct id",
323
- idLogKey: "topicId"
324
- });
325
- if (direct) {
326
- return direct;
327
- }
328
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId2));
329
- if (hostTopic) {
330
- return hostTopic;
331
- }
332
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId2))) ?? null;
333
- }
334
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
335
- const directTopic = await resolveDirectLegacyProjectTopic(
336
- ctx,
337
- legacyProjectId
338
- );
339
- if (directTopic) {
340
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
341
- fallbackProjectId: legacyProjectId
342
- });
343
- }
344
- const primary = pickPrimaryTopic(
345
- await findTopicsByScopeAlias(ctx, legacyProjectId)
346
- );
347
- if (primary) {
348
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
349
- fallbackProjectId: legacyProjectId
350
- });
351
- }
352
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
353
- if (nodeScope) {
354
- return {
355
- ...nodeScope,
356
- projectId: nodeScope.projectId ?? legacyProjectId
357
- };
358
- }
359
- throw new Error(
360
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
361
- );
362
- }
363
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
364
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
365
- failureLog: "[topicScope] Failed to load direct project topic",
366
- idLogKey: "projectId"
367
- });
368
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
369
- }
370
- async function tryReadTopicDoc(ctx, id, log) {
371
- try {
372
- return await ctx.db.get(id);
373
- } catch (error) {
374
- debugGraphPrimitiveFallback(log.failureLog, {
375
- error,
376
- [log.idLogKey]: id
377
- });
378
- return null;
379
- }
380
- }
381
- async function buildTopicScope(ctx, topic, source, options = {}) {
382
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
383
- const mapped = asMappedProjectId(topic);
384
- return {
385
- topicId: topic._id,
386
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
387
- tenantId: inherited.tenantId,
388
- workspaceId: inherited.workspaceId,
389
- source
390
- };
236
+ throw new Error(`Topic not found: ${topicGlobalId}`);
391
237
  }
392
238
  var optionalScopeArgs = {
393
239
  projectId: v.optional(v.string()),
@@ -4,7 +4,7 @@ import { BeliefConfidenceTrigger } from './epistemicBeliefs.helpers.js';
4
4
  import '@lucern/access-control/convex';
5
5
  import '@lucern/contracts/convex/unsafeAnyApi';
6
6
  import 'convex/values';
7
- import './topicScope-DJVa0mLa.js';
7
+ import './topicScope-CL1IVOmv.js';
8
8
  import './beliefLifecycle-CXwdDw5e.js';
9
9
  import '@lucern/access-control/structuredMutationError';
10
10
 
@@ -1,6 +1,6 @@
1
1
  import { requireScopeWriteAccess } from '@lucern/access-control/access';
2
2
  import { normalizeTupleContradictionPolicy, mkOpinion, conditionalDeduction, project, dampedDependencyCascade, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, confidenceFromSL, detectTupleContradiction, evaluateTupleContradictionTransition, readOpinionFromRecord, hasProjectedOpinionChanged } from '@lucern/confidence';
3
- import { generateUuidV7 } from '@lucern/contracts/ids';
3
+ import { generateUuidV7, isUuidV7 } from '@lucern/contracts/ids';
4
4
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
5
5
  import { v } from 'convex/values';
6
6
  import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
@@ -421,7 +421,6 @@ function getTraversalDirections(direction) {
421
421
  var unsafeApi = unsafeConvexAnyApi(
422
422
  "graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
423
423
  );
424
- var api = unsafeApi;
425
424
  componentsGeneric();
426
425
  var internal = unsafeApi;
427
426
  var internalMutation = internalMutationGeneric;
@@ -459,16 +458,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
459
458
  if (!node) {
460
459
  return null;
461
460
  }
462
- const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
461
+ const scopeKey = canonicalTopicGlobalId(node);
463
462
  if (!scopeKey) {
464
- return null;
463
+ throw new Error(
464
+ `topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
465
+ );
465
466
  }
467
+ const metadata = node.metadata ?? {};
466
468
  return {
467
469
  topicId: scopeKey,
468
470
  projectId: asMappedProjectId(node),
469
- source: "topic_node"
471
+ source: "topic_node",
472
+ tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
473
+ workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
470
474
  };
471
475
  }
476
+ function canonicalTopicGlobalId(node) {
477
+ const globalId = normalizeScopeValue(node.globalId);
478
+ if (globalId && isUuidV7(globalId)) {
479
+ return globalId;
480
+ }
481
+ const topicId = normalizeScopeValue(node.topicId);
482
+ return topicId && isUuidV7(topicId) ? topicId : null;
483
+ }
484
+ function requireUuidV7TopicScope(field, value) {
485
+ if (!isUuidV7(value)) {
486
+ throw new Error(
487
+ `topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
488
+ );
489
+ }
490
+ }
472
491
  function asMappedProjectId(topic) {
473
492
  if (!topic) {
474
493
  return;
@@ -490,200 +509,25 @@ function normalizeScopeValue(value) {
490
509
  const normalized = value.trim();
491
510
  return normalized.length > 0 ? normalized : void 0;
492
511
  }
493
- function pickPrimaryTopic(candidates) {
494
- return [...candidates].sort((a, b) => {
495
- const depthA = a.depth ?? 9999;
496
- const depthB = b.depth ?? 9999;
497
- if (depthA !== depthB) {
498
- return depthA - depthB;
499
- }
500
- const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
501
- const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
502
- if (createdA !== createdB) {
503
- return createdA - createdB;
504
- }
505
- return String(a.name || "").localeCompare(String(b.name || ""));
506
- })[0];
507
- }
508
- async function findTopicsByScopeAlias(ctx, scopeId) {
509
- const query = ctx.db.query("topics");
510
- try {
511
- return await query.withIndex(
512
- "by_graph_scope_project",
513
- (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
514
- ).collect();
515
- } catch (error) {
516
- debugGraphPrimitiveFallback(
517
- "[topicScope] Failed to resolve scope alias via index",
518
- {
519
- error,
520
- scopeId
521
- }
522
- );
523
- const topics = await query.collect();
524
- return topics.filter((topic) => {
525
- const normalizedGlobalId = normalizeScopeValue(topic.globalId);
526
- const mappedProjectId = asMappedProjectId(topic);
527
- return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
528
- });
529
- }
530
- }
531
- async function tryResolveHostTopicById(ctx, topicId) {
532
- if (typeof ctx.runQuery !== "function") {
533
- return null;
534
- }
535
- try {
536
- return await ctx.runQuery(api.topics.get, {
537
- id: topicId
538
- }) ?? null;
539
- } catch (error) {
540
- debugGraphPrimitiveFallback(
541
- "[topicScope] Failed to resolve topic by host query",
542
- {
543
- error,
544
- topicId
545
- }
546
- );
547
- return null;
548
- }
549
- }
550
- async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
551
- if (typeof ctx.runQuery !== "function") {
552
- return null;
553
- }
554
- try {
555
- return await ctx.runQuery(api.topics.getByLegacyScopeId, {
556
- projectId: legacyScopeId
557
- }) ?? null;
558
- } catch (error) {
559
- debugGraphPrimitiveFallback(
560
- "[topicScope] Failed to resolve topic by legacy scope",
561
- {
562
- error,
563
- legacyScopeId
564
- }
565
- );
566
- return null;
567
- }
568
- }
569
- async function resolveInheritedWorkspaceScope(ctx, topic) {
570
- const MAX_DEPTH = 10;
571
- let tenantId = normalizeScopeValue(topic.tenantId);
572
- let workspaceId = normalizeScopeValue(topic.workspaceId);
573
- if (tenantId && workspaceId) {
574
- return { tenantId, workspaceId };
575
- }
576
- let current = topic;
577
- for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
578
- current = await ctx.db.get(current.parentTopicId);
579
- if (!current) {
580
- break;
581
- }
582
- if (!tenantId) {
583
- tenantId = normalizeScopeValue(current.tenantId);
584
- }
585
- if (!workspaceId) {
586
- workspaceId = normalizeScopeValue(current.workspaceId);
587
- }
588
- if (tenantId && workspaceId) {
589
- break;
590
- }
591
- }
592
- return { tenantId, workspaceId };
593
- }
594
512
  async function resolveTopicProjectScope(ctx, args) {
595
513
  if (args.topicId) {
596
514
  return await resolveScopeFromTopicId(ctx, args.topicId);
597
515
  }
598
516
  if (args.projectId) {
599
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
517
+ throw new Error(
518
+ "topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
519
+ );
600
520
  }
601
- throw new Error(
602
- "Missing scope: provide topicId (preferred) or legacy projectId alias."
603
- );
521
+ throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
604
522
  }
605
523
  async function resolveScopeFromTopicId(ctx, topicId) {
606
- const topic = await resolveTopicDocFromTopicId(ctx, topicId);
607
- if (topic) {
608
- return await buildTopicScope(ctx, topic, "topic");
609
- }
610
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
524
+ const topicGlobalId = String(topicId);
525
+ requireUuidV7TopicScope("topicId", topicGlobalId);
526
+ const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
611
527
  if (nodeScope) {
612
528
  return nodeScope;
613
529
  }
614
- throw new Error(`Topic not found: ${String(topicId)}`);
615
- }
616
- async function resolveTopicDocFromTopicId(ctx, topicId) {
617
- const direct = await tryReadTopicDoc(ctx, topicId, {
618
- failureLog: "[topicScope] Failed to load topic by direct id",
619
- idLogKey: "topicId"
620
- });
621
- if (direct) {
622
- return direct;
623
- }
624
- const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
625
- if (hostTopic) {
626
- return hostTopic;
627
- }
628
- return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
629
- }
630
- async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
631
- const directTopic = await resolveDirectLegacyProjectTopic(
632
- ctx,
633
- legacyProjectId
634
- );
635
- if (directTopic) {
636
- return await buildTopicScope(ctx, directTopic, "topic_inferred", {
637
- fallbackProjectId: legacyProjectId
638
- });
639
- }
640
- const primary = pickPrimaryTopic(
641
- await findTopicsByScopeAlias(ctx, legacyProjectId)
642
- );
643
- if (primary) {
644
- return await buildTopicScope(ctx, primary, "project_mapped_topic", {
645
- fallbackProjectId: legacyProjectId
646
- });
647
- }
648
- const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
649
- if (nodeScope) {
650
- return {
651
- ...nodeScope,
652
- projectId: nodeScope.projectId ?? legacyProjectId
653
- };
654
- }
655
- throw new Error(
656
- `Legacy project scope ${legacyProjectId} has no mapped topic.`
657
- );
658
- }
659
- async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
660
- const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
661
- failureLog: "[topicScope] Failed to load direct project topic",
662
- idLogKey: "projectId"
663
- });
664
- return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
665
- }
666
- async function tryReadTopicDoc(ctx, id, log) {
667
- try {
668
- return await ctx.db.get(id);
669
- } catch (error) {
670
- debugGraphPrimitiveFallback(log.failureLog, {
671
- error,
672
- [log.idLogKey]: id
673
- });
674
- return null;
675
- }
676
- }
677
- async function buildTopicScope(ctx, topic, source, options = {}) {
678
- const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
679
- const mapped = asMappedProjectId(topic);
680
- return {
681
- topicId: topic._id,
682
- ...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
683
- tenantId: inherited.tenantId,
684
- workspaceId: inherited.workspaceId,
685
- source
686
- };
530
+ throw new Error(`Topic not found: ${topicGlobalId}`);
687
531
  }
688
532
  ({
689
533
  projectId: v.optional(v.string()),