Connector — diagram coverage (registry side vs caller side)
The connector module is two designs sharing one package: the registry side (how a connector gets installed, credentialed, activated) and the caller side (how a consumer invokes a category verb without ever seeing a provider or a credential). One diagram would blur exactly the boundary the module exists to enforce — so, two.
1 · Registry side — install, credential, activate
classDiagram
class connectorService {
<<connector.Service - the admin-facing side, service.go + svc_*.go>>
Repo.SaveUploaded / Service.UpdateUploaded - plugin upload
connect / oauth / activate / disconnect
credential seal-unseal via cryptobox
}
class Hub {
-conns map[string]Connector
-mu sync.RWMutex
+Register(c Connector)
+Upsert(c Connector)
+Resolve(name) (Connector, bool)
}
class Connector {
<<interface>>
+Name() string
+Kind() string
+Connected(ctx, ownerID) (bool, error)
}
class Verifier {
<<interface - optional>>
+Verify(ctx, ownerID) error
}
class cryptobox {
AES-GCM seal and open
creds ciphertext at rest
}
class owner_connectors {
<<postgres>>
per-owner connection rows
credential ciphertext lives here
}
connectorService --> Hub : Register / Upsert on activate
Hub o-- Connector : by name
Connector <|-- Verifier : optionally also
connectorService ..> cryptobox : seal creds
cryptobox ..> owner_connectors : ciphertext
The registry side is the ONLY place credentials are plaintext, and only transiently. Everything downstream of Hub.Resolve sees a Connector — name, kind, connected; nothing else.
2 · Caller side — category verbs, no provider, no creds
classDiagram
class Consumer {
agent tool / platform feature
future IM-gateway, job-loop
}
class CalendarProxy {
<<interface - connector/contract>>
+Connected(ctx, ownerID) (bool, error)
+FreeBusy(ctx, ownerID, FreeBusyReq) ([]BusyInterval, error)
+InsertEvent(ctx, ownerID, *InsertEventReq) (InsertedEvent, error)
+DeleteEvent(ctx, ownerID, eventID, attendeeEmail) error
}
class MailProxy {
<<interface - connector/contract>>
+Connected(ctx, ownerID) (bool, error)
+Send(ctx, ownerID, MailMessage) (MailReceipt, error)
}
class DepRegistry {
+AllConnected(ctx, ownerID, names) (bool, error)
gate BEFORE exposure - fail-closed
}
class openapiRuntime {
-spec *Spec
-binding *Binding - JSONata
-doer Doer
-baseURL string
+Call(ctx, op, input, dst, AuthInjector) error
}
class AuthInjector {
<<func type>>
func(req *http.Request) error
built inside the connector pkg per call
}
class protocolImpl {
built-in Go clients - SMTP, CalDAV
telegram - token vault only, no contract
}
class StatusError {
Code int
Transient bool - retry per call-class
}
Consumer ..> DepRegistry : exposure gated first
Consumer --> CalendarProxy : category verbs only
Consumer --> MailProxy : category verbs only
CalendarProxy <|.. openapiRuntime
CalendarProxy <|.. protocolImpl
MailProxy <|.. openapiRuntime
MailProxy <|.. protocolImpl
openapiRuntime ..> AuthInjector : injected per request
openapiRuntime --> StatusError : transient vs fatal
The caller's world contains no provider names and no credentials — the proxy interfaces are the whole vocabulary. AuthInjector is constructed inside the package at call time; the consumer cannot even name it.
Coverage checklist (anti-omission gate)
| type / area | diagrammed at | verified |
|---|---|---|
| Hub, Connector, Verifier | this page §1 | ✓ code |
connector.Service responsibilities (was connectorsvc until 2026-07-26, 1bc9ba8b0) |
this page §1 | ✓ names, ☐ full signatures |
| cryptobox / owner_connectors | this page §1 | ✓ mechanism, ☐ column list |
| CalendarProxy / MailProxy + DTOs | this page §2, connector-plugins | ✓ code |
| openapi Runtime / AuthInjector / StatusError | this page §2 | ✓ code |
| DepProvider / DepRegistry / RequiresDeps | connector-deps | ✓ code |
| ingest (spec parse, binding validate) | ☐ not yet diagrammed | — |
| egress guard types | connector-egress-guard (prose) | ☐ no class diagram |