@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
@@ -5,5 +5,5 @@ export { edgeTypeValidator, epistemicLayerValidator } from './epistemicEdges.hel
5
5
  export { g as getByTopic } from './epistemicEdges-BF-cn4i3.js';
6
6
  import 'convex/values';
7
7
  import './epistemicEdges.types.js';
8
- import './topicScope-By_zp4tt.js';
8
+ import './topicScope-7zhyeGl7.js';
9
9
  import './convex.js';
@@ -1,6 +1,8 @@
1
1
  import { v } from 'convex/values';
2
2
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
3
3
  import { componentsGeneric, internalMutationGeneric } from 'convex/server';
4
+ import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
5
+ import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
4
6
 
5
7
  // src/epistemicEdges.handlers.ts
6
8
  v.union(
@@ -126,6 +128,48 @@ var optionalScopeArgs = {
126
128
  projectId: v.optional(v.string()),
127
129
  topicId: v.optional(v.string())
128
130
  };
131
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
132
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
133
+ const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
134
+ if (!node) {
135
+ throw new Error(
136
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
137
+ );
138
+ }
139
+ }
140
+ async function insertEpistemicEdge(ctx, doc) {
141
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
142
+ assertStorageEdgeVocabulary(doc.edgeType);
143
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
144
+ throw new Error(
145
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
146
+ );
147
+ }
148
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
149
+ throw new Error(
150
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
151
+ );
152
+ }
153
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
154
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
155
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
156
+ assertEdgePolicyAllowed(
157
+ edgePolicyManifest,
158
+ doc.edgeType,
159
+ {
160
+ kind: "epistemic_node",
161
+ nodeId: doc.fromNodeId,
162
+ nodeType: doc.fromNodeType
163
+ },
164
+ {
165
+ kind: "epistemic_node",
166
+ nodeId: doc.toNodeId,
167
+ nodeType: doc.toNodeType
168
+ }
169
+ );
170
+ }
171
+ return ctx.db.insert("epistemicEdges", doc);
172
+ }
129
173
 
130
174
  // src/epistemicEdges.handlers.ts
131
175
  var mirrorEdgeToConvex = internalMutation({
@@ -171,11 +215,21 @@ var mirrorEdgeToConvex = internalMutation({
171
215
  console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);
172
216
  return buildEdgeMirrorWriteResult(existing._id, true);
173
217
  }
218
+ if (!toNode) {
219
+ console.log(
220
+ `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`
221
+ );
222
+ return buildEdgeMirrorSkippedResult();
223
+ }
174
224
  const now = Date.now();
175
- const edgeId = await ctx.db.insert("epistemicEdges", {
225
+ const edgeId = await insertEpistemicEdge(ctx, {
176
226
  globalId: args.globalId,
177
- fromNodeId: fromNode._id,
178
- toNodeId: toNode?._id,
227
+ // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex
228
+ // doc ids. fromNode/toNode are resolved by_globalId above, so their
229
+ // stored globalId equals args.from/toGlobalId; persisting the doc _id here
230
+ // was the "mixed _id/globalId" defect.
231
+ fromNodeId: fromNode.globalId,
232
+ toNodeId: toNode.globalId,
179
233
  sourceGlobalId: args.fromGlobalId,
180
234
  targetGlobalId: args.toGlobalId,
181
235
  // Preserve the canonical string value even when the local schema lags.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/epistemicEdges.helpers.ts","../src/convex.ts","../src/topicScope.ts","../src/epistemicEdges.handlers.ts"],"names":["v"],"mappings":"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","file":"epistemicEdges.handlers.js","sourcesContent":["/**\n * Shared validators and private helpers for epistemic edge operations.\n */\n\nimport { v } from \"convex/values\";\nimport type {\n EdgeStatusResult,\n SubjectiveOpinionDocument,\n WorkspaceEdgeScope,\n WorkspaceScopedEdge,\n} from \"./epistemicEdges.types\";\nimport type { Id, QueryCtx } from \"./convex\";\n\ntype TopicIndexQuery = {\n eq(field: string, value: unknown): TopicIndexQuery;\n};\n\nexport const epistemicLayerValidator = v.union(\n v.literal(\"L4\"),\n v.literal(\"L3\"),\n v.literal(\"L2\"),\n v.literal(\"L1\"),\n v.literal(\"ontological\"),\n v.literal(\"organizational\"),\n);\n\nexport const subjectiveOpinionValidator = v.object({\n b: v.number(),\n d: v.number(),\n u: v.number(),\n a: v.number(),\n} satisfies Record<keyof SubjectiveOpinionDocument, unknown>);\n\nexport const edgeTypeValidator = v.union(\n // --- L4 Decision Edges (Phase 2A) ---\n v.literal(\"based_on_belief\"),\n v.literal(\"based_on_question\"),\n v.literal(\"blocked_by_contradiction\"),\n v.literal(\"informed_by_theme\"),\n\n // --- Evidence Flow (L2 → L3, L2 → L1) ---\n v.literal(\"derived_from\"),\n v.literal(\"responds_to\"),\n v.literal(\"informs\"),\n v.literal(\"tests\"),\n v.literal(\"explores\"),\n v.literal(\"qualifies\"),\n\n // --- Synthesis (L2 → L2, L2 → L1) ---\n // \"based_on\" removed — use \"derived_from\" instead\n\n // --- Theme Relationships (L3 → L3) ---\n v.literal(\"relates_to_thesis\"),\n v.literal(\"belongs_to\"),\n v.literal(\"plays_theme\"),\n v.literal(\"scoped_by\"),\n\n // --- Deal/Company ---\n v.literal(\"evaluates\"),\n\n // --- People (ontological → ontological, ontological → L3) ---\n v.literal(\"perspective_on\"),\n v.literal(\"works_at\"),\n v.literal(\"mentioned_in\"),\n v.literal(\"founded_by\"),\n\n // --- Value Chain (ontological → ontological) ---\n v.literal(\"participates_in\"),\n v.literal(\"performs\"),\n v.literal(\"function_in\"),\n v.literal(\"impacts\"),\n\n // --- Investment (ontological → ontological) ---\n v.literal(\"invested_in\"),\n v.literal(\"raised_from\"),\n\n // --- Lifecycle (same layer only) ---\n v.literal(\"supersedes\"),\n v.literal(\"same_as\"),\n\n // --- Same-Type Relationships: Belief ↔ Belief (L3 → L3) ---\n v.literal(\"depends_on\"),\n v.literal(\"supports\"),\n v.literal(\"contains\"),\n\n // --- Belief Cluster Mapping (L3 → L3) ---\n v.literal(\"counterfactual_of\"),\n v.literal(\"cascade_to\"),\n v.literal(\"cascade_from\"),\n v.literal(\"mutually_exclusive\"),\n v.literal(\"correlates_with\"),\n v.literal(\"amplifies\"),\n v.literal(\"precondition_for\"),\n v.literal(\"in_tension_with\"),\n\n // --- Belief ↔ Belief: Epistemic Impact (Confidence Propagation) ---\n v.literal(\"falsified_by\"),\n v.literal(\"exclusive_with\"),\n v.literal(\"contradicts\"),\n v.literal(\"collapses_if\"),\n v.literal(\"strengthened_by\"),\n v.literal(\"weakened_by\"),\n v.literal(\"alternative_to\"),\n v.literal(\"subsumes\"),\n v.literal(\"validated_by\"),\n v.literal(\"required_for\"),\n v.literal(\"blocks\"),\n\n // --- Same-Type Relationships: Question ↔ Question (L3 → L3) ---\n v.literal(\"prerequisite_for\"),\n v.literal(\"parallel_to\"),\n\n // --- Same-Type Relationships: Evidence ↔ Evidence (L2 → L2) ---\n v.literal(\"corroborates\"),\n v.literal(\"extends\"),\n v.literal(\"same_source_as\"),\n v.literal(\"same_theme_as\"),\n\n // --- NEW: Deep Epistemic Analysis Edges (Phase: Schema Upgrade) ---\n v.literal(\"assumes\"),\n v.literal(\"would_predict\"),\n v.literal(\"analogous_to\"),\n v.literal(\"independent_of\"),\n\n // --- Entity↔Belief Bridge (OE-B) ---\n v.literal(\"competes_with\"),\n);\n\nexport function buildEdgeStatusSuccessResult(): EdgeStatusResult {\n return { success: true };\n}\n\nexport function buildEdgeNotFoundResult(): EdgeStatusResult {\n const result = {} as Extract<\n EdgeStatusResult,\n { success: false; error: \"Edge not found\" }\n >;\n result.success = false;\n result.error = \"Edge not found\";\n return result;\n}\n\nexport function buildEdgeMirrorSkippedResult(): EdgeStatusResult {\n return {\n success: false,\n reason: \"source_not_in_convex\",\n };\n}\n\nexport function buildEdgeMirrorMissingResult(): EdgeStatusResult {\n return {\n success: false,\n reason: \"not_found\",\n };\n}\n\nexport function buildEdgeMirrorWriteResult(\n edgeId: Id<\"epistemicEdges\">,\n existed: boolean,\n): EdgeStatusResult {\n return {\n success: true,\n edgeId,\n existed,\n };\n}\n\nexport function edgeMatchesWorkspaceReasoningScope(\n edge: Pick<WorkspaceScopedEdge, \"topicId\" | \"projectId\">,\n scope: WorkspaceEdgeScope,\n): boolean {\n return (\n (scope.topicId !== undefined && edge.topicId === scope.topicId) ||\n (scope.projectId !== undefined && edge.projectId === scope.projectId)\n );\n}\n\nexport async function collectScopedEdges(\n ctx: QueryCtx,\n scope: WorkspaceEdgeScope,\n scanLimit: number,\n): Promise<WorkspaceScopedEdge[]> {\n const queries: Promise<WorkspaceScopedEdge[]>[] = [];\n\n if (scope.topicId) {\n queries.push(\n ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_topic\", (q: TopicIndexQuery) =>\n q.eq(\"topicId\", scope.topicId),\n )\n .order(\"desc\")\n .take(scanLimit) as Promise<WorkspaceScopedEdge[]>,\n );\n }\n\n // Kernel v2: also check edges scoped to projectId (many edges still have\n // topicId=s97 scope while UI passes projectId=n97). Query both to catch all.\n if (scope.projectId) {\n queries.push(\n ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_topic\", (q: TopicIndexQuery) =>\n q.eq(\"topicId\", scope.projectId),\n )\n .order(\"desc\")\n .take(scanLimit) as Promise<WorkspaceScopedEdge[]>,\n );\n }\n\n const seen = new Set<string>();\n const deduped: WorkspaceScopedEdge[] = [];\n\n const flattened = (await Promise.all(queries)).flat();\n for (const edge of flattened) {\n const key = String(edge._id);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n deduped.push(edge);\n }\n\n return deduped;\n}\n","import {\n actionGeneric,\n anyApi,\n componentsGeneric,\n httpActionGeneric,\n internalActionGeneric,\n internalMutationGeneric,\n internalQueryGeneric,\n mutationGeneric,\n queryGeneric,\n} from \"convex/server\";\nimport type { GenericId } from \"convex/values\";\n\nexport const api = anyApi as any;\nexport const components = componentsGeneric() as any;\nexport const internal = anyApi as any;\n\nexport type TableNames = string;\nexport type Id<TableName extends TableNames = string> = GenericId<TableName>;\nexport type Doc<TableName extends TableNames = string> = any;\nexport type DataModel = any;\ntype IndexRangeBuilder = {\n field(fieldName: string): string;\n eq(fieldName: string, value: unknown): IndexRangeBuilder;\n gt(fieldName: string, value: unknown): IndexRangeBuilder;\n gte(fieldName: string, value: unknown): IndexRangeBuilder;\n lt(fieldName: string, value: unknown): IndexRangeBuilder;\n lte(fieldName: string, value: unknown): IndexRangeBuilder;\n};\ntype FilterBuilder = {\n eq(left: unknown, right: unknown): unknown;\n neq(left: unknown, right: unknown): unknown;\n gt(left: unknown, right: unknown): unknown;\n gte(left: unknown, right: unknown): unknown;\n lt(left: unknown, right: unknown): unknown;\n lte(left: unknown, right: unknown): unknown;\n and(...clauses: unknown[]): unknown;\n or(...clauses: unknown[]): unknown;\n field(fieldName: string): unknown;\n};\ntype QueryInitializer<TableName extends TableNames> = {\n withIndex(\n indexName: string,\n range?: (q: any) => unknown\n ): QueryInitializer<TableName>;\n filter(predicate: (q: any) => unknown): QueryInitializer<TableName>;\n order(direction: \"asc\" | \"desc\"): QueryInitializer<TableName>;\n collect(): Promise<Doc<TableName>[]>;\n take(limit: number): Promise<Doc<TableName>[]>;\n first(): Promise<Doc<TableName> | null>;\n unique(): Promise<Doc<TableName> | null>;\n};\nexport type DatabaseReader = {\n get<TableName extends TableNames>(\n id: Id<TableName>\n ): Promise<Doc<TableName> | null>;\n query<TableName extends TableNames>(\n tableName: TableName\n ): QueryInitializer<TableName>;\n normalizeId?<TableName extends TableNames>(\n tableName: TableName,\n id: string\n ): Id<TableName> | null;\n};\nexport type DatabaseWriter = DatabaseReader & {\n insert<TableName extends TableNames>(\n tableName: TableName,\n value: Record<string, unknown>\n ): Promise<Id<TableName>>;\n patch<TableName extends TableNames>(\n id: Id<TableName>,\n value: Record<string, unknown>\n ): Promise<void>;\n replace<TableName extends TableNames>(\n id: Id<TableName>,\n value: Record<string, unknown>\n ): Promise<void>;\n delete<TableName extends TableNames>(id: Id<TableName>): Promise<void>;\n};\ntype Scheduler = {\n runAfter(delayMs: number, functionReference: unknown, args?: unknown): Promise<void>;\n};\ntype AuthReader = {\n getUserIdentity(): Promise<unknown>;\n};\ntype RuntimeInvoker = {\n runQuery(functionReference: unknown, args?: unknown): Promise<any>;\n runMutation(functionReference: unknown, args?: unknown): Promise<any>;\n runAction(functionReference: unknown, args?: unknown): Promise<any>;\n};\nexport type QueryCtx = RuntimeInvoker & {\n auth: AuthReader;\n db: DatabaseReader;\n scheduler: Scheduler;\n};\nexport type MutationCtx = RuntimeInvoker & {\n auth: AuthReader;\n db: DatabaseWriter;\n scheduler: Scheduler;\n};\nexport type ActionCtx = RuntimeInvoker & {\n auth: AuthReader;\n scheduler: Scheduler;\n};\n\ntype ConvexFunctionBuilder<Ctx> = <\n Definition extends { handler?: (ctx: Ctx, args: any) => any },\n>(\n definition: Definition\n) => any;\n\nexport const action = actionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const httpAction =\n httpActionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const internalAction =\n internalActionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const internalMutation =\n internalMutationGeneric as unknown as ConvexFunctionBuilder<MutationCtx>;\nexport const internalQuery =\n internalQueryGeneric as unknown as ConvexFunctionBuilder<QueryCtx>;\nexport const mutation =\n mutationGeneric as unknown as ConvexFunctionBuilder<MutationCtx>;\nexport const query = queryGeneric as unknown as ConvexFunctionBuilder<QueryCtx>;\n","/** Topic-scope resolution helpers for graph-primitive operations. */\nimport { v } from \"convex/values\";\nimport { api as appApi } from \"./convex\";\nimport type { Id } from \"./convex\";\nimport { debugGraphPrimitiveFallback } from \"./debug\";\n\nconst LEGACY_SCOPE_FIELD = \"graphScope\" + \"ProjectId\";\n\ntype TopicDoc = Record<string, unknown> & {\n _id: Id<\"topics\">;\n globalId?: string;\n name?: string;\n depth?: number;\n createdAt?: number;\n parentTopicId?: Id<\"topics\">;\n tenantId?: string;\n workspaceId?: string;\n metadata?: Record<string, unknown>;\n};\n\nexport type TopicProjectScope = {\n topicId: Id<\"topics\">;\n projectId?: string;\n tenantId?: string;\n workspaceId?: string;\n source: \"topic\" | \"project_mapped_topic\" | \"topic_inferred\";\n};\n\ntype MaterializedTopicNodeDoc = {\n _id: string | Id<\"epistemicNodes\">;\n metadata?: Record<string, unknown>;\n};\n\nfunction asMappedProjectId(\n topic: TopicDoc | null | undefined,\n): string | undefined {\n if (!topic) {\n return;\n }\n const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);\n if (directLegacyProjectId) {\n return directLegacyProjectId;\n }\n const metadata = (topic.metadata || {}) as Record<string, unknown>;\n const candidate =\n (metadata[LEGACY_SCOPE_FIELD] as string | undefined) ||\n (metadata.legacyProjectId as string | undefined) ||\n (metadata.projectId as string | undefined) ||\n (metadata.scopeProjectId as string | undefined);\n return candidate ? (candidate as string) : undefined;\n}\n\nfunction normalizeScopeValue(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return;\n }\n const normalized = value.trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n\nfunction pickPrimaryTopic(candidates: TopicDoc[]): TopicDoc | undefined {\n return [...candidates].sort((a, b) => {\n const depthA = a.depth ?? 9999;\n const depthB = b.depth ?? 9999;\n if (depthA !== depthB) {\n return depthA - depthB;\n }\n const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;\n const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;\n if (createdA !== createdB) {\n return createdA - createdB;\n }\n return String(a.name || \"\").localeCompare(String(b.name || \"\"));\n })[0];\n}\n\nasync function findTopicsByScopeAlias(\n ctx: any,\n scopeId: string,\n): Promise<TopicDoc[]> {\n try {\n return (await ctx.db\n .query(\"topics\")\n .withIndex(\"by_graph_scope_project\", (q: any) =>\n q.eq(LEGACY_SCOPE_FIELD, scopeId),\n )\n .collect()) as TopicDoc[];\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve scope alias via index\",\n {\n error,\n scopeId,\n },\n );\n // Older Lucern dev deployments can lag index backfills while still carrying\n // the underlying topics table. Scan and filter so scope resolution keeps\n // working during the rollout.\n const topics = (await ctx.db.query(\"topics\").collect()) as TopicDoc[];\n return topics.filter((topic) => {\n const normalizedGlobalId = normalizeScopeValue(topic.globalId);\n const mappedProjectId = asMappedProjectId(topic);\n return (\n String(topic._id) === scopeId ||\n normalizedGlobalId === scopeId ||\n mappedProjectId === scopeId\n );\n });\n }\n}\n\nasync function tryResolveHostTopicById(\n ctx: any,\n topicId: string,\n): Promise<TopicDoc | null> {\n if (typeof ctx.runQuery !== \"function\") {\n return null;\n }\n try {\n return ((await ctx.runQuery(appApi.topics.get as any, {\n id: topicId,\n })) ?? null) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve topic by host query\",\n {\n error,\n topicId,\n },\n );\n return null;\n }\n}\n\nasync function tryResolveHostTopicByLegacyScope(\n ctx: any,\n legacyScopeId: string,\n): Promise<TopicDoc | null> {\n if (typeof ctx.runQuery !== \"function\") {\n return null;\n }\n try {\n return ((await ctx.runQuery(appApi.topics.getByLegacyScopeId as any, {\n projectId: legacyScopeId,\n })) ?? null) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve topic by legacy scope\",\n {\n error,\n legacyScopeId,\n },\n );\n return null;\n }\n}\n\nexport function readMaterializedTopicTableId(\n topicNode: MaterializedTopicNodeDoc | null | undefined,\n): string | undefined {\n if (!topicNode) {\n return;\n }\n\n const metadata = (topicNode.metadata || {}) as Record<string, unknown>;\n const topicTableId =\n (metadata.topicTableId as string | undefined) ||\n (metadata.topicId as string | undefined);\n\n return typeof topicTableId === \"string\" && topicTableId.trim().length > 0\n ? topicTableId.trim()\n : undefined;\n}\n\n/**\n * Walk the topic parent chain to find inherited workspaceId and tenantId.\n * Mirrors the pattern used by resolveTopicOntology for ontologyId inheritance.\n * Max depth: 10 (defensive limit against pathological nesting).\n */\nasync function resolveInheritedWorkspaceScope(\n ctx: any,\n topic: TopicDoc,\n): Promise<{ tenantId?: string; workspaceId?: string }> {\n const MAX_DEPTH = 10;\n let tenantId = normalizeScopeValue(topic.tenantId);\n let workspaceId = normalizeScopeValue(topic.workspaceId);\n\n if (tenantId && workspaceId) {\n return { tenantId, workspaceId };\n }\n\n let current: TopicDoc | null = topic;\n for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {\n current = (await ctx.db.get(current.parentTopicId)) as TopicDoc | null;\n if (!current) break;\n\n if (!tenantId) {\n tenantId = normalizeScopeValue(current.tenantId);\n }\n if (!workspaceId) {\n workspaceId = normalizeScopeValue(current.workspaceId);\n }\n if (tenantId && workspaceId) break;\n }\n\n return { tenantId, workspaceId };\n}\n\nexport async function resolveTopicProjectScope(\n ctx: any,\n args: { topicId?: Id<\"topics\"> | string; projectId?: string },\n): Promise<TopicProjectScope> {\n if (args.topicId) {\n // Try direct lookup, fall back to the legacy mapped-project bridge\n let topic: TopicDoc | null = null;\n try {\n topic = (await ctx.db.get(\n args.topicId as Id<\"topics\">,\n )) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to load topic by direct id\",\n {\n error,\n topicId: args.topicId,\n },\n );\n // Not a valid topics table ID\n }\n if (!topic) {\n topic = await tryResolveHostTopicById(ctx, String(args.topicId));\n }\n if (!topic) {\n // Try as legacy projectId / globalId via the mapped-project bridge.\n topic =\n pickPrimaryTopic(\n await findTopicsByScopeAlias(ctx, String(args.topicId)),\n ) ?? null;\n }\n if (!topic) {\n throw new Error(`Topic not found: ${String(args.topicId)}`);\n }\n const inherited = await resolveInheritedWorkspaceScope(ctx, topic);\n const mapped = asMappedProjectId(topic);\n if (mapped) {\n return {\n topicId: topic._id,\n projectId: mapped,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic\",\n };\n }\n return {\n topicId: topic._id,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic\",\n };\n }\n\n if (args.projectId) {\n let directTopic: TopicDoc | null = null;\n try {\n directTopic = (await ctx.db.get(\n args.projectId as Id<\"topics\">,\n )) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to load direct project topic\",\n {\n error,\n projectId: args.projectId,\n },\n );\n // Not a valid topics table ID, continue to legacy graph-scope lookup.\n }\n\n if (directTopic) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);\n const mapped = asMappedProjectId(directTopic);\n return {\n topicId: directTopic._id,\n projectId: mapped ?? args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic_inferred\",\n };\n }\n\n directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);\n if (directTopic) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);\n const mapped = asMappedProjectId(directTopic);\n return {\n topicId: directTopic._id,\n projectId: mapped ?? args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic_inferred\",\n };\n }\n\n const topics = await findTopicsByScopeAlias(ctx, args.projectId);\n const primary = pickPrimaryTopic(topics);\n if (primary) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, primary);\n return {\n topicId: primary._id,\n projectId: args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"project_mapped_topic\",\n };\n }\n throw new Error(\n `Legacy project scope ${String(args.projectId)} has no mapped topic.`,\n );\n }\n\n throw new Error(\n \"Missing scope: provide topicId (preferred) or legacy projectId alias.\",\n );\n}\n\n/** Shared scope args for graph-primitive functions. topicId is canonical; projectId is a legacy alias. */\nexport const optionalScopeArgs = {\n projectId: v.optional(v.string()),\n topicId: v.optional(v.string()),\n} as const;\n","/**\n * Internal mutations for Neo4j/Convex dual-write edge mirroring.\n */\n\nimport { v } from \"convex/values\";\nimport { permissiveReturn } from \"@lucern/contracts/schema-helpers/validators\";\nimport {\n buildEdgeMirrorMissingResult,\n buildEdgeMirrorSkippedResult,\n buildEdgeMirrorWriteResult,\n buildEdgeStatusSuccessResult,\n subjectiveOpinionValidator,\n} from \"./epistemicEdges.helpers\";\nimport { optionalScopeArgs } from \"./topicScope\";\nimport { internalMutation } from \"./convex\";\n\n/**\n * Internal mutation to mirror a Neo4j edge to Convex for reactive UI.\n * Called by createEdge action for edge types that need reactive UI updates.\n */\nexport const mirrorEdgeToConvex = internalMutation({\n args: {\n globalId: v.string(),\n fromGlobalId: v.string(),\n toGlobalId: v.string(),\n edgeType: v.string(),\n weight: v.optional(v.number()),\n confidence: v.optional(v.number()),\n context: v.optional(v.string()),\n derivationType: v.optional(v.string()),\n createdBy: v.string(),\n ...optionalScopeArgs,\n fromLayer: v.optional(v.string()),\n toLayer: v.optional(v.string()),\n fromNodeType: v.optional(v.string()),\n toNodeType: v.optional(v.string()),\n reasoningMethod: v.optional(v.string()),\n logicalRole: v.optional(v.string()),\n temporalClass: v.optional(v.string()),\n validFrom: v.optional(v.number()),\n validUntil: v.optional(v.number()),\n constraint: v.optional(v.string()),\n propagation: v.optional(v.string()),\n blocking: v.optional(v.boolean()),\n implicit: v.optional(v.boolean()),\n conditionalA: v.optional(subjectiveOpinionValidator),\n conditionalNotA: v.optional(subjectiveOpinionValidator),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const fromNode = await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.fromGlobalId))\n .first();\n\n if (!fromNode) {\n console.log(\n `[Dual-Write] Skipping mirror - source node not in Convex: ${args.fromGlobalId}`,\n );\n return buildEdgeMirrorSkippedResult();\n }\n\n const toNode = await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.toGlobalId))\n .first();\n\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (existing) {\n console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);\n return buildEdgeMirrorWriteResult(existing._id, true);\n }\n\n const now = Date.now();\n const edgeId = await ctx.db.insert(\"epistemicEdges\", {\n globalId: args.globalId,\n fromNodeId: fromNode._id,\n toNodeId: toNode?._id,\n sourceGlobalId: args.fromGlobalId,\n targetGlobalId: args.toGlobalId,\n // Preserve the canonical string value even when the local schema lags.\n edgeType: args.edgeType as string,\n weight: args.weight,\n confidence: args.confidence,\n context: args.context,\n derivationType: args.derivationType,\n constraint: args.constraint as\n | \"xor\"\n | \"alternative\"\n | \"counterfactual\"\n | undefined,\n propagation: args.propagation as \"threshold\" | \"continuous\" | undefined,\n blocking: args.blocking,\n implicit: args.implicit,\n conditionalA: args.conditionalA,\n conditionalNotA: args.conditionalNotA,\n createdBy: args.createdBy,\n createdAt: now,\n updatedAt: now,\n topicId: args.projectId as string | undefined,\n fromLayer: args.fromLayer as\n | \"L4\"\n | \"L3\"\n | \"L2\"\n | \"L1\"\n | \"ontological\"\n | undefined,\n toLayer: args.toLayer as\n | \"L4\"\n | \"L3\"\n | \"L2\"\n | \"L1\"\n | \"ontological\"\n | undefined,\n fromNodeType: args.fromNodeType,\n toNodeType: args.toNodeType,\n reasoningMethod: args.reasoningMethod as\n | \"deductive\"\n | \"causal\"\n | \"inductive\"\n | \"abductive\"\n | \"statistical\"\n | \"analogical\"\n | \"correlational\"\n | \"testimonial\"\n | \"implicit\"\n | \"pattern_match\"\n | undefined,\n logicalRole: args.logicalRole as\n | \"necessary\"\n | \"sufficient\"\n | \"necessary_sufficient\"\n | \"contributory\"\n | \"corroborative\"\n | undefined,\n temporalClass: args.temporalClass as\n | \"structural\"\n | \"cyclical\"\n | \"transient\"\n | \"point_in_time\"\n | undefined,\n validFrom: args.validFrom,\n validUntil: args.validUntil,\n });\n\n console.log(\n `[Dual-Write] Mirrored edge to Convex: ${args.globalId} (${args.edgeType})`,\n );\n return buildEdgeMirrorWriteResult(edgeId, false);\n },\n});\n\n/**\n * Internal mutation to delete a mirrored edge from Convex.\n * Called by deleteEdge action to maintain Convex mirror consistency.\n */\nexport const deleteEdgeFromConvex = internalMutation({\n args: {\n globalId: v.string(),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (!existing) {\n return buildEdgeMirrorMissingResult();\n }\n\n await ctx.db.delete(existing._id);\n console.log(`[Dual-Write] Deleted edge from Convex: ${args.globalId}`);\n return buildEdgeStatusSuccessResult();\n },\n});\n\n/**\n * Internal mutation to update a mirrored edge in Convex.\n * Called by updateEdge action to maintain Convex mirror consistency.\n */\nexport const updateEdgeInConvex = internalMutation({\n args: {\n globalId: v.string(),\n weight: v.optional(v.number()),\n confidence: v.optional(v.number()),\n context: v.optional(v.string()),\n derivationType: v.optional(v.string()),\n constraint: v.optional(v.string()),\n propagation: v.optional(v.string()),\n blocking: v.optional(v.boolean()),\n implicit: v.optional(v.boolean()),\n conditionalA: v.optional(subjectiveOpinionValidator),\n conditionalNotA: v.optional(subjectiveOpinionValidator),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (!existing) {\n return buildEdgeMirrorMissingResult();\n }\n\n const updates: Record<string, unknown> = {\n updatedAt: Date.now(),\n };\n\n if (args.weight !== undefined) {\n updates.weight = args.weight;\n }\n if (args.confidence !== undefined) {\n updates.confidence = args.confidence;\n }\n if (args.context !== undefined) {\n updates.context = args.context;\n }\n if (args.derivationType !== undefined) {\n updates.derivationType = args.derivationType;\n }\n if (args.constraint !== undefined) {\n updates.constraint = args.constraint;\n }\n if (args.propagation !== undefined) {\n updates.propagation = args.propagation;\n }\n if (args.blocking !== undefined) {\n updates.blocking = args.blocking;\n }\n if (args.implicit !== undefined) {\n updates.implicit = args.implicit;\n }\n if (args.conditionalA !== undefined) {\n updates.conditionalA = args.conditionalA;\n }\n if (args.conditionalNotA !== undefined) {\n updates.conditionalNotA = args.conditionalNotA;\n }\n\n await ctx.db.patch(existing._id, updates);\n console.log(`[Dual-Write] Updated edge in Convex: ${args.globalId}`);\n\n return buildEdgeStatusSuccessResult();\n },\n});\n"]}
1
+ {"version":3,"sources":["../src/epistemicEdges.helpers.ts","../src/convex.ts","../src/topicScope.ts","../src/epistemicInsert.ts","../src/epistemicEdges.handlers.ts"],"names":["v"],"mappings":"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","file":"epistemicEdges.handlers.js","sourcesContent":["/**\n * Shared validators and private helpers for epistemic edge operations.\n */\n\nimport { v } from \"convex/values\";\nimport type {\n EdgeStatusResult,\n SubjectiveOpinionDocument,\n WorkspaceEdgeScope,\n WorkspaceScopedEdge,\n} from \"./epistemicEdges.types\";\nimport type { Id, QueryCtx } from \"./convex\";\n\ntype TopicIndexQuery = {\n eq(field: string, value: unknown): TopicIndexQuery;\n};\n\nexport const epistemicLayerValidator = v.union(\n v.literal(\"L4\"),\n v.literal(\"L3\"),\n v.literal(\"L2\"),\n v.literal(\"L1\"),\n v.literal(\"ontological\"),\n v.literal(\"organizational\"),\n);\n\nexport const subjectiveOpinionValidator = v.object({\n b: v.number(),\n d: v.number(),\n u: v.number(),\n a: v.number(),\n} satisfies Record<keyof SubjectiveOpinionDocument, unknown>);\n\nexport const edgeTypeValidator = v.union(\n // --- L4 Decision Edges (Phase 2A) ---\n v.literal(\"based_on_belief\"),\n v.literal(\"based_on_question\"),\n v.literal(\"blocked_by_contradiction\"),\n v.literal(\"informed_by_theme\"),\n\n // --- Evidence Flow (L2 → L3, L2 → L1) ---\n v.literal(\"derived_from\"),\n v.literal(\"responds_to\"),\n v.literal(\"informs\"),\n v.literal(\"tests\"),\n v.literal(\"explores\"),\n v.literal(\"qualifies\"),\n\n // --- Synthesis (L2 → L2, L2 → L1) ---\n // \"based_on\" removed — use \"derived_from\" instead\n\n // --- Theme Relationships (L3 → L3) ---\n v.literal(\"relates_to_thesis\"),\n v.literal(\"belongs_to\"),\n v.literal(\"plays_theme\"),\n v.literal(\"scoped_by\"),\n\n // --- Deal/Company ---\n v.literal(\"evaluates\"),\n\n // --- People (ontological → ontological, ontological → L3) ---\n v.literal(\"perspective_on\"),\n v.literal(\"works_at\"),\n v.literal(\"mentioned_in\"),\n v.literal(\"founded_by\"),\n\n // --- Value Chain (ontological → ontological) ---\n v.literal(\"participates_in\"),\n v.literal(\"performs\"),\n v.literal(\"function_in\"),\n v.literal(\"impacts\"),\n\n // --- Investment (ontological → ontological) ---\n v.literal(\"invested_in\"),\n v.literal(\"raised_from\"),\n\n // --- Lifecycle (same layer only) ---\n v.literal(\"supersedes\"),\n v.literal(\"same_as\"),\n\n // --- Same-Type Relationships: Belief ↔ Belief (L3 → L3) ---\n v.literal(\"depends_on\"),\n v.literal(\"supports\"),\n v.literal(\"contains\"),\n\n // --- Belief Cluster Mapping (L3 → L3) ---\n v.literal(\"counterfactual_of\"),\n v.literal(\"cascade_to\"),\n v.literal(\"cascade_from\"),\n v.literal(\"mutually_exclusive\"),\n v.literal(\"correlates_with\"),\n v.literal(\"amplifies\"),\n v.literal(\"precondition_for\"),\n v.literal(\"in_tension_with\"),\n\n // --- Belief ↔ Belief: Epistemic Impact (Confidence Propagation) ---\n v.literal(\"falsified_by\"),\n v.literal(\"exclusive_with\"),\n v.literal(\"contradicts\"),\n v.literal(\"collapses_if\"),\n v.literal(\"strengthened_by\"),\n v.literal(\"weakened_by\"),\n v.literal(\"alternative_to\"),\n v.literal(\"subsumes\"),\n v.literal(\"validated_by\"),\n v.literal(\"required_for\"),\n v.literal(\"blocks\"),\n\n // --- Same-Type Relationships: Question ↔ Question (L3 → L3) ---\n v.literal(\"prerequisite_for\"),\n v.literal(\"parallel_to\"),\n\n // --- Same-Type Relationships: Evidence ↔ Evidence (L2 → L2) ---\n v.literal(\"corroborates\"),\n v.literal(\"extends\"),\n v.literal(\"same_source_as\"),\n v.literal(\"same_theme_as\"),\n\n // --- NEW: Deep Epistemic Analysis Edges (Phase: Schema Upgrade) ---\n v.literal(\"assumes\"),\n v.literal(\"would_predict\"),\n v.literal(\"analogous_to\"),\n v.literal(\"independent_of\"),\n\n // --- Entity↔Belief Bridge (OE-B) ---\n v.literal(\"competes_with\"),\n);\n\nexport function buildEdgeStatusSuccessResult(): EdgeStatusResult {\n return { success: true };\n}\n\nexport function buildEdgeNotFoundResult(): EdgeStatusResult {\n const result = {} as Extract<\n EdgeStatusResult,\n { success: false; error: \"Edge not found\" }\n >;\n result.success = false;\n result.error = \"Edge not found\";\n return result;\n}\n\nexport function buildEdgeMirrorSkippedResult(): EdgeStatusResult {\n return {\n success: false,\n reason: \"source_not_in_convex\",\n };\n}\n\nexport function buildEdgeMirrorMissingResult(): EdgeStatusResult {\n return {\n success: false,\n reason: \"not_found\",\n };\n}\n\nexport function buildEdgeMirrorWriteResult(\n edgeId: Id<\"epistemicEdges\">,\n existed: boolean,\n): EdgeStatusResult {\n return {\n success: true,\n edgeId,\n existed,\n };\n}\n\nexport function edgeMatchesWorkspaceReasoningScope(\n edge: Pick<WorkspaceScopedEdge, \"topicId\" | \"projectId\">,\n scope: WorkspaceEdgeScope,\n): boolean {\n return (\n (scope.topicId !== undefined && edge.topicId === scope.topicId) ||\n (scope.projectId !== undefined && edge.projectId === scope.projectId)\n );\n}\n\nexport async function collectScopedEdges(\n ctx: QueryCtx,\n scope: WorkspaceEdgeScope,\n scanLimit: number,\n): Promise<WorkspaceScopedEdge[]> {\n const queries: Promise<WorkspaceScopedEdge[]>[] = [];\n\n if (scope.topicId) {\n queries.push(\n ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_topic\", (q: TopicIndexQuery) =>\n q.eq(\"topicId\", scope.topicId),\n )\n .order(\"desc\")\n .take(scanLimit) as Promise<WorkspaceScopedEdge[]>,\n );\n }\n\n // Kernel v2: also check edges scoped to projectId (many edges still have\n // topicId=s97 scope while UI passes projectId=n97). Query both to catch all.\n if (scope.projectId) {\n queries.push(\n ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_topic\", (q: TopicIndexQuery) =>\n q.eq(\"topicId\", scope.projectId),\n )\n .order(\"desc\")\n .take(scanLimit) as Promise<WorkspaceScopedEdge[]>,\n );\n }\n\n const seen = new Set<string>();\n const deduped: WorkspaceScopedEdge[] = [];\n\n const flattened = (await Promise.all(queries)).flat();\n for (const edge of flattened) {\n const key = String(edge._id);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n deduped.push(edge);\n }\n\n return deduped;\n}\n","import {\n actionGeneric,\n anyApi,\n componentsGeneric,\n httpActionGeneric,\n internalActionGeneric,\n internalMutationGeneric,\n internalQueryGeneric,\n mutationGeneric,\n queryGeneric,\n} from \"convex/server\";\nimport type { GenericId } from \"convex/values\";\n\nexport const api = anyApi as any;\nexport const components = componentsGeneric() as any;\nexport const internal = anyApi as any;\n\nexport type TableNames = string;\nexport type Id<TableName extends TableNames = string> = GenericId<TableName>;\nexport type Doc<TableName extends TableNames = string> = any;\nexport type DataModel = any;\ntype IndexRangeBuilder = {\n field(fieldName: string): string;\n eq(fieldName: string, value: unknown): IndexRangeBuilder;\n gt(fieldName: string, value: unknown): IndexRangeBuilder;\n gte(fieldName: string, value: unknown): IndexRangeBuilder;\n lt(fieldName: string, value: unknown): IndexRangeBuilder;\n lte(fieldName: string, value: unknown): IndexRangeBuilder;\n};\ntype FilterBuilder = {\n eq(left: unknown, right: unknown): unknown;\n neq(left: unknown, right: unknown): unknown;\n gt(left: unknown, right: unknown): unknown;\n gte(left: unknown, right: unknown): unknown;\n lt(left: unknown, right: unknown): unknown;\n lte(left: unknown, right: unknown): unknown;\n and(...clauses: unknown[]): unknown;\n or(...clauses: unknown[]): unknown;\n field(fieldName: string): unknown;\n};\ntype QueryInitializer<TableName extends TableNames> = {\n withIndex(\n indexName: string,\n range?: (q: any) => unknown\n ): QueryInitializer<TableName>;\n filter(predicate: (q: any) => unknown): QueryInitializer<TableName>;\n order(direction: \"asc\" | \"desc\"): QueryInitializer<TableName>;\n collect(): Promise<Doc<TableName>[]>;\n take(limit: number): Promise<Doc<TableName>[]>;\n first(): Promise<Doc<TableName> | null>;\n unique(): Promise<Doc<TableName> | null>;\n};\nexport type DatabaseReader = {\n get<TableName extends TableNames>(\n id: Id<TableName>\n ): Promise<Doc<TableName> | null>;\n query<TableName extends TableNames>(\n tableName: TableName\n ): QueryInitializer<TableName>;\n normalizeId?<TableName extends TableNames>(\n tableName: TableName,\n id: string\n ): Id<TableName> | null;\n};\nexport type DatabaseWriter = DatabaseReader & {\n insert<TableName extends TableNames>(\n tableName: TableName,\n value: Record<string, unknown>\n ): Promise<Id<TableName>>;\n patch<TableName extends TableNames>(\n id: Id<TableName>,\n value: Record<string, unknown>\n ): Promise<void>;\n replace<TableName extends TableNames>(\n id: Id<TableName>,\n value: Record<string, unknown>\n ): Promise<void>;\n delete<TableName extends TableNames>(id: Id<TableName>): Promise<void>;\n};\ntype Scheduler = {\n runAfter(delayMs: number, functionReference: unknown, args?: unknown): Promise<void>;\n};\ntype AuthReader = {\n getUserIdentity(): Promise<unknown>;\n};\ntype RuntimeInvoker = {\n runQuery(functionReference: unknown, args?: unknown): Promise<any>;\n runMutation(functionReference: unknown, args?: unknown): Promise<any>;\n runAction(functionReference: unknown, args?: unknown): Promise<any>;\n};\nexport type QueryCtx = RuntimeInvoker & {\n auth: AuthReader;\n db: DatabaseReader;\n scheduler: Scheduler;\n};\nexport type MutationCtx = RuntimeInvoker & {\n auth: AuthReader;\n db: DatabaseWriter;\n scheduler: Scheduler;\n};\nexport type ActionCtx = RuntimeInvoker & {\n auth: AuthReader;\n scheduler: Scheduler;\n};\n\ntype ConvexFunctionBuilder<Ctx> = <\n Definition extends { handler?: (ctx: Ctx, args: any) => any },\n>(\n definition: Definition\n) => any;\n\nexport const action = actionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const httpAction =\n httpActionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const internalAction =\n internalActionGeneric as unknown as ConvexFunctionBuilder<ActionCtx>;\nexport const internalMutation =\n internalMutationGeneric as unknown as ConvexFunctionBuilder<MutationCtx>;\nexport const internalQuery =\n internalQueryGeneric as unknown as ConvexFunctionBuilder<QueryCtx>;\nexport const mutation =\n mutationGeneric as unknown as ConvexFunctionBuilder<MutationCtx>;\nexport const query = queryGeneric as unknown as ConvexFunctionBuilder<QueryCtx>;\n","/** Topic-scope resolution helpers for graph-primitive operations. */\nimport { v } from \"convex/values\";\nimport { api as appApi } from \"./convex\";\nimport type { Id } from \"./convex\";\nimport { debugGraphPrimitiveFallback } from \"./debug\";\n\nconst LEGACY_SCOPE_FIELD = \"graphScope\" + \"ProjectId\";\n\ntype TopicDoc = Record<string, unknown> & {\n _id: Id<\"topics\">;\n globalId?: string;\n name?: string;\n depth?: number;\n createdAt?: number;\n parentTopicId?: Id<\"topics\">;\n tenantId?: string;\n workspaceId?: string;\n metadata?: Record<string, unknown>;\n};\n\nexport type TopicProjectScope = {\n topicId: Id<\"topics\">;\n projectId?: string;\n tenantId?: string;\n workspaceId?: string;\n source:\n | \"topic\"\n | \"project_mapped_topic\"\n | \"topic_inferred\"\n | \"topic_node\";\n};\n\n/**\n * RR.5 (legacy topics-table retirement) — graph-primitives mirror of the\n * kernel's epistemicNodes-first topic-scope fallback. Kept in lockstep with\n * `packages/reasoning-kernel/src/adapters/lib/topicScope.ts`\n * (`tests/unit/convex/topic-scope-fallbacks.test.ts` asserts parity).\n *\n * Resolves a topic that exists only as `epistemicNodes(nodeType=\"topic\")` —\n * the write-of-record — when no legacy `topics` row backs the ref, returning the\n * node's `topicId` self-key so scoped beliefs/evidence/questions stay reachable.\n * See the kernel module for the full silent-empty-result rationale.\n */\ntype TopicNodeScopeDoc = {\n _id: string | Id<\"epistemicNodes\">;\n globalId?: string;\n nodeType?: string;\n topicId?: string;\n metadata?: Record<string, unknown>;\n};\n\nasync function resolveTopicNodeScopeOrNull(\n ctx: any,\n ref: string,\n): Promise<TopicProjectScope | null> {\n if (!ctx?.db || typeof ctx.db.query !== \"function\") {\n return null;\n }\n\n let node: TopicNodeScopeDoc | null = null;\n\n // 1. The ref IS the topic node's UUIDv7 globalId.\n try {\n const byGlobalId = (await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q: any) => q.eq(\"globalId\", ref))\n .first()) as TopicNodeScopeDoc | null;\n if (byGlobalId && byGlobalId.nodeType === \"topic\") {\n node = byGlobalId;\n }\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] topic-node scope lookup by globalId failed\",\n { error, ref },\n );\n }\n\n if (!node) {\n return null;\n }\n\n const scopeKey =\n normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);\n if (!scopeKey) {\n return null;\n }\n\n return {\n topicId: scopeKey as Id<\"topics\">,\n projectId: asMappedProjectId(node as unknown as TopicDoc),\n source: \"topic_node\",\n };\n}\n\ntype MaterializedTopicNodeDoc = {\n _id: string | Id<\"epistemicNodes\">;\n metadata?: Record<string, unknown>;\n};\n\nfunction asMappedProjectId(\n topic: TopicDoc | null | undefined,\n): string | undefined {\n if (!topic) {\n return;\n }\n const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);\n if (directLegacyProjectId) {\n return directLegacyProjectId;\n }\n const metadata = (topic.metadata || {}) as Record<string, unknown>;\n const candidate =\n (metadata[LEGACY_SCOPE_FIELD] as string | undefined) ||\n (metadata.legacyProjectId as string | undefined) ||\n (metadata.projectId as string | undefined) ||\n (metadata.scopeProjectId as string | undefined);\n return candidate ? (candidate as string) : undefined;\n}\n\nfunction normalizeScopeValue(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return;\n }\n const normalized = value.trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n\nfunction pickPrimaryTopic(candidates: TopicDoc[]): TopicDoc | undefined {\n return [...candidates].sort((a, b) => {\n const depthA = a.depth ?? 9999;\n const depthB = b.depth ?? 9999;\n if (depthA !== depthB) {\n return depthA - depthB;\n }\n const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;\n const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;\n if (createdA !== createdB) {\n return createdA - createdB;\n }\n return String(a.name || \"\").localeCompare(String(b.name || \"\"));\n })[0];\n}\n\nasync function findTopicsByScopeAlias(\n ctx: any,\n scopeId: string,\n): Promise<TopicDoc[]> {\n try {\n return (await ctx.db\n .query(\"topics\")\n .withIndex(\"by_graph_scope_project\", (q: any) =>\n q.eq(LEGACY_SCOPE_FIELD, scopeId),\n )\n .collect()) as TopicDoc[];\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve scope alias via index\",\n {\n error,\n scopeId,\n },\n );\n // Older Lucern dev deployments can lag index backfills while still carrying\n // the underlying topics table. Scan and filter so scope resolution keeps\n // working during the rollout.\n const topics = (await ctx.db.query(\"topics\").collect()) as TopicDoc[];\n return topics.filter((topic) => {\n const normalizedGlobalId = normalizeScopeValue(topic.globalId);\n const mappedProjectId = asMappedProjectId(topic);\n return (\n String(topic._id) === scopeId ||\n normalizedGlobalId === scopeId ||\n mappedProjectId === scopeId\n );\n });\n }\n}\n\nasync function tryResolveHostTopicById(\n ctx: any,\n topicId: string,\n): Promise<TopicDoc | null> {\n if (typeof ctx.runQuery !== \"function\") {\n return null;\n }\n try {\n return ((await ctx.runQuery(appApi.topics.get as any, {\n id: topicId,\n })) ?? null) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve topic by host query\",\n {\n error,\n topicId,\n },\n );\n return null;\n }\n}\n\nasync function tryResolveHostTopicByLegacyScope(\n ctx: any,\n legacyScopeId: string,\n): Promise<TopicDoc | null> {\n if (typeof ctx.runQuery !== \"function\") {\n return null;\n }\n try {\n return ((await ctx.runQuery(appApi.topics.getByLegacyScopeId as any, {\n projectId: legacyScopeId,\n })) ?? null) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to resolve topic by legacy scope\",\n {\n error,\n legacyScopeId,\n },\n );\n return null;\n }\n}\n\nexport function readMaterializedTopicTableId(\n topicNode: MaterializedTopicNodeDoc | null | undefined,\n): string | undefined {\n if (!topicNode) {\n return;\n }\n\n const metadata = (topicNode.metadata || {}) as Record<string, unknown>;\n const topicTableId =\n (metadata.topicTableId as string | undefined) ||\n (metadata.topicId as string | undefined);\n\n return typeof topicTableId === \"string\" && topicTableId.trim().length > 0\n ? topicTableId.trim()\n : undefined;\n}\n\n/**\n * Walk the topic parent chain to find inherited workspaceId and tenantId.\n * Mirrors the pattern used by resolveTopicOntology for ontologyId inheritance.\n * Max depth: 10 (defensive limit against pathological nesting).\n */\nasync function resolveInheritedWorkspaceScope(\n ctx: any,\n topic: TopicDoc,\n): Promise<{ tenantId?: string; workspaceId?: string }> {\n const MAX_DEPTH = 10;\n let tenantId = normalizeScopeValue(topic.tenantId);\n let workspaceId = normalizeScopeValue(topic.workspaceId);\n\n if (tenantId && workspaceId) {\n return { tenantId, workspaceId };\n }\n\n let current: TopicDoc | null = topic;\n for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {\n current = (await ctx.db.get(current.parentTopicId)) as TopicDoc | null;\n if (!current) break;\n\n if (!tenantId) {\n tenantId = normalizeScopeValue(current.tenantId);\n }\n if (!workspaceId) {\n workspaceId = normalizeScopeValue(current.workspaceId);\n }\n if (tenantId && workspaceId) break;\n }\n\n return { tenantId, workspaceId };\n}\n\nexport async function resolveTopicProjectScope(\n ctx: any,\n args: { topicId?: Id<\"topics\"> | string; projectId?: string },\n): Promise<TopicProjectScope> {\n if (args.topicId) {\n // Try direct lookup, fall back to the legacy mapped-project bridge\n let topic: TopicDoc | null = null;\n try {\n topic = (await ctx.db.get(\n args.topicId as Id<\"topics\">,\n )) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to load topic by direct id\",\n {\n error,\n topicId: args.topicId,\n },\n );\n // Not a valid topics table ID\n }\n if (!topic) {\n topic = await tryResolveHostTopicById(ctx, String(args.topicId));\n }\n if (!topic) {\n // Try as legacy projectId / globalId via the mapped-project bridge.\n topic =\n pickPrimaryTopic(\n await findTopicsByScopeAlias(ctx, String(args.topicId)),\n ) ?? null;\n }\n if (!topic) {\n // RR.5: no legacy topics row — resolve the canonical topic node store\n // before throwing (silent-empty-result fix).\n const nodeScope = await resolveTopicNodeScopeOrNull(\n ctx,\n String(args.topicId),\n );\n if (nodeScope) {\n return nodeScope;\n }\n throw new Error(`Topic not found: ${String(args.topicId)}`);\n }\n const inherited = await resolveInheritedWorkspaceScope(ctx, topic);\n const mapped = asMappedProjectId(topic);\n if (mapped) {\n return {\n topicId: topic._id,\n projectId: mapped,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic\",\n };\n }\n return {\n topicId: topic._id,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic\",\n };\n }\n\n if (args.projectId) {\n let directTopic: TopicDoc | null = null;\n try {\n directTopic = (await ctx.db.get(\n args.projectId as Id<\"topics\">,\n )) as TopicDoc | null;\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[topicScope] Failed to load direct project topic\",\n {\n error,\n projectId: args.projectId,\n },\n );\n // Not a valid topics table ID, continue to legacy graph-scope lookup.\n }\n\n if (directTopic) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);\n const mapped = asMappedProjectId(directTopic);\n return {\n topicId: directTopic._id,\n projectId: mapped ?? args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic_inferred\",\n };\n }\n\n directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);\n if (directTopic) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);\n const mapped = asMappedProjectId(directTopic);\n return {\n topicId: directTopic._id,\n projectId: mapped ?? args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"topic_inferred\",\n };\n }\n\n const topics = await findTopicsByScopeAlias(ctx, args.projectId);\n const primary = pickPrimaryTopic(topics);\n if (primary) {\n const inherited = await resolveInheritedWorkspaceScope(ctx, primary);\n return {\n topicId: primary._id,\n projectId: args.projectId,\n tenantId: inherited.tenantId,\n workspaceId: inherited.workspaceId,\n source: \"project_mapped_topic\",\n };\n }\n // RR.5: try the canonical topic node store before failing.\n const nodeScope = await resolveTopicNodeScopeOrNull(\n ctx,\n String(args.projectId),\n );\n if (nodeScope) {\n return {\n ...nodeScope,\n projectId: nodeScope.projectId ?? String(args.projectId),\n };\n }\n\n throw new Error(\n `Legacy project scope ${String(args.projectId)} has no mapped topic.`,\n );\n }\n\n throw new Error(\n \"Missing scope: provide topicId (preferred) or legacy projectId alias.\",\n );\n}\n\n/** Shared scope args for graph-primitive functions. topicId is canonical; projectId is a legacy alias. */\nexport const optionalScopeArgs = {\n projectId: v.optional(v.string()),\n topicId: v.optional(v.string()),\n} as const;\n","import type { WithoutSystemFields } from \"convex/server\";\nimport {\n assertUuidV7Identity,\n assertStorageEdgeVocabulary,\n assertUuidShapedEdgeEndpoint,\n} from \"@lucern/contracts/ids\";\nimport {\n assertEdgePolicyAllowed,\n edgePolicyManifest,\n type EpistemicNodeType,\n} from \"@lucern/contracts\";\nimport type { Doc, Id, MutationCtx } from \"./convex\";\n\nexport async function insertEpistemicNode(\n ctx: MutationCtx,\n doc: WithoutSystemFields<Doc<\"epistemicNodes\">>,\n): Promise<Id<\"epistemicNodes\">> {\n assertUuidV7Identity(\"epistemicNodes\", doc.globalId);\n return ctx.db.insert(\"epistemicNodes\", doc);\n}\n\n/**\n * C2-RR.4b — REFERENTIAL edge-endpoint canonicality (amends RR.4's FORMAT guard).\n *\n * Proves an endpoint is the `globalId` of an EXISTING `epistemicNodes` row, not\n * that it has a v7 shape. Refuses doc-ids and fabricated/unknown endpoints while\n * keeping legacy v4-`globalId` nodes (the established prod corpus) linkable.\n * SHAPE pre-filter accepts both v4 and v7; the indexed `by_globalId` lookup is\n * the referential authority (no full scan). v7-format stays enforced only at new\n * identity minting (`assertUuidV7Identity`). See c2-rr4b-endpoint-referential.\n */\nasync function assertExistingNodeEndpoint(\n ctx: MutationCtx,\n endpointRole: \"fromNodeId\" | \"toNodeId\",\n endpoint: string,\n): Promise<void> {\n assertUuidShapedEdgeEndpoint(endpointRole, endpoint);\n const node = await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q: any) => q.eq(\"globalId\", endpoint))\n .first();\n if (!node) {\n throw new Error(\n `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`,\n );\n }\n}\n\nexport async function insertEpistemicEdge(\n ctx: MutationCtx,\n doc: WithoutSystemFields<Doc<\"epistemicEdges\">>,\n): Promise<Id<\"epistemicEdges\">> {\n assertUuidV7Identity(\"epistemicEdges\", doc.globalId);\n\n // R1.1a — STORAGE-VOCABULARY MEMBERSHIP\n assertStorageEdgeVocabulary(doc.edgeType);\n\n // R1.1b — ENDPOINT PRESENCE\n if (!doc.fromNodeId || typeof doc.fromNodeId !== \"string\") {\n throw new Error(\n \"edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId\"\n );\n }\n if (!doc.toNodeId || typeof doc.toNodeId !== \"string\") {\n throw new Error(\n \"edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId\"\n );\n }\n\n // C2-RR.4b Defect E (REFERENTIAL) — ENDPOINT CANONICAL IDENTITY. Edge\n // endpoints must each be the globalId of an EXISTING epistemicNodes row,\n // never a Convex doc id, a legacy topic id, or a fabricated/unknown uuid.\n // This refuses the mixed _id/globalId endpoint writes at the floor while\n // keeping legacy v4-globalId nodes linkable (RR.4b prod-incident cure).\n await assertExistingNodeEndpoint(ctx, \"fromNodeId\", doc.fromNodeId);\n await assertExistingNodeEndpoint(ctx, \"toNodeId\", doc.toNodeId);\n\n // R1.1c — FULL POLICY ASSERT (when nodeTypes are present and edgeType is in public manifest)\n if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== \"extracted_from\") {\n assertEdgePolicyAllowed(\n edgePolicyManifest,\n doc.edgeType,\n {\n kind: \"epistemic_node\",\n nodeId: doc.fromNodeId,\n nodeType: doc.fromNodeType as EpistemicNodeType,\n },\n {\n kind: \"epistemic_node\",\n nodeId: doc.toNodeId,\n nodeType: doc.toNodeType as EpistemicNodeType,\n }\n );\n }\n\n return ctx.db.insert(\"epistemicEdges\", doc);\n}\n","/**\n * Internal mutations for Neo4j/Convex dual-write edge mirroring.\n */\n\nimport { v } from \"convex/values\";\nimport { permissiveReturn } from \"@lucern/contracts/schema-helpers/validators\";\nimport {\n buildEdgeMirrorMissingResult,\n buildEdgeMirrorSkippedResult,\n buildEdgeMirrorWriteResult,\n buildEdgeStatusSuccessResult,\n subjectiveOpinionValidator,\n} from \"./epistemicEdges.helpers\";\nimport { optionalScopeArgs } from \"./topicScope\";\nimport { internalMutation } from \"./convex\";\nimport { insertEpistemicEdge } from \"./epistemicInsert.js\";\n\n/**\n * Internal mutation to mirror a Neo4j edge to Convex for reactive UI.\n * Called by createEdge action for edge types that need reactive UI updates.\n */\nexport const mirrorEdgeToConvex = internalMutation({\n args: {\n globalId: v.string(),\n fromGlobalId: v.string(),\n toGlobalId: v.string(),\n edgeType: v.string(),\n weight: v.optional(v.number()),\n confidence: v.optional(v.number()),\n context: v.optional(v.string()),\n derivationType: v.optional(v.string()),\n createdBy: v.string(),\n ...optionalScopeArgs,\n fromLayer: v.optional(v.string()),\n toLayer: v.optional(v.string()),\n fromNodeType: v.optional(v.string()),\n toNodeType: v.optional(v.string()),\n reasoningMethod: v.optional(v.string()),\n logicalRole: v.optional(v.string()),\n temporalClass: v.optional(v.string()),\n validFrom: v.optional(v.number()),\n validUntil: v.optional(v.number()),\n constraint: v.optional(v.string()),\n propagation: v.optional(v.string()),\n blocking: v.optional(v.boolean()),\n implicit: v.optional(v.boolean()),\n conditionalA: v.optional(subjectiveOpinionValidator),\n conditionalNotA: v.optional(subjectiveOpinionValidator),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const fromNode = await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.fromGlobalId))\n .first();\n\n if (!fromNode) {\n console.log(\n `[Dual-Write] Skipping mirror - source node not in Convex: ${args.fromGlobalId}`,\n );\n return buildEdgeMirrorSkippedResult();\n }\n\n const toNode = await ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.toGlobalId))\n .first();\n\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (existing) {\n console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);\n return buildEdgeMirrorWriteResult(existing._id, true);\n }\n\n if (!toNode) {\n console.log(\n `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`\n );\n return buildEdgeMirrorSkippedResult();\n }\n\n const now = Date.now();\n const edgeId = await insertEpistemicEdge(ctx, {\n globalId: args.globalId,\n // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex\n // doc ids. fromNode/toNode are resolved by_globalId above, so their\n // stored globalId equals args.from/toGlobalId; persisting the doc _id here\n // was the \"mixed _id/globalId\" defect.\n fromNodeId: fromNode.globalId,\n toNodeId: toNode.globalId,\n sourceGlobalId: args.fromGlobalId,\n targetGlobalId: args.toGlobalId,\n // Preserve the canonical string value even when the local schema lags.\n edgeType: args.edgeType as string,\n weight: args.weight,\n confidence: args.confidence,\n context: args.context,\n derivationType: args.derivationType,\n constraint: args.constraint as\n | \"xor\"\n | \"alternative\"\n | \"counterfactual\"\n | undefined,\n propagation: args.propagation as \"threshold\" | \"continuous\" | undefined,\n blocking: args.blocking,\n implicit: args.implicit,\n conditionalA: args.conditionalA,\n conditionalNotA: args.conditionalNotA,\n createdBy: args.createdBy,\n createdAt: now,\n updatedAt: now,\n topicId: args.projectId as string | undefined,\n fromLayer: args.fromLayer as\n | \"L4\"\n | \"L3\"\n | \"L2\"\n | \"L1\"\n | \"ontological\"\n | undefined,\n toLayer: args.toLayer as\n | \"L4\"\n | \"L3\"\n | \"L2\"\n | \"L1\"\n | \"ontological\"\n | undefined,\n fromNodeType: args.fromNodeType,\n toNodeType: args.toNodeType,\n reasoningMethod: args.reasoningMethod as\n | \"deductive\"\n | \"causal\"\n | \"inductive\"\n | \"abductive\"\n | \"statistical\"\n | \"analogical\"\n | \"correlational\"\n | \"testimonial\"\n | \"implicit\"\n | \"pattern_match\"\n | undefined,\n logicalRole: args.logicalRole as\n | \"necessary\"\n | \"sufficient\"\n | \"necessary_sufficient\"\n | \"contributory\"\n | \"corroborative\"\n | undefined,\n temporalClass: args.temporalClass as\n | \"structural\"\n | \"cyclical\"\n | \"transient\"\n | \"point_in_time\"\n | undefined,\n validFrom: args.validFrom,\n validUntil: args.validUntil,\n });\n\n console.log(\n `[Dual-Write] Mirrored edge to Convex: ${args.globalId} (${args.edgeType})`,\n );\n return buildEdgeMirrorWriteResult(edgeId, false);\n },\n});\n\n/**\n * Internal mutation to delete a mirrored edge from Convex.\n * Called by deleteEdge action to maintain Convex mirror consistency.\n */\nexport const deleteEdgeFromConvex = internalMutation({\n args: {\n globalId: v.string(),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (!existing) {\n return buildEdgeMirrorMissingResult();\n }\n\n await ctx.db.delete(existing._id);\n console.log(`[Dual-Write] Deleted edge from Convex: ${args.globalId}`);\n return buildEdgeStatusSuccessResult();\n },\n});\n\n/**\n * Internal mutation to update a mirrored edge in Convex.\n * Called by updateEdge action to maintain Convex mirror consistency.\n */\nexport const updateEdgeInConvex = internalMutation({\n args: {\n globalId: v.string(),\n weight: v.optional(v.number()),\n confidence: v.optional(v.number()),\n context: v.optional(v.string()),\n derivationType: v.optional(v.string()),\n constraint: v.optional(v.string()),\n propagation: v.optional(v.string()),\n blocking: v.optional(v.boolean()),\n implicit: v.optional(v.boolean()),\n conditionalA: v.optional(subjectiveOpinionValidator),\n conditionalNotA: v.optional(subjectiveOpinionValidator),\n },\n returns: permissiveReturn,\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"epistemicEdges\")\n .withIndex(\"by_globalId\", (q) => q.eq(\"globalId\", args.globalId))\n .first();\n\n if (!existing) {\n return buildEdgeMirrorMissingResult();\n }\n\n const updates: Record<string, unknown> = {\n updatedAt: Date.now(),\n };\n\n if (args.weight !== undefined) {\n updates.weight = args.weight;\n }\n if (args.confidence !== undefined) {\n updates.confidence = args.confidence;\n }\n if (args.context !== undefined) {\n updates.context = args.context;\n }\n if (args.derivationType !== undefined) {\n updates.derivationType = args.derivationType;\n }\n if (args.constraint !== undefined) {\n updates.constraint = args.constraint;\n }\n if (args.propagation !== undefined) {\n updates.propagation = args.propagation;\n }\n if (args.blocking !== undefined) {\n updates.blocking = args.blocking;\n }\n if (args.implicit !== undefined) {\n updates.implicit = args.implicit;\n }\n if (args.conditionalA !== undefined) {\n updates.conditionalA = args.conditionalA;\n }\n if (args.conditionalNotA !== undefined) {\n updates.conditionalNotA = args.conditionalNotA;\n }\n\n await ctx.db.patch(existing._id, updates);\n console.log(`[Dual-Write] Updated edge in Convex: ${args.globalId}`);\n\n return buildEdgeStatusSuccessResult();\n },\n});\n"]}
@@ -1,7 +1,7 @@
1
1
  import * as convex_values from 'convex/values';
2
2
  import { EdgeStatusResult, WorkspaceEdgeScope, WorkspaceScopedEdge } from './epistemicEdges.types.js';
3
3
  import { Id, QueryCtx } from './convex.js';
4
- import './topicScope-By_zp4tt.js';
4
+ import './topicScope-7zhyeGl7.js';
5
5
 
6
6
  declare const epistemicLayerValidator: convex_values.VUnion<"L4" | "L3" | "L2" | "L1" | "ontological" | "organizational", [convex_values.VLiteral<"L4", "required">, convex_values.VLiteral<"L3", "required">, convex_values.VLiteral<"L2", "required">, convex_values.VLiteral<"L1", "required">, convex_values.VLiteral<"ontological", "required">, convex_values.VLiteral<"organizational", "required">], "required", never>;
7
7
  declare const subjectiveOpinionValidator: convex_values.VObject<{
@@ -15,7 +15,7 @@ declare const subjectiveOpinionValidator: convex_values.VObject<{
15
15
  u: convex_values.VFloat64<number, "required">;
16
16
  a: convex_values.VFloat64<number, "required">;
17
17
  }, "required", "b" | "d" | "u" | "a">;
18
- declare const edgeTypeValidator: convex_values.VUnion<"supports" | "informs" | "depends_on" | "derived_from" | "contains" | "tests" | "based_on_belief" | "based_on_question" | "blocked_by_contradiction" | "informed_by_theme" | "works_at" | "invested_in" | "competes_with" | "participates_in" | "founded_by" | "evaluates" | "performs" | "function_in" | "impacts" | "raised_from" | "mentioned_in" | "perspective_on" | "supersedes" | "responds_to" | "belongs_to" | "relates_to_thesis" | "plays_theme" | "contradicts" | "prerequisite_for" | "parallel_to" | "corroborates" | "extends" | "same_source_as" | "same_theme_as" | "qualifies" | "explores" | "scoped_by" | "same_as" | "assumes" | "would_predict" | "analogous_to" | "independent_of" | "counterfactual_of" | "cascade_to" | "mutually_exclusive" | "correlates_with" | "amplifies" | "precondition_for" | "in_tension_with" | "cascade_from" | "falsified_by" | "exclusive_with" | "collapses_if" | "strengthened_by" | "weakened_by" | "alternative_to" | "subsumes" | "validated_by" | "required_for" | "blocks", [convex_values.VLiteral<"based_on_belief", "required">, convex_values.VLiteral<"based_on_question", "required">, convex_values.VLiteral<"blocked_by_contradiction", "required">, convex_values.VLiteral<"informed_by_theme", "required">, convex_values.VLiteral<"derived_from", "required">, convex_values.VLiteral<"responds_to", "required">, convex_values.VLiteral<"informs", "required">, convex_values.VLiteral<"tests", "required">, convex_values.VLiteral<"explores", "required">, convex_values.VLiteral<"qualifies", "required">, convex_values.VLiteral<"relates_to_thesis", "required">, convex_values.VLiteral<"belongs_to", "required">, convex_values.VLiteral<"plays_theme", "required">, convex_values.VLiteral<"scoped_by", "required">, convex_values.VLiteral<"evaluates", "required">, convex_values.VLiteral<"perspective_on", "required">, convex_values.VLiteral<"works_at", "required">, convex_values.VLiteral<"mentioned_in", "required">, convex_values.VLiteral<"founded_by", "required">, convex_values.VLiteral<"participates_in", "required">, convex_values.VLiteral<"performs", "required">, convex_values.VLiteral<"function_in", "required">, convex_values.VLiteral<"impacts", "required">, convex_values.VLiteral<"invested_in", "required">, convex_values.VLiteral<"raised_from", "required">, convex_values.VLiteral<"supersedes", "required">, convex_values.VLiteral<"same_as", "required">, convex_values.VLiteral<"depends_on", "required">, convex_values.VLiteral<"supports", "required">, convex_values.VLiteral<"contains", "required">, convex_values.VLiteral<"counterfactual_of", "required">, convex_values.VLiteral<"cascade_to", "required">, convex_values.VLiteral<"cascade_from", "required">, convex_values.VLiteral<"mutually_exclusive", "required">, convex_values.VLiteral<"correlates_with", "required">, convex_values.VLiteral<"amplifies", "required">, convex_values.VLiteral<"precondition_for", "required">, convex_values.VLiteral<"in_tension_with", "required">, convex_values.VLiteral<"falsified_by", "required">, convex_values.VLiteral<"exclusive_with", "required">, convex_values.VLiteral<"contradicts", "required">, convex_values.VLiteral<"collapses_if", "required">, convex_values.VLiteral<"strengthened_by", "required">, convex_values.VLiteral<"weakened_by", "required">, convex_values.VLiteral<"alternative_to", "required">, convex_values.VLiteral<"subsumes", "required">, convex_values.VLiteral<"validated_by", "required">, convex_values.VLiteral<"required_for", "required">, convex_values.VLiteral<"blocks", "required">, convex_values.VLiteral<"prerequisite_for", "required">, convex_values.VLiteral<"parallel_to", "required">, convex_values.VLiteral<"corroborates", "required">, convex_values.VLiteral<"extends", "required">, convex_values.VLiteral<"same_source_as", "required">, convex_values.VLiteral<"same_theme_as", "required">, convex_values.VLiteral<"assumes", "required">, convex_values.VLiteral<"would_predict", "required">, convex_values.VLiteral<"analogous_to", "required">, convex_values.VLiteral<"independent_of", "required">, convex_values.VLiteral<"competes_with", "required">], "required", never>;
18
+ declare const edgeTypeValidator: convex_values.VUnion<"supports" | "informs" | "depends_on" | "derived_from" | "contains" | "tests" | "based_on_belief" | "based_on_question" | "blocked_by_contradiction" | "informed_by_theme" | "works_at" | "invested_in" | "competes_with" | "participates_in" | "founded_by" | "evaluates" | "performs" | "function_in" | "impacts" | "raised_from" | "mentioned_in" | "perspective_on" | "supersedes" | "responds_to" | "belongs_to" | "relates_to_thesis" | "plays_theme" | "qualifies" | "contradicts" | "corroborates" | "strengthened_by" | "weakened_by" | "validated_by" | "falsified_by" | "amplifies" | "precondition_for" | "prerequisite_for" | "required_for" | "in_tension_with" | "mutually_exclusive" | "exclusive_with" | "alternative_to" | "subsumes" | "extends" | "assumes" | "would_predict" | "analogous_to" | "independent_of" | "correlates_with" | "counterfactual_of" | "parallel_to" | "cascade_from" | "cascade_to" | "collapses_if" | "same_as" | "explores" | "same_theme_as" | "scoped_by" | "same_source_as" | "blocks", [convex_values.VLiteral<"based_on_belief", "required">, convex_values.VLiteral<"based_on_question", "required">, convex_values.VLiteral<"blocked_by_contradiction", "required">, convex_values.VLiteral<"informed_by_theme", "required">, convex_values.VLiteral<"derived_from", "required">, convex_values.VLiteral<"responds_to", "required">, convex_values.VLiteral<"informs", "required">, convex_values.VLiteral<"tests", "required">, convex_values.VLiteral<"explores", "required">, convex_values.VLiteral<"qualifies", "required">, convex_values.VLiteral<"relates_to_thesis", "required">, convex_values.VLiteral<"belongs_to", "required">, convex_values.VLiteral<"plays_theme", "required">, convex_values.VLiteral<"scoped_by", "required">, convex_values.VLiteral<"evaluates", "required">, convex_values.VLiteral<"perspective_on", "required">, convex_values.VLiteral<"works_at", "required">, convex_values.VLiteral<"mentioned_in", "required">, convex_values.VLiteral<"founded_by", "required">, convex_values.VLiteral<"participates_in", "required">, convex_values.VLiteral<"performs", "required">, convex_values.VLiteral<"function_in", "required">, convex_values.VLiteral<"impacts", "required">, convex_values.VLiteral<"invested_in", "required">, convex_values.VLiteral<"raised_from", "required">, convex_values.VLiteral<"supersedes", "required">, convex_values.VLiteral<"same_as", "required">, convex_values.VLiteral<"depends_on", "required">, convex_values.VLiteral<"supports", "required">, convex_values.VLiteral<"contains", "required">, convex_values.VLiteral<"counterfactual_of", "required">, convex_values.VLiteral<"cascade_to", "required">, convex_values.VLiteral<"cascade_from", "required">, convex_values.VLiteral<"mutually_exclusive", "required">, convex_values.VLiteral<"correlates_with", "required">, convex_values.VLiteral<"amplifies", "required">, convex_values.VLiteral<"precondition_for", "required">, convex_values.VLiteral<"in_tension_with", "required">, convex_values.VLiteral<"falsified_by", "required">, convex_values.VLiteral<"exclusive_with", "required">, convex_values.VLiteral<"contradicts", "required">, convex_values.VLiteral<"collapses_if", "required">, convex_values.VLiteral<"strengthened_by", "required">, convex_values.VLiteral<"weakened_by", "required">, convex_values.VLiteral<"alternative_to", "required">, convex_values.VLiteral<"subsumes", "required">, convex_values.VLiteral<"validated_by", "required">, convex_values.VLiteral<"required_for", "required">, convex_values.VLiteral<"blocks", "required">, convex_values.VLiteral<"prerequisite_for", "required">, convex_values.VLiteral<"parallel_to", "required">, convex_values.VLiteral<"corroborates", "required">, convex_values.VLiteral<"extends", "required">, convex_values.VLiteral<"same_source_as", "required">, convex_values.VLiteral<"same_theme_as", "required">, convex_values.VLiteral<"assumes", "required">, convex_values.VLiteral<"would_predict", "required">, convex_values.VLiteral<"analogous_to", "required">, convex_values.VLiteral<"independent_of", "required">, convex_values.VLiteral<"competes_with", "required">], "required", never>;
19
19
  declare function buildEdgeStatusSuccessResult(): EdgeStatusResult;
20
20
  declare function buildEdgeNotFoundResult(): EdgeStatusResult;
21
21
  declare function buildEdgeMirrorSkippedResult(): EdgeStatusResult;
@@ -2,6 +2,8 @@ import { v } from 'convex/values';
2
2
  import { checkScopeAccess, requireProjectAccess } from '@lucern/access-control/access';
3
3
  import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
4
4
  import { componentsGeneric, anyApi, queryGeneric, mutationGeneric, internalMutationGeneric } from 'convex/server';
5
+ import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
6
+ import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
5
7
 
6
8
  // src/epistemicEdges.queries.ts
7
9
 
@@ -476,7 +478,72 @@ var EDGE_LAYER_RULES = {
476
478
  from: ["ontological"],
477
479
  to: ["L3"],
478
480
  description: "Deal -> Theme"
479
- }
481
+ },
482
+ // C2-RR.4 — storage migration alias + the remaining 55 reachable edge types.
483
+ // Mirrors the kernel rule table
484
+ // (packages/reasoning-kernel/src/adapters/lib/edgeValidation.ts) so this
485
+ // graph-primitives write path admits the same public edge vocabulary. See the
486
+ // kernel file for the per-group rationale.
487
+ extracted_from: {
488
+ from: ["L2", "L3", "L4"],
489
+ to: ["L1", "L2", "L3"],
490
+ description: "Legacy storage migration alias for derived_from; public writes use derived_from"
491
+ },
492
+ qualifies: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief qualifies a Belief" },
493
+ contradicts: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief contradicts a Belief" },
494
+ reinforces: { from: ["L2", "L3"], to: ["L3"], description: "Evidence/Belief reinforces a Belief" },
495
+ corroborates: { from: ["L2", "L3"], to: ["L3"], description: "Independent evidence/belief corroborates a Belief" },
496
+ strengthened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is strengthened by Evidence/Belief" },
497
+ weakened_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is weakened by Evidence/Belief" },
498
+ validated_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is validated by Evidence/Belief" },
499
+ falsified_by: { from: ["L3"], to: ["L2", "L3"], description: "Belief is falsified by Evidence/Belief" },
500
+ amplifies: { from: ["L3"], to: ["L3"], description: "Belief amplifies another Belief" },
501
+ precondition_for: { from: ["L3"], to: ["L3"], description: "Belief is a precondition for another Belief" },
502
+ prerequisite_for: { from: ["L3"], to: ["L3"], description: "Belief is a prerequisite for another Belief" },
503
+ required_for: { from: ["L3"], to: ["L3"], description: "Belief is required for another Belief" },
504
+ in_tension_with: { from: ["L3"], to: ["L3"], description: "Belief is in tension with another Belief" },
505
+ mutually_exclusive: { from: ["L3"], to: ["L3"], description: "Beliefs cannot both hold" },
506
+ exclusive_with: { from: ["L3"], to: ["L3"], description: "Belief is exclusive with another Belief" },
507
+ alternative_to: { from: ["L3"], to: ["L3"], description: "Belief is an alternative to another Belief" },
508
+ subsumes: { from: ["L3"], to: ["L3"], description: "Belief subsumes a narrower Belief" },
509
+ extends: { from: ["L3"], to: ["L3"], description: "Belief extends another Belief" },
510
+ refines: { from: ["L3"], to: ["L3"], description: "Belief refines another Belief" },
511
+ implements: { from: ["L3"], to: ["L3"], description: "Belief implements an abstract Belief" },
512
+ violates: { from: ["L3"], to: ["L3"], description: "Belief violates a constraint Belief" },
513
+ assumes: { from: ["L3"], to: ["L3"], description: "Belief assumes another Belief" },
514
+ would_predict: { from: ["L3"], to: ["L3"], description: "Belief would predict another Belief" },
515
+ analogous_to: { from: ["L3"], to: ["L3"], description: "Belief is analogous to another Belief" },
516
+ independent_of: { from: ["L3"], to: ["L3"], description: "Belief is independent of another Belief" },
517
+ correlates_with: { from: ["L3"], to: ["L3"], description: "Belief correlates with another Belief" },
518
+ co_changes_with: { from: ["L3"], to: ["L3"], description: "Belief co-changes with another Belief" },
519
+ counterfactual_of: { from: ["L3"], to: ["L3"], description: "Belief is a counterfactual of another Belief" },
520
+ parallel_to: { from: ["L3"], to: ["L3"], description: "Belief runs parallel to another Belief" },
521
+ cascade_from: { from: ["L3"], to: ["L3"], description: "Cascade from an upstream Belief" },
522
+ cascade_to: { from: ["L3"], to: ["L3"], description: "Cascade to a downstream Belief" },
523
+ collapses_if: { from: ["L3"], to: ["L3"], description: "Belief collapses if another fails" },
524
+ branches_from: { from: ["L3"], to: ["L3"], description: "Belief branches from an ancestor Belief" },
525
+ same_as: { from: ["L2", "L3", "ontological"], to: ["L2", "L3", "ontological"], description: "Two nodes are the same entity" },
526
+ answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief answers a Question" },
527
+ partially_answers: { from: ["L2", "L3"], to: ["L3"], description: "Answer/Belief partially answers a Question" },
528
+ explores: { from: ["L3"], to: ["L3"], description: "Question explores a Belief/Theme" },
529
+ informed_by_theme: { from: ["L3"], to: ["L3"], description: "Belief is informed by a Theme" },
530
+ same_theme_as: { from: ["L3"], to: ["L3"], description: "Two Beliefs share a Theme" },
531
+ based_on: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief/Question" },
532
+ based_on_belief: { from: ["L4"], to: ["L3"], description: "Decision is based on a Belief" },
533
+ based_on_question: { from: ["L4"], to: ["L3"], description: "Decision is based on a Question" },
534
+ blocked_by_contradiction: { from: ["L4"], to: ["L3"], description: "Decision is blocked by a Contradiction" },
535
+ parent_of: { from: ["L3", "L4", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "A is the parent of B" },
536
+ child_of: { from: ["L2", "L3", "ontological", "organizational"], to: ["L3", "L4", "ontological", "organizational"], description: "A is the child of B" },
537
+ scoped_by: { from: ["L2", "L3"], to: ["L3", "organizational"], description: "Object is scoped by a Topic/Theme" },
538
+ cites: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Evidence/Belief cites a Source/Excerpt" },
539
+ summarizes: { from: ["L2", "L3"], to: ["L1", "L2"], description: "Synthesis/Evidence summarizes a Source/Evidence" },
540
+ same_source_as: { from: ["L1", "L2"], to: ["L1", "L2"], description: "Two nodes derive from the same Source" },
541
+ migrating_from: { from: ["L2", "L3", "L4"], to: ["L1", "L2", "L3"], description: "Migration lineage: from an ancestor" },
542
+ migrating_to: { from: ["L1", "L2", "L3"], to: ["L2", "L3", "L4"], description: "Migration lineage: to a successor" },
543
+ about_entity: { from: ["L2", "L3"], to: ["ontological"], description: "Belief/Evidence is about an Entity" },
544
+ entity_referenced_in: { from: ["ontological"], to: ["L1", "L2"], description: "Entity is referenced in a Source/Evidence" },
545
+ related_to: { from: ["L2", "L3", "ontological", "organizational"], to: ["L2", "L3", "ontological", "organizational"], description: "Lateral adjacency; no confidence pressure" },
546
+ blocks: { from: ["L3", "L4"], to: ["L3", "L4"], description: "A blocks B (structural gate)" }
480
547
  };
481
548
  function validateEdgeLayers(edgeType, fromLayer, toLayer) {
482
549
  const rules = EDGE_LAYER_RULES[edgeType];
@@ -516,6 +583,35 @@ function validateEdgeLayers(edgeType, fromLayer, toLayer) {
516
583
  return { valid: true };
517
584
  }
518
585
  var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
586
+ async function resolveTopicNodeScopeOrNull(ctx, ref) {
587
+ if (!ctx?.db || typeof ctx.db.query !== "function") {
588
+ return null;
589
+ }
590
+ let node = null;
591
+ try {
592
+ const byGlobalId = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", ref)).first();
593
+ if (byGlobalId && byGlobalId.nodeType === "topic") {
594
+ node = byGlobalId;
595
+ }
596
+ } catch (error) {
597
+ debugGraphPrimitiveFallback(
598
+ "[topicScope] topic-node scope lookup by globalId failed",
599
+ { error, ref }
600
+ );
601
+ }
602
+ if (!node) {
603
+ return null;
604
+ }
605
+ const scopeKey = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
606
+ if (!scopeKey) {
607
+ return null;
608
+ }
609
+ return {
610
+ topicId: scopeKey,
611
+ projectId: asMappedProjectId(node),
612
+ source: "topic_node"
613
+ };
614
+ }
519
615
  function asMappedProjectId(topic) {
520
616
  if (!topic) {
521
617
  return;
@@ -656,6 +752,13 @@ async function resolveTopicProjectScope(ctx, args) {
656
752
  ) ?? null;
657
753
  }
658
754
  if (!topic) {
755
+ const nodeScope = await resolveTopicNodeScopeOrNull(
756
+ ctx,
757
+ String(args.topicId)
758
+ );
759
+ if (nodeScope) {
760
+ return nodeScope;
761
+ }
659
762
  throw new Error(`Topic not found: ${String(args.topicId)}`);
660
763
  }
661
764
  const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
@@ -726,6 +829,16 @@ async function resolveTopicProjectScope(ctx, args) {
726
829
  source: "project_mapped_topic"
727
830
  };
728
831
  }
832
+ const nodeScope = await resolveTopicNodeScopeOrNull(
833
+ ctx,
834
+ String(args.projectId)
835
+ );
836
+ if (nodeScope) {
837
+ return {
838
+ ...nodeScope,
839
+ projectId: nodeScope.projectId ?? String(args.projectId)
840
+ };
841
+ }
729
842
  throw new Error(
730
843
  `Legacy project scope ${String(args.projectId)} has no mapped topic.`
731
844
  );
@@ -1258,6 +1371,9 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
1258
1371
  type: mapProjectType(topic, metadata),
1259
1372
  description: readNonEmptyString(topic.description),
1260
1373
  ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
1374
+ // FR.7 creator-grant: surface the principal-shaped owner field (column-first,
1375
+ // metadata fallback for legacy rows that recorded it in metadata).
1376
+ ownerPrincipalId: readNonEmptyString(topic.ownerPrincipalId) || readNonEmptyString(metadata.ownerPrincipalId),
1261
1377
  sharedWith: readStringArray(metadata.sharedWith),
1262
1378
  visibility,
1263
1379
  tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
@@ -1836,6 +1952,50 @@ var deleteEdges = mutation({
1836
1952
  };
1837
1953
  }
1838
1954
  });
1955
+ async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
1956
+ assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
1957
+ const node = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", endpoint)).first();
1958
+ if (!node) {
1959
+ throw new Error(
1960
+ `edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
1961
+ );
1962
+ }
1963
+ }
1964
+ async function insertEpistemicEdge(ctx, doc) {
1965
+ assertUuidV7Identity("epistemicEdges", doc.globalId);
1966
+ assertStorageEdgeVocabulary(doc.edgeType);
1967
+ if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
1968
+ throw new Error(
1969
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
1970
+ );
1971
+ }
1972
+ if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
1973
+ throw new Error(
1974
+ "edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
1975
+ );
1976
+ }
1977
+ await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
1978
+ await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
1979
+ if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
1980
+ assertEdgePolicyAllowed(
1981
+ edgePolicyManifest,
1982
+ doc.edgeType,
1983
+ {
1984
+ kind: "epistemic_node",
1985
+ nodeId: doc.fromNodeId,
1986
+ nodeType: doc.fromNodeType
1987
+ },
1988
+ {
1989
+ kind: "epistemic_node",
1990
+ nodeId: doc.toNodeId,
1991
+ nodeType: doc.toNodeType
1992
+ }
1993
+ );
1994
+ }
1995
+ return ctx.db.insert("epistemicEdges", doc);
1996
+ }
1997
+
1998
+ // src/epistemicEdges.handlers.ts
1839
1999
  var mirrorEdgeToConvex = internalMutation({
1840
2000
  args: {
1841
2001
  globalId: v.string(),
@@ -1879,11 +2039,21 @@ var mirrorEdgeToConvex = internalMutation({
1879
2039
  console.log(`[Dual-Write] Edge already exists: ${args.globalId}`);
1880
2040
  return buildEdgeMirrorWriteResult(existing._id, true);
1881
2041
  }
2042
+ if (!toNode) {
2043
+ console.log(
2044
+ `[Dual-Write] Skipping mirror - to-node not in Convex: ${args.toGlobalId} (edge ${args.globalId} type ${args.edgeType})`
2045
+ );
2046
+ return buildEdgeMirrorSkippedResult();
2047
+ }
1882
2048
  const now = Date.now();
1883
- const edgeId = await ctx.db.insert("epistemicEdges", {
2049
+ const edgeId = await insertEpistemicEdge(ctx, {
1884
2050
  globalId: args.globalId,
1885
- fromNodeId: fromNode._id,
1886
- toNodeId: toNode?._id,
2051
+ // C2-RR.4 Defect E — endpoints are canonical node globalIds, not Convex
2052
+ // doc ids. fromNode/toNode are resolved by_globalId above, so their
2053
+ // stored globalId equals args.from/toGlobalId; persisting the doc _id here
2054
+ // was the "mixed _id/globalId" defect.
2055
+ fromNodeId: fromNode.globalId,
2056
+ toNodeId: toNode.globalId,
1887
2057
  sourceGlobalId: args.fromGlobalId,
1888
2058
  targetGlobalId: args.toGlobalId,
1889
2059
  // Preserve the canonical string value even when the local schema lags.