SDK Runtime Contract
External contract statement: the threading model, ABI, exceptions, logging, and dependency boundaries that the fulla SDK (fulla::common / fulla::oauth2 / fulla::identity / fulla::storage-* / fulla::drogon) commits to for consumers during v1.x.
This document is the single source of truth for external commitments; wherever SDK header comments conflict with it, this document prevails and the headers are to be fixed.
1. Threading Model
- Domain services (
libs/oauth2,libs/identity) do not own an event loop; all asynchronous operations return via callbacks. - Callbacks may fire on any Drogon IO thread — the calling thread is not guaranteed. Consumers must not assume thread affinity; when work must return to a specific thread, the consumer is responsible for dispatching it there.
- Read-only singletons (e.g.
JwkManager) follow init-once-then-read-only: a one-shotinit()completes before the service starts accepting requests; the object is then published asshared_ptr<const T>and never mutated afterwards. Concurrent reads by consumers are safe once SDK assembly is complete. - Service objects hold
shared_ptrrepository handles to guarantee lifetimes; async continuations always captureauto self = shared_from_this()—[this]/[&]are forbidden.
2. ABI Stability
- v1.x supports
find_packagesource integration only and makes no binary-ABI commitment. - Semantic versioning covers the source-level API only: public headers under
include/fulla/**follow SemVer, and breaking changes require a major-version bump (enforced in CI by the api-diff tool). - Mixing precompiled binaries across compilers / STLs is out of scope; a dedicated ABI policy will be defined separately once the project moves to Conan binary-package distribution.
- Deprecation process:
[[deprecated]]annotation + at least one minor-cycle transition period before removal.
3. Exception-Safety Contract
- Domain public APIs return expected errors via
Result<T, Error>; exceptions are not used to express business failure. - Exceptions are thrown only for unrecoverable programming errors (contract violations, assertion-level problems).
- Storage-level exceptions (e.g.
DrogonDbException) must be caught in the Adapter layer (libs/storage-*,libs/drogon) and converted toError— they must not leak into Domain callbacks. Consumers need no try/catch for storage exceptions inside Domain callbacks.
4. Logging Abstraction
- Domain code emits logs through the
common::ports::ILoggerport and does not use DrogonLOG_*macros directly (arch-guard enforces that the Domain layer does not include drogon headers). - The SDK provides a default Drogon logging adapter implementation (the
libs/drogonAdapter); consumers hosted outside Drogon can inject their ownILoggerimplementation as a replacement.
5. Dependency Declarations
- Feature-surface dependencies are explicitly gated by the root
conanfile.py'swith_webauthn/with_identity/with_socialoptions, not smuggled in as transitive surprises. NOTE: the CBOR decoding dependency (libcbor) required by real WebAuthn (FIDO2) used to be declared here, but the current WebAuthn controller is a non-cryptographic stub (it consumes no CBOR), solibcborhas been removed as a dead dependency; it must be re-added once real WebAuthn cryptography lands (see the corresponding comment inconanfile.py). - Disabling an option (e.g.
-o with_webauthn=False) maps through to the CMake-sideWITH_*variables and prunes the corresponding compiled surface, so consumers can shrink their dependency footprint accordingly.
6. Plugin Registration and Configuration Contract (Host Integration)
- The
OAuth2Pluginclass name and the configplugins[].namereflective-loading contract remain stable (Option A): the class-name string in"plugins":[{"name":"OAuth2Plugin","config":{...}}]across the configs (config.{json,dev,ci,prod,bench}.json) and the schema of theconfig{}block are part of the product configuration contract and will not be renamed in v1.x. - The plugin itself is linked into the host as a CMake OBJECT library: object files are linked in directly, one by one, so there is no static-library on-demand extraction that could drop self-registration symbols — whole-archive is not needed today.
- If the plugin is ever distributed in static-library form, the linker may strip its self-registration symbols and Drogon reflection will fail with "plugin not found" — at that point whole-archive (or an equivalent forced-linking scheme) becomes mandatory;
OAuth2Pluginhas noAutoCreationparameter available, so a whole-archive-free scheme is not applicable. - See
examples/third-party-host/for a third-party host integration example.