@indexnetwork/protocol 5.0.0 → 5.2.0-rc.368.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/index.d.ts +8 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/negotiation/negotiation.summarizer.d.ts +1 -1
  7. package/dist/negotiation/negotiation.summarizer.d.ts.map +1 -1
  8. package/dist/negotiation/negotiation.summarizer.js.map +1 -1
  9. package/dist/opportunity/discovery-question.helper.d.ts +1 -1
  10. package/dist/opportunity/discovery-question.helper.d.ts.map +1 -1
  11. package/dist/opportunity/discovery-question.helper.js.map +1 -1
  12. package/dist/opportunity/discriminator/discriminator.miner.d.ts +3 -1
  13. package/dist/opportunity/discriminator/discriminator.miner.d.ts.map +1 -1
  14. package/dist/opportunity/discriminator/discriminator.miner.js +48 -18
  15. package/dist/opportunity/discriminator/discriminator.miner.js.map +1 -1
  16. package/dist/opportunity/negotiation-summary.builder.d.ts +1 -1
  17. package/dist/opportunity/negotiation-summary.builder.d.ts.map +1 -1
  18. package/dist/opportunity/negotiation-summary.builder.js.map +1 -1
  19. package/dist/opportunity/opportunity.discover.js.map +1 -1
  20. package/dist/opportunity/opportunity.graph.d.ts +323 -15
  21. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  22. package/dist/opportunity/opportunity.state.d.ts +68 -2
  23. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  24. package/dist/opportunity/opportunity.state.js.map +1 -1
  25. package/dist/opportunity/outcome/outcome.env.d.ts +61 -0
  26. package/dist/opportunity/outcome/outcome.env.d.ts.map +1 -0
  27. package/dist/opportunity/outcome/outcome.env.js +64 -0
  28. package/dist/opportunity/outcome/outcome.env.js.map +1 -0
  29. package/dist/opportunity/outcome/outcome.hypotheses.d.ts +30 -0
  30. package/dist/opportunity/outcome/outcome.hypotheses.d.ts.map +1 -0
  31. package/dist/opportunity/outcome/outcome.hypotheses.js +118 -0
  32. package/dist/opportunity/outcome/outcome.hypotheses.js.map +1 -0
  33. package/dist/opportunity/outcome/outcome.shadow.d.ts +57 -0
  34. package/dist/opportunity/outcome/outcome.shadow.d.ts.map +1 -0
  35. package/dist/opportunity/outcome/outcome.shadow.js +88 -0
  36. package/dist/opportunity/outcome/outcome.shadow.js.map +1 -0
  37. package/dist/opportunity/outcome/outcome.types.d.ts +111 -0
  38. package/dist/opportunity/outcome/outcome.types.d.ts.map +1 -0
  39. package/dist/opportunity/outcome/outcome.types.js +27 -0
  40. package/dist/opportunity/outcome/outcome.types.js.map +1 -0
  41. package/dist/questioner/questioner.discovery.prompt.d.ts +9 -0
  42. package/dist/questioner/questioner.discovery.prompt.d.ts.map +1 -0
  43. package/dist/{opportunity/question.prompt.js → questioner/questioner.discovery.prompt.js} +6 -7
  44. package/dist/questioner/questioner.discovery.prompt.js.map +1 -0
  45. package/dist/questioner/questioner.presets.js +2 -2
  46. package/dist/questioner/questioner.presets.js.map +1 -1
  47. package/dist/questioner/questioner.types.d.ts +2 -2
  48. package/dist/questioner/questioner.types.d.ts.map +1 -1
  49. package/dist/questioner/questioner.types.js.map +1 -1
  50. package/dist/shared/agent/model.config.d.ts +0 -5
  51. package/dist/shared/agent/model.config.d.ts.map +1 -1
  52. package/dist/shared/agent/model.config.js +0 -1
  53. package/dist/shared/agent/model.config.js.map +1 -1
  54. package/dist/shared/interfaces/database.interface.d.ts +28 -2
  55. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  56. package/dist/shared/interfaces/database.interface.js.map +1 -1
  57. package/dist/shared/interfaces/negotiation-summary.interface.d.ts +1 -1
  58. package/dist/shared/interfaces/negotiation-summary.interface.d.ts.map +1 -1
  59. package/dist/shared/interfaces/negotiation-summary.interface.js.map +1 -1
  60. package/dist/shared/interfaces/question-generator.interface.d.ts +7 -5
  61. package/dist/shared/interfaces/question-generator.interface.d.ts.map +1 -1
  62. package/dist/shared/interfaces/question-generator.interface.js.map +1 -1
  63. package/dist/shared/schemas/discovery-question.schema.d.ts +2 -2
  64. package/package.json +1 -1
  65. package/dist/opportunity/question.generator.d.ts +0 -22
  66. package/dist/opportunity/question.generator.d.ts.map +0 -1
  67. package/dist/opportunity/question.generator.js +0 -147
  68. package/dist/opportunity/question.generator.js.map +0 -1
  69. package/dist/opportunity/question.prompt.d.ts +0 -102
  70. package/dist/opportunity/question.prompt.d.ts.map +0 -1
  71. package/dist/opportunity/question.prompt.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"./question.prompt.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n *\n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n *\n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n\n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable networks: the bound network plus the user's personal network).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n // ─── Intermediate Fields (Accumulated) ───\n\n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; text: string; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n // ─── Output Fields (Overwrite per turn) ───\n\n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
1
+ {"version":3,"file":"opportunity.state.js","sourceRoot":"/","sources":["opportunity/opportunity.state.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotation } from \"@langchain/langgraph\";\nimport type { Id } from '../shared/interfaces/database.interface.js';\nimport type { OpportunityStatus, Opportunity } from '../shared/interfaces/database.interface.js';\nimport type { Lens } from '../shared/interfaces/embedder.interface.js';\nimport type { EvaluatorEntity } from './opportunity.evaluator.js';\nimport type { DebugMetaAgent } from '../chat/chat-streaming.types.js';\nimport type { OpportunityEvidence } from '../shared/schemas/network-assignment.schema.js';\nimport type { DiscoveryNegotiation, DiscoverySummary } from \"../shared/schemas/discovery-question.schema.js\";\n\n/**\n * Opportunity Graph State (Linear Multi-Step Workflow)\n *\n * Flow: Prep → Scope → Discovery → Evaluation → Ranking → Persist → END\n *\n * Following the intent graph pattern with Annotation-based state management.\n */\n\n/** Asker's profile shape (identity + context). Used by sourceProfile annotation. */\nexport interface SourceProfileData {\n identity?: { name?: string; bio?: string; location?: string };\n context?: string;\n}\n\n/**\n * Indexed intent with hyde document (from prep node)\n */\nexport interface IndexedIntent {\n intentId: Id<'intents'>;\n payload: string;\n summary?: string;\n hydeDocumentId?: string;\n hydeEmbedding?: number[];\n indexes: Id<'networks'>[];\n}\n\n/**\n * Target index for search (from scope node)\n */\nexport interface TargetNetwork {\n networkId: Id<'networks'>;\n title: string;\n memberCount: number;\n}\n\n/**\n * Candidate match from discovery (semantic search).\n */\nexport interface CandidateMatch {\n candidateUserId: Id<'users'>;\n candidateIntentId?: Id<'intents'>;\n /** Source premise that produced this candidate (set when discoverySource is 'premise-similarity'). */\n sourcePremiseId?: Id<'premises'>;\n /** Candidate premise that matched this candidate (set for premise-based matches). */\n candidatePremiseId?: Id<'premises'>;\n /** Source context that produced this candidate (set when discoverySource is 'context-to-intent'). */\n sourceContextId?: string;\n networkId: Id<'networks'>;\n similarity: number;\n /** Free-text lens label that produced this match. */\n lens: string;\n candidatePayload: string;\n candidateSummary?: string;\n /** How this candidate was found: 'query' (HyDE from search text), 'premise-similarity', or 'context-to-intent'. */\n discoverySource?: 'query' | 'premise-similarity' | 'context-to-intent';\n /** Which discovery strategies found this candidate (set by mergeStrategyCandidates). */\n matchedStrategies?: string[];\n /** Typed evidence that explains why this candidate entered evaluation. */\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Evaluated candidate with LLM scoring (legacy; used when evaluator returns source/candidate pair).\n * candidateIntentId is set for intent matches; omitted for profile-only matches.\n */\nexport interface EvaluatedCandidate {\n sourceUserId: Id<'users'>;\n candidateUserId: Id<'users'>;\n sourceIntentId?: Id<'intents'>;\n candidateIntentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n score: number; // 0-100\n reasoning: string; // Third-party analytical explanation of the match (for LLM agents)\n valencyRole: 'Agent' | 'Patient' | 'Peer';\n /** Free-text lens label that produced this match. */\n lens: string;\n}\n\n/**\n * Actor in an evaluated opportunity (from entity-bundle evaluator).\n * networkId is filled from the entity bundle in the graph, not by the evaluator.\n */\nexport interface EvaluatedOpportunityActor {\n userId: Id<'users'>;\n role: 'agent' | 'patient' | 'peer';\n intentId?: Id<'intents'>;\n networkId: Id<'networks'>;\n}\n\n/**\n * Evaluated opportunity with multi-actor output (entity-bundle evaluator).\n */\nexport interface EvaluatedOpportunity {\n actors: EvaluatedOpportunityActor[];\n score: number;\n reasoning: string;\n evidence?: OpportunityEvidence[];\n}\n\n/**\n * Which flow triggered this graph invocation. Determines initial persist status,\n * park-window timeout, streaming behavior, and whether AbortSignal is honored.\n *\n * - 'ambient' (default): queue-driven. Persists at the trigger default of\n * `pending` unless `options.initialStatus` overrides (the queue worker\n * passes `'latent'`, chat-bound ambient discovery passes `'draft'`). 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven. Persists at the trigger default of\n * `negotiating` unless `options.initialStatus` overrides. 60s park window,\n * streams `opportunity_draft_ready` events, honors abort.\n *\n * See {@link resolveInitialStatus} for the exact fallback used when\n * `options.initialStatus` is undefined.\n */\nexport type OpportunityTrigger = 'ambient' | 'orchestrator';\n\n/**\n * Resolves the initial status for opportunities created in the persist node.\n *\n * Explicit `options.initialStatus` always wins (callers like the chat tool or\n * maintenance scripts override per-call). When the caller leaves it\n * undefined, the trigger drives the default:\n * - 'orchestrator' → 'negotiating' (chat-driven; negotiations run before the\n * user sees a draft card).\n * - 'ambient' (or any other trigger) → 'pending' (long-standing default for\n * queue- and intent-driven discovery).\n *\n * Lives here rather than in opportunity.graph.ts so unit tests can exercise\n * it without pulling in the full graph (and the evaluator's LLM requirements).\n *\n * @param trigger - The graph invocation's trigger\n * @param explicit - Caller-supplied initial status from options.initialStatus\n */\nexport function resolveInitialStatus(\n trigger: OpportunityTrigger,\n explicit: OpportunityStatus | undefined,\n): OpportunityStatus {\n if (explicit !== undefined) return explicit;\n return trigger === 'orchestrator' ? 'negotiating' : 'pending';\n}\n\n/**\n * Options passed to the graph\n */\nexport interface OpportunityGraphOptions {\n /** Initial status for created opportunities (default: 'pending') */\n initialStatus?: OpportunityStatus;\n /** Minimum score threshold (default: 50) */\n minScore?: number;\n /** Maximum opportunities to return (default: 20) */\n limit?: number;\n /** Pre-inferred lenses (if not provided, lens inference runs automatically in HyDE graph) */\n lenses?: Lens[];\n /** User's search query for HyDE generation */\n hydeDescription?: string;\n /** Existing opportunities summary for evaluator deduplication */\n existingOpportunities?: string;\n /** Chat session ID for draft opportunities; stored as context.conversationId for visibility filtering. */\n conversationId?: string;\n /**\n * MCP-only: cap the negotiate-phase wall-clock at this many milliseconds.\n * When set, `negotiateNode` races `negotiateCandidates(...)` against a timer;\n * if the timer wins, the node returns early with a `timed_out` trace and the\n * unawaited negotiation chains finalize each opp's DB status in the\n * background. Set to 20_000 by the MCP `discover_opportunities` handler.\n * Chat, ambient queue, and all other callers omit this — existing behavior.\n */\n negotiateTimeoutMs?: number;\n}\n\n/**\n * Opportunity Graph State Annotation\n */\nexport const OpportunityGraphState = Annotation.Root({\n // ─── Input Fields (Required) ───\n userId: Annotation<Id<'users'>>({\n reducer: (curr, next) => next ?? curr,\n default: () => '' as Id<'users'>,\n }),\n\n searchQuery: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n networkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /**\n * Optional set of indexes discovery may search within (e.g. a network-scoped\n * agent's reachable networks: the bound network plus the user's personal network).\n * The scope node intersects this with the user's actual memberships. Ignored\n * when `networkId` is set (single-network override). When unset, discovery\n * spans all of the user's networks.\n */\n indexScope: Annotation<Id<'networks'>[] | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional intent to use as discovery source and for triggeredBy. When set, used for search text (if query empty) and persist. */\n triggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: restrict discovery to this specific user ID only (direct connection). */\n targetUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Optional: discover on behalf of this user (introducer flow). When set, prep/eval use this user's profile/intents; userId becomes the introducer. */\n onBehalfOfUserId: Annotation<Id<'users'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n options: Annotation<OpportunityGraphOptions>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /**\n * Which flow triggered this graph invocation. See {@link OpportunityTrigger}\n * for the exact branch behavior and {@link resolveInitialStatus} for the\n * persist default when `options.initialStatus` is unset.\n *\n * - 'ambient' (default): queue-driven, persist default `pending`, 5-min\n * park window, no streaming, ignores abort.\n * - 'orchestrator': chat-driven, persist default `negotiating`, 60s park\n * window, streams `opportunity_draft_ready` events, honors abort.\n */\n trigger: Annotation<OpportunityTrigger>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'ambient',\n }),\n\n /**\n * Accepted opportunities the persist node discovered between the discoverer\n * and a candidate actor (same pair, status='accepted'). The orchestrator\n * branch populates this so the discover_opportunities tool (Task 7) can tell\n * the LLM \"these pairs are already connected, surface the existing chat\n * rather than creating a new draft\". Always empty for the ambient trigger.\n *\n * Left intentionally minimal — conversationId/URL resolution happens at\n * Start Chat time (Task 8), not here.\n */\n dedupAlreadyAccepted: Annotation<Array<{\n opportunityId: string;\n counterpartyUserId: string;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /**\n * Operation mode controls graph flow:\n * - 'create': Existing discover pipeline (Prep → Scope → Discovery → Evaluation → Ranking → Persist)\n * - 'create_introduction': Introduction path (validation → evaluation → persist) for chat-driven intros\n * - 'continue_discovery': Pagination path (Prep → Evaluation → Ranking → Persist) using pre-loaded candidates\n * - 'read': List opportunities filtered by userId and optionally networkId (fast path)\n * - 'update': Change opportunity status (accept, reject, etc.)\n * - 'delete': Expire/archive an opportunity\n * - 'send': Promote latent opportunity to pending + queue notification\n * - 'negotiate_existing': Load an existing opportunity by opportunityId and run bilateral negotiation.\n * Used after introducer approval to trigger the normal negotiation flow.\n * - 'approve_introduction': Mark the caller as having approved a latent introducer opportunity,\n * then enqueue a negotiate_existing job for that opportunity.\n *\n * Defaults to 'create' for backward compatibility.\n */\n operationMode: Annotation<'create' | 'create_introduction' | 'continue_discovery' | 'read' | 'update' | 'delete' | 'send' | 'negotiate_existing' | 'approve_introduction'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'create' as const,\n }),\n\n /** Introduction mode: pre-gathered entities (profiles + intents per party). */\n introductionEntities: Annotation<EvaluatorEntity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Introduction mode: optional hint from the introducer. */\n introductionHint: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** When set (e.g. chat scope), networkId must match this. */\n requiredNetworkId: Annotation<Id<'networks'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Set by intro_evaluation; used by persist to build manual detection and introducer actor. */\n introductionContext: Annotation<{ createdByName?: string } | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Target opportunity ID for update/delete/send modes. */\n opportunityId: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** New status for update mode (e.g. 'accepted', 'rejected'). */\n newStatus: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n // ─── Intermediate Fields (Accumulated) ───\n\n /** User's indexed intents with hyde documents (from prep) */\n indexedIntents: Annotation<IndexedIntent[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User's network memberships (from prep) */\n userNetworks: Annotation<Id<'networks'>[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Target indexes to search within (from scope) */\n targetNetworks: Annotation<TargetNetwork[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Per-index relevancy scores for dedup tie-breaking. Background path: from intent_indexes. Chat path: transient from IntentIndexer. */\n indexRelevancyScores: Annotation<Record<string, number>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Whether discovery used intent (path A) or user context (path B/C). Used by persist for triggeredBy. In-memory routing state only; never persisted. */\n discoverySource: Annotation<'intent' | 'context'>({\n reducer: (curr, next) => next ?? curr,\n default: () => 'intent',\n }),\n\n /** Resolved intent ID used for this discovery run (when discoverySource is 'intent'). Set by intent-resolution. */\n resolvedTriggerIntentId: Annotation<Id<'intents'> | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** Asker's profile (from prep). Used for profile-as-source discovery and evaluation. */\n sourceProfile: Annotation<SourceProfileData | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** User's active premises with embeddings (from prep). Used for premise-to-premise discovery path D. */\n sourcePremises: Annotation<Array<{ premiseId: Id<'premises'>; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** User context embeddings per network (from prep). Used for context-to-intent discovery. */\n sourceContexts: Annotation<Array<{ contextId: string; networkId: Id<'networks'>; text: string; embedding: number[] }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Resolved intent is in at least one target index (path A vs C). */\n resolvedIntentInIndex: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Create-intent signal: when true, tool should return createIntentSuggested so agent can auto-call create_intent. */\n createIntentSuggested: Annotation<boolean>({\n reducer: (curr, next) => next ?? curr,\n default: () => false,\n }),\n\n /** Suggested description for create_intent when createIntentSuggested is true. */\n suggestedIntentDescription: Annotation<string | undefined>({\n reducer: (curr, next) => next ?? curr,\n default: () => undefined,\n }),\n\n /** HyDE embeddings per lens label (from discovery) */\n hydeEmbeddings: Annotation<Record<string, number[]>>({\n reducer: (curr, next) => next ?? curr,\n default: () => ({}),\n }),\n\n /** Candidate matches from semantic search (from discovery) */\n candidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Candidates not yet evaluated (for pagination -- cached in Redis by caller). */\n remainingCandidates: Annotation<CandidateMatch[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Discovery session ID for pagination (maps to Redis cache key). */\n discoveryId: Annotation<string | null>({\n reducer: (curr, next) => next ?? curr,\n default: () => null,\n }),\n\n /** Evaluated candidates with scores (from evaluation; legacy) */\n evaluatedCandidates: Annotation<EvaluatedCandidate[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Evaluated opportunities with actors (from entity-bundle evaluator) */\n evaluatedOpportunities: Annotation<EvaluatedOpportunity[]>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n // ─── Output Fields (Overwrite per turn) ───\n\n /** Final ranked and persisted opportunities */\n opportunities: Annotation<Opportunity[]>({\n reducer: (curr, next) => next,\n default: () => [],\n }),\n\n /** Discovery path: pairs skipped because an opportunity already exists between viewer and candidate (no duplicate created). */\n existingBetweenActors: Annotation<Array<{\n candidateUserId: Id<'users'>;\n networkId: Id<'networks'>;\n existingOpportunityId?: Id<'opportunities'>;\n existingStatus?: OpportunityStatus;\n }>>({\n reducer: (curr, next) => next ?? curr,\n default: () => [],\n }),\n\n /** Error message if any step fails */\n error: Annotation<string | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for read mode: enriched list of opportunities. */\n readResult: Annotation<{\n count: number;\n message?: string;\n opportunities: Array<{\n id: string;\n indexName: string;\n connectedWith: string[];\n suggestedBy: string | null;\n reasoning: string;\n status: string;\n category: string;\n confidence: number | null;\n source: string | null;\n }>;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n /** Output for update/delete/send modes. */\n mutationResult: Annotation<{\n success: boolean;\n message?: string;\n opportunityId?: string;\n notified?: string[];\n conversationId?: string;\n error?: string;\n } | undefined>({\n reducer: (curr, next) => next,\n default: () => undefined,\n }),\n\n // ─── Trace Output ───\n\n /**\n * Accumulated trace entries from each graph node.\n * Used for observability: surfaces internal processing steps (search query, HyDE strategies,\n * candidates found, evaluation results) to the frontend.\n */\n trace: Annotation<Array<{ node: string; detail?: string; data?: Record<string, unknown> }>>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Timing records for each agent invocation within this graph run. */\n agentTimings: Annotation<DebugMetaAgent[]>({\n reducer: (acc, val) => [...acc, ...val],\n default: () => [],\n }),\n\n /**\n * Per-candidate negotiation records captured by `negotiateNode`. Populated\n * regardless of accept/reject so the question generator sees a complete\n * picture. Populated for ALL triggers (ambient + orchestrator) since the\n * negotiate node's `onCandidateResolved` hook is unconditional; only the\n * orchestrator streaming side-effects (opportunity_draft_ready emission)\n * are trigger-gated. Empty when the negotiate node was skipped (no\n * opportunities to negotiate).\n */\n discoveryNegotiations: Annotation<DiscoveryNegotiation[]>({\n reducer: (curr, next) => [...curr, ...(next || [])],\n default: () => [],\n }),\n\n /** Aggregate counters across `discoveryNegotiations`. Built in the negotiate node. */\n discoverySummary: Annotation<DiscoverySummary | null>({\n reducer: (_curr, next) => next ?? null,\n default: () => null,\n }),\n});\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Centralized accessors for Lens B outcome-question environment variables
3
+ * (IND-434).
4
+ *
5
+ * OUTCOME_QUESTIONS_MODE off | shadow | on — Lens B "explicit opportunity
6
+ * outcomes → grounded questions" pipeline.
7
+ *
8
+ * off (default): no outcome feedback events are
9
+ * captured and no hypothesis mining runs. Zero
10
+ * behavior change.
11
+ * shadow: capture one idempotent append-only
12
+ * feedback event per explicit owner action, mine
13
+ * neutral trade-off hypotheses for the exact
14
+ * recipient + intent + fingerprint, and emit
15
+ * aggregate telemetry only. No question, ranking,
16
+ * intent, premise, memory, newborn-stamp, or push
17
+ * writes.
18
+ * on: reserved for a later phase (IND-438) that
19
+ * turns a grounded hypothesis into a user-facing
20
+ * question. This slice treats "on" exactly like
21
+ * "shadow" for capture + mining; it never emits
22
+ * questions.
23
+ *
24
+ * All reads go through this module — do not read this variable via
25
+ * `process.env` elsewhere. Values are read on every call (no caching) so tests
26
+ * and long-lived processes observe changes.
27
+ */
28
+ /** Lens B outcome-question mode. */
29
+ export type OutcomeQuestionsMode = "off" | "shadow" | "on";
30
+ /**
31
+ * Current OUTCOME_QUESTIONS_MODE (default off). Only the exact trimmed literals
32
+ * "shadow" and "on" activate; every other value (including unset/empty) is off.
33
+ */
34
+ export declare function outcomeQuestionsMode(): OutcomeQuestionsMode;
35
+ /**
36
+ * True when Lens B capture + mining should run at all. Both "shadow" and "on"
37
+ * activate the shadow pipeline; only the future question-emitting behavior is
38
+ * gated on "on" (not part of this slice).
39
+ */
40
+ export declare function isOutcomeQuestionsActivated(): boolean;
41
+ /**
42
+ * Independent-support threshold (k). Every compared group (discriminator side)
43
+ * must have at least this many INDEPENDENT (related-opportunity-deduplicated)
44
+ * examples before the hypothesis is eligible. Enforced per side, so no side
45
+ * can be traced back to a small handful of individuals.
46
+ */
47
+ export declare const OUTCOME_MIN_INDEPENDENT_SUPPORT = 5;
48
+ /** Minimum number of qualified sides (each ≥ k) for a hypothesis to compare. */
49
+ export declare const OUTCOME_MIN_COMPARED_SIDES = 2;
50
+ /**
51
+ * k-anonymity floor for attempting a mining pass at all: the number of
52
+ * distinct independent examples (after related-opportunity dedup) required
53
+ * before the miner is invoked. Set to k × minimum compared sides so a pass can
54
+ * never produce an eligible hypothesis below the aggregate floor.
55
+ */
56
+ export declare const OUTCOME_MIN_INDEPENDENT_EXAMPLES: number;
57
+ /** Max independent examples sent to the miner LLM (most recent first). */
58
+ export declare const OUTCOME_MAX_CANDIDATES = 48;
59
+ /** Max chars of presentation-safe candidate snapshot stored/sent per example. */
60
+ export declare const OUTCOME_MAX_PUBLIC_CONTEXT_CHARS = 400;
61
+ //# sourceMappingURL=outcome.env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.env.d.ts","sourceRoot":"/","sources":["opportunity/outcome/outcome.env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,oCAAoC;AACpC,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,oBAAoB,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,QACiB,CAAC;AAE/D,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,iFAAiF;AACjF,eAAO,MAAM,gCAAgC,MAAM,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Centralized accessors for Lens B outcome-question environment variables
3
+ * (IND-434).
4
+ *
5
+ * OUTCOME_QUESTIONS_MODE off | shadow | on — Lens B "explicit opportunity
6
+ * outcomes → grounded questions" pipeline.
7
+ *
8
+ * off (default): no outcome feedback events are
9
+ * captured and no hypothesis mining runs. Zero
10
+ * behavior change.
11
+ * shadow: capture one idempotent append-only
12
+ * feedback event per explicit owner action, mine
13
+ * neutral trade-off hypotheses for the exact
14
+ * recipient + intent + fingerprint, and emit
15
+ * aggregate telemetry only. No question, ranking,
16
+ * intent, premise, memory, newborn-stamp, or push
17
+ * writes.
18
+ * on: reserved for a later phase (IND-438) that
19
+ * turns a grounded hypothesis into a user-facing
20
+ * question. This slice treats "on" exactly like
21
+ * "shadow" for capture + mining; it never emits
22
+ * questions.
23
+ *
24
+ * All reads go through this module — do not read this variable via
25
+ * `process.env` elsewhere. Values are read on every call (no caching) so tests
26
+ * and long-lived processes observe changes.
27
+ */
28
+ /**
29
+ * Current OUTCOME_QUESTIONS_MODE (default off). Only the exact trimmed literals
30
+ * "shadow" and "on" activate; every other value (including unset/empty) is off.
31
+ */
32
+ export function outcomeQuestionsMode() {
33
+ const value = process.env.OUTCOME_QUESTIONS_MODE?.trim();
34
+ return value === "shadow" || value === "on" ? value : "off";
35
+ }
36
+ /**
37
+ * True when Lens B capture + mining should run at all. Both "shadow" and "on"
38
+ * activate the shadow pipeline; only the future question-emitting behavior is
39
+ * gated on "on" (not part of this slice).
40
+ */
41
+ export function isOutcomeQuestionsActivated() {
42
+ return outcomeQuestionsMode() !== "off";
43
+ }
44
+ /**
45
+ * Independent-support threshold (k). Every compared group (discriminator side)
46
+ * must have at least this many INDEPENDENT (related-opportunity-deduplicated)
47
+ * examples before the hypothesis is eligible. Enforced per side, so no side
48
+ * can be traced back to a small handful of individuals.
49
+ */
50
+ export const OUTCOME_MIN_INDEPENDENT_SUPPORT = 5;
51
+ /** Minimum number of qualified sides (each ≥ k) for a hypothesis to compare. */
52
+ export const OUTCOME_MIN_COMPARED_SIDES = 2;
53
+ /**
54
+ * k-anonymity floor for attempting a mining pass at all: the number of
55
+ * distinct independent examples (after related-opportunity dedup) required
56
+ * before the miner is invoked. Set to k × minimum compared sides so a pass can
57
+ * never produce an eligible hypothesis below the aggregate floor.
58
+ */
59
+ export const OUTCOME_MIN_INDEPENDENT_EXAMPLES = OUTCOME_MIN_INDEPENDENT_SUPPORT * OUTCOME_MIN_COMPARED_SIDES;
60
+ /** Max independent examples sent to the miner LLM (most recent first). */
61
+ export const OUTCOME_MAX_CANDIDATES = 48;
62
+ /** Max chars of presentation-safe candidate snapshot stored/sent per example. */
63
+ export const OUTCOME_MAX_PUBLIC_CONTEXT_CHARS = 400;
64
+ //# sourceMappingURL=outcome.env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.env.js","sourceRoot":"/","sources":["opportunity/outcome/outcome.env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,CAAC;IACzD,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,oBAAoB,EAAE,KAAK,KAAK,CAAC;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,CAAC;AAEjD,gFAAgF;AAChF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAC3C,+BAA+B,GAAG,0BAA0B,CAAC;AAE/D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC,iFAAiF;AACjF,MAAM,CAAC,MAAM,gCAAgC,GAAG,GAAG,CAAC","sourcesContent":["/**\n * Centralized accessors for Lens B outcome-question environment variables\n * (IND-434).\n *\n * OUTCOME_QUESTIONS_MODE off | shadow | on — Lens B \"explicit opportunity\n * outcomes → grounded questions\" pipeline.\n *\n * off (default): no outcome feedback events are\n * captured and no hypothesis mining runs. Zero\n * behavior change.\n * shadow: capture one idempotent append-only\n * feedback event per explicit owner action, mine\n * neutral trade-off hypotheses for the exact\n * recipient + intent + fingerprint, and emit\n * aggregate telemetry only. No question, ranking,\n * intent, premise, memory, newborn-stamp, or push\n * writes.\n * on: reserved for a later phase (IND-438) that\n * turns a grounded hypothesis into a user-facing\n * question. This slice treats \"on\" exactly like\n * \"shadow\" for capture + mining; it never emits\n * questions.\n *\n * All reads go through this module — do not read this variable via\n * `process.env` elsewhere. Values are read on every call (no caching) so tests\n * and long-lived processes observe changes.\n */\n\n/** Lens B outcome-question mode. */\nexport type OutcomeQuestionsMode = \"off\" | \"shadow\" | \"on\";\n\n/**\n * Current OUTCOME_QUESTIONS_MODE (default off). Only the exact trimmed literals\n * \"shadow\" and \"on\" activate; every other value (including unset/empty) is off.\n */\nexport function outcomeQuestionsMode(): OutcomeQuestionsMode {\n const value = process.env.OUTCOME_QUESTIONS_MODE?.trim();\n return value === \"shadow\" || value === \"on\" ? value : \"off\";\n}\n\n/**\n * True when Lens B capture + mining should run at all. Both \"shadow\" and \"on\"\n * activate the shadow pipeline; only the future question-emitting behavior is\n * gated on \"on\" (not part of this slice).\n */\nexport function isOutcomeQuestionsActivated(): boolean {\n return outcomeQuestionsMode() !== \"off\";\n}\n\n/**\n * Independent-support threshold (k). Every compared group (discriminator side)\n * must have at least this many INDEPENDENT (related-opportunity-deduplicated)\n * examples before the hypothesis is eligible. Enforced per side, so no side\n * can be traced back to a small handful of individuals.\n */\nexport const OUTCOME_MIN_INDEPENDENT_SUPPORT = 5;\n\n/** Minimum number of qualified sides (each ≥ k) for a hypothesis to compare. */\nexport const OUTCOME_MIN_COMPARED_SIDES = 2;\n\n/**\n * k-anonymity floor for attempting a mining pass at all: the number of\n * distinct independent examples (after related-opportunity dedup) required\n * before the miner is invoked. Set to k × minimum compared sides so a pass can\n * never produce an eligible hypothesis below the aggregate floor.\n */\nexport const OUTCOME_MIN_INDEPENDENT_EXAMPLES =\n OUTCOME_MIN_INDEPENDENT_SUPPORT * OUTCOME_MIN_COMPARED_SIDES;\n\n/** Max independent examples sent to the miner LLM (most recent first). */\nexport const OUTCOME_MAX_CANDIDATES = 48;\n\n/** Max chars of presentation-safe candidate snapshot stored/sent per example. */\nexport const OUTCOME_MAX_PUBLIC_CONTEXT_CHARS = 400;\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Lens B outcome join + threshold — pure functions, no LLM, no I/O (IND-434).
3
+ *
4
+ * Given discriminators whose candidate → side assignments were produced BLIND
5
+ * to outcome (the miner never saw which side the user chose), this module joins
6
+ * the explicit owner-outcome labels and produces aggregate-only telemetry:
7
+ *
8
+ * - Independence: capture admits only one unique counterpart per opportunity;
9
+ * the caller then deduplicates by that recipient-scoped counterpart hash.
10
+ * Every retained entry therefore represents one distinct counterpart.
11
+ * - Threshold: a discriminator side is "qualified" only when it holds at
12
+ * least `minIndependentSupport` (k) independent examples. A hypothesis is
13
+ * eligible only when at least `minComparedSides` sides qualify.
14
+ * - Small-cell suppression: only qualified sides (≥ k) are ever emitted, so
15
+ * no aggregate row can be traced to a small handful of individuals.
16
+ *
17
+ * The outcome label is joined here and ONLY here — the miner and the side
18
+ * assignments upstream are independent of it, so association can never leak
19
+ * into classification.
20
+ */
21
+ import type { JoinOutcomeHypothesesInput, OutcomeShadowResult } from "./outcome.types.js";
22
+ /**
23
+ * Join outcome labels onto blind side assignments and keep only hypotheses that
24
+ * clear the independent-support threshold on at least `minComparedSides` sides.
25
+ *
26
+ * @returns Aggregate telemetry: eligible hypotheses sorted by (min support desc,
27
+ * label asc) for deterministic ordering, each carrying only qualified sides.
28
+ */
29
+ export declare function joinOutcomeHypotheses(input: JoinOutcomeHypothesesInput): OutcomeShadowResult;
30
+ //# sourceMappingURL=outcome.hypotheses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.hypotheses.d.ts","sourceRoot":"/","sources":["opportunity/outcome/outcome.hypotheses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,0BAA0B,EAAqB,mBAAmB,EAAsB,MAAM,oBAAoB,CAAC;AAOjI;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,mBAAmB,CAsF5F"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Lens B outcome join + threshold — pure functions, no LLM, no I/O (IND-434).
3
+ *
4
+ * Given discriminators whose candidate → side assignments were produced BLIND
5
+ * to outcome (the miner never saw which side the user chose), this module joins
6
+ * the explicit owner-outcome labels and produces aggregate-only telemetry:
7
+ *
8
+ * - Independence: capture admits only one unique counterpart per opportunity;
9
+ * the caller then deduplicates by that recipient-scoped counterpart hash.
10
+ * Every retained entry therefore represents one distinct counterpart.
11
+ * - Threshold: a discriminator side is "qualified" only when it holds at
12
+ * least `minIndependentSupport` (k) independent examples. A hypothesis is
13
+ * eligible only when at least `minComparedSides` sides qualify.
14
+ * - Small-cell suppression: only qualified sides (≥ k) are ever emitted, so
15
+ * no aggregate row can be traced to a small handful of individuals.
16
+ *
17
+ * The outcome label is joined here and ONLY here — the miner and the side
18
+ * assignments upstream are independent of it, so association can never leak
19
+ * into classification.
20
+ */
21
+ import { OUTCOME_MIN_COMPARED_SIDES, OUTCOME_MIN_INDEPENDENT_SUPPORT } from "./outcome.env.js";
22
+ /** Round a rate to 0.01 for telemetry (never a raw count). */
23
+ function roundRate(n) {
24
+ return Math.round(n * 100) / 100;
25
+ }
26
+ /**
27
+ * Join outcome labels onto blind side assignments and keep only hypotheses that
28
+ * clear the independent-support threshold on at least `minComparedSides` sides.
29
+ *
30
+ * @returns Aggregate telemetry: eligible hypotheses sorted by (min support desc,
31
+ * label asc) for deterministic ordering, each carrying only qualified sides.
32
+ */
33
+ export function joinOutcomeHypotheses(input) {
34
+ const minSupport = input.minIndependentSupport ?? OUTCOME_MIN_INDEPENDENT_SUPPORT;
35
+ const minSides = input.minComparedSides ?? OUTCOME_MIN_COMPARED_SIDES;
36
+ // Distinct independent examples with a joinable outcome label.
37
+ const poolSize = input.examples.size;
38
+ const hypotheses = [];
39
+ for (const discriminator of input.discriminators) {
40
+ // Normalize sides by trimming, then reject the ENTIRE discriminator if any
41
+ // side is empty or if labels are not unique after normalization. Silently
42
+ // deduplicating malformed model output would turn an ambiguous comparison
43
+ // into apparently valid evidence.
44
+ const normalizedSides = discriminator.sides.map((side) => side.trim());
45
+ if (normalizedSides.some((side) => side.length === 0))
46
+ continue;
47
+ if (new Set(normalizedSides).size !== normalizedSides.length)
48
+ continue;
49
+ if (normalizedSides.length < minSides)
50
+ continue;
51
+ const validSides = new Set(normalizedSides);
52
+ // First normalize VERIFIED, labelled assignments by candidate id. If the
53
+ // same independent example is assigned to different sides, mark it
54
+ // ambiguous and exclude it from every side; it must never support two cells.
55
+ const sideById = new Map();
56
+ const ambiguousIds = new Set();
57
+ for (const assignment of discriminator.assignments) {
58
+ if (assignment.side === null || !assignment.verified)
59
+ continue;
60
+ const side = assignment.side.trim();
61
+ if (!validSides.has(side) || !input.examples.has(assignment.id))
62
+ continue;
63
+ const previous = sideById.get(assignment.id);
64
+ if (previous !== undefined && previous !== side)
65
+ ambiguousIds.add(assignment.id);
66
+ else if (previous === undefined)
67
+ sideById.set(assignment.id, side);
68
+ }
69
+ // Tally DISTINCT independent example ids per unambiguous side. Repeating
70
+ // the same assignment never increases support.
71
+ const idsBySide = new Map();
72
+ const acceptedIdsBySide = new Map();
73
+ for (const [id, side] of sideById) {
74
+ if (ambiguousIds.has(id))
75
+ continue;
76
+ const label = input.examples.get(id);
77
+ if (label === undefined)
78
+ continue;
79
+ if (!idsBySide.has(side))
80
+ idsBySide.set(side, new Set());
81
+ idsBySide.get(side).add(id);
82
+ if (label === "accepted") {
83
+ if (!acceptedIdsBySide.has(side))
84
+ acceptedIdsBySide.set(side, new Set());
85
+ acceptedIdsBySide.get(side).add(id);
86
+ }
87
+ }
88
+ // Keep only sides that clear the independent-support threshold (>= k
89
+ // genuinely distinct independent examples).
90
+ const qualifiedSides = [];
91
+ for (const side of normalizedSides) {
92
+ const support = idsBySide.get(side)?.size ?? 0;
93
+ if (support < minSupport)
94
+ continue; // small-cell: never emitted
95
+ const accepted = acceptedIdsBySide.get(side)?.size ?? 0;
96
+ qualifiedSides.push({
97
+ side,
98
+ independentSupport: support,
99
+ acceptRate: roundRate(accepted / support),
100
+ });
101
+ }
102
+ if (qualifiedSides.length < minSides)
103
+ continue; // not enough to compare
104
+ // Deterministic side ordering: support desc, then label asc.
105
+ qualifiedSides.sort((a, b) => b.independentSupport - a.independentSupport || a.side.localeCompare(b.side));
106
+ hypotheses.push({
107
+ label: discriminator.label,
108
+ questionSeed: discriminator.questionSeed,
109
+ sides: qualifiedSides,
110
+ evidenceRate: discriminator.evidenceRate,
111
+ minIndependentSupport: Math.min(...qualifiedSides.map((s) => s.independentSupport)),
112
+ });
113
+ }
114
+ // Deterministic hypothesis ordering: strongest support first, then label.
115
+ hypotheses.sort((a, b) => b.minIndependentSupport - a.minIndependentSupport || a.label.localeCompare(b.label));
116
+ return { poolSize, eligibleCount: hypotheses.length, hypotheses };
117
+ }
118
+ //# sourceMappingURL=outcome.hypotheses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.hypotheses.js","sourceRoot":"/","sources":["opportunity/outcome/outcome.hypotheses.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lens B outcome join + threshold — pure functions, no LLM, no I/O (IND-434).\n *\n * Given discriminators whose candidate → side assignments were produced BLIND\n * to outcome (the miner never saw which side the user chose), this module joins\n * the explicit owner-outcome labels and produces aggregate-only telemetry:\n *\n * - Independence: capture admits only one unique counterpart per opportunity;\n * the caller then deduplicates by that recipient-scoped counterpart hash.\n * Every retained entry therefore represents one distinct counterpart.\n * - Threshold: a discriminator side is \"qualified\" only when it holds at\n * least `minIndependentSupport` (k) independent examples. A hypothesis is\n * eligible only when at least `minComparedSides` sides qualify.\n * - Small-cell suppression: only qualified sides (≥ k) are ever emitted, so\n * no aggregate row can be traced to a small handful of individuals.\n *\n * The outcome label is joined here and ONLY here — the miner and the side\n * assignments upstream are independent of it, so association can never leak\n * into classification.\n */\n\nimport { OUTCOME_MIN_COMPARED_SIDES, OUTCOME_MIN_INDEPENDENT_SUPPORT } from \"./outcome.env.js\";\nimport type { JoinOutcomeHypothesesInput, OutcomeHypothesis, OutcomeShadowResult, OutcomeSideSupport } from \"./outcome.types.js\";\n\n/** Round a rate to 0.01 for telemetry (never a raw count). */\nfunction roundRate(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Join outcome labels onto blind side assignments and keep only hypotheses that\n * clear the independent-support threshold on at least `minComparedSides` sides.\n *\n * @returns Aggregate telemetry: eligible hypotheses sorted by (min support desc,\n * label asc) for deterministic ordering, each carrying only qualified sides.\n */\nexport function joinOutcomeHypotheses(input: JoinOutcomeHypothesesInput): OutcomeShadowResult {\n const minSupport = input.minIndependentSupport ?? OUTCOME_MIN_INDEPENDENT_SUPPORT;\n const minSides = input.minComparedSides ?? OUTCOME_MIN_COMPARED_SIDES;\n\n // Distinct independent examples with a joinable outcome label.\n const poolSize = input.examples.size;\n\n const hypotheses: OutcomeHypothesis[] = [];\n\n for (const discriminator of input.discriminators) {\n // Normalize sides by trimming, then reject the ENTIRE discriminator if any\n // side is empty or if labels are not unique after normalization. Silently\n // deduplicating malformed model output would turn an ambiguous comparison\n // into apparently valid evidence.\n const normalizedSides = discriminator.sides.map((side) => side.trim());\n if (normalizedSides.some((side) => side.length === 0)) continue;\n if (new Set(normalizedSides).size !== normalizedSides.length) continue;\n if (normalizedSides.length < minSides) continue;\n const validSides = new Set(normalizedSides);\n\n // First normalize VERIFIED, labelled assignments by candidate id. If the\n // same independent example is assigned to different sides, mark it\n // ambiguous and exclude it from every side; it must never support two cells.\n const sideById = new Map<string, string>();\n const ambiguousIds = new Set<string>();\n for (const assignment of discriminator.assignments) {\n if (assignment.side === null || !assignment.verified) continue;\n const side = assignment.side.trim();\n if (!validSides.has(side) || !input.examples.has(assignment.id)) continue;\n const previous = sideById.get(assignment.id);\n if (previous !== undefined && previous !== side) ambiguousIds.add(assignment.id);\n else if (previous === undefined) sideById.set(assignment.id, side);\n }\n\n // Tally DISTINCT independent example ids per unambiguous side. Repeating\n // the same assignment never increases support.\n const idsBySide = new Map<string, Set<string>>();\n const acceptedIdsBySide = new Map<string, Set<string>>();\n for (const [id, side] of sideById) {\n if (ambiguousIds.has(id)) continue;\n const label = input.examples.get(id);\n if (label === undefined) continue;\n if (!idsBySide.has(side)) idsBySide.set(side, new Set());\n idsBySide.get(side)!.add(id);\n if (label === \"accepted\") {\n if (!acceptedIdsBySide.has(side)) acceptedIdsBySide.set(side, new Set());\n acceptedIdsBySide.get(side)!.add(id);\n }\n }\n\n // Keep only sides that clear the independent-support threshold (>= k\n // genuinely distinct independent examples).\n const qualifiedSides: OutcomeSideSupport[] = [];\n for (const side of normalizedSides) {\n const support = idsBySide.get(side)?.size ?? 0;\n if (support < minSupport) continue; // small-cell: never emitted\n const accepted = acceptedIdsBySide.get(side)?.size ?? 0;\n qualifiedSides.push({\n side,\n independentSupport: support,\n acceptRate: roundRate(accepted / support),\n });\n }\n\n if (qualifiedSides.length < minSides) continue; // not enough to compare\n\n // Deterministic side ordering: support desc, then label asc.\n qualifiedSides.sort(\n (a, b) => b.independentSupport - a.independentSupport || a.side.localeCompare(b.side),\n );\n\n hypotheses.push({\n label: discriminator.label,\n questionSeed: discriminator.questionSeed,\n sides: qualifiedSides,\n evidenceRate: discriminator.evidenceRate,\n minIndependentSupport: Math.min(...qualifiedSides.map((s) => s.independentSupport)),\n });\n }\n\n // Deterministic hypothesis ordering: strongest support first, then label.\n hypotheses.sort(\n (a, b) => b.minIndependentSupport - a.minIndependentSupport || a.label.localeCompare(b.label),\n );\n\n return { poolSize, eligibleCount: hypotheses.length, hypotheses };\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Lens B shadow orchestrator (IND-434): deduplicate related opportunities,
3
+ * mine neutral axes over the deduplicated historical pool BLIND to outcome,
4
+ * then join the owner-outcome labels and threshold the result.
5
+ *
6
+ * No persistence, no questions, no UI. The output is consumed only by aggregate
7
+ * telemetry for human review.
8
+ *
9
+ * Pipeline:
10
+ * 1. Dedup related opportunities: collapse examples sharing a dedup key to a
11
+ * single representative (most recent), so a cluster of related opps can
12
+ * neither dominate the miner nor inflate a side's independent support.
13
+ * 2. Cap to the most recent OUTCOME_MAX_CANDIDATES for a bounded LLM pass.
14
+ * 3. Build the miner pool — PoolCandidate carries id + publicContext + score
15
+ * and, critically, NO outcome label. The miner is structurally blind.
16
+ * 4. Mine neutral axes + assign candidates (evidence-verified).
17
+ * 5. Join outcome labels (only now) and apply the k-support threshold.
18
+ */
19
+ import type { PoolDiscriminatorMiner } from "../discriminator/discriminator.miner.js";
20
+ import type { OutcomeExample, OutcomeShadowResult } from "./outcome.types.js";
21
+ /** Input for one Lens B shadow mining+join pass. */
22
+ export interface OutcomeShadowInput {
23
+ /**
24
+ * Intent payload (+ summary) text that owns this scope. Used only as miner
25
+ * context; never mixed with outcome labels.
26
+ */
27
+ intentText: string;
28
+ /**
29
+ * Captured owner-outcome examples for exactly one recipient + intent +
30
+ * fingerprint scope. Each carries a presentation-safe snapshot and a dedup
31
+ * key; outcome labels live here but are withheld from the miner.
32
+ */
33
+ examples: OutcomeExample[];
34
+ miner: Pick<PoolDiscriminatorMiner, "mine">;
35
+ /** Override the independent-support threshold (defaults to k). */
36
+ minIndependentSupport?: number;
37
+ /** Override the minimum qualified sides. */
38
+ minComparedSides?: number;
39
+ /** Override the LLM candidate cap. */
40
+ maxCandidates?: number;
41
+ signal?: AbortSignal;
42
+ }
43
+ /**
44
+ * Collapse related opportunities to independent examples: one representative
45
+ * (most recent by occurredAt) per distinct dedup key. Deterministic — ties on
46
+ * occurredAt fall back to opportunityId ordering.
47
+ */
48
+ export declare function deduplicateOutcomeExamples(examples: OutcomeExample[]): OutcomeExample[];
49
+ /**
50
+ * Run the Lens B shadow pipeline for one scope.
51
+ *
52
+ * Throws only when *mining* fails (callers are fire-and-forget and must catch).
53
+ * A pool that is empty or below the compare floor after dedup returns an empty
54
+ * result rather than throwing.
55
+ */
56
+ export declare function runOutcomeShadow(input: OutcomeShadowInput): Promise<OutcomeShadowResult>;
57
+ //# sourceMappingURL=outcome.shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome.shadow.d.ts","sourceRoot":"/","sources":["opportunity/outcome/outcome.shadow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAItF,OAAO,KAAK,EAAE,cAAc,EAAgB,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAI5F,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,KAAK,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;IAC5C,kEAAkE;IAClE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAgBvF;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgD9F"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Lens B shadow orchestrator (IND-434): deduplicate related opportunities,
3
+ * mine neutral axes over the deduplicated historical pool BLIND to outcome,
4
+ * then join the owner-outcome labels and threshold the result.
5
+ *
6
+ * No persistence, no questions, no UI. The output is consumed only by aggregate
7
+ * telemetry for human review.
8
+ *
9
+ * Pipeline:
10
+ * 1. Dedup related opportunities: collapse examples sharing a dedup key to a
11
+ * single representative (most recent), so a cluster of related opps can
12
+ * neither dominate the miner nor inflate a side's independent support.
13
+ * 2. Cap to the most recent OUTCOME_MAX_CANDIDATES for a bounded LLM pass.
14
+ * 3. Build the miner pool — PoolCandidate carries id + publicContext + score
15
+ * and, critically, NO outcome label. The miner is structurally blind.
16
+ * 4. Mine neutral axes + assign candidates (evidence-verified).
17
+ * 5. Join outcome labels (only now) and apply the k-support threshold.
18
+ */
19
+ import { protocolLogger } from "../../shared/observability/protocol.logger.js";
20
+ import { OUTCOME_MAX_CANDIDATES, OUTCOME_MIN_COMPARED_SIDES, OUTCOME_MIN_INDEPENDENT_SUPPORT } from "./outcome.env.js";
21
+ import { joinOutcomeHypotheses } from "./outcome.hypotheses.js";
22
+ const logger = protocolLogger("OutcomeQuestionShadow");
23
+ /**
24
+ * Collapse related opportunities to independent examples: one representative
25
+ * (most recent by occurredAt) per distinct dedup key. Deterministic — ties on
26
+ * occurredAt fall back to opportunityId ordering.
27
+ */
28
+ export function deduplicateOutcomeExamples(examples) {
29
+ const byKey = new Map();
30
+ for (const example of examples) {
31
+ const existing = byKey.get(example.dedupKey);
32
+ if (existing === undefined) {
33
+ byKey.set(example.dedupKey, example);
34
+ continue;
35
+ }
36
+ const newer = example.occurredAt > existing.occurredAt ||
37
+ (example.occurredAt === existing.occurredAt && example.opportunityId > existing.opportunityId);
38
+ if (newer)
39
+ byKey.set(example.dedupKey, example);
40
+ }
41
+ return [...byKey.values()].sort((a, b) => b.occurredAt.localeCompare(a.occurredAt) || a.opportunityId.localeCompare(b.opportunityId));
42
+ }
43
+ /**
44
+ * Run the Lens B shadow pipeline for one scope.
45
+ *
46
+ * Throws only when *mining* fails (callers are fire-and-forget and must catch).
47
+ * A pool that is empty or below the compare floor after dedup returns an empty
48
+ * result rather than throwing.
49
+ */
50
+ export async function runOutcomeShadow(input) {
51
+ const minSupport = input.minIndependentSupport ?? OUTCOME_MIN_INDEPENDENT_SUPPORT;
52
+ const minSides = input.minComparedSides ?? OUTCOME_MIN_COMPARED_SIDES;
53
+ const maxCandidates = input.maxCandidates ?? OUTCOME_MAX_CANDIDATES;
54
+ // 1–2. Dedup related opportunities, then cap to a bounded, recent pool.
55
+ const independent = deduplicateOutcomeExamples(input.examples).slice(0, maxCandidates);
56
+ if (independent.length < minSupport * minSides) {
57
+ return { poolSize: independent.length, eligibleCount: 0, hypotheses: [] };
58
+ }
59
+ // 3. Build the miner pool with RUN-LOCAL aliases (c0, c1, …) as candidate ids
60
+ // — raw opportunity ids are never sent to the LLM. The alias→outcome map is
61
+ // kept internally for the join. PoolCandidate deliberately excludes the
62
+ // outcome label, so the miner cannot condition on which side was chosen.
63
+ const aliased = independent.map((example, index) => ({ alias: `c${index}`, example }));
64
+ const candidates = aliased.map(({ alias, example }) => ({
65
+ id: alias,
66
+ publicContext: example.publicContext,
67
+ score: example.score ?? 1,
68
+ }));
69
+ // 4. Blind assignment (assignments come back keyed by the run-local alias).
70
+ const mined = await input.miner.mine({ intentText: input.intentText, candidates }, input.signal ? { signal: input.signal } : undefined);
71
+ if (mined.length === 0) {
72
+ return { poolSize: independent.length, eligibleCount: 0, hypotheses: [] };
73
+ }
74
+ // 5. Join outcome labels (only now) by alias, and threshold.
75
+ const examples = new Map(aliased.map(({ alias, example }) => [alias, example.label]));
76
+ const result = joinOutcomeHypotheses({
77
+ discriminators: mined,
78
+ examples,
79
+ minIndependentSupport: minSupport,
80
+ minComparedSides: minSides,
81
+ });
82
+ logger.debug("shadow join complete", {
83
+ poolSize: result.poolSize,
84
+ eligibleCount: result.eligibleCount,
85
+ });
86
+ return result;
87
+ }
88
+ //# sourceMappingURL=outcome.shadow.js.map