@lucern/graph-primitives 1.0.50 → 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 +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 { componentsGeneric, internalQueryGeneric, internalMutationGeneric } from
|
|
|
8
8
|
import '@lucern/access-control/auth';
|
|
9
9
|
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
10
10
|
import { normalizeTupleContradictionPolicy, 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
|
|
|
@@ -520,6 +520,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
520
520
|
...resolverOverrides
|
|
521
521
|
};
|
|
522
522
|
}
|
|
523
|
+
|
|
524
|
+
// src/topicScope.ts
|
|
523
525
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
524
526
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
525
527
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -540,16 +542,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
540
542
|
if (!node) {
|
|
541
543
|
return null;
|
|
542
544
|
}
|
|
543
|
-
const scopeKey =
|
|
545
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
544
546
|
if (!scopeKey) {
|
|
545
|
-
|
|
547
|
+
throw new Error(
|
|
548
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
549
|
+
);
|
|
546
550
|
}
|
|
551
|
+
const metadata = node.metadata ?? {};
|
|
547
552
|
return {
|
|
548
553
|
topicId: scopeKey,
|
|
549
554
|
projectId: asMappedProjectId(node),
|
|
550
|
-
source: "topic_node"
|
|
555
|
+
source: "topic_node",
|
|
556
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
557
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
551
558
|
};
|
|
552
559
|
}
|
|
560
|
+
function canonicalTopicGlobalId(node) {
|
|
561
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
562
|
+
if (globalId && isUuidV7(globalId)) {
|
|
563
|
+
return globalId;
|
|
564
|
+
}
|
|
565
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
566
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
567
|
+
}
|
|
568
|
+
function requireUuidV7TopicScope(field, value) {
|
|
569
|
+
if (!isUuidV7(value)) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
553
575
|
function asMappedProjectId(topic) {
|
|
554
576
|
if (!topic) {
|
|
555
577
|
return;
|
|
@@ -571,200 +593,25 @@ function normalizeScopeValue(value) {
|
|
|
571
593
|
const normalized = value.trim();
|
|
572
594
|
return normalized.length > 0 ? normalized : void 0;
|
|
573
595
|
}
|
|
574
|
-
function pickPrimaryTopic(candidates) {
|
|
575
|
-
return [...candidates].sort((a, b) => {
|
|
576
|
-
const depthA = a.depth ?? 9999;
|
|
577
|
-
const depthB = b.depth ?? 9999;
|
|
578
|
-
if (depthA !== depthB) {
|
|
579
|
-
return depthA - depthB;
|
|
580
|
-
}
|
|
581
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
582
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
583
|
-
if (createdA !== createdB) {
|
|
584
|
-
return createdA - createdB;
|
|
585
|
-
}
|
|
586
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
587
|
-
})[0];
|
|
588
|
-
}
|
|
589
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
590
|
-
const query = ctx.db.query("topics");
|
|
591
|
-
try {
|
|
592
|
-
return await query.withIndex(
|
|
593
|
-
"by_graph_scope_project",
|
|
594
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
595
|
-
).collect();
|
|
596
|
-
} catch (error) {
|
|
597
|
-
debugGraphPrimitiveFallback(
|
|
598
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
599
|
-
{
|
|
600
|
-
error,
|
|
601
|
-
scopeId
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
const topics = await query.collect();
|
|
605
|
-
return topics.filter((topic) => {
|
|
606
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
607
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
608
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
613
|
-
if (typeof ctx.runQuery !== "function") {
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
try {
|
|
617
|
-
return await ctx.runQuery(api.topics.get, {
|
|
618
|
-
id: topicId
|
|
619
|
-
}) ?? null;
|
|
620
|
-
} catch (error) {
|
|
621
|
-
debugGraphPrimitiveFallback(
|
|
622
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
623
|
-
{
|
|
624
|
-
error,
|
|
625
|
-
topicId
|
|
626
|
-
}
|
|
627
|
-
);
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
632
|
-
if (typeof ctx.runQuery !== "function") {
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
try {
|
|
636
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
637
|
-
projectId: legacyScopeId
|
|
638
|
-
}) ?? null;
|
|
639
|
-
} catch (error) {
|
|
640
|
-
debugGraphPrimitiveFallback(
|
|
641
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
642
|
-
{
|
|
643
|
-
error,
|
|
644
|
-
legacyScopeId
|
|
645
|
-
}
|
|
646
|
-
);
|
|
647
|
-
return null;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
651
|
-
const MAX_DEPTH = 10;
|
|
652
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
653
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
654
|
-
if (tenantId && workspaceId) {
|
|
655
|
-
return { tenantId, workspaceId };
|
|
656
|
-
}
|
|
657
|
-
let current = topic;
|
|
658
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
659
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
660
|
-
if (!current) {
|
|
661
|
-
break;
|
|
662
|
-
}
|
|
663
|
-
if (!tenantId) {
|
|
664
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
665
|
-
}
|
|
666
|
-
if (!workspaceId) {
|
|
667
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
668
|
-
}
|
|
669
|
-
if (tenantId && workspaceId) {
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return { tenantId, workspaceId };
|
|
674
|
-
}
|
|
675
596
|
async function resolveTopicProjectScope(ctx, args) {
|
|
676
597
|
if (args.topicId) {
|
|
677
598
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
678
599
|
}
|
|
679
600
|
if (args.projectId) {
|
|
680
|
-
|
|
601
|
+
throw new Error(
|
|
602
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
603
|
+
);
|
|
681
604
|
}
|
|
682
|
-
throw new Error(
|
|
683
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
684
|
-
);
|
|
605
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
685
606
|
}
|
|
686
607
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
608
|
+
const topicGlobalId = String(topicId);
|
|
609
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
610
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
692
611
|
if (nodeScope) {
|
|
693
612
|
return nodeScope;
|
|
694
613
|
}
|
|
695
|
-
throw new Error(`Topic not found: ${
|
|
696
|
-
}
|
|
697
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
698
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
699
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
700
|
-
idLogKey: "topicId"
|
|
701
|
-
});
|
|
702
|
-
if (direct) {
|
|
703
|
-
return direct;
|
|
704
|
-
}
|
|
705
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
706
|
-
if (hostTopic) {
|
|
707
|
-
return hostTopic;
|
|
708
|
-
}
|
|
709
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
710
|
-
}
|
|
711
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
712
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
713
|
-
ctx,
|
|
714
|
-
legacyProjectId
|
|
715
|
-
);
|
|
716
|
-
if (directTopic) {
|
|
717
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
718
|
-
fallbackProjectId: legacyProjectId
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
const primary = pickPrimaryTopic(
|
|
722
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
723
|
-
);
|
|
724
|
-
if (primary) {
|
|
725
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
726
|
-
fallbackProjectId: legacyProjectId
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
730
|
-
if (nodeScope) {
|
|
731
|
-
return {
|
|
732
|
-
...nodeScope,
|
|
733
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
throw new Error(
|
|
737
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
741
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
742
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
743
|
-
idLogKey: "projectId"
|
|
744
|
-
});
|
|
745
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
746
|
-
}
|
|
747
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
748
|
-
try {
|
|
749
|
-
return await ctx.db.get(id);
|
|
750
|
-
} catch (error) {
|
|
751
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
752
|
-
error,
|
|
753
|
-
[log.idLogKey]: id
|
|
754
|
-
});
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
759
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
760
|
-
const mapped = asMappedProjectId(topic);
|
|
761
|
-
return {
|
|
762
|
-
topicId: topic._id,
|
|
763
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
764
|
-
tenantId: inherited.tenantId,
|
|
765
|
-
workspaceId: inherited.workspaceId,
|
|
766
|
-
source
|
|
767
|
-
};
|
|
614
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
768
615
|
}
|
|
769
616
|
var optionalScopeArgs = {
|
|
770
617
|
projectId: v.optional(v.string()),
|
|
@@ -1212,6 +1059,9 @@ function resolveBeliefStatus(node, metadata) {
|
|
|
1212
1059
|
}
|
|
1213
1060
|
function insertEpistemicNode(ctx, doc) {
|
|
1214
1061
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
1062
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
1063
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
1064
|
+
}
|
|
1215
1065
|
return ctx.db.insert("epistemicNodes", doc);
|
|
1216
1066
|
}
|
|
1217
1067
|
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|
package/dist/epistemicBeliefs.js
CHANGED
|
@@ -7,8 +7,8 @@ import { canAudienceClassAccess, normalizeAudienceKey, classFromAudienceKey } fr
|
|
|
7
7
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
8
8
|
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
9
9
|
import { normalizeTupleContradictionPolicy, mkOpinion, createInheritedContractRecord, readOpinionFromRecord, confidenceFromSL, conditionalDeduction, project, dampedDependencyCascade, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, detectTupleContradiction, evaluateTupleContradictionTransition, hasProjectedOpinionChanged } from '@lucern/confidence';
|
|
10
|
+
import { generateUuidV7, generateGlobalId, assertUuidV7Identity, assertUuidV7Reference, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
10
11
|
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
11
|
-
import { generateUuidV7, generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
12
12
|
import { assertSchemaEnumValue } from '@lucern/contracts/schema-helpers/enumValidation';
|
|
13
13
|
import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
|
|
14
14
|
import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
@@ -533,6 +533,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
533
533
|
...resolverOverrides
|
|
534
534
|
};
|
|
535
535
|
}
|
|
536
|
+
|
|
537
|
+
// src/topicScope.ts
|
|
536
538
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
537
539
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
538
540
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -553,16 +555,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
553
555
|
if (!node) {
|
|
554
556
|
return null;
|
|
555
557
|
}
|
|
556
|
-
const scopeKey =
|
|
558
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
557
559
|
if (!scopeKey) {
|
|
558
|
-
|
|
560
|
+
throw new Error(
|
|
561
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
562
|
+
);
|
|
559
563
|
}
|
|
564
|
+
const metadata = node.metadata ?? {};
|
|
560
565
|
return {
|
|
561
566
|
topicId: scopeKey,
|
|
562
567
|
projectId: asMappedProjectId(node),
|
|
563
|
-
source: "topic_node"
|
|
568
|
+
source: "topic_node",
|
|
569
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
570
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
564
571
|
};
|
|
565
572
|
}
|
|
573
|
+
function canonicalTopicGlobalId(node) {
|
|
574
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
575
|
+
if (globalId && isUuidV7(globalId)) {
|
|
576
|
+
return globalId;
|
|
577
|
+
}
|
|
578
|
+
const topicId2 = normalizeScopeValue(node.topicId);
|
|
579
|
+
return topicId2 && isUuidV7(topicId2) ? topicId2 : null;
|
|
580
|
+
}
|
|
581
|
+
function requireUuidV7TopicScope(field, value) {
|
|
582
|
+
if (!isUuidV7(value)) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
566
588
|
function asMappedProjectId(topic) {
|
|
567
589
|
if (!topic) {
|
|
568
590
|
return;
|
|
@@ -584,200 +606,25 @@ function normalizeScopeValue(value) {
|
|
|
584
606
|
const normalized = value.trim();
|
|
585
607
|
return normalized.length > 0 ? normalized : void 0;
|
|
586
608
|
}
|
|
587
|
-
function pickPrimaryTopic(candidates) {
|
|
588
|
-
return [...candidates].sort((a, b) => {
|
|
589
|
-
const depthA = a.depth ?? 9999;
|
|
590
|
-
const depthB = b.depth ?? 9999;
|
|
591
|
-
if (depthA !== depthB) {
|
|
592
|
-
return depthA - depthB;
|
|
593
|
-
}
|
|
594
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
595
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
596
|
-
if (createdA !== createdB) {
|
|
597
|
-
return createdA - createdB;
|
|
598
|
-
}
|
|
599
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
600
|
-
})[0];
|
|
601
|
-
}
|
|
602
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
603
|
-
const query2 = ctx.db.query("topics");
|
|
604
|
-
try {
|
|
605
|
-
return await query2.withIndex(
|
|
606
|
-
"by_graph_scope_project",
|
|
607
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
608
|
-
).collect();
|
|
609
|
-
} catch (error) {
|
|
610
|
-
debugGraphPrimitiveFallback(
|
|
611
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
612
|
-
{
|
|
613
|
-
error,
|
|
614
|
-
scopeId
|
|
615
|
-
}
|
|
616
|
-
);
|
|
617
|
-
const topics = await query2.collect();
|
|
618
|
-
return topics.filter((topic) => {
|
|
619
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
620
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
621
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
async function tryResolveHostTopicById(ctx, topicId2) {
|
|
626
|
-
if (typeof ctx.runQuery !== "function") {
|
|
627
|
-
return null;
|
|
628
|
-
}
|
|
629
|
-
try {
|
|
630
|
-
return await ctx.runQuery(api.topics.get, {
|
|
631
|
-
id: topicId2
|
|
632
|
-
}) ?? null;
|
|
633
|
-
} catch (error) {
|
|
634
|
-
debugGraphPrimitiveFallback(
|
|
635
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
636
|
-
{
|
|
637
|
-
error,
|
|
638
|
-
topicId: topicId2
|
|
639
|
-
}
|
|
640
|
-
);
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
645
|
-
if (typeof ctx.runQuery !== "function") {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
try {
|
|
649
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
650
|
-
projectId: legacyScopeId
|
|
651
|
-
}) ?? null;
|
|
652
|
-
} catch (error) {
|
|
653
|
-
debugGraphPrimitiveFallback(
|
|
654
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
655
|
-
{
|
|
656
|
-
error,
|
|
657
|
-
legacyScopeId
|
|
658
|
-
}
|
|
659
|
-
);
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
664
|
-
const MAX_DEPTH = 10;
|
|
665
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
666
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
667
|
-
if (tenantId && workspaceId) {
|
|
668
|
-
return { tenantId, workspaceId };
|
|
669
|
-
}
|
|
670
|
-
let current = topic;
|
|
671
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
672
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
673
|
-
if (!current) {
|
|
674
|
-
break;
|
|
675
|
-
}
|
|
676
|
-
if (!tenantId) {
|
|
677
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
678
|
-
}
|
|
679
|
-
if (!workspaceId) {
|
|
680
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
681
|
-
}
|
|
682
|
-
if (tenantId && workspaceId) {
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
return { tenantId, workspaceId };
|
|
687
|
-
}
|
|
688
609
|
async function resolveTopicProjectScope(ctx, args) {
|
|
689
610
|
if (args.topicId) {
|
|
690
611
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
691
612
|
}
|
|
692
613
|
if (args.projectId) {
|
|
693
|
-
|
|
614
|
+
throw new Error(
|
|
615
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
616
|
+
);
|
|
694
617
|
}
|
|
695
|
-
throw new Error(
|
|
696
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
697
|
-
);
|
|
618
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
698
619
|
}
|
|
699
620
|
async function resolveScopeFromTopicId(ctx, topicId2) {
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId2));
|
|
621
|
+
const topicGlobalId = String(topicId2);
|
|
622
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
623
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
705
624
|
if (nodeScope) {
|
|
706
625
|
return nodeScope;
|
|
707
626
|
}
|
|
708
|
-
throw new Error(`Topic not found: ${
|
|
709
|
-
}
|
|
710
|
-
async function resolveTopicDocFromTopicId(ctx, topicId2) {
|
|
711
|
-
const direct = await tryReadTopicDoc(ctx, topicId2, {
|
|
712
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
713
|
-
idLogKey: "topicId"
|
|
714
|
-
});
|
|
715
|
-
if (direct) {
|
|
716
|
-
return direct;
|
|
717
|
-
}
|
|
718
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId2));
|
|
719
|
-
if (hostTopic) {
|
|
720
|
-
return hostTopic;
|
|
721
|
-
}
|
|
722
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId2))) ?? null;
|
|
723
|
-
}
|
|
724
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
725
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
726
|
-
ctx,
|
|
727
|
-
legacyProjectId
|
|
728
|
-
);
|
|
729
|
-
if (directTopic) {
|
|
730
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
731
|
-
fallbackProjectId: legacyProjectId
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
const primary = pickPrimaryTopic(
|
|
735
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
736
|
-
);
|
|
737
|
-
if (primary) {
|
|
738
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
739
|
-
fallbackProjectId: legacyProjectId
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
743
|
-
if (nodeScope) {
|
|
744
|
-
return {
|
|
745
|
-
...nodeScope,
|
|
746
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
throw new Error(
|
|
750
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
754
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
755
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
756
|
-
idLogKey: "projectId"
|
|
757
|
-
});
|
|
758
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
759
|
-
}
|
|
760
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
761
|
-
try {
|
|
762
|
-
return await ctx.db.get(id);
|
|
763
|
-
} catch (error) {
|
|
764
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
765
|
-
error,
|
|
766
|
-
[log.idLogKey]: id
|
|
767
|
-
});
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
772
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
773
|
-
const mapped = asMappedProjectId(topic);
|
|
774
|
-
return {
|
|
775
|
-
topicId: topic._id,
|
|
776
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
777
|
-
tenantId: inherited.tenantId,
|
|
778
|
-
workspaceId: inherited.workspaceId,
|
|
779
|
-
source
|
|
780
|
-
};
|
|
627
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
781
628
|
}
|
|
782
629
|
var optionalScopeArgs = {
|
|
783
630
|
projectId: v.optional(v.string()),
|
|
@@ -3738,6 +3585,9 @@ async function resolveForkTriggerEvidence(ctx, args) {
|
|
|
3738
3585
|
}
|
|
3739
3586
|
function insertEpistemicNode(ctx, doc) {
|
|
3740
3587
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
3588
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
3589
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
3590
|
+
}
|
|
3741
3591
|
return ctx.db.insert("epistemicNodes", doc);
|
|
3742
3592
|
}
|
|
3743
3593
|
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|