Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
708 changes: 708 additions & 0 deletions docs/adr/0024-public-provider-extensibility-model.md

Large diffs are not rendered by default.

438 changes: 438 additions & 0 deletions docs/adr/0025-compono-nsubstitute-package-design.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ the mechanics: numbering, status, and the index.
| [0021](0021-row-composition-entry-point-for-test-framework-integrations.md) | Row Composition Entry Point for Test-Framework Integrations | Accepted |
| [0022](0022-compono-xunit-package-design.md) | Compono.Xunit Package Design | Accepted |
| [0023](0023-rename-compono-xunit-to-compono-xunitv3.md) | Rename Compono.Xunit to Compono.XunitV3 | Accepted |
| [0024](0024-public-provider-extensibility-model.md) | Public Provider Extensibility Model | Accepted |
| [0025](0025-compono-nsubstitute-package-design.md) | Compono.NSubstitute Package Design | Accepted |
49 changes: 31 additions & 18 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,22 @@ not every stage is the same *kind* of thing:
| 2 | Shared or scoped values | Context-owned deterministic check against the scope. Milestone 2/3 shipped this gated by the *current* request's own `IsShared` flag on both the read and write side; [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md) (implemented, Milestone 4 Phase 0) changed the **read** side to an unconditional scope check (any request, `IsShared` or not, sees an already-shared value for its type) while leaving the **write** side unchanged (only an `IsShared` request ever populates scope) β€” required for a Milestone 4 `[Shared]` test parameter's value to reach an *ordinary*, unmarked nested constructor parameter of the same type. |
| 3 | Exact registrations | **Hybrid**, per [ADR-0019](adr/0019-registrations-and-service-provider-injection.md) (implemented, Milestone 3 Phase 1): a context-owned deterministic lookup against the exact-registration table, then β€” only on a miss, if a consumer called `UseServiceProvider(...)` β€” a fallback `IServiceProvider.GetService(typeof(T))` call. Milestone 2 shipped this stage as internal-only with no public builder; Milestone 3 Phase 1 shipped its real public shape (`builder.Register<T>(...)`, `builder.UseServiceProvider(...)`). |
| 4 | Configuration rules | Ordered `ICompositionProvider` collection, per [ADR-0020](adr/0020-composition-configuration-rules.md) (implemented, Milestone 3 Phase 3). Renamed from "profile rules" (`PipelineStage.ConfigurationRule`): populated by type/member value rules compiled from `builder.For<T>()...`, whether reached directly or via a profile's `Configure` β€” a profile is a reusable application mechanism over this stage, not its owner ([ADR-0018](adr/0018-composition-profiles.md)). Collection-size configuration does **not** populate this stage β€” see Configuration Rules, below. |
| 5 | Semantic value providers | Ordered `ICompositionProvider` collection β€” empty until Milestone 6 (Bogus). How an integration package populates this stage with open-ended, pattern-matching logic is deliberately undesigned until Milestone 5 gives it a real consumer β€” see Open Architectural Decisions, below. |
| 6 | Test-double providers | Ordered `ICompositionProvider` collection β€” empty until Milestone 5 (NSubstitute), same deferred-extensibility note as stage 5. |
| 5 | Semantic value providers | Ordered `ICompositionProvider` collection. The public registration surface (`builder.AddSemanticProvider(ICompositionValueProvider)`) is implemented as of Milestone 5 Phase 0 ([ADR-0024](adr/0024-public-provider-extensibility-model.md)) β€” but no `Compono`-shipped package registers anything into it by default, since `Compono.Bogus` (Milestone 6) doesn't exist yet. Empty in practice until then, populated in mechanism now. |
| 6 | Test-double providers | Ordered `ICompositionProvider` collection. Same status as stage 5: the public registration surface (`builder.AddTestDoubleProvider(ICompositionValueProvider)`) is implemented ([ADR-0024](adr/0024-public-provider-extensibility-model.md)), but empty in practice until `Compono.NSubstitute` (Milestone 5's own package, [ADR-0025](adr/0025-compono-nsubstitute-package-design.md)) ships β€” see [PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for its phase status. |
| 7 | Built-in value providers | **Hybrid** (ADR-0014): an ordered `ICompositionProvider` collection (primitive/simple types, enums, nullable value types), populated internally by `Compono` itself, tried first β€” followed by a context-owned deterministic dispatch through `CollectionPlanCache<T>` for the five built-in collection shapes (array, `List<T>`, `IReadOnlyList<T>`, `HashSet<T>`, `Dictionary<TKey, TValue>`), the same closed-generic-field-read mechanism stage 8 uses, since `ICompositionProvider` can't itself construct a generic collection without reflection |
| 8 | Generated composition plans | Context-owned deterministic dispatch via `PlanCache<T>` β€” **not** an `ICompositionProvider` (see Source-Generated Composition Plans, below) |
| 9 | Diagnostic failure | Context-owned terminal stage |

Only stages 4/6/7 hold an actual ordered collection of providers in
Milestone 2 (and of those, only 7 has anything registered in it β€” 4/5/6
are wired but empty until their owning milestone). Provider order
Stages 4/5/6/7 each hold an actual ordered collection of providers, and
every one of their public registration surfaces is implemented today
(`.For<T>()` for stage 4, since Milestone 3; `AddSemanticProvider`/
`AddTestDoubleProvider` for stages 5/6, since Milestone 5 Phase 0 β€”
[ADR-0024](adr/0024-public-provider-extensibility-model.md)) β€” but only 4
and 7 have anything registered in them *by default*: stage 4 only when a
consumer actually calls `.For<T>()`, stage 7 unconditionally
(`BuiltInProviders.Default`). Stages 5/6 stay empty until a consumer
registers a provider directly, or until `Compono.Bogus`/`Compono.NSubstitute`
(which don't exist yet) do it on their behalf. Provider order
*within* an extensible stage is registration order; stage 7 alone already
holds three real providers (`PrimitiveValueProvider`, `EnumValueProvider`,
`NullableValueProvider` β€” `BuiltInProviders.Default`), so "no stage has
Expand Down Expand Up @@ -862,19 +869,25 @@ Owns:
[ADR-0018](adr/0018-composition-profiles.md): `ICompositionProfile` interface
(not an abstract base class), eager in-order application, type-keyed cycle
detection.
- **Public provider extensibility (how integration packages contribute open-ended
pattern-matching logic to stages 5/6)** β€” evaluated and explicitly deferred to
Milestone 5 during the Milestone 3 design review: Milestone 3's own scope
(registrations, profiles, type/member rules, `IServiceProvider` fallback) needs no
such interface β€” type/member value rules compile into internal,
Compono-authored providers ([ADR-0020](adr/0020-composition-configuration-rules.md)),
and service injection folds into stage 3 as a context-owned fallback
([ADR-0019](adr/0019-registrations-and-service-provider-injection.md)) β€” neither
needs a public `ICompositionProvider`-shaped contract. NSubstitute (Milestone 5)
is expected to be the first real consumer that needs one (matching "any interface
type," a pattern no closed-set rule can express), so the interface gets designed
against that concrete need rather than speculatively now. No design has been
chosen yet.
- ~~Public provider extensibility (how integration packages contribute open-ended
pattern-matching logic to stages 5/6)~~ β€” evaluated and explicitly deferred to
Milestone 5 during the Milestone 3 design review, for the reasons this bullet
originally gave (type/member value rules compile into internal,
Compono-authored providers per [ADR-0020](adr/0020-composition-configuration-rules.md);
service injection folds into stage 3 per
[ADR-0019](adr/0019-registrations-and-service-provider-injection.md); neither
needed a public contract). **Resolved by
[ADR-0024](adr/0024-public-provider-extensibility-model.md)** (core contract:
`ICompositionValueProvider`, `CompositionBuilder.AddSemanticProvider`/
`AddTestDoubleProvider`, compiled into stage 5/6 the same way ADR-0020's rules
compile into stage 4 β€” **implemented, PLAN-0005 Phase 0**) **and
[ADR-0025](adr/0025-compono-nsubstitute-package-design.md)** (`Compono.NSubstitute`,
the first real consumer of that contract β€” design only, not yet implemented;
see [PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for its
phase status). The core extension point itself is real and tested; stages
5/6 in the Resolution Pipeline table above stay empty in practice only
because no `Compono`-shipped package (`Compono.Bogus`, `Compono.NSubstitute`)
registers anything into them yet.
- **Richer `Microsoft.Extensions.DependencyInjection` integration** (`IServiceCollection`
auto-registration, per-composition scoping, keyed services) β€” explicitly out of
scope for `Compono` core per
Expand Down
15 changes: 15 additions & 0 deletions docs/mvp.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,25 @@ internal Compono-authored stage-4 providers
(`docs/adr/0020-composition-configuration-rules.md`). Deliberately not designed in
Milestone 3, since it had no real consumer there.

Design: [ADR-0024](adr/0024-public-provider-extensibility-model.md) (the core
public provider contract β€” `ICompositionValueProvider`, registration into stages
5/6, diagnostics identity β€” reusable by Milestone 6 without a redesign),
[ADR-0025](adr/0025-compono-nsubstitute-package-design.md) (`Compono.NSubstitute`
package: substitutable-shape rules including delegate types, `NSubstituteOptions`,
diagnostics). **ADR-0024's core engine extension point is implemented
(PLAN-0005 Phase 0)** β€” `builder.AddSemanticProvider(...)`/
`builder.AddTestDoubleProvider(...)` are real, tested public API today.
**`Compono.NSubstitute` itself (ADR-0025) is not yet implemented** β€” see
[PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for the
phase-by-phase tracker; this milestone's own exit criteria aren't met until
that package ships.

### Scope

- Test-double provider contract
- Interface substitutes
- Delegate substitutes (added during ADR-0025's design β€” NSubstitute supports
this natively at negligible extra cost; not in this bullet list originally)
- Optional abstract-class substitutes
- Shared substitute reuse
- Integration-specific configuration
Expand Down
Loading