@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.
- 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 +1 -1
- 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
|
@@ -8,8 +8,8 @@ import '@lucern/access-control/audience';
|
|
|
8
8
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
9
9
|
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
10
10
|
import { normalizeTupleContradictionPolicy, createInheritedContractRecord, confidenceFromSL } from '@lucern/confidence';
|
|
11
|
+
import { generateGlobalId, assertUuidV7Identity, assertUuidV7Reference, generateUuidV7, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
11
12
|
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
12
|
-
import { generateGlobalId, assertUuidV7Identity, generateUuidV7, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
13
13
|
import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
|
|
14
14
|
import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
15
15
|
|
|
@@ -546,6 +546,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
546
546
|
...resolverOverrides
|
|
547
547
|
};
|
|
548
548
|
}
|
|
549
|
+
|
|
550
|
+
// src/topicScope.ts
|
|
549
551
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
550
552
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
551
553
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -566,16 +568,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
566
568
|
if (!node) {
|
|
567
569
|
return null;
|
|
568
570
|
}
|
|
569
|
-
const scopeKey =
|
|
571
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
570
572
|
if (!scopeKey) {
|
|
571
|
-
|
|
573
|
+
throw new Error(
|
|
574
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
575
|
+
);
|
|
572
576
|
}
|
|
577
|
+
const metadata = node.metadata ?? {};
|
|
573
578
|
return {
|
|
574
579
|
topicId: scopeKey,
|
|
575
580
|
projectId: asMappedProjectId(node),
|
|
576
|
-
source: "topic_node"
|
|
581
|
+
source: "topic_node",
|
|
582
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
583
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
577
584
|
};
|
|
578
585
|
}
|
|
586
|
+
function canonicalTopicGlobalId(node) {
|
|
587
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
588
|
+
if (globalId && isUuidV7(globalId)) {
|
|
589
|
+
return globalId;
|
|
590
|
+
}
|
|
591
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
592
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
593
|
+
}
|
|
594
|
+
function requireUuidV7TopicScope(field, value) {
|
|
595
|
+
if (!isUuidV7(value)) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
579
601
|
function asMappedProjectId(topic) {
|
|
580
602
|
if (!topic) {
|
|
581
603
|
return;
|
|
@@ -597,200 +619,25 @@ function normalizeScopeValue(value) {
|
|
|
597
619
|
const normalized = value.trim();
|
|
598
620
|
return normalized.length > 0 ? normalized : void 0;
|
|
599
621
|
}
|
|
600
|
-
function pickPrimaryTopic(candidates) {
|
|
601
|
-
return [...candidates].sort((a, b) => {
|
|
602
|
-
const depthA = a.depth ?? 9999;
|
|
603
|
-
const depthB = b.depth ?? 9999;
|
|
604
|
-
if (depthA !== depthB) {
|
|
605
|
-
return depthA - depthB;
|
|
606
|
-
}
|
|
607
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
608
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
609
|
-
if (createdA !== createdB) {
|
|
610
|
-
return createdA - createdB;
|
|
611
|
-
}
|
|
612
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
613
|
-
})[0];
|
|
614
|
-
}
|
|
615
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
616
|
-
const query2 = ctx.db.query("topics");
|
|
617
|
-
try {
|
|
618
|
-
return await query2.withIndex(
|
|
619
|
-
"by_graph_scope_project",
|
|
620
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
621
|
-
).collect();
|
|
622
|
-
} catch (error) {
|
|
623
|
-
debugGraphPrimitiveFallback(
|
|
624
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
625
|
-
{
|
|
626
|
-
error,
|
|
627
|
-
scopeId
|
|
628
|
-
}
|
|
629
|
-
);
|
|
630
|
-
const topics = await query2.collect();
|
|
631
|
-
return topics.filter((topic) => {
|
|
632
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
633
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
634
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
639
|
-
if (typeof ctx.runQuery !== "function") {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
try {
|
|
643
|
-
return await ctx.runQuery(api.topics.get, {
|
|
644
|
-
id: topicId
|
|
645
|
-
}) ?? null;
|
|
646
|
-
} catch (error) {
|
|
647
|
-
debugGraphPrimitiveFallback(
|
|
648
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
649
|
-
{
|
|
650
|
-
error,
|
|
651
|
-
topicId
|
|
652
|
-
}
|
|
653
|
-
);
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
658
|
-
if (typeof ctx.runQuery !== "function") {
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
try {
|
|
662
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
663
|
-
projectId: legacyScopeId
|
|
664
|
-
}) ?? null;
|
|
665
|
-
} catch (error) {
|
|
666
|
-
debugGraphPrimitiveFallback(
|
|
667
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
668
|
-
{
|
|
669
|
-
error,
|
|
670
|
-
legacyScopeId
|
|
671
|
-
}
|
|
672
|
-
);
|
|
673
|
-
return null;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
677
|
-
const MAX_DEPTH = 10;
|
|
678
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
679
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
680
|
-
if (tenantId && workspaceId) {
|
|
681
|
-
return { tenantId, workspaceId };
|
|
682
|
-
}
|
|
683
|
-
let current = topic;
|
|
684
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
685
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
686
|
-
if (!current) {
|
|
687
|
-
break;
|
|
688
|
-
}
|
|
689
|
-
if (!tenantId) {
|
|
690
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
691
|
-
}
|
|
692
|
-
if (!workspaceId) {
|
|
693
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
694
|
-
}
|
|
695
|
-
if (tenantId && workspaceId) {
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return { tenantId, workspaceId };
|
|
700
|
-
}
|
|
701
622
|
async function resolveTopicProjectScope(ctx, args) {
|
|
702
623
|
if (args.topicId) {
|
|
703
624
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
704
625
|
}
|
|
705
626
|
if (args.projectId) {
|
|
706
|
-
|
|
627
|
+
throw new Error(
|
|
628
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
629
|
+
);
|
|
707
630
|
}
|
|
708
|
-
throw new Error(
|
|
709
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
710
|
-
);
|
|
631
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
711
632
|
}
|
|
712
633
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
634
|
+
const topicGlobalId = String(topicId);
|
|
635
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
636
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
718
637
|
if (nodeScope) {
|
|
719
638
|
return nodeScope;
|
|
720
639
|
}
|
|
721
|
-
throw new Error(`Topic not found: ${
|
|
722
|
-
}
|
|
723
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
724
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
725
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
726
|
-
idLogKey: "topicId"
|
|
727
|
-
});
|
|
728
|
-
if (direct) {
|
|
729
|
-
return direct;
|
|
730
|
-
}
|
|
731
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
732
|
-
if (hostTopic) {
|
|
733
|
-
return hostTopic;
|
|
734
|
-
}
|
|
735
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
736
|
-
}
|
|
737
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
738
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
739
|
-
ctx,
|
|
740
|
-
legacyProjectId
|
|
741
|
-
);
|
|
742
|
-
if (directTopic) {
|
|
743
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
744
|
-
fallbackProjectId: legacyProjectId
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
const primary = pickPrimaryTopic(
|
|
748
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
749
|
-
);
|
|
750
|
-
if (primary) {
|
|
751
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
752
|
-
fallbackProjectId: legacyProjectId
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
756
|
-
if (nodeScope) {
|
|
757
|
-
return {
|
|
758
|
-
...nodeScope,
|
|
759
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
throw new Error(
|
|
763
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
767
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
768
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
769
|
-
idLogKey: "projectId"
|
|
770
|
-
});
|
|
771
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
772
|
-
}
|
|
773
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
774
|
-
try {
|
|
775
|
-
return await ctx.db.get(id);
|
|
776
|
-
} catch (error) {
|
|
777
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
778
|
-
error,
|
|
779
|
-
[log.idLogKey]: id
|
|
780
|
-
});
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
785
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
786
|
-
const mapped = asMappedProjectId(topic);
|
|
787
|
-
return {
|
|
788
|
-
topicId: topic._id,
|
|
789
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
790
|
-
tenantId: inherited.tenantId,
|
|
791
|
-
workspaceId: inherited.workspaceId,
|
|
792
|
-
source
|
|
793
|
-
};
|
|
640
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
794
641
|
}
|
|
795
642
|
var optionalScopeArgs = {
|
|
796
643
|
projectId: v.optional(v.string()),
|
|
@@ -1114,6 +961,9 @@ async function resolveForkTriggerEvidence(ctx, args) {
|
|
|
1114
961
|
}
|
|
1115
962
|
function insertEpistemicNode(ctx, doc) {
|
|
1116
963
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
964
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
965
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
966
|
+
}
|
|
1117
967
|
return ctx.db.insert("epistemicNodes", doc);
|
|
1118
968
|
}
|
|
1119
969
|
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|
|
@@ -12,6 +12,6 @@ import './convex.js';
|
|
|
12
12
|
import '@lucern/access-control/convex';
|
|
13
13
|
import '@lucern/contracts/convex/unsafeAnyApi';
|
|
14
14
|
import 'convex/values';
|
|
15
|
-
import './topicScope-
|
|
15
|
+
import './topicScope-CL1IVOmv.js';
|
|
16
16
|
import './beliefLifecycle-CXwdDw5e.js';
|
|
17
17
|
import '@lucern/access-control/structuredMutationError';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as resolveTopicProjectScope, T as TopicProjectScope } from './topicScope-
|
|
1
|
+
import { r as resolveTopicProjectScope, T as TopicProjectScope } from './topicScope-CL1IVOmv.js';
|
|
2
2
|
import * as convex_values from 'convex/values';
|
|
3
3
|
import { VerificationConfidenceTrigger, SLOperator, SLOpinion, Opinion, ConfidencePolicyConfig } from '@lucern/confidence';
|
|
4
4
|
import { B as BeliefLifecycleStatus } from './beliefLifecycle-CXwdDw5e.js';
|
|
@@ -6,6 +6,7 @@ import { normalizeTupleContradictionPolicy, confidenceFromSL, readOpinionFromRec
|
|
|
6
6
|
import { v } from 'convex/values';
|
|
7
7
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
8
8
|
import { componentsGeneric } from 'convex/server';
|
|
9
|
+
import { isUuidV7 } from '@lucern/contracts/ids';
|
|
9
10
|
import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
10
11
|
|
|
11
12
|
// src/epistemicBeliefs.helpers.ts
|
|
@@ -513,6 +514,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
513
514
|
...resolverOverrides
|
|
514
515
|
};
|
|
515
516
|
}
|
|
517
|
+
|
|
518
|
+
// src/topicScope.ts
|
|
516
519
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
517
520
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
518
521
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -533,16 +536,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
533
536
|
if (!node) {
|
|
534
537
|
return null;
|
|
535
538
|
}
|
|
536
|
-
const scopeKey =
|
|
539
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
537
540
|
if (!scopeKey) {
|
|
538
|
-
|
|
541
|
+
throw new Error(
|
|
542
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
543
|
+
);
|
|
539
544
|
}
|
|
545
|
+
const metadata = node.metadata ?? {};
|
|
540
546
|
return {
|
|
541
547
|
topicId: scopeKey,
|
|
542
548
|
projectId: asMappedProjectId(node),
|
|
543
|
-
source: "topic_node"
|
|
549
|
+
source: "topic_node",
|
|
550
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
551
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
544
552
|
};
|
|
545
553
|
}
|
|
554
|
+
function canonicalTopicGlobalId(node) {
|
|
555
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
556
|
+
if (globalId && isUuidV7(globalId)) {
|
|
557
|
+
return globalId;
|
|
558
|
+
}
|
|
559
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
560
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
561
|
+
}
|
|
562
|
+
function requireUuidV7TopicScope(field, value) {
|
|
563
|
+
if (!isUuidV7(value)) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
546
569
|
function asMappedProjectId(topic) {
|
|
547
570
|
if (!topic) {
|
|
548
571
|
return;
|
|
@@ -564,200 +587,25 @@ function normalizeScopeValue(value) {
|
|
|
564
587
|
const normalized = value.trim();
|
|
565
588
|
return normalized.length > 0 ? normalized : void 0;
|
|
566
589
|
}
|
|
567
|
-
function pickPrimaryTopic(candidates) {
|
|
568
|
-
return [...candidates].sort((a, b) => {
|
|
569
|
-
const depthA = a.depth ?? 9999;
|
|
570
|
-
const depthB = b.depth ?? 9999;
|
|
571
|
-
if (depthA !== depthB) {
|
|
572
|
-
return depthA - depthB;
|
|
573
|
-
}
|
|
574
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
575
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
576
|
-
if (createdA !== createdB) {
|
|
577
|
-
return createdA - createdB;
|
|
578
|
-
}
|
|
579
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
580
|
-
})[0];
|
|
581
|
-
}
|
|
582
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
583
|
-
const query = ctx.db.query("topics");
|
|
584
|
-
try {
|
|
585
|
-
return await query.withIndex(
|
|
586
|
-
"by_graph_scope_project",
|
|
587
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
588
|
-
).collect();
|
|
589
|
-
} catch (error) {
|
|
590
|
-
debugGraphPrimitiveFallback(
|
|
591
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
592
|
-
{
|
|
593
|
-
error,
|
|
594
|
-
scopeId
|
|
595
|
-
}
|
|
596
|
-
);
|
|
597
|
-
const topics = await query.collect();
|
|
598
|
-
return topics.filter((topic) => {
|
|
599
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
600
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
601
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
606
|
-
if (typeof ctx.runQuery !== "function") {
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
try {
|
|
610
|
-
return await ctx.runQuery(api.topics.get, {
|
|
611
|
-
id: topicId
|
|
612
|
-
}) ?? null;
|
|
613
|
-
} catch (error) {
|
|
614
|
-
debugGraphPrimitiveFallback(
|
|
615
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
616
|
-
{
|
|
617
|
-
error,
|
|
618
|
-
topicId
|
|
619
|
-
}
|
|
620
|
-
);
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
625
|
-
if (typeof ctx.runQuery !== "function") {
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
try {
|
|
629
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
630
|
-
projectId: legacyScopeId
|
|
631
|
-
}) ?? null;
|
|
632
|
-
} catch (error) {
|
|
633
|
-
debugGraphPrimitiveFallback(
|
|
634
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
635
|
-
{
|
|
636
|
-
error,
|
|
637
|
-
legacyScopeId
|
|
638
|
-
}
|
|
639
|
-
);
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
644
|
-
const MAX_DEPTH = 10;
|
|
645
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
646
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
647
|
-
if (tenantId && workspaceId) {
|
|
648
|
-
return { tenantId, workspaceId };
|
|
649
|
-
}
|
|
650
|
-
let current = topic;
|
|
651
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
652
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
653
|
-
if (!current) {
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
if (!tenantId) {
|
|
657
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
658
|
-
}
|
|
659
|
-
if (!workspaceId) {
|
|
660
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
661
|
-
}
|
|
662
|
-
if (tenantId && workspaceId) {
|
|
663
|
-
break;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
return { tenantId, workspaceId };
|
|
667
|
-
}
|
|
668
590
|
async function resolveTopicProjectScope(ctx, args) {
|
|
669
591
|
if (args.topicId) {
|
|
670
592
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
671
593
|
}
|
|
672
594
|
if (args.projectId) {
|
|
673
|
-
|
|
595
|
+
throw new Error(
|
|
596
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
597
|
+
);
|
|
674
598
|
}
|
|
675
|
-
throw new Error(
|
|
676
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
677
|
-
);
|
|
599
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
678
600
|
}
|
|
679
601
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
602
|
+
const topicGlobalId = String(topicId);
|
|
603
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
604
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
685
605
|
if (nodeScope) {
|
|
686
606
|
return nodeScope;
|
|
687
607
|
}
|
|
688
|
-
throw new Error(`Topic not found: ${
|
|
689
|
-
}
|
|
690
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
691
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
692
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
693
|
-
idLogKey: "topicId"
|
|
694
|
-
});
|
|
695
|
-
if (direct) {
|
|
696
|
-
return direct;
|
|
697
|
-
}
|
|
698
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
699
|
-
if (hostTopic) {
|
|
700
|
-
return hostTopic;
|
|
701
|
-
}
|
|
702
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
703
|
-
}
|
|
704
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
705
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
706
|
-
ctx,
|
|
707
|
-
legacyProjectId
|
|
708
|
-
);
|
|
709
|
-
if (directTopic) {
|
|
710
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
711
|
-
fallbackProjectId: legacyProjectId
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
const primary = pickPrimaryTopic(
|
|
715
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
716
|
-
);
|
|
717
|
-
if (primary) {
|
|
718
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
719
|
-
fallbackProjectId: legacyProjectId
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
723
|
-
if (nodeScope) {
|
|
724
|
-
return {
|
|
725
|
-
...nodeScope,
|
|
726
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
throw new Error(
|
|
730
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
734
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
735
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
736
|
-
idLogKey: "projectId"
|
|
737
|
-
});
|
|
738
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
739
|
-
}
|
|
740
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
741
|
-
try {
|
|
742
|
-
return await ctx.db.get(id);
|
|
743
|
-
} catch (error) {
|
|
744
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
745
|
-
error,
|
|
746
|
-
[log.idLogKey]: id
|
|
747
|
-
});
|
|
748
|
-
return null;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
752
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
753
|
-
const mapped = asMappedProjectId(topic);
|
|
754
|
-
return {
|
|
755
|
-
topicId: topic._id,
|
|
756
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
757
|
-
tenantId: inherited.tenantId,
|
|
758
|
-
workspaceId: inherited.workspaceId,
|
|
759
|
-
source
|
|
760
|
-
};
|
|
608
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
761
609
|
}
|
|
762
610
|
var optionalScopeArgs = {
|
|
763
611
|
projectId: v.optional(v.string()),
|