Unclassified

PRD — Backend Layer Development (Complete)

Date: June 5, 2026 | Authority: Nirmal (Founder)
Objective: Make all backend layers development-complete before any frontend wiring.
Reference: docs/tech/INTEGRATEWISE_PRODUCT_ARCHITECTURE.md (Sections A–D)


1. Goal

Complete the backend so that every projection endpoint returns real data from real schemas.
No stubs. No mocks. No "TODO" in production paths. Every layer works end-to-end independently
before the frontend touches it.


2. Success Criteria

When this PRD is complete:
  ✅ Any frontend store can call its projection endpoint and get real structured data
  ✅ A user can sign up, connect a tool, and see real Spine data in D1
  ✅ Memory loop captures → promotes → indexes → retrieves end-to-end
  ✅ Governance board has tasks, proposals, decisions flowing through
  ✅ Twin can assemble Entity 360 (Spine + Memory + Signals) from live data
  ✅ All services compile, deploy, and respond to health checks

3. Deliverables (Ordered by Dependency)

3.1 D1 Schema — Complete Data Layer

All tables needed by the system must exist in D1 (integratewise-spine-cache).

MIGRATION 0001: tenants, tenant_users, workspaces                    ✅ DONE
MIGRATION 0002: tenant_spine_config
  - tenant_id TEXT PRIMARY KEY
  - domains TEXT (JSON array)
  - industry TEXT
  - schema_version TEXT
  - allowed_entity_types TEXT (JSON array)
  - connector_configs TEXT (JSON object)
  - connected_connectors TEXT (JSON array)
  - subscription_tier TEXT DEFAULT 'free'
  - created_at, updated_at

MIGRATION 0003: spine_entities (the Spine in D1)
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - entity_type TEXT NOT NULL
  - name TEXT NOT NULL
  - source TEXT NOT NULL
  - source_id TEXT NOT NULL
  - health_score INTEGER
  - health_trend TEXT
  - arr INTEGER
  - tier TEXT
  - status TEXT
  - data TEXT (JSON — all entity-specific fields)
  - metadata TEXT (JSON)
  - created_at, updated_at
  - UNIQUE(tenant_id, source, source_id)
  INDEXES: tenant_id, entity_type, tenant_id+entity_type

MIGRATION 0004: signals
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - entity_id TEXT
  - entity_name TEXT
  - type TEXT NOT NULL
  - severity TEXT NOT NULL
  - title TEXT
  - body TEXT
  - evidence TEXT (JSON array)
  - data TEXT (JSON)
  - status TEXT DEFAULT 'active'
  - created_at TEXT
  INDEXES: tenant_id, tenant_id+entity_id, tenant_id+severity

MIGRATION 0005: proposals
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - entity_id TEXT
  - entity_name TEXT
  - title TEXT NOT NULL
  - body TEXT
  - confidence REAL
  - risk REAL
  - category TEXT
  - evidence TEXT (JSON array)
  - state TEXT DEFAULT 'pending'
  - proposed_by TEXT DEFAULT 'twin'
  - reviewed_by TEXT
  - reviewed_at TEXT
  - created_at, updated_at
  INDEXES: tenant_id+state, tenant_id+entity_id

MIGRATION 0006: governance (tasks + decisions + outcomes)
  tasks:
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - title TEXT NOT NULL
  - description TEXT
  - owner TEXT NOT NULL (human | twin | team)
  - status TEXT DEFAULT 'pending'
  - priority TEXT DEFAULT 'medium'
  - entity_refs TEXT (JSON array)
  - procedures TEXT (JSON array of {step, description, completed})
  - knowledge_refs TEXT (JSON array)
  - assignee TEXT
  - due_date TEXT
  - outcomes TEXT (JSON array)
  - created_at, updated_at
  INDEXES: tenant_id+status, tenant_id+owner

  decisions:
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - title TEXT NOT NULL
  - body TEXT
  - approved_by TEXT
  - evidence TEXT (JSON array)
  - expected_outcome TEXT
  - actual_outcome TEXT
  - entity_refs TEXT (JSON array)
  - source_proposal_id TEXT
  - created_at TEXT
  INDEXES: tenant_id

MIGRATION 0007: memory_metadata (D1 mirror for human browse)
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - category TEXT NOT NULL
  - title TEXT NOT NULL
  - entity_refs TEXT (JSON array)
  - confidence REAL
  - governance_state TEXT DEFAULT 'staging'
  - source TEXT
  - r2_key TEXT (pointer to R2 content)
  - vectorize_indexed INTEGER DEFAULT 0
  - created_at, updated_at
  INDEXES: tenant_id+category, tenant_id+governance_state

MIGRATION 0008: connectors_d1 (connection status for D1-first users)
  - id TEXT PRIMARY KEY
  - tenant_id TEXT NOT NULL
  - provider TEXT NOT NULL
  - status TEXT DEFAULT 'connected'
  - flow_type TEXT (A | B | C)
  - nango_connection_id TEXT
  - last_sync_at TEXT
  - sync_error TEXT
  - metadata TEXT (JSON)
  - created_at, updated_at
  - UNIQUE(tenant_id, provider)
  INDEXES: tenant_id

3.2 Auth Flow — CF Edge JWT

ENDPOINT: POST /api/v1/auth/signup
  Body: { email, password, name }
  Action: creates tenant + user in D1, issues JWT
  Returns: { token, tenant_id, user_id }

ENDPOINT: POST /api/v1/auth/login
  Body: { email, password }
  Action: validates credentials (D1 lookup), issues JWT
  Returns: { token, tenant_id, user_id }

ENDPOINT: POST /api/v1/auth/google
  Body: { google_token }
  Action: validates Google token, finds/creates user in D1, issues JWT
  Returns: { token, tenant_id, user_id }

ENDPOINT: GET /api/v1/auth/me
  Headers: Authorization: Bearer <jwt>
  Returns: { user_id, tenant_id, email, name, role, plan }

JWT STRUCTURE:
  sub: user_id
  tenant_id: tenant_id
  role: owner | admin | member
  plan: free | starter | pro | enterprise
  iat, exp (24h)

LOCATION: services/gateway/src/auth.ts (new file)
  Uses: services/tenants D1 tables for user lookup
  Uses: lib/jwt.ts for signJWT (already exists)

3.3 Onboarding Endpoint

ENDPOINT: POST /api/v1/onboarding/initialize
  Headers: Authorization: Bearer <jwt>
  Body: {
    domain: string,
    industry: string,
    department: string,
    company_size: string,
    workspace_name: string,
    connectors: [{ provider, entityTypes, flowType }]
  }
  Action:
    1. Write tenant_spine_config to D1
    2. Update tenant name/settings in D1
    3. For each connector: create connectors_d1 record
    4. Return { tenant_config, next_step: 'connect' }

ENDPOINT: POST /api/v1/onboarding/connect
  Headers: Authorization: Bearer <jwt>
  Body: { provider: string }
  Action:
    1. Call Nango createConnectSession (gets session token)
    2. Return { session_token, integration_id }
    3. Frontend opens Nango Connect UI with token
    4. After connect: Nango webhook → /nango/webhook → triggers creamy sync

LOCATION: services/gateway/src/onboarding.ts (new file)
  Or: extend services/tenants with these routes

3.4 Pipeline — Spine Write to D1

The Pipeline currently writes to Supabase. For initial users it must also write to D1.

CHANGE: services/pipeline (the 8-stage Normalizer output)
  After normalization: write entity to D1 spine_entities table
  Pattern:
    INSERT INTO spine_entities (id, tenant_id, entity_type, name, source, source_id, data, ...)
    ON CONFLICT (tenant_id, source, source_id) DO UPDATE SET ...

  This is the Spine write path for D1-first users.
  For promoted users: dual-write (D1 + Supabase).

SIGNAL GENERATION:
  After Spine write, if health_score changed or entity state changed:
    INSERT INTO signals (id, tenant_id, entity_id, type, severity, ...)
  Signal types: health_drop, renewal_approaching, engagement_drop, etc.
  These are what /v1/signals returns and what /desk shows.

3.5 Service Route Verification

Every downstream service must respond correctly to the paths the projection layer calls.

INTELLIGENCE SERVICE:
  GET /v1/signals?tenant_id=X&severity=Y&limit=Z           ✅ EXISTS (think)
  GET /v1/proposals?tenant_id=X&status=Y&limit=Z           ✅ EXISTS (govern)
  POST /v1/proposals (create proposal)                      ✅ EXISTS (govern)
  POST /v1/proposals/:id/approve                            ✅ EXISTS (govern)
  POST /v1/proposals/:id/reject                             ✅ EXISTS (govern)
  POST /v1/triage (trigger TriageWorkflow)                  ✅ EXISTS

PIPELINE SERVICE:
  GET /v1/spine/entities/:entityId                          ⬜ NEEDS BUILDING
    Read from D1 spine_entities WHERE id = ?
    Return: full entity object with parsed JSON fields

  POST /v1/spine/write                                      ⬜ NEEDS BUILDING
    Accept normalized entity from Normalizer
    Write to D1 spine_entities
    Generate signal if state changed
    Return: { written: true, entity_id, signal_generated: bool }

KNOWLEDGE SERVICE:
  GET /v1/memory/search?entity_id=X&limit=Y                ⬜ NEEDS BUILDING
    Query Vectorize with entity_id filter
    Return: top K memory entries (title, category, body snippet, confidence)

  GET /v1/memory/org?category=X&limit=Y                    ⬜ NEEDS BUILDING
    Query D1 memory_metadata WHERE category = X
    Return: metadata list (for human browse)

BFF/WORKFLOW SERVICE:
  GET /v1/tasks?tenant_id=X&limit=Y                        ⬜ NEEDS BUILDING
    Query D1 tasks table
    Return: task list with parsed procedures/outcomes

  POST /v1/tasks                                            ⬜ NEEDS BUILDING
    Create task in D1

  PATCH /v1/tasks/:id                                       ⬜ NEEDS BUILDING
    Update task status, procedures, outcomes

  GET /v1/decisions?tenant_id=X&limit=Y                    ⬜ NEEDS BUILDING
    Query D1 decisions table
    Return: decision list

3.6 Nango Live Connection

PREREQUISITE: NANGO_SECRET_KEY added to Cloudflare Secrets Store
  Secret store ID: 1fd83fc5881e4cd296ae3c2fbe80693c
  Secret name: NANGO_SECRET_KEY

NANGO DASHBOARD:
  Configure integrations: hubspot, jira (minimum for demo)
  Set webhook URL: https://gateway.integratewise.ai/nango/webhook
  Connection ID pattern: tenant_id

VERIFICATION:
  POST /internal/nango/session → returns { session_token }
  Frontend opens Nango UI → user authorizes → webhook fires
  Webhook → connector-sync triggers creamy sync
  Pipeline writes to D1 spine_entities
  GET /api/v1/projections/morning-context → shows real signals

3.7 Memory Loop End-to-End

WRITE PATH (code-complete from this session):
  memory.write_conversational → Pipeline db-proxy → D1
  memory.propose → D1 proposals table
  TriageWorkflow → R2 + Vectorize + D1 memory_metadata (atomic write)

READ PATH (needs verification):
  /v1/memory/search → Vectorize query → returns results
  /v1/memory/org → D1 memory_metadata → returns metadata list

INITIAL LOAD (needs trigger):
  memory-repo sync script → batch → memory.write_conversational
  → memory.propose (per doc, confidence 0.85)
  → TriageWorkflow auto-approves
  → R2 + Vectorize + D1

VERIFICATION:
  After initial load: Vectorize index has entries
  Twin can query: "what do we know about X" → gets results from Vectorize
  Entity 360: memory panel shows relevant entries for entity

3.8 R2 Spine Backup (A12 Mitigation)

After every Pipeline D1 write:
  Append to R2: spine-backup/{tenant_id}/{YYYY-MM-DD}.jsonl
  Content: one JSON line per entity written
  Retention: 30 days daily, weekly beyond that (cleanup cron)

IMPLEMENTATION:
  services/pipeline — after D1 write, env.FILES.put() one JSONL line
  R2 binding already exists on connector service (integratewise-files-prod)
  Pipeline needs FILES binding added to wrangler.toml

4. Dependency Order (Build Sequence)

STEP 1: D1 Migrations (0002–0008)
  No external dependency. Pure SQL.
  Apply: wrangler d1 migrations apply integratewise-spine-cache --remote

STEP 2: Auth flow (signup/login/me)
  Depends on: D1 tenants table (✅ done)
  Depends on: lib/jwt.ts (✅ exists)

STEP 3: Onboarding endpoint
  Depends on: D1 tenant_spine_config (step 1)
  Depends on: Auth flow (step 2)

STEP 4: Pipeline D1 write path
  Depends on: D1 spine_entities table (step 1)
  Depends on: D1 signals table (step 1)

STEP 5: Service route endpoints (Pipeline, Knowledge, BFF)
  Depends on: D1 tables (step 1)
  Depends on: Pipeline write (step 4) for data to exist

STEP 6: Nango live connection
  Depends on: NANGO_SECRET_KEY in secrets (manual)
  Depends on: Pipeline D1 write (step 4)
  Depends on: connector-sync triggering creamy (already wired)

STEP 7: Memory loop verification
  Depends on: Vectorize pipeline (TriageWorkflow — ✅ wired this session)
  Depends on: Knowledge /v1/memory/search endpoint (step 5)
  Depends on: memory-repo sync script invocation (manual or cron)

STEP 8: R2 backup
  Depends on: Pipeline D1 write (step 4)
  Depends on: R2 binding on Pipeline (needs wrangler.toml addition)

5. What Is NOT In Scope

✗ Frontend development (separate PRD: IW_FRONTEND_ARCHITECTURE.md)
✗ External LLM MCP access (Phase 2)
✗ Subscription/payment wiring (Phase 2)
✗ mcp.integratewise.ai DNS setup (after memory loop verified)
✗ Agent token issuance for Gemini/Claude/ChatGPT (after MCP DNS)
✗ Advanced chunking strategy (DOC-09 §5.1 — can iterate after basic works)
✗ Docs publication pipeline (Connection 5 — after memory loop)
✗ Folder watcher automation (Connection 2 — after memory loop)

6. Definition of Done

For each step to be "done":
  ✅ Code compiles (tsc --noEmit passes)
  ✅ D1 migration applied (--remote)
  ✅ Service responds to health check
  ✅ Endpoint returns correct shape (matches TypeScript interface in IW_FRONTEND_ARCHITECTURE.md)
  ✅ Committed to main with descriptive message
  ✅ Tested with curl or wrangler dev (at minimum)

7. Handoff Notes

THIS SESSION COMPLETED:
  ✅ Tenants → D1 migration (applied, Customer Zero seeded)
  ✅ TriageWorkflow → R2 + Vectorize + D1 atomic write
  ✅ BulkCleanupSwarmWorkflow binding enabled
  ✅ Gateway projection endpoints (morning-context, entity360, governance, inbox)
  ✅ Architecture docs (Sections A–D, Frontend Architecture)

NEXT SESSION STARTS AT:
  Step 1 — D1 migrations 0002–0008 (pure SQL, no external deps)
  Then Step 2 — Auth flow
  Then Step 4 — Pipeline D1 write

READ BEFORE STARTING:
  docs/tech/INTEGRATEWISE_PRODUCT_ARCHITECTURE.md
  docs/tech/IW_FRONTEND_ARCHITECTURE.md (Section 4 — API contract)
  services/gateway/src/projections.ts (projection pattern)
  services/tenants/migrations/0001_init_tenants.sql (schema pattern)

Backend layers complete = frontend can wire. Not before.
Build depth-first, not breadth-first.
Every endpoint must return real data from real schemas.