@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,4 +1,4 @@
1
- import { v } from 'convex/values';
1
+ import { v, ConvexError } from 'convex/values';
2
2
  import { checkScopeAccess, checkProjectAccess } from '@lucern/access-control/access';
3
3
  import { assertSchemaEnumValue } from '@lucern/contracts/schema-helpers/enumValidation';
4
4
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
@@ -7,7 +7,8 @@ import { normalizeTupleContradictionPolicy, createInheritedContractRecord, confi
7
7
  import '@lucern/access-control/audience';
8
8
  import { getCurrentUserId } from '@lucern/access-control/auth';
9
9
  import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
10
- import { generateGlobalId, generateUuidV7 } from '@lucern/contracts/ids';
10
+ import { generateGlobalId, assertUuidV7Identity, generateUuidV7, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
11
+ import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
11
12
 
12
13
  // src/epistemicBeliefs.core.ts
13
14
 
@@ -272,6 +273,9 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
272
273
  type: mapProjectType(topic, metadata),
273
274
  description: readNonEmptyString(topic.description),
274
275
  ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
276
+ // FR.7 creator-grant: surface the principal-shaped owner field (column-first,
277
+ // metadata fallback for legacy rows that recorded it in metadata).
278
+ ownerPrincipalId: readNonEmptyString(topic.ownerPrincipalId) || readNonEmptyString(metadata.ownerPrincipalId),
275
279
  sharedWith: readStringArray(metadata.sharedWith),
276
280
  visibility,
277
281
  tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
@@ -466,6 +470,35 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
466
470
  };
467
471
  }
468
472
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
473
+ async function resolveTopicNodeScopeOrNull(ctx, ref) {
474
+ if (!ctx?.db || typeof ctx.db.query !== "function") {
475
+ return null;
476
+ }
477
+ let node = null;
478
+ try {
479
+ const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
480
+ if (byGlobalId && byGlobalId.nodeType === "topic") {
481
+ node = byGlobalId;
482
+ }
483
+ } catch (error) {
484
+ debugGraphPrimitiveFallback(
485
+ "[topicScope] topic-node scope lookup by globalId failed",
486
+ { error, ref }
487
+ );
488
+ }
489
+ if (!node) {
490
+ return null;
491
+ }
492
+ const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
493
+ if (!scopeKey) {
494
+ return null;
495
+ }
496
+ return {
497
+ topicId: scopeKey,
498
+ projectId: asMappedProjectId(node),
499
+ source: "topic_node"
500
+ };
501
+ }
469
502
  function asMappedProjectId(topic) {
470
503
  if (!topic) {
471
504
  return;
@@ -606,6 +639,13 @@ async function resolveTopicProjectScope(ctx, args) {
606
639
  ) ?? null;
607
640
  }
608
641
  if (!topic) {
642
+ const nodeScope = await resolveTopicNodeScopeOrNull(
643
+ ctx,
644
+ String(args.topicId)
645
+ );
646
+ if (nodeScope) {
647
+ return nodeScope;
648
+ }
609
649
  throw new Error(`Topic not found: ${String(args.topicId)}`);
610
650
  }
611
651
  const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
@@ -676,6 +716,16 @@ async function resolveTopicProjectScope(ctx, args) {
676
716
  source: "project_mapped_topic"
677
717
  };
678
718
  }
719
+ const nodeScope = await resolveTopicNodeScopeOrNull(
720
+ ctx,
721
+ String(args.projectId)
722
+ );
723
+ if (nodeScope) {
724
+ return {
725
+ ...nodeScope,
726
+ projectId: nodeScope.projectId ?? String(args.projectId)
727
+ };
728
+ }
679
729
  throw new Error(
680
730
  `Legacy project scope ${String(args.projectId)} has no mapped topic.`
681
731
  );
@@ -759,7 +809,18 @@ var DEFAULT_CONFIDENCE_POLICY = {
759
809
  tupleContradiction: normalizeTupleContradictionPolicy()
760
810
  };
761
811
  function throwStructuredMutationError(args) {
762
- const error = new Error(args.message);
812
+ const data = {
813
+ structuredMutationError: true,
814
+ message: args.message,
815
+ status: args.status,
816
+ code: args.code,
817
+ invariantCode: args.invariantCode,
818
+ suggestion: args.suggestion,
819
+ details: args.details
820
+ };
821
+ const error = new ConvexError(
822
+ data
823
+ );
763
824
  error.status = args.status;
764
825
  error.code = args.code;
765
826
  error.invariantCode = args.invariantCode;
@@ -917,12 +978,12 @@ async function requireProjectWriteAccess(ctx, projectId, userId) {
917
978
  );
918
979
  if (!hasAccess) {
919
980
  throwStructuredMutationError({
920
- message: "Project access required.",
981
+ message: `Project write access denied for topic ${projectId}.`,
921
982
  status: 403,
922
- code: "FORBIDDEN",
983
+ code: "PROJECT_ACCESS_DENIED",
923
984
  invariantCode: "policy.scope_required",
924
- suggestion: "Request write access for the project and retry.",
925
- details: { projectId, userId }
985
+ 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.",
986
+ details: { topicId: projectId, principalId: userId }
926
987
  });
927
988
  }
928
989
  }
@@ -1023,6 +1084,52 @@ async function resolveForkTriggerEvidence(ctx, args) {
1023
1084
  }
1024
1085
  return { evidenceNodeId: args.triggeringEvidenceId, relation };
1025
1086
  }
1087
+ async function insertEpistemicNode(ctx, doc) {
1088
+ assertUuidV7Identity("epistemicNodes", doc.globalId);
1089
+ return ctx.db.insert("epistemicNodes", doc);
1090
+ }
1091
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
1092
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
1093
+ const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
1094
+ if (!node) {
1095
+ throw new Error(
1096
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
1097
+ );
1098
+ }
1099
+ }
1100
+ async function insertEpistemicEdge(ctx, doc) {
1101
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
1102
+ assertStorageEdgeVocabulary(doc.edgeType);
1103
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
1104
+ throw new Error(
1105
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
1106
+ );
1107
+ }
1108
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
1109
+ throw new Error(
1110
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
1111
+ );
1112
+ }
1113
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
1114
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
1115
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
1116
+ assertEdgePolicyAllowed(
1117
+ edgePolicyManifest,
1118
+ doc.edgeType,
1119
+ {
1120
+ kind: "epistemic_node",
1121
+ nodeId: doc.fromNodeId,
1122
+ nodeType: doc.fromNodeType
1123
+ },
1124
+ {
1125
+ kind: "epistemic_node",
1126
+ nodeId: doc.toNodeId,
1127
+ nodeType: doc.toNodeType
1128
+ }
1129
+ );
1130
+ }
1131
+ return ctx.db.insert("epistemicEdges", doc);
1132
+ }
1026
1133
 
1027
1134
  // src/epistemicBeliefs.topicAnchor.ts
1028
1135
  function cleanString(value) {
@@ -1074,18 +1181,15 @@ async function createRequiredBeliefTopicEdge(ctx, args) {
1074
1181
  const now = Date.now();
1075
1182
  const existingEdges = await ctx.db.query("epistemicEdges").withIndex(
1076
1183
  "by_from_to",
1077
- (q) => q.eq("fromNodeId", String(args.beliefNodeId)).eq(
1078
- "toNodeId",
1079
- String(args.topicNode._id)
1080
- )
1184
+ (q) => q.eq("fromNodeId", args.beliefGlobalId).eq("toNodeId", topicGlobalId)
1081
1185
  ).collect();
1082
1186
  const existing = existingEdges.find((edge) => edge.edgeType === "belongs_to");
1083
1187
  const edgeGlobalId = cleanString(existing?.globalId) ?? generateUuidV7();
1084
1188
  if (!existing) {
1085
- await ctx.db.insert("epistemicEdges", {
1189
+ await insertEpistemicEdge(ctx, {
1086
1190
  globalId: edgeGlobalId,
1087
- fromNodeId: String(args.beliefNodeId),
1088
- toNodeId: String(args.topicNode._id),
1191
+ fromNodeId: args.beliefGlobalId,
1192
+ toNodeId: topicGlobalId,
1089
1193
  sourceGlobalId: args.beliefGlobalId,
1090
1194
  targetGlobalId: topicGlobalId,
1091
1195
  edgeType: "belongs_to",
@@ -1264,7 +1368,7 @@ var create = mutation({
1264
1368
  const pillar = normalizePillar(args.pillar);
1265
1369
  const additionalMeta = args.metadata || {};
1266
1370
  const beliefGlobalId = generateGlobalId();
1267
- const nodeId = await ctx.db.insert("epistemicNodes", {
1371
+ const nodeId = await insertEpistemicNode(ctx, {
1268
1372
  globalId: beliefGlobalId,
1269
1373
  nodeType: "belief",
1270
1374
  epistemicLayer: "L3",
@@ -1322,7 +1426,6 @@ var create = mutation({
1322
1426
  })
1323
1427
  );
1324
1428
  await createRequiredBeliefTopicEdge(ctx, {
1325
- beliefNodeId: nodeId,
1326
1429
  beliefGlobalId,
1327
1430
  topicNode,
1328
1431
  createdBy: authenticatedUserId
@@ -1737,7 +1840,7 @@ var forkBelief = mutation({
1737
1840
  });
1738
1841
  }
1739
1842
  const newBeliefGlobalId = generateGlobalId();
1740
- const newNodeId = await ctx.db.insert("epistemicNodes", {
1843
+ const newNodeId = await insertEpistemicNode(ctx, {
1741
1844
  globalId: newBeliefGlobalId,
1742
1845
  nodeType: "belief",
1743
1846
  epistemicLayer: "L3",