@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.
Files changed (136) hide show
  1. package/dist/beliefDecay.js +49 -0
  2. package/dist/beliefDecay.js.map +1 -1
  3. package/dist/beliefEvidenceLinks.js +99 -5
  4. package/dist/beliefEvidenceLinks.js.map +1 -1
  5. package/dist/beliefEvidenceLinks.operational.js +50 -5
  6. package/dist/beliefEvidenceLinks.operational.js.map +1 -1
  7. package/dist/contradictions.js +46 -0
  8. package/dist/contradictions.js.map +1 -1
  9. package/dist/edgeValidation.js +66 -1
  10. package/dist/edgeValidation.js.map +1 -1
  11. package/dist/entityBridge.js +66 -1
  12. package/dist/entityBridge.js.map +1 -1
  13. package/dist/entityCanonicalMatch.d.ts +40 -0
  14. package/dist/entityCanonicalMatch.js +33 -0
  15. package/dist/entityCanonicalMatch.js.map +1 -0
  16. package/dist/entityLifecycle.js +149 -39
  17. package/dist/entityLifecycle.js.map +1 -1
  18. package/dist/epistemicAnswers.js +64 -11
  19. package/dist/epistemicAnswers.js.map +1 -1
  20. package/dist/epistemicBeliefs.admin.js +63 -6
  21. package/dist/epistemicBeliefs.admin.js.map +1 -1
  22. package/dist/epistemicBeliefs.backfills.js +59 -2
  23. package/dist/epistemicBeliefs.backfills.js.map +1 -1
  24. package/dist/epistemicBeliefs.confidence.d.ts +1 -1
  25. package/dist/epistemicBeliefs.confidence.js +70 -12
  26. package/dist/epistemicBeliefs.confidence.js.map +1 -1
  27. package/dist/epistemicBeliefs.core.js +120 -17
  28. package/dist/epistemicBeliefs.core.js.map +1 -1
  29. package/dist/epistemicBeliefs.d.ts +1 -1
  30. package/dist/epistemicBeliefs.forkEvidence.js +13 -2
  31. package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
  32. package/dist/epistemicBeliefs.helpers.d.ts +18 -3
  33. package/dist/epistemicBeliefs.helpers.js +66 -6
  34. package/dist/epistemicBeliefs.helpers.js.map +1 -1
  35. package/dist/epistemicBeliefs.internal.js +115 -12
  36. package/dist/epistemicBeliefs.internal.js.map +1 -1
  37. package/dist/epistemicBeliefs.js +132 -28
  38. package/dist/epistemicBeliefs.js.map +1 -1
  39. package/dist/epistemicBeliefs.lifecycle.js +70 -12
  40. package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
  41. package/dist/epistemicBeliefs.links.js +111 -10
  42. package/dist/epistemicBeliefs.links.js.map +1 -1
  43. package/dist/epistemicBeliefs.topicAnchor.js +48 -8
  44. package/dist/epistemicBeliefs.topicAnchor.js.map +1 -1
  45. package/dist/epistemicContracts.evaluators.js +70 -12
  46. package/dist/epistemicContracts.evaluators.js.map +1 -1
  47. package/dist/epistemicContracts.handlers.js +71 -16
  48. package/dist/epistemicContracts.handlers.js.map +1 -1
  49. package/dist/epistemicContracts.js +71 -16
  50. package/dist/epistemicContracts.js.map +1 -1
  51. package/dist/epistemicEdges.d.ts +1 -1
  52. package/dist/epistemicEdges.handlers.js +57 -3
  53. package/dist/epistemicEdges.handlers.js.map +1 -1
  54. package/dist/epistemicEdges.helpers.d.ts +2 -2
  55. package/dist/epistemicEdges.js +174 -4
  56. package/dist/epistemicEdges.js.map +1 -1
  57. package/dist/epistemicEdges.mutations.js +115 -1
  58. package/dist/epistemicEdges.mutations.js.map +1 -1
  59. package/dist/epistemicEdges.queries.js +46 -0
  60. package/dist/epistemicEdges.queries.js.map +1 -1
  61. package/dist/epistemicEdges.types.d.ts +1 -1
  62. package/dist/epistemicEvidence.d.ts +1 -1
  63. package/dist/epistemicEvidence.js +180 -14
  64. package/dist/epistemicEvidence.js.map +1 -1
  65. package/dist/epistemicEvidenceHelpers.d.ts +1 -1
  66. package/dist/epistemicEvidenceHelpers.js +49 -0
  67. package/dist/epistemicEvidenceHelpers.js.map +1 -1
  68. package/dist/epistemicEvidenceMutations.js +180 -14
  69. package/dist/epistemicEvidenceMutations.js.map +1 -1
  70. package/dist/epistemicEvidenceQueries.js +49 -0
  71. package/dist/epistemicEvidenceQueries.js.map +1 -1
  72. package/dist/epistemicHelpers.js +11 -6
  73. package/dist/epistemicHelpers.js.map +1 -1
  74. package/dist/epistemicInsert.d.ts +8 -0
  75. package/dist/epistemicInsert.js +54 -0
  76. package/dist/epistemicInsert.js.map +1 -0
  77. package/dist/epistemicNodeCreation.js +11 -6
  78. package/dist/epistemicNodeCreation.js.map +1 -1
  79. package/dist/epistemicNodes.helpers.d.ts +1 -1
  80. package/dist/epistemicNodes.internal.js +53 -1
  81. package/dist/epistemicNodes.internal.js.map +1 -1
  82. package/dist/epistemicNodes.js +56 -4
  83. package/dist/epistemicNodes.js.map +1 -1
  84. package/dist/epistemicNodes.mutations.js +55 -3
  85. package/dist/epistemicNodes.mutations.js.map +1 -1
  86. package/dist/epistemicNodes.queries.js +46 -0
  87. package/dist/epistemicNodes.queries.js.map +1 -1
  88. package/dist/epistemicNodes.validators.d.ts +1 -1
  89. package/dist/epistemicQuestions.conviction.js +49 -0
  90. package/dist/epistemicQuestions.conviction.js.map +1 -1
  91. package/dist/epistemicQuestions.create.js +61 -7
  92. package/dist/epistemicQuestions.create.js.map +1 -1
  93. package/dist/epistemicQuestions.d.ts +1 -1
  94. package/dist/epistemicQuestions.evidence.js +56 -2
  95. package/dist/epistemicQuestions.evidence.js.map +1 -1
  96. package/dist/epistemicQuestions.helpers.d.ts +1 -1
  97. package/dist/epistemicQuestions.helpers.js +49 -0
  98. package/dist/epistemicQuestions.helpers.js.map +1 -1
  99. package/dist/epistemicQuestions.js +63 -9
  100. package/dist/epistemicQuestions.js.map +1 -1
  101. package/dist/epistemicQuestions.lifecycle.js +49 -0
  102. package/dist/epistemicQuestions.lifecycle.js.map +1 -1
  103. package/dist/epistemicQuestions.queries.js +49 -0
  104. package/dist/epistemicQuestions.queries.js.map +1 -1
  105. package/dist/epistemicQuestions.sprint.js +46 -0
  106. package/dist/epistemicQuestions.sprint.js.map +1 -1
  107. package/dist/epistemicQuestions.tail.js +56 -2
  108. package/dist/epistemicQuestions.tail.js.map +1 -1
  109. package/dist/epistemicSources.js +53 -2
  110. package/dist/epistemicSources.js.map +1 -1
  111. package/dist/helpers.js +66 -1
  112. package/dist/helpers.js.map +1 -1
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.js +379 -115
  115. package/dist/index.js.map +1 -1
  116. package/dist/proof-attestation.json +1 -1
  117. package/dist/questionEvidenceLinks.js +49 -0
  118. package/dist/questionEvidenceLinks.js.map +1 -1
  119. package/dist/resolvers.js +3 -0
  120. package/dist/resolvers.js.map +1 -1
  121. package/dist/scopeResolverCompat.d.ts +1 -1
  122. package/dist/scopeResolverCompat.js +46 -0
  123. package/dist/scopeResolverCompat.js.map +1 -1
  124. package/dist/topicProjectOverlay.d.ts +4 -0
  125. package/dist/topicProjectOverlay.js +3 -0
  126. package/dist/topicProjectOverlay.js.map +1 -1
  127. package/dist/{topicScope-By_zp4tt.d.ts → topicScope-7zhyeGl7.d.ts} +1 -1
  128. package/dist/topicScope.d.ts +1 -1
  129. package/dist/topicScope.js +46 -0
  130. package/dist/topicScope.js.map +1 -1
  131. package/dist/workflowBridge.js +46 -0
  132. package/dist/workflowBridge.js.map +1 -1
  133. package/dist/workspaceIsolation.d.ts +1 -1
  134. package/dist/workspaceIsolation.js +46 -0
  135. package/dist/workspaceIsolation.js.map +1 -1
  136. package/package.json +4 -4
@@ -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 error = new Error(args.message);
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: "Project access required.",
869
+ message: `Project write access denied for topic ${projectId}.`,
733
870
  status: 403,
734
- code: "FORBIDDEN",
871
+ code: "PROJECT_ACCESS_DENIED",
735
872
  invariantCode: "policy.scope_required",
736
- suggestion: "Request write access for the project and retry.",
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.db.insert("epistemicNodes", {
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.db.insert("epistemicEdges", {
1330
+ await insertEpistemicEdge(ctx, {
1222
1331
  globalId: edgeGlobalId,
1223
- fromNodeId: args.duplicateNodeId,
1224
- toNodeId: args.canonicalNodeId,
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",