Connector dependencies — Requires, resolved fail-closed
The formal interface between the capabilities pillar and the connector pillar — fail-closed and consumer-agnostic by design. A plugin manifest declares Requires: ["calendar", "smtp"] — NAMES of in-app dependency providers, each backed by a connector category. capreg/depresolver.go is the registry; resolution happens at assembly: any required dep not Connected → the capability is globally hidden (fail-closed) — degraded discovery, not runtime error. Providers expose exactly two things: a Connected predicate and a call handle — never credentials (the connector-plugins boundary).
The consumer-agnostic decision makes the mechanism universal and bidirectional: the same named-dep resolution serves the agent, platform features, and future consumers (IM gateway, job-loop), in both directions (action and sync).
flowchart LR
M["plugin manifest
Requires: [calendar]"] --> DR["capreg depresolver
(named providers)"]
DR --> Q{"provider Connected?"}
Q -- yes --> H["inject call handle
(never creds)"]
Q -- no --> X["capability HIDDEN
(fail-closed)"]
Class view
classDiagram
class DepProvider {
<<interface>>
+Name() string
+Connected(ctx, ownerID) (bool, error)
}
class DepRegistry {
-providers map[string]DepProvider
+Register(p)
+Lookup(name) (DepProvider, bool)
+Unknown(names) []string
+AllConnected(ctx, ownerID, names) (bool, error)
}
class RequiresDeps {
<<interface>>
+Requires() []string
}
class funcProvider {
-name string
-connected func(ctx, ownerID) (bool, error)
built by NamedProvider(name, fn)
}
DepProvider <|.. funcProvider
DepRegistry o-- DepProvider : by name
RequiresDeps ..> DepRegistry : AllConnected at assembly
Precision note: DepProvider answers only Connected — it is a predicate, not the call handle. The actual handle a consumer uses is the category proxy (connector-plugins's CalendarProxy/MailProxy), wired separately; the dep mechanism just decides whether the capability is exposed at all (AllConnected false → hidden, fail-closed).
Since 2026-08-20 (a9c446937) there is a second predicate on top: OpProvider.CanPerform(ctx, ownerID, op) (depresolver.go:151), built by NamedOpProvider. A capability can require an operation (calendar:events.insert), not just a connection, so a read-only grant hides "book a meeting" but not "free slots". At 36789537d the composition root registers calendar as a NamedOpProvider (backed by ConnectorSlots.CanPerform) and smtp as a plain NamedProvider (cmd/server/axisconn/register.go:207-214) — those two names are the whole provider table.