diff --git a/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.fr.md b/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.fr.md new file mode 100644 index 00000000..310180b2 --- /dev/null +++ b/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.fr.md @@ -0,0 +1,1048 @@ +# Dummies — Audit d'architecture et de conception + +🌍 **Langues :** +đŸ‡«đŸ‡· Français (ce fichier) | 🇬🇧 [English](./2026-07-20-dummies-architecture-and-design-audit.md) + +**Date :** 2026-07-20 +**RĂ©vision auditĂ©e :** `3bf89e3` (sommet de `main` au moment de l'audit) +**PĂ©rimĂštre :** la seule bibliothĂšque `Dummies` — `Dummies/`, `Dummies.UnitTests/`, son outillage de +garde (`tools/dummies-check/`, `.github/workflows/dummies.yml`), sa documentation, et les ADR qui la +gouvernent. +**Statut :** consultatif. ConformĂ©ment Ă  la convention du dĂ©pĂŽt (ADR-0004), cet audit produit des +recommandations, jamais des bloqueurs ; toute modification d'ADR proposĂ©e est un brouillon que +`@reefact` accepte ou rejette. + +**MĂ©thode.** L'intĂ©gralitĂ© du code de la bibliothĂšque (~8 700 lignes rĂ©parties sur 54 fichiers C#) et de +la suite de tests (~2 500 lignes, 17 fichiers) a Ă©tĂ© lue ; les 26 ADR ont Ă©tĂ© classĂ©es par +applicabilitĂ© et les 8 applicables revues Ă  la fois pour leur qualitĂ© intrinsĂšque et pour la conformitĂ© +de l'implĂ©mentation ; les constats ont Ă©tĂ© vĂ©rifiĂ©s de façon contradictoire face au code, et les trois +dĂ©fauts de comportement rapportĂ©s ci-dessous ont Ă©tĂ© **reproduits indĂ©pendamment Ă  l'exĂ©cution** contre +la bibliothĂšque compilĂ©e. La suite de tests unitaires complĂšte a Ă©tĂ© exĂ©cutĂ©e : **222/222 rĂ©ussis** +(`dotnet test Dummies.UnitTests`, exĂ©cuteur net10.0). Les jugements sont calibrĂ©s sur les objectifs +affichĂ©s de la bibliothĂšque — des tests lisibles, des donnĂ©es de test expressives, un dĂ©terminisme +optionnel, une API fluide et dĂ©couvrable, la simplicitĂ© — et dĂ©libĂ©rĂ©ment *pas* sur les objectifs des +frameworks de test par propriĂ©tĂ©s ou de fuzzing, ce que cette bibliothĂšque n'est explicitement pas. + +--- + +## 1. RĂ©sumĂ© exĂ©cutif + +Dummies est une jeune bibliothĂšque construite avec un niveau d'exigence inhabituel. Son idĂ©e +architecturale centrale — projeter chaque type discret dans un espace ordinal 64 bits partagĂ© pour +qu'un seul moteur possĂšde les bornes, les exclusions, la dĂ©tection de conflits et l'Ă©chantillonnage de +treize gĂ©nĂ©rateurs Ă  la fois — est Ă©lĂ©gante et correctement exĂ©cutĂ©e. Sa discipline de messages +d'erreur (chaque contrainte contradictoire nomme les *deux* cĂŽtĂ©s, chaque Ă©chec de gĂ©nĂ©ration nomme la +graine qui le rejoue) surpasse celle de la plupart des bibliothĂšques matures du domaine. Sa base d'ADR +est exemplaire : les dĂ©cisions sont consignĂ©es avec des contraintes honnĂȘtes, de vraies alternatives et +des compromis chiffrĂ©s. + +L'audit a nĂ©anmoins trouvĂ© **trois vĂ©ritables dĂ©fauts de comportement**, tous reproduits Ă  l'exĂ©cution : + +1. **Critique — `AnyDecimal` ne peut jamais gĂ©nĂ©rer la moitiĂ© haute de sa plage.** Une fraction censĂ©e + ĂȘtre uniforme dans [0, 1) est construite Ă  partir de trois tirages de 31 bits divisĂ©s par un + dĂ©nominateur de 96 bits, et plafonne prĂšs de 0,5 ; `Any.Decimal().Between(0m, 100m)` ne dĂ©passe + jamais ~49,9999 (`DecimalIntervalSpec.cs:145`). +2. **Majeur — le « nudge » d'exclusion de `AnySingle`/`AnyHalf` se bloque.** La marche + d'Ă©vitement des collisions avance d'un ulp de *double* au lieu de l'ulp du type, si bien que la + quantification retombe sur la mĂȘme valeur et qu'une spĂ©cification satisfiable comme + `Any.Half().Between((Half)1f, (Half)1.001f).DifferentFrom((Half)1f)` lĂšve une + `AnyGenerationException` pour ~la moitiĂ© des graines (`ContinuousIntervalSpec.cs:189`). +3. **Majeur — une plage de classe regex se terminant Ă  `ïżż` boucle Ă  l'infini.** La boucle + d'expansion de la classe incrĂ©mente un `char` 16 bits qui reboucle Ă  `0xFFFF`, de sorte que + `Any.StringMatching(@"[ -ïżż]")` ne rend jamais la main (`RegexParser.cs:398`). + +Les trois partagent une cause racine qu'il vaut la peine de nommer : **la suite de tests vĂ©rifie +l'appartenance, jamais l'atteignabilitĂ©.** Les tests vĂ©rifient que les valeurs gĂ©nĂ©rĂ©es satisfont les +contraintes ; aucun test ne vĂ©rifie que le domaine dĂ©clarĂ© est atteignable dans son entier, ni qu'une +spĂ©cification dĂ©clarĂ©e satisfiable gĂ©nĂšre effectivement. C'est l'angle mort prĂ©cis d'une suite par +ailleurs bien conçue, et le combler compte davantage que n'importe quel correctif isolĂ©. + +Un fait de cadrage attĂ©nue considĂ©rablement tout cela : **Dummies n'a jamais Ă©tĂ© publiĂ©e.** Il n'y a +aucun tag `dum-v*` ; le changelog ne contient qu'une section *Unreleased* vide. Chaque dĂ©faut ci-dessus +peut ĂȘtre corrigĂ©, et chaque contrat dĂ©cidĂ©, Ă  coĂ»t de compatibilitĂ© nul. La recommandation-phare de +cet audit est de traiter la fenĂȘtre prĂ©-1.0 comme l'a fait l'ADR-0020 — le moment le moins cher pour +dĂ©cider — et de solder les points des §11–§12 avant la premiĂšre publication. + +Au-delĂ  des dĂ©fauts, les constats significatifs sont : la surface `Any`/`AnyContext` recopiĂ©e Ă  la main +et les quatorze gĂ©nĂ©rateurs numĂ©riques clonĂ©s ne portent **aucun garde-fou de paritĂ©** (et la dĂ©rive de +documentation a dĂ©jĂ  commencĂ©) ; le **contrat de dĂ©terminisme prĂ©sente des lacunes de documentation** +(des tirages concurrents dans un mĂȘme scope Ă  graine annulent silencieusement la rejouabilitĂ© ; la +stabilitĂ© des graines entre versions n'est ni promise ni Ă©cartĂ©e ; l'ancrage ADR du contrat a Ă©tĂ© perdu +lorsque l'ADR-0006 a Ă©tĂ© remplacĂ©e) ; la **cible netstandard2.0 n'est jamais exĂ©cutĂ©e par la propre +suite de tests de Dummies** (seulement transitivement, via le job de plancher de FirstClassErrors) ; et +il n'existe **aucune rĂ©fĂ©rence utilisateur de la surface de contraintes** — le README du dĂ©pĂŽt ne +mentionne mĂȘme pas le paquet. L'analyse des manques (§10) juge la couverture de types rĂ©ellement +complĂšte au regard de la philosophie de la bibliothĂšque ; les deux absences qui mĂ©ritent la qualitĂ© de +surprenantes sont un combinateur de choix *de premier niveau* (`Any.OneOf(params T[])` / +`Any.ElementOf(...)`) et des contraintes d'exclusion sur `AnyString` — le seul gĂ©nĂ©rateur scalaire qui +en soit dĂ©pourvu. + +## 2. Évaluation globale + +**Verdict : une bibliothĂšque prĂ©-publication trĂšs solide — l'architecture et le processus sont ses +forces ; le test de correction dans l'espace des valeurs est sa seule faiblesse systĂ©mique.** + +JugĂ©e domaine par domaine face aux objectifs affichĂ©s : + +| Domaine | Évaluation | +|---|---| +| Architecture | Excellente. DĂ©coupage en couches propre (gĂ©nĂ©rateurs publics → specs internes → Ă©chantillonnage), un moteur ordinal partagĂ©, une sĂ©paration de moteurs fondĂ©e, des points de composition sur une interface minuscule. | +| Conception d'API | Excellente, avec une poignĂ©e d'asymĂ©tries d'apparence dĂ©libĂ©rĂ©e mais non consignĂ©es (§8). | +| Diagnostics d'erreur | Exceptionnels — la force signature de la bibliothĂšque. | +| DĂ©terminisme | Conception saine, correctement implĂ©mentĂ©e au niveau `AsyncLocal`/`ExecutionContext` ; contrat sous-documentĂ© Ă  ses bords (§7.3). | +| Correction | Trois dĂ©fauts reproduits, deux d'entre eux exactement dans le code qu'une suite fondĂ©e sur la seule appartenance ne peut pas voir (§4.1). | +| StratĂ©gie de test | Bien formĂ©e (comportement d'abord, boĂźte noire, adossĂ©e Ă  un oracle pour la regex, Ă  l'abri des tests instables) mais aveugle Ă  l'atteignabilitĂ© (§9.3). | +| Documentation | Docs XML remarquables ; documentation utilisateur maigre et difficile Ă  trouver (§4.4). | +| MaintenabilitĂ© | La duplication est importante mais disciplinĂ©e (zĂ©ro erreur de copier-coller trouvĂ©e dans les familles de clones) ; le risque est la dĂ©rive non gardĂ©e, pas la pourriture prĂ©sente (§9). | +| Base d'ADR | QualitĂ© exemplaire ; deux lacunes structurelles — le contrat de dĂ©terminisme et le moteur ordinal n'ont pas d'ADR propre (§5). | + +La forme d'ensemble est celle d'une bibliothĂšque Ă©crite avec grand soin par un petit nombre de mains : +les *dĂ©cisions* sont systĂ©matiquement justes et systĂ©matiquement consignĂ©es, tandis que les filets de +sĂ©curitĂ© qui protĂšgent ces dĂ©cisions des mains futures (gardes de paritĂ©, tests d'atteignabilitĂ©, +rĂ©fĂ©rences d'API) ne sont pas encore en place. Le prĂ©-1.0 est le moment de les installer. + +## 3. Forces + +Elles sont mĂ©ritĂ©es, vĂ©rifiĂ©es face au code, et Ă  prĂ©server dĂ©libĂ©rĂ©ment. + +### 3.1 L'unification par l'espace ordinal + +Chaque type discret — les dix entiers de 64 bits ou moins, `DateTime`, `DateTimeOffset`, `TimeSpan`, +`DateOnly`, `TimeOnly` — se projette en prĂ©servant l'ordre dans un espace ordinal non signĂ© 64 bits +(`OrdinalMapping.FromInt64` inverse le bit de signe ; `OrdinalIntervalSpec.cs:9-23`) et partage **un +seul** moteur pour les bornes, les listes d'autorisation, les exclusions, la dĂ©tection de conflits, la +cardinalitĂ© et l'Ă©chantillonnage (`OrdinalIntervalSpec`). L'algorithme d'exclusion est exact — un index +tirĂ© est projetĂ© sur le k-iĂšme ordinal non exclu en une seule passe sur une liste d'exclusions triĂ©e +(`OrdinalIntervalSpec.cs:194-202`) — de sorte que la gĂ©nĂ©ration est un tirage unique, jamais +tirer-puis-recommencer. Un correctif Ă  un message de conflit ou Ă  un cas limite atteint tous les +gĂ©nĂ©rateurs discrets simultanĂ©ment. C'est le bon niveau oĂč appliquer DRY : la *logique* est partagĂ©e +tandis que les fines façades par type restent simples et lisibles. + +### 3.2 Provenance des contraintes et validation immĂ©diate + +Chaque borne se souvient de la chaĂźne de contrainte qui l'a posĂ©e (`"Between(1, 6)"`, `"Positive()"`), +de sorte qu'un conflit nomme **les deux** cĂŽtĂ©s au moment de la dĂ©claration : + +``` +Cannot apply LessThan(10) because GreaterThan(100) already requires values greater than or equal to 101. +``` + +La discipline tient uniformĂ©ment sur chaque gĂ©nĂ©rateur et chaque moteur de spĂ©cification, y compris des +validations transversales qu'on ne s'attendrait pas Ă  trouver (un jeu de caractĂšres `Numeric()` rejette +un prĂ©fixe contenant des lettres, en nommant le caractĂšre fautif — `StringSpec.cs:254-275`). CombinĂ©e Ă  +la vĂ©rification immĂ©diate de satisfiabilitĂ© (« un gĂ©nĂ©rateur qui existe peut toujours gĂ©nĂ©rer »), un +`Arrange` impossible Ă©choue Ă  la ligne qui l'a Ă©crit, pas Ă  un tirage ultĂ©rieur. C'est la signature de +la bibliothĂšque, et elle est exĂ©cutĂ©e avec constance. + +### 3.3 La machinerie de dĂ©terminisme est bien faite au niveau difficile + +La piĂšce maĂźtresse — les gĂ©nĂ©rateurs stockent une `RandomSource` et ne rĂ©solvent `.Current` qu'au +moment de `Generate()` — est ce qui permet Ă  une recette construite hors d'un `Any.Reproducibly(...)` +de gĂ©nĂ©rer de façon dĂ©terministe Ă  l'intĂ©rieur (`RandomSource.cs:3-9`). La sĂ©mantique de scope +`AsyncLocal` a Ă©tĂ© examinĂ©e de prĂšs et tient : la restauration par `using` de la surcharge synchrone est +correcte ; la mutation `UseSeed` de la surcharge asynchrone ne peut pas fuir vers l'appelant (les +mutations d'`ExecutionContext` d'une mĂ©thode asynchrone ne reviennent pas en arriĂšre) ; l'imbrication +restaure le scope externe intact ; `ConfigureAwait(false)` est sans effet sur la circulation de +l'`ExecutionContext`. La graine est rapportĂ©e de bout en bout : une fabrique utilisateur qui lĂšve une +exception dans `.As(...)` produit une `AnyGenerationException` nommant la valeur gĂ©nĂ©rĂ©e *et* la graine +(`AnyDerivation.cs:59-73`) ; une saturation de collection distincte fait de mĂȘme +(`CollectionState.cs:246-254`). + +Deux piĂšges subtils liĂ©s Ă  la double cible ont Ă©tĂ© anticipĂ©s et documentĂ©s Ă  l'endroit exact du danger : +l'Ă©chantillonneur inclusif de `RandomSampling` n'est dĂ©libĂ©rĂ©ment *pas* nommĂ© `NextInt64` parce que, sur +la cible net8.0, la mĂ©thode d'instance Ă  borne exclusive du framework l'emporterait dans la rĂ©solution +de surcharge et changerait silencieusement la sĂ©mantique (`RandomSource.cs:129-137`) ; et l'extension +`OrNull` est scindĂ©e en deux classes parce que des surcharges d'un mĂȘme nom contraintes l'une Ă  +`struct` et l'autre Ă  `class` entreraient en collision (`NullableExtensions.cs:47-52`). C'est le genre +de soin qui ne se rattrape pas aprĂšs coup. + +### 3.4 Des Ă©chappatoires bornĂ©es partout — aucune reprise non bornĂ©e nulle part + +L'affirmation « construit pour satisfaire, jamais gĂ©nĂ©rer-puis-filtrer » rĂ©siste Ă  l'examen avec trois +exceptions honnĂȘtes, consignĂ©es en ADR, chacune *bornĂ©e* : le tirage dĂ©dupliquĂ© des collections +distinctes (budgetĂ©, gĂ©nĂ©reux façon collectionneur de coupons, remis Ă  zĂ©ro Ă  chaque progrĂšs — +`CollectionState.cs:236-244`), le nudge d'exclusion du domaine continu (marche jusqu'Ă  la valeur +reprĂ©sentable voisine), et l'Ă©vitement de collision d'`AnyGuid` (une incrĂ©mentation avec retenue sur +toute la largeur qui se termine de façon prouvable — `AnyGuid.cs:27-36`). Chaque mode d'Ă©chec produit un +message actionnable, porteur de graine, plutĂŽt qu'un blocage. + +### 3.5 Le soin aux bords + +De petites choses qui rĂ©vĂšlent le niveau d'exigence : `AnyDateTime.OneOf` se souvient des valeurs +originales de l'appelant pour que l'aller-retour ordinal ne normalise pas silencieusement le +`DateTimeKind` (`AnyDateTime.cs:124-131`) ; l'agencement d'une collection est mĂ©langĂ© par Fisher-Yates +pour qu'une collection factice n'annonce jamais un invariant de position sur lequel un test pourrait +s'appuyer par accident (`CollectionState.cs:46-53`) ; `CountSpec` et `StringSpec` saturent au lieu de +dĂ©border sur des minima dĂ©clarĂ©s Ă©normes ; la covariance d'`IAny` fait que les interfaces de +collection en lecture seule (`IReadOnlyList`, etc.) sont servies gratuitement. + +### 3.6 Le sous-systĂšme regex est bien bĂąti pour le pĂ©rimĂštre dĂ©cidĂ© + +L'analyseur syntaxique Ă  descente rĂ©cursive Ă©crit Ă  la main (`RegexParser.cs`, 457 lignes — le plus gros +bloc de logique unique de la bibliothĂšque) est bien structurĂ©, commentĂ© sur le « pourquoi » et +disciplinĂ© sur sa taxonomie de rejet Ă  deux canaux : `ArgumentException` pour les motifs malformĂ©s, +`UnsupportedRegexException` nommant la construction et la position pour ceux qui sont bien formĂ©s mais +non rĂ©guliers. La suite de tests valide les chaĂźnes gĂ©nĂ©rĂ©es contre le **vrai moteur regex de .NET +utilisĂ© comme oracle** sur un corpus Ă  graine fixe — exactement la bonne façon de tester un gĂ©nĂ©rateur. +(Les dĂ©fauts trouvĂ©s Ă  ses bords sont cataloguĂ©s aux §4.1 et §4.2 ; ils ne changent rien Ă  l'Ă©valuation +selon laquelle l'approche de l'ADR-0025 Ă©tait saine et honnĂȘtement argumentĂ©e.) + +### 3.7 Empaquetage, frontiĂšre et processus + +La frontiĂšre zĂ©ro-dĂ©pendance, agnostique aux erreurs, est appliquĂ©e de trois façons : un commentaire de +`.csproj` Ă©nonçant la rĂšgle, un test d'architecture fondĂ© sur l'intention qui Ă©choue Ă  toute rĂ©fĂ©rence +d'assemblage hors BCL (`ArchitectureTests.cs:27-37`), et le garde-fou d'artefact empaquetĂ© +`dummies-check` — un vrai programme consommateur, exĂ©cutĂ© en CI contre l'*artefact packĂ©* pour chaque +cible, qui prouve que l'asset net8.0 porte les gĂ©nĂ©rateurs modernes, que l'asset netstandard2.0 ne les +porte pas, que les contraintes et conflits se comportent bien, et que des contextes Ă  mĂȘme graine +rejouent (`tools/dummies-check/Program.cs`). L'empaquetage lui-mĂȘme est de niveau production : SourceLink +avec sources non suivies embarquĂ©es, builds CI dĂ©terministes, symboles snupkg, un SBOM SPDX embarquĂ© au +moment du pack, des assets de release Ă  provenance attestĂ©e (`Directory.Build.props:10-23`, +`Dummies.csproj:60-66`, `release.yml`). La base d'ADR qui consigne tout cela est discutĂ©e au §5 — c'est +une force en soi. + +### 3.8 La philosophie d'API est cohĂ©rente et documentĂ©e lĂ  oĂč l'utilisateur regarde + +L'idĂ©e « les contraintes expriment ce que le code environnant *requiert*, jamais ce que le test +assertit » est Ă©noncĂ©e sur le point d'entrĂ©e, sur chaque gĂ©nĂ©rateur, dans le README du paquet et dans le +guide utilisateur — la mĂȘme phrase, dĂ©libĂ©rĂ©ment. Le parti pris de ne pas offrir de contraintes +relatives Ă  l'horloge (`AnyDateTime` n'a pas d'`InThePast()`) est documentĂ© Ă  chaque endroit oĂč +l'utilisateur le chercherait, avec la justification de reproductibilitĂ© attachĂ©e. Les quasi-synonymes +rĂ©vĂ©lateurs d'intention sont expliquĂ©s honnĂȘtement : `DifferentFrom(x)` est documentĂ© comme +sĂ©mantiquement `Except(x)` avec un nom qui porte l'intention ; `Containing` (une valeur connue +maintenant) vs `ContainingAny` (un gĂ©nĂ©rateur tirĂ© au moment de la construction) est une distinction +rĂ©ellement utile. + +## 4. Faiblesses + +ClassĂ©es par sĂ©vĂ©ritĂ©. Les points 4.1 et 4.3 sont ceux qui devraient conditionner une premiĂšre +publication. + +### 4.1 DĂ©fauts de comportement reproduits + +**(a) `AnyDecimal` n'atteint jamais la moitiĂ© haute d'une plage — critique.** + +`DecimalIntervalSpec.cs:144-149` : + +```csharp +// A uniform-enough fraction in [0, 1): 93 random bits over the full decimal mantissa scale. +decimal fraction = new decimal(random.Next(), random.Next(), random.Next(), false, 28) / MaxFraction; +decimal mid = _min / 2 + _max / 2; +decimal half = _max / 2 - _min / 2; +decimal candidate = Clamped(mid + (fraction * 2 - 1) * half); +``` + +`Random.Next()` renvoie un `int` non nĂ©gatif, si bien que le bit de poids fort de **chaque limbe de +32 bits** de la mantisse de 96 bits est toujours zĂ©ro, tandis que `MaxFraction` est le maximum *plein* +de la mantisse 96 bits (`7,9228
`, `DecimalIntervalSpec.cs:14`). La fraction vit donc dans +[0, ~0,49999986], pas [0, 1) ; `(fraction * 2 - 1)` vit dans [−1, ~0) ; et chaque candidat atterrit dans +`[min, mid)`. Le maximum inclusif documentĂ© sur `AnyDecimal.Between` (`AnyDecimal.cs:112`) est +inatteignable — tout comme tout ce qui est au-dessus du milieu. Reproduit indĂ©pendamment pour cet +audit : le maximum de 200 000 tirages de `Any.Decimal().Between(0m, 100m)` valait **49,99992
**. + +Pourquoi cela compte au-delĂ  de l'Ă©vidence : un test utilisant `Any.Decimal().Between(0m, 100m)` pour +exercer « n'importe quel pourcentage valide » n'exerce silencieusement jamais 50–100 — la promesse +centrale de la bibliothĂšque (« arbitraire mais valide, pour que les hypothĂšses cachĂ©es ressortent ») est +retournĂ©e en une hypothĂšse cachĂ©e qui lui est propre. Le correctif est petit : construire la fraction Ă  +partir de 96 bits vĂ©ritablement uniformes, par exemple : + +```csharp +// after: 12 random bytes fill all three 32-bit limbs uniformly +// (the decimal ctor reads the int limbs as raw 32-bit patterns) +byte[] limbs = new byte[12]; +random.NextBytes(limbs); +decimal fraction = new decimal( + BitConverter.ToInt32(limbs, 0), + BitConverter.ToInt32(limbs, 4), + BitConverter.ToInt32(limbs, 8), + false, 28) / MaxFraction; +``` + +(toute construction remplissant les 96 bits de mantisse de façon uniforme convient — les trois appels +`Next()` actuels fixent Ă  zĂ©ro le bit de poids fort de chaque limbe et ne peuvent jamais tirer un limbe +de `2^31−1`), puis ajouter le test d'atteignabilitĂ© du §11 point 2. À noter : le commentaire lui-mĂȘme +(« 93 random bits over the full mantissa scale ») documente une intention que le code n'honore pas — et +mĂȘme 93 bits bien placĂ©s n'atteindraient pas l'octant supĂ©rieur d'un dĂ©nominateur de 96 bits. + +**(b) Le nudge d'exclusion de `AnySingle`/`AnyHalf` se bloque sur des specs satisfiables — majeur.** + +`ContinuousIntervalSpec.cs:188-198` : quand une valeur tirĂ©e entre en collision avec un point exclu, la +marche avance avec le `NextUp` **statique, en espace double** (ligne 189) au lieu du lambda `_nextUp` +*conscient du type* que `AnySingle`/`AnyHalf` fournissent prĂ©cisĂ©ment pour avancer dans leur propre +Ă©chelle de valeurs reprĂ©sentables (`AnySingle.cs:20`, `AnyHalf.cs:22`) — et que les chemins Ă  borne +exclusive utilisent dĂ©jĂ  correctement (lignes 120, 125). Un ulp de double au-dessus d'un `float`/`Half` +reprĂ©sentable se re-quantifie vers la mĂȘme valeur, l'Ă©chappatoire `next > _max` de la ligne 190 est +inatteignable (`Quantized` borne d'abord Ă  `_max`, lignes 203-209), si bien que le budget de 128 pas se +consume et qu'une spec *satisfiable* lĂšve. Reproduit indĂ©pendamment : +`Any.Half().Between((Half)1f, (Half)1.001f).DifferentFrom((Half)1f).Generate()` a levĂ© une +`AnyGenerationException` pour **250 graines sur 500** ; le scĂ©nario `AnyDouble` identique ne lĂšve jamais +(sa quantification est l'identitĂ©). Le correctif tient en un jeton — `Quantized(_nextUp(candidate))` — +plus un test de non-rĂ©gression par type continu. + +Ce dĂ©faut mĂ©rite une note de conception : c'est exactement la classe d'Ă©chec que prĂ©dit l'architecture +de la bibliothĂšque. Le moteur a Ă©tĂ© paramĂ©trĂ© par des lambdas `quantize`/`nextUp` *parce que* les types +Ă©troits doivent avancer dans leur propre Ă©chelle ; un site d'appel dans le mĂȘme fichier a oubliĂ© le +paramĂštre. Une suite de scĂ©narios paramĂ©trĂ©e et transverse aux moteurs (§9.3) est la rĂ©ponse +structurelle. + +**(c) Une plage de classe se terminant Ă  `ïżż` boucle Ă  l'infini — majeur.** + +`RegexParser.cs:398` : + +```csharp +for (char character = low; character <= high; character++) { set.Add(character); } +``` + +Quand `high == 'ïżż'` (atteignable via l'Ă©chappement supportĂ© `\uHHHH`), le `char` 16 bits reboucle Ă  +`0x0000` et `character <= high` est toujours vrai. Reproduit indĂ©pendamment : +`Any.StringMatching(@"[ -ïżż]")` n'a pas rendu la main en cinq secondes (blocage dur), alors que le mĂȘme +motif est une regex .NET valide. Un blocage au moment de la dĂ©claration est le pire mode d'Ă©chec que +cette bibliothĂšque puisse exhiber — son identitĂ© est d'*Ă©chouer vite avec une cause nommĂ©e*. Correctif : +garder le rebouclage (`if (character == high) break;` dans la boucle, ou itĂ©rer sur un `int`), et +rĂ©pliquer le contrĂŽle dans la boucle jumelle privĂ©e `RegexAlphabet.Range` (`RegexAlphabet.cs:66-71`) +par dĂ©fense en profondeur. + +**(d) Les groupes d'Ă©quilibrage et les noms de groupe invalides sont acceptĂ©s silencieusement — majeur, +dans le sens qui rompt le contrat.** + +`SkipGroupName` (`RegexParser.cs:295-300`) balaie jusqu'au terminateur sans aucune validation. En +consĂ©quence `(?<-a>x)` — un *groupe d'Ă©quilibrage* (balancing group), non rĂ©gulier, de la mĂȘme famille +que les rĂ©fĂ©rences arriĂšres que la bibliothĂšque rejette fiĂšrement — est traitĂ© comme un groupe nommĂ© +ordinaire : `Any.StringMatching(@"(?y)?(?<-a>x)")` gĂ©nĂšre `"x"`, que le vrai moteur ne reconnaĂźt +**pas** (vĂ©rifiĂ© : le langage du motif est exactement `{"yx"}`). Les noms de groupe invalides +(`(?x)`) sont de mĂȘme acceptĂ©s lĂ  oĂč .NET les rejette. C'est le seul endroit trouvĂ© par l'audit oĂč +la promesse signature de la bibliothĂšque — *« une erreur claire vaut mieux qu'une valeur qui ne +correspond pas rĂ©ellement »* (ADR-0025) — est rompue. Le correctif est local : valider le nom capturĂ© +(rejeter `-` en `Unsupported("a balancing group 
")`, rejeter les caractĂšres non-mot en +`Malformed(...)`). + +**(e) DĂ©fauts mineurs dans le mĂȘme sous-systĂšme.** L'exception de limite de gĂ©nĂ©ration accuse « a nested +unbounded quantifier » mĂȘme quand la vraie cause est un quantificateur *bornĂ©* de grande taille +(`(a{1000}){1000}` — le message affirme un diagnostic faux ; `RegexNode.cs:31-37`) ; quelques motifs que +le vrai moteur accepte sont refusĂ©s par prudence (`^*`, `abc$$` — tandis que `^^abc` est acceptĂ©, une +asymĂ©trie Ă©vitable ; un `-[` en tĂȘte de classe est mal lu comme une soustraction) ; et une classe +nĂ©gative bien formĂ©e dont les membres sortent de l'univers imprimable est mal classĂ©e en *malformĂ©e* au +lieu de *non supportĂ©e*. Tous ces cas Ă©chouent dans le sens sĂ»r (refus, jamais mauvaise gĂ©nĂ©ration) et +sont cosmĂ©tiques Ă  cĂŽtĂ© de (c) et (d). + +### 4.2 L'affirmation « ASCII imprimable » est exagĂ©rĂ©e en trois endroits + +`RegexAlphabet.cs:3-9`, `AnyPattern.cs:15-16` et `Any.cs:70` affirment tous que chaque terminal se +rĂ©sout en ASCII imprimable (0x20–0x7E). Le code — correctement — Ă©met exactement les caractĂšres que le +motif exige : `\t`, `\a`, `\cA`, `\0` et les littĂ©raux `\uHHHH` peuvent ĂȘtre non imprimables ou +non-ASCII, et le propre test de la bibliothĂšque l'assertit (`AnyPatternTests` — `\a` → U+0007). La +restriction ne s'applique vraiment que lĂ  oĂč le motif laisse le caractĂšre *libre* (raccourcis, le point, +classes nĂ©gatives). Comme l'ADR-0025 dĂ©clare explicitement l'univers de caractĂšres comme un comportement +sur lequel les consommateurs peuvent s'appuyer, les trois emplacements de doc devraient le dire +prĂ©cisĂ©ment (§11 point 6). + +### 4.3 Des surfaces recopiĂ©es Ă  la main sans garde-fou de paritĂ©, et la dĂ©rive a dĂ©jĂ  commencĂ© + +Deux structures miroir doivent s'accorder mĂ©thode par mĂ©thode, et rien ne contrĂŽle ni l'une ni l'autre : + +* **`Any` ↔ `AnyContext`** : chaque point d'entrĂ©e scalaire existe deux fois (21 sur la cible + netstandard2.0, 26 sur net8.0, en comptant les deux surcharges de `StringMatching`) — + `Any.cs:54-317` face Ă  `AnyContext.cs:48-296`. Le miroir est une conception lĂ©gitime (la composition + et les collections ne sont dĂ©libĂ©rĂ©ment *pas* recopiĂ©es — elles hĂ©ritent d'un contexte via les sources + de leurs opĂ©randes, ce qui est Ă©lĂ©gant), mais un nouveau type scalaire ajoutĂ© Ă  `Any` et oubliĂ© sur + `AnyContext` compilerait, passerait les 222 tests, et livrerait un trou dans la surface dĂ©terministe. + La dĂ©rive de formulation est dĂ©jĂ  visible Ă  l'intĂ©rieur d'`AnyContext` lui-mĂȘme (deux tournures + diffĂ©rentes du dĂ©terminisme selon les fabriques ; sa doc de `Guid()` mentionne `Any.Reproducibly`, + qu'un contexte fixe ignore par conception). +* **Les quatorze gĂ©nĂ©rateurs numĂ©riques** sont des clones identiques Ă  l'octet prĂšs modulo substitution + de type (~2 450 lignes ; le quatuor signĂ©, le quatuor non signĂ©, le trio continu et la paire large ; + les cinq gĂ©nĂ©rateurs temporels suivent le mĂȘme patron pour ~800 lignes de plus). Au crĂ©dit des + familles de clones, un balayage scriptĂ© n'a trouvĂ© **aucune erreur de copier-coller** dans le code + lui-mĂȘme — mais trois rĂ©sumĂ©s XML disent « Same constraint algebra as `AnyInt32` » sur des gĂ©nĂ©rateurs + oĂč c'est littĂ©ralement faux (les types non signĂ©s n'ont pas `Positive`/`Negative` ; les types + temporels renomment la famille de bornes), et trois DisplayName de test affirment encore que les + gĂ©nĂ©rateurs « convert implicitly to their value type » + (`AnyContinuousTests.cs:108`, `AnySignedIntegerTests.cs:87`, `AnyUnsignedIntegerTests.cs:76`) — des + conversions que l'ADR-0020 a supprimĂ©es. Un commentaire pĂ©rimĂ© dans `SeedReproducibilityTests.cs:17-18` + explique du code par ces mĂȘmes conversions supprimĂ©es. + +L'absence de gardes est le constat ; l'analyse d'attĂ©nuation et la recommandation (tests de paritĂ© par +rĂ©flexion, *pas* une classe de base gĂ©nĂ©rique) sont au §9.2. + +### 4.4 La documentation n'atteint ni celui qui dĂ©couvre ni l'utilisateur avancĂ© + +* Le **README du dĂ©pĂŽt ne mentionne jamais Dummies** (vĂ©rifiĂ© : zĂ©ro occurrence), alors que le README du + paquet renvoie au dĂ©pĂŽt pour la « documentation complĂšte ». Celui qui dĂ©couvre le paquet sur NuGet + arrive sur une page d'accueil portant sur une autre bibliothĂšque ; ce qui ressemble le plus Ă  un guide + Dummies (`ArbitraryTestValues.en.md`) est un guide d'intĂ©gration de FirstClassErrors.Testing qui + renvoie lui-mĂȘme Ă  « documented with Dummies itself » — une rĂ©fĂ©rence circulaire. +* **Aucune rĂ©fĂ©rence utilisateur ne documente la surface de contraintes par gĂ©nĂ©rateur.** OĂč + l'utilisateur apprend-il que `Except`/`OneOf`/`DifferentFrom` existent sur les numĂ©riques, que + `WithLengthBetween` existe, que `ContainingAny` diffĂšre de `Containing`, ou quel dialecte regex + `StringMatching` supporte ? Aujourd'hui : seulement IntelliSense, un gĂ©nĂ©rateur Ă  la fois. Le propre + suivi de l'ADR-0025 (« documenter le dialecte supportĂ© ») est toujours ouvert. +* La **surprise du vide-par-dĂ©faut** (une collection non contrainte peut avoir 0 Ă©lĂ©ment, une chaĂźne non + contrainte peut ĂȘtre vide) est bien documentĂ©e dans les remarques XML mais absente du README du paquet, + lĂ  oĂč un utilisateur qui survole en profiterait le plus — c'est un choix dĂ©libĂ©rĂ©, porteur de + philosophie (« un test qui itĂšre zĂ©ro fois sur une collection non contrainte, c'est une hypothĂšse + cachĂ©e qui ressort ») et il mĂ©rite d'ĂȘtre annoncĂ© comme tel. + +### 4.5 Lacunes du contrat de dĂ©terminisme (documentation, pas implĂ©mentation) + +DĂ©taillĂ© au §7.3 : des tirages concurrents dans un mĂȘme scope Ă  graine annulent silencieusement la +rejouabilitĂ© (et exposent un `System.Random` non thread-safe Ă  une course) — documentĂ© nulle part ; les +rapports de graine peuvent nommer une graine erronĂ©e ou inapplicable pour les compositions Ă  contexte +fixe et Ă  sources mixtes ; la stabilitĂ© de la sĂ©quence de graines entre versions et entre TFM n'est ni +promise ni Ă©cartĂ©e ; et le contrat entier a perdu son ancrage ADR lorsque l'ADR-0006 a Ă©tĂ© remplacĂ©e. + +### 4.6 La cible netstandard2.0 n'est jamais exĂ©cutĂ©e par la propre suite de Dummies + +`Dummies.UnitTests` ne cible que net10.0. L'assemblage netstandard2.0 — celui que chargeront les +consommateurs .NET Framework — n'est exercĂ© que *transitivement* : le job de plancher de +FirstClassErrors (`ci.yml:98-115`) exĂ©cute `FirstClassErrors.UnitTests` sur net472, qui prĂ©pare ses +`Arrange` avec `Dummies.Any` via rĂ©fĂ©rence de projet et via les fabriques de Testing, si bien que +Dummies se charge et gĂ©nĂšre bien sur le vrai CLR .NET Framework — mais sa propre suite de contrat de +222 tests (oracle regex, dĂ©tection de conflits, gating de distinction, reproductibilitĂ© de graine) n'y +tourne jamais, et l'Ă©galitĂ© mĂȘme-graine-mĂȘmes-valeurs entre les deux assets empaquetĂ©s n'est assertĂ©e +nulle part. Le dĂ©pĂŽt possĂšde dĂ©jĂ  exactement la machinerie nĂ©cessaire (`build/Net472TestFloor.props`, +utilisĂ©e par `FirstClassErrors.UnitTests`) ; l'Ă©tendre Ă  `Dummies.UnitTests` (en conditionnant hors +scope les tests net8-only) est mĂ©canique. Voir la conformitĂ© Ă  l'ADR-0022, §6. + +### 4.7 Garde-fous d'ingĂ©nierie de release pas encore installĂ©s + +Aucune rĂ©fĂ©rence d'API publique (`Microsoft.CodeAnalysis.PublicApiAnalyzers`), aucun +`EnablePackageValidation`/ApiCompat. Le changelog engage Dummies vers la gestion sĂ©mantique de version +tandis que l'audit lui-mĂȘme dĂ©montre que la surface d'API est recopiĂ©e Ă  la main et dĂ©rive dĂ©jĂ  dans la +documentation ; la dĂ©tection de changements cassants contre une rĂ©fĂ©rence publiĂ©e est le mĂ©canisme +complĂ©mentaire que les tests de paritĂ© ne peuvent pas remplacer (une surcharge supprimĂ©e ou un type de +retour rĂ©trĂ©ci passe un test de miroir). L'avant-premiĂšre-publication est le moment le moins cher pour +installer les deux. Un commentaire pĂ©rimĂ© trouvĂ© ici : `Directory.Build.props:3-9` dit que le dĂ©pĂŽt +livre « FirstClassErrors and FirstClassErrors.Testing » — il omet Dummies, le paquet mĂȘme que ces +propriĂ©tĂ©s de pack gouvernent dĂ©sormais aussi. + +## 5. Revue des ADR + +Dix-huit des vingt-six ADR ne concernent pas Dummies (elles nomment les analyseurs, le request binder, +l'outillage GenDoc/CLI, l'API Outcome, ou le processus du dĂ©pĂŽt). Huit s'appliquent, et leur qualitĂ© a +Ă©tĂ© revue individuellement. Le niveau d'ensemble est assez Ă©levĂ© pour le dire simplement : cette base +d'ADR est un modĂšle du genre. Les dĂ©cisions portent des contraintes honnĂȘtes, des alternatives +rĂ©ellement considĂ©rĂ©es, des inconvĂ©nients chiffrĂ©s, et des suivis qui ont effectivement Ă©tĂ© exĂ©cutĂ©s. + +### ADR-0006 — Fournir des valeurs de test arbitraires depuis une source unique semable *(RemplacĂ©e)* + +**QualitĂ© : exemplaire, historiquement.** Les contraintes Ă©taient rĂ©elles (promesse zĂ©ro-dĂ©pendance, +sĂ»retĂ© des tests parallĂšles netstandard2.0 sans `Random.Shared`), les quatre alternatives ont Ă©tĂ© +pesĂ©es Ă©quitablement, et ses suivis (extraire le moteur quand un second consommateur apparaĂźt ; envisager +un adaptateur xUnit) ont Ă©tĂ© honorĂ©s ou consciemment diffĂ©rĂ©s. Son analyse du risque de collision du +dĂ©faut non semĂ© est exactement Ă  la bonne profondeur. **ProblĂšme :** sa mise en remplacement a créé une +lacune — voir « lacunes structurelles » plus bas. + +### ADR-0011 — HĂ©berger Dummies comme paquet autonome *(AcceptĂ©e)* + +**QualitĂ© : bonne.** Le raisonnement nom/identitĂ©/frontiĂšre est sain et la rĂšgle de non-rĂ©fĂ©rence est +vĂ©rifiĂ©e par machine. Deux points de prĂ©cision. PremiĂšrement, l'invariant *appliquĂ©* est plus fort que +celui *consignĂ©* : le test d'architecture interdit **toute** rĂ©fĂ©rence hors BCL +(`ArchitectureTests.cs:27-37`), et l'ADR-0025 s'appuie sur une « identitĂ© zĂ©ro-dĂ©pendance 
 la frontiĂšre +est vĂ©rifiĂ©e par machine (ADR-0011) » — mais le texte de dĂ©cision de l'ADR-0011 n'interdit que de +rĂ©fĂ©rencer des *projets FirstClassErrors*. La rĂšgle du zĂ©ro-dĂ©pendance-*tierce*, porteuse pour tout +l'argument de l'ADR-0025, n'est consignĂ©e nulle part comme dĂ©cision. DeuxiĂšmement, les alternatives ne +pĂšsent jamais les risques de l'identifiant NuGet ultra-gĂ©nĂ©rique `Dummies` (squattage/collision/ +recherchabilitĂ©) — une identitĂ© de paquet que l'ADR elle-mĂȘme qualifie de coĂ»teuse Ă  renommer. Aucun de +ces points ne change la dĂ©cision ; les deux mĂ©ritent une ligne au dossier. + +### ADR-0013 — Gater les collections distinctes par cardinalitĂ©, sinon par tirage bornĂ© *(AcceptĂ©e)* + +**QualitĂ© : remarquable.** L'argument de soliditĂ© — ne compter que les Ă©lĂ©ments que le gĂ©nĂ©rateur doit +fournir, crĂ©diter les valeurs `Containing` hors de son domaine, traiter les tirages opaques +`ContainingAny` de façon conservatrice, laisser le tirage bornĂ© ĂȘtre le filet de sĂ©curitĂ© final — est +Ă©noncĂ© dans le document et reflĂ©tĂ© de façon prouvable dans le code +(`CollectionState.Validate`/`CardinalityCap`/`FixedOutsideCount`). La section des risques anticipe mĂȘme +un mauvais rĂ©glage du budget et enjoint de « rĂ©viser sur preuves plutĂŽt que de dĂ©crire l'Ă©chec comme +impossible ». **ProblĂšme (partagĂ© avec l'ADR-0015) :** elle diffĂšre « l'interface exacte de l'indice, +l'Ă©tat de collection, le budget de tirage, la charge utile d'exception et la propagation de graine » vers +la rĂ©fĂ©rence d'implĂ©mentation — mais la section Dummies de cette rĂ©fĂ©rence +(`adr-implementation-reference.md:58-68`) ne consigne aucune de ces spĂ©cificitĂ©s (pas de chiffres de +budget, pas de charge utile d'exception, pas de rĂšgle de propagation de graine). Le renvoi promet plus +que la destination ne contient ; soit enrichir la rĂ©fĂ©rence, soit adoucir le renvoi. + +### ADR-0015 — Plafonner Any.Combine Ă  l'aritĂ© huit *(AcceptĂ©e)* + +**QualitĂ© : bonne.** HonnĂȘte sur le caractĂšre heuristique du plafond, avec une Ă©chappatoire dĂ©finie +(ajouter des aritĂ©s de façon compatible via une nouvelle dĂ©cision sur preuves). Les alternatives sont +rĂ©elles. Le mĂȘme point sur le renvoi Ă  la rĂ©fĂ©rence d'implĂ©mentation que pour l'ADR-0013 s'applique. + +### ADR-0020 — MatĂ©rialiser les dummies uniquement via Generate() *(AcceptĂ©e)* + +**QualitĂ© : exemplaire — le meilleur document de la base.** Preuves concrĂštes (les formes syntaxiques oĂč +la conversion se comportait mal en silence, tirĂ©es de la suite elle-mĂȘme), trois alternatives pesĂ©es +Ă©quitablement dont la voie analyseur qu'elle dĂ©cline dĂ©libĂ©rĂ©ment, coĂ»ts honnĂȘtes, et l'argument de +timing prĂ©-1.0 Ă©noncĂ© comme tel. Elle a de plus manifestement orientĂ© des travaux ultĂ©rieurs (l'ADR-0026 +rĂ©utilise Ă  la fois son patron de raisonnement et son cadrage de risque). Aucune modification +recommandĂ©e. + +### ADR-0022 — Fixer le plancher de support .NET Framework Ă  4.7.2 *(AcceptĂ©e)* + +**QualitĂ© : politique saine ; formulation de pĂ©rimĂštre vieillie.** « Une promesse de compatibilitĂ© qui +n'est pas exercĂ©e ne peut pas fournir une frontiĂšre de support fiable » est le bon principe. Mais l'ADR +prĂ©cĂšde Dummies et parle des « bibliothĂšques `netstandard2.0` livrĂ©es » sans les nommer ; savoir si +Dummies est dans son pĂ©rimĂštre relĂšve dĂ©sormais de l'infĂ©rence, et le job de plancher ne l'inclut pas +(§6). Quand le mainteneur touchera Ă  nouveau Ă  ce domaine, une clarification d'une ligne des paquets +couverts lĂšverait l'ambiguĂŻtĂ© — ou la dĂ©cision de plancher propre Ă  Dummies pourrait chevaucher la +nouvelle ADR de dĂ©terminisme proposĂ©e ci-dessous. + +### ADR-0025 — GĂ©nĂ©rer des chaĂźnes correspondantes depuis un sous-ensemble rĂ©gulier maison *(ProposĂ©e)* + +**QualitĂ© : un dossier construire-ou-acheter d'une honnĂȘtetĂ© inhabituelle.** Le rejet de Fare est +argumentĂ© sur des motifs d'identitĂ© et de contrat d'erreur (abandon silencieux des constructions non +rĂ©guliĂšres vs refus de premiĂšre classe), pas sur du FUD ; le cadrage « les constructions non rĂ©guliĂšres +sont impossibles pour *tout* gĂ©nĂ©rateur fini, donc le sous-ensemble n'est pas une coupe de confort » est +exactement juste ; la dĂ©cision de gĂ©nĂ©rateur terminal est bien argumentĂ©e. **ProblĂšmes :** (1) Elle est +toujours **ProposĂ©e** alors qu'elle est entiĂšrement implĂ©mentĂ©e, livrĂ©e dans le README du paquet, et +*porteuse pour l'ADR-0026 AcceptĂ©e* (dont `ErrorCodeFactory` est bĂąti sur `StringMatching`) — tant que +le statut n'a pas basculĂ©, une dĂ©cision acceptĂ©e repose formellement sur une dĂ©cision indĂ©cise. Le rĂŽle +de l'audit est de le signaler ; seul `@reefact` bascule un statut. (2) La phrase de justification « les +terminaux tirent de l'ASCII imprimable » est imprĂ©cise — `\s` inclut la tabulation (0x09) et les +Ă©chappements explicites Ă©mettent exactement le caractĂšre qu'ils nomment (§4.2) ; la formulation devrait +ĂȘtre corrigĂ©e *avant* l'acceptation, puisque l'ADR elle-mĂȘme dĂ©clare l'univers comme un comportement +pertinent pour la compatibilitĂ©. (3) Elle cite « un test par propriĂ©tĂ© » contre le vrai moteur ; ce qui +existe est un test-oracle Ă  graine fixe et corpus fixe dans le projet de tests unitaires — excellent, +mais pas par propriĂ©tĂ© ; le texte devrait dire ce qu'est le filet de sĂ©curitĂ©. + +### ADR-0026 — Rebaser les valeurs arbitraires du paquet de test sur Dummies *(AcceptĂ©e)* + +**QualitĂ© : un dossier de consolidation approfondi** — six vraies alternatives, la justification de +l'histoire Ă  graine unique, un risque d'empaquetage intermĂ©diaire honnĂȘte. **Deux dĂ©rives de +prĂ©cision :** (1) le texte de dĂ©cision dit que chaque fabrique expose « an `IAny` generator through a +distinct method where composition is needed » — aucune fabrique n'expose une telle mĂ©thode aujourd'hui +(vĂ©rifiĂ© : zĂ©ro occurrence d'`IAny` dans les sources de `FirstClassErrors.Testing`). YAGNI dĂ©fendable, +mais le texte se lit comme une forme d'API dĂ©cidĂ©e, et un contrĂŽle de conformitĂ© dans un an ne pourra pas +distinguer le report dĂ©libĂ©rĂ© de la migration inachevĂ©e. (2) Sa clause de risque dit que le danger de +double assemblage existe « precisely because Dummies types appear in Testing's public API » — +aujourd'hui aucun n'y apparaĂźt ; la prĂ©misse est mal Ă©noncĂ©e (le danger est rĂ©el pour d'autres raisons +tant que Dummies est livrĂ©e dans l'artefact). Comme les ADR acceptĂ©es ne sont jamais Ă©ditĂ©es en place, +les deux relĂšvent d'une courte note dans la rĂ©fĂ©rence d'implĂ©mentation. + +### Lacunes structurelles de la base (recommandations de crĂ©ation) + +1. **Le contrat de dĂ©terminisme de Dummies n'a pas d'ADR acceptĂ©e.** La source ambiante `AsyncLocal`, + le `Reproducibly` optionnel, l'Ă©pinglage paresseux, le rapport de graine Ă  l'Ă©chec — la garantie + joyau de la couronne — a Ă©tĂ© dĂ©cidĂ©e dans l'ADR-0006, dĂ©sormais RemplacĂ©e *et* cadrĂ©e sur + FirstClassErrors.Testing ; la dĂ©cision de l'ADR-0026 porte sur le rebasage de Testing, pas sur le + contrat propre de Dummies. Un futur mainteneur qui demande « pourquoi `AsyncLocal` et pas un + paramĂštre ? pourquoi un `System.Random` mis en course est-il acceptable ? » ne trouve le raisonnement + que dans un dossier remplacĂ©. **Recommandation : rĂ©diger une ADR ProposĂ©e** (« Dummies fournit des + valeurs arbitraires depuis une source ambiante, semable, locale au contexte d'exĂ©cution, avec + reproductibilitĂ© optionnelle ») reprenant la justification de l'ADR-0006 et rĂ©glant, dans le mĂȘme + document, les bords ouverts que cet audit a fait remonter : la sĂ©mantique de concurrence Ă  flux + logique unique, le point de couture fermĂ© `IHasRandomSource`, et la politique de stabilitĂ© de graine + entre versions (§7.3). +2. **L'architecture du moteur ordinal n'a pas d'ADR.** Un espace ordinal 64 bits partagĂ© avec quatre + moteurs par substrat arithmĂ©tique est une dĂ©cision durable, questionnable-par-un-futur-mainteneur + (pourquoi quatre moteurs ? pourquoi `decimal` n'est-il pas projetĂ© en ordinal ?) qui ne vit + aujourd'hui que dans des docs XML internes. Elle passe le propre test d'ADR du dĂ©pĂŽt (« si + l'implĂ©mentation changeait mais que la dĂ©cision tenait
 »). Une courte ADR ProposĂ©e corrigerait + l'asymĂ©trie avec des dĂ©cisions bien plus petites (plafonds d'aritĂ©) qui, elles, ont eu des dossiers. + +## 6. ConformitĂ© aux ADR + +| ADR | Statut | ConformitĂ© de l'implĂ©mentation | +|---|---|---| +| 0006 (historique) | RemplacĂ©e | **Conforme et dĂ©passĂ©e.** Le contrat de graine hĂ©ritĂ© (local au contexte, dĂ©terminisme optionnel, rapport de graine) est implĂ©mentĂ© fidĂšlement ; Dummies ajoute l'`AnyContext` isolĂ© que l'ADR d'origine ne faisait qu'anticiper. | +| 0011 | AcceptĂ©e | **Conforme.** Aucune rĂ©fĂ©rence Ă  FirstClassErrors ; frontiĂšre vĂ©rifiĂ©e par machine (`ArchitectureTests`) ; identitĂ© autonome, train de release et docs en place. Note : l'application est *plus forte* que la dĂ©cision consignĂ©e (§5). | +| 0013 | AcceptĂ©e | **Conforme, vĂ©rifiĂ©e en dĂ©tail** — gate immĂ©diat net des crĂ©dits `Containing` hors domaine, comptage conservateur de `ContainingAny`, arithmĂ©tique Ă  l'abri du dĂ©bordement, budget bornĂ©, les deux canaux d'Ă©chec. **Une dĂ©viation mineure :** le message de saturation promet *inconditionnellement* le rejeu `Any.Reproducibly({seed}, 
)` (`CollectionState.cs:246-254` ; la garde `seed is not null` est du code mort — la graine n'y peut jamais ĂȘtre nulle). Pour un gĂ©nĂ©rateur d'Ă©lĂ©ments **Ă©tranger** dont les tirages ignorent la source ambiante, cette promesse est fausse ; l'ADR dit que les Ă©checs sont « explicites et reproductibles ». Qualifier le message quand le gĂ©nĂ©rateur d'Ă©lĂ©ments ne porte aucune source de la bibliothĂšque. | +| 0015 | AcceptĂ©e | **Conforme exactement** — aritĂ©s 2–8, pas plus ; suppressions localisĂ©es avec justifications renvoyant Ă  l'ADR (`Any.cs:622-623`) ; plafond documentĂ© sur la surcharge d'aritĂ© 8. | +| 0020 | AcceptĂ©e | **Pleinement conforme.** Aucune conversion implicite nulle part ; `Generate()` est la seule matĂ©rialisation ; gĂ©nĂ©rateurs vĂ©rifiĂ©s immuables (chaque mĂ©thode fluide renvoie une nouvelle instance). RĂ©sidu : trois DisplayName de test et un commentaire *dĂ©crivent* encore les conversions supprimĂ©es (§4.3). | +| 0022 | AcceptĂ©e | **Partielle pour Dummies.** L'asset netstandard2.0 n'est chargĂ© et exercĂ© sur net472 que transitivement via le job de plancher de FirstClassErrors ; la propre suite de Dummies n'y tourne jamais, et le README du paquet n'Ă©nonce aucun plancher .NET Framework (celui de FirstClassErrors le fait). À solder avant la premiĂšre publication (§11 point 5). | +| 0025 | ProposĂ©e | **Conforme sur chaque clause majeure** (analyseur maison, refus de premiĂšre classe, gĂ©nĂ©rateur terminal, zĂ©ro dĂ©pendance, univers ASCII imprimable *par dĂ©faut*, spread bornĂ© des quantificateurs non bornĂ©s). Les dĂ©fauts §4.1(c)/(d) sont des bugs de qualitĂ© *Ă  l'intĂ©rieur* du pĂ©rimĂštre dĂ©cidĂ©, pas des dĂ©viations — avec la rĂ©serve que (d) rompt la *promesse* de refus que l'ADR consigne. Un bord de taxonomie : une classe nĂ©gative bien formĂ©e hors de l'univers imprimable lĂšve `ArgumentException` (« malformĂ©e ») au lieu d'`UnsupportedRegexException`. | +| 0026 | AcceptĂ©e | **Conforme sur chaque clause exĂ©cutĂ©e** — moteur unique, scope de graine unique, `Testing.Any` supprimĂ©, fabriques livrĂ©es, horloge/ids sur le contexte ambiant, docs mises Ă  jour EN/FR. La moitiĂ© « distinct `IAny` method » non implĂ©mentĂ©e et la prĂ©misse de risque mal Ă©noncĂ©e sont consignĂ©es au §5. | + +## 7. Revue de l'architecture + +### 7.1 DĂ©coupage en couches et forme + +La bibliothĂšque compte trois couches propres : **gĂ©nĂ©rateurs fluides publics** (fins, par type, scellĂ©s, +immuables) → **moteurs de spĂ©cification internes** (`OrdinalIntervalSpec`, `WideIntervalSpec`, +`ContinuousIntervalSpec`, `DecimalIntervalSpec`, `StringSpec`, `CountSpec`, `CollectionState`) → +**primitives d'Ă©chantillonnage** (`RandomSampling`). La surface publique ne laisse jamais fuir de type +interne ; les moteurs internes ne touchent jamais directement l'Ă©tat ambiant (les sources sont passĂ©es +vers le bas). Les points de composition — `.As(factory)`, `Any.Combine(...)`, les fabriques de +collection — sont tous dĂ©finis sur l'`IAny` Ă  un seul membre, qui est aussi petit qu'une interface +peut l'ĂȘtre (ISP par construction) et covariant, si bien que les gĂ©nĂ©rateurs dĂ©rivĂ©s et Ă©trangers +traversent chaque point de couture uniformĂ©ment. + +La **sĂ©paration en quatre moteurs est fondĂ©e, pas accidentelle** : les types discrets projetables sur +64 bits partagent `OrdinalIntervalSpec` ; les entiers 128 bits ont besoin de `WideIntervalSpec` seulement +parce que netstandard2.0 n'a pas `UInt128` (les deux sont des jumeaux mot pour mot — l'unique +duplication regrettable, forcĂ©e par le TFM) ; les flottants IEEE ont besoin d'un Ă©chantillonnage continu +avec quantification consciente du type ; `decimal` n'est ni projetable en ordinal (mantisse 96 bits × +Ă©chelle) ni IEEE. L'existence de chaque moteur est justifiĂ©e par son substrat arithmĂ©tique. Ce qui +*manque*, c'est l'ADR qui le consigne (§5), et — comme l'a montrĂ© §4.1(b) — une suite de tests paramĂ©trĂ©e +exerçant chaque moteur Ă  travers chacune de ses façades de type. + +La **hiĂ©rarchie de collections** est un CRTP propre comme un manuel : `AnyCollection` porte la surface fluide partagĂ©e de compte/contenance renvoyant `TSelf` (sans le classique cast +non sĂ»r `(TSelf)this` — les types concrets implĂ©mentent une fabrique `With(state)`), et les cinq +gĂ©nĂ©rateurs concrets n'ajoutent que la mise en forme des Ă©lĂ©ments et la conversion `Build(List)`. +L'exception est `AnyDictionary`, qui ne peut pas hĂ©riter de la base (son Ă©lĂ©ment est une paire) et donc +**duplique toute la façade de compte mot pour mot** (~60 lignes, `AnyDictionary.cs:51-113`) et n'offre +aucune contrainte de contenance — le seul endroit de la famille collection oĂč le partage a Ă©chouĂ©. +Extraire la façade de compte au-dessus de `CollectionState` (ou ajouter `ContainingKey`, qui +chevaucherait gratuitement la machinerie d'Ă©tat de clĂ©s existante) refermerait Ă  la fois la duplication +et le trou de test reconnu (`AnyCollectionTests.cs:161-163` le commente). + +### 7.2 ExtensibilitĂ© + +**Pour les utilisateurs, la conception est fermĂ©e, et c'est un choix lĂ©gitime mais non documentĂ©.** +`IAny` est public, donc n'importe qui peut implĂ©menter un gĂ©nĂ©rateur et le composer via +`As`/`Combine`/collections. Mais `RandomSource`, `IHasRandomSource` et `ICardinalityHint` sont tous +internes, si bien qu'un gĂ©nĂ©rateur Ă©tranger (a) ne peut pas tirer de la source semĂ©e ambiante — sous +`Any.Reproducibly` ses valeurs ne rejouent pas, et (b) ne peut pas annoncer un domaine fini — une +collection distincte au-dessus de lui emprunte toujours le chemin du tirage bornĂ© (sĂ»r, et exactement ce +que promet l'ADR-0013). La dĂ©gradation est gracieuse partout (vĂ©rifiĂ© : `OrNull` retombe sur la source +ambiante pour le tirage Ă  pile ou face du null ; `Combine` propage les sources `null` sans Ă©chouer). Ce +qui manque est un paragraphe honnĂȘte sur la doc XML d'`IAny` disant aux implĂ©menteurs oĂč ils se +situent — aujourd'hui le contrat n'est dĂ©couvrable qu'en lisant du code interne. Si le point de couture +doit s'ouvrir un jour, un `ISeedableAny` dans une release mineure est la forme naturelle ; rien ne +demande Ă  ĂȘtre dĂ©cidĂ© maintenant, sinon la documentation. + +**Pour les mainteneurs**, ajouter un nouveau type scalaire touche 6 Ă  9 fichiers (gĂ©nĂ©rateur, `Any`, +`AnyContext`, tests, docs utilisateur EN/FR, README du paquet, `dummies-check` si net8-only, +Ă©ventuellement un moteur de spec). Le processus est mĂ©canique mais rĂ©el, et n'est que partiellement gardĂ© +(§9.2). + +### 7.3 La machinerie de dĂ©terminisme — plongĂ©e en profondeur + +L'implĂ©mentation est correcte au niveau qu'il est difficile de bien faire (§3.3). Les risques restants +sont tous des risques de *documentation de contrat*, et ils se regroupent en quatre : + +**(a) La concurrence dans un mĂȘme scope Ă  graine annule silencieusement la rejouabilitĂ© — non +documentĂ©.** Un `AsyncLocal` copie la *rĂ©fĂ©rence* : les enfants `Task.Run`/`Parallel.ForEach` Ă  +l'intĂ©rieur d'un corps `Reproducibly` voient tous la mĂȘme instance `SeededRandom`. Deux consĂ©quences. +PremiĂšrement, mĂȘme avec un entrelacement bĂ©nin, l'*ordre* des tirages devient dĂ©pendant de +l'ordonnanceur, si bien que la graine rapportĂ©e ne rejoue plus le run — la garantie mĂȘme pour laquelle la +fonctionnalitĂ© existe. DeuxiĂšmement, `System.Random` n'est pas thread-safe, et netstandard2.0 n'offre +aucune alternative thread-safe ; un tirage mis en course peut corrompre l'Ă©tat (sur .NET Framework, un +`Random` mis en course peut dĂ©gĂ©nĂ©rer et renvoyer des zĂ©ros). Les docs expliquent soigneusement que la +source « ne fuit jamais *entre* les tests qui tournent en parallĂšle » (vrai — flux logiques diffĂ©rents) +mais ne disent rien du parallĂ©lisme *Ă  l'intĂ©rieur* d'un corps. Le correctif est un paragraphe honnĂȘte +sur `Reproducibly` (« un run semĂ© est Ă  flux logique unique ; les tirages concurrents dans le corps ne +sont ni rejouables ni sĂ»rs ») — plus, optionnellement, consigner dans la nouvelle ADR de dĂ©terminisme +pourquoi un forkage par flux (une source enfant par `Task.Run`) n'a pas Ă©tĂ© tentĂ© (il changerait toute +sĂ©quence et compliquerait `WithSeed` ; la restriction honnĂȘte est la bonne V1). + +**(b) Les rapports de graine peuvent nommer une graine erronĂ©e ou inapplicable dans une composition Ă  +source mixte/fixe.** `Combine` propage la source d'opĂ©rande **premiĂšre non nulle** pour le rapport +d'Ă©chec (`Any.cs:446` et al.). `Any.Combine(Any.WithSeed(1).Int32(), Any.WithSeed(2).Int32(), throwing)` +Ă©choue avec « seeded with 1; reproduce with `Any.Reproducibly(1, 
)` » — doublement faux : la graine 2 +n'est pas rapportĂ©e, et l'instruction est inapplicable parce que `Reproducibly` Ă©pingle la source +*ambiante*, que les gĂ©nĂ©rateurs adossĂ©s Ă  `FixedRandomSource` ignorent par conception. C'est un cas +limite (mĂ©langer des contextes semĂ©s dans une mĂȘme composition est inhabituel), mais le mode d'Ă©chec est +un *diagnostic trompeur avec assurance* dans la bibliothĂšque dont la signature est l'honnĂȘtetĂ© +diagnostique. Un petit correctif l'atteint : laisser le type de source produire l'indice de rejeu +(ambiante → « reproduce with `Any.Reproducibly({seed}, 
)` » ; fixe → « ce gĂ©nĂ©rateur tire de +`Any.WithSeed({seed})`, qui rejoue dĂ©jĂ  de lui-mĂȘme »), et faire collecter Ă  `Combine` les sources +distinctes plutĂŽt que la premiĂšre. + +**(c) La stabilitĂ© de graine entre versions et entre runtimes n'est ni promise ni Ă©cartĂ©e.** La +description du paquet dit « any run is reproducible from a reported seed » sans rĂ©serve. Dans un mĂȘme +processus, cela tient. Entre *versions de la bibliothĂšque*, tout changement d'ordre ou de nombre de +tirages change silencieusement chaque sĂ©quence — et l'ADR-0025 reconnaĂźt dĂ©jĂ  que les consommateurs +peuvent s'appuyer sur les formes gĂ©nĂ©rĂ©es. Entre *runtimes*, un `new Random(seed)` semĂ© conserve +l'algorithme historique sur .NET moderne prĂ©cisĂ©ment par compatibilitĂ©, de sorte que la surface commune +devrait s'accorder entre les assets netstandard2.0 et net8.0 — mais rien ne le teste (§4.6), et la +documentation de `Random` se rĂ©serve explicitement le droit que les implĂ©mentations diffĂšrent entre +versions du framework. La politique mature, avant la v1 : **promettre la stabilitĂ© au sein d'une version +de paquet, l'Ă©carter entre versions**, une phrase dans le README et dans la nouvelle ADR de dĂ©terminisme. +(Pour comparaison : FsCheck et AutoFixture ont tous deux appris Ă  l'Ă©carter explicitement.) + +**(d) L'Ă©pinglage ambiant paresseux rend un Ă©chec *non enveloppĂ©* seulement approximativement +rejouable.** Hors de `Reproducibly`, le premier tirage dans un flux logique Ă©pingle une graine +mĂ©morisĂ©e. Les tirages survenus *avant* le bloc fautif dans le mĂȘme flux (une fixture, un `Arrange` +antĂ©rieur) consomment de la mĂȘme sĂ©quence, de sorte que rejouer « juste le corps du test » avec la graine +rapportĂ©e peut diverger. La conception est juste (c'est pour cela que `Reproducibly` existe) ; le rĂ©cit +de rejeu du guide utilisateur pourrait porter une phrase disant que la fidĂ©litĂ© de rejeu commence Ă  la +frontiĂšre du scope. + +Des non-problĂšmes vĂ©rifiĂ©s qu'il vaut la peine de consigner pour ne pas les rejuger : la sĂ©mantique +d'`ExecutionContext` de la surcharge asynchrone (correcte — voir §3.3) ; +`NewSeed() = Guid.NewGuid().GetHashCode()` (usage tolĂ©rant aux collisions, analysĂ© dans l'ADR-0006) ; +l'Ă©tendue de graine sous xUnit (chaque invocation de test est son propre cadre asynchrone ; un +constructeur de classe partagĂ© participe au flux de son test, ce qui est le bon scope) ; la +rĂ©-Ă©numĂ©ration de `SequenceOf` (matĂ©rialisĂ©e une fois, ne re-tire jamais). + +### 7.4 SOLID, briĂšvement et seulement lĂ  oĂč cela vaut la peine + +SRP : les gĂ©nĂ©rateurs portent la surface fluide, les moteurs portent la sĂ©mantique — propre. OCP : +ajouter une *contrainte* Ă  un type discret est l'ajout d'une mĂ©thode de façade au-dessus d'une opĂ©ration +de moteur existante ; ajouter un *type* est dĂ©libĂ©rĂ©ment fermĂ© (gĂ©nĂ©rateurs scellĂ©s, moteurs internes) — +le bon compromis pour une bibliothĂšque riche en invariants. LSP : la base de collection CRTP est saine +(pas d'astuce d'auto-cast, borne `TSelf` imposĂ©e). ISP : `IAny` Ă  membre unique ; les deux membres +d'`ICardinalityHint` voyagent ensemble par conception explicite et documentĂ©e (une cardinalitĂ© sans +appartenance serait fausse — la doc d'interface l'argumente). DIP est intentionnellement absent au point +de couture utilisateur (aucune abstraction d'alĂ©atoire injectable) — c'est *cela* la dĂ©cision +d'extensibilitĂ© fermĂ©e du §7.2, acceptable mais mĂ©ritant son paragraphe de documentation. + +## 8. Revue de l'API + +### 8.1 L'algĂšbre de contraintes est uniforme lĂ  oĂč cela compte + +La matrice vĂ©rifiĂ©e : les cinq gĂ©nĂ©rateurs d'entiers signĂ©s et les quatre gĂ©nĂ©rateurs continus/dĂ©cimaux +exposent exactement `Positive · Negative · Zero · NonZero · GreaterThan[OrEqualTo] · LessThan[OrEqualTo] +· Between · OneOf · Except · DifferentFrom` ; les cinq gĂ©nĂ©rateurs non signĂ©s retirent exactement +`Positive`/`Negative` (sans objet ici — `NonZero` couvre l'intention) ; les quatre gĂ©nĂ©rateurs de type +instant (`DateTime`, `DateTimeOffset`, `DateOnly`, `TimeOnly`) renomment la famille de bornes en +vocabulaire mĂ©tier (`After`/`AfterOrEqualTo`/`Before`/`BeforeOrEqualTo`/`Between`) avec une sĂ©mantique +inclusive/exclusive identique, tandis qu'`AnyTimeSpan` — une magnitude, pas un instant — garde +correctement l'algĂšbre numĂ©rique complĂšte, y compris `Positive`/`Negative`/`Zero` ; `AnyChar` porte les +familles de caractĂšres plus le trio d'exclusion ; `AnyGuid` a +`NonEmpty`/`Empty`/`OneOf`/`Except`/`DifferentFrom` ; `AnyEnum` a le trio d'exclusion avec validation des +membres dĂ©clarĂ©s ; les collections partagent `NonEmpty · Empty · WithCount · WithMinCount · WithMaxCount +· WithCountBetween · Containing · ContainingAny` (+ variantes `Distinct` lĂ  oĂč c'est pertinent). Les +bornes sont uniformĂ©ment inclusives pour `Between`/`
OrEqualTo` et exclusives pour +`GreaterThan`/`LessThan`/`After`/`Before` — aucune surprise sĂ©mantique n'a Ă©tĂ© trouvĂ©e nulle part dans la +matrice. Ce niveau de cohĂ©rence sur dix-neuf gĂ©nĂ©rateurs d'intervalle Ă©crits Ă  la main — plus leurs +frĂšres chaĂźne, char, guid, enum, bool et collection — est un accomplissement en soi. + +### 8.2 Les asymĂ©tries qui valent d'ĂȘtre corrigĂ©es ou consignĂ©es + +* **`AnyString` est le seul gĂ©nĂ©rateur scalaire sans contraintes d'exclusion** — pas d'`OneOf`, pas + d'`Except`, pas de `DifferentFrom`. « Un nom diffĂ©rent de celui que je dĂ©tiens dĂ©jĂ  » est l'un des + besoins de chaĂźne factice les plus courants (c'est exactement pourquoi `DifferentFrom` existe partout + ailleurs, selon sa propre doc XML). La raison honnĂȘte du trou : les chaĂźnes ne sont pas projetĂ©es en + ordinal, donc les exclusions ne peuvent pas chevaucher le moteur d'intervalle ; `DifferentFrom` + nĂ©cessiterait soit un retirage bornĂ© (collisions attendues ≈ 0 pour toute spec non triviale — cohĂ©rent + avec les autres Ă©chappatoires bornĂ©es de la bibliothĂšque) soit un ajustement d'agencement conscient de + la spec. RecommandĂ© (§10 Indispensable) : + + ```csharp + // Aujourd'hui — aucun moyen d'exprimer ceci : + string other = Any.String().NonEmpty().Generate(); // pourrait ĂȘtre Ă©gal Ă  l'existant ! + // ProposĂ© : + string other = Any.String().NonEmpty().DifferentFrom(existing).Generate(); + ``` + +* **`AnyDictionary` abandonne `Containing`/`ContainingAny`** et duplique la façade de compte (§7.1). + `ContainingKey(TKey)` chevaucherait la machinerie d'Ă©tat de clĂ©s existante sans changement. +* **`Any.Bool()` est la seule dĂ©viation de la convention de fabriques aux noms CLR** (`Int32`, `SByte`, + `Single`, 
 sont tous des noms CLR ; le nom CLR ici est `Boolean`). La forme courte est sans doute la + meilleure ergonomie — mais alors la convention devient « noms CLR, sauf un », et aprĂšs la 1.0 le + renommage est cassant dans les deux sens. DĂ©cider dĂ©libĂ©rĂ©ment et consigner une ligne, avant la + publication (le dĂ©pĂŽt a des ADR prĂ©cisĂ©ment pour cette classe de dĂ©cision de nommage). +* **`PairOf`/`TripleOf` s'arrĂȘtent Ă  l'aritĂ© 3** tandis que `Combine` va jusqu'Ă  8. DĂ©fendable (les + tuples au-delĂ  de 3 se lisent mal ; `Combine` les couvre), mais le point d'arrĂȘt n'est consignĂ© nulle + part — une phrase de doc le referme. + +### 8.3 DĂ©couvrabilitĂ© et cĂ©rĂ©monie + +Le point d'entrĂ©e statique `Any.` rend toute la surface scalaire dĂ©couvrable en une frappe, et les +mĂ©thodes fluides de chaque gĂ©nĂ©rateur Ă©numĂšrent tout son vocabulaire de contraintes dans IntelliSense — +bien. Deux points de couture sont moins dĂ©couvrables : `As` et `OrNull` sont des mĂ©thodes d'extension +dans des classes statiques sĂ©parĂ©es (invisibles tant que le `using` n'existe pas — bien que l'espace de +noms soit partagĂ©, donc en pratique elles apparaissent), et `As` est le `Select` de la bibliothĂšque sous +un nom d'intention mĂ©tier ; une ligne de doc faisant le pont depuis le vocabulaire LINQ (« `As` est le +`Select` des gĂ©nĂ©rateurs — nommĂ© pour son usage dominant : passer par la fabrique d'un objet-valeur ») +aiderait les lecteurs familiers de LINQ. La cĂ©rĂ©monie terminale `Generate()` est le compromis de +l'ADR-0020, consciemment chiffrĂ© lĂ -bas ; l'audit confirme que le coĂ»t est rĂ©el mais petit (un appel par +matĂ©rialisation), que le bĂ©nĂ©fice (aucune conversion cachĂ©e Ă  effet de bord) est structurel, et que la +dĂ©cision doit tenir. `AnyContext` ne recopie que les scalaires — la composition hĂ©rite du contexte via +les sources d'opĂ©randes, ce qui est *plus* Ă©lĂ©gant que le recopiage et correctement documentĂ©. + +### 8.4 Nommage + +`StartingWith`/`EndingWith`/`Containing`, `After`/`Before`, `DifferentFrom` vs `Except`, `Containing` vs +`ContainingAny` — le vocabulaire est rĂ©vĂ©lateur d'intention et se lit sur le site d'appel comme la +philosophie l'entend. Les fabriques aux noms de type CLR (`Any.Int32()`, pas `Any.Int()`) sont cohĂ©rentes +avec les noms de type des gĂ©nĂ©rateurs (`AnyInt32`) et contournent les restrictions de mots-clĂ©s C# ; +c'est dĂ©fendable et, plus important, uniforme (le cas `Bool` du §8.2 mis Ă  part). + +## 9. Revue de la maintenabilitĂ© + +### 9.1 Duplication, mesurĂ©e + +Quatre familles de clones parmi les gĂ©nĂ©rateurs numĂ©riques (quatuor signĂ©, quatuor non signĂ©, trio +continu, paire large — identiques Ă  l'octet prĂšs modulo substitution de type ; ~2 450 lignes), les cinq +gĂ©nĂ©rateurs temporels sur le mĂȘme patron (~800 lignes), la logique de contrainte-et-conflit quadruplĂ©e Ă  +travers les quatre moteurs (~910 lignes), et le miroir scalaire `Any`/`AnyContext` (~300 lignes riches en +doc). Une comparaison scriptĂ©e n'a trouvĂ© **aucune erreur de copier-coller comportementale** Ă  travers +les familles de clones — preuve d'une vraie discipline — tandis que toute la dĂ©rive trouvĂ©e jusqu'ici est +une dĂ©rive de *documentation* (§4.3), exactement le genre pour lequel les gardes n'existent pas encore. + +### 9.2 AttĂ©nuation : des gardes, pas de gĂ©nĂ©riques + +Le refactoring Ă©vident — une base gĂ©nĂ©rique CRTP (`AnyOrdinal`) — bute sur les contraintes de +ce projet : C# exige une classe de base publique pour un gĂ©nĂ©rateur public scellĂ© (CS0060), si bien que +le point de couture du moteur interne fuirait dans l'API publique ; netstandard2.0 n'a pas de +mathĂ©matiques gĂ©nĂ©riques (`INumber` est net7+), donc les lambdas `Ord`/`Val`/d'affichage par type +demeurent ; et la barre affichĂ©e de la bibliothĂšque est la simplicitĂ© de maintenance, que 14 fichiers +plats, ennuyeux et « greppables » servent mieux qu'une base astucieuse. Les gĂ©nĂ©rateurs de source/T4 +achĂštent la dĂ©duplication au prix d'une machinerie de build et d'une dĂ©bogabilitĂ© — mauvais compromis ici +aussi. **RecommandĂ© Ă  la place : des gardes de paritĂ© exĂ©cutables**, ~3 courts tests par rĂ©flexion : + +1. *ParitĂ© du miroir :* chaque mĂ©thode statique publique `Any` renvoyant un type de gĂ©nĂ©rateur a une + contrepartie d'instance `AnyContext` de nom/signature/type de retour identiques, par TFM (~20 lignes ; + tue d'un coup la classe de dĂ©rive du §4.3). +2. *ParitĂ© de l'algĂšbre :* chaque famille de gĂ©nĂ©rateurs expose son ensemble exact de noms de mĂ©thodes + attendus (la matrice du §8.1, encodĂ©e une fois comme donnĂ©e) — un nouveau gĂ©nĂ©rateur privĂ© de + `DifferentFrom`, ou une mĂ©thode renommĂ©e, Ă©choue avec un diff nommĂ©. +3. *Suite de scĂ©narios transverse aux moteurs :* un fichier de test paramĂ©trĂ© passe la mĂȘme batterie de + scĂ©narios (un tirage pleine-plage touche les deux moitiĂ©s ; les bornes de `Between` sont atteignables ; + `DifferentFrom` sur un domaine Ă©troit ; interaction `OneOf`+`Except` ; messages de conflit) contre + **chaque** gĂ©nĂ©rateur via de petits adaptateurs par type. C'est la suite qui aurait attrapĂ© §4.1(a) et + §4.1(b) avant toute revue humaine. + +En complĂ©ment, installer les gardes d'ingĂ©nierie de release du §4.7 (rĂ©fĂ©rence d'API publique + +validation de paquet) — ils attrapent la classe de changements cassants que les tests de paritĂ© ne +peuvent pas. + +### 9.3 StratĂ©gie de test + +Ce qui existe est bien formĂ© : un nommage comportement-d'abord qui se lit comme documentation vivante ; +les *messages* d'exception testĂ©s comme des contrats de premiĂšre classe ; l'oracle regex du vrai moteur ; +des tests de non-rĂ©gression qui encodent l'historique des bugs (le test de course d'`AnyGuid` qui court +contre une Ă©chĂ©ance au lieu de bloquer la suite) ; des assertions façon-propriĂ©tĂ© Ă  l'abri des tests +instables (les tirages non semĂ©s assertĂ©s seulement contre leur domaine dĂ©clarĂ©) ; et une posture +strictement boĂźte noire — aucun `InternalsVisibleTo` n'existe, si bien que les 222 tests n'exercent que +la surface publique. Ce dernier fait coupe dans les deux sens et devrait ĂȘtre tenu pour un choix +dĂ©libĂ©rĂ© : il prouve que l'API publique suffit Ă  spĂ©cifier la bibliothĂšque (et rend les refactorings de +moteur transparents aux tests), *et* il est cohĂ©rent avec la façon dont les deux dĂ©fauts d'atteignabilitĂ© +ont survĂ©cu — aucun test ne regarde directement la couverture de l'espace de valeurs d'un moteur. Les +ajouts qui referment l'Ă©cart, par ordre de levier : la suite de scĂ©narios transverse ci-dessus ; des +**assertions d'atteignabilitĂ©** (pour chaque gĂ©nĂ©rateur, une boucle semĂ©e sur `Between(lo, hi)` doit +observer des valeurs dans les deux moitiĂ©s et toucher les deux bornes — peu coĂ»teux, dĂ©terministe sous +`WithSeed`) ; un test de limite de gĂ©nĂ©ration pour `AnyPattern` (actuellement non testĂ©e) ; des tests +dĂ©diĂ©s aux contrats documentĂ©s-mais-non-testĂ©s (enum vide, capturabilitĂ© de la base `AnyException`, +chemin du comparateur de clĂ©s de `DictionaryOf`) ; et l'assertion mĂȘme-graine inter-TFM dans +`dummies-check` (Ă©tendre `SeedBatch` d'une sĂ©quence de rĂ©fĂ©rence comparĂ©e entre les cibles consommatrices +net8.0 et net6.0, et Ă©tendre le test de fumĂ©e pour couvrir les tirages +`OrNull`/`SequenceOf`/`PairOf`/`StringMatching`/enum, que le garde-fou d'artefact empaquetĂ© ne touche +actuellement jamais). + +### 9.4 Organisation et hygiĂšne + +La racine plate de 54 fichiers est acceptable aujourd'hui parce que la discipline de nommage fait office +de dossiers (`Any*` = gĂ©nĂ©rateurs, `*Spec` = moteurs, `Regex*` = sous-systĂšme de motifs) ; regrouper en +dossiers est un polissage optionnel, Ă  ne faire qu'Ă  l'occasion d'un autre changement structurel. Points +d'hygiĂšne trouvĂ©s : le membre mort `RegexCharacters.Count` ; la garde-null morte dans +`CollectionState.Exhausted` (ligne §6/ADR-0013) ; les commentaires et DisplayName pĂ©rimĂ©s du §4.3 ; +l'en-tĂȘte pĂ©rimĂ© de `Directory.Build.props` (§4.7). + +## 10. Analyse des manques fonctionnels + +MĂ©thode : chaque proposition a Ă©tĂ© passĂ©e au crible de (i) la philosophie de la bibliothĂšque (les +contraintes expriment des invariants ; pas de fausses donnĂ©es rĂ©alistes, pas de graphes d'objets, pas de +couplage Ă  l'horloge), (ii) le test de composition — *`As`/`Combine`/`StringMatching` peuvent-ils dĂ©jĂ  +exprimer ceci en une ligne lisible ?* — et (iii) le coĂ»t complet d'un nouveau gĂ©nĂ©rateur (gĂ©nĂ©rateur + +`Any` + `AnyContext` + donnĂ©es de paritĂ© + tests + docs EN/FR + README du paquet + Ă©ventuellement +`dummies-check`). La barre de l'**Indispensable** est celle du mandat : une absence vĂ©ritablement +surprenante. La conception composition-d'abord de la bibliothĂšque garde cette liste courte — la plupart +des types BCL sont dĂ©jĂ  Ă  un `As` de distance, ce qui est la conception fonctionnant comme prĂ©vu. + +### Indispensable + +**1. Un combinateur de choix de premier niveau : `Any.OneOf(params T[])` et +`Any.ElementOf(IReadOnlyList)`.** +Choisir un Ă©lĂ©ment arbitraire dans un ensemble fourni par l'appelant est parmi les besoins factices les +plus courants dans les vraies suites (« l'une des trois devises configurĂ©es », « l'un des Ă©tats de cette +table »). Aujourd'hui `OneOf` n'existe qu'*Ă  l'intĂ©rieur* des gĂ©nĂ©rateurs typĂ©s — il n'y a aucun moyen de +tirer d'un ensemble d'objets mĂ©tier ou de chaĂźnes. Chaque utilisateur recode les mĂȘmes trois lignes (et +oublie la source semĂ©e, cassant silencieusement `Reproducibly` pour ce tirage — un piĂšge que la +bibliothĂšque existe pour prĂ©venir) : + +```csharp +// Aujourd'hui — codĂ© Ă  la main, et non conscient de la graine : +var currencies = new[] { eur, usd, gbp }; +var currency = currencies[new Random().Next(currencies.Length)]; // graine ambiante ignorĂ©e ! + +// ProposĂ© — conscient de la graine, cohĂ©rent avec la philosophie, validĂ© immĂ©diatement (ensemble vide → lĂšve) : +Currency currency = Any.OneOf(eur, usd, gbp).Generate(); +Order order = Any.ElementOf(existingOrders).Generate(); +``` + +Constructif (tirage unique), trivialement implĂ©mentable sur la source ambiante avec un +`ICardinalityHint` (compte distinct du rĂ©servoir — il se compose gratuitement avec les collections +distinctes), recopiĂ© sur `AnyContext`. Qui en bĂ©nĂ©ficie : chaque consommateur, chaque semaine. CoĂ»t : un +petit gĂ©nĂ©rateur. C'est l'ajout au plus fort levier disponible. + +**2. `AnyString.DifferentFrom(string)` / `Except(params string[])`.** +L'asymĂ©trie du §8.2 : le gĂ©nĂ©rateur le plus utilisĂ© est le seul scalaire qui ne puisse pas exclure de +valeurs. CoĂ»t honnĂȘte : un retirage bornĂ© (le patron d'Ă©chappatoire Ă©tabli de la bibliothĂšque) ou une +exclusion consciente des fragments ; l'un comme l'autre s'inscrit dans le modĂšle de validation +`StringSpec` existant. Qui en bĂ©nĂ©ficie : quiconque teste des chemins d'Ă©galitĂ©/inĂ©galitĂ© avec des +identifiants chaĂźne — un cas trĂšs courant. (`OneOf` sur les chaĂźnes est alors gratuit via la +proposition 1.) + +### Souhaitable + +* **GĂ©nĂ©rateur `Uri`** (`Any.Uri().UsingHttps().WithHost("example.com")`) — le seul type BCL de type + valeur Ă  la fois couramment nĂ©cessaire dans les tests et rĂ©ellement pĂ©nible Ă  composer Ă  la main + (rĂšgles de validitĂ© schĂ©ma/hĂŽte/chemin/query). IntĂ©grĂ© aux deux TFM. CoĂ»t modĂ©rĂ© (sa propre mini + algĂšbre de contraintes) ; un timing guidĂ© par la demande convient. +* **`WithChars(string pool)` / alphabet personnalisĂ© sur `AnyString`** — aujourd'hui le texte non-ASCII + (accents, i18n) n'est atteignable que via des littĂ©raux `StringMatching` ; un rĂ©servoir personnalisĂ© + est une petite extension composable du mĂ©canisme de jeu de caractĂšres existant, et dĂ©bloque le cas + d'usage du code sensible Ă  l'i18n sans aucune machinerie de tables Unicode. +* **`MultipleOf(int)` sur les entiers / `WithScale(int)` sur decimal** — « un montant valide en + centimes », « une quantitĂ© en douzaines » : de vĂ©ritables invariants (pas des assertions) qui forcent + aujourd'hui des contournements `As(x => x * 100)` qui dĂ©forment la plage dĂ©clarĂ©e. Constructif Ă  + implĂ©menter (tirer dans l'espace du quotient). +* **`ContainingKey(TKey)` sur `AnyDictionary`** (§7.1/§8.2) — referme d'un coup un trou d'API, une + duplication et un trou de test. +* **Combinaisons d'enum [Flags], en opt-in** (`Any.Enum().AllowingCombinations()`) — + aujourd'hui les valeurs combinĂ©es non dĂ©clarĂ©es sont inatteignables *par conception* (membres-dĂ©clarĂ©s- + seulement est le bon dĂ©faut) ; un opt-in explicite respecte le dĂ©faut tout en servant les domaines + riches en drapeaux. NĂ©cessite une position documentĂ©e sur ce que « valide » signifie pour les drapeaux + (union des membres dĂ©clarĂ©s). +* **`WithOffset`/contrĂŽle d'offset sur `AnyDateTimeOffset`** — la dimension d'offset est actuellement + dĂ©gĂ©nĂ©rĂ©e (toujours zĂ©ro, documentĂ©) ; les tests qui exercent l'arithmĂ©tique d'offset ne peuvent pas la + faire varier. Un tirage d'offset bornĂ© (±14 h en minutes, selon les propres rĂšgles du type) prĂ©serve la + validitĂ©. +* **GranularitĂ© temporelle** (`WholeSeconds()`/`WholeDays()` ou `WithGranularity(TimeSpan)`) — les + instants Ă  prĂ©cision de tick sont presque jamais ronds, ce qui surprend les tests qui sĂ©rialisent des + horodatages ; constructif via le moteur ordinal (tirer dans l'espace des granules, multiplier). + Referme aussi entretemps le trou de documentation (« les valeurs sont Ă  prĂ©cision de tick »). +* **Terminal `GenerateMany(int)`** — sucre pour « N valeurs sans la cĂ©rĂ©monie `ListOf` » ; une *mĂ©thode + nommĂ©e* renvoyant `IReadOnlyList`, donc elle reste dans la lettre et l'esprit de l'ADR-0020. +* **Un adaptateur de graine pour framework de test** (`[ReproducibleFact]`) — anticipĂ© par les suivis de + l'ADR-0006, abandonnĂ© lors du rebasage, remplacĂ© par rien. Dummies zĂ©ro-dĂ©pendance ne peut pas + rĂ©fĂ©rencer xUnit, c'est donc une dĂ©cision de *paquet compagnon* (`Dummies.Xunit`) — mĂ©ritant une ADR + explicite oui/non plutĂŽt que le silence, car chaque consommateur re-dĂ©rive aujourd'hui Ă  la main + l'habitude d'envelopper dans `Reproducibly`. + +### IdĂ©es optionnelles + +`Version` (composable aujourd'hui : +`Combine(Any.Int32().Between(0,99), 
, (ma,mi,pa) => new Version(ma,mi,pa))` ; faible frĂ©quence) ; +`IPAddress`/`IPEndPoint` (intĂ©grĂ©s, de niche ; une recette de doc d'abord) ; `Encoding` et `CultureInfo` +(faisables **seulement** depuis un rĂ©servoir fixe embarquĂ© — l'ensemble des cultures installĂ©es est un +danger de reproductibilitĂ© entre machines que la bibliothĂšque ne doit pas hĂ©riter ; les deux sont +subsumĂ©s par la proposition 1 + un rĂ©servoir documentĂ©) ; `MailAddress`, chemins de systĂšme de fichiers, +`Stream`, blobs `byte[]` (toutes des recettes d'une ligne sur la surface existante — `ArrayOf(Any.Byte())` +*est* dĂ©jĂ  le gĂ©nĂ©rateur de blob ; les documenter dans la section recettes du guide utilisateur plutĂŽt +que de livrer des gĂ©nĂ©rateurs) ; sucre `KeyValuePair` ; collections `Queue`/`Stack`/`LinkedList` et +`Sorted*` (conversions `As` d'une ligne ; un `Sorted()` de premiĂšre classe nĂ©cessite un gate de +comparabilitĂ© analogue Ă  l'indice de cardinalitĂ© — la conception existe si la demande apparaĂźt) ; +`BigInteger` (intĂ©grĂ© aux deux TFM mais rompt la symĂ©trie « pleine plage sauf contrainte » — il n'y a pas +de pleine plage ; nĂ©cessite sa propre position de dĂ©faut bornĂ©) ; `Rune` (cible net8 ; en conflit avec le +modĂšle de texte dĂ©libĂ©rĂ©ment ASCII-centrique tant que `WithChars` n'a pas atterri) ; sucre +`ContainingAll(params T[])`. + +### Hors pĂ©rimĂštre (recommandĂ© de rester absent, avec les raisons) + +* **Filtrage `Where(predicate)`** — gĂ©nĂ©rer-puis-filtrer est l'exact opposĂ© du modĂšle constructif de la + bibliothĂšque ; des prĂ©dicats insatisfiables rĂ©introduisent la classe de reprise non bornĂ©e que toute la + conception existe pour exclure. La rĂ©ponse existante (exprimer l'invariant en contraintes, ou + construire via `As` depuis un tirage contraint) est la philosophie. +* **Enregistrement de gĂ©nĂ©rateurs / graphes d'objets façon AutoFixture** — le remplissage automatique + pilotĂ© par rĂ©flexion est le produit voisin que le README Ă©carte explicitement ; de simples membres C# + sont le mĂ©canisme de rĂ©utilisation. +* **Collections immuables** — `System.Collections.Immutable` est un paquet externe sur la cible + netstandard2.0, donc un gĂ©nĂ©rateur romprait l'identitĂ© zĂ©ro-dĂ©pendance lĂ -bas ; cĂŽtĂ© consommateur, + `.As(ImmutableList.CreateRange)` est une ligne. (Une surface net8-only fracturerait l'API entre TFM + pour un gain marginal — ne vaut pas la peine.) +* **`Index`/`Range`** — la validitĂ© est contextuelle (dĂ©pend de la longueur de la sĂ©quence), donc + « arbitraire mais valide » ne peut pas tenir de façon autonome. +* **`RegionInfo`**, **`Complex`** — dĂ©pendant de l'environnement resp. de niche scientifique ; les deux + Ă©chouent au test de frĂ©quence. +* **Fausses donnĂ©es rĂ©alistes** (noms, e-mails, adresses) — explicitement Ă©cartĂ©es ; Bogus existe. + +## 11. AmĂ©liorations recommandĂ©es + +Par ordre de prioritĂ© ; les points 1–7 sont la porte prĂ©-publication recommandĂ©e. + +1. **Corriger les trois dĂ©fauts reproduits** — construction de la fraction dĂ©cimale + (`DecimalIntervalSpec.cs:145`), nudge conscient du type (`ContinuousIntervalSpec.cs:189` → + `_nextUp`), garde de dĂ©bordement du char (`RegexParser.cs:398` + `RegexAlphabet.Range`) ; et la + validation de groupe d'Ă©quilibrage/nom dans `SkipGroupName` (§4.1 d). Chacun avec un test de + non-rĂ©gression. +2. **Ajouter des tests d'atteignabilitĂ© et la suite de scĂ©narios transverse aux moteurs** (§9.3) — la + rĂ©ponse structurelle Ă  la classe de dĂ©fauts, pas seulement aux instances. +3. **Ajouter les gardes de paritĂ©** (§9.2) : test de miroir `Any`↔`AnyContext`, test de matrice + d'algĂšbre. +4. **Solder le contrat de dĂ©terminisme** (§7.3) : documenter le semis Ă  flux logique unique sur + `Reproducibly` ; des indices de rejeu conscients du type de source (et le rapport multi-source de + `Combine`) ; la phrase de politique de stabilitĂ© entre versions ; la qualification gĂ©nĂ©rateur-Ă©tranger + dans le message de saturation (garde-null morte retirĂ©e). RĂ©diger l'**ADR de dĂ©terminisme** et l'**ADR + du moteur ordinal** (§5, lacunes structurelles) en `ProposĂ©e` pour `@reefact`. +5. **ExĂ©cuter Dummies sur ses planchers** : importer `build/Net472TestFloor.props` dans + `Dummies.UnitTests` (tests net8-only conditionnĂ©s hors scope), l'ajouter Ă  la boucle de plancher de + ci.yml ; ajouter l'assertion de sĂ©quence de rĂ©fĂ©rence inter-TFM Ă  `dummies-check` ; Ă©noncer le + plancher .NET Framework dans le README du paquet (suivi de l'ADR-0022). +6. **Passe de documentation** : faire apparaĂźtre Dummies dans le README du dĂ©pĂŽt (table des paquets + + sommaire) ; Ă©crire le guide utilisateur Dummies avec la rĂ©fĂ©rence de contraintes par gĂ©nĂ©rateur et le + dialecte `StringMatching` (refermant le suivi de l'ADR-0025) ; corriger les trois emplacements « ASCII + imprimable » (§4.2) ; annoncer le comportement vide-par-dĂ©faut dans le README du paquet ; corriger les + commentaires/DisplayName pĂ©rimĂ©s (§4.3) et l'en-tĂȘte de `Directory.Build.props`. +7. **Gardes d'ingĂ©nierie de release** : rĂ©fĂ©rence d'API publique (`PublicApiAnalyzers`) et + `EnablePackageValidation` ; dĂ©cider `Bool()` vs `Boolean()` et le consigner ; demander Ă  `@reefact` + de trancher le statut de l'ADR-0025 (aprĂšs sa correction de formulation) ; consigner les deux + clarifications de l'ADR-0026 dans la rĂ©fĂ©rence d'implĂ©mentation ; enrichir ou adoucir les renvois Ă  la + rĂ©fĂ©rence d'implĂ©mentation des ADR-0013/0015. +8. **Livrer les deux fonctionnalitĂ©s Indispensables** (§10) : `Any.OneOf`/`Any.ElementOf`, et les + exclusions de chaĂźne (`DifferentFrom`/`Except` sur `AnyString`). +9. **`AnyDictionary`** : extraire la façade de compte partagĂ©e ; ajouter `ContainingKey`. +10. **Ensuite, guidĂ© par la demande** : la liste Souhaitable (§10), chacune sur preuve de besoin, avec les + donnĂ©es de garde de paritĂ© mises Ă  jour comme partie du « terminĂ© » de chaque ajout. + +## 12. Feuille de route proposĂ©e + +**Phase 0 — avant la premiĂšre release `dum-v*` (correction et contrat).** Points 1–7 ci-dessus. La +justification est celle de l'ADR-0020 : chacun de ces points est bon marchĂ© maintenant et coĂ»teux aprĂšs +adoption — le correctif dĂ©cimal change chaque sĂ©quence semĂ©e (un non-Ă©vĂ©nement aujourd'hui, un Ă©vĂ©nement +de compatibilitĂ© aprĂšs la v1) ; la politique de dĂ©terminisme, le nommage `Bool`, la rĂ©fĂ©rence d'API et +les statuts d'ADR sont tous des dĂ©cisions d'une ligne ou d'un fichier qui deviennent des migrations plus +tard. CritĂšre de sortie : la table des faiblesses du §4 est vide, sauf les points explicitement diffĂ©rĂ©s +par dĂ©cision consignĂ©e. + +**Phase 1 — premier cycle stable (complĂ©tude au sein de la philosophie).** Point 8 (les deux +Indispensables, additifs et Ă  faible risque), point 9, la section recettes du guide utilisateur (blobs, +chemins, Version, Uri-via-Combine — transformant les types de la liste Optionnelle en documentation +plutĂŽt qu'en surface), et la dĂ©cision de paquet compagnon `Dummies.Xunit` (oui ou non, en ADR). + +**Phase 2 — croissance guidĂ©e par la demande.** Les Souhaitables au fur et Ă  mesure que de vraies +demandes arrivent (`Uri` et `WithChars` d'abord, au vu des preuves actuelles), chaque ajout portant son +entrĂ©e de matrice de paritĂ©, ses tests et ses docs EN/FR comme une seule unitĂ©. Revisiter la liste +Optionnelle chaque annĂ©e ; rĂ©sister Ă  la liste Hors pĂ©rimĂštre en permanence — c'est ce qui garde cette +bibliothĂšque telle qu'elle est. + +## 13. Conclusion + +Dummies est ce Ă  quoi ressemble une bibliothĂšque focalisĂ©e quand les auteurs savent exactement Ă  quoi +elle sert et — tout aussi important — Ă  quoi elle ne sert pas. Le moteur en espace ordinal, les +diagnostics Ă  provenance de contraintes, la discipline d'Ă©chappatoires bornĂ©es et la trace d'ADR sont +tous meilleurs que la norme de la catĂ©gorie, et la conception composition-d'abord garde honnĂȘte la +surface de fonctionnalitĂ©s future : la plupart des « types manquants » sont correctement Ă  un `As` de +distance, pas Ă  un gĂ©nĂ©rateur de distance. + +Les constats de l'audit se concentrent en un seul endroit : l'espace entre le comportement *dĂ©clarĂ©* et +le comportement *atteignable*. Deux des trois dĂ©fauts reproduits vivent exactement lĂ , invisibles Ă  une +suite fondĂ©e sur la seule appartenance ; les surfaces miroir dĂ©rivent exactement lĂ  oĂč aucune garde ne +regarde ; la promesse de dĂ©terminisme est saine prĂ©cisĂ©ment jusqu'aux bords qu'aucun document ne dĂ©crit. +Tout cela est corrigeable de ce cĂŽtĂ©-ci de la premiĂšre publication, la plupart en quelques jours, et les +points de plus grande valeur ne sont pas les correctifs mais les gardes — la suite d'atteignabilitĂ©, les +tests de paritĂ©, la rĂ©fĂ©rence d'API — qui rendent impossible de livrer silencieusement le prochain dĂ©faut +de chaque classe. + +La Phase 0 faite, c'est une bibliothĂšque qui peut promettre de façon crĂ©dible ce que dit son README : +arbitraire mais valide, des conflits nommĂ©s Ă  la ligne qui les a causĂ©s, et tout run rejouable depuis une +graine rapportĂ©e — sur chaque cible pour laquelle elle est livrĂ©e. + +## 14. Suivi des issues + +Les recommandations de la §11 ont Ă©tĂ© ouvertes en issues GitHub le 2026-07-20, sur le gabarit d'issue +Dummies du dĂ©pĂŽt. Cette table est un **instantanĂ© figĂ©** : l'Ă©tat vivant de chaque issue (ouverte, fermĂ©e, +en cours) vit dans le tracker, pas ici — ne pas maintenir de statut dans ce document. + +| Point §11 | Issue(s) | Phase (§12) | +|---|---|---| +| 1 — Corriger les dĂ©fauts reproduits | [#206](https://github.com/Reefact/first-class-errors/issues/206) AnyDecimal moitiĂ© haute · [#207](https://github.com/Reefact/first-class-errors/issues/207) nudge Single/Half · [#208](https://github.com/Reefact/first-class-errors/issues/208) blocage U+FFFF · [#209](https://github.com/Reefact/first-class-errors/issues/209) groupes d'Ă©quilibrage · [#210](https://github.com/Reefact/first-class-errors/issues/210) bords regex mineurs | 0 | +| 2 — AtteignabilitĂ© + suite transverse | [#213](https://github.com/Reefact/first-class-errors/issues/213) | 0 | +| 3 — Gardes de paritĂ© | [#214](https://github.com/Reefact/first-class-errors/issues/214) | 0 | +| 4 — Solder le contrat de dĂ©terminisme | [#216](https://github.com/Reefact/first-class-errors/issues/216) doc contrat + ADR · [#217](https://github.com/Reefact/first-class-errors/issues/217) ADR moteur ordinal · [#211](https://github.com/Reefact/first-class-errors/issues/211) rapport de graine · [#212](https://github.com/Reefact/first-class-errors/issues/212) message de saturation | 0 | +| 5 — ExĂ©cuter sur les planchers | [#215](https://github.com/Reefact/first-class-errors/issues/215) | 0 | +| 6 — Passe de documentation | [#218](https://github.com/Reefact/first-class-errors/issues/218) README + guide utilisateur · [#219](https://github.com/Reefact/first-class-errors/issues/219) ASCII imprimable & docs pĂ©rimĂ©es | 0 | +| 7 — Gardes d'ingĂ©nierie de release | [#221](https://github.com/Reefact/first-class-errors/issues/221) baseline API · [#222](https://github.com/Reefact/first-class-errors/issues/222) nommage Bool · [#220](https://github.com/Reefact/first-class-errors/issues/220) hygiĂšne ADR | 0 | +| 8 — Livrer les Indispensables | [#223](https://github.com/Reefact/first-class-errors/issues/223) Any.OneOf/ElementOf · [#224](https://github.com/Reefact/first-class-errors/issues/224) exclusions AnyString | 1 | +| 9 — AnyDictionary | [#225](https://github.com/Reefact/first-class-errors/issues/225) | 1 | +| 10 — Souhaitables guidĂ©s par la demande | [#226](https://github.com/Reefact/first-class-errors/issues/226) backlog | 2 | + +--- + +*Produit par un audit menĂ© par agents (revue multi-agents avec vĂ©rification contradictoire ; tous les +dĂ©fauts rapportĂ©s reproduits indĂ©pendamment contre la bibliothĂšque compilĂ©e ; suite de tests complĂšte +exĂ©cutĂ©e). Consultatif au sens de l'ADR-0004 : recommandations et brouillons seulement — chaque dĂ©cision +demeure au mainteneur.* diff --git a/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.md b/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.md new file mode 100644 index 00000000..4cee8419 --- /dev/null +++ b/doc/handwritten/for-maintainers/audit/2026-07-20-dummies-architecture-and-design-audit.md @@ -0,0 +1,980 @@ +# Dummies — Architecture & Design Audit + +🌍 **Languages:** +🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./2026-07-20-dummies-architecture-and-design-audit.fr.md) + +**Date:** 2026-07-20 +**Audited revision:** `3bf89e3` (tip of `main` at audit time) +**Scope:** the `Dummies` library only — `Dummies/`, `Dummies.UnitTests/`, its guard tooling +(`tools/dummies-check/`, `.github/workflows/dummies.yml`), its documentation, and the ADRs that govern it. +**Status:** advisory. Per the repository's own convention (ADR-0004), this audit produces +recommendations, never blockers; every proposed ADR change is a draft for `@reefact` to accept or reject. + +**Method.** The whole library source (~8,700 lines across 54 C# files) and test suite (~2,500 lines, +17 files) were read; all 26 ADRs were classified for applicability and the 8 applicable ones reviewed +for both intrinsic quality and implementation compliance; findings were adversarially verified against +the code, and the three behavioral defects reported below were **independently reproduced at runtime** +against the built library. The full unit-test suite was executed: **222/222 pass** (`dotnet test +Dummies.UnitTests`, net10.0 runner). Judgments are calibrated against the library's stated goals — +readable tests, expressive test data, opt-in determinism, a fluent discoverable API, simplicity — and +deliberately *not* against the goals of property-based-testing or fuzzing frameworks, which this +library explicitly is not. + +--- + +## 1. Executive Summary + +Dummies is a young library built to an unusually high standard. Its central architectural idea — map +every discrete type into a shared 64-bit ordinal space so that one engine owns bounds, exclusions, +conflict detection, and sampling for thirteen builders at once — is elegant and correctly executed. +Its error-message discipline (every conflicting constraint names *both* sides, every generation +failure names the seed that replays it) is better than that of most mature libraries in this space. +Its ADR base is exemplary: decisions are recorded with honest constraints, real alternatives, and +priced trade-offs. + +The audit nevertheless found **three genuine behavioral defects**, all reproduced at runtime: + +1. **Critical — `AnyDecimal` can never generate the upper half of its range.** A fraction intended + to be uniform in [0, 1) is constructed from three 31-bit draws against a 96-bit denominator and + tops out near 0.5; `Any.Decimal().Between(0m, 100m)` never exceeds ~49.9999 + (`DecimalIntervalSpec.cs:145`). +2. **Major — `AnySingle`/`AnyHalf` exclusion nudge stalls.** The exclusion-collision walk steps by a + *double* ulp instead of the type's own ulp, so quantization lands back on the same value and a + satisfiable spec such as `Any.Half().Between((Half)1f, (Half)1.001f).DifferentFrom((Half)1f)` + throws `AnyGenerationException` for ~half of all seeds (`ContinuousIntervalSpec.cs:189`). +3. **Major — a regex character-class range ending at `ïżż` hangs forever.** The class-expansion + loop increments a 16-bit `char` that wraps at `0xFFFF`, so + `Any.StringMatching(@"[ -ïżż]")` never returns (`RegexParser.cs:398`). + +All three share one root cause worth naming: **the test suite asserts membership, never +reachability.** Tests check that generated values satisfy the constraints; no test checks that the +whole declared domain is reachable, or that a declared-satisfiable spec actually generates. That is +the precise blind spot in an otherwise well-designed suite, and closing it matters more than any +individual fix. + +One framing fact softens all of this considerably: **Dummies has never been released.** There is no +`dum-v*` tag; the changelog holds only an empty *Unreleased* section. Every defect above can be fixed, +and every contract decided, with zero compatibility cost. This audit's headline recommendation is to +treat the pre-1.0 window the way ADR-0020 did — as the cheapest moment to decide — and close the +items in §11–§12 before the first publication. + +Beyond the defects, the significant findings are: the hand-mirrored `Any`/`AnyContext` surface and +the fourteen cloned numeric builders carry **no parity guard** (and documentation drift has already +begun); the **determinism contract has documentation gaps** (concurrent draws inside one seeded scope +silently void replayability; cross-version seed stability is neither promised nor disclaimed; the +contract's ADR anchoring was lost when ADR-0006 was superseded); the **netstandard2.0 leg is never +executed by Dummies' own test suite** (only transitively, via FirstClassErrors' floor job); and there +is **no user-facing reference of the constraint surface** — the repository README does not even +mention the package. The feature-gap analysis (§10) finds the type coverage genuinely complete for +the library's philosophy; the two absences that qualify as surprising are a *top-level* choice +combinator (`Any.OneOf(params T[])` / `Any.ElementOf(...)`) and exclusion constraints on +`AnyString` — the only scalar builder without them. + +## 2. Overall Assessment + +**Verdict: a very strong pre-release library — architecture and process are its strengths; value-space +correctness testing is its one systemic weakness.** + +Judged area by area against the stated goals: + +| Area | Assessment | +|---|---| +| Architecture | Excellent. Clean layering (public builders → internal specs → sampling), one shared ordinal engine, principled engine split, composition seams over one tiny interface. | +| API design | Excellent, with a handful of deliberate-looking but unrecorded asymmetries (§8). | +| Error diagnostics | Exceptional — the library's signature strength. | +| Determinism | Sound design, correctly implemented at the `AsyncLocal`/`ExecutionContext` level; contract under-documented at its edges (§7.3). | +| Correctness | Three reproduced defects, two of them in exactly the code a membership-only test suite cannot see (§4.1). | +| Testing strategy | Well-shaped (behavior-first, black-box, oracle-backed for regex, flake-safe) but reachability-blind (§9.3). | +| Documentation | XML docs outstanding; user-facing documentation thin and hard to discover (§4.4). | +| Maintainability | Duplication is large but disciplined (zero copy-paste slips found in the clone families); the risk is unguarded drift, not present-day rot (§9). | +| ADR base | Exemplary quality; two structural gaps — the determinism contract and the ordinal engine have no ADR of their own (§5). | + +The overall shape is characteristic of a library written with great care by a small number of hands: +the *decisions* are consistently right and consistently recorded, while the safety nets that protect +those decisions from future hands (parity guards, reachability tests, API baselines) are not yet in +place. Pre-1.0 is the moment to install them. + +## 3. Strengths + +These are earned, verified against the code, and worth preserving deliberately. + +### 3.1 The ordinal-space unification + +Every discrete type — all ten 64-bit-or-narrower integers, `DateTime`, `DateTimeOffset`, `TimeSpan`, +`DateOnly`, `TimeOnly` — maps order-preservingly into unsigned 64-bit ordinal space +(`OrdinalMapping.FromInt64` flips the sign bit; `OrdinalIntervalSpec.cs:9-23`) and shares **one** +engine for bounds, allow-lists, exclusions, conflict detection, cardinality, and sampling +(`OrdinalIntervalSpec`). The exclusion algorithm is exact — a drawn index is mapped onto the k-th +non-excluded ordinal in a single pass over a sorted exclusion list (`OrdinalIntervalSpec.cs:194-202`) +— so generation is one draw, never draw-and-retry. A fix to a conflict message or an edge case +reaches every discrete builder simultaneously. This is the right level at which to be DRY: the +*logic* is shared while the thin per-type facades stay simple and readable. + +### 3.2 Constraint provenance and eager validation + +Every bound remembers the constraint string that set it (`"Between(1, 6)"`, `"Positive()"`), so a +conflict names **both** sides at the moment of declaration: + +``` +Cannot apply LessThan(10) because GreaterThan(100) already requires values greater than or equal to 101. +``` + +The discipline holds uniformly across every builder and spec engine, including +cross-cutting validations one would not expect to find (a `Numeric()` charset rejects a prefix +containing letters, naming the offending character — `StringSpec.cs:254-275`). Combined with eager +satisfiability checking ("a generator that exists can always generate"), an impossible `Arrange` +fails at the line that wrote it, not at some later draw. This is the library's signature, and it is +executed consistently. + +### 3.3 The determinism machinery is done right at the hard level + +The linchpin — generators store a `RandomSource` and resolve `.Current` only at `Generate()` time — +is what lets a recipe built outside `Any.Reproducibly(...)` generate deterministically inside one +(`RandomSource.cs:3-9`). The `AsyncLocal` scope semantics were scrutinized closely and hold: the +sync overload's `using`-restore is correct; the async overload's `UseSeed` mutation cannot leak to +the caller (an async method's `ExecutionContext` mutations do not flow back); nesting restores the +outer scope untouched; `ConfigureAwait(false)` is immaterial to `ExecutionContext` flow. The seed is +reported end-to-end: a user factory that throws inside `.As(...)` produces an +`AnyGenerationException` naming the generated value *and* the seed (`AnyDerivation.cs:59-73`); a +distinct-collection exhaustion does the same (`CollectionState.cs:246-254`). + +Two subtle dual-target traps were caught in advance and documented at the exact point of danger: +`RandomSampling`'s inclusive sampler is deliberately *not* named `NextInt64` because on the net8.0 +leg the framework's own exclusive-bound instance method would win overload resolution and silently +change semantics (`RandomSource.cs:129-137`); and the `OrNull` extension is split into two classes +because `struct`- and `class`-constrained overloads of one name would collide +(`NullableExtensions.cs:47-52`). This is the kind of care that cannot be retrofitted. + +### 3.4 Bounded escapes everywhere — no unbounded retry anywhere + +The library's "built to satisfy, never generate-then-filter" claim survives scrutiny with three +honest, ADR-recorded exceptions, each *bounded*: the distinct-collection dedup draw (budgeted, +coupon-collector-generous, reset-on-progress — `CollectionState.cs:236-244`), the continuous-domain +exclusion nudge (walks to the neighbouring representable value), and `AnyGuid`'s collision escape (a +full-width carry increment that provably terminates — `AnyGuid.cs:27-36`). Each failure mode +produces an actionable, seed-bearing message instead of a hang. + +### 3.5 Care at the edges + +Small things that reveal the quality bar: `AnyDateTime.OneOf` remembers callers' original values so +the ordinal round-trip does not silently normalize `DateTimeKind` (`AnyDateTime.cs:124-131`); +collection layout is Fisher-Yates-shuffled so a dummy collection never advertises a positional +invariant a test might accidentally rely on (`CollectionState.cs:46-53`); `CountSpec` and +`StringSpec` saturate rather than overflow on huge declared minima; `IAny` covariance means +the read-only collection interfaces (`IReadOnlyList`, etc.) are served for free. + +### 3.6 The regex subsystem is well-built for its decided scope + +The hand-written recursive-descent parser (`RegexParser.cs`, 457 lines — the largest single piece of +logic in the library) is well-structured, why-commented, and disciplined about its two-channel +rejection taxonomy: `ArgumentException` for malformed patterns, `UnsupportedRegexException` naming +the construct and position for well-formed-but-non-regular ones. The test suite validates generated +strings against the **real .NET regex engine as an oracle** over a fixed-seed corpus — exactly the +right way to test a generator. (Defects found at its edges are cataloged in §4.1 and §4.2; they do +not change the assessment that the ADR-0025 approach was sound and honestly argued.) + +### 3.7 Packaging, boundary, and process + +The zero-dependency, error-agnostic boundary is enforced three ways: a `.csproj` comment stating the +rule, an intent-based architecture test that fails on any non-BCL assembly reference +(`ArchitectureTests.cs:27-37`), and the `dummies-check` packaged-asset guard — a real consumer +program, run in CI against the *packed artifact* per target framework, that proves the net8.0 asset +carries the modern generators, the netstandard2.0 asset does not, constraints and conflicts behave, +and same-seed contexts replay (`tools/dummies-check/Program.cs`). Packaging itself is +production-grade: SourceLink with embedded untracked sources, deterministic CI builds, snupkg +symbols, an SPDX SBOM embedded at pack time, provenance-attested release assets +(`Directory.Build.props:10-23`, `Dummies.csproj:60-66`, `release.yml`). The ADR base recording all +of this is discussed in §5 — it is a strength in itself. + +### 3.8 The API philosophy is coherent and documented where users look + +The "constraints express what the surrounding code *requires*, never what the test asserts" idea is +stated on the entry point, on every builder, in the package README, and in the user guide — the same +sentence, deliberately. The no-clock-relative-constraints stance (`AnyDateTime` has no +`InThePast()`) is documented at every point a user would look for it, with the reproducibility +rationale attached. Intention-revealing near-synonyms are honestly explained: `DifferentFrom(x)` is +documented as semantically `Except(x)` with a name that carries intent; `Containing` (a value known +now) vs `ContainingAny` (a generator drawn at build time) is a genuinely useful distinction. + +## 4. Weaknesses + +Ordered by severity. Items 4.1 and 4.3 are the ones that should gate a first release. + +### 4.1 Reproduced behavioral defects + +**(a) `AnyDecimal` never reaches the upper half of any range — critical.** + +`DecimalIntervalSpec.cs:144-149`: + +```csharp +// A uniform-enough fraction in [0, 1): 93 random bits over the full decimal mantissa scale. +decimal fraction = new decimal(random.Next(), random.Next(), random.Next(), false, 28) / MaxFraction; +decimal mid = _min / 2 + _max / 2; +decimal half = _max / 2 - _min / 2; +decimal candidate = Clamped(mid + (fraction * 2 - 1) * half); +``` + +`Random.Next()` returns a non-negative `int`, so the top bit of **each 32-bit limb** of the 96-bit +mantissa is always zero, while `MaxFraction` is the *full* 96-bit mantissa maximum +(`7.9228
`, `DecimalIntervalSpec.cs:14`). The fraction therefore lives in [0, ~0.49999986], not +[0, 1); `(fraction * 2 - 1)` lives in [−1, ~0); and every candidate lands in `[min, mid)`. The +inclusive maximum documented on `AnyDecimal.Between` (`AnyDecimal.cs:112`) is unreachable — as is +everything above the midpoint. Independently reproduced for this audit: the maximum of 200,000 draws +of `Any.Decimal().Between(0m, 100m)` was **49.99992
**. + +Why it matters beyond the obvious: a test using `Any.Decimal().Between(0m, 100m)` to exercise "any +valid percentage" silently never exercises 50–100 — the library's core promise ("arbitrary yet +valid, so hidden assumptions surface") is inverted into a hidden assumption of its own. The fix is +small: build the fraction from 96 genuinely uniform bits, e.g. + +```csharp +// after: 12 random bytes fill all three 32-bit limbs uniformly +// (the decimal ctor reads the int limbs as raw 32-bit patterns) +byte[] limbs = new byte[12]; +random.NextBytes(limbs); +decimal fraction = new decimal( + BitConverter.ToInt32(limbs, 0), + BitConverter.ToInt32(limbs, 4), + BitConverter.ToInt32(limbs, 8), + false, 28) / MaxFraction; +``` + +(any construction that fills all 96 mantissa bits uniformly is fine — the current three `Next()` +calls fix the top bit of each limb at zero and can never draw a limb of `2^31−1`), then add the +reachability test from §11 item 2. Note the comment's own claim ("93 random bits over the full +mantissa scale") documents an intent the code does not meet — and even 93 well-placed bits would +not reach a 96-bit denominator's upper octant. + +**(b) `AnySingle`/`AnyHalf` exclusion nudge stalls on satisfiable specs — major.** + +`ContinuousIntervalSpec.cs:188-198`: when a drawn value collides with an excluded point, the walk +steps with the **static, double-space** `NextUp` (line 189) instead of the *type-aware* `_nextUp` +lambda that `AnySingle`/`AnyHalf` supply precisely for stepping in their own representable ladder +(`AnySingle.cs:20`, `AnyHalf.cs:22`) — and which the exclusive-bound paths already use correctly +(lines 120, 125). One double-ulp above a representable `float`/`Half` re-quantizes to the same +value, the `next > _max` escape at line 190 is unreachable (`Quantized` clamps to `_max` first, +lines 203-209), so the 128-step budget burns and a *satisfiable* spec throws. Independently +reproduced: `Any.Half().Between((Half)1f, (Half)1.001f).DifferentFrom((Half)1f).Generate()` threw +`AnyGenerationException` for **250 of 500 seeds**; the identical `AnyDouble` scenario never throws +(its quantize is identity). The fix is one token — `Quantized(_nextUp(candidate))` — plus a +regression test per continuous type. + +This defect is worth a design note: it is exactly the failure class the library's own architecture +predicts. The engine was parameterized by `quantize`/`nextUp` lambdas *because* narrow types must +step in their own ladder; one call site inside the same file forgot the parameter. A +cross-engine, parameterized scenario suite (§9.3) is the structural answer. + +**(c) A character-class range ending at `ïżż` hangs forever — major.** + +`RegexParser.cs:398`: + +```csharp +for (char character = low; character <= high; character++) { set.Add(character); } +``` + +When `high == 'ïżż'` (reachable through the supported `\uHHHH` escape), the 16-bit `char` wraps +to `0x0000` and `character <= high` is always true. Independently reproduced: +`Any.StringMatching(@"[ -ïżż]")` did not return within five seconds (hard hang), while the +same pattern is valid .NET regex. A declaration-time hang is the worst failure mode this library +can exhibit — its identity is *failing fast with a named cause*. Fix: guard the wrap +(`if (character == high) break;` inside the loop, or iterate an `int`), and mirror the check in the +private twin loop `RegexAlphabet.Range` (`RegexAlphabet.cs:66-71`) for defense in depth. + +**(d) Balancing groups and invalid group names are silently accepted — major, contract-breaking +direction.** + +`SkipGroupName` (`RegexParser.cs:295-300`) scans to the terminator with no validation. Consequently +`(?<-a>x)` — a *balancing group*, non-regular, same family as the backreferences the library +proudly rejects — is treated as an ordinary named group: `Any.StringMatching(@"(?y)?(?<-a>x)")` +generates `"x"`, which the real engine does **not** match (verified: the pattern's language is +exactly `{"yx"}`). Invalid group names (`(?x)`) are likewise accepted where .NET rejects them. +This is the single place the audit found where the library's signature promise — *"a clear error +beats a value which does not actually match"* (ADR-0025) — is broken. The fix is local: validate +the captured name (reject `-` as `Unsupported("a balancing group 
")`, reject non-word characters +as `Malformed(...)`). + +**(e) Minor defects in the same subsystem.** The generation-limit exception blames "a nested +unbounded quantifier" even when the true cause is a large *bounded* quantifier +(`(a{1000}){1000}` — message asserts a diagnosis that is false; `RegexNode.cs:31-37`); a few +patterns the real engine accepts are conservatively refused (`^*`, `abc$$` — while `^^abc` is +accepted, an avoidable asymmetry; a leading `-[` in a class is misread as subtraction); and a +well-formed negated class whose members lie outside the printable universe is misclassified as +*malformed* instead of *unsupported*. All of these fail in the safe direction (refusal, never +mis-generation) and are cosmetic next to (c) and (d). + +### 4.2 The "printable ASCII" claim is overstated in three places + +`RegexAlphabet.cs:3-9`, `AnyPattern.cs:15-16`, and `Any.cs:70` all claim every terminal resolves to +printable ASCII (0x20–0x7E). The code — correctly — emits exactly the characters the pattern +demands: `\t`, `\a`, `\cA`, `\0`, and `\uHHHH` literals can be non-printable or non-ASCII, and the +library's own test asserts it (`AnyPatternTests` — `\a` → U+0007). The restriction genuinely +applies only where the pattern leaves the character *free* (shorthands, the dot, negated classes). +Since ADR-0025 explicitly declares the character universe a behavior consumers may rely on, the +three doc sites should say precisely that (§11 item 6). + +### 4.3 Hand-mirrored surfaces with no parity guard, and drift has already begun + +Two mirror structures must agree method-for-method, and nothing checks either: + +* **`Any` ↔ `AnyContext`**: every scalar entry point exists twice (21 on the netstandard2.0 leg, 26 + on net8.0, counting both `StringMatching` overloads) — `Any.cs:54-317` vs `AnyContext.cs:48-296`. + The mirror is legitimate design (composition and collections are deliberately *not* mirrored — + they inherit a context through operand sources, which is elegant), but a new scalar type added to + `Any` and forgotten on `AnyContext` would compile, pass all 222 tests, and ship a hole in the + deterministic surface. Wording drift is already visible inside `AnyContext` itself (two different + determinism phrasings across its factories; its `Guid()` doc mentions `Any.Reproducibly`, which a + fixed context ignores by design). +* **The fourteen numeric builders** are byte-identical clones modulo type substitution (~2,450 + lines; the signed quartet, unsigned quartet, continuous trio, and wide pair; the five temporal + builders follow the same pattern for ~800 more). To the clone families' credit, a scripted scan + found **zero copy-paste slips** in the code itself — but three XML summaries say "Same constraint + algebra as `AnyInt32`" on builders where it is literally false (unsigned types lack + `Positive`/`Negative`; temporal types rename the bound family), and three test DisplayNames still + claim generators "convert implicitly to their value type" + (`AnyContinuousTests.cs:108`, `AnySignedIntegerTests.cs:87`, `AnyUnsignedIntegerTests.cs:76`) — + conversions ADR-0020 removed. A stale comment in `SeedReproducibilityTests.cs:17-18` explains + code by those same removed conversions. + +The absence of guards is the finding; the mitigation analysis and recommendation (reflection-based +parity tests, *not* a generic base class) is in §9.2. + +### 4.4 Documentation reaches neither the discoverer nor the power user + +* The **repository README never mentions Dummies** (verified: zero occurrences), while the package + README points to the repository for "full documentation". A NuGet discoverer lands on a front + page about a different library; the closest thing to a Dummies guide + (`ArbitraryTestValues.en.md`) is a FirstClassErrors.Testing integration guide that defers back to + "documented with Dummies itself" — a circular reference. +* **No user-facing reference documents the per-builder constraint surface.** Where does a user + learn that `Except`/`OneOf`/`DifferentFrom` exist on numerics, that `WithLengthBetween` exists, + that `ContainingAny` differs from `Containing`, or which regex dialect `StringMatching` supports? + Today: only IntelliSense, one builder at a time. ADR-0025's own follow-up ("document the + supported dialect") is still open. +* The **empty-by-default surprise** (an unconstrained collection can have 0 elements, an + unconstrained string can be empty) is well-documented in XML remarks but absent from the package + README, where a skimming user would most benefit from it — it is a deliberate, + philosophy-bearing choice ("a test iterating an unconstrained collection zero times is a hidden + assumption surfacing") and deserves to be advertised as such. + +### 4.5 Determinism contract gaps (documentation, not implementation) + +Detailed in §7.3: concurrent draws inside one seeded scope silently void replayability (and race a +non-thread-safe `System.Random`) — documented nowhere; seed reports can name a wrong or +inapplicable seed for fixed-context and mixed-source compositions; cross-version and cross-TFM +seed-sequence stability is neither promised nor disclaimed; and the whole contract lost its ADR +anchor when ADR-0006 was superseded. + +### 4.6 The netstandard2.0 leg is never executed by Dummies' own suite + +`Dummies.UnitTests` targets net10.0 only. The netstandard2.0 assembly — the one .NET Framework +consumers will load — is exercised only *transitively*: the FirstClassErrors floor job +(`ci.yml:98-115`) runs `FirstClassErrors.UnitTests` on net472, which arranges with `Dummies.Any` +via project reference and the Testing factories, so Dummies does load and generate on the real +.NET Framework CLR — but its own 222-test contract suite (regex oracle, conflict detection, +distinctness gating, seed reproducibility) never runs there, and same-seed-same-values across the +two packaged assets is asserted nowhere. The repository already owns the exact machinery needed +(`build/Net472TestFloor.props`, used by `FirstClassErrors.UnitTests`); extending it to +`Dummies.UnitTests` (with the net8-only tests conditioned out) is mechanical. See ADR-0022 +compliance, §6. + +### 4.7 Release-engineering guardrails not yet installed + +No public-API baseline (`Microsoft.CodeAnalysis.PublicApiAnalyzers`), no +`EnablePackageValidation`/ApiCompat. The changelog commits Dummies to semantic versioning while the +audit itself demonstrates the API surface is hand-mirrored and already drifting in documentation; +breaking-change detection against a shipped baseline is the complementary mechanism parity tests +cannot replace (a removed overload or narrowed return type passes a mirror test). Pre-first-release +is the cheapest moment to install both. One stale comment found here: `Directory.Build.props:3-9` +says the repository ships "FirstClassErrors and FirstClassErrors.Testing" — it omits Dummies, the +very package those pack-time properties now also govern. + +## 5. ADR Review + +Eighteen of the twenty-six ADRs do not concern Dummies (they name the analyzers, the request +binder, GenDoc/CLI tooling, the Outcome API, or repository process). Eight apply, and their quality +was reviewed individually. The overall standard is high enough to say plainly: this ADR base is a +model of the form. Decisions carry honest constraints, genuinely-considered alternatives, priced +negatives, and follow-ups that were actually executed. + +### ADR-0006 — Supply arbitrary test values from a single seedable source *(Superseded)* + +**Quality: exemplary, historically.** The constraints were real (zero-dependency promise, +netstandard2.0 parallel-test safety without `Random.Shared`), the four alternatives were fairly +weighed, and its follow-ups (extract the engine when a second consumer appears; consider an xUnit +adapter) were honored or consciously deferred. Its collision-risk analysis of the unseeded default +is exactly the right depth. **Issue:** its supersession created a gap — see "structural gaps" below. + +### ADR-0011 — Host Dummies as a standalone package *(Accepted)* + +**Quality: good.** The name/identity/boundary reasoning is sound and the no-reference rule is +machine-checked. Two precision nits. First, the *enforced* invariant is stronger than the *recorded* +one: the architecture test forbids **any** non-BCL reference (`ArchitectureTests.cs:27-37`), and +ADR-0025 leans on a "zero-dependency identity 
 the boundary is machine-checked (ADR-0011)" — but +ADR-0011's decision text only forbids referencing *FirstClassErrors projects*. The +zero-*third-party*-dependency rule, load-bearing for ADR-0025's whole argument, is written down +nowhere as a decision. Second, the alternatives never weigh the risks of the ultra-generic NuGet ID +`Dummies` (squat/collision/searchability) — a package identity the ADR itself calls costly to +rename. Neither nit changes the decision; both deserve a line in the record. + +### ADR-0013 — Gate distinct collections by cardinality, else bounded draw *(Accepted)* + +**Quality: outstanding.** The soundness argument — count only the elements the generator must +supply, credit `Containing` values outside its domain, treat opaque `ContainingAny` draws +conservatively, let the bounded draw be the final safety net — is stated in the document and +provably mirrored in the code (`CollectionState.Validate`/`CardinalityCap`/`FixedOutsideCount`). +The risks section even anticipates budget mistuning and instructs "revise based on evidence rather +than describing failure as impossible." **Issue (shared with ADR-0015):** it defers "the exact hint +interface, collection state, draw budget, exception payload, and seed propagation" to the +implementation reference — but the reference's Dummies section +(`adr-implementation-reference.md:58-68`) records none of those specifics (no budget numbers, no +exception payload, no seed-propagation rule). The pointer promises more than the destination holds; +either enrich the reference or soften the pointer. + +### ADR-0015 — Cap Any.Combine at arity eight *(Accepted)* + +**Quality: good.** Honest about the ceiling being heuristic, with a defined escape hatch (add +arities compatibly via a new decision on evidence). The alternatives are real. The same +implementation-reference pointer nit as ADR-0013 applies. + +### ADR-0020 — Materialize dummies only through Generate() *(Accepted)* + +**Quality: exemplary — the best document in the base.** Concrete evidence (the syntactic shapes +where the conversion silently misbehaved, drawn from the suite itself), three fairly-weighed +alternatives including the analyzer route it deliberately declines, honest costs, and the pre-1.0 +timing argument stated as such. It also demonstrably steered later work (ADR-0026 reuses both its +reasoning pattern and its risk framing). No changes recommended. + +### ADR-0022 — Floor the library's .NET Framework support at 4.7.2 *(Accepted)* + +**Quality: sound policy; scope wording aged.** "A compatibility promise that is not exercised +cannot provide a trustworthy support boundary" is the right principle. But the ADR predates Dummies +and speaks of "the shipped `netstandard2.0` libraries" without naming them; whether Dummies is +inside its scope is now a matter of inference, and the floor job does not include it (§6). When the +maintainer next touches this area, a one-line clarification of covered packages would close the +ambiguity — or the Dummies-specific floor decision can ride the new determinism ADR proposed below. + +### ADR-0025 — Generate matching strings from a home-grown regular subset *(Proposed)* + +**Quality: an unusually honest build-vs-buy record.** The rejection of Fare is argued on identity +and error-contract grounds (silent dropping of non-regular constructs vs first-class refusal), not +on FUD; the "non-regular constructs are impossible for *any* finite generator, so the subset is not +a convenience cut" framing is exactly right; the terminal-generator decision is well-argued. +**Issues:** (1) It is still **Proposed** while fully implemented, shipped in the package README, +and *load-bearing for the Accepted ADR-0026* (whose `ErrorCodeFactory` is built on +`StringMatching`) — until the status flips, an accepted decision formally rests on an undecided +one. The audit's role is to flag it; only `@reefact` flips a status. (2) The "terminals draw from +printable ASCII" rationale sentence is imprecise — `\s` includes tab (0x09) and explicit escapes +emit exactly the character they name (§4.2); the wording should be corrected *before* acceptance, +since the ADR itself declares the universe a compatibility-relevant behavior. (3) It cites "a +property test" against the real engine; what exists is a fixed-seed, fixed-corpus oracle test in +the unit-test project — excellent, but not property-based; the text should say what the safety net +is. + +### ADR-0026 — Rebase the testing package's arbitrary values on Dummies *(Accepted)* + +**Quality: a thorough consolidation record** — six real alternatives, the one-seed-story rationale, +honest interim-packaging risk. **Two precision drifts:** (1) the decision text says each factory +exposes "an `IAny` generator through a distinct method where composition is needed" — no factory +exposes any such method today (verified: zero `IAny` occurrences in `FirstClassErrors.Testing` +sources). Defensible YAGNI, but the text reads as a decided API shape, and a compliance check a +year from now cannot tell deliberate deferral from unfinished migration. (2) Its risk clause says +the double-assembly hazard exists "precisely because Dummies types appear in Testing's public API" +— today none do; the premise is misstated (the hazard is real for other reasons while Dummies ships +inside the artifact). Since accepted ADRs are never edited in place, both belong as a short note in +the implementation reference. + +### Structural gaps in the base (Create-recommendations) + +1. **Dummies' determinism contract has no accepted ADR.** The `AsyncLocal` ambient source, opt-in + `Reproducibly`, lazy pinning, seed-on-failure reporting — the crown-jewel guarantee — was decided + in ADR-0006, which is now Superseded *and* was scoped to FirstClassErrors.Testing; ADR-0026's + decision is about rebasing Testing, not about Dummies' own contract. A future maintainer asking + "why `AsyncLocal` and not a parameter? why is raced `System.Random` acceptable?" finds the + reasoning only in a superseded record. **Recommend drafting one Proposed ADR** ("Dummies supplies + arbitrary values from an ambient, seedable, execution-context-local source with opt-in + reproducibility") carrying ADR-0006's rationale forward and settling, in the same document, the + open edges this audit surfaced: single-logical-flow concurrency semantics, the closed + `IHasRandomSource` seam, and the cross-version seed-stability policy (§7.3). +2. **The ordinal-engine architecture has no ADR.** One shared 64-bit ordinal space with four + arithmetic-substrate engines is a lasting, questionable-by-a-future-maintainer decision + (why four engines? why is `decimal` not ordinal-mapped?) that currently lives only in internal + XML docs. It passes the repository's own ADR test ("if the implementation changed but the + decision stood
"). A short Proposed ADR would fix the asymmetry with far smaller decisions + (arity caps) that did get records. + +## 6. ADR Compliance + +| ADR | Status | Compliance of the implementation | +|---|---|---| +| 0006 (historical) | Superseded | **Compliant and exceeded.** The inherited seeding contract (context-local, opt-in determinism, seed reporting) is implemented faithfully; Dummies adds the isolated `AnyContext` the original ADR only anticipated. | +| 0011 | Accepted | **Compliant.** No FirstClassErrors reference; boundary machine-checked (`ArchitectureTests`); standalone identity, release train, and docs in place. Note: enforcement is *stronger* than the recorded decision (§5). | +| 0013 | Accepted | **Compliant, verified in detail** — eager gate net of outside-domain `Containing` credits, conservative `ContainingAny` accounting, overflow-safe arithmetic, bounded budget, both failure channels. **One minor deviation:** the exhaustion message *unconditionally* promises `Any.Reproducibly({seed}, 
)` replay (`CollectionState.cs:246-254`; the `seed is not null` guard is dead code — the seed can never be null there). For a **foreign** element generator whose draws ignore the ambient source, that promise is false; the ADR says failures are "explicit and reproducible". Qualify the message when the element generator carries no library source. | +| 0015 | Accepted | **Compliant exactly** — arities 2–8, no more; suppressions localized with ADR-referencing justifications (`Any.cs:622-623`); ceiling documented on the arity-8 overload. | +| 0020 | Accepted | **Fully compliant.** No implicit conversions anywhere; `Generate()` is the sole materialization; builders verified immutable (every fluent method returns a new instance). Residue: three test DisplayNames and one comment still *describe* the removed conversions (§4.3). | +| 0022 | Accepted | **Partial for Dummies.** The netstandard2.0 asset is loaded and driven on net472 only transitively through FirstClassErrors' floor job; Dummies' own suite never runs there, and the package README states no .NET Framework floor at all (FirstClassErrors' README does). Close before first publication (§11 item 5). | +| 0025 | Proposed | **Compliant on every major clause** (home-grown parser, first-class rejection, terminal generator, zero dependencies, printable-ASCII *default* universe, bounded unbounded-quantifier spread). The §4.1(c)/(d) defects are quality bugs *within* the decided scope, not deviations — with the caveat that (d) breaks the rejection *promise* the ADR records. One taxonomy edge: a well-formed negated class outside the printable universe raises `ArgumentException` ("malformed") instead of `UnsupportedRegexException`. | +| 0026 | Accepted | **Compliant on every executed clause** — single engine, single seed scope, `Testing.Any` removed, factories shipped, clock/ids on the ambient context, docs updated EN/FR. The unimplemented "distinct `IAny` method" half and the misstated risk premise are recorded in §5. | + +## 7. Architecture Review + +### 7.1 Layering and shape + +The library is three clean layers: **public fluent builders** (thin, per-type, sealed, immutable) → +**internal spec engines** (`OrdinalIntervalSpec`, `WideIntervalSpec`, `ContinuousIntervalSpec`, +`DecimalIntervalSpec`, `StringSpec`, `CountSpec`, `CollectionState`) → **sampling primitives** +(`RandomSampling`). Public surface never leaks internal types; internal engines never touch the +ambient state directly (sources are passed down). The composition seams — `.As(factory)`, +`Any.Combine(...)`, the collection factories — are all defined over the one-member `IAny`, +which is as small as an interface can be (ISP by construction) and covariant, so derived and foreign +generators flow through every seam uniformly. + +The **four-engine split is principled, not accidental**: 64-bit-mappable discrete types share +`OrdinalIntervalSpec`; 128-bit integers need `WideIntervalSpec` only because netstandard2.0 has no +`UInt128` (the two are verbatim siblings — the one regrettable, TFM-forced duplication); IEEE floats +need continuous sampling with type-aware quantization; `decimal` is neither ordinal-mappable (96-bit +mantissa × scale) nor IEEE. Each engine's existence is justified by its arithmetic substrate. What +is *missing* is the ADR recording this (§5), and — as §4.1(b) showed — a parameterized test suite +exercising each engine through each of its type facades. + +The **collection hierarchy** is a textbook-clean CRTP: +`AnyCollection` holds the shared fluent count/containment surface returning +`TSelf` (without the classic unsafe `(TSelf)this` cast — concrete types implement a +`With(state)` factory), and the five concrete builders add only element shaping and the +`Build(List)` conversion. The exception is `AnyDictionary`, which cannot inherit the base +(its element is a pair) and therefore **duplicates the entire count facade verbatim** (~60 lines, +`AnyDictionary.cs:51-113`) and offers no containment constraint at all — the one place in the +collection family where sharing failed. Extracting the count facade over `CollectionState` (or +adding `ContainingKey`, which would ride the existing key-state machinery for free) would close +both the duplication and the acknowledged test hole (`AnyCollectionTests.cs:161-163` comments on +it). + +### 7.2 Extensibility + +**For users, the design is closed, and that is a legitimate but undocumented choice.** `IAny` is +public, so anyone can implement a generator and compose it through `As`/`Combine`/collections. But +`RandomSource`, `IHasRandomSource`, and `ICardinalityHint` are all internal, so a foreign +generator (a) cannot draw from the ambient seeded source — under `Any.Reproducibly` its values do +not replay, and (b) cannot advertise a finite domain — a distinct collection over it always takes +the bounded-draw path (safe, and exactly what ADR-0013 promises). The degradation is graceful +everywhere (verified: `OrNull` falls back to the ambient source for the null coin; `Combine` +propagates `null` sources without failing). What is missing is one honest paragraph on `IAny`'s +XML doc telling implementers where they stand — today the contract is discoverable only by reading +internal code. If the seam is ever to open, an `ISeedableAny` in a minor release is the natural +shape; nothing needs deciding now except the documentation. + +**For maintainers**, adding one new scalar type touches 6–9 files (builder, `Any`, `AnyContext`, +tests, user docs EN/FR, package README, `dummies-check` if net8-only, possibly a spec engine). The +process is mechanical but real, and only partially guarded (§9.2). + +### 7.3 The determinism machinery — deep dive + +The implementation is correct at the level that is hard to get right (§3.3). The remaining risks +are all *contract-documentation* risks, and they cluster into four: + +**(a) Concurrency inside one seeded scope silently voids replayability — undocumented.** An +`AsyncLocal` copies the *reference*: `Task.Run`/`Parallel.ForEach` children inside one +`Reproducibly` body all see the same `SeededRandom` instance. Two consequences. First, even with +benign interleaving, the draw *order* becomes scheduler-dependent, so the reported seed no longer +replays the run — the exact guarantee the feature exists for. Second, `System.Random` is not +thread-safe, and netstandard2.0 offers no thread-safe alternative; a racing draw can corrupt state +(on .NET Framework, a raced `Random` can degrade to returning zeros). The docs carefully explain +that the source "never leaks *across* tests running in parallel" (true — different logical flows) +but say nothing about parallelism *within* a body. The fix is one honest paragraph on +`Reproducibly` ("a seeded run is single-logical-flow; concurrent draws inside the body are neither +replayable nor safe") — plus, optionally, recording in the new determinism ADR why per-flow +forking (a child source per `Task.Run`) was not attempted (it would change every sequence and +complicate `WithSeed`; the honest restriction is the right V1). + +**(b) Seed reports can name a wrong or inapplicable seed in mixed/fixed-source composition.** +`Combine` propagates the **first non-null** operand source for failure reporting +(`Any.cs:446` et al.). `Any.Combine(Any.WithSeed(1).Int32(), Any.WithSeed(2).Int32(), throwing)` +fails with "seeded with 1; reproduce with `Any.Reproducibly(1, 
)`" — doubly wrong: seed 2 goes +unreported, and the instruction is inapplicable because `Reproducibly` pins the *ambient* source, +which `FixedRandomSource`-backed generators ignore by design. This is an edge case (mixing seeded +contexts inside one composition is unusual), but the failure mode is a *confidently misleading +diagnostic* in the library whose signature is diagnostic honesty. A small fix reaches it: let the +source kind produce the replay hint (ambient → "reproduce with `Any.Reproducibly({seed}, 
)`"; +fixed → "this generator draws from `Any.WithSeed({seed})`, which already replays by itself"), and +have `Combine` collect distinct sources rather than the first. + +**(c) Cross-version and cross-runtime seed stability is neither promised nor disclaimed.** The +package description says "any run is reproducible from a reported seed" without qualification. +Within one process this holds. Across *library versions*, any change to draw order or count +silently changes every sequence — and ADR-0025 already acknowledges consumers may rely on generated +shapes. Across *runtimes*, seeded `new Random(seed)` retains the legacy algorithm on modern .NET +precisely for compatibility, so the common surface should agree between the netstandard2.0 and +net8.0 assets — but nothing tests it (§4.6), and `Random`'s documentation explicitly reserves the +right for implementations to differ across framework versions. The mature policy, before v1: +**promise stability within a package version, disclaim it across versions**, one sentence in the +README and the new determinism ADR. (For comparison: FsCheck and AutoFixture both learned to +disclaim this explicitly.) + +**(d) Lazy ambient pinning makes an *unwrapped* failure only approximately replayable.** Outside +`Reproducibly`, the first draw in a logical flow pins a remembered seed. Draws that happened +*before* the failing block in the same flow (a fixture, an earlier arrange) consume from the same +sequence, so replaying "just the test body" with the reported seed can diverge. The design is +right (this is why `Reproducibly` exists); the user guide's replay narrative could carry one +sentence saying replay fidelity starts at the scope boundary. + +Verified non-issues worth recording so they are not re-litigated: the async-overload +`ExecutionContext` semantics (correct — see §3.3); `NewSeed() = Guid.NewGuid().GetHashCode()` +(collision-tolerant use, analyzed in ADR-0006); xUnit seed-spanning (each test invocation is its +own async frame; a shared class constructor participates in its test's flow, which is the correct +scope); `SequenceOf` re-enumeration (materialized once, never re-draws). + +### 7.4 SOLID, briefly and only where it earns its keep + +SRP: builders carry fluent surface, engines carry semantics — clean. OCP: adding a *constraint* to +a discrete type is a one-method facade addition over an existing engine operation; adding a *type* +is deliberately closed (sealed builders, internal engines) — the right trade for an invariant-heavy +library. LSP: the CRTP collection base is sound (no self-cast trick, `TSelf` bound enforced). ISP: +`IAny` single-member; `ICardinalityHint`'s two members travel together by explicit, +documented design (cardinality without membership would be unsound — the interface doc argues it). +DIP is intentionally absent at the user seam (no injectable randomness abstraction) — that *is* the +closed-extensibility decision of §7.2, acceptable but deserving its paragraph of documentation. + +## 8. API Review + +### 8.1 The constraint algebra is uniform where it counts + +The verified matrix: all five signed integer builders and all four continuous/decimal builders +expose exactly `Positive · Negative · Zero · NonZero · GreaterThan[OrEqualTo] · LessThan[OrEqualTo] +· Between · OneOf · Except · DifferentFrom`; the five unsigned builders drop exactly +`Positive`/`Negative` (meaningless there — `NonZero` covers the intent); the four instant-like +builders (`DateTime`, `DateTimeOffset`, `DateOnly`, `TimeOnly`) rename the bound family to domain +vocabulary (`After`/`AfterOrEqualTo`/`Before`/`BeforeOrEqualTo`/`Between`) with identical +inclusive/exclusive semantics, while `AnyTimeSpan` — a magnitude, not an instant — correctly keeps +the full numeric algebra including `Positive`/`Negative`/`Zero`; `AnyChar` carries the character families +plus the exclusion trio; `AnyGuid` has `NonEmpty`/`Empty`/`OneOf`/`Except`/`DifferentFrom`; +`AnyEnum` has the exclusion trio with declared-members validation; collections share +`NonEmpty · Empty · WithCount · WithMinCount · WithMaxCount · WithCountBetween · Containing · +ContainingAny` (+ `Distinct` variants where meaningful). Bounds are consistently inclusive for +`Between`/`
OrEqualTo` and exclusive for `GreaterThan`/`LessThan`/`After`/`Before` — no semantic +surprises were found anywhere in the matrix. This level of consistency across nineteen hand-written +interval builders — plus their string, char, guid, enum, bool and collection siblings — is an +achievement in itself. + +### 8.2 The asymmetries worth fixing or recording + +* **`AnyString` is the only scalar builder with no exclusion constraints** — no `OneOf`, no + `Except`, no `DifferentFrom`. "A name different from the one I already hold" is one of the most + common dummy-string needs (it is exactly why `DifferentFrom` exists everywhere else, per its own + XML doc). The honest reason for the gap: strings are not ordinal-mapped, so exclusions cannot + ride the interval engine; `DifferentFrom` would need either a bounded redraw (expected collisions + ≈ 0 for any non-trivial spec — consistent with the library's other bounded escapes) or a + spec-aware layout tweak. Recommended (§10 Must-Have): + + ```csharp + // Today — no way to express this: + string other = Any.String().NonEmpty().Generate(); // might equal existing! + // Proposed: + string other = Any.String().NonEmpty().DifferentFrom(existing).Generate(); + ``` + +* **`AnyDictionary` drops `Containing`/`ContainingAny`** and duplicates the count facade (§7.1). + `ContainingKey(TKey)` would ride the existing key-state machinery unchanged. +* **`Any.Bool()` is the single deviation from the CLR-name factory convention** + (`Int32`, `SByte`, `Single`, 
 are all CLR names; the CLR name here is `Boolean`). The short form + is arguably the better ergonomics — but then the convention is "CLR names, except one", and after + 1.0 the rename is breaking in either direction. Decide deliberately and record one line, before + release (the repository has ADRs for precisely this class of naming decision). +* **`PairOf`/`TripleOf` stop at arity 3** while `Combine` runs to 8. Defensible (tuples beyond 3 + read poorly; `Combine` covers them), but the stopping point is recorded nowhere — one doc + sentence closes it. + +### 8.3 Discoverability and ceremony + +The static `Any.` entry point makes the whole scalar surface one keystroke discoverable, and each +builder's fluent methods enumerate its full constraint vocabulary in IntelliSense — good. Two +seams are less discoverable: `As` and `OrNull` are extension methods in separate static classes +(invisible until the `using` exists — though the namespace is shared, so in practice they appear), +and `As` is the library's `Select` under a domain-intent name; one doc line bridging from LINQ +vocabulary ("`As` is `Select` for generators — named for its dominant use: passing through a value +object's factory") would help LINQ-native readers. The `Generate()` terminal ceremony is the +ADR-0020 trade, consciously priced there; the audit confirms the cost is real but small (one call +per materialization), the benefit (no effectful hidden conversions) is structural, and the decision +should stand. `AnyContext` mirrors scalars only — composition inherits the context through operand +sources, which is *more* elegant than mirroring and correctly documented. + +### 8.4 Naming + +`StartingWith`/`EndingWith`/`Containing`, `After`/`Before`, `DifferentFrom` vs `Except`, +`Containing` vs `ContainingAny` — the vocabulary is intention-revealing and reads at the call site +the way the philosophy intends. CLR type-name factories (`Any.Int32()`, not `Any.Int()`) are +consistent with the builder type names (`AnyInt32`) and sidestep C# keyword restrictions; +this is defensible and, more importantly, uniform (§8.2's `Bool` aside). + +## 9. Maintainability Review + +### 9.1 Duplication, measured + +Four clone families among the numeric builders (signed quartet, unsigned quartet, continuous trio, +wide pair — byte-identical modulo type substitution; ~2,450 lines), the five temporal builders on +the same pattern (~800 lines), the constraint-and-conflict logic quadruplicated across the four +engines (~910 lines), and the `Any`/`AnyContext` scalar mirror (~300 doc-heavy lines). A scripted +comparison found **zero behavioral copy-paste slips** across the clone families — evidence of real +discipline — while all drift found so far is *documentation* drift (§4.3), which is exactly the +kind guards don't exist for yet. + +### 9.2 Mitigation: guards, not generics + +The obvious refactor — a CRTP generic base (`AnyOrdinal`) — fails this project's +constraints: C# requires a public base class for a public sealed builder (CS0060), so the internal +engine seam would leak into the public API; netstandard2.0 has no generic math (`INumber` is +net7+), so the per-type `Ord`/`Val`/display lambdas remain; and the library's stated bar is +simplicity of maintenance, which 14 flat, boring, greppable files serve better than one clever +base. Source generators/T4 buy deduplication at the cost of build machinery and debuggability — +also a poor trade here. **Recommended instead: executable parity guards**, ~3 short +reflection-based tests: + +1. *Mirror parity:* every public static `Any` method returning a builder type has an + `AnyContext` instance counterpart with identical name/signature/return type, per TFM (~20 + lines; kills the §4.3 drift class outright). +2. *Algebra parity:* each builder family exposes its exact expected method-name set (the §8.1 + matrix, encoded once as data) — a new builder missing `DifferentFrom`, or a renamed method, + fails with a named diff. +3. *Cross-engine scenario suite:* one parameterized test file runs the same scenario battery + (full-range draw touches both halves; `Between` endpoints reachable; `DifferentFrom` on a + narrow domain; `OneOf`+`Except` interplay; conflict messages) against **every** builder via + small per-type adapters. This is the suite that would have caught both §4.1(a) and §4.1(b) + before any human review. + +Complementarily, install the release-engineering guards of §4.7 (public-API baseline + package +validation) — they catch the breaking-change class parity tests cannot. + +### 9.3 Testing strategy + +What exists is well-shaped: behavior-first naming that reads as living documentation; exception +*messages* tested as first-class contracts; the real-engine regex oracle; regression tests that +encode bug history (the `AnyGuid` race test that races a deadline instead of hanging the suite); +flake-safe property-style assertions (unseeded draws asserted only against their declared domain); +and a strictly black-box posture — no `InternalsVisibleTo` exists, so all 222 tests exercise the +public surface only. That last fact cuts both ways and should be held as a deliberate choice: it +proves the public API is sufficient to specify the library (and makes engine refactors +test-transparent), *and* it is consistent with how both reachability defects survived — no test +looks at an engine's value-space coverage directly. The additions that close the gap, in order of +leverage: the cross-engine scenario suite above; **reachability assertions** (for each builder, a +seeded loop over `Between(lo, hi)` must observe values in both halves and hit both endpoints — +cheap, deterministic under `WithSeed`); a generation-limit test for `AnyPattern` (currently +untested); dedicated tests for the documented-but-untested contracts (empty enum, `AnyException` +base catchability, `DictionaryOf` key-comparer flow); and the cross-TFM same-seed assertion in +`dummies-check` (extend `SeedBatch` with a golden sequence compared across the net8.0 and net6.0 +consumer legs, and extend the smoke to cover `OrNull`/`SequenceOf`/`PairOf`/`StringMatching`/enum +draws, which the packaged-asset guard currently never touches). + +### 9.4 Organization and hygiene + +The flat 54-file root is acceptable today because naming discipline does the foldering (`Any*` = +builders, `*Spec` = engines, `Regex*` = pattern subsystem); grouping into folders is optional +polish, worth doing only alongside another structural change. Hygiene nits found: dead member +`RegexCharacters.Count`; the dead null-guard in `CollectionState.Exhausted` (§6/ADR-0013 row); the +stale comments and DisplayNames of §4.3; the stale `Directory.Build.props` header (§4.7). + +## 10. Feature Gap Analysis + +Method: every proposal was screened against (i) the library's philosophy (constraints express +invariants; no realistic-fake-data, no object graphs, no clock coupling), (ii) the composition +test — *can `As`/`Combine`/`StringMatching` already express this in one readable line?* — and +(iii) the full cost of a new builder (builder + `Any` + `AnyContext` + parity data + tests + docs +EN/FR + package README + possibly `dummies-check`). The bar for **Must Have** is the mandate's: +absence genuinely surprising. The library's composition-first design keeps this list short — most +BCL types are already one `As` away, which is the design working as intended. + +### Must Have + +**1. A top-level choice combinator: `Any.OneOf(params T[])` and `Any.ElementOf(IReadOnlyList)`.** +Picking an arbitrary element from a caller-supplied set is among the most common dummy needs in +real suites ("any of the three configured currencies", "one of the states in this table"). Today +`OneOf` exists only *inside* typed builders — there is no way to draw from a set of domain objects +or strings at all. Every user hand-rolls the same three lines (and forgets the seeded source, +silently breaking `Reproducibly` for that draw — a trap the library exists to prevent): + +```csharp +// Today — hand-rolled, and not seed-aware: +var currencies = new[] { eur, usd, gbp }; +var currency = currencies[new Random().Next(currencies.Length)]; // ambient seed ignored! + +// Proposed — seed-aware, philosophy-consistent, eagerly validated (empty set throws): +Currency currency = Any.OneOf(eur, usd, gbp).Generate(); +Order order = Any.ElementOf(existingOrders).Generate(); +``` + +Constructive (single draw), trivially implemented over the ambient source with an +`ICardinalityHint` (distinct count of the pool — it composes with distinct collections for free), +mirrored on `AnyContext`. Who benefits: every consumer, weekly. Cost: one small builder. This is +the highest-leverage addition available. + +**2. `AnyString.DifferentFrom(string)` / `Except(params string[])`.** +The §8.2 asymmetry: the most-used builder is the only scalar one that cannot exclude values. Honest +cost: a bounded redraw (the library's established escape pattern) or fragment-aware exclusion; +either fits in the existing `StringSpec` validation model. Who benefits: anyone testing +equality/inequality paths with string identifiers — a very common case. (`OneOf` on strings is then +free via proposal 1.) + +### Nice to Have + +* **`Uri` builder** (`Any.Uri().UsingHttps().WithHost("example.com")`) — the one BCL value-like + type that is both commonly needed in tests and genuinely awkward to compose by hand (scheme/host/ + path/query validity rules). In-box on both TFMs. Moderate cost (its own mini constraint algebra); + demand-driven timing is fine. +* **`WithChars(string pool)` / custom alphabet on `AnyString`** — today non-ASCII text (accents, + i18n) is reachable only through `StringMatching` literals; a custom pool is a small, composable + extension of the existing charset mechanism, and unlocks the i18n-sensitive-code use case without + any Unicode-table machinery. +* **`MultipleOf(int)` on integers / `WithScale(int)` on decimal** — "a valid amount in cents", "a + quantity in dozens": genuine invariants (not assertions) that today force `As(x => x * 100)` + workarounds that distort the declared range. Constructive to implement (draw in the quotient + space). +* **`ContainingKey(TKey)` on `AnyDictionary`** (§7.1/§8.2) — closes an API hole, a duplication, and + a test hole at once. +* **[Flags] enum combinations, opt-in** (`Any.Enum().AllowingCombinations()`) — today + undeclared combined values are unreachable *by design* (declared-members-only is the right + default); an explicit opt-in respects the default while serving flag-heavy domains. Requires a + documented stance on what "valid" means for flags (union of declared members). +* **`WithOffset`/offset control on `AnyDateTimeOffset`** — the offset dimension is currently + degenerate (always zero, documented); tests exercising offset math cannot vary it. A bounded + offset draw (±14 h in minutes, per the type's own rules) keeps validity. +* **Temporal granularity** (`WholeSeconds()`/`WholeDays()` or `WithGranularity(TimeSpan)`) — tick- + precision instants are almost never round, which surprises tests that serialize timestamps; + constructive via the ordinal engine (draw in the granule space, multiply). Also closes the + documentation gap ("values are tick-precision") in the meantime. +* **`GenerateMany(int)` terminal** — sugar for "N values without `ListOf` ceremony"; a *named + method* returning `IReadOnlyList`, so it stays inside ADR-0020's letter and spirit. +* **A test-framework seed adapter** (`[ReproducibleFact]`) — anticipated by ADR-0006's follow-ups, + dropped in the rebase, replaced by nothing. Zero-dependency Dummies cannot reference xUnit, so + this is a *companion package* decision (`Dummies.Xunit`) — worth an explicit yes/no ADR rather + than silence, because every consumer currently re-derives the `Reproducibly`-wrapping habit + by hand. + +### Optional Ideas + +`Version` (composable today: `Combine(Any.Int32().Between(0,99), 
, (ma,mi,pa) => new Version(ma,mi,pa))`; +low frequency); `IPAddress`/`IPEndPoint` (in-box, niche; a doc recipe first); `Encoding` and +`CultureInfo` (feasible **only** from a fixed embedded pool — the installed-culture set is a +cross-machine reproducibility hazard the library must not inherit; both are subsumed by proposal 1 ++ a documented pool); `MailAddress`, file-system paths, `Stream`, `byte[]` blobs (all one-line +recipes over existing surface — `ArrayOf(Any.Byte())` already is the blob builder; document them +in the user guide's recipe section instead of shipping builders); `KeyValuePair` sugar; +`Queue`/`Stack`/`LinkedList` and `Sorted*` collections (one-line `As` conversions; a first-class +`Sorted()` needs a comparability gate analogous to the cardinality hint — design exists if demand +appears); `BigInteger` (in-box on both TFMs but breaks the "full range unless constrained" +symmetry — there is no full range; needs its own bounded-default stance); `Rune` (net8 leg; +conflicts with the deliberate ASCII-centric text model unless `WithChars` lands first); +`ContainingAll(params T[])` sugar. + +### Out of Scope (recommended to stay absent, with reasons) + +* **`Where(predicate)` filtering** — generate-and-filter is the exact opposite of the library's + constructive model; unsatisfiable predicates reintroduce the unbounded-retry class the whole + design exists to exclude. The existing answer (express the invariant as constraints, or build via + `As` from a constrained draw) is the philosophy. +* **Generator registration / AutoFixture-style object graphs** — reflection-driven auto-filling is + the adjacent product the README explicitly disclaims; plain C# helpers are the reuse mechanism. +* **Immutable collections** — `System.Collections.Immutable` is an external package on the + netstandard2.0 leg, so a builder would break the zero-dependency identity there; consumer-side + `.As(ImmutableList.CreateRange)` is one line. (A net8-leg-only surface would fracture the API + across TFMs for marginal gain — not worth it.) +* **`Index`/`Range`** — validity is contextual (depends on the sequence length), so "arbitrary yet + valid" cannot hold standalone. +* **`RegionInfo`**, **`Complex`** — environment-dependent resp. scientific-niche; both fail the + frequency test. +* **Realistic fake data** (names, emails, addresses) — explicitly disclaimed; Bogus exists. + +## 11. Recommended Improvements + +In priority order; items 1–7 are the recommended pre-release gate. + +1. **Fix the three reproduced defects** — decimal fraction construction + (`DecimalIntervalSpec.cs:145`), type-aware nudge (`ContinuousIntervalSpec.cs:189` → + `_nextUp`), char-overflow guard (`RegexParser.cs:398` + `RegexAlphabet.Range`); and the + balancing-group/name validation in `SkipGroupName` (§4.1 d). Each with a regression test. +2. **Add reachability tests and the cross-engine scenario suite** (§9.3) — the structural answer to + the defect class, not just the instances. +3. **Add the parity guards** (§9.2): `Any`↔`AnyContext` mirror test, algebra-matrix test. +4. **Close the determinism contract** (§7.3): document single-logical-flow seeding on + `Reproducibly`; source-kind-aware replay hints (and multi-source `Combine` reporting); the + cross-version stability policy sentence; the foreign-generator qualification in the exhaustion + message (dead null-guard removed). Draft the **determinism ADR** and the **ordinal-engine ADR** + (§5, structural gaps) as `Proposed` for `@reefact`. +5. **Run Dummies on its floors**: import `build/Net472TestFloor.props` into `Dummies.UnitTests` + (net8-only tests conditioned out), add it to the ci.yml floor loop; add the cross-TFM golden- + sequence assertion to `dummies-check`; state the .NET Framework floor in the package README + (ADR-0022 follow-up). +6. **Documentation pass**: surface Dummies in the repository README (packages table + TOC); write + the Dummies user guide with the per-builder constraint reference and the `StringMatching` + dialect (closing ADR-0025's follow-up); correct the three "printable ASCII" sites (§4.2); + advertise the empty-by-default behavior in the package README; fix the stale + comments/DisplayNames (§4.3) and the `Directory.Build.props` header. +7. **Release-engineering guards**: public-API baseline (`PublicApiAnalyzers`) and + `EnablePackageValidation`; decide `Bool()` vs `Boolean()` and record it; ask `@reefact` to + resolve ADR-0025's status (after its wording fix); record the two ADR-0026 clarifications in the + implementation reference; enrich or soften the ADR-0013/0015 implementation-reference pointers. +8. **Ship the two Must-Have features** (§10): `Any.OneOf`/`Any.ElementOf`, and string + exclusions (`DifferentFrom`/`Except` on `AnyString`). +9. **`AnyDictionary`**: extract the shared count facade; add `ContainingKey`. +10. **Then, demand-driven**: the Nice-to-Have list (§10), each on evidence of need, with the + parity-guard data updated as part of each addition's definition of done. + +## 12. Suggested Roadmap + +**Phase 0 — before the first `dum-v*` release (correctness and contract).** Items 1–7 above. The +rationale is ADR-0020's own: every one of these is cheap now and expensive after adoption — the +decimal fix changes every seeded sequence (a non-event today, a compatibility event after v1); the +determinism policy, the `Bool` naming, the API baseline, and the ADR statuses are all +one-line-or-one-file decisions that become migrations later. Exit criterion: the §4 weaknesses +table is empty except items explicitly deferred by recorded decision. + +**Phase 1 — first stable cycle (completeness within the philosophy).** Item 8 (the two Must-Haves, +which are additive and low-risk), item 9, the user-guide recipe section (blobs, paths, Version, +Uri-via-Combine — turning Optional-list types into documentation instead of surface), and the +`Dummies.Xunit` companion-package decision (yes or no, as an ADR). + +**Phase 2 — demand-driven growth.** Nice-to-Haves as real requests arrive (`Uri` and `WithChars` +first, on current evidence), each addition carrying its parity-matrix entry, tests, and EN/FR docs +as one unit. Revisit the Optional list yearly; resist the Out-of-Scope list permanently — it is +what keeps this library what it is. + +## 13. Conclusion + +Dummies is what a focused library looks like when the authors know exactly what it is for and — +just as importantly — what it is not for. The ordinal-space engine, the constraint-provenance +diagnostics, the bounded-escape discipline, and the ADR trail are all better than the norm for this +category, and the composition-first design keeps the future feature surface honest: most "missing +types" are correctly one `As` away, not one builder away. + +The audit's findings concentrate in one place: the space between *declared* behavior and *reachable* +behavior. Two of the three reproduced defects live exactly there, invisible to a membership-only +test suite; the mirrored surfaces drift exactly where no guard looks; the determinism promise is +sound precisely up to the edges no document describes. All of it is fixable this side of the first +release, most of it in days, and the highest-value items are not the fixes but the guards — the +reachability suite, the parity tests, the API baseline — that make the next defect of each class +impossible to ship silently. + +With Phase 0 done, this is a library that can credibly promise what its README says: arbitrary yet +valid, conflicts named at the line that caused them, and any run replayable from one reported seed +— on every target it ships for. + +## 14. Issue tracking + +The §11 recommendations were opened as GitHub issues on 2026-07-20, mirroring the repository's Dummies +issue template. This table is a **static snapshot**: the live state of each issue (open, closed, in +progress) lives in the issue tracker, not here — do not maintain status in this document. + +| §11 item | Issue(s) | Phase (§12) | +|---|---|---| +| 1 — Fix the reproduced defects | [#206](https://github.com/Reefact/first-class-errors/issues/206) AnyDecimal upper half · [#207](https://github.com/Reefact/first-class-errors/issues/207) Single/Half nudge · [#208](https://github.com/Reefact/first-class-errors/issues/208) U+FFFF hang · [#209](https://github.com/Reefact/first-class-errors/issues/209) balancing groups · [#210](https://github.com/Reefact/first-class-errors/issues/210) minor regex edges | 0 | +| 2 — Reachability + cross-engine suite | [#213](https://github.com/Reefact/first-class-errors/issues/213) | 0 | +| 3 — Parity guards | [#214](https://github.com/Reefact/first-class-errors/issues/214) | 0 | +| 4 — Close the determinism contract | [#216](https://github.com/Reefact/first-class-errors/issues/216) contract docs + ADR · [#217](https://github.com/Reefact/first-class-errors/issues/217) ordinal-engine ADR · [#211](https://github.com/Reefact/first-class-errors/issues/211) seed report · [#212](https://github.com/Reefact/first-class-errors/issues/212) exhaustion message | 0 | +| 5 — Run on the floors | [#215](https://github.com/Reefact/first-class-errors/issues/215) | 0 | +| 6 — Documentation pass | [#218](https://github.com/Reefact/first-class-errors/issues/218) README + user guide · [#219](https://github.com/Reefact/first-class-errors/issues/219) printable-ASCII & stale docs | 0 | +| 7 — Release-engineering guards | [#221](https://github.com/Reefact/first-class-errors/issues/221) API baseline · [#222](https://github.com/Reefact/first-class-errors/issues/222) Bool naming · [#220](https://github.com/Reefact/first-class-errors/issues/220) ADR hygiene | 0 | +| 8 — Ship the Must-Have features | [#223](https://github.com/Reefact/first-class-errors/issues/223) Any.OneOf/ElementOf · [#224](https://github.com/Reefact/first-class-errors/issues/224) AnyString exclusions | 1 | +| 9 — AnyDictionary | [#225](https://github.com/Reefact/first-class-errors/issues/225) | 1 | +| 10 — Demand-driven Nice-to-Haves | [#226](https://github.com/Reefact/first-class-errors/issues/226) backlog | 2 | + +--- + +*Produced by an agent-run audit (multi-agent review with adversarial verification; all reported +defects independently reproduced against the built library; full test suite executed). Advisory +per ADR-0004: recommendations and drafts only — every decision remains with the maintainer.*