@lucern/graph-primitives 1.0.22 → 1.0.24
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 +49 -0
- package/dist/beliefDecay.js.map +1 -1
- package/dist/beliefEvidenceLinks.js +99 -5
- package/dist/beliefEvidenceLinks.js.map +1 -1
- package/dist/beliefEvidenceLinks.operational.js +50 -5
- package/dist/beliefEvidenceLinks.operational.js.map +1 -1
- package/dist/contradictions.js +46 -0
- package/dist/contradictions.js.map +1 -1
- package/dist/edgeValidation.js +66 -1
- package/dist/edgeValidation.js.map +1 -1
- package/dist/entityBridge.js +66 -1
- package/dist/entityBridge.js.map +1 -1
- package/dist/entityCanonicalMatch.d.ts +40 -0
- package/dist/entityCanonicalMatch.js +33 -0
- package/dist/entityCanonicalMatch.js.map +1 -0
- package/dist/entityLifecycle.js +149 -39
- package/dist/entityLifecycle.js.map +1 -1
- package/dist/epistemicAnswers.js +64 -11
- package/dist/epistemicAnswers.js.map +1 -1
- package/dist/epistemicBeliefs.admin.js +63 -6
- package/dist/epistemicBeliefs.admin.js.map +1 -1
- package/dist/epistemicBeliefs.backfills.js +59 -2
- package/dist/epistemicBeliefs.backfills.js.map +1 -1
- package/dist/epistemicBeliefs.confidence.d.ts +1 -1
- package/dist/epistemicBeliefs.confidence.js +70 -12
- package/dist/epistemicBeliefs.confidence.js.map +1 -1
- package/dist/epistemicBeliefs.core.js +120 -17
- package/dist/epistemicBeliefs.core.js.map +1 -1
- package/dist/epistemicBeliefs.d.ts +1 -1
- package/dist/epistemicBeliefs.forkEvidence.js +13 -2
- package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +18 -3
- package/dist/epistemicBeliefs.helpers.js +66 -6
- package/dist/epistemicBeliefs.helpers.js.map +1 -1
- package/dist/epistemicBeliefs.internal.js +115 -12
- package/dist/epistemicBeliefs.internal.js.map +1 -1
- package/dist/epistemicBeliefs.js +132 -28
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicBeliefs.lifecycle.js +70 -12
- package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
- package/dist/epistemicBeliefs.links.js +111 -10
- package/dist/epistemicBeliefs.links.js.map +1 -1
- package/dist/epistemicBeliefs.topicAnchor.js +48 -8
- package/dist/epistemicBeliefs.topicAnchor.js.map +1 -1
- package/dist/epistemicContracts.evaluators.js +70 -12
- package/dist/epistemicContracts.evaluators.js.map +1 -1
- package/dist/epistemicContracts.handlers.js +71 -16
- package/dist/epistemicContracts.handlers.js.map +1 -1
- package/dist/epistemicContracts.js +71 -16
- package/dist/epistemicContracts.js.map +1 -1
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.handlers.js +57 -3
- package/dist/epistemicEdges.handlers.js.map +1 -1
- package/dist/epistemicEdges.helpers.d.ts +2 -2
- package/dist/epistemicEdges.js +174 -4
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/epistemicEdges.mutations.js +115 -1
- package/dist/epistemicEdges.mutations.js.map +1 -1
- package/dist/epistemicEdges.queries.js +46 -0
- package/dist/epistemicEdges.queries.js.map +1 -1
- package/dist/epistemicEdges.types.d.ts +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +180 -14
- package/dist/epistemicEvidence.js.map +1 -1
- package/dist/epistemicEvidenceHelpers.d.ts +1 -1
- package/dist/epistemicEvidenceHelpers.js +49 -0
- package/dist/epistemicEvidenceHelpers.js.map +1 -1
- package/dist/epistemicEvidenceMutations.js +180 -14
- package/dist/epistemicEvidenceMutations.js.map +1 -1
- package/dist/epistemicEvidenceQueries.js +49 -0
- package/dist/epistemicEvidenceQueries.js.map +1 -1
- package/dist/epistemicHelpers.js +11 -6
- package/dist/epistemicHelpers.js.map +1 -1
- package/dist/epistemicInsert.d.ts +8 -0
- package/dist/epistemicInsert.js +54 -0
- package/dist/epistemicInsert.js.map +1 -0
- package/dist/epistemicNodeCreation.js +11 -6
- package/dist/epistemicNodeCreation.js.map +1 -1
- package/dist/epistemicNodes.helpers.d.ts +1 -1
- package/dist/epistemicNodes.internal.js +53 -1
- package/dist/epistemicNodes.internal.js.map +1 -1
- package/dist/epistemicNodes.js +56 -4
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/epistemicNodes.mutations.js +55 -3
- package/dist/epistemicNodes.mutations.js.map +1 -1
- package/dist/epistemicNodes.queries.js +46 -0
- package/dist/epistemicNodes.queries.js.map +1 -1
- package/dist/epistemicNodes.validators.d.ts +1 -1
- package/dist/epistemicQuestions.conviction.js +49 -0
- package/dist/epistemicQuestions.conviction.js.map +1 -1
- package/dist/epistemicQuestions.create.js +61 -7
- package/dist/epistemicQuestions.create.js.map +1 -1
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.evidence.js +56 -2
- package/dist/epistemicQuestions.evidence.js.map +1 -1
- package/dist/epistemicQuestions.helpers.d.ts +1 -1
- package/dist/epistemicQuestions.helpers.js +49 -0
- package/dist/epistemicQuestions.helpers.js.map +1 -1
- package/dist/epistemicQuestions.js +63 -9
- package/dist/epistemicQuestions.js.map +1 -1
- package/dist/epistemicQuestions.lifecycle.js +49 -0
- package/dist/epistemicQuestions.lifecycle.js.map +1 -1
- package/dist/epistemicQuestions.queries.js +49 -0
- package/dist/epistemicQuestions.queries.js.map +1 -1
- package/dist/epistemicQuestions.sprint.js +46 -0
- package/dist/epistemicQuestions.sprint.js.map +1 -1
- package/dist/epistemicQuestions.tail.js +56 -2
- package/dist/epistemicQuestions.tail.js.map +1 -1
- package/dist/epistemicSources.js +53 -2
- package/dist/epistemicSources.js.map +1 -1
- package/dist/helpers.js +66 -1
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +379 -115
- package/dist/index.js.map +1 -1
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +49 -0
- package/dist/questionEvidenceLinks.js.map +1 -1
- package/dist/resolvers.js +3 -0
- package/dist/resolvers.js.map +1 -1
- package/dist/scopeResolverCompat.d.ts +1 -1
- package/dist/scopeResolverCompat.js +46 -0
- package/dist/scopeResolverCompat.js.map +1 -1
- package/dist/topicProjectOverlay.d.ts +4 -0
- package/dist/topicProjectOverlay.js +3 -0
- package/dist/topicProjectOverlay.js.map +1 -1
- package/dist/{topicScope-By_zp4tt.d.ts → topicScope-7zhyeGl7.d.ts} +1 -1
- package/dist/topicScope.d.ts +1 -1
- package/dist/topicScope.js +46 -0
- package/dist/topicScope.js.map +1 -1
- package/dist/workflowBridge.js +46 -0
- package/dist/workflowBridge.js.map +1 -1
- package/dist/workspaceIsolation.d.ts +1 -1
- package/dist/workspaceIsolation.js +46 -0
- package/dist/workspaceIsolation.js.map +1 -1
- package/package.json +4 -4
package/dist/entityLifecycle.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { v } from 'convex/values';
|
|
1
|
+
import { v, ConvexError } from 'convex/values';
|
|
2
2
|
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
3
3
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
4
4
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
5
5
|
import { componentsGeneric, anyApi, queryGeneric, mutationGeneric } from 'convex/server';
|
|
6
|
-
import { generateGlobalId } from '@lucern/contracts/ids';
|
|
6
|
+
import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
7
|
+
import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
|
|
7
8
|
|
|
8
9
|
// src/entityLifecycle.ts
|
|
9
10
|
var api = anyApi;
|
|
@@ -76,6 +77,36 @@ async function validateEntityMetadata(ctx, nodeType, metadata, tenantId) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// src/entityCanonicalMatch.ts
|
|
81
|
+
function normalizeCanonicalEntityText(value) {
|
|
82
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
83
|
+
}
|
|
84
|
+
function buildEntityTitle(canonicalText) {
|
|
85
|
+
return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
|
|
86
|
+
}
|
|
87
|
+
function matchesCanonicalEntityRecord(node, args) {
|
|
88
|
+
if (node.epistemicLayer !== "ontological") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (node.workspaceId !== void 0) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (args.nodeType && node.nodeType !== args.nodeType) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (args.contentHash && node.contentHash !== args.contentHash) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
|
|
101
|
+
if (rowTenantId !== args.tenantId) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (args.includeArchived) {
|
|
105
|
+
return node.status !== "deleted";
|
|
106
|
+
}
|
|
107
|
+
return node.status === "active";
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
// src/topicOntologyResolver.ts
|
|
80
111
|
var MAX_RESOLUTION_DEPTH = 10;
|
|
81
112
|
async function resolveTopicOntologyInternal(ctx, topicId) {
|
|
@@ -285,6 +316,9 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
|
285
316
|
type: mapProjectType(topic, metadata),
|
|
286
317
|
description: readNonEmptyString(topic.description),
|
|
287
318
|
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
319
|
+
// FR.7 creator-grant: surface the principal-shaped owner field (column-first,
|
|
320
|
+
// metadata fallback for legacy rows that recorded it in metadata).
|
|
321
|
+
ownerPrincipalId: readNonEmptyString(topic.ownerPrincipalId) || readNonEmptyString(metadata.ownerPrincipalId),
|
|
288
322
|
sharedWith: readStringArray(metadata.sharedWith),
|
|
289
323
|
visibility,
|
|
290
324
|
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
@@ -479,6 +513,35 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
479
513
|
};
|
|
480
514
|
}
|
|
481
515
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
516
|
+
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
517
|
+
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
let node = null;
|
|
521
|
+
try {
|
|
522
|
+
const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
|
|
523
|
+
if (byGlobalId && byGlobalId.nodeType === "topic") {
|
|
524
|
+
node = byGlobalId;
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
debugGraphPrimitiveFallback(
|
|
528
|
+
"[topicScope] topic-node scope lookup by globalId failed",
|
|
529
|
+
{ error, ref }
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (!node) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
|
|
536
|
+
if (!scopeKey) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
topicId: scopeKey,
|
|
541
|
+
projectId: asMappedProjectId(node),
|
|
542
|
+
source: "topic_node"
|
|
543
|
+
};
|
|
544
|
+
}
|
|
482
545
|
function asMappedProjectId(topic) {
|
|
483
546
|
if (!topic) {
|
|
484
547
|
return;
|
|
@@ -619,6 +682,13 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
619
682
|
) ?? null;
|
|
620
683
|
}
|
|
621
684
|
if (!topic) {
|
|
685
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
686
|
+
ctx,
|
|
687
|
+
String(args.topicId)
|
|
688
|
+
);
|
|
689
|
+
if (nodeScope) {
|
|
690
|
+
return nodeScope;
|
|
691
|
+
}
|
|
622
692
|
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
623
693
|
}
|
|
624
694
|
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
@@ -689,6 +759,16 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
689
759
|
source: "project_mapped_topic"
|
|
690
760
|
};
|
|
691
761
|
}
|
|
762
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
763
|
+
ctx,
|
|
764
|
+
String(args.projectId)
|
|
765
|
+
);
|
|
766
|
+
if (nodeScope) {
|
|
767
|
+
return {
|
|
768
|
+
...nodeScope,
|
|
769
|
+
projectId: nodeScope.projectId ?? String(args.projectId)
|
|
770
|
+
};
|
|
771
|
+
}
|
|
692
772
|
throw new Error(
|
|
693
773
|
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
694
774
|
);
|
|
@@ -701,10 +781,67 @@ var optionalScopeArgs = {
|
|
|
701
781
|
projectId: v.optional(v.string()),
|
|
702
782
|
topicId: v.optional(v.string())
|
|
703
783
|
};
|
|
784
|
+
async function insertEpistemicNode(ctx, doc) {
|
|
785
|
+
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
786
|
+
return ctx.db.insert("epistemicNodes", doc);
|
|
787
|
+
}
|
|
788
|
+
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|
|
789
|
+
assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
|
|
790
|
+
const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
|
|
791
|
+
if (!node) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
`edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async function insertEpistemicEdge(ctx, doc) {
|
|
798
|
+
assertUuidV7Identity("epistemicEdges", doc.globalId);
|
|
799
|
+
assertStorageEdgeVocabulary(doc.edgeType);
|
|
800
|
+
if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
|
|
801
|
+
throw new Error(
|
|
802
|
+
"edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
|
|
806
|
+
throw new Error(
|
|
807
|
+
"edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
|
|
811
|
+
await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
|
|
812
|
+
if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
|
|
813
|
+
assertEdgePolicyAllowed(
|
|
814
|
+
edgePolicyManifest,
|
|
815
|
+
doc.edgeType,
|
|
816
|
+
{
|
|
817
|
+
kind: "epistemic_node",
|
|
818
|
+
nodeId: doc.fromNodeId,
|
|
819
|
+
nodeType: doc.fromNodeType
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
kind: "epistemic_node",
|
|
823
|
+
nodeId: doc.toNodeId,
|
|
824
|
+
nodeType: doc.toNodeType
|
|
825
|
+
}
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return ctx.db.insert("epistemicEdges", doc);
|
|
829
|
+
}
|
|
704
830
|
|
|
705
831
|
// src/entityLifecycle.ts
|
|
706
832
|
function throwStructuredMutationError(args) {
|
|
707
|
-
const
|
|
833
|
+
const data = {
|
|
834
|
+
structuredMutationError: true,
|
|
835
|
+
message: args.message,
|
|
836
|
+
status: args.status,
|
|
837
|
+
code: args.code,
|
|
838
|
+
invariantCode: args.invariantCode,
|
|
839
|
+
suggestion: args.suggestion,
|
|
840
|
+
details: args.details
|
|
841
|
+
};
|
|
842
|
+
const error = new ConvexError(
|
|
843
|
+
data
|
|
844
|
+
);
|
|
708
845
|
error.status = args.status;
|
|
709
846
|
error.code = args.code;
|
|
710
847
|
error.invariantCode = args.invariantCode;
|
|
@@ -729,12 +866,12 @@ async function requireProjectWriteAccess(ctx, projectId, userId) {
|
|
|
729
866
|
const hasAccess = await checkProjectAccess(ctx, projectId, userId);
|
|
730
867
|
if (!hasAccess) {
|
|
731
868
|
throwStructuredMutationError({
|
|
732
|
-
message:
|
|
869
|
+
message: `Project write access denied for topic ${projectId}.`,
|
|
733
870
|
status: 403,
|
|
734
|
-
code: "
|
|
871
|
+
code: "PROJECT_ACCESS_DENIED",
|
|
735
872
|
invariantCode: "policy.scope_required",
|
|
736
|
-
suggestion: "
|
|
737
|
-
details: { projectId, userId }
|
|
873
|
+
suggestion: "The acting principal lacks project-write access to this topic. Request a topic grant (or, if the principal created this topic, run the creator-grant backfill) and retry.",
|
|
874
|
+
details: { topicId: projectId, principalId: userId }
|
|
738
875
|
});
|
|
739
876
|
}
|
|
740
877
|
}
|
|
@@ -758,12 +895,6 @@ var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
|
|
|
758
895
|
var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
|
|
759
896
|
ONTOLOGICAL_NODE_TYPE_SET
|
|
760
897
|
);
|
|
761
|
-
function normalizeCanonicalEntityText(value) {
|
|
762
|
-
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
763
|
-
}
|
|
764
|
-
function buildEntityTitle(canonicalText) {
|
|
765
|
-
return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
|
|
766
|
-
}
|
|
767
898
|
async function resolveCanonicalEntityScope(ctx, args) {
|
|
768
899
|
const scope = await resolveTopicProjectScope(ctx, args);
|
|
769
900
|
const topic = scope.topicId ? await ctx.db.get(scope.topicId) ?? null : null;
|
|
@@ -779,28 +910,6 @@ async function resolveCanonicalEntityScope(ctx, args) {
|
|
|
779
910
|
project
|
|
780
911
|
};
|
|
781
912
|
}
|
|
782
|
-
function matchesCanonicalEntityRecord(node, args) {
|
|
783
|
-
if (node.epistemicLayer !== "ontological") {
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
|
-
if (node.workspaceId !== void 0) {
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
|
-
if (args.nodeType && node.nodeType !== args.nodeType) {
|
|
790
|
-
return false;
|
|
791
|
-
}
|
|
792
|
-
if (args.contentHash && node.contentHash !== args.contentHash) {
|
|
793
|
-
return false;
|
|
794
|
-
}
|
|
795
|
-
const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
|
|
796
|
-
if (rowTenantId !== args.tenantId) {
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
if (args.includeArchived) {
|
|
800
|
-
return node.status !== "deleted";
|
|
801
|
-
}
|
|
802
|
-
return node.status === "active";
|
|
803
|
-
}
|
|
804
913
|
async function findExistingCanonicalEntity(ctx, args) {
|
|
805
914
|
const contentHash = generateContentHash(args.nodeType, args.canonicalText);
|
|
806
915
|
const candidates = await ctx.db.query("epistemicNodes").withIndex(
|
|
@@ -966,7 +1075,7 @@ var createEntity = mutation({
|
|
|
966
1075
|
};
|
|
967
1076
|
}
|
|
968
1077
|
const entityGlobalId = generateGlobalId();
|
|
969
|
-
const nodeId = await ctx
|
|
1078
|
+
const nodeId = await insertEpistemicNode(ctx, {
|
|
970
1079
|
globalId: entityGlobalId,
|
|
971
1080
|
nodeType: args.nodeType,
|
|
972
1081
|
epistemicLayer: "ontological",
|
|
@@ -1218,10 +1327,11 @@ var mergeEntities = mutation({
|
|
|
1218
1327
|
);
|
|
1219
1328
|
}
|
|
1220
1329
|
const edgeGlobalId = generateGlobalId();
|
|
1221
|
-
await ctx
|
|
1330
|
+
await insertEpistemicEdge(ctx, {
|
|
1222
1331
|
globalId: edgeGlobalId,
|
|
1223
|
-
|
|
1224
|
-
|
|
1332
|
+
// C2-RR.4 Defect E — canonical UUIDv7 endpoints, not Convex doc ids.
|
|
1333
|
+
fromNodeId: duplicate.globalId,
|
|
1334
|
+
toNodeId: canonical.globalId,
|
|
1225
1335
|
sourceGlobalId: duplicate.globalId,
|
|
1226
1336
|
targetGlobalId: canonical.globalId,
|
|
1227
1337
|
edgeType: "derived_from",
|