diff --git a/benchmarks/Compono.Benchmarks/ArchitectureBenchmarks.cs b/benchmarks/Compono.Benchmarks/ArchitectureBenchmarks.cs
deleted file mode 100644
index 75428cc..0000000
--- a/benchmarks/Compono.Benchmarks/ArchitectureBenchmarks.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-///
-/// Does generated construction () outperform a comparable
-/// reflection-based implementation - the architectural question behind Milestone 1
-/// (docs/mvp.md's explicit benchmark ask). is the theoretical floor
-/// bare new Leaf() gives, so 's overhead against it is visible on
-/// its own terms, not just relative to reflection.
-///
-[MemoryDiagnoser]
-public class ArchitectureBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
-
- /// Constructs directly - the theoretical floor.
- [Benchmark(Baseline = true)]
- public Leaf Direct() => new();
-
- /// Constructs via its generated .
- [Benchmark]
- public Leaf Generated() => _composer.Create();
-
- /// Constructs via reflection.
- [Benchmark]
- public Leaf Reflection() => ReflectionComposer.Compose();
-}
diff --git a/benchmarks/Compono.Benchmarks/AutoFixtureComposer.cs b/benchmarks/Compono.Benchmarks/AutoFixtureComposer.cs
deleted file mode 100644
index 4acfd57..0000000
--- a/benchmarks/Compono.Benchmarks/AutoFixtureComposer.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using AutoFixture;
-
-namespace Compono.Benchmarks;
-
-///
-/// Ecosystem-comparison reference point - the established framework developers reach for today
-/// to construct test data, benchmarked as a recognizable baseline rather than a target Compono
-/// is trying to "beat" (docs/performance.md).
-///
-public sealed class AutoFixtureComposer
-{
- private readonly Fixture _fixture = new();
-
- /// Constructs an instance of via AutoFixture.
- /// The type to construct.
- public T Compose() => _fixture.Create();
-}
diff --git a/benchmarks/Compono.Benchmarks/Baselines/AutoFixtureComposer.cs b/benchmarks/Compono.Benchmarks/Baselines/AutoFixtureComposer.cs
new file mode 100644
index 0000000..4bd8c15
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Baselines/AutoFixtureComposer.cs
@@ -0,0 +1,18 @@
+using AutoFixture;
+
+namespace Compono.Benchmarks.Baselines;
+
+///
+/// External-comparison reference point, per ADR-0034: AutoFixture is one comparison point
+/// answering "what should a developer expect when migrating," not the suite's center. Stock
+/// Fixture, no customization - an honest out-of-the-box comparison, not tuned to
+/// artificially favor either library.
+///
+public sealed class AutoFixtureComposer
+{
+ private readonly Fixture _fixture = new();
+
+ /// Constructs an instance of via AutoFixture.
+ /// The type to construct.
+ public T Compose() => _fixture.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/BenchmarkTypes.cs b/benchmarks/Compono.Benchmarks/BenchmarkTypes.cs
deleted file mode 100644
index 0b66917..0000000
--- a/benchmarks/Compono.Benchmarks/BenchmarkTypes.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Compono.Benchmarks;
-
-///
-/// A flat, parameterless representative type - the only shape
-/// can fully execute in Milestone 1 today.
-///
-///
-/// A type with a constructor parameter (even one that's itself composable, like
-/// docs/mvp.md's Customer(Address HomeAddress) exit-criteria example) still throws
-/// at Compose time: every constructor argument in
-/// generated code is resolved via context.Resolve<TParam>()
-/// (src/Compono.Generators/Templates/CompositionPlan.scriban), and Milestone 1's
-/// placeholder (src/Compono/Composer.cs) always throws
-/// there regardless of whether TParam has its own generated plan - dispatching a
-/// Resolve<TParam>() call to the matching PlanCache<TParam> is the real
-/// provider-resolution pipeline, which is Milestone 2 scope. So a parameterless type is the only
-/// shape this benchmark can honestly measure end-to-end until then.
-///
-public sealed record Leaf;
diff --git a/benchmarks/Compono.Benchmarks/Compono.Benchmarks.csproj b/benchmarks/Compono.Benchmarks/Compono.Benchmarks.csproj
index 2b530f4..e5de239 100644
--- a/benchmarks/Compono.Benchmarks/Compono.Benchmarks.csproj
+++ b/benchmarks/Compono.Benchmarks/Compono.Benchmarks.csproj
@@ -28,14 +28,32 @@
+
+
+
+
+
+
+
+
-
+
+ OutputItemType="Analyzer" />
+
+
+
diff --git a/benchmarks/Compono.Benchmarks/ConsumerScenarios/ProviderEnabledBenchmarks.cs b/benchmarks/Compono.Benchmarks/ConsumerScenarios/ProviderEnabledBenchmarks.cs
new file mode 100644
index 0000000..6fdb3a6
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/ConsumerScenarios/ProviderEnabledBenchmarks.cs
@@ -0,0 +1,27 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.ConsumerScenarios;
+
+///
+/// What performance should a user expect from a profile with a package provider active - a
+/// Compono.Bogus-enabled profile composing (whose
+/// FirstName/LastName members match Bogus's built-in convention allowlist), and a
+/// Compono.NSubstitute-enabled profile composing (whose
+/// member NSubstitute's stage-6 provider satisfies) - realistic usage, not
+/// the isolated marginal cost FeatureOverhead/ProviderOverheadBenchmarks measures.
+///
+[MemoryDiagnoser]
+public class ProviderEnabledBenchmarks
+{
+ private readonly Composer _bogusComposer = Composer.Create(builder => builder.UseBogus());
+ private readonly Composer _nsubstituteComposer = Composer.Create(builder => builder.UseNSubstitute());
+
+ /// Composes with UseBogus() active.
+ [Benchmark]
+ public MediumAggregate BogusEnabledScenario() => _bogusComposer.Create();
+
+ /// Composes with UseNSubstitute() active.
+ [Benchmark]
+ public ProviderBackedModel NSubstituteEnabledScenario() => _nsubstituteComposer.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/ConsumerScenarios/RepresentativeModelBenchmarks.cs b/benchmarks/Compono.Benchmarks/ConsumerScenarios/RepresentativeModelBenchmarks.cs
new file mode 100644
index 0000000..4c0ae54
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/ConsumerScenarios/RepresentativeModelBenchmarks.cs
@@ -0,0 +1,33 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.ConsumerScenarios;
+
+///
+/// What performance should a user expect composing each of ADR-0034's representative models in a
+/// realistic application - no comparison baseline, just the absolute cost a consumer actually
+/// pays for Create<T>() against each shape. This is the category most likely to
+/// surface in public documentation, per ADR-0034.
+///
+[MemoryDiagnoser]
+public class RepresentativeModelBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+ private readonly Composer _largeCollectionComposer = Composer.Create(builder => builder.WithCollectionSize(100));
+
+ /// Composes the flat model.
+ [Benchmark]
+ public SimplePoco SimplePocoScenario() => _composer.Create();
+
+ /// Composes the moderately-nested model.
+ [Benchmark]
+ public MediumAggregate MediumAggregateScenario() => _composer.Create();
+
+ /// Composes the 8-level-deep model.
+ [Benchmark]
+ public DeepGraph DeepGraphScenario() => _composer.Create();
+
+ /// Composes with a 100-element collection size.
+ [Benchmark]
+ public LargeCollection LargeCollectionScenario() => _largeCollectionComposer.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/ConsumerScenarios/SharedValueBenchmarks.cs b/benchmarks/Compono.Benchmarks/ConsumerScenarios/SharedValueBenchmarks.cs
new file mode 100644
index 0000000..34cc11d
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/ConsumerScenarios/SharedValueBenchmarks.cs
@@ -0,0 +1,38 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.ConsumerScenarios;
+
+///
+/// What performance should a user expect from a shared value reused across sibling row
+/// parameters - the mechanism Compono.XunitV3's [Shared] attribute builds on, per
+/// ADR-0021. Composed via Composer.CreateRow/ResolveShared directly (the same public
+/// core API a test-framework integration uses) rather than depending on
+/// Compono.XunitV3 itself.
+///
+[MemoryDiagnoser]
+public class SharedValueBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+
+ ///
+ /// Composes one row value plus two sibling consumers, both of
+ /// which nest it as an ordinary constructor parameter - the row's shared instance is
+ /// transparently reused by both, never composed independently.
+ ///
+ [Benchmark]
+ public (ConsumerOne One, ConsumerTwo Two) SharedContextAcrossRow()
+ {
+ var row = _composer.CreateRow(typeof(SharedValueBenchmarks));
+
+ row.ResolveShared(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 0, "context", typeof(SharedValueBenchmarks), Nullability.NotNullable));
+
+ var one = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 1, "one", typeof(SharedValueBenchmarks), Nullability.NotNullable));
+ var two = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 2, "two", typeof(SharedValueBenchmarks), Nullability.NotNullable));
+
+ return (one, two);
+ }
+}
diff --git a/benchmarks/Compono.Benchmarks/DeepGraphBenchmarks.cs b/benchmarks/Compono.Benchmarks/DeepGraphBenchmarks.cs
deleted file mode 100644
index ca48096..0000000
--- a/benchmarks/Compono.Benchmarks/DeepGraphBenchmarks.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-/// An 8-level-deep chain of composable types - purely to exercise 's growth path.
-public sealed record DeepLevel8(string Value);
-
-/// See 's remarks.
-public sealed record DeepLevel7(DeepLevel8 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel6(DeepLevel7 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel5(DeepLevel6 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel4(DeepLevel5 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel3(DeepLevel4 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel2(DeepLevel3 Child);
-
-/// See 's remarks.
-public sealed record DeepLevel1(DeepLevel2 Child);
-
-///
-/// Measures a genuinely deep composable-type chain (8 nested generated-plan dispatches), not just
-/// ' shallow / graph -
-/// a PR #13 review point: each active ancestor frame dispatching through stage 8 retains ~6 trace
-/// entries until its own child returns (5 declined stages + a Pending marker), so this
-/// 8-level chain (~48 entries at its deepest point) exceeds 's
-/// 32-entry initial capacity and triggers a real Array.Resize - unlike the shallow
-/// Customer graph, which never gets deep enough to.
-///
-[MemoryDiagnoser]
-public class DeepGraphBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
-
- /// Composes the 8-level-deep chain.
- [Benchmark]
- public DeepLevel1 Create() => _composer.Create();
-}
diff --git a/benchmarks/Compono.Benchmarks/EcosystemBenchmarks.cs b/benchmarks/Compono.Benchmarks/EcosystemBenchmarks.cs
deleted file mode 100644
index f8a2de0..0000000
--- a/benchmarks/Compono.Benchmarks/EcosystemBenchmarks.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-///
-/// How does Compono compare with AutoFixture, the established framework developers reach for
-/// today to construct test data - a separate question from ,
-/// which validates the architecture on its own terms. AutoFixture does substantially more
-/// runtime work and has different goals (randomized value generation, kept here unexercised
-/// since has no properties to fill), so this is a recognizable reference
-/// point, not the success criterion for Milestone 1 (docs/performance.md).
-///
-[MemoryDiagnoser]
-public class EcosystemBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
- private readonly AutoFixtureComposer _autoFixture = new();
-
- /// Constructs via its generated .
- [Benchmark(Baseline = true)]
- public Leaf Generated() => _composer.Create();
-
- /// Constructs via AutoFixture.
- [Benchmark]
- public Leaf AutoFixture() => _autoFixture.Compose();
-}
diff --git a/benchmarks/Compono.Benchmarks/ExternalComparison/MediumAggregateComparisonBenchmarks.cs b/benchmarks/Compono.Benchmarks/ExternalComparison/MediumAggregateComparisonBenchmarks.cs
new file mode 100644
index 0000000..72d6641
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/ExternalComparison/MediumAggregateComparisonBenchmarks.cs
@@ -0,0 +1,26 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Baselines;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.ExternalComparison;
+
+///
+/// What should a developer expect when migrating from AutoFixture, for the moderately-nested
+/// model - the nested-graph counterpart to
+/// , giving AutoFixture real randomized-value-
+/// generation work to do (unlike the flat model).
+///
+[MemoryDiagnoser]
+public class MediumAggregateComparisonBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+ private readonly AutoFixtureComposer _autoFixture = new();
+
+ /// Composes through the real resolution pipeline.
+ [Benchmark(Baseline = true)]
+ public MediumAggregate Generated() => _composer.Create();
+
+ /// Constructs via AutoFixture.
+ [Benchmark]
+ public MediumAggregate AutoFixture() => _autoFixture.Compose();
+}
diff --git a/benchmarks/Compono.Benchmarks/ExternalComparison/SimplePocoComparisonBenchmarks.cs b/benchmarks/Compono.Benchmarks/ExternalComparison/SimplePocoComparisonBenchmarks.cs
new file mode 100644
index 0000000..731fc80
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/ExternalComparison/SimplePocoComparisonBenchmarks.cs
@@ -0,0 +1,25 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Baselines;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.ExternalComparison;
+
+///
+/// What should a developer expect when migrating from AutoFixture, for the flat
+/// model - per ADR-0034, AutoFixture is one comparison point, not the
+/// suite's center. Equivalent object graph, equivalent work - published honestly either way.
+///
+[MemoryDiagnoser]
+public class SimplePocoComparisonBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+ private readonly AutoFixtureComposer _autoFixture = new();
+
+ /// Composes through the real resolution pipeline.
+ [Benchmark(Baseline = true)]
+ public SimplePoco Generated() => _composer.Create();
+
+ /// Constructs via AutoFixture.
+ [Benchmark]
+ public SimplePoco AutoFixture() => _autoFixture.Compose();
+}
diff --git a/benchmarks/Compono.Benchmarks/FeatureOverhead/BogusOverheadBenchmarks.cs b/benchmarks/Compono.Benchmarks/FeatureOverhead/BogusOverheadBenchmarks.cs
new file mode 100644
index 0000000..7b8c9ca
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/FeatureOverhead/BogusOverheadBenchmarks.cs
@@ -0,0 +1,30 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.FeatureOverhead;
+
+///
+/// How expensive is UseBogus(), on its own? Both arms pin
+/// identically (a plain registration - unrelated to what's being measured) and vary only how
+/// is resolved: a stage-4 member rule (the baseline) vs.
+/// Compono.Bogus's stage-5 convention provider.
+///
+[MemoryDiagnoser]
+public class BogusOverheadBenchmarks
+{
+ private readonly Composer _memberRule = Composer.Create(builder => builder
+ .Register(_ => new FixedClock())
+ .For().Member(x => x.Email).Use("fixed@example.com"));
+
+ private readonly Composer _bogus = Composer.Create(builder => builder
+ .Register(_ => new FixedClock())
+ .UseBogus());
+
+ /// Resolves Email via a stage-4 member rule - the baseline is measured against.
+ [Benchmark(Baseline = true)]
+ public ProviderBackedModel EmailViaMemberRule() => _memberRule.Create();
+
+ /// Resolves Email via Compono.Bogus's stage-5 convention provider.
+ [Benchmark]
+ public ProviderBackedModel EmailViaBogus() => _bogus.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/FeatureOverhead/ConfigurationOverheadBenchmarks.cs b/benchmarks/Compono.Benchmarks/FeatureOverhead/ConfigurationOverheadBenchmarks.cs
new file mode 100644
index 0000000..c3ad61e
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/FeatureOverhead/ConfigurationOverheadBenchmarks.cs
@@ -0,0 +1,51 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.FeatureOverhead;
+
+///
+/// Isolates the incremental cost of one Compono configuration mechanism at a time, per ADR-0034 -
+/// each step composes the same model, adding exactly one mechanism
+/// on top of 's plain composer. Every mechanism here can decline
+/// without breaking composition (a member/type rule that doesn't match, a semantic provider that
+/// declines) - each step's marginal cost is real, not required for the graph to compose at all.
+///
+[MemoryDiagnoser]
+public class ConfigurationOverheadBenchmarks
+{
+ private readonly Composer _generatedOnly = Composer.Create();
+
+ private readonly Composer _plusMemberRule = Composer.Create(builder => builder
+ .For().Member(x => x.FirstName).Use("Fixed"));
+
+ private readonly Composer _plusTypeRule = Composer.Create(builder => builder
+ .For().Use(_ => new Address("Fixed St", "Fixed City")));
+
+ private readonly Composer _plusCustomProvider = Composer.Create(builder => builder
+ .AddSemanticProvider(new FirstNameProvider()));
+
+ /// Composes with no configuration - the floor this class' other benchmarks are compared against.
+ [Benchmark(Baseline = true)]
+ public MediumAggregate GeneratedOnly() => _generatedOnly.Create();
+
+ /// Composes with one stage-4 member rule active.
+ [Benchmark]
+ public MediumAggregate PlusMemberRule() => _plusMemberRule.Create();
+
+ /// Composes with one stage-4 type rule (on the nested ) active.
+ [Benchmark]
+ public MediumAggregate PlusTypeRule() => _plusTypeRule.Create();
+
+ /// Composes with one stage-5 semantic provider active.
+ [Benchmark]
+ public MediumAggregate PlusCustomProvider() => _plusCustomProvider.Create();
+
+ /// A minimal stage-5 provider claiming only members literally named FirstName - exercises the public provider extension point's own dispatch cost.
+ private sealed class FirstNameProvider : ICompositionValueProvider
+ {
+ public CompositionProviderResult TryProvide(in CompositionProviderRequest request, ICompositionContext context) =>
+ request.Name == "FirstName"
+ ? CompositionProviderResult.Handled("Provided")
+ : CompositionProviderResult.NotHandled;
+ }
+}
diff --git a/benchmarks/Compono.Benchmarks/FeatureOverhead/NSubstituteOverheadBenchmarks.cs b/benchmarks/Compono.Benchmarks/FeatureOverhead/NSubstituteOverheadBenchmarks.cs
new file mode 100644
index 0000000..dfe16fe
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/FeatureOverhead/NSubstituteOverheadBenchmarks.cs
@@ -0,0 +1,31 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.FeatureOverhead;
+
+///
+/// How expensive is UseNSubstitute(), on its own? Both arms pin
+/// identically (a plain member rule - unrelated to what's
+/// being measured) and vary only how is resolved: a
+/// stage-3 exact registration (the cheapest possible alternative, and the baseline) vs.
+/// Compono.NSubstitute's stage-6 test-double provider.
+///
+[MemoryDiagnoser]
+public class NSubstituteOverheadBenchmarks
+{
+ private readonly Composer _registration = Composer.Create(builder => builder
+ .Register(_ => new FixedClock())
+ .For().Member(x => x.Email).Use("fixed@example.com"));
+
+ private readonly Composer _nsubstitute = Composer.Create(builder => builder
+ .UseNSubstitute()
+ .For().Member(x => x.Email).Use("fixed@example.com"));
+
+ /// Resolves Clock via a stage-3 exact registration - the baseline is measured against.
+ [Benchmark(Baseline = true)]
+ public ProviderBackedModel ClockViaRegistration() => _registration.Create();
+
+ /// Resolves Clock via Compono.NSubstitute's stage-6 test-double provider.
+ [Benchmark]
+ public ProviderBackedModel ClockViaNSubstitute() => _nsubstitute.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/FeatureOverhead/SharingOverheadBenchmarks.cs b/benchmarks/Compono.Benchmarks/FeatureOverhead/SharingOverheadBenchmarks.cs
new file mode 100644
index 0000000..23e6fca
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/FeatureOverhead/SharingOverheadBenchmarks.cs
@@ -0,0 +1,53 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.FeatureOverhead;
+
+///
+/// Isolates the marginal cost of [Shared]'s underlying mechanism
+/// (CreateRow/ResolveShared) against composing the same two sibling values with no
+/// sharing at all - both arms go through CreateRow, so the only difference is whether
+/// sharing is actually invoked, per ADR-0034's "isolate one feature at a time" principle.
+///
+[MemoryDiagnoser]
+public class SharingOverheadBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+
+ ///
+ /// Composes two sibling row values, each independently composing its own
+ /// - no sharing established.
+ ///
+ [Benchmark(Baseline = true)]
+ public (ConsumerOne One, ConsumerTwo Two) WithoutSharing()
+ {
+ var row = _composer.CreateRow(typeof(SharingOverheadBenchmarks));
+
+ var one = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 0, "one", typeof(SharingOverheadBenchmarks), Nullability.NotNullable));
+ var two = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 1, "two", typeof(SharingOverheadBenchmarks), Nullability.NotNullable));
+
+ return (one, two);
+ }
+
+ ///
+ /// Composes one shared row value, then two sibling consumers that
+ /// both reuse it - the same shape as , with sharing added.
+ ///
+ [Benchmark]
+ public (ConsumerOne One, ConsumerTwo Two) WithSharing()
+ {
+ var row = _composer.CreateRow(typeof(SharingOverheadBenchmarks));
+
+ row.ResolveShared(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 0, "context", typeof(SharingOverheadBenchmarks), Nullability.NotNullable));
+
+ var one = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 1, "one", typeof(SharingOverheadBenchmarks), Nullability.NotNullable));
+ var two = row.Resolve(new CompositionRequestDescriptor(
+ CompositionRequestKind.TestParameter, 2, "two", typeof(SharingOverheadBenchmarks), Nullability.NotNullable));
+
+ return (one, two);
+ }
+}
diff --git a/benchmarks/Compono.Benchmarks/Models/DeepGraph.cs b/benchmarks/Compono.Benchmarks/Models/DeepGraph.cs
new file mode 100644
index 0000000..0a88c90
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/DeepGraph.cs
@@ -0,0 +1,31 @@
+namespace Compono.Benchmarks.Models;
+
+/// An 8-level-deep chain of composable types, per ADR-0034's Scalability category (shallow vs. deep graphs). See 's remarks.
+public sealed record DeepLevel8(string Value);
+
+/// See 's remarks.
+public sealed record DeepLevel7(DeepLevel8 Child);
+
+/// See 's remarks.
+public sealed record DeepLevel6(DeepLevel7 Child);
+
+/// See 's remarks.
+public sealed record DeepLevel5(DeepLevel6 Child);
+
+/// See 's remarks.
+public sealed record DeepLevel4(DeepLevel5 Child);
+
+/// See 's remarks.
+public sealed record DeepLevel3(DeepLevel4 Child);
+
+/// See 's remarks.
+public sealed record DeepLevel2(DeepLevel3 Child);
+
+///
+/// The deep-graph representative model's root, per ADR-0034: an 8-level chain of single-field
+/// composable types, deep enough (~48 trace entries at its deepest point) to exceed
+/// CompositionTraceBuffer's 32-entry initial capacity and trigger a real
+/// Array.Resize - unlike 's shallow, 2-level graph. Replaces
+/// the old suite's DeepLevel1-DeepLevel8 one-off benchmark types.
+///
+public sealed record DeepGraph(DeepLevel2 Child);
diff --git a/benchmarks/Compono.Benchmarks/Models/LargeCollection.cs b/benchmarks/Compono.Benchmarks/Models/LargeCollection.cs
new file mode 100644
index 0000000..a2d8a29
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/LargeCollection.cs
@@ -0,0 +1,8 @@
+namespace Compono.Benchmarks.Models;
+
+///
+/// A model whose only member is a collection - per ADR-0034's Scalability category, its actual
+/// element count is controlled at composition time via WithCollectionSize(...), not fixed
+/// on the type itself, so one model serves every collection-size data point in the matrix.
+///
+public sealed record LargeCollection(List Items);
diff --git a/benchmarks/Compono.Benchmarks/Models/MediumAggregate.cs b/benchmarks/Compono.Benchmarks/Models/MediumAggregate.cs
new file mode 100644
index 0000000..f3026b6
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/MediumAggregate.cs
@@ -0,0 +1,14 @@
+namespace Compono.Benchmarks.Models;
+
+///
+/// A nested composable dependency of - see that type's remarks.
+///
+public sealed record Address(string Street, string City);
+
+///
+/// The canonical "representative graph" model, per ADR-0034: one nested composable dependency
+/// (), every built-in kind via string, and a collection member -
+/// reused across every ADR-0034 category that needs a realistic, moderately-nested type, instead
+/// of each category inventing its own. Replaces the old suite's Customer/Address.
+///
+public sealed record MediumAggregate(string FirstName, string LastName, Address HomeAddress, List Tags);
diff --git a/benchmarks/Compono.Benchmarks/Models/ProviderBackedModel.cs b/benchmarks/Compono.Benchmarks/Models/ProviderBackedModel.cs
new file mode 100644
index 0000000..35173a5
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/ProviderBackedModel.cs
@@ -0,0 +1,27 @@
+namespace Compono.Benchmarks.Models;
+
+/// An interface-typed dependency Compono.NSubstitute can satisfy - see 's remarks.
+public interface IClock
+{
+ /// The current instant, per this clock.
+ DateTimeOffset Now { get; }
+}
+
+/// A fixed, non-substitute - the baseline registration ADR-0034's provider-overhead comparisons measure against, not a real system clock.
+public sealed class FixedClock : IClock
+{
+ private static readonly DateTimeOffset FixedInstant = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
+
+ ///
+ public DateTimeOffset Now => FixedInstant;
+}
+
+///
+/// The provider-eligible representative model, per ADR-0034: an interface-typed member
+/// () Compono.NSubstitute's stage-6 provider can satisfy, and a
+/// string member named Email - matching Compono.Bogus's built-in
+/// member-name-convention allowlist - its stage-5 provider can satisfy. Reused by
+/// ConsumerScenarios' Bogus/NSubstitute-enabled cases and FeatureOverhead's provider-overhead
+/// comparisons, so both categories draw on the same model rather than inventing their own.
+///
+public sealed record ProviderBackedModel(IClock Clock, string Email);
diff --git a/benchmarks/Compono.Benchmarks/Models/SharedValueGraph.cs b/benchmarks/Compono.Benchmarks/Models/SharedValueGraph.cs
new file mode 100644
index 0000000..064d03d
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/SharedValueGraph.cs
@@ -0,0 +1,18 @@
+namespace Compono.Benchmarks.Models;
+
+/// A value shared across / in one composition row - see 's remarks.
+public sealed record SharedContext(string CorrelationId);
+
+///
+/// One of two sibling row values in ADR-0034's shared-value representative shape - each nests
+/// as an ordinary, unmarked constructor parameter. Composed via
+/// Composer.CreateRow/ResolveShared (mirroring Compono.XunitV3's
+/// [Shared] mechanism at the core API level, without depending on that package): the row's
+/// shared instance is transparently reused by both consumers' nested
+/// constructor parameter, per ADR-0021's unconditional-read-side scope check. Replaces the old
+/// suite's complete lack of shared-value coverage.
+///
+public sealed record ConsumerOne(SharedContext Context, string Label);
+
+/// See 's remarks.
+public sealed record ConsumerTwo(SharedContext Context, int Sequence);
diff --git a/benchmarks/Compono.Benchmarks/Models/SimplePoco.cs b/benchmarks/Compono.Benchmarks/Models/SimplePoco.cs
new file mode 100644
index 0000000..9154948
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Models/SimplePoco.cs
@@ -0,0 +1,8 @@
+namespace Compono.Benchmarks.Models;
+
+///
+/// A flat, parameterless-dependency type - the simplest possible composition target, reused
+/// across every ADR-0034 category that needs a "floor" model (no nested composable dependency,
+/// no collection). Replaces the old suite's Leaf.
+///
+public sealed record SimplePoco(string Name, int Count, bool IsActive);
diff --git a/benchmarks/Compono.Benchmarks/ReflectionComposer.cs b/benchmarks/Compono.Benchmarks/ReflectionComposer.cs
deleted file mode 100644
index c444741..0000000
--- a/benchmarks/Compono.Benchmarks/ReflectionComposer.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System.Collections;
-
-namespace Compono.Benchmarks;
-
-///
-/// A minimal reflection-based construction baseline - what
-/// replaces with source generation.
-///
-public static class ReflectionComposer
-{
- // Matches Compono's own defaults exactly, so this baseline does comparable real work rather
- // than a cheaper strawman: PrimitiveValueProvider.StringLength (src/Compono/Providers) and
- // ADR-0013's default collection size.
- private const string Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- private const int StringLength = 8;
- private const int CollectionSize = 3;
-
- ///
- /// Constructs an instance of via its parameterless constructor,
- /// found and invoked through reflection rather than generated code.
- ///
- /// The type to construct.
- public static T Compose()
- {
- var constructor = typeof(T).GetConstructors().Single();
-
- return (T)constructor.Invoke([]);
- }
-
- ///
- /// Constructs an instance of by walking its constructor's parameters
- /// recursively through reflection, filling every leaf field with a genuinely random value
- /// (an 8-character alphanumeric string, a 3-element collection - Compono's own defaults) rather
- /// than a fixed placeholder. This is the actual reflection-based alternative someone would write
- /// by hand for ' representative graph, not a dispatch-cost-only
- /// strawman: an earlier version of this method used fixed placeholder values, which made it
- /// faster than for doing categorically less work, not because
- /// reflective dispatch beats source-generated dispatch (PR #13 review). Deliberately narrow:
- /// only the shapes / actually use (string,
- /// List<T>, and a type with a single public constructor) - this is a benchmark
- /// baseline, not a general reflection-based composer, and its randomness is ordinary
- /// , not Compono's deterministic, seed-forked
- /// - reproducibility isn't a property this baseline needs.
- ///
- /// The type to construct.
- public static T ComposeRecursive() => (T)ComposeValue(typeof(T))!;
-
- private static object? ComposeValue(Type type)
- {
- if (type == typeof(string))
- return NextString();
-
- if (type.IsEnum)
- {
- var values = Enum.GetValues(type);
- return values.GetValue(Random.Shared.Next(values.Length));
- }
-
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
- {
- var elementType = type.GetGenericArguments()[0];
- var list = (IList)Activator.CreateInstance(type)!;
- for (var i = 0; i < CollectionSize; i++)
- list.Add(ComposeValue(elementType));
-
- return list;
- }
-
- var constructor = type.GetConstructors().Single();
- var arguments = constructor.GetParameters()
- .Select(parameter => ComposeValue(parameter.ParameterType))
- .ToArray();
-
- return constructor.Invoke(arguments);
- }
-
- private static string NextString()
- {
- Span chars = stackalloc char[StringLength];
- for (var i = 0; i < StringLength; i++)
- chars[i] = Alphabet[Random.Shared.Next(Alphabet.Length)];
-
- return new string(chars);
- }
-}
diff --git a/benchmarks/Compono.Benchmarks/ResolutionArchitectureBenchmarks.cs b/benchmarks/Compono.Benchmarks/ResolutionArchitectureBenchmarks.cs
deleted file mode 100644
index b72ea33..0000000
--- a/benchmarks/Compono.Benchmarks/ResolutionArchitectureBenchmarks.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-///
-/// The nested-graph counterpart to : what does the real
-/// resolution pipeline (provider dispatch, deterministic random forking, collection generation,
-/// the diagnostics trace buffer) cost against 's theoretical floor and
-/// 's hand-rolled alternative, for a representative graph rather than a
-/// flat, property-less type.
-///
-///
-/// stays the theoretical floor (no fields to fill, same as
-/// ' Leaf), but here does
-/// comparable real work to :
-/// fills every field with a genuinely random value (an 8-character alphanumeric string, a
-/// 3-element collection - Compono's own defaults), not a fixed placeholder. An earlier version of
-/// this baseline used fixed placeholders, which made faster than
-/// for doing categorically less work rather than because reflective
-/// dispatch actually beats source-generated dispatch - a misleading comparison caught in PR #13
-/// review, fixed by rewriting to do real
-/// value generation. The one remaining, deliberate asymmetry: 's randomness
-/// is ordinary , not Compono's deterministic, seed-forked
-/// - reproducibility is a Compono product feature
-/// (README.md's "Deterministic by design"), not a cost every random-value generator has to
-/// pay, so 's cost here includes work 's doesn't.
-///
-[MemoryDiagnoser]
-public class ResolutionArchitectureBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
-
- /// Constructs directly - the theoretical floor.
- [Benchmark(Baseline = true)]
- public Customer Direct() => new("first", "last", new Address("street", "city"), ["tag1", "tag2", "tag3"]);
-
- /// Composes through the real resolution pipeline.
- [Benchmark]
- public Customer Generated() => _composer.Create();
-
- ///
- /// Constructs via a recursive reflection-based composer that fills every
- /// field with a genuinely random value - see this class' remarks for the one remaining,
- /// deliberate asymmetry (ordinary randomness, not Compono's deterministic seed-forking).
- ///
- [Benchmark]
- public Customer Reflection() => ReflectionComposer.ComposeRecursive();
-}
diff --git a/benchmarks/Compono.Benchmarks/ResolutionBenchmarkTypes.cs b/benchmarks/Compono.Benchmarks/ResolutionBenchmarkTypes.cs
deleted file mode 100644
index 9a4cff0..0000000
--- a/benchmarks/Compono.Benchmarks/ResolutionBenchmarkTypes.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Compono.Benchmarks;
-
-///
-/// The nested-composable-type + built-in + collection shape
-/// docs/plans/0002-milestone-2-core-composition-engine.md's Execution Flow section and
-/// Phase 4 benchmark task use as "a representative graph" - the first point in that plan a type
-/// exists that's worth benchmarking resolution against, rather than just construction dispatch
-/// ('s ).
-///
-public sealed record Address(string Street, string City);
-
-/// See 's remarks.
-public sealed record Customer(string FirstName, string LastName, Address HomeAddress, List Tags);
diff --git a/benchmarks/Compono.Benchmarks/ResolutionBenchmarks.cs b/benchmarks/Compono.Benchmarks/ResolutionBenchmarks.cs
deleted file mode 100644
index 49e52ed..0000000
--- a/benchmarks/Compono.Benchmarks/ResolutionBenchmarks.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-///
-/// CreateMany<T>(count)'s scaling behavior across a few batch sizes, against its own
-/// Create<T>() baseline () - no external comparison here, since the
-/// question this benchmark answers ("does allocation grow linearly or super-linearly with the
-/// batch size?", per docs/plans/0002-milestone-2-core-composition-engine.md's Phase 4
-/// benchmark task) has no equivalent in `new()`/reflection/AutoFixture. For the representative-graph
-/// comparison against those baselines, see and
-/// - both use the same /
-/// graph (nested composable type, every Phase 2 built-in kind via
-/// string, a List<string> collection member) this class' own
-/// composes.
-///
-[MemoryDiagnoser]
-public class ResolutionBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
-
- /// The batch size is benchmarked at.
- [Params(1, 10, 100)]
- public int Count { get; set; }
-
- /// Composes one through the real resolution pipeline.
- [Benchmark(Baseline = true)]
- public Customer Create() => _composer.Create();
-
- /// Composes independent instances.
- [Benchmark]
- public IReadOnlyList CreateMany() => _composer.CreateMany(Count);
-}
diff --git a/benchmarks/Compono.Benchmarks/ResolutionEcosystemBenchmarks.cs b/benchmarks/Compono.Benchmarks/ResolutionEcosystemBenchmarks.cs
deleted file mode 100644
index 3e0a926..0000000
--- a/benchmarks/Compono.Benchmarks/ResolutionEcosystemBenchmarks.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using BenchmarkDotNet.Attributes;
-
-namespace Compono.Benchmarks;
-
-///
-/// The nested-graph counterpart to : how does Compono compare
-/// with AutoFixture once there's an actual representative graph to fill (nested composable type,
-/// a collection member), rather than ' flat, property-less
-/// , which never exercised AutoFixture's real value-generation work.
-///
-[MemoryDiagnoser]
-public class ResolutionEcosystemBenchmarks
-{
- private readonly Composer _composer = Composer.Create();
- private readonly AutoFixtureComposer _autoFixture = new();
-
- /// Composes through the real resolution pipeline.
- [Benchmark(Baseline = true)]
- public Customer Generated() => _composer.Create();
-
- /// Constructs via AutoFixture.
- [Benchmark]
- public Customer AutoFixture() => _autoFixture.Compose();
-}
diff --git a/benchmarks/Compono.Benchmarks/Scalability/BatchScalingBenchmarks.cs b/benchmarks/Compono.Benchmarks/Scalability/BatchScalingBenchmarks.cs
new file mode 100644
index 0000000..0a25a70
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Scalability/BatchScalingBenchmarks.cs
@@ -0,0 +1,28 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.Scalability;
+
+///
+/// CreateMany<T>(count)'s scaling behavior across a batch-size matrix, against its
+/// own Create<T>() baseline - exists to catch algorithmic (super-linear) regressions
+/// in the checkpoint/rewind trace buffer, per-item seed forking, or scope allocation, not just
+/// constant-factor ones, per ADR-0034.
+///
+[MemoryDiagnoser]
+public class BatchScalingBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+
+ /// The batch size is benchmarked at.
+ [Params(1, 10, 100, 1000)]
+ public int Count { get; set; }
+
+ /// Composes one through the real resolution pipeline.
+ [Benchmark(Baseline = true)]
+ public MediumAggregate Create() => _composer.Create();
+
+ /// Composes independent instances.
+ [Benchmark]
+ public IReadOnlyList CreateMany() => _composer.CreateMany(Count);
+}
diff --git a/benchmarks/Compono.Benchmarks/Scalability/CollectionSizeScalingBenchmarks.cs b/benchmarks/Compono.Benchmarks/Scalability/CollectionSizeScalingBenchmarks.cs
new file mode 100644
index 0000000..4cdd371
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Scalability/CollectionSizeScalingBenchmarks.cs
@@ -0,0 +1,29 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.Scalability;
+
+///
+/// Composition cost as a single collection member's size grows, per ADR-0034 - catches
+/// algorithmic regressions in stage 7's collection dispatch (CollectionPlanCache<T>)
+/// that a fixed, small collection size would never surface.
+///
+[MemoryDiagnoser]
+public class CollectionSizeScalingBenchmarks
+{
+ // Assigned in GlobalSetup, which BenchmarkDotNet guarantees runs (once per Params value)
+ // before any [Benchmark] method executes.
+ private Composer _composer = null!;
+
+ /// ' element count for this run.
+ [Params(3, 10, 50, 200)]
+ public int CollectionSize { get; set; }
+
+ /// Builds a composer configured for this run's - kept out of the timed benchmark method.
+ [GlobalSetup]
+ public void Setup() => _composer = Composer.Create(builder => builder.WithCollectionSize(CollectionSize));
+
+ /// Composes at this run's .
+ [Benchmark]
+ public LargeCollection Create() => _composer.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/Scalability/GraphDepthScalingBenchmarks.cs b/benchmarks/Compono.Benchmarks/Scalability/GraphDepthScalingBenchmarks.cs
new file mode 100644
index 0000000..3de4af7
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/Scalability/GraphDepthScalingBenchmarks.cs
@@ -0,0 +1,36 @@
+using BenchmarkDotNet.Attributes;
+using Compono.Benchmarks.Models;
+
+namespace Compono.Benchmarks.Scalability;
+
+///
+/// Shallow vs. deep graph composition cost, per ADR-0034 - generalizes the old suite's one-off
+/// DeepGraphBenchmarks (which only ever exercised in isolation)
+/// into a real depth-only comparison.
+///
+///
+/// composes directly - the exact same leaf shape
+/// (one member, nothing else) 's
+/// resolves at the bottom of its 8-level chain, just at depth 1 instead of depth 8. An earlier
+/// version of this benchmark used as the shallow arm instead, which
+/// resolves two objects, seven strings, and a collection - categorically more value-generation
+/// work than 's single string, so any difference between the two couldn't
+/// be attributed to depth alone versus the extra work. Both arms here resolve exactly one leaf
+/// string value; depth (1 vs. 8) is the only variable. 's 8-level chain is
+/// deep enough (~48 trace entries at its deepest point) to exceed
+/// CompositionTraceBuffer's 32-entry initial capacity and trigger a real
+/// Array.Resize, unlike the depth-1 case.
+///
+[MemoryDiagnoser]
+public class GraphDepthScalingBenchmarks
+{
+ private readonly Composer _composer = Composer.Create();
+
+ /// Composes directly - depth 1, one string leaf, the same leaf shape resolves at depth 8.
+ [Benchmark(Baseline = true)]
+ public DeepLevel8 Shallow() => _composer.Create();
+
+ /// Composes the 8-level-deep chain - depth 8, the same one-string leaf shape as .
+ [Benchmark]
+ public DeepGraph Deep() => _composer.Create();
+}
diff --git a/benchmarks/Compono.Benchmarks/SourceGeneration/GeneratorDriverBenchmarks.cs b/benchmarks/Compono.Benchmarks/SourceGeneration/GeneratorDriverBenchmarks.cs
new file mode 100644
index 0000000..e2fc1f9
--- /dev/null
+++ b/benchmarks/Compono.Benchmarks/SourceGeneration/GeneratorDriverBenchmarks.cs
@@ -0,0 +1,124 @@
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using Basic.Reference.Assemblies;
+using Compono.Generators;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Compono.Benchmarks.SourceGeneration;
+
+///
+/// Clean vs. incremental generation cost, across a composable-type-count matrix, per ADR-0034 - a
+/// separate concern from every other category's runtime performance, measured in-process via
+/// Roslyn's directly (the same way
+/// Compono.Generators.Tests drives it), not a separate timing harness. Primarily serves
+/// maintainers.
+///
+[MemoryDiagnoser]
+public class GeneratorDriverBenchmarks
+{
+ // Assigned in GlobalSetup, which BenchmarkDotNet guarantees runs (once per Params value)
+ // before any [Benchmark] method executes.
+ private Compilation _baseCompilation = null!;
+ private Compilation _touchedCompilation = null!;
+ private GeneratorDriver _warmDriver = null!;
+
+ /// How many Composer.Create<T>() call sites (and matching composable types) the compilation contains for this run.
+ [Params(1, 10, 50)]
+ public int TypeCount { get; set; }
+
+ ///
+ /// Builds this run's base and touched compilations, and runs the generator once against the
+ /// base compilation to fully warm 's incremental cache - kept out of
+ /// both timed benchmark methods.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ _baseCompilation = BuildCompilation(TypeCount);
+
+ // Derived from the base tree via WithChangedText - not a second independently-parsed
+ // SyntaxTree swapped in via ReplaceSyntaxTree, even though both approaches produce a
+ // Compilation the driver considers "related" to the base one. WithChangedText performs a
+ // real incremental re-lex/re-parse that reuses the unaffected internal (green) nodes for
+ // every untouched region of text, so nodes before the appended text keep the same
+ // identity they had in the base tree - which is what actually lets the generator's
+ // incremental pipeline (keyed on node identity/equivalence, not just textual content) skip
+ // recomputing work for call sites nothing here changed. A tree built from scratch via
+ // ParseText has no such relationship to the base tree's nodes at all, even if the two
+ // trees' text is nearly identical - every node in it is new, so incremental work gets
+ // rerun for the whole tree, silently measuring a wholesale reparse instead of a real
+ // small-edit scenario.
+ var baseTree = _baseCompilation.SyntaxTrees.Single();
+ var baseText = baseTree.GetText();
+ var appended = baseText.WithChanges(new TextChange(
+ new TextSpan(baseText.Length, 0), "\n// incremental-touch marker - no call site changed\n"));
+ var touchedTree = baseTree.WithChangedText(appended);
+ _touchedCompilation = _baseCompilation.ReplaceSyntaxTree(baseTree, touchedTree);
+
+ var driver = CSharpGeneratorDriver.Create(new ComponoIncrementalGenerator().AsSourceGenerator());
+ _warmDriver = driver.RunGenerators(_baseCompilation);
+ }
+
+ /// A brand-new driver against the base compilation - no incremental cache to reuse, models a first/cold build.
+ [Benchmark(Baseline = true)]
+ public GeneratorDriver CleanGeneration()
+ {
+ var driver = CSharpGeneratorDriver.Create(new ComponoIncrementalGenerator().AsSourceGenerator());
+ return driver.RunGenerators(_baseCompilation);
+ }
+
+ /// The already-warmed driver against a trivially touched compilation (an unrelated trailing comment, no call site changed) - models an incremental rebuild after a small, unrelated source edit.
+ [Benchmark]
+ public GeneratorDriver IncrementalGeneration() => _warmDriver.RunGenerators(_touchedCompilation);
+
+ private static Compilation BuildCompilation(int typeCount)
+ {
+ var syntaxTree = BuildSyntaxTree(typeCount);
+
+ List references =
+ [
+#if NET11_0_OR_GREATER
+ .. Net110.References.All,
+#elif NET10_0_OR_GREATER
+ .. Net100.References.All,
+#endif
+ MetadataReference.CreateFromFile(typeof(Composer).Assembly.Location),
+ ];
+
+ var compilationOptions = new CSharpCompilationOptions(
+ OutputKind.DynamicallyLinkedLibrary,
+ nullableContextOptions: NullableContextOptions.Enable);
+
+ return CSharpCompilation.Create("SourceGenerationBenchmarkAssembly", [syntaxTree], references, compilationOptions);
+ }
+
+ private static SyntaxTree BuildSyntaxTree(int typeCount)
+ {
+ var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp14);
+ return CSharpSyntaxTree.ParseText(BuildSource(typeCount), parseOptions, "GeneratedTypes.cs");
+ }
+
+ private static string BuildSource(int typeCount)
+ {
+ var source = new StringBuilder();
+ source.AppendLine("namespace Compono.Benchmarks.SourceGeneration.Generated;");
+ source.AppendLine();
+
+ for (var i = 0; i < typeCount; i++)
+ source.AppendLine($"public sealed record GeneratedType{i}(string Value);");
+
+ source.AppendLine();
+ source.AppendLine("public static class EntryPoint");
+ source.AppendLine("{");
+ source.AppendLine(" public static void ComposeAll(global::Compono.Composer composer)");
+ source.AppendLine(" {");
+ for (var i = 0; i < typeCount; i++)
+ source.AppendLine($" composer.Create();");
+ source.AppendLine(" }");
+ source.AppendLine("}");
+
+ return source.ToString();
+ }
+}
diff --git a/docs/adr/0027-compono-bogus-package-design.md b/docs/adr/0027-compono-bogus-package-design.md
index 95d47c9..1fa9ffe 100644
--- a/docs/adr/0027-compono-bogus-package-design.md
+++ b/docs/adr/0027-compono-bogus-package-design.md
@@ -552,3 +552,58 @@ callback, asserted deterministic for the same seed).
shared in either direction
- [ADR-0026](0026-deterministic-seed-derivation-for-providers.md) — the
`DeriveSeed()` capability every model in this ADR builds its determinism on
+
+## Amendment (2026-08-05): `BogusMemberNameProvider` reuses a per-thread `Faker`, not a fresh one per request
+
+[ADR-0034](0034-benchmark-suite-strategy-and-redesign.md)'s benchmark
+suite measured `BogusMemberNameProvider`'s real cost: constructing a
+fresh `Faker` per handled request costs ~865x a plain member rule
+(291.5 μs vs. 337 ns, isolated single-member measurement) — Bogus's
+`Faker` constructor building out its full set of category generators
+(`Name`, `Internet`, `Address`, `Phone`, `Company`, ...) is genuinely
+expensive, independent of anything Compono does around it. Fixed by
+caching one `Faker` per thread (`ThreadLocal`) instead of
+constructing one per request, reseeding its `Random` immediately before
+every use.
+
+**This is not the cached-`Faker` alternative this ADR already
+considered and rejected** (see the "Caching a configured `Faker`
+across requests was considered and deliberately rejected" paragraph
+above) — that paragraph is about `Faker` (Model 3's whole-object
+`UseBogus()` registration), a different, more stateful type
+(`RuleFor`/`RuleSets`/`FinishWith` bindings) than `BogusMemberNameProvider`'s
+plain, untyped `Faker`, and this Amendment doesn't touch `Faker` or
+`UseBogus()` at all — that code, and the rejection reasoning behind
+it, stand exactly as originally written. The rejected alternative was
+also a **globally shared** instance: one `Faker`/`Faker` touched by
+every request, from every thread, with no guarantee Bogus tolerates
+concurrent `Generate()` calls on it — exactly the hazard this ADR's
+original text names. A `ThreadLocal` is structurally different,
+not just a smaller version of the same risk: each thread gets its own
+private instance that no other thread can ever observe or mutate, so
+"does Bogus tolerate concurrent access to one instance" never becomes a
+question this code has to answer — concurrent access to a single
+instance is impossible by construction, not merely avoided by
+convention. Reuse *within* one thread is sequential by definition (a
+thread executes one call at a time), and `BogusMemberNameProvider`'s
+convention generators (`f => f.Name.FirstName()`, etc.) don't retain
+state across calls beyond what `.Random` drives — unlike `Faker`,
+there's no `RuleFor` binding or generation-in-progress state for a
+later call to observe. `Compono.Bogus.Tests.DeterminismTests`'
+`AddingAnUnrelatedBogusBackedMember_DoesNotPerturbAnExistingOnesValue`
+already exercises two sequential resolutions against what the fix makes
+the same reused `Faker` instance and continues to pass unmodified — the
+existing regression coverage for this exact "does an unrelated resolve
+leak into a later one" question caught nothing, which is itself evidence
+the reuse is safe, not just an assumption. A new concurrency test
+(`DeterminismTests.TryProvide_ProducesCorrectValues_WhenCalledConcurrently_OnOneSharedProviderInstance`)
+adds coverage this ADR's original text never had: many concurrent
+resolutions against one shared `BogusMemberNameProvider` instance, each
+compared against an independently-computed single-threaded reference
+value for the same seed.
+
+A future contributor extending this same caching approach to `Faker`
+(Model 3) should not assume this Amendment already clears the way — the
+original rejection paragraph's concern (shared mutable rule-evaluation
+state, not just constructor cost) is a different, harder problem than
+what this Amendment solves, and remains unaddressed.
diff --git a/docs/adr/0034-benchmark-suite-strategy-and-redesign.md b/docs/adr/0034-benchmark-suite-strategy-and-redesign.md
new file mode 100644
index 0000000..46c6470
--- /dev/null
+++ b/docs/adr/0034-benchmark-suite-strategy-and-redesign.md
@@ -0,0 +1,445 @@
+# [ADR-0034] Benchmark Suite Strategy and Redesign
+
+**Status:** Accepted
+
+**Date:** 2026-08-05
+
+**Decision Makers:** Nick Cipollina (solo, confirmed through direct discussion)
+
+## Context
+
+`benchmarks/Compono.Benchmarks` grew organically across Milestones 1–2:
+`ArchitectureBenchmarks`/`EcosystemBenchmarks` exist to answer one
+Milestone 1 exit criterion (does generated construction beat a
+reflection baseline, for a single flat `Leaf` type);
+`ResolutionArchitectureBenchmarks`/`ResolutionEcosystemBenchmarks`/
+`ResolutionBenchmarks` repeat that shape once Milestone 2 made nested
+composition real, against a `Customer`/`Address` graph invented for that
+milestone's own Execution Flow example; `DeepGraphBenchmarks` exists
+solely to trigger `CompositionTraceBuffer`'s `Array.Resize` path, a
+one-off PR #13 review artifact. Each class answers a real question that
+mattered *at the time it was written*, but the result is eight files with
+no shared model set, no consistent categorization, no source-generator
+build-time coverage, no consumer-scenario coverage (a real `[Compose]`
+row, a profile with providers active), and no CI job running any of it —
+a milestone-by-milestone accretion, not a designed suite. This became
+visible while reviewing Milestone 8 Phase 5's plan to simply *document*
+these existing benchmarks under `architecture/current/performance.md`:
+documenting an undesigned suite would publish that lack of design as if
+it were intentional.
+
+This ADR replaces that suite's questions and structure from first
+principles, discussed and confirmed directly rather than designed in
+isolation: build/source-generator benchmarks stay in the same
+BenchmarkDotNet project (in-process `GeneratorDriver` invocations, not a
+separate toolchain); the suite is structured to make a future CI
+regression gate straightforward, but no such gate is stood up in this
+phase — that is deliberately deferred, separate scope.
+
+## Decision Drivers
+
+- The suite should answer engineering questions first, and marketing/
+ documentation questions second — a benchmark that only exists to
+ produce a favorable number for `README.md` is a benchmark that
+ shouldn't exist.
+- Every benchmark must have a stated purpose; a benchmark that no longer
+ answers a meaningful question should be deleted, not kept out of
+ inertia.
+- AutoFixture comparisons are one data point among several audiences,
+ not the suite's organizing principle — this repo already carries an
+ explicit non-goal against comparative marketing claims
+ ([ADR-0030 Amendment 2](0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+ benchmark-claims policy), and the suite's *shape* should reflect that,
+ not just the prose around it.
+- A result unfavorable to Compono (cached reflection wins a scenario,
+ AutoFixture wins a scenario) must be published and explained, not
+ dropped or reframed — honesty is a harder bar than reproducibility
+ alone.
+- The suite must remain a permanent engineering asset — reused models,
+ stable benchmark names, and a structure a future CI regression gate
+ can adopt without a second redesign.
+- Reflection, as a baseline, must represent what a competent hand-rolled
+ alternative would actually do (caching reflection metadata), not a
+ naive strawman that makes Compono look better than a fair comparison
+ would — the same "baseline parity" lesson PR #13 review already
+ learned the hard way for randomness cost, generalized into a
+ permanent rule rather than a one-off fix.
+
+## Considered Options
+
+1. **Incrementally patch the existing 8 files** — add a few new
+ benchmark classes (build-time, consumer scenario) alongside what
+ already exists, leaving `ArchitectureBenchmarks`/`EcosystemBenchmarks`/
+ `ResolutionArchitectureBenchmarks`/etc. in place.
+2. **A flat, larger benchmark list** — design new categories of
+ questions (as below) but keep every benchmark as an independent,
+ uncategorized class in one folder, matching the project's current
+ physical layout.
+3. **A fully redesigned suite**, organized into explicit categories by
+ audience and question, built on one reused model set, with explicit
+ fair-comparison rules, replacing every existing benchmark class
+ rather than layering on top of them.
+
+## Decision Outcome
+
+Chosen option: **3 — a fully redesigned suite**, replacing all 8 existing
+benchmark files. Patching (Option 1) would leave the actual defect (no
+coherent question set, milestone-specific models) untouched — the new
+categories would sit next to old ones answering superseded milestone
+questions, which is exactly the accretion problem this ADR exists to
+fix. A flat list of new categories (Option 2) fixes the *question* gap
+but not the *model* gap — without one reused representative model set,
+each new category would reinvent its own types the same way `Leaf` and
+`Customer`/`Address` were invented, and the suite would still lack a
+structure a future contributor could extend predictably.
+
+### Suite philosophy
+
+The suite exists to answer engineering questions first, and marketing/
+documentation questions second. Every benchmark class states the
+question it answers (in its own summary XML doc, matching this repo's
+existing documentation convention) — a benchmark that stops answering a
+meaningful question gets deleted in the PR that makes it meaningless, not
+left to accumulate. A result unfavorable to Compono is published exactly
+like a favorable one: isolated, explained, and left for a maintainer to
+judge whether it's worth optimizing — never dropped, hidden, or reframed.
+
+### What this suite is (and isn't)
+
+`BenchmarkDotNet` benchmarks answer narrow, **comparative engineering
+questions** — "does approach A cost more than approach B, for this one
+operation, isolated from everything else" — under an artificially clean
+environment (JIT-warmed, GC-isolated, single-operation iteration).
+They are **not**:
+
+- A substitute for full-application performance testing (an app's actual
+ hot path includes far more than one composition call).
+- A scalability or load test (no concurrency, no sustained throughput,
+ no resource contention — see Scalability below for what this suite
+ *can* say about growth, which is still single-threaded and isolated).
+- A guarantee about any specific consumer's real-world numbers — every
+ published result is a data point from one environment (disclosed in
+ full, per Reporting Rules below), not a promise.
+
+This section exists so a reader of the published results (maintainer or
+consumer) calibrates expectations correctly, rather than reading a
+microbenchmark's nanosecond figure as "how fast my test suite will run."
+
+### Representative models
+
+`Models/` holds the canonical representative types every category draws
+from, instead of each category inventing its own (the mistake this ADR
+corrects) — the common language the rest of this ADR's categories and
+rules are defined in terms of: `SimplePoco` (flat, no dependencies —
+replaces `Leaf`), `MediumAggregate` (one nested composable dependency +
+every built-in kind + a collection member — replaces `Customer`/
+`Address`), `DeepGraph` (an N-level chain — replaces `DeepLevel1`-
+`DeepLevel8`), `LargeCollection` (a model with a large collection
+member, for Scalability), `SharedValueGraph` (a sibling-parameter shape
+for Consumer Scenarios' shared-value case), and `ProviderBackedModel`
+(an interface-typed member NSubstitute can satisfy, plus a
+convention-matching `string` member Bogus can satisfy). A category adds
+a new model only when none of these already represents its question —
+not as a matter of course.
+
+### Benchmark categories
+
+Six benchmark categories (folders), each answering a distinct question
+for a distinct audience, drawing on the representative models above,
+plus cross-cutting Fair Comparison and Reporting rules that apply to all
+of them:
+
+1. **Implementation strategies** (`ImplementationStrategies/`) —
+ maintainer-facing: how close does generated composition come to
+ handwritten construction? What does runtime reflection actually cost,
+ cached and uncached? Did an implementation change regress
+ performance? These compare **implementation techniques for the same
+ job**, not competing frameworks, ordered against a single theoretical
+ upper bound:
+
+ ```text
+ Handwritten construction (theoretical ceiling — hand-authored, no
+ abstraction cost at all)
+ ↓
+ Generated composition (Compono's actual mechanism — the number
+ that matters: how close to the ceiling?)
+ ↓
+ Cached reflection (a competent hand-rolled alternative that
+ memoizes constructor/member metadata —
+ the fair "should Compono use reflection
+ instead" comparison)
+ ↓
+ Uncached reflection (the naive case — shows what caching alone
+ buys, kept as its own baseline rather
+ than folded into "reflection")
+ ```
+
+ Handwritten construction is the **theoretical upper bound**, not just
+ another baseline — generated composition is always evaluated against
+ how close it gets to that ceiling, not against reflection as if
+ reflection were the target. Reflection (cached and uncached) is a
+ second, independent comparison this category also answers: is
+ Compono's own generated approach actually justified over a realistic
+ reflection-based alternative? Expression-tree-based construction is a
+ future candidate for this same ordering if ever explored, not built
+ now.
+2. **Consumer scenarios** (`ConsumerScenarios/`) — "what performance
+ should a user expect in realistic applications?" A simple POCO, a
+ medium aggregate, a deep object graph, large collections, shared
+ values, a `Compono.Bogus`-enabled profile, a `Compono.NSubstitute`-
+ enabled profile — realistic usage, not isolated mechanism cost. This
+ is the category most likely to surface in public documentation.
+3. **External comparison** (`ExternalComparison/`) — AutoFixture belongs
+ here, as one comparison point answering "what should a developer
+ expect when migrating," not the suite's center. Equivalent object
+ graphs, equivalent work, both directions published honestly: if
+ AutoFixture wins a scenario, that result ships too.
+4. **Feature overhead** (`FeatureOverhead/`) — isolates the incremental
+ cost of one Compono feature at a time via additive layering: generated
+ composition alone → + shared values → + member rules → + type rules →
+ + providers → + `UseBogus()` → + `UseNSubstitute()`. Answers "how
+ expensive is this one feature, on its own?"
+5. **Scalability** (`Scalability/`) — performance as complexity grows:
+ `CreateMany` at 1/10/100/1000, shallow vs. deep graphs, growing
+ collection sizes. Exists to catch **algorithmic** regressions
+ (super-linear growth), not just constant-factor ones — this is where
+ `DeepGraphBenchmarks`' original question (does a deep enough graph
+ trigger `CompositionTraceBuffer`'s resize path) actually lives now,
+ generalized into a real shallow-vs-deep comparison instead of a
+ one-off artifact. Still single-threaded and isolated per "What this
+ suite is (and isn't)" above — not a substitute for a real load test.
+6. **Source generation** (`SourceGeneration/`) — a separate concern from
+ runtime performance: clean vs. incremental generation cost, across a
+ matrix of composable-type counts, measured in-process via Roslyn's
+ `GeneratorDriver`/`CSharpGeneratorDriver` — same BenchmarkDotNet
+ project, not a separate timing harness (confirmed directly: staying
+ in one project keeps one report format and one reproduction story,
+ and BenchmarkDotNet can benchmark arbitrary in-process code, including
+ a generator driver run). Primarily serves maintainers.
+
+### Fair comparison rules
+
+1. **Baseline parity.** Any non-Compono baseline (handwritten, reflection,
+ AutoFixture) does equivalent real work to what Compono actually does
+ for that model — same output shape, same randomness cost where
+ randomness is part of the comparison — never a placeholder or
+ simplified alternative. (This is the existing `ReflectionComposer`
+ lesson from PR #13 review, generalized from a one-off fix into a
+ permanent rule every new baseline is held to.)
+2. **Reflection means two baselines, not one.** "Reflection" is always
+ reported as **cached** (a realistic hand-rolled composer that
+ memoizes `ConstructorInfo`/member metadata per type) and **uncached**
+ (naive, re-reflecting every call) — never conflated into a single
+ "reflection" number. This is one of the strongest design decisions in
+ this suite: comparing against a *competent* reflection implementation,
+ not a strawman, is what makes a result honest. If cached reflection
+ legitimately wins a scenario, that result is published exactly like
+ any other — it's valuable information about whether Compono's
+ generated-code overhead is actually worth paying in that shape.
+3. **Handwritten construction is the ceiling, not a baseline among
+ equals.** Every Implementation Strategies comparison reports Generated
+ against Handwritten first (how close to the theoretical floor?), then
+ against Cached/Uncached Reflection second (is Compono's approach
+ justified over the realistic alternative?) — never presented as if
+ beating reflection were the goal on its own.
+4. **Comparisons stay inside their category.** A number from one
+ category (e.g. Implementation Strategies' isolated construction-
+ dispatch cost) is never directly compared against a number from a
+ different category (e.g. a Consumer Scenario's end-to-end cost) —
+ only benchmarks on the same model, in the same category, from the
+ same run are compared to each other.
+5. **Honest publication.** A result unfavorable to Compono is published
+ exactly like a favorable one — isolated, explained, left for a
+ maintainer to judge whether it's worth optimizing.
+6. **Every benchmark states its question**, in its own class-level XML
+ doc summary — a project convention (checked in review), not just
+ aspirational text.
+
+### Reporting rules
+
+Every published benchmark reports the same fixed set of columns — no
+future documentation gets to cherry-pick a single favorable metric out
+of a richer result set:
+
+- **Mean**
+- **Error** and **StdDev** (BenchmarkDotNet's own noise-characterization
+ columns — a Mean without them is not a trustworthy number)
+- **Ratio** against the category's designated baseline, where the
+ category has one (Implementation Strategies' Handwritten/Generated/
+ Cached/Uncached Reflection rows; External Comparison's AutoFixture
+ rows) — omitted only where no baseline is meaningful (e.g. Scalability's
+ intrinsic `CreateMany` batch-size comparison, which is already a ratio
+ against its own `count=1` case)
+- **Allocated bytes**, and **Gen0**/**Gen1**/**Gen2** collection counts
+ where BenchmarkDotNet reports them as nonzero — `[MemoryDiagnoser]` is
+ mandatory on every benchmark class in every category, full stop, not a
+ per-class judgment call. Memory behavior matters as much as throughput
+ for test infrastructure that runs constantly.
+- **Full environment disclosure** on every published result — .NET
+ version, architecture, OS, Release configuration, BenchmarkDotNet job —
+ already existing practice, generalized here into a permanent rule
+ every category follows, not just the ones written so far.
+
+A page that reports Mean alone (or Mean and Allocated alone) is not
+meeting this bar, even if every other rule in this ADR is followed.
+
+### Regression-detection readiness, not automation
+
+Per direct discussion: this phase structures the suite so a future CI
+regression gate is straightforward — stable, permanent benchmark class/
+method names (renaming breaks historical comparability), and full
+BenchmarkDotNet result artifacts (`*-report-github.md`/`.csv`/`.html`)
+produced per category — but does **not** stand up that gate now. A real
+CI job comparing against a stored baseline needs a runner with
+consistent hardware, baseline storage/versioning, and a noise-tolerance
+threshold policy this repo's CI doesn't have yet — that's separate,
+future scope (see `docs/roadmap/future-packages.md`'s sibling page,
+`docs/roadmap/post-mvp.md`, for where a concrete future candidate like
+this gets tracked once there's real evidence it's needed).
+
+### Positive Consequences
+
+- One coherent question set per audience (maintainers, consumers,
+ migrators), instead of milestone-specific artifacts a new reader has
+ to reverse-engineer the history of to understand.
+- A reused model set makes adding a new benchmark cheap and consistent,
+ rather than inventing a new bespoke type each time.
+- The cached-vs-uncached reflection split, evaluated against handwritten
+ construction as the explicit ceiling, answers the implementation-
+ strategy question ("should Compono reach for reflection anywhere, and
+ how close does generated code get to the theoretical best case")
+ more honestly than the old suite's single naive baseline with no
+ stated ceiling at all.
+- Public performance documentation can be capability-oriented (what does
+ a consumer actually experience) without either fabricating numbers or
+ drowning the reader in maintainer-facing internals.
+
+### Negative Consequences
+
+- A full rewrite discards the old suite's git history for each
+ individual benchmark number (mitigated: the historical figures already
+ published in ADRs/plans stay exactly as recorded — this ADR doesn't
+ retroactively invalidate past `Accepted` decisions that cited them,
+ only replaces the suite going forward).
+- More benchmark classes overall (six categories plus baselines/models)
+ means more to keep green and update when the engine's shape changes —
+ accepted, since the alternative (the old accreted suite) already had
+ this cost without the benefit of a coherent structure.
+- No automated regression gate yet — a real regression could still land
+ undetected until someone manually reruns the suite. Accepted per the
+ direct discussion above: standing up that gate is separate, future
+ scope, not blocked on but also not solved by this ADR.
+
+## Pros and Cons of the Options
+
+### Option 1: Incrementally patch the existing 8 files
+
+- Good, because it's the smallest diff.
+- Bad, because it doesn't remove the actual defect (milestone-specific
+ models, no categorization) — new, well-designed benchmarks would sit
+ next to old ones answering superseded questions.
+
+### Option 2: Flat list of new categories, no reused model set
+
+- Good, because it fixes the question-coverage gap (build-time, consumer
+ scenarios) without a full rewrite.
+- Bad, because each new category would still invent its own one-off
+ types, reproducing the exact problem (`Leaf`, `Customer`/`Address`,
+ `DeepLevel1`-`8`) this ADR exists to fix.
+
+### Option 3: Fully redesigned suite (chosen)
+
+- Good, because it fixes both the question gap and the model gap at
+ once, and leaves a structure a future contributor can extend
+ predictably.
+- Bad, because it's the largest diff and discards the old suite's
+ benchmark-by-benchmark continuity (mitigated above).
+
+## Links
+
+- [PLAN-0008](../plans/0008-milestone-8-public-preview.md) Phase 5 — the
+ plan this ADR's implementation is tracked under.
+- [ADR-0030 Amendment 2](0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items) —
+ the benchmark-claims policy this ADR's public-documentation direction
+ and "AutoFixture is one comparison point, not the center" framing
+ implement.
+- `benchmarks/Compono.Benchmarks/` — the existing suite this ADR
+ replaces.
+
+## Amendment (2026-08-05): Implementation Strategies removed — it compares different systems, not one variable
+
+Implemented, then reconsidered after direct review of the actual
+published results (see
+[architecture/current/performance.md](../architecture/current/performance.md)'s
+history): the Implementation Strategies category (Handwritten/Generated/
+Cached-Reflection/Uncached-Reflection) is **removed**, not just
+re-framed. This is a correction to this ADR's original category
+taxonomy, not a reversal of the suite redesign itself — the reused
+model set, the remaining five categories, the fair comparison rules, and
+the reporting rules all stand exactly as originally decided.
+
+**Why removal, not better framing.** A prior documentation pass already
+tried reframing this category's results (explaining that `Generated`
+measures the whole pipeline, not construction) rather than removing it.
+That reframing was accurate but didn't fix the underlying problem: the
+category still doesn't isolate one variable. `Handwritten`, `Cached`/
+`UncachedReflection`, and `Generated` are not three implementations of
+the same job — they're three systems doing different, non-comparable
+amounts of work (bare construction; construction plus real value
+generation; a full multi-stage resolution pipeline with provider
+dispatch, diagnostics tracing, deterministic random forking, and an
+extensibility surface that in an unconfigured `Composer` is guaranteed
+to miss on every stage before the one that actually produces a value).
+A result like "reflection is 3x faster than generated" doesn't identify
+*why* — the gap could be diagnostics, provider dispatch, deterministic
+randomness, the trace buffer, or any other pipeline behavior, and the
+benchmark itself gives no way to attribute it to one of those. It's
+interesting, but it doesn't guide an engineering decision, which this
+ADR's own Decision Drivers already required ("every benchmark must have
+a stated purpose; a benchmark that no longer answers a meaningful
+question should be deleted").
+
+**Revised philosophy.** Every benchmark in this suite must now satisfy
+at least one of four concrete goals:
+
+1. It measures the runtime cost of a specific Compono feature.
+2. It measures Compono's scalability.
+3. It measures Compono's build-time (source-generation) cost.
+4. It measures the migration experience from AutoFixture.
+
+A benchmark that can't be tied to one of these shouldn't exist. This
+sharpens (not reverses) the original "engineering questions first,
+marketing second" philosophy: "engineering question" now means one of
+the four goals above, not any comparison between implementations that
+happens to be interesting.
+
+**AutoFixture is the only remaining external comparison, and stays for a
+different reason than Implementation Strategies did.** External
+Comparison survives this amendment because it satisfies goal 4 directly:
+AutoFixture solves the same problem Compono does (composing object
+graphs, generating values, handling nested objects, extensibility), so
+"what happens if I replace AutoFixture with Compono" is a real adoption
+question with an actionable answer, not an unattributable one — unlike
+Handwritten/reflection, which were never alternatives a real consumer
+chooses between.
+
+**What's removed:**
+- The `ImplementationStrategies/` benchmark category (`SimplePocoConstructionBenchmarks.cs`,
+ `MediumAggregateConstructionBenchmarks.cs`).
+- `Baselines/HandwrittenComposer.cs`, `Baselines/CachedReflectionComposer.cs`,
+ `Baselines/UncachedReflectionComposer.cs` — dead once
+ `ImplementationStrategies/` is gone; no other category ever referenced
+ them. `Baselines/AutoFixtureComposer.cs` stays (External Comparison's
+ only remaining consumer).
+- The "Implementation strategies" section of
+ `architecture/current/performance.md`, and every rule text above that
+ named it specifically (the rules themselves — baseline parity, honest
+ publication, full environment disclosure, etc. — still apply to every
+ remaining category; only the Implementation-Strategies-specific
+ wording is superseded by this Amendment).
+
+**What's unchanged**: `Models/`, `Baselines/AutoFixtureComposer.cs`,
+`ConsumerScenarios/`, `ExternalComparison/`, `FeatureOverhead/`,
+`Scalability/`, `SourceGeneration/`, every fair comparison and reporting
+rule not specific to Implementation Strategies, and the regression-
+detection-readiness decision. Five categories remain, not six.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index d7462f7..408564b 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -95,3 +95,4 @@ the mechanics: numbering, status, and the index.
| [0031](0031-public-preview-release-and-versioning-policy.md) | Public Preview Release and Versioning Policy | Accepted |
| [0032](0032-api-reference-documentation-toolchain.md) | API Reference Documentation Toolchain | Accepted |
| [0033](0033-public-preview-samples-strategy.md) | Public Preview Samples Strategy | Accepted |
+| [0034](0034-benchmark-suite-strategy-and-redesign.md) | Benchmark Suite Strategy and Redesign | Accepted |
diff --git a/docs/architecture.md b/docs/architecture.md
index 182ccb9..8930e53 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -1,1131 +1,18 @@
-# Compono Architecture
-
-## Overview
-
-Compono is a modular test composition framework.
-
-The architecture is centered on a `CompositionContext`.
-
-The context represents one active composition operation and coordinates:
-
-- The deterministic seed
-- Random streams
-- Scope and shared instances
-- Registrations
-- Profiles
-- Providers
-- Generated composition plans
-- The current request path
-- Diagnostics
-- Cancellation and runtime state
-
-The context is the internal center of the system even when the public API exposes simpler concepts such as `Composer`, `Create()`, or a test-framework attribute.
-
-## Architectural Shape
-
-```text
-Consumer API
- |
- v
-Composer / Test Framework Integration
- |
- v
-CompositionContext
- |
- +--> Explicit Values
- +--> Shared Scope
- +--> Registrations
- +--> Profile Rules
- +--> Semantic Value Providers
- +--> Test Double Providers
- +--> Built-in Value Providers
- +--> Generated Composition Plans
- |
- v
-Composed Result or Diagnostic Failure
-```
-
-## Composition Context
-
-A composition context should contain all state required to resolve a graph without relying on mutable global configuration.
-
-Conceptually:
-
-```csharp
-public interface ICompositionContext
-{
- T Resolve(in CompositionRequestDescriptor descriptor);
-
- // Descriptor-less overload (shipped, Milestone 3 Phase 1) - the manual-resolve
- // entry point a registration/configuration-rule factory calls to compose its
- // own nested dependencies, distinct from the descriptor-based overload above
- // generated code uses. See ADR-0019's Registrations and Service Injection
- // section below.
- T Resolve();
-
- // Milestone 6 Phase 0 (implemented, PLAN-0006) - an on-demand, path-derived
- // deterministic seed a public provider or registration/rule factory can use for
- // its own randomness (e.g. Compono.Bogus's Faker/Randomizer), without exposing
- // the engine's own internal IRandomSource or path representation. See ADR-0026.
- int DeriveSeed();
-}
-```
-
-`ICompositionContext` is `public` — generated plan code (in the consumer's
-own assembly) calls it directly, so it has to be. Everything the context
-*owns* (seed, scope, path, random source, active construction frames,
-provider pipeline) is deliberately not exposed as properties on this
-interface — the context is internal state generated code never touches
-directly, per
-[ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md).
-Resolution is synchronous: every provider planned for the MVP is
-in-memory/CPU-bound, and Milestone 1 already shipped a synchronous
-`ICompositionPlan.Compose`. A genuinely async provider need, if one
-ever arises, gets its own distinct opt-in contract rather than reworking
-this one.
-
-### Context lifetime
-
-A new root context should normally be created for:
-
-- A call to `Create()`
-- A call to `CreateMany()`
-- One xUnit theory row
-- One explicit composition scope
-
-Nested requests should derive child contexts or child paths without losing the root seed, scope, or diagnostics.
-
-## Composition Requests
-
-Every value is resolved from a rich request rather than only a `Type` —
-but generated code never constructs that rich request directly. Per
-[ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md),
-there are two distinct shapes:
-
-- **`CompositionRequestDescriptor`** (`public`) — the small, compact,
- compile-time-constructible value generated plan code actually passes.
- A plain `readonly struct`, not a `record struct` — equality,
- `Deconstruct`, and record-style formatting aren't part of its contract,
- per [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md)'s
- second amendment:
- ```csharp
- public readonly struct CompositionRequestDescriptor
- {
- public CompositionRequestDescriptor(
- CompositionRequestKind kind, // ConstructorParameter | RequiredMember |
- // CollectionElement | DictionaryKey | DictionaryValue
- int ordinal, // stable identity - see Deterministic Randomness, below
- string name, // diagnostic display only, never identity
- Type? declaringType, // Milestone 3 - see Configuration Rules, below
- Nullability nullability);
-
- public CompositionRequestKind Kind { get; }
- public int Ordinal { get; }
- public string Name { get; }
- public Type? DeclaringType { get; }
- public Nullability Nullability { get; }
- }
- ```
- `DeclaringType` ([ADR-0020](adr/0020-composition-configuration-rules.md),
- implemented, Milestone 3 Phase 3 — an additive extension to this
- `Accepted` descriptor shape, not a change to ADR-0010's own text) is the
- type whose constructor/required-member declares this parameter/member —
- generator-emitted alongside `Ordinal`/`Name`, meaningful only for
- `ConstructorParameter`/`RequiredMember` requests. **Nullable**, not a
- sentinel value: `null` for any other request kind (a collection element/
- dictionary key/value, or a `ManualResolve` invocation), which have no
- declaring type to report at all. It exists so a
- configuration rule can match by declaring type + member name directly
- off the request, rather than inferring the declaring type from path
- state — see Configuration Rules, below.
-- **`CompositionRequest`** (`internal`) — the richer record the context
- expands a descriptor into, by appending a `PathSegment`
- ([ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md))
- derived from the descriptor to its own current path. This is what the
- internal provider pipeline actually operates on; generated code never
- sees or builds one.
-
-Only fields with a real Milestone 2 consumer exist today — `RequestedType`,
-`Nullability`, the descriptor's `Kind`/`Name` (folded into the request's
-path segment), `Path`, `IsShared`. `DeclaringType` is Milestone 3's first
-addition to this set (above). `CustomAttributes`, generic context,
-requested lifetime, semantic hints, and "whether a test double is
-acceptable" are still deliberately not modeled — they get added once a
-later milestone (xUnit inline values, Bogus hints, NSubstitute
-eligibility) has an actual consumer for them, not speculatively now.
-
-[ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md)
-(implemented, Milestone 4 Phase 0 — see
-[PLAN-0004](plans/0004-milestone-4-xunit-integration.md)) adds a seventh
-`CompositionRequestKind`, `TestParameter` (and a matching `PathSegment.TestParameter`,
-the seventh `PathSegment` kind — the five original structured segments
-plus `ManualResolve` made six), for a value a test-framework integration
-is composing directly for one of
-the *test method's own* parameters — as distinct from a constructor
-parameter or required member a *generated plan* is filling in. It reuses
-`DeclaringType` for the type whose method declares the parameter (a test
-class, for `Compono.XunitV3`), extending that field's existing contract to a
-third kind of declaring construct rather than adding a new field.
-
-Generated plans avoid requiring runtime reflection merely to construct
-this metadata — the descriptor is a plain, compiler-emittable value, and
-path/type expansion happens entirely inside the context.
-
-## Resolution Pipeline
-
-The default resolution order is:
-
-1. Explicit values
-2. Shared or scoped values
-3. Exact registrations
-4. Configuration rules
-5. Semantic value providers
-6. Test-double providers
-7. Built-in value providers
-8. Generated object composition plans
-9. Diagnostic failure
-
-This precedence is part of the product contract, and stage *order* is
-fixed — not configurable, by users or by providers reordering themselves.
-But per
-[ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md),
-not every stage is the same *kind* of thing:
-
-| # | Stage | Kind |
-|---|---|---|
-| 1 | Explicit values | Context-owned deterministic check (no consumer until a later milestone's inline-value API exists) |
-| 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(...)`, `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 since Milestone 5 Phase 0 ([ADR-0024](adr/0024-public-provider-extensibility-model.md)). `Compono.Bogus` ([ADR-0027](adr/0027-compono-bogus-package-design.md), **implemented, PLAN-0006 Phase 1**) is this stage's first real registrant via `UseBogus()` — `BogusMemberNameProvider` matches `string`-typed member-name conventions. |
-| 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 |
-
-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)). 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), or calls `UseBogus()`
-(`Compono.Bogus`, implemented, stage 5 — [ADR-0027](adr/0027-compono-bogus-package-design.md),
-PLAN-0006 Phase 1). 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
-more than one provider" is not actually true today, a stale claim
-corrected during PR #13 review. No *richer* ordering rule (priority,
-specificity, or similar) exists yet because none has been needed:
-`PrimitiveValueProvider`/`EnumValueProvider`/`NullableValueProvider`
-claim disjoint type sets, so plain registration order has never had two
-providers genuinely compete for the same request — a richer rule becomes
-a real question only once two providers could plausibly both claim the
-same type differently. Stage 7's `CollectionPlanCache` dispatch is
-tried only after
-its ordered provider collection has already declined, so a registration,
-profile rule, semantic provider, or test-double provider (stages 1–6)
-still gets first refusal over a collection request — collections stay
-ordinary pipeline requests, per ADR-0013.
-
-## Providers
-
-Providers satisfy composition requests within one of the extensible
-pipeline stages above (4/5/6/7) — the context-owned stages (1/2/3/8/9)
-are not providers and don't implement this interface.
-
-A provider is independently replaceable and reports whether it:
-
-- Did not apply (`NotHandled`)
-- Successfully composed a value (`Success`)
-
-Conceptually:
-
-```csharp
-internal interface ICompositionProvider
-{
- CompositionResult TryCompose(
- CompositionRequest request,
- ICompositionContext context);
-}
-```
-
-Ordinary providers **cannot** report `Failure` — the type only gives them
-`NotHandled`/`Success` to return. `Failure` is reserved for the
-context-owned authoritative stages (an exact registration whose factory
-throws, generated-plan dispatch when a plan exists but fails or a
-recursion cycle is detected) — the rule, per
-[ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md):
-`Failure` means "authoritative ownership was established, but resolution
-could not complete," never a stronger form of `NotHandled`. This is what
-stops a provider that merely can't produce *this* particular request from
-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.
-
-For a constructible type, the generator should emit a plan that:
-
-- Selects the constructor
-- Requests constructor arguments
-- Invokes the constructor directly
-- Assigns required or configured members
-- Preserves nullability and member context
-- Produces diagnostic metadata
-- Registers the plan with the runtime
-
-Conceptually:
-
-```csharp
-internal sealed class CustomerCompositionPlan
- : ICompositionPlan
-{
- public Customer Compose(ICompositionContext context)
- {
- var firstName = context.Resolve(
- new CompositionRequestDescriptor(
- CompositionRequestKind.ConstructorParameter,
- 0,
- "firstName",
- Nullability.NotNullable));
-
- var lastName = context.Resolve(
- new CompositionRequestDescriptor(
- CompositionRequestKind.ConstructorParameter,
- 1,
- "lastName",
- Nullability.NotNullable));
-
- return new Customer(firstName, lastName);
- }
-}
-```
-
-Generated code only ever calls `context.Resolve(descriptor)` per
-member — it never constructs a `CompositionRequest`, touches
-`CompositionPath`, or manages recursion state directly. The context owns
-all of that internally
-([ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md)),
-which is what makes incorrect path propagation structurally difficult
-rather than merely documented against. The final generated code may use
-lower-level APIs for performance.
-
-### Discovery and Dispatch
-
-How the generator decides a type needs the plan above, and how
-`Create()` reaches it without reflection, is
-[ADR-0004](adr/0004-composition-plan-discovery-and-dispatch.md): discovery
-walks `Create()`/`CreateMany()` call sites and their types'
-transitive constructor parameters, with `[Composable]` as an opt-in marker
-for a type with no local call site — applied directly to a type this
-compilation owns, or at assembly level
-(`[assembly: Composable(typeof(SomeType))]`) for a type in a referenced
-assembly that can't be annotated directly. Both forms are equivalent
-plan-generation requests, deduplicated alongside call-site discovery.
-"Registers the plan with the runtime"
-above means a generated module initializer populates a closed-generic
-static field (`PlanCache.Instance = ...`) that `Create()`
-reads directly — not a `typeof(T)`-keyed dictionary lookup.
-
-A generated plan never redispatches into itself directly — each
-`context.Resolve(descriptor)` call it makes is a fresh pipeline
-evaluation for whatever type that member actually is, not a recursive
-call back into the same plan. A genuinely self-referencing type (e.g. a
-`Node` with a `Node` property) only becomes a problem if nothing earlier
-in the pipeline (an explicit value, a shared value, a registration)
-terminates it before generated-plan dispatch is reached a second time for
-the same type while the first invocation is still on the stack — see
-Recursion Detection, below.
-
-### Generator responsibilities
-
-The generator should identify:
-
-- Accessible constructors
-- Primary constructors
-- Required members
-- Init-only members
-- Nullability metadata
-- Unsupported types
-- Ambiguous construction paths
-- Cyclic compile-time dependencies where detectable
-
-### Runtime responsibilities
-
-The runtime should:
-
-- Execute generated plans
-- Resolve provider-backed values
-- Manage scopes
-- Manage deterministic random streams
-- Track the composition path
-- Produce runtime diagnostics
-
-## Runtime Reflection Policy
-
-The reflection policy is intentionally undecided.
-
-Candidate approaches:
-
-### Generated plans required
-
-Composition fails when no generated plan exists.
-
-Advantages:
-
-- Predictable performance
-- Strong trimming and AOT characteristics
-- Simple runtime model
-
-Tradeoffs:
-
-- External or dynamically discovered types may require explicit support
-- Some test scenarios may be less convenient
-
-### Automatic reflection fallback
-
-The runtime reflects when no generated plan exists.
-
-Advantages:
-
-- High compatibility
-- Lower migration friction
-
-Tradeoffs:
-
-- More complex runtime
-- Weaker AOT guarantees
-- Performance becomes less predictable
-- Reflection can hide source-generation gaps
-
-### Opt-in compatibility package or mode
-
-Reflection support is isolated from the default runtime.
-
-Advantages:
-
-- Keeps the core architecture clean
-- Allows compatibility where necessary
-- Makes performance tradeoffs explicit
-
-This is the current leading compromise, but it is not yet an accepted decision.
-
-## Scopes and Shared Values
-
-A composition scope stores values that should be reused during an active composition.
-
-Examples:
-
-- A repository parameter shared with the system under test
-- A fake clock reused throughout an object graph
-- A substitute reused by multiple dependencies
-
-Scope semantics must be explicit.
-
-Resolved for Milestone 2 by
-[ADR-0011](adr/0011-composition-scope-shared-values-and-recursion-detection.md):
-one scope per root composition operation (one `Create()` call, or one
-item of a `CreateMany()` call — each item gets its own independent
-scope, not a scope shared across the batch). Sharing is type-keyed only
-for Milestone 2; name/qualifier-based sharing is deferred until a
-Milestone 4 `[Shared]`-attribute use case needs it. A broader "test case"
-or "user-created scope" lifetime is deferred until Milestone 4 has a
-concrete consumer to design against, rather than building the general
-menu of possible lifetimes below speculatively:
-
-Milestone 4 is that concrete consumer:
-[ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md)
-(implemented, Milestone 4 Phase 0) adds `Composer.CreateRow(Type)` and a
-public `CompositionRow`, one per theory row, whose `CompositionScope` is
-exactly this same per-root-operation scope — still type-keyed only, still
-one instance per root operation, no new lifetime concept. What changes is
-*when a request consults it* — see the Resolution Pipeline table's stage-2
-row above.
-
-- Request
-- Composition graph (Milestone 2's chosen lifetime)
-- Test case
-- User-created scope
-
-The MVP should begin with one clear shared lifetime rather than a general-purpose dependency injection lifetime system.
-
-### Recursion Detection
-
-A repeated *type* appearing twice in a graph (two sibling properties of
-the same type, or the same type reachable via two different paths) is
-ordinary graph shape, not a cycle. A genuine cycle is a type whose
-*construction* is still actively in progress when it's requested again.
-[ADR-0011](adr/0011-composition-scope-shared-values-and-recursion-detection.md)
-keeps these deliberately separate: `CompositionPath` (below) records
-every request edge for diagnostics and random forking, while a distinct
-internal **active-construction-frame** stack is pushed only around
-structural construction (generated-plan dispatch, stage 8) and checked
-only there — after explicit values, shared/scoped values, and exact
-registrations have already had a chance to terminate the graph. A
-self-referencing type resolved by a registered or shared instance never
-touches the recursion mechanism at all; only an actual in-progress
-construction cycle does, and the resulting diagnostic reports the chain
-of active frames — the request edges that formed the cycle — not just a
-list of repeated types.
-
-## Immutable Configuration Model
-
-Resolved by [ADR-0017](adr/0017-immutable-composer-configuration-and-builder-model.md).
-**The builder/configuration split, `WithSeed`, `Register`, `UseServiceProvider`,
-`AddProfile`, the `.For()` rule DSL, and `WithCollectionSize` are all implemented
-(Milestone 3 Phases 0-3, [PLAN-0003](plans/0003-milestone-3-profiles-and-configuration.md)).**
-`CompositionBuilder` is a mutable
-accumulator that exists only for the duration of the `Composer.Create(builder =>
-...)` callback; when the callback returns, its accumulated state is validated and
-frozen into an internal `CompositionConfiguration` — a `Composer` holds exactly one,
-reused across every `Create()`/`CreateMany()` call it ever serves, with no
-mutable state on that hot path at all. Every scalar configuration verb — `WithSeed`,
-`UseServiceProvider`, `WithCollectionSize`'s global default — may be set at most once
-per configuration, the same fail-fast rule keyed configuration (registrations,
-profile provenance, and type/member rules) follows: a second call is a build-time
-conflict, never last-wins, so a scalar's effective value never depends on
-`AddProfile` call order.
-
-Two distinct failure moments both surface from `Composer.Create(...)`, but aren't
-the same mechanism: a profile cycle ([ADR-0018](adr/0018-composition-profiles.md),
-implemented, Milestone 3 Phase 2) is detected **eagerly**, during
-`AddProfile` itself, and throws immediately with exactly one error naming the cycle
-— configuration stops right there, nothing further is aggregated. Every other
-conflict (duplicate registrations, duplicate scalars, duplicate type/member rules)
-is detected by `Build()`'s single validation pass, which runs
-only after the whole `configure(builder)` callback has already returned
-successfully, and aggregates every conflict found across the complete accumulated
-state into one `CompositionConfigurationException` — distinct from the per-value
-`CompositionException` a running `Create()` call can throw. That exception
-carries a structured, inspectable list of errors (kind, affected type/member,
-contributing sources) that its rendered message is derived from, not the other way
-around — a test (or a consumer) can assert on the structured data directly rather
-than parsing message text.
-
-## Profiles
-
-Resolved by [ADR-0018](adr/0018-composition-profiles.md) (implemented, Milestone 3
-Phase 2). A profile is `ICompositionProfile` — one method,
-`void Configure(CompositionBuilder builder)` — not an abstract base class: profiles
-define behavior, not shared implementation, matching the repo's composition-over-
-inheritance preference and AutoFixture's `ICustomization` prior art.
-
-```csharp
-public sealed class ApplicationTestProfile : ICompositionProfile
-{
- public void Configure(CompositionBuilder builder)
- {
- builder
- .UseNSubstitute()
- .UseBogus(options => options.Locale = "en_US")
- .Register(_ => new FakeClock(...));
- }
-}
-```
-
-`builder.AddProfile()` (where `TProfile : ICompositionProfile, new()`) or
-`builder.AddProfile(ICompositionProfile profile)` runs `Configure` **immediately**,
-synchronously, against the same shared `CompositionBuilder` a direct call would use
-— a profile is a named, reusable grouping over the builder's ordinary surface, not a
-distinct configuration mechanism. Multiple profiles combine purely through call
-order (`AddProfile().AddProfile()`); there is no separate merge step. A
-profile calling `AddProfile` for another profile already being applied
-(`ProfileA → ProfileB → ProfileA`) is a build-time
-`CompositionConfigurationException` naming the cycle — detected via a type-keyed
-stack pushed around `Configure`, mirroring the shape of the engine's own
-active-construction-frame recursion check. Every accumulated registration/rule
-entry retains its full source chain (direct, or the nested profile types that
-applied it) so a conflict or cycle diagnostic can always name where each entry
-actually came from.
-
-## Registrations and Service Injection
-
-Resolved by [ADR-0019](adr/0019-registrations-and-service-provider-injection.md)
-(implemented, Milestone 3 Phase 1). `builder.Register(Func
-factory)` (plus a `Register(Func factory)` convenience overload) populates
-pipeline stage 3's exact-registration table. A duplicate registration for the same
-type — from any combination of direct calls and profiles — is a build-time
-`CompositionConfigurationException` naming every conflicting source; there is no
-last-wins override in Milestone 3.
-
-`ICompositionContext` gains a second, descriptor-less `Resolve()` overload
-alongside the existing descriptor-based one, for hand-written registration/
-configuration-rule factories that have no generated-plan position to describe. Each
-factory invocation gets its own manual-resolve invocation frame (pushed before the
-factory runs, popped in `finally` after it returns or throws); every descriptor-less
-`Resolve()` call made during that invocation shares and advances that frame's
-counter (`PathSegment.ManualResolve`, a call-sequence ordinal, never the requested
-type), while a nested factory invocation gets its own independent frame — defined in
-[ADR-0019](adr/0019-registrations-and-service-provider-injection.md), verified
-against [ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md)'s
-existing reproducibility contract without editing that ADR's `Accepted` text.
-
-Service injection — this milestone's headline new capability — is a fallback *inside*
-stage 3, not a new pipeline stage or a new public extensibility surface:
-`builder.UseServiceProvider(IServiceProvider provider)` stores the BCL's own
-`System.IServiceProvider` (no new package dependency for core `Compono`, since it
-ships in `System`, not a NuGet package). On a stage-3 registration miss, the
-configured provider is consulted before falling through to stage 4; `null` means
-unresolved, an exception the provider throws is authoritative (surfaced as
-`CompositionException` with the original preserved as `InnerException`, never
-swallowed), and a non-null result is checked assignable to the requested type before
-use. `Compono` never creates or disposes a scope — the caller owns the provider and
-its lifetime entirely. A richer `Microsoft.Extensions.DependencyInjection`-specific
-integration (`IServiceCollection` auto-registration, scoping, keyed services) is
-explicitly out of scope for core — a future optional package, not designed yet,
-would build on `UseServiceProvider` the same way `Compono.NSubstitute`/
-`Compono.Bogus` build on core's other extension points.
-
-## Configuration Rules
-
-Resolved by [ADR-0020](adr/0020-composition-configuration-rules.md) (implemented,
-Milestone 3 Phase 3). Two structurally different mechanisms share one public
-`builder.For()` DSL:
-
-- **Type and member value rules** (`.For().Use(...)`,
- `.For().Member(x => x.Y).Use(...)`) compile into small, internal,
- Compono-authored `ICompositionProvider` implementations registered into stage 4 —
- a user never implements `ICompositionProvider` directly. A member rule's matching
- identity is `(declaring type, member name)`, matched directly against the
- incoming request's `DeclaringType`/`Name` (Composition Requests, above, and
- [ADR-0020](adr/0020-composition-configuration-rules.md)) — never inferred from
- path state — with the rule's own key
- captured from the member-access expression at the point `.Member(...)` is called;
- a type rule matches any request for exactly that type (no assignability matching
- in Milestone 3). Member rules take precedence over type rules for the same
- effective request — specificity-based, not call-order-based. Two rules claiming
- the identical key is a build-time `CompositionConfigurationException`, the same as
- a duplicate registration.
-- **Collection-size configuration** (`builder.WithCollectionSize(n)`,
- `.For().Member(x => x.Y).WithCollectionSize(n)`) is **not** a stage-4 rule —
- it's immutable policy on `CompositionConfiguration`, queried directly by stage 7's
- collection dispatch. A single, **parameterless** `ICompositionContext.ResolveCollectionSize()`
- method (not a descriptor-taking overload — a generated collection plan's
- `Compose(ICompositionContext)` has no descriptor to pass; the context reads the
- current member's declaring type/name off the already-expanded internal request
- it's still resolving — the same `DeclaringType` field member value-rule matching
- uses, correctly base-aware for an inherited required member, not a
- separately-derived parent-path-node type) — used identically by root-level and
- member-scoped collection plans — replaces
- [ADR-0013](adr/0013-collection-generation-semantics.md)'s previously-hardcoded
- default of `3`. This parameterizes ADR-0013's constant without reopening its
- retry/uniqueness/ordering semantics.
-
-## Deterministic Randomness
-
-The root context owns the seed.
-
-Random sources should be forkable by stable keys:
-
-```text
-root seed
-└── test parameter: command
- └── Customer
- └── Email
-```
-
-This reduces accidental changes when unrelated members are added elsewhere in a graph.
-
-Resolved by [ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md):
-`CompositionPath` is a chain of structured `PathSegment`s — not just
-types — so two constructor parameters or members of the same type
-(`Customer(string FirstName, string LastName)`) fork independently
-instead of colliding on an identical key. Forking hashes the structured
-segment data directly (a per-kind tag plus its `Ordinal`/index — a
-constructor parameter's position in the selected constructor, or a
-required member's generator-assigned declaration-order index; never
-`Name`, which exists on the segment for diagnostic display only) via
-FNV-1a, never a formatted display string, which is what makes the fork
-key collision-free by construction rather than by careful
-string-escaping. This is a reproducibility *contract*, not an
-implementation detail: renaming a constructor parameter or required
-member (with no reordering) never changes its derived value — only
-reordering does.
-That structured state feeds a small Compono-owned PRNG (not
-`System.Random`), so the byte-for-byte output sequence is something
-Compono controls rather than an inherited BCL implementation detail. The
-stability guarantee is explicit: the same seed produces the same output
-for a given `Compono` package version — cross-version stability across a
-`Compono` upgrade is not promised.
-
-`CreateMany(count)` derives each item's independent root seed by
-forking the batch's root seed through a stable `"CreateMany"` key, then
-by the item's index — so item `i`'s output depends only on the batch root
-and `i`, never on `count`: items 0–2 of `CreateMany(3)` and
-`CreateMany(10)` (same root seed) are byte-for-byte identical.
-
-## Diagnostics
-
-Diagnostics should track:
-
-- Root request
-- Current request path
-- Provider decisions
-- Selected plan
-- Constructor selection
-- Scope reuse
-- Registration matches
-- Seed
-- Failure reason
-- Suggested remediation
-
-Example:
-
-```text
-Unable to compose CreateOrderHandler.
-
-CreateOrderHandler
-└── IOrderProcessor processor
- └── OrderValidator validator
- └── IRuleProvider rules
-
-No registration, semantic provider, test-double provider,
-built-in provider, or generated plan could satisfy IRuleProvider.
-
-Seed: 8492173
-```
-
-Per [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md),
-this level of detail is designed to cost as little as possible on the
-normal successful path — "near-zero-allocation on success, not
-zero-cost," in the ADR's own words: a context-owned, reusable,
-array-backed trace buffer (`CompositionTraceBuffer`) records a compact
-struct (`ProviderAttempt`: stage, provider type, outcome — no strings, no
-per-append allocation) per stage attempt, and rewinds on success instead
-of retaining anything. Only a failing request materializes its slice of
-that buffer into the durable `CompositionDiagnostic` above
-(`exception.Diagnostic`, `docs/public-api.md`'s Diagnostics API), before
-the buffer unwinds further. `ProviderAttempt.Provider` is the concrete
-`ICompositionProvider` type that made the attempt (`null` for a
-context-owned stage, which isn't a provider instance at all) —
-[ADR-0016](adr/0016-provider-identity-restored-in-provider-attempt.md)
-restores this identity field after
-[ADR-0015](adr/0015-provider-identity-deferred-in-provider-attempt.md)
-deferred it on a premise (no stage has more than one provider) that was
-already false for stage 7's three built-in providers.
-
-`CompositionTraceBuffer` itself is not literally zero-allocation, though:
-its backing `ProviderAttempt[32]` array is allocated once per root
-`CompositionContext`, measured directly at **~536 B per instance** (32
-entries; capacity bumped from an original 16 after a second PR #13
-review round, and each entry's own size roughly doubled after a fourth
-round restored `ProviderAttempt.Provider` —
-[ADR-0016](adr/0016-provider-identity-restored-in-provider-attempt.md))
-— see this page's Open Architectural Decisions entry below for the
-precise breakdown and why pooling it entirely is deferred rather than
-fixed as a same-PR change.
-
-Confirmed via `Compono.Benchmarks`' `ResolutionBenchmarks` (Milestone 2
-Phase 4, full `DefaultJob` numbers in [`docs/performance.md`](performance.md)):
-composing the `Customer`/`Address` representative graph allocates ~2.71 KB
-total (of which the trace buffer's ~536 B is ~20%) regardless of the
-trace buffer's presence, and `CreateMany(count)` scales linearly with
-`count` (10.18× allocation at `count=10`, 101.48× at `count=100`, against
-a `count=1` baseline) — no super-linear growth from checkpoint/rewind
-bookkeeping. That graph is only 2 levels deep, though — never deep enough
-to trigger `CompositionTraceBuffer`'s own growth path (each active
-ancestor frame retains ~6 entries until its own child returns, so a
-32-entry buffer holds ~5 levels before resizing); `docs/performance.md`'s
-"Deep graph result" measures an 8-level-deep graph that does trigger a
-real `Array.Resize`, rather than only benchmarking the shallow case. No
-fallback to shallow diagnostics was needed.
-
-## Package Boundaries
-
-### Compono
-
-Owns:
-
-- Composition context
-- Runtime engine
-- Requests and results
-- Provider contracts
-- Scopes
-- Profiles
-- Registrations
-- Deterministic random
-- Built-in providers
-- Diagnostics
-- Generated-plan contracts
-
-### Compono.Generators
-
-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
-
-### Compono.XunitV3
-
-Design: [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md)
-(the `CompositionRow` entry point this package builds on, owned by core
-`Compono`), [ADR-0022](adr/0022-compono-xunit-package-design.md) (this
-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. 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:
-
-- xUnit v3 data integration
-- Per-row composition contexts
-- Inline value precedence
-- Parameter attributes
-- Seed reporting
-- Profile selection
-
-### 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:
-
-- `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
-
-Design: [ADR-0026](adr/0026-deterministic-seed-derivation-for-providers.md) (the
-core `ICompositionContext.DeriveSeed()` capability this package builds on, owned
-by core `Compono` — **implemented, PLAN-0006 Phase 0**), [ADR-0027](adr/0027-compono-bogus-package-design.md)
-(this package itself — **implemented, PLAN-0006 Phase 1**; build-verified only,
-test coverage/end-to-end verification still pending Phase 3), [ADR-0028](adr/0028-configurable-bogus-member-name-conventions.md)
-(configurable member-name conventions — aliases and custom exact-name
-conventions on top of ADR-0027's fixed allowlist; a new ADR, not an amendment
-to ADR-0027 — **implemented, PLAN-0006 Phase 2**; build-verified only, test
-coverage still pending Phase 3) — see
-[PLAN-0006](plans/0006-milestone-6-bogus-integration.md) for the phase-by-phase
-account.
-
-Owns:
-
-- `BogusMemberNameProvider` — the stage-5 semantic value provider (registered
- via `AddSemanticProvider`), matching an exact-match, `string`-typed lookup
- merging the conservative built-in allowlist (`FirstName`/`Email`/etc.,
- ADR-0027) with any consumer-configured aliases/custom conventions
- (ADR-0028)
-- `BogusOptions` — `Locale`, `EnableMemberNameConventions`,
- `AddAlias(string, BogusConvention)`/`AddConvention(string, Func)`
- (ADR-0028) — both validated eagerly against the same call's own already-
- configured entries and the built-in allowlist, scoped to a single
- `UseBogus(...)` call (no cross-call/cross-profile detection, see
- ADR-0028's Non-Goals)
-- `BogusConvention` — a closed public enum identifying each built-in
- convention, for `AddAlias`'s own target parameter (ADR-0028)
-- `CompositionBuilderExtensions.UseBogus()`/`UseBogus(Action)`/
- `UseBogus(Action>)`/`UseBogus(string, Action>)`
- — the last two are purely ergonomic sugar over the existing `Register`
- registration mechanism (stage 3): no hidden pipeline stage, no special
- runtime behavior of their own
-- `MemberRuleExtensions.UseBogus(Func, string)` — sugar over the
- existing `.For().Member(...).Use(...)` stage-4 rule mechanism
-
-Correlated values are satisfied by Bogus's own `Faker` (the whole-object
-`UseBogus()` model above), not a separate Compono-native member-dependency
-mechanism — `.DependsOn(...)` is explicitly deferred (ADR-0027). Coexists with
-`Compono.NSubstitute` with zero reference between the two packages in either
-direction: `BogusMemberNameProvider` only ever claims `string`-typed members,
-`NSubstituteProvider` only ever claims interface/delegate/abstract-class
-requests — disjoint by construction, unaffected by ADR-0028 (aliases/custom
-conventions are `string`-only too).
-
-## Package Dependency Diagram
-
-```text
- Compono.Generators
- (netstandard2.0, IsPackable=false,
- never independently published — see
- ADR-0003)
- |
- | ProjectReference,
- | OutputItemType="Analyzer"
- v
- Compono
- (core engine, no
- dependency on any
- integration package;
- packs Compono.Generators'
- output into its own nupkg
- under analyzers/dotnet/cs)
- ^
- |
- +------------------+------------------+
- | | |
- Compono.XunitV3 Compono.NSubstitute Compono.Bogus
- | | |
- xunit.v3 NSubstitute Bogus
-```
-
-- `Compono` depends on nothing else *published* in this diagram — every
- arrow from an integration package points *into* it, never out, per the
- "core package must not know about integrations" rule
- (`design-decisions.md` rule 3). Its build-time-only relationship to
- `Compono.Generators` is a different kind of dependency (analyzer, not a
- normal reference) and doesn't violate that rule — see
- [ADR-0003](adr/0003-generator-package-distribution.md).
-- Each integration package depends on `Compono` plus exactly one
- third-party library (`xunit.v3`, `NSubstitute`, or `Bogus`). Integration
- packages don't depend on each other.
-- `Compono.Generators` is never published to NuGet on its own — its
- compiled output is packed directly into the `Compono` nupkg, so from a
- *consumer's* point of view it doesn't exist as a separate dependency at
- all ([ADR-0003](adr/0003-generator-package-distribution.md)).
-
-## Open Architectural Decisions
-
-- ~~Runtime reflection policy~~ — default direction resolved by
- [ADR-0001](adr/0001-source-generation-first.md); the exact opt-in
- mechanism for a future compatibility mode is still open.
-- ~~Whether generated plans are required for external types~~ — resolved
- by [ADR-0004](adr/0004-composition-plan-discovery-and-dispatch.md):
- external/library types are fully supported via the `PlanCache`
- registry dispatch mechanism, not required to be `partial` or
- Compono-owned.
-- ~~Sync versus async provider contracts~~ — resolved by
- [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md)
- (carried forward from the now-superseded ADR-0007): synchronous, with
- any future async need getting a distinct opt-in contract.
-- ~~Public versus internal visibility of the core engine types~~ —
- resolved by
- [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md):
- `CompositionRequest`, `ICompositionProvider`, `CompositionResult`, and
- `IRandomSource` are `internal` in Milestone 2; `CompositionRequestDescriptor`,
- `CompositionRequestKind`, and `ICompositionContext` are `public`, since
- they're the generated-code call surface every plan crosses the assembly
- boundary to use. [ADR-0014](adr/0014-generator-emitted-collection-plans.md)
- extends that same surface for generated collection plans specifically:
- `CollectionPlanCache` and `UniqueValueResolver` are also `public` for
- the identical reason, not a discretionary API design choice.
-- Public versus internal use of `Type`
-- ~~Exact profile model~~ — resolved by
- [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, 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 — **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 for both stages: stage 6 in the
- Resolution Pipeline table above is populated whenever a consumer calls
- `UseNSubstitute()`; stage 5 is populated whenever a consumer calls
- `UseBogus()` (`Compono.Bogus`, [ADR-0027](adr/0027-compono-bogus-package-design.md),
- built on [ADR-0026](adr/0026-deterministic-seed-derivation-for-providers.md)'s
- `ICompositionContext.DeriveSeed()` capability — **implemented, PLAN-0006
- Phase 1**, test coverage/end-to-end verification still pending Phase 3).
- Tracked by [PLAN-0006](plans/0006-milestone-6-bogus-integration.md).
-- **Richer `Microsoft.Extensions.DependencyInjection` integration** (`IServiceCollection`
- auto-registration, per-composition scoping, keyed services) — explicitly out of
- scope for `Compono` core per
- [ADR-0019](adr/0019-registrations-and-service-provider-injection.md); core only
- supports the BCL's own `System.IServiceProvider` via `UseServiceProvider(...)`. A
- future optional package could build MEDI-specific ergonomics on top of that
- extension point, but isn't designed here — no concrete requirement has motivated
- one yet.
-- ~~Scope lifetime model~~ — resolved for Milestone 2 by
- [ADR-0011](adr/0011-composition-scope-shared-values-and-recursion-detection.md)
- (carried forward from the now-superseded ADR-0008): one scope per root
- composition operation, type-keyed sharing only.
-- ~~Recursion detection timing~~ — resolved by
- [ADR-0011](adr/0011-composition-scope-shared-values-and-recursion-detection.md):
- checked only immediately before generated-plan dispatch, via a distinct
- active-construction-frame stack, after explicit/shared/registration
- stages have had a chance to terminate the graph.
-- ~~Constructor selection rules~~ — resolved by
- [ADR-0002](adr/0002-constructor-selection-algorithm.md).
-- ~~Composition path identity for random forking~~ — resolved by
- [ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md):
- structured `PathSegment`s (constructor parameter/member name,
- collection index, dictionary key/value role), not a type-only chain.
-- ~~Stability guarantees for deterministic output~~ — resolved by
- [ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md)
- (carried forward from the now-superseded ADR-0009): same seed/same
- output within a `Compono` version, not guaranteed across versions.
-- ~~`CreateMany` seed derivation~~ — resolved by
- [ADR-0012](adr/0012-composition-path-identity-and-deterministic-random-forking.md):
- each item forks from the batch root seed by a stable `"CreateMany"` +
- index key, stable regardless of the requested `count`.
-- ~~Collection generation semantics (default size, key uniqueness,
- ordering guarantees)~~ — resolved by
- [ADR-0013](adr/0013-collection-generation-semantics.md).
-- ~~Whether source-generation contracts live in `Compono` or
- `Compono.Generators`~~ — resolved by
- [ADR-0003](adr/0003-generator-package-distribution.md).
-- **Cross-assembly plan-cache collision** — `PlanCache` (ADR-0004) and
- `CollectionPlanCache` (ADR-0014) both register via
- an unconditional `Instance = new ...Plan()` in a generated module
- initializer; if two different consuming assemblies loaded into the same
- process both discover a generated plan for the exact same closed type
- (most plausible for `CollectionPlanCache`, since a BCL collection
- type like `List` is exactly the kind of type two independently
- compiled assemblies could both legitimately reach if they share a
- library type), whichever assembly's module initializer runs last wins
- silently — module initializer order across assemblies isn't something
- either `PlanCache` or `CollectionPlanCache` controls or detects.
- Flagged during PR #11 review as a `CollectionPlanCache` concern, but
- it's actually a `PlanCache`-level property unchanged since Milestone
- 1 (ADR-0004) that `CollectionPlanCache` deliberately mirrors, not a
- new defect Milestone 2 introduced — deferred as a class-of-problem
- design question (assembly-qualified keys? last-wins-with-a-diagnostic?
- something else?) affecting both caches uniformly, not patched narrowly
- into just the newer one. Revisit if/when a real multi-assembly
- collision is actually hit — no design has been chosen yet.
-- **`CollectionPlanCache` rooting a collectible `AssemblyLoadContext`**
- — flagged during PR #11 review. For an ordinary composable type
- (`PlanCache`), if `Customer` is defined in a collectible ALC,
- the CLR ties the closed generic instantiation `PlanCache`
- itself to that same collectible context (a closed generic's home
- context is the narrowest context spanned by its generic definition and
- all of its type arguments), so the static field disappears when the ALC
- unloads — no external root survives it. `CollectionPlanCache` breaks
- this for a collection whose type arguments are *entirely* BCL types
- (`List`, `Dictionary`): every type composing
- that closed `T` lives in the non-collectible default context, so
- `CollectionPlanCache>`'s instantiation also lives there — but
- its generated `[ModuleInitializer]`, running from the collectible
- consumer assembly, still stores an instance of a plan class *defined in
- that consumer assembly* into it. The default-context static field then
- permanently roots the consumer assembly (and its whole ALC), the same
- leak class the `EnumValueProvider` cache fix (`ConditionalWeakTable` in place of `ConcurrentDictionary`) closed
- elsewhere. That fix doesn't transfer here: `CollectionPlanCache.Instance`
- is a plain closed-generic static field precisely so stage 7 dispatch is
- one direct field read (ADR-0004's zero-overhead dispatch), not a
- `Type`-keyed lookup; any weak-reference indirection able to key off the
- consumer assembly/ALC instead of `T` would reintroduce a per-resolve
- lookup on every collection, undoing the reason `CollectionPlanCache`
- mirrors `PlanCache`'s shape in the first place. Deferred, consistent
- with the cross-assembly-collision item above: this only manifests for a
- collectible `AssemblyLoadContext` unloading a consumer assembly that
- composes a BCL-only-typed collection, which neither `docs/mvp.md`'s
- scope nor Compono's primary xUnit-test-runner consumer currently
- exercises. Revisit alongside the collision item if collectible-ALC
- hosting becomes an actual target — no design has been chosen yet.
-- **`CompositionTraceBuffer`'s own array allocation, per root operation**
- — flagged during PR #13 review: `CompositionTraceBuffer`'s backing
- `ProviderAttempt[]` array (allocated eagerly in its constructor, one
- instance per `CompositionContext`) is a genuine, unconditional
- allocation on every `Create()`/`CreateMany()` item, not literally
- zero — measured directly (isolated from the rest of a real composition)
- at ~536 B per `CompositionTraceBuffer` instance (32-entry initial
- capacity), ~20% of a real `Customer`/`Address` representative
- composition's ~2.71 KB total (`ResolutionBenchmarks`, `docs/performance.md`)
- — consistent with [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md)'s
- explicit "near-zero-allocation on success, not zero-cost" framing, not
- a violation of it, but real enough that this page and `docs/performance.md`
- now state the precise figure instead of an unqualified "allocation-free"
- claim. (The jump from an earlier-measured ~280 B is itself real, not a
- re-measurement artifact — a fourth PR #13 review round restored
- `ProviderAttempt.Provider`, per
- [ADR-0016](adr/0016-provider-identity-restored-in-provider-attempt.md),
- roughly doubling each trace entry's size; `docs/performance.md` records
- that as an accepted tradeoff, not a regression.)
-
- A second PR #13 review round found the *growth* path is real too, not
- just the fixed initial allocation: each active ancestor frame dispatching
- through stage 8 or a collection plan retains ~6 trace entries (5 declined
- stages plus a `CompositionAttemptOutcome.Pending` marker) until its own
- child returns, so a composable-type chain more than ~5 levels deep
- exceeds a 32-entry buffer and triggers a real `Array.Resize` — the
- shallow, 2-level-deep `Customer` graph the original benchmark used never
- exercised this at all. Fixed in two parts: the initial capacity was
- bumped from 16 to 32 (covering `docs/architecture.md`'s own 4-level
- Diagnostics example without resizing), and `docs/performance.md`'s
- `DeepGraphBenchmarks` now measures an 8-level-deep chain that does
- trigger a resize, so the growth cost is a real recorded number, not an
- assumed-away one.
-
- A true zero-allocation design would still need the buffer pooled/reused
- across root operations (`CompositionContext` isn't currently pooled or
- reset-and-reused at all) — a real architecture change, not a
- same-PR-sized fix. Deferred: revisit if a future benchmark shows this
- mattering at a scale `docs/mvp.md`'s scope actually exercises — no
- design has been chosen yet.
+# Compono Architecture (moved)
+
+This page's content has moved to the docs site's **Architecture** section,
+per [ADR-0030 Amendment 2](adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+"one canonical home" principle — this file is a tombstone, not deleted
+outright, because ADRs that link to it by path must stay resolvable
+([ADR-0030](adr/0030-compono-documentation-architecture.md), and this
+repo's own ADR-immutability rule).
+
+See:
+
+- [architecture/index.md](architecture/index.md) — overview
+- [architecture/current/source-generation.md](architecture/current/source-generation.md)
+- [architecture/current/generated-plans-and-discovery.md](architecture/current/generated-plans-and-discovery.md)
+- [architecture/current/provider-pipeline.md](architecture/current/provider-pipeline.md)
+- [architecture/current/deterministic-seeding.md](architecture/current/deterministic-seeding.md)
+- [architecture/current/performance.md](architecture/current/performance.md)
+- [architecture/decision-log.md](architecture/decision-log.md)
diff --git a/docs/architecture/current/deterministic-seeding.md b/docs/architecture/current/deterministic-seeding.md
index f3b3f61..f56d8f4 100644
--- a/docs/architecture/current/deterministic-seeding.md
+++ b/docs/architecture/current/deterministic-seeding.md
@@ -1,10 +1,76 @@
# Deterministic Seeding
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../../plans/0008-milestone-8-public-preview.md)).
+Resolved by [ADR-0012](../../adr/0012-composition-path-identity-and-deterministic-random-forking.md)
+and [ADR-0026](../../adr/0026-deterministic-seed-derivation-for-providers.md).
+[Concepts: Determinism and Seeding](../../concepts/determinism-and-seeding.md)
+covers what "deterministic by design" means for a test author; this page
+is the derivation algorithm itself.
-The derivation algorithm itself (ADR-0012/ADR-0026) - the internals behind Concepts' conceptual page.
+## Path-derived forking
-See [Documentation Architecture](../../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+The root context owns the seed. Random sources are forkable by stable
+keys:
+
+```text
+root seed
+└── test parameter: command
+ └── Customer
+ └── Email
+```
+
+`CompositionPath` is a chain of structured `PathSegment`s — not just
+types — so two constructor parameters or members of the same type
+(`Customer(string FirstName, string LastName)`) fork independently
+instead of colliding on an identical key. Forking hashes the structured
+segment data directly (a per-kind tag plus its `Ordinal`/index — a
+constructor parameter's position in the selected constructor, or a
+required member's generator-assigned declaration-order index; never
+`Name`, which exists on the segment for diagnostic display only) via
+FNV-1a, never a formatted display string — this is what makes the fork
+key collision-free by construction rather than by careful
+string-escaping. This is a reproducibility *contract*, not an
+implementation detail: renaming a constructor parameter or required
+member (with no reordering) never changes its derived value — only
+reordering does.
+
+That structured state feeds a small Compono-owned PRNG (not
+`System.Random`), so the byte-for-byte output sequence is something
+Compono controls rather than an inherited BCL implementation detail. The
+stability guarantee is explicit: the same seed produces the same output
+for a given `Compono` package version — cross-version stability across a
+`Compono` upgrade is not promised.
+
+## `CreateMany` seed derivation
+
+`CreateMany(count)` derives each item's independent root seed by
+forking the batch's root seed through a stable `"CreateMany"` key, then
+by the item's index — so item `i`'s output depends only on the batch root
+and `i`, never on `count`. Items 0–2 of `CreateMany(3)` and
+`CreateMany(10)` (same root seed) are byte-for-byte identical.
+
+## `DeriveSeed()` for providers and registration factories
+
+A provider or registration/configuration-rule factory that needs its own
+deterministic randomness — `Compono.Bogus`'s `BogusMemberNameProvider`, or
+its `UseBogus(...)`/`UseBogus(...)` sugar — calls
+`context.DeriveSeed()`: an on-demand, path-derived `int` a public
+provider or factory can use for its own randomness, without exposing the
+engine's own internal `IRandomSource` or path representation. It reuses
+the same path-hash mechanism above internally, so it's just as
+reproducible as the engine's own built-in resolution.
+
+## Failure reporting
+
+A composition failure's message ends with `Seed: {value}`, matching the
+[Provider Pipeline](provider-pipeline.md#diagnostics)'s Diagnostics
+example — a successful row does not surface its seed anywhere by default,
+to keep passing-test output unchanged.
+`CompositionException.WithSeedInMessage(original, seed)` is a static
+factory for the one case where a `CompositionException` has no
+`Diagnostic` to render a seed line from on its own (e.g. a generated
+`HashSet`/`Dictionary` collection plan's unique-value-exhaustion
+failure): it returns a copy of `original` whose `Message` has the seed
+appended directly, with `original` preserved as `InnerException`.
+`Compono.XunitV3`'s own `[Compose]` binding algorithm uses this to
+guarantee every composition failure's message carries a pasteable seed,
+not only ones that happen to have a `Diagnostic`.
diff --git a/docs/architecture/current/generated-plans-and-discovery.md b/docs/architecture/current/generated-plans-and-discovery.md
index e7f5ce6..d9a6f02 100644
--- a/docs/architecture/current/generated-plans-and-discovery.md
+++ b/docs/architecture/current/generated-plans-and-discovery.md
@@ -1,10 +1,85 @@
# Generated Plans and Discovery
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../../plans/0008-milestone-8-public-preview.md)).
+Resolved by [ADR-0004](../../adr/0004-composition-plan-discovery-and-dispatch.md).
+This page covers how the generator decides a type needs a plan (see
+[Source Generation](source-generation.md) for what a plan itself looks
+like), and how `Create()` reaches it without reflection.
-What a generated plan is, how it's discovered and dispatched (ADR-0004).
+## Discovery
-See [Documentation Architecture](../../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+Discovery walks `Create()`/`CreateMany()` call sites and their
+types' transitive constructor parameters. `[Composable]` is an opt-in
+marker for a type with no local call site — applied directly to a type
+this compilation owns, or at assembly level
+(`[assembly: Composable(typeof(SomeType))]`) for a type in a referenced
+assembly that can't be annotated directly. Both forms are equivalent
+plan-generation requests, deduplicated alongside call-site discovery.
+
+## Dispatch
+
+"Registering the plan with the runtime" means a generated module
+initializer populates a closed-generic static field
+(`PlanCache.Instance = ...`) that `Create()` reads directly —
+**not** a `typeof(T)`-keyed dictionary lookup. This is a zero-overhead
+dispatch mechanism: no hashing, no lookup, just a direct field read.
+[ADR-0014](../../adr/0014-generator-emitted-collection-plans.md) extends
+the same mechanism to the five built-in collection shapes (array,
+`List`, `IReadOnlyList`, `HashSet`, `Dictionary`)
+via a parallel `CollectionPlanCache`.
+
+A generated plan never redispatches into itself directly — each
+`context.Resolve(descriptor)` call it makes is a fresh pipeline
+evaluation for whatever type that member actually is, not a recursive
+call back into the same plan. A genuinely self-referencing type (e.g. a
+`Node` with a `Node` property) only becomes a problem if nothing earlier
+in the pipeline (an explicit value, a shared value, a registration)
+terminates it before generated-plan dispatch is reached a second time for
+the same type while the first invocation is still on the stack — see
+[The Provider Pipeline](provider-pipeline.md#recursion-detection).
+
+## Open questions
+
+**Cross-assembly plan-cache collision.** `PlanCache` and
+`CollectionPlanCache` both register via an unconditional
+`Instance = new ...Plan()` in a generated module initializer. If two
+different consuming assemblies loaded into the same process both
+discover a generated plan for the exact same closed type — most
+plausible for `CollectionPlanCache`, since a BCL collection type like
+`List` is exactly the kind of type two independently compiled
+assemblies could both legitimately reach if they share a library type —
+whichever assembly's module initializer runs last wins silently: module
+initializer order across assemblies isn't something either cache
+controls or detects. This is a `PlanCache`-level property unchanged
+since [ADR-0004](../../adr/0004-composition-plan-discovery-and-dispatch.md),
+not a new defect `CollectionPlanCache` introduced — deferred as a
+class-of-problem design question (assembly-qualified keys? last-wins-
+with-a-diagnostic? something else?) affecting both caches uniformly, not
+patched narrowly into just the newer one. Revisit if/when a real
+multi-assembly collision is actually hit — no design has been chosen yet.
+
+**`CollectionPlanCache` rooting a collectible `AssemblyLoadContext`.**
+For an ordinary composable type (`PlanCache`), if `Customer` is
+defined in a collectible ALC, the CLR ties the closed generic
+instantiation `PlanCache` itself to that same collectible
+context (a closed generic's home context is the narrowest context
+spanned by its generic definition and all of its type arguments), so the
+static field disappears when the ALC unloads — no external root survives
+it. `CollectionPlanCache` breaks this for a collection whose type
+arguments are *entirely* BCL types (`List`,
+`Dictionary`): every type composing that closed `T`
+lives in the non-collectible default context, so
+`CollectionPlanCache>`'s instantiation also lives there — but
+its generated `[ModuleInitializer]`, running from the collectible
+consumer assembly, still stores an instance of a plan class *defined in
+that consumer assembly* into it. The default-context static field then
+permanently roots the consumer assembly (and its whole ALC) for the
+process's lifetime. Any weak-reference indirection able to key off the
+consumer assembly/ALC instead of `T` would reintroduce a per-resolve
+lookup on every collection, undoing the reason `CollectionPlanCache`
+mirrors `PlanCache`'s shape in the first place. Deferred, consistent
+with the collision item above: this only manifests for a collectible
+`AssemblyLoadContext` unloading a consumer assembly that composes a
+BCL-only-typed collection, which neither `docs/mvp.md`'s scope nor
+Compono's primary xUnit-test-runner consumer currently exercises. Revisit
+alongside the collision item if collectible-ALC hosting becomes an actual
+target — no design has been chosen yet.
diff --git a/docs/architecture/current/performance.md b/docs/architecture/current/performance.md
index 08e92a2..5df8792 100644
--- a/docs/architecture/current/performance.md
+++ b/docs/architecture/current/performance.md
@@ -1,10 +1,264 @@
# Performance
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../../plans/0008-milestone-8-public-preview.md)).
+This page documents what Compono's benchmark suite measures and what the
+results actually say, per [ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+redesigned suite — capability-oriented, not a history of milestone-by-
+milestone optimization work. Per
+[ADR-0030 Amendment 2](../../adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+benchmark-claims policy, the numbers below are published so a reader can
+independently evaluate them — this page makes claims only about Compono
+itself, never a comparative headline like "faster than AutoFixture."
-Methodology, caveats, and how to reproduce Compono's benchmark results. Moves here from `docs/performance.md`.
+Every current table on this page — Consumer Scenarios, External
+Comparison, Feature Overhead, Scalability, Source Generation — comes from
+one full suite run (see Methodology below); none mixes results from
+different runs. The one exception is explicitly marked: the Feature
+Overhead section's `UseBogus()` discussion cites historical, pre-fix
+figures alongside the current ones, because that history is the record
+of a real engineering finding, not a live result.
-See [Documentation Architecture](../../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+## What Compono optimizes for
+
+Compono is source-generated by default
+([Design Principles](../design-principles.md),
+[ADR-0001](../../adr/0001-source-generation-first.md)) so that composing
+a type at runtime dispatches to generated code rather than reflecting
+over it. That buys predictable, generated-path performance and minimal
+allocations on the construction-dispatch path itself — but the full
+resolution pipeline (provider dispatch, deterministic random forking,
+collection generation, diagnostics tracing) is real, non-zero work on
+top of that dispatch, and this page reports both honestly, per
+[ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+"engineering questions first" philosophy.
+
+## What the benchmarks measure (and don't)
+
+`BenchmarkDotNet` benchmarks answer narrow, comparative engineering
+questions — "does approach A cost more than approach B, for one
+operation, isolated from everything else" — under an artificially clean,
+JIT-warmed, GC-isolated environment. They are **not** a substitute for
+full-application performance testing, a scalability/load test, or a
+guarantee about any specific consumer's real-world numbers. Every
+benchmark in this suite ties to one of four goals, per
+[ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+Amendment: the runtime cost of a specific Compono feature, Compono's
+scalability, Compono's build-time cost, or the migration experience from
+AutoFixture — a benchmark that can't be tied to one of those doesn't
+belong in the suite. Five categories, each answering a different
+question for a different audience — full detail in [ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md):
+
+- **Consumer Scenarios** — what performance should a user expect
+ composing each representative model in a realistic application?
+- **External Comparison** — what should a developer expect migrating
+ from AutoFixture?
+- **Feature Overhead** — how expensive is one specific mechanism
+ (a member rule, a type rule, a custom provider, `UseBogus()`,
+ `UseNSubstitute()`, `[Shared]`'s underlying row-sharing mechanism),
+ isolated from everything else?
+- **Scalability** — does cost grow linearly or super-linearly as batch
+ size, graph depth, or collection size increases?
+- **Source Generation** — clean vs. incremental generator cost, a
+ build-time concern entirely separate from runtime performance.
+
+## Methodology
+
+Recorded with `BenchmarkDotNet` v0.15.8, Apple M3 Max, macOS Tahoe 26.6,
+.NET 10.0.3 arm64 RyuJIT, Release configuration, `DefaultJob`. Concretely,
+`DefaultJob` means: a pilot stage that determines how many iterations a
+run needs, a warmup phase, then a set of measured iterations — each
+launched in its own isolated, managed process, not measured in-process
+alongside the benchmark harness. Per
+[ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+Reporting Rules, every table below reports the full mandatory column set:
+Mean, Error, StdDev, Allocated, and `Gen0`/`Gen1` wherever BenchmarkDotNet
+reports them as nonzero, plus a Ratio column wherever the category has a
+designated baseline. A page that reported Mean alone, or Mean and
+Allocated alone, would not meet that bar — this page doesn't. Full
+detail — every category's complete result set, raw CSV/HTML exports — is
+in `benchmarks/Compono.Benchmarks`'s `BenchmarkDotNet.Artifacts/results/`
+after a real run (see Reproducing, below).
+
+## Consumer-facing results
+
+**Representative models** (`Composer.Create()`, no comparison
+baseline — the absolute cost a consumer actually pays):
+
+| Model | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
+|---|---:|---:|---:|---:|---:|---:|
+| `SimplePoco` (flat, no dependencies) | 368.3 ns | 3.17 ns | 2.65 ns | 0.2084 | 0.0005 | 1.70 KB |
+| `MediumAggregate` (nested dependency + collection) | 981.1 ns | 8.21 ns | 6.86 ns | 0.3529 | - | 2.88 KB |
+| `DeepGraph` (8-level chain) | 1,016.2 ns | 10.46 ns | 8.73 ns | 0.4330 | 0.0019 | 3.54 KB |
+| `LargeCollection` (100-element collection) | 8,615.9 ns | 50.54 ns | 44.80 ns | 2.5482 | 0.0458 | 20.86 KB |
+
+**Migrating from AutoFixture.** "Equivalent work" means both frameworks
+compose the same object graph shape, fill the same number of fields, and
+(for Compono's side) use its own real default value-generation cost — an
+8-character string, a 3-element collection — not a stripped-down or
+otherwise favorable graph for either side. Every Ratio/Alloc Ratio value
+below is **AutoFixture relative to Compono** (Compono is always the
+baseline, `1.00×`):
+
+| Model | Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+|---|---|---:|---:|---:|---:|---:|---:|---:|---:|
+| `SimplePoco` | Compono (baseline) | 378.2 ns | 6.30 ns | 8.83 ns | 1.00× | 0.2084 | 0.0005 | 1.70 KB | 1.00× |
+| `SimplePoco` | AutoFixture | 24,036.9 ns | 466.53 ns | 572.94 ns | 63.6× | 3.6621 | - | 29.96 KB | 17.6× |
+| `MediumAggregate` | Compono (baseline) | 962.0 ns | 15.68 ns | 13.90 ns | 1.00× | 0.3529 | 0.0010 | 2.88 KB | 1.00× |
+| `MediumAggregate` | AutoFixture | 77,990.7 ns | 1,543.97 ns | 3,389.05 ns | 81.1× | 11.7188 | - | 99.21 KB | 34.4× |
+
+AutoFixture is doing substantially more runtime work here (reflection-
+based construction plus its own randomized-value-generation pipeline) —
+this is a recognizable reference point for what migrating changes, not a
+target Compono is trying to "beat."
+
+**Provider-enabled profiles** (`Composer.Create()` with a package's
+provider active):
+
+| Scenario | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
+|---|---:|---:|---:|---:|---:|---:|
+| `UseNSubstitute()` (composing an interface member) | 1.235 μs | 0.0178 μs | 0.0166 μs | 0.8621 | 0.0114 | 7.05 KB |
+| `UseBogus()` (composing two convention-matching `string` members) | 5.870 μs | 0.1104 μs | 0.1033 μs | 0.8545 | 0.0076 | 7.04 KB |
+
+`UseBogus()`'s cost here reflects a fix applied after an earlier run of
+this suite found it substantially higher — see Feature Overhead below
+for the full account. The two numbers aren't measuring the same scope:
+this row is the full profile (two Bogus-backed members plus the rest of
+the `MediumAggregate` graph); Feature Overhead's `UseBogus()` row below
+isolates a single member's marginal cost against its cheapest
+alternative. Same underlying mechanism, different scope — expect the
+per-member number below to be smaller than this full-profile one.
+
+## Feature overhead
+
+Isolates one mechanism's marginal cost at a time (full detail: [ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)):
+
+| Mechanism | Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+|---|---|---:|---:|---:|---:|---:|---:|---:|---:|
+| Configuration rules | GeneratedOnly (baseline) | 962.2 ns | 10.61 ns | 9.40 ns | 1.00× | 0.3529 | - | 2.88 KB | 1.00× |
+| Configuration rules | + member rule | 1,208.9 ns | 17.65 ns | 15.65 ns | 1.26× | 0.4082 | 0.0019 | 3.34 KB | 1.16× |
+| Configuration rules | + type rule | 956.4 ns | 6.82 ns | 6.38 ns | 0.99× (noise) | 0.3586 | - | 2.93 KB | 1.02× |
+| Configuration rules | + custom `ICompositionValueProvider` | 1,312.8 ns | 12.24 ns | 10.85 ns | 1.36× | 0.4368 | 0.0019 | 3.58 KB | 1.24× |
+| `[Shared]` row-sharing | Without sharing (baseline) | 805.5 ns | 5.05 ns | 4.48 ns | 1.00× | 0.3042 | 0.0010 | 2.49 KB | 1.00× |
+| `[Shared]` row-sharing | With sharing | 742.7 ns | 6.20 ns | 5.80 ns | 0.92× (sharing is cheaper — avoids composing a second independent value) | 0.3109 | 0.0010 | 2.55 KB | 1.02× |
+| `UseNSubstitute()` | Registration (baseline) | 332.0 ns | 2.58 ns | 2.42 ns | 1.00× | 0.2027 | 0.0010 | 1.66 KB | 1.00× |
+| `UseNSubstitute()` | NSubstitute provider | 1,206.9 ns | 12.61 ns | 11.79 ns | 3.64× | 0.8698 | 0.0114 | 7.12 KB | 4.30× |
+| `UseBogus()` | Member rule (baseline) | 333.4 ns | 4.51 ns | 4.22 ns | 1.00× | 0.2027 | 0.0010 | 1.66 KB | 1.00× |
+| `UseBogus()` | Bogus convention provider | 2,254.2 ns | 15.50 ns | 13.74 ns | 6.76× | 0.4425 | - | 3.62 KB | 2.18× |
+
+**`UseBogus()`: a finding, a root cause, a fix, a confirmation.** An
+earlier run of this benchmark measured `UseBogus()` at ~865× a plain
+member rule (291.5 μs, isolated single-member measurement). That result
+was published in full, per
+[ADR-0034](../../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+publication rule — an unfavorable result is reported exactly like a
+favorable one. Investigation traced the cost to `BogusMemberNameProvider`
+constructing a new `Bogus.Faker` instance on every resolution
+(`src/Compono.Bogus/BogusMemberNameProvider.cs`); `Faker` construction,
+which builds out its full set of category generators, is genuinely
+expensive. The implementation was changed to cache one `Faker` per
+thread for built-in conventions, reseeding its `Random` immediately
+before every use, while a custom `AddConvention` delegate still gets its
+own single-use `Faker` (it could mutate state a shared instance
+shouldn't carry between requests) — see
+[ADR-0027's Amendment](../../adr/0027-compono-bogus-package-design.md#amendment-2026-08-05-bogusmembernameprovider-reuses-a-per-thread-faker-not-a-fresh-one-per-request)
+for the full account and the regression coverage backing it. Rerun after
+the fix, the benchmark confirms the result: ~865× dropped to ~6.76×
+(this table), and the full-profile Consumer Scenario cost dropped from
+903.4 μs / 2,229.31 KB to 5.870 μs / 7.04 KB (Consumer-facing results,
+above).
+
+## Scalability
+
+**Batch scaling** (`CreateMany(count)` against its own `Create()`
+baseline, per batch size):
+
+| Count | Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1 | Create (baseline) | 994.0 ns | 7.88 ns | 7.37 ns | 1.00× | 0.3529 | - | 2.88 KB | 1.00× |
+| 1 | CreateMany | 1,032.4 ns | 9.32 ns | 8.72 ns | 1.04× | 0.3681 | 0.0019 | 3.02 KB | 1.05× |
+| 10 | Create (baseline) | 994.0 ns | 8.53 ns | 7.56 ns | 1.00× | 0.3529 | - | 2.88 KB | 1.00× |
+| 10 | CreateMany | 10,329.1 ns | 126.09 ns | 117.94 ns | 10.39× | 3.5858 | 0.0610 | 29.31 KB | 10.17× |
+| 100 | Create (baseline) | 988.6 ns | 6.32 ns | 5.91 ns | 1.00× | 0.3529 | - | 2.88 KB | 1.00× |
+| 100 | CreateMany | 102,933.0 ns | 829.95 ns | 776.33 ns | 104.12× | 35.7666 | 4.7607 | 292.29 KB | 101.39× |
+| 1,000 | Create (baseline) | 994.4 ns | 10.48 ns | 9.81 ns | 1.00× | 0.3529 | - | 2.88 KB | 1.00× |
+| 1,000 | CreateMany | 1,180,425.4 ns | 7,238.77 ns | 6,771.15 ns | 1,187.19× | 359.3750 | 179.6875 | 2,943.90 KB | 1,021.19× |
+
+Scaling is linear through 100 items; at 1,000 items the ratio (1,187.19×
+against a 1,000× input-size increase) shows a modest, real super-linear
+component — `Gen1` collections start appearing at this scale (0 at
+`count=10`, ~180 at `count=1,000`) where they don't at smaller batches,
+consistent with GC promotion pressure rather than an algorithmic
+regression in the composition pipeline itself.
+
+**Graph depth** (`DeepLevel8` at depth 1 vs. `DeepGraph`'s chain at depth
+8 — both resolve exactly one `string` leaf value, so depth is the only
+variable; an earlier version of this benchmark compared against
+`MediumAggregate` instead, which resolves seven strings and a collection
+on top of its own object graph, conflating depth with total
+value-generation work):
+
+| Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|
+| Shallow (`DeepLevel8`, depth 1, baseline) | 240.4 ns | 4.79 ns | 4.00 ns | 1.00× | 0.1631 | 0.0005 | 1.34 KB | 1.00× |
+| Deep (`DeepGraph`, depth 8) | 1,074.5 ns | 12.74 ns | 11.29 ns | 4.47× | 0.4330 | 0.0019 | 3.54 KB | 2.65× |
+
+With depth isolated as the only variable, the real cost is clear: 4.47×
+the mean, 2.65× the allocation, for a chain 8× as deep — consistent with
+each additional level's own dispatch, path-segment, and diagnostics-
+trace-buffer bookkeeping (see
+[The Provider Pipeline](provider-pipeline.md#diagnostics)), including the
+real `Array.Resize` `DeepGraph`'s depth is enough to trigger in the trace
+buffer that `DeepLevel8` alone never reaches.
+
+**Collection size** (`WithCollectionSize(n)`, 3 to 200 elements):
+
+| CollectionSize | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
+|---:|---:|---:|---:|---:|---:|---:|
+| 3 | 517.6 ns | 4.59 ns | 3.83 ns | 0.2337 | 0.0010 | 1.91 KB |
+| 10 | 1,116.5 ns | 9.41 ns | 8.34 ns | 0.4005 | 0.0019 | 3.28 KB |
+| 50 | 4,552.5 ns | 24.15 ns | 22.59 ns | 1.3580 | 0.0153 | 11.09 KB |
+| 200 | 17,247.1 ns | 261.28 ns | 218.18 ns | 4.9438 | 0.1831 | 40.39 KB |
+
+Sub-linear relative to the 66.7× size increase (a ~33.3× time increase
+from 3 to 200 elements), since a fixed per-`Create` dispatch cost is
+amortized across more elements at larger sizes.
+
+## Source generation
+
+Clean vs. incremental generator cost, in-process via Roslyn's
+`GeneratorDriver` (a maintainer-facing, build-time concern, unrelated to
+every result above). The incremental compilation is derived from the
+clean one via `SyntaxTree.WithChangedText` with an append-only edit — not
+a second, independently-parsed tree swapped in — so unaffected nodes keep
+the identity they had in the base tree, which is what actually lets the
+generator's incremental pipeline skip recomputing work for call sites
+nothing changed, rather than measuring a wholesale reparse under an
+"incremental" label:
+
+| TypeCount | Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
+|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1 | CleanGeneration (baseline) | 55.58 μs | 0.156 μs | 0.122 μs | 1.00× | 12.2070 | 1.4648 | 103.46 KB | 1.00× |
+| 1 | IncrementalGeneration | 25.81 μs | 0.448 μs | 0.397 μs | 0.46× | 3.0518 | - | 25.88 KB | 0.25× |
+| 10 | CleanGeneration (baseline) | 230.53 μs | 1.614 μs | 1.431 μs | 1.00× | 72.2656 | 15.6250 | 600.10 KB | 1.00× |
+| 10 | IncrementalGeneration | 85.56 μs | 1.509 μs | 1.260 μs | 0.37× | 8.0566 | 0.3662 | 66.60 KB | 0.11× |
+| 50 | CleanGeneration (baseline) | 1,030.43 μs | 7.461 μs | 7.328 μs | 1.00× | 343.7500 | 109.3750 | 2,809.42 KB | 1.00× |
+| 50 | IncrementalGeneration | 364.52 μs | 7.273 μs | 8.084 μs | 0.35× | 29.2969 | 3.9063 | 247.04 KB | 0.09× |
+
+Incremental generation is consistently faster and allocates
+substantially less (0.09×–0.25× of clean generation's allocation) across
+every type count measured — confirming the generator's incremental
+caching genuinely avoids re-processing unrelated syntax on a small,
+unrelated source edit, rather than silently falling back to a full
+recompute.
+
+## Reproducing
+
+```
+dotnet run -c Release --project benchmarks/Compono.Benchmarks -f net10.0
+```
+
+`-c Release` is required — `BenchmarkDotNet` refuses to run a Debug
+build. Add `-- --filter "*ClassName*"` to run one category at a time (the
+full suite, across every category's parameter matrix, takes on the order
+of 15 minutes). Full per-category results (every method, every parameter
+value, raw CSV/HTML) are written to `BenchmarkDotNet.Artifacts/results/`
+relative to the working directory `dotnet run` was invoked from.
diff --git a/docs/architecture/current/provider-pipeline.md b/docs/architecture/current/provider-pipeline.md
index fc899df..861c754 100644
--- a/docs/architecture/current/provider-pipeline.md
+++ b/docs/architecture/current/provider-pipeline.md
@@ -1,10 +1,135 @@
# The Provider Pipeline
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../../plans/0008-milestone-8-public-preview.md)).
+Resolved by [ADR-0010](../../adr/0010-composition-request-pipeline-and-diagnostics-tracing.md).
+[Concepts: Providers](../../concepts/providers.md) covers what a provider
+is conceptually; this page answers "what order do providers execute,"
+at full depth.
-The actual stage order and how providers compose (ADR-0010) - what order providers execute, at full depth.
+## Resolution order
-See [Documentation Architecture](../../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+The default resolution order is fixed — not configurable, by users or by
+providers reordering themselves:
+
+| # | Stage | Kind |
+|---|---|---|
+| 1 | Explicit values | Context-owned deterministic check |
+| 2 | Shared or scoped values | Context-owned deterministic check against the scope. Any request (`[Shared]` or not) sees an already-shared value for its type on read; only an `IsShared` request ever populates scope on write. |
+| 3 | Exact registrations | **Hybrid**: a context-owned deterministic lookup against the exact-registration table, then — only on a miss, if `UseServiceProvider(...)` was configured — a fallback `IServiceProvider.GetService(typeof(T))` call. |
+| 4 | Configuration rules | Ordered `ICompositionProvider` collection populated by type/member value rules compiled from `builder.For()...`, whether reached directly or via a profile. |
+| 5 | Semantic value providers | Ordered `ICompositionProvider` collection. Public registration surface: `builder.AddSemanticProvider(ICompositionValueProvider)`. `Compono.Bogus`'s `BogusMemberNameProvider` is this stage's first real registrant. |
+| 6 | Test-double providers | Ordered `ICompositionProvider` collection. Public registration surface: `builder.AddTestDoubleProvider(ICompositionValueProvider)`. `Compono.NSubstitute`'s `NSubstituteProvider` is a real registrant. |
+| 7 | Built-in value providers | **Hybrid**: an ordered provider collection (primitives, enums, nullable value types) tried first, followed by a context-owned deterministic dispatch through `CollectionPlanCache` for the five built-in collection shapes. |
+| 8 | Generated composition plans | Context-owned deterministic dispatch via `PlanCache` — **not** an `ICompositionProvider`; see [Generated Plans and Discovery](generated-plans-and-discovery.md). |
+| 9 | Diagnostic failure | Context-owned terminal stage |
+
+Only stage 7 has anything registered *unconditionally*
+(`BuiltInProviders.Default`); every other stage is opt-in, populated only
+when a consumer actually calls `.For()` (stage 4), `UseNSubstitute()`
+(stage 6), `UseBogus()` (stage 5), or registers a hand-written provider
+directly. Provider order *within* an extensible stage is registration
+order — stage 7 alone holds three real providers
+(`PrimitiveValueProvider`, `EnumValueProvider`, `NullableValueProvider`),
+so "no stage has more than one provider" isn't true today. No *richer*
+ordering rule (priority, specificity) exists yet because these three
+providers claim disjoint type sets — a richer rule becomes a real
+question only once two providers could plausibly both claim the same
+type differently.
+
+## Providers
+
+Providers satisfy composition requests within one of the extensible
+pipeline stages above (4/5/6/7) — the context-owned stages (1/2/3/8/9)
+are not providers. A provider reports whether it did not apply
+(`NotHandled`) or successfully composed a value (`Success`); ordinary
+providers **cannot** report `Failure` — that's reserved for the
+context-owned authoritative stages (an exact registration whose factory
+throws, or generated-plan dispatch when a plan exists but fails or a
+recursion cycle is detected). The rule: `Failure` means "authoritative
+ownership was established, but resolution could not complete," never a
+stronger form of `NotHandled` — this is what stops a provider that merely
+can't produce *this* particular request from accidentally blocking a
+later stage that could have.
+
+**Public providers (stages 5/6).** Stages 4/7 are implemented entirely
+inside `Compono` and never exposed for an outside package to author its
+own. Stages 5/6 exist specifically for an integration package to
+contribute open-ended, pattern-matching logic ("any interface type"),
+resolved by
+[ADR-0024](../../adr/0024-public-provider-extensibility-model.md):
+
+```csharp
+public interface ICompositionValueProvider
+{
+ CompositionProviderResult TryProvide(
+ in CompositionProviderRequest request,
+ ICompositionContext context);
+}
+```
+
+`CompositionProviderRequest`/`CompositionProviderResult` are decoupled
+from the internal `CompositionRequest`/`CompositionResult` pair — no
+path, no shared-scope flag, no pipeline plumbing a provider author has no
+legitimate use for. Internally, each public provider is wrapped in a
+`PublicProviderAdapter : ICompositionProvider`, so the rest of the
+pipeline treats it exactly like an internal one, with diagnostics naming
+the real wrapped provider's type. A thrown exception from `TryProvide`
+propagates uncaught — same "exceptions signal a bug" principle as
+everywhere else in this pipeline.
+
+## Recursion detection
+
+A repeated *type* appearing twice in a graph (two sibling properties of
+the same type, or the same type reachable via two different paths) is
+ordinary graph shape, not a cycle. A genuine cycle is a type whose
+*construction* is still actively in progress when it's requested again.
+Resolved by
+[ADR-0011](../../adr/0011-composition-scope-shared-values-and-recursion-detection.md):
+`CompositionPath` records every request edge for diagnostics and random
+forking, while a distinct internal active-construction-frame stack is
+pushed only around structural construction (generated-plan dispatch,
+stage 8) and checked only there — after explicit values, shared/scoped
+values, and exact registrations have already had a chance to terminate
+the graph. A self-referencing type resolved by a registered or shared
+instance never touches the recursion mechanism at all; only an actual
+in-progress construction cycle does, and the resulting diagnostic reports
+the chain of active frames that formed the cycle, not just a list of
+repeated types.
+
+## Diagnostics
+
+Diagnostics track the root request, current request path, provider
+decisions, selected plan, constructor selection, scope reuse,
+registration matches, seed, failure reason, and suggested remediation:
+
+```text
+Unable to compose CreateOrderHandler.
+
+CreateOrderHandler
+└── IOrderProcessor processor
+ └── OrderValidator validator
+ └── IRuleProvider rules
+
+No registration, semantic provider, test-double provider,
+built-in provider, or generated plan could satisfy IRuleProvider.
+
+Seed: 8492173
+```
+
+This is designed to cost as little as possible on the normal successful
+path — "near-zero-allocation on success, not zero-cost": a context-owned,
+reusable, array-backed trace buffer (`CompositionTraceBuffer`) records a
+compact struct (`ProviderAttempt`: stage, provider type, outcome — no
+strings, no per-append allocation) per stage attempt, and rewinds on
+success instead of retaining anything. Only a failing request
+materializes its slice of that buffer into the durable
+`CompositionDiagnostic` (`exception.Diagnostic`) before the buffer
+unwinds further. `ProviderAttempt.Provider` is the concrete
+`ICompositionProvider` type that made the attempt (`null` for a
+context-owned stage, which isn't a provider instance at all) — see
+[Performance](performance.md) for the measured allocation cost of this
+mechanism.
+
+## Open questions
+
+**Public versus internal use of `Type`** in provider-facing contracts
+remains an open design question, not yet resolved by an ADR.
diff --git a/docs/architecture/current/source-generation.md b/docs/architecture/current/source-generation.md
index 112a4eb..5184d97 100644
--- a/docs/architecture/current/source-generation.md
+++ b/docs/architecture/current/source-generation.md
@@ -1,10 +1,89 @@
# Source Generation
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../../plans/0008-milestone-8-public-preview.md)).
+Source generation is Compono's preferred construction strategy, resolved
+by [ADR-0001](../../adr/0001-source-generation-first.md) — generated
+composition plans are the expected execution path, not an optimization
+layered on top of a reflection-based default. See
+[Design Principles](../design-principles.md) for why this matters
+philosophically; this page is *how* it actually works.
-Why generated-first, not reflection-first (ADR-0001).
+## What the generator does
-See [Documentation Architecture](../../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+For a constructible type, the generator identifies accessible
+constructors, primary constructors, required members, init-only members,
+nullability metadata, unsupported types, ambiguous construction paths, and
+cyclic compile-time dependencies where detectable — then emits a plan
+that selects the constructor, requests each argument, invokes the
+constructor directly, assigns required/configured members, and preserves
+nullability and member context.
+
+A generated plan looks like this, conceptually:
+
+```csharp
+internal sealed class CustomerCompositionPlan
+ : ICompositionPlan
+{
+ public Customer Compose(ICompositionContext context)
+ {
+ var firstName = context.Resolve(
+ new CompositionRequestDescriptor(
+ CompositionRequestKind.ConstructorParameter,
+ 0,
+ "firstName",
+ Nullability.NotNullable));
+
+ var lastName = context.Resolve(
+ new CompositionRequestDescriptor(
+ CompositionRequestKind.ConstructorParameter,
+ 1,
+ "lastName",
+ Nullability.NotNullable));
+
+ return new Customer(firstName, lastName);
+ }
+}
+```
+
+Generated code only ever calls `context.Resolve(descriptor)` per
+member — it never constructs a `CompositionRequest`, touches
+`CompositionPath`, or manages recursion state directly. The context owns
+all of that internally (see [The Provider Pipeline](provider-pipeline.md)),
+which is what makes incorrect path propagation structurally difficult
+rather than merely documented against.
+
+At runtime, the engine executes generated plans, resolves provider-backed
+values, manages scopes, manages deterministic random streams, tracks the
+composition path, and produces diagnostics — see
+[The Provider Pipeline](provider-pipeline.md) and
+[Deterministic Seeding](deterministic-seeding.md) for those pieces in
+depth, and [Generated Plans and Discovery](generated-plans-and-discovery.md)
+for how a plan is actually found and dispatched to.
+
+## Runtime reflection policy
+
+Runtime reflection is intentionally **not** part of the default
+architecture, and this remains an open decision — the exact opt-in
+mechanism for a future compatibility mode is still undecided. Three
+candidate approaches:
+
+**Generated plans required.** Composition fails when no generated plan
+exists. Predictable performance, strong trimming/AOT characteristics, a
+simple runtime model — but external or dynamically discovered types may
+need explicit support, and some test scenarios may be less convenient.
+
+**Automatic reflection fallback.** The runtime reflects when no generated
+plan exists. High compatibility and lower migration friction — but a more
+complex runtime, weaker AOT guarantees, less predictable performance, and
+reflection can hide real source-generation gaps instead of surfacing
+them.
+
+**Opt-in compatibility package or mode.** Reflection support is isolated
+from the default runtime. Keeps the core architecture clean, allows
+compatibility where necessary, and makes performance tradeoffs explicit —
+this is the current leading compromise, but it is **not yet an accepted
+decision**.
+
+Whichever direction is chosen, reflection must never silently become the
+fallback path — an explicit, compiler-visible opt-in (an MSBuild property
+or a dedicated compatibility package) is the baseline requirement any of
+the three candidates above already satisfies.
diff --git a/docs/architecture/decision-log.md b/docs/architecture/decision-log.md
index 0350ade..fbdb989 100644
--- a/docs/architecture/decision-log.md
+++ b/docs/architecture/decision-log.md
@@ -1,10 +1,49 @@
# Historical Decision Log
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../plans/0008-milestone-8-public-preview.md)).
+The public-facing index into [`docs/adr/`](../adr/README.md): every
+`Accepted`/`Superseded` Architecture Decision Record, one line each, for a
+reader who wants the full paper trail behind Compono's design. This is
+**not** a duplicate of `docs/adr/README.md` (the engineering-process
+index, which also tracks `Proposed` ADRs) — a status-filtered view of
+those still under discussion lives in
+[Roadmap: Proposed ADRs](../roadmap/proposed-adrs.md) instead. As this log
+grows across years of decisions, it stays a pure historical record —
+readers wanting "how it works today" belong in
+[Current Architecture](current/source-generation.md), not here.
-The public-facing index into `docs/adr/`: every Accepted/Superseded ADR, one line each, for a reader who wants the full paper trail.
-
-See [Documentation Architecture](../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+| ADR | Title | Status |
+|---|---|---|
+| [0001](../adr/0001-source-generation-first.md) | Source Generation First — generated composition plans, not reflection, are the default execution model | Accepted |
+| [0002](../adr/0002-constructor-selection-algorithm.md) | Constructor Selection Algorithm | Accepted |
+| [0003](../adr/0003-generator-package-distribution.md) | Generator Package Distribution — `Compono.Generators` packs into `Compono`'s own nupkg, never published independently | Accepted |
+| [0004](../adr/0004-composition-plan-discovery-and-dispatch.md) | Composition Plan Discovery and Dispatch | Accepted |
+| [0005](../adr/0005-generator-implementation-conventions.md) | Source Generator Implementation Conventions | Accepted |
+| [0006](../adr/0006-required-members-and-nullability-metadata.md) | Required Members and Nullability Metadata | Accepted |
+| [0007](../adr/0007-composition-request-and-provider-pipeline.md) | Composition Request and Provider Pipeline | Superseded by [ADR-0010](../adr/0010-composition-request-pipeline-and-diagnostics-tracing.md) |
+| [0008](../adr/0008-composition-scope-shared-values-and-recursion-detection.md) | Composition Scope, Shared Values, and Recursion Detection | Superseded by [ADR-0011](../adr/0011-composition-scope-shared-values-and-recursion-detection.md) |
+| [0009](../adr/0009-deterministic-seed-and-forkable-random-source.md) | Deterministic Seed and Forkable Random Source | Superseded by [ADR-0012](../adr/0012-composition-path-identity-and-deterministic-random-forking.md) |
+| [0010](../adr/0010-composition-request-pipeline-and-diagnostics-tracing.md) | Composition Request, Provider Pipeline, Failure Semantics, and Diagnostics Tracing | Accepted |
+| [0011](../adr/0011-composition-scope-shared-values-and-recursion-detection.md) | Composition Scope, Shared Values, and Recursion Detection | Accepted |
+| [0012](../adr/0012-composition-path-identity-and-deterministic-random-forking.md) | Composition Path Identity, Deterministic Random Forking, and CreateMany Seed Derivation | Accepted |
+| [0013](../adr/0013-collection-generation-semantics.md) | Collection Generation Semantics | Accepted |
+| [0014](../adr/0014-generator-emitted-collection-plans.md) | Generator-Emitted Collection Plans Replace the Reflection-Based Dispatch Bridge | Accepted |
+| [0015](../adr/0015-provider-identity-deferred-in-provider-attempt.md) | Provider Identity Deferred in `ProviderAttempt` | Superseded by [ADR-0016](../adr/0016-provider-identity-restored-in-provider-attempt.md) |
+| [0016](../adr/0016-provider-identity-restored-in-provider-attempt.md) | Provider Identity Restored in `ProviderAttempt` | Accepted |
+| [0017](../adr/0017-immutable-composer-configuration-and-builder-model.md) | Immutable Composer Configuration and Builder Model | Accepted |
+| [0018](../adr/0018-composition-profiles.md) | Composition Profiles — `ICompositionProfile`, eager in-order application | Accepted |
+| [0019](../adr/0019-registrations-and-service-provider-injection.md) | Registrations and Service Provider Injection | Accepted |
+| [0020](../adr/0020-composition-configuration-rules.md) | Composition Configuration Rules — type/member value rules and collection-size policy | Accepted |
+| [0021](../adr/0021-row-composition-entry-point-for-test-framework-integrations.md) | Row Composition Entry Point for Test-Framework Integrations | Accepted |
+| [0022](../adr/0022-compono-xunit-package-design.md) | Compono.Xunit Package Design | Accepted |
+| [0023](../adr/0023-rename-compono-xunit-to-compono-xunitv3.md) | Rename Compono.Xunit to Compono.XunitV3 | Accepted |
+| [0024](../adr/0024-public-provider-extensibility-model.md) | Public Provider Extensibility Model — `ICompositionValueProvider` for stages 5/6 | Accepted |
+| [0025](../adr/0025-compono-nsubstitute-package-design.md) | Compono.NSubstitute Package Design | Accepted |
+| [0026](../adr/0026-deterministic-seed-derivation-for-providers.md) | Deterministic Seed Derivation for Providers and Registration Factories | Accepted |
+| [0027](../adr/0027-compono-bogus-package-design.md) | Compono.Bogus Package Design | Accepted |
+| [0028](../adr/0028-configurable-bogus-member-name-conventions.md) | Configurable Bogus Member-Name Conventions | Accepted |
+| [0029](../adr/0029-milestone-7-dogfooding-strategy-and-capability-gap-decision-framework.md) | Milestone 7 Dogfooding Strategy and Capability-Gap Decision Framework | Accepted |
+| [0030](../adr/0030-compono-documentation-architecture.md) | Compono Documentation Architecture | Accepted |
+| [0031](../adr/0031-public-preview-release-and-versioning-policy.md) | Public Preview Release and Versioning Policy | Accepted |
+| [0032](../adr/0032-api-reference-documentation-toolchain.md) | API Reference Documentation Toolchain | Accepted |
+| [0033](../adr/0033-public-preview-samples-strategy.md) | Public Preview Samples Strategy | Accepted |
+| [0034](../adr/0034-benchmark-suite-strategy-and-redesign.md) | Benchmark Suite Strategy and Redesign — replaces the accreted benchmark suite with a categorized, audience-driven design | Accepted |
diff --git a/docs/architecture/design-principles.md b/docs/architecture/design-principles.md
index c4fc065..71432fe 100644
--- a/docs/architecture/design-principles.md
+++ b/docs/architecture/design-principles.md
@@ -1,10 +1,111 @@
# Design Principles
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../plans/0008-milestone-8-public-preview.md)).
+This page is Compono's current, evolving statement of what it believes —
+revised as the project's philosophy actually evolves, not a historical
+snapshot. It absorbs and retires `docs/design-principles.md` and
+`docs/manifesto.md`'s original content, per
+[ADR-0030 Amendment 2](../adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items).
+For *how* these beliefs actually shape the running system today, see
+[Current Architecture](current/source-generation.md); for the sequence of
+decisions that got here, see the [Historical Decision Log](decision-log.md).
-Current, evolving: what Compono believes (composition over object generation, predictability over magic, source-generated by default, deterministic by design).
+## Why Compono exists
-See [Documentation Architecture](../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+Test composition in .NET deserves a design grounded in the capabilities
+and expectations of modern .NET, not just automatic object creation.
+AutoFixture demonstrated the value of automatic object creation,
+declarative test data, shared instances, and test-framework integration —
+and enabled a style of testing many teams came to depend on. But the
+platform has changed: records, primary constructors, required members,
+nullable reference types, source generators, trimming, Native AOT, modern
+test frameworks, better compile-time analysis, and stronger expectations
+around determinism and diagnostics are all part of modern .NET in a way
+they weren't when AutoFixture's design took shape.
+
+Compono asks a fresh question: if a test composition framework were
+designed for modern .NET today, what should it look like? It is not
+intended to reproduce AutoFixture feature-for-feature — it's a new
+product focused on composing complete test environments, not just
+generating objects. See [Migrating from AutoFixture](../migrating-from-autofixture.md)
+for what that means concretely for an existing test suite.
+
+## What test composition means
+
+Object generation is only one part of preparing a test. A test may
+require a system under test, constructor dependencies, shared instances,
+test doubles, anonymous values, realistic semantic data, reusable project
+conventions, framework-specific parameter binding, and deterministic
+reproduction of failures. Compono treats those needs as parts of a single
+composition problem: tests declare what they require, and Compono
+determines how those requirements are satisfied. See
+[The Composition Model](../concepts/composition-model.md) for what this
+means concretely from a test author's point of view.
+
+## Guiding principles
+
+- **Composition over object generation.** Compono is not primarily a
+ fake-data generator or object factory — its purpose is to coordinate
+ every contributor involved in preparing a test (constructor
+ dependencies, shared instances, test doubles, semantic data), not just
+ fill in field values.
+- **Predictability over magic.** Convenience should not come at the cost
+ of understanding. Resolution order is deterministic and documented (see
+ [The Provider Pipeline](current/provider-pipeline.md)); when composition
+ fails, Compono explains what was requested, why, which providers were
+ considered, which was selected, where resolution failed, and how the
+ failure can be reproduced.
+- **Source-generated by default.** Compono discovers object construction
+ metadata at compile time and generates composition plans wherever
+ possible ([Source Generation](current/source-generation.md), resolved by
+ [ADR-0001](../adr/0001-source-generation-first.md)) — runtime execution
+ focuses on executing known plans rather than repeatedly inspecting types
+ through reflection. Runtime reflection is not part of the default
+ architecture; if ever supported, it requires an explicit opt-in (a
+ compiler-visible MSBuild property, or a dedicated compatibility
+ package/mode) and must never silently become the fallback path — a
+ still-open decision, tracked in Current Architecture.
+- **Deterministic by design.** A composition is reproducible from its seed
+ and configuration; a failed test reports enough information to recreate
+ the generated values and object graph (see
+ [Determinism and Seeding](current/deterministic-seeding.md)).
+- **Modular architecture.** The core `Compono` package must not depend on
+ any test framework (xUnit, NUnit, MSTest) or test-double/data library
+ (NSubstitute, Moq, FakeItEasy, Bogus) — those capabilities live in
+ integration packages built on stable, public extension contracts (see
+ [Package Guides](../packages/index.md)).
+- **Modern .NET first.** Compono prefers a small, coherent design for
+ modern .NET over broad compatibility with legacy runtimes.
+- **Performance is a feature.** Test infrastructure runs constantly:
+ composition startup time, allocations, generated-plan execution, and
+ test discovery overhead all matter, and are measured and protected
+ rather than treated as a later optimization (see
+ [Performance](current/performance.md)).
+- **Diagnostics are a feature.** Composition failures should be
+ understandable without stepping through framework internals — a useful
+ failure is better than a clever fallback.
+
+## What Compono should avoid becoming
+
+- An AutoFixture compatibility layer
+- A monolithic testing toolkit
+- A service locator hidden inside tests
+- A reflection-heavy runtime
+- A collection of unrelated convenience APIs
+- A system whose behavior depends on mutable global state
+- A framework where providers silently override one another
+- A source of random, irreproducible test failures
+- A feature-complete wrapper over every third-party integration
+
+## Product priorities
+
+When design goals compete, Compono prioritizes, in order: a coherent test
+composition model; predictable behavior and diagnostics; performance
+through source-generated execution; broad compatibility and convenience.
+This order may be refined as the project evolves, but convenience should
+not override clarity or architectural integrity.
+
+## The north star
+
+> Test authors declare what a test needs. Compono satisfies those needs
+> through explicit, deterministic, replaceable composition providers
+> operating within a composition context.
diff --git a/docs/architecture/index.md b/docs/architecture/index.md
index c8085f5..e5f02ae 100644
--- a/docs/architecture/index.md
+++ b/docs/architecture/index.md
@@ -1,10 +1,33 @@
# Architecture
-> **Status:** Skeleton — placeholder created by Milestone 7 Phase 5's
-> documentation skeleton (`docs/documentation-architecture.md`). Written in
-> Milestone 8 ([PLAN-0008](../plans/0008-milestone-8-public-preview.md)).
+**Audience:** three related but distinct readers, kept visibly separate
+rather than merged into one undifferentiated "Architecture" page: most
+readers only need Current Architecture; contributors additionally want
+the Historical Decision Log; anyone evaluating Compono's philosophy wants
+Design Principles.
-Why Compono exists and how it works internally - tradeoffs and rejected alternatives, split into Design Principles, Current Architecture, and the Historical Decision Log.
+This section explains *why* Compono exists and how it works internally —
+tradeoffs and rejected alternatives, not just a description of the
+current shape. It assumes [Concepts](../concepts/index.md)'s mental model
+already; every page here cross-links to the ADR(s) that made the
+underlying decision rather than re-deriving the reasoning.
-See [Documentation Architecture](../documentation-architecture.md) for this page's full audience,
-contents, and relationship to the rest of the site.
+- **[Design Principles](design-principles.md)** — current, evolving: what
+ Compono believes (composition over object generation, predictability
+ over magic, source-generated by default, deterministic by design).
+- **Current Architecture** — how it works today:
+ [Source Generation](current/source-generation.md),
+ [Generated Plans and Discovery](current/generated-plans-and-discovery.md),
+ [The Provider Pipeline](current/provider-pipeline.md),
+ [Deterministic Seeding](current/deterministic-seeding.md), and
+ [Performance](current/performance.md).
+- **[Historical Decision Log](decision-log.md)** — the public-facing
+ index into every `Accepted`/`Superseded` ADR, for a reader who wants the
+ full paper trail.
+
+This is `docs/architecture.md`, `docs/performance.md`,
+`docs/design-principles.md`, and `docs/manifesto.md`'s real content,
+consolidated here per
+[ADR-0030 Amendment 2](../adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+"one canonical home" principle — those four pages are now short
+tombstones pointing back here.
diff --git a/docs/best-practices/performance-recommendations.md b/docs/best-practices/performance-recommendations.md
index 3dc739f..b531b53 100644
--- a/docs/best-practices/performance-recommendations.md
+++ b/docs/best-practices/performance-recommendations.md
@@ -1,7 +1,7 @@
# Performance Recommendations
Practical guidance — what to actually do. For the methodology and measured
-numbers behind these recommendations, see [Performance](../performance.md).
+numbers behind these recommendations, see [Performance](../architecture/current/performance.md).
## Let source generation do its job
@@ -48,6 +48,6 @@ applies to your own suite.
## Next
- The measured numbers and methodology behind "generated construction
- avoids reflection overhead" → [Performance](../performance.md).
+ avoids reflection overhead" → [Performance](../architecture/current/performance.md).
- Collection composition in depth → [Collections](../concepts/collections.md).
- Applies at scale → [Large Test Suites](large-test-suites.md).
diff --git a/docs/concepts/collections.md b/docs/concepts/collections.md
index 3c282d3..4c6251f 100644
--- a/docs/concepts/collections.md
+++ b/docs/concepts/collections.md
@@ -51,4 +51,4 @@ conflict (`CompositionConfigurationException`), not last-write-wins.
- Apply this to a real task → [Create an Object](../how-to/create-an-object.md).
- The configuration surface `WithCollectionSize` is part of →
[Registrations and Rules](registrations-and-rules.md).
-- Precise API contract → [Public API](../public-api.md).
+- Precise API contract → [Reference](../reference/index.md).
diff --git a/docs/concepts/composition-model.md b/docs/concepts/composition-model.md
index b8e973c..40b4f5c 100644
--- a/docs/concepts/composition-model.md
+++ b/docs/concepts/composition-model.md
@@ -47,7 +47,8 @@ var customers = composer.CreateMany(3);
A `Composer`'s configuration is fixed at `Create` time — there's no method
to mutate an already-built `Composer`'s rules afterward. This is a
deliberate consequence of Compono's immutable-by-design goal (no mutable
-global state, per `docs/public-api.md`'s API goals): the composer you get
+global state, per [Design Principles](../architecture/design-principles.md)'s
+guiding principles): the composer you get
back from `Composer.Create(...)` behaves the same way on every call, for
every test that reuses it, with nothing to accidentally leak between tests
via shared mutable state.
@@ -78,4 +79,4 @@ reflection exception.
[Shared Values](shared-values.md).
- Group configuration into a reusable unit → [Profiles](profiles.md).
- The deeper "how" behind the resolution pipeline →
- [Architecture](../architecture.md).
+ [The Provider Pipeline](../architecture/current/provider-pipeline.md).
diff --git a/docs/concepts/determinism-and-seeding.md b/docs/concepts/determinism-and-seeding.md
index 228ddeb..7eddadd 100644
--- a/docs/concepts/determinism-and-seeding.md
+++ b/docs/concepts/determinism-and-seeding.md
@@ -81,7 +81,7 @@ failure is never a "works on my machine, can't repro" report — the seed
## Next
- The pipeline stage that actually derives per-value randomness from a
- seed → [Architecture](../architecture.md).
+ seed → [Deterministic Seeding](../architecture/current/deterministic-seeding.md).
- Reproduce a specific failing xUnit theory row →
[`Compono.XunitV3` Package Guide](../packages/compono-xunitv3.md).
- Diagnostic codes and messages → [Diagnostics Reference](../reference/diagnostics.md).
diff --git a/docs/concepts/index.md b/docs/concepts/index.md
index 61dda2d..3630c25 100644
--- a/docs/concepts/index.md
+++ b/docs/concepts/index.md
@@ -2,7 +2,7 @@
This section builds the mental model Compono's other sections assume —
what each piece is and when to reach for it, not the implementation details
-behind it (that's [Architecture](../architecture.md)'s job).
+behind it (that's [Architecture](../architecture/index.md)'s job).
- [The Composition Model](composition-model.md) — what "composing" a graph
means in Compono's terms.
diff --git a/docs/concepts/providers.md b/docs/concepts/providers.md
index e7a23fc..0fb7f11 100644
--- a/docs/concepts/providers.md
+++ b/docs/concepts/providers.md
@@ -44,7 +44,8 @@ two logically distinct stages precisely so that ordering between
`UseBogus()`/`UseNSubstitute()` calls never matters — a provider only
influences the stage it's registered into. The exact stage order relative
to registrations, rules, and generated default construction is
-[Architecture](../architecture.md)'s concern, not this page's — what
+[The Provider Pipeline](../architecture/current/provider-pipeline.md)'s
+concern, not this page's — what
matters here is that a provider is always a fallback, tried only once
nothing more specific (an exact registration, a type/member rule) already
claimed the value.
@@ -53,6 +54,6 @@ claimed the value.
- Providers that ship today → [`Compono.NSubstitute`](../packages/compono-nsubstitute.md),
[`Compono.Bogus`](../packages/compono-bogus.md) Package Guides.
-- Write your own → [Public API](../public-api.md)'s provider extensibility
- contract.
-- The full pipeline a provider participates in → [Architecture](../architecture.md).
+- Write your own → [The Provider Pipeline](../architecture/current/provider-pipeline.md)'s
+ provider extensibility contract.
+- The full pipeline a provider participates in → [The Provider Pipeline](../architecture/current/provider-pipeline.md).
diff --git a/docs/concepts/registrations-and-rules.md b/docs/concepts/registrations-and-rules.md
index 85f5e8c..61e9ae0 100644
--- a/docs/concepts/registrations-and-rules.md
+++ b/docs/concepts/registrations-and-rules.md
@@ -67,7 +67,7 @@ builder.For().Member(x => x.PlacedAt).Use(context => context.Resolve()`) runs ~6.1x faster and
-allocates ~43% as much as an equivalent reflection-based baseline, for
-the flat-type case Milestone 1 can measure end-to-end today. See
-[Performance](performance.md) for methodology, caveats, and how to
-reproduce - `benchmarks/Compono.Benchmarks`.
+Compono is source-generated by default so that composing a type
+dispatches to generated code rather than reflecting over it at runtime.
+See [Performance](architecture/current/performance.md) for the full,
+current methodology, results, and how to reproduce them -
+`benchmarks/Compono.Benchmarks`.
## Status
Compono is under active development. APIs are experimental until the
first public preview.
-See the [Manifesto](manifesto.md), [Architecture](architecture.md),
-[Design Principles](design-principles.md), [Public API](public-api.md),
-[Performance](performance.md), [MVP](mvp.md), and
-[ADRs](adr/0001-source-generation-first.md) for more.
+See the [Design Principles](architecture/design-principles.md),
+[Architecture](architecture/index.md), [Performance](architecture/current/performance.md),
+[MVP](mvp.md), and [ADRs](adr/0001-source-generation-first.md) for more.
diff --git a/docs/manifesto.md b/docs/manifesto.md
index 270774f..b7d2efa 100644
--- a/docs/manifesto.md
+++ b/docs/manifesto.md
@@ -1,150 +1,7 @@
-# Compono Manifesto
+# Compono Manifesto (moved)
-## Why Compono Exists
-
-Compono exists because test composition in .NET deserves a design grounded in the capabilities and expectations of modern .NET.
-
-AutoFixture demonstrated the value of automatic object creation, declarative test data, shared instances, and test-framework integration. It also enabled a style of testing that many teams came to depend on.
-
-But the platform has changed.
-
-Modern .NET now includes:
-
-- Records
-- Primary constructors
-- Required members
-- Nullable reference types
-- Source generators
-- Trimming
-- Native AOT
-- Modern test frameworks
-- Better compile-time analysis
-- Stronger expectations around determinism and diagnostics
-
-Compono asks a fresh question:
-
-> If a test composition framework were designed for modern .NET today, what should it look like?
-
-Compono is not intended to reproduce AutoFixture feature-for-feature. It is a new product focused on composing complete test environments.
-
-## What Test Composition Means
-
-Object generation is only one part of preparing a test.
-
-A test may require:
-
-- A system under test
-- Constructor dependencies
-- Shared instances
-- Test doubles
-- Anonymous values
-- Realistic semantic data
-- Reusable project conventions
-- Framework-specific parameter binding
-- Deterministic reproduction of failures
-
-Compono treats those needs as parts of a single composition problem.
-
-Tests declare what they require. Compono determines how those requirements are satisfied.
-
-## Core Principles
-
-### Composition over generation
-
-Compono is not primarily a fake-data generator or object factory.
-
-Its purpose is to coordinate every contributor involved in preparing a test.
-
-### Predictability over magic
-
-Convenience should not come at the cost of understanding.
-
-Resolution order must be deterministic and documented. When composition fails, Compono should explain:
-
-- What was requested
-- Why it was requested
-- Which providers were considered
-- Which provider was selected
-- Where resolution failed
-- How the failure can be reproduced
-
-### Source-generated first
-
-Compono should discover object construction metadata at compile time and generate composition plans wherever possible.
-
-Runtime execution should focus on executing known plans rather than repeatedly inspecting types.
-
-Runtime reflection remains an explicit architectural decision. It may eventually be:
-
-- Disallowed
-- Supported as a compatibility fallback
-- Available only through an opt-in package or mode
-
-Until that decision is finalized, reflection must not become an accidental dependency of the core design.
-
-### Deterministic by default
-
-A composition should be reproducible from its seed and configuration.
-
-A failed test should report enough information to recreate the generated values and object graph.
-
-### Modular by design
-
-The core package must not depend on:
-
-- xUnit
-- NUnit
-- MSTest
-- NSubstitute
-- Moq
-- FakeItEasy
-- Bogus
-
-Those capabilities belong in integration packages built on stable extension contracts.
-
-### Modern .NET first
-
-Compono should prefer a small, coherent design for modern .NET over broad compatibility with legacy runtimes.
-
-### Performance is a feature
-
-Test infrastructure runs constantly.
-
-Composition startup time, allocations, generated-plan execution, and test discovery overhead all matter.
-
-Performance should be measured and protected rather than treated as a later optimization.
-
-### Diagnostics are a product feature
-
-Composition failures should be understandable without stepping through framework internals.
-
-A useful failure is better than a clever fallback.
-
-## What Compono Should Avoid Becoming
-
-Compono should not become:
-
-- An AutoFixture compatibility layer
-- A monolithic testing toolkit
-- A service locator hidden inside tests
-- A reflection-heavy runtime
-- A collection of unrelated convenience APIs
-- A system whose behavior depends on mutable global state
-- A framework where providers silently override one another
-- A source of random, irreproducible test failures
-- A feature-complete wrapper over every third-party integration
-
-## Product Priorities
-
-When design goals compete, Compono should prioritize:
-
-1. A coherent test composition model
-2. Predictable behavior and diagnostics
-3. Performance through source-generated execution
-4. Broad compatibility and convenience
-
-This order may be refined as the project evolves, but convenience should not override clarity or architectural integrity.
-
-## The North Star
-
-> Test authors declare what a test needs. Compono satisfies those needs through explicit, deterministic, replaceable composition providers operating within a composition context.
+This page's content has moved to
+[architecture/design-principles.md](architecture/design-principles.md),
+per [ADR-0030 Amendment 2](adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+"one canonical home" principle — this file is a tombstone, not deleted
+outright, because ADRs that link to it by path must stay resolvable.
diff --git a/docs/packages/compono.md b/docs/packages/compono.md
index bfec69c..3f604a6 100644
--- a/docs/packages/compono.md
+++ b/docs/packages/compono.md
@@ -4,8 +4,8 @@ The core composition engine — `Composer`, the resolution pipeline, and the
source generator. Every other Compono package depends on this one; this one
depends on nothing else in the ecosystem — the core package never
references or knows about an integration package (see
-[Public API: Configuration](../public-api.md#configuration), "the core
-package must not know those methods exist").
+[Design Principles](../architecture/design-principles.md#guiding-principles),
+"modular architecture").
## When to install
diff --git a/docs/performance.md b/docs/performance.md
index 030172f..ed5aeff 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -1,329 +1,7 @@
-# Performance
+# Performance (moved)
-Compono's source generator exists to avoid reflection-based construction
-at runtime (`docs/manifesto.md`, `docs/adr/0001-source-generation-first.md`).
-`benchmarks/Compono.Benchmarks` (a `BenchmarkDotNet` project) makes that a
-measured claim rather than an assumption, per Milestone 1's explicit
-"benchmark harness comparing generated construction with reflection
-baselines" exit criteria (`docs/mvp.md`).
-
-## What's measured, and what isn't yet
-
-Milestone 1 only implements direct constructor invocation end-to-end for a
-type whose constructor takes no arguments -
-`ICompositionContext.Resolve()` is a placeholder that throws
-`NotSupportedException` for any real value resolution
-(`src/Compono/Composer.cs`). Concretely: a type with a
-constructor parameter - even a parameter whose type has its own generated
-plan - can't be composed end-to-end yet, because generated code resolves
-every constructor argument through `context.Resolve()`
-(`src/Compono.Generators/Templates/CompositionPlan.scriban`), and
-dispatching that call to the matching plan is Milestone 2's provider
-resolution pipeline, not built yet.
-
-So today's benchmark compares construction of a single flat, parameterless
-type (`Leaf`) across two groups, kept deliberately separate so ecosystem
-numbers can't be mistaken for the architecture's success criterion:
-
-- **`ArchitectureBenchmarks`** - answers "does generated construction
- outperform a comparable reflection-based implementation?", the question
- Milestone 1's architecture actually needs to prove:
- - **Direct** - `new Leaf()`, the theoretical floor.
- - **Generated** - `composer.Create()`, dispatching to a
- source-generated `ICompositionPlan` via `PlanCache`.
- - **Reflection** - `typeof(Leaf).GetConstructors().Single()` +
- `ConstructorInfo.Invoke([])`, the direct alternative Compono's
- generator replaces.
-- **`EcosystemBenchmarks`** - answers a different question: "how does
- Compono compare with AutoFixture, the established framework developers
- reach for today?" AutoFixture does substantially more runtime work and
- has different goals (randomized value generation, unexercised here since
- `Leaf` has no properties to fill), so this is a recognizable reference
- point users will ask about, not a target Compono is trying to "beat" -
- Compono is not an AutoFixture replacement.
- - **Generated** - same as above, repeated as this group's baseline.
- - **AutoFixture** - `new Fixture().Create()`.
-
-This expanded once Milestone 2 made nested/primitive composition real -
-see "Milestone 2 Phase 4: resolution-pipeline result" below for the
-representative-graph comparison this section couldn't measure honestly
-until then.
-
-## Baseline result
-
-Recorded at Milestone 1 Phase 4's completion, Apple M3 Max, .NET 10.0.3
-arm64 RyuJIT, Release configuration, `BenchmarkDotNet` `DefaultJob`:
-
-**Architecture benchmark**
-
-| Method | Mean | Allocated |
-|------------|----------:|----------:|
-| Direct | 2.309 ns | 24 B |
-| Generated | 3.309 ns | 24 B |
-| Reflection | 19.769 ns | 56 B |
-
-Generated construction ran **~6.0x faster** than the reflection baseline
-and allocated the same as direct construction - within ~1 ns of the
-theoretical floor.
-
-**Ecosystem comparison**
-
-| Method | Mean | Allocated |
-|-------------|-------------:|----------:|
-| Generated | 2.474 ns | 24 B |
-| AutoFixture | 1,523.054 ns | 4,440 B |
-
-Generated construction ran **~616x faster** and allocated **~0.5%** as
-much as AutoFixture. This gap is expected, not the point - AutoFixture is
-doing real randomized-value-generation work that this flat, parameterless
-type never exercises. Take it as a recognizable reference point, not
-evidence the architecture benchmark above doesn't already establish on
-its own.
-
-Numbers will shift as the composition engine grows past Milestone 1's
-placeholder context - re-run and update this page rather than treating it
-as a permanent result.
-
-## Milestone 2 Phase 4: resolution-pipeline result
-
-`ArchitectureBenchmarks`/`EcosystemBenchmarks` above only ever measured
-generated *construction* dispatch versus reflection for a flat,
-parameterless type - nothing exercised the real resolution pipeline
-(provider dispatch, deterministic random forking, collection generation,
-the diagnostics trace buffer) until Milestone 2 made it real. Three new
-benchmark classes close that gap with the `Customer`/`Address`
-representative graph from `docs/plans/0002-milestone-2-core-composition-engine.md`'s
-Execution Flow section (a nested composable type, every Phase 2 built-in
-kind via `string`, and a `List` collection member), run through
-the real generator (`benchmarks/Compono.Benchmarks/ResolutionBenchmarkTypes.cs`),
-mirroring `ArchitectureBenchmarks`/`EcosystemBenchmarks`' split so
-ecosystem numbers stay separate from the architecture question:
-
-- **`ResolutionArchitectureBenchmarks`** - `Direct`/`Generated`/`Reflection`,
- same shape as `ArchitectureBenchmarks` above. `Direct` stays the
- theoretical floor (no fields to fill, same as `Leaf`), but `Reflection`
- here does comparable real work to `Generated`:
- `ReflectionComposer.ComposeRecursive()` fills every field with a
- genuinely random value (an 8-character alphanumeric string, a
- 3-element collection - Compono's own defaults), not a fixed
- placeholder. An earlier version of this baseline used fixed
- placeholders, which made `Reflection` faster than `Generated` for
- doing categorically less work rather than because reflective dispatch
- actually beats source-generated dispatch - a misleading comparison
- caught in PR #13 review and fixed by rewriting the baseline to do real
- value generation. The one remaining, deliberate asymmetry:
- `Reflection`'s randomness is ordinary `Random.Shared`, not Compono's
- deterministic, seed-forked `IRandomSource` - reproducibility is a
- Compono product feature (`README.md`'s "Deterministic by design"), not
- a cost every random-value generator has to pay, so `Generated`'s cost
- below includes work `Reflection`'s doesn't.
-- **`ResolutionEcosystemBenchmarks`** - `Generated`/`AutoFixture`, same
- shape as `EcosystemBenchmarks` above, except this time `Customer`
- actually has fields for AutoFixture's real randomized-value-generation
- work to fill (unlike `Leaf`).
-- **`ResolutionBenchmarks`** - `Create`/`CreateMany` only, no external
- baseline; the comparison here is intrinsic (`CreateMany`'s cost at
- `count=10`/`count=100` against its own `count=1` baseline), the scaling
- question `docs/plans/0002-...`'s Phase 4 benchmark task asks. This is
- also the benchmark gate [ADR-0010](adr/0010-composition-request-pipeline-and-diagnostics-tracing.md)
- reserved for the diagnostics trace buffer: confirm it's actually
- allocation-free on the success path, and fall back to shallow
- diagnostics by default if it measurably harms the hot path.
-
-Recorded at Milestone 2 Phase 4's completion (updated after a second PR #13
-review round - see below), Apple M3 Max, .NET 10.0.3 arm64 RyuJIT, Release
-configuration, `BenchmarkDotNet` `DefaultJob`:
-
-**Resolution architecture benchmark**
-
-| Method | Mean | Allocated |
-|------------|----------:|----------:|
-| Direct | 14.17 ns | 160 B |
-| Generated | 837.50 ns | 2,776 B |
-| Reflection | 403.08 ns | 832 B |
-
-Generated resolution ran ~59.1x slower than `Direct`'s theoretical-floor
-hardcoded construction and allocated ~17.3x as much - the real cost of
-provider dispatch, random forking, collection generation, and diagnostics
-tracing for this graph, not just constructor invocation. Against a
-reflection baseline doing genuinely comparable work (real random values,
-not placeholders), `Generated` ran ~2.1x slower and allocated ~3.3x as
-much as `Reflection` - a real, honest gap. This is the number worth
-discussing if the question is "why not just use reflection": Compono's
-overhead over a comparable hand-rolled reflective composer is real, and
-this table is where to look for it, not the `Direct` comparison above
-(which was never a fair alternative to begin with - nobody ships
-hardcoded test data).
-
-**Resolution ecosystem comparison**
-
-| Method | Mean | Allocated |
-|-------------|-------------:|----------:|
-| Generated | 832.50 ns | 2.71 KB |
-| AutoFixture | 76,314.30 ns | 99.21 KB |
-
-Generated construction ran **~91.7x faster** and allocated **~2.7%** as
-much as AutoFixture - this time with `Customer` actually giving
-AutoFixture real randomized-value-generation work to do, unlike `Leaf`.
-
-**Resolution pipeline (`Create`/`CreateMany`)**
-
-| Method | Count | Mean | Allocated | Alloc Ratio |
-|------------|------:|-------------:|-----------:|------------:|
-| Create | 1 | 848.7 ns | 2.71 KB | 1.00 |
-| CreateMany | 1 | 883.0 ns | 2.84 KB | 1.05 |
-| Create | 10 | 853.6 ns | 2.71 KB | 1.00 |
-| CreateMany | 10 | 8,898.3 ns | 27.59 KB | 10.18 |
-| Create | 100 | 826.2 ns | 2.71 KB | 1.00 |
-| CreateMany | 100 | 87,801.8 ns | 275.10 KB | 101.48 |
-
-`Create()` allocates ~2.71 KB per call regardless of
-`CreateMany`'s batch size - a single root operation's cost, unaffected by
-how many other independent items get composed around it in the same
-process. `CreateMany(count)` scales linearly with `count` (10.18×
-allocation at `count=10`, 101.48× at `count=100`, against the `count=1`
-baseline) - no super-linear growth from the checkpoint/rewind trace
-buffer's bookkeeping, `IRandomSource`'s per-item seed forking, or scope
-allocation.
-
-### A second PR #13 review round: real fixes, not just documentation
-
-A follow-up review pass found three more real issues (all fixed) beyond
-the trace-buffer-isolation point already addressed above, plus a
-separately-requested optimization:
-
-- **`CompositionRequest` converted from a class to a `readonly record
- struct`.** It's never stored beyond the synchronous `ResolveCore` call
- that builds it, so the heap allocation was pure waste. Measured directly
- (constructed and consumed the same way `ResolveCore` does, passed by
- `in`, not boxed): **40 B → 0 B**, **14.9 ns → 5.5 ns**. This is what
- moved every table above from their original numbers (`Generated` was
- 2,792 B/873.0 ns in the first version of this page) to the ones shown
- now.
-- **A generic root type rendered in raw CLR form in the diagnostic
- heading and failure message** (`Unable to compose List\`1.` instead of
- `List`) - the same class of bug `CompositionPath.FriendlyTypeName`
- was added to fix for the path tree, just missed at two more call sites
- (`CompositionDiagnostic.ToString()`'s heading, and the stage-9 failure
- `Message` text). Fixed by reusing the same helper at both sites.
-- **An ancestor's in-flight generated-plan/collection-plan dispatch was
- silently absent from the trace** when a descendant failed - `Success`
- was (correctly, per the earlier fix) only ever recorded *after*
- `Compose` returned, but that meant an ancestor still waiting on a
- failing descendant never got *any* entry for its own stage-8/collection
- dispatch. Fixed by recording a new `CompositionAttemptOutcome.Pending`
- immediately before `Compose` runs - rewound away on success exactly like
- every other entry, but surviving (correctly) when a descendant's failure
- means the ancestor's own `Success` never gets recorded either.
-- **The trace buffer's growth path wasn't measured** - only the shallow
- `Customer` graph (2 levels deep) had been benchmarked, which never gets
- deep enough to trigger a resize. See "Deep graph result" below for a
- graph that does.
-
-### A third PR #13 review round: two more real gaps, no benchmark impact
-
-A third pass found two more real issues - both fixed, neither changing
-any number in this page, since they're trace-fidelity/formatting
-corrections, not allocation or timing changes (re-measured to confirm:
-every table above is unchanged within normal run-to-run noise).
-
-- **Stage 7's three built-in providers collapsed into one trace entry.**
- `BuiltInProviders.Default` genuinely has three providers registered
- today (`PrimitiveValueProvider`, `EnumValueProvider`,
- `NullableValueProvider`) - directly contradicting a claim this page's
- own earlier review-round text (and `docs/architecture.md`, now also
- fixed) had made: "no stage has more than one competing provider yet."
- That claim was simply wrong for stage 7. `TryProviders` used to let its
- caller record a single aggregate `NotHandled` for the whole stage
- regardless of how many providers actually declined - now each
- provider's own decline is recorded as it's tried, so a failing
- request's trace shows the real number of attempts made, not a
- collapsed count of 1.
-- **`FriendlyTypeName` didn't handle arrays of generic types.** `Type.IsGenericType`
- is `false` for an array type itself (array-ness and generic-ness are
- orthogonal in reflection), so `List[]` fell straight through
- to the raw CLR form (`List\`1[]`) despite `FriendlyTypeName` existing
- specifically to prevent that. Fixed by checking `IsArray` first and
- recursing into `GetElementType()`.
-
-**Isolating the trace buffer's own share** of `Create()`'s
-2.71 KB - measured directly via `GC.GetAllocatedBytesForCurrentThread()`
-around `new CompositionTraceBuffer()` in isolation, 1,000,000 iterations,
-Release, .NET 10.0.3 arm64 (re-measured after bumping the buffer's initial
-capacity from 16 to 32, and again after
-[ADR-0016](adr/0016-provider-identity-restored-in-provider-attempt.md)
-widened `ProviderAttempt` - see below):
-
-| What | Bytes/instance |
-|-----------------------------------------------------------|---------------:|
-| `CompositionTraceBuffer` alone (its `ProviderAttempt[32]`) | ~536 B |
-| Bare `new CompositionContext()` (scope + active-frames + trace, no resolution) | ~780 B |
-
-Still real, not literally zero (`ADR-0010`'s "near-zero-allocation on
-success, not zero-cost," not a stronger claim than that) - and, per the
-second review round, **not bounded at that number for every graph**: see
-below. A true zero-allocation trace buffer would need pooling/reuse across
-root operations - `docs/architecture.md`'s Open Architectural Decisions
-tracks this as a deferred item, not a same-PR fix.
-
-### Deep graph result
-
-`ResolutionBenchmarks`' `Customer`/`Address` graph is only 2 levels deep -
-never enough to exercise `CompositionTraceBuffer`'s growth path, since
-each active ancestor frame dispatching through stage 8/a collection plan
-retains ~6 entries (5 declined stages + a `Pending` marker) until its own
-child returns. `DeepGraphBenchmarks` composes an 8-level-deep chain of
-single-field composable types (`DeepLevel1` through `DeepLevel8`) instead
-- deep enough (~48 entries at its deepest point) to exceed the buffer's
-32-entry initial capacity and trigger a real `Array.Resize`:
-
-| Method | Mean | Allocated |
-|--------|---------:|----------:|
-| Create | 846.1 ns | 3.37 KB |
-
-That's a genuinely deeper graph allocating *more* than the shallower,
-wider `Customer` graph (2.71 KB) despite composing fewer total leaf values
-(8 strings vs. `Customer`'s 7 strings + a 3-element list) - the resize is
-a real, measurable contributor, not a theoretical one. The fix isn't to
-pretend resizing can't happen (any growable array-backed collection
-resizes eventually); it's to size the initial capacity for a typical
-graph (bumped 16 → 32, covering ~5 levels of nesting without resizing -
-`docs/architecture.md`'s own Diagnostics example is 4 levels) and be
-honest that deeper graphs still pay a real, amortized `Array.Resize` cost,
-which this table now measures rather than assumes away.
-
-**No fallback to shallow diagnostics was needed** - the
-allocation-free-on-success trace buffer design shipped as scoped, with
-the growth-path caveat above now documented rather than glossed over.
-
-### A fourth PR #13 review round: provider identity restored, a real (accepted) allocation increase
-
-Unlike the third round, this one *does* move every number above -
-honestly, not glossed over. [ADR-0016](adr/0016-provider-identity-restored-in-provider-attempt.md)
-reverses [ADR-0015](adr/0015-provider-identity-deferred-in-provider-attempt.md)'s
-deferral: `ProviderAttempt` gained a `Type? Provider` field so a failing
-request's trace can tell stage 7's three real built-in providers apart,
-not just count that three attempts happened. Widening the struct
-(`PipelineStage` + `Type?` + `CompositionAttemptOutcome`) roughly doubles
-each trace entry's size, which is exactly what moved
-`CompositionTraceBuffer`'s own isolated cost from ~280 B to ~536 B per
-instance, and `Create()`'s total from 2.46 KB to 2.71 KB (+256 B,
-~10%) - fully attributable to the wider struct, confirmed by the isolated
-`CompositionTraceBuffer` measurement moving by almost exactly the same
-256 B on its own. This is a real, accepted cost, not an oversight: three
-indistinguishable `(BuiltInProvider, NotHandled)` trace entries were a
-genuine diagnostic-quality gap, and paying ~10% more allocation on the
-failure-adjacent bookkeeping path for a working "which provider" answer
-is the tradeoff ADR-0016 accepts explicitly, not a regression to chase
-back down.
-
-## Reproducing
-
-```
-dotnet run -c Release --project benchmarks/Compono.Benchmarks -f net10.0
-```
-
-`-c Release` is required - `BenchmarkDotNet` refuses to run a Debug build.
-Add `-- --filter "*Resolution*" "*DeepGraph*"` to run only the Milestone 2
-resolution-pipeline benchmarks (the full suite, including
-`ArchitectureBenchmarks`/`EcosystemBenchmarks`, takes several minutes).
+This page's content has moved to
+[architecture/current/performance.md](architecture/current/performance.md),
+per [ADR-0030 Amendment 2](adr/0030-compono-documentation-architecture.md#amendment-2-2026-08-04-resolving-milestone-8s-remaining-open-items)'s
+"one canonical home" principle — this file is a tombstone, not deleted
+outright, because ADRs that link to it by path must stay resolvable.
diff --git a/docs/plans/0002-milestone-2-core-composition-engine.md b/docs/plans/0002-milestone-2-core-composition-engine.md
index f68feb0..b6c8036 100644
--- a/docs/plans/0002-milestone-2-core-composition-engine.md
+++ b/docs/plans/0002-milestone-2-core-composition-engine.md
@@ -1367,12 +1367,15 @@ ADR-0014. The task list below reflects the corrected shape.
Compono's real random-value generation, so it's faster than `Generated`
for doing categorically less work, not because reflection dispatch
beats source-generated dispatch - documented explicitly in both the
- class' XML doc `` and `docs/performance.md`, the mirror image
- of the AutoFixture caveat. Full tables and reproduction steps recorded
+ class' XML doc `` and (at the time) `docs/performance.md`, the
+ mirror image of the AutoFixture caveat. Reproduction steps are recorded
permanently in
- [`docs/performance.md`](../performance.md#milestone-2-phase-4-resolution-pipeline-result)
- (`docs/architecture.md`'s Diagnostics section links there too, rather
- than duplicating the table).
+ [`architecture/current/performance.md`](../architecture/current/performance.md)
+ (`architecture/current/provider-pipeline.md`'s Diagnostics section links
+ there too, rather than duplicating the table) - the specific tables this
+ bullet originally pointed to were superseded by
+ [ADR-0034](../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+ benchmark suite redesign and no longer exist on that page.
- A PR #13 review round (Codex) found three real issues, all fixed in the
same PR. **Trace buffer's own allocation was asserted, not measured
(P1).** The Phase 4 benchmark result above reported total allocation
diff --git a/docs/plans/0008-milestone-8-public-preview.md b/docs/plans/0008-milestone-8-public-preview.md
index 6635f4e..5da130c 100644
--- a/docs/plans/0008-milestone-8-public-preview.md
+++ b/docs/plans/0008-milestone-8-public-preview.md
@@ -444,28 +444,33 @@ packages).
`reusing-configuration.md`, `performance-recommendations.md`,
`deterministic-and-non-brittle-tests.md`.
-## Phase 5: Architecture consolidation and legacy retirement
+## Phase 5: Architecture consolidation, legacy retirement, and benchmark suite redesign
-**Status:** Not Started
+**Status:** Done
-Executes ADR-0030 Amendment 2's "one canonical home" principle. Sequenced
-after Phase 2 (Concepts must exist for Architecture pages to cross-link
-back to) — the last phase touching `docs/architecture.md`/
-`docs/performance.md`/`docs/design-principles.md`/`docs/manifesto.md`/
-`docs/public-api.md`'s real pre-existing content, so it can safely
-consume and then retire them.
+Executes ADR-0030 Amendment 2's "one canonical home" principle for the
+architecture/roadmap documentation, and — added after this phase's
+doc-consolidation work was already done, per direct review of the
+existing benchmark suite — [ADR-0034](../adr/0034-benchmark-suite-strategy-and-redesign.md)'s
+full benchmark-suite redesign. Sequenced after Phase 2 (Concepts must
+exist for Architecture pages to cross-link back to) — the last phase
+touching `docs/architecture.md`/`docs/performance.md`/
+`docs/design-principles.md`/`docs/manifesto.md`/`docs/public-api.md`'s
+real pre-existing content, so it can safely consume and then retire
+them.
-- [ ] `architecture/index.md`, `architecture/design-principles.md`
+### Part A: Architecture and roadmap documentation (done)
+
+- [x] `architecture/index.md`, `architecture/design-principles.md`
(absorbs `docs/design-principles.md`/`docs/manifesto.md`'s
content).
-- [ ] `architecture/current/source-generation.md`,
+- [x] `architecture/current/source-generation.md`,
`generated-plans-and-discovery.md`, `provider-pipeline.md`,
- `deterministic-seeding.md`, `performance.md` (moves
- `docs/performance.md`'s real methodology/results, publishing them
- per ADR-0030 Amendment 2's benchmark-claims policy).
-- [ ] `architecture/decision-log.md` (public-facing index into
- `docs/adr/`).
-- [ ] Retire **all five** pre-existing legacy pages this phase
+ `deterministic-seeding.md` (real content, migrated from
+ `docs/architecture.md`).
+- [x] `architecture/decision-log.md` (public-facing index into
+ `docs/adr/`, including [ADR-0034](../adr/0034-benchmark-suite-strategy-and-redesign.md)).
+- [x] Retire **all five** pre-existing legacy pages this phase
consolidates — `docs/public-api.md`, `docs/manifesto.md`,
`docs/architecture.md`, `docs/design-principles.md`, and
`docs/performance.md` — from navigation and canonical-content
@@ -483,10 +488,240 @@ consume and then retire them.
plan can't touch. (An earlier version of this task only tombstoned
the first two — the same problem applies identically to the other
three, since they're excluded from the canonical tree the same way.)
-- [ ] `roadmap/index.md`, `roadmap/proposed-adrs.md`,
+- [x] `roadmap/index.md`, `roadmap/proposed-adrs.md`,
`roadmap/future-packages.md` (`roadmap/post-mvp.md` already real
content from PLAN-0007 Phase 3 — just needs its nav confirmed).
+### Part B: Benchmark suite redesign (per ADR-0034 — not started)
+
+`architecture/current/performance.md` currently still documents the
+**old** benchmark suite (migrated as-is from `docs/performance.md` during
+Part A, before the redesign decision below was made) — it gets
+overwritten by this Part's last task, not before. Sequencing matters:
+design and implement the new suite first, produce real results from it,
+*then* write the page — this phase does not narrate a redesign still in
+progress.
+
+- [x] Design the benchmark strategy from first principles: the question
+ set, audiences, and category taxonomy — done as
+ [ADR-0034](../adr/0034-benchmark-suite-strategy-and-redesign.md)
+ (`Accepted`).
+- [x] Define and implement the reused `Models/` set (`SimplePoco`,
+ `MediumAggregate`/`Address`, `DeepGraph` (`DeepLevel2`-`8`),
+ `LargeCollection`, `SharedValueGraph` (`SharedContext`/
+ `ConsumerOne`/`ConsumerTwo`), `ProviderBackedModel` (`IClock`/
+ `FixedClock`)) and `Baselines/` (`AutoFixtureComposer`) — replaced
+ `BenchmarkTypes.cs`, `ResolutionBenchmarkTypes.cs`,
+ `ReflectionComposer.cs`, `AutoFixtureComposer.cs`.
+- [x] ~~Implement `ImplementationStrategies/`~~ — implemented
+ (`SimplePocoConstructionBenchmarks.cs`/
+ `MediumAggregateConstructionBenchmarks.cs`, handwritten-as-ceiling/
+ generated/cached-reflection/uncached-reflection), then **removed**
+ per [ADR-0034's Amendment](../adr/0034-benchmark-suite-strategy-and-redesign.md#amendment-2026-08-05-implementation-strategies-removed-it-compares-different-systems-not-one-variable):
+ the category compared different systems doing different amounts of
+ work (bare construction vs. a full resolution pipeline), not one
+ isolated variable, so a result from it couldn't be attributed to a
+ specific cause or guide an optimization decision. Its `Baselines/`
+ (`HandwrittenComposer`, `CachedReflectionComposer`,
+ `UncachedReflectionComposer`) were deleted alongside it — no other
+ category referenced them. `ArchitectureBenchmarks.cs`/
+ `ResolutionArchitectureBenchmarks.cs` (the old files this category
+ was meant to replace) are still correctly deleted below; their
+ question just doesn't get a new home.
+- [x] Implement `ConsumerScenarios/` — `RepresentativeModelBenchmarks.cs`
+ (simple POCO, medium aggregate, deep graph, large collection),
+ `SharedValueBenchmarks.cs` (`CreateRow`/`ResolveShared`),
+ `ProviderEnabledBenchmarks.cs` (Bogus-enabled, NSubstitute-enabled)
+ — new; no prior equivalent existed.
+- [x] Implement `ExternalComparison/` — split into
+ `SimplePocoComparisonBenchmarks.cs`/
+ `MediumAggregateComparisonBenchmarks.cs` (one file per model, so
+ each class's `[Benchmark(Baseline = true)]` ratio stays meaningful
+ — a single class covering both models would compute every ratio
+ against one model's baseline) — replaces `EcosystemBenchmarks.cs`
+ and `ResolutionEcosystemBenchmarks.cs`.
+- [x] Implement `FeatureOverhead/` — diverged from the plan's literal
+ additive chain (a single `Composer` can't coherently stack a
+ `CreateRow`-based sharing step onto a `Create()`-based rule
+ step): `ConfigurationOverheadBenchmarks.cs` (generated-only vs.
+ +member-rule vs. +type-rule vs. +custom-provider, all on
+ `MediumAggregate`), `SharingOverheadBenchmarks.cs` (row without
+ sharing vs. with sharing, both via `CreateRow`),
+ `BogusOverheadBenchmarks.cs`/`NSubstituteOverheadBenchmarks.cs`
+ (pairwise: the cheapest alternative mechanism vs. the package
+ provider, isolating exactly one member's resolution) — new; no
+ prior equivalent existed.
+- [x] Implement `Scalability/` — `BatchScalingBenchmarks.cs`
+ (`CreateMany` at 1/10/100/1000), `GraphDepthScalingBenchmarks.cs`
+ (shallow `MediumAggregate` vs. deep `DeepGraph`),
+ `CollectionSizeScalingBenchmarks.cs` (3/10/50/200-element
+ collections) — replaces `ResolutionBenchmarks.cs` and
+ `DeepGraphBenchmarks.cs`'s construction, generalizing the latter's
+ one-off trace-buffer-resize question into a real shallow-vs-deep
+ comparison.
+- [x] Implement `SourceGeneration/` — `GeneratorDriverBenchmarks.cs`
+ (in-process `GeneratorDriver`, clean vs. incremental generation
+ across a 1/10/50 composable-type-count matrix) — new; no prior
+ equivalent existed. The incremental compilation is derived from
+ the clean one via `Compilation.ReplaceSyntaxTree` (not built as an
+ independently-parsed `CSharpCompilation`), since Roslyn's
+ incremental-generator cache is keyed on compilation/tree identity
+ — two separately-constructed compilations look entirely unrelated
+ to the driver even with near-identical source text, which would
+ silently defeat the whole point of the incremental-vs-clean
+ comparison. `Compono.Generators` changed from analyzer-only
+ (`ReferenceOutputAssembly="false"`) to *also* a normal compile
+ reference on the same `ProjectReference` item (not a second,
+ separate reference to the same project), plus
+ `InternalsVisibleTo="Compono.Benchmarks"` added to
+ `Compono.Generators.csproj` so this class can construct
+ `ComponoIncrementalGenerator` directly, matching
+ `Compono.Generators.Tests`' own pattern.
+- [x] Add `Compono.NSubstitute`/`Compono.Bogus` `ProjectReference`s to
+ `Compono.Benchmarks.csproj` (needed by `ConsumerScenarios/` and
+ `FeatureOverhead/`'s provider-enabled cases; not needed previously
+ since no prior benchmark exercised either package). Also added
+ `Basic.Reference.Assemblies.Net100`/`Net110` (conditional on
+ `$(TargetFramework)`), needed to build a real reference-assembly
+ set for `SourceGeneration/`'s in-process compilations, matching
+ `Compono.Generators.Tests`' own pattern.
+- [x] Delete `ArchitectureBenchmarks.cs`, `EcosystemBenchmarks.cs`,
+ `ResolutionArchitectureBenchmarks.cs`,
+ `ResolutionEcosystemBenchmarks.cs`, `ResolutionBenchmarks.cs`,
+ `DeepGraphBenchmarks.cs`, `BenchmarkTypes.cs`,
+ `ResolutionBenchmarkTypes.cs` once every question they answered has
+ a home in the categories above — no benchmark class straddles the
+ old and new structure once this task is done.
+- [x] Build and smoke-test the redesigned suite: `dotnet build
+ Compono.slnx -c Release` (0 warnings, 0 errors) and `dotnet run -c
+ Release --project benchmarks/Compono.Benchmarks -f net10.0 --
+ --job Dry --filter '*'` (all 49 benchmarks, across the 15 classes
+ that existed at that point in this task list — before
+ Implementation Strategies was implemented-then-removed later in
+ this same Part, which brought the suite down to 41 benchmarks
+ across 13 classes — executed without throwing) — confirms the
+ suite is structurally correct. This is not the statistically real
+ run the next task produces; `--job Dry` is one cold iteration per
+ benchmark, fast enough to smoke-test but not meant to be reported
+ as a real result.
+- [x] Run the full redesigned suite (`dotnet run -c Release --project
+ benchmarks/Compono.Benchmarks -f net10.0 -- --filter '*'`),
+ `DefaultJob` (not `--job Dry`), and record real results — all 15
+ classes (at the time; 13 after Implementation Strategies' later
+ removal) produced real Mean/Error/StdDev/Allocated/`Gen0`/`Gen1`
+ results (Apple M3 Max, macOS Tahoe 26.6, .NET 10.0.3 arm64 RyuJIT,
+ `BenchmarkDotNet` v0.15.8; total run ~15 minutes). One real,
+ published finding the run surfaced: `UseBogus()` costs ~865x a
+ plain member rule (291.5 μs vs. 337 ns, isolated) because
+ `BogusMemberNameProvider` constructs a new `Bogus.Faker` per
+ resolution — root-caused, not investigated further or fixed here,
+ per ADR-0034's "publish an unfavorable result, don't hide it" rule.
+- [x] Rewrite `architecture/current/performance.md` as capability-
+ oriented public documentation per ADR-0034's public-documentation
+ direction: what Compono optimizes for, what the benchmarks measure
+ (and don't), methodology, representative Consumer Scenario/External
+ Comparison results, the `UseBogus()` finding above, scaling
+ behavior, reproducibility, and how to get the full per-category
+ artifacts — not a historical narrative of Milestone 1/2/PR-review
+ optimization work. Implementation Strategies/Feature Overhead/
+ Source Generation results are summarized (not every parameter
+ value reproduced) per ADR-0034's "mostly engineering documentation
+ rather than front-page marketing" direction for implementation-
+ strategy benchmarks. Also fixed
+ `best-practices/performance-recommendations.md`'s two links, which
+ pointed at the now-tombstoned `docs/performance.md` instead of this
+ page.
+- [x] Removed the Implementation Strategies category entirely, per
+ [ADR-0034's Amendment](../adr/0034-benchmark-suite-strategy-and-redesign.md#amendment-2026-08-05-implementation-strategies-removed-it-compares-different-systems-not-one-variable) —
+ deleted `ImplementationStrategies/` and its dedicated `Baselines/`
+ classes (`HandwrittenComposer`, `CachedReflectionComposer`,
+ `UncachedReflectionComposer`), removed the "Implementation
+ strategies" section from `architecture/current/performance.md`,
+ and updated its category-count/overview text (six categories → five).
+ The five remaining categories, the reused model set, and every
+ fair-comparison/reporting rule not specific to Implementation
+ Strategies are unchanged.
+- [x] Fixed the `UseBogus()` finding above, per
+ [ADR-0027's Amendment](../adr/0027-compono-bogus-package-design.md#amendment-2026-08-05-bogusmembernameprovider-reuses-a-per-thread-faker-not-a-fresh-one-per-request):
+ `BogusMemberNameProvider` now caches one `Faker` per thread
+ (`ThreadLocal`, `trackAllValues: false`) instead of
+ constructing one per request, reseeding `Random` immediately before
+ every use — safe under concurrent access because a thread-local
+ instance is never touched by more than one thread, distinct from
+ the shared-`Faker` alternative ADR-0027 already considered and
+ rejected. New regression coverage: a concurrency test (200
+ concurrent resolutions on one shared provider instance, each
+ checked against an independent single-threaded reference value)
+ and a "convention throws mid-generate" test. Re-ran the full suite
+ afterward (`--filter '*'`, `DefaultJob`, same environment) and
+ rewrote `architecture/current/performance.md` from that single
+ fresh run — `UseBogus()`'s isolated overhead dropped from ~865× to
+ ~6.31×, and the full-profile Consumer Scenario cost from
+ 903.4 μs / 2,229.31 KB to 5.481 μs / 7.04 KB. The page also picked
+ up a clarity pass: explicit AutoFixture-relative-to-Compono ratio
+ direction, precise `DefaultJob` methodology wording (pilot/warmup/
+ measured iterations, not "statistically significant"), an explicit
+ "equivalent work" definition, a note connecting the Consumer
+ Scenario and Feature Overhead `UseBogus()` numbers' different
+ scope, and consistent `×` formatting throughout.
+- [x] Addressed a real adversarial PR review of this phase's work
+ (PR #53): **`BogusMemberNameProvider`'s thread-local reuse was
+ real but incomplete** — a custom `AddConvention` delegate could
+ mutate `Faker` state (`DateTimeReference`, a sub-generator, any of
+ `Faker`'s ~20 other public settable properties) that would then
+ leak into a later, unrelated built-in-convention request on the
+ same thread; reseeding `Random` alone didn't restore isolation.
+ Fixed by reusing the per-thread `Faker` only for built-in/alias
+ conventions (`BogusConventions.IsBuiltIn`, a reference-equality
+ check against the ten built-in delegates) and giving every custom
+ `AddConvention` delegate its own single-use `Faker`, with a new
+ regression test mutating `DateTimeReference` in a custom
+ convention and proving it doesn't perturb a later built-in
+ request. **The concurrency test didn't force genuine thread
+ overlap** — `Parallel.ForEachAsync` over a fully-synchronous body
+ could pass serially by scheduler luck; replaced with real
+ `Thread` + `Barrier` so all workers release simultaneously.
+ **`GeneratorDriverBenchmarks`' incremental tree was still built
+ via a fresh `ParseText` call** (given a different constructor
+ argument than the base tree, but not derived from it) rather than
+ `SyntaxTree.WithChangedText`, so it measured a wholesale reparse
+ under an "incremental" label; fixed to derive the touched tree via
+ an append-only `WithChangedText` edit so unaffected nodes keep
+ their base-tree identity. **`GraphDepthScalingBenchmarks` didn't
+ isolate depth** — its `MediumAggregate` shallow arm resolved seven
+ strings and a collection against `DeepGraph`'s single string,
+ conflating depth with total value-generation work; fixed to
+ compare `DeepLevel8` (depth 1, one string) against `DeepGraph`
+ (depth 8, same one-string leaf shape) — the real, isolated result
+ is 4.47× the mean and 2.65× the allocation for 8× the depth, a
+ much more meaningful number than the original 1.03×/1.23×.
+ **`architecture/current/performance.md` violated ADR-0034's own
+ Reporting Rules** by omitting Error/StdDev/Gen0/Gen1 from most
+ tables despite the page's own Methodology section claiming full
+ columns; rewritten with the complete mandatory column set on every
+ table. Also fixed: several broken/stale doc links and anchors
+ (a `removed--it` double-hyphen anchor MkDocs never generates;
+ `docs/plans/0002-...`'s link to a heading removed from the
+ rewritten performance page; `docs/index.md`/
+ `docs/getting-started/learning-paths.md`/`docs/concepts/providers.md`/
+ `docs/concepts/composition-model.md`/`docs/concepts/index.md`/
+ `docs/concepts/determinism-and-seeding.md`/
+ `docs/concepts/registrations-and-rules.md`/`docs/concepts/collections.md`/
+ `docs/how-to/register-a-type.md` still linking to tombstoned legacy
+ pages instead of their canonical replacements, including
+ `docs/index.md`'s stale "~6.1× faster" claim tied to a benchmark
+ class that no longer exists); a stale "49 benchmarks/15 classes"
+ verification-record bullet earlier in this list, now annotated with
+ the post-removal counts; and `docs/roadmap/future-packages.md`
+ wording that implied `Compono.Generators` was a fifth installable
+ package rather than an embedded analyzer. One reviewer claim
+ (returning `GeneratorDriver` from a benchmark method breaks
+ standard `net10` dry runs via missing `Compono.Generators` restore
+ assets) was investigated directly — reproduced from a fully clean
+ `bin`/`obj` state, `--job Dry` and `DefaultJob`, both TypeCount
+ matrices — and did not reproduce; left as-is, noted in the PR
+ reply.
+
## Phase 6: Contributor and repository readiness
**Status:** Not Started
@@ -833,6 +1068,24 @@ individually resolved, not left ambiguous):
- `Directory.Build.props` — package-validation and tags/release-notes
properties added, shared across all five packages (Phase 0).
- `Compono.slnx` — both new sample projects added (Phase 4).
+- `docs/adr/0034-benchmark-suite-strategy-and-redesign.md` — new (Phase
+ 5 Part B): the benchmark-suite redesign decision.
+- `benchmarks/Compono.Benchmarks/` — fully restructured (Phase 5 Part
+ B, done) into `Models/`, `Baselines/`, `ConsumerScenarios/`,
+ `ExternalComparison/`, `FeatureOverhead/`, `Scalability/`,
+ `SourceGeneration/` (five categories — `ImplementationStrategies/` was
+ implemented, then removed per
+ [ADR-0034's Amendment](../adr/0034-benchmark-suite-strategy-and-redesign.md#amendment-2026-08-05-implementation-strategies-removed-it-compares-different-systems-not-one-variable)),
+ replacing all 8 existing benchmark files per ADR-0034.
+ `Compono.Benchmarks.csproj` gains
+ `ProjectReference`s to `Compono.NSubstitute`/`Compono.Bogus`, its
+ existing `Compono.Generators` reference is now also a normal compile
+ reference (not analyzer-only), and it gains conditional
+ `Basic.Reference.Assemblies.Net100`/`Net110` package references.
+- `src/Compono.Generators/Compono.Generators.csproj` — gains
+ `InternalsVisibleTo="Compono.Benchmarks"` (Phase 5 Part B, done), so
+ `SourceGeneration/GeneratorDriverBenchmarks.cs` can construct
+ `ComponoIncrementalGenerator` directly.
- `docs/documentation-architecture.md` — Open Items section already
updated to reflect all six resolutions as part of this design pass;
further updated in place as content lands and stub statuses flip to
@@ -848,8 +1101,15 @@ buildable projects with, where practical, their own tests demonstrating
the pattern they showcase, matching `testing.md`'s bar for any real code
this plan produces. Package-readiness changes (Phase 0) are verified by
the new CI gates themselves (package validation, contents inspection,
-local-feed restore) rather than a separate hand-run test plan. Phase 8's
-acceptance checklist is this plan's actual end-to-end verification.
+local-feed restore) rather than a separate hand-run test plan. The
+redesigned benchmark suite (Phase 5 Part B) is verified by actually
+running it (`dotnet run -c Release`) and confirming every category
+produces real, sane results before `architecture/current/performance.md`
+is rewritten from them — a `BenchmarkDotNet` project has no pass/fail
+test suite of its own; its correctness is verified by inspection of its
+results, not by `testing.md`'s xUnit-based test conventions, which don't
+apply to `benchmarks/`. Phase 8's acceptance checklist is this plan's
+actual end-to-end verification.
## Notes
diff --git a/docs/public-api.md b/docs/public-api.md
index f7b6165..67ba1e1 100644
--- a/docs/public-api.md
+++ b/docs/public-api.md
@@ -1,687 +1,19 @@
-# Compono Public API Design
-
-## Purpose
-
-This document describes the intended developer experience.
-
-It is not a final API specification. Examples are design targets used to evaluate whether the underlying architecture remains approachable.
-
-## API Goals
-
-The public API should be:
-
-- Easy to discover
-- Small enough to learn
-- Explicit about configuration
-- Consistent between programmatic and test-framework usage
-- Friendly to source generation
-- Deterministic
-- Free of mutable global state
-
-## Programmatic Composition
-
-Basic creation should be simple:
-
-```csharp
-var composer = Composer.Create();
-
-var customer = composer.Create();
-var customers = composer.CreateMany(3);
-```
-
-Configuration uses the same root type via a builder callback (shipped, Milestone 3
-Phase 0 — [ADR-0017](adr/0017-immutable-composer-configuration-and-builder-model.md)).
-`WithSeed`, `Register`, `UseServiceProvider`, `AddProfile`, `WithCollectionSize`,
-and the `.For()` rule DSL below are all shipped (Phase 0/1/2/3 —
-[ADR-0019](adr/0019-registrations-and-service-provider-injection.md),
-[ADR-0018](adr/0018-composition-profiles.md),
-[ADR-0020](adr/0020-composition-configuration-rules.md)):
-
-```csharp
-var composer = Composer.Create(builder => builder
- .WithSeed(4219)
- .Register(_ => new FakeClock())
- .UseServiceProvider(app.Services)
- .AddProfile());
-```
-
-A registration factory can call `ICompositionContext.Resolve()` (no
-descriptor) to compose its own nested dependencies manually, distinct from the
-descriptor-based overload generated code uses — see
-[ADR-0019](adr/0019-registrations-and-service-provider-injection.md).
-
-`Composer` is the settled root type name — `Composer.Create()` (no configuration) and
-`Composer.Create(builder => ...)` (explicit configuration) are the same method,
-the latter with an empty callback for the former.
-
-## Configuration
-
-Configuration should read as a description of composition behavior:
-
-```csharp
-var composer = Composer.Create(builder => builder
- .WithSeed(4219)
- .WithCollectionSize(3)
- .Register(_ => new FakeClock())
- .AddProfile());
-```
-
-Integrations should add themselves through extension methods:
-
-```csharp
-var composer = Composer.Create(builder => builder
- .UseNSubstitute()
- .UseBogus());
-```
-
-The core package must not know those methods exist.
-
-Service injection uses the BCL's own `System.IServiceProvider` — no core dependency
-on `Microsoft.Extensions.DependencyInjection` or any container package:
-
-```csharp
-var composer = Composer.Create(builder => builder
- .UseServiceProvider(app.Services));
-```
-
-An exact `Register(...)` always wins over the configured `IServiceProvider`; a
-container miss (`null`) falls through to profile/type/member rules. See
-[ADR-0019](adr/0019-registrations-and-service-provider-injection.md) for full
-fallback semantics.
-
-## Profiles
-
-Profiles should make project-wide conventions reusable. A profile implements
-`ICompositionProfile` — an interface, not a base class, per
-[ADR-0018](adr/0018-composition-profiles.md):
-
-```csharp
-public sealed class ApplicationTestProfile : ICompositionProfile
-{
- public void Configure(CompositionBuilder builder)
- {
- builder
- .UseNSubstitute()
- .UseBogus(options => options.Locale = "en_US")
- .Register(_ =>
- new FakeClock(
- new DateTimeOffset(
- 2026, 1, 1, 0, 0, 0,
- TimeSpan.Zero)));
- }
-}
-```
-
-Profile composition should be supported:
-
-```csharp
-builder
- .AddProfile()
- .AddProfile();
-```
-
-Profiles apply eagerly, in call order — that order *is* the precedence rule. A
-conflicting registration or rule (from any combination of direct calls and
-profiles) is a build-time `CompositionConfigurationException` naming every
-conflicting source, not a silent override; a profile that (transitively) adds
-itself is a build-time cycle diagnostic, not a stack overflow.
-
-## Row Composition (Test-Framework Integrations)
-
-Resolved by [ADR-0021](adr/0021-row-composition-entry-point-for-test-framework-integrations.md)
-(`Accepted`, implemented — Milestone 4 Phase 0). A test-framework
-integration that needs to compose several *sibling* top-level values in
-one shared scope — e.g. one xUnit theory row's own method parameters —
-uses `Composer.CreateRow`, not `Create()`/`CreateMany()`, which each
-start a brand-new, independent scope per call:
-
-```csharp
-var composer = Composer.Create();
-var row = composer.CreateRow(typeof(OrderServiceTests));
-
-var repository = row.ResolveShared(repositoryDescriptor);
-var service = row.Resolve(serviceDescriptor);
-```
-
-`CompositionRow` is the only public surface a test-framework integration
-uses to reach the engine this way — `Compono` core's own
-`CompositionContext` stays `internal`. It implements
-`ICompositionContext`, so a composed value's own nested requests (a
-generated plan's constructor parameters) are unaffected — generated code
-always programs against `ICompositionContext`, never `CompositionRow`
-directly.
-
-- `Resolve(descriptor)`/`ResolveCollectionSize()` — ordinary
- composition, forwarded straight to the wrapped context; no different
- from `Create()`'s own resolution.
-- `Resolve()` (the descriptor-less overload `CompositionRow` only
- carries to satisfy `ICompositionContext`'s full interface shape) is
- **not** a usable direct row-composition entry point — it forwards to
- the manual-resolve seam meant for a registration/configuration-rule
- factory's own `context.Resolve()` calls, which throws
- `InvalidOperationException` unless such a factory is actively being
- invoked. A `CompositionRow`-holding caller can never satisfy that
- condition (factories are always invoked with the raw internal context,
- never a `CompositionRow`), so calling this overload directly on a row
- always throws.
-- `ResolveShared(descriptor)` — composes `TValue` and additionally
- stores the result into this row's shared scope: a later request for the
- same type in this row — including one made by a nested generated plan,
- e.g. a SUT's own constructor parameter — transparently reuses it instead
- of composing an independent value. This is the mechanism `[Shared]`
- parameters (see xUnit v3 Experience, below) are built on.
-- `ShareExplicit(descriptor, value)` — stores an already-known
- value (an inline theory argument) directly into the row's shared scope,
- with no pipeline dispatch or random fork consumed.
-- `Seed` — this row's deterministic root seed, an `int` matching
- `WithSeed(int)`'s own contract exactly, so a seed read here is always
- pasteable back into `WithSeed(...)`/`[Compose(Seed = ...)]` to reproduce
- the same row.
-- Only one shared value per type is allowed per row — a second
- `ResolveShared`/`ShareExplicit` call for a type already shared in this
- row throws a `CompositionException` naming the type, rather than
- silently overwriting or reusing the first value.
-
-## xUnit v3 Experience
-
-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)). 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
-other non-serializable reference type, aren't safely enumerable before a
-test actually runs).
-
-A composed theory should be concise:
-
-```csharp
-[Theory]
-[Compose]
-public async Task Saves_order(
- [Shared] IOrderRepository repository,
- CreateOrderHandler handler,
- CreateOrder command)
-{
- await handler.Handle(command);
-
- await repository.Received(1)
- .SaveAsync(
- Arg.Any