> ## Documentation Index
> Fetch the complete documentation index at: https://www.composer.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Upstream connectors

> How Triage's diagnostic runs inside chat and portal surfaces through the Intake Gateway, and where composerID takes over.

Upstream connectors put Triage's diagnostic questions inside the tools requesters already use, starting with Microsoft Teams. This page explains the Intake Gateway that sits between a chat surface and the Triage decision core, the card lifecycle that keeps the diagnostic honest, and the hand-off to composerID. It is written for platform engineers and risk reviewers evaluating an intake surface.

<Info>
  Upstream connectors are Triage's intake surfaces: where the diagnostic questions are asked and the intent is born. They are never composerID destinations. composerID carries the decision downstream. Surface status follows the registry:

  * [Microsoft Teams](/connectors/teams): Beta. Bot Framework, Adaptive Cards, Microsoft Entra ID SSO.
  * [Slack](/connectors/slack): Roadmap. Same gateway; the Block Kit renderer ships in the reference implementation.
  * [Google Chat](/connectors/googlechat): Roadmap. Same gateway; the Cards v2 renderer ships in the reference implementation.
  * [ServiceNow](/connectors/servicenow): Roadmap. Service Portal widget and Employee Center; the decision publishes back into the same instance.

  The [destination catalogue](/destinations/index) lists every downstream system with its coverage status. The surfaces on this page are Triage's intake, not destinations, so they are not in it.
</Info>

## How it fits together

Nothing about the diagnostic moves into the chat platform. The questions, the scoring, the routing and the audit spine stay where they live today. The Intake Gateway only translates between chat cards and the same decision API the web intake uses.

```mermaid theme={"system"}
flowchart LR
  T["Microsoft Teams"] -->|"bot activity"| G["Intake Gateway"]
  G -->|"complete answer set"| D["Triage decision core"]
  D -->|"Intent ID minted"| C["composerID"]
  C -->|"publish"| S["Systems of record"]
  C -.->|"publish receipt"| G
  C -.->|"enrichment request"| G
  G -.->|"card update"| T
```

Solid arrows are the forward path. Dashed arrows are the return path: the publish receipt becomes a deep link on the completion card, and an enrichment request returns to the same thread as another card.

### Three components

<Columns cols={3}>
  <Card title="Teams app package" icon="package">
    A manifest, icons and a Microsoft Entra app registration. Installed once per tenant from the org catalog and authorised with a single admin consent. Nothing is deployed inside the tenant; the package only points at the gateway.
  </Card>

  <Card title="Intake Gateway" icon="route">
    Receives bot activities, verifies the requester through Entra ID SSO, renders questions as Adaptive Cards and translates submissions back into answers. It holds the conversation position and nothing else. Renderer-agnostic: Slack and Google Chat are the same core with a different renderer.
  </Card>

  <Card title="Decision core, unchanged" icon="workflow">
    The gateway calls the same evaluate contract as the web intake. Scoring, routing, anti-gaming pattern detection and Compliance File generation run server-side and are identical wherever the questions were asked.
  </Card>
</Columns>

## From first question to published record

Steps 1 to 8 are the upstream connector. From step 9 the flow is standard composerID publishing: the five stages in [How integrations work](/concepts/how-it-works).

<Steps>
  <Step title="The requester opens the bot">
    The requester opens the Triage bot in Teams, or is pointed at it the moment they ask for work in a channel. Where it runs: Teams.
  </Step>

  <Step title="Identity is verified">
    Entra ID SSO verifies the requester silently; there is no separate login. Contextual data is fetched in the background where the tenant tier allows. Where it runs: the gateway, against Entra ID.
  </Step>

  <Step title="The first question arrives">
    An Adaptive Card carries the prompt, embedded guidance and choice answers. Where it runs: gateway to Teams.
  </Step>

  <Step title="The answer locks">
    The answer is submitted and the card is replaced in place by its locked state: no inputs remain. Where it runs: the gateway.
  </Step>

  <Step title="The next question arrives">
    One card per question, in fixed order, for however many questions the tenant's diagnostic defines. Where it runs: gateway to Teams.
  </Step>

  <Step title="The decision core scores and routes">
    On the final answer the gateway hands the complete answer set to the decision core. Scoring and routing produce the channel decision. Where it runs: the Triage decision core.
  </Step>

  <Step title="The Intent Record is minted">
    One traceable Intent ID. The Compliance File records the intake surface and the verified requester identity on the audit spine. Where it runs: the Triage decision core.
  </Step>

  <Step title="The completion card is sent">
    The card shows the recommended channel, confidence, rationale and the `intent://` chip. Where it runs: gateway to Teams.
  </Step>

  <Step title="composerID preflights and publishes">
    composerID preflights the intent against the destination's tenant requirements and publishes it into the system or systems of record, idempotently. Where it runs: composerID.
  </Step>

  <Step title="The receipt returns to the thread">
    The completion card gains a deep link to the created record. If a destination needs a missing field, the enrichment request returns to the same thread as another card. Where it runs: composerID to gateway to Teams.
  </Step>
</Steps>

## The card lifecycle keeps the diagnostic honest

The intake has deliberate properties, and they are what make the decision defensible. Each one maps to a card mechanic, so the chat surface enforces them by construction rather than by policy.

```mermaid theme={"system"}
flowchart LR
  Q["Question card"] -->|"answer submitted"| L["Locked card, no inputs"]
  L -->|"next question"| Q
  L -->|"final answer"| C["Completion card: decision and Intent ID"]
  C -.->|"publish receipt lands"| D["Deep link added"]
```

<AccordionGroup>
  <Accordion title="One question per screen" icon="list">
    One card per message. The next question only arrives after the current one is answered.
  </Accordion>

  <Accordion title="Answers lock on submission" icon="lock">
    The submission replaces the card with a version that has no input elements left. There is nothing to edit.
  </Accordion>

  <Accordion title="No back button" icon="circle-dot">
    Earlier cards are inert text, and the gateway rejects out-of-order or repeated answers.
  </Accordion>

  <Accordion title="Anti-gaming pattern detection" icon="shield-check">
    Runs server-side in the decision core: identical to the web intake, invisible to the surface.
  </Accordion>

  <Accordion title="Abandonment leaves no record" icon="history">
    If the thread goes quiet mid-flow, partial answers are not persisted: no Compliance File, no Intent Record.
  </Accordion>
</AccordionGroup>

## Identity and tenancy

### Identity

Entra ID SSO verifies the requester silently: the person answering is the person signed in to Teams. That verified identity rides the audit spine into the Compliance File, so "who asked for this?" is answered by the record.

### Tenancy

The app package is published to the tenant's org catalog and authorised with one admin consent. Per-tenant configuration (the question set, tiers, contextual data sources) lives in Triage, not in the package, so updating the diagnostic never means reinstalling the app.

| Case                        | Behaviour                                                                                     |
| --------------------------- | --------------------------------------------------------------------------------------------- |
| SSO fails                   | The intake does not load. The surface shows the error, and nothing reaches the decision core. |
| Admin consent absent        | The install blocks before any data flows. Consent is the gate, once, for the whole tenant.    |
| Requester abandons mid-flow | No partial answers persist. No Compliance File, no Intent Record.                             |
| Client drops mid-flow       | Conversation state is server-side; the thread resumes at the current question on any device.  |

## Where composerID takes over

### The trigger: a completed assessment becomes an intent

Triage tells composerID that a decision was made the same way a destination tells it that a record changed: a signed webhook. `POST /triggers/triage/{tenant}` accepts the completion event, verifies the `X-Signature-HMAC-SHA256-1` header against the tenant's webhook signing key, and mints the intent. The Intent ID is derived from the assessment response id, so if Triage delivers the same completion twice, the second delivery returns the existing intent rather than creating another.

<CodeGroup>
  ```bash Sign and deliver theme={"system"}
  BODY='{"type":"assessment.completed","data":{"responseId":"resp-001","response":{
    "responseId":"resp-001","score":87,
    "questions":[{"text":"What is the job title?","answerValue":"Data Engineer"},
                 {"text":"When should the work start?","answerValue":"2026-11-02"}],
    "rankedScores":[{"rank":1,"name":"Contingent"}]}}}'
  SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_KEY" -binary | base64)
  curl -s -X POST https://sandbox.composer.id/v1/triggers/triage/$TENANT \
    -H "X-Signature-HMAC-SHA256-1: $SIG" -H 'Content-Type: application/json' \
    --data "$BODY"
  ```

  ```json Result (abridged) theme={"system"}
  {
    "kind": "intent",
    "response_id": "resp-001",
    "intent_id": "itn_...",
    "created": true,
    "intent": {"payload": {"job_title": "Data Engineer", "channel": "contingent",
               "source": {"system": "triage", "assessment_response_id": "resp-001"}}}
  }
  ```
</CodeGroup>

`$TENANT` and `$WEBHOOK_KEY` are the `tenant` and `webhook_signing_key` from [minting sandbox credentials](/quickstart). The answers land in the intent payload through the tenant's question map, the rank-1 channel becomes `payload.channel`, and the provenance (response id, score, completion time) is recorded under `payload.source`, never the diagnostic content itself.

<Note>
  The sandbox reads the completed assessment from the delivery (`data.response`). A deployment connected to a Triage tenant fetches it by `responseId` instead, and the delivery need carry only the id. The [endpoint reference](/api-reference/overview) has the full shapes.
</Note>

From the moment the Intent ID is minted, the flow is the standard composerID contract: preflight against the destination's tenant requirements, an idempotent publish keyed `{intent_id}-{version}-{destination}`, external IDs and a receipt on the timeline, then the webhook and reconciliation contract that keeps the record truthful. [Publishing](/concepts/publishing) and [Events](/concepts/events) document each stage. Two returns are specific to the chat surface.

### The deep link

When the publish receipt lands, the completion card is updated with a link straight to the record composerID created. The requester watches their ask become a requisition, an SOW or an agreement without leaving the thread.

### The enrichment loop

If preflight finds a gap the diagnostic did not cover (a cost centre, a location code), the `enrichment_request` comes back to the same thread as another card.

## Run the reference implementation

The `upstream/` package is the executable version of this page: standard-library Python, no network, the same conventions as the adapter spine. It is a sandbox, and its contract tests pin the intake properties above.

<CodeGroup>
  ```bash Full journey theme={"system"}
  python3 -m upstream.demo
  ```

  ```bash Contract tests theme={"system"}
  python3 -m unittest discover -s upstream -t .
  ```
</CodeGroup>

The demo runs a scripted requester through the cards, completes the decision, mints the Intent ID on the spine, stamps a mock SAP Fieldglass tenant and prints the completion card with its deep link. The tests cover locked answers, fixed order, abandonment and the card shapes for every renderer.

| Module                     | Role                                        |
| -------------------------- | ------------------------------------------- |
| `questions.py`             | The diagnostic set (indicative; any length) |
| `gateway.py`               | Renderer-agnostic state machine             |
| `teams_cards.py`           | Adaptive Card renderer (schema 1.4)         |
| `slack_blocks.py`          | Block Kit renderer, same contract           |
| `google_chat_cards.py`     | Cards v2 renderer, same contract            |
| `demo.py`                  | Full journey in one process                 |
| `test_upstream_gateway.py` | 20 contract tests                           |

## Next steps

<Columns cols={3}>
  <Card title="Microsoft Teams" icon="message-square" href="/connectors/teams">
    Bot Framework, Adaptive Card versions, Entra ID SSO and proactive messages.
  </Card>

  <Card title="Destination catalogue" icon="table" href="/destinations/index">
    Every downstream system composerID publishes into, with its coverage status.
  </Card>

  <Card title="How integrations work" icon="workflow" href="/concepts/how-it-works">
    The five publishing stages that begin once the Intent ID exists.
  </Card>
</Columns>
