@indexnetwork/protocol 0.1.0 → 0.2.1

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 (128) hide show
  1. package/README.md +113 -0
  2. package/dist/agents/chat.agent.js +2 -2
  3. package/dist/agents/chat.agent.js.map +1 -1
  4. package/dist/agents/chat.prompt.d.ts.map +1 -1
  5. package/dist/agents/chat.prompt.js +44 -32
  6. package/dist/agents/chat.prompt.js.map +1 -1
  7. package/dist/agents/chat.prompt.modules.js +16 -16
  8. package/dist/agents/chat.prompt.modules.js.map +1 -1
  9. package/dist/agents/negotiation.proposer.d.ts +1 -1
  10. package/dist/agents/negotiation.proposer.d.ts.map +1 -1
  11. package/dist/agents/negotiation.responder.d.ts +1 -1
  12. package/dist/agents/negotiation.responder.d.ts.map +1 -1
  13. package/dist/agents/opportunity.evaluator.d.ts +1 -1
  14. package/dist/agents/opportunity.evaluator.d.ts.map +1 -1
  15. package/dist/agents/opportunity.evaluator.js +2 -2
  16. package/dist/agents/opportunity.evaluator.js.map +1 -1
  17. package/dist/agents/opportunity.presenter.js +1 -1
  18. package/dist/agents/opportunity.presenter.js.map +1 -1
  19. package/dist/graphs/chat.graph.d.ts +9 -9
  20. package/dist/graphs/chat.graph.d.ts.map +1 -1
  21. package/dist/graphs/chat.graph.js +2 -2
  22. package/dist/graphs/chat.graph.js.map +1 -1
  23. package/dist/graphs/home.graph.d.ts +5 -5
  24. package/dist/graphs/home.graph.d.ts.map +1 -1
  25. package/dist/graphs/home.graph.js +2 -2
  26. package/dist/graphs/home.graph.js.map +1 -1
  27. package/dist/graphs/intent.graph.d.ts +21 -21
  28. package/dist/graphs/intent.graph.d.ts.map +1 -1
  29. package/dist/graphs/intent.graph.js +8 -8
  30. package/dist/graphs/intent.graph.js.map +1 -1
  31. package/dist/graphs/intent_network.graph.d.ts +398 -0
  32. package/dist/graphs/intent_network.graph.d.ts.map +1 -0
  33. package/dist/graphs/intent_network.graph.js +345 -0
  34. package/dist/graphs/intent_network.graph.js.map +1 -0
  35. package/dist/graphs/negotiation.graph.d.ts +27 -27
  36. package/dist/graphs/negotiation.graph.d.ts.map +1 -1
  37. package/dist/graphs/negotiation.graph.js +2 -2
  38. package/dist/graphs/negotiation.graph.js.map +1 -1
  39. package/dist/graphs/network.graph.d.ts +620 -0
  40. package/dist/graphs/network.graph.d.ts.map +1 -0
  41. package/dist/graphs/network.graph.js +226 -0
  42. package/dist/graphs/network.graph.js.map +1 -0
  43. package/dist/graphs/network_membership.graph.d.ts +250 -0
  44. package/dist/graphs/network_membership.graph.d.ts.map +1 -0
  45. package/dist/graphs/network_membership.graph.js +204 -0
  46. package/dist/graphs/network_membership.graph.js.map +1 -0
  47. package/dist/graphs/opportunity.graph.d.ts +33 -33
  48. package/dist/graphs/opportunity.graph.d.ts.map +1 -1
  49. package/dist/graphs/opportunity.graph.js +161 -144
  50. package/dist/graphs/opportunity.graph.js.map +1 -1
  51. package/dist/graphs/profile.graph.js +4 -4
  52. package/dist/graphs/profile.graph.js.map +1 -1
  53. package/dist/graphs/tests/chat.graph.mocks.d.ts +14 -14
  54. package/dist/graphs/tests/chat.graph.mocks.d.ts.map +1 -1
  55. package/dist/graphs/tests/chat.graph.mocks.js +23 -23
  56. package/dist/graphs/tests/chat.graph.mocks.js.map +1 -1
  57. package/dist/index.d.ts +3 -3
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -3
  60. package/dist/index.js.map +1 -1
  61. package/dist/interfaces/database.interface.d.ts +111 -111
  62. package/dist/interfaces/database.interface.d.ts.map +1 -1
  63. package/dist/interfaces/embedder.interface.d.ts +1 -1
  64. package/dist/interfaces/embedder.interface.d.ts.map +1 -1
  65. package/dist/mcp/mcp.server.js +1 -1
  66. package/dist/mcp/mcp.server.js.map +1 -1
  67. package/dist/states/chat.state.d.ts +2 -2
  68. package/dist/states/chat.state.js +2 -2
  69. package/dist/states/chat.state.js.map +1 -1
  70. package/dist/states/home.state.d.ts +1 -1
  71. package/dist/states/home.state.js +1 -1
  72. package/dist/states/home.state.js.map +1 -1
  73. package/dist/states/intent.state.d.ts +4 -4
  74. package/dist/states/intent.state.d.ts.map +1 -1
  75. package/dist/states/intent.state.js +1 -1
  76. package/dist/states/intent.state.js.map +1 -1
  77. package/dist/states/intent_network.state.d.ts +148 -0
  78. package/dist/states/intent_network.state.d.ts.map +1 -0
  79. package/dist/states/intent_network.state.js +100 -0
  80. package/dist/states/intent_network.state.js.map +1 -0
  81. package/dist/states/negotiation.state.d.ts +4 -4
  82. package/dist/states/negotiation.state.d.ts.map +1 -1
  83. package/dist/states/negotiation.state.js +1 -1
  84. package/dist/states/negotiation.state.js.map +1 -1
  85. package/dist/states/network.state.d.ts +179 -0
  86. package/dist/states/network.state.d.ts.map +1 -0
  87. package/dist/states/network.state.js +56 -0
  88. package/dist/states/network.state.js.map +1 -0
  89. package/dist/states/network_membership.state.d.ts +77 -0
  90. package/dist/states/network_membership.state.d.ts.map +1 -0
  91. package/dist/states/network_membership.state.js +43 -0
  92. package/dist/states/network_membership.state.js.map +1 -0
  93. package/dist/states/opportunity.state.d.ts +15 -15
  94. package/dist/states/opportunity.state.d.ts.map +1 -1
  95. package/dist/states/opportunity.state.js +6 -6
  96. package/dist/states/opportunity.state.js.map +1 -1
  97. package/dist/streamers/chat.streamer.d.ts +2 -2
  98. package/dist/streamers/chat.streamer.d.ts.map +1 -1
  99. package/dist/streamers/chat.streamer.js +6 -6
  100. package/dist/streamers/chat.streamer.js.map +1 -1
  101. package/dist/support/opportunity.discover.js +2 -2
  102. package/dist/support/opportunity.discover.js.map +1 -1
  103. package/dist/support/opportunity.enricher.js +2 -2
  104. package/dist/support/opportunity.enricher.js.map +1 -1
  105. package/dist/support/protocol.logger.d.ts +1 -1
  106. package/dist/support/protocol.logger.d.ts.map +1 -1
  107. package/dist/tools/index.d.ts.map +1 -1
  108. package/dist/tools/index.js +21 -17
  109. package/dist/tools/index.js.map +1 -1
  110. package/dist/tools/intent.tools.js +67 -67
  111. package/dist/tools/intent.tools.js.map +1 -1
  112. package/dist/tools/network.tools.d.ts +3 -0
  113. package/dist/tools/network.tools.d.ts.map +1 -0
  114. package/dist/tools/network.tools.js +423 -0
  115. package/dist/tools/network.tools.js.map +1 -0
  116. package/dist/tools/opportunity.tools.d.ts.map +1 -1
  117. package/dist/tools/opportunity.tools.js +51 -44
  118. package/dist/tools/opportunity.tools.js.map +1 -1
  119. package/dist/tools/profile.tools.js +21 -21
  120. package/dist/tools/profile.tools.js.map +1 -1
  121. package/dist/tools/tool.helpers.d.ts +10 -10
  122. package/dist/tools/tool.helpers.d.ts.map +1 -1
  123. package/dist/tools/tool.helpers.js +16 -16
  124. package/dist/tools/tool.helpers.js.map +1 -1
  125. package/dist/tools/tool.registry.js +3 -3
  126. package/dist/tools/tool.registry.js.map +1 -1
  127. package/dist/tools/utility.tools.js +3 -3
  128. package/package.json +4 -2
@@ -113,18 +113,18 @@ export class OpportunityGraphFactory {
113
113
  const prepNode = withNodeTrace("opportunity-prep", async (state) => timed("OpportunityGraph.prep", async () => withCallLogging(logger, '[Graph:Prep] prepNode', {
114
114
  userId: state.userId,
115
115
  hasSearchQuery: !!state.searchQuery,
116
- requestedIndexId: state.indexId ?? undefined,
116
+ requestedIndexId: state.networkId ?? undefined,
117
117
  }, async () => {
118
- // Use getIndexMemberships (all memberships) for search scope — NOT getUserIndexIds
118
+ // Use getNetworkMemberships (all memberships) for search scope — NOT getUserIndexIds
119
119
  // (which filters by autoAssign=true and is intended only for intent assignment).
120
- const memberships = await this.database.getIndexMemberships(state.userId);
121
- const userIndexIds = memberships.map(m => m.indexId);
122
- if (userIndexIds.length === 0) {
123
- logger.verbose('[Graph:Prep] User has no index memberships - cannot find opportunities');
120
+ const memberships = await this.database.getNetworkMemberships(state.userId);
121
+ const userNetworkIds = memberships.map(m => m.networkId);
122
+ if (userNetworkIds.length === 0) {
123
+ logger.verbose('[Graph:Prep] User has no network memberships - cannot find opportunities');
124
124
  return {
125
- userIndexes: [],
125
+ userNetworks: [],
126
126
  sourceProfile: null,
127
- error: 'You need to join at least one index to find opportunities.',
127
+ error: 'You need to join at least one network to find opportunities.',
128
128
  };
129
129
  }
130
130
  const discoveryUserId = state.onBehalfOfUserId ?? state.userId;
@@ -147,12 +147,12 @@ export class OpportunityGraphFactory {
147
147
  }
148
148
  : null;
149
149
  return {
150
- userIndexes: userIndexIds,
150
+ userNetworks: userNetworkIds,
151
151
  indexedIntents,
152
152
  sourceProfile,
153
153
  trace: [{
154
154
  node: "prep",
155
- detail: `${userIndexIds.length} index(es), ${intents.length} intent(s), ${profile ? 'profile loaded' : 'no profile'}`,
155
+ detail: `${userNetworkIds.length} network(s), ${intents.length} intent(s), ${profile ? 'profile loaded' : 'no profile'}`,
156
156
  }],
157
157
  };
158
158
  }, { context: { userId: state.userId }, logOutput: true }).catch((error) => {
@@ -170,47 +170,49 @@ export class OpportunityGraphFactory {
170
170
  const r = result;
171
171
  if (r?.error)
172
172
  return `error: ${r.error}`;
173
- const indexes = r?.userIndexes;
173
+ const indexes = r?.userNetworks;
174
174
  const intents = r?.indexedIntents;
175
175
  return indexes && intents ? `${indexes.length} index(es), ${intents.length} intent(s)` : undefined;
176
176
  });
177
177
  /**
178
178
  * Node 1: Scope
179
179
  * Determines which indexes to search within.
180
- * If indexId provided: searches only that index.
180
+ * If networkId provided: searches only that index.
181
181
  * Otherwise: searches all user's indexes.
182
182
  */
183
183
  const scopeNode = withNodeTrace("opportunity-scope", async (state) => {
184
184
  return timed("OpportunityGraph.scope", async () => {
185
185
  logger.verbose('[Graph:Scope] Determining search scope', {
186
- requestedIndexId: state.indexId,
187
- userIndexesCount: state.userIndexes.length,
186
+ requestedIndexId: state.networkId,
187
+ userNetworksCount: state.userNetworks.length,
188
188
  });
189
189
  try {
190
190
  let targetIndexIds;
191
- if (state.indexId) {
192
- // Validate user is member of requested index
193
- if (!state.userIndexes.includes(state.indexId)) {
194
- logger.warn('[Graph:Scope] User not member of requested index', {
195
- indexId: state.indexId,
191
+ if (state.networkId) {
192
+ // Validate user is member or owner of requested network
193
+ const isInScope = state.userNetworks.includes(state.networkId);
194
+ const isOwner = !isInScope && await this.database.isIndexOwner(state.networkId, state.userId);
195
+ if (!isInScope && !isOwner) {
196
+ logger.warn('[Graph:Scope] User not member of requested network', {
197
+ networkId: state.networkId,
196
198
  });
197
199
  return {
198
200
  targetIndexes: [],
199
- error: 'You are not a member of that index.',
201
+ error: 'You are not a member of that network.',
200
202
  };
201
203
  }
202
- targetIndexIds = [state.indexId];
204
+ targetIndexIds = [state.networkId];
203
205
  }
204
206
  else {
205
207
  // Search all user's indexes
206
- targetIndexIds = state.userIndexes;
208
+ targetIndexIds = state.userNetworks;
207
209
  }
208
210
  // Fetch index details
209
- const targetIndexes = await Promise.all(targetIndexIds.map(async (indexId) => {
210
- const index = await this.database.getIndex(indexId);
211
- const memberCount = await this.database.getIndexMemberCount(indexId);
211
+ const targetIndexes = await Promise.all(targetIndexIds.map(async (networkId) => {
212
+ const index = await this.database.getIndex(networkId);
213
+ const memberCount = await this.database.getIndexMemberCount(networkId);
212
214
  return {
213
- indexId,
215
+ networkId,
214
216
  title: index?.title ?? 'Unknown',
215
217
  memberCount,
216
218
  };
@@ -225,9 +227,9 @@ export class OpportunityGraphFactory {
225
227
  // Background path: look up persisted scores from intent_indexes
226
228
  try {
227
229
  const scores = await this.database.getIntentIndexScores(state.triggerIntentId);
228
- for (const { indexId, relevancyScore } of scores) {
230
+ for (const { networkId, relevancyScore } of scores) {
229
231
  if (relevancyScore != null) {
230
- indexRelevancyScores[indexId] = relevancyScore;
232
+ indexRelevancyScores[networkId] = relevancyScore;
231
233
  }
232
234
  }
233
235
  }
@@ -242,32 +244,35 @@ export class OpportunityGraphFactory {
242
244
  const scopeAgentTimings = [];
243
245
  const scorableIndexes = targetIndexes.filter(ti => ti.title !== 'Unknown');
244
246
  const scoringPromises = scorableIndexes.map(async (ti) => {
247
+ const ctx = await this.database.getIndexMemberContext(ti.networkId, state.userId);
248
+ if (!ctx?.indexPrompt?.trim() && !ctx?.memberPrompt?.trim()) {
249
+ return { networkId: ti.networkId, score: 1.0 };
250
+ }
251
+ const _indexerStart = Date.now();
252
+ const traceEmitter = requestContext.getStore()?.traceEmitter;
253
+ traceEmitter?.({ type: "agent_start", name: "intent-indexer" });
254
+ let result = null;
245
255
  try {
246
- const ctx = await this.database.getIndexMemberContext(ti.indexId, state.userId);
247
- if (!ctx?.indexPrompt?.trim() && !ctx?.memberPrompt?.trim()) {
248
- return { indexId: ti.indexId, score: 1.0 };
249
- }
250
- const _indexerStart = Date.now();
251
- const traceEmitter = requestContext.getStore()?.traceEmitter;
252
- traceEmitter?.({ type: "agent_start", name: "intent-indexer" });
253
- const result = await indexer.invoke(state.searchQuery, ctx?.indexPrompt ?? null, ctx?.memberPrompt ?? null);
254
- const _indexerDuration = Date.now() - _indexerStart;
255
- traceEmitter?.({ type: "agent_end", name: "intent-indexer", durationMs: _indexerDuration, summary: `Scored index ${ti.indexId}` });
256
- scopeAgentTimings.push({ name: 'intent.indexer', durationMs: _indexerDuration });
257
- if (!result)
258
- return { indexId: ti.indexId, score: 1.0 };
259
- const score = ctx?.indexPrompt && ctx?.memberPrompt
260
- ? result.indexScore * 0.6 + result.memberScore * 0.4
261
- : ctx?.indexPrompt ? result.indexScore : result.memberScore;
262
- return { indexId: ti.indexId, score };
256
+ result = await indexer.invoke(state.searchQuery, ctx?.indexPrompt ?? null, ctx?.memberPrompt ?? null);
263
257
  }
264
258
  catch {
265
- return { indexId: ti.indexId, score: 1.0 };
259
+ return { networkId: ti.networkId, score: 1.0 };
266
260
  }
261
+ finally {
262
+ const _indexerDuration = Date.now() - _indexerStart;
263
+ traceEmitter?.({ type: "agent_end", name: "intent-indexer", durationMs: _indexerDuration, summary: `Scored index ${ti.networkId}` });
264
+ scopeAgentTimings.push({ name: 'intent.indexer', durationMs: _indexerDuration });
265
+ }
266
+ if (!result)
267
+ return { networkId: ti.networkId, score: 1.0 };
268
+ const score = ctx?.indexPrompt && ctx?.memberPrompt
269
+ ? result.indexScore * 0.6 + result.memberScore * 0.4
270
+ : ctx?.indexPrompt ? result.indexScore : result.memberScore;
271
+ return { networkId: ti.networkId, score };
267
272
  });
268
273
  const results = await Promise.all(scoringPromises);
269
- for (const { indexId, score } of results) {
270
- indexRelevancyScores[indexId] = score;
274
+ for (const { networkId, score } of results) {
275
+ indexRelevancyScores[networkId] = score;
271
276
  }
272
277
  // Accumulate indexer timings into graph state
273
278
  if (scopeAgentTimings.length > 0) {
@@ -331,12 +336,12 @@ export class OpportunityGraphFactory {
331
336
  hasSearchQuery: !!state.searchQuery,
332
337
  indexedIntentsCount: state.indexedIntents.length,
333
338
  });
334
- const targetIndexIds = state.targetIndexes.map((t) => t.indexId);
339
+ const targetIndexIds = state.targetIndexes.map((t) => t.networkId);
335
340
  try {
336
341
  let resolvedIntentId;
337
342
  if (state.triggerIntentId) {
338
- const inIndex = await this.database.getIndexIdsForIntent(state.triggerIntentId);
339
- const inTarget = inIndex.some((id) => targetIndexIds.includes(id));
343
+ const inNetwork = await this.database.getNetworkIdsForIntent(state.triggerIntentId);
344
+ const inTarget = inNetwork.some((id) => targetIndexIds.includes(id));
340
345
  resolvedIntentId = state.triggerIntentId;
341
346
  const resolvedIntentInIndex = inTarget;
342
347
  const discoverySource = resolvedIntentInIndex ? 'intent' : 'profile';
@@ -351,8 +356,8 @@ export class OpportunityGraphFactory {
351
356
  const matched = state.indexedIntents.find((i) => i.payload?.toLowerCase().includes(q));
352
357
  if (matched) {
353
358
  resolvedIntentId = matched.intentId;
354
- const inIndex = await this.database.getIndexIdsForIntent(matched.intentId);
355
- const resolvedIntentInIndex = inIndex.some((id) => targetIndexIds.includes(id));
359
+ const inNetwork = await this.database.getNetworkIdsForIntent(matched.intentId);
360
+ const resolvedIntentInIndex = inNetwork.some((id) => targetIndexIds.includes(id));
356
361
  const discoverySource = resolvedIntentInIndex ? 'intent' : 'profile';
357
362
  return {
358
363
  resolvedTriggerIntentId: resolvedIntentId,
@@ -452,15 +457,15 @@ export class OpportunityGraphFactory {
452
457
  logger.verbose('[Graph:Discovery] Direct-connection mode — bypassing vector search', {
453
458
  targetUserId: state.targetUserId,
454
459
  });
455
- const targetMemberships = await this.database.getIndexMemberships(state.targetUserId);
456
- const targetUserIndexIds = targetMemberships.map(m => m.indexId);
460
+ const targetMemberships = await this.database.getNetworkMemberships(state.targetUserId);
461
+ const targetUserIndexIds = targetMemberships.map(m => m.networkId);
457
462
  const sharedIndexIds = state.targetIndexes
458
- .filter(ti => targetUserIndexIds.includes(ti.indexId))
459
- .map(ti => ti.indexId);
463
+ .filter(ti => targetUserIndexIds.includes(ti.networkId))
464
+ .map(ti => ti.networkId);
460
465
  if (sharedIndexIds.length === 0) {
461
466
  logger.warn('[Graph:Discovery] Target user shares no indexes with discoverer', {
462
467
  targetUserId: state.targetUserId,
463
- discovererIndexes: state.targetIndexes.map(ti => ti.indexId),
468
+ discovererIndexes: state.targetIndexes.map(ti => ti.networkId),
464
469
  });
465
470
  return {
466
471
  candidates: [],
@@ -477,13 +482,13 @@ export class OpportunityGraphFactory {
477
482
  if (targetIntents.length > 0) {
478
483
  // Build one candidate per intent per shared index it belongs to
479
484
  for (const intent of targetIntents) {
480
- const intentIndexIds = await this.database.getIndexIdsForIntent(intent.id);
481
- const overlapping = sharedIndexIds.filter(id => intentIndexIds.includes(id));
482
- for (const indexId of overlapping) {
485
+ const intentNetworkIds = await this.database.getNetworkIdsForIntent(intent.id);
486
+ const overlapping = sharedIndexIds.filter(id => intentNetworkIds.includes(id));
487
+ for (const networkId of overlapping) {
483
488
  directCandidates.push({
484
489
  candidateUserId: state.targetUserId,
485
490
  candidateIntentId: intent.id,
486
- indexId,
491
+ networkId,
487
492
  similarity: 1.0,
488
493
  lens: 'explicit_mention',
489
494
  candidatePayload: intent.payload,
@@ -498,7 +503,7 @@ export class OpportunityGraphFactory {
498
503
  directCandidates.push({
499
504
  candidateUserId: state.targetUserId,
500
505
  candidateIntentId: undefined,
501
- indexId: sharedIndexIds[0],
506
+ networkId: sharedIndexIds[0],
502
507
  similarity: 1.0,
503
508
  lens: 'explicit_mention',
504
509
  candidatePayload: '',
@@ -604,7 +609,7 @@ export class OpportunityGraphFactory {
604
609
  const profileCandidates = [];
605
610
  for (const targetIndex of state.targetIndexes) {
606
611
  const results = await this.embedder.searchWithProfileEmbedding(vector, {
607
- indexScope: [targetIndex.indexId],
612
+ indexScope: [targetIndex.networkId],
608
613
  excludeUserId: discoveryUserId,
609
614
  limitPerStrategy: Math.floor(limitPerStrategy / 2),
610
615
  limit: Math.floor(perIndexLimit / 2),
@@ -614,7 +619,7 @@ export class OpportunityGraphFactory {
614
619
  profileCandidates.push({
615
620
  candidateUserId: result.userId,
616
621
  candidateIntentId: result.type === 'intent' ? result.id : undefined,
617
- indexId: targetIndex.indexId,
622
+ networkId: targetIndex.networkId,
618
623
  similarity: result.score,
619
624
  lens: result.matchedVia,
620
625
  candidatePayload: '',
@@ -626,7 +631,7 @@ export class OpportunityGraphFactory {
626
631
  // Merge and dedupe - keep both intent and profile candidates per user
627
632
  const byKey = new Map();
628
633
  for (const c of [...queryCandidates, ...profileCandidates]) {
629
- const key = `${c.candidateUserId}:${c.indexId}:${c.candidateIntentId ?? 'profile'}:${c.discoverySource ?? 'unknown'}`;
634
+ const key = `${c.candidateUserId}:${c.networkId}:${c.candidateIntentId ?? 'profile'}:${c.discoverySource ?? 'unknown'}`;
630
635
  if (!byKey.has(key) || c.similarity > (byKey.get(key)?.similarity ?? 0)) {
631
636
  byKey.set(key, c);
632
637
  }
@@ -656,7 +661,7 @@ export class OpportunityGraphFactory {
656
661
  const allCandidates = [];
657
662
  for (const targetIndex of state.targetIndexes) {
658
663
  const results = await this.embedder.searchWithProfileEmbedding(vector, {
659
- indexScope: [targetIndex.indexId],
664
+ indexScope: [targetIndex.networkId],
660
665
  excludeUserId: discoveryUserId,
661
666
  limitPerStrategy,
662
667
  limit: perIndexLimit,
@@ -667,7 +672,7 @@ export class OpportunityGraphFactory {
667
672
  allCandidates.push({
668
673
  candidateUserId: result.userId,
669
674
  candidateIntentId: result.id,
670
- indexId: targetIndex.indexId,
675
+ networkId: targetIndex.networkId,
671
676
  similarity: result.score,
672
677
  lens: result.matchedVia,
673
678
  candidatePayload: '',
@@ -678,7 +683,7 @@ export class OpportunityGraphFactory {
678
683
  else {
679
684
  allCandidates.push({
680
685
  candidateUserId: result.userId,
681
- indexId: targetIndex.indexId,
686
+ networkId: targetIndex.networkId,
682
687
  similarity: result.score,
683
688
  lens: result.matchedVia,
684
689
  candidatePayload: '',
@@ -690,7 +695,7 @@ export class OpportunityGraphFactory {
690
695
  }
691
696
  const byUserAndIndex = new Map();
692
697
  for (const c of allCandidates) {
693
- const key = `${c.candidateUserId}:${c.indexId}:${c.candidateIntentId ?? 'profile'}`;
698
+ const key = `${c.candidateUserId}:${c.networkId}:${c.candidateIntentId ?? 'profile'}`;
694
699
  if (!byUserAndIndex.has(key) || c.similarity > (byUserAndIndex.get(key)?.similarity ?? 0)) {
695
700
  byUserAndIndex.set(key, c);
696
701
  }
@@ -793,7 +798,7 @@ export class OpportunityGraphFactory {
793
798
  const all = [];
794
799
  await Promise.all(state.targetIndexes.map(async (targetIndex) => {
795
800
  const results = await self.embedder.searchWithHydeEmbeddings(lensEmbeddings, {
796
- indexScope: [targetIndex.indexId],
801
+ indexScope: [targetIndex.networkId],
797
802
  excludeUserId: discoveryUserId,
798
803
  limitPerStrategy,
799
804
  limit: perIndexLimit,
@@ -803,7 +808,7 @@ export class OpportunityGraphFactory {
803
808
  all.push({
804
809
  candidateUserId: r.userId,
805
810
  candidateIntentId: r.id,
806
- indexId: targetIndex.indexId,
811
+ networkId: targetIndex.networkId,
807
812
  similarity: r.score,
808
813
  lens: r.matchedVia,
809
814
  candidatePayload: '',
@@ -814,7 +819,7 @@ export class OpportunityGraphFactory {
814
819
  for (const r of results.filter((x) => x.type === 'profile')) {
815
820
  all.push({
816
821
  candidateUserId: r.userId,
817
- indexId: targetIndex.indexId,
822
+ networkId: targetIndex.networkId,
818
823
  similarity: r.score,
819
824
  lens: r.matchedVia,
820
825
  candidatePayload: '',
@@ -876,7 +881,7 @@ export class OpportunityGraphFactory {
876
881
  const allCandidates = [];
877
882
  await Promise.all(state.targetIndexes.map(async (targetIndex) => {
878
883
  const results = await this.embedder.searchWithHydeEmbeddings(lensEmbeddings, {
879
- indexScope: [targetIndex.indexId],
884
+ indexScope: [targetIndex.networkId],
880
885
  excludeUserId: discoveryUserId,
881
886
  limitPerStrategy,
882
887
  limit: perIndexLimit,
@@ -886,7 +891,7 @@ export class OpportunityGraphFactory {
886
891
  allCandidates.push({
887
892
  candidateUserId: result.userId,
888
893
  candidateIntentId: result.id,
889
- indexId: targetIndex.indexId,
894
+ networkId: targetIndex.networkId,
890
895
  similarity: result.score,
891
896
  lens: result.matchedVia,
892
897
  candidatePayload: '',
@@ -897,7 +902,7 @@ export class OpportunityGraphFactory {
897
902
  for (const result of results.filter((r) => r.type === 'profile')) {
898
903
  allCandidates.push({
899
904
  candidateUserId: result.userId,
900
- indexId: targetIndex.indexId,
905
+ networkId: targetIndex.networkId,
901
906
  similarity: result.score,
902
907
  lens: result.matchedVia,
903
908
  candidatePayload: '',
@@ -908,7 +913,7 @@ export class OpportunityGraphFactory {
908
913
  }));
909
914
  const byUserAndIndex = new Map();
910
915
  for (const c of allCandidates) {
911
- const key = `${c.candidateUserId}:${c.indexId}:${c.candidateIntentId ?? 'profile'}`;
916
+ const key = `${c.candidateUserId}:${c.networkId}:${c.candidateIntentId ?? 'profile'}`;
912
917
  if (!byUserAndIndex.has(key) || c.similarity > (byUserAndIndex.get(key)?.similarity ?? 0)) {
913
918
  byUserAndIndex.set(key, c);
914
919
  }
@@ -1017,7 +1022,7 @@ export class OpportunityGraphFactory {
1017
1022
  });
1018
1023
  /**
1019
1024
  * Node 3: Evaluation (Entity bundle)
1020
- * Builds entity bundle from source + candidates, invokes entity-bundle evaluator, maps to EvaluatedOpportunity with indexId from entities.
1025
+ * Builds entity bundle from source + candidates, invokes entity-bundle evaluator, maps to EvaluatedOpportunity with networkId from entities.
1021
1026
  */
1022
1027
  const evaluationNode = async (state) => {
1023
1028
  return timed("OpportunityGraph.evaluation", async () => {
@@ -1045,8 +1050,8 @@ export class OpportunityGraphFactory {
1045
1050
  }
1046
1051
  else if (c.similarity === existing.similarity) {
1047
1052
  // Tie-break: prefer index with higher relevancy score
1048
- const cScore = state.indexRelevancyScores[c.indexId] ?? 0;
1049
- const existingScore = state.indexRelevancyScores[existing.indexId] ?? 0;
1053
+ const cScore = state.indexRelevancyScores[c.networkId] ?? 0;
1054
+ const existingScore = state.indexRelevancyScores[existing.networkId] ?? 0;
1050
1055
  if (cScore > existingScore) {
1051
1056
  bestByUser.set(c.candidateUserId, c);
1052
1057
  }
@@ -1100,7 +1105,7 @@ export class OpportunityGraphFactory {
1100
1105
  payload: i.payload,
1101
1106
  summary: i.summary,
1102
1107
  })),
1103
- indexId: '', // Placeholder — overwritten per-pairing below
1108
+ networkId: '', // Placeholder — overwritten per-pairing below
1104
1109
  ragScore: undefined,
1105
1110
  matchedVia: undefined,
1106
1111
  };
@@ -1128,7 +1133,7 @@ export class OpportunityGraphFactory {
1128
1133
  intents: c.candidateIntentId != null
1129
1134
  ? [{ intentId: c.candidateIntentId, payload: intentPayload ?? '', summary: intentSummary }]
1130
1135
  : undefined,
1131
- indexId: c.indexId,
1136
+ networkId: c.networkId,
1132
1137
  ragScore: c.similarity * 100,
1133
1138
  matchedVia: c.lens,
1134
1139
  };
@@ -1136,7 +1141,7 @@ export class OpportunityGraphFactory {
1136
1141
  const userIdToIndexId = new Map();
1137
1142
  for (const e of candidateEntities) {
1138
1143
  if (!userIdToIndexId.has(e.userId))
1139
- userIdToIndexId.set(e.userId, e.indexId);
1144
+ userIdToIndexId.set(e.userId, e.networkId);
1140
1145
  }
1141
1146
  // Lower default threshold to 50 for better recall
1142
1147
  const minScore = state.options.minScore ?? 50;
@@ -1297,23 +1302,23 @@ export class OpportunityGraphFactory {
1297
1302
  actors: op.actors.map((a) => {
1298
1303
  const isSource = a.userId === discoveryUserId;
1299
1304
  if (isSource) {
1300
- // Source actor inherits the counterpart's indexId (shared match context)
1305
+ // Source actor inherits the counterpart's networkId (shared match context)
1301
1306
  const counterpart = op.actors.find((other) => other.userId !== a.userId);
1302
1307
  const counterpartIndexId = counterpart
1303
- ? userIdToIndexId.get(counterpart.userId) ?? candidateEntities.find((e) => e.userId === counterpart.userId)?.indexId
1308
+ ? userIdToIndexId.get(counterpart.userId) ?? candidateEntities.find((e) => e.userId === counterpart.userId)?.networkId
1304
1309
  : undefined;
1305
1310
  return {
1306
1311
  userId: a.userId,
1307
1312
  role: a.role,
1308
1313
  intentId: a.intentId,
1309
- indexId: counterpartIndexId ?? userIdToIndexId.get(a.userId) ?? '',
1314
+ networkId: counterpartIndexId ?? userIdToIndexId.get(a.userId) ?? '',
1310
1315
  };
1311
1316
  }
1312
1317
  return {
1313
1318
  userId: a.userId,
1314
1319
  role: a.role,
1315
1320
  intentId: a.intentId,
1316
- indexId: userIdToIndexId.get(a.userId) ?? candidateEntities.find((e) => e.userId === a.userId)?.indexId,
1321
+ networkId: userIdToIndexId.get(a.userId) ?? candidateEntities.find((e) => e.userId === a.userId)?.networkId,
1317
1322
  };
1318
1323
  }),
1319
1324
  }));
@@ -1457,7 +1462,7 @@ export class OpportunityGraphFactory {
1457
1462
  },
1458
1463
  };
1459
1464
  // Build candidates with enriched context from database.
1460
- // Each actor carries its own indexId — use it for per-candidate index context.
1465
+ // Each actor carries its own networkId — use it for per-candidate index context.
1461
1466
  const candidateEntries = state.evaluatedOpportunities
1462
1467
  .map(opp => {
1463
1468
  const candidateActor = opp.actors.find(a => a.userId !== discoveryUserId);
@@ -1497,7 +1502,7 @@ export class OpportunityGraphFactory {
1497
1502
  score: opp.score,
1498
1503
  reasoning: opp.reasoning,
1499
1504
  valencyRole: candidateActor.role ?? 'peer',
1500
- indexId: candidateActor.indexId,
1505
+ networkId: candidateActor.networkId,
1501
1506
  candidateUser: {
1502
1507
  id: userId,
1503
1508
  intents: candidateIntents,
@@ -1513,19 +1518,19 @@ export class OpportunityGraphFactory {
1513
1518
  }));
1514
1519
  const isChatPath = !!state.options?.conversationId;
1515
1520
  const maxTurns = isChatPath ? 4 : 6;
1516
- // Fetch per-candidate index context (group by indexId to avoid duplicate lookups)
1517
- const uniqueIndexIds = [...new Set(candidates.map(c => c.indexId).filter((id) => !!id))];
1521
+ // Fetch per-candidate index context (group by networkId to avoid duplicate lookups)
1522
+ const uniqueIndexIds = [...new Set(candidates.map(c => c.networkId).filter((id) => !!id))];
1518
1523
  const indexContextMap = new Map();
1519
- await Promise.all(uniqueIndexIds.map(async (indexId) => {
1520
- const ctx = await this.database.getIndexMemberContext(indexId, discoveryUserId).catch(() => null);
1524
+ await Promise.all(uniqueIndexIds.map(async (networkId) => {
1525
+ const ctx = await this.database.getIndexMemberContext(networkId, discoveryUserId).catch(() => null);
1521
1526
  const prompt = [ctx?.indexPrompt, ctx?.memberPrompt]
1522
1527
  .filter((v) => !!v?.trim())
1523
1528
  .join('\n\n');
1524
1529
  if (prompt)
1525
- indexContextMap.set(indexId, prompt);
1530
+ indexContextMap.set(networkId, prompt);
1526
1531
  }));
1527
1532
  // Run negotiations per candidate with their actual index context
1528
- const acceptedResults = await negotiateCandidates(this.negotiationGraph, sourceUser, candidates, { indexId: '', prompt: '' }, // base context, overridden per-candidate below
1533
+ const acceptedResults = await negotiateCandidates(this.negotiationGraph, sourceUser, candidates, { networkId: '', prompt: '' }, // base context, overridden per-candidate below
1529
1534
  { maxTurns, traceEmitter: traceEmitter ?? undefined,
1530
1535
  indexContextOverrides: indexContextMap });
1531
1536
  // Filter opportunities to only those with an opportunity outcome, update scores
@@ -1563,7 +1568,7 @@ export class OpportunityGraphFactory {
1563
1568
  const limit = state.options.limit ?? 20;
1564
1569
  const ranked = sorted.slice(0, limit);
1565
1570
  const actorSetKey = (opp) => opp.actors
1566
- .map((a) => `${a.userId}:${a.indexId}`)
1571
+ .map((a) => `${a.userId}:${a.networkId}`)
1567
1572
  .sort()
1568
1573
  .join('|');
1569
1574
  const seen = new Set();
@@ -1610,37 +1615,46 @@ export class OpportunityGraphFactory {
1610
1615
  return timed("OpportunityGraph.introValidation", async () => {
1611
1616
  logger.verbose('[Graph:IntroValidation] Starting', {
1612
1617
  userId: state.userId,
1613
- indexId: state.indexId,
1618
+ networkId: state.networkId,
1614
1619
  entitiesCount: state.introductionEntities?.length ?? 0,
1615
1620
  });
1616
1621
  try {
1617
1622
  const entities = state.introductionEntities ?? [];
1618
- const primaryIndexId = (state.indexId ?? entities[0]?.indexId);
1623
+ const primaryNetworkId = (state.networkId ?? entities[0]?.networkId);
1619
1624
  const partyUserIds = [...new Set(entities.map((e) => e.userId).filter((id) => id !== state.userId))];
1620
- if (!primaryIndexId || partyUserIds.length < 1) {
1625
+ if (!primaryNetworkId || partyUserIds.length < 1) {
1621
1626
  return {
1622
- error: 'Introduction requires indexId and at least two entities (introducer + one counterpart).',
1627
+ error: 'Introduction requires networkId and at least two entities (introducer + one counterpart).',
1623
1628
  };
1624
1629
  }
1625
- if (state.requiredIndexId && primaryIndexId !== state.requiredIndexId) {
1630
+ if (state.requiredNetworkId && primaryNetworkId !== state.requiredNetworkId) {
1626
1631
  return {
1627
1632
  error: 'This chat is scoped to a different community. You can only introduce members of the current community.',
1628
1633
  };
1629
1634
  }
1630
- const introducerIsMember = await this.database.isIndexMember(primaryIndexId, state.userId);
1631
- if (!introducerIsMember) {
1635
+ const [introducerIsMember, introducerIsOwner] = await Promise.all([
1636
+ this.database.isNetworkMember(primaryNetworkId, state.userId),
1637
+ this.database.isIndexOwner(primaryNetworkId, state.userId),
1638
+ ]);
1639
+ if (!introducerIsMember && !introducerIsOwner) {
1632
1640
  return {
1633
- error: 'One or more users are not members of the specified community. You can only introduce members who share an index.',
1641
+ error: 'One or more users are not members of the specified community. You can only introduce members who share a network.',
1634
1642
  };
1635
1643
  }
1636
- const partyMemberships = await Promise.all(partyUserIds.map((userId) => this.database.isIndexMember(primaryIndexId, userId)));
1637
- const allPartyMembers = partyMemberships.every(Boolean);
1644
+ const partyInScope = await Promise.all(partyUserIds.map(async (userId) => {
1645
+ const [isMember, isOwner] = await Promise.all([
1646
+ this.database.isNetworkMember(primaryNetworkId, userId),
1647
+ this.database.isIndexOwner(primaryNetworkId, userId),
1648
+ ]);
1649
+ return isMember || isOwner;
1650
+ }));
1651
+ const allPartyMembers = partyInScope.every(Boolean);
1638
1652
  if (!allPartyMembers) {
1639
1653
  return {
1640
- error: 'One or more users are not members of the specified community. You can only introduce members who share an index.',
1654
+ error: 'One or more users are not members of the specified community. You can only introduce members who share a network.',
1641
1655
  };
1642
1656
  }
1643
- const exists = await this.database.opportunityExistsBetweenActors(partyUserIds, primaryIndexId);
1657
+ const exists = await this.database.opportunityExistsBetweenActors(partyUserIds, primaryNetworkId);
1644
1658
  if (exists) {
1645
1659
  return { error: 'An opportunity already exists between these people.' };
1646
1660
  }
@@ -1651,7 +1665,7 @@ export class OpportunityGraphFactory {
1651
1665
  const errMsg = err instanceof Error ? err.message : String(err);
1652
1666
  logger.error('[Graph:IntroValidation] Failed', {
1653
1667
  userId: state.userId,
1654
- indexId: state.indexId,
1668
+ networkId: state.networkId,
1655
1669
  error: err,
1656
1670
  });
1657
1671
  return {
@@ -1668,7 +1682,7 @@ export class OpportunityGraphFactory {
1668
1682
  /**
1669
1683
  * Build fallback reasoning and actors when evaluator returns empty or throws.
1670
1684
  */
1671
- function buildIntroFallback(entities, state, primaryIndexId, introducerName) {
1685
+ function buildIntroFallback(entities, state, primaryNetworkId, introducerName) {
1672
1686
  const reasoning = `${introducerName ?? 'A member'} believes these people should connect.` +
1673
1687
  (state.introductionHint ? ` Context: ${state.introductionHint}` : '');
1674
1688
  const score = 70;
@@ -1676,7 +1690,7 @@ export class OpportunityGraphFactory {
1676
1690
  const actors = partyUserIds.map((uid) => ({
1677
1691
  userId: uid,
1678
1692
  role: 'peer',
1679
- indexId: primaryIndexId,
1693
+ networkId: primaryNetworkId,
1680
1694
  }));
1681
1695
  return { reasoning, score, actors };
1682
1696
  }
@@ -1691,9 +1705,9 @@ export class OpportunityGraphFactory {
1691
1705
  return { evaluatedOpportunities: [], agentTimings: [] };
1692
1706
  }
1693
1707
  const entities = state.introductionEntities ?? [];
1694
- const primaryIndexId = (state.indexId ?? entities[0]?.indexId);
1695
- if (!primaryIndexId || entities.length < 2) {
1696
- return { evaluatedOpportunities: [], error: 'Missing entities or index for introduction.', agentTimings: [] };
1708
+ const primaryNetworkId = (state.networkId ?? entities[0]?.networkId);
1709
+ if (!primaryNetworkId || entities.length < 2) {
1710
+ return { evaluatedOpportunities: [], error: 'Missing entities or network for introduction.', agentTimings: [] };
1697
1711
  }
1698
1712
  const agentTimingsAccum = [];
1699
1713
  let introducerName;
@@ -1728,11 +1742,11 @@ export class OpportunityGraphFactory {
1728
1742
  userId: a.userId,
1729
1743
  role: a.role,
1730
1744
  intentId: a.intentId ?? undefined,
1731
- indexId: primaryIndexId,
1745
+ networkId: primaryNetworkId,
1732
1746
  }));
1733
1747
  }
1734
1748
  else {
1735
- const fallback = buildIntroFallback(entities, state, primaryIndexId, introducerName);
1749
+ const fallback = buildIntroFallback(entities, state, primaryNetworkId, introducerName);
1736
1750
  reasoning = fallback.reasoning;
1737
1751
  score = fallback.score;
1738
1752
  actors = fallback.actors;
@@ -1747,7 +1761,7 @@ export class OpportunityGraphFactory {
1747
1761
  agentTimingsAccum.push({ name: 'opportunity.evaluator', durationMs: _introErrDuration });
1748
1762
  }
1749
1763
  logger.warn('[Graph:IntroEvaluation] Evaluator or getUser failed, using fallback', { error: evalErr });
1750
- const fallback = buildIntroFallback(entities, state, primaryIndexId, introducerName);
1764
+ const fallback = buildIntroFallback(entities, state, primaryNetworkId, introducerName);
1751
1765
  reasoning = fallback.reasoning;
1752
1766
  score = fallback.score;
1753
1767
  actors = fallback.actors;
@@ -1778,7 +1792,7 @@ export class OpportunityGraphFactory {
1778
1792
  };
1779
1793
  /**
1780
1794
  * Node 5: Persist
1781
- * Creates opportunities from evaluator-proposed actors (indexId, userId, role, optional intent).
1795
+ * Creates opportunities from evaluator-proposed actors (networkId, userId, role, optional intent).
1782
1796
  */
1783
1797
  const persistNode = withNodeTrace("opportunity-persist", async (state) => {
1784
1798
  return timed("OpportunityGraph.persist", async () => {
@@ -1806,18 +1820,18 @@ export class OpportunityGraphFactory {
1806
1820
  ? await this.database.getUser(state.userId)
1807
1821
  : null;
1808
1822
  for (const evaluated of state.evaluatedOpportunities) {
1809
- const indexIdForActors = state.indexId ?? evaluated.actors[0]?.indexId;
1823
+ const indexIdForActors = state.networkId ?? evaluated.actors[0]?.networkId;
1810
1824
  let actors;
1811
1825
  let data;
1812
1826
  logger.verbose('[Graph:Persist:PathSelect]', {
1813
1827
  isIntroduction: !!state.introductionContext,
1814
1828
  stateUserId: state.userId,
1815
- stateIndexId: state.indexId,
1829
+ stateIndexId: state.networkId,
1816
1830
  evaluatedActorUserIds: evaluated.actors.map(a => a.userId),
1817
1831
  });
1818
1832
  if (state.introductionContext) {
1819
1833
  if (indexIdForActors === undefined) {
1820
- logger.warn('[Graph:Persist] Introduction path missing indexId; skipping opportunity', {
1834
+ logger.warn('[Graph:Persist] Introduction path missing networkId; skipping opportunity', {
1821
1835
  userId: state.userId,
1822
1836
  actorsCount: evaluated.actors.length,
1823
1837
  });
@@ -1825,7 +1839,7 @@ export class OpportunityGraphFactory {
1825
1839
  }
1826
1840
  // Introduction path: manual detection, introducer actor, curator_judgment signal.
1827
1841
  const evaluatorActors = evaluated.actors.map((a) => ({
1828
- indexId: a.indexId ?? indexIdForActors,
1842
+ networkId: a.networkId ?? indexIdForActors,
1829
1843
  userId: a.userId,
1830
1844
  role: a.role,
1831
1845
  ...(a.intentId ? { intent: a.intentId } : {}),
@@ -1835,7 +1849,7 @@ export class OpportunityGraphFactory {
1835
1849
  ? evaluatorActors
1836
1850
  : [
1837
1851
  ...evaluatorActors,
1838
- { indexId: indexIdForActors, userId: state.userId, role: 'introducer' },
1852
+ { networkId: indexIdForActors, userId: state.userId, role: 'introducer' },
1839
1853
  ];
1840
1854
  data = {
1841
1855
  detection: {
@@ -1858,7 +1872,7 @@ export class OpportunityGraphFactory {
1858
1872
  ],
1859
1873
  },
1860
1874
  context: {
1861
- indexId: state.indexId ?? indexIdForActors,
1875
+ networkId: state.networkId ?? indexIdForActors,
1862
1876
  ...(state.options.conversationId ? { conversationId: state.options.conversationId } : {}),
1863
1877
  },
1864
1878
  confidence: String(evaluated.score / 100),
@@ -1867,7 +1881,7 @@ export class OpportunityGraphFactory {
1867
1881
  }
1868
1882
  else if (state.onBehalfOfUserId) {
1869
1883
  if (indexIdForActors === undefined) {
1870
- logger.warn('[Graph:Persist] Introducer discovery path missing indexId; skipping opportunity', {
1884
+ logger.warn('[Graph:Persist] Introducer discovery path missing networkId; skipping opportunity', {
1871
1885
  userId: state.userId,
1872
1886
  actorsCount: evaluated.actors.length,
1873
1887
  });
@@ -1875,7 +1889,7 @@ export class OpportunityGraphFactory {
1875
1889
  }
1876
1890
  // Introducer discovery path: manual detection, introducer is state.userId, target is onBehalfOfUserId.
1877
1891
  const evaluatorActors = evaluated.actors.map((a) => ({
1878
- indexId: a.indexId ?? indexIdForActors,
1892
+ networkId: a.networkId ?? indexIdForActors,
1879
1893
  userId: a.userId,
1880
1894
  role: a.role,
1881
1895
  ...(a.intentId ? { intent: a.intentId } : {}),
@@ -1885,7 +1899,7 @@ export class OpportunityGraphFactory {
1885
1899
  ? evaluatorActors
1886
1900
  : [
1887
1901
  ...evaluatorActors,
1888
- { indexId: indexIdForActors, userId: state.userId, role: 'introducer' },
1902
+ { networkId: indexIdForActors, userId: state.userId, role: 'introducer' },
1889
1903
  ];
1890
1904
  const candidateUserId = evaluated.actors.find((a) => a.userId !== state.onBehalfOfUserId)?.userId;
1891
1905
  const overlapping = candidateUserId
@@ -1915,7 +1929,7 @@ export class OpportunityGraphFactory {
1915
1929
  if (existing.status !== 'expired' && candidateUserId) {
1916
1930
  existingBetweenActors.push({
1917
1931
  candidateUserId: candidateUserId,
1918
- indexId: (state.indexId ?? indexIdForActors ?? ''),
1932
+ networkId: (state.networkId ?? indexIdForActors ?? ''),
1919
1933
  existingOpportunityId: existing.id,
1920
1934
  existingStatus: existing.status,
1921
1935
  });
@@ -1941,7 +1955,7 @@ export class OpportunityGraphFactory {
1941
1955
  }],
1942
1956
  },
1943
1957
  context: {
1944
- indexId: state.indexId ?? indexIdForActors,
1958
+ networkId: state.networkId ?? indexIdForActors,
1945
1959
  ...(state.options.conversationId ? { conversationId: state.options.conversationId } : {}),
1946
1960
  },
1947
1961
  confidence: String(evaluated.score / 100),
@@ -1951,7 +1965,7 @@ export class OpportunityGraphFactory {
1951
1965
  else {
1952
1966
  // Discovery path: opportunity_graph source, no introducer, lifecycle guard for agent/patient.
1953
1967
  const evaluatorActors = evaluated.actors.map((a) => ({
1954
- indexId: a.indexId ?? indexIdForActors,
1968
+ networkId: a.networkId ?? indexIdForActors,
1955
1969
  userId: a.userId,
1956
1970
  role: a.role,
1957
1971
  ...(a.intentId ? { intent: a.intentId } : {}),
@@ -1972,7 +1986,7 @@ export class OpportunityGraphFactory {
1972
1986
  }
1973
1987
  }
1974
1988
  // Index-agnostic dedup: find ANY existing opportunity between these users,
1975
- // regardless of which index it was created in or whether context.indexId is set.
1989
+ // regardless of which index it was created in or whether context.networkId is set.
1976
1990
  const candidateUserId = evaluated.actors.find((a) => a.userId !== state.userId)?.userId;
1977
1991
  logger.verbose('[Graph:Persist:Dedup] Checking overlapping opportunities', {
1978
1992
  stateUserId: state.userId,
@@ -1988,7 +2002,7 @@ export class OpportunityGraphFactory {
1988
2002
  });
1989
2003
  if (overlapping.length > 0) {
1990
2004
  const existing = overlapping[0];
1991
- const existingIndexId = (existing.context?.indexId ?? state.indexId ?? state.userIndexes?.[0] ?? '');
2005
+ const existingIndexId = (existing.context?.networkId ?? state.networkId ?? state.userNetworks?.[0] ?? '');
1992
2006
  if (existing.status === 'expired') {
1993
2007
  const reactivated = await this.database.updateOpportunityStatus(existing.id, initialStatus);
1994
2008
  if (reactivated) {
@@ -2016,7 +2030,7 @@ export class OpportunityGraphFactory {
2016
2030
  else if (candidateUserId) {
2017
2031
  existingBetweenActors.push({
2018
2032
  candidateUserId: candidateUserId,
2019
- indexId: existingIndexId,
2033
+ networkId: existingIndexId,
2020
2034
  existingOpportunityId: existing.id,
2021
2035
  existingStatus: existing.status,
2022
2036
  });
@@ -2051,7 +2065,7 @@ export class OpportunityGraphFactory {
2051
2065
  ],
2052
2066
  },
2053
2067
  context: {
2054
- ...(state.indexId ? { indexId: state.indexId } : {}),
2068
+ ...(state.networkId ? { networkId: state.networkId } : {}),
2055
2069
  ...(state.options.conversationId ? { conversationId: state.options.conversationId } : {}),
2056
2070
  },
2057
2071
  confidence: String(evaluated.score / 100),
@@ -2124,29 +2138,32 @@ export class OpportunityGraphFactory {
2124
2138
  // CRUD NODES (read, update, delete, send)
2125
2139
  // ═══════════════════════════════════════════════════════════════
2126
2140
  /**
2127
- * Read Node: List opportunities for the user, optionally filtered by indexId.
2141
+ * Read Node: List opportunities for the user, optionally filtered by networkId.
2128
2142
  * Fast path — no LLM calls.
2129
2143
  */
2130
2144
  const readNode = async (state) => {
2131
2145
  return timed("OpportunityGraph.read", async () => {
2132
2146
  logger.verbose('[Graph:Read] Listing opportunities', {
2133
2147
  userId: state.userId,
2134
- indexId: state.indexId,
2148
+ networkId: state.networkId,
2135
2149
  });
2136
2150
  try {
2137
2151
  let indexIdFilter;
2138
- if (state.indexId) {
2139
- const isMember = await this.database.isIndexMember(state.indexId, state.userId);
2140
- if (!isMember) {
2152
+ if (state.networkId) {
2153
+ const [isMember, isOwner] = await Promise.all([
2154
+ this.database.isNetworkMember(state.networkId, state.userId),
2155
+ this.database.isIndexOwner(state.networkId, state.userId),
2156
+ ]);
2157
+ if (!isMember && !isOwner) {
2141
2158
  return {
2142
- readResult: { count: 0, opportunities: [], message: 'Index not found or you are not a member.' },
2159
+ readResult: { count: 0, opportunities: [], message: 'Network not found or you are not a member.' },
2143
2160
  };
2144
2161
  }
2145
- indexIdFilter = state.indexId;
2162
+ indexIdFilter = state.networkId;
2146
2163
  }
2147
2164
  const rawList = await this.database.getOpportunitiesForUser(state.userId, {
2148
2165
  limit: 30,
2149
- ...(indexIdFilter ? { indexId: indexIdFilter } : {}),
2166
+ ...(indexIdFilter ? { networkId: indexIdFilter } : {}),
2150
2167
  });
2151
2168
  const list = rawList.filter((opp) => opp.status !== 'expired');
2152
2169
  if (list.length === 0) {
@@ -2193,10 +2210,10 @@ export class OpportunityGraphFactory {
2193
2210
  const introducer = opp.actors.find((a) => a.role === 'introducer');
2194
2211
  const partyIds = otherParties.map((a) => a.userId);
2195
2212
  const idsToResolve = introducer ? [...partyIds, introducer.userId] : partyIds;
2196
- // Use the counterpart's (non-viewer) indexId — it reflects where the match was found.
2213
+ // Use the counterpart's (non-viewer) networkId — it reflects where the match was found.
2197
2214
  // actors[0] is typically the viewer with an arbitrary first-target-index value.
2198
2215
  const counterpartActor = opp.actors.find((a) => a.userId !== state.userId);
2199
- const actorIndexId = counterpartActor?.indexId ?? opp.actors[0]?.indexId;
2216
+ const actorIndexId = counterpartActor?.networkId ?? opp.actors[0]?.networkId;
2200
2217
  const [indexRecord, ...profileAndUserPairs] = await Promise.all([
2201
2218
  actorIndexId ? this.database.getIndex(actorIndexId) : Promise.resolve(null),
2202
2219
  ...idsToResolve.map(async (uid) => {