HIP-1250: Integration — Connection Registry. Status Final. Hanzo architectural specification.
An integration is a connection to a provider the platform does not own — Slack, GitHub, Google, Stripe — held on behalf of a tenant, with the credential in custody and the connection state in one registry. hanzoai/cloud apps/integrations is that registry, and /v1/integrations is its address.
The capability has two audiences over the one store: the org plane, where an admin connects a provider for the whole org, and the user plane, where a person links their own accounts. HIP-0126 fixed the vocabulary and HIP-1065 specifies the user plane's custody rules; this HIP is the capability declaration — the store, the target address, the operations, and what the surface refuses.
Provider connections were the platform's most duplicated concern: each consumer that needed a token — automations, channels, marketing — was one bad refactor away from its own OAuth path and its own secret row. One registry with one custody exit is what makes "is this org connected to X" a fact askable in one place, and a token something no peer ever stores.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
The capability owns one store: the integrations SQLite database (apps/integrations/store.go:73, sqlpool.Open("integrations", dir)). One row per (org, provider, owner); tenancy is the org column, which is part of the primary key. Rows hold non-secret state only. Credentials live solely in the key store (HIP-0027), sealed under a path built from the tenant — org connections under the org, user connectors under the (org, user) pair (apps/integrations/integrations.go:1564). A row MUST NOT carry a secret and a custody failure MUST NOT leave a row behind.
Every route answers under /v1/integrations: the provider lifecycle (/{provider}, /{provider}/connect, /callback, /verify, /disconnect), the provider-specific endpoints (Slack, GitHub, Discord, Teams, Telegram, GitLab, OpenRouter), and the user plane at /v1/integrations/connectors. Today the user plane is served at a second root, /v1/connectors; that pair is carried by cloud's openapi/misfiled.txt:34 and closes by fold — one store, so per HIP-0139 §7.1 there is no boundary to split on. The generated clients re-point on regeneration; the installed CLI's interactive device and PKCE flows (cli/src/commands/product/generated.rs:794) hard-code the old root and a flow in flight when the fold lands polls a dead path, so the fold MUST ship in a CLI release that re-points them.
The connectors plane is typed end to end (apps/integrations/connectors.go:55): list, providers, token read, device start/poll, credential intake, refresh, delete. The org plane's OAuth callbacks, link flows and provider webhooks are declared with prose beside the route (openapi.Describe, apps/integrations/integrations.go) because none can be a value: a callback answers a redirect the provider dictates, a link flow renders HTML, and a webhook's authentication is the provider's signature over the raw body — the signature check IS the authentication and it fails closed (apps/integrations/integrations.go:602).
A request becomes a tenant through the validated principal's org (HIP-0026, principal.Org); a client-forged org header is refused. Org-plane writes that change what the whole org is connected to require the caller's own-org admin bit (principal.IsOrgAdmin — never SuperAdmin, apps/integrations/integrations.go:1096). User-plane rows are keyed by the (org, user) pair per HIP-1065, with no admin gate. Provider webhooks arrive with no principal and are admitted by signature alone.
The capability is free, said in those words: plugin/integrations/main.go:29 declares Price: cloud.Free and no spend table names it. It publishes no events on the bus. Peers do not read its store: whether an org is connected is asked over the internal plane (apps/integrations/connection_rpc.go:30), and token handoff to in-process consumers goes through the same seam, never the address. It emits nothing to observability beyond the request span every route gets. Stage: ga.
The capability derives from none. Every provider client is hand-rolled Go over net/http against the provider's public API; no vendor SDK is imported and no third-party project is forked, embedded or mirrored.
The alternative to one registry with two planes is two capabilities — org integrations and user connectors — which reads well until the store is drawn: both would open the same database, the defect HIP-0106 names. The two audiences differ in key, not in kind, so they are one capability whose address says which plane a route is on.
The wrong implementation hands an attacker credentials. The exposures, each closed in code: a secret in a row or log (custody is the only holder, one operation returns a token); a cross-tenant read (org in the primary key, the (org, user) pair as the user-plane row key); a forged provider webhook (signature verification, fail closed); a concurrent refresh destroying the credential it refreshes (single-flight with adoption, apps/integrations/refresh.go:20); and custody-path smuggling (the path is validated before use).
Released under CC0 1.0 Universal Public Domain Dedication.