ADR-0011: The driver API¶
- Status: Accepted (2026-10-31, by the owner)
- Date: 2026-10-31
- Amends: ADR-0001 (how driver packages register) and ADR-0010 (a second public surface, not for modules)
- Research:
design/driver-api/research.md
Context¶
A contract has two readers. A module asks what it can call; a driver asks what it must
implement. They need different promises: a new abstract method does not touch a caller
and breaks an implementer. Sandloom showed both readers the same class and gave neither
a public home. Accessors returned Any, and a driver imported from sandloom.core.
Discovery had its own faults. Every installed package ran register(registry) at
startup in an order Python does not define, so a driver could lose to the package that
declares its capability. A failure was only logged, and a package could silently
replace a built-in driver, sqlalchemy included. Features were declared twice, so a
requirement could pass at startup and fail on first use.
Decision¶
- Two doors, one class. Modules get a Protocol of operations from
sandloom.sdk.infra(Cache,Notifier,ObjectStore,TaskQueue), and accessors return it. Driver packages get the provider ABC, which subclasses that Protocol and adds the lifecycle, fromsandloom.driverkit. A module may not importdriverkit(C004) and the kernel never does (lint-imports). - Find drivers by name, lazily. An entry point
"<capability>.<driver>" = "pkg.driver:Class"insandloom.drivers, and"<capability>" = "pkg.contracts:SPEC"insandloom.capabilities. Only whatINFRAselects is imported. There is noregistercallable, so there is no order. - Conflicts and failures stop startup. A selected name with two providers, or a package claiming a built-in's name, is SANDLOOM-B021. A selected driver that cannot be used is SANDLOOM-B022. There is no override: another behaviour is another name.
- Declare once, on the class.
supported_features, orfeatures_for(options)when a feature depends on an option. An instance that reports otherwise is refused. A required method is not a feature, and a test holds every spec to that. - Required contract version.
contract_versionon the driver class; a missing one, or another major version, is SANDLOOM-B022. Signatures are pinned per version by a test, andsandloom.driverkitis in the API baseline (T008). - A contract is public only with a kit.
NotifierProviderhasNotifierConformance, a pytest class with one test per obligation. A test fails a contract with no kit. Thecache,objectstoreandtaskqueuecontracts stay private until theirs exist. The kit is itself tested against broken drivers. CapabilityRef[P], nottype[T].capability(REF, name) -> P. mypy rejects an abstract class or a Protocol passed astype[T]; the key mirrorsContractRef[P].- Experimental means refused in production. Configuring an experimental capability
there needs the accepted risk
experimental_capability(SANDLOOM-B023, ADR-0006). A documentation label alone does not hold: PEP 411 records that. - A module cannot import a driver. C004 reads installed
sandloom.driversentry points and refuses them and their parents, so a package keeps its reference and Protocol in a separatecontractsmodule.redisandceleryjoin the banned backend SDKs.
Consequences¶
- Removed:
register(registry),InfraRegistryfrom the driver API, and thes3,gcsandycplaceholders. Selecting those names is the ordinary "no driver" error, naming what to install. Nothing is kept for compatibility: Sandloom is unreleased. graph_store,workflow,search,lockandsecretsremain defined and not public. Each becomes public with its first driver and kit, as planned for a Temporalworkflowdriver.- Rejected, with reasons in the research: eager loading in sorted order, an override
flag, wrappers over providers,
TypeForm, asandloom.experimentalnamespace, and an experimental comment marker in modules, which a static check cannot resolve for a reference imported from another package.