@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
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { normalizeTupleContradictionPolicy, mkOpinion, createInheritedContractRecord, confidenceFromSL, getRescoringSchedule, conditionalDeduction, project, dampedDependencyCascade, hasProjectedOpinionChanged, detectTupleContradiction, evaluateTupleContradictionTransition, readOpinionFromRecord, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, computeEffectiveDecay, computeDeadlineUrgency, computeBaseDecay, bayesianUpdate, DECAY_TIERS, DEADLINE_URGENCY, parseEvidentialEvaluatorConfig, compareMetricValue, buildEvidentialRationale, parseMetricCheckerConfig, getEvaluatorInputRecord, pickFiniteNumber, resolveComparisonResult, buildComparisonRationale, parseReferenceCheckCounterConfig, parseTemporalDeadlineConfig, parseMarketIndexComparatorConfig } from '@lucern/confidence';
2
- import { v } from 'convex/values';
2
+ import { v, ConvexError } from 'convex/values';
3
3
  import { checkScopeAccess, getAccessibleProjectIds, checkProjectAccess, requireProjectAccess } from '@lucern/access-control/access';
4
4
  import { canAudienceClassAccess, normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
5
5
  import { getCurrentUserId } from '@lucern/access-control/auth';
@@ -7,7 +7,8 @@ import { componentsGeneric, anyApi, internalMutationGeneric, mutationGeneric, qu
7
7
  import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
8
8
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
9
9
  import { assertSchemaEnumValue } from '@lucern/contracts/schema-helpers/enumValidation';
10
- import { generateGlobalId, generateUuidV7, isUuidV7 } from '@lucern/contracts/ids';
10
+ import { generateGlobalId, assertUuidV7Identity, generateUuidV7, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint, isUuidV7 } from '@lucern/contracts/ids';
11
+ import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
11
12
  import { listAudienceRegistryRows } from '@lucern/access-control/audienceRegistry';
12
13
  import { scoreEntityTypeMatch, scoreEntityConnection, rankEntityTypeMatches, rankEntityConnections } from '@lucern/contracts/v1/ontologies/v1';
13
14
  import { wordTokenize, wordOverlapScore, tokenizeSearchText, tokenOverlapScore, stemToken, scoreLexicalSignals, scoreLexicalSignal, rerankLexicalWindow, rankWindowScore, prepareLexicalQuery, jaccardSimilarity, bigramTokenize } from '@lucern/contracts/text-matching.contract';
@@ -642,7 +643,72 @@ var EDGE_LAYER_RULES = {
642
643
  from: ["ontological"],
643
644
  to: ["L3"],
644
645
  description: "Deal -> Theme"
645
- }
646
+ },
647
+ // C2-RR.4 — storage migration alias + the remaining 55 reachable edge types.
648
+ // Mirrors the kernel rule table
649
+ // (packages/reasoning-kernel/src/adapters/lib/edgeValidation.ts) so this
650
+ // graph-primitives write path admits the same public edge vocabulary. See the
651
+ // kernel file for the per-group rationale.
652
+ extracted_from: {
653
+ from: ["L2", "L3", "L4"],
654
+ to: ["L1", "L2", "L3"],
655
+ description: "Legacy storage migration alias for derived_from; public writes use derived_from"
656
+ },
657
+ qualifies: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief qualifies a Belief" },
658
+ contradicts: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief contradicts a Belief" },
659
+ reinforces: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief reinforces a Belief" },
660
+ corroborates: { from: ["L2", "L3"], to: ["L3"], description: "Independent evidence/belief corroborates a Belief" },
661
+ strengthened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is strengthened by Evidence/Belief" },
662
+ weakened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is weakened by Evidence/Belief" },
663
+ validated_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is validated by Evidence/Belief" },
664
+ falsified_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is falsified by Evidence/Belief" },
665
+ amplifies: { from: ["L3"], to: ["L3"], description: "Belief amplifies another Belief" },
666
+ precondition_for: { from: ["L3"], to: ["L3"], description: "Belief is a precondition for another Belief" },
667
+ prerequisite_for: { from: ["L3"], to: ["L3"], description: "Belief is a prerequisite for another Belief" },
668
+ required_for: { from: ["L3"], to: ["L3"], description: "Belief is required for another Belief" },
669
+ in_tension_with: { from: ["L3"], to: ["L3"], description: "Belief is in tension with another Belief" },
670
+ mutually_exclusive: { from: ["L3"], to: ["L3"], description: "Beliefs cannot both hold" },
671
+ exclusive_with: { from: ["L3"], to: ["L3"], description: "Belief is exclusive with another Belief" },
672
+ alternative_to: { from: ["L3"], to: ["L3"], description: "Belief is an alternative to another Belief" },
673
+ subsumes: { from: ["L3"], to: ["L3"], description: "Belief subsumes a narrower Belief" },
674
+ extends: { from: ["L3"], to: ["L3"], description: "Belief extends another Belief" },
675
+ refines: { from: ["L3"], to: ["L3"], description: "Belief refines another Belief" },
676
+ implements: { from: ["L3"], to: ["L3"], description: "Belief implements an abstract Belief" },
677
+ violates: { from: ["L3"], to: ["L3"], description: "Belief violates a constraint Belief" },
678
+ assumes: { from: ["L3"], to: ["L3"], description: "Belief assumes another Belief" },
679
+ would_predict: { from: ["L3"], to: ["L3"], description: "Belief would predict another Belief" },
680
+ analogous_to: { from: ["L3"], to: ["L3"], description: "Belief is analogous to another Belief" },
681
+ independent_of: { from: ["L3"], to: ["L3"], description: "Belief is independent of another Belief" },
682
+ correlates_with: { from: ["L3"], to: ["L3"], description: "Belief correlates with another Belief" },
683
+ co_changes_with: { from: ["L3"], to: ["L3"], description: "Belief co-changes with another Belief" },
684
+ counterfactual_of: { from: ["L3"], to: ["L3"], description: "Belief is a counterfactual of another Belief" },
685
+ parallel_to: { from: ["L3"], to: ["L3"], description: "Belief runs parallel to another Belief" },
686
+ cascade_from: { from: ["L3"], to: ["L3"], description: "Cascade from an upstream Belief" },
687
+ cascade_to: { from: ["L3"], to: ["L3"], description: "Cascade to a downstream Belief" },
688
+ collapses_if: { from: ["L3"], to: ["L3"], description: "Belief collapses if another fails" },
689
+ branches_from: { from: ["L3"], to: ["L3"], description: "Belief branches from an ancestor Belief" },
690
+ same_as: { from: ["L2", "L3", "ontological"], to: ["L2", "L3", "ontological"], description: "Two nodes are the same entity" },
691
+ answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief answers a Question" },
692
+ partially_answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief partially answers a Question" },
693
+ explores: { from: ["L3"], to: ["L3"], description: "Question explores a Belief/Theme" },
694
+ informed_by_theme: { from: ["L3"], to: ["L3"], description: "Belief is informed by a Theme" },
695
+ same_theme_as: { from: ["L3"], to: ["L3"], description: "Two Beliefs share a Theme" },
696
+ based_on: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief/Question" },
697
+ based_on_belief: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief" },
698
+ based_on_question: { from: ["L4"], to: ["L3"], description: "Decision is based on a Question" },
699
+ blocked_by_contradiction: { from: ["L4"], to: ["L3"], description: "Decision is blocked by a Contradiction" },
700
+ parent_of: { from: ["L3", "L4", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "A is the parent of B" },
701
+ child_of: { from: ["L2", "L3", "ontological", "organizational"], to: ["L3", "L4", "ontological", "organizational"], description: "A is the child of B" },
702
+ scoped_by: { from: ["L2", "L3"], to: ["L3", "organizational"], description: "Object is scoped by a Topic/Theme" },
703
+ cites: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Evidence/Belief cites a Source/Excerpt" },
704
+ summarizes: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Synthesis/Evidence summarizes a Source/Evidence" },
705
+ same_source_as: { from: ["L1", "L2"], to: ["L1", "L2"], description: "Two nodes derive from the same Source" },
706
+ migrating_from: { from: ["L2", "L3", "L4"], to: ["L1", "L2", "L3"], description: "Migration lineage: from an ancestor" },
707
+ migrating_to: { from: ["L1", "L2", "L3"], to: ["L2", "L3", "L4"], description: "Migration lineage: to a successor" },
708
+ about_entity: { from: ["L2", "L3"], to: ["ontological"], description: "Belief/Evidence is about an Entity" },
709
+ entity_referenced_in: { from: ["ontological"], to: ["L1", "L2"], description: "Entity is referenced in a Source/Evidence" },
710
+ related_to: { from: ["L2", "L3", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "Lateral adjacency; no confidence pressure" },
711
+ blocks: { from: ["L3", "L4"], to: ["L3", "L4"], description: "A blocks B (structural gate)" }
646
712
  };
647
713
  function validateEdgeLayers(edgeType, fromLayer, toLayer) {
648
714
  const rules = EDGE_LAYER_RULES[edgeType];
@@ -1026,6 +1092,9 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
1026
1092
  type: mapProjectType(topic, metadata),
1027
1093
  description: readNonEmptyString(topic.description),
1028
1094
  ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
1095
+ // FR.7 creator-grant: surface the principal-shaped owner field (column-first,
1096
+ // metadata fallback for legacy rows that recorded it in metadata).
1097
+ ownerPrincipalId: readNonEmptyString(topic.ownerPrincipalId) || readNonEmptyString(metadata.ownerPrincipalId),
1029
1098
  sharedWith: readStringArray(metadata.sharedWith),
1030
1099
  visibility,
1031
1100
  tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
@@ -1237,6 +1306,35 @@ __export(topicScope_exports, {
1237
1306
  resolveTopicProjectScope: () => resolveTopicProjectScope
1238
1307
  });
1239
1308
  var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
1309
+ async function resolveTopicNodeScopeOrNull(ctx, ref) {
1310
+ if (!ctx?.db || typeof ctx.db.query !== "function") {
1311
+ return null;
1312
+ }
1313
+ let node = null;
1314
+ try {
1315
+ const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
1316
+ if (byGlobalId && byGlobalId.nodeType === "topic") {
1317
+ node = byGlobalId;
1318
+ }
1319
+ } catch (error) {
1320
+ debugGraphPrimitiveFallback(
1321
+ "[topicScope] topic-node scope lookup by globalId failed",
1322
+ { error, ref }
1323
+ );
1324
+ }
1325
+ if (!node) {
1326
+ return null;
1327
+ }
1328
+ const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
1329
+ if (!scopeKey) {
1330
+ return null;
1331
+ }
1332
+ return {
1333
+ topicId: scopeKey,
1334
+ projectId: asMappedProjectId(node),
1335
+ source: "topic_node"
1336
+ };
1337
+ }
1240
1338
  function asMappedProjectId(topic) {
1241
1339
  if (!topic) {
1242
1340
  return;
@@ -1385,6 +1483,13 @@ async function resolveTopicProjectScope(ctx, args) {
1385
1483
  ) ?? null;
1386
1484
  }
1387
1485
  if (!topic) {
1486
+ const nodeScope = await resolveTopicNodeScopeOrNull(
1487
+ ctx,
1488
+ String(args.topicId)
1489
+ );
1490
+ if (nodeScope) {
1491
+ return nodeScope;
1492
+ }
1388
1493
  throw new Error(`Topic not found: ${String(args.topicId)}`);
1389
1494
  }
1390
1495
  const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
@@ -1455,6 +1560,16 @@ async function resolveTopicProjectScope(ctx, args) {
1455
1560
  source: "project_mapped_topic"
1456
1561
  };
1457
1562
  }
1563
+ const nodeScope = await resolveTopicNodeScopeOrNull(
1564
+ ctx,
1565
+ String(args.projectId)
1566
+ );
1567
+ if (nodeScope) {
1568
+ return {
1569
+ ...nodeScope,
1570
+ projectId: nodeScope.projectId ?? String(args.projectId)
1571
+ };
1572
+ }
1458
1573
  throw new Error(
1459
1574
  `Legacy project scope ${String(args.projectId)} has no mapped topic.`
1460
1575
  );
@@ -1626,7 +1741,18 @@ var DEFAULT_CONFIDENCE_POLICY = {
1626
1741
  tupleContradiction: normalizeTupleContradictionPolicy()
1627
1742
  };
1628
1743
  function throwStructuredMutationError(args) {
1629
- const error = new Error(args.message);
1744
+ const data = {
1745
+ structuredMutationError: true,
1746
+ message: args.message,
1747
+ status: args.status,
1748
+ code: args.code,
1749
+ invariantCode: args.invariantCode,
1750
+ suggestion: args.suggestion,
1751
+ details: args.details
1752
+ };
1753
+ const error = new ConvexError(
1754
+ data
1755
+ );
1630
1756
  error.status = args.status;
1631
1757
  error.code = args.code;
1632
1758
  error.invariantCode = args.invariantCode;
@@ -1975,12 +2101,12 @@ async function requireProjectWriteAccess(ctx, projectId, userId) {
1975
2101
  );
1976
2102
  if (!hasAccess) {
1977
2103
  throwStructuredMutationError({
1978
- message: "Project access required.",
2104
+ message: `Project write access denied for topic ${projectId}.`,
1979
2105
  status: 403,
1980
- code: "FORBIDDEN",
2106
+ code: "PROJECT_ACCESS_DENIED",
1981
2107
  invariantCode: "policy.scope_required",
1982
- suggestion: "Request write access for the project and retry.",
1983
- details: { projectId, userId }
2108
+ 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.",
2109
+ details: { topicId: projectId, principalId: userId }
1984
2110
  });
1985
2111
  }
1986
2112
  }
@@ -2351,14 +2477,14 @@ async function applyBeliefConfidenceChange(ctx, args) {
2351
2477
  beliefConfidenceId
2352
2478
  };
2353
2479
  }
2354
- function propagationTriggerForEdge(edgeType, weight) {
2480
+ function propagationPressureLabel(edgeType, weight) {
2355
2481
  if (edgeType === "contradicts" || edgeType === "refutes") {
2356
- return "contradiction_detected";
2482
+ return "contradictory";
2357
2483
  }
2358
2484
  if ((edgeType === "supports" || edgeType === "informs") && weight < 0) {
2359
- return "contradiction_detected";
2485
+ return "contradictory";
2360
2486
  }
2361
- return "evidence_added";
2487
+ return "supportive";
2362
2488
  }
2363
2489
  var propagateConfidenceChange = internalMutation({
2364
2490
  args: {
@@ -2395,14 +2521,15 @@ var propagateConfidenceChange = internalMutation({
2395
2521
  getNode: async (nodeId) => await ctx.db.get(nodeId)
2396
2522
  });
2397
2523
  for (const dispatch of dispatches) {
2524
+ const pressureLabel = propagationPressureLabel(dispatch.edgeType, dispatch.weight);
2398
2525
  await applyBeliefConfidenceChange(ctx, {
2399
2526
  nodeId: dispatch.targetNodeId,
2400
2527
  belief: dispatch.opinion.b,
2401
2528
  disbelief: dispatch.opinion.d,
2402
2529
  uncertainty: dispatch.opinion.u,
2403
2530
  baseRate: dispatch.opinion.a,
2404
- trigger: propagationTriggerForEdge(dispatch.edgeType, dispatch.weight),
2405
- rationale: `SL propagation via ${dispatch.edgeType} edge: ${dispatch.rationale}`,
2531
+ trigger: "propagation",
2532
+ rationale: `SL propagation via ${dispatch.edgeType} edge (pressure: ${pressureLabel}): ${dispatch.rationale}`,
2406
2533
  authenticatedUserId: args.userId,
2407
2534
  slOperator: dispatch.operator
2408
2535
  });
@@ -2522,6 +2649,52 @@ __export(globalId_exports, {
2522
2649
  generateUuidV7: () => generateUuidV7,
2523
2650
  isUuidV7: () => isUuidV7
2524
2651
  });
2652
+ async function insertEpistemicNode(ctx, doc) {
2653
+ assertUuidV7Identity("epistemicNodes", doc.globalId);
2654
+ return ctx.db.insert("epistemicNodes", doc);
2655
+ }
2656
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
2657
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
2658
+ const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
2659
+ if (!node) {
2660
+ throw new Error(
2661
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
2662
+ );
2663
+ }
2664
+ }
2665
+ async function insertEpistemicEdge(ctx, doc) {
2666
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
2667
+ assertStorageEdgeVocabulary(doc.edgeType);
2668
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
2669
+ throw new Error(
2670
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
2671
+ );
2672
+ }
2673
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
2674
+ throw new Error(
2675
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
2676
+ );
2677
+ }
2678
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
2679
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
2680
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
2681
+ assertEdgePolicyAllowed(
2682
+ edgePolicyManifest,
2683
+ doc.edgeType,
2684
+ {
2685
+ kind: "epistemic_node",
2686
+ nodeId: doc.fromNodeId,
2687
+ nodeType: doc.fromNodeType
2688
+ },
2689
+ {
2690
+ kind: "epistemic_node",
2691
+ nodeId: doc.toNodeId,
2692
+ nodeType: doc.toNodeType
2693
+ }
2694
+ );
2695
+ }
2696
+ return ctx.db.insert("epistemicEdges", doc);
2697
+ }
2525
2698
 
2526
2699
  // src/epistemicBeliefs.topicAnchor.ts
2527
2700
  function cleanString(value) {
@@ -2573,18 +2746,15 @@ async function createRequiredBeliefTopicEdge(ctx, args) {
2573
2746
  const now = Date.now();
2574
2747
  const existingEdges = await ctx.db.query("epistemicEdges").withIndex(
2575
2748
  "by_from_to",
2576
- (q) => q.eq("fromNodeId", String(args.beliefNodeId)).eq(
2577
- "toNodeId",
2578
- String(args.topicNode._id)
2579
- )
2749
+ (q) => q.eq("fromNodeId", args.beliefGlobalId).eq("toNodeId", topicGlobalId)
2580
2750
  ).collect();
2581
2751
  const existing = existingEdges.find((edge) => edge.edgeType === "belongs_to");
2582
2752
  const edgeGlobalId = cleanString(existing?.globalId) ?? generateUuidV7();
2583
2753
  if (!existing) {
2584
- await ctx.db.insert("epistemicEdges", {
2754
+ await insertEpistemicEdge(ctx, {
2585
2755
  globalId: edgeGlobalId,
2586
- fromNodeId: String(args.beliefNodeId),
2587
- toNodeId: String(args.topicNode._id),
2756
+ fromNodeId: args.beliefGlobalId,
2757
+ toNodeId: topicGlobalId,
2588
2758
  sourceGlobalId: args.beliefGlobalId,
2589
2759
  targetGlobalId: topicGlobalId,
2590
2760
  edgeType: "belongs_to",
@@ -2763,7 +2933,7 @@ var create = mutation({
2763
2933
  const pillar = normalizePillar(args.pillar);
2764
2934
  const additionalMeta = args.metadata || {};
2765
2935
  const beliefGlobalId = generateGlobalId();
2766
- const nodeId = await ctx.db.insert("epistemicNodes", {
2936
+ const nodeId = await insertEpistemicNode(ctx, {
2767
2937
  globalId: beliefGlobalId,
2768
2938
  nodeType: "belief",
2769
2939
  epistemicLayer: "L3",
@@ -2821,7 +2991,6 @@ var create = mutation({
2821
2991
  })
2822
2992
  );
2823
2993
  await createRequiredBeliefTopicEdge(ctx, {
2824
- beliefNodeId: nodeId,
2825
2994
  beliefGlobalId,
2826
2995
  topicNode,
2827
2996
  createdBy: authenticatedUserId
@@ -3236,7 +3405,7 @@ var forkBelief = mutation({
3236
3405
  });
3237
3406
  }
3238
3407
  const newBeliefGlobalId = generateGlobalId();
3239
- const newNodeId = await ctx.db.insert("epistemicNodes", {
3408
+ const newNodeId = await insertEpistemicNode(ctx, {
3240
3409
  globalId: newBeliefGlobalId,
3241
3410
  nodeType: "belief",
3242
3411
  epistemicLayer: "L3",
@@ -4024,10 +4193,11 @@ var linkEvidence = mutation({
4024
4193
  const edgeType = "informs";
4025
4194
  const logicalRole = evidenceNodeId ? await computeLogicalRole(ctx, evidenceNodeId, args.beliefNodeId) : "contributory";
4026
4195
  const edgeGlobalId = generateGlobalId();
4027
- await ctx.db.insert("epistemicEdges", {
4196
+ await insertEpistemicEdge(ctx, {
4028
4197
  globalId: edgeGlobalId,
4029
- fromNodeId: evidenceNodeId,
4030
- toNodeId: args.beliefNodeId,
4198
+ // C2-RR.4 Defect E — canonical UUIDv7 endpoints, not Convex doc ids.
4199
+ fromNodeId: evidenceGlobalId,
4200
+ toNodeId: belief.globalId,
4031
4201
  sourceGlobalId: evidenceGlobalId,
4032
4202
  targetGlobalId: belief.globalId,
4033
4203
  edgeType,
@@ -4823,7 +4993,7 @@ var internalCreate = internalMutation({
4823
4993
  opinion_u: 1,
4824
4994
  opinion_a: baseRate
4825
4995
  };
4826
- const nodeId = await ctx.db.insert("epistemicNodes", {
4996
+ const nodeId = await insertEpistemicNode(ctx, {
4827
4997
  globalId,
4828
4998
  topicId: scope.topicId,
4829
4999
  projectId: scope.projectId,
@@ -4879,7 +5049,6 @@ var internalCreate = internalMutation({
4879
5049
  })
4880
5050
  );
4881
5051
  await createRequiredBeliefTopicEdge(ctx, {
4882
- beliefNodeId: nodeId,
4883
5052
  beliefGlobalId: globalId,
4884
5053
  topicNode,
4885
5054
  createdBy: args.userId
@@ -6084,7 +6253,7 @@ async function createEpistemicNodeForInsight(ctx, _insightId, insight) {
6084
6253
  });
6085
6254
  return existing._id;
6086
6255
  }
6087
- const nodeId = await ctx.db.insert("epistemicNodes", {
6256
+ const nodeId = await insertEpistemicNode(ctx, {
6088
6257
  globalId,
6089
6258
  nodeType: "evidence",
6090
6259
  epistemicLayer: "L2",
@@ -6160,7 +6329,7 @@ async function createEpistemicNodeForBelief(ctx, beliefId, belief) {
6160
6329
  });
6161
6330
  return existing._id;
6162
6331
  }
6163
- const nodeId = await ctx.db.insert("epistemicNodes", {
6332
+ const nodeId = await insertEpistemicNode(ctx, {
6164
6333
  globalId,
6165
6334
  nodeType: "belief",
6166
6335
  epistemicLayer: "L3",
@@ -6208,7 +6377,7 @@ async function createEpistemicNodeForQuestion(ctx, _questionId, question) {
6208
6377
  return existing._id;
6209
6378
  }
6210
6379
  const sourceType = question.source === "manual" ? "human" : question.source === "ai_suggested" ? "ai_generated" : "ai_extracted";
6211
- const nodeId = await ctx.db.insert("epistemicNodes", {
6380
+ const nodeId = await insertEpistemicNode(ctx, {
6212
6381
  globalId,
6213
6382
  nodeType: "question",
6214
6383
  epistemicLayer: "L3",
@@ -6269,7 +6438,7 @@ async function createEpistemicNodeForArtifact(ctx, artifactId, artifact) {
6269
6438
  return existing._id;
6270
6439
  }
6271
6440
  const epistemicLayer = "L1";
6272
- const nodeId = await ctx.db.insert("epistemicNodes", {
6441
+ const nodeId = await insertEpistemicNode(ctx, {
6273
6442
  globalId,
6274
6443
  nodeType,
6275
6444
  epistemicLayer,
@@ -6333,7 +6502,7 @@ async function findOrCreateSourceNode(ctx, artifactId, createdBy, scopeProjectId
6333
6502
  title + (artifact.content?.slice(0, 500) || "")
6334
6503
  );
6335
6504
  const epistemicLayer = nodeType === "synthesis" ? "L2" : "L1";
6336
- const nodeId = await ctx.db.insert("epistemicNodes", {
6505
+ const nodeId = await insertEpistemicNode(ctx, {
6337
6506
  globalId,
6338
6507
  nodeType,
6339
6508
  epistemicLayer,
@@ -6796,7 +6965,7 @@ async function applyOperationalLinkEffects(ctx, args) {
6796
6965
  if (beliefSpineNode?.nodeType === "belief" && evidenceSpineNode?.nodeType === "evidence") {
6797
6966
  const existingEdges = await ctx.db.query("epistemicEdges").withIndex(
6798
6967
  "by_from_to",
6799
- (q) => q.eq("fromNodeId", evidenceSpineNode._id).eq("toNodeId", beliefSpineNode._id)
6968
+ (q) => q.eq("fromNodeId", evidenceSpineNode.globalId).eq("toNodeId", beliefSpineNode.globalId)
6800
6969
  ).collect();
6801
6970
  for (const edge of existingEdges) {
6802
6971
  if (edge.edgeType === "informs") {
@@ -6806,12 +6975,13 @@ async function applyOperationalLinkEffects(ctx, args) {
6806
6975
  await ctx.db.delete(edge._id);
6807
6976
  }
6808
6977
  }
6809
- const globalId = `edge-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
6978
+ const globalId = generateGlobalId();
6810
6979
  const context = args.rationale || `Linked as ${args.relation}`;
6811
- await ctx.db.insert("epistemicEdges", {
6980
+ await insertEpistemicEdge(ctx, {
6812
6981
  globalId,
6813
- fromNodeId: evidenceSpineNode._id,
6814
- toNodeId: beliefSpineNode._id,
6982
+ // C2-RR.4 Defect E — canonical UUIDv7 endpoints, not Convex doc ids.
6983
+ fromNodeId: evidenceSpineNode.globalId,
6984
+ toNodeId: beliefSpineNode.globalId,
6815
6985
  sourceGlobalId: evidenceSpineNode.globalId,
6816
6986
  targetGlobalId: beliefSpineNode.globalId,
6817
6987
  edgeType: "informs",
@@ -8486,6 +8656,36 @@ async function isRegisteredEntityType(ctx, nodeType, tenantId) {
8486
8656
  return schema !== null;
8487
8657
  }
8488
8658
 
8659
+ // src/entityCanonicalMatch.ts
8660
+ function normalizeCanonicalEntityText(value) {
8661
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
8662
+ }
8663
+ function buildEntityTitle(canonicalText) {
8664
+ return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
8665
+ }
8666
+ function matchesCanonicalEntityRecord(node, args) {
8667
+ if (node.epistemicLayer !== "ontological") {
8668
+ return false;
8669
+ }
8670
+ if (node.workspaceId !== void 0) {
8671
+ return false;
8672
+ }
8673
+ if (args.nodeType && node.nodeType !== args.nodeType) {
8674
+ return false;
8675
+ }
8676
+ if (args.contentHash && node.contentHash !== args.contentHash) {
8677
+ return false;
8678
+ }
8679
+ const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
8680
+ if (rowTenantId !== args.tenantId) {
8681
+ return false;
8682
+ }
8683
+ if (args.includeArchived) {
8684
+ return node.status !== "deleted";
8685
+ }
8686
+ return node.status === "active";
8687
+ }
8688
+
8489
8689
  // src/topicOntologyResolver.ts
8490
8690
  var MAX_RESOLUTION_DEPTH = 10;
8491
8691
  async function resolveTopicOntologyInternal(ctx, topicId) {
@@ -8552,7 +8752,18 @@ async function validateEntityTypeForTopic(ctx, topicId, nodeType) {
8552
8752
 
8553
8753
  // src/entityLifecycle.ts
8554
8754
  function throwStructuredMutationError2(args) {
8555
- const error = new Error(args.message);
8755
+ const data = {
8756
+ structuredMutationError: true,
8757
+ message: args.message,
8758
+ status: args.status,
8759
+ code: args.code,
8760
+ invariantCode: args.invariantCode,
8761
+ suggestion: args.suggestion,
8762
+ details: args.details
8763
+ };
8764
+ const error = new ConvexError(
8765
+ data
8766
+ );
8556
8767
  error.status = args.status;
8557
8768
  error.code = args.code;
8558
8769
  error.invariantCode = args.invariantCode;
@@ -8577,12 +8788,12 @@ async function requireProjectWriteAccess3(ctx, projectId, userId) {
8577
8788
  const hasAccess = await checkProjectAccess(ctx, projectId, userId);
8578
8789
  if (!hasAccess) {
8579
8790
  throwStructuredMutationError2({
8580
- message: "Project access required.",
8791
+ message: `Project write access denied for topic ${projectId}.`,
8581
8792
  status: 403,
8582
- code: "FORBIDDEN",
8793
+ code: "PROJECT_ACCESS_DENIED",
8583
8794
  invariantCode: "policy.scope_required",
8584
- suggestion: "Request write access for the project and retry.",
8585
- details: { projectId, userId }
8795
+ 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.",
8796
+ details: { topicId: projectId, principalId: userId }
8586
8797
  });
8587
8798
  }
8588
8799
  }
@@ -8606,12 +8817,6 @@ var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
8606
8817
  var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
8607
8818
  ONTOLOGICAL_NODE_TYPE_SET
8608
8819
  );
8609
- function normalizeCanonicalEntityText(value) {
8610
- return value.trim().toLowerCase().replace(/\s+/g, " ");
8611
- }
8612
- function buildEntityTitle(canonicalText) {
8613
- return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
8614
- }
8615
8820
  async function resolveCanonicalEntityScope(ctx, args) {
8616
8821
  const scope = await resolveTopicProjectScope(ctx, args);
8617
8822
  const topic = scope.topicId ? await ctx.db.get(scope.topicId) ?? null : null;
@@ -8627,28 +8832,6 @@ async function resolveCanonicalEntityScope(ctx, args) {
8627
8832
  project: project3
8628
8833
  };
8629
8834
  }
8630
- function matchesCanonicalEntityRecord(node, args) {
8631
- if (node.epistemicLayer !== "ontological") {
8632
- return false;
8633
- }
8634
- if (node.workspaceId !== void 0) {
8635
- return false;
8636
- }
8637
- if (args.nodeType && node.nodeType !== args.nodeType) {
8638
- return false;
8639
- }
8640
- if (args.contentHash && node.contentHash !== args.contentHash) {
8641
- return false;
8642
- }
8643
- const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
8644
- if (rowTenantId !== args.tenantId) {
8645
- return false;
8646
- }
8647
- if (args.includeArchived) {
8648
- return node.status !== "deleted";
8649
- }
8650
- return node.status === "active";
8651
- }
8652
8835
  async function findExistingCanonicalEntity(ctx, args) {
8653
8836
  const contentHash = generateContentHash3(args.nodeType, args.canonicalText);
8654
8837
  const candidates = await ctx.db.query("epistemicNodes").withIndex(
@@ -8814,7 +8997,7 @@ var createEntity = mutation({
8814
8997
  };
8815
8998
  }
8816
8999
  const entityGlobalId = generateGlobalId();
8817
- const nodeId = await ctx.db.insert("epistemicNodes", {
9000
+ const nodeId = await insertEpistemicNode(ctx, {
8818
9001
  globalId: entityGlobalId,
8819
9002
  nodeType: args.nodeType,
8820
9003
  epistemicLayer: "ontological",
@@ -9066,10 +9249,11 @@ var mergeEntities = mutation({
9066
9249
  );
9067
9250
  }
9068
9251
  const edgeGlobalId = generateGlobalId();
9069
- await ctx.db.insert("epistemicEdges", {
9252
+ await insertEpistemicEdge(ctx, {
9070
9253
  globalId: edgeGlobalId,
9071
- fromNodeId: args.duplicateNodeId,
9072
- toNodeId: args.canonicalNodeId,
9254
+ // C2-RR.4 Defect E — canonical UUIDv7 endpoints, not Convex doc ids.
9255
+ fromNodeId: duplicate.globalId,
9256
+ toNodeId: canonical.globalId,
9073
9257
  sourceGlobalId: duplicate.globalId,
9074
9258
  targetGlobalId: canonical.globalId,
9075
9259
  edgeType: "derived_from",
@@ -10354,7 +10538,7 @@ var create4 = mutation({
10354
10538
  }
10355
10539
  }
10356
10540
  }
10357
- const nodeId = await ctx.db.insert("epistemicNodes", {
10541
+ const nodeId = await insertEpistemicNode(ctx, {
10358
10542
  globalId,
10359
10543
  projectId: topicId,
10360
10544
  topicId: questionNode.topicId,
@@ -10409,7 +10593,7 @@ var create4 = mutation({
10409
10593
  }
10410
10594
  );
10411
10595
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10412
- globalId: crypto.randomUUID(),
10596
+ globalId: generateGlobalId(),
10413
10597
  fromGlobalId: globalId,
10414
10598
  toGlobalId: questionNode.globalId,
10415
10599
  edgeType: "responds_to",
@@ -10423,7 +10607,7 @@ var create4 = mutation({
10423
10607
  });
10424
10608
  for (const oldAnswer of activeAnswersForQuestion) {
10425
10609
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10426
- globalId: crypto.randomUUID(),
10610
+ globalId: generateGlobalId(),
10427
10611
  fromGlobalId: globalId,
10428
10612
  toGlobalId: oldAnswer.globalId,
10429
10613
  edgeType: "supersedes",
@@ -10440,7 +10624,7 @@ var create4 = mutation({
10440
10624
  const evNode = await ctx.db.get(evId);
10441
10625
  if (evNode) {
10442
10626
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10443
- globalId: crypto.randomUUID(),
10627
+ globalId: generateGlobalId(),
10444
10628
  fromGlobalId: globalId,
10445
10629
  toGlobalId: evNode.globalId,
10446
10630
  edgeType: "derived_from",
@@ -10462,7 +10646,7 @@ var create4 = mutation({
10462
10646
  const evidenceNode = await ctx.db.get(edge.fromNodeId);
10463
10647
  if (evidenceNode) {
10464
10648
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10465
- globalId: crypto.randomUUID(),
10649
+ globalId: generateGlobalId(),
10466
10650
  fromGlobalId: evidenceNode.globalId,
10467
10651
  toGlobalId: globalId,
10468
10652
  edgeType: "derived_from",
@@ -10554,7 +10738,7 @@ var createInternal = internalMutation({
10554
10738
  }
10555
10739
  }
10556
10740
  }
10557
- const nodeId = await ctx.db.insert("epistemicNodes", {
10741
+ const nodeId = await insertEpistemicNode(ctx, {
10558
10742
  globalId,
10559
10743
  projectId: topicId,
10560
10744
  topicId: questionNode.topicId,
@@ -10610,7 +10794,7 @@ var createInternal = internalMutation({
10610
10794
  }
10611
10795
  );
10612
10796
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10613
- globalId: crypto.randomUUID(),
10797
+ globalId: generateGlobalId(),
10614
10798
  fromGlobalId: globalId,
10615
10799
  toGlobalId: questionNode.globalId,
10616
10800
  edgeType: "responds_to",
@@ -10624,7 +10808,7 @@ var createInternal = internalMutation({
10624
10808
  });
10625
10809
  for (const oldAnswer of activeAnswersForQuestion) {
10626
10810
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10627
- globalId: crypto.randomUUID(),
10811
+ globalId: generateGlobalId(),
10628
10812
  fromGlobalId: globalId,
10629
10813
  toGlobalId: oldAnswer.globalId,
10630
10814
  edgeType: "supersedes",
@@ -10641,7 +10825,7 @@ var createInternal = internalMutation({
10641
10825
  const evNode = await ctx.db.get(evId);
10642
10826
  if (evNode) {
10643
10827
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10644
- globalId: crypto.randomUUID(),
10828
+ globalId: generateGlobalId(),
10645
10829
  fromGlobalId: globalId,
10646
10830
  toGlobalId: evNode.globalId,
10647
10831
  edgeType: "derived_from",
@@ -10663,7 +10847,7 @@ var createInternal = internalMutation({
10663
10847
  const evidenceNode = await ctx.db.get(edge.fromNodeId);
10664
10848
  if (evidenceNode) {
10665
10849
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
10666
- globalId: crypto.randomUUID(),
10850
+ globalId: generateGlobalId(),
10667
10851
  fromGlobalId: evidenceNode.globalId,
10668
10852
  toGlobalId: globalId,
10669
10853
  edgeType: "derived_from",
@@ -11880,11 +12064,21 @@ var mirrorEdgeToConvex = internalMutation({
11880
12064
  console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);
11881
12065
  return buildEdgeMirrorWriteResult(existing._id, true);
11882
12066
  }
12067
+ if (!toNode) {
12068
+ console.log(
12069
+ `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`
12070
+ );
12071
+ return buildEdgeMirrorSkippedResult();
12072
+ }
11883
12073
  const now = Date.now();
11884
- const edgeId = await ctx.db.insert("epistemicEdges", {
12074
+ const edgeId = await insertEpistemicEdge(ctx, {
11885
12075
  globalId: args.globalId,
11886
- fromNodeId: fromNode._id,
11887
- toNodeId: toNode?._id,
12076
+ // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex
12077
+ // doc ids. fromNode/toNode are resolved by_globalId above, so their
12078
+ // stored globalId equals args.from/toGlobalId; persisting the doc _id here
12079
+ // was the "mixed _id/globalId" defect.
12080
+ fromNodeId: fromNode.globalId,
12081
+ toNodeId: toNode.globalId,
11888
12082
  sourceGlobalId: args.fromGlobalId,
11889
12083
  targetGlobalId: args.toGlobalId,
11890
12084
  // Preserve the canonical string value even when the local schema lags.
@@ -12230,17 +12424,17 @@ function normalizeEvidenceRelation(relation, weight, context) {
12230
12424
  return weight < 0 ? "contradicts" : "supports";
12231
12425
  }
12232
12426
  async function createEvidenceBeliefEdge(ctx, args) {
12233
- const edgeGlobalId = crypto.randomUUID();
12427
+ const edgeGlobalId = generateGlobalId();
12234
12428
  const confidence = Math.abs(args.weight);
12235
12429
  const existingEdges = await ctx.db.query("epistemicEdges").withIndex(
12236
12430
  "by_from_to",
12237
- (q) => q.eq("fromNodeId", args.evidenceNodeId).eq("toNodeId", args.beliefNodeId)
12431
+ (q) => q.eq("fromNodeId", args.evidenceGlobalId).eq("toNodeId", args.beliefGlobalId)
12238
12432
  ).collect();
12239
12433
  const existing = existingEdges.find((edge) => edge.edgeType === "informs");
12240
12434
  const edgeDoc = {
12241
12435
  globalId: edgeGlobalId,
12242
- fromNodeId: args.evidenceNodeId,
12243
- toNodeId: args.beliefNodeId,
12436
+ fromNodeId: args.evidenceGlobalId,
12437
+ toNodeId: args.beliefGlobalId,
12244
12438
  sourceGlobalId: args.evidenceGlobalId,
12245
12439
  targetGlobalId: args.beliefGlobalId,
12246
12440
  edgeType: "informs",
@@ -12272,7 +12466,7 @@ async function createEvidenceBeliefEdge(ctx, args) {
12272
12466
  createdAt: existing.createdAt ?? edgeDoc.createdAt
12273
12467
  });
12274
12468
  } else {
12275
- await ctx.db.insert("epistemicEdges", edgeDoc);
12469
+ await insertEpistemicEdge(ctx, edgeDoc);
12276
12470
  }
12277
12471
  await ctx.scheduler.runAfter(5e3, internal.neo4jEdgeAPI.createEdge, {
12278
12472
  globalId: existing?.globalId ?? edgeGlobalId,
@@ -12311,10 +12505,23 @@ var create6 = mutation({
12311
12505
  sourceQuestionId: v.optional(v.string()),
12312
12506
  userId: v.string(),
12313
12507
  rationale: v.string(),
12314
- // Classification fields (from AI tools)
12508
+ // RC.1 taxonomy fields — top-level projection (mirroring kernel evidenceCreate.ts)
12509
+ sourceRef: v.optional(v.string()),
12510
+ sourceQuality: v.optional(
12511
+ v.union(
12512
+ v.literal("primary"),
12513
+ v.literal("analyzed"),
12514
+ v.literal("secondary"),
12515
+ v.literal("tertiary"),
12516
+ v.literal("unknown")
12517
+ )
12518
+ ),
12315
12519
  methodology: v.optional(v.string()),
12316
12520
  informationAsymmetry: v.optional(v.string()),
12317
12521
  sourceDescription: v.optional(v.string()),
12522
+ // signedImpact is derived from weight; accepted as an explicit override for
12523
+ // callers that already have the signed value (kernel parity).
12524
+ signedImpact: v.optional(v.number()),
12318
12525
  metadata: v.optional(v.any()),
12319
12526
  // Required belief impact link.
12320
12527
  linkedBeliefNodeId: v.id("epistemicNodes"),
@@ -12344,6 +12551,7 @@ var create6 = mutation({
12344
12551
  const kind = normalizeKind(args.kind);
12345
12552
  const sourceType = normalizeSourceType(args.sourceType);
12346
12553
  const weight = assertSignedImpactScore2(args.weight, "Evidence creation");
12554
+ const signedImpact = args.signedImpact !== void 0 ? assertSignedImpactScore2(args.signedImpact, "Evidence creation signedImpact") : weight;
12347
12555
  const evidenceRelation = normalizeEvidenceRelation(
12348
12556
  args.evidenceRelation,
12349
12557
  weight,
@@ -12354,7 +12562,7 @@ var create6 = mutation({
12354
12562
  throw new Error("Evidence creation requires a linked belief node");
12355
12563
  }
12356
12564
  const additionalMetadata = args.metadata && typeof args.metadata === "object" ? args.metadata : {};
12357
- const nodeId = await ctx.db.insert("epistemicNodes", {
12565
+ const nodeId = await insertEpistemicNode(ctx, {
12358
12566
  globalId,
12359
12567
  topicId: scope.topicId,
12360
12568
  projectId: scope.projectId,
@@ -12369,6 +12577,15 @@ var create6 = mutation({
12369
12577
  status: "active",
12370
12578
  epistemicLayer: "L2",
12371
12579
  sourceType,
12580
+ // RC.1 taxonomy fields — top-level projection
12581
+ evidenceRelation,
12582
+ signedImpact,
12583
+ targetBeliefId: String(args.linkedBeliefNodeId),
12584
+ ...typeof args.sourceRef === "string" && args.sourceRef.trim().length > 0 ? { sourceRef: args.sourceRef.trim() } : {},
12585
+ ...typeof args.sourceQuality === "string" && args.sourceQuality.length > 0 ? { sourceQuality: args.sourceQuality } : {},
12586
+ ...typeof args.methodology === "string" && args.methodology.length > 0 ? { methodology: args.methodology } : {},
12587
+ ...typeof args.informationAsymmetry === "string" && args.informationAsymmetry.length > 0 ? { informationAsymmetry: args.informationAsymmetry } : {},
12588
+ ...typeof args.sourceDescription === "string" && args.sourceDescription.trim().length > 0 ? { sourceDescription: args.sourceDescription.trim() } : {},
12372
12589
  createdAt: now,
12373
12590
  updatedAt: now,
12374
12591
  createdBy: args.userId,
@@ -12404,7 +12621,6 @@ var create6 = mutation({
12404
12621
  text: args.text
12405
12622
  });
12406
12623
  await createEvidenceBeliefEdge(ctx, {
12407
- evidenceNodeId: nodeId,
12408
12624
  evidenceGlobalId: globalId,
12409
12625
  beliefNodeId: args.linkedBeliefNodeId,
12410
12626
  beliefGlobalId: linkedBeliefNode.globalId,
@@ -12474,7 +12690,22 @@ var createAndLink = mutation({
12474
12690
  beliefNodeId: v.id("epistemicNodes"),
12475
12691
  relation: v.union(v.literal("supports"), v.literal("contradicts")),
12476
12692
  weight: v.number(),
12477
- confidence: v.optional(v.number())
12693
+ confidence: v.optional(v.number()),
12694
+ // RC.1 taxonomy fields — top-level projection (mirroring kernel evidenceCreate.ts)
12695
+ sourceRef: v.optional(v.string()),
12696
+ sourceQuality: v.optional(
12697
+ v.union(
12698
+ v.literal("primary"),
12699
+ v.literal("analyzed"),
12700
+ v.literal("secondary"),
12701
+ v.literal("tertiary"),
12702
+ v.literal("unknown")
12703
+ )
12704
+ ),
12705
+ methodology: v.optional(v.string()),
12706
+ informationAsymmetry: v.optional(v.string()),
12707
+ sourceDescription: v.optional(v.string()),
12708
+ signedImpact: v.optional(v.number())
12478
12709
  },
12479
12710
  returns: permissiveReturn,
12480
12711
  handler: async (ctx, args) => {
@@ -12489,6 +12720,7 @@ var createAndLink = mutation({
12489
12720
  const kind = normalizeKind(args.kind);
12490
12721
  const sourceType = normalizeSourceType(args.sourceType);
12491
12722
  const weight = assertSignedImpactScore2(args.weight, "Evidence createAndLink");
12723
+ const signedImpact = args.signedImpact !== void 0 ? assertSignedImpactScore2(args.signedImpact, "Evidence createAndLink signedImpact") : weight;
12492
12724
  const relation = normalizeEvidenceRelation(
12493
12725
  args.relation,
12494
12726
  weight,
@@ -12499,7 +12731,7 @@ var createAndLink = mutation({
12499
12731
  if (!beliefNode || beliefNode.nodeType !== "belief") {
12500
12732
  throw new Error("Belief node not found for edge creation");
12501
12733
  }
12502
- const nodeId = await ctx.db.insert("epistemicNodes", {
12734
+ const nodeId = await insertEpistemicNode(ctx, {
12503
12735
  globalId,
12504
12736
  topicId: scope.topicId,
12505
12737
  projectId: scope.projectId,
@@ -12511,6 +12743,15 @@ var createAndLink = mutation({
12511
12743
  status: "active",
12512
12744
  epistemicLayer: "L2",
12513
12745
  sourceType,
12746
+ // RC.1 taxonomy fields — top-level projection
12747
+ evidenceRelation: relation,
12748
+ signedImpact,
12749
+ targetBeliefId: String(args.beliefNodeId),
12750
+ ...typeof args.sourceRef === "string" && args.sourceRef.trim().length > 0 ? { sourceRef: args.sourceRef.trim() } : {},
12751
+ ...typeof args.sourceQuality === "string" && args.sourceQuality.length > 0 ? { sourceQuality: args.sourceQuality } : {},
12752
+ ...typeof args.methodology === "string" && args.methodology.length > 0 ? { methodology: args.methodology } : {},
12753
+ ...typeof args.informationAsymmetry === "string" && args.informationAsymmetry.length > 0 ? { informationAsymmetry: args.informationAsymmetry } : {},
12754
+ ...typeof args.sourceDescription === "string" && args.sourceDescription.trim().length > 0 ? { sourceDescription: args.sourceDescription.trim() } : {},
12514
12755
  createdAt: now,
12515
12756
  updatedAt: now,
12516
12757
  createdBy: args.userId,
@@ -12529,7 +12770,6 @@ var createAndLink = mutation({
12529
12770
  operation: "upsert"
12530
12771
  });
12531
12772
  const edgeGlobalId = await createEvidenceBeliefEdge(ctx, {
12532
- evidenceNodeId: nodeId,
12533
12773
  evidenceGlobalId: globalId,
12534
12774
  beliefNodeId: args.beliefNodeId,
12535
12775
  beliefGlobalId: beliefNode.globalId,
@@ -12602,6 +12842,21 @@ var internalCreate2 = internalMutation({
12602
12842
  evidenceRelation: v.optional(v.string()),
12603
12843
  weight: v.number(),
12604
12844
  confidence: v.optional(v.number()),
12845
+ // RC.1 taxonomy fields — top-level projection (mirroring kernel evidenceCreate.ts)
12846
+ sourceRef: v.optional(v.string()),
12847
+ sourceQuality: v.optional(
12848
+ v.union(
12849
+ v.literal("primary"),
12850
+ v.literal("analyzed"),
12851
+ v.literal("secondary"),
12852
+ v.literal("tertiary"),
12853
+ v.literal("unknown")
12854
+ )
12855
+ ),
12856
+ methodology: v.optional(v.string()),
12857
+ informationAsymmetry: v.optional(v.string()),
12858
+ sourceDescription: v.optional(v.string()),
12859
+ signedImpact: v.optional(v.number()),
12605
12860
  metadata: v.optional(v.any()),
12606
12861
  runtimeToolName: v.optional(v.string()),
12607
12862
  runtimePackKey: v.optional(v.string()),
@@ -12636,6 +12891,7 @@ var internalCreate2 = internalMutation({
12636
12891
  const kind = normalizeKind(args.kind);
12637
12892
  const sourceType = normalizeSourceType(args.sourceType);
12638
12893
  const weight = assertSignedImpactScore2(args.weight, "Internal evidence creation");
12894
+ const signedImpact = args.signedImpact !== void 0 ? assertSignedImpactScore2(args.signedImpact, "Internal evidence creation signedImpact") : weight;
12639
12895
  const evidenceRelation = normalizeEvidenceRelation(
12640
12896
  args.evidenceRelation,
12641
12897
  weight,
@@ -12646,7 +12902,7 @@ var internalCreate2 = internalMutation({
12646
12902
  throw new Error("Internal evidence creation requires a linked belief node");
12647
12903
  }
12648
12904
  const additionalMetadata = args.metadata && typeof args.metadata === "object" ? args.metadata : {};
12649
- const nodeId = await ctx.db.insert("epistemicNodes", {
12905
+ const nodeId = await insertEpistemicNode(ctx, {
12650
12906
  globalId,
12651
12907
  topicId: scope.topicId,
12652
12908
  projectId: scope.projectId,
@@ -12661,6 +12917,15 @@ var internalCreate2 = internalMutation({
12661
12917
  status: "active",
12662
12918
  epistemicLayer: "L2",
12663
12919
  sourceType,
12920
+ // RC.1 taxonomy fields — top-level projection
12921
+ evidenceRelation,
12922
+ signedImpact,
12923
+ targetBeliefId: String(args.linkedBeliefNodeId),
12924
+ ...typeof args.sourceRef === "string" && args.sourceRef.trim().length > 0 ? { sourceRef: args.sourceRef.trim() } : {},
12925
+ ...typeof args.sourceQuality === "string" && args.sourceQuality.length > 0 ? { sourceQuality: args.sourceQuality } : {},
12926
+ ...typeof args.methodology === "string" && args.methodology.length > 0 ? { methodology: args.methodology } : {},
12927
+ ...typeof args.informationAsymmetry === "string" && args.informationAsymmetry.length > 0 ? { informationAsymmetry: args.informationAsymmetry } : {},
12928
+ ...typeof args.sourceDescription === "string" && args.sourceDescription.trim().length > 0 ? { sourceDescription: args.sourceDescription.trim() } : {},
12664
12929
  createdAt: now,
12665
12930
  updatedAt: now,
12666
12931
  createdBy: args.userId,
@@ -12706,7 +12971,6 @@ var internalCreate2 = internalMutation({
12706
12971
  operation: "upsert"
12707
12972
  });
12708
12973
  await createEvidenceBeliefEdge(ctx, {
12709
- evidenceNodeId: nodeId,
12710
12974
  evidenceGlobalId: globalId,
12711
12975
  beliefNodeId: args.linkedBeliefNodeId,
12712
12976
  beliefGlobalId: linkedBeliefNode.globalId,
@@ -14235,7 +14499,7 @@ var create7 = mutation({
14235
14499
  "Workspace-scoped reasoning isolation requires topicId or projectId for non-ontological node creation."
14236
14500
  );
14237
14501
  }
14238
- const nodeId = await ctx.db.insert("epistemicNodes", {
14502
+ const nodeId = await insertEpistemicNode(ctx, {
14239
14503
  globalId: args.globalId,
14240
14504
  nodeType: args.nodeType,
14241
14505
  epistemicLayer,
@@ -14400,7 +14664,7 @@ var supersede = mutation({
14400
14664
  });
14401
14665
  const now = Date.now();
14402
14666
  const epistemicLayer = oldNode.epistemicLayer || getNodeLayer(oldNode.nodeType);
14403
- const newNodeId = await ctx.db.insert("epistemicNodes", {
14667
+ const newNodeId = await insertEpistemicNode(ctx, {
14404
14668
  globalId: args.newGlobalId,
14405
14669
  nodeType: oldNode.nodeType,
14406
14670
  epistemicLayer,
@@ -14606,7 +14870,7 @@ var batchCreate2 = mutation({
14606
14870
  });
14607
14871
  const epistemicLayer = getNodeLayer(node.nodeType);
14608
14872
  const resolvedScope = await resolveNodeScope(node);
14609
- const nodeId = await ctx.db.insert("epistemicNodes", {
14873
+ const nodeId = await insertEpistemicNode(ctx, {
14610
14874
  ...node,
14611
14875
  epistemicLayer,
14612
14876
  // Phase 2B: Auto-derived from nodeType
@@ -14708,7 +14972,7 @@ var createInternal2 = internalMutation({
14708
14972
  })() : void 0;
14709
14973
  const contentHash = args.contentHash || `${args.nodeType}:${args.canonicalText}`.slice(0, 64);
14710
14974
  const epistemicLayer = args.epistemicLayer || getNodeLayer(args.nodeType);
14711
- const nodeId = await ctx.db.insert("epistemicNodes", {
14975
+ const nodeId = await insertEpistemicNode(ctx, {
14712
14976
  globalId: args.globalId,
14713
14977
  nodeType: args.nodeType,
14714
14978
  epistemicLayer,
@@ -15316,7 +15580,7 @@ var create8 = mutation({
15316
15580
  const globalId = generateGlobalId();
15317
15581
  const contentHash = generateContentHash6(args.question);
15318
15582
  const category = normalizeCategory(args.category);
15319
- const nodeId = await ctx.db.insert("epistemicNodes", {
15583
+ const nodeId = await insertEpistemicNode(ctx, {
15320
15584
  globalId,
15321
15585
  topicId: scope.topicId,
15322
15586
  projectId: scope.projectId,
@@ -15484,7 +15748,7 @@ var createBatch = mutation({
15484
15748
  const globalId = generateGlobalId();
15485
15749
  const contentHash = generateContentHash6(q.question);
15486
15750
  const category = normalizeCategory(q.category);
15487
- const nodeId = await ctx.db.insert("epistemicNodes", {
15751
+ const nodeId = await insertEpistemicNode(ctx, {
15488
15752
  globalId,
15489
15753
  topicId: scope.topicId,
15490
15754
  projectId: scope.projectId,
@@ -15536,7 +15800,7 @@ var createBatch = mutation({
15536
15800
  const beliefNode = await ctx.db.get(q.linkedBeliefNodeId);
15537
15801
  if (beliefNode) {
15538
15802
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
15539
- globalId: crypto.randomUUID(),
15803
+ globalId: generateGlobalId(),
15540
15804
  fromGlobalId: beliefNode.globalId,
15541
15805
  toGlobalId: globalId,
15542
15806
  edgeType: "tests",
@@ -15600,7 +15864,7 @@ var internalCreate3 = internalMutation({
15600
15864
  const globalId = generateGlobalId();
15601
15865
  const contentHash = generateContentHash6(args.question);
15602
15866
  const category = normalizeCategory(args.category);
15603
- const nodeId = await ctx.db.insert("epistemicNodes", {
15867
+ const nodeId = await insertEpistemicNode(ctx, {
15604
15868
  globalId,
15605
15869
  topicId: scope.topicId,
15606
15870
  projectId: scope.projectId,
@@ -15632,7 +15896,7 @@ var internalCreate3 = internalMutation({
15632
15896
  const beliefNode = await ctx.db.get(args.linkedBeliefNodeId);
15633
15897
  if (beliefNode) {
15634
15898
  await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
15635
- globalId: crypto.randomUUID(),
15899
+ globalId: generateGlobalId(),
15636
15900
  fromGlobalId: beliefNode.globalId,
15637
15901
  toGlobalId: globalId,
15638
15902
  edgeType: "tests",
@@ -15703,7 +15967,7 @@ var addQuestion = mutation({
15703
15967
  const contentHash = generateContentHash6(args.question);
15704
15968
  const category = normalizeCategory(args.category);
15705
15969
  const additionalMetadata = args.metadata && typeof args.metadata === "object" ? args.metadata : {};
15706
- const nodeId = await ctx.db.insert("epistemicNodes", {
15970
+ const nodeId = await insertEpistemicNode(ctx, {
15707
15971
  globalId,
15708
15972
  topicId: scope.topicId,
15709
15973
  projectId: scope.projectId,
@@ -16377,7 +16641,7 @@ var consolidate = mutation({
16377
16641
  const now = Date.now();
16378
16642
  const globalId = generateGlobalId();
16379
16643
  const contentHash = generateContentHash6(args.consolidatedQuestion);
16380
- const newNodeId = await ctx.db.insert("epistemicNodes", {
16644
+ const newNodeId = await insertEpistemicNode(ctx, {
16381
16645
  globalId,
16382
16646
  projectId: scope.projectId,
16383
16647
  topicId: scope.topicId,
@@ -16642,7 +16906,7 @@ var createEvidenceFromScoredQuestion = internalMutation({
16642
16906
  A: ${args.answerText}`;
16643
16907
  const globalId = generateGlobalId();
16644
16908
  const contentHash = `evidence:${args.questionNodeId}:scored`;
16645
- const evidenceNodeId = await ctx.db.insert("epistemicNodes", {
16909
+ const evidenceNodeId = await insertEpistemicNode(ctx, {
16646
16910
  globalId,
16647
16911
  projectId: args.projectId,
16648
16912
  topicId: normalizeQuestionTopicId(args.topicId),
@@ -18116,7 +18380,7 @@ var upsertSource = mutation({
18116
18380
  capturedAt: normalizeNumber(args.capturedAt),
18117
18381
  metadata: metadataPatch
18118
18382
  });
18119
- const nodeId = await ctx.db.insert("epistemicNodes", {
18383
+ const nodeId = await insertEpistemicNode(ctx, {
18120
18384
  globalId,
18121
18385
  topicId: scope.topicId,
18122
18386
  projectId: scope.projectId,