@indexnetwork/protocol 18.0.0-rc.481.1 → 19.0.0-rc.482.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.
package/CHANGELOG.md CHANGED
@@ -20,6 +20,30 @@ went 6.7.1 → 8.0.2 with no 7.x in between because the whole 7.x line shipped a
20
20
  prereleases between the two promotions. To track every change, read `rc`; to
21
21
  pin a supported release, use `latest`.
22
22
 
23
+ ## 19.0.0 - 2026-08-17
24
+
25
+ ### Removed
26
+
27
+ - **BREAKING**: Retire ghost users. `softDeleteGhost`, `mergeGhostUser` and
28
+ `findDuplicateUser` are gone from the database port, along with `isGhost` on
29
+ every entity, contact payload, member row and opportunity card.
30
+ - **BREAKING**: Remove `shouldEnrichGhostDisplayNameFromParallel` and
31
+ `isEnrichedNameMeaningful` (and the `enrichment.enricher` module). Both were
32
+ ghost-only: the first returned `false` for every real account.
33
+ - **BREAKING**: `buildMinimalOpportunityCard` loses its trailing
34
+ `isCounterpartGhost` parameter and the `isGhost` field on its result. The
35
+ primary action label was never derived from it — it reads `viewerRole` alone —
36
+ so labels are unchanged.
37
+
38
+ ### Changed
39
+
40
+ - The enrichment graph no longer soft-deletes or merges on the ghost paths. A
41
+ non-human enrichment result still aborts with `"Non-human entity detected"`;
42
+ it previously also called `softDeleteGhost`, which the host implemented as a
43
+ no-op for any non-ghost, so behaviour for real accounts is identical.
44
+ - Low-confidence and failed enrichment now always fall through to basic info.
45
+ That was already the real-account path; only the ghost early-return is gone.
46
+
23
47
  ## 18.0.0 - 2026-08-17
24
48
 
25
49
  ### Changed
@@ -43,7 +43,6 @@ export function createContactTools(defineTool, deps) {
43
43
  name: c.user.name,
44
44
  email: c.user.email,
45
45
  avatar: c.user.avatar,
46
- isGhost: c.user.isGhost,
47
46
  })),
48
47
  });
49
48
  }
@@ -81,7 +80,7 @@ export function createContactTools(defineTool, deps) {
81
80
  "Use when the user refers to a contact by partial name or email and you need their userId for another tool " +
82
81
  "(e.g. read_user_contexts).\n\n" +
83
82
  "**When to use:** Before list_contacts when the network is large — returns only matching contacts, bounded by limit.\n\n" +
84
- "**Returns:** Array of matching contacts: userId, name, email, avatar, isGhost.",
83
+ "**Returns:** Array of matching contacts: userId, name, email, avatar.",
85
84
  querySchema: z.object({
86
85
  query: z.string().trim().min(1).describe('Free-text query matched against contact name and email (case-insensitive, substring).'),
87
86
  limit: z.number().int().positive().max(100).optional().describe('Maximum rows to return. Defaults to 25.'),
@@ -96,7 +95,6 @@ export function createContactTools(defineTool, deps) {
96
95
  name: r.name,
97
96
  email: r.email,
98
97
  avatar: r.avatar,
99
- isGhost: r.isGhost,
100
98
  })),
101
99
  });
102
100
  }
@@ -13,7 +13,6 @@ export interface ContactEntry {
13
13
  name: string;
14
14
  email: string;
15
15
  avatar: string | null;
16
- isGhost: boolean;
17
16
  };
18
17
  }
19
18
  /** Flat contact row returned by searchContacts. */
@@ -22,5 +21,4 @@ export interface ContactSearchResult {
22
21
  name: string;
23
22
  email: string;
24
23
  avatar: string | null;
25
- isGhost: boolean;
26
24
  }
@@ -59,7 +59,6 @@ export { PremiseAnalyzer, type PremiseAnalyzerOutput, } from "../../premises/pre
59
59
  export { PremiseIndexer, type PremiseIndexerOutput, } from "../../premises/premise.indexer.js";
60
60
  export { EnrichmentGraphFactory, type CompiledPremiseGraph, } from "../../enrichment/enrichment.graph.js";
61
61
  export { EnrichmentGenerator, type GeneratedProfile } from "../../enrichment/enrichment.generator.js";
62
- export { shouldEnrichGhostDisplayNameFromParallel, isEnrichedNameMeaningful, } from "../../enrichment/enrichment.enricher.js";
63
62
  export { createEnrichmentTools } from "../../enrichment/enrichment.tools.js";
64
63
  export { createPremiseTools } from "../../premises/premise.tools.js";
65
64
  export { UserContextGenerator } from "../../contexts/context.generator.js";
@@ -62,7 +62,6 @@ export { PremiseIndexer, } from "../../premises/premise.indexer.js";
62
62
  // ── Enrichment graph + service classes ───────────────────────────────────────
63
63
  export { EnrichmentGraphFactory, } from "../../enrichment/enrichment.graph.js";
64
64
  export { EnrichmentGenerator } from "../../enrichment/enrichment.generator.js";
65
- export { shouldEnrichGhostDisplayNameFromParallel, isEnrichedNameMeaningful, } from "../../enrichment/enrichment.enricher.js";
66
65
  // ── Tool factories (foreground adapters) ──────────────────────────────────────
67
66
  // These are foreground adapter entry points consumed by the tool composition root (shared/agent)
68
67
  // composition. They accept compiled graphs and host deps through the tool-dep
@@ -1,6 +1,5 @@
1
1
  import { StateGraph, START, END } from "@langchain/langgraph";
2
2
  import { EnrichmentGraphState } from "./enrichment.state.js";
3
- import { shouldEnrichGhostDisplayNameFromParallel, isEnrichedNameMeaningful } from "./enrichment.enricher.js";
4
3
  import { socialsToEnrichmentRequest } from "../shared/utils/social-label.js";
5
4
  import { protocolLogger } from "../shared/observability/protocol.logger.js";
6
5
  import { timed } from "../shared/observability/performance.js";
@@ -376,8 +375,7 @@ export async function autoGenerateNode(state, deps) {
376
375
  try {
377
376
  const enrichment = await deps.enricher.enrichUserProfile(request);
378
377
  if (enrichment && !enrichment.isHuman) {
379
- logger.info("Enrichment detected non-human entity, soft-deleting ghost", { userId: state.userId });
380
- await deps.database.softDeleteGhost(state.userId);
378
+ logger.info("Enrichment detected non-human entity, aborting", { userId: state.userId });
381
379
  return { error: "Non-human entity detected" };
382
380
  }
383
381
  const hasMeaningfulEnrichment = !!enrichment &&
@@ -387,11 +385,6 @@ export async function autoGenerateNode(state, deps) {
387
385
  enrichment.attributes.skills.length > 0 ||
388
386
  enrichment.attributes.interests.length > 0);
389
387
  if (hasMeaningfulEnrichment) {
390
- if (user.isGhost && !isEnrichedNameMeaningful(user.email || '', enrichment.identity.name || '')) {
391
- logger.info("Enrichment has content but no real name for ghost, soft-deleting", { userId: state.userId });
392
- await deps.database.softDeleteGhost(state.userId);
393
- return { error: "No real name found for ghost user" };
394
- }
395
388
  logger.verbose("Chat API enrichment succeeded", {
396
389
  userId: state.userId,
397
390
  skillsCount: enrichment.attributes.skills.length,
@@ -399,11 +392,6 @@ export async function autoGenerateNode(state, deps) {
399
392
  });
400
393
  // Update user record with enriched data
401
394
  const updatePayload = {};
402
- const enrichedName = enrichment.identity.name?.trim();
403
- if (enrichedName &&
404
- shouldEnrichGhostDisplayNameFromParallel({ isGhost: !!user.isGhost, name: user.name ?? '', email: user.email ?? '' }, enrichedName)) {
405
- updatePayload.name = enrichedName;
406
- }
407
395
  if (enrichment.identity.bio?.trim())
408
396
  updatePayload.intro = enrichment.identity.bio.trim();
409
397
  if (enrichment.identity.location?.trim())
@@ -435,19 +423,6 @@ export async function autoGenerateNode(state, deps) {
435
423
  if (Object.keys(updatePayload).length > 0) {
436
424
  await deps.database.updateUser(state.userId, updatePayload);
437
425
  }
438
- // Post-enrichment dedup: check if this ghost matches an existing user
439
- if (user.isGhost) {
440
- const currentSocials = await deps.database.getUserSocials(state.userId);
441
- const duplicate = await deps.database.findDuplicateUser(state.userId, currentSocials);
442
- if (duplicate) {
443
- logger.info("Post-enrichment dedup: merging ghost into existing user", {
444
- ghostId: state.userId,
445
- targetId: duplicate.id,
446
- });
447
- await deps.database.mergeGhostUser(state.userId, duplicate.id);
448
- return { error: `Merged as duplicate of user ${duplicate.id}` };
449
- }
450
- }
451
426
  // Build a text blob from the enrichment result so it flows
452
427
  // through premise decomposition (when available) rather than
453
428
  // bypassing premises via prePopulatedProfile.
@@ -468,19 +443,9 @@ export async function autoGenerateNode(state, deps) {
468
443
  operationsPerformed: { scraped: true },
469
444
  };
470
445
  }
471
- if (user.isGhost) {
472
- logger.info("Low-confidence enrichment for ghost, soft-deleting", { userId: state.userId });
473
- await deps.database.softDeleteGhost(state.userId);
474
- return { error: "Enrichment not confident for ghost user" };
475
- }
476
446
  logger.warn("Chat API returned low-signal enrichment, falling back to basic info", { userId: state.userId });
477
447
  }
478
448
  catch (enrichErr) {
479
- if (user.isGhost) {
480
- logger.info("Enrichment failed for ghost, soft-deleting", { userId: state.userId });
481
- await deps.database.softDeleteGhost(state.userId);
482
- return { error: "Enrichment failed for ghost user" };
483
- }
484
449
  logger.warn("Chat API enrichment failed, falling back to basic info", {
485
450
  userId: state.userId,
486
451
  error: enrichErr instanceof Error ? enrichErr.message : String(enrichErr),
@@ -111,7 +111,7 @@ export declare const CHAT_DISPLAY_LIMIT = 6;
111
111
  *
112
112
  * Exported for use in tests (opportunity.tools.spec.ts).
113
113
  */
114
- export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId: string, counterpartUserId: string, counterpartName: string, counterpartAvatar: string | null, introducerName?: string | null, introducerAvatar?: string | null, viewerName?: string, secondPartyName?: string, secondPartyAvatar?: string | null, secondPartyUserId?: string, isCounterpartGhost?: boolean): {
114
+ export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId: string, counterpartUserId: string, counterpartName: string, counterpartAvatar: string | null, introducerName?: string | null, introducerAvatar?: string | null, viewerName?: string, secondPartyName?: string, secondPartyAvatar?: string | null, secondPartyUserId?: string): {
115
115
  opportunityId: string;
116
116
  userId: string;
117
117
  name: string;
@@ -131,7 +131,6 @@ export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId:
131
131
  viewerRole: string;
132
132
  score: number | undefined;
133
133
  status: string;
134
- isGhost: boolean;
135
134
  secondParty?: {
136
135
  name: string;
137
136
  avatar?: string | null;
@@ -172,7 +172,7 @@ export const CHAT_DISPLAY_LIMIT = 6;
172
172
  *
173
173
  * Exported for use in tests (opportunity.tools.spec.ts).
174
174
  */
175
- export function buildMinimalOpportunityCard(opp, viewerId, counterpartUserId, counterpartName, counterpartAvatar, introducerName, introducerAvatar, viewerName, secondPartyName, secondPartyAvatar, secondPartyUserId, isCounterpartGhost) {
175
+ export function buildMinimalOpportunityCard(opp, viewerId, counterpartUserId, counterpartName, counterpartAvatar, introducerName, introducerAvatar, viewerName, secondPartyName, secondPartyAvatar, secondPartyUserId) {
176
176
  const viewerActor = opp.actors.find((a) => a.userId === viewerId);
177
177
  const viewerRole = viewerActor?.role ?? "party";
178
178
  const introducerActor = opp.actors.find((a) => a.role === "introducer" && a.userId !== viewerId);
@@ -218,7 +218,6 @@ export function buildMinimalOpportunityCard(opp, viewerId, counterpartUserId, co
218
218
  viewerRole,
219
219
  score,
220
220
  status: opp.status ?? "latent",
221
- isGhost: isCounterpartGhost ?? false,
222
221
  ...(viewerIsIntroducer && secondPartyName
223
222
  ? {
224
223
  secondParty: {
@@ -212,7 +212,6 @@ export function createListOpportunitiesTool(defineTool, deps) {
212
212
  : undefined;
213
213
  const viewerActor = opp.actors.find((a) => a.userId === context.userId);
214
214
  const viewerRole = viewerActor?.role ?? "party";
215
- const isCounterpartGhost = counterpartUser?.isGhost ?? false;
216
215
  try {
217
216
  // Load the negotiation context alongside presenter context so
218
217
  // the digest copy can explain *why* the opportunity surfaced
@@ -271,7 +270,6 @@ export function createListOpportunitiesTool(defineTool, deps) {
271
270
  ? opp.interpretation.confidence
272
271
  : undefined,
273
272
  status: opp.status,
274
- isGhost: isCounterpartGhost,
275
273
  ...(redeliveryIds.has(opp.id) ? { redelivery: true } : {}),
276
274
  ...(viewerIsIntroducerHere && secondPartyNameForHeadline
277
275
  ? {
@@ -371,7 +369,6 @@ export function createListOpportunitiesTool(defineTool, deps) {
371
369
  : undefined;
372
370
  const viewerActor = opp.actors.find((a) => a.userId === context.userId);
373
371
  const viewerRole = viewerActor?.role ?? "party";
374
- const isCounterpartGhost = counterpartUser?.isGhost ?? false;
375
372
  const [ctx, negotiationContext] = await Promise.all([
376
373
  gatherOpportunityPresenterContext(presenterDb, opp, context.userId, counterpartUserId),
377
374
  loadNegotiationContext(deps.negotiationDatabase, opp.id, opp.status),
@@ -417,7 +414,6 @@ export function createListOpportunitiesTool(defineTool, deps) {
417
414
  ? opp.interpretation.confidence
418
415
  : undefined,
419
416
  status: opp.status,
420
- isGhost: isCounterpartGhost,
421
417
  ...(viewerIsIntroducerHere && secondPartyNameForHeadline
422
418
  ? {
423
419
  secondParty: {
@@ -478,7 +478,6 @@ export async function generateCardTextNode(state, deps) {
478
478
  };
479
479
  }
480
480
  }
481
- const isCounterpartGhost = otherUser?.isGhost ?? false;
482
481
  // Skeleton presentation: return an identity-only card without the
483
482
  // deps.presenter LLM or negotiation-context load. Name resolution and
484
483
  // the unresolvable-counterpart drop above still apply, so the card
@@ -496,7 +495,6 @@ export async function generateCardTextNode(state, deps) {
496
495
  secondaryActionLabel: SECONDARY_ACTION_LABEL,
497
496
  mutualIntentsLabel: isIntroducer ? 'Connector match' : 'Shared interests',
498
497
  viewerRole,
499
- isGhost: isCounterpartGhost,
500
498
  ...(secondPartyData ? { secondParty: secondPartyData } : {}),
501
499
  presentationPending: true,
502
500
  _cardIndex: cardIndex,
@@ -520,7 +518,6 @@ export async function generateCardTextNode(state, deps) {
520
518
  ? { name: 'You', text: 'Worth a look.', userId: state.userId }
521
519
  : { name: 'Index', text: 'Worth a look.' },
522
520
  viewerRole,
523
- isGhost: isCounterpartGhost,
524
521
  ...(secondPartyData ? { secondParty: secondPartyData } : {}),
525
522
  _presentationFallback: true,
526
523
  _cardIndex: cardIndex,
@@ -580,7 +577,6 @@ export async function generateCardTextNode(state, deps) {
580
577
  mutualIntentsLabel: presentation.mutualIntentsLabel,
581
578
  narratorChip,
582
579
  viewerRole,
583
- isGhost: isCounterpartGhost,
584
580
  ...(secondPartyData ? { secondParty: secondPartyData } : {}),
585
581
  _cardIndex: cardIndex,
586
582
  };
@@ -27,8 +27,6 @@ export interface RadarCardItem {
27
27
  };
28
28
  /** Viewer's role in this opportunity (e.g. 'introducer', 'party', 'agent', 'patient', 'peer'). */
29
29
  viewerRole?: string;
30
- /** Whether the counterpart is a ghost (not yet onboarded) user. */
31
- isGhost?: boolean;
32
30
  /** Template-only explanation for a pool-answer demotion (never evaluator reasoning). */
33
31
  deprioritizedReason?: string;
34
32
  /** Second party in introducer arrow layout. Present when viewerRole is 'introducer'. */
@@ -119,8 +119,7 @@ Contacts are people in a user's personal network, stored as members of their per
119
119
  - **Intents**: Signals of interest/need — what a user is looking for (e.g. "Looking for a React developer in Berlin"). Each has a description (payload), summary, confidence score (0-1), inferenceType (explicit/implicit), source tracking, and vector embedding.
120
120
  - **IntentNetworks**: Many-to-many junction between Intents and Indexes. An intent can be in multiple indexes. Has a relevancyScore (0-1) indicating how well the intent fits the index's purpose.
121
121
  - **Opportunities**: Discovered connections between users based on complementary intents within shared networks. Have actors with roles (introducer, party), status lifecycle, match reasoning, confidence score, and presentation data.
122
- - **Contacts**: People in a user's personal network, stored as network members with 'contact' permission on the personal network. Can be real users or ghost users (placeholder accounts enriched from public data).
123
- - **Ghost Users**: Placeholder accounts created for contacts who aren't on the platform yet. Enriched with public profile data (LinkedIn, GitHub) and participate in opportunity matching.
122
+ - **Contacts**: People in a user's personal network, stored as network members with 'contact' permission on the personal network. Established by accepting an opportunity, which adds both people to each other's personal network. Always real accounts.
124
123
 
125
124
  ### Key Relationships
126
125
  - Users → Profiles (1:1)
@@ -12,7 +12,7 @@ import type { Database } from './database.port.js';
12
12
  *
13
13
  * Access layer: Primarily UserDatabase (user's own profile)
14
14
  */
15
- export type EnrichmentGraphDatabase = Pick<Database, 'getProfile' | 'getUser' | 'updateUser' | 'saveProfile' | 'getProfileByUserId' | 'softDeleteGhost' | 'findDuplicateUser' | 'mergeGhostUser' | 'getUserSocials' | 'setUserSocials' | 'getPremisesForUser' | 'getUserContext'> & {
15
+ export type EnrichmentGraphDatabase = Pick<Database, 'getProfile' | 'getUser' | 'updateUser' | 'saveProfile' | 'getProfileByUserId' | 'getUserSocials' | 'setUserSocials' | 'getPremisesForUser' | 'getUserContext'> & {
16
16
  /**
17
17
  * Optional premise retraction support. When present, write-mode input that
18
18
  * disavows existing premises ("remove X", "I have nothing to do with Y")
@@ -37,7 +37,7 @@ export type PremiseGraphDatabase = Pick<Database, 'createPremise' | 'getPremise'
37
37
  *
38
38
  * Access layer: Both UserDatabase + SystemDatabase (orchestrates all operations)
39
39
  */
40
- export type ChatGraphCompositeDatabase = Pick<Database, 'getProfile' | 'getActiveIntents' | 'getActiveIntentsAcrossIndexes' | 'getIntentsInIndexForMember' | 'getUser' | 'updateUser' | 'getUserSocials' | 'setUserSocials' | 'saveProfile' | 'softDeleteGhost' | 'createIntent' | 'updateIntent' | 'archiveIntent' | 'createOpportunity' | 'createOpportunityIfNetworkEligible' | 'createOpportunityAndExpireIdsIfNetworkEligible' | 'persistIntentScopedOpportunityIfNetworkEligible' | 'updateOpportunityStatusIfNetworkEligible' | 'getOpportunity' | 'getOpportunitiesByIds' | 'opportunityExistsBetweenActors' | 'findOpportunitiesByActors' | 'getOpportunitiesForUser' | 'updateOpportunityStatus' | 'compensateTasklessNegotiatingOpportunity' | 'updateOpportunityActorApproval' | 'stampOpportunityActorAction' | 'getOrCreateDM' | 'getHydeDocument' | 'getHydeDocumentsForSource' | 'saveHydeDocument' | 'getIntent' | 'getPublicIndexesNotJoined' | 'getUserIndexIds' | 'getAssignmentNetworkMembershipsForUser' | 'getAssignmentNetworkIdsForUser' | 'getNetworkMemberships' | 'getNetworkMembership' | 'getActiveNetworkMembershipPairs' | 'getNetwork' | 'getNetworkWithPermissions' | 'getIntentForIndexing' | 'getNetworkMemberContext' | 'getNetworkAssignmentContext' | 'isIntentAssignedToIndex' | 'assignIntentToNetwork' | 'assignIntentToNetworkIfMember' | 'unassignIntentFromIndex' | 'getNetworkIdsForIntent' | 'getIntentIndexScores' | 'getPersonalIndexesForContact' | 'getOwnedIndexes' | 'isIndexOwner' | 'isNetworkMember' | 'getNetworkMembersForOwner' | 'getNetworkMembersForMember' | 'getMembersFromUserIndexes' | 'getNetworkIntentsForOwner' | 'getNetworkIntentsForMember' | 'updateIndexSettings' | 'softDeleteNetwork' | 'deleteProfile' | 'getProfileByUserId' | 'createNetwork' | 'getNetworkMemberCount' | 'addMemberToNetwork' | 'removeMemberFromIndex' | 'findDuplicateUser' | 'mergeGhostUser' | 'getPremisesForUser' | 'getPremisesForUserInNetworks' | 'createPremise' | 'getPremise' | 'updatePremise' | 'assignPremiseToNetwork' | 'getPremiseNetworks' | 'searchPremisesBySimilarity' | 'searchPremisesBySimilarityBatch' | 'getUserContext' | 'getUserContexts' | 'searchIntentsByContextEmbedding' | 'searchUserContextsBySimilarity'> & Pick<NegotiationQueries, 'getNegotiationTaskForOpportunity'>;
40
+ export type ChatGraphCompositeDatabase = Pick<Database, 'getProfile' | 'getActiveIntents' | 'getActiveIntentsAcrossIndexes' | 'getIntentsInIndexForMember' | 'getUser' | 'updateUser' | 'getUserSocials' | 'setUserSocials' | 'saveProfile' | 'createIntent' | 'updateIntent' | 'archiveIntent' | 'createOpportunity' | 'createOpportunityIfNetworkEligible' | 'createOpportunityAndExpireIdsIfNetworkEligible' | 'persistIntentScopedOpportunityIfNetworkEligible' | 'updateOpportunityStatusIfNetworkEligible' | 'getOpportunity' | 'getOpportunitiesByIds' | 'opportunityExistsBetweenActors' | 'findOpportunitiesByActors' | 'getOpportunitiesForUser' | 'updateOpportunityStatus' | 'compensateTasklessNegotiatingOpportunity' | 'updateOpportunityActorApproval' | 'stampOpportunityActorAction' | 'getOrCreateDM' | 'getHydeDocument' | 'getHydeDocumentsForSource' | 'saveHydeDocument' | 'getIntent' | 'getPublicIndexesNotJoined' | 'getUserIndexIds' | 'getAssignmentNetworkMembershipsForUser' | 'getAssignmentNetworkIdsForUser' | 'getNetworkMemberships' | 'getNetworkMembership' | 'getActiveNetworkMembershipPairs' | 'getNetwork' | 'getNetworkWithPermissions' | 'getIntentForIndexing' | 'getNetworkMemberContext' | 'getNetworkAssignmentContext' | 'isIntentAssignedToIndex' | 'assignIntentToNetwork' | 'assignIntentToNetworkIfMember' | 'unassignIntentFromIndex' | 'getNetworkIdsForIntent' | 'getIntentIndexScores' | 'getPersonalIndexesForContact' | 'getOwnedIndexes' | 'isIndexOwner' | 'isNetworkMember' | 'getNetworkMembersForOwner' | 'getNetworkMembersForMember' | 'getMembersFromUserIndexes' | 'getNetworkIntentsForOwner' | 'getNetworkIntentsForMember' | 'updateIndexSettings' | 'softDeleteNetwork' | 'deleteProfile' | 'getProfileByUserId' | 'createNetwork' | 'getNetworkMemberCount' | 'addMemberToNetwork' | 'removeMemberFromIndex' | 'getPremisesForUser' | 'getPremisesForUserInNetworks' | 'createPremise' | 'getPremise' | 'updatePremise' | 'assignPremiseToNetwork' | 'getPremiseNetworks' | 'searchPremisesBySimilarity' | 'searchPremisesBySimilarityBatch' | 'getUserContext' | 'getUserContexts' | 'searchIntentsByContextEmbedding' | 'searchUserContextsBySimilarity'> & Pick<NegotiationQueries, 'getNegotiationTaskForOpportunity'>;
41
41
  /**
42
42
  * Database interface for Opportunity Graph operations.
43
43
  * Includes prep/scope (network membership, intents, index details), persist (create, dedupe),
@@ -121,7 +121,6 @@ export interface UserRecord {
121
121
  location?: string | null;
122
122
  socials: UserSocial[];
123
123
  onboarding?: OnboardingState | null;
124
- isGhost?: boolean;
125
124
  deletedAt?: Date | null;
126
125
  }
127
126
  /**
@@ -402,8 +401,6 @@ export interface IndexMemberDetails {
402
401
  joinedAt: Date;
403
402
  /** Count of their intents in this network */
404
403
  intentCount: number;
405
- /** Whether this user is a ghost (not yet onboarded) */
406
- isGhost?: boolean;
407
404
  }
408
405
  /**
409
406
  * Intent details visible to network owners.
@@ -44,34 +44,6 @@ export interface DatabaseIdentityQueries {
44
44
  label: string;
45
45
  value: string;
46
46
  }[]): Promise<void>;
47
- /**
48
- * Soft-delete a ghost user and all their contact memberships.
49
- * Used when enrichment determines the entity is not a real person.
50
- * @param userId - The ghost user to soft-delete
51
- * @returns true if the user was soft-deleted
52
- */
53
- softDeleteGhost(userId: string): Promise<boolean>;
54
- /**
55
- * Find an existing user that matches the given social handles.
56
- * Checks LinkedIn, GitHub, and Twitter/X handles (case-insensitive, exact match).
57
- * Excludes the given userId and soft-deleted users.
58
- * Prefers real users over ghosts; among ghosts, returns the oldest.
59
- * @param userId - The ghost user being enriched (excluded from results)
60
- * @param socials - Enriched social handles to match against
61
- * @returns The matching user's id, or null if no match
62
- */
63
- findDuplicateUser(userId: string, socials: UserSocial[]): Promise<{
64
- id: string;
65
- } | null>;
66
- /**
67
- * Merge a ghost user (source) into a target user.
68
- * Re-points all data (intents, opportunities, memberships, etc.) from source to target,
69
- * deletes ghost-only records (profile, sessions, etc.), and soft-deletes the source user.
70
- * Runs in a single transaction.
71
- * @param sourceId - The ghost user to merge away
72
- * @param targetId - The user to merge into
73
- */
74
- mergeGhostUser(sourceId: string, targetId: string): Promise<void>;
75
47
  /**
76
48
  * Retrieves all active (non-archived) intents for a user.
77
49
  * Used to populate the `activeIntents` field in the Intent Graph state
@@ -35,7 +35,6 @@ export interface DatabaseMemberQueries {
35
35
  name: string;
36
36
  email: string;
37
37
  avatar: string | null;
38
- isGhost: boolean;
39
38
  };
40
39
  }>>;
41
40
  /** Clear a reverse opt-out (reactivate soft-deleted contact membership in another user's personal network). */
@@ -55,7 +54,6 @@ export interface DatabaseMemberQueries {
55
54
  id: string;
56
55
  name: string;
57
56
  email: string;
58
- isGhost: boolean;
59
57
  } | null>;
60
58
  createPremise(input: {
61
59
  userId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "18.0.0-rc.481.1",
3
+ "version": "19.0.0-rc.482.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,18 +0,0 @@
1
- /**
2
- * Returns true when the enriched name is meaningfully better than the email local-part.
3
- * A name that is empty, contains '@', or case-insensitively matches the prefix is NOT meaningful.
4
- */
5
- export declare function isEnrichedNameMeaningful(email: string, enrichedName: string): boolean;
6
- /**
7
- * Decides whether to set {@link users.name} from Parallel `enrichment.identity.name` for ghost users.
8
- * @remarks Real users (Google login, etc.) are never touched. Ghost users always get
9
- * their name enriched when Parallel returns a non-empty name that isn't an email.
10
- * @param user - Current user row (must include `email`, `name`, `isGhost`)
11
- * @param enrichedName - `enrichment.identity.name` from Parallel (may be untrimmed)
12
- * @returns True if `users.name` should be updated to the enriched full name
13
- */
14
- export declare function shouldEnrichGhostDisplayNameFromParallel(user: {
15
- name: string;
16
- email: string;
17
- isGhost?: boolean | null;
18
- }, enrichedName: string): boolean;
@@ -1,30 +0,0 @@
1
- /**
2
- * Returns true when the enriched name is meaningfully better than the email local-part.
3
- * A name that is empty, contains '@', or case-insensitively matches the prefix is NOT meaningful.
4
- */
5
- export function isEnrichedNameMeaningful(email, enrichedName) {
6
- const trimmed = enrichedName.trim();
7
- if (!trimmed || trimmed.includes('@'))
8
- return false;
9
- const localPart = email.split('@')[0].toLowerCase();
10
- return trimmed.toLowerCase() !== localPart;
11
- }
12
- /**
13
- * Decides whether to set {@link users.name} from Parallel `enrichment.identity.name` for ghost users.
14
- * @remarks Real users (Google login, etc.) are never touched. Ghost users always get
15
- * their name enriched when Parallel returns a non-empty name that isn't an email.
16
- * @param user - Current user row (must include `email`, `name`, `isGhost`)
17
- * @param enrichedName - `enrichment.identity.name` from Parallel (may be untrimmed)
18
- * @returns True if `users.name` should be updated to the enriched full name
19
- */
20
- export function shouldEnrichGhostDisplayNameFromParallel(user, enrichedName) {
21
- if (!user.isGhost)
22
- return false;
23
- const trimmed = enrichedName.trim();
24
- if (!trimmed || trimmed.includes("@"))
25
- return false;
26
- // Skip only if exactly the same (case-sensitive)
27
- if (user.name.trim() === trimmed)
28
- return false;
29
- return true;
30
- }