pkg/sdk/iface
The consumer-facing interfaces. Cross-module imports go only through this package — never into another module's services/ or repository/ directories.
Implemented by the core
A representative slice, not the full list — read the package for the rest.
| Interface | Provided by | Consumed by |
|---|---|---|
UserProvider | user | auth, and every module that needs a user record |
OperatorUserProvider / ClientUserProvider | user | tier-specific lookups |
SessionTerminator | user | any module that must revoke a principal's sessions |
NotificationSender | notification | auth (verification, password reset) |
CategoryConfiguredChecker | notification | auth pre-flight guards — optional; IsConfiguredForCategory falls back to IsConfigured |
TenantProvider | tenant | authz, auth (JWT membership embedding), middleware |
AccessProvider | tenant | middleware (RequireCapability) |
AuthzProvider | authz | middleware (permission checks) |
AuthzCacheInvalidator | authz | user (retiring a principal's cached verdicts after a system-role change) |
JWTProvider / TenantScopedTokenProvider | auth | the dev-token endpoint, any custom auth integration |
PasswordHasher / AdminAuthInviter | auth | user (admin-direct Tier-2 flows) |
ServiceAccountLister | auth | user |
AuditSink | compliance | every module performing a security-sensitive action |
PIIProducer | user, auth, tenant, authz, notification | compliance (the GDPR DSR pipeline) |
KMSProvider | compliance | per-tenant envelope encryption and crypto-shred |
ObjectStore / ObjectStoreProvider | the platform's blob provider | user (avatars), and any module storing uploads |
PDFRenderer | the platform's PDF renderer (a Gotenberg sidecar, wired by the server binary as an optional service — not by a core module) | any module compositing a printable document (e.g. a receipt) |
AuditSinkSetter and KMSProviderSetter are the setter seams on core services: nil by default, so a fork can wire its own audit or key-management implementation the way the core compliance module does.
KMSProvider.CreateKey is idempotent under concurrency, not just for sequential callers: two calls racing to create a key for the same tenantUUID must both return the single winning keyID, never mint two DEKs for one tenant. The in-tree compliance implementation backs this with the collection's unique tenantUuid index plus a duplicate-key reread; any other implementation of KMSProvider (a remote HTTP-backed client, say) must uphold the same guarantee, since a resumable operation (like tenant provisioning) may call CreateKey more than once for the same tenant.
PDFRenderer — an optional platform service, not a module
PDFRenderer.RenderHTML(ctx, HTMLDocument) ([]byte, error) turns one self-contained HTML page (HTMLDocument.HTML plus a HTMLDocument.Assets map of any files the page references — the implementation never fetches remote resources) into PDF bytes, at the geometry described by HTMLDocument.Paper (PaperSpec — size, orientation, margins; zero value is A4 with 15 mm margins). Unlike the interfaces above, nothing in the base implements it as a module: cmd/server/main.go registers a Gotenberg-backed client under module.ServicePDFRenderer only when PDF_RENDERER_URL is configured, and registers it even when the sidecar is unreachable at boot, since a consumer resolves it per call —
renderer, ok := module.GetTyped[iface.PDFRenderer](deps.Services, module.ServicePDFRenderer)
if !ok {
// no renderer configured — degrade (e.g. skip the PDF attachment)
}
— and degrades on iface.ErrPDFRendererUnavailable (absent, unreachable, timed out, 5xx), iface.ErrPDFRenderFailed (the sidecar refused the document, or its reply wasn't a PDF), or iface.ErrPDFTooLarge (over the renderer's own byte cap), rather than failing at boot. Its liveness is also surfaced operationally: GET /v1/admin/modules/health reports it in the response's platform array, independent of any module's own health.
Notification attachments
NotificationRequest.Attachments (Send only — SendTemplated ignores it) carries []iface.Attachment{Filename, ContentType, Data []byte}, raw bytes never base64. The notification module enforces the limits (raw size cap, application/pdf only, filename sanitization) and a driver without attachment support fails the send rather than silently dropping the files; see notification for the delivery-side contract. When a send fails for that reason, NotificationResult.FailureReason carries the iface.FailureAttachmentRejected string — returned by a fresh send and by an idempotent replay alike, so a caller never needs to inspect a driver-specific error to react to it.
Error sentinels
A representative slice — sentinels declared beside the interface whose consumers
must match them across the module boundary with errors.Is.
ErrPasswordLoginDisabled— the email/password method is administratively disabled for the target user's surface; the admin send-password-reset routes answer 409auth.password_login_disabledwith it.ErrAuthPolicyUnavailable— a persisted sign-in policy could not be read or parsed; consumers fail closed with 503auth.policy_unavailable.ErrKMSKeyNotFound— the precedent for the pattern: a per-tenant key lookup found nothing, either because the key never existed or because it was shredded; callers branch on it without importing thecomplianceimplementation.ErrUserNotFound— the user module'sservices.ErrUserNotFoundaliases it, so auth's refresh path can classify a deleted account witherrors.Iswithout importing the user module: a 401 for an account that is gone, a 503 for a store it could not read. AUserProviderimplementation MUST return or wrap this sentinel when the user does not exist — any other error reads as "the store could not be read", so the refresh path answers 503 for a deleted account and the SPA keeps its access token and session marker forever, never learning the session is over.
Reserved for a fork's addons
ADR-0006 collapsed the base to core-only, but kept the interfaces the removed verticals used. Nothing in the base implements these — they are the shape a fork's addon satisfies so it plugs into the same seam:
PDFProvider · GraphProvider · AIModelProvider · RAGQueryProvider · PaymentProvider · SubscriptionReconciler · SelfServiceCheckoutPlanner · BillingTenantProvider · ClientSelfDeletionGate
Do not confuse PDFProvider with PDFRenderer above: PDFProvider is the wider, module-shaped generate/store/retrieve contract a fork's own documents addon can choose to implement; PDFRenderer is the narrow, core-wired HTML→PDF conversion service described above, with no storage or template concerns of its own.
Why the indirection
Any implementation — in-process, or a remote HTTP-backed client a fork wires in — satisfies the same interface, so consumer code never changes. It is also what keeps the dependency graph honest: a module declares what it needs, and the registry resolves who provides it.