@indexnetwork/protocol 2.0.1 → 3.0.0-rc.245.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/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/intent/intent.graph.d.ts +26 -26
- package/dist/intent/intent.inferrer.d.ts +8 -8
- package/dist/intent/intent.reconciler.d.ts +12 -12
- package/dist/intent/intent.state.d.ts +6 -6
- package/dist/mcp/mcp.server.d.ts +5 -0
- package/dist/mcp/mcp.server.d.ts.map +1 -1
- package/dist/mcp/mcp.server.js +43 -2
- package/dist/mcp/mcp.server.js.map +1 -1
- package/dist/opportunity/opportunity.evaluator.d.ts +3 -3
- package/dist/premise/premise.analyzer.d.ts +2 -2
- package/dist/premise/premise.decomposer.d.ts +2 -2
- package/dist/premise/premise.tools.js.map +1 -1
- package/dist/shared/agent/tool.factory.js.map +1 -1
- package/dist/shared/agent/tool.helpers.d.ts +1 -1
- package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
- package/dist/shared/agent/tool.helpers.js.map +1 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.d.ts +1 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.js.map +1 -1
- package/dist/shared/interfaces/auth.interface.d.ts +16 -9
- package/dist/shared/interfaces/auth.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/auth.interface.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +2 -2
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/dist/shared/interfaces/question-generator.interface.d.ts +1 -1
- package/dist/shared/interfaces/question-generator.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/question-generator.interface.js.map +1 -1
- package/dist/shared/schemas/discovery-question.schema.d.ts +242 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts.map +1 -0
- package/dist/shared/schemas/discovery-question.schema.js +49 -0
- package/dist/shared/schemas/discovery-question.schema.js.map +1 -0
- package/dist/shared/schemas/mcp-auth.schema.d.ts +18 -0
- package/dist/shared/schemas/mcp-auth.schema.d.ts.map +1 -0
- package/dist/shared/schemas/mcp-auth.schema.js +2 -0
- package/dist/shared/schemas/mcp-auth.schema.js.map +1 -0
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/dist/shared/schemas/negotiation-state.schema.d.ts +118 -0
- package/dist/shared/schemas/negotiation-state.schema.d.ts.map +1 -0
- package/dist/shared/schemas/negotiation-state.schema.js +29 -0
- package/dist/shared/schemas/negotiation-state.schema.js.map +1 -0
- package/dist/shared/schemas/profile.schema.d.ts +100 -0
- package/dist/shared/schemas/profile.schema.d.ts.map +1 -0
- package/dist/shared/schemas/profile.schema.js +26 -0
- package/dist/shared/schemas/profile.schema.js.map +1 -0
- package/dist/shared/schemas/question.schema.d.ts +8 -8
- package/package.json +1 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.interface.js","sourceRoot":"/","sources":["shared/interfaces/database.interface.ts"],"names":[],"mappings":"","sourcesContent":["import { ProfileDocument } from '../../profile/profile.generator.js';\n\n// ─── Inlined types (previously imported from outside the protocol lib) ───────\n\n/** Branded string ID for type-safe entity references (keyed by Drizzle table name). */\nexport type Id<T extends string = string> = string & { readonly __table?: T };\n\nexport type PrivacyConsentSource = 'agentvillage_onboarding' | 'hermes_setup' | 'web_onboarding' | 'api';\n\nexport interface PrivacyConsentDecision {\n granted: boolean;\n decidedAt: string;\n source: PrivacyConsentSource;\n}\n\nexport interface OnboardingPrivacyState {\n edgeosImport?: PrivacyConsentDecision;\n publicProfileLookup?: PrivacyConsentDecision;\n}\n\nexport interface OnboardingProfileSeed {\n source: 'experiment_signup' | 'experiment_csv_import';\n networkId: string;\n capturedAt: string;\n name?: string;\n bio?: string;\n location?: string;\n socials?: { label: string; value: string }[];\n}\n\n/** Onboarding flow state stored as JSON on the user record. */\nexport interface OnboardingState {\n completedAt?: string;\n flow?: 1 | 2 | 3;\n currentStep?: 'profile' | 'summary' | 'connections' | 'create_network' | 'invite_members' | 'join_networks';\n networkId?: string;\n invitationCode?: string;\n privacy?: OnboardingPrivacyState;\n profileSeeds?: OnboardingProfileSeed[];\n}\n\n/** Single social-link row from the user_socials table. */\nexport interface UserSocial {\n id: string;\n userId: string;\n label: string;\n value: string;\n}\n\n/** Detection metadata recorded when an opportunity is created. */\nexport interface OpportunityDetection {\n source: 'opportunity_graph' | 'chat' | 'manual' | 'cron' | 'member_added' | 'enrichment' | 'introducer_discovery';\n createdBy?: Id<'users'> | string;\n createdByName?: string;\n triggeredBy?: Id<'intents'>;\n timestamp: string;\n enrichedFrom?: string[];\n}\n\n/** A participant (user + network) involved in an opportunity. */\nexport interface OpportunityActor {\n networkId: Id<'networks'>;\n userId: Id<'users'>;\n intent?: Id<'intents'>;\n /** Which premise grounded this match (set when discoverySource is 'premise-similarity'). */\n premise?: Id<'premises'>;\n role: string;\n /** Only set on role === 'introducer'. false until the introducer explicitly approves; true after approval. */\n approved?: boolean;\n /**\n * ISO-8601 timestamp set the first time this actor advanced the opportunity's\n * state (patient sending, agent accepting, peer \"accepting\" on draft = sending\n * under the hood, peer accepting on pending, introducer sending). Once set,\n * this actor has committed and cannot be the one to subsequently `accept` the\n * same opportunity — enforced by the self-accept guard in `updateNode`.\n */\n actedAt?: string;\n}\n\n/** Individual signal contributing to an opportunity score. */\nexport interface OpportunitySignal {\n type: string;\n weight: number;\n detail?: string;\n}\n\n/** LLM-generated interpretation of an opportunity's category and confidence. */\nexport interface OpportunityInterpretation {\n category: string;\n reasoning: string;\n confidence: number;\n signals?: OpportunitySignal[];\n}\n\n/** Optional scoping context (network / conversation) for an opportunity. */\nexport interface OpportunityContext {\n networkId?: Id<'networks'>;\n conversationId?: Id<'conversations'>;\n}\n\n/** User record returned by getUser (minimal fields plus optional profile fields). */\nexport interface UserRecord {\n id: string;\n name: string;\n email: string;\n intro?: string | null;\n avatar?: string | null;\n location?: string | null;\n socials: UserSocial[];\n onboarding?: OnboardingState | null;\n isGhost?: boolean;\n deletedAt?: Date | null;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// INTENT TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Minimal intent representation used for graph state population.\n * Contains only the fields needed for reconciliation logic.\n */\nexport interface ActiveIntent {\n /** Unique identifier of the intent */\n id: string;\n /** Full intent description/payload */\n payload: string;\n /** Short summary of the intent (may be null if not generated) */\n summary: string | null;\n /** When the intent was created */\n createdAt: Date;\n /** Relevancy score for this intent in its index context (0.0–1.0, null if not scored) */\n relevancyScore?: number | null;\n}\n\n/**\n * Input data for creating a new intent.\n * Supports the full intent pipeline including embedding and index association.\n */\nexport interface CreateIntentData {\n /** The user who owns this intent */\n userId: string;\n /** Full intent description/payload */\n payload: string;\n /** Pre-computed summary (optional, will be generated if not provided) */\n summary?: string | null;\n /** Pre-computed embedding vector (optional, will be generated if not provided) */\n embedding?: number[];\n /** Whether the intent should be hidden from public views */\n isIncognito?: boolean;\n /** Index IDs to associate with (optional, uses dynamic scoping if empty) */\n networkIds?: string[];\n /** Source type for provenance tracking */\n sourceType?: 'file' | 'integration' | 'link' | 'discovery_form' | 'enrichment';\n /** Source ID for provenance tracking */\n sourceId?: string;\n /** Confidence score from inference (0-1, required) */\n confidence: number;\n /** How the intent was inferred */\n inferenceType: 'explicit' | 'implicit';\n /** Semantic entropy from verifier (0 specific -> 1 vague) */\n semanticEntropy?: number | null;\n /** Referential anchor extracted by verifier (if any) */\n referentialAnchor?: string | null;\n /** Felicity authority score from verifier (0-100) */\n felicityAuthority?: number | null;\n /** Felicity sincerity score from verifier (0-100) */\n felicitySincerity?: number | null;\n /** Felicity clarity score from verifier (0-100) */\n felicityClarity?: number | null;\n /** Donnellan intent mode */\n intentMode?: 'REFERENTIAL' | 'ATTRIBUTIVE' | null;\n /** Speech act category used by protocol enum */\n speechActType?: 'COMMISSIVE' | 'DIRECTIVE' | null;\n}\n\n/**\n * Input data for updating an existing intent.\n * All fields are optional - only provided fields will be updated.\n */\nexport interface UpdateIntentData {\n /** Updated intent description/payload */\n payload?: string;\n /** Updated summary */\n summary?: string | null;\n /** Updated embedding vector */\n embedding?: number[];\n /** Updated incognito status */\n isIncognito?: boolean;\n /** Updated index associations (replaces existing) */\n networkIds?: string[];\n /** Semantic entropy from verifier (0 specific -> 1 vague) */\n semanticEntropy?: number | null;\n /** Referential anchor extracted by verifier (if any) */\n referentialAnchor?: string | null;\n /** Felicity authority score from verifier (0-100) */\n felicityAuthority?: number | null;\n /** Felicity sincerity score from verifier (0-100) */\n felicitySincerity?: number | null;\n /** Felicity clarity score from verifier (0-100) */\n felicityClarity?: number | null;\n /** Donnellan intent mode */\n intentMode?: 'REFERENTIAL' | 'ATTRIBUTIVE' | null;\n /** Speech act category used by protocol enum */\n speechActType?: 'COMMISSIVE' | 'DIRECTIVE' | null;\n}\n\n/**\n * The result of a successful intent creation.\n * Contains the core fields needed for immediate use.\n */\nexport interface CreatedIntent {\n /** Unique identifier of the created intent */\n id: string;\n /** Full intent description/payload */\n payload: string;\n /** Generated or provided summary */\n summary: string | null;\n /** Incognito status */\n isIncognito: boolean;\n /** Creation timestamp */\n createdAt: Date;\n /** Last update timestamp */\n updatedAt: Date;\n /** Owner user ID */\n userId: string;\n}\n\n/**\n * Full intent record with all fields (for detailed queries).\n */\nexport interface IntentRecord extends CreatedIntent {\n /** Archival timestamp (null if active) */\n archivedAt: Date | null;\n /** Embedding vector (may be null) */\n embedding?: number[] | null;\n /** Source type for provenance */\n sourceType?: string | null;\n /** Source ID for provenance */\n sourceId?: string | null;\n}\n\n/**\n * Intent with similarity score from vector search.\n */\nexport interface SimilarIntent extends IntentRecord {\n /** Cosine similarity score (0-1) */\n similarity: number;\n}\n\n/**\n * Result of an archive operation.\n */\nexport interface ArchiveResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** Error message if failed */\n error?: string;\n}\n\n/**\n * Options for vector similarity search.\n */\nexport interface SimilarIntentSearchOptions {\n /** Maximum number of results to return (default: 10) */\n limit?: number;\n /** Minimum similarity threshold (default: 0.7) */\n threshold?: number;\n}\n\n/**\n * Represents a user's membership in an index with full details.\n * Used for displaying index memberships in chat (index_query).\n */\nexport interface NetworkMembership {\n /** Unique identifier of the index */\n networkId: string;\n /** Display title of the index */\n networkTitle: string;\n /** Index description/prompt (what the community is about) */\n indexPrompt: string | null;\n /** Member's permissions in this index */\n permissions: string[];\n /** Member's custom prompt (overrides index prompt for their intents) */\n memberPrompt: string | null;\n /** Whether new intents are auto-assigned to this index */\n autoAssign: boolean;\n /** Whether this is the user's personal index (\"My Network\") */\n isPersonal: boolean;\n /** When the user joined the index */\n joinedAt: Date;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// PREMISE TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport interface PremiseAssertion {\n text: string;\n tier: 'assertive' | 'contextual';\n summary?: string;\n}\n\nexport interface PremiseProvenance {\n source: 'explicit' | 'enrichment' | 'integration' | 'onboarding';\n sourceId?: string;\n confidence: number;\n timestamp: string;\n}\n\nexport interface PremiseAnalysis {\n speechActType: 'DECLARATIVE' | 'ASSERTIVE';\n felicityAuthority: number;\n felicitySincerity: number;\n felicityClarity: number;\n semanticEntropy: number;\n}\n\nexport interface PremiseValidity {\n validFrom?: string;\n validUntil?: string;\n volatile: boolean;\n}\n\nexport interface PremiseRecord {\n id: string;\n userId: string;\n assertion: PremiseAssertion;\n provenance: PremiseProvenance;\n analysis: PremiseAnalysis | null;\n validity: PremiseValidity;\n embedding: number[] | null;\n status: 'ACTIVE' | 'RETRACTED' | 'EXPIRED';\n createdAt: Date;\n updatedAt: Date;\n retractedAt: Date | null;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// INDEX OWNERSHIP TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Represents an index owned by the user with full details.\n */\nexport interface OwnedIndex {\n /** Index ID */\n id: string;\n /** Display title */\n title: string;\n /** Index purpose/scope prompt */\n prompt: string | null;\n /** Cover image URL */\n imageUrl: string | null;\n /** Permission settings */\n permissions: {\n joinPolicy: 'anyone' | 'invite_only';\n allowGuestVibeCheck: boolean;\n invitationLink: { code: string } | null;\n };\n /** Whether this is a personal index */\n isPersonal: boolean;\n /** When the index was created */\n createdAt: Date;\n /** When the index was last updated */\n updatedAt: Date;\n /** Member count */\n memberCount: number;\n /** Total intents indexed */\n intentCount: number;\n /** Owner summary */\n user: { id: string; name: string; avatar: string | null };\n /** Aggregate counts for frontend compatibility */\n _count: { members: number };\n}\n\n/**\n * Member details visible to index owners (and optionally to members with privacy rules).\n */\nexport interface IndexMemberDetails {\n /** User ID */\n userId: string;\n /** User's display name */\n name: string;\n /** User's avatar URL */\n avatar: string | null;\n /** User's email; only present when viewer is owner/admin or the member themselves (privacy-safe) */\n email?: string | null;\n /** Member's permissions in this index */\n permissions: string[];\n /** Member's custom prompt */\n memberPrompt: string | null;\n /** Whether auto-assign is enabled */\n autoAssign: boolean;\n /** When they joined */\n joinedAt: Date;\n /** Count of their intents in this index */\n intentCount: number;\n /** Whether this user is a ghost (not yet onboarded) */\n isGhost?: boolean;\n}\n\n/**\n * Intent details visible to index owners.\n */\nexport interface IndexedIntentDetails {\n /** Intent ID */\n id: string;\n /** Intent payload/description */\n payload: string;\n /** Intent summary */\n summary: string | null;\n /** Owner's user ID */\n userId: string;\n /** Owner's name */\n userName: string;\n /** When the intent was created */\n createdAt: Date;\n /** Relevancy score for this intent in its index context (0.0–1.0, null if not scored) */\n relevancyScore?: number | null;\n}\n\n/**\n * Options for updating index settings.\n */\nexport interface UpdateIndexSettingsData {\n /** New title (optional) */\n title?: string;\n /** New prompt (optional) */\n prompt?: string | null;\n /** New image URL (optional) */\n imageUrl?: string | null;\n /** New join policy (optional) */\n joinPolicy?: 'anyone' | 'invite_only';\n /** Allow guest vibe check (optional) */\n allowGuestVibeCheck?: boolean;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// HYDE DOCUMENT TYPES (Opportunity Redesign)\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport type HydeSourceType = 'intent' | 'profile' | 'query' | 'context';\n\nexport interface HydeDocument {\n id: string;\n sourceType: HydeSourceType;\n sourceId: string | null;\n sourceText: string | null;\n strategy: string;\n targetCorpus: string;\n hydeText: string;\n hydeEmbedding: number[];\n context: Record<string, unknown> | null;\n createdAt: Date;\n expiresAt: Date | null;\n}\n\nexport interface CreateHydeDocumentData {\n sourceType: HydeSourceType;\n sourceId?: string;\n sourceText?: string;\n strategy: string;\n targetCorpus: string;\n hydeText: string;\n hydeEmbedding: number[];\n context?: Record<string, unknown>;\n expiresAt?: Date;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// OPPORTUNITY TYPES (Opportunity Redesign)\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport type OpportunityStatus = 'latent' | 'draft' | 'negotiating' | 'pending' | 'stalled' | 'accepted' | 'rejected' | 'expired';\n\nexport interface Opportunity {\n id: string;\n detection: OpportunityDetection;\n actors: OpportunityActor[];\n interpretation: OpportunityInterpretation;\n context: OpportunityContext;\n confidence: string;\n status: OpportunityStatus;\n createdAt: Date;\n updatedAt: Date;\n expiresAt: Date | null;\n}\n\nexport interface CreateOpportunityData {\n detection: OpportunityDetection;\n actors: OpportunityActor[];\n interpretation: OpportunityInterpretation;\n context: OpportunityContext;\n confidence: string;\n status?: OpportunityStatus;\n expiresAt?: Date;\n}\n\nexport interface OpportunityQueryOptions {\n status?: OpportunityStatus;\n /** When set, filter to opportunities whose status is in this list. Orthogonal to `status` (single) — callers pick one. */\n statuses?: OpportunityStatus[];\n networkId?: string;\n role?: string;\n limit?: number;\n offset?: number;\n /** When set, include draft opportunities for this chat session. When unset, exclude all draft opportunities (e.g. home view, API). */\n conversationId?: string;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// DATABASE INTERFACE\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Abstract database interface for performing specific domain operations.\n * Decouples the protocol layer from the infrastructure layer.\n */\nexport interface Database {\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (Preserved)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves a user profile by userId.\n * @param userId - The unique identifier of the user\n * @returns The user's profile or null if not found\n */\n getProfile(userId: string): Promise<ProfileDocument | null>;\n\n /**\n * Creates or updates a user profile.\n * @param userId - The unique identifier of the user\n * @param profile - The profile data to save\n */\n saveProfile(userId: string, profile: ProfileDocument): Promise<void>;\n\n /**\n * Retrieves basic user information (name, email, socials) by userId.\n * @param userId - The unique identifier of the user\n * @returns The user record or null if not found\n */\n getUser(userId: string): Promise<UserRecord | null>;\n\n /**\n * Updates user account fields (name, location, socials).\n * Merges socials with existing values (does not overwrite the whole object).\n * Used by create_user_profile tool to persist user-provided info before\n * invoking the Profile Graph in generate mode.\n *\n * @param userId - The unique identifier of the user\n * @param data - Partial user fields to update\n * @returns The updated user record or null if not found\n */\n updateUser(userId: string, data: { name?: string; intro?: string; location?: string; onboarding?: OnboardingState }): Promise<UserRecord | null>;\n\n getUserSocials(userId: string): Promise<UserSocial[]>;\n setUserSocials(userId: string, socials: { label: string; value: string }[]): Promise<void>;\n\n /**\n * Soft-delete a ghost user and all their contact memberships.\n * Used when enrichment determines the entity is not a real person.\n * @param userId - The ghost user to soft-delete\n * @returns true if the user was soft-deleted\n */\n softDeleteGhost(userId: string): Promise<boolean>;\n\n /**\n * Find an existing user that matches the given social handles.\n * Checks LinkedIn, GitHub, and Twitter/X handles (case-insensitive, exact match).\n * Excludes the given userId and soft-deleted users.\n * Prefers real users over ghosts; among ghosts, returns the oldest.\n * @param userId - The ghost user being enriched (excluded from results)\n * @param socials - Enriched social handles to match against\n * @returns The matching user's id, or null if no match\n */\n findDuplicateUser(userId: string, socials: UserSocial[]): Promise<{ id: string } | null>;\n\n /**\n * Merge a ghost user (source) into a target user.\n * Re-points all data (intents, opportunities, memberships, etc.) from source to target,\n * deletes ghost-only records (profile, sessions, etc.), and soft-deletes the source user.\n * Runs in a single transaction.\n * @param sourceId - The ghost user to merge away\n * @param targetId - The user to merge into\n */\n mergeGhostUser(sourceId: string, targetId: string): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Pre-Graph Operations (State Population)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves all active (non-archived) intents for a user.\n * Used to populate the `activeIntents` field in the Intent Graph state\n * before graph execution.\n *\n * @param userId - The unique identifier of the user\n * @returns Array of active intents with minimal fields needed for reconciliation\n *\n * @example\n * ```typescript\n * const activeIntents = await db.getActiveIntents(userId);\n * const formattedIntents = activeIntents\n * .map(i => `ID: ${i.id}, Description: ${i.payload}, Summary: ${i.summary || 'N/A'}`)\n * .join('\\n');\n * ```\n */\n getActiveIntents(userId: string): Promise<ActiveIntent[]>;\n\n /**\n * Get active intents that belong to the user and are assigned to a specific index.\n * Caller must be a member of that index; only the user's own intents are returned.\n *\n * @param userId - The user requesting (must be a member of the index)\n * @param indexNameOrId - Index UUID or display name (e.g. \"Commons\")\n * @returns Array of active intents in that index for the user, or empty if not a member / no match\n */\n getIntentsInIndexForMember(userId: string, indexNameOrId: string): Promise<ActiveIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Post-Graph Operations (Action Execution)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Creates a new intent with full processing pipeline.\n * Handles summarization, embedding generation, and index association.\n *\n * Called when the reconciler outputs a \"create\" action.\n *\n * @param data - The intent creation data\n * @returns The created intent with generated fields\n *\n * @example\n * ```typescript\n * // After graph outputs CREATE action\n * const newIntent = await db.createIntent({\n * userId,\n * payload: action.payload,\n * confidence: action.score / 100,\n * inferenceType: 'explicit',\n * sourceType: 'discovery_form'\n * });\n * ```\n */\n createIntent(data: CreateIntentData): Promise<CreatedIntent>;\n\n /**\n * Updates an existing intent.\n * Re-generates summary and embedding if payload changes.\n *\n * Called when the reconciler outputs an \"update\" action.\n *\n * @param intentId - The unique identifier of the intent to update\n * @param data - The fields to update\n * @returns The updated intent or null if not found\n * @throws Error if the intent exists but user doesn't have access\n *\n * @example\n * ```typescript\n * // After graph outputs UPDATE action\n * const updated = await db.updateIntent(action.id, {\n * payload: action.payload\n * });\n * ```\n */\n updateIntent(intentId: string, data: UpdateIntentData): Promise<CreatedIntent | null>;\n\n /**\n * Archives (soft-deletes) an intent.\n * Sets the archivedAt timestamp rather than hard deleting.\n *\n * Called when the reconciler outputs an \"expire\" action.\n *\n * @param intentId - The unique identifier of the intent to archive\n * @returns Result object indicating success or failure with error message\n *\n * @example\n * ```typescript\n * // After graph outputs EXPIRE action\n * const result = await db.archiveIntent(action.id);\n * if (!result.success) {\n * console.error(`Failed to archive: ${result.error}`);\n * }\n * ```\n */\n archiveIntent(intentId: string): Promise<ArchiveResult>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Query Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves a single intent by ID.\n *\n * @param intentId - The unique identifier of the intent\n * @returns The full intent record or null if not found\n */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /**\n * Retrieves an intent with ownership verification.\n * Ensures the requesting user owns the intent before returning.\n *\n * Used for processing operations (refine, suggestions) that require ownership.\n *\n * @param intentId - The unique identifier of the intent\n * @param userId - The user requesting access\n * @returns The intent if found and owned by user, null if not found\n * @throws Error with message 'Access denied' if intent exists but is not owned by user\n *\n * @example\n * ```typescript\n * try {\n * const intent = await db.getIntentWithOwnership(intentId, userId);\n * if (!intent) return res.status(404).json({ error: 'Not found' });\n * // Process intent...\n * } catch (e) {\n * if (e.message === 'Access denied') {\n * return res.status(403).json({ error: 'Forbidden' });\n * }\n * throw e;\n * }\n * ```\n */\n getIntentWithOwnership(intentId: string, userId: string): Promise<IntentRecord | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Association Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Gets Index IDs where the user has auto-assign membership enabled.\n * Used for determining which indexes to associate new intents with.\n *\n * @param userId - The unique identifier of the user\n * @returns Array of index IDs\n *\n * @example\n * ```typescript\n * const networkIds = await db.getUserIndexIds(userId);\n * if (networkIds.length > 0) {\n * await db.associateIntentWithNetworks(intentId, networkIds);\n * }\n * ```\n */\n getUserIndexIds(userId: string): Promise<string[]>;\n\n /**\n * Retrieves all indexes the user is a member of with full details.\n * Used for displaying index memberships in chat (index_query).\n *\n * @param userId - The unique identifier of the user\n * @returns Array of index memberships with details\n */\n getNetworkMemberships(userId: string): Promise<NetworkMembership[]>;\n\n /**\n * Get a single index membership by index and user.\n * Used when the preloaded memberships list may not contain this index (e.g. after isNetworkMember check).\n *\n * @param networkId - The index ID\n * @param userId - The user ID\n * @returns The membership or null if not found\n */\n getNetworkMembership(networkId: string, userId: string): Promise<NetworkMembership | null>;\n\n /**\n * Get index by ID with core fields. Used for opportunity presentation and context rendering.\n */\n getNetwork(networkId: string): Promise<{\n id: string;\n title: string;\n prompt?: string | null;\n type?: string;\n metadata?: Record<string, unknown> | null;\n permissions?: Record<string, unknown> | null;\n } | null>;\n\n /**\n * Get index by ID with permissions (e.g. joinPolicy). Used by chat tools for create_index_membership.\n */\n getNetworkWithPermissions(networkId: string): Promise<{ id: string; title: string; permissions: { joinPolicy: 'anyone' | 'invite_only' } } | null>;\n\n /**\n * Associates an intent with one or more networks.\n * Creates entries in the intentNetworks join table.\n *\n * @param intentId - The intent to associate\n * @param networkIds - Array of network IDs to associate with\n *\n * @example\n * ```typescript\n * await db.associateIntentWithNetworks(intentId, ['idx_1', 'idx_2']);\n * ```\n */\n associateIntentWithNetworks(intentId: string, networkIds: string[]): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Vector Search Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Finds semantically similar intents using vector search.\n * Used for deduplication during intent creation and discovery.\n *\n * Privacy scoping: Results are always filtered by userId to ensure\n * users only see their own intents.\n *\n * @param embedding - The query embedding vector\n * @param userId - The user ID for privacy scoping (required)\n * @param options - Search options (limit, threshold)\n * @returns Array of intents with similarity scores, sorted by similarity\n *\n * @example\n * ```typescript\n * // Check for duplicates before creating\n * const embedding = await embedder.generate(payload);\n * const similar = await db.findSimilarIntents(embedding, userId, {\n * limit: 5,\n * threshold: 0.85\n * });\n * if (similar.length > 0 && similar[0].similarity > 0.95) {\n * // Likely duplicate - consider updating instead\n * }\n * ```\n */\n findSimilarIntents(\n embedding: number[],\n userId: string,\n options?: SimilarIntentSearchOptions\n ): Promise<SimilarIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Graph Operations (Intent–Index Assignment)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Intent fields needed for index appropriateness evaluation.\n */\n getIntentForIndexing(intentId: string): Promise<{\n id: string;\n payload: string;\n userId: string;\n sourceType: string | null;\n sourceId: string | null;\n } | null>;\n\n /**\n * Index + member prompts for a user in an index (only when member has autoAssign).\n * Returns null if user is not a member or autoAssign is false.\n */\n getNetworkMemberContext(\n networkId: string,\n userId: string\n ): Promise<{\n networkId: string;\n indexPrompt: string | null;\n memberPrompt: string | null;\n } | null>;\n\n /**\n * Whether the intent is currently assigned to the index.\n */\n isIntentAssignedToIndex(intentId: string, networkId: string): Promise<boolean>;\n\n /**\n * Assigns an intent to an index (inserts intent_indexes row).\n */\n assignIntentToNetwork(intentId: string, networkId: string, relevancyScore?: number): Promise<void>;\n\n /**\n * Returns per-index relevancy scores for an intent's index assignments.\n */\n getIntentIndexScores(intentId: string): Promise<Array<{ networkId: string; relevancyScore: number | null }>>;\n\n /**\n * Removes an intent from an index (deletes intent_indexes row).\n */\n unassignIntentFromIndex(intentId: string, networkId: string): Promise<void>;\n\n /**\n * Returns all network IDs that an intent is registered to.\n */\n getNetworkIdsForIntent(intentId: string): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Ownership Operations (Owner-Only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get indexes where the user has owner permissions.\n * Returns full index details with member and intent counts.\n *\n * @param userId - The user ID to check ownership for\n * @returns Array of owned indexes with counts\n */\n getOwnedIndexes(userId: string): Promise<OwnedIndex[]>;\n\n /**\n * Get public indexes (joinPolicy 'anyone') that the user has not joined.\n * Used for discovering communities available to join.\n *\n * @param userId - The user ID to check memberships against\n * @returns Object containing array of public indexes with owner info\n */\n getPublicIndexesNotJoined(userId: string): Promise<{\n networks: Array<{\n id: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n owner: { id: string; name: string; avatar: string | null } | null;\n }>;\n }>;\n\n /**\n * Check if user is an owner of a specific index.\n *\n * @param networkId - The index to check\n * @param userId - The user to verify ownership for\n * @returns True if user is an owner\n */\n isIndexOwner(networkId: string, userId: string): Promise<boolean>;\n\n /**\n * Check if user is a member of a specific index.\n *\n * @param networkId - The index to check\n * @param userId - The user to verify membership for\n * @returns True if user is a member\n */\n isNetworkMember(networkId: string, userId: string): Promise<boolean>;\n\n /**\n * Get all members of an index with their details.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to get members for\n * @param requestingUserId - The user requesting (must be owner)\n * @returns Array of member details with intent counts\n * @throws Error if requestingUserId is not an owner\n */\n getNetworkMembersForOwner(\n networkId: string,\n requestingUserId: string\n ): Promise<IndexMemberDetails[]>;\n\n /**\n * Get all members of an index with their details.\n * **MEMBER ONLY** - any member of the index can list members (not just owners).\n * Returns same shape as getNetworkMembersForOwner; email may be omitted for privacy.\n *\n * @param networkId - The index to get members for\n * @param requestingUserId - The user requesting (must be a member of the index)\n * @returns Array of member details with intent counts\n * @throws Error if requestingUserId is not a member of the index\n */\n getNetworkMembersForMember(\n networkId: string,\n requestingUserId: string\n ): Promise<IndexMemberDetails[]>;\n\n /**\n * Get all members from every index the user is a member of (deduplicated).\n * Used for mentionable-users: anyone who shares at least one index with the requesting user.\n *\n * @param userId - The signed-in user\n * @returns Array of member summaries (id, name, avatar only; no email)\n */\n getMembersFromUserIndexes(userId: Id<'users'>): Promise<{ userId: Id<'users'>; name: string; avatar: string | null }[]>;\n\n /**\n * Get all indexed intents for an index.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to get intents for\n * @param requestingUserId - The user requesting (must be owner)\n * @param options - Pagination options\n * @returns Array of intent details with owner info\n * @throws Error if requestingUserId is not an owner\n */\n getNetworkIntentsForOwner(\n networkId: string,\n requestingUserId: string,\n options?: { limit?: number; offset?: number }\n ): Promise<IndexedIntentDetails[]>;\n\n /**\n * Get all indexed intents for an index.\n * **MEMBER ONLY** - any member of the index can list intents (not just owners).\n *\n * @param networkId - The index to get intents for\n * @param requestingUserId - The user requesting (must be a member of the index)\n * @param options - Pagination options\n * @returns Array of intent details with owner info\n * @throws Error if requestingUserId is not a member of the index\n */\n getNetworkIntentsForMember(\n networkId: string,\n requestingUserId: string,\n options?: { limit?: number; offset?: number }\n ): Promise<IndexedIntentDetails[]>;\n\n /**\n * Get the caller's own active intents across a set of indexes.\n * Returns intents owned by `userId` that are linked (via intent_networks)\n * to at least one of `indexIds`. Used by network-scoped agents to honor\n * indexScope without falling back to global getActiveIntents (which would\n * include intents in indexes outside scope).\n *\n * @param userId - The intent owner (always the caller).\n * @param indexIds - The set of index IDs to filter on. Empty → empty result.\n * @returns Active intents owned by userId in any of indexIds, deduped by intent id.\n */\n getActiveIntentsAcrossIndexes(userId: string, indexIds: string[]): Promise<ActiveIntent[]>;\n\n /**\n * Update index settings.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to update\n * @param requestingUserId - The user requesting (must be owner)\n * @param data - The settings to update\n * @returns The updated index\n * @throws Error if requestingUserId is not an owner\n */\n updateIndexSettings(\n networkId: string,\n requestingUserId: string,\n data: UpdateIndexSettingsData\n ): Promise<OwnedIndex>;\n\n /**\n * Soft-delete a network (set deletedAt).\n * Caller must ensure network is not personal and has no other members.\n *\n * @param networkId - The network to soft-delete\n */\n softDeleteNetwork(networkId: string): Promise<void>;\n\n /**\n * Delete a user's profile (removes profile row).\n * Used after confirmation in chat tools.\n *\n * @param userId - User whose profile to delete\n */\n deleteProfile(userId: string): Promise<void>;\n\n /**\n * Get a user's profile including its row id (for update_user_profile validation).\n *\n * @param userId - The user whose profile to fetch\n * @returns Profile with id, or null if not found\n */\n getProfileByUserId(userId: string): Promise<(ProfileDocument & { id: string }) | null>;\n\n /**\n * Create a new index and return its record.\n *\n * @param data - Title, optional prompt, optional imageUrl, optional joinPolicy\n * @returns The created index with id, title, prompt, imageUrl, permissions\n */\n createNetwork(data: {\n title: string;\n prompt?: string | null;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n }): Promise<{\n id: string;\n title: string;\n prompt: string | null;\n imageUrl: string | null;\n permissions: { joinPolicy: 'anyone' | 'invite_only'; invitationLink: { code: string } | null; allowGuestVibeCheck: boolean };\n }>;\n\n /**\n * Count members in an index (for delete guard).\n *\n * @param networkId - The index to count\n * @returns Number of members\n */\n getNetworkMemberCount(networkId: string): Promise<number>;\n\n /**\n * Add a user as a member of a network.\n *\n * @param networkId - The network to add to\n * @param userId - The user to add\n * @param role - owner | member\n * @returns success and optionally alreadyMember if they were already in the network\n */\n addMemberToNetwork(\n networkId: string,\n userId: string,\n role: 'owner' | 'member'\n ): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n /**\n * Removes a user from an index.\n * Only the index owner can remove members. Cannot remove the owner.\n *\n * @param networkId - The index to remove from\n * @param userId - The user to remove\n * @returns success, or wasOwner/notMember if removal failed\n */\n removeMemberFromIndex(\n networkId: string,\n userId: string\n ): Promise<{ success: boolean; wasOwner?: boolean; notMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Document Operations (Opportunity Redesign)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get a HyDE document by source and strategy/lens hash.\n * Returns the first matching document when multiple target corpuses exist.\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID (e.g. intent ID, user ID)\n * @param strategy - Lens hash (SHA-256 of lens label) or legacy strategy name\n * @returns The HyDE document or null if not found\n */\n getHydeDocument(\n sourceType: HydeSourceType,\n sourceId: string,\n strategy: string\n ): Promise<HydeDocument | null>;\n\n /**\n * Get all HyDE documents for a source (all strategies).\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID\n * @returns Array of HyDE documents for that source\n */\n getHydeDocumentsForSource(\n sourceType: HydeSourceType,\n sourceId: string\n ): Promise<HydeDocument[]>;\n\n /**\n * Save a HyDE document (upsert by sourceType + sourceId + strategy/lensHash + targetCorpus).\n *\n * @param data - HyDE document data\n * @returns The saved HyDE document\n */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /**\n * Delete all HyDE documents for a source (e.g. when intent/profile archived).\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID\n * @returns Number of documents deleted\n */\n deleteHydeDocumentsForSource(\n sourceType: HydeSourceType,\n sourceId: string\n ): Promise<number>;\n\n /**\n * Delete expired HyDE documents (expires_at <= now). Used by maintenance jobs.\n *\n * @returns Number of documents deleted\n */\n deleteExpiredHydeDocuments(): Promise<number>;\n\n /**\n * Get stale HyDE documents for refresh (e.g. createdAt < threshold).\n *\n * @param threshold - Date threshold; documents created before this are considered stale\n * @returns Array of stale HyDE documents\n */\n getStaleHydeDocuments(threshold: Date): Promise<HydeDocument[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (Opportunity Redesign)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Create a new opportunity.\n *\n * @param data - Opportunity creation data\n * @returns The created opportunity\n */\n createOpportunity(data: CreateOpportunityData): Promise<Opportunity>;\n\n /**\n * Get a single opportunity by ID.\n *\n * @param id - Opportunity ID\n * @returns The opportunity or null if not found\n */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /**\n * Get multiple opportunities by ID in a single batched query.\n *\n * Returns rows in arbitrary order; callers should index by `id`.\n * Missing IDs are silently dropped (no error).\n *\n * @param ids - Opportunity IDs (deduplicated by the caller is fine but not required)\n * @returns Opportunities found\n */\n getOpportunitiesByIds(ids: string[]): Promise<Opportunity[]>;\n\n /**\n * Find opportunities that superseded a previous opportunity through enrichment.\n * Uses the existing JSONB `detection.enrichedFrom` array, so no schema-level relation is required.\n * Results are newest-first so callers can choose the newest visible replacement.\n *\n * @param opportunityId - Superseded opportunity ID\n * @returns Replacement opportunities, newest first\n */\n findEnrichedReplacementOpportunities(opportunityId: string): Promise<Opportunity[]>;\n\n /**\n * Resolve an opportunity identifier (full UUID or short prefix) to a full UUID.\n * @param idOrPrefix - Full UUID or short hex prefix\n * @param userId - The user ID (for visibility scoping)\n * @returns Resolved ID, ambiguous marker, or null if not found\n */\n resolveOpportunityId(idOrPrefix: string, userId: string): Promise<{ id: string } | { ambiguous: true } | null>;\n\n /**\n * Get opportunities for a user (as any actor role).\n *\n * @param userId - User ID (actor userId)\n * @param options - Optional filters and pagination\n * @returns Array of opportunities\n */\n getOpportunitiesForUser(\n userId: string,\n options?: OpportunityQueryOptions\n ): Promise<Opportunity[]>;\n\n /**\n * Get opportunities in an index (for index admins).\n *\n * @param networkId - Index ID\n * @param options - Optional filters and pagination\n * @returns Array of opportunities\n */\n getOpportunitiesForNetwork(\n networkId: string,\n options?: OpportunityQueryOptions\n ): Promise<Opportunity[]>;\n\n /**\n * Update an opportunity's status.\n *\n * @param id - Opportunity ID\n * @param status - New status\n * @returns The updated opportunity or null if not found\n */\n updateOpportunityStatus(\n id: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /**\n * Stamp `actedAt` on the actor matching `actorUserId` and update the\n * opportunity's status atomically (row-lock + JSONB merge in one txn).\n *\n * Used by `sendNode` (status → 'pending') and `updateNode` (status →\n * 'accepted'). The self-accept guard is enforced in the caller, not here —\n * this method blindly stamps. Callers must pre-check `actor.actedAt` before\n * invocation when the semantics require it (i.e. accepting).\n *\n * @param id - Opportunity ID\n * @param actorUserId - The user whose actor entry should be stamped\n * @param status - New opportunity status\n * @param acceptedBy - Required when `status === 'accepted'`\n * @returns The updated opportunity, or null if not found\n */\n stampOpportunityActorAction(\n id: string,\n actorUserId: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /**\n * Update the `approved` field on an opportunity's introducer actor.\n * Fetches the opportunity, patches the matching actor in JS, and writes\n * the updated actors JSONB back. Returns the updated opportunity or null.\n */\n updateOpportunityActorApproval(\n id: string,\n introducerUserId: string,\n approved: boolean,\n ): Promise<Opportunity | null>;\n\n /**\n * Create one opportunity and expire others in a single transaction.\n * Atomic: insert then update status to 'expired' for each id in expireIds.\n * Used when enriching replaces overlapping opportunities so subscribers see consistent state.\n *\n * @param data - Opportunity creation data (caller may set status when enriched)\n * @param expireIds - Opportunity IDs to set status to 'expired'\n * @returns The created opportunity and the list of opportunities that were expired\n */\n createOpportunityAndExpireIds(\n data: CreateOpportunityData,\n expireIds: string[]\n ): Promise<{ created: Opportunity; expired: Opportunity[] }>;\n\n /**\n * Check if an opportunity already exists between the given actors in the index (deduplication).\n *\n * @param actorIds - Array of user IDs that would be actors\n * @param networkId - Index ID\n * @returns True if a non-expired opportunity exists with exactly these actors in this index\n */\n opportunityExistsBetweenActors(\n actorIds: string[],\n networkId: string\n ): Promise<boolean>;\n\n /**\n * Find opportunities whose actors contain all the given user IDs.\n *\n * The `includeIntroducers` flag controls actor matching: when false (default), matching\n * is restricted to non-introducer roles; when true, any role in `actors` counts.\n *\n * Index-agnostic. Ordered by updatedAt desc.\n *\n * @param actorIds - User IDs that must all appear in each returned opportunity's actors\n * @param options - includeIntroducers (default false), statuses (include filter), excludeStatuses (exclude filter)\n * @returns Matching opportunities, newest first\n */\n findOpportunitiesByActors(\n actorIds: string[],\n options?: {\n includeIntroducers?: boolean;\n statuses?: OpportunityStatus[];\n excludeStatuses?: OpportunityStatus[];\n }\n ): Promise<Opportunity[]>;\n\n /**\n * Expire opportunities referencing an intent (e.g. when intent is archived).\n *\n * @param intentId - Intent ID to match in opportunity actors\n * @returns Number of opportunities updated to expired\n */\n expireOpportunitiesByIntent(intentId: string): Promise<number>;\n\n /**\n * Expire opportunities for a user removed from an index.\n *\n * @param networkId - Index ID\n * @param userId - User ID that was removed\n * @returns Number of opportunities updated to expired\n */\n expireOpportunitiesForRemovedMember(\n networkId: string,\n userId: string\n ): Promise<number>;\n\n /**\n * Expire opportunities whose expires_at <= now. Used by maintenance cron.\n *\n * @returns Number of opportunities updated to expired\n */\n expireStaleOpportunities(): Promise<number>;\n\n /**\n * Accept all sibling opportunities between the same actor pair in one transaction.\n * Selects opportunities where both userId and counterpartUserId are actors and status\n * is not accepted/expired/rejected, excludes excludeOpportunityId, then bulk-updates status to accepted.\n * Rolls back on any failure.\n *\n * @param userId - First actor user ID\n * @param counterpartUserId - Second actor user ID\n * @param excludeOpportunityId - Opportunity ID to exclude (the one already being accepted)\n * @returns IDs of opportunities that were updated to accepted\n */\n acceptSiblingOpportunities(\n userId: string,\n counterpartUserId: string,\n excludeOpportunityId: string\n ): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Contact / My Network Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create a ghost user (unregistered contact) with empty profile. */\n createGhostUser(data: { name: string; email: string }): Promise<{ id: string }>;\n\n /** Upsert a contact membership in the owner's personal index (index_members with permissions=['contact']). */\n upsertContactMembership(ownerId: string, contactUserId: string, options?: { restore?: boolean }): Promise<void>;\n\n /**\n * Finds an existing DM conversation between two users, or creates one.\n * Uses a unique `dmPair` column (sorted user IDs joined by ':') to\n * prevent duplicate DMs under concurrency. Used by the Start Chat flow\n * (Plan B Task 8) to atomically surface the h2h conversation when\n * accepting an opportunity.\n */\n getOrCreateDM(userA: string, userB: string, participantType?: 'user' | 'agent'): Promise<{ id: string }>;\n\n /**\n * Clears hiddenAt for a user on a conversation, making it visible in their\n * conversation list again. Called by startChat when reusing an existing DM\n * that the user had previously hidden.\n */\n unhideConversation(userId: string, conversationId: string): Promise<void>;\n\n /** Hard-delete a contact membership from the owner's personal index. */\n hardDeleteContactMembership(ownerId: string, contactUserId: string): Promise<void>;\n\n /** Get all contact members from the owner's personal index with user details. */\n getContactMembers(ownerId: string): Promise<Array<{\n userId: string;\n user: { id: string; name: string; email: string; avatar: string | null; isGhost: boolean };\n }>>;\n\n /** Clear a reverse opt-out (reactivate soft-deleted contact membership in another user's personal index). */\n clearReverseOptOut(ownerId: string, otherUserId: string): Promise<void>;\n\n /**\n * Returns the IDs of personal indexes where the given user is a contact member.\n * Used for auto-assigning new intents to personal indexes of contacts who imported this user.\n *\n * @param userId - The user whose contact memberships to look up\n * @returns Array of personal index IDs\n */\n getPersonalIndexesForContact(userId: string): Promise<{ networkId: string }[]>;\n\n /** Find a user by email. */\n getUserByEmail(email: string): Promise<{ id: string; name: string; email: string; isGhost: boolean } | null>;\n\n // ─── Premise operations ──────────────────────────────────────────────────────\n\n createPremise(input: {\n userId: string;\n assertion: PremiseAssertion;\n provenance: PremiseProvenance;\n analysis?: PremiseAnalysis;\n validity: PremiseValidity;\n embedding?: number[];\n }): Promise<PremiseRecord>;\n\n getPremise(premiseId: string): Promise<PremiseRecord | null>;\n\n getPremisesForUser(userId: string, status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED'): Promise<PremiseRecord[]>;\n\n /**\n * Retrieve a user's premises assigned to one of the provided networks.\n * Optional for older/test adapters; OpportunityGraph falls back to capped\n * getPremisesForUser results when unavailable.\n */\n getPremisesForUserInNetworks?(userId: string, networkIds: string[], status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED', limit?: number): Promise<PremiseRecord[]>;\n\n updatePremise(premiseId: string, updates: {\n assertion?: PremiseAssertion;\n analysis?: PremiseAnalysis;\n validity?: PremiseValidity;\n embedding?: number[];\n status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED';\n retractedAt?: Date;\n }): Promise<PremiseRecord>;\n\n assignPremiseToNetwork(premiseId: string, networkId: string, relevancyScore: number): Promise<void>;\n\n getPremiseNetworks(premiseId: string): Promise<Array<{ networkId: string; relevancyScore: number | null }>>;\n\n /**\n * Cosine similarity search against premise embeddings, scoped to shared networks.\n * Used by the opportunity graph's premise discovery path (path D).\n */\n searchPremisesBySimilarity(params: {\n embedding: number[];\n networkIds: string[];\n excludeUserId: string;\n limit: number;\n }): Promise<Array<{\n premiseId: string;\n userId: string;\n networkId: string;\n assertionText: string;\n similarity: number;\n }>>;\n\n /**\n * Batched version of premise similarity search. Executes one bounded DB call\n * for all selected source premises instead of one query per source premise.\n * Optional for older/test adapters; OpportunityGraph falls back to the\n * single-source method when unavailable.\n */\n searchPremisesBySimilarityBatch?(params: {\n sources: Array<{ premiseId: string; embedding: number[] }>;\n networkIds: string[];\n excludeUserId: string;\n limitPerSource: number;\n }): Promise<Array<{\n sourcePremiseId: string;\n premiseId: string;\n userId: string;\n networkId: string;\n assertionText: string;\n similarity: number;\n }>>;\n\n // ─── User Context Methods ───\n\n /**\n * Upsert a user context for a specific network.\n * Creates or updates the synthesized context paragraph + embedding.\n */\n upsertUserContext(params: {\n userId: string;\n networkId: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n }): Promise<{ id: string }>;\n\n /**\n * Get the user context for a specific user+network pair.\n */\n getUserContext(userId: string, networkId: string): Promise<{\n id: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n generatedAt: Date;\n } | null>;\n\n /**\n * Get user contexts for a user across all their networks.\n */\n getUserContexts(userId: string): Promise<Array<{\n id: string;\n networkId: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n generatedAt: Date;\n }>>;\n\n /**\n * Cosine similarity search against intent embeddings using a context embedding.\n * Restores the profile→intent cross-search deleted when Path B was removed.\n */\n searchIntentsByContextEmbedding(params: {\n embedding: number[];\n networkIds: string[];\n excludeUserId: string;\n limit: number;\n minScore?: number;\n }): Promise<Array<{\n intentId: string;\n userId: string;\n networkId: string;\n payload: string;\n summary: string | null;\n similarity: number;\n }>>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// USER DATABASE INTERFACE (Own Resources Only)\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Context-bound database for accessing the authenticated user's own resources.\n * Created with authUserId bound at construction; no userId parameter needed on methods.\n *\n * **NOT index-scoped**: Returns ALL of the user's own resources regardless of index.\n * This is critical for the IntentReconciler which needs the full picture for deduplication.\n *\n * Use via `createUserDatabase(db, authUserId)` factory function.\n */\nexport interface UserDatabase {\n /** The bound authenticated user ID */\n readonly authUserId: string;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (own only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get the authenticated user's profile. */\n getProfile(): Promise<ProfileDocument | null>;\n\n /** Get the authenticated user's profile with row ID. */\n getProfileByUserId(): Promise<(ProfileDocument & { id: string }) | null>;\n\n /** Save/update the authenticated user's profile. */\n saveProfile(profile: ProfileDocument): Promise<void>;\n\n /** Delete the authenticated user's profile. */\n deleteProfile(): Promise<void>;\n\n /** Get the authenticated user's basic record (name, email, socials). */\n getUser(): Promise<UserRecord | null>;\n\n /** Update the authenticated user's account fields. */\n updateUser(data: { name?: string; intro?: string; location?: string; onboarding?: OnboardingState }): Promise<UserRecord | null>;\n\n getUserSocials(): Promise<UserSocial[]>;\n setUserSocials(socials: { label: string; value: string }[]): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Intent Operations (own only, ALL intents - not index-scoped)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get ALL active intents for the authenticated user (not index-filtered). */\n getActiveIntents(): Promise<ActiveIntent[]>;\n\n /**\n * Case-insensitive substring search over the authenticated user's own\n * active intents. Matches against `payload` and `summary`. Most recent first.\n */\n searchOwnIntents(\n q: string,\n limit: number,\n ): Promise<Array<{ id: string; payload: string; summary: string | null; createdAt: Date }>>;\n\n /** Get a single intent by ID (ownership enforced). */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /** Create a new intent for the authenticated user. */\n createIntent(data: Omit<CreateIntentData, 'userId'>): Promise<CreatedIntent>;\n\n /** Update an intent owned by the authenticated user. */\n updateIntent(intentId: string, data: UpdateIntentData): Promise<CreatedIntent | null>;\n\n /** Archive an intent owned by the authenticated user. */\n archiveIntent(intentId: string): Promise<ArchiveResult>;\n\n /** Find similar intents among the user's own intents (for deduplication). */\n findSimilarIntents(embedding: number[], options?: SimilarIntentSearchOptions): Promise<SimilarIntent[]>;\n\n /** Get intent fields for indexing (own intent). */\n getIntentForIndexing(intentId: string): Promise<{\n id: string;\n payload: string;\n userId: string;\n sourceType: string | null;\n sourceId: string | null;\n } | null>;\n\n /** Associate an intent with networks. */\n associateIntentWithNetworks(intentId: string, networkIds: string[]): Promise<void>;\n\n /** Assign an intent to an index. */\n assignIntentToNetwork(intentId: string, networkId: string, relevancyScore?: number): Promise<void>;\n\n /** Unassign an intent from an index. */\n unassignIntentFromIndex(intentId: string, networkId: string): Promise<void>;\n\n /** Get network IDs for an intent. */\n getNetworkIdsForIntent(intentId: string): Promise<string[]>;\n\n /** Check if intent is assigned to index. */\n isIntentAssignedToIndex(intentId: string, networkId: string): Promise<boolean>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Membership Operations (own memberships only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get all index memberships for the authenticated user. */\n getNetworkMemberships(): Promise<NetworkMembership[]>;\n\n /** Get index IDs with auto-assign enabled for the authenticated user. */\n getUserIndexIds(): Promise<string[]>;\n\n /** Get indexes owned by the authenticated user. */\n getOwnedIndexes(): Promise<OwnedIndex[]>;\n\n /** Get a specific index membership for the authenticated user. */\n getNetworkMembership(networkId: string): Promise<NetworkMembership | null>;\n\n /** Get index + member context for the authenticated user (for auto-assign). */\n getNetworkMemberContext(networkId: string): Promise<{\n networkId: string;\n indexPrompt: string | null;\n memberPrompt: string | null;\n } | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index CRUD Operations (owner operations on own indexes)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create a new index (user becomes owner). */\n createNetwork(data: {\n title: string;\n prompt?: string | null;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n }): Promise<{\n id: string;\n title: string;\n prompt: string | null;\n imageUrl: string | null;\n permissions: { joinPolicy: 'anyone' | 'invite_only'; invitationLink: { code: string } | null; allowGuestVibeCheck: boolean };\n }>;\n\n /** Update index settings (owner only). */\n updateIndexSettings(networkId: string, data: UpdateIndexSettingsData): Promise<OwnedIndex>;\n\n /** Soft-delete a network (owner only). */\n softDeleteNetwork(networkId: string): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public Index Discovery (joinable indexes the user is not a member of)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get public indexes (joinPolicy 'anyone') that the user has not joined. */\n getPublicIndexesNotJoined(): Promise<{\n networks: Array<{\n id: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n owner: { id: string; name: string; avatar: string | null } | null;\n }>;\n }>;\n\n /** Join a public index (validates joinPolicy === 'anyone'). */\n joinPublicNetwork(networkId: string): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (where user is actor)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get opportunities where the authenticated user is an actor. */\n getOpportunitiesForUser(options?: OpportunityQueryOptions): Promise<Opportunity[]>;\n\n /** Get a specific opportunity (if user is an actor). */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /** Update an opportunity's status (if user is an actor). acceptedBy is derived from the auth context. */\n updateOpportunityStatus(id: string, status: OpportunityStatus): Promise<Opportunity | null>;\n\n /** Accept sibling opportunities between the authenticated user and another actor. */\n acceptSiblingOpportunities(counterpartUserId: string, excludeOpportunityId: string): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Operations (own sources only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a HyDE document for the user's own source. */\n getHydeDocument(sourceType: HydeSourceType, sourceId: string, strategy: string): Promise<HydeDocument | null>;\n\n /** Get all HyDE documents for the user's own source. */\n getHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<HydeDocument[]>;\n\n /** Save a HyDE document for the user's own source. */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /** Delete HyDE documents for the user's own source. */\n deleteHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<number>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// SYSTEM DATABASE INTERFACE (Cross-User Within Shared Indexes)\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Context-bound database for LLM/system operations that access cross-user resources.\n * Created with authUserId + indexScope[]; validates membership before access.\n *\n * **Index-scoped**: All cross-user operations are restricted to users/resources\n * within the bound indexScope[]. This prevents the LLM from accessing arbitrary users' data.\n *\n * Use via `createSystemDatabase(db, authUserId, indexScope)` factory function.\n */\nexport interface SystemDatabase {\n /** The bound authenticated user ID */\n readonly authUserId: string;\n\n /** The indexes the authenticated user has access to (determines cross-user scope) */\n readonly indexScope: string[];\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (any user in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a user's profile (requires shared index membership). */\n getProfile(userId: string): Promise<ProfileDocument | null>;\n\n /** Get a user's basic record (requires shared index membership). */\n getUser(userId: string): Promise<UserRecord | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Intent Operations (cross-user within shared indexes)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get all intents in an index (cross-user, requires membership). */\n getIntentsInIndex(networkId: string, options?: { limit?: number; offset?: number }): Promise<IndexedIntentDetails[]>;\n\n /** Get a specific user's intents in an index (requires shared membership). */\n getUserIntentsInIndex(userId: string, networkId: string): Promise<ActiveIntent[]>;\n\n /**\n * Get the caller's own active intents across a set of indexes.\n * Returns intents owned by `userId` that are linked (via intent_networks)\n * to at least one of `indexIds`. Used by network-scoped agents to honor\n * indexScope without falling back to global getActiveIntents (which would\n * include intents in indexes outside scope).\n *\n * @param userId - The intent owner (always the caller).\n * @param indexIds - The set of index IDs to filter on. Empty → empty result.\n * @returns Active intents owned by userId in any of indexIds, deduped by intent id.\n */\n getActiveIntentsAcrossIndexes(userId: string, indexIds: string[]): Promise<ActiveIntent[]>;\n\n /** Get a single intent by ID (if in scope). */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /** Find similar intents across users within the index scope. */\n findSimilarIntentsInScope(embedding: number[], options?: SimilarIntentSearchOptions): Promise<SimilarIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Membership Operations (any index in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Check if a user is a member of an index. */\n isNetworkMember(networkId: string, userId: string): Promise<boolean>;\n\n /** Check if a user is an owner of an index. */\n isIndexOwner(networkId: string, userId: string): Promise<boolean>;\n\n /** Get all members of an index (requires membership). */\n getNetworkMembers(networkId: string): Promise<IndexMemberDetails[]>;\n\n /** Get all members across all indexes in scope (deduplicated). */\n getMembersFromScope(): Promise<{ userId: Id<'users'>; name: string; avatar: string | null }[]>;\n\n /** Add a user to an index (requires ownership or 'anyone' policy). */\n addMemberToNetwork(networkId: string, userId: string, role: 'owner' | 'member'): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n /** Remove a user from an index (requires ownership). Cannot remove the owner. */\n removeMemberFromIndex(networkId: string, userId: string): Promise<{ success: boolean; wasOwner?: boolean; notMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Operations (any index in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get index info by ID with core fields (requires scope). */\n getNetwork(networkId: string): Promise<{\n id: string;\n title: string;\n prompt?: string | null;\n type?: string;\n metadata?: Record<string, unknown> | null;\n permissions?: Record<string, unknown> | null;\n } | null>;\n\n /** Get index with permissions (requires scope). */\n getNetworkWithPermissions(networkId: string): Promise<{ id: string; title: string; permissions: { joinPolicy: 'anyone' | 'invite_only' } } | null>;\n\n /** Get member count for an index (requires scope). */\n getNetworkMemberCount(networkId: string): Promise<number>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (cross-user within scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create an opportunity (cross-user). */\n createOpportunity(data: CreateOpportunityData): Promise<Opportunity>;\n\n /** Create opportunity and expire overlapping ones atomically. */\n createOpportunityAndExpireIds(data: CreateOpportunityData, expireIds: string[]): Promise<{ created: Opportunity; expired: Opportunity[] }>;\n\n /** Get an opportunity by ID (for system processing). */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /** Get opportunities for an index (requires membership). */\n getOpportunitiesForNetwork(networkId: string, options?: OpportunityQueryOptions): Promise<Opportunity[]>;\n\n /** Update an opportunity's status (system-level). */\n updateOpportunityStatus(id: string, status: OpportunityStatus, acceptedBy?: string): Promise<Opportunity | null>;\n\n /** Stamp actor `actedAt` + update status atomically (system-level). */\n stampOpportunityActorAction(\n id: string,\n actorUserId: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /** Check if opportunity exists between actors in an index. */\n opportunityExistsBetweenActors(actorIds: string[], networkId: string): Promise<boolean>;\n\n /** Find opportunities by actor IDs with optional include/exclude status filters. */\n findOpportunitiesByActors(\n actorIds: string[],\n options?: { includeIntroducers?: boolean; statuses?: OpportunityStatus[]; excludeStatuses?: OpportunityStatus[] }\n ): Promise<Opportunity[]>;\n\n /** Expire opportunities referencing an intent. */\n expireOpportunitiesByIntent(intentId: string): Promise<number>;\n\n /** Expire opportunities for a removed member. */\n expireOpportunitiesForRemovedMember(networkId: string, userId: string): Promise<number>;\n\n /** Expire stale opportunities (maintenance). */\n expireStaleOpportunities(): Promise<number>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Operations (cross-user for opportunity matching)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a HyDE document (cross-user for matching). */\n getHydeDocument(sourceType: HydeSourceType, sourceId: string, strategy: string): Promise<HydeDocument | null>;\n\n /** Get all HyDE documents for a source (cross-user). */\n getHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<HydeDocument[]>;\n\n /** Save a HyDE document (system-level). */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /** Delete expired HyDE documents (maintenance). */\n deleteExpiredHydeDocuments(): Promise<number>;\n\n /** Get stale HyDE documents for refresh (maintenance). */\n getStaleHydeDocuments(threshold: Date): Promise<HydeDocument[]>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// NARROWED DATABASE INTERFACES (Interface Segregation)\n// ═══════════════════════════════════════════════════════════════════════════════\n//\n// These narrowed types are Pick types from the raw Database interface.\n// They are used by graph factories to enforce interface segregation at compile time.\n//\n// Access control relationship to UserDatabase/SystemDatabase:\n// - ProfileGraphDatabase → maps to UserDatabase (user's own profile operations)\n// - IntentGraphDatabase → maps to UserDatabase (mutations) + SystemDatabase (reads)\n// - OpportunityGraphDatabase → maps to SystemDatabase (cross-user operations)\n// - NetworkGraphDatabase → maps to UserDatabase (own indexes)\n// - IntentNetworkGraphDatabase → maps to both (own intent ↔ shared index)\n// - NetworkMembershipGraphDatabase → maps to SystemDatabase (cross-user)\n// - HydeGraphDatabase → maps to both (own HyDE vs cross-user matching)\n//\n// Graphs continue to use these narrowed types because:\n// 1. They receive the raw database adapter with userId passed per method\n// 2. Access control is enforced at the tool/factory layer via createUserDatabase/createSystemDatabase\n// 3. These types ensure graphs only depend on methods they actually use\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Database interface narrowed for Profile Graph operations.\n * Provides full profile lifecycle: read, write, and query mode.\n *\n * Access layer: Primarily UserDatabase (user's own profile)\n */\nexport type ProfileGraphDatabase = Pick<\n Database,\n 'getProfile' | 'getUser' | 'updateUser' | 'saveProfile' | 'getProfileByUserId' | 'softDeleteGhost' | 'findDuplicateUser' | 'mergeGhostUser' | 'getUserSocials' | 'setUserSocials' | 'getPremisesForUser'\n>;\n\n/**\n * Database interface narrowed for Premise Graph operations.\n * Provides premise lifecycle: create, read, update, and network assignment.\n *\n * Access layer: UserDatabase (user's own premises)\n */\nexport type PremiseGraphDatabase = Pick<\n Database,\n 'createPremise' | 'getPremise' | 'getPremisesForUser' | 'updatePremise' | 'assignPremiseToNetwork' | 'getPremiseNetworks' | 'getUserIndexIds' | 'getNetwork' | 'getNetworkMemberContext'\n>;\n\n/**\n * Composite database interface for Chat Graph.\n * Includes direct ChatGraph operations plus all methods needed by\n * internally composed subgraphs (ProfileGraph, OpportunityGraph, IntentGraph, NetworkGraph).\n *\n * Use this type when ChatGraph orchestrates subgraphs internally.\n *\n * Access layer: Both UserDatabase + SystemDatabase (orchestrates all operations)\n */\nexport type ChatGraphCompositeDatabase = Pick<\n Database,\n // Direct ChatGraph operations\n | 'getProfile'\n | 'getActiveIntents'\n | 'getActiveIntentsAcrossIndexes'\n | 'getIntentsInIndexForMember'\n // ProfileGraph subgraph requirements\n | 'getUser'\n | 'updateUser'\n | 'getUserSocials'\n | 'setUserSocials'\n | 'saveProfile'\n | 'softDeleteGhost'\n // IntentGraph subgraph requirements (getActiveIntents already included)\n | 'createIntent'\n | 'updateIntent'\n | 'archiveIntent'\n // OpportunityGraph subgraph requirements (getProfile already included)\n | 'createOpportunity'\n | 'getOpportunity'\n | 'getOpportunitiesByIds'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'getOpportunitiesForUser'\n | 'updateOpportunityStatus'\n | 'updateOpportunityActorApproval'\n | 'stampOpportunityActorAction'\n | 'getOrCreateDM'\n // HyDE graph (used by OpportunityGraph)\n | 'getHydeDocument'\n | 'getHydeDocumentsForSource'\n | 'saveHydeDocument'\n | 'getIntent'\n // NetworkGraph subgraph requirements (index created intents in user's indexes)\n | 'getPublicIndexesNotJoined'\n | 'getUserIndexIds'\n | 'getNetworkMemberships'\n | 'getNetworkMembership'\n | 'getNetwork'\n | 'getNetworkWithPermissions'\n | 'getIntentForIndexing'\n | 'getNetworkMemberContext'\n | 'isIntentAssignedToIndex'\n | 'assignIntentToNetwork'\n | 'unassignIntentFromIndex'\n | 'getNetworkIdsForIntent'\n | 'getIntentIndexScores'\n // Personal index auto-assignment (used by intent graph executor)\n | 'getPersonalIndexesForContact'\n // Index Ownership Operations (owner-only)\n | 'getOwnedIndexes'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getNetworkMembersForOwner'\n | 'getNetworkMembersForMember'\n | 'getMembersFromUserIndexes'\n | 'getNetworkIntentsForOwner'\n | 'getNetworkIntentsForMember'\n | 'updateIndexSettings'\n | 'softDeleteNetwork'\n | 'deleteProfile'\n | 'getProfileByUserId'\n | 'createNetwork'\n | 'getNetworkMemberCount'\n | 'addMemberToNetwork'\n | 'removeMemberFromIndex'\n // ProfileGraph post-enrichment ghost deduplication\n | 'findDuplicateUser'\n | 'mergeGhostUser'\n // ProfileGraph aggregate mode (premise-to-profile materialization)\n | 'getPremisesForUser'\n | 'getPremisesForUserInNetworks'\n // Premise-to-premise discovery (path D) in OpportunityGraph\n | 'searchPremisesBySimilarity'\n | 'searchPremisesBySimilarityBatch'\n // User context methods (context-to-intent discovery) in OpportunityGraph\n | 'getUserContext'\n | 'getUserContexts'\n | 'searchIntentsByContextEmbedding'\n> & Pick<\n NegotiationQueries,\n // Orphan heal in OpportunityGraph persist node\n | 'getNegotiationTaskForOpportunity'\n>;\n\n/**\n * Database interface for Opportunity Graph operations.\n * Includes prep/scope (index membership, intents, index details), persist (create, dedupe),\n * and CRUD operations (read, update status, send).\n *\n * Access layer: SystemDatabase (cross-user opportunity operations)\n */\nexport type OpportunityGraphDatabase = Pick<\n Database,\n | 'getProfile'\n | 'createOpportunity'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'getUserIndexIds'\n | 'getNetworkMemberships'\n | 'getActiveIntents'\n | 'getNetworkIdsForIntent'\n | 'getNetwork'\n | 'getNetworkMemberCount'\n | 'getIntentIndexScores'\n | 'getNetworkMemberContext'\n // Read/update/send modes\n | 'getOpportunity'\n | 'getOpportunitiesForUser'\n | 'updateOpportunityStatus'\n | 'stampOpportunityActorAction'\n | 'updateOpportunityActorApproval'\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getUser'\n | 'getOrCreateDM'\n // Load candidate intent payload/summary for evaluator\n | 'getIntent'\n // Premise-to-premise discovery (path D)\n | 'getPremisesForUser'\n | 'getPremisesForUserInNetworks'\n | 'searchPremisesBySimilarity'\n | 'searchPremisesBySimilarityBatch'\n // User context methods (context-to-intent discovery)\n | 'getUserContext'\n | 'getUserContexts'\n | 'searchIntentsByContextEmbedding'\n // HyDE documents for context-to-intent HyDE search\n | 'getHydeDocumentsForSource'\n> & Pick<\n NegotiationQueries,\n // Orphan heal: check if a prior negotiating opportunity has a stale task\n | 'getNegotiationTaskForOpportunity'\n>;\n\n/**\n * Negotiation-specific query operations not covered by generic\n * conversation/task primitives.\n */\n/** A user's answer to a questioner-generated question, stored on opportunity metadata. */\nexport interface NegotiationUserAnswer {\n questionId: string;\n selectedOptions: string[];\n freeText?: string;\n answeredAt: string;\n}\n\nexport interface NegotiationQueries {\n /**\n * Persists the full negotiation turn context (source/candidate user contexts,\n * seed assessment, index context, discovery query) onto the task metadata so\n * that polling agents can reconstruct the same context the system agent sees\n * in-process. Merges into `metadata.turnContext`, leaving other keys intact.\n * @param taskId - Task whose metadata to enrich\n * @param turnContext - Absolute (source/candidate) view of the negotiation context\n */\n setTaskTurnContext(taskId: string, turnContext: Record<string, unknown>): Promise<void>;\n\n /**\n * Returns the most-recently-created task whose metadata carries\n * `type: 'negotiation'` and `opportunityId: <id>`. Returns null if no\n * negotiation has been started for that opportunity yet.\n */\n getNegotiationTaskForOpportunity(opportunityId: string): Promise<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n } | null>;\n\n /**\n * Returns user answers collected by the questioner system for a given\n * opportunity. Reads `metadata.userAnswers` from the opportunities table.\n * Used by the negotiation graph to inject between-session context into\n * continuation prompts.\n */\n getOpportunityUserAnswers(opportunityId: string): Promise<NegotiationUserAnswer[]>;\n}\n\n/**\n * Database dependency for the negotiation graph (A2A conversation/task/artifact\n * persistence). Composes generic conversation ops with negotiation-specific queries.\n *\n * Access layer: ConversationDatabaseAdapter\n */\nexport type NegotiationGraphDatabase = Pick<\n Database,\n | 'getOrCreateDM'\n> & NegotiationQueries & {\n /**\n * Update the status of an opportunity. Called from the negotiation graph to\n * advance the opportunity lifecycle (negotiating -> pending/rejected/stalled).\n * Returns only the narrow { id, status } needed by the graph, not the full Opportunity.\n */\n updateOpportunityStatus(\n id: string,\n status: OpportunityStatus,\n ): Promise<{ id: string; status: OpportunityStatus } | null>;\n /** Persists a negotiation turn message within a conversation. */\n createMessage(data: {\n conversationId: string;\n senderId: string;\n role: 'user' | 'agent';\n parts: unknown[];\n taskId?: string;\n metadata?: Record<string, unknown> | null;\n }): Promise<{ id: string; senderId: string; role: 'user' | 'agent'; parts: unknown; createdAt: Date }>;\n\n /** Creates a task to track the negotiation lifecycle within a conversation. */\n createTask(conversationId: string, metadata?: Record<string, unknown>): Promise<{ id: string; conversationId: string; state: string }>;\n\n /** Transitions a task to a new state (e.g. working, completed, failed). */\n updateTaskState(taskId: string, state: string, statusMessage?: unknown): Promise<{ id: string; conversationId: string; state: string }>;\n\n /** Persists a negotiation outcome artifact attached to a task. */\n createArtifact(data: { taskId: string; name?: string; parts: unknown[]; metadata?: Record<string, unknown> | null }): Promise<{ id: string }>;\n\n /** Lists negotiation tasks where the given user is source or candidate. */\n getTasksForUser(userId: string, options?: { state?: string }): Promise<Array<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n }>>;\n\n /** Gets a specific task by ID. */\n getTask(taskId: string): Promise<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n } | null>;\n\n /** Gets all messages for a conversation, ordered by creation time. */\n getMessagesForConversation(conversationId: string): Promise<Array<{\n id: string;\n senderId: string;\n role: 'user' | 'agent';\n parts: unknown[];\n createdAt: Date;\n }>>;\n\n /** Gets artifacts for a task (e.g. negotiation outcome). */\n getArtifactsForTask(taskId: string): Promise<Array<{\n id: string;\n name: string | null;\n parts: unknown[];\n metadata: Record<string, unknown> | null;\n }>>;\n};\n\n/**\n * Database interface for opportunity controller (API).\n *\n * Access layer: Both UserDatabase + SystemDatabase (API handles auth)\n */\nexport type OpportunityControllerDatabase = Pick<\n Database,\n | 'getOpportunity'\n | 'getOpportunitiesByIds'\n | 'findEnrichedReplacementOpportunities'\n | 'getOpportunitiesForUser'\n | 'getOpportunitiesForNetwork'\n | 'resolveOpportunityId'\n | 'updateOpportunityStatus'\n | 'createOpportunity'\n | 'createOpportunityAndExpireIds'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'acceptSiblingOpportunities'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getUser'\n | 'getNetwork'\n | 'getNetworkMemberships'\n | 'getProfile'\n | 'getActiveIntents'\n | 'upsertContactMembership'\n // Start Chat endpoint (Plan B Task 8): atomic pair → conversation resolution\n // for the \"Open h2h chat from this opportunity\" flow. Kept on this interface\n // (rather than ConversationControllerDatabase) because the transition is\n // owned by OpportunityService — services cannot import other services.\n | 'getOrCreateDM'\n | 'unhideConversation'\n // Approve-introduction endpoint: flip introducer actor's approved flag.\n | 'updateOpportunityActorApproval'\n // Self-accept guard + actedAt stamping on service-layer status flips.\n | 'stampOpportunityActorAction'\n>;\n\n/**\n * Database interface narrowed for Intent Graph operations.\n * Provides state population (getActiveIntents), action execution (create/update/archive),\n * and read operations (query intents; getIntentsInIndexForMember for index-scoped reads).\n *\n * Access layer: UserDatabase (mutations on own intents) + SystemDatabase (index-scoped reads)\n */\nexport type IntentGraphDatabase = Pick<\n Database,\n | 'getActiveIntents'\n | 'getActiveIntentsAcrossIndexes'\n | 'getIntentsInIndexForMember'\n | 'createIntent'\n | 'updateIntent'\n | 'archiveIntent'\n // Read mode (queryNode) requirements\n | 'isNetworkMember'\n | 'getNetworkIntentsForMember'\n | 'getUser'\n // Profile check (prepNode gate for write operations)\n | 'getProfile'\n // Personal index auto-assignment\n | 'getPersonalIndexesForContact'\n | 'assignIntentToNetwork'\n>;\n\n/**\n * Database interface narrowed for Index Graph CRUD operations.\n * Handles create, read, update, delete of indexes (communities).\n *\n * Access layer: UserDatabase (CRUD on own indexes and memberships)\n */\nexport type NetworkGraphDatabase = Pick<\n Database,\n | 'getNetworkMemberships'\n | 'getOwnedIndexes'\n | 'getPublicIndexesNotJoined'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getNetwork'\n | 'createNetwork'\n | 'addMemberToNetwork'\n | 'updateIndexSettings'\n | 'softDeleteNetwork'\n | 'getNetworkMemberCount'\n>;\n\n/**\n * Database interface narrowed for Intent Index Graph operations.\n * Provides intent/index context and assignment for intent–index evaluation.\n * (Migrated from the old NetworkGraphDatabase.)\n *\n * Access layer: UserDatabase (own intent assignment) + SystemDatabase (index context)\n */\nexport type IntentNetworkGraphDatabase = Pick<\n Database,\n | 'getIntentForIndexing'\n | 'getNetworkMemberContext'\n | 'getNetwork'\n | 'isIntentAssignedToIndex'\n | 'assignIntentToNetwork'\n | 'unassignIntentFromIndex'\n | 'getIntent'\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getNetworkIdsForIntent'\n | 'getNetworkIntentsForMember'\n | 'getIntentsInIndexForMember'\n>;\n\n/**\n * Database interface narrowed for Index Membership Graph operations.\n * Handles CRUD for index memberships (add, list, remove members).\n *\n * Access layer: SystemDatabase (cross-user membership operations)\n */\nexport type NetworkMembershipGraphDatabase = Pick<\n Database,\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getNetworkWithPermissions'\n | 'addMemberToNetwork'\n | 'removeMemberFromIndex'\n | 'getNetworkMembersForMember'\n>;\n\n/**\n * Database interface narrowed for HyDE Graph operations.\n * Provides HyDE document CRUD and intent lookup for refresh.\n *\n * Access layer: UserDatabase (own HyDE) + SystemDatabase (cross-user matching)\n */\nexport type HydeGraphDatabase = Pick<\n Database,\n 'getHydeDocument' | 'getHydeDocumentsForSource' | 'saveHydeDocument' | 'getIntent'\n>;\n\n/**\n * Database interface for Home Graph (opportunity home view).\n * Load opportunities, enrich with profile/index, and support presenter context.\n *\n * Access layer: UserDatabase (own opportunities and profile)\n */\nexport type HomeGraphDatabase = Pick<\n Database,\n | 'getOpportunitiesForUser'\n | 'getOpportunity'\n | 'getProfile'\n | 'getActiveIntents'\n | 'getNetwork'\n | 'getUser'\n> & Pick<\n NegotiationGraphDatabase,\n | 'getNegotiationTaskForOpportunity'\n | 'getMessagesForConversation'\n | 'getArtifactsForTask'\n>;\n"]}
|
|
1
|
+
{"version":3,"file":"database.interface.js","sourceRoot":"/","sources":["shared/interfaces/database.interface.ts"],"names":[],"mappings":"","sourcesContent":["import type { ProfileDocument } from '../schemas/profile.schema.js';\n\n// ─── Inlined types (previously imported from outside the protocol lib) ───────\n\n/** Branded string ID for type-safe entity references (keyed by Drizzle table name). */\nexport type Id<T extends string = string> = string & { readonly __table?: T };\n\nexport type PrivacyConsentSource = 'agentvillage_onboarding' | 'hermes_setup' | 'web_onboarding' | 'api';\n\nexport interface PrivacyConsentDecision {\n granted: boolean;\n decidedAt: string;\n source: PrivacyConsentSource;\n}\n\nexport interface OnboardingPrivacyState {\n edgeosImport?: PrivacyConsentDecision;\n publicProfileLookup?: PrivacyConsentDecision;\n}\n\nexport interface OnboardingProfileSeed {\n source: 'experiment_signup' | 'experiment_csv_import';\n networkId: string;\n capturedAt: string;\n name?: string;\n bio?: string;\n location?: string;\n socials?: { label: string; value: string }[];\n}\n\n/** Onboarding flow state stored as JSON on the user record. */\nexport interface OnboardingState {\n completedAt?: string;\n flow?: 1 | 2 | 3;\n currentStep?: 'profile' | 'summary' | 'connections' | 'create_network' | 'invite_members' | 'join_networks';\n networkId?: string;\n invitationCode?: string;\n privacy?: OnboardingPrivacyState;\n profileSeeds?: OnboardingProfileSeed[];\n}\n\n/** Single social-link row from the user_socials table. */\nexport interface UserSocial {\n id: string;\n userId: string;\n label: string;\n value: string;\n}\n\n/** Detection metadata recorded when an opportunity is created. */\nexport interface OpportunityDetection {\n source: 'opportunity_graph' | 'chat' | 'manual' | 'cron' | 'member_added' | 'enrichment' | 'introducer_discovery';\n createdBy?: Id<'users'> | string;\n createdByName?: string;\n triggeredBy?: Id<'intents'>;\n timestamp: string;\n enrichedFrom?: string[];\n}\n\n/** A participant (user + network) involved in an opportunity. */\nexport interface OpportunityActor {\n networkId: Id<'networks'>;\n userId: Id<'users'>;\n intent?: Id<'intents'>;\n /** Which premise grounded this match (set when discoverySource is 'premise-similarity'). */\n premise?: Id<'premises'>;\n role: string;\n /** Only set on role === 'introducer'. false until the introducer explicitly approves; true after approval. */\n approved?: boolean;\n /**\n * ISO-8601 timestamp set the first time this actor advanced the opportunity's\n * state (patient sending, agent accepting, peer \"accepting\" on draft = sending\n * under the hood, peer accepting on pending, introducer sending). Once set,\n * this actor has committed and cannot be the one to subsequently `accept` the\n * same opportunity — enforced by the self-accept guard in `updateNode`.\n */\n actedAt?: string;\n}\n\n/** Individual signal contributing to an opportunity score. */\nexport interface OpportunitySignal {\n type: string;\n weight: number;\n detail?: string;\n}\n\n/** LLM-generated interpretation of an opportunity's category and confidence. */\nexport interface OpportunityInterpretation {\n category: string;\n reasoning: string;\n confidence: number;\n signals?: OpportunitySignal[];\n}\n\n/** Optional scoping context (network / conversation) for an opportunity. */\nexport interface OpportunityContext {\n networkId?: Id<'networks'>;\n conversationId?: Id<'conversations'>;\n}\n\n/** User record returned by getUser (minimal fields plus optional profile fields). */\nexport interface UserRecord {\n id: string;\n name: string;\n email: string;\n intro?: string | null;\n avatar?: string | null;\n location?: string | null;\n socials: UserSocial[];\n onboarding?: OnboardingState | null;\n isGhost?: boolean;\n deletedAt?: Date | null;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// INTENT TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Minimal intent representation used for graph state population.\n * Contains only the fields needed for reconciliation logic.\n */\nexport interface ActiveIntent {\n /** Unique identifier of the intent */\n id: string;\n /** Full intent description/payload */\n payload: string;\n /** Short summary of the intent (may be null if not generated) */\n summary: string | null;\n /** When the intent was created */\n createdAt: Date;\n /** Relevancy score for this intent in its index context (0.0–1.0, null if not scored) */\n relevancyScore?: number | null;\n}\n\n/**\n * Input data for creating a new intent.\n * Supports the full intent pipeline including embedding and index association.\n */\nexport interface CreateIntentData {\n /** The user who owns this intent */\n userId: string;\n /** Full intent description/payload */\n payload: string;\n /** Pre-computed summary (optional, will be generated if not provided) */\n summary?: string | null;\n /** Pre-computed embedding vector (optional, will be generated if not provided) */\n embedding?: number[];\n /** Whether the intent should be hidden from public views */\n isIncognito?: boolean;\n /** Index IDs to associate with (optional, uses dynamic scoping if empty) */\n networkIds?: string[];\n /** Source type for provenance tracking */\n sourceType?: 'file' | 'integration' | 'link' | 'discovery_form' | 'enrichment';\n /** Source ID for provenance tracking */\n sourceId?: string;\n /** Confidence score from inference (0-1, required) */\n confidence: number;\n /** How the intent was inferred */\n inferenceType: 'explicit' | 'implicit';\n /** Semantic entropy from verifier (0 specific -> 1 vague) */\n semanticEntropy?: number | null;\n /** Referential anchor extracted by verifier (if any) */\n referentialAnchor?: string | null;\n /** Felicity authority score from verifier (0-100) */\n felicityAuthority?: number | null;\n /** Felicity sincerity score from verifier (0-100) */\n felicitySincerity?: number | null;\n /** Felicity clarity score from verifier (0-100) */\n felicityClarity?: number | null;\n /** Donnellan intent mode */\n intentMode?: 'REFERENTIAL' | 'ATTRIBUTIVE' | null;\n /** Speech act category used by protocol enum */\n speechActType?: 'COMMISSIVE' | 'DIRECTIVE' | null;\n}\n\n/**\n * Input data for updating an existing intent.\n * All fields are optional - only provided fields will be updated.\n */\nexport interface UpdateIntentData {\n /** Updated intent description/payload */\n payload?: string;\n /** Updated summary */\n summary?: string | null;\n /** Updated embedding vector */\n embedding?: number[];\n /** Updated incognito status */\n isIncognito?: boolean;\n /** Updated index associations (replaces existing) */\n networkIds?: string[];\n /** Semantic entropy from verifier (0 specific -> 1 vague) */\n semanticEntropy?: number | null;\n /** Referential anchor extracted by verifier (if any) */\n referentialAnchor?: string | null;\n /** Felicity authority score from verifier (0-100) */\n felicityAuthority?: number | null;\n /** Felicity sincerity score from verifier (0-100) */\n felicitySincerity?: number | null;\n /** Felicity clarity score from verifier (0-100) */\n felicityClarity?: number | null;\n /** Donnellan intent mode */\n intentMode?: 'REFERENTIAL' | 'ATTRIBUTIVE' | null;\n /** Speech act category used by protocol enum */\n speechActType?: 'COMMISSIVE' | 'DIRECTIVE' | null;\n}\n\n/**\n * The result of a successful intent creation.\n * Contains the core fields needed for immediate use.\n */\nexport interface CreatedIntent {\n /** Unique identifier of the created intent */\n id: string;\n /** Full intent description/payload */\n payload: string;\n /** Generated or provided summary */\n summary: string | null;\n /** Incognito status */\n isIncognito: boolean;\n /** Creation timestamp */\n createdAt: Date;\n /** Last update timestamp */\n updatedAt: Date;\n /** Owner user ID */\n userId: string;\n}\n\n/**\n * Full intent record with all fields (for detailed queries).\n */\nexport interface IntentRecord extends CreatedIntent {\n /** Archival timestamp (null if active) */\n archivedAt: Date | null;\n /** Embedding vector (may be null) */\n embedding?: number[] | null;\n /** Source type for provenance */\n sourceType?: string | null;\n /** Source ID for provenance */\n sourceId?: string | null;\n}\n\n/**\n * Intent with similarity score from vector search.\n */\nexport interface SimilarIntent extends IntentRecord {\n /** Cosine similarity score (0-1) */\n similarity: number;\n}\n\n/**\n * Result of an archive operation.\n */\nexport interface ArchiveResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** Error message if failed */\n error?: string;\n}\n\n/**\n * Options for vector similarity search.\n */\nexport interface SimilarIntentSearchOptions {\n /** Maximum number of results to return (default: 10) */\n limit?: number;\n /** Minimum similarity threshold (default: 0.7) */\n threshold?: number;\n}\n\n/**\n * Represents a user's membership in an index with full details.\n * Used for displaying index memberships in chat (index_query).\n */\nexport interface NetworkMembership {\n /** Unique identifier of the index */\n networkId: string;\n /** Display title of the index */\n networkTitle: string;\n /** Index description/prompt (what the community is about) */\n indexPrompt: string | null;\n /** Member's permissions in this index */\n permissions: string[];\n /** Member's custom prompt (overrides index prompt for their intents) */\n memberPrompt: string | null;\n /** Whether new intents are auto-assigned to this index */\n autoAssign: boolean;\n /** Whether this is the user's personal index (\"My Network\") */\n isPersonal: boolean;\n /** When the user joined the index */\n joinedAt: Date;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// PREMISE TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport interface PremiseAssertion {\n text: string;\n tier: 'assertive' | 'contextual';\n summary?: string;\n}\n\nexport interface PremiseProvenance {\n source: 'explicit' | 'enrichment' | 'integration' | 'onboarding';\n sourceId?: string;\n confidence: number;\n timestamp: string;\n}\n\nexport interface PremiseAnalysis {\n speechActType: 'DECLARATIVE' | 'ASSERTIVE';\n felicityAuthority: number;\n felicitySincerity: number;\n felicityClarity: number;\n semanticEntropy: number;\n}\n\nexport interface PremiseValidity {\n validFrom?: string;\n validUntil?: string;\n volatile: boolean;\n}\n\nexport interface PremiseRecord {\n id: string;\n userId: string;\n assertion: PremiseAssertion;\n provenance: PremiseProvenance;\n analysis: PremiseAnalysis | null;\n validity: PremiseValidity;\n embedding: number[] | null;\n status: 'ACTIVE' | 'RETRACTED' | 'EXPIRED';\n createdAt: Date;\n updatedAt: Date;\n retractedAt: Date | null;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// INDEX OWNERSHIP TYPES\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Represents an index owned by the user with full details.\n */\nexport interface OwnedIndex {\n /** Index ID */\n id: string;\n /** Display title */\n title: string;\n /** Index purpose/scope prompt */\n prompt: string | null;\n /** Cover image URL */\n imageUrl: string | null;\n /** Permission settings */\n permissions: {\n joinPolicy: 'anyone' | 'invite_only';\n allowGuestVibeCheck: boolean;\n invitationLink: { code: string } | null;\n };\n /** Whether this is a personal index */\n isPersonal: boolean;\n /** When the index was created */\n createdAt: Date;\n /** When the index was last updated */\n updatedAt: Date;\n /** Member count */\n memberCount: number;\n /** Total intents indexed */\n intentCount: number;\n /** Owner summary */\n user: { id: string; name: string; avatar: string | null };\n /** Aggregate counts for frontend compatibility */\n _count: { members: number };\n}\n\n/**\n * Member details visible to index owners (and optionally to members with privacy rules).\n */\nexport interface IndexMemberDetails {\n /** User ID */\n userId: string;\n /** User's display name */\n name: string;\n /** User's avatar URL */\n avatar: string | null;\n /** User's email; only present when viewer is owner/admin or the member themselves (privacy-safe) */\n email?: string | null;\n /** Member's permissions in this index */\n permissions: string[];\n /** Member's custom prompt */\n memberPrompt: string | null;\n /** Whether auto-assign is enabled */\n autoAssign: boolean;\n /** When they joined */\n joinedAt: Date;\n /** Count of their intents in this index */\n intentCount: number;\n /** Whether this user is a ghost (not yet onboarded) */\n isGhost?: boolean;\n}\n\n/**\n * Intent details visible to index owners.\n */\nexport interface IndexedIntentDetails {\n /** Intent ID */\n id: string;\n /** Intent payload/description */\n payload: string;\n /** Intent summary */\n summary: string | null;\n /** Owner's user ID */\n userId: string;\n /** Owner's name */\n userName: string;\n /** When the intent was created */\n createdAt: Date;\n /** Relevancy score for this intent in its index context (0.0–1.0, null if not scored) */\n relevancyScore?: number | null;\n}\n\n/**\n * Options for updating index settings.\n */\nexport interface UpdateIndexSettingsData {\n /** New title (optional) */\n title?: string;\n /** New prompt (optional) */\n prompt?: string | null;\n /** New image URL (optional) */\n imageUrl?: string | null;\n /** New join policy (optional) */\n joinPolicy?: 'anyone' | 'invite_only';\n /** Allow guest vibe check (optional) */\n allowGuestVibeCheck?: boolean;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// HYDE DOCUMENT TYPES (Opportunity Redesign)\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport type HydeSourceType = 'intent' | 'profile' | 'query' | 'context';\n\nexport interface HydeDocument {\n id: string;\n sourceType: HydeSourceType;\n sourceId: string | null;\n sourceText: string | null;\n strategy: string;\n targetCorpus: string;\n hydeText: string;\n hydeEmbedding: number[];\n context: Record<string, unknown> | null;\n createdAt: Date;\n expiresAt: Date | null;\n}\n\nexport interface CreateHydeDocumentData {\n sourceType: HydeSourceType;\n sourceId?: string;\n sourceText?: string;\n strategy: string;\n targetCorpus: string;\n hydeText: string;\n hydeEmbedding: number[];\n context?: Record<string, unknown>;\n expiresAt?: Date;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// OPPORTUNITY TYPES (Opportunity Redesign)\n// ═══════════════════════════════════════════════════════════════════════════════\n\nexport type OpportunityStatus = 'latent' | 'draft' | 'negotiating' | 'pending' | 'stalled' | 'accepted' | 'rejected' | 'expired';\n\nexport interface Opportunity {\n id: string;\n detection: OpportunityDetection;\n actors: OpportunityActor[];\n interpretation: OpportunityInterpretation;\n context: OpportunityContext;\n confidence: string;\n status: OpportunityStatus;\n createdAt: Date;\n updatedAt: Date;\n expiresAt: Date | null;\n}\n\nexport interface CreateOpportunityData {\n detection: OpportunityDetection;\n actors: OpportunityActor[];\n interpretation: OpportunityInterpretation;\n context: OpportunityContext;\n confidence: string;\n status?: OpportunityStatus;\n expiresAt?: Date;\n}\n\nexport interface OpportunityQueryOptions {\n status?: OpportunityStatus;\n /** When set, filter to opportunities whose status is in this list. Orthogonal to `status` (single) — callers pick one. */\n statuses?: OpportunityStatus[];\n networkId?: string;\n role?: string;\n limit?: number;\n offset?: number;\n /** When set, include draft opportunities for this chat session. When unset, exclude all draft opportunities (e.g. home view, API). */\n conversationId?: string;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// DATABASE INTERFACE\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Abstract database interface for performing specific domain operations.\n * Decouples the protocol layer from the infrastructure layer.\n */\nexport interface Database {\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (Preserved)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves a user profile by userId.\n * @param userId - The unique identifier of the user\n * @returns The user's profile or null if not found\n */\n getProfile(userId: string): Promise<ProfileDocument | null>;\n\n /**\n * Creates or updates a user profile.\n * @param userId - The unique identifier of the user\n * @param profile - The profile data to save\n */\n saveProfile(userId: string, profile: ProfileDocument): Promise<void>;\n\n /**\n * Retrieves basic user information (name, email, socials) by userId.\n * @param userId - The unique identifier of the user\n * @returns The user record or null if not found\n */\n getUser(userId: string): Promise<UserRecord | null>;\n\n /**\n * Updates user account fields (name, location, socials).\n * Merges socials with existing values (does not overwrite the whole object).\n * Used by create_user_profile tool to persist user-provided info before\n * invoking the Profile Graph in generate mode.\n *\n * @param userId - The unique identifier of the user\n * @param data - Partial user fields to update\n * @returns The updated user record or null if not found\n */\n updateUser(userId: string, data: { name?: string; intro?: string; location?: string; onboarding?: OnboardingState }): Promise<UserRecord | null>;\n\n getUserSocials(userId: string): Promise<UserSocial[]>;\n setUserSocials(userId: string, socials: { label: string; value: string }[]): Promise<void>;\n\n /**\n * Soft-delete a ghost user and all their contact memberships.\n * Used when enrichment determines the entity is not a real person.\n * @param userId - The ghost user to soft-delete\n * @returns true if the user was soft-deleted\n */\n softDeleteGhost(userId: string): Promise<boolean>;\n\n /**\n * Find an existing user that matches the given social handles.\n * Checks LinkedIn, GitHub, and Twitter/X handles (case-insensitive, exact match).\n * Excludes the given userId and soft-deleted users.\n * Prefers real users over ghosts; among ghosts, returns the oldest.\n * @param userId - The ghost user being enriched (excluded from results)\n * @param socials - Enriched social handles to match against\n * @returns The matching user's id, or null if no match\n */\n findDuplicateUser(userId: string, socials: UserSocial[]): Promise<{ id: string } | null>;\n\n /**\n * Merge a ghost user (source) into a target user.\n * Re-points all data (intents, opportunities, memberships, etc.) from source to target,\n * deletes ghost-only records (profile, sessions, etc.), and soft-deletes the source user.\n * Runs in a single transaction.\n * @param sourceId - The ghost user to merge away\n * @param targetId - The user to merge into\n */\n mergeGhostUser(sourceId: string, targetId: string): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Pre-Graph Operations (State Population)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves all active (non-archived) intents for a user.\n * Used to populate the `activeIntents` field in the Intent Graph state\n * before graph execution.\n *\n * @param userId - The unique identifier of the user\n * @returns Array of active intents with minimal fields needed for reconciliation\n *\n * @example\n * ```typescript\n * const activeIntents = await db.getActiveIntents(userId);\n * const formattedIntents = activeIntents\n * .map(i => `ID: ${i.id}, Description: ${i.payload}, Summary: ${i.summary || 'N/A'}`)\n * .join('\\n');\n * ```\n */\n getActiveIntents(userId: string): Promise<ActiveIntent[]>;\n\n /**\n * Get active intents that belong to the user and are assigned to a specific index.\n * Caller must be a member of that index; only the user's own intents are returned.\n *\n * @param userId - The user requesting (must be a member of the index)\n * @param indexNameOrId - Index UUID or display name (e.g. \"Commons\")\n * @returns Array of active intents in that index for the user, or empty if not a member / no match\n */\n getIntentsInIndexForMember(userId: string, indexNameOrId: string): Promise<ActiveIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Post-Graph Operations (Action Execution)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Creates a new intent with full processing pipeline.\n * Handles summarization, embedding generation, and index association.\n *\n * Called when the reconciler outputs a \"create\" action.\n *\n * @param data - The intent creation data\n * @returns The created intent with generated fields\n *\n * @example\n * ```typescript\n * // After graph outputs CREATE action\n * const newIntent = await db.createIntent({\n * userId,\n * payload: action.payload,\n * confidence: action.score / 100,\n * inferenceType: 'explicit',\n * sourceType: 'discovery_form'\n * });\n * ```\n */\n createIntent(data: CreateIntentData): Promise<CreatedIntent>;\n\n /**\n * Updates an existing intent.\n * Re-generates summary and embedding if payload changes.\n *\n * Called when the reconciler outputs an \"update\" action.\n *\n * @param intentId - The unique identifier of the intent to update\n * @param data - The fields to update\n * @returns The updated intent or null if not found\n * @throws Error if the intent exists but user doesn't have access\n *\n * @example\n * ```typescript\n * // After graph outputs UPDATE action\n * const updated = await db.updateIntent(action.id, {\n * payload: action.payload\n * });\n * ```\n */\n updateIntent(intentId: string, data: UpdateIntentData): Promise<CreatedIntent | null>;\n\n /**\n * Archives (soft-deletes) an intent.\n * Sets the archivedAt timestamp rather than hard deleting.\n *\n * Called when the reconciler outputs an \"expire\" action.\n *\n * @param intentId - The unique identifier of the intent to archive\n * @returns Result object indicating success or failure with error message\n *\n * @example\n * ```typescript\n * // After graph outputs EXPIRE action\n * const result = await db.archiveIntent(action.id);\n * if (!result.success) {\n * console.error(`Failed to archive: ${result.error}`);\n * }\n * ```\n */\n archiveIntent(intentId: string): Promise<ArchiveResult>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Query Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Retrieves a single intent by ID.\n *\n * @param intentId - The unique identifier of the intent\n * @returns The full intent record or null if not found\n */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /**\n * Retrieves an intent with ownership verification.\n * Ensures the requesting user owns the intent before returning.\n *\n * Used for processing operations (refine, suggestions) that require ownership.\n *\n * @param intentId - The unique identifier of the intent\n * @param userId - The user requesting access\n * @returns The intent if found and owned by user, null if not found\n * @throws Error with message 'Access denied' if intent exists but is not owned by user\n *\n * @example\n * ```typescript\n * try {\n * const intent = await db.getIntentWithOwnership(intentId, userId);\n * if (!intent) return res.status(404).json({ error: 'Not found' });\n * // Process intent...\n * } catch (e) {\n * if (e.message === 'Access denied') {\n * return res.status(403).json({ error: 'Forbidden' });\n * }\n * throw e;\n * }\n * ```\n */\n getIntentWithOwnership(intentId: string, userId: string): Promise<IntentRecord | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Association Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Gets Index IDs where the user has auto-assign membership enabled.\n * Used for determining which indexes to associate new intents with.\n *\n * @param userId - The unique identifier of the user\n * @returns Array of index IDs\n *\n * @example\n * ```typescript\n * const networkIds = await db.getUserIndexIds(userId);\n * if (networkIds.length > 0) {\n * await db.associateIntentWithNetworks(intentId, networkIds);\n * }\n * ```\n */\n getUserIndexIds(userId: string): Promise<string[]>;\n\n /**\n * Retrieves all indexes the user is a member of with full details.\n * Used for displaying index memberships in chat (index_query).\n *\n * @param userId - The unique identifier of the user\n * @returns Array of index memberships with details\n */\n getNetworkMemberships(userId: string): Promise<NetworkMembership[]>;\n\n /**\n * Get a single index membership by index and user.\n * Used when the preloaded memberships list may not contain this index (e.g. after isNetworkMember check).\n *\n * @param networkId - The index ID\n * @param userId - The user ID\n * @returns The membership or null if not found\n */\n getNetworkMembership(networkId: string, userId: string): Promise<NetworkMembership | null>;\n\n /**\n * Get index by ID with core fields. Used for opportunity presentation and context rendering.\n */\n getNetwork(networkId: string): Promise<{\n id: string;\n title: string;\n prompt?: string | null;\n type?: string;\n metadata?: Record<string, unknown> | null;\n permissions?: Record<string, unknown> | null;\n } | null>;\n\n /**\n * Get index by ID with permissions (e.g. joinPolicy). Used by chat tools for create_index_membership.\n */\n getNetworkWithPermissions(networkId: string): Promise<{ id: string; title: string; permissions: { joinPolicy: 'anyone' | 'invite_only' } } | null>;\n\n /**\n * Associates an intent with one or more networks.\n * Creates entries in the intentNetworks join table.\n *\n * @param intentId - The intent to associate\n * @param networkIds - Array of network IDs to associate with\n *\n * @example\n * ```typescript\n * await db.associateIntentWithNetworks(intentId, ['idx_1', 'idx_2']);\n * ```\n */\n associateIntentWithNetworks(intentId: string, networkIds: string[]): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Vector Search Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Finds semantically similar intents using vector search.\n * Used for deduplication during intent creation and discovery.\n *\n * Privacy scoping: Results are always filtered by userId to ensure\n * users only see their own intents.\n *\n * @param embedding - The query embedding vector\n * @param userId - The user ID for privacy scoping (required)\n * @param options - Search options (limit, threshold)\n * @returns Array of intents with similarity scores, sorted by similarity\n *\n * @example\n * ```typescript\n * // Check for duplicates before creating\n * const embedding = await embedder.generate(payload);\n * const similar = await db.findSimilarIntents(embedding, userId, {\n * limit: 5,\n * threshold: 0.85\n * });\n * if (similar.length > 0 && similar[0].similarity > 0.95) {\n * // Likely duplicate - consider updating instead\n * }\n * ```\n */\n findSimilarIntents(\n embedding: number[],\n userId: string,\n options?: SimilarIntentSearchOptions\n ): Promise<SimilarIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Graph Operations (Intent–Index Assignment)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Intent fields needed for index appropriateness evaluation.\n */\n getIntentForIndexing(intentId: string): Promise<{\n id: string;\n payload: string;\n userId: string;\n sourceType: string | null;\n sourceId: string | null;\n } | null>;\n\n /**\n * Index + member prompts for a user in an index (only when member has autoAssign).\n * Returns null if user is not a member or autoAssign is false.\n */\n getNetworkMemberContext(\n networkId: string,\n userId: string\n ): Promise<{\n networkId: string;\n indexPrompt: string | null;\n memberPrompt: string | null;\n } | null>;\n\n /**\n * Whether the intent is currently assigned to the index.\n */\n isIntentAssignedToIndex(intentId: string, networkId: string): Promise<boolean>;\n\n /**\n * Assigns an intent to an index (inserts intent_indexes row).\n */\n assignIntentToNetwork(intentId: string, networkId: string, relevancyScore?: number): Promise<void>;\n\n /**\n * Returns per-index relevancy scores for an intent's index assignments.\n */\n getIntentIndexScores(intentId: string): Promise<Array<{ networkId: string; relevancyScore: number | null }>>;\n\n /**\n * Removes an intent from an index (deletes intent_indexes row).\n */\n unassignIntentFromIndex(intentId: string, networkId: string): Promise<void>;\n\n /**\n * Returns all network IDs that an intent is registered to.\n */\n getNetworkIdsForIntent(intentId: string): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Ownership Operations (Owner-Only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get indexes where the user has owner permissions.\n * Returns full index details with member and intent counts.\n *\n * @param userId - The user ID to check ownership for\n * @returns Array of owned indexes with counts\n */\n getOwnedIndexes(userId: string): Promise<OwnedIndex[]>;\n\n /**\n * Get public indexes (joinPolicy 'anyone') that the user has not joined.\n * Used for discovering communities available to join.\n *\n * @param userId - The user ID to check memberships against\n * @returns Object containing array of public indexes with owner info\n */\n getPublicIndexesNotJoined(userId: string): Promise<{\n networks: Array<{\n id: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n owner: { id: string; name: string; avatar: string | null } | null;\n }>;\n }>;\n\n /**\n * Check if user is an owner of a specific index.\n *\n * @param networkId - The index to check\n * @param userId - The user to verify ownership for\n * @returns True if user is an owner\n */\n isIndexOwner(networkId: string, userId: string): Promise<boolean>;\n\n /**\n * Check if user is a member of a specific index.\n *\n * @param networkId - The index to check\n * @param userId - The user to verify membership for\n * @returns True if user is a member\n */\n isNetworkMember(networkId: string, userId: string): Promise<boolean>;\n\n /**\n * Get all members of an index with their details.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to get members for\n * @param requestingUserId - The user requesting (must be owner)\n * @returns Array of member details with intent counts\n * @throws Error if requestingUserId is not an owner\n */\n getNetworkMembersForOwner(\n networkId: string,\n requestingUserId: string\n ): Promise<IndexMemberDetails[]>;\n\n /**\n * Get all members of an index with their details.\n * **MEMBER ONLY** - any member of the index can list members (not just owners).\n * Returns same shape as getNetworkMembersForOwner; email may be omitted for privacy.\n *\n * @param networkId - The index to get members for\n * @param requestingUserId - The user requesting (must be a member of the index)\n * @returns Array of member details with intent counts\n * @throws Error if requestingUserId is not a member of the index\n */\n getNetworkMembersForMember(\n networkId: string,\n requestingUserId: string\n ): Promise<IndexMemberDetails[]>;\n\n /**\n * Get all members from every index the user is a member of (deduplicated).\n * Used for mentionable-users: anyone who shares at least one index with the requesting user.\n *\n * @param userId - The signed-in user\n * @returns Array of member summaries (id, name, avatar only; no email)\n */\n getMembersFromUserIndexes(userId: Id<'users'>): Promise<{ userId: Id<'users'>; name: string; avatar: string | null }[]>;\n\n /**\n * Get all indexed intents for an index.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to get intents for\n * @param requestingUserId - The user requesting (must be owner)\n * @param options - Pagination options\n * @returns Array of intent details with owner info\n * @throws Error if requestingUserId is not an owner\n */\n getNetworkIntentsForOwner(\n networkId: string,\n requestingUserId: string,\n options?: { limit?: number; offset?: number }\n ): Promise<IndexedIntentDetails[]>;\n\n /**\n * Get all indexed intents for an index.\n * **MEMBER ONLY** - any member of the index can list intents (not just owners).\n *\n * @param networkId - The index to get intents for\n * @param requestingUserId - The user requesting (must be a member of the index)\n * @param options - Pagination options\n * @returns Array of intent details with owner info\n * @throws Error if requestingUserId is not a member of the index\n */\n getNetworkIntentsForMember(\n networkId: string,\n requestingUserId: string,\n options?: { limit?: number; offset?: number }\n ): Promise<IndexedIntentDetails[]>;\n\n /**\n * Get the caller's own active intents across a set of indexes.\n * Returns intents owned by `userId` that are linked (via intent_networks)\n * to at least one of `indexIds`. Used by network-scoped agents to honor\n * indexScope without falling back to global getActiveIntents (which would\n * include intents in indexes outside scope).\n *\n * @param userId - The intent owner (always the caller).\n * @param indexIds - The set of index IDs to filter on. Empty → empty result.\n * @returns Active intents owned by userId in any of indexIds, deduped by intent id.\n */\n getActiveIntentsAcrossIndexes(userId: string, indexIds: string[]): Promise<ActiveIntent[]>;\n\n /**\n * Update index settings.\n * **OWNER ONLY** - throws if user is not an owner.\n *\n * @param networkId - The index to update\n * @param requestingUserId - The user requesting (must be owner)\n * @param data - The settings to update\n * @returns The updated index\n * @throws Error if requestingUserId is not an owner\n */\n updateIndexSettings(\n networkId: string,\n requestingUserId: string,\n data: UpdateIndexSettingsData\n ): Promise<OwnedIndex>;\n\n /**\n * Soft-delete a network (set deletedAt).\n * Caller must ensure network is not personal and has no other members.\n *\n * @param networkId - The network to soft-delete\n */\n softDeleteNetwork(networkId: string): Promise<void>;\n\n /**\n * Delete a user's profile (removes profile row).\n * Used after confirmation in chat tools.\n *\n * @param userId - User whose profile to delete\n */\n deleteProfile(userId: string): Promise<void>;\n\n /**\n * Get a user's profile including its row id (for update_user_profile validation).\n *\n * @param userId - The user whose profile to fetch\n * @returns Profile with id, or null if not found\n */\n getProfileByUserId(userId: string): Promise<(ProfileDocument & { id: string }) | null>;\n\n /**\n * Create a new index and return its record.\n *\n * @param data - Title, optional prompt, optional imageUrl, optional joinPolicy\n * @returns The created index with id, title, prompt, imageUrl, permissions\n */\n createNetwork(data: {\n title: string;\n prompt?: string | null;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n }): Promise<{\n id: string;\n title: string;\n prompt: string | null;\n imageUrl: string | null;\n permissions: { joinPolicy: 'anyone' | 'invite_only'; invitationLink: { code: string } | null; allowGuestVibeCheck: boolean };\n }>;\n\n /**\n * Count members in an index (for delete guard).\n *\n * @param networkId - The index to count\n * @returns Number of members\n */\n getNetworkMemberCount(networkId: string): Promise<number>;\n\n /**\n * Add a user as a member of a network.\n *\n * @param networkId - The network to add to\n * @param userId - The user to add\n * @param role - owner | member\n * @returns success and optionally alreadyMember if they were already in the network\n */\n addMemberToNetwork(\n networkId: string,\n userId: string,\n role: 'owner' | 'member'\n ): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n /**\n * Removes a user from an index.\n * Only the index owner can remove members. Cannot remove the owner.\n *\n * @param networkId - The index to remove from\n * @param userId - The user to remove\n * @returns success, or wasOwner/notMember if removal failed\n */\n removeMemberFromIndex(\n networkId: string,\n userId: string\n ): Promise<{ success: boolean; wasOwner?: boolean; notMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Document Operations (Opportunity Redesign)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get a HyDE document by source and strategy/lens hash.\n * Returns the first matching document when multiple target corpuses exist.\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID (e.g. intent ID, user ID)\n * @param strategy - Lens hash (SHA-256 of lens label) or legacy strategy name\n * @returns The HyDE document or null if not found\n */\n getHydeDocument(\n sourceType: HydeSourceType,\n sourceId: string,\n strategy: string\n ): Promise<HydeDocument | null>;\n\n /**\n * Get all HyDE documents for a source (all strategies).\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID\n * @returns Array of HyDE documents for that source\n */\n getHydeDocumentsForSource(\n sourceType: HydeSourceType,\n sourceId: string\n ): Promise<HydeDocument[]>;\n\n /**\n * Save a HyDE document (upsert by sourceType + sourceId + strategy/lensHash + targetCorpus).\n *\n * @param data - HyDE document data\n * @returns The saved HyDE document\n */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /**\n * Delete all HyDE documents for a source (e.g. when intent/profile archived).\n *\n * @param sourceType - 'intent' | 'profile' | 'query'\n * @param sourceId - Source entity ID\n * @returns Number of documents deleted\n */\n deleteHydeDocumentsForSource(\n sourceType: HydeSourceType,\n sourceId: string\n ): Promise<number>;\n\n /**\n * Delete expired HyDE documents (expires_at <= now). Used by maintenance jobs.\n *\n * @returns Number of documents deleted\n */\n deleteExpiredHydeDocuments(): Promise<number>;\n\n /**\n * Get stale HyDE documents for refresh (e.g. createdAt < threshold).\n *\n * @param threshold - Date threshold; documents created before this are considered stale\n * @returns Array of stale HyDE documents\n */\n getStaleHydeDocuments(threshold: Date): Promise<HydeDocument[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (Opportunity Redesign)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Create a new opportunity.\n *\n * @param data - Opportunity creation data\n * @returns The created opportunity\n */\n createOpportunity(data: CreateOpportunityData): Promise<Opportunity>;\n\n /**\n * Get a single opportunity by ID.\n *\n * @param id - Opportunity ID\n * @returns The opportunity or null if not found\n */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /**\n * Get multiple opportunities by ID in a single batched query.\n *\n * Returns rows in arbitrary order; callers should index by `id`.\n * Missing IDs are silently dropped (no error).\n *\n * @param ids - Opportunity IDs (deduplicated by the caller is fine but not required)\n * @returns Opportunities found\n */\n getOpportunitiesByIds(ids: string[]): Promise<Opportunity[]>;\n\n /**\n * Find opportunities that superseded a previous opportunity through enrichment.\n * Uses the existing JSONB `detection.enrichedFrom` array, so no schema-level relation is required.\n * Results are newest-first so callers can choose the newest visible replacement.\n *\n * @param opportunityId - Superseded opportunity ID\n * @returns Replacement opportunities, newest first\n */\n findEnrichedReplacementOpportunities(opportunityId: string): Promise<Opportunity[]>;\n\n /**\n * Resolve an opportunity identifier (full UUID or short prefix) to a full UUID.\n * @param idOrPrefix - Full UUID or short hex prefix\n * @param userId - The user ID (for visibility scoping)\n * @returns Resolved ID, ambiguous marker, or null if not found\n */\n resolveOpportunityId(idOrPrefix: string, userId: string): Promise<{ id: string } | { ambiguous: true } | null>;\n\n /**\n * Get opportunities for a user (as any actor role).\n *\n * @param userId - User ID (actor userId)\n * @param options - Optional filters and pagination\n * @returns Array of opportunities\n */\n getOpportunitiesForUser(\n userId: string,\n options?: OpportunityQueryOptions\n ): Promise<Opportunity[]>;\n\n /**\n * Get opportunities in an index (for index admins).\n *\n * @param networkId - Index ID\n * @param options - Optional filters and pagination\n * @returns Array of opportunities\n */\n getOpportunitiesForNetwork(\n networkId: string,\n options?: OpportunityQueryOptions\n ): Promise<Opportunity[]>;\n\n /**\n * Update an opportunity's status.\n *\n * @param id - Opportunity ID\n * @param status - New status\n * @returns The updated opportunity or null if not found\n */\n updateOpportunityStatus(\n id: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /**\n * Stamp `actedAt` on the actor matching `actorUserId` and update the\n * opportunity's status atomically (row-lock + JSONB merge in one txn).\n *\n * Used by `sendNode` (status → 'pending') and `updateNode` (status →\n * 'accepted'). The self-accept guard is enforced in the caller, not here —\n * this method blindly stamps. Callers must pre-check `actor.actedAt` before\n * invocation when the semantics require it (i.e. accepting).\n *\n * @param id - Opportunity ID\n * @param actorUserId - The user whose actor entry should be stamped\n * @param status - New opportunity status\n * @param acceptedBy - Required when `status === 'accepted'`\n * @returns The updated opportunity, or null if not found\n */\n stampOpportunityActorAction(\n id: string,\n actorUserId: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /**\n * Update the `approved` field on an opportunity's introducer actor.\n * Fetches the opportunity, patches the matching actor in JS, and writes\n * the updated actors JSONB back. Returns the updated opportunity or null.\n */\n updateOpportunityActorApproval(\n id: string,\n introducerUserId: string,\n approved: boolean,\n ): Promise<Opportunity | null>;\n\n /**\n * Create one opportunity and expire others in a single transaction.\n * Atomic: insert then update status to 'expired' for each id in expireIds.\n * Used when enriching replaces overlapping opportunities so subscribers see consistent state.\n *\n * @param data - Opportunity creation data (caller may set status when enriched)\n * @param expireIds - Opportunity IDs to set status to 'expired'\n * @returns The created opportunity and the list of opportunities that were expired\n */\n createOpportunityAndExpireIds(\n data: CreateOpportunityData,\n expireIds: string[]\n ): Promise<{ created: Opportunity; expired: Opportunity[] }>;\n\n /**\n * Check if an opportunity already exists between the given actors in the index (deduplication).\n *\n * @param actorIds - Array of user IDs that would be actors\n * @param networkId - Index ID\n * @returns True if a non-expired opportunity exists with exactly these actors in this index\n */\n opportunityExistsBetweenActors(\n actorIds: string[],\n networkId: string\n ): Promise<boolean>;\n\n /**\n * Find opportunities whose actors contain all the given user IDs.\n *\n * The `includeIntroducers` flag controls actor matching: when false (default), matching\n * is restricted to non-introducer roles; when true, any role in `actors` counts.\n *\n * Index-agnostic. Ordered by updatedAt desc.\n *\n * @param actorIds - User IDs that must all appear in each returned opportunity's actors\n * @param options - includeIntroducers (default false), statuses (include filter), excludeStatuses (exclude filter)\n * @returns Matching opportunities, newest first\n */\n findOpportunitiesByActors(\n actorIds: string[],\n options?: {\n includeIntroducers?: boolean;\n statuses?: OpportunityStatus[];\n excludeStatuses?: OpportunityStatus[];\n }\n ): Promise<Opportunity[]>;\n\n /**\n * Expire opportunities referencing an intent (e.g. when intent is archived).\n *\n * @param intentId - Intent ID to match in opportunity actors\n * @returns Number of opportunities updated to expired\n */\n expireOpportunitiesByIntent(intentId: string): Promise<number>;\n\n /**\n * Expire opportunities for a user removed from an index.\n *\n * @param networkId - Index ID\n * @param userId - User ID that was removed\n * @returns Number of opportunities updated to expired\n */\n expireOpportunitiesForRemovedMember(\n networkId: string,\n userId: string\n ): Promise<number>;\n\n /**\n * Expire opportunities whose expires_at <= now. Used by maintenance cron.\n *\n * @returns Number of opportunities updated to expired\n */\n expireStaleOpportunities(): Promise<number>;\n\n /**\n * Accept all sibling opportunities between the same actor pair in one transaction.\n * Selects opportunities where both userId and counterpartUserId are actors and status\n * is not accepted/expired/rejected, excludes excludeOpportunityId, then bulk-updates status to accepted.\n * Rolls back on any failure.\n *\n * @param userId - First actor user ID\n * @param counterpartUserId - Second actor user ID\n * @param excludeOpportunityId - Opportunity ID to exclude (the one already being accepted)\n * @returns IDs of opportunities that were updated to accepted\n */\n acceptSiblingOpportunities(\n userId: string,\n counterpartUserId: string,\n excludeOpportunityId: string\n ): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Contact / My Network Operations\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create a ghost user (unregistered contact) with empty profile. */\n createGhostUser(data: { name: string; email: string }): Promise<{ id: string }>;\n\n /** Upsert a contact membership in the owner's personal index (index_members with permissions=['contact']). */\n upsertContactMembership(ownerId: string, contactUserId: string, options?: { restore?: boolean }): Promise<void>;\n\n /**\n * Finds an existing DM conversation between two users, or creates one.\n * Uses a unique `dmPair` column (sorted user IDs joined by ':') to\n * prevent duplicate DMs under concurrency. Used by the Start Chat flow\n * (Plan B Task 8) to atomically surface the h2h conversation when\n * accepting an opportunity.\n */\n getOrCreateDM(userA: string, userB: string, participantType?: 'user' | 'agent'): Promise<{ id: string }>;\n\n /**\n * Clears hiddenAt for a user on a conversation, making it visible in their\n * conversation list again. Called by startChat when reusing an existing DM\n * that the user had previously hidden.\n */\n unhideConversation(userId: string, conversationId: string): Promise<void>;\n\n /** Hard-delete a contact membership from the owner's personal index. */\n hardDeleteContactMembership(ownerId: string, contactUserId: string): Promise<void>;\n\n /** Get all contact members from the owner's personal index with user details. */\n getContactMembers(ownerId: string): Promise<Array<{\n userId: string;\n user: { id: string; name: string; email: string; avatar: string | null; isGhost: boolean };\n }>>;\n\n /** Clear a reverse opt-out (reactivate soft-deleted contact membership in another user's personal index). */\n clearReverseOptOut(ownerId: string, otherUserId: string): Promise<void>;\n\n /**\n * Returns the IDs of personal indexes where the given user is a contact member.\n * Used for auto-assigning new intents to personal indexes of contacts who imported this user.\n *\n * @param userId - The user whose contact memberships to look up\n * @returns Array of personal index IDs\n */\n getPersonalIndexesForContact(userId: string): Promise<{ networkId: string }[]>;\n\n /** Find a user by email. */\n getUserByEmail(email: string): Promise<{ id: string; name: string; email: string; isGhost: boolean } | null>;\n\n // ─── Premise operations ──────────────────────────────────────────────────────\n\n createPremise(input: {\n userId: string;\n assertion: PremiseAssertion;\n provenance: PremiseProvenance;\n analysis?: PremiseAnalysis;\n validity: PremiseValidity;\n embedding?: number[];\n }): Promise<PremiseRecord>;\n\n getPremise(premiseId: string): Promise<PremiseRecord | null>;\n\n getPremisesForUser(userId: string, status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED'): Promise<PremiseRecord[]>;\n\n /**\n * Retrieve a user's premises assigned to one of the provided networks.\n * Optional for older/test adapters; OpportunityGraph falls back to capped\n * getPremisesForUser results when unavailable.\n */\n getPremisesForUserInNetworks?(userId: string, networkIds: string[], status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED', limit?: number): Promise<PremiseRecord[]>;\n\n updatePremise(premiseId: string, updates: {\n assertion?: PremiseAssertion;\n analysis?: PremiseAnalysis;\n validity?: PremiseValidity;\n embedding?: number[];\n status?: 'ACTIVE' | 'RETRACTED' | 'EXPIRED';\n retractedAt?: Date;\n }): Promise<PremiseRecord>;\n\n assignPremiseToNetwork(premiseId: string, networkId: string, relevancyScore: number): Promise<void>;\n\n getPremiseNetworks(premiseId: string): Promise<Array<{ networkId: string; relevancyScore: number | null }>>;\n\n /**\n * Cosine similarity search against premise embeddings, scoped to shared networks.\n * Used by the opportunity graph's premise discovery path (path D).\n */\n searchPremisesBySimilarity(params: {\n embedding: number[];\n networkIds: string[];\n excludeUserId: string;\n limit: number;\n }): Promise<Array<{\n premiseId: string;\n userId: string;\n networkId: string;\n assertionText: string;\n similarity: number;\n }>>;\n\n /**\n * Batched version of premise similarity search. Executes one bounded DB call\n * for all selected source premises instead of one query per source premise.\n * Optional for older/test adapters; OpportunityGraph falls back to the\n * single-source method when unavailable.\n */\n searchPremisesBySimilarityBatch?(params: {\n sources: Array<{ premiseId: string; embedding: number[] }>;\n networkIds: string[];\n excludeUserId: string;\n limitPerSource: number;\n }): Promise<Array<{\n sourcePremiseId: string;\n premiseId: string;\n userId: string;\n networkId: string;\n assertionText: string;\n similarity: number;\n }>>;\n\n // ─── User Context Methods ───\n\n /**\n * Upsert a user context for a specific network.\n * Creates or updates the synthesized context paragraph + embedding.\n */\n upsertUserContext(params: {\n userId: string;\n networkId: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n }): Promise<{ id: string }>;\n\n /**\n * Get the user context for a specific user+network pair.\n */\n getUserContext(userId: string, networkId: string): Promise<{\n id: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n generatedAt: Date;\n } | null>;\n\n /**\n * Get user contexts for a user across all their networks.\n */\n getUserContexts(userId: string): Promise<Array<{\n id: string;\n networkId: string;\n text: string;\n embedding: number[];\n premiseHash: string;\n generatedAt: Date;\n }>>;\n\n /**\n * Cosine similarity search against intent embeddings using a context embedding.\n * Restores the profile→intent cross-search deleted when Path B was removed.\n */\n searchIntentsByContextEmbedding(params: {\n embedding: number[];\n networkIds: string[];\n excludeUserId: string;\n limit: number;\n minScore?: number;\n }): Promise<Array<{\n intentId: string;\n userId: string;\n networkId: string;\n payload: string;\n summary: string | null;\n similarity: number;\n }>>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// USER DATABASE INTERFACE (Own Resources Only)\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Context-bound database for accessing the authenticated user's own resources.\n * Created with authUserId bound at construction; no userId parameter needed on methods.\n *\n * **NOT index-scoped**: Returns ALL of the user's own resources regardless of index.\n * This is critical for the IntentReconciler which needs the full picture for deduplication.\n *\n * Use via `createUserDatabase(db, authUserId)` factory function.\n */\nexport interface UserDatabase {\n /** The bound authenticated user ID */\n readonly authUserId: string;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (own only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get the authenticated user's profile. */\n getProfile(): Promise<ProfileDocument | null>;\n\n /** Get the authenticated user's profile with row ID. */\n getProfileByUserId(): Promise<(ProfileDocument & { id: string }) | null>;\n\n /** Save/update the authenticated user's profile. */\n saveProfile(profile: ProfileDocument): Promise<void>;\n\n /** Delete the authenticated user's profile. */\n deleteProfile(): Promise<void>;\n\n /** Get the authenticated user's basic record (name, email, socials). */\n getUser(): Promise<UserRecord | null>;\n\n /** Update the authenticated user's account fields. */\n updateUser(data: { name?: string; intro?: string; location?: string; onboarding?: OnboardingState }): Promise<UserRecord | null>;\n\n getUserSocials(): Promise<UserSocial[]>;\n setUserSocials(socials: { label: string; value: string }[]): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Intent Operations (own only, ALL intents - not index-scoped)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get ALL active intents for the authenticated user (not index-filtered). */\n getActiveIntents(): Promise<ActiveIntent[]>;\n\n /**\n * Case-insensitive substring search over the authenticated user's own\n * active intents. Matches against `payload` and `summary`. Most recent first.\n */\n searchOwnIntents(\n q: string,\n limit: number,\n ): Promise<Array<{ id: string; payload: string; summary: string | null; createdAt: Date }>>;\n\n /** Get a single intent by ID (ownership enforced). */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /** Create a new intent for the authenticated user. */\n createIntent(data: Omit<CreateIntentData, 'userId'>): Promise<CreatedIntent>;\n\n /** Update an intent owned by the authenticated user. */\n updateIntent(intentId: string, data: UpdateIntentData): Promise<CreatedIntent | null>;\n\n /** Archive an intent owned by the authenticated user. */\n archiveIntent(intentId: string): Promise<ArchiveResult>;\n\n /** Find similar intents among the user's own intents (for deduplication). */\n findSimilarIntents(embedding: number[], options?: SimilarIntentSearchOptions): Promise<SimilarIntent[]>;\n\n /** Get intent fields for indexing (own intent). */\n getIntentForIndexing(intentId: string): Promise<{\n id: string;\n payload: string;\n userId: string;\n sourceType: string | null;\n sourceId: string | null;\n } | null>;\n\n /** Associate an intent with networks. */\n associateIntentWithNetworks(intentId: string, networkIds: string[]): Promise<void>;\n\n /** Assign an intent to an index. */\n assignIntentToNetwork(intentId: string, networkId: string, relevancyScore?: number): Promise<void>;\n\n /** Unassign an intent from an index. */\n unassignIntentFromIndex(intentId: string, networkId: string): Promise<void>;\n\n /** Get network IDs for an intent. */\n getNetworkIdsForIntent(intentId: string): Promise<string[]>;\n\n /** Check if intent is assigned to index. */\n isIntentAssignedToIndex(intentId: string, networkId: string): Promise<boolean>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Membership Operations (own memberships only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get all index memberships for the authenticated user. */\n getNetworkMemberships(): Promise<NetworkMembership[]>;\n\n /** Get index IDs with auto-assign enabled for the authenticated user. */\n getUserIndexIds(): Promise<string[]>;\n\n /** Get indexes owned by the authenticated user. */\n getOwnedIndexes(): Promise<OwnedIndex[]>;\n\n /** Get a specific index membership for the authenticated user. */\n getNetworkMembership(networkId: string): Promise<NetworkMembership | null>;\n\n /** Get index + member context for the authenticated user (for auto-assign). */\n getNetworkMemberContext(networkId: string): Promise<{\n networkId: string;\n indexPrompt: string | null;\n memberPrompt: string | null;\n } | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index CRUD Operations (owner operations on own indexes)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create a new index (user becomes owner). */\n createNetwork(data: {\n title: string;\n prompt?: string | null;\n imageUrl?: string | null;\n joinPolicy?: 'anyone' | 'invite_only';\n }): Promise<{\n id: string;\n title: string;\n prompt: string | null;\n imageUrl: string | null;\n permissions: { joinPolicy: 'anyone' | 'invite_only'; invitationLink: { code: string } | null; allowGuestVibeCheck: boolean };\n }>;\n\n /** Update index settings (owner only). */\n updateIndexSettings(networkId: string, data: UpdateIndexSettingsData): Promise<OwnedIndex>;\n\n /** Soft-delete a network (owner only). */\n softDeleteNetwork(networkId: string): Promise<void>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public Index Discovery (joinable indexes the user is not a member of)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get public indexes (joinPolicy 'anyone') that the user has not joined. */\n getPublicIndexesNotJoined(): Promise<{\n networks: Array<{\n id: string;\n title: string;\n prompt: string | null;\n memberCount: number;\n owner: { id: string; name: string; avatar: string | null } | null;\n }>;\n }>;\n\n /** Join a public index (validates joinPolicy === 'anyone'). */\n joinPublicNetwork(networkId: string): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (where user is actor)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get opportunities where the authenticated user is an actor. */\n getOpportunitiesForUser(options?: OpportunityQueryOptions): Promise<Opportunity[]>;\n\n /** Get a specific opportunity (if user is an actor). */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /** Update an opportunity's status (if user is an actor). acceptedBy is derived from the auth context. */\n updateOpportunityStatus(id: string, status: OpportunityStatus): Promise<Opportunity | null>;\n\n /** Accept sibling opportunities between the authenticated user and another actor. */\n acceptSiblingOpportunities(counterpartUserId: string, excludeOpportunityId: string): Promise<string[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Operations (own sources only)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a HyDE document for the user's own source. */\n getHydeDocument(sourceType: HydeSourceType, sourceId: string, strategy: string): Promise<HydeDocument | null>;\n\n /** Get all HyDE documents for the user's own source. */\n getHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<HydeDocument[]>;\n\n /** Save a HyDE document for the user's own source. */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /** Delete HyDE documents for the user's own source. */\n deleteHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<number>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// SYSTEM DATABASE INTERFACE (Cross-User Within Shared Indexes)\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Context-bound database for LLM/system operations that access cross-user resources.\n * Created with authUserId + indexScope[]; validates membership before access.\n *\n * **Index-scoped**: All cross-user operations are restricted to users/resources\n * within the bound indexScope[]. This prevents the LLM from accessing arbitrary users' data.\n *\n * Use via `createSystemDatabase(db, authUserId, indexScope)` factory function.\n */\nexport interface SystemDatabase {\n /** The bound authenticated user ID */\n readonly authUserId: string;\n\n /** The indexes the authenticated user has access to (determines cross-user scope) */\n readonly indexScope: string[];\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Profile Operations (any user in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a user's profile (requires shared index membership). */\n getProfile(userId: string): Promise<ProfileDocument | null>;\n\n /** Get a user's basic record (requires shared index membership). */\n getUser(userId: string): Promise<UserRecord | null>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Intent Operations (cross-user within shared indexes)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get all intents in an index (cross-user, requires membership). */\n getIntentsInIndex(networkId: string, options?: { limit?: number; offset?: number }): Promise<IndexedIntentDetails[]>;\n\n /** Get a specific user's intents in an index (requires shared membership). */\n getUserIntentsInIndex(userId: string, networkId: string): Promise<ActiveIntent[]>;\n\n /**\n * Get the caller's own active intents across a set of indexes.\n * Returns intents owned by `userId` that are linked (via intent_networks)\n * to at least one of `indexIds`. Used by network-scoped agents to honor\n * indexScope without falling back to global getActiveIntents (which would\n * include intents in indexes outside scope).\n *\n * @param userId - The intent owner (always the caller).\n * @param indexIds - The set of index IDs to filter on. Empty → empty result.\n * @returns Active intents owned by userId in any of indexIds, deduped by intent id.\n */\n getActiveIntentsAcrossIndexes(userId: string, indexIds: string[]): Promise<ActiveIntent[]>;\n\n /** Get a single intent by ID (if in scope). */\n getIntent(intentId: string): Promise<IntentRecord | null>;\n\n /** Find similar intents across users within the index scope. */\n findSimilarIntentsInScope(embedding: number[], options?: SimilarIntentSearchOptions): Promise<SimilarIntent[]>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Membership Operations (any index in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Check if a user is a member of an index. */\n isNetworkMember(networkId: string, userId: string): Promise<boolean>;\n\n /** Check if a user is an owner of an index. */\n isIndexOwner(networkId: string, userId: string): Promise<boolean>;\n\n /** Get all members of an index (requires membership). */\n getNetworkMembers(networkId: string): Promise<IndexMemberDetails[]>;\n\n /** Get all members across all indexes in scope (deduplicated). */\n getMembersFromScope(): Promise<{ userId: Id<'users'>; name: string; avatar: string | null }[]>;\n\n /** Add a user to an index (requires ownership or 'anyone' policy). */\n addMemberToNetwork(networkId: string, userId: string, role: 'owner' | 'member'): Promise<{ success: boolean; alreadyMember?: boolean }>;\n\n /** Remove a user from an index (requires ownership). Cannot remove the owner. */\n removeMemberFromIndex(networkId: string, userId: string): Promise<{ success: boolean; wasOwner?: boolean; notMember?: boolean }>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Index Operations (any index in scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get index info by ID with core fields (requires scope). */\n getNetwork(networkId: string): Promise<{\n id: string;\n title: string;\n prompt?: string | null;\n type?: string;\n metadata?: Record<string, unknown> | null;\n permissions?: Record<string, unknown> | null;\n } | null>;\n\n /** Get index with permissions (requires scope). */\n getNetworkWithPermissions(networkId: string): Promise<{ id: string; title: string; permissions: { joinPolicy: 'anyone' | 'invite_only' } } | null>;\n\n /** Get member count for an index (requires scope). */\n getNetworkMemberCount(networkId: string): Promise<number>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Opportunity Operations (cross-user within scope)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Create an opportunity (cross-user). */\n createOpportunity(data: CreateOpportunityData): Promise<Opportunity>;\n\n /** Create opportunity and expire overlapping ones atomically. */\n createOpportunityAndExpireIds(data: CreateOpportunityData, expireIds: string[]): Promise<{ created: Opportunity; expired: Opportunity[] }>;\n\n /** Get an opportunity by ID (for system processing). */\n getOpportunity(id: string): Promise<Opportunity | null>;\n\n /** Get opportunities for an index (requires membership). */\n getOpportunitiesForNetwork(networkId: string, options?: OpportunityQueryOptions): Promise<Opportunity[]>;\n\n /** Update an opportunity's status (system-level). */\n updateOpportunityStatus(id: string, status: OpportunityStatus, acceptedBy?: string): Promise<Opportunity | null>;\n\n /** Stamp actor `actedAt` + update status atomically (system-level). */\n stampOpportunityActorAction(\n id: string,\n actorUserId: string,\n status: OpportunityStatus,\n acceptedBy?: string,\n ): Promise<Opportunity | null>;\n\n /** Check if opportunity exists between actors in an index. */\n opportunityExistsBetweenActors(actorIds: string[], networkId: string): Promise<boolean>;\n\n /** Find opportunities by actor IDs with optional include/exclude status filters. */\n findOpportunitiesByActors(\n actorIds: string[],\n options?: { includeIntroducers?: boolean; statuses?: OpportunityStatus[]; excludeStatuses?: OpportunityStatus[] }\n ): Promise<Opportunity[]>;\n\n /** Expire opportunities referencing an intent. */\n expireOpportunitiesByIntent(intentId: string): Promise<number>;\n\n /** Expire opportunities for a removed member. */\n expireOpportunitiesForRemovedMember(networkId: string, userId: string): Promise<number>;\n\n /** Expire stale opportunities (maintenance). */\n expireStaleOpportunities(): Promise<number>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // HyDE Operations (cross-user for opportunity matching)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Get a HyDE document (cross-user for matching). */\n getHydeDocument(sourceType: HydeSourceType, sourceId: string, strategy: string): Promise<HydeDocument | null>;\n\n /** Get all HyDE documents for a source (cross-user). */\n getHydeDocumentsForSource(sourceType: HydeSourceType, sourceId: string): Promise<HydeDocument[]>;\n\n /** Save a HyDE document (system-level). */\n saveHydeDocument(data: CreateHydeDocumentData): Promise<HydeDocument>;\n\n /** Delete expired HyDE documents (maintenance). */\n deleteExpiredHydeDocuments(): Promise<number>;\n\n /** Get stale HyDE documents for refresh (maintenance). */\n getStaleHydeDocuments(threshold: Date): Promise<HydeDocument[]>;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// NARROWED DATABASE INTERFACES (Interface Segregation)\n// ═══════════════════════════════════════════════════════════════════════════════\n//\n// These narrowed types are Pick types from the raw Database interface.\n// They are used by graph factories to enforce interface segregation at compile time.\n//\n// Access control relationship to UserDatabase/SystemDatabase:\n// - ProfileGraphDatabase → maps to UserDatabase (user's own profile operations)\n// - IntentGraphDatabase → maps to UserDatabase (mutations) + SystemDatabase (reads)\n// - OpportunityGraphDatabase → maps to SystemDatabase (cross-user operations)\n// - NetworkGraphDatabase → maps to UserDatabase (own indexes)\n// - IntentNetworkGraphDatabase → maps to both (own intent ↔ shared index)\n// - NetworkMembershipGraphDatabase → maps to SystemDatabase (cross-user)\n// - HydeGraphDatabase → maps to both (own HyDE vs cross-user matching)\n//\n// Graphs continue to use these narrowed types because:\n// 1. They receive the raw database adapter with userId passed per method\n// 2. Access control is enforced at the tool/factory layer via createUserDatabase/createSystemDatabase\n// 3. These types ensure graphs only depend on methods they actually use\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Database interface narrowed for Profile Graph operations.\n * Provides full profile lifecycle: read, write, and query mode.\n *\n * Access layer: Primarily UserDatabase (user's own profile)\n */\nexport type ProfileGraphDatabase = Pick<\n Database,\n 'getProfile' | 'getUser' | 'updateUser' | 'saveProfile' | 'getProfileByUserId' | 'softDeleteGhost' | 'findDuplicateUser' | 'mergeGhostUser' | 'getUserSocials' | 'setUserSocials' | 'getPremisesForUser'\n>;\n\n/**\n * Database interface narrowed for Premise Graph operations.\n * Provides premise lifecycle: create, read, update, and network assignment.\n *\n * Access layer: UserDatabase (user's own premises)\n */\nexport type PremiseGraphDatabase = Pick<\n Database,\n 'createPremise' | 'getPremise' | 'getPremisesForUser' | 'updatePremise' | 'assignPremiseToNetwork' | 'getPremiseNetworks' | 'getUserIndexIds' | 'getNetwork' | 'getNetworkMemberContext'\n>;\n\n/**\n * Composite database interface for Chat Graph.\n * Includes direct ChatGraph operations plus all methods needed by\n * internally composed subgraphs (ProfileGraph, OpportunityGraph, IntentGraph, NetworkGraph).\n *\n * Use this type when ChatGraph orchestrates subgraphs internally.\n *\n * Access layer: Both UserDatabase + SystemDatabase (orchestrates all operations)\n */\nexport type ChatGraphCompositeDatabase = Pick<\n Database,\n // Direct ChatGraph operations\n | 'getProfile'\n | 'getActiveIntents'\n | 'getActiveIntentsAcrossIndexes'\n | 'getIntentsInIndexForMember'\n // ProfileGraph subgraph requirements\n | 'getUser'\n | 'updateUser'\n | 'getUserSocials'\n | 'setUserSocials'\n | 'saveProfile'\n | 'softDeleteGhost'\n // IntentGraph subgraph requirements (getActiveIntents already included)\n | 'createIntent'\n | 'updateIntent'\n | 'archiveIntent'\n // OpportunityGraph subgraph requirements (getProfile already included)\n | 'createOpportunity'\n | 'getOpportunity'\n | 'getOpportunitiesByIds'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'getOpportunitiesForUser'\n | 'updateOpportunityStatus'\n | 'updateOpportunityActorApproval'\n | 'stampOpportunityActorAction'\n | 'getOrCreateDM'\n // HyDE graph (used by OpportunityGraph)\n | 'getHydeDocument'\n | 'getHydeDocumentsForSource'\n | 'saveHydeDocument'\n | 'getIntent'\n // NetworkGraph subgraph requirements (index created intents in user's indexes)\n | 'getPublicIndexesNotJoined'\n | 'getUserIndexIds'\n | 'getNetworkMemberships'\n | 'getNetworkMembership'\n | 'getNetwork'\n | 'getNetworkWithPermissions'\n | 'getIntentForIndexing'\n | 'getNetworkMemberContext'\n | 'isIntentAssignedToIndex'\n | 'assignIntentToNetwork'\n | 'unassignIntentFromIndex'\n | 'getNetworkIdsForIntent'\n | 'getIntentIndexScores'\n // Personal index auto-assignment (used by intent graph executor)\n | 'getPersonalIndexesForContact'\n // Index Ownership Operations (owner-only)\n | 'getOwnedIndexes'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getNetworkMembersForOwner'\n | 'getNetworkMembersForMember'\n | 'getMembersFromUserIndexes'\n | 'getNetworkIntentsForOwner'\n | 'getNetworkIntentsForMember'\n | 'updateIndexSettings'\n | 'softDeleteNetwork'\n | 'deleteProfile'\n | 'getProfileByUserId'\n | 'createNetwork'\n | 'getNetworkMemberCount'\n | 'addMemberToNetwork'\n | 'removeMemberFromIndex'\n // ProfileGraph post-enrichment ghost deduplication\n | 'findDuplicateUser'\n | 'mergeGhostUser'\n // ProfileGraph aggregate mode (premise-to-profile materialization)\n // Premise lifecycle (CRUD + network assignment)\n | 'getPremisesForUser'\n | 'getPremisesForUserInNetworks'\n | 'createPremise'\n | 'getPremise'\n | 'updatePremise'\n | 'assignPremiseToNetwork'\n | 'getPremiseNetworks'\n // Premise-to-premise discovery (path D) in OpportunityGraph\n | 'searchPremisesBySimilarity'\n | 'searchPremisesBySimilarityBatch'\n // User context methods (context-to-intent discovery) in OpportunityGraph\n | 'getUserContext'\n | 'getUserContexts'\n | 'searchIntentsByContextEmbedding'\n> & Pick<\n NegotiationQueries,\n // Orphan heal in OpportunityGraph persist node\n | 'getNegotiationTaskForOpportunity'\n>;\n\n/**\n * Database interface for Opportunity Graph operations.\n * Includes prep/scope (index membership, intents, index details), persist (create, dedupe),\n * and CRUD operations (read, update status, send).\n *\n * Access layer: SystemDatabase (cross-user opportunity operations)\n */\nexport type OpportunityGraphDatabase = Pick<\n Database,\n | 'getProfile'\n | 'createOpportunity'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'getUserIndexIds'\n | 'getNetworkMemberships'\n | 'getActiveIntents'\n | 'getNetworkIdsForIntent'\n | 'getNetwork'\n | 'getNetworkMemberCount'\n | 'getIntentIndexScores'\n | 'getNetworkMemberContext'\n // Read/update/send modes\n | 'getOpportunity'\n | 'getOpportunitiesForUser'\n | 'updateOpportunityStatus'\n | 'stampOpportunityActorAction'\n | 'updateOpportunityActorApproval'\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getUser'\n | 'getOrCreateDM'\n // Load candidate intent payload/summary for evaluator\n | 'getIntent'\n // Premise-to-premise discovery (path D)\n | 'getPremisesForUser'\n | 'getPremisesForUserInNetworks'\n | 'searchPremisesBySimilarity'\n | 'searchPremisesBySimilarityBatch'\n // User context methods (context-to-intent discovery)\n | 'getUserContext'\n | 'getUserContexts'\n | 'searchIntentsByContextEmbedding'\n // HyDE documents for context-to-intent HyDE search\n | 'getHydeDocumentsForSource'\n> & Pick<\n NegotiationQueries,\n // Orphan heal: check if a prior negotiating opportunity has a stale task\n | 'getNegotiationTaskForOpportunity'\n>;\n\n/**\n * Negotiation-specific query operations not covered by generic\n * conversation/task primitives.\n */\n/** A user's answer to a questioner-generated question, stored on opportunity metadata. */\nexport interface NegotiationUserAnswer {\n questionId: string;\n selectedOptions: string[];\n freeText?: string;\n answeredAt: string;\n}\n\nexport interface NegotiationQueries {\n /**\n * Persists the full negotiation turn context (source/candidate user contexts,\n * seed assessment, index context, discovery query) onto the task metadata so\n * that polling agents can reconstruct the same context the system agent sees\n * in-process. Merges into `metadata.turnContext`, leaving other keys intact.\n * @param taskId - Task whose metadata to enrich\n * @param turnContext - Absolute (source/candidate) view of the negotiation context\n */\n setTaskTurnContext(taskId: string, turnContext: Record<string, unknown>): Promise<void>;\n\n /**\n * Returns the most-recently-created task whose metadata carries\n * `type: 'negotiation'` and `opportunityId: <id>`. Returns null if no\n * negotiation has been started for that opportunity yet.\n */\n getNegotiationTaskForOpportunity(opportunityId: string): Promise<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n } | null>;\n\n /**\n * Returns user answers collected by the questioner system for a given\n * opportunity. Reads `metadata.userAnswers` from the opportunities table.\n * Used by the negotiation graph to inject between-session context into\n * continuation prompts.\n */\n getOpportunityUserAnswers(opportunityId: string): Promise<NegotiationUserAnswer[]>;\n}\n\n/**\n * Database dependency for the negotiation graph (A2A conversation/task/artifact\n * persistence). Composes generic conversation ops with negotiation-specific queries.\n *\n * Access layer: ConversationDatabaseAdapter\n */\nexport type NegotiationGraphDatabase = Pick<\n Database,\n | 'getOrCreateDM'\n> & NegotiationQueries & {\n /**\n * Update the status of an opportunity. Called from the negotiation graph to\n * advance the opportunity lifecycle (negotiating -> pending/rejected/stalled).\n * Returns only the narrow { id, status } needed by the graph, not the full Opportunity.\n */\n updateOpportunityStatus(\n id: string,\n status: OpportunityStatus,\n ): Promise<{ id: string; status: OpportunityStatus } | null>;\n /** Persists a negotiation turn message within a conversation. */\n createMessage(data: {\n conversationId: string;\n senderId: string;\n role: 'user' | 'agent';\n parts: unknown[];\n taskId?: string;\n metadata?: Record<string, unknown> | null;\n }): Promise<{ id: string; senderId: string; role: 'user' | 'agent'; parts: unknown; createdAt: Date }>;\n\n /** Creates a task to track the negotiation lifecycle within a conversation. */\n createTask(conversationId: string, metadata?: Record<string, unknown>): Promise<{ id: string; conversationId: string; state: string }>;\n\n /** Transitions a task to a new state (e.g. working, completed, failed). */\n updateTaskState(taskId: string, state: string, statusMessage?: unknown): Promise<{ id: string; conversationId: string; state: string }>;\n\n /** Persists a negotiation outcome artifact attached to a task. */\n createArtifact(data: { taskId: string; name?: string; parts: unknown[]; metadata?: Record<string, unknown> | null }): Promise<{ id: string }>;\n\n /** Lists negotiation tasks where the given user is source or candidate. */\n getTasksForUser(userId: string, options?: { state?: string }): Promise<Array<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n }>>;\n\n /** Gets a specific task by ID. */\n getTask(taskId: string): Promise<{\n id: string;\n conversationId: string;\n state: string;\n metadata: Record<string, unknown> | null;\n createdAt: Date;\n updatedAt: Date;\n } | null>;\n\n /** Gets all messages for a conversation, ordered by creation time. */\n getMessagesForConversation(conversationId: string): Promise<Array<{\n id: string;\n senderId: string;\n role: 'user' | 'agent';\n parts: unknown[];\n createdAt: Date;\n }>>;\n\n /** Gets artifacts for a task (e.g. negotiation outcome). */\n getArtifactsForTask(taskId: string): Promise<Array<{\n id: string;\n name: string | null;\n parts: unknown[];\n metadata: Record<string, unknown> | null;\n }>>;\n};\n\n/**\n * Database interface for opportunity controller (API).\n *\n * Access layer: Both UserDatabase + SystemDatabase (API handles auth)\n */\nexport type OpportunityControllerDatabase = Pick<\n Database,\n | 'getOpportunity'\n | 'getOpportunitiesByIds'\n | 'findEnrichedReplacementOpportunities'\n | 'getOpportunitiesForUser'\n | 'getOpportunitiesForNetwork'\n | 'resolveOpportunityId'\n | 'updateOpportunityStatus'\n | 'createOpportunity'\n | 'createOpportunityAndExpireIds'\n | 'opportunityExistsBetweenActors'\n | 'findOpportunitiesByActors'\n | 'acceptSiblingOpportunities'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getUser'\n | 'getNetwork'\n | 'getNetworkMemberships'\n | 'getProfile'\n | 'getActiveIntents'\n | 'upsertContactMembership'\n // Start Chat endpoint (Plan B Task 8): atomic pair → conversation resolution\n // for the \"Open h2h chat from this opportunity\" flow. Kept on this interface\n // (rather than ConversationControllerDatabase) because the transition is\n // owned by OpportunityService — services cannot import other services.\n | 'getOrCreateDM'\n | 'unhideConversation'\n // Approve-introduction endpoint: flip introducer actor's approved flag.\n | 'updateOpportunityActorApproval'\n // Self-accept guard + actedAt stamping on service-layer status flips.\n | 'stampOpportunityActorAction'\n>;\n\n/**\n * Database interface narrowed for Intent Graph operations.\n * Provides state population (getActiveIntents), action execution (create/update/archive),\n * and read operations (query intents; getIntentsInIndexForMember for index-scoped reads).\n *\n * Access layer: UserDatabase (mutations on own intents) + SystemDatabase (index-scoped reads)\n */\nexport type IntentGraphDatabase = Pick<\n Database,\n | 'getActiveIntents'\n | 'getActiveIntentsAcrossIndexes'\n | 'getIntentsInIndexForMember'\n | 'createIntent'\n | 'updateIntent'\n | 'archiveIntent'\n // Read mode (queryNode) requirements\n | 'isNetworkMember'\n | 'getNetworkIntentsForMember'\n | 'getUser'\n // Profile check (prepNode gate for write operations)\n | 'getProfile'\n // Personal index auto-assignment\n | 'getPersonalIndexesForContact'\n | 'assignIntentToNetwork'\n>;\n\n/**\n * Database interface narrowed for Index Graph CRUD operations.\n * Handles create, read, update, delete of indexes (communities).\n *\n * Access layer: UserDatabase (CRUD on own indexes and memberships)\n */\nexport type NetworkGraphDatabase = Pick<\n Database,\n | 'getNetworkMemberships'\n | 'getOwnedIndexes'\n | 'getPublicIndexesNotJoined'\n | 'isIndexOwner'\n | 'isNetworkMember'\n | 'getNetwork'\n | 'createNetwork'\n | 'addMemberToNetwork'\n | 'updateIndexSettings'\n | 'softDeleteNetwork'\n | 'getNetworkMemberCount'\n>;\n\n/**\n * Database interface narrowed for Intent Index Graph operations.\n * Provides intent/index context and assignment for intent–index evaluation.\n * (Migrated from the old NetworkGraphDatabase.)\n *\n * Access layer: UserDatabase (own intent assignment) + SystemDatabase (index context)\n */\nexport type IntentNetworkGraphDatabase = Pick<\n Database,\n | 'getIntentForIndexing'\n | 'getNetworkMemberContext'\n | 'getNetwork'\n | 'isIntentAssignedToIndex'\n | 'assignIntentToNetwork'\n | 'unassignIntentFromIndex'\n | 'getIntent'\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getNetworkIdsForIntent'\n | 'getNetworkIntentsForMember'\n | 'getIntentsInIndexForMember'\n>;\n\n/**\n * Database interface narrowed for Index Membership Graph operations.\n * Handles CRUD for index memberships (add, list, remove members).\n *\n * Access layer: SystemDatabase (cross-user membership operations)\n */\nexport type NetworkMembershipGraphDatabase = Pick<\n Database,\n | 'isNetworkMember'\n | 'isIndexOwner'\n | 'getNetworkWithPermissions'\n | 'addMemberToNetwork'\n | 'removeMemberFromIndex'\n | 'getNetworkMembersForMember'\n>;\n\n/**\n * Database interface narrowed for HyDE Graph operations.\n * Provides HyDE document CRUD and intent lookup for refresh.\n *\n * Access layer: UserDatabase (own HyDE) + SystemDatabase (cross-user matching)\n */\nexport type HydeGraphDatabase = Pick<\n Database,\n 'getHydeDocument' | 'getHydeDocumentsForSource' | 'saveHydeDocument' | 'getIntent'\n>;\n\n/**\n * Database interface for Home Graph (opportunity home view).\n * Load opportunities, enrich with profile/index, and support presenter context.\n *\n * Access layer: UserDatabase (own opportunities and profile)\n */\nexport type HomeGraphDatabase = Pick<\n Database,\n | 'getOpportunitiesForUser'\n | 'getOpportunity'\n | 'getProfile'\n | 'getActiveIntents'\n | 'getNetwork'\n | 'getUser'\n> & Pick<\n NegotiationGraphDatabase,\n | 'getNegotiationTaskForOpportunity'\n | 'getMessagesForConversation'\n | 'getArtifactsForTask'\n>;\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* its own LLM-bound `QuestionGenerator` — callers inject one (or `undefined` to
|
|
8
8
|
* opt out).
|
|
9
9
|
*/
|
|
10
|
-
import type { DiscoveryQuestionInput } from "
|
|
10
|
+
import type { DiscoveryQuestionInput } from "../schemas/discovery-question.schema.js";
|
|
11
11
|
import type { QuestionGenerationResult } from "../schemas/question.schema.js";
|
|
12
12
|
export interface QuestionGeneratorReader {
|
|
13
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question-generator.interface.d.ts","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"question-generator.interface.d.ts","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,QAAQ,CACN,KAAK,EAAE,sBAAsB,EAC7B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CAC7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question-generator.interface.js","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @deprecated Use QuestionerAgent instead. Will be removed in a future version.\n *\n * Protocol-level read contract for decision-question generation. Implementations\n * live in the backend (see `QuestionGeneratorService`) and are injected into the\n * protocol via `ProtocolDeps`/`ToolContext`. The protocol module never constructs\n * its own LLM-bound `QuestionGenerator` — callers inject one (or `undefined` to\n * opt out).\n */\nimport type { DiscoveryQuestionInput } from \"
|
|
1
|
+
{"version":3,"file":"question-generator.interface.js","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @deprecated Use QuestionerAgent instead. Will be removed in a future version.\n *\n * Protocol-level read contract for decision-question generation. Implementations\n * live in the backend (see `QuestionGeneratorService`) and are injected into the\n * protocol via `ProtocolDeps`/`ToolContext`. The protocol module never constructs\n * its own LLM-bound `QuestionGenerator` — callers inject one (or `undefined` to\n * opt out).\n */\nimport type { DiscoveryQuestionInput } from \"../schemas/discovery-question.schema.js\";\nimport type { QuestionGenerationResult } from \"../schemas/question.schema.js\";\n\nexport interface QuestionGeneratorReader {\n /**\n * Run the question generator over a single discovery turn.\n *\n * @param input Discovery turn payload (query + negotiation digests + chat context).\n * @param options.signal Optional AbortSignal. When aborted (deadline reached or\n * upstream cancel) the in-flight LLM call is cancelled and `null` is returned —\n * discovery still emits its response, just without questions.\n * @returns The structured result, or `null` when generation failed,\n * guardrails dropped all candidates, the underlying LLM threw, or\n * the call was aborted.\n */\n generate(\n input: DiscoveryQuestionInput,\n options?: { signal?: AbortSignal },\n ): Promise<QuestionGenerationResult | null>;\n}\n"]}
|