Documentation
¶
Overview ¶
connectors.go is the per-USER connector plane — /v1/connectors, the sibling of the org-scoped /v1/integrations surface. Same package, same registry, same store file, same KMS client; only the custody key differs (org,user,provider, label → userPath).
Surface (registered by connectorRoutes, called last from routes()):
GET /v1/connectors this user's connectors -> {connectors:[...]}
GET /v1/connectors/providers user-scoped provider cards -> {providers:[...]}
GET /v1/connectors/:id/token custodied access token -> {token,...}
POST /v1/connectors/:provider/device begin device sign-in -> {flow,userCode,...}
POST /v1/connectors/:provider/device/:flow/poll poll device sign-in -> {status,...}
POST /v1/connectors/:provider/credential token / oauth-bundle intake-> {connected,connector}
POST /v1/connectors/:id/refresh force a token rotation -> {refreshed,connector}
DELETE /v1/connectors/:id forget + delete secrets -> {disconnected:true}
TENANTING. Every read/write is bound org=? AND user=? — the (org,user) pair from caller() IS the row key, so another user's connector id is simply "no row" → 404. No admin gate: a user owns their own connectors.
CUSTODY. id = provider + ":" + label. Every intake path verifies the credential live BEFORE anything is stored (saveUser: sanitize → seal-before- row → upsert). No secret ever appears in a row, response, or log line except GET /:id/token's body — the ONE custody exit, readable only by the same validated (org,user). Device pending state lives in the cek-encrypted grants table (see the grants DDL comment in store.go), never in KMS.
Package integrations is the generic, provider-agnostic OAuth connector plane for the unified Hanzo Cloud binary — the /v1/integrations surface that lets an org connect a third-party account (Slack today; GitHub scaffolded; Google / Salesforce plug into the SAME registry later) and hands the resulting per-org tokens to KMS custody.
ONE framework, N providers. A provider self-registers (its file's init calls register) into a package registry declaring how to build its authorize URL, exchange the code, and revoke. The five HTTP handlers here are provider-blind: they resolve the provider by :provider, apply the SAME org gate, CSRF/state, KMS custody and console redirect for every one. Adding a provider is a new file, never a new route.
Surface (subsystem name "integrations", prefix /v1/integrations):
GET /v1/integrations list providers + this org's status -> {providers:[...]}
GET /v1/integrations/:provider one provider (404 unknown id) -> Provider
POST /v1/integrations/:provider/connect begin OAuth (org-authed) -> {authorizeUrl} | 503 | 403
GET /v1/integrations/:provider/callback PUBLIC, state-authed -> 302 to console
POST /v1/integrations/:provider/disconnect revoke + forget (org-authed) -> {disconnected:true}
TENANT ISOLATION. connect/list/get/disconnect derive the org from principal.Org (a VALIDATED principal — a client-forged X-Org-Id with no bearer is refused 403). The callback is Slack/GitHub-initiated and therefore UNAUTHENTICATED, so its org is taken ONLY from the HMAC-signed, single-use state — never a header (see state.go). Every org that reaches KMS or the store is additionally validOrg-checked so it can never smuggle path structure into a secret key.
SECRET CUSTODY. Per-org customer tokens live ONLY in KMS (sealed, AES-256-GCM envelope), keyed /orgs/{org}/integrations/{provider}. The store holds only non-secret connection metadata (external id, account label, granted scopes). Provider APP creds (client id/secret) come from ENV, injected by the operator from KMS via KMSSecret — never plaintext in the store or a manifest. If KMS is not Ready the connect/callback flows fail closed (503 / failure redirect); a token is NEVER written in plaintext and NEVER put in SQLite.
Index ¶
- func InstallationToken(ctx context.Context, org string) (string, error)
- func LinkedSubject(org, provider, extUser string) (string, bool, error)
- func Mount(app *zip.App, deps cloud.Deps) error
- func NotifySlack(ctx context.Context, org, channel, text string, blocks []any) error
- func OrgForExternalID(provider, externalID string) (string, bool)
- func PostSlackBlocks(ctx context.Context, botToken, channel, text string, blocks []any) error
- func PostSlackBlocksThread(ctx context.Context, botToken, channel, threadTS, text string, blocks []any) error
- func RegisterIngress(fn func(context.Context, IngressEvent))
- func SendDiscord(ctx context.Context, channelID, replyTo, text string) (string, error)
- func SendSlack(ctx context.Context, org, channel, threadTS, text string) error
- func SendTeams(ctx context.Context, serviceURL, conversationID, text string) error
- func SendTelegram(ctx context.Context, chatID, replyTo int64, text string) error
- func SetAutomationTrigger(f TriggerFunc)
- func SetCodingDispatcher(d coding.Dispatcher)
- func Shutdown(_ context.Context) error
- func TokenFor(ctx context.Context, org, provider, name string) ([]byte, error)
- type Bundle
- type Connection
- type Connector
- type Device
- type DevicePoll
- type DeviceStart
- type ExchangeResult
- type Grant
- type Inbound
- type IngressEvent
- type OAuthConfig
- type Provider
- type Store
- func (s *Store) ClaimNonce(ctx context.Context, nonce, provider string) (string, bool, error)
- func (s *Store) Close() error
- func (s *Store) ConsumeNonce(ctx context.Context, nonce, org, provider string) (bool, error)
- func (s *Store) CountConnectors(ctx context.Context, org, user, provider string) (int, error)
- func (s *Store) Delete(ctx context.Context, org, provider string) (bool, error)
- func (s *Store) DeleteConnector(ctx context.Context, org, user, provider, label string) (bool, error)
- func (s *Store) DeleteGrant(ctx context.Context, id string) error
- func (s *Store) GCEvents(ctx context.Context, before int64) (int64, error)
- func (s *Store) GCGrants(ctx context.Context, now int64) (int64, error)
- func (s *Store) GCNonces(ctx context.Context, before int64) (int64, error)
- func (s *Store) Get(ctx context.Context, org, provider string) (Connection, bool, error)
- func (s *Store) GetConnector(ctx context.Context, org, user, provider, label string) (Connector, bool, error)
- func (s *Store) GetGrant(ctx context.Context, id, org, user string) (Grant, bool, error)
- func (s *Store) List(ctx context.Context, org string) ([]Connection, error)
- func (s *Store) ListConnectors(ctx context.Context, org, user string) ([]Connector, error)
- func (s *Store) MarkEvent(ctx context.Context, provider, key string) (bool, error)
- func (s *Store) PutGrant(ctx context.Context, g Grant) error
- func (s *Store) PutNonce(ctx context.Context, nonce, org, provider string) error
- func (s *Store) ResolveOrgByExternalID(ctx context.Context, provider, externalID string) (string, bool, error)
- func (s *Store) SetGrantInterval(ctx context.Context, id string, sec int64) error
- func (s *Store) TouchGrant(ctx context.Context, id string, now int64) error
- func (s *Store) Upsert(ctx context.Context, c Connection) error
- func (s *Store) UpsertConnector(ctx context.Context, c Connector) error
- type SyncHook
- type TriggerFunc
- type VerifyInput
- type WritebackHook
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InstallationToken ¶ added in v1.801.23
InstallationToken mints a fresh short-lived GitHub App installation token for org's connected installation. Fails closed (error, never a value) when GitHub is not connected for org or the App creds are absent. Called by the git object plane (outbound mirror) and the sync handlers below.
func LinkedSubject ¶ added in v1.801.115
LinkedSubject returns the Hanzo account subject bound to (org, provider, extUser) by the account-link flow (bridge_link.go / *_link.go). Returns ("", false, nil) when the user has not linked; an error (fail closed) on an unmounted subsystem, invalid org, or KMS-down.
func Mount ¶
Mount wires /v1/integrations/* onto app. Complex flavour: it publishes the package global `mounted` (the in-process token-custody seam) and pairs with a Shutdown, so it constructs the cloud.Service value directly (cloud.NewBase + &cloud.Service[state]{…}) rather than via cloud.Mount.
func NotifySlack ¶ added in v1.800.1
NotifySlack posts a Block Kit message to channel on behalf of org's connected Slack workspace. It resolves the org's KMS-sealed bot token (TokenFor — fail-closed: unmounted / org not connected / KMS-down all error, never post) and delivers through PostSlackBlocks. The caller never handles the raw token. This is the door the git-lifecycle notifier (clients/git) uses so token custody stays entirely inside the integrations plane.
func OrgForExternalID ¶
OrgForExternalID resolves a provider account id (Slack team_id / GitHub installation_id) back to the org that connected it. Used by inbound provider events (which carry the external id, not an org) to find the tenant.
func PostSlackBlocks ¶ added in v1.800.1
PostSlackBlocks posts a Block Kit message (with a text fallback shown in notifications) to channel using botToken, via the shared chat.postMessage path. blocks is Block Kit JSON (a []any of section/context/… maps); nil posts text only. Exported so a subsystem holding a resolved bot token (an automations connector) posts through the SAME code path as the OAuth bridge — no third chat.postMessage implementation.
func PostSlackBlocksThread ¶ added in v1.801.23
func PostSlackBlocksThread(ctx context.Context, botToken, channel, threadTS, text string, blocks []any) error
PostSlackBlocksThread posts a Block Kit message threaded under threadTS (when non-empty) via the shared chat.postMessage path — the same door PostSlackBlocks uses, plus in-thread delivery so a coding result lands under the triggering @hanzo message.
func RegisterIngress ¶ added in v1.801.115
func RegisterIngress(fn func(context.Context, IngressEvent))
RegisterIngress installs the ingress consumer. Called once at channels.Mount.
func SendDiscord ¶ added in v1.801.115
SendDiscord posts text to a Discord channel via POST /channels/{id}/messages, referencing replyTo when non-empty. Content is capped at discordMaxContent; the bot token rides only the Authorization header, which is never logged. Returns the created message id.
func SendSlack ¶ added in v1.801.115
SendSlack posts text to channel, threaded under threadTS when non-empty (slackPostThread, slack_events.go — posts top-level chat.postMessage when threadTS == ""). The token is the org's OWN custodied bot token: TokenFor fails closed for unmounted/unknown/not-connected/KMS-down, so an org that never connected Slack cannot post — the per-org token IS the tenancy gate.
func SendTeams ¶ added in v1.801.115
SendTeams posts a message activity to conversationID at the Bot Connector serviceURL (teamsSendActivity, teams_events.go).
func SendTelegram ¶ added in v1.801.115
SendTelegram posts text to chatID via the Bot API sendMessage, threaded under replyTo when non-zero (telegramSend, telegram_events.go).
func SetAutomationTrigger ¶ added in v1.801.186
func SetAutomationTrigger(f TriggerFunc)
SetAutomationTrigger wires the automations Deliver seam. Called once at the composition root, before serving.
func SetCodingDispatcher ¶ added in v1.801.23
func SetCodingDispatcher(d coding.Dispatcher)
SetCodingDispatcher injects the coding orchestrator. Called once at wiring time by the composition root; production never reassigns it.
Types ¶
type Bundle ¶ added in v1.801.186
type Bundle struct{ Access, Refresh, Account string }
Bundle is an externally obtained OAuth token set (CLI local PKCE) submitted for adoption. Access/Refresh are secret; Account is a non-secret hint.
type Connection ¶
type Connection struct {
Org string
Provider string
ExternalID string
AccountLabel string
BotUserID string
Scopes []string
ConnectedAt int64
UpdatedAt int64
}
Connection is an org's non-secret link to a provider account. The token itself is NEVER here — it lives in KMS, keyed by (org,provider); this row holds only the metadata needed to render the card and route inbound events.
func ConnectionFor ¶
func ConnectionFor(org, provider string) (Connection, bool)
ConnectionFor returns an org's non-secret connection metadata for a provider.
NOTE (single contract deviation): the contract names this seam `Connection`, but Go forbids a func and a type sharing an identifier and `Connection` is the domain-noun TYPE (used by SyncHook/WritebackHook, the store, and this return value). The accessor is therefore `ConnectionFor` — the idiomatic Go name for "the Connection for (org,provider)". The bridge calls integrations.ConnectionFor.
type Connector ¶ added in v1.801.186
type Connector struct {
Org, User, Provider, Label string
ExternalID, AccountLabel string
Scopes []string
ExpiresAt int64 // access-token expiry, unix seconds; 0 = non-expiring
ConnectedAt, UpdatedAt int64
}
Connector is a user's non-secret link to a provider account — the per-user sibling of Connection (the /v1/connectors plane). The credential itself lives ONLY in KMS at userPath(org,user,provider,label); this row holds metadata.
type Device ¶ added in v1.801.186
type Device struct {
Start func(ctx context.Context) (*DeviceStart, error)
Poll func(ctx context.Context, g Grant) (*DevicePoll, error)
}
Device is a provider's RFC-8628-style device authorization capability.
type DevicePoll ¶ added in v1.801.186
type DevicePoll struct {
Status string
Interval int64 // seconds; pollSlow and throttled pending
Result *ExchangeResult // pollDone only
}
DevicePoll is one poll outcome. Interval is set for pollSlow (the new poll cadence) and on server-throttled pending answers (current cadence, no upstream call). Result is set for pollDone and MUST be live-proven by the provider (a real token exchange or verify call) — saveUser trusts it. Errors returned by Device funcs never carry token or device-code material.
type DeviceStart ¶ added in v1.801.186
type DeviceStart struct {
Code, UserCode, VerifyURL string
Interval int64 // seconds between polls, raw from the provider
ExpiresAt int64 // unix seconds
}
DeviceStart is the non-secret-facing half of a started device authorization. Code is the provider device handle (secret-adjacent; persisted only in the cek-encrypted grants table, never a response). Interval is the raw wire value; begin() is the sole normalizer.
type ExchangeResult ¶
type ExchangeResult struct {
Tokens map[string]string // secret name -> value (sealed into KMS)
ExternalID string // provider account id (Slack team.id / GitHub installation_id)
AccountLabel string // human label (Slack team.name / GitHub org login)
BotUserID string // Slack bot_user_id (non-secret)
Scopes []string // granted scopes
ExpiresAt int64 // access-token expiry, unix seconds; 0 = non-expiring/unknown. Set by user-plane device/refresh providers; the org plane ignores it.
}
ExchangeResult is what a provider's Exchange returns after trading the OAuth code for tokens. Tokens is a map of KMS secret-name -> secret-value; each entry is sealed into the org's KMS namespace. ExternalID/AccountLabel/BotUserID/Scopes are NON-secret and land in the connection row.
type Grant ¶ added in v1.801.186
type Grant struct {
ID, Org, User, Provider, Label string
Code, UserCode string
Interval int64 // seconds between polls
LastPollAt int64 // unix seconds of the last upstream poll; 0 = never
CreatedAt, ExpiresAt int64 // unix seconds
}
Grant is one in-flight device authorization. Code/UserCode come from the provider; Interval (seconds) is raised by slow_down; LastPollAt gates the server-side poll throttle; ExpiresAt is enforced at read time, not only GC.
type Inbound ¶ added in v1.801.30
type Inbound struct {
Provider string // registry slug: "slack","teams","discord","telegram"
ExternalID string // workspace/tenant/guild/chat id → OrgForExternalID (isolation root)
User string // platform-verified user id (billing/attribution subject via the link)
Channel string // reply target (channel/conversation/chat id)
ThreadID string // thread/message id to reply under (optional)
Text string // the user's prompt, mention stripped
DedupeKey string // event/update/interaction id ("" ⇒ non-dedupable)
}
Inbound is the normalized inbound chat event — ONE shape for every platform. An adapter produces it AFTER it has authenticated the request and parsed the payload. The core never sees a raw platform payload.
type IngressEvent ¶ added in v1.801.115
IngressEvent is one authenticated inbound chat event crossing the seam. Org is resolved via OrgForExternalID on a signature-verified payload; In is the adapter-normalized Inbound (bridge.go); ReplyRoot is a transport-verified reply root (Teams: the JWT-verified serviceURL; "" elsewhere).
type OAuthConfig ¶
OAuthConfig is a provider's resolved APP credentials, read from ENV at request time by the provider's Creds func. ClientID/ClientSecret cover OAuth2; Extra carries provider-specific non-secret config (e.g. the GitHub App slug). It is NEVER persisted — it lives only for the duration of one authorize/exchange.
type Provider ¶
type Provider struct {
ID string // stable slug, the :provider path segment ("slack","github")
Name string // display name
Description string // one-line card copy
Category string // grouping ("Communication","Developer",...)
Scopes []string // requested scopes (display + authorize URL)
RedirectPath string // OAuth redirect path; MUST equal /v1/integrations/{id}/callback
Secrets []string // KMS secret names this provider custodies (deleted on disconnect)
// Kind selects credential acquisition. Empty/"oauth" (default) uses the
// 3-legged Authorize/Exchange flow. "apikey" (apiKeyKind) takes a
// customer-held credential submitted to /connect (from `hanzo connector add`,
// read on STDIN — never argv/URL), VERIFIES it live, and seals it to KMS; such
// providers use Verify, not Authorize/Exchange, and have no OAuth callback.
Kind string
// AdminOnly gates /connect and /disconnect on the caller being an admin of its
// OWN org (principal.IsOrgAdmin — NOT SuperAdmin), parity with the platform
// deploy-provider adminProcedure. OAuth social/chat providers leave it false.
AdminOnly bool
// Verify validates an apikey credential against the provider and returns the
// token(s) to seal + non-secret account metadata. It MUST fail closed (a
// bad/inactive credential returns an error and NOTHING is stored) and its error
// MUST NOT contain the credential value (it is logged). nil for oauth providers.
// On the user plane it doubles as the token/apikey intake method.
Verify func(ctx context.Context, in VerifyInput) (*ExchangeResult, error)
// Scope selects the custody plane: "" = org-scoped /v1/integrations (default);
// userScope = per-user /v1/connectors (rows keyed (org,user,provider,label),
// KMS under /orgs/{org}/users/{user}/connectors/...). The planes are disjoint:
// a user-scoped provider 404s on the org surface and vice versa; Mount asserts
// scope coherence at boot.
Scope string
// Device: device-code sign-in (user scope). nil = unsupported.
Device *Device
// Adopt verifies an externally obtained OAuth bundle (CLI local PKCE) before
// custody. Implementations MUST live-verify (e.g. one refresh) and return the
// rotated material — custody owns the canonical refresh token afterwards.
// nil = unsupported.
Adopt func(ctx context.Context, b Bundle) (*ExchangeResult, error)
// Refresh trades a refresh token for rotated material. The result MUST carry
// Secrets[0] and refreshSecret entries and an ExpiresAt. nil = static credential.
Refresh func(ctx context.Context, refresh string) (*ExchangeResult, error)
// Configured reports whether the provider's APP creds are present in ENV.
// When false: available=false in the card, and connect/callback fail closed
// with an honest 503 / failure redirect (never a dead-end, never a fake OK).
// Org plane only — nil for Scope == userScope (nothing on the user plane
// calls it; list() skips user providers before providerViewFor).
Configured func() bool
// Creds resolves the APP creds from ENV. Called only when Configured is true.
// Org plane only — nil for Scope == userScope.
Creds func() OAuthConfig
// Authorize builds the provider's consent URL for (creds, redirectURI, state).
Authorize func(creds OAuthConfig, redirectURI, state string) (string, error)
// Exchange trades the OAuth code for tokens + account metadata.
Exchange func(ctx context.Context, creds OAuthConfig, redirectURI, code string) (*ExchangeResult, error)
// Revoke best-effort invalidates a token at the provider on disconnect. nil
// when the provider has no revoke endpoint.
Revoke func(ctx context.Context, creds OAuthConfig, token string) error
// #51 seams — declared, nil today, not wired to a route (see SyncHook/WritebackHook).
Sync SyncHook
Writeback WritebackHook
}
Provider is one connectable third-party. Everything provider-specific is a field here so the handlers stay provider-blind. The func fields read ENV at call time (not at init), so an operator can inject creds without a rebuild.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the integrations database. ONE SQLite file ({DataDir}/integrations.db) holds every org's connections + in-flight OAuth nonces; tenancy is the org column (PK includes it).
func (*Store) ClaimNonce ¶ added in v1.801.30
ClaimNonce atomically resolves the org bound to a nonce for `provider` and consumes it (single-use), returning the org. Unlike ConsumeNonce it does NOT know the org up front — it is the redemption side of a deep-link connect code (Telegram): the code arrives in a webhook that has not yet resolved a tenant, so the code ITSELF carries the org. found=false (nil error) when the code is unknown/already-claimed. The Store opens with MaxOpenConns(1) (store.go), so this read-then-delete pair runs with no concurrent writer — the DELETE's RowsAffected is the single-use proof.
func (*Store) ConsumeNonce ¶
ConsumeNonce atomically deletes the nonce bound to (org,provider) and reports whether exactly one row went. A second consume (replay) or a mismatched org/provider deletes zero rows → consumed=false. This single-DELETE-with-rows- affected IS the single-use proof (no read-then-delete race).
func (*Store) CountConnectors ¶ added in v1.801.186
CountConnectors counts (org,user,provider) rows — the maxConnectors intake cap.
func (*Store) DeleteConnector ¶ added in v1.801.186
func (s *Store) DeleteConnector(ctx context.Context, org, user, provider, label string) (bool, error)
DeleteConnector removes a connector. Reports whether a row went (idempotent caller).
func (*Store) DeleteGrant ¶ added in v1.801.186
DeleteGrant forgets a grant (terminal poll outcomes; idempotent).
func (*Store) GCEvents ¶ added in v1.801.30
GCEvents reaps dedupe rows created before `before` (unix seconds), bounding the table's growth. Returns how many rows were removed. Called opportunistically from the webhook path so the table cannot accrete without bound.
func (*Store) GCGrants ¶ added in v1.801.186
GCGrants deletes expired grants. Returns how many were reaped. Called opportunistically on device start so abandoned flows don't accrete (GCNonces parity).
func (*Store) GCNonces ¶
GCNonces deletes nonces created before `before` (unix seconds). Returns how many were reaped. Called opportunistically on connect so abandoned flows don't accrete.
func (*Store) Get ¶
Get returns the connection for (org,provider). found=false (nil error) when there is no row; a real DB error is returned as err.
func (*Store) GetConnector ¶ added in v1.801.186
func (s *Store) GetConnector(ctx context.Context, org, user, provider, label string) (Connector, bool, error)
GetConnector returns the connector for (org,user,provider,label). found=false (nil error) when there is no row.
func (*Store) GetGrant ¶ added in v1.801.186
GetGrant is (id,org,user)-scoped — another tenant's poll of a known id is found=false — and enforces the TTL in SQL (expires_at > now), so an expired grant is indistinguishable from an unknown one at read time.
func (*Store) ListConnectors ¶ added in v1.801.186
ListConnectors returns every connector for (org,user), newest-connected first, with a deterministic (provider,label) tiebreak.
func (*Store) MarkEvent ¶ added in v1.801.30
MarkEvent is the atomic durable dedupe test-and-set: it inserts (provider,key) and returns fresh=true only on the FIRST sighting. A duplicate (a platform retry of the same event/update/interaction id) hits the PRIMARY KEY, the insert affects zero rows, and fresh=false. An empty key is non-dedupable (fresh) — callers only dedupe non-empty keys onto the billed path. The single INSERT ... ON CONFLICT DO NOTHING + RowsAffected IS the single-use proof (no read-then-write race). A genuine DB error is surfaced so the caller fails CLOSED (skips the run) rather than risk a double charge.
func (*Store) PutGrant ¶ added in v1.801.186
PutGrant records a started device authorization. A duplicate 128-bit id (astronomically unlikely) is a conflict, surfaced so the flow fails rather than silently overwriting an in-flight one (PutNonce parity).
func (*Store) PutNonce ¶
PutNonce records a single-use OAuth nonce bound to (org,provider). A duplicate nonce (astronomically unlikely from 128 bits) is a conflict, surfaced so connect fails rather than silently overwriting an in-flight one.
func (*Store) ResolveOrgByExternalID ¶
func (s *Store) ResolveOrgByExternalID(ctx context.Context, provider, externalID string) (string, bool, error)
ResolveOrgByExternalID maps a provider account id back to the connecting org. An empty externalID never matches (so unset/scaffold connections don't collide on ""). Ambiguity (two orgs, same external id — should not happen) resolves to the earliest-connected deterministically.
func (*Store) SetGrantInterval ¶ added in v1.801.186
SetGrantInterval records a provider slow_down bump so every later poll honors the raised cadence.
func (*Store) TouchGrant ¶ added in v1.801.186
TouchGrant sets last_poll_at — the server-side poll-throttle clock. Tests pass 0 to rewind the throttle.
func (*Store) Upsert ¶
func (s *Store) Upsert(ctx context.Context, c Connection) error
Upsert stores (or refreshes) a connection. On a re-connect the original connected_at is PRESERVED ("connected since"), only updated_at advances.
func (*Store) UpsertConnector ¶ added in v1.801.186
UpsertConnector stores (or refreshes) a connector. On a re-connect or token refresh the original connected_at is PRESERVED ("connected since"), only the metadata and updated_at advance — Upsert (connections) parity.
type SyncHook ¶
type SyncHook func(ctx context.Context, conn Connection) error
SyncHook pulls provider-side state INTO Hanzo (e.g. a GitHub App installation's repo list). It is a #51 seam: DECLARED on Provider, nil for every provider today, and NOT wired to any route. When GitHub creds land, github.go sets this to the installation-token-minting + repo-sync implementation.
type TriggerFunc ¶ added in v1.801.186
type TriggerFunc func(ctx context.Context, org, source, name, dedupeKey string, depth int, payload map[string]any) (int, error)
TriggerFunc delivers a signature-VERIFIED inbound event to the automations engine. org is the resolved tenant (OrgForExternalID / a verified principal) — never a client-supplied field. depth is the causation depth (0 for an external-origin webhook). It returns how many flows the event started.
type VerifyInput ¶ added in v1.801.79
VerifyInput is what an apikey provider's Verify receives: the customer's credential (from the /connect body, originally read on STDIN by `hanzo connector add`) plus an OPTIONAL non-secret account hint the caller may supply when the provider's own verify response cannot disclose it (e.g. a Cloudflare least-privilege token that can list neither its own name nor its account).
type WritebackHook ¶
type WritebackHook func(ctx context.Context, conn Connection, payload []byte) error
WritebackHook pushes Hanzo state TO the provider. It is a #51 seam: declared, nil today, not wired.
Source Files
¶
- adroll.go
- aimodels.go
- analytics.go
- anthropic.go
- automation_trigger.go
- bridge.go
- bridge_dedupe.go
- bridge_link.go
- bridge_state.go
- cloudflare.go
- commerce.go
- connectors.go
- content.go
- copilot.go
- crm.go
- device.go
- discord.go
- discord_events.go
- discord_link.go
- github.go
- github_app.go
- github_pages.go
- github_webhook.go
- gitlab.go
- google.go
- google_data.go
- google_marketing.go
- ingress.go
- integrations.go
- keyverify.go
- linkedin.go
- messaging.go
- meta.go
- microsoft.go
- oauth_http.go
- openai.go
- payments.go
- recaptcha.go
- reddit.go
- refresh.go
- saas.go
- salesforce.go
- slack.go
- slack_coding.go
- slack_events.go
- slack_link.go
- slack_verify.go
- state.go
- store.go
- teams.go
- teams_events.go
- teams_link.go
- teams_verify.go
- telegram.go
- telegram_events.go
- telegram_link.go
- tiktok.go
- twitter.go
- verifypost.go
- warpcast.go
- whatsapp.go