@lucern/graph-primitives 1.0.48 → 1.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beliefDecay.js +34 -186
- package/dist/beliefEvidenceLinks.js +32 -187
- package/dist/contradictions.js +34 -187
- package/dist/entityLifecycle.js +88 -205
- package/dist/epistemicAnswers.js +35 -187
- package/dist/epistemicBeliefs.admin.js +34 -187
- package/dist/epistemicBeliefs.backfills.js +34 -188
- package/dist/epistemicBeliefs.confidence.d.ts +1 -1
- package/dist/epistemicBeliefs.confidence.js +32 -188
- package/dist/epistemicBeliefs.core.js +37 -187
- package/dist/epistemicBeliefs.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.js +34 -186
- package/dist/epistemicBeliefs.internal.js +37 -187
- package/dist/epistemicBeliefs.js +37 -187
- package/dist/epistemicBeliefs.lifecycle.js +32 -188
- package/dist/epistemicBeliefs.links.js +34 -188
- package/dist/epistemicContracts.evaluators.js +34 -188
- package/dist/epistemicContracts.handlers.js +34 -188
- package/dist/epistemicContracts.js +34 -188
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.helpers.d.ts +2 -2
- package/dist/epistemicEdges.js +32 -187
- package/dist/epistemicEdges.mutations.js +34 -186
- package/dist/epistemicEdges.queries.js +35 -188
- package/dist/epistemicEdges.types.d.ts +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +37 -187
- package/dist/epistemicEvidenceHelpers.d.ts +1 -1
- package/dist/epistemicEvidenceHelpers.js +34 -186
- package/dist/epistemicEvidenceMutations.js +37 -187
- package/dist/epistemicEvidenceQueries.js +34 -186
- package/dist/epistemicHelpers.js +4 -1
- package/dist/epistemicInsert.js +4 -1
- package/dist/epistemicNodeCreation.js +4 -1
- package/dist/epistemicNodes.helpers.d.ts +1 -1
- package/dist/epistemicNodes.internal.js +35 -188
- package/dist/epistemicNodes.js +37 -188
- package/dist/epistemicNodes.mutations.js +35 -188
- package/dist/epistemicNodes.queries.js +35 -188
- package/dist/epistemicQuestions.conviction.js +34 -186
- package/dist/epistemicQuestions.create.js +37 -187
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.evidence.js +37 -187
- package/dist/epistemicQuestions.helpers.d.ts +1 -1
- package/dist/epistemicQuestions.helpers.js +34 -186
- package/dist/epistemicQuestions.js +37 -187
- package/dist/epistemicQuestions.lifecycle.js +34 -186
- package/dist/epistemicQuestions.queries.js +34 -186
- package/dist/epistemicQuestions.sprint.js +35 -188
- package/dist/epistemicQuestions.tail.js +37 -187
- package/dist/epistemicSources.js +35 -188
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -213
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +34 -187
- package/dist/scopeResolverCompat.d.ts +1 -1
- package/dist/scopeResolverCompat.js +32 -193
- package/dist/topicOntologyResolver.d.ts +3 -3
- package/dist/topicOntologyResolver.js +57 -18
- package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
- package/dist/topicScope.d.ts +1 -1
- package/dist/topicScope.js +32 -193
- package/dist/workflowBridge.js +32 -193
- package/dist/workspaceIsolation.d.ts +1 -1
- package/dist/workspaceIsolation.js +32 -193
- 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 =
|
|
447
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
445
448
|
if (!scopeKey) {
|
|
446
|
-
|
|
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
|
-
|
|
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
|
|
589
|
-
|
|
590
|
-
|
|
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: ${
|
|
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
|
-
|
|
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 =
|
|
67
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
66
68
|
if (!scopeKey) {
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
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: ${
|
|
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()),
|