Technical enablement · 07
Integrations & provider connections
How {{product.name}} connects to the systems around it: the AI providers whose spend it reads, the LiteLLM gateway that reports usage, and Microsoft Entra ID for sign-in. It also gives the recipe for adding a provider.
Status is stated per integration. Shipped means an adapter is registered in
lib/tdal/adapters/registry.ts. Anything else is labelled configurable-only or roadmap. Third-party licensing and data-sharing terms
are for Legal and Procurement, not engineering.
The model: ports and adapters
Product code talks to one product-owned interface, the ProviderAdapter contract in lib/tdal/adapters/base.ts. Each provider
implements it in its own file and writes into one unified schema that uses FOCUS 1.5 field names (ADR 006). Adding a provider is therefore
adding one adapter file and registering it; the schema does not change. The registry is also the only list the connections page consults, so a
provider cannot be syncable in one place and unknown in another.
Integration inventory
| Integration | Purpose | Status | Swap effort |
|---|---|---|---|
| Microsoft Entra ID | Staff sign-in (Auth.js v5) | Shipped | Configuration for another OIDC tenant; a different identity provider needs changes in lib/auth/config.ts |
| LiteLLM gateway | Usage telemetry, session keys, enforcement | Shipped | High: ADR 001 makes it the substrate |
| Azure OpenAI and Foundry (Azure Cost Management, Monitor) | Billing and token metrics | Shipped | n/a |
| AWS Bedrock (Cost Explorer, CloudWatch) | Billing and token metrics | Shipped | n/a |
| Google Vertex AI (BigQuery, Monitoring, price catalogue) | Usage; cost actual with a billing export, otherwise estimated | Shipped (estimated cost without an export) | n/a |
| OpenAI, Anthropic (admin APIs) | Usage and cost reports | Shipped | n/a |
| SaaS-embedded AI (for example GitHub Copilot) | Seat and rate records | Configurable only: connectors can be registered at /api/saas/connectors but there is no sync adapter | Medium: write an adapter |
| Client outcome systems | Verified outcome events | Shipped as a webhook at /api/ingest/outcomes; scheduled file drop is roadmap | Client builds the push |
| Per-tenant client identity providers, email one-time codes | Client user sign-in | Roadmap: described in the build brief, not in the code | No plan or date. Proposed: Entra ID guest invitation first; other identity providers only on client demand. |
Provider connections
A connection is a named record per provider account, with a scope and a credential. It is created disconnected; verification promotes it to
active, and only active (and errored) connections are synced. verify() runs on save and edit, reports the granted scope,
and the Test connection button re-runs it.
| Provider | Credential and scope | Client-side prerequisite |
|---|---|---|
| Azure OpenAI / Foundry | Read access to Cost Management and Monitoring on the subscription or resource group. One connection can cover a resource group or a whole subscription. | None. Provisioned-throughput deployments report capacity rather than tokens. |
| AWS Bedrock | A read-only IAM principal allowed ce:GetCostAndUsage and cloudwatch:GetMetricData. Both are account-wide and cannot read workload content. | Model invocation logging must be enabled; without it there are no token counts. |
| Google Vertex AI | A service account (JSON key or token). BigQuery read on the billing export dataset and Monitoring viewer. | A detailed usage cost export to BigQuery, if actual cost is wanted. There is no retroactive backfill. |
| OpenAI | An organisation admin key with usage and cost read. A project key will not work. | None. |
| Anthropic | An admin API key with usage and cost report read. | None. |
The platform requests read-only access and never model-invocation scope. The in-app "What do I need?" guide is generated from
lib/connections/credential-fields.ts; change requirements there, not in the UI.
How a credential is supplied
- Entered in the app. The value is encrypted at rest with AES-256-GCM using
CREDENTIAL_ENCRYPTION_KEY; the connection stores only that a value exists. - By reference. Accepted forms are
env:<VAR>,keyvault:<secret>and, on Azure,managed-identity:system,managed-identity:<id>orazure-default. A field decides which it is by its value; a malformed reference is rejected rather than stored as a secret. - Never in the repository, and never in these pages.
Azure credentials
Use the azure-default reference. It resolves by chain: a managed identity where the platform provides one, else a service principal from
AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET, else AZURE_ARM_TOKEN. The same connection record works in Azure,
in another cloud and on a laptop; moving host means changing environment variables, not connections. Pinning to a specific managed identity works only inside Azure.
AZURE_MI_CLIENT_ID selects a user-assigned identity and is deliberately separate from AZURE_CLIENT_ID, because they are different directory objects.
AWS credentials
A deployed connection needs a long-lived read-only IAM user (key ids beginning AKIA), not temporary SSO credentials, which expire within hours and would break a
scheduled sync. The policy needs only the two actions above. AWS charges per Cost Explorer request, so sync frequency is a cost decision (see
operations: cost). make aws-creds refreshes temporary credentials in a local .env for testing only.
A least-privilege policy document is kept in docs/aws/.
Google credentials
A service account key is recognised however it was pasted (raw JSON or base64), and a token is distinguished from a key by its value. Without a billing export, Vertex cost is estimated from the public price catalogue and marked as such.
LiteLLM gateway
LiteLLM is the substrate that routes model calls; the product adds Gate A authorisation and monitoring on top and does not fork it (ADR 001).
| Aspect | Detail |
|---|---|
| Telemetry in | LiteLLM fires an asynchronous callback after each call to POST /api/ingest/litellm, signed with the shared secret LITELLM_CALLBACK_SECRET. Requests are checked for a valid signature inside a short window and rejected on replay (Redis nonce). |
| Attribution | Each key should carry a project_id and, ideally, a workload id in its metadata. Events from keys that name no workload are stored and can be adopted in Attribute → Workloads. |
| Enforcement out | Gate A returns a scoped gateway key with a hard budget (gateway-enforced mode). A pause sets the budget to current consumption; a stop revokes the key. |
| What is stored | Token counts, model identifiers, estimated cost, session and workload identifiers. No prompt or completion content (ADR 005). |
| Known gap | Agents that call provider SDKs directly bypass the gateway and sit outside Gate A coverage. This is surfaced as a named gap. |
| Deployment | A separate container, not part of this repository. It is being built as a separate project, so its location and owner are set there. Keys are scoped per application: the build brief has the app use the gateway master key only to mint and revoke scoped keys, and unattributed keys are named in Attribute → Workloads. Proposed: the gateway project team owns the gateway and issues keys, with ai-yield holding only the callback secret and a minting credential. |
An architectural change to LiteLLM's callback API requires an adapter update. Record the traffic level at which LiteLLM's operating model becomes limiting when it is observed (ADR 001 revisit trigger). No threshold is recorded because none has been observed. Proposed: note the sustained request rate for one tenant at which callback latency is first noticed, and record it in the RUNBOOK.
Microsoft Entra ID
- Auth.js v5 with the Microsoft Entra ID provider; configuration in
lib/auth/config.ts. - The OAuth callback path is
/api/auth/callback/azure-adon the app's public base URL; register that full URL as a redirect URI under Authentication → Web in the app registration. - Variables:
AUTH_SECRET,AUTH_URL,AUTH_TRUST_HOST,ENTRA_CLIENT_ID,ENTRA_CLIENT_SECRET,ENTRA_TENANT_ID. - A signed-in user must be provisioned with a role in the app; otherwise they reach a no-access page.
- Client users: a per-tenant identity provider or email one-time code is described in the build brief but is not built. Proposed approach: guest invitation to the product's Entra ID directory, with a role assigned in the app; to be confirmed.
Adding a provider
-
Confirm the port
Read
lib/tdal/adapters/base.tsand a similar existing adapter, for exampleopenai.tsorbedrock.ts. -
Map the capability gap
List what the provider cannot do or does differently: whether it publishes actual cost, how it meters tokens, its rate limits and its grouping concept (for example account, resource group or project).
-
Implement and register
Write the adapter, use the shared retry profiles in
retry.tsso waiting fits inside the request ceiling, and register it inregistry.ts. Add the provider to the provider enum with a migration, and describe its credential requirements incredential-fields.ts. -
Test against recorded fixtures
Add a contract test in
tests/domain/in the style ofadapters-openai.test.ts. An adapter should not ship without one. -
Verify on a real account, then roll out
Create a connection, press Test connection, check the granted scope, and watch a scheduled run. Adding a column to the unified schema does not break the lock; renaming one does.
API access
The product exposes route handlers for Gate A, outcome ingest, checkpoints, escalations, optimisation and exports (see the
API routes entry). There is no published OpenAPI description and no MCP server. Proposed: if clients need programmatic access, publish an OpenAPI description for the outcome ingest route first, since it is the one clients call; no date is set.
Machine paths authenticate with their own credential (bearer token, signature or x-cron-secret), not a session cookie.