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