{"slug":"business/ENTITY_BINDING_AND_RELATIONSHIPS","title":"Entity Binding & Relationship Graph Architecture","section":"business","sectionLabel":"Business","status":"unclassified","type":"document","path":"business/ENTITY_BINDING_AND_RELATIONSHIPS.mdx","bytes":12096,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n> **Date:** 2026-06-09  \n> **Version:** 1.0.0  \n> **Authority:** Nirmal (Founder)  \n> **Status:** Canonical Reference\n\n---\n\n## Overview\n\nThe **Spine** is IntegrateWise's relationship graph - the single source of truth that binds emails, documents, chats, and all entities together. This document describes how entity resolution, binding, and relationships work across the entire system.\n\n---\n\n## The Three Layers\n\n### Layer 1: Structured Truth (Spine Entities)\n\n- **Source:** Flow A (Connectors → Normalizer → Spine)\n- **Writer:** Pipeline service (SOLE writer to Spine)\n- **Storage:** D1 (edge cache) + Supabase (fortress backup for promoted users)\n- **Entity Types:** Person, Account, Deal, Task, Ticket, Invoice, Project, etc.\n- **How It Works:** HubSpot Contact + Jira Reporter + Gmail sender → SAME Person entity\n\n### Layer 2: Unstructured Context (Linked to Entities)\n\n- **Source:** Flow B (Documents, emails, Slack, meetings, files)\n- **Writer:** Knowledge service\n- **Storage:** R2 (raw content) + Vectorize (embeddings) + D1 (metadata + entity_refs)\n- **Table:** `context_extractions` with `entity_refs` column\n- **How It Works:** Email about Account X → linked to Account X via entity_refs\n\n### Layer 3: AI Memory (Linked to Entities)\n\n- **Source:** Flow C (Twin sessions, AI learnings)\n- **Writer:** Triage Bot (SOLE writer to memory after governance)\n- **Storage:** Supabase `memory.*` tables + D1 mirror + Vectorize\n- **Tables:** `conversational_memory`, `org_memory`, `personal_memory`\n- **How It Works:** Twin learns about Account X → memory entry linked to Account X\n\n---\n\n## Entity Resolution (How We Bind)\n\n### The 18 Core Traits\n\nEvery entity from every tool is analyzed for these traits:\n\n```typescript\nconst CORE_TRAITS = [\n  \"name\",\n  \"email\",\n  \"domain\",\n  \"phone\",\n  \"address\",\n  \"company\",\n  \"title\",\n  \"identifier\",\n  \"status\",\n  \"stage\",\n  \"amount\",\n  \"date\",\n  \"assignee\",\n  \"priority\",\n  \"description\",\n  \"url\",\n  \"type\",\n  \"category\",\n];\n```\n\n### The 7 Canonical Resource Types\n\nAll entities map to one of 7 types:\n\n1. **Person** - HubSpot Contact, Salesforce Lead, Jira Reporter, Gmail sender\n2. **Account** - HubSpot Company, Salesforce Account, Stripe Customer\n3. **Deal** - HubSpot Deal, Salesforce Opportunity, PandaDoc Proposal\n4. **Task** - Jira Issue, Asana Task, GitHub Issue, Linear Issue\n5. **Communication** - Email, Slack message, Intercom conversation\n6. **Document** - Google Doc, Notion page, Confluence page, PDF upload\n7. **Event** - Calendar meeting, Zoom call, webhook event\n\n### Resolution Algorithm\n\n**Normalizer Stage 4: Entity Resolution**\n\n```\nFor each incoming entity:\n1. Extract traits (name, email, domain, etc.)\n2. Detect resource type (Person, Account, Deal, etc.)\n3. Query Spine for potential matches:\n   - Exact email match (high confidence)\n   - Domain + name fuzzy match (medium confidence)\n   - Identifier match (tool-specific ID, low confidence)\n4. If match found → UPDATE existing entity\n5. If no match → CREATE new entity\n6. Record provenance: which tools contributed to this entity\n```\n\n---\n\n## Relationship Graph\n\n### Core Relationships Table\n\n**`entity_relationships` (D1)**\n\n```sql\nCREATE TABLE entity_relationships (\n  id TEXT PRIMARY KEY,\n  tenant_id TEXT NOT NULL,\n  from_entity_id TEXT NOT NULL,      -- spine_id\n  from_entity_type TEXT NOT NULL,    -- Person, Account, etc.\n  to_entity_id TEXT NOT NULL,        -- spine_id\n  to_entity_type TEXT NOT NULL,\n  relationship_type TEXT NOT NULL,   -- see types below\n  strength REAL DEFAULT 1.0,         -- 0.0-1.0 confidence\n  metadata JSONB,                    -- context, source, timestamps\n  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n\n  INDEX idx_from_entity (from_entity_id),\n  INDEX idx_to_entity (to_entity_id),\n  INDEX idx_tenant (tenant_id),\n  INDEX idx_relationship_type (relationship_type)\n);\n```\n\n### Relationship Types\n\n**Primary relationships (auto-detected by Normalizer):**\n\n```typescript\nconst RELATIONSHIP_TYPES = {\n  // Person → Account\n  works_at: \"Person works at Account\",\n  contacts_with: \"Person is contact at Account\",\n\n  // Person → Deal\n  owns_deal: \"Person owns Deal\",\n  involved_in: \"Person involved in Deal\",\n\n  // Account → Deal\n  has_deal: \"Account has Deal\",\n\n  // Person → Task\n  assigned_to: \"Person assigned to Task\",\n  created_task: \"Person created Task\",\n\n  // Account → Task\n  related_to_account: \"Task related to Account\",\n\n  // Communication → Any\n  mentions: \"Communication mentions Entity\",\n  about: \"Communication about Entity\",\n\n  // Document → Any\n  references: \"Document references Entity\",\n  belongs_to: \"Document belongs to Entity\",\n\n  // Memory → Any\n  learned_about: \"AI learned about Entity\",\n};\n```\n\n---\n\n## Context Linking (Flow B)\n\n### How Emails/Docs/Chats Get Linked\n\n**`context_extractions` table (D1):**\n\n```sql\nCREATE TABLE context_extractions (\n  id TEXT PRIMARY KEY,\n  tenant_id TEXT NOT NULL,\n  content_type TEXT NOT NULL,        -- email, slack, doc, meeting\n  source_tool TEXT NOT NULL,         -- gmail, slack, notion, etc.\n  source_id TEXT NOT NULL,           -- original ID from source\n  title TEXT,\n  summary TEXT,                      -- AI-generated summary\n  content_hash TEXT,                 -- R2 object key\n  entity_refs JSONB NOT NULL,        -- [{entity_id, entity_type, confidence}]\n  extracted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n\n  INDEX idx_entity_refs (entity_refs),  -- GIN index for JSONB queries\n  INDEX idx_tenant (tenant_id),\n  INDEX idx_source (source_tool, source_id)\n);\n```\n\n**Example: Email about Account**\n\n```json\n{\n  \"id\": \"ctx_abc123\",\n  \"tenant_id\": \"iw-customer-zero\",\n  \"content_type\": \"email\",\n  \"source_tool\": \"gmail\",\n  \"source_id\": \"msg_xyz789\",\n  \"title\": \"RE: Q4 Renewal Discussion\",\n  \"summary\": \"Discussion about Q4 contract renewal with pricing options\",\n  \"content_hash\": \"r2://emails/msg_xyz789.json\",\n  \"entity_refs\": [\n    {\n      \"entity_id\": \"spine_account_001\",\n      \"entity_type\": \"Account\",\n      \"confidence\": 0.95,\n      \"extraction_method\": \"email_domain_match\"\n    },\n    {\n      \"entity_id\": \"spine_person_042\",\n      \"entity_type\": \"Person\",\n      \"confidence\": 0.98,\n      \"extraction_method\": \"email_sender_match\"\n    },\n    {\n      \"entity_id\": \"spine_deal_017\",\n      \"entity_type\": \"Deal\",\n      \"confidence\": 0.85,\n      \"extraction_method\": \"subject_line_analysis\"\n    }\n  ],\n  \"extracted_at\": \"2026-06-09T10:30:00Z\"\n}\n```\n\n### Entity Extraction Process\n\n**Knowledge Service Pipeline:**\n\n```\n1. Receive document/email/chat\n2. Extract text content\n3. Run AI extraction (via Workers AI + OpenRouter):\n   - Identify mentioned entities (people, accounts, deals)\n   - Extract key topics and intents\n   - Generate summary\n4. Query Spine for entity matches:\n   - Email address → Person\n   - Company name → Account\n   - Deal ID / subject keywords → Deal\n5. Create context_extractions record with entity_refs\n6. Store raw content in R2\n7. Embed and index in Vectorize\n8. Update Entity 360 cache in D1\n```\n\n---\n\n## Entity 360 View\n\n### What It Is\n\nEntity 360 is the **unified view** of everything related to an entity:\n\n- Structured data (Spine)\n- Unstructured context (linked emails, docs, chats)\n- AI memory (learnings, signals)\n- Relationship graph (connected entities)\n\n### How It's Built\n\n**L2 Service: Entity 360 Worker**\n\n```typescript\nasync function buildEntity360(entityId: string, tenantId: string, env: Env) {\n  // 1. Get core entity from Spine (D1)\n  const entity = await env.D1.prepare(\"SELECT * FROM spine_entities WHERE id = ? AND tenant_id = ?\")\n    .bind(entityId, tenantId)\n    .first();\n\n  // 2. Get relationships\n  const relationships = await env.D1.prepare(\n    `\n      SELECT * FROM entity_relationships \n      WHERE (from_entity_id = ? OR to_entity_id = ?) \n      AND tenant_id = ?\n    `\n  )\n    .bind(entityId, entityId, tenantId)\n    .all();\n\n  // 3. Get linked context (emails, docs, chats)\n  const context = await env.D1.prepare(\n    `\n      SELECT * FROM context_extractions \n      WHERE entity_refs @> ?\n      AND tenant_id = ?\n      ORDER BY extracted_at DESC\n      LIMIT 50\n    `\n  )\n    .bind(JSON.stringify([{ entity_id: entityId }]), tenantId)\n    .all();\n\n  // 4. Get AI memory\n  const memory = await env.D1.prepare(\n    `\n      SELECT * FROM org_memory \n      WHERE metadata->>'entity_id' = ?\n      AND tenant_id = ?\n      AND governance_state = 'approved'\n      ORDER BY updated_at DESC\n      LIMIT 20\n    `\n  )\n    .bind(entityId, tenantId)\n    .all();\n\n  // 5. Get signals\n  const signals = await env.D1.prepare(\n    `\n      SELECT * FROM signals \n      WHERE entity_id = ?\n      AND tenant_id = ?\n      AND processed = 0\n      ORDER BY created_at DESC\n      LIMIT 10\n    `\n  )\n    .bind(entityId, tenantId)\n    .all();\n\n  return {\n    entity,\n    relationships: relationships.results,\n    context: context.results,\n    memory: memory.results,\n    signals: signals.results,\n    _cached_at: new Date().toISOString(),\n  };\n}\n```\n\n---\n\n## MCP Tool Exposure\n\n### Spine Tools (Available to Twin via MCP)\n\n```typescript\n// Read single entity + relationships\nspine.entity.get({ entity_id: \"spine_account_001\" });\n\n// Search entities by trait/type\nspine.entity.search({\n  type: \"Account\",\n  traits: { domain: \"acme.com\" },\n});\n\n// List relationships for an entity\nspine.relationship.list({\n  entity_id: \"spine_person_042\",\n  relationship_type: \"works_at\",\n});\n\n// Get Entity 360 view\nspine.entity360.get({ entity_id: \"spine_account_001\" });\n```\n\n---\n\n## Data Flow Summary\n\n### Flow A: Structured Entities (Repeat Loop)\n\n```\nHubSpot → Connector → Loader → Pipeline (Normalizer 8 stages)\n  → Spine D1 (entity resolution + relationships)\n  → Signal detection → Think → HITL → Act → back to HubSpot\n```\n\n### Flow B: Unstructured Context (No Repeat Loop)\n\n```\nGmail/Slack/Docs → Connector → Loader → Knowledge service\n  → Extract entities → Link via entity_refs → context_extractions D1\n  → R2 (raw content) + Vectorize (embeddings)\n  → Enriches Entity 360 (read-only, no loop back)\n```\n\n### Flow C: AI Memory (No Repeat Loop, Governed)\n\n```\nTwin session → Twin proposes memory → Triage Bot scores\n  → HITL approval → Triage Bot writes org_memory\n  → Linked to entities → Vectorize (retrieval)\n  → Twin reads via memory.* MCP tools\n```\n\n---\n\n## Current Implementation Status\n\n### ✅ Implemented (Phase 1)\n\n- Spine entity resolution (Normalizer stages 1-8)\n- Relationship graph (entity_relationships table)\n- Context extraction (context_extractions table)\n- Entity 360 caching (D1)\n- Memory linking (org_memory metadata)\n- MCP tools: spine.entity.get, spine.relationship.list\n\n### ⏳ Pending (Phase 2-4)\n\n- Auto-linking for emails (Gmail connector Flow B)\n- Auto-linking for Slack messages\n- Auto-linking for documents (Drive, Notion)\n- Relationship strength scoring algorithm\n- Entity merge/split UI\n- Relationship visualization\n\n---\n\n## Key Rules\n\n1. **Single Source of Truth:** Spine entities are canonical. Everything else links TO them, never replaces them.\n\n2. **One Writer Per Layer:**\n   - Spine: Pipeline service only\n   - Context: Knowledge service only\n   - Memory: Triage Bot only\n\n3. **No Direct Access:** Twin never writes to Spine. Twin proposes → Govern approves → Act executes → Pipeline writes.\n\n4. **Entity Refs Everywhere:** Every piece of unstructured content MUST have entity_refs. No orphaned context.\n\n5. **Provenance Tracking:** Every entity records which tools contributed to it. Every relationship records its source.\n\n6. **Edge-First:** D1 for speed, Supabase for durability (promoted users only).\n\n---\n\n## Related Documents\n\n- `INTEGRATEWISE_PRODUCT_ARCHITECTURE.md` - Overall system design\n- `MCP_ADK_ROUTING_SPINE_MEMORY_ARCHITECTURE.md` - MCP tools and routing\n- `CANONICAL_TAXONOMY.md` - S/U/V/CZ layers\n- `LAYER_OWNERSHIP_AND_TWIN_RUNTIME.md` - Twin responsibilities\n\n---\n\n**Document Status:** CANONICAL REFERENCE  \n**Last Updated:** 2026-06-09  \n**Next Review:** After Phase 2 Queue Wiring\n"}