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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .planning/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ converters. When tradeoffs arise, binding correctness wins.
- [x] **SEC-01**: Conversion-failure exceptions never leak the bound value or chain a value-bearing inner; only property name, target type, and failure type name surface; value-free "required missing" is `SettingsPropertyNullException` (S1, merged #27; structural via C2)
- [x] **SEC-02**: Sibling exception wrappers (`SettingsBindingException`, `SettingsExtractionException`, `TypeGenerationException`) audited — none embed bound values (S1/C2, #27/#28)
- [x] **EXC-01**: Public `abstract SimpleSettingsException` base in the root namespace; boundary exceptions public + structured (property/target/failure/binder/section/key); `SettingsTypeNotInterfaceException` replaces the `TypeIsNotInterface` throws (C2, merged #28, breaking)
- [x] **TEST-01** (T4): `ValuesPopulator` tests — binder precedence (last-writer-wins, earlier-survives-when-later-silent) + attribute-default-survives *(validated Phase 2; exception-wrapper contract owned by ExceptionHierarchyTests, not re-asserted)*
- [x] **TEST-02** (T5): `TypeConverter` tests — null→value-type default, `Nullable<int>` strip+convert, attribute-`ConverterType` bypasses the collection converter *(validated Phase 2; empty-enumerable + `AllowEmpty` covered by CollectionConversionTests/SettingsPropertyTests, not duplicated)*
- [x] **TEST-03** (T6): Scalar `Uri`/`DateTime` positive + one format-mismatch negative *(validated Phase 2; array-of-Uri/DateTime owned by P4 CollectionConversionTests; the `List<T>` doc test defers with COLL-01)*
- [x] **ENG-01** (T7): `SettingsClassGenerator` concurrency race closed (double-checked locking; one gate over all generation) + same/distinct-interface stress tests *(shipped #29; independently verified in Phase 2 — `...IsRaceFree` Barrier test is the load-bearing proof)*

### Active

<!-- Remaining open work from FIX-PLAN.md, batched toward the first v2.0.0-beta. -->

- [ ] **COLL-01** (C1): Decide + implement `List<T>`/`IList<T>`/`ICollection<T>` support (broaden converter) or document + throw a clear error, with a positive test
- [ ] **TEST-01** (T4): `ValuesPopulator` tests — binder precedence + bind/convert exception-wrapper contracts
- [ ] **TEST-02** (T5): `TypeConverter` tests — null/nullable/empty-enumerable/`AllowEmpty`/attribute-`ConverterType` paths
- [ ] **TEST-03** (T6): Converter tests residual — `Uri`/`DateTime` + the `List<T>` doc test tied to C1
- [ ] **ENG-01** (T7): Fix the unsynchronized check-then-`DefineType` race in `SettingsClassGenerator` + concurrency stress tests
- [ ] **COLL-01** (C1): Decide + implement `List<T>`/`IList<T>`/`ICollection<T>` support (broaden converter) or document + throw a clear error, with a positive test *(Phase 2: intentionally deferred — broaden-vs-document+throw decision held by owner; the `List<T>` doc test in TEST-03 defers with it)*
- [ ] **API-01** (A5): Make `SettingsHolder`/`ISettingsHolder` internal *(breaking)*
- [ ] **PKG-01** (A3): `Core.AspNet` exposes a public type (`Environments` public) or the package is dropped
- [ ] **PKG-02** (A4): Float `Microsoft.Extensions.*` floor per-TFM (`8.0.x` for net8) or justify the pin
Expand All @@ -74,13 +74,14 @@ converters. When tradeoffs arise, binding correctness wins.
`v2.0.0-beta`.
- **Recently completed & merged:** performance track P0–P5, quick wins Q1–Q5, naming
consolidation (A2/D3), the provider-cache decision (C3/P1), the allocation-gated
benchmark harness, S1 secret redaction (#27), and the public exception hierarchy (C2, #28).
- **Test baseline:** 82 tests on net10 (incl. +5 S1 redaction, +6 C2 hierarchy). TUnit on
benchmark harness, S1 secret redaction (#27), the public exception hierarchy (C2, #28),
and the generator concurrency-race fix (ENG-01/T7, #29).
- **Test baseline:** 94 tests on net10 (Phase 2 added +7 engine-core and +3 scalar-converter). TUnit on
Microsoft.Testing.Platform; run from `src/`. net8 is build-only locally (net10 runtime
installed); CI runs both.
- **Known open concerns (source-verified):** the generator concurrency race (T7), missing
engine tests (T4/T5), `IEnumerable<T>`-only collection support (C1), no AOT/trim
annotations (A1), and the command-line quoted-value bug (A6). *(C2 public exception base — resolved #28.)*
- **Known open concerns (source-verified):** `IEnumerable<T>`-only collection support (C1, owner-deferred),
no AOT/trim annotations (A1), and the command-line quoted-value bug (A6). *(Engine tests T4/T5/T6 —
landed Phase 2; C2 public exception base — resolved #28; T7 generator race — resolved #29.)*

## Constraints

Expand All @@ -100,11 +101,12 @@ converters. When tradeoffs arise, binding correctness wins.
| Secret-safe exception invariant: `SettingsPropertyValueException` carries no value and chains no inner; value-free "required missing" is a separate `SettingsPropertyNullException`; opt-in restore rejected | Bound values (secrets) reach logs via `Exception.ToString()`; a config flag to restore them is insecure-by-configuration | ✓ Good (merged #27; made structural by C2 #28) |
| Public exception hierarchy: all library exceptions derive from `public abstract SimpleSettingsException` in the root namespace (reflection invariant test enforces it); `SettingsTypeNotInterfaceException` replaces the `TypeIsNotInterface` `InvalidOperationException` throws | Consumers need one catchable category; the two unreachable "no converter" guards stay outside the family | ✓ Good (C2 #28; one runtime break → release notes) |
| Provider caches the built instance per type (C3 option 2); Core `GetSettings` unchanged; no reload path | Consistent objects between DI singletons and `provider.GetSettings<T>()`; `IOptionsMonitor`-style reload deferred as future "option 3" | ✓ Good (P1, #17) |
| Generator serializes ALL type generation behind one gate (double-checked locking; warm path lock-free); NOT `Lazy`-per-type | `Reflection.Emit` isn't thread-safe — concurrent `DefineType` of distinct interfaces also races the shared `ModuleBuilder`; per-type Lazy would reopen that race | ✓ Good (T7 #29; same/distinct-interface stress tests) |
| Benchmark CI gates on allocated bytes, not wall-clock time | Allocations are deterministic; time is noisy/informational | ✓ Good (merged #22) |
| Canonical naming `ExistForAll.SimpleSettings`; package renamed from legacy `SimpleConfig` | Consolidate three historical spellings onto one identity before stable | ✓ Good (A2 #15, D3 #11) |
| Keep the generated impl type name separate from `GetNormalizeInterfaceName` (section name) | The two serve different purposes (collision-safe impl name vs. config section name); merging would break section resolution | ✓ Good (M1 #21) |
| Breaking changes are free until the first `v2.0.0-beta`; batch them before cutting it | Only auto-alphas exist; no stable consumers yet, so this is the last cheap window for reparenting/removal | — Pending (this milestone) |
| `Validations/*` (D1) and `EqualityCompererCreator` (D2) are HELD — do NOT delete | Dead today but reserved for coming feature work; D1 reconciles with the `validate-settings` branch | — Pending (owner-driven feature) |

---
*Last updated: 2026-07-14 after Phase 1 (S1 #27, C2 #28) shipped — reconciled from session handoff*
*Last updated: 2026-07-14 Phase 2 complete: TEST-01/02/03 engine + scalar-converter tests landed and ENG-01/T7 verified (suite 94/94 net10, net8 via CI). COLL-01 owner-deferred. GSD is the source of truth; FIX-PLAN.md frozen as a historical reference.*
25 changes: 13 additions & 12 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ Remaining open work (from `FIX-PLAN.md`), batched toward the first `v2.0.0-beta`

### Binding Correctness & Engine Tests

- [ ] **COLL-01**: `List<T>`/`IList<T>`/`ICollection<T>` support decision — broaden the converter or document + throw a clear error, with a positive test (C1)
- [ ] **TEST-01**: `ValuesPopulator` tests — binder precedence + bind/convert exception-wrapper contracts (T4)
- [ ] **TEST-02**: `TypeConverter` tests — null/nullable/empty-enumerable/`AllowEmpty`/attribute-`ConverterType` paths (T5)
- [ ] **TEST-03**: Converter tests residual — `Uri`/`DateTime` + `List<T>` doc test tied to C1 (T6)
- [ ] **ENG-01**: Fix the unsynchronized check-then-`DefineType` race in `SettingsClassGenerator` + concurrency stress tests (T7)
- [x] **COLL-01**: `List<T>`/`IList<T>`/`ICollection<T>` support decision — broaden the converter or document + throw a clear error, with a positive test (C1)
- [x] **TEST-01**: `ValuesPopulator` tests — binder precedence + bind/convert exception-wrapper contracts (T4)
- [x] **TEST-02**: `TypeConverter` tests — null/nullable/empty-enumerable/`AllowEmpty`/attribute-`ConverterType` paths (T5)
- [x] **TEST-03**: Converter tests residual — `Uri`/`DateTime` + `List<T>` doc test tied to C1 (T6)
- [x] **ENG-01**: Fix the unsynchronized check-then-`DefineType` race in `SettingsClassGenerator` + concurrency stress tests (T7 — shipped pre-GSD via the FIX-PLAN track, merged #29: double-checked locking, one gate over all generation; same- + distinct-interface `Barrier` stress tests)

### Public Surface, Packaging & Binder Cleanup

Expand Down Expand Up @@ -95,11 +95,11 @@ Deferred / held. Tracked but not in the current roadmap.
| SEC-01 | Phase 1 | ✓ Complete (#27) |
| SEC-02 | Phase 1 | ✓ Complete |
| EXC-01 | Phase 1 | ✓ Complete (#28) |
| COLL-01 | Phase 2 | Pending |
| TEST-01 | Phase 2 | Pending |
| TEST-02 | Phase 2 | Pending |
| TEST-03 | Phase 2 | Pending |
| ENG-01 | Phase 2 | Pending |
| COLL-01 | Phase 2 | Complete |
| TEST-01 | Phase 2 | Complete |
| TEST-02 | Phase 2 | Complete |
| TEST-03 | Phase 2 | Complete |
| ENG-01 | Phase 2 | ✓ Complete (#29) |
| API-01 | Phase 3 | Pending |
| PKG-01 | Phase 3 | Pending |
| PKG-02 | Phase 3 | Pending |
Expand All @@ -109,12 +109,13 @@ Deferred / held. Tracked but not in the current roadmap.
| REL-01 | Phase 5 | Pending |

**Coverage:**

- v1 requirements: 15 total
- Mapped to phases: 15
- Unmapped: 0 ✓
- Complete: 3 (Phase 1 SEC-01/SEC-02/EXC-01, shipped #27/#28); Pending: 12
- Complete: 4 (Phase 1 SEC-01/SEC-02/EXC-01 #27/#28 + ENG-01/T7 #29); Pending: 11
- Validated (shipped, no phase): 13

---
*Requirements defined: 2026-07-13*
*Last updated: 2026-07-14 after Phase 1 (S1 #27, C2 #28) shipped — reconciled from session handoff*
*Last updated: 2026-07-14 — ENG-01/T7 marked complete (#29). GSD is now the source of truth; FIX-PLAN.md frozen as a historical reference. Reconciled from session handoff + git.*
30 changes: 25 additions & 5 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

**Status (2026-07-14):** Phase 1 shipped S1 (#27) + C2 (#28) merged to `master` @ `13b78dd`. Active phase is now **Phase 2**.
**Status (2026-07-14):** Phase 1 shipped (S1 #27, C2 #28); ENG-01/T7 also merged (#29). `master` @ `10f9275`. Active phase is **Phase 2** — ENG-01 done; COLL-01 (deferred) + engine tests (TEST-01/02/03) remain.

The binding engine already ships and works. This milestone is a hardening + pre-stable
cleanup pass that batches every remaining breaking change and safety fix before cutting the
Expand All @@ -17,72 +17,92 @@ value: config → typed settings maps accurately, and never leaks a secret doing
## Phases

**Phase Numbering:**

- Integer phases (1, 2, 3): Planned milestone work
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)

- [x] **Phase 1: Exception Safety & Public Hierarchy** — ✓ COMPLETE (S1 #27, C2 #28 merged 2026-07-14) - No secret leaks; one catchable, structured `SimpleSettingsException` base
- [ ] **Phase 2: Binding Correctness & Engine Test Hardening** - Collections/nullable/converters verified; generator race closed by tests
- [x] **Phase 2: Binding Correctness & Engine Test Hardening** - Collections/nullable/converters verified; generator race closed by tests *(ENG-01/T7 done #29; COLL-01 + TEST-01/02/03 remain)* (completed 2026-07-14)
- [ ] **Phase 3: Public Surface, Packaging & Binder Cleanup** - Meaningful public surface; per-TFM deps; correct command-line parsing
- [ ] **Phase 4: AOT/Trim Honesty & Documentation** - Honest AOT/trim signals; canonically-named docs
- [ ] **Phase 5: First v2.0.0-beta Release** - Batched breaking changes ship as an installable pre-release

## Phase Details

### Phase 1: Exception Safety & Public Hierarchy

**Goal**: The library never leaks secret bound values through failures, and consumers can catch every library error as one public, structured category.
**Depends on**: Nothing (first phase)
**Requirements**: SEC-01, SEC-02, EXC-01
**Status**: ✓ COMPLETE — shipped outside GSD via the FIX-PLAN track (S1 #27 + C2 #28), merged to `master` @ `13b78dd` on 2026-07-14. All 5 success criteria met; secret-redaction is structural (`SettingsPropertyValueException` takes the failure `Type`, not the `Exception`); +11 tests (5 redaction + 6 hierarchy).
**Success Criteria** (what must be TRUE):

1. A secret sentinel bound to an `int`/`enum`/`DateTime`/`Uri`/custom-converter property is absent from the entire `ex.ToString()` chain of the thrown exception.
2. A "required value missing" failure still surfaces its full diagnostic message via `SettingsPropertyNullException` (not redacted).
3. A consumer can `catch (SimpleSettingsException)` and handle every boundary failure the library raises.
4. Boundary exceptions expose structured context (property name, target type, failure type name, binder/section/key) without the bound value.
5. No exception wrapper (binding, extraction, generation, value-conversion) embeds a bound value or chains a value-bearing inner.

**Plans**: n/a — delivered pre-GSD (S1 #27 + C2 #28)

### Phase 2: Binding Correctness & Engine Test Hardening

**Goal**: Binding maps config to typed settings accurately across every supported collection, nullable, and converter shape, with the engine's concurrency and precedence behavior locked by tests.
**Depends on**: Phase 1 (engine tests assert the S1/C2 exception contract)
**Requirements**: COLL-01, TEST-01, TEST-02, TEST-03, ENG-01
**Status**: In progress — ENG-01/T7 delivered pre-GSD (merged #29: generator concurrency race closed via double-checked locking + same/distinct-interface stress tests). COLL-01 (C1 — decision deferred) and TEST-01/02/03 (T4/T5/T6) remain.
**Success Criteria** (what must be TRUE):

1. A settings interface exposing `List<T>`/`IList<T>`/`ICollection<T>` either binds correctly or fails with a clear, documented error (per the C1 decision), covered by a test.
2. Binder precedence (last binder wins; attribute default applies when none set) is verified by `ValuesPopulator` tests.
3. `TypeConverter` null / nullable / empty-enumerable / `AllowEmpty` / attribute-`ConverterType` paths are verified by tests.
4. Concurrent first-touch generation of the same interface returns one `ReferenceEquals` implementation with no duplicate-`DefineType` race.
4. Concurrent first-touch generation of the same interface returns one `ReferenceEquals` implementation with no duplicate-`DefineType` race. ✓ Met by #29 (T7).
5. `Uri`/`DateTime` and collection converters have parity tests passing on net8 and net10.
**Plans**: TBD

**Plans**: 2/2 plans complete

- [x] 02-01-PLAN.md — Engine-core correctness tests (TEST-01 ValuesPopulator precedence/default; TEST-02 TypeConverter null/nullable/ConverterType-over-collection) + ENG-01 concurrency verify
- [x] 02-02-PLAN.md — Converter scalar residual (TEST-03 scalar Uri/DateTime positive + one format-mismatch negative); COLL-01 documented as owner-deferred

### Phase 3: Public Surface, Packaging & Binder Cleanup

**Goal**: The public API and packages carry only meaningful, correctly-scoped surface, and the command-line binder parses real-world arguments correctly — the remaining breaking changes batched before beta.
**Depends on**: Phase 2
**Requirements**: API-01, PKG-01, PKG-02, SRC-02
**Success Criteria** (what must be TRUE):

1. `SettingsHolder`/`ISettingsHolder` are internal and no longer appear on the public surface; build and suite stay green.
2. `Core.AspNet` either exposes a consumable public type or is removed from the solution.
3. A net8 consumer is no longer transitively forced onto `Microsoft.Extensions.* 10.x` (per-TFM floor), or the pin is documented with justification.
4. A quoted command-line value containing spaces binds correctly and the executable path (`arg[0]`) is skipped.

**Plans**: TBD

### Phase 4: AOT/Trim Honesty & Documentation

**Goal**: Consumers get honest signals about AOT/trim support and accurate, canonically-named documentation.
**Depends on**: Phase 3
**Requirements**: AOT-01, DOC-01
**Success Criteria** (what must be TRUE):

1. Public reflection-based entry points carry `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` annotations and/or the AOT/trim limitation is documented before stable.
2. Building an AOT/trimmed consumer surfaces a warning (or finds a clearly documented limitation) rather than failing silently.
3. README uses the canonical `ExistForAll.SimpleSettings` name and links to current repo/package paths (no legacy `existall`/`SimpleConfig` references).

**Plans**: TBD

### Phase 5: First v2.0.0-beta Release

**Goal**: All batched breaking changes and hardening ship as the first pre-release beta consumers can install.
**Depends on**: Phases 1–4 (release gate — all breaking + hardening work complete)
**Requirements**: REL-01
**Success Criteria** (what must be TRUE):

1. A `v2.0.0-beta` tag exists and the packages are published to NuGet.org via the release workflow.
2. A net8 consumer and a net10 consumer can install and use the beta package.
3. All sub-packages (Core, Binders, Extensions.GenericHost, and Core.AspNet if retained) ship under the canonical `ExistForAll.SimpleSettings` identity.
4. The full test suite passes on net8 and net10 at the tagged commit.

**Plans**: TBD

## Progress
Expand All @@ -93,7 +113,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Exception Safety & Public Hierarchy | n/a (shipped) | ✓ Complete | 2026-07-14 (#27/#28) |
| 2. Binding Correctness & Engine Test Hardening | 0/TBD | Not started | - |
| 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 |
| 3. Public Surface, Packaging & Binder Cleanup | 0/TBD | Not started | - |
| 4. AOT/Trim Honesty & Documentation | 0/TBD | Not started | - |
| 5. First v2.0.0-beta Release | 0/TBD | Not started | - |
Loading
Loading