diff --git a/docs/architecture.md b/docs/architecture.md index e2fcc35..d0aa38c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -192,7 +192,7 @@ not every stage is the same *kind* of thing: | 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(...)`, `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()...`, 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. 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. | +| 6 | Test-double providers | Ordered `ICompositionProvider` collection. The public registration surface (`builder.AddTestDoubleProvider(ICompositionValueProvider)`) is implemented ([ADR-0024](adr/0024-public-provider-extensibility-model.md)), and `Compono.NSubstitute` ([ADR-0025](adr/0025-compono-nsubstitute-package-design.md), implemented and test-covered — see [PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md)) is a real registrant via `UseNSubstitute()`. Still empty by default in any composition that doesn't opt into it — registration is per-`Composer`, not automatic just because the package is referenced. | | 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` for the five built-in collection shapes (array, `List`, `IReadOnlyList`, `HashSet`, `Dictionary`), 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` — **not** an `ICompositionProvider` (see Source-Generated Composition Plans, below) | | 9 | Diagnostic failure | Context-owned terminal stage | @@ -201,12 +201,14 @@ 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()` 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()`, 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 +[ADR-0024](adr/0024-public-provider-extensibility-model.md)). Only stage 7 +has anything registered *unconditionally* (`BuiltInProviders.Default`); +every other stage is opt-in, populated only when a consumer actually does +something — calls `.For()` (stage 4), calls `UseNSubstitute()` +(`Compono.NSubstitute`, implemented, stage 6 — [ADR-0025](adr/0025-compono-nsubstitute-package-design.md)), +or calls `AddSemanticProvider`/`AddTestDoubleProvider` directly with a +hand-written provider (either stage). Stage 5 alone has no shipped package +to opt into yet — `Compono.Bogus` (Milestone 6) doesn't exist. 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 @@ -259,6 +261,49 @@ accidentally blocking a later stage (or a generated plan) that could have. This avoids exception-driven provider selection and preserves meaningful failures. +### Public providers (stages 5/6) + +`ICompositionProvider` above is `Compono`-internal — stages 4/7 (registered +type/member rules, built-in providers) are implemented entirely inside the +core package and never exposed for an outside package to author its own. +Stages 5/6 (semantic values, test doubles) are different: they exist +specifically for an integration package to contribute open-ended, +pattern-matching logic ("any interface type"), which means the contract a +provider author implements has to be public, small, and decoupled from +`Compono`'s internal request/pipeline plumbing. Resolved by +[ADR-0024](adr/0024-public-provider-extensibility-model.md), implemented +(PLAN-0005 Phase 0): + +```csharp +public interface ICompositionValueProvider +{ + CompositionProviderResult TryProvide( + in CompositionProviderRequest request, + ICompositionContext context); +} +``` + +`CompositionProviderRequest` (`RequestedType`/`DeclaringType`/`Name`/ +`Nullability`) and `CompositionProviderResult` (`NotHandled`/`Handled(value)`) +are their own public types, decoupled from the internal +`CompositionRequest`/`CompositionResult` pair above — no path, no +shared-scope flag, no pipeline plumbing a provider author has no legitimate +use for. `CompositionBuilder.AddSemanticProvider`/`AddTestDoubleProvider` +register a public provider into stage 5/6 respectively, in registration +order; internally, each is wrapped in a `PublicProviderAdapter : +ICompositionProvider` — an adapter, not a second provider contract — so the +rest of the pipeline (dispatch, tracing, diagnostics identity via +`ICompositionProvider.ProviderType`) treats a public provider exactly like an +internal one, with diagnostics naming the real wrapped provider's type, never +the adapter. A public provider may call `context.Resolve()` +(descriptor-less) to compose part of its value from a nested request, exactly +as an internal provider already may; a thrown exception from `TryProvide` +propagates uncaught, per this ADR's Provider Failure Semantics — same +"exceptions signal a bug" principle as everywhere else in this pipeline, not +a stronger contract than an internal provider gets. +`Compono.NSubstitute`'s `NSubstituteProvider` (registered via +`AddTestDoubleProvider`) is the first real consumer of this contract. + ## Source-Generated Composition Plans Source generation is the preferred construction strategy. @@ -749,14 +794,17 @@ Owns: ### Compono.Generators -Potentially owns: +Resolved by [ADR-0003](adr/0003-generator-package-distribution.md): never +published to NuGet on its own — its compiled output is packed directly into +the `Compono` nupkg as an analyzer dependency, so from a consumer's point of +view it doesn't exist as a separate package at all. + +Owns: - Incremental source generator - Generated plan registration - Compile-time diagnostics -Whether this ships separately or is bundled as an analyzer dependency of `Compono` remains open. - ### Compono.XunitV3 Design: [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md) @@ -765,9 +813,11 @@ Design: [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integ package itself), [ADR-0023](adr/0023-rename-compono-xunit-to-compono-xunitv3.md) (the `Compono.Xunit` → `Compono.XunitV3` rename). Implemented - see [PLAN-0004](plans/0004-milestone-4-xunit-integration.md) for the phase-by-phase -account and its Open Items for one known compile-time gap (an interface/ -abstract/delegate-typed `[Compose]`-attributed parameter reports CMP0003 -unconditionally). +account. The one compile-time gap tracked in that plan's Open Items (an +interface/abstract/delegate-typed `[Compose]`-attributed parameter reported +CMP0003 unconditionally) is resolved by +[PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) Phase 2, see +[ADR-0024's Amendment 2](adr/0024-public-provider-extensibility-model.md). Owns: @@ -780,12 +830,29 @@ Owns: ### Compono.NSubstitute +Design: [ADR-0024](adr/0024-public-provider-extensibility-model.md) (the +public provider extension point this package builds on, owned by core +`Compono`), [ADR-0025](adr/0025-compono-nsubstitute-package-design.md) (this +package itself). Implemented and test-covered/end-to-end verified - see +[PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for the +phase-by-phase account. + Owns: -- NSubstitute-backed test-double provider -- Interface support -- Optional abstract-class support -- NSubstitute-specific diagnostics +- `NSubstituteProvider` — the stage-6 test-double provider (registered via + `AddTestDoubleProvider`), composing an interface, delegate, or (when + configured) unsealed abstract-class request as a real + `Substitute.For(Type[], object[])` value +- `NSubstituteOptions` — `SubstituteAbstractClasses` +- `CompositionBuilderExtensions.UseNSubstitute()`/`UseNSubstitute(Action)` + +Contributes no diagnostics of its own — an unsubstitutable request (a sealed +concrete class) falls through `NotHandled` to later pipeline stages exactly +like any other stage-6 decline, so it still composes normally at stage 8 if a +generated plan exists for it. Only when nothing later in the pipeline can +satisfy the request either does it reach the engine's existing stage-9 +"nothing could satisfy this" diagnostic, naming the type and path — never a +package-specific one (ADR-0025's Diagnostics section). ### Compono.Bogus @@ -882,12 +949,12 @@ Owns: `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. + the first real consumer of that contract — **implemented and test-covered/ + end-to-end verified, PLAN-0005**, all phases done). Both the core extension + point and its first real consumer are shipped: stage 6 in the Resolution + Pipeline table above is populated whenever a consumer calls + `UseNSubstitute()`; stage 5 stays empty in practice only because + `Compono.Bogus` (Milestone 6) doesn't exist yet. - **Richer `Microsoft.Extensions.DependencyInjection` integration** (`IServiceCollection` auto-registration, per-composition scoping, keyed services) — explicitly out of scope for `Compono` core per diff --git a/docs/mvp.md b/docs/mvp.md index 4e68b79..b3048af 100644 --- a/docs/mvp.md +++ b/docs/mvp.md @@ -203,11 +203,13 @@ diagnostics, package dependencies), [ADR-0023](adr/0023-rename-compono-xunit-to- Phases 0-4 (core entry point, attribute skeleton, binding algorithm, test suites/verification, docs/cleanup) — see [PLAN-0004](plans/0004-milestone-4-xunit-integration.md) for the phase-by-phase -account. One known gap remains open past Phase 4: an interface/abstract/ +account. The one gap that remained open past Phase 4 — an interface/abstract/ delegate-typed `[Compose]`-attributed parameter (including the `IRepository` -shape in the Example below) reports CMP0003 and fails to compile even when a -profile registration or an inline value would satisfy it at runtime — see -PLAN-0004's Open Items. +shape in the Example below) reported CMP0003 and failed to compile even when a +profile registration or a runtime provider would satisfy it — is now resolved +by [PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) Phase 2, see +[ADR-0024's Amendment 2](adr/0024-public-provider-extensibility-model.md) and +PLAN-0004's Open Items for the full account. ### Scope @@ -233,18 +235,21 @@ public void Creates_service( } ``` -This is the target shape; `IRepository` being interface-typed currently hits -the CMP0003 gap noted above — `test/Compono.XunitV3.SampleTests` uses a -concrete `Repository` type for its own `[Shared]` theory to route around it -until that gap is resolved. +This shape compiles and runs today — an interface-typed `[Shared]` parameter +like `IRepository` is composed as a provider-resolved leaf +([ADR-0024's Amendment 2](adr/0024-public-provider-extensibility-model.md)), +satisfied at runtime by whatever `TProfile.Configure` registers (a plain +`Register(...)`, or `Compono.NSubstitute`'s `UseNSubstitute()`). +`test/Compono.XunitV3.SampleTests`' original `[Shared]` theory still uses a +concrete `Repository` type (predating this fix); `NSubstituteTests.cs` in the +same project runs this exact interface-typed shape for real. ### Exit Criteria -Met for the parameter shapes `Compono.XunitV3` currently supports, verified -through `test/Compono.XunitV3.Tests` and a real xUnit v3 runner against -`test/Compono.XunitV3.SampleTests` (PLAN-0004 Phase 3); the CMP0003 gap above -means "composed parameters work under xUnit v3" doesn't yet extend to a bare -interface/abstract/delegate-typed `[Compose]` parameter: +Met, verified through `test/Compono.XunitV3.Tests` and a real xUnit v3 runner +against `test/Compono.XunitV3.SampleTests` (PLAN-0004 Phase 3), including a +bare interface/abstract/delegate-typed `[Compose]` parameter as of PLAN-0005 +Phase 2: - Composed parameters work under xUnit v3 - Inline values take precedence @@ -274,9 +279,9 @@ diagnostics). **ADR-0024's core engine extension point is implemented end-to-end verified (PLAN-0005 Phase 2)** — `NSubstituteProvider`/ `NSubstituteOptions`/`UseNSubstitute()` are real, tested code, verified both by `Compono.NSubstitute.Tests` and by a real packaged `Compono.XunitV3.SampleTests` -run of this milestone's own Goal scenario. See -[PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for the -phase-by-phase tracker (Phase 3, docs/cleanup, still open) and +run of this milestone's own Goal scenario. **This milestone is complete** — +see [PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) for the +phase-by-phase tracker (all four phases `Done`) and [ADR-0024's Amendment 2](adr/0024-public-provider-extensibility-model.md) for a `Compono.Generators` compile-time check (`CMP0003`) Phase 2's real verification found and fixed along the way — an interface/abstract-class/ diff --git a/docs/plans/0004-milestone-4-xunit-integration.md b/docs/plans/0004-milestone-4-xunit-integration.md index e87aae1..fb3eb9e 100644 --- a/docs/plans/0004-milestone-4-xunit-integration.md +++ b/docs/plans/0004-milestone-4-xunit-integration.md @@ -1079,39 +1079,32 @@ exercised indirectly through `Compono.XunitV3.Tests`. ## Open Items -- **`ComposeMethodDiscovery` reports CMP0003 for an interface/abstract/ - delegate-typed `[Compose]`-attributed parameter unconditionally, even - when the author intends it to be satisfied entirely by - `TProfile.Configure`'s own `Register(...)` or an always-supplied - inline value (PR #23 review) — genuinely blocking: the project fails - to compile for that otherwise-valid usage.** Deliberately *not* fixed - as a drive-by change here, because it isn't a gap unique to - `Compono.XunitV3` — `ComposeMethodDiscovery.TransformMethod` reaches - every eligible parameter through the exact same - `ComposedTypeAnalyzer.Analyze` root-request path - `Composer.Create()` and `[Composable]` already use, and that - path's "an abstract/delegate root always reaches constructor selection - and gets CMP0003, even when a registration might satisfy it at - runtime" behavior is deliberate, cross-cutting, and specifically - regression-tested (`LeafTypeClassifier.IsRuntimeProviderResolved` is - documented as *narrower than* `IsProviderResolved` for exactly this - reason; `CompositionPlanVerifyTests.AbstractRootType_ - StillReportsDiagnostic_AfterRootProviderCheck` exists specifically to - keep a registered-but-abstract `Create()` root reaching CMP0003 - rather than silently compiling into a call that can only fail at - runtime with a useless message - the PR #11 regression this guards - against). Loosening that check for `[Compose]`-attributed parameters - alone - e.g. reusing `LeafTypeClassifier.IsProviderResolved`'s lenient, - member-style leaf treatment for a parameter root instead of the - stricter `IsRuntimeProviderResolved` - would resolve this specific - complaint, but it's a change to a foundational, deliberately-tested - diagnostic philosophy spanning every discovery path, not a one-line - fix scoped to this package; it needs its own design dive - (`design-decisions.md`) weighing the same false-positive-vs-silent- - failure tradeoff `Create()`'s root already settled, not a decision - made inline while triaging PR feedback. Phase 2's binding algorithm - didn't touch generator-level discovery, so this item is unresolved by - it — still tracked here for a dedicated design dive or follow-up. +- ~~**`ComposeMethodDiscovery` reports CMP0003 for an interface/abstract/ + delegate-typed `[Compose]`-attributed parameter unconditionally...**~~ + **Resolved by [PLAN-0005](0005-milestone-5-nsubstitute-integration.md) + Phase 2 (2026-07-31), see [ADR-0024 Amendment 2](../adr/0024-public-provider-extensibility-model.md).** + This item predicted exactly the right fix (reusing + `LeafTypeClassifier.IsProviderResolved`'s lenient, member-style leaf + treatment for a parameter root instead of the stricter + `IsRuntimeProviderResolved`) and correctly identified it as needing its + own design dive rather than a drive-by fix here — that dive happened as + part of PLAN-0005 Phase 2's real end-to-end verification, when this + plan's own `[Shared] IRepository repository` Goal-section shape (see the + note below) failed to compile against a real packaged consumer, giving + the "a registration/provider might satisfy this at runtime" side of the + false-positive-vs-silent-failure tradeoff real dogfooding evidence for + the first time. `IsRuntimeProviderResolved` now delegates directly to + `IsProviderResolved` — an interface/abstract-class/delegate root + (including a `[Compose]`-attributed parameter, since + `ComposeMethodDiscovery` shares the same root-request path this item + described) is classified identically to a member, at every discovery + path, not just this package's own. The regression test this item named, + `CompositionPlanVerifyTests.AbstractRootType_ + StillReportsDiagnostic_AfterRootProviderCheck`, was rewritten (its + assertion inverted) as part of that fix, alongside a new + `ConcreteRootTypeWithNoAccessibleConstructor_ + StillReportsDiagnostic_AfterRootProviderCheck` proving a genuinely + unconstructible concrete root is unaffected. The one item Phase 0 left open (no generator discovery path for a `[Compose]`-attributed method's own parameter) was closed by Phase 1's diff --git a/docs/plans/0005-milestone-5-nsubstitute-integration.md b/docs/plans/0005-milestone-5-nsubstitute-integration.md index f32339b..a6a6494 100644 --- a/docs/plans/0005-milestone-5-nsubstitute-integration.md +++ b/docs/plans/0005-milestone-5-nsubstitute-integration.md @@ -1,6 +1,6 @@ # [PLAN-0005] Milestone 5: NSubstitute Integration -**Status:** In Progress +**Status:** Done **Implements:** [ADR-0024](../adr/0024-public-provider-extensibility-model.md) (core public provider extensibility: `ICompositionValueProvider`, @@ -199,7 +199,7 @@ Each phase ships as its own PR, per `design-decisions.md`'s phase rule. ### Phase 3: Docs and cleanup -**Status:** Not Started +**Status:** Done - [x] `docs/architecture.md`: Resolution Pipeline stage 5/6 rows, the stages-4/5/6/7 summary paragraph, and the "Public provider @@ -209,12 +209,24 @@ Each phase ships as its own PR, per `design-decisions.md`'s phase rule. review (Codex, P2): the design-phase wording became stale the moment Phase 0 merged. Still accurately says `Compono.NSubstitute` itself doesn't exist yet. -- [ ] `docs/architecture.md`: Providers section still needs the - public/internal provider split write-up (`ICompositionValueProvider` - alongside the existing internal `ICompositionProvider` sketch); a - Package Boundaries entry for `Compono.NSubstitute`'s real `Owns` - list — both deferred to this phase since they're additive - documentation, not corrections of a false claim. +- [x] `docs/architecture.md`: Providers section now has the public/internal + provider split write-up (a new "Public providers (stages 5/6)" + subsection documenting `ICompositionValueProvider`/ + `CompositionProviderRequest`/`CompositionProviderResult`/ + `PublicProviderAdapter` alongside the existing internal + `ICompositionProvider` sketch); the `Compono.NSubstitute` Package + Boundaries entry now has a real `Owns` list (`NSubstituteProvider`/ + `NSubstituteOptions`/`CompositionBuilderExtensions`) plus a Design + line and implementation status, matching the `Compono.XunitV3` entry's + shape. Also caught and fixed while closing this out: the + `Compono.XunitV3` Package Boundaries entry, `docs/mvp.md`'s Milestone + 4 section/Example/Exit Criteria, `docs/public-api.md`'s xUnit v3 + Experience section, and PLAN-0004's own Open Items all still described + the pre-Phase-2 CMP0003 gap (an interface/abstract/delegate-typed + `[Compose]` parameter failing to compile) as open — stale the moment + this plan's own Phase 2 fixed it (ADR-0024 Amendment 2). All five + updated to point at that Amendment instead of leaving Milestone 4's + docs contradicted by Milestone 5's own fix. - [x] `docs/mvp.md` Milestone 5 section: links ADR-0024/ADR-0025/PLAN-0005, states ADR-0024's core mechanism is implemented (PLAN-0005 Phase 0) versus `Compono.NSubstitute` itself not yet — done early, same PR @@ -327,6 +339,34 @@ alpha must: ## Notes +**Phase 3 (Done):** + +- Closed out the one remaining Phase 3 task: `docs/architecture.md`'s + Providers section gained a "Public providers (stages 5/6)" subsection + (`ICompositionValueProvider`/`CompositionProviderRequest`/ + `CompositionProviderResult`/`PublicProviderAdapter`, alongside the + pre-existing internal `ICompositionProvider` sketch), and the + `Compono.NSubstitute` Package Boundaries entry gained a Design line, + implementation-status note, and its real `Owns` list + (`NSubstituteProvider`/`NSubstituteOptions`/`CompositionBuilderExtensions`), + matching `Compono.XunitV3`'s existing entry shape. +- Auditing the rest of the doc set for this closeout surfaced a second, + larger round of staleness: PLAN-0004's Open Items, `docs/architecture.md`'s + `Compono.XunitV3` Package Boundaries entry, `docs/mvp.md`'s Milestone 4 + section/Example/Exit Criteria, and `docs/public-api.md`'s xUnit v3 + Experience section all still described the CMP0003 interface/abstract/ + delegate-root gap as open — every one of them written or last touched + before this plan's own Phase 2 fixed exactly that gap (ADR-0024 Amendment + 2). Same pattern as PR #28/#29's doc-staleness rounds, just spanning a + different plan's docs this time since the fix happened to land here rather + than in PLAN-0004. All five updated to point at the Amendment instead of + contradicting Milestone 5's own shipped fix; PLAN-0004's Open Items entry + itself is marked resolved (struck through, not deleted, so the original + problem statement stays legible) rather than removed. +- No code changes in this phase — docs only, per its own scope. This plan's + overall `Status` moved to `Done`: every phase (0-3) is now checked off, and + this milestone's exit criteria (`docs/mvp.md`) are met. + **Phase 2 (Done):** - `test/Compono.NSubstitute.Tests` created (23 tests × 2 TFMs = 46), covering diff --git a/docs/plans/README.md b/docs/plans/README.md index 8a4137d..f755528 100644 --- a/docs/plans/README.md +++ b/docs/plans/README.md @@ -47,4 +47,4 @@ one. This file is just the mechanics: numbering, status, and the index. | [0002](0002-milestone-2-core-composition-engine.md) | Milestone 2: Core Composition Engine | Done | | [0003](0003-milestone-3-profiles-and-configuration.md) | Milestone 3: Profiles and Configuration | Done | | [0004](0004-milestone-4-xunit-integration.md) | Milestone 4: xUnit v3 Integration | Done | -| [0005](0005-milestone-5-nsubstitute-integration.md) | Milestone 5: NSubstitute Integration | Not Started | +| [0005](0005-milestone-5-nsubstitute-integration.md) | Milestone 5: NSubstitute Integration | Done | diff --git a/docs/public-api.md b/docs/public-api.md index 800bfe4..21fbd64 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -186,10 +186,12 @@ directly. Resolved by [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md)/ [ADR-0022](adr/0022-compono-xunit-package-design.md) (`Accepted`, implemented — -see [PLAN-0004](plans/0004-milestone-4-xunit-integration.md); one gap remains -open past Phase 4, see that plan's Open Items — an interface/abstract/ -delegate-typed `[Compose]`-attributed parameter reports CMP0003 unconditionally, -even when a profile registration or inline value would satisfy it). +see [PLAN-0004](plans/0004-milestone-4-xunit-integration.md)). The one gap that +remained open past Phase 4 — an interface/abstract/delegate-typed +`[Compose]`-attributed parameter reported CMP0003 unconditionally, even when a +profile registration or runtime provider would satisfy it — is resolved by +[PLAN-0005](plans/0005-milestone-5-nsubstitute-integration.md) Phase 2, see +[ADR-0024's Amendment 2](adr/0024-public-provider-extensibility-model.md). `[Compose]`/`[Compose]` implement `Xunit.v3.DataAttribute` directly; composition happens once per theory row, at execution time (not discovery time — composed values, especially a future substitute or any