{"slug":"architecture/PLATFORM_TO_FRONTEND_CONTRACT","title":"Platform-to-Frontend Contract","section":"architecture","sectionLabel":"Architecture","status":"unclassified","type":"document","path":"architecture/PLATFORM_TO_FRONTEND_CONTRACT.mdx","bytes":27030,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n**Generated:** July 29, 2026  \n**Purpose:** Define the clear frontend contract from the Platform\n\n---\n\n## Core Principle\n\n> **The frontend should not infer architecture, call multiple internal services, or reconstruct tenant state. It should receive a server-authoritative description of:**\n\n> **Who the user is, which workspace they are in, what they can see, what is available, what state it is in, and what actions they may perform.**\n\n---\n\n# 1. One Platform Boundary\n\nThe frontend should call only one base URL:\n\n```env\nNEXT_PUBLIC_IW_API_URL=https://api.integratewise.ai\n```\n\nAll browser requests should flow through:\n\n```\nFrontend\n↓\nPlatform Gateway\n↓\nIdentity / Configuration / Projection / Integration / Governance\n↓\nSpine and internal services\n```\n\n**The frontend must not call:**\n\n- MCP Pool directly\n- Provider Management MCP directly\n- Nango or Descope directly\n- D1, Neon, KV or R2\n- Provider APIs\n- Hermes\n- Individual Cloudflare Workers\n\n---\n\n# 2. The First API Call\n\nAfter login or workspace change, the frontend should call:\n\n```http\nGET /api/v1/workspace/discovery\n```\n\nThis should be the frontend's initial source of truth.\n\n## Suggested Response\n\n```typescript\ninterface WorkspaceDiscoveryResponse {\n  contractVersion: string;\n  traceId: string;\n  generatedAt: string;\n  state: \"ready\" | \"partial\" | \"degraded\" | \"pending\";\n\n  principal: {\n    userId: string;\n    displayName: string;\n    email?: string;\n    roles: string[];\n    permissions: string[];\n  };\n\n  workspace: {\n    tenantId: string;\n    workspaceId: string;\n    spineNetworkId: string;\n    name: string;\n    environment: \"development\" | \"customer-zero\" | \"production\";\n  };\n\n  navigation: NavigationSection[];\n\n  projections: ProjectionDescriptor[];\n\n  connectors: {\n    available: number;\n    connected: number;\n    degraded: number;\n    syncRunning: number;\n  };\n\n  capabilities: CapabilityDescriptor[];\n\n  approvals: {\n    pending: number;\n    urgent: number;\n  };\n\n  continuity: {\n    freshness: string;\n    lastHydratedAt?: string;\n    contextAvailable: boolean;\n  };\n\n  activation: {\n    complete: boolean;\n    blockers: ActivationBlocker[];\n  };\n\n  health: {\n    gateway: \"healthy\" | \"degraded\" | \"unavailable\";\n    spine: \"healthy\" | \"degraded\" | \"unavailable\";\n    integrations: \"healthy\" | \"degraded\" | \"unavailable\";\n  };\n}\n```\n\n**This prevents the frontend from hardcoding:**\n\n- Sidebar entries\n- Role-specific pages\n- Tenant IDs\n- Connector availability\n- Capability visibility\n- Approval availability\n- Activation status\n\n---\n\n# 3. What the Platform Must Expose\n\n## A. Identity and Workspace Context\n\nThe frontend needs a trusted context describing:\n\n```typescript\ninterface WorkspaceContext {\n  userId: string;\n  tenantId: string;\n  workspaceId: string;\n  spineNetworkId: string;\n\n  roles: string[];\n  permissions: string[];\n\n  department?: string;\n  industry?: string;\n\n  plan?: string;\n  entitlements: string[];\n}\n```\n\n**The Platform must derive this from the authenticated session.**\n\n**The browser must never be authoritative for:**\n\n```\ntenantId\nuserId\nrole\npermissions\nworkspaceId\nenvironment\n```\n\nThe frontend may request a workspace switch, but the Platform validates and resolves it.\n\n---\n\n## B. Navigation Contract\n\nThe Platform should tell the frontend which modules to render.\n\n```typescript\ninterface NavigationItem {\n  id: string;\n  label: string;\n  icon?: string;\n\n  route: string;\n  projectionId?: string;\n\n  visibility: \"visible\" | \"hidden\" | \"disabled\";\n  access: \"read\" | \"operate\" | \"admin\";\n\n  badge?: {\n    value: number;\n    severity?: \"info\" | \"warning\" | \"critical\";\n  };\n\n  reasonDisabled?: string;\n}\n\ninterface NavigationSection {\n  id: string;\n  label: string;\n  items: NavigationItem[];\n}\n```\n\n**Example:**\n\n```json\n{\n  \"id\": \"platform\",\n  \"label\": \"Platform\",\n  \"items\": [\n    {\n      \"id\": \"integration-manager\",\n      \"label\": \"Integration Manager\",\n      \"route\": \"/platform/integrations\",\n      \"visibility\": \"visible\",\n      \"access\": \"admin\"\n    },\n    {\n      \"id\": \"approvals\",\n      \"label\": \"Governance\",\n      \"route\": \"/approvals\",\n      \"visibility\": \"visible\",\n      \"access\": \"operate\",\n      \"badge\": {\n        \"value\": 6,\n        \"severity\": \"warning\"\n      }\n    }\n  ]\n}\n```\n\n**The frontend renders this. It should not independently decide what an admin, manager or member sees.**\n\n---\n\n## C. Projection Contract\n\nThe Platform should expose views as projection definitions rather than making the frontend understand database tables.\n\n```typescript\ninterface ProjectionDescriptor {\n  id: string;\n  version: string;\n  title: string;\n\n  type: \"table\" | \"cards\" | \"timeline\" | \"canvas\" | \"graph\" | \"inbox\" | \"queue\" | \"detail\";\n\n  entityTypes: string[];\n\n  endpoint: string;\n\n  capabilities: string[];\n\n  freshness: {\n    generatedAt: string;\n    sourceUpdatedAt?: string;\n    stale: boolean;\n  };\n\n  state: \"ready\" | \"partial\" | \"degraded\" | \"pending\";\n}\n```\n\n**Fetching a view:**\n\n```http\nGET /api/v1/projections/inbox\nGET /api/v1/projections/approvals\nGET /api/v1/projections/account-success\n```\n\n**Response:**\n\n```typescript\ninterface ProjectionResponse<T> {\n  contractVersion: string;\n  projectionId: string;\n  traceId: string;\n\n  state: \"ready\" | \"partial\" | \"degraded\" | \"pending\";\n\n  generatedAt: string;\n  sourceFreshness?: string;\n\n  schema: ProjectionSchema;\n  data: T;\n\n  allowedCapabilities: string[];\n\n  provenance: {\n    sources: string[];\n    evidenceIds?: string[];\n  };\n\n  remediation?: {\n    code: string;\n    message: string;\n    action?: string;\n  };\n}\n```\n\n**The frontend should render the projection rather than assemble raw Spine entities itself.**\n\n---\n\n## D. Connector Catalog\n\nThe Platform must expose a canonical connector catalog.\n\n```http\nGET /api/v1/connectors/catalog\n```\n\n```typescript\ninterface ConnectorDefinition {\n  id: string;\n  providerKey: string;\n\n  name: string;\n  description: string;\n  category: string;\n\n  iconUrl?: string;\n\n  authentication: \"oauth2\" | \"api-key\" | \"service-account\" | \"mcp\" | \"none\";\n\n  supportedEntities: string[];\n  supportedCapabilities: string[];\n\n  availability: {\n    status: \"available\" | \"private-alpha\" | \"beta\" | \"unavailable\";\n    reason?: string;\n  };\n}\n```\n\n**The Customer Zero marketplace can keep its UI, but the actual connector list and status should come from this endpoint.**\n\n---\n\n## E. Tenant Connections\n\nThe frontend needs a clear connection model.\n\n```http\nGET /api/v1/connectors/connections\nPOST /api/v1/connectors/:provider/connect\nGET /api/v1/connectors/connections/:id\nDELETE /api/v1/connectors/connections/:id\n```\n\n```typescript\ninterface ConnectionSummary {\n  connectionId: string;\n  provider: string;\n\n  status: \"pending\" | \"connected\" | \"degraded\" | \"expired\" | \"revoked\";\n\n  health: \"healthy\" | \"degraded\" | \"unknown\";\n\n  owner: {\n    type: \"user\" | \"workspace\";\n    id: string;\n  };\n\n  scopes: string[];\n\n  lastValidatedAt?: string;\n  lastUsedAt?: string;\n\n  sync: {\n    status: \"idle\" | \"running\" | \"failed\" | \"disabled\";\n    lastCompletedAt?: string;\n    nextScheduledAt?: string;\n  };\n\n  availableActions: string[];\n}\n```\n\n**The response must never contain:**\n\n- Access tokens\n- Refresh tokens\n- Client secrets\n- API keys\n- Provider-management credentials\n- Raw authorization references unless required for a trusted server flow\n\n---\n\n## F. OAuth Connection Initiation\n\nThe frontend should request a connection:\n\n```http\nPOST /api/v1/connectors/vercel/connect\n```\n\n**Request:**\n\n```json\n{\n  \"workspaceId\": \"ws_customer_zero\",\n  \"returnPath\": \"/platform/connectors\"\n}\n```\n\n**Response:**\n\n```json\n{\n  \"state\": \"human_consent_required\",\n  \"authorizationUrl\": \"https://...\",\n  \"connectionAttemptId\": \"attempt_123\",\n  \"expiresAt\": \"2026-07-29T06:00:00Z\"\n}\n```\n\nThe frontend redirects the user.\n\n**It should not:**\n\n- Generate OAuth state\n- Exchange OAuth codes\n- Store credentials\n- Mark a connection as active\n- Derive tenant identity from callback parameters\n\n---\n\n## G. Sync Contract\n\nThe Platform should expose sync independently from connection state.\n\n```http\nPOST /api/v1/connectors/connections/:id/sync\nGET /api/v1/connectors/connections/:id/sync-status\nGET /api/v1/connectors/connections/:id/sync-history\n```\n\n```typescript\ninterface SyncStatus {\n  syncJobId?: string;\n\n  state: \"idle\" | \"queued\" | \"running\" | \"completed\" | \"failed\" | \"cancelled\";\n\n  stage?: \"loading\" | \"normalizing\" | \"storing\" | \"projecting\" | \"reconciling\";\n\n  progress?: {\n    processed: number;\n    total?: number;\n    percentage?: number;\n  };\n\n  startedAt?: string;\n  completedAt?: string;\n\n  error?: {\n    code: string;\n    message: string;\n    retryable: boolean;\n  };\n}\n```\n\n**This gives the marketplace real state instead of showing a fake \"connected\" status.**\n\n---\n\n## H. Capability Contract\n\nThe frontend should know which actions are available for the current user and object.\n\n```http\nGET /api/v1/capabilities?projectionId=inbox\nGET /api/v1/capabilities?entityType=account&entityId=acc_123\n```\n\n```typescript\ninterface CapabilityDescriptor {\n  id: string;\n  name: string;\n  description: string;\n\n  mode: \"read\" | \"propose\" | \"execute\";\n\n  availability:\n    | \"available\"\n    | \"requires-approval\"\n    | \"missing-connection\"\n    | \"not-authorized\"\n    | \"unavailable\";\n\n  requiredPermissions: string[];\n  requiredConnections: string[];\n\n  governance?: {\n    approvalRequired: boolean;\n    policyId?: string;\n  };\n\n  inputSchema?: Record<string, unknown>;\n}\n```\n\n**This lets the frontend render:**\n\n- Enabled button\n- Disabled button\n- Connect provider\n- Request approval\n- Execute\n- Explain why unavailable\n\n---\n\n## I. Governance and Approvals\n\nThe frontend needs explicit proposal and approval APIs.\n\n```http\nGET /api/v1/governance/approvals\nGET /api/v1/governance/approvals/:id\nPOST /api/v1/governance/approvals/:id/approve\nPOST /api/v1/governance/approvals/:id/reject\n```\n\n```typescript\ninterface ApprovalItem {\n  id: string;\n\n  title: string;\n  description: string;\n\n  proposedCapability: string;\n\n  requestedBy: {\n    type: \"user\" | \"twin\" | \"system\";\n    id: string;\n  };\n\n  target: {\n    entityType?: string;\n    entityId?: string;\n    provider?: string;\n  };\n\n  risk: \"low\" | \"medium\" | \"high\" | \"critical\";\n\n  consequences: string[];\n\n  evidence: {\n    id: string;\n    label: string;\n    source: string;\n  }[];\n\n  status: \"pending\" | \"approved\" | \"rejected\" | \"expired\" | \"executing\" | \"completed\" | \"failed\";\n\n  availableActions: string[];\n}\n```\n\n**The approval endpoint should return an approval result, not let the frontend call the provider afterward.**\n\n**Correct flow:**\n\n```\nFrontend approves\n→ Governance validates\n→ Governance issues authority\n→ Hermes executes\n→ Platform returns execution state\n```\n\n---\n\n## J. Twin Contract\n\nThe frontend should not call models directly.\n\nIt should call semantic Twin operations:\n\n```http\nPOST /api/v1/twin/ask\nPOST /api/v1/twin/assign\nGET /api/v1/twin/assignments\nGET /api/v1/twin/assignments/:id\n```\n\n**Ask request:**\n\n```typescript\ninterface AskTwinRequest {\n  context: {\n    projectionId?: string;\n    entityRefs?: string[];\n    selectedRecordIds?: string[];\n  };\n\n  question: string;\n}\n```\n\n**Assign request:**\n\n```typescript\ninterface AssignTwinRequest {\n  objective: string;\n\n  context: {\n    projectionId?: string;\n    entityRefs?: string[];\n  };\n\n  constraints?: string[];\n\n  desiredOutcome?: string;\n}\n```\n\n**The Platform resolves the actual permitted context. The frontend only sends references to the active surface.**\n\n---\n\n## K. Signals and Attention\n\nFor Home and Inbox, expose a common attention contract.\n\n```http\nGET /api/v1/signals\nGET /api/v1/work/items\nGET /api/v1/notifications\n```\n\n```typescript\ninterface AttentionItem {\n  id: string;\n\n  kind: \"task\" | \"approval\" | \"signal\" | \"notification\" | \"blocked-work\" | \"sync-error\";\n\n  title: string;\n  summary?: string;\n\n  severity: \"info\" | \"warning\" | \"critical\";\n\n  status: string;\n\n  dueAt?: string;\n\n  relatedEntities: {\n    type: string;\n    id: string;\n    label: string;\n  }[];\n\n  projectionTarget?: {\n    route: string;\n    projectionId?: string;\n  };\n\n  allowedCapabilities: string[];\n}\n```\n\n**This enables Inbox, My Work, Approvals and Notifications to share a consistent data shape.**\n\n---\n\n# 4. Common Response Envelope\n\nEvery Platform response should have the same operational metadata.\n\n```typescript\ninterface PlatformResponse<T> {\n  contractVersion: string;\n  traceId: string;\n\n  tenantId: string;\n  workspaceId: string;\n\n  generatedAt: string;\n  freshness?: string;\n\n  state: \"ready\" | \"partial\" | \"degraded\" | \"pending\";\n\n  data: T;\n\n  errors?: {\n    code: string;\n    message: string;\n    retryable: boolean;\n  }[];\n\n  provenance?: {\n    sources: string[];\n    evidenceIds?: string[];\n  };\n}\n```\n\n**The frontend should never silently replace an error with demo data.**\n\n**Example:**\n\n```json\n{\n  \"contractVersion\": \"2026-07-29.1\",\n  \"traceId\": \"tr_01J...\",\n  \"tenantId\": \"iw-customer-zero\",\n  \"workspaceId\": \"ws_primary\",\n  \"generatedAt\": \"2026-07-29T05:20:00Z\",\n  \"state\": \"degraded\",\n  \"data\": {\n    \"items\": []\n  },\n  \"errors\": [\n    {\n      \"code\": \"SALESFORCE_SYNC_UNAVAILABLE\",\n      \"message\": \"Salesforce data is temporarily unavailable.\",\n      \"retryable\": true\n    }\n  ]\n}\n```\n\n---\n\n# 5. Frontend Vocabulary\n\n**The frontend should understand only these core concepts:**\n\n```\nEntity\nProjection\nCapability\nSignal\nContext\nTwin\n```\n\n**Optionally, for operational implementation:**\n\n```\nConnection\nApproval\nSync\n```\n\n**It should not understand internal terms such as:**\n\n```\nMCP Pool binding\nCloudflare service binding\nNango connection ID\nDescope management app\nD1 table\nNeon migration\nHermes queue\nProvider adapter\nWorker name\n```\n\n**Those are Platform implementation details.**\n\n---\n\n# 6. Recommended Frontend SDK Structure\n\n```\nlib/platform/\n├── client.ts\n├── types.ts\n├── discovery.ts\n├── projections.ts\n├── connectors.ts\n├── capabilities.ts\n├── governance.ts\n├── twin.ts\n├── signals.ts\n└── hooks/\n    ├── use-workspace-discovery.ts\n    ├── use-projection.ts\n    ├── use-connectors.ts\n    ├── use-capabilities.ts\n    ├── use-approvals.ts\n    └── use-twin-assignment.ts\n```\n\n**The frontend should use domain-level calls:**\n\n```typescript\nplatform.workspace.discover();\nplatform.projections.get(\"inbox\");\nplatform.connectors.connect(\"vercel\");\nplatform.connectors.sync(connectionId);\nplatform.capabilities.forEntity(entityRef);\nplatform.governance.approve(approvalId);\nplatform.twin.ask(input);\n```\n\n**Not generic calls like:**\n\n```typescript\nfetch(\"/api/platform/some-worker/some-route\");\n```\n\n---\n\n# 7. Minimum Platform APIs Needed Now\n\nTo make the current Customer Zero frontend real, the immediate minimum is:\n\n| Endpoint                                             | Purpose                   |\n| ---------------------------------------------------- | ------------------------- |\n| `GET /api/v1/workspace/discovery`                    | Initial frontend contract |\n| `GET /api/v1/connectors/catalog`                     | Connector Marketplace     |\n| `GET /api/v1/connectors/connections`                 | Connection states         |\n| `POST /api/v1/connectors/:provider/connect`          | Start authorization       |\n| `DELETE /api/v1/connectors/connections/:id`          | Disconnect                |\n| `POST /api/v1/connectors/connections/:id/sync`       | Trigger sync              |\n| `GET /api/v1/connectors/connections/:id/sync-status` | Show progress             |\n| `GET /api/v1/projections/inbox`                      | Inbox Workbench           |\n| `GET /api/v1/projections/approvals`                  | Approvals Workbench       |\n| `GET /api/v1/capabilities`                           | Available actions         |\n| `POST /api/v1/governance/approvals/:id/approve`      | Governed approval         |\n| `POST /api/v1/governance/approvals/:id/reject`       | Rejection                 |\n| `POST /api/v1/twin/ask`                              | Ask Your Twin             |\n| `POST /api/v1/twin/assign`                           | Assign Your Twin          |\n| `GET /api/v1/platform/health`                        | Platform health           |\n\n---\n\n# 8. Marketplace Onboarding Contract\n\n**Partially aligned — the frontend contract is mostly post-onboarding, not the complete marketplace onboarding contract.**\n\nThe proposed Platform APIs correctly support the Workbench **after** a marketplace installation has been bound to an IntegrateWise workspace:\n\n- Workspace discovery\n- Connector catalog\n- Connection status\n- OAuth initiation\n- Sync\n- Projections\n- Capabilities\n- Governance\n- Twin operations\n\nBut marketplace onboarding needs an additional layer before `/api/v1/workspace/discovery`.\n\n## Correct Marketplace Onboarding Sequence\n\n```\nMarketplace installation initiated\n→ Validate marketplace installation context\n→ Authenticate or create IntegrateWise user\n→ Select or create IntegrateWise workspace\n→ Resolve tenant and Spine Network\n→ Review scopes and requested access\n→ Reuse existing provider connection when permitted\n→ Request provider consent when required\n→ Create marketplace installation binding\n→ Create connector and Spine connection binding\n→ Verify connection health\n→ Begin initial hydration\n→ Compile initial Workbench projections\n→ Redirect user to activated Workbench\n→ Frontend calls /api/v1/workspace/discovery\n```\n\n**So `/workspace/discovery` is the first Workbench request, but it is not the first marketplace onboarding request.**\n\n## Two Separate Platform Contracts\n\n### 1. Marketplace Acquisition and Installation Contract\n\nThis owns the channel-specific onboarding process.\n\n```\nVercel / ChatGPT / GitHub / Notion / Coda\n→ marketplace installation API\n→ IntegrateWise identity and workspace selection\n→ tenant-bound installation\n```\n\n### 2. Product Runtime Contract\n\nThis is the contract already described:\n\n```\nIntegrateWise Workbench\n→ workspace discovery\n→ projections\n→ connectors\n→ capabilities\n→ governance\n→ Twin\n```\n\n**The frontend should not combine these into one generic connector flow.**\n\n---\n\n## Marketplace Onboarding APIs Required\n\nA provider-neutral API family would be preferable:\n\n```http\nPOST /api/v1/marketplace/installations/start\nGET /api/v1/marketplace/installations/:attemptId\nPOST /api/v1/marketplace/installations/:attemptId/workspace\nPOST /api/v1/marketplace/installations/:attemptId/consent\nPOST /api/v1/marketplace/installations/:attemptId/activate\nGET /api/v1/marketplace/installations/:attemptId/status\nPOST /api/v1/marketplace/installations/:attemptId/cancel\nDELETE /api/v1/marketplace/installations/:installationId\n```\n\n**Channel-specific callbacks can remain separate:**\n\n```http\nGET /api/v1/marketplace/vercel/callback\nPOST /api/v1/marketplace/vercel/webhooks\n\nGET /api/v1/marketplace/github/callback\nPOST /api/v1/marketplace/github/webhooks\n```\n\n**The channel adapter converts provider-specific payloads into one canonical installation model.**\n\n---\n\n## Canonical Installation Model\n\n```typescript\ninterface MarketplaceInstallation {\n  id: string;\n  channel: \"vercel\" | \"chatgpt\" | \"github\" | \"notion\" | \"coda\" | \"mcp-directory\";\n\n  externalInstallationId: string;\n  externalAccountId?: string;\n  externalTeamId?: string;\n\n  tenantId: string;\n  workspaceId: string;\n  spineNetworkId: string;\n\n  connectorBindingId?: string;\n  spineConnectionId?: string;\n  credentialRef?: string;\n\n  grantedScopes: string[];\n\n  status:\n    | \"initiated\"\n    | \"identity_required\"\n    | \"workspace_required\"\n    | \"consent_required\"\n    | \"binding\"\n    | \"hydrating\"\n    | \"active\"\n    | \"degraded\"\n    | \"revoked\"\n    | \"failed\";\n\n  createdAt: string;\n  activatedAt?: string;\n}\n```\n\n**This installation record is different from a normal connector connection.**\n\nA connector answers:\n\n> \"Can this workspace communicate with Vercel?\"\n\nA marketplace installation answers:\n\n> \"Which external Vercel installation is bound to which IntegrateWise tenant, workspace, Spine Network, connector, permissions, and lifecycle?\"\n\n---\n\n## Marketplace Installation Start Request\n\n```http\nPOST /api/v1/marketplace/installations/start\n```\n\n```json\n{\n  \"channel\": \"vercel\",\n  \"externalInstallationId\": \"icfg_123\",\n  \"externalTeamId\": \"team_456\",\n  \"requestedScopes\": [\"projects:read\", \"deployments:read\"],\n  \"returnUrl\": \"https://vercel.com/dashboard/integrations\"\n}\n```\n\n**Response:**\n\n```json\n{\n  \"installationAttemptId\": \"mai_123\",\n  \"nextStep\": \"identity_required\",\n  \"continueUrl\": \"https://app.integratewise.ai/marketplace/onboarding/mai_123\",\n  \"expiresAt\": \"2026-07-29T08:00:00Z\"\n}\n```\n\n**The browser should receive only a resumable installation attempt. It should not receive credentials or authoritative tenant information.**\n\n---\n\n## Workspace Selection\n\nAfter authentication:\n\n```http\nPOST /api/v1/marketplace/installations/mai_123/workspace\n```\n\n```json\n{\n  \"workspaceId\": \"ws_customer_zero\"\n}\n```\n\n**The Platform validates:**\n\n- The user belongs to that workspace.\n- The user has installation authority.\n- The workspace belongs to the correct environment.\n- The external installation is not already bound elsewhere.\n- Existing connector connections can be reused.\n- Requested scopes comply with workspace policy.\n\n---\n\n## Consent Model\n\nMarketplace consent and provider OAuth consent are not always the same thing.\n\n### Marketplace Consent\n\nThe user approves:\n\n- Binding the Vercel installation to the IW workspace\n- Requested project access\n- Data ingestion scope\n- Available capabilities\n- Retention policy\n- Write/action policy\n\n### Provider Consent\n\nThe user authorizes Vercel, GitHub, Salesforce, or another provider to issue credentials.\n\nA marketplace may already provide a usable installation credential. In that case, a second OAuth flow should not be created unnecessarily.\n\n**The Platform should determine:**\n\n```\nExisting approved connection exists\n→ reuse it\n\nMarketplace credential is sufficient\n→ bind it\n\nAdditional provider authorization required\n→ request consent\n\nNo permitted credential path\n→ fail closed\n```\n\n---\n\n## Activation Response\n\n```http\nPOST /api/v1/marketplace/installations/mai_123/activate\n```\n\n```json\n{\n  \"state\": \"hydrating\",\n  \"installationId\": \"mi_789\",\n  \"workspaceId\": \"ws_customer_zero\",\n  \"connector\": {\n    \"provider\": \"vercel\",\n    \"connectionId\": \"conn_456\",\n    \"status\": \"connected\"\n  },\n  \"hydration\": {\n    \"status\": \"running\",\n    \"stage\": \"loading\"\n  },\n  \"redirectUrl\": \"https://app.integratewise.ai/workbench\"\n}\n```\n\n**The user can enter the Workbench while hydration continues, provided the frontend clearly renders:**\n\n```\npending\npartial\ndegraded\nready\n```\n\n**It must not show seed data as though the installation succeeded.**\n\n---\n\n## `/workspace/discovery` After Marketplace Activation\n\nOnce the installation is bound, the existing discovery contract becomes correct.\n\nIt should expose marketplace context indirectly:\n\n```json\n{\n  \"state\": \"partial\",\n  \"workspace\": {\n    \"workspaceId\": \"ws_customer_zero\",\n    \"spineNetworkId\": \"spn_123\"\n  },\n  \"activation\": {\n    \"complete\": false,\n    \"blockers\": [\n      {\n        \"code\": \"VERCEL_INITIAL_HYDRATION_RUNNING\",\n        \"message\": \"Vercel project data is still loading.\"\n      }\n    ]\n  },\n  \"connectors\": {\n    \"connected\": 1,\n    \"degraded\": 0,\n    \"syncRunning\": 1\n  },\n  \"marketplaceInstallations\": [\n    {\n      \"channel\": \"vercel\",\n      \"status\": \"hydrating\"\n    }\n  ]\n}\n```\n\n**The frontend does not need Vercel-specific implementation details beyond what is required to render status and remediation.**\n\n---\n\n## Frontend Onboarding Screens\n\nThe Product repo should render these screens:\n\n```\n1. Welcome / sign in\n2. Select or create workspace\n3. Review requested access\n4. Choose projects or account scope\n5. Confirm connection reuse or authorize\n6. Activation progress\n7. Success / remediation\n8. Enter Workbench\n```\n\n**The Platform must supply all authoritative data and transitions.**\n\n**The frontend must not decide:**\n\n- Whether a connection can be reused\n- Which tenant the installation belongs to\n- Whether requested scopes are allowed\n- Whether installation is active\n- Whether OAuth succeeded\n- Whether hydration completed\n- Which capabilities are enabled\n\n---\n\n## Alignment Assessment\n\n| Component                                  | Marketplace onboarding alignment            |\n| ------------------------------------------ | ------------------------------------------- |\n| One typed Gateway client                   | **Aligned**                                 |\n| Workspace discovery                        | **Aligned, but post-installation**          |\n| Connector catalog                          | **Aligned**                                 |\n| Connector connection APIs                  | **Aligned as a subordinate step**           |\n| OAuth initiation                           | **Aligned, but must be installation-aware** |\n| Sync and hydration APIs                    | **Aligned**                                 |\n| Projection APIs                            | **Aligned after activation**                |\n| Capability APIs                            | **Aligned**                                 |\n| Governance APIs                            | **Aligned**                                 |\n| Marketplace installation state             | **Missing**                                 |\n| External installation-to-workspace binding | **Missing**                                 |\n| Workspace selection during install         | **Missing**                                 |\n| Scope review and marketplace consent       | **Missing**                                 |\n| Connection-reuse decision                  | **Missing**                                 |\n| Channel callback abstraction               | **Missing**                                 |\n| Lifecycle webhooks and uninstall           | **Partially present**                       |\n| Post-install redirect contract             | **Missing**                                 |\n\n---\n\n# Final Rule\n\n> **The Platform must expose business and operational contracts; the frontend must never be required to understand the Platform's internal topology.**\n\n**The frontend should receive clear answers to:**\n\n```\nWho am I?\nWhich workspace am I in?\nWhat can I see?\nWhat work needs attention?\nWhich connectors are available?\nWhich connections are active?\nWhat can I do?\nWhat requires approval?\nWhat is fresh, partial or degraded?\nWhere should the UI take me next?\n```\n\n**That is the contract the Platform now needs to formalize.**\n\n---\n\n# Final Answer\n\n> **The Platform-to-frontend contract is aligned with the operational phase of marketplace onboarding, but it does not yet cover the acquisition and installation phase.**\n\n**Add a canonical `MarketplaceInstallation` contract and installation state machine ahead of `/workspace/discovery`. After activation, the existing discovery, connectors, sync, projections, capabilities, governance, and Twin APIs form the correct frontend boundary.**\n"}