Architecture and product decisions¶
Recorded: 2026-09-14. These decisions summarize the agreed direction from the module review and test restructuring. Implementation status and proposed future milestones live in the roadmap. Detailed module authoring requirements live in module conventions.
| ID | Accepted decision | Reason and consequence |
|---|---|---|
| D01 | Keep the current capability-based module architecture | Small lifecycle interfaces, named registration and typed configuration are sufficient for incremental expansion; no module-system rewrite is planned |
| D02 | Run real applications against real dependencies | Application projects use their native production clients and contain no Stove dependency; configuration points them to the test environment |
| D03 | Keep modules independently consumable | Each provider has its own package and acceptance project; adding MongoDB must not pull it into SQL Server tests or every example |
| D04 | Preserve native APIs | Provider-specific parameters, filters and clients remain accessible; shared helpers should not erase useful database or protocol behavior |
| D05 | Separate test responsibilities | Core tests verify orchestration; module suites verify provider behavior; real-app contracts verify public setup; OrderService demonstrates composition; package smoke tests verify distribution |
| D06 | Share contracts without coupling providers | The shared testing project has no database-driver dependencies; each provider owns its adapter, application and runtime fixture |
| D07 | Keep OrderService focused | It demonstrates PostgreSQL, Redis, Kafka and external HTTP interactions; it is not a registry of every available module |
| D08 | Make isolation explicit | A named instance is not a per-test reset; correlation does not isolate rows, and a test-owned transaction does not include the app's separate connections |
| D09 | Preserve failures while releasing resources | Cleanup failure must not skip later disposal; rollback waits for starts already in flight and retains startup/rollback errors |
| D10 | State messaging guarantees precisely | Publication, routing, offset commits and successful business processing are different observations; negative assertions describe a bounded observation window |
| D11 | Use independent CI suite jobs | A failing provider must not suppress other suite results; packaging and release wait for the suite matrix |
| D12 | Expand database coverage first | SQL Server, MongoDB and MySQL are implemented; messaging/protocol/cloud follow with explicit scope |
| D13 | MongoDB uses native document APIs and explicit database selection | Default mongo:8.0 single-node replica set supports verified native transactions; standalone is optional. Ordered Setup manages collections/indexes/seeds without a migration-history engine |
| D14 | MySQL uses MySqlConnector and the relational acceptance contract | Default mysql:8.4 is tested with MySqlConnector 2.6.2; callbacks and parameters remain native; MariaDB parity is not claimed |
| D15 | Bound broker evidence per test and require correlation by default | Kafka/RabbitMQ share count/byte limits; overflow invalidates assertions. Headerless fallback is opt-in and limited to one active scope; arrival time cannot identify delayed old work |
| D16 | RabbitMQ observes dedicated queues and exposes native confirms | Bind an exclusive queue to named exchanges; never compete for application messages. Keep publisher confirms, routing and application processing distinct; fail on observer loss rather than hide gaps with recovery |
| D17 | Azure is a package family; Service Bus is independently consumable | StoveDotnet.Azure.ServiceBus owns its SDK/emulator dependencies; a future StoveDotnet.Azure may be a convenience meta-package |
| D18 | Service Bus separates broker state, destructive receive and business processing | Peek scheduled state without waiting; explicit receive settlement may compete, and successful processing requires a business-visible assertion |
What the original Stove informs¶
Practical consumer evidence: RailSense¶
RailSense's response/upload helpers, waits, clock and fake OIDC server informed the reusable features in practical testing. The consumer was inspected without modification. Accepted decisions:
- Extend HTTP wrappers with fluent status checks and configurable bounded/redacted diagnostics; retain raw access and lazy deserialization. Raw response data remains caller-controlled.
- Preserve legacy retry behavior and add token-aware overloads with explicit retry selection. Await cooperative probes sequentially; never abandon timed-out work and start overlapping probes. Absence sampling has a bounded, documented scope.
- Use Microsoft's
FakeTimeProviderin an optional package. Fixtures explicitly own clocks and register them per app; avoid a second timer scheduler. External database and token-validation clocks remain independent. - Reuse named applications for shared unique-data and independent environment examples. No implicit transaction/reset API.
- Keep the optional OIDC module generic: local discovery/JWKS and signed token scenarios verified through JWT bearer. Domain roles and interactive identity-provider flows stay outside this module.
Named in-process applications are now supported. APIs and Generic Host workers keep separate configuration and
lifetimes while sharing dependencies. Registration order controls startup/readiness; reverse order controls shutdown.
HTTP clients explicitly target applications when no default exists. Core stays independent of Microsoft hosting APIs;
StoveDotnet.Hosting provides worker lifecycle and application-local log collection. Separate-process hosting and
global instrumentation isolation remain outside this increment. See the adoption guide.
Framework adoption now has executable evidence: separate xUnit v3, NUnit, MSTest and TUnit examples share only application/environment setup. CI checks native assertions, cancellation, runner failure/skip outcomes, filtering and teardown on .NET 10/MTP, then repeats against isolated package consumers. The core remains free of framework dependencies. The immediate next step is a real consumer pilot; dashboard integration is deferred while existing failure output serves diagnosis. See the adopter guide.
The local original-Stove checkout was reviewed during the initial assessment. Useful references include structured operation reporting, explicit container/provided runtime capabilities, migration behavior and separate gRPC client/mock modules. Its Spring Kafka integration can observe consumer outcomes directly, whereas StoveDotnet's current Kafka consumption assertion uses committed offsets.
The decision is to borrow useful capabilities and behavior, not reproduce JVM-specific abstractions or claim feature parity. Broader operation reporting, container controls, reuse and processing adapters remain future work.
Accepted direction versus unresolved design¶
Generic dependency containers are supported through the optional StoveDotnet.Containers package. Consumers supply a
fresh Testcontainers factory with native readiness and optional initialization; Stove owns startup, configuration mapping,
failure evidence and disposal. Native builder options are reused rather than mirrored into a second Docker API.
Dedicated modules remain useful for provider clients and testing semantics. Existing-container adoption, resource reuse,
dependency graphs and Compose orchestration are outside this increment. A real MinIO/API example validates the boundary.
The database-first direction and the architecture/testing rules above are accepted. MongoDB's API/topology/transaction scope and MySQL's driver/version policy are implemented (D13/D14). Broker retention/correlation and RabbitMQ observation are now implemented (D15/D16). Azure Service Bus is implemented under D17/D18. gRPC streaming scope and other cloud emulator choices are not finalized. The roadmap proposes a sequence and initial boundaries for those decisions. They should be resolved with their module's implementation and acceptance evidence.
When changing an accepted decision, record the new requirement, the tradeoff and which earlier decision it replaces. Do not silently rewrite an implemented limitation as a guaranteed capability.