From 79b697656bab10caed2e5225784c13fddb83b984 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Sun, 19 Jul 2026 15:31:24 +0300 Subject: [PATCH 01/23] =?UTF-8?q?docs(planning):=20Phase=204=20closeout=20?= =?UTF-8?q?=E2=80=94=20verify=20+=20security=20gate=20+=20mark=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VERIFICATION.md: 6/6 ROADMAP success criteria MET (non-vacuous tests, net8+net10 153/153). - SECURITY.md: 15/15 threats closed, 0 open; D-06 secret-redaction gate signed off; T-04-VAL DI path holds by construction in merged code. - ROADMAP.md + STATE.md: Phase 4 marked complete; advance to Phase 5 (AOT-01/DOC-01). --- .planning/ROADMAP.md | 11 +- .planning/STATE.md | 30 ++--- .../SECURITY.md | 107 ++++++++++++++++++ .../VERIFICATION.md | 90 +++++++++++++++ 4 files changed, 218 insertions(+), 20 deletions(-) create mode 100644 .planning/phases/04-collection-validation-binding/SECURITY.md create mode 100644 .planning/phases/04-collection-validation-binding/VERIFICATION.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 524650c..b75d948 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -2,7 +2,7 @@ ## Overview -**Status (2026-07-14):** Phases 1–3 complete (Phase 1 #27/#28, Phase 2 #30, Phase 3 #31; ENG-01/T7 #29). `master` @ `7f9e17c`. Active phase is **Phase 4 — Collection & Validation Binding** (new engine phase for the client pre-beta requirements); AOT/Trim & Docs renumbered to Phase 5, first beta to Phase 6. +**Status (2026-07-19):** Phases 1–4 complete (Phase 1 #27/#28, Phase 2 #30, Phase 3 #31, Phase 4 #33/#34/#35; ENG-01/T7 #29). `master` @ `0c858fa`. Active phase is **Phase 5 — AOT/Trim Honesty & Documentation**; first beta is Phase 6. 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 @@ -25,7 +25,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing - [x] **Phase 1: Exception Safety & Public Hierarchy** — ✓ COMPLETE (S1 #27, C2 #28 merged 2026-07-14) - No secret leaks; one catchable, structured `SimpleSettingsException` base - [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) - [x] **Phase 3: Public Surface, Packaging & Binder Cleanup** - Meaningful public surface; per-TFM deps; correct command-line parsing (completed 2026-07-14) -- [ ] **Phase 4: Collection & Validation Binding** - Empty/sequence collection binding, working settings validation, and DI collection surface (client pre-beta engine requirements) +- [x] **Phase 4: Collection & Validation Binding** — ✓ COMPLETE (Waves 1–3 merged #33/#34/#35, 2026-07-19) - Empty/sequence collection binding, working settings validation, and DI collection surface (client pre-beta engine requirements) - [ ] **Phase 5: AOT/Trim Honesty & Documentation** - Honest AOT/trim signals; canonically-named docs - [ ] **Phase 6: First v2.0.0-beta Release** - Batched breaking changes ship as an installable pre-release @@ -92,6 +92,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Goal**: Collections bind correctly across empty, comma-scalar, and YAML-sequence shapes; declared settings validation actually runs; and the DI extension exposes the settings collection — the client-requested engine features batched before beta. **Depends on**: Phase 3 **Requirements**: COLL-02, COLL-03, VAL-01, VAL-02, API-02 +**Status**: ✓ COMPLETE — Waves 1–3 merged (#33 Waves 1–2, #34 planning, #35 Wave 3) to `master` @ `0c858fa` on 2026-07-19. All 6 success criteria MET (VERIFICATION.md — 6/6, non-vacuous tests); security gate cleared (SECURITY.md — 15/15 threats closed, D-06 secret-redaction gate signed off); suite 153/153 on net8 + net10. **Success Criteria** (what must be TRUE): 1. An unset `T[]` / `List` / `IEnumerable` binds to an empty collection, never `null` (COLL-02). @@ -101,7 +102,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing 5. `AddSimpleSettings(...)` exposes the `ISettingsCollection` (return value or resolvable service) (API-02). 6. After COLL-03 edits `ConfigurationBinder.BindPropertySettings`, the S1/SEC-01 secret-redaction invariant is re-verified; suite green on net8 + net10. -**Plans**: 4/5 plans executed +**Plans**: 5/5 plans complete **Wave 1** @@ -115,7 +116,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Wave 3** *(blocked on Wave 2 completion)* -- [ ] 04-04-PLAN.md — API-02 ISettingsCollection exposure (DI singleton + out-overload, D-15) + VAL-01 deferred DI-resolved validator runner (D-11/Q3) [Wave 3, depends on 04-03] +- [x] 04-04-PLAN.md — API-02 ISettingsCollection exposure (DI singleton + out-overload, D-15) + VAL-01 deferred DI-resolved validator runner (D-11/Q3, DIM-bridge dispatch) [Wave 3, depends on 04-03] ### Phase 5: AOT/Trim Honesty & Documentation @@ -154,6 +155,6 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 1. Exception Safety & Public Hierarchy | n/a (shipped) | ✓ Complete | 2026-07-14 (#27/#28) | | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | -| 4. Collection & Validation Binding | 4/5 | In Progress| | +| 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | | 5. AOT/Trim Honesty & Documentation | 0/TBD | Not started | - | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 2e0c50a..f0831dc 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,19 +2,19 @@ gsd_state_version: 1.0 milestone: v2.0.0 milestone_name: milestone -current_phase: 04 -current_phase_name: collection-validation-binding -status: executing -stopped_at: Completed 04-04-PLAN.md (VAL-01 DI path + API-02, Wave 3) — all 5 Phase 4 plans landed; phase verify + secure + mark-complete pending -last_updated: "2026-07-15T18:00:00.000Z" -last_activity: 2026-07-15 -last_activity_desc: Phase 04 Plan 04 (Wave 3) executed + reviewed +current_phase: 05 +current_phase_name: aot-trim-docs +status: ready +stopped_at: Phase 4 COMPLETE — Waves 1–3 merged (#33/#34/#35 @ 0c858fa); verify 6/6 + security gate 15/15 (D-06 signed off). Phase 5 (AOT-01/DOC-01) not started. +last_updated: "2026-07-19T12:00:00.000Z" +last_activity: 2026-07-19 +last_activity_desc: Phase 04 closeout — verify + security gate passed; phase marked complete progress: total_phases: 6 - completed_phases: 2 + completed_phases: 3 total_plans: 9 completed_plans: 9 - percent: 33 + percent: 50 --- # Project State @@ -24,16 +24,16 @@ progress: See: .planning/PROJECT.md (updated 2026-07-13) **Core value:** Correctness of binding — config → strongly-typed settings maps accurately across every supported shape (sections, arrays/enumerables, defaults, nullable, custom converters). -**Current focus:** Phase 04 — collection-validation-binding +**Current focus:** Phase 05 — AOT/Trim Honesty & Documentation (not started); Phase 04 complete ## Current Position -Phase: 04 (collection-validation-binding) — EXECUTING (all 5 plans landed; verify + secure + mark-complete pending) -Plan: 5 of 5 — complete -Status: Wave 3 (04-04) executed + reviewed; ready for phase verify -Last activity: 2026-07-15 — Phase 04 Plan 04 (Wave 3) executed + reviewed +Phase: 04 (collection-validation-binding) — ✓ COMPLETE (Waves 1–3 merged #33/#34/#35; verify 6/6, security 15/15, D-06 signed off) +Next: Phase 05 (AOT/Trim Honesty & Documentation) — not started +Status: Phase 4 closed; ready to plan Phase 5 +Last activity: 2026-07-19 — Phase 04 closeout (verify + secure) passed; phase marked complete -Progress: [███░░░░░░░] 33% +Progress: [█████░░░░░] 50% ## Performance Metrics diff --git a/.planning/phases/04-collection-validation-binding/SECURITY.md b/.planning/phases/04-collection-validation-binding/SECURITY.md new file mode 100644 index 0000000..9d5d3f9 --- /dev/null +++ b/.planning/phases/04-collection-validation-binding/SECURITY.md @@ -0,0 +1,107 @@ +--- +phase: 04-collection-validation-binding +type: security +asvs_level: 1 +block_on: high +threats_total: 15 +threats_closed: 15 +threats_open: 0 +status: secured +verified: 2026-07-19 +diff_range: master@0c858fa (Phase 4 Waves 1-3 merged) +--- + +# Phase 4 — Collection / Validation / Binding: Security Gate + +Retroactive verification that every threat mitigation declared in the Phase 4 plan +threat models (`04-01`..`04-05-PLAN.md`) is present in the merged implementation. Each +`mitigate` threat is confirmed by locating the mitigation in code (file:line) AND a +green proving test; each `accept` threat is confirmed against the accepted-risk basis. + +Config (from `04-RESEARCH.md:481`): `security_enforcement: true`, +`security_asvs_level: 1`, `security_block_on: high`. + +Load-bearing invariant under test — **S1 / SEC-01:** no bound configuration value (which +may be a secret) may appear in any library exception's `ToString()` chain that reaches +logs. Every library exception is value-free (Type/property metadata only) and never chains +an inner exception that saw a value. + +## Verdict + +**SECURED.** 15/15 threats CLOSED. 0 blocking-open, 0 non-blocking-open. Every security +suite is green on net10; the B-1 InternalsVisibleTo gate compiles clean on net8 and net10. +The prior in-flight "by construction" sign-off on T-04-VAL (`04-04-REVIEW.md`) is confirmed +to hold in the merged tree. + +## Threat Verification + +| Threat ID | Wave/Plan | Category | Severity | Disposition | Status | Evidence (code + test) | +|-----------|-----------|----------|----------|-------------|--------|------------------------| +| T-04-01 | W1 / 04-01 | Info Disclosure | medium | mitigate | CLOSED | Element-convert failure wrapped value-free at `ValuesPopulator.cs:187-191` (`SettingsPropertyValueException(type, property, e.GetType())`, no inner chained); `ListTypeConverter`/`CollectionTypeConverter` add no value to any message. Test: `ExceptionRedactionTests.Convert_SecretInListSequenceElement_DoesNotLeakValue` (8/8 green). | +| T-04-02 (W1) | W1 / 04-01 | Tampering | low | mitigate | CLOSED | `TypeConvertersCollections.cs:11` registers `ListTypeConverter` after `EnumerableTypeConverter`, before `EnumTypeConverter`; disjoint `IsListLike` predicate (`ListTypeConverter.cs:23-26`); user-first/Default-last order preserved. Test: `CollectionConversionTests` (green). | +| T-04-S1 (D-06) | W2 / 04-02 | Info Disclosure | **high** | mitigate | CLOSED | **SECURITY GATE.** Sequence walk `ConfigurationBinder.TrySetChildSequence` (`ConfigurationBinder.cs:58-75`) only collects child strings + `SetNewValue` — no conversion inside the binder, so the element-convert failure never lands in the inner-chaining `SettingsBindingException`; it surfaces later via `ValuesPopulator.ConvertPropertyValue` (`:179-193`) as value-free `SettingsPropertyValueException`. Tests: `Convert_SecretInSequenceElement_DoesNotLeakValue`, `Convert_SecretInFirstSequenceElement_DoesNotLeakValue`, `Convert_SecretInListSequenceElement_DoesNotLeakValue` — first + later element, int[] + List (8/8 green). | +| T-04-02 (W2) | W2 / 04-02 | Tampering | medium | mitigate | CLOSED | Children-win precedence: `ConfigurationBinder.cs:35` calls `TrySetChildSequence` first and returns early when children exist; comma-scalar path preserved (`:38-49`). Test: `SettingsBuilderConfigurationBinderIntegrationTests` (14/14 green). | +| T-04-03 | W2 / 04-02 | Denial of Service | low | accept | CLOSED | Sequence length is operator-controlled config, no untrusted remote input; consistent with the pre-existing scalar-binding exposure. No code required. | +| T-04-VAL (core) | W2 / 04-03 | Info Disclosure | **high** | mitigate | CLOSED | Aggregate message composed only from author `ValidationError` text (`Resources.cs:56-68`), no bound value; `SettingsValidationException` retains only `Errors` (`SettingsValidationException.cs`). Throwing-validator vector wrapped value-free as `SettingsValidatorInvocationException(validatorType, e.GetType())` — no inner chained (`ValuesPopulator.cs:110-115`). Tests: `Exception_CarriesAuthorMessage_AndNoBoundValue`, `ThrowingValidator_IsWrappedValueFree_NotLeaked` (12/12 green). | +| T-04-04 | W2 / 04-03 | Tampering | low | mitigate | CLOSED | Validator type comes from a compile-time attribute; parameterless `Activator.CreateInstance` (`ValuesPopulator.cs:107`). No untrusted-type input. | +| T-04-05 | W2 / 04-03 | Denial of Service | low | accept | CLOSED | Validators are operator-authored startup code; failures fail-fast at populate. Acceptable startup behavior. | +| T-04-VAL (DI) | W3 / 04-04 | Info Disclosure | **high** | mitigate | CLOSED | DI runner mirrors the core contract: aggregates via the SHARED `SettingsValidationException.ThrowIfAny` (`SettingsValidationRunner.cs:52`); throwing validator wrapped value-free `SettingsValidatorInvocationException(validator.GetType(), e.GetType())`, no inner chained (`:40-44`). Dispatch is the DIM bridge `((ISettingsValidator)validator).Validate(...)` (`:38`) — no `MethodInfo.Invoke`, so no `TargetInvocationException` inner-chaining vector. Test: `ValidateSimpleSettings_WhenValidatorThrows_RedactsBoundValue` (guards non-vacuous: asserts secret IS bound first), `ValidateSimpleSettings_WhenDiValidatorFails_ThrowsAggregatedException` (15/15 green). | +| T-04-06 | W3 / 04-04 | Elevation of Privilege | medium | mitigate | CLOSED | DI validators run only via explicit `IServiceProvider.ValidateSimpleSettings()` (`ServiceProviderValidationExtensions.cs:20-30`) after `BuildServiceProvider()`; registration only adds the runner singleton (`ServicesSettingsBuilderExtensions.cs:53`), never invokes it. Test: `AddSimpleSettings_DoesNotRunDiValidators_UntilValidateIsCalled` (counter 0 → 1). | +| T-04-07 | W3 / 04-04 | Spoofing | low | accept | CLOSED | Validators resolve from the consumer's own DI container via `GetServices(typeof(ISettingValidation<>).MakeGenericType(pair.Key))` (`SettingsValidationRunner.cs:27-29`); no untrusted type input crosses the boundary. | +| T-04-SC | W1-W3 (all) | Supply Chain | n/a | accept | CLOSED | No package installs this phase. GenericHost csproj carries only `Microsoft.Extensions.DependencyInjection.Abstractions` + the Core `ProjectReference` (`...GenericHost.csproj:7-13`) — no hosting dependency added. Binders reuses Core via IVT, not a new package (`Info.cs:5`). | +| T-04-08 | W2 / 04-05 | Info Disclosure | low | mitigate | CLOSED | AllowEmpty=false rejection reuses value-free `SettingsPropertyNullException(_propertyName)` (`PropertyConversion.cs:34-35, 43-44`; message = property name only, `Resources.cs:44-45`). Test: `SettingsPropertyTests` (6/6 green). | +| T-04-09 | W2 / 04-05 | Tampering | medium | mitigate | CLOSED | `SettingsPropertyNullException` stays excluded from the redaction filter by construction (`ValuesPopulator.cs:187` `when (e is not SettingsPropertyNullException)`); every other exception is wrapped into value-free `SettingsPropertyValueException`, so no value-bearing type bypasses redaction and the empty diagnostic is never over-redacted. | + +## New Attack Surface Introduced During Implementation (unregistered flags — non-blocking) + +The SUMMARY files carry no `## Threat Flags` section. Two items of new surface were detected +by direct code inspection: + +- **`SettingsValidatorInvocationException` (new public type, `SettingsValidatorInvocationException.cs`)** — + not named by ID in any plan threat register (the registers named only `SettingsValidationException`). + It is in fact the *primary* leak vector for both validator paths: a validator that reads a secret and + then throws with it in its own message. The implementation handles it correctly and value-free — + type-only ctor, `Resources.cs:70-73` message names only the validator + failure types, and neither + call site chains the inner (`ValuesPopulator.cs:114`, `SettingsValidationRunner.cs:43`). It is covered + by the T-04-VAL mitigation intent and pinned by two dedicated redaction tests (core + DI). Informational + only — mitigated and tested; no action required. +- **Object-level validator declaration moved to the existing `[SettingsSection(ValidatorType = ...)]`** + (`ValuesPopulator.cs:167-168`) rather than the new `SettingsValidatorAttribute` that plan 04-03 Task 1 + described. Implementation deviation from the plan text; no security impact — the value-free exception + contract is independent of which attribute declares the validator. Informational. + +## Accepted / Residual Risks + +- **Author-supplied `ValidationError.ErrorMessage` reaches `SettingsValidationException.ToString()`** — by + design (author text, not a bound value; `Resources.cs:56-68`). If a validator author echoes a secret + into their own error message it will surface. This is the documented validator-author responsibility + stated in the T-04-VAL mitigation plan. Accepted residual (author boundary, not a library defect). +- **A validator returning a `null` `ValidationResult` would NRE at `result.Errors`** (outside the try in + both paths). Identical behavior core vs DI; an NRE carries no bound settings value. Accepted (parity + preserved intentionally — `04-04-REVIEW.md` non-findings). +- **`provider.GetServices(...)` activation failures propagate raw** (`SettingsValidationRunner.cs:29`, + outside the try). Not a `SimpleSettingsException` and cannot carry a bound settings value. Accepted. +- **Repeated `AddSimpleSettings` validates only the last collection** (`04-04-REVIEW.md` LOW-3). DI + last-wins edge case, pre-existing pattern, multi-call likely unsupported. Not a security regression. + +## D-06 Sign-off (mandatory security gate, T-04-S1, severity high) + +**SIGNED OFF — CLOSED.** A secret carried in a YAML / child-section sequence element is provably absent +from the entire exception `ToString()` chain on a bind/convert failure. The child-sequence walk +(`ConfigurationBinder.TrySetChildSequence`) never converts inside the binder, so the value-bearing +converter exception never reaches the inner-chaining `SettingsBindingException`; it is caught and rewrapped +value-free as `SettingsPropertyValueException(settingsType, property, e.GetType())` at +`ValuesPopulator.cs:187-191` (no message value, no chained inner). This is proven across both element +positions (first + later) and both converter shapes (int[] + List) by three green regression tests in +`ExceptionRedactionTests`, and the whole redaction suite (5 pre-existing + 3 new) is green on net10. + +## Verification Runs (merged tree, net10.0 Release) + +- `ExceptionRedactionTests` — 8/8 pass (D-06 gate: 5 pre-existing S1 + 3 new sequence). +- `SettingsValidationTests` — 12/12 pass (core-path VAL, aggregate, cross-property, throwing-validator redaction). +- `AddSimpleSettingsIntegrationTests` — 15/15 pass (DI VAL, deferred timing, fresh-scope, DI throwing-validator redaction). +- `SettingsPropertyTests` — 6/6 pass (VAL-02 empty/whitespace rejection, value-free). +- `SettingsBuilderConfigurationBinderIntegrationTests` — 14/14 pass (children-win, comma-scalar, empty→empty, prefix). +- B-1 IVT gate: `ExistForAll.SimpleSettings.Binders` + `...Extensions.GenericHost` build clean on net8.0 AND net10.0 (no CS0122 — Core's `internal IsCollectionShape` is visible via the `Info.cs:5` grant). + +Implementation files were treated as READ-ONLY; only this SECURITY.md was written. diff --git a/.planning/phases/04-collection-validation-binding/VERIFICATION.md b/.planning/phases/04-collection-validation-binding/VERIFICATION.md new file mode 100644 index 0000000..2044a78 --- /dev/null +++ b/.planning/phases/04-collection-validation-binding/VERIFICATION.md @@ -0,0 +1,90 @@ +--- +phase: 04-collection-validation-binding +verified: 2026-07-19T00:00:00Z +status: passed +score: 6/6 must-haves verified +behavior_unverified: 0 +overrides_applied: 0 +--- + +# Phase 4: Collection & Validation Binding — Verification Report + +**Phase Goal:** Collections bind correctly across empty, comma-scalar, and YAML-sequence shapes; declared settings validation actually runs; and the DI extension exposes the settings collection — the client-requested engine features batched before beta. +**Verified:** 2026-07-19 +**Status:** passed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths (ROADMAP Phase 4 Success Criteria) + +| # | Truth | Status | Evidence | +| --- | --- | --- | --- | +| 1 | An unset `T[]` / `List` / `IEnumerable` binds to an empty collection, never `null` (COLL-02) | ✓ VERIFIED | `TypeConverter.CreateNullResult` branches IsArray→IsEnumerable→IsListLike→scalar (`Core/Reflection/TypeConverter.cs:32-48`); List branch bakes a fresh-per-bind factory (`:40-45`, `PropertyConversion.cs:39`). Tests: `Core/TypeConverterTests.cs` `Convert_NullForArrayProperty_ReturnsEmptyArray`, `Convert_NullForListProperty_ReturnsEmptyList`, `Convert_NullForEnumerableProperty_ReturnsEmptyArray`, `Convert_NullForListProperty_YieldsFreshInstancePerBind` | +| 2 | A collection binds from a YAML/child-section sequence; comma-scalar still binds; children win; each element flows the inner converter chain; empty→empty (COLL-03) | ✓ VERIFIED | `ConfigurationBinder.TrySetChildSequence` enumerates `GetChildren()` into `string[]` gated by `IsCollectionShape()` (`Extensions.Binders/ConfigurationBinder.cs:33-75`); children checked before scalar (`:35`), whitespace scalar skipped (`:42`). Tests: `ConfigBuilderConfigurationBinderIntegrationTests.cs` `Bind_ChildSequence_To{StringArray,IntArray,StringList}...`, `Bind_ScalarAndChildren_ChildrenWin`, `Bind_CommaScalar_NoChildren_StillBinds`, `Bind_WhitespaceScalar_...BindsEmpty`, `Bind_EmptySequence_BindsEmpty`, `Bind_ChildSequence_WithRootSection_ResolvesUnderPrefix` | +| 3 | A settings object's declared `ISettingValidation` / `[SettingsProperty(ValidatorType=...)]` is invoked in the bind pipeline incl. cross-property — core path AND DI-resolved path (VAL-01) | ✓ VERIFIED | Core: `ValuesPopulator.RunValidators` runs after property-set loop, object validator from `[SettingsSection].ValidatorType`, gated by `SettingsPlan.HasValidators` (`ValuesPopulator.cs:68-124`, `SettingsPlan.cs:22`, `SettingsSectionAttribute.cs:8`). DI: `SettingsValidationRunner.Validate` + `IServiceProvider.ValidateSimpleSettings()` (`Extensions.GenericHost/SettingsValidationRunner.cs`, `ServiceProviderValidationExtensions.cs:20`). Tests: `SettingsValidationTests.cs` (12 incl. `CrossPropertyValidator_*`, `ObjectAndPropertyValidators_BothFail_Aggregate...`, `ScanAssemblies_WhenSectionCarriesValidator_...`) + `AddSimpleSettingsIntegrationTests.cs` DI tests A3–A7 | +| 4 | `[SettingsProperty(AllowEmpty=false)]` rejects empty / whitespace at bind, not just `null` (VAL-02) | ✓ VERIFIED | `PropertyConversion.Convert` throws `SettingsPropertyNullException` for null AND `IsNullOrWhiteSpace` string when `_throwOnNull` (`Conversion/PropertyConversion.cs:31-47`). Tests: `SettingsPropertyTests.cs` `..._EmptyString_ShouldThrowNullException`, `..._WhitespaceString_ShouldThrowNullException`, `..._MessageIsValueFreeWithPropertyName`, `..._AllowEmptyIsTrue_{Empty,Whitespace}String_BindsValue`. (Unsubstituted `${ENV:-}` placeholder detection deferred per ROADMAP note / CONTEXT D-13 — explicitly out of scope.) | +| 5 | `AddSimpleSettings(...)` exposes the `ISettingsCollection` (resolvable singleton AND out-overload) (API-02) | ✓ VERIFIED | `AddSingleton(settingsCollection)` (`Extensions.GenericHost/ServicesSettingsBuilderExtensions.cs:51`) + `AddSimpleSettings(out ISettingsCollection, Action? = null)` overload returning the built instance (`:21-27`, `:55`). Tests: `AddSimpleSettingsIntegrationTests.cs` `AddSimpleSettings_RegistersSettingsCollection_ServingTheContainerInstance`, `..._OutOverload_SurfacesBoundCollection_AndPreservesChain`, `..._OutOverload_SurfacesSameInstanceAsResolvedSingleton` | +| 6 | After COLL-03 edits `BindPropertySettings`, the S1/SEC-01 secret-redaction invariant is re-verified; suite green on net8 + net10 | ✓ VERIFIED | Sequence element-convert failure wraps value-free as `SettingsPropertyValueException(type, property, e.GetType())` — no chained inner, no value (`ConfigurationBinder.cs:57`, `ValuesPopulator.cs:187-192`). Tests: `ExceptionRedactionTests.cs` `Convert_SecretInSequenceElement_...`, `Convert_SecretInFirstSequenceElement_...`, `Convert_SecretInListSequenceElement_...` + DI `ValidateSimpleSettings_WhenValidatorThrows_RedactsBoundValue`. Build 0 warn/0 err net8.0+net10.0; suite **153/153 net10** and **153/153 net8** (both run locally) | + +**Score:** 6/6 truths verified (0 present, behavior-unverified) + +### Required Artifacts + +| Artifact | Expected | Status | Details | +| --- | --- | --- | --- | +| `Conversion/ListTypeConverter.cs` | List family conversion via cached per-element factory | ✓ VERIFIED | Reuses `CollectionTypeConverter`; `CanConvert` = `IsListLike()`; registered after Enumerable, before Enum (`TypeConvertersCollections.cs:10-12`) | +| `Core/Reflection/TypeExtensions.cs` | `IsListLike` + `IsCollectionShape` disjoint predicates | ✓ VERIFIED | `IsListLike` matches List/IList/ICollection/IReadOnlyList/IReadOnlyCollection; `IsCollectionShape` = IsArray‖IsEnumerable‖IsListLike (`:29-48`) | +| `Core/Reflection/TypeConverter.cs` | CreateNullResult shape branch (fresh list factory) | ✓ VERIFIED | `:32-53` — shared empty array for array/IEnumerable, fresh `List` factory delegate for list family | +| `Conversion/PropertyConversion.cs` | fresh-per-bind list null-result + AllowEmpty empty/whitespace reject | ✓ VERIFIED | `:29-47` | +| `Extensions.Binders/ConfigurationBinder.cs` | GetChildren() child-sequence branch | ✓ VERIFIED | `:33-75`; consumes Core `IsCollectionShape` via InternalsVisibleTo grant (`Info.cs`) | +| `Validations/*.cs` + `SettingsValidationException.cs` + `SettingsValidatorAttribute`/`[SettingsSection].ValidatorType` | sync validator contracts + aggregate exception + `ThrowIfAny` | ✓ VERIFIED | `ISettingValidation` DIM bridge (`Validations/ISettingValidation.cs:9-10`); `SettingsValidationException.ThrowIfAny` shared by both paths (`SettingsValidationException.cs:24-32`); object validator on `[SettingsSection].ValidatorType` (`SettingsSectionAttribute.cs:8`) | +| `SettingsPlan.cs` | HasValidators short-circuit + validator types | ✓ VERIFIED | `:22,45,49` — computed once at plan build | +| `Extensions.GenericHost/{ISettingsValidationRunner,SettingsValidationRunner,ServiceProviderValidationExtensions}.cs` | DI runner + public ValidateSimpleSettings | ✓ VERIFIED | Fresh-scope resolution via `IServiceScopeFactory.CreateScope()`, DIM-bridge dispatch, shared `ThrowIfAny` (`SettingsValidationRunner.cs:20-52`) | + +### Key Link Verification + +| From | To | Via | Status | +| --- | --- | --- | --- | +| `ListTypeConverter.CanConvert` | shape detection | `IsListLike()` — disjoint from IsArray/IsEnumerable | ✓ WIRED (`ListTypeConverter.cs:25`) | +| `ConfigurationBinder` | Core shape predicate | `IsCollectionShape()` via InternalsVisibleTo | ✓ WIRED (`ConfigurationBinder.cs:33`) | +| Core validators + DI runner | one thrown contract | shared `SettingsValidationException.ThrowIfAny` | ✓ WIRED (`ValuesPopulator.cs:96`, `SettingsValidationRunner.cs:52`) | +| `AddSimpleSettings(out ...)` + DI singleton | same instance | `IntegrateSimpleSettings` returns built `ISettingsCollection` | ✓ WIRED (`ServicesSettingsBuilderExtensions.cs:25,51,55`) | +| DI runner | scoped-dependency resolution | `IServiceScopeFactory.CreateScope()` (not root) | ✓ WIRED (`SettingsValidationRunner.cs:20`) | +| validator-free warm path | zero-alloc | `if (!plan.HasValidators) return;` before any alloc | ✓ WIRED (`ValuesPopulator.cs:76-77`) | + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +| --- | --- | --- | --- | +| Build both TFMs clean | `dotnet build SimpleSettings.slnx -c Release` | 0 warnings, 0 errors (net8.0 + net10.0) | ✓ PASS | +| Full suite net10 | `dotnet test SimpleSettings.slnx -c Release -f net10.0 --no-build` | 153/153 passed | ✓ PASS | +| Full suite net8 | `dotnet test UnitTests.csproj -c Release -f net8.0 --no-build` | 153/153 passed | ✓ PASS | + +### Requirements Coverage + +| Requirement | Source Plan | Status | Evidence | +| --- | --- | --- | --- | +| COLL-01 | 04-01 | ✓ SATISFIED | List family conversion (`ListTypeConverter.cs`); `CollectionConversionTests` (List/IList/ICollection/IReadOnlyList/IReadOnlyCollection) | +| COLL-02 | 04-01 | ✓ SATISFIED | Criterion 1 above | +| COLL-03 | 04-02 | ✓ SATISFIED | Criterion 2 above | +| VAL-01 | 04-03, 04-04 | ✓ SATISFIED | Criterion 3 above (core + DI paths) | +| VAL-02 | 04-05 | ✓ SATISFIED | Criterion 4 above | +| API-02 | 04-04 | ✓ SATISFIED | Criterion 5 above | + +### Anti-Patterns Found + +None. No `TBD`/`FIXME`/`XXX`/`TODO`/`HACK`/`PLACEHOLDER` markers in any phase-modified source file. (04-04-REVIEW.md notes 3 LOW style items — a `See S1` comment tail and multi-line rationale comments matching established core style — none material; not debt markers.) + +### Human Verification Required + +None. Every success criterion is exercised by a passing, non-vacuous behavioral test (state transitions, children-win precedence, cross-property rules, deferred DI timing, fresh-scope resolution, and secret redaction all have dedicated tests that assert the observable outcome). + +### Gaps Summary + +No gaps. All 6 ROADMAP Phase 4 success criteria are MET with codebase evidence and proving tests. Two confirmed design facts (not gaps): the object validator is read from `[SettingsSection].ValidatorType` rather than a separate attribute, and the DI validator path is opt-in via the explicit `ValidateSimpleSettings()` call — both documented, deliberate decisions (D-11, comment #3). The `${ENV:-}` placeholder detection excluded from VAL-02 is explicitly deferred post-beta per ROADMAP criterion 4 / CONTEXT D-13. + +--- + +_Verified: 2026-07-19_ +_Verifier: Claude (gsd-verifier)_ From cc6b75902590ad33bcf648eb767521a5b479c156 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Sun, 19 Jul 2026 15:41:40 +0300 Subject: [PATCH 02/23] docs: refresh session handoff (Phase 4 complete + closeout held; Phase 5 next) --- SESSION-HANDOFF.md | 69 +++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/SESSION-HANDOFF.md b/SESSION-HANDOFF.md index 69a1611..e7dd33c 100644 --- a/SESSION-HANDOFF.md +++ b/SESSION-HANDOFF.md @@ -1,73 +1,66 @@ # SESSION HANDOFF — SimpleSettings -_Last updated: 2026-07-15 (reconcile + wrap session) · owner: Guy Ludvig (guy@frontegg.com)_ +_Last updated: 2026-07-19 (Phase 4 Wave 3 + closeout) · owner: Guy Ludvig (guy@frontegg.com)_ ## TL;DR **GSD is the source of truth** (`.planning/`); `FIX-PLAN.md` frozen historical reference only. -**Phases 1–3 + Phase 4 Waves 1–2 (incl. review comment #3) are SHIPPED to `master`** (@ `1388c5c`). Phase 4 **Wave 3 (plan 04-04) is NOT started**; phase verify + security gate + "mark complete" also pending. -This session: resolved PR #33 review comment #3, answered all 4 review comments, then **merged PR #33** (Waves 1–2, `52a2c6f`) and **PR #34** (Phase-4 `.planning` docs + a cosmetic `[]` tweak, `1388c5c`) to master. **Two `2.0.0-alpha.0.*` alphas auto-published** (one per merge) — add to the unlist list. +**Phases 1–4 are COMPLETE.** Phase 4 (Waves 1–3) is fully merged to `master` (@ `0c858fa`, PRs #33/#34/#35). Phase 4 **closeout (verify 6/6 + security gate 15/15, D-06 signed off) is DONE** but lives **doc-only on branch `gsd/phase-4-closeout`** (held, not merged — rides to master with Phase 5's first code PR, per the no-doc-only-alpha rule). **Next work = Phase 5** (AOT-01 + DOC-01), not started. +This session: executed Wave 3 (plan 04-04 — VAL-01 DI path + API-02), reviewed it (plan-review trio + 3 post-code reviews), merged **PR #35** (`0c858fa`), then ran the Phase 4 closeout (verify + secure + mark complete). **One `2.0.0-alpha.0.*` alpha auto-published** on the #35 merge — add to the unlist list. ## Do this first (new session) -1. **Verify git state:** on branch **`gsd/phase-4-wave-3`** (fresh off master, local-only, holds this handoff); `master` @ `1388c5c`; `git status` clean; `gh pr list` shows no open Phase-4 PRs (#33/#34 merged). -2. **Next work = Wave 3 / plan 04-04** (below), then verify → secure → mark Phase 4 complete. -3. **Push/PR only via `guy-lud`** (`gh auth switch --user guy-lud` … then switch back to `guy-frontegg`). `git push` uses the `github-guy-lud` SSH alias. See `[[simplesettings-push-access]]`. +1. **Verify git state:** on branch **`gsd/phase-4-closeout`** (holds the Phase-4 closeout docs — `VERIFICATION.md`, `SECURITY.md`, ROADMAP/STATE marked complete — plus THIS handoff; pushed to origin as a backup, NOT merged). `master` @ `0c858fa`; `git status` clean; `gh pr list` shows no open PRs. +2. **The closeout is doc-only and rides to master with Phase 5's first code PR** — do NOT give it its own PR (would burn a throwaway alpha). Branch Phase 5's first plan off `gsd/phase-4-closeout` (or cherry-pick these docs onto the Phase-5 branch) so they land with real code. +3. **Next work = Phase 5** (AOT-01 + DOC-01): discuss → plan → execute. See Remaining roadmap for the DOC-01 must-includes. +4. **Push/PR only via `guy-lud`** (`gh auth switch --user guy-lud` … then switch back to `guy-frontegg`). `git push` uses the `github-guy-lud` SSH alias. See `[[simplesettings-push-access]]`. -## What shipped this session (on master @ 1388c5c) -- **Review comment #3 RESOLVED:** the object-level validator now rides on **`SettingsSectionAttribute.ValidatorType`** (`SettingsValidatorAttribute` deleted — it was unreleased). `ValuesPopulator.GetOrBuildPlan` reads it there; dispatch + redaction unchanged. - - **Design consequence (now a fact):** `[SettingsSection]` is also the scan-discovery marker, so **attaching an object validator makes a type scan-discoverable** (validate ⇒ discoverable). The deliberately-failing validation fixtures therefore moved to a **new never-scanned test library `ExistForAll.SimpleSettings.UnitTests.Fixtures`** (public types; UnitTests references it but never passes it to `ScanAssemblies`). A regression test proves a `[SettingsSection(ValidatorType=…)]` type is validated under the scan path. - - **Why the fixtures are public, not internal:** the Reflection.Emit proxy generator emits into a **random-GUID dynamic assembly that cannot implement an internal interface** (`TypeLoadException`) — settings interfaces resolved through the library MUST be public. Empirically proven. See `[[simplesettings-proxy-internal-interface]]`. -- **All 4 PR #33 review comments answered** (ConfigurationBinder `TrySetChildSequence` rationale; PropertyConversion double-`if`; attribute reuse; ValuesPopulator cast). -- **Waves 1–2** (COLL-01/02/03, VAL-01 core, VAL-02) all on master. Full suite **143/143** on net10; build clean net8 + net10. +## What shipped this session +- **Phase 4 Wave 3 (plan 04-04, PR #35 @ `0c858fa`) — VAL-01 DI path + API-02:** + - **API-02:** `AddSimpleSettings` registers `ISettingsCollection` as a resolvable DI singleton AND adds an `AddSimpleSettings(out ISettingsCollection, Action?)` overload (same instance, fluent chain preserved). + - **VAL-01 DI path:** opt-in `IServiceProvider.ValidateSimpleSettings()` runs DI-registered `ISettingValidation` in a deferred post-`BuildServiceProvider()` step. Internal `SettingsValidationRunner` resolves validators from a **fresh scope** (`IServiceScopeFactory.CreateScope()` — scoped-dependency validators work under `validateScopes:true`), dispatches via the **DIM bridge** (`(ISettingsValidator)v).Validate(...)`, no reflection), and aggregates through the shared `SettingsValidationException.ThrowIfAny` → **contract-identical to the core path**. Throwing validator → value-free `SettingsValidatorInvocationException(type,type)`. + - Full suite **153/153** (net8 + net10); benchmark allocation gate **0-byte diff** (warm path untouched). +- **Phase 4 CLOSEOUT (on `gsd/phase-4-closeout`, held):** `VERIFICATION.md` (6/6 success criteria MET), `SECURITY.md` (15/15 threats closed, **D-06 secret-redaction gate SIGNED OFF**, T-04-VAL DI holds by construction), ROADMAP + STATE mark Phase 4 ✓ complete and advance to Phase 5. -## Wave 3 — plan 04-04 (NEXT code work) — VAL-01 DI path + API-02 -Delivers `ISettingsCollection` DI singleton + `out`-overload of `AddSimpleSettings`; a deferred DI validator runner (`ValidateSimpleSettings()`, fresh `CreateScope()` per S-1); shared `ThrowIfAny` (S-3). -- ⚠ **04-04-PLAN is DOUBLY superseded — do NOT follow it literally:** - 1. It describes **reflective** dispatch (`GetMethod("Validate")` + `MakeGenericType`). Use the **DIM bridge** instead: `((ISettingsValidator)validator).Validate(new ValidationContext(instance))`, wrapped in the MED-3 value-free guard (`SettingsValidatorInvocationException`) — same as the core path. - 2. It predates comment #3: the object validator is now read from **`SettingsSectionAttribute.ValidatorType`**, not a separate attribute. The DI runner must read it the same way. - Keep the fresh-scope resolution + the shared `ThrowIfAny`. -- **New cadence for landing it:** this branch is a clean descendant of master (which now has `.planning`), so the **Wave-3 PR includes src + `.planning` together** — the old src-only cherry-pick dance is retired (it was only needed while `.planning` wasn't on master). After merge: phase **verify** (`gsd-verifier`), **security gate** (`/gsd-secure-phase 4`, D-06 sign-off), mark complete. - -## Cadence / mechanics (updated) -- **PR #33 was merged mid-phase** (owner shipped Waves 1–2 standalone). The old "keep ONE evolving draft PR, stop after each wave" cadence is **retired**. Going forward: **fresh branch off master per chunk → PR (src + .planning) → merge.** Each master merge burns an alpha (pre-stable, acceptable). -- **The handoff lives on the work branch, not master** — a handoff-only master push would burn a doc-only alpha. It rides to master with the next code PR. This wrap committed it on `gsd/phase-4-wave-3`. See `[[simplesettings-handoff-workflow]]`. -- **Subagents run async in the background** (notify on completion); executors run sequentially on the branch (no worktree isolation in this harness). Spot-check commits + SUMMARY + clean tree + build/test before re-dispatching — stalls can be cosmetic. +## Cadence / mechanics +- **Fresh branch off master per chunk → PR (src + .planning) → merge.** Each master merge burns a throwaway `-alpha` (pre-stable, acceptable). Reviews happen BEFORE the PR (see STANDING review rule). +- **Doc-only changes ride with the next code PR** — a doc-only master push burns a wasted alpha. This is why the Phase-4 closeout + this handoff sit on `gsd/phase-4-closeout` unmerged. See `[[simplesettings-handoff-workflow]]`. +- **Subagents run async in the background** (notify on completion). Dispatch plan-review + code/test reviews in parallel; keep their output out of the main context. ## STANDING review rule (memory `[[dotnet-review-workflow]]`) -Before EACH PR: review code with **both** the `gsd-code-review` skill AND `dotnet-claude-kit:code-review`, and review tests with the **`dotnet-claude-kit:test-engineer`** agent. Plan-review trio (architect/perf/security) up front before writing code. Be proportional — skip for trivial docs/cosmetic PRs (e.g. #34). +Plan-review trio (**dotnet-architect + performance-analyst + security-auditor**) up front on the design BEFORE writing code. Before EACH PR: review code with **both** `gsd-code-review` (gsd-code-reviewer) AND `dotnet-claude-kit:code-reviewer`, and review tests with **`dotnet-claude-kit:test-engineer`**. Be proportional — skip for trivial docs/cosmetic PRs. ## Remaining roadmap -- **Phase 4:** Wave 3 (04-04) + verify + secure + complete. -- **Phase 5:** AOT-01; **DOC-01 (README)** — MUST add: Phase-3 breaking-change list, "spaced secrets bind via `AddCommandLine`", VAL-01 validator-author-must-not-echo-secrets, DI-path `ValidateSimpleSettings()` is opt-in, **and NEW: the validate ⇒ discoverable coupling** (validating via `[SettingsSection(ValidatorType=…)]` also makes the type scan-discovered). Fold in **validator-dispatch caching** (code-review M2, deferred perf). +- **Phase 4: ✓ COMPLETE** (Waves 1–3 merged; verify + secure done; closeout held on branch). +- **Phase 5 (NEXT):** AOT-01; **DOC-01 (README)** — MUST document: Phase-3 breaking-change list; "spaced secrets bind via `AddCommandLine`"; VAL-01 **validator authors must not echo secrets** in `ValidationError` text; **DI-path `ValidateSimpleSettings()` is opt-in / deferred** (must call after `BuildServiceProvider()`; attribute validators run inline); **validator CONSTRUCTORS must not echo injected secrets** (DI resolution runs outside the value-free guard — from the Wave-3 security sign-off); the **validate ⇒ discoverable coupling** (`[SettingsSection(ValidatorType=…)]` also makes the type scan-discovered). Fold in **validator-dispatch caching** (deferred perf, code-review M2). - **Phase 6:** REL-01 (first `v2.0.0-beta`; suite green net8 + net10). - **Held:** EQ-01 (D2 EqualityComparerCreator). **Deferred:** PERF-03 (compiled setter); `${ENV:-}` placeholder detection (VAL-02, D-13). ## How releasing works (durable) -- **`ci.yml`** — PRs to `master`: build + test (net8.0 + net10.0). **`release.yml`**: push to `master` → auto-publishes a MinVer height-based `-alpha` (NO `paths` filter — **every push publishes an alpha**). Manual **Release** (`workflow_dispatch`) tags `v*`. -- **`benchmark.yml`** — push to `master` + PRs: BDN, gates PRs on **allocation** regressions (baseline in `gh-pages`). Green through #34. +- **`ci.yml`** — PRs to `master`: build + test (net8.0 + net10.0). **`release.yml`**: push to `master` → auto-publishes a MinVer height-based `-alpha` (NO `paths` filter — **every master push publishes an alpha**). Manual **Release** (`workflow_dispatch`) tags `v*`. +- **`benchmark.yml`** — push to `master` + PRs: BDN, gates PRs on **allocation** regressions only (baseline in `gh-pages`; time recorded but not gated). Green through #35 (0-byte diff). - **Versioning = MinVer**, tag prefix `v`, baseline **2.0.0**, keyless NuGet Trusted Publishing (OIDC). `SOLUTION=SimpleSettings.slnx`. **Everything through PRs; never commit to `master` directly.** ## Gotchas a new session MUST know - **TUnit test filtering:** `dotnet test --filter "*Name*"` exits **5** with zero tests (looks green). Use `--treenode-filter "/*/*/ClassNameTests/*"` or run unfiltered. See `[[simplesettings-test-stack]]`. -- **Run `dotnet` from `src/`** (global.json → Microsoft.Testing.Platform). net10 runtime only locally; net8 in CI. Build first, then `dotnet test SimpleSettings.slnx -c Release -f net10.0 --no-build`. +- **Run `dotnet` from `src/`** (global.json → Microsoft.Testing.Platform). Build first, then `dotnet test SimpleSettings.slnx -c Release -f net10.0 --no-build`. - **Solution-wide `-f net8.0` build trips `NETSDK1005`** on the net10-only `Benchmark`. Use `dotnet build SimpleSettings.slnx -c Release` (no `-f`). -- **Pushing/PRs:** active `guy-frontegg` is **read-only**; push/PR/merge via **`guy-lud`**. See `[[simplesettings-push-access]]`. +- **Pushing/PRs:** active `guy-frontegg` is **read-only**; push/PR/merge via **`guy-lud`**. Remote `origin` already uses the `github-guy-lud` SSH alias. See `[[simplesettings-push-access]]`. - **Never commit to `master` directly.** Wrap ritual: refresh THIS file on the current work branch. See `[[simplesettings-handoff-workflow]]`. -- **GSD branching:** `branching_strategy: none` → executors commit on the *current* branch. Stay on `gsd/phase-4-wave-3`. See `[[simplesettings-gsd-source-of-truth]]`. +- **GSD branching:** `branching_strategy: none` → executors commit on the *current* branch. Currently `gsd/phase-4-closeout`. See `[[simplesettings-gsd-source-of-truth]]`. - **`pre-bash-guard` silently blocks `git reset --hard`.** Use temp-branch + merge / `-s ours` / `--ff-only`. See `[[simplesettings-bash-guard]]`. - **Settings interfaces MUST be public** — the proxy generator can't implement internal interfaces. See `[[simplesettings-proxy-internal-interface]]`. -- Commits/PRs **omit** the Co-Authored-By / Generated-with trailer (project preference). +- Commits/PRs **omit** the Co-Authored-By / Generated-with trailer (project preference). See `[[no-claude-attribution]]`. ## Key decisions & context (carry forward) -- **Object validator = `[SettingsSection].ValidatorType`** (this session, merged). One attribute; validate ⇒ discoverable. Core reads it in `ValuesPopulator.GetOrBuildPlan`. -- **Exception-redaction invariant (S1+C2, locked).** `SettingsValidatorInvocationException` = value-free Type-only wrap for a throwing validator; `SettingsValidationException` composes only author `ValidationError` text; null/property exceptions value-free; no inner chaining of anything that saw a value. D-06 approved for the COLL-03 sequence path. Don't weaken. -- **VAL-01 dispatch = DIM bridge, no reflection.** `ISettingValidation` default-implements the base `Validate`. Core + (upcoming) DI runner dispatch via the `ISettingsValidator` cast. +- **Object validator = `[SettingsSection].ValidatorType`** (merged). One attribute; validate ⇒ discoverable. Core reads it in `ValuesPopulator.GetOrBuildPlan`. +- **VAL-01 dispatch = DIM bridge, no reflection** (both core AND the now-shipped DI runner). `ISettingValidation` default-implements the base `Validate`; dispatch via the `ISettingsValidator` cast. DI runner is **internal**, resolves from a fresh scope, injects `IServiceScopeFactory`, and eagerly allocates the error list (ThrowIfAny rejects null). +- **Exception-redaction invariant (S1+C2, locked).** `SettingsValidatorInvocationException` = value-free Type-only wrap for a throwing validator; `SettingsValidationException` composes only author `ValidationError` text; null/property exceptions value-free; no inner chaining of anything that saw a value. D-06 signed off for the COLL-03 sequence path AND T-04-VAL for the DI path. Don't weaken. - **`SettingsPlan.HasValidators` zero-alloc short-circuit** protects the validator-free warm path (benchmark gate). Don't add per-populate allocation before it. - **Generator concurrency (T7, #29):** one `_generationGate` (double-checked lock; warm path lock-free). Don't switch to `Lazy`-per-type. - **Pre-stable window:** no `v*` tag; breaking changes free until the first `v2.0.0-beta`. ## Minor tracked follow-ups (non-blocking) -- **Owner:** unlist published `2.0.0-alpha.0.*` prereleases on NuGet.org (guy-lud) — **grew by 2 this session** (#33, #34 merges). -- **Old branch retired:** `gsd/phase-4-collection-validation-binding` (+ `…-pr`) deleted this wrap — fully superseded by master. If any lingers on origin, delete it. +- **Owner:** unlist published `2.0.0-alpha.0.*` prereleases on NuGet.org (guy-lud) — **grew by 1 this session** (#35 merge; running total incl. prior #33/#34). - **`.claude/`/`.codex/` tracked on `master`**: add to `.gitignore` + `git rm --cached` on a branch/PR (never on `master`). - **REQUIREMENTS.md traceability:** 13 brownfield baseline IDs (`BIND-01…NAME-01`) in the body but not the traceability table (pre-existing). - **Codebase-map drift:** root files predate the map — refresh via `/gsd-map-codebase` when convenient. +- **`SettingsValidatorInvocationException`** is a public exception never registered as its own threat ID (security-audit note) — handled value-free + tested; register it if the threat model is revisited. From 40f0fd9de8d51b4bf7553528045197d3d580d5b9 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:08:42 +0300 Subject: [PATCH 03/23] docs(05): defer AOT-01 to v2.1, scope phase 5 to Documentation (DOC-01) --- .planning/REQUIREMENTS.md | 21 ++++++++++++--------- .planning/ROADMAP.md | 27 ++++++++++++++++----------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 8724f0d..8e6572c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -65,10 +65,11 @@ Remaining open work (from `FIX-PLAN.md`), batched toward the first `v2.0.0-beta` - [x] **VAL-02**: `[SettingsProperty(AllowEmpty=false)]` rejects `""`/whitespace/unsubstituted `${ENV:-}` at bind, not just `null`; `TypeConverter.ValidateNullAcceptance` (naturally rides on VAL-01) (client pre-beta) - [ ] **API-02**: `AddSimpleSettings(...)` exposes the `ISettingsCollection` (return value or resolvable service — shape TBD in planning) (client pre-beta #4) -### AOT/Trim & Documentation +### Documentation -- [ ] **AOT-01**: Annotate reflection entry points (`[RequiresDynamicCode]`/`[RequiresUnreferencedCode]`) and/or document the AOT/trim limitation before stable (A1) -- [ ] **DOC-01**: Refresh README to canonical naming and current repo/package links +- [ ] **DOC-01**: Refresh README + docs/ to canonical `ExistForAll.SimpleSettings` naming and current repo/package links; document the Phase 1–4 security/behavior guidance (secret-redaction; validator secret-safety incl. constructors; opt-in/deferred DI validation; validate⇒discoverable coupling; spaced-secrets binding; Phase-3 breaking-change list) + +*(AOT-01 was mapped here but was **deferred to a future v2.1 milestone** on 2026-07-19 — see v2 Requirements → Deferred.)* ### Release @@ -85,6 +86,7 @@ Deferred / held. Tracked but not in the current roadmap. ### Deferred - **PERF-03**: Tiered/lazy compiled property setter — only if set-*time* shows up in a real profile (P3b); a compiled setter was tried and reverted (regressed cold scan, no warm gain) +- **AOT-01** (A1): Annotate reflection entry points (`[RequiresDynamicCode]`/`[RequiresUnreferencedCode]`) and/or document the AOT/trim limitation — **DEFERRED to a future v2.1 milestone** (2026-07-19, Phase-5 discussion). The annotations are additive/non-breaking, so they need not batch pre-beta; adding them post-stable is safe. Reflection entry points: `SettingsBuilder.CreateBuilder`/`.ScanAssemblies`/`.GetSettings`, `AddSimpleSettings(...)` (all funnel into `Reflection.Emit` with `RunAndCollect`). ## Out of Scope @@ -115,19 +117,20 @@ Deferred / held. Tracked but not in the current roadmap. | COLL-03 | Phase 4 | Complete | | VAL-01 | Phase 4 | Complete | | VAL-02 | Phase 4 | Complete | -| API-02 | Phase 4 | Pending | -| AOT-01 | Phase 5 | Pending | +| API-02 | Phase 4 | ✓ Complete (#35) | +| AOT-01 | v2.1 (deferred) | Deferred | | DOC-01 | Phase 5 | Pending | | REL-01 | Phase 6 | Pending | **Coverage:** -- v1 requirements: 20 total (added Phase 4 engine reqs COLL-02/COLL-03/VAL-02/API-02 + promoted VAL-01 from Held) -- Mapped to phases: 20 +- v1 in-milestone requirements: 19 (was 20; **AOT-01 deferred to v2.1** on 2026-07-19) +- Mapped to phases: 19 - Unmapped: 0 ✓ -- Complete: 12 (Phase 1 SEC-01/SEC-02/EXC-01 #27/#28; Phase 2 COLL-01/TEST-01/TEST-02/TEST-03 + ENG-01/T7 #29; Phase 3 API-01/PKG-01/PKG-02/SRC-02 #31); Pending: 8 (Phase 4 COLL-02/COLL-03/VAL-01/VAL-02/API-02; Phase 5 AOT-01/DOC-01; Phase 6 REL-01) +- Complete: 17 (Phase 1 SEC-01/SEC-02/EXC-01 #27/#28; Phase 2 COLL-01/TEST-01/TEST-02/TEST-03 + ENG-01/T7 #29; Phase 3 API-01/PKG-01/PKG-02/SRC-02 #31; Phase 4 COLL-02/COLL-03/VAL-01/VAL-02/API-02 #33/#34/#35); Pending: 2 (Phase 5 DOC-01; Phase 6 REL-01) - Validated (shipped, no phase): 13 --- *Requirements defined: 2026-07-13* -*Last updated: 2026-07-14 — Phase 4 "Collection & Validation Binding" formalized (COLL-02/COLL-03/VAL-02/API-02 added; VAL-01 promoted from Held D1); AOT/Docs → Phase 5, beta → Phase 6. ENG-01/T7 complete (#29). GSD is the source of truth; FIX-PLAN.md frozen as historical reference.* +*Last updated: 2026-07-19 — Phase 5 scoped to Documentation (DOC-01) only; **AOT-01 deferred to a future v2.1 milestone** (additive/non-breaking annotations need not batch pre-beta). Phase 4 marked complete in traceability (COLL-02/COLL-03/VAL-01/VAL-02/API-02, #33/#34/#35).* +*Prior: 2026-07-14 — Phase 4 "Collection & Validation Binding" formalized (COLL-02/COLL-03/VAL-02/API-02 added; VAL-01 promoted from Held D1); AOT/Docs → Phase 5, beta → Phase 6. ENG-01/T7 complete (#29). GSD is the source of truth; FIX-PLAN.md frozen as historical reference.* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index b75d948..2a6d87a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -2,7 +2,7 @@ ## Overview -**Status (2026-07-19):** Phases 1–4 complete (Phase 1 #27/#28, Phase 2 #30, Phase 3 #31, Phase 4 #33/#34/#35; ENG-01/T7 #29). `master` @ `0c858fa`. Active phase is **Phase 5 — AOT/Trim Honesty & Documentation**; first beta is Phase 6. +**Status (2026-07-19):** Phases 1–4 complete (Phase 1 #27/#28, Phase 2 #30, Phase 3 #31, Phase 4 #33/#34/#35; ENG-01/T7 #29). `master` @ `0c858fa`. Active phase is **Phase 5 — Documentation** (docs-only; AOT-01 deferred to a future v2.1 milestone); first beta is Phase 6. 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 @@ -11,8 +11,10 @@ consumers one catchable, structured exception base (Phase 1), proves binding cor across collection/nullable/converter shapes and closes the generator concurrency race with tests (Phase 2), trims and corrects the public surface, packaging, and command-line binder (Phase 3), binds collections and settings validation correctly across empty/sequence/validator -shapes (Phase 4), tells consumers the truth about AOT/trim and refreshes the docs (Phase 5), and -finally publishes the batched result as the first beta (Phase 6). Everything serves the core +shapes (Phase 4), refreshes the docs to canonical naming and accurate, current content (Phase 5), and +finally publishes the batched result as the first beta (Phase 6). AOT/trim honesty (AOT-01) is +deferred to a future v2.1 milestone — its `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` +annotations are additive and non-breaking, so they need not batch pre-beta. Everything serves the core value: config → typed settings maps accurately, and never leaks a secret doing it. ## Phases @@ -26,7 +28,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing - [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) - [x] **Phase 3: Public Surface, Packaging & Binder Cleanup** - Meaningful public surface; per-TFM deps; correct command-line parsing (completed 2026-07-14) - [x] **Phase 4: Collection & Validation Binding** — ✓ COMPLETE (Waves 1–3 merged #33/#34/#35, 2026-07-19) - Empty/sequence collection binding, working settings validation, and DI collection surface (client pre-beta engine requirements) -- [ ] **Phase 5: AOT/Trim Honesty & Documentation** - Honest AOT/trim signals; canonically-named docs +- [ ] **Phase 5: Documentation** - Canonically-named, accurate, current docs (README + docs/); AOT-01 deferred to a future v2.1 milestone - [ ] **Phase 6: First v2.0.0-beta Release** - Batched breaking changes ship as an installable pre-release ## Phase Details @@ -118,16 +120,19 @@ value: config → typed settings maps accurately, and never leaks a secret doing - [x] 04-04-PLAN.md — API-02 ISettingsCollection exposure (DI singleton + out-overload, D-15) + VAL-01 deferred DI-resolved validator runner (D-11/Q3, DIM-bridge dispatch) [Wave 3, depends on 04-03] -### Phase 5: AOT/Trim Honesty & Documentation +### Phase 5: Documentation -**Goal**: Consumers get honest signals about AOT/trim support and accurate, canonically-named documentation. +**Goal**: Consumers get accurate, canonically-named documentation — the consumer-facing README and the docs/ folder tell the truth about the current API and carry the Phase 1–4 security/behavior guidance. **Depends on**: Phase 4 -**Requirements**: AOT-01, DOC-01 +**Requirements**: 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). + 1. README uses the canonical `ExistForAll.SimpleSettings` name and links to current repo/package paths (no legacy `SimpleConfig` repo/product references; repo stays `existall/SimpleSettings`). + 2. The README code example reflects the real API (`[SettingsSection]` / `[SettingsProperty(DefaultValue=…)]`), not the stale `[DefaultValue]` form, and the install command + logo resolve. + 3. The mandated Phase 1–4 guidance is documented (secret-redaction; validators must not echo secrets, incl. constructors; opt-in/deferred DI `ValidateSimpleSettings()`; validate⇒discoverable coupling; spaced-secrets bind via `AddCommandLine`; Phase-3 breaking-change list) — concise in README, detailed in docs/. + 4. The docs/ folder carries no legacy `SimpleConfig` naming or dead `existall/SimpleConfig` links, and package metadata (``, repo/package URLs, logo) is canonical. + +**Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). **Plans**: TBD @@ -156,5 +161,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. AOT/Trim Honesty & Documentation | 0/TBD | Not started | - | +| 5. Documentation | 0/TBD | Not started | - | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | From decc982323905b9e6cce3266d0ca13bd0a3cebe2 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:08:47 +0300 Subject: [PATCH 04/23] docs(05): capture phase context --- .../phases/05-documentation/05-CONTEXT.md | 183 ++++++++++++++++++ .../05-documentation/05-DISCUSSION-LOG.md | 101 ++++++++++ 2 files changed, 284 insertions(+) create mode 100644 .planning/phases/05-documentation/05-CONTEXT.md create mode 100644 .planning/phases/05-documentation/05-DISCUSSION-LOG.md diff --git a/.planning/phases/05-documentation/05-CONTEXT.md b/.planning/phases/05-documentation/05-CONTEXT.md new file mode 100644 index 0000000..7f970e8 --- /dev/null +++ b/.planning/phases/05-documentation/05-CONTEXT.md @@ -0,0 +1,183 @@ +# Phase 5: Documentation - Context + +**Gathered:** 2026-07-19 +**Status:** Ready for planning + + +## Phase Boundary + +Make the consumer-facing documentation **accurate and canonically-named**: refresh the root +`README.md` (which ships to nuget.org via `PackageReadmeFile`) and the `docs/` folder so they tell +the truth about the current API, use the canonical `ExistForAll.SimpleSettings` naming and current +`existall/SimpleSettings` links, and carry the Phase 1–4 security/behavior guidance. Requirement +**DOC-01** is locked by ROADMAP; this discussion settled **HOW**. + +**Scope change (this discussion):** **AOT-01 was removed from Phase 5 and deferred to a future v2.1 +milestone.** The `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` annotations are additive and +non-breaking, so they need not batch into the pre-beta window; adding them post-stable is safe. +Recorded in `.planning/ROADMAP.md` (Phase 5 note) and `.planning/REQUIREMENTS.md` (AOT-01 → Deferred). +Phase 5 was renamed **"AOT/Trim Honesty & Documentation" → "Documentation"** (slug `05-documentation`). + +**Not in scope:** AOT-01 (deferred to v2.1); REL-01 / cutting the beta (Phase 6); validator-dispatch +caching (deferred perf, code-review M2 — a code change, not docs); any runtime/behavior change. + + + +## Implementation Decisions + +### README shape — modernize, self-contained +- **D-01:** Rewrite `README.md` to a modern, self-contained OSS shape: (1) canonical title (drop the + "(previously SimpleConfig)" tag); (2) **install** via `dotnet add package ExistForAll.SimpleSettings` + (plus `.Binders` / `.Extensions.GenericHost` where relevant), replacing the legacy `Install-Package` + PMC command; (3) a ~30-second **quickstart** with a CORRECT minimal example; (4) a short **feature + overview**; (5) **links into `docs/`** for depth. Trim the long "why `IOptions<>` is bad" polemic to a + tight blurb — keep the positioning, drop the wall of text. +- **D-02 (correctness, not just naming):** The README code example MUST reflect the **real API** — a + settings **interface** (discovered via `ISettingsSection` base / `[SettingsSection]` / `Settings` + suffix) with **`[SettingsProperty(DefaultValue = "…")]`**, NOT the stale `[DefaultValue("…")]` shown + today (that attribute form is factually wrong). Verify every API token against current source before + writing (see research flags). +- **D-03:** Fix all consumer-facing surface in the README: the broken `existall/Shepherd` logo, every + dead `existall/SimpleConfig/blob/master/docs/*` ToC link (repoint to `existall/SimpleSettings`), the + install command, and prose typos. + +### Guidance placement — concise in README, deep in docs/ +- **D-04:** The README carries a **concise** "Security notes" + "Breaking changes / migration" section + (visible to every nuget.org reader); the **deep detail lives in `docs/`**. The mandated content (LOCKED + by the Phase-4 security sign-off — `[[04-CONTEXT]]` D-06/D-12, Phase 4 `SECURITY.md`) that MUST be + documented: + - **Secret-redaction invariant** — conversion/bind failures never surface the bound value or chain a + value-bearing inner (S1/SEC-01). + - **Validator authors must not echo secrets** — neither in `ValidationError` message text NOR in + **validator constructors** (DI resolution runs *outside* the value-free guard, so a ctor that logs an + injected secret leaks it). + - **DI-path `ValidateSimpleSettings()` is opt-in / deferred** — must be called *after* + `BuildServiceProvider()`; attribute / `ValidatorType` validators run **inline** in the bind pipeline. + - **validate ⇒ discoverable coupling** — `[SettingsSection(ValidatorType=…)]` (object-level validator) + also makes the type scan-discovered. + - **Spaced secrets bind via `AddCommandLine`** — a quoted value containing spaces arrives as its own + token; document the `--key value` lookahead + `arg[0]` skip behavior (SRC-02). + - **Phase-3 breaking-change list** — `SettingsHolder`/`ISettingsHolder` internal (API-01); `Core.AspNet` + package dropped (PKG-01); per-TFM `Microsoft.Extensions.*` floor (PKG-02); public exception hierarchy + (`SimpleSettingsException` base, EXC-01). + +### docs/ — refresh all six in place +- **D-05:** Update **all six** `docs/` files in place: purge `SimpleConfig` product naming + dead + `existall/SimpleConfig` links (repoint to `existall/SimpleSettings`), **rename `docs/Extend Simple + Config.md`** to a canonical name (e.g. `Extending SimpleSettings.md`) and fix inbound links, correct + stale API references (e.g. `[DefaultValue]`), and add the deep security/behavior + migration content the + README summarizes. Preserve existing page structure/history (in-place, not a restructure). + +### Metadata — fix alongside docs +- **D-06:** Fix `src/Directory.Build.props` `` typos ("appliaction" → "application"; the + "decouples the frameworks from your appliaction" prose), canonicalize/replace the broken + `existall/Shepherd` logo reference, and verify ``/`` (already + `existall/SimpleSettings`) + the three `ExistForAll.*` NuGet package links resolve. + +### Claude's Discretion +- Exact README section ordering/wording and how far to trim the IOptions polemic; the precise canonical + name for the renamed `Extend Simple Config.md`; whether "migration" is a README subsection vs a dedicated + `docs/` page; which `docs/` page holds the deep security guidance; whether to add a `CHANGELOG.md`; + whether to add a CI legacy-reference / dead-link check (nice-to-have — not required by the criteria). + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Requirement / roadmap / decision source +- `.planning/ROADMAP.md` — Phase 5 "Documentation" goal + 4 success criteria + the AOT-01 deferral note +- `.planning/REQUIREMENTS.md` — DOC-01 (active) + AOT-01 (v2 → Deferred) + traceability +- `SESSION-HANDOFF.md` (repo root) — the authoritative **DOC-01 must-include list** and cadence rules +- `.planning/phases/04-collection-validation-binding/SECURITY.md` — the redaction invariant + T-04-VAL DI + path sign-off (authoritative for the security guidance the docs must carry) +- `.planning/phases/04-collection-validation-binding/04-CONTEXT.md` — D-06/D-12 (redaction), D-11 (discovery + mechanisms / validate⇒discoverable), D-15 (API-02 `ISettingsCollection`) +- `.planning/phases/03-public-surface-packaging-binder-cleanup/03-CONTEXT.md` — Phase-3 breaking-change + detail (API-01, PKG-01, PKG-02, SRC-02) for the migration section + +### Files to edit +- `README.md` — the refresh target (ships to NuGet via `PackageReadmeFile`) +- `docs/getting_started.md`, `docs/building_the_collection.md`, `docs/Build Config Interface.md`, + `docs/Default Values.md`, `docs/Build a SectionBinder.md`, `docs/Extend Simple Config.md` (rename) — all six +- `src/Directory.Build.props:16-18,23` — `` (typos), ``, ``, + `` (metadata + logo) + +### Current-API ground truth (so every example/claim is accurate — VERIFY against these, do not trust the old docs) +- `src/Core/ExistForAll.SimpleSettings/SettingsBuilder.cs` — `CreateBuilder` / `ScanAssemblies` / `GetSettings` (direct API) +- `src/Core/ExistForAll.SimpleSettings.Extensions.GenericHost/ServicesSettingsBuilderExtensions.cs` — `AddSimpleSettings(...)`, the API-02 `ISettingsCollection` exposure, and the opt-in `ValidateSimpleSettings()` (DI validation path) +- `src/Core/ExistForAll.SimpleSettings/SettingsPropertyAttribute.cs` — `DefaultValue`/`Name`/`ConverterType`/`AllowEmpty`/`ValidatorType` (grounds the corrected example) +- `src/Core/ExistForAll.SimpleSettings/SettingsSectionAttribute.cs` — `[SettingsSection]` incl. object-level `ValidatorType` (validate⇒discoverable) +- `src/Core/ExistForAll.SimpleSettings/ISettingsCollection.cs` — API-02 public surface +- `src/Core/ExistForAll.SimpleSettings/Validations/` — `ISettingValidation.cs`, `ISettingsValidator.cs`, `ValidationError.cs`, `ValidationResult.cs` (validator authoring; the secret-safety guidance) +- `src/Core/ExistForAll.SimpleSettings.Extensions.Binders/CommandLineSettingsBinder.cs` + `CommandLineSettingsBinderOptions.cs` — spaced-value binding / `AddCommandLine` / `SkipFirstArgument` +- Exception hierarchy (secret-redaction): `SimpleSettingsException`, `SettingsPropertyValueException`, `SettingsPropertyNullException`, `SettingsValidationException`, `SettingsValidatorInvocationException` (Core project; the invariant is authoritative in Phase 4 `SECURITY.md`) +- `.planning/codebase/CONVENTIONS.md`, `.planning/codebase/STRUCTURE.md` — naming/structure so doc examples match house style + + + +## Existing Code Insights + +### Reusable Assets +- **`docs/` folder (6 pages)** already exists — refresh in place rather than starting from scratch. +- **README is packaged** (`PackageReadmeFile` in `Directory.Build.props`) — so the README refresh is a + real *package-content* change that ships in the `.nupkg`, not throwaway planning docs. + +### Established Patterns +- Canonical identity: namespace/packages `ExistForAll.SimpleSettings` (+`.Binders`, `.Extensions.GenericHost`); + repo `existall/SimpleSettings`. The **`existall` org stays** — only the legacy `SimpleConfig` repo/product + name is purged. +- Settings types are **interfaces** (proxy generator can't implement internal interfaces — keep example + interfaces `public`). Modern C#, block-scoped namespaces, `net8.0;net10.0`. + +### Integration Points +- `README.md` → nuget.org package page (`PackageReadmeFile`) and the GitHub repo landing page. +- README ToC / "learn more" links → `docs/` pages (must resolve to `existall/SimpleSettings` paths). +- `Directory.Build.props` metadata → the published package (``, URLs, logo, README). + +### Research flags (for gsd-phase-researcher / gsd-planner) +- **Verify every API token against current source** before writing examples — the *current* README example + (`[DefaultValue]`) is already wrong. Confirm the exact public entry-point signatures, `[SettingsProperty]` + members, and especially the **`ValidateSimpleSettings()`** API shape/usage (Phase 4 Wave 3) — name/receiver + must match source, not this doc's paraphrase. +- **Acceptance sweep:** a legacy-reference grep (`existall/SimpleConfig`, standalone `SimpleConfig`, + `Install-Package`, `[DefaultValue]`) across README + docs/ + `Directory.Build.props` should come back clean; + links should resolve. Planner may wire this as the DOC-01 verification. + + + +## Specific Ideas +- The current README's `[DefaultValue("SomeUrl")]` example is a **factual bug**, not just a naming issue — + the real API is `[SettingsProperty(DefaultValue = "…")]`. Treat doc accuracy as a first-class goal. +- **Phase 5 earns a real master alpha (not a throwaway doc-only push):** because the README is packaged + (`PackageReadmeFile`) and `` ships in the `.nupkg`, this phase changes *package content* — + so its PR is a legitimate release-worthy master merge. The **held Phase-4 closeout docs** on branch + `gsd/phase-4-closeout` (VERIFICATION.md, SECURITY.md, ROADMAP/STATE) should **ride to master on Phase 5's + PR** — branch Phase 5 off `gsd/phase-4-closeout` (or cherry-pick those docs onto the Phase-5 branch). See + `[[simplesettings-handoff-workflow]]`. +- Ship on a feature branch → PR via the **`guy-lud`** account (never commit to `master`; `origin` uses the + `github-guy-lud` SSH alias). See `[[simplesettings-push-access]]`. +- Standing review rule still applies but be proportional for a docs phase: plan-review is light; the payoff + is a careful accuracy/security-content read of the finished docs (a `security-auditor`/`code-reviewer` + pass confirming no secret-echoing examples and that the mandated guidance is present and correct). + + + +## Deferred Ideas +- **AOT-01** — annotate reflection entry points (`[RequiresDynamicCode]`/`[RequiresUnreferencedCode]`) + and/or document the AOT/trim limitation. **Deferred to a future v2.1 milestone** (this discussion). + Non-breaking to add later. Recorded in ROADMAP + REQUIREMENTS. +- **Validator-dispatch caching** — deferred perf follow-up (code-review M2). A code change, not docs; not + Phase 5. +- **REL-01** — cut the first `v2.0.0-beta`. Phase 6. +- **EQ-01** (HELD), **PERF-03** (deferred), **`${ENV:-}` placeholder detection** (VAL-02/D-13, deferred) — + unchanged; out of scope. + +None of the above block Phase 5. + + +--- + +*Phase: 05-documentation* +*Context gathered: 2026-07-19* diff --git a/.planning/phases/05-documentation/05-DISCUSSION-LOG.md b/.planning/phases/05-documentation/05-DISCUSSION-LOG.md new file mode 100644 index 0000000..29b7532 --- /dev/null +++ b/.planning/phases/05-documentation/05-DISCUSSION-LOG.md @@ -0,0 +1,101 @@ +# Phase 5: Documentation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-07-19 +**Phase:** 05-documentation (renamed from "AOT/Trim Honesty & Documentation") +**Areas discussed:** Gray-area selection, AOT-01 deferral, README depth, Guidance placement, docs/ pass, Metadata scope + +--- + +## Gray-area selection + +| Option | Description | Selected | +|--------|-------------|----------| +| AOT honesty strength | How far on annotate vs document vs full analyzer sweep | | +| README depth | Link/naming fix vs substantive refresh + mandated guidance | ✓ | +| docs/ + metadata | Sweep docs/ + csproj/logo, or README-only | ✓ | +| AOT verification proof | CI AOT sample vs local smoke-check vs annotations-present | | + +**User's choice:** README depth + docs/ + metadata. Free-text: "lets move aot to future plans like 2.1". +**Notes:** The free-text directive removed AOT-01 from Phase 5 entirely — handled as a scope change (below), not a discussion area. + +--- + +## AOT-01 deferral + +| Option | Description | Selected | +|--------|-------------|----------| +| v2.1 milestone (post-beta) | AOT ships in a future v2.1 cycle, after the first v2.0.0-beta; 'before stable' wording relaxes | ✓ | +| New late phase, this milestone | AOT gets its own phase after the beta but still inside v2.0.0 | | +| Backlog, no version yet | Record deferred/unscheduled; pick target later | | + +**User's choice:** v2.1 milestone (post-beta). +**Notes:** Surfaced the trade-off — `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so deferring past the beta (even past stable) is low-risk. Formalized in ROADMAP.md (Phase 5 renamed "Documentation", AOT note) + REQUIREMENTS.md (AOT-01 → v2 Deferred; traceability + coverage updated; Phase 4/API-02 marked complete while there). + +--- + +## README depth + +| Option | Description | Selected | +|--------|-------------|----------| +| Modernize, self-contained | Modern OSS shape: install, quickstart, correct example, feature overview, links to docs/; trim IOptions polemic | ✓ | +| Refresh in place | Only fix naming, dead links, logo, install cmd, stale example | | +| Full inline rewrite | Document all features inline; docs/ becomes secondary | | + +**User's choice:** Modernize, self-contained. +**Notes:** README ships to nuget.org via `PackageReadmeFile`, so it's the consumer-facing front door. + +--- + +## Guidance placement + +| Option | Description | Selected | +|--------|-------------|----------| +| Concise README + detail in docs/ | Tight Security + Breaking-changes/migration sections in README; deep detail in docs/ | ✓ | +| All in README | Every note fully inline | | +| docs/ only, README links out | Keep README lean; all guidance in a docs/ page | | + +**User's choice:** Concise README + detail in docs/. +**Notes:** Confirms docs/ stays and must become canonical (README links into it; deep mandated guidance lives there). + +--- + +## docs/ pass + +| Option | Description | Selected | +|--------|-------------|----------| +| Refresh all 6 in place | Fix legacy naming/links, rename 'Extend Simple Config.md', correct stale API, add security/migration content | ✓ | +| Restructure the docs tree | Reorganize into a cleaner set; rewrite/merge old pages | | +| Minimal — only what README links | Only repoint linked pages + add the mandated page | | + +**User's choice:** Refresh all 6 in place. +**Notes:** Preserves existing page structure/history. + +--- + +## Metadata scope + +| Option | Description | Selected | +|--------|-------------|----------| +| Fix metadata + logo too | Correct csproj `` typos, canonicalize/replace logo, verify repo/package URLs | ✓ | +| Naming/links only | Fix only strict naming/links; leave typos | | +| README + docs/ only | Treat csproj/package metadata as separate | | + +**User's choice:** Fix metadata + logo too. +**Notes:** All of it is consumer-facing "canonical naming/links" surface. + +--- + +## Claude's Discretion + +- Exact README section ordering/wording; how far to trim the IOptions polemic. +- Canonical name for the renamed `Extend Simple Config.md`; whether migration is a README subsection vs a dedicated docs/ page; which docs/ page holds the deep security guidance. +- Whether to add a `CHANGELOG.md`; whether to add a CI legacy-reference / dead-link check. + +## Deferred Ideas + +- **AOT-01** → future v2.1 milestone (recorded in ROADMAP + REQUIREMENTS). +- **Validator-dispatch caching** (perf, code-review M2) — stays deferred; a code change, not docs. +- **REL-01** → Phase 6. From ce02773f0ad14a22dfa9efc3b3f4f5958a05cd6a Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:13:30 +0300 Subject: [PATCH 05/23] docs(state): record phase 5 context session (Documentation; AOT-01 deferred to v2.1) --- .planning/STATE.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.planning/STATE.md b/.planning/STATE.md index f0831dc..18f9f92 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,12 +3,12 @@ gsd_state_version: 1.0 milestone: v2.0.0 milestone_name: milestone current_phase: 05 -current_phase_name: aot-trim-docs +current_phase_name: documentation status: ready -stopped_at: Phase 4 COMPLETE — Waves 1–3 merged (#33/#34/#35 @ 0c858fa); verify 6/6 + security gate 15/15 (D-06 signed off). Phase 5 (AOT-01/DOC-01) not started. +stopped_at: Phase 5 context gathered — scoped to Documentation (DOC-01); AOT-01 deferred to a future v2.1 milestone. Ready to plan. last_updated: "2026-07-19T12:00:00.000Z" last_activity: 2026-07-19 -last_activity_desc: Phase 04 closeout — verify + security gate passed; phase marked complete +last_activity_desc: Phase 05 context gathered (Documentation / DOC-01); AOT-01 deferred to v2.1 progress: total_phases: 6 completed_phases: 3 @@ -24,14 +24,14 @@ progress: See: .planning/PROJECT.md (updated 2026-07-13) **Core value:** Correctness of binding — config → strongly-typed settings maps accurately across every supported shape (sections, arrays/enumerables, defaults, nullable, custom converters). -**Current focus:** Phase 05 — AOT/Trim Honesty & Documentation (not started); Phase 04 complete +**Current focus:** Phase 05 — Documentation (DOC-01) — context gathered, ready to plan; Phase 04 complete ## Current Position -Phase: 04 (collection-validation-binding) — ✓ COMPLETE (Waves 1–3 merged #33/#34/#35; verify 6/6, security 15/15, D-06 signed off) -Next: Phase 05 (AOT/Trim Honesty & Documentation) — not started -Status: Phase 4 closed; ready to plan Phase 5 -Last activity: 2026-07-19 — Phase 04 closeout (verify + secure) passed; phase marked complete +Phase: 05 (documentation) — context gathered; ready to plan (DOC-01 only; AOT-01 deferred to v2.1) +Next: /gsd-plan-phase 5 +Status: 05-CONTEXT.md written; ROADMAP + REQUIREMENTS updated (Phase 5 → Documentation, AOT-01 → v2.1) +Last activity: 2026-07-19 — Phase 05 context gathered Progress: [█████░░░░░] 50% @@ -103,6 +103,7 @@ None yet. ### Roadmap Evolution - Phase 4 inserted: Phase 4 Collection & Validation Binding formalized (COLL-02/COLL-03/VAL-01/VAL-02/API-02); AOT/Docs renumbered to Phase 5, beta to Phase 6 +- Phase 5 rescoped (2026-07-19): "AOT/Trim Honesty & Documentation" → "Documentation" (DOC-01 only); AOT-01 deferred to a future v2.1 milestone — additive/non-breaking annotations need not batch pre-beta ## Deferred Items @@ -113,9 +114,10 @@ Items acknowledged and carried forward: | Held feature | VAL-01 Validations API (D1) | Promoted → Phase 4 (2026-07-14) | 2026-07-13 | | Held feature | EQ-01 EqualityCompererCreator (D2) | Held | 2026-07-13 | | Perf | PERF-03 compiled setter (P3b) | Deferred (profile-gated) | 2026-07-13 | +| AOT/Trim | AOT-01 annotations/docs (A1) | Deferred → v2.1 milestone | 2026-07-19 | ## Session Continuity -Last session: 2026-07-15T13:58:04.982Z -Stopped at: Completed 04-05-PLAN.md (VAL-02) — Phase 4 all 5 plans done -Resume file: None +Last session: 2026-07-19 +Stopped at: Phase 5 context gathered — Documentation (DOC-01); AOT-01 deferred to v2.1 +Resume file: .planning/phases/05-documentation/05-CONTEXT.md From d2475c05f8b2f10c3cf5269303a5d27e58c65fb3 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:30:24 +0300 Subject: [PATCH 06/23] docs(05): research phase documentation domain --- .../phases/05-documentation/05-RESEARCH.md | 580 ++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 .planning/phases/05-documentation/05-RESEARCH.md diff --git a/.planning/phases/05-documentation/05-RESEARCH.md b/.planning/phases/05-documentation/05-RESEARCH.md new file mode 100644 index 0000000..1207bea --- /dev/null +++ b/.planning/phases/05-documentation/05-RESEARCH.md @@ -0,0 +1,580 @@ +# Phase 5: Documentation - Research + +**Researched:** 2026-07-20 +**Domain:** Consumer-facing documentation accuracy (README + docs/ + package metadata) for a .NET settings-binding library +**Confidence:** HIGH (every API fact and legacy-reference is a direct read of the canonical source at a cited file:line; nothing here is from training data) + +## Summary + +This is a **documentation phase — no runtime/behavior change**. The research question is not "how do we build X" but "what is the exact current ground truth so every doc claim is accurate." I read the real source for every public API token the docs must show, and I greppe the whole consumer-facing surface (root `README.md`, all six `docs/*.md`, `src/Directory.Build.props`) for legacy naming and stale API forms. + +**Key finding:** the `docs/` folder is already **substantially modernized** — five of six pages use canonical `existall/SimpleSettings` links and the correct `[SettingsProperty(DefaultValue = …)]` form, and every API token they reference (generic `GetSettings()` helpers, `Set*` factory extensions, `AddInMemoryCollection`, `BindingContext`, `ISettingsTypeConverter`) was verified to exist in source. The **README is the stale artifact**: it still carries the broken `existall/Shepherd` logo, the legacy `Install-Package` PMC command, six dead `existall/SimpleConfig/blob/...` ToC links, the factually-wrong `[DefaultValue("SomeUrl")]` example, a "(previously SimpleConfig)" tag, and none of the Phase 1–4 security/behavior guidance. Residual legacy in docs/ is narrow: one "(previously SimpleConfig)" line in `getting_started.md`, and the file `docs/Extend Simple Config.md` still bears its legacy filename (its *content* is already titled "Extend SimpleSettings") with six inbound links pointing at it. In metadata, `Directory.Build.props` has the `` "appliaction" typo and a `SimpleConfig` `` token; repo/package URLs are already canonical. + +**Primary recommendation:** Rewrite the README against the verified API table in this document (never trust the old README); do a targeted purge (not a rewrite) of the six docs pages; rename `docs/Extend Simple Config.md` and repoint its six inbound links; fix the two `Directory.Build.props` tokens. Gate the phase on the mechanical DOC-VERIFICATION grep/build checks in the Validation Architecture section. Every security/behavior claim must be copied from the invariants documented here (sourced from Phase 4 `SECURITY.md`), because a paraphrase risks over-promising a guarantee. + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions + +**README shape — modernize, self-contained** +- **D-01:** Rewrite `README.md` to a modern, self-contained OSS shape: (1) canonical title (drop the "(previously SimpleConfig)" tag); (2) **install** via `dotnet add package ExistForAll.SimpleSettings` (plus `.Binders` / `.Extensions.GenericHost` where relevant), replacing the legacy `Install-Package` PMC command; (3) a ~30-second **quickstart** with a CORRECT minimal example; (4) a short **feature overview**; (5) **links into `docs/`** for depth. Trim the long "why `IOptions<>` is bad" polemic to a tight blurb — keep the positioning, drop the wall of text. +- **D-02 (correctness, not just naming):** The README code example MUST reflect the **real API** — a settings **interface** (discovered via `ISettingsSection` base / `[SettingsSection]` / `Settings` suffix) with **`[SettingsProperty(DefaultValue = "…")]`**, NOT the stale `[DefaultValue("…")]` shown today (that attribute form is factually wrong). Verify every API token against current source before writing. +- **D-03:** Fix all consumer-facing surface in the README: the broken `existall/Shepherd` logo, every dead `existall/SimpleConfig/blob/master/docs/*` ToC link (repoint to `existall/SimpleSettings`), the install command, and prose typos. + +**Guidance placement — concise in README, deep in docs/** +- **D-04:** The README carries a **concise** "Security notes" + "Breaking changes / migration" section (visible to every nuget.org reader); the **deep detail lives in `docs/`**. The mandated content (LOCKED by the Phase-4 security sign-off — `04-CONTEXT` D-06/D-12, Phase 4 `SECURITY.md`) that MUST be documented: + - **Secret-redaction invariant** — conversion/bind failures never surface the bound value or chain a value-bearing inner (S1/SEC-01). + - **Validator authors must not echo secrets** — neither in `ValidationError` message text NOR in **validator constructors** (DI resolution runs *outside* the value-free guard, so a ctor that logs an injected secret leaks it). + - **DI-path `ValidateSimpleSettings()` is opt-in / deferred** — must be called *after* `BuildServiceProvider()`; attribute / `ValidatorType` validators run **inline** in the bind pipeline. + - **validate ⇒ discoverable coupling** — `[SettingsSection(ValidatorType=…)]` (object-level validator) also makes the type scan-discovered. + - **Spaced secrets bind via `AddCommandLine`** — a quoted value containing spaces arrives as its own token; document the `--key value` lookahead + `arg[0]` skip behavior (SRC-02). + - **Phase-3 breaking-change list** — `SettingsHolder`/`ISettingsHolder` internal (API-01); `Core.AspNet` package dropped (PKG-01); per-TFM `Microsoft.Extensions.*` floor (PKG-02); public exception hierarchy (`SimpleSettingsException` base, EXC-01). + +**docs/ — refresh all six in place** +- **D-05:** Update **all six** `docs/` files in place: purge `SimpleConfig` product naming + dead `existall/SimpleConfig` links (repoint to `existall/SimpleSettings`), **rename `docs/Extend Simple Config.md`** to a canonical name (e.g. `Extending SimpleSettings.md`) and fix inbound links, correct stale API references (e.g. `[DefaultValue]`), and add the deep security/behavior + migration content the README summarizes. Preserve existing page structure/history (in-place, not a restructure). + +**Metadata — fix alongside docs** +- **D-06:** Fix `src/Directory.Build.props` `` typos ("appliaction" → "application"; the "decouples the frameworks from your appliaction" prose), canonicalize/replace the broken `existall/Shepherd` logo reference, and verify ``/`` (already `existall/SimpleSettings`) + the three `ExistForAll.*` NuGet package links resolve. + +### Claude's Discretion +- Exact README section ordering/wording and how far to trim the IOptions polemic; the precise canonical name for the renamed `Extend Simple Config.md`; whether "migration" is a README subsection vs a dedicated `docs/` page; which `docs/` page holds the deep security guidance; whether to add a `CHANGELOG.md`; whether to add a CI legacy-reference / dead-link check (nice-to-have — not required by the criteria). + +### Deferred Ideas (OUT OF SCOPE) +- **AOT-01** — annotate reflection entry points (`[RequiresDynamicCode]`/`[RequiresUnreferencedCode]`) and/or document the AOT/trim limitation. **Deferred to a future v2.1 milestone.** Non-breaking to add later. +- **Validator-dispatch caching** — deferred perf follow-up (code-review M2). A code change, not docs; not Phase 5. +- **REL-01** — cut the first `v2.0.0-beta`. Phase 6. +- **EQ-01** (HELD), **PERF-03** (deferred), **`${ENV:-}` placeholder detection** (VAL-02/D-13, deferred) — out of scope. + + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| DOC-01 | Refresh README + docs/ to canonical `ExistForAll.SimpleSettings` naming and current repo/package links; document the Phase 1–4 security/behavior guidance (secret-redaction; validator secret-safety incl. constructors; opt-in/deferred DI validation; validate⇒discoverable coupling; spaced-secrets binding; Phase-3 breaking-change list) | Legacy-Reference Inventory gives every file:line to fix; Current Public API (Ground Truth) gives copy-pasteable correct tokens for examples; Phase 1–4 Behavior Facts gives the exact, source-cited security/behavior invariants to document; Validation Architecture gives falsifiable DOC-VERIFICATION gates that assert DOC-01 completeness. | + + +## Project Constraints (from CLAUDE.md) + +No project `./CLAUDE.md` or `./.claude/CLAUDE.md` file exists (config references `./.claude/CLAUDE.md` but the file is absent). The governing directives therefore come from the org-level policy and user memory: + +- **No emojis in code/comments.** README is the documented exception (emojis permitted in README prose if desired). Docs `.md` files are documentation — treat prose freely, but keep code fences emoji-free. +- **Meaningful names, minimal comments** in any code snippet the docs show. +- **Build/test from `src/`** — `dotnet` commands run from the `src/` directory (test stack: TUnit on Microsoft.Testing.Platform). +- **Settings interfaces in examples must be `public`** — the proxy generator emits a runtime impl of the interface and cannot implement an `internal` interface (random-GUID dynamic assembly). Every example settings interface must be `public`. +- **No Claude attribution** in commits/PRs (omit Co-Authored-By / Generated-with). +- **Push/PR via the `guy-lud` account** (SSH alias `github-guy-lud`); never commit to `master`. +- **Ship on a feature branch** off `gsd/phase-4-closeout` so the held Phase-4 closeout docs ride to master on Phase 5's PR (this phase changes package content via `PackageReadmeFile`, so it earns a real master alpha). + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|------------|-------------|----------------|-----------| +| Consumer-facing narrative + quickstart | `README.md` (packaged via `PackageReadmeFile`) | `docs/` | README ships in every `.nupkg` and is the nuget.org + GitHub landing page; it is the first read. | +| Deep API / extension / security reference | `docs/*.md` | — | D-04 places concise guidance in README, depth in docs/. | +| Package identity + logo + description | `src/Directory.Build.props` | — | Shared MSBuild props inject ``, URLs, icon, README into every published package. | +| API ground truth (the source of every claim) | `src/Core/**` source files | — | The library source is the single authority for what the current public API is; docs must match it, not the reverse. | +| Security/behavior invariants to document | `.planning/phases/04-*/SECURITY.md` + Phase 3/4 CONTEXT | Core exception source | The security sign-off is authoritative for the exact guarantee wording; over-claiming in docs is a real risk. | + +## Current Public API (Ground Truth) + +> Every token below is a direct read of source. Use these EXACT spellings in examples. Do not trust the current README (it is wrong on `[DefaultValue]`). + +### Settings-interface declaration (three mechanisms — pick one) + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/ISettingsSection.cs:3]` `public interface ISettingsSection {}` — empty marker base interface (namespace `ExistForAll.SimpleSettings`). + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsSectionAttribute.cs:4-17]` `[SettingsSection]` — `[AttributeUsage(AttributeTargets.Interface)]`, class `SettingsSectionAttribute`. Members: `string? Name { get; set; }`, `Type? ValidatorType { get; set; }`. Ctors: `SettingsSectionAttribute()` and `SettingsSectionAttribute(string name)`. + +`Settings` name-suffix — an interface whose name ends in `Settings` is discovered (default suffix, configurable). `[VERIFIED: docs/Extend Simple Config.md:11]` (default `SettingsSuffix = "Settings"` in `SettingsOptions`). + +**Interfaces must be `public`** (only exported interfaces are scanned) `[VERIFIED: docs/building_the_collection.md:12]` and the proxy generator cannot implement a non-public interface (memory: proxy internal-interface limit). + +### `[SettingsProperty]` — exact members + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsPropertyAttribute.cs:4-22]` — `[AttributeUsage(AttributeTargets.Property)]`, class `SettingsPropertyAttribute`: + +| Member | Type | Default | Notes | +|--------|------|---------|-------| +| `DefaultValue` | `object?` | `null` | The correct form is `[SettingsProperty(DefaultValue = "…")]` — NOT `[DefaultValue("…")]`. | +| `Name` | `string?` | `null` | Overrides the binder lookup key (defaults to the property name). | +| `ConverterType` | `Type?` | `null` | Per-property `ISettingsTypeConverter`. | +| `AllowEmpty` | `bool` | `true` | When `false`, binding throws `SettingsPropertyNullException` if no value resolves. | +| `ValidatorType` | `Type?` | `null` | Per-property `ISettingValidation`; declaring it makes the type discoverable. | + +Ctors: `SettingsPropertyAttribute(string name)` and `SettingsPropertyAttribute()`. + +### Direct API — `SettingsBuilder` + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsBuilder.cs]` (namespace `ExistForAll.SimpleSettings`, `public class SettingsBuilder`): +- `static SettingsBuilder CreateBuilder(Action buildAction)` (`:44`) +- `static SettingsBuilder CreateBuilder()` (`:55`) +- `object GetSettings(Type settingsType)` (`:62`) — throws `SettingsTypeNotInterfaceException` for a non-interface. +- `ISettingsCollection ScanAssemblies(IEnumerable assemblies)` (`:74`) + +Generic + params helpers `[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsBuilderExtensions.cs]`: +- `ISettingsCollection ScanAssemblies(this SettingsBuilder, Assembly assembly, params Assembly[] assemblies)` (`:7`) +- `T GetSettings(this SettingsBuilder) where T : class` (`:21`) + +`ISettingsBuilderFactory` `[VERIFIED: src/Core/ExistForAll.SimpleSettings/ISettingsBuilderFactory.cs]`: `void AddSectionBinder(ISectionBinder)`, `SettingsOptions Options { get; }`. + +Factory extension helpers `[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsBuilderFactoryExtensions.cs]` (all generic ` where T : ISettingsBuilderFactory`, all return `target`): `AddTypeConverter(ISettingsTypeConverter)` (`:8`), `AddInMemoryCollection(InMemoryCollection)` (`:15`), `SetupOptions(Action)` (`:23`), `SetSettingsSuffix(string)` (`:30`), `SetArraySplitDelimiter(string)` (`:36`), `SetAttributeType(Type)` (`:43`), `SetDateTimeFormat(string)` (`:50`), `SetInterfaceBase(Type)` (`:56`), `SetSectionNameFormatter(Func)` (`:62`). + +### `ISettingsCollection` + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/ISettingsCollection.cs]` — `public interface ISettingsCollection : IEnumerable>`: +- `object GetSettings(Type type)` +- `bool TryGetSettings(Type type, out object? settings)` + +Generic helper `[VERIFIED: src/Core/ExistForAll.SimpleSettings/SettingsCollectionExtensions.cs:5]`: `T GetSettings(this ISettingsCollection) where T : class`. + +### DI entry points (`ExistForAll.SimpleSettings.Extensions.GenericHost`) + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings.Extensions.GenericHost/ServicesSettingsBuilderExtensions.cs]` — `public static class ServicesSettingsBuilderExtensions`, all extend `IServiceCollection`: +- `AddSimpleSettings(this IServiceCollection)` (`:7`) +- `AddSimpleSettings(this IServiceCollection, Action action)` (`:13`) +- `AddSimpleSettings(this IServiceCollection, out ISettingsCollection settings, Action? action = null)` (`:21`) — the API-02 exposure overload. + +Registration side-effects (`:46-53`): each scanned interface added as a singleton of its startup-built instance; plus singletons for `ISettingsCollection`, `ISettingsProvider` (a `SettingsProvider`), and `ISettingsValidationRunner` (a `SettingsValidationRunner`). No validators are invoked at registration time. + +`ISettingsBuilderOptions` `[VERIFIED: .../ISettingsBuilderOptions.cs]`: `interface ISettingsBuilderOptions : ISettingsBuilderFactory { void AddAssemblies(IEnumerable assemblies); }`. Helper extensions `[VERIFIED: .../SettingsBuilderOptionsExtensions.cs]`: `AddAssembly(Assembly)`, `AddAssembly()`, `AddAssemblies(Assembly, params Assembly[])`. + +**Opt-in / deferred DI validation** `[VERIFIED: src/Core/ExistForAll.SimpleSettings.Extensions.GenericHost/ServiceProviderValidationExtensions.cs:20]`: +``` +public static IServiceProvider ValidateSimpleSettings(this IServiceProvider provider) +``` +- **Exact receiver:** extends `IServiceProvider` (NOT `IServiceCollection`, NOT `IHost`). +- **Namespace:** `ExistForAll.SimpleSettings.Extensions.GenericHost`, class `ServiceProviderValidationExtensions`. +- **Returns** the same `IServiceProvider` (chainable; returns `provider` at `:29`). +- Throws `InvalidOperationException` if `AddSimpleSettings` was never called (`:24-26`). +- Must be called **after** `BuildServiceProvider()`; the XML doc (`:12-19`) states it is opt-in and deferred, resolves validators from a **fresh scope** (scoped deps supported), and that attribute/`ValidatorType` validators run **inline during binding** and do NOT need this call. + +`ISettingsProvider` `[VERIFIED: .../ISettingsProvider.cs]`: `object GetSettings(Type type)`; generic helper `T GetSettings(this ISettingsProvider)` `[VERIFIED: .../SettingsProviderExtensions.cs:5]`. + +### Validator authoring surface (`ExistForAll.SimpleSettings.Validations`) + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/Validations/]`: +- `ISettingsValidator` (`ISettingsValidator.cs`): `ValidationResult Validate(ValidationContext context)`. +- `ISettingValidation : ISettingsValidator` (`ISettingValidation.cs`): `ValidationResult Validate(ValidationContext context)`. Authors implement ONLY the generic overload; a default interface method bridges the non-generic call (no reflection). +- `ValidationResult` (`ValidationResult.cs`): `IEnumerable Errors`, `bool IsValid`, `void AddError(ValidationError)`. +- `ValidationError` (`ValidationError.cs`): ctor `ValidationError(string settingsName, string errorMessage)`; props `SettingsName`, `ErrorMessage`. +- `ValidationContext` (`ValidationContext.cs`): `object? Settings`. `ValidationContext` (`ValidationContextOfT.cs`): `T? Settings`. + +### Command-line binder (`ExistForAll.SimpleSettings.Binders`) + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings.Extensions.Binders/SettingsBuilderFactoryExtensions.cs]`: +- `AddCommandLine(this T, Action? action = null)` (`:40`) — sources `Environment.GetCommandLineArgs()` and sets `SkipFirstArgument = true` internally (`:43-49`) because `GetCommandLineArgs()[0]` is the exe path. +- `AddArguments(this T, string[] args, Action? action = null)` (`:54`) — binds exactly the array handed in (`SkipFirstArgument` default `false`). + +`CommandLineSettingsBinderOptions` `[VERIFIED: .../CommandLineSettingsBinderOptions.cs]`: `ArgumentPrefixes` default `'-'`,`'/'`; `Delimiters` default `":"`,`"="`; `IsCaseSensitive = true`; `SkipFirstArgument = false`. Spaced-value behavior `[VERIFIED: CommandLineSettingsBinder.cs:32-63]`: for a prefixed key token with no inline delimiter, the parser looks ahead to the **next** token and binds it as the value **unless** that next token itself starts with a prefix char (then it is treated as a new key). So a quoted value containing spaces (`--Key "a b c"`) arrives from the shell as a single token and binds; but a *value* that begins with `-`/`/` will not bind via the space form — use the inline `--Key=value` delimiter form for those. + +### Exception hierarchy (for the secret-redaction section) + +`[VERIFIED: src/Core/ExistForAll.SimpleSettings/*.cs]`, all in namespace `ExistForAll.SimpleSettings`: +- `SimpleSettingsException` (`SimpleSettingsException.cs:8`) — `public abstract class ... : Exception`; the common catch-all base (EXC-01). Two forwarding ctors only; no `[Serializable]` (BinaryFormatter obsolete on net8/net10). +- `SettingsPropertyValueException` (`:13`) — conversion failure; carries `SettingsType`, `PropertyName`, `TargetType`, `ConversionErrorType` (the failure's `Type`, never the value or a chained inner). +- `SettingsPropertyNullException` (`:7`) — required-value-missing (`AllowEmpty = false`); carries `PropertyName` only (value-free by construction — there is no value). +- `SettingsValidationException` (`:10`) — aggregates `IReadOnlyList Errors`; message composed only from author text. Static `ThrowIfAny(errors)` is the shared throw point for both validation paths. +- `SettingsValidatorInvocationException` (`:6`) — a validator threw; carries `ValidatorType` + `FailureType` only. +- (also present: `SettingsPropertyExtractionException`, `SettingsTypeNotInterfaceException`.) + +### The three shipping packages + +`[VERIFIED: src/Core/**/*.csproj]` — three packable projects (all `TargetFrameworks = net8.0;net10.0`); the benchmark is `IsPackable = false`; **no `Core.AspNet` project exists** (dropped in Phase 3, PKG-01): +- `ExistForAll.SimpleSettings` (Core) +- `ExistForAll.SimpleSettings.Binders` (project file `ExistForAll.SimpleSettings.Binders.csproj`; namespace `ExistForAll.SimpleSettings.Binders`) +- `ExistForAll.SimpleSettings.Extensions.GenericHost` + +`Core.AspNet`-string hits exist only in **test fixtures** (`ASPNETCORE_ENVIRONMENT` env-var test, `UnitTests.Core.AspNet` test namespace) — NOT in consumer docs or shipping packages. No docs impact. + +## Legacy-Reference Inventory (complete, every file:line DOC-01 must fix) + +> Grepped README.md + docs/ + src/Directory.Build.props for `existall/SimpleConfig`, standalone `SimpleConfig`, `Install-Package`, `[DefaultValue]`, and the raw `Shepherd` logo URL. + +### `README.md` — the primary stale artifact (rewrite per D-01/D-02/D-03) + +| Line | Issue | Fix | +|------|-------|-----| +| 1 | `` — broken `existall/Shepherd` logo | Replace with canonical logo (local `icon.png` already packed, or a canonical raw URL) | +| 3 | Title `ExistForAll.SimpleSettings (previously SimpleConfig)` | Drop the "(previously SimpleConfig)" tag | +| 7 | `Install-Package ExistForAll.SimpleSettings` (legacy PMC) | `dotnet add package ExistForAll.SimpleSettings` | +| 11 | ToC link → `existall/SimpleConfig/blob/master/docs/getting_started.md` (dead) | repoint to `existall/SimpleSettings` | +| 12 | ToC link → `existall/SimpleConfig/.../building_the_collection.md` (dead) | repoint | +| 13 | ToC link → `existall/SimpleConfig/.../Build%20Config%20Interface.md` (dead) | repoint | +| 14 | ToC link → `existall/SimpleConfig/.../Default%20Values.md` (dead) | repoint | +| 15 | ToC link → `existall/SimpleConfig/.../Build%20a%20SectionBinder.md` (dead) | repoint | +| 16 | ToC link "Extending SimpleConfig" → `existall/SimpleConfig/.../Extend%20Simple%20Config.md` (dead + legacy filename) | repoint to `existall/SimpleSettings` + renamed file | +| 18 | Heading "Introduction - or why SimpleConfig was created" | rename product to SimpleSettings | +| 58 | "### TL;DR - or what SimpleConfig does?" | rename | +| 66 | `[DefaultValue("SomeUrl")]` — **factually wrong API** | `[SettingsProperty(DefaultValue = "SomeUrl")]` | +| 44, 60 | `IOption` — typo "Configuratation" (+ `IOption` should be `IOptions`) | fix in the trimmed polemic | +| 56 | broken URL `http://https://...` + typos "explenation", "SimpleInjecor" | fix or drop with the polemic trim | +| 53 | "scale-able" | prose cleanup (discretionary) | + +The README has **no** Phase 1–4 security/behavior guidance yet — D-04 content is entirely additive. + +### `docs/` — mostly already modernized; narrow residuals + +| File | Legacy residual | Action | +|------|-----------------|--------| +| `docs/getting_started.md:4` | "SimpleSettings (previously SimpleConfig)" | drop the parenthetical | +| `docs/Extend Simple Config.md` | **filename** is legacy (content is already titled "Extend SimpleSettings", links already canonical) | **rename** file (e.g. `Extending SimpleSettings.md`) per D-05 | +| all six pages | already use `existall/SimpleSettings` links and correct `[SettingsProperty(DefaultValue=…)]` — **no dead `existall/SimpleConfig` links remain in docs/** | verify, then add the D-04 deep guidance | + +**Inbound links to the renamed file (6 — must repoint on rename):** +- `README.md:16` (ToC) +- `docs/Build a SectionBinder.md:38` and `:60` +- `docs/Build Config Interface.md:39` +- `docs/building_the_collection.md:16` and `:45` + +(All six currently point at `.../docs/Extend%20Simple%20Config.md`; five already use the `existall/SimpleSettings` host, README:16 still uses `existall/SimpleConfig`.) + +### `src/Directory.Build.props` — metadata (D-06) + +| Line | Issue | Fix | +|------|-------|-----| +| 16 | `` typo "appliaction" (appears in "from your appliaction"); prose "decouples the frameworks from your appliaction" | "application"; tidy prose | +| 21 | `` ends with `..., Options, SimpleConfig` — legacy `SimpleConfig` tag | drop or replace with `SimpleSettings` | +| 17-18 | `` / `` = `https://github.com/existall/SimpleSettings` | **already canonical** — verify only | +| 22, 29-30 | `icon.png` packs `../icon.png` (repo-root `icon.png` exists, 8320 bytes) | canonical local icon; ensure README logo aligns (README:1 uses the broken Shepherd URL, props uses local icon) | + +**Note:** the broken `existall/Shepherd` logo lives in `README.md:1`, NOT in `Directory.Build.props` (props already uses the local `icon.png`). D-06's "canonicalize the logo" work is really a README fix; keep them consistent. + +## Phase 1–4 Behavior Facts to Document (source-cited — do not paraphrase loosely) + +> These are the D-04 mandated invariants. Wording is derived from Phase 4 `SECURITY.md` and the exception source. Documenting an over-strong guarantee is a real risk — keep the caveats. + +### Secret-redaction invariant (S1 / SEC-01) `[CITED: 04-collection-validation-binding/SECURITY.md:24-27]` +No bound configuration value (which may be a secret) appears in any library exception's `ToString()` chain that reaches logs. Every library exception is **value-free** (Type/property metadata only) and never chains an inner exception that saw the value. Concretely `[VERIFIED: SettingsPropertyValueException.cs:5-22]`: conversion failures surface only settings type, property name, target type, and the CLR **Type** of the converter's failure — the ctor takes the failure's `Type`, not the `Exception`, so the guarantee is *structural*, not conventional. + +### Validator authors must not echo secrets — incl. constructors `[CITED: SECURITY.md:75-78 (accepted residual)]` +The library's value-free guarantee does NOT extend to author-supplied text: `ValidationError.ErrorMessage` reaches `SettingsValidationException.ToString()` by design. If a validator author echoes a secret into their own error message, it surfaces. **Additionally**, DI-resolved validators are constructed by the container *outside* the value-free bind guard — so a validator **constructor** that logs/echoes an injected secret leaks it. Document both: (1) never put a bound value in `ValidationError` text; (2) never log secrets in a validator constructor. + +### DI-path `ValidateSimpleSettings()` is opt-in / deferred `[VERIFIED: ServiceProviderValidationExtensions.cs:20; CITED: SECURITY.md:49 (T-04-06)]` +DI-registered `ISettingValidation` validators cannot run during `AddSimpleSettings` (container not built yet). The host must call `provider.ValidateSimpleSettings()` explicitly **after** `BuildServiceProvider()`. Attribute/`ValidatorType` validators (`[SettingsSection(ValidatorType=…)]`, `[SettingsProperty(ValidatorType=…)]`) run **inline during binding** and need no such call. Registration adds only the runner singleton; it never invokes validators (proven: counter 0→1 test). + +### validate ⇒ discoverable coupling `[VERIFIED: SettingsSectionAttribute.cs:8; CITED: 04-CONTEXT D-11]` +Declaring an object-level validator via `[SettingsSection(ValidatorType = typeof(...))]` also marks the type as scan-discovered (it carries `SettingsSectionAttribute`). So attaching a validator to a type is sufficient to make it a discovered settings section — a side effect authors should know. + +### Spaced secrets bind via `AddCommandLine` (SRC-02) `[VERIFIED: CommandLineSettingsBinder.cs:32-63, CommandLineSettingsBinderOptions.cs:12-24]` +`AddCommandLine` sources `Environment.GetCommandLineArgs()` and skips `arg[0]` (the exe path) internally (`SkipFirstArgument = true`). For a prefixed key with no inline delimiter, the parser looks ahead to the next token and binds it as the value — **unless** that next token itself starts with a prefix char (`-`/`/`), in which case it is a new key. A shell-quoted value with spaces (`--Key "a b c"`) is one token and binds. `AddArguments(args)` binds the array as-is (`SkipFirstArgument` default `false`, because `Main(string[])` already excludes the exe). + +### Phase-3 breaking-change list (for the migration section) `[CITED: REQUIREMENTS.md:55-58 + 03-CONTEXT]` +- **API-01:** `SettingsHolder` / `ISettingsHolder` made **internal** (breaking). +- **PKG-01:** `Core.AspNet` package **dropped** (it exposed no public type). +- **PKG-02:** `Microsoft.Extensions.*` dependency floor **per-TFM** (`8.0.x` on net8, current on net10). +- **EXC-01:** public **`abstract SimpleSettingsException`** base; boundary exceptions made public + structured; the bare `Exception` throw removed. `SettingsTypeNotInterfaceException` replaces the old `TypeIsNotInterface` throw. + +## Standard Stack + +This phase writes documentation and edits MSBuild metadata — it installs **no packages** and adds **no runtime code**. The only tools are the existing toolchain. + +### Core (tools, not libraries) +| Tool | Version | Purpose | Why Standard | +|------|---------|---------|--------------| +| `dotnet` SDK | 10.0.301 (verified present) | `dotnet build` / `dotnet pack` sanity for the metadata + README-packaging change | Repo targets `net8.0;net10.0`; SDK already the build authority | +| `grep` / `ripgrep` | present (both) | Legacy-reference DOC-VERIFICATION gates | Mechanical, falsifiable, zero-dependency | + +### Alternatives Considered +| Instead of | Could Use | Tradeoff | +|------------|-----------|----------| +| Hand-maintained ToC | a docs generator (DocFX) | Out of scope — D-05 says refresh in place, not restructure; not worth the tooling weight for six pages | +| Manual dead-link check | a markdown link-checker CI action | Discretionary (D-05 lists CI link-check as nice-to-have); the grep gate covers the criteria | + +**Installation:** none. (No `npm install` / `dotnet add package` — documentation-only.) + +## Package Legitimacy Audit + +**Not applicable — this phase installs no external packages.** It edits documentation (`README.md`, `docs/*.md`) and MSBuild metadata (`src/Directory.Build.props`) only; no `` is added or changed. No SLOP/SUS surface exists. + +## Architecture Patterns + +### Documentation Flow + +``` + src/Core/** (library source — the SINGLE source of truth) + │ (verify every token against this, never the old README) + ▼ + ┌──────────────────────────────────────────────────────────┐ + │ Authoring pass │ + │ │ + │ README.md ──concise──► Security notes + Migration │ + │ │ links into │ + │ ▼ │ + │ docs/*.md ──deep──► full security/behavior + migration │ + │ ▲ │ + │ Directory.Build.props (Description, URLs, icon, README) │ + └──────────────────────────────────────────────────────────┘ + │ dotnet pack + ▼ + README.md + icon.png + ──► .nupkg ──► nuget.org page + docs/*.md ──► GitHub repo (existall/SimpleSettings) +``` + +Entry point for a consumer = the README (nuget.org + GitHub landing). README links resolve into `docs/` on `existall/SimpleSettings`. `Directory.Build.props` injects the packaged README + icon + description into all three packages. + +### Pattern 1: Verify-against-source before writing any example +**What:** For every API token an example uses, confirm the exact spelling/signature in the cited source file before writing it. +**When to use:** Every code fence in README/docs. +**Example:** The current README shows `[DefaultValue("SomeUrl")]`; source proves the real form is `[SettingsProperty(DefaultValue = "SomeUrl")]` `[VERIFIED: SettingsPropertyAttribute.cs:6]`. + +### Pattern 2: Concise-in-README, deep-in-docs (D-04) +**What:** README gets a short "Security notes" + "Breaking changes / migration" section; the full treatment lives in a docs/ page. +**When to use:** All the Phase 1–4 mandated guidance. + +### Pattern 3: Rename-with-backlink-repoint +**What:** When renaming `docs/Extend Simple Config.md`, update all six inbound links in the same change. +**When to use:** The file rename in D-05. A DOC-VERIFICATION gate must assert zero links point at the old filename. + +### Anti-Patterns to Avoid +- **Copying the old README's example forward** — it is factually wrong (`[DefaultValue]`). Rebuild from the verified API table. +- **Over-claiming the redaction guarantee** — it does NOT cover author-supplied `ValidationError` text or validator constructors. State the caveat. +- **Restructuring docs/** — D-05 says in-place refresh; preserve page structure/history. +- **Documenting `Core.AspNet` as available** — it was dropped (PKG-01). +- **Showing an `internal` example interface** — the proxy generator cannot implement it; examples must be `public`. + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Confirming the current API | Trust memory / the old README | Read the cited source file | The old README is already provably wrong; only source is authoritative | +| Legacy-reference completeness | Eyeballing the files | `grep`/`rg` gates (Validation Architecture) | Mechanical, falsifiable, repeatable | +| Package metadata plumbing | New props/targets | Existing `Directory.Build.props` | It already injects README+icon+description into all packages | + +**Key insight:** In a docs-accuracy phase the "custom solution" trap is *paraphrasing from memory*. Every claim must trace to a source file:line or a Phase 4 sign-off line. + +## Runtime State Inventory + +> This is a documentation/metadata phase, not a rename/refactor of runtime code. The one rename is a **documentation file** (`docs/Extend Simple Config.md`), which has no runtime state — only inbound markdown links. + +| Category | Items Found | Action Required | +|----------|-------------|------------------| +| Stored data | None — no datastore keys/collections reference any doc name (verified: docs are static files) | none | +| Live service config | None — no external service embeds a doc filename (verified: repo is a library, no live services) | none | +| OS-registered state | None — no OS registration references docs (verified) | none | +| Secrets/env vars | None — the only `ASPNETCORE_ENVIRONMENT` hits are test fixtures, unrelated to docs (verified grep) | none | +| Build artifacts | `icon.png` (repo root) is packed into each `.nupkg` via `Directory.Build.props:30`; README is packed via `:29` — a README/metadata edit changes package **content** (rebuild/repack picks it up automatically, no stale artifact) | `dotnet pack` sanity check | +| Doc cross-links (the rename) | 6 inbound links to `Extend%20Simple%20Config.md` (README:16; Build a SectionBinder.md:38,60; Build Config Interface.md:39; building_the_collection.md:16,45) | repoint all six when the file is renamed | + +## Common Pitfalls + +### Pitfall 1: Trusting the current README example +**What goes wrong:** Copying `[DefaultValue("SomeUrl")]` forward — it does not compile against the real API. +**Why it happens:** The README predates the `[SettingsProperty]` API; it was never corrected. +**How to avoid:** Use `[SettingsProperty(DefaultValue = "…")]` from `SettingsPropertyAttribute.cs`. Add a DOC-VERIFICATION gate: `[DefaultValue(` returns zero hits in README+docs. +**Warning signs:** Any `[DefaultValue(` token; any `Install-Package`. + +### Pitfall 2: Renaming the docs file but leaving dangling links +**What goes wrong:** `docs/Extend Simple Config.md` renamed, but the 6 inbound links 404. +**How to avoid:** Repoint all six in the same change; gate on zero references to the old filename. +**Warning signs:** grep for `Extend%20Simple%20Config` returns > 0 after the rename. + +### Pitfall 3: `ValidateSimpleSettings()` documented on the wrong receiver +**What goes wrong:** Writing `services.ValidateSimpleSettings()` or `host.ValidateSimpleSettings()`. +**Why it happens:** It reads like a registration call. +**How to avoid:** It extends **`IServiceProvider`** and runs **after** `BuildServiceProvider()`. Verified at `ServiceProviderValidationExtensions.cs:20`. +**Warning signs:** the call shown before the provider is built. + +### Pitfall 4: Over-stating the redaction guarantee +**What goes wrong:** Docs claim "secrets never appear in any exception," omitting the author-text and constructor carve-outs. +**How to avoid:** Always pair the invariant with the two caveats (validator message text + validator constructor). Sourced from `SECURITY.md:75-78`. + +### Pitfall 5: Spaced-value command-line claim too broad +**What goes wrong:** Claiming any spaced value binds. A value beginning with `-`/`/` is treated as a new key. +**How to avoid:** State the lookahead rule precisely (`CommandLineSettingsBinder.cs:55-59`); recommend the inline `--Key=value` form for prefix-leading values. + +## Code Examples + +> Copy-pasteable, verified against source. Use these in the README quickstart / docs. + +### Minimal quickstart (direct API) — the CORRECT replacement for the stale README example +```csharp +// Source: SettingsPropertyAttribute.cs, SettingsSectionAttribute.cs, SettingsBuilder.cs, SettingsBuilderExtensions.cs +[SettingsSection] +public interface IEmailSenderSettings +{ + [SettingsProperty(DefaultValue = "https://smtp.example.com")] + string ServiceUrl { get; set; } + + [SettingsProperty(DefaultValue = 3)] + int Retries { get; set; } +} + +var settings = SettingsBuilder + .CreateBuilder() + .GetSettings(); +``` + +### DI (generic host) +```csharp +// Source: ServicesSettingsBuilderExtensions.cs, SettingsBuilderOptionsExtensions.cs +services.AddSimpleSettings(o => +{ + o.AddAssemblies(new[] { typeof(IEmailSenderSettings).Assembly }); +}); + +// after building the provider — opt-in, deferred DI validation: +// Source: ServiceProviderValidationExtensions.cs:20 +serviceProvider.ValidateSimpleSettings(); +``` + +### Install (replaces `Install-Package`) +```bash +dotnet add package ExistForAll.SimpleSettings +dotnet add package ExistForAll.SimpleSettings.Binders +dotnet add package ExistForAll.SimpleSettings.Extensions.GenericHost +``` + +### A validator (secret-safe) +```csharp +// Source: Validations/ISettingValidation.cs, ValidationError.cs, ValidationResult.cs +public class EmailSettingsValidator : ISettingValidation +{ + public ValidationResult Validate(ValidationContext context) + { + var result = new ValidationResult(); + if (context.Settings!.Retries < 0) + result.AddError(new ValidationError(nameof(IEmailSenderSettings.Retries), "Retries must be >= 0")); + return result; // never put a bound value (possible secret) in the message + } +} +``` + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| `[DefaultValue("…")]` | `[SettingsProperty(DefaultValue = "…")]` | current API (pre-Phase-5) | README example must change | +| `Install-Package …` (PMC) | `dotnet add package …` | modern .NET tooling | README install must change | +| `SimpleConfig` product / `existall/SimpleConfig` repo | `ExistForAll.SimpleSettings` / `existall/SimpleSettings` | NAME-01 (shipped) | all naming + links | +| `Core.AspNet` package | dropped | Phase 3 (PKG-01) | do not document it | +| bare/ad-hoc exceptions | `abstract SimpleSettingsException` family | Phase 1 (EXC-01) | document the catch-all base | + +**Deprecated/outdated:** +- The entire current `README.md` body (naming, install, example, links, logo) — stale. +- Any reference to `SimpleConfig` as a product name or repo. + +## Assumptions Log + +| # | Claim | Section | Risk if Wrong | +|---|-------|---------|---------------| +| A1 | The `existall` GitHub org and the three `ExistForAll.*` package pages resolve on nuget.org | Metadata / D-06 | Docs link to a 404. Mitigation: the DOC-VERIFICATION link check + a manual/CI resolve; URLs in props are already canonical (`existall/SimpleSettings`). | +| A2 | Replacing the README logo with the packed local `icon.png` (or a canonical raw URL) is acceptable as "canonicalize the logo" | README / D-06 | Wrong logo choice. Low risk — discretion is granted (D-06 says "canonicalize/replace"); confirm the intended logo asset with the user if ambiguous. | + +**Everything else in this research is `[VERIFIED]` against source or `[CITED]` from a Phase 3/4 sign-off — no user confirmation needed for the API facts, the legacy inventory, or the security invariants.** + +## Open Questions + +1. **Which docs/ page holds the deep security guidance?** + - What we know: D-04 says depth lives in docs/; D-05 says refresh in place. `Extend Simple Config.md` (extension/behavior) or a new dedicated page are both viable. + - What's unclear: page placement (explicitly Claude's discretion). + - Recommendation: add a "Security & behavior" section to the renamed extend page, or a short new `docs/Security.md` if it grows; planner picks. +2. **README logo asset** (see A2). + - Recommendation: default to the packed local `icon.png`; confirm with user if a hosted brand logo is preferred. + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|------------|------------|-----------|---------|----------| +| `dotnet` SDK | `dotnet build`/`pack` metadata sanity | ✓ | 10.0.301 | — | +| `grep` | legacy-reference gates | ✓ | system | `rg` | +| `ripgrep` (`rg`) | legacy-reference gates | ✓ | present | `grep` | + +**Missing dependencies with no fallback:** none. +**Missing dependencies with fallback:** none needed. + +## Validation Architecture + +> Nyquist is enabled. This is a docs phase — validation is framed as **DOC-VERIFICATION gates**, not unit tests. Each is a falsifiable command/assertion a plan-checker or executor can run. Run all from the repo root unless noted. + +### Test "Framework" +| Property | Value | +|----------|-------| +| Framework | Shell grep/`rg` assertions + `dotnet build`/`pack` (no unit-test framework needed) | +| Config file | none — assertions are inline commands | +| Quick run command | the grep gates below (sub-second) | +| Full suite command | grep gates + `dotnet build -c Release` from `src/` + `dotnet pack -c Release` from `src/` | +| Phase gate | all grep gates return the expected count AND `dotnet pack` succeeds before `/gsd-verify-work` | + +### Phase Requirements → DOC-VERIFICATION Map +| Req ID | Behavior | Check type | Automated command (expect the stated result) | +|--------|----------|-----------|-----------------------------------------------| +| DOC-01 | No legacy repo links | grep gate | `grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props` → **0 hits** | +| DOC-01 | No standalone `SimpleConfig` product/tag references | grep gate | `grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props` → **0 hits** | +| DOC-01 | No legacy PMC install command | grep gate | `grep -rn "Install-Package" README.md docs/` → **0 hits** | +| DOC-01 | No stale attribute form | grep gate | `grep -rn "\[DefaultValue" README.md docs/` → **0 hits** | +| DOC-01 | No broken Shepherd logo | grep gate | `grep -rn "existall/Shepherd" README.md src/Directory.Build.props` → **0 hits** | +| DOC-01 | Description typo fixed | grep gate | `grep -n "appliaction" src/Directory.Build.props` → **0 hits** | +| DOC-01 | Renamed doc file — no dangling links | grep gate | `grep -rn "Extend%20Simple%20Config" README.md docs/` → **0 hits** (after rename) AND the new file exists | +| DOC-01 | Canonical repo URL present | grep gate | `grep -rn "existall/SimpleSettings" README.md docs/ src/Directory.Build.props` → **≥ 1 hit per doc that links out** | +| DOC-01 | Three package IDs canonical + correct install verb | grep gate | README contains `dotnet add package ExistForAll.SimpleSettings`, `...Binders`, `...Extensions.GenericHost` | +| DOC-01 | Example uses real API tokens | token match | README/docs examples contain `[SettingsProperty(DefaultValue` and `SettingsBuilder.CreateBuilder` and `AddSimpleSettings`; `ValidateSimpleSettings()` shown on an `IServiceProvider` | +| DOC-01 | Mandated guidance present | presence check | README + docs together mention: secret-redaction, validator secret-safety (incl. constructor), opt-in/deferred `ValidateSimpleSettings`, validate⇒discoverable, `AddCommandLine` spaced values, and the Phase-3 breaking-change list (API-01/PKG-01/PKG-02/EXC-01) | +| DOC-01 | Internal doc links resolve | link check | every relative/`existall/SimpleSettings/blob/master/docs/` link resolves to an existing `docs/` file | +| DOC-01 | Package content still builds/packs | build gate | from `src/`: `dotnet build -c Release` and `dotnet pack -c Release` succeed (README+icon+Description repack cleanly) | + +### Sampling Rate +- **Per task commit:** the relevant grep gate(s) for the file just edited. +- **Per wave merge:** full grep-gate sweep. +- **Phase gate:** full grep sweep + `dotnet build`/`pack` from `src/` green. + +### Wave 0 Gaps +- [ ] No test-file gaps — validation is grep/build-based, no fixtures needed. +- [ ] (Discretionary) A CI legacy-reference + dead-link check could wrap these gates — D-05 marks it nice-to-have, not required. + +*If a plan wires the grep sweep as a script, place it under a scratch/CI path — do not add runtime code.* + +## Security Domain + +> `security_enforcement: true`, `security_asvs_level: 1`. This is a documentation phase — it introduces **no code, no new attack surface, no packages**. The security relevance is *documenting existing guarantees correctly*, not enforcing new controls. + +### Applicable ASVS Categories + +| ASVS Category | Applies | Standard Control | +|---------------|---------|-----------------| +| V2 Authentication | no | n/a — docs | +| V3 Session Management | no | n/a — docs | +| V4 Access Control | no | n/a — docs | +| V5 Input Validation | no (no runtime input) | n/a — docs; but the docs must correctly describe the library's own validation surface | +| V6 Cryptography | no | n/a | +| V7 Error Handling & Logging | **yes (documentation of an existing control)** | Accurately document the secret-redaction invariant (value-free exceptions) and its two caveats (validator message text + validator constructor). Do not over-claim. | + +### Known Threat Patterns for this phase + +| Pattern | STRIDE | Standard Mitigation | +|---------|--------|---------------------| +| Docs over-claim the redaction guarantee → author relies on it and echoes a secret in a `ValidationError` | Information Disclosure | Document the caveat explicitly (SECURITY.md:75-78): author text and validator constructors are outside the value-free guard | +| Docs show a secret-leaking example (value in exception/log/validator message) | Information Disclosure | Security/code-review pass on finished docs: no example echoes a bound value; validator examples never log injected secrets | +| Docs advise the insecure `AddCommandLine` spaced-value shape for a secret that starts with `-`/`/` | Information Disclosure / correctness | Document the lookahead rule and recommend inline `--Key=value` for prefix-leading values | + +**Recommended finished-docs review (per CONTEXT specifics):** a light `security-auditor`/`code-reviewer` pass confirming (1) no example echoes a secret, (2) all six mandated guidance items are present and correctly caveated. + +## Sources + +### Primary (HIGH confidence — direct source reads) +- `src/Core/ExistForAll.SimpleSettings/*.cs` — `SettingsBuilder`, `SettingsPropertyAttribute`, `SettingsSectionAttribute`, `ISettingsSection`, `ISettingsCollection`, exception hierarchy, `SettingsBuilder*Extensions` +- `src/Core/ExistForAll.SimpleSettings/Validations/*.cs` — validator authoring surface +- `src/Core/ExistForAll.SimpleSettings.Extensions.GenericHost/*.cs` — `AddSimpleSettings`, `ValidateSimpleSettings`, `ISettingsProvider` +- `src/Core/ExistForAll.SimpleSettings.Extensions.Binders/*.cs` — `AddCommandLine`/`AddArguments`, `CommandLineSettingsBinder(Options)` +- `src/Directory.Build.props`, three `*.csproj` files — package identity + metadata +- `README.md`, all six `docs/*.md` — legacy-reference inventory (grepped) +- `.planning/phases/04-collection-validation-binding/SECURITY.md` — secret-redaction invariant + residuals + +### Secondary (MEDIUM confidence) +- `.planning/REQUIREMENTS.md`, `03-CONTEXT.md`, `04-CONTEXT.md` — Phase-3 breaking-change list, D-06/D-11/D-12 + +### Tertiary (LOW confidence) +- none — no web/training claims in this research + +## Metadata + +**Confidence breakdown:** +- Current public API: HIGH — every token read from the cited source file:line this session. +- Legacy-reference inventory: HIGH — exhaustive grep across README + docs/ + props. +- Security/behavior invariants: HIGH — cited from Phase 4 `SECURITY.md` + exception source. +- Validation gates: HIGH — commands were run this session and returned the documented counts. +- Metadata (URL resolution on nuget.org): MEDIUM — URLs are canonical in-repo; external resolution is A1. + +**Research date:** 2026-07-20 +**Valid until:** ~2026-08-19 (30 days — stable; re-verify API tokens if Core source changes before planning) From 6dc1bf9cd76d2d87da03b2dbf8ed986faea38f17 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:32:32 +0300 Subject: [PATCH 07/23] docs(phase-5): add validation strategy --- .../phases/05-documentation/05-VALIDATION.md | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .planning/phases/05-documentation/05-VALIDATION.md diff --git a/.planning/phases/05-documentation/05-VALIDATION.md b/.planning/phases/05-documentation/05-VALIDATION.md new file mode 100644 index 0000000..f129137 --- /dev/null +++ b/.planning/phases/05-documentation/05-VALIDATION.md @@ -0,0 +1,92 @@ +--- +phase: 5 +slug: documentation +status: draft +nyquist_compliant: true +wave_0_complete: true +created: 2026-07-19 +--- + +# Phase 5 — Validation Strategy + +> Per-phase validation contract for feedback sampling during execution. +> This is a **documentation** phase — validation is framed as falsifiable **DOC-VERIFICATION gates** +> (shell grep/assertions + `dotnet build`/`pack`), not unit tests. Source: `05-RESEARCH.md` ## Validation Architecture. + +--- + +## Test Infrastructure + +| Property | Value | +|----------|-------| +| **Framework** | Shell `grep`/`rg` assertions + `dotnet build` / `dotnet pack` (no unit-test framework needed) | +| **Config file** | none — assertions are inline commands | +| **Quick run command** | the relevant grep gate(s) for the edited file (sub-second) | +| **Full suite command** | full grep-gate sweep + `dotnet build -c Release` + `dotnet pack -c Release` (both from `src/`) | +| **Estimated runtime** | grep gates ~1s; `build`+`pack` ~30–90s | + +*Run grep gates from the repo root; run `dotnet` from `src/` (global.json → Microsoft.Testing.Platform).* + +--- + +## Sampling Rate + +- **After every task commit:** Run the grep gate(s) for the file just edited. +- **After every plan wave:** Run the full grep-gate sweep. +- **Before `/gsd-verify-work`:** Full grep sweep returns every expected count AND `dotnet build`/`pack` from `src/` succeed (README + icon + `` repack cleanly). +- **Max feedback latency:** ~90 seconds (build/pack); grep gates are sub-second. + +--- + +## Per-Requirement Verification Map + +Single phase requirement (**DOC-01**); the 13 gates below are its acceptance sweep. Per-task rows are finalized against the plan's tasks at execution — every task's `` verify must be one (or more) of these gates. + +| # | Requirement | Behavior | Check type | Automated command (expect stated result) | Status | +|---|-------------|----------|-----------|-------------------------------------------|--------| +| 1 | DOC-01 | No legacy repo links | grep gate | `grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props` → **0 hits** | ⬜ pending | +| 2 | DOC-01 | No standalone `SimpleConfig` product/tag refs | grep gate | `grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props` → **0 hits** | ⬜ pending | +| 3 | DOC-01 | No legacy PMC install command | grep gate | `grep -rn "Install-Package" README.md docs/` → **0 hits** | ⬜ pending | +| 4 | DOC-01 | No stale attribute form | grep gate | `grep -rn "\[DefaultValue" README.md docs/` → **0 hits** | ⬜ pending | +| 5 | DOC-01 | No broken Shepherd logo | grep gate | `grep -rn "existall/Shepherd" README.md src/Directory.Build.props` → **0 hits** | ⬜ pending | +| 6 | DOC-01 | `` typo fixed | grep gate | `grep -n "appliaction" src/Directory.Build.props` → **0 hits** | ⬜ pending | +| 7 | DOC-01 | Renamed doc file — no dangling links | grep gate | `grep -rn "Extend%20Simple%20Config" README.md docs/` → **0 hits** AND the renamed file exists | ⬜ pending | +| 8 | DOC-01 | Canonical repo URL present | grep gate | `grep -rn "existall/SimpleSettings" README.md docs/` → **≥1 hit per doc that links out** | ⬜ pending | +| 9 | DOC-01 | Three package IDs + correct install verb | token match | README contains `dotnet add package ExistForAll.SimpleSettings`, `...Binders`, `...Extensions.GenericHost` | ⬜ pending | +| 10 | DOC-01 | Examples use real API tokens | token match | README/docs contain `[SettingsProperty(DefaultValue`, `SettingsBuilder.CreateBuilder`, `AddSimpleSettings`; `ValidateSimpleSettings()` shown on an `IServiceProvider` | ⬜ pending | +| 11 | DOC-01 | Mandated Phase 1–4 guidance present | presence check | README + docs together cover: secret-redaction; validator secret-safety (incl. constructor); opt-in/deferred `ValidateSimpleSettings`; validate⇒discoverable; `AddCommandLine` spaced values; Phase-3 breaking-change list (API-01/PKG-01/PKG-02/EXC-01) | ⬜ pending | +| 12 | DOC-01 | Internal doc links resolve | link check | every relative / `existall/SimpleSettings/blob/master/docs/` link resolves to an existing `docs/` file | ⬜ pending | +| 13 | DOC-01 | Package content still builds/packs | build gate | from `src/`: `dotnet build -c Release` and `dotnet pack -c Release` succeed | ⬜ pending | + +*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* + +--- + +## Wave 0 Requirements + +- No test-file gaps — validation is grep/build-based; no fixtures or framework install needed. +- *(Discretionary)* A CI legacy-reference + dead-link check could wrap gates 1–8/12; D-05 marks it nice-to-have, not required. If wired as a script, place it under a scratch/CI path — do not add runtime code. + +*Existing infrastructure (grep + the existing `dotnet build`/`pack`) covers all phase requirements.* + +--- + +## Manual-Only Verifications + +| Behavior | Requirement | Why Manual | Test Instructions | +|----------|-------------|------------|-------------------| +| No example echoes a bound value / secret; validator examples never log injected secrets | DOC-01 | Semantic — grep can't judge whether an example is secret-safe | Finished-docs review (light `security-auditor`/`code-reviewer` pass): read every code example in README + docs/; confirm none prints/echoes a bound value or an injected secret in a ctor/`ValidationError` | +| The six mandated guidance items are present AND correctly caveated (not over-claimed) | DOC-01 | Presence + correctness of caveats needs human/semantic read (gate 11 checks presence only) | Confirm the redaction section states its two caveats (author `ValidationError` text + validator constructors are outside the value-free guard) per SECURITY.md:75-78 | + +--- + +## Validation Sign-Off + +- [x] All requirement checks have an `` grep/build gate (11 automated) or a documented manual verification (2 semantic) +- [x] Sampling continuity: every edited-file commit has a relevant grep gate; no gap +- [x] Wave 0 covers all MISSING references (none — grep/build-based) +- [x] No watch-mode flags +- [x] Feedback latency < 90s +- [x] `nyquist_compliant: true` set in frontmatter + +**Approval:** approved 2026-07-19 (docs phase — grep/build DOC-VERIFICATION gates from 05-RESEARCH.md) From 33e04d4b07f25c68be05d6dfe690e5e26d50f4da Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:48:23 +0300 Subject: [PATCH 08/23] =?UTF-8?q?docs(05):=20create=20phase=20plan=20(4=20?= =?UTF-8?q?plans=20=E2=80=94=20docs=20canonicalization,=20metadata,=20deep?= =?UTF-8?q?=20security=20page,=20README=20rewrite)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/ROADMAP.md | 14 +- .../phases/05-documentation/05-01-PLAN.md | 145 +++++++++++++++ .../phases/05-documentation/05-02-PLAN.md | 108 +++++++++++ .../phases/05-documentation/05-03-PLAN.md | 156 ++++++++++++++++ .../phases/05-documentation/05-04-PLAN.md | 171 ++++++++++++++++++ 5 files changed, 592 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/05-documentation/05-01-PLAN.md create mode 100644 .planning/phases/05-documentation/05-02-PLAN.md create mode 100644 .planning/phases/05-documentation/05-03-PLAN.md create mode 100644 .planning/phases/05-documentation/05-04-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 2a6d87a..702eff5 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -134,7 +134,17 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). -**Plans**: TBD +**Plans**: 4 plans + +**Wave 1** *(parallel — no file overlap)* + +- [ ] 05-01-PLAN.md — docs/ canonicalization: rename the extension guide to a canonical filename + repoint its 5 in-docs inbound links; drop the residual legacy parenthetical in getting_started.md (D-05) [Wave 1] +- [ ] 05-02-PLAN.md — package metadata: fix `` typo + legacy `` token in src/Directory.Build.props; build sanity (D-06) [Wave 1] +- [ ] 05-03-PLAN.md — new deep-guidance page docs/Security.md: the 6 mandated Phase 1–4 items (secret-redaction + 2 caveats, validator secret-safety incl. constructor, opt-in/deferred `ValidateSimpleSettings()` on `IServiceProvider`, validate⇒discoverable, `AddCommandLine` spaced values, v1→v2 breaking-change list) (D-04 deep) [Wave 1] + +**Wave 2** *(blocked on Wave 1 completion)* + +- [ ] 05-04-PLAN.md — README full rewrite: canonical logo/title, `dotnet add package` ×3, canonical ToC (incl. renamed page + Security), correct `[SettingsProperty(DefaultValue=…)]` quickstart, trimmed positioning, concise Security + migration sections; phase-final 13-gate grep sweep + `dotnet build`/`pack` (D-01/D-02/D-03/D-04) [Wave 2, depends on 05-01/05-02/05-03] ### Phase 6: First v2.0.0-beta Release @@ -161,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 0/TBD | Not started | - | +| 5. Documentation | 0/4 | Not started | - | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/phases/05-documentation/05-01-PLAN.md b/.planning/phases/05-documentation/05-01-PLAN.md new file mode 100644 index 0000000..54988ae --- /dev/null +++ b/.planning/phases/05-documentation/05-01-PLAN.md @@ -0,0 +1,145 @@ +--- +phase: 05-documentation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - docs/Extending SimpleSettings.md + - docs/getting_started.md + - docs/building_the_collection.md + - docs/Build a SectionBinder.md + - docs/Build Config Interface.md +autonomous: true +requirements: [DOC-01] + +must_haves: + truths: + - "The extension guide is reachable at a canonical, space-encoded filename with no legacy product name in its path" + - "Every in-docs link that targeted the old extension-page filename now resolves to the renamed page" + - "The docs/ tree carries no legacy product name and no dead legacy-repository link" + artifacts: + - "docs/Extending SimpleSettings.md (renamed from the legacy extension-page filename via git mv, history preserved)" + - "docs/getting_started.md (parenthetical legacy-product tag removed on line 4)" + - "docs/building_the_collection.md (two extension-page links repointed)" + - "docs/Build a SectionBinder.md (two extension-page links repointed)" + - "docs/Build Config Interface.md (one extension-page link repointed)" + key_links: + - "The 5 in-docs inbound links -> the renamed extension page (space-encoded filename) on github.com/existall/SimpleSettings" +--- + + +Canonicalize the six-page docs/ tree in place (D-05): rename the legacy-named extension guide to a canonical filename, repoint its five in-docs inbound links, and drop the one residual legacy-product parenthetical in getting_started.md. This is a targeted purge, NOT a restructure — page structure and git history are preserved. + +Purpose: The docs/ folder is already substantially modernized (five of six pages use canonical `existall/SimpleSettings` links and the correct `[SettingsProperty(DefaultValue = …)]` form). Only two residuals remain in docs/: the legacy extension-page filename (with inbound links pointing at it) and one parenthetical legacy-product tag. Closing these lets the README (05-04) link to a stable, canonical extension-page URL. + +Output: A canonically-named extension guide, all in-docs inbound links repointed, and a legacy-name-free docs/ tree. + +Artifacts this plan produces: +- RENAMED: `docs/Extend Simple Config.md` -> `docs/Extending SimpleSettings.md` +- EDITED: `docs/getting_started.md`, `docs/building_the_collection.md`, `docs/Build a SectionBinder.md`, `docs/Build Config Interface.md` +- No new code symbols (documentation-only). + + + +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/workflows/execute-plan.md +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/05-documentation/05-RESEARCH.md +@.planning/phases/05-documentation/05-CONTEXT.md +@.planning/phases/05-documentation/05-VALIDATION.md + + + + + + Task 1: Rename the extension guide to a canonical filename + docs/Extending SimpleSettings.md (renamed from docs/Extend Simple Config.md) + + - docs/Extend Simple Config.md (the file to rename; its content is already canonical — title "Extend SimpleSettings", links already on existall/SimpleSettings — so this is a rename only, no content edit) + - .planning/phases/05-documentation/05-RESEARCH.md ## Legacy-Reference Inventory -> docs/ table + "Inbound links to the renamed file (6 — must repoint on rename)" (per D-05); ## Architecture Patterns -> Pattern 3 (Rename-with-backlink-repoint) + + + Rename the legacy-named extension guide to the canonical name using `git mv` so history is preserved: `git mv "docs/Extend Simple Config.md" "docs/Extending SimpleSettings.md"`. Do NOT edit the file's content — RESEARCH confirms its body and internal links are already canonical. This establishes the new filename first (interface-first) so Task 2 and README (05-04) can repoint their links at a target that already exists. + + + test -f "docs/Extending SimpleSettings.md" && test ! -f "docs/Extend Simple Config.md" && git ls-files --error-unmatch "docs/Extending SimpleSettings.md" + + + - `test -f "docs/Extending SimpleSettings.md"` exits 0 (new file exists) + - `test ! -f "docs/Extend Simple Config.md"` exits 0 (old filename gone) + - `git ls-files --error-unmatch "docs/Extending SimpleSettings.md"` exits 0 (rename is tracked, history preserved) + - `grep -rniE "\bSimpleConfig\b" "docs/Extending SimpleSettings.md"` returns 0 hits (renamed file's content carries no legacy product name) + + The extension guide is tracked at `docs/Extending SimpleSettings.md`; the legacy filename no longer exists; git history is preserved. + + + + Task 2: Repoint in-docs inbound links and drop the residual legacy parenthetical + docs/getting_started.md, docs/building_the_collection.md, docs/Build a SectionBinder.md, docs/Build Config Interface.md + + - docs/getting_started.md (line 4 carries the residual parenthetical legacy-product tag after "SimpleSettings") + - docs/building_the_collection.md (lines 16, 45 link to the old extension-page filename) + - docs/Build a SectionBinder.md (lines 38, 60 link to the old extension-page filename) + - docs/Build Config Interface.md (line 39 links to the old extension-page filename) + - .planning/phases/05-documentation/05-RESEARCH.md ## Legacy-Reference Inventory (the exact file:line inventory) + ## Common Pitfalls -> Pitfall 2 (renaming but leaving dangling links) + + + Two edits, both mechanical: + (1) On docs/getting_started.md line 4, remove the parenthetical legacy-product tag that immediately follows the word "SimpleSettings" so the sentence begins "SimpleSettings uses a `SettingsBuilder` in order to create your settings objects." — drop only the parenthetical, keep the rest of the sentence intact. + (2) In the five in-docs links that target the old extension-page filename (building_the_collection.md:16 and :45; Build a SectionBinder.md:38 and :60; Build Config Interface.md:39), change ONLY the URL-encoded filename segment to `Extending%20SimpleSettings.md`. The host segment is already `github.com/existall/SimpleSettings/blob/master/docs/` in all five — leave it untouched. Use the exact old->new filename mapping from RESEARCH's Legacy-Reference Inventory. Do NOT touch README.md (that ToC entry is repointed in 05-04). + + + test $(grep -rn "Extend%20Simple%20Config" docs/ | wc -l) -eq 0 && test $(grep -rniE "\bSimpleConfig\b" docs/ | wc -l) -eq 0 && test $(grep -rn "Extending%20SimpleSettings.md" docs/ | wc -l) -ge 5 + + + - `grep -rn "Extend%20Simple%20Config" docs/` returns 0 hits (no in-docs link points at the old filename) + - `grep -rniE "\bSimpleConfig\b" docs/` returns 0 hits (getting_started.md:4 parenthetical removed; renamed file clean; no other residual) + - `grep -rn "existall/SimpleConfig" docs/` returns 0 hits (no dead legacy-repository link in docs/) + - `grep -rn "Extending%20SimpleSettings.md" docs/` returns >= 5 hits (all five inbound links repointed) + - `test -f "docs/Extending SimpleSettings.md"` exits 0 (repointed links resolve to an existing file) + + docs/ carries no legacy product name and no link to the old extension-page filename; all five in-docs inbound links resolve to the renamed page on `existall/SimpleSettings`. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| repo docs -> GitHub reader | Rendered markdown links must resolve; a dangling link is a broken navigation contract, not an injection surface | + +No package installs occur in this plan (RESEARCH ## Package Legitimacy Audit: N/A — documentation-only, no `` changed). + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-05-01 | Tampering | docs link integrity | low | mitigate | Rename via `git mv` AND repoint all five in-docs inbound links in the same plan; acceptance gate asserts zero references to the old filename AND the new file exists | +| T-05-02 | Information Disclosure | docs prose | low | accept | Rename + parenthetical-removal edits touch no secret-bearing content; no bound value or secret is present in any edited line | + + + +Run from the repo root: +- `grep -rn "Extend%20Simple%20Config" docs/` -> 0 hits +- `grep -rniE "\bSimpleConfig\b" docs/` -> 0 hits +- `grep -rn "existall/SimpleConfig" docs/` -> 0 hits +- `grep -rn "Extending%20SimpleSettings.md" docs/` -> >= 5 hits +- `test -f "docs/Extending SimpleSettings.md"` and `test ! -f "docs/Extend Simple Config.md"` +(The cross-file sweep spanning README + props is the phase-final gate in 05-04.) + + + +The extension guide is renamed to a canonical filename with history preserved; all five in-docs inbound links resolve to it; the one residual legacy-product parenthetical in getting_started.md is removed; docs/ is free of legacy product naming and dead legacy-repository links. + + + +Create `.planning/phases/05-documentation/05-01-SUMMARY.md` when done. + diff --git a/.planning/phases/05-documentation/05-02-PLAN.md b/.planning/phases/05-documentation/05-02-PLAN.md new file mode 100644 index 0000000..b6aea2e --- /dev/null +++ b/.planning/phases/05-documentation/05-02-PLAN.md @@ -0,0 +1,108 @@ +--- +phase: 05-documentation +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Directory.Build.props +autonomous: true +requirements: [DOC-01] + +must_haves: + truths: + - "The packaged reads with correct spelling and no legacy product name" + - " carries no legacy product token" + - "The shared props still parse and the three packages build cleanly" + artifacts: + - "src/Directory.Build.props ( typo corrected on line 16; legacy token replaced on line 21)" + key_links: + - "src/Directory.Build.props + -> every published .nupkg (shared MSBuild props inject metadata into all three packages)" +--- + + +Fix the two residual legacy tokens in the shared package metadata (D-06): the misspelled word in `` and the legacy-product token in ``. The repo/package URLs and ``/`` are already canonical (RESEARCH verified) — this plan changes only the two stale tokens and proves the props still parse. + +Purpose: `src/Directory.Build.props` injects ``, URLs, icon, and README into every published package. The `` and tags ship in the `.nupkg` and render on the nuget.org page, so a typo/legacy token there is consumer-visible package content. + +Output: A correctly-spelled, canonically-tagged ``/``; a green `dotnet build -c Release` proving the props change did not break the shared build. + +Artifacts this plan produces: +- EDITED: `src/Directory.Build.props` (metadata only) +- No new code symbols, no `` change (documentation/metadata-only). + + + +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/workflows/execute-plan.md +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/05-documentation/05-RESEARCH.md +@.planning/phases/05-documentation/05-CONTEXT.md + + + + + + Task 1: Correct the Description typo and replace the legacy PackageTags token + src/Directory.Build.props + + - src/Directory.Build.props (line 16 `` carries the misspelled word and the "decouples the frameworks from your …" clause; line 21 `` ends with a legacy-product token; lines 17-18 ``/`` are already canonical; lines 22-23/29-30 pack the local `icon.png` + `README.md`) + - .planning/phases/05-documentation/05-RESEARCH.md ## Legacy-Reference Inventory -> `src/Directory.Build.props` table (D-06); ## Metadata note (the broken logo lives in README, NOT here — props already uses the local icon) + + + Two token edits in src/Directory.Build.props, no structural change: + (1) Line 16 ``: correct the misspelled word so the clause reads "decouples the frameworks from your application" (the current spelling has the letters transposed — see the RESEARCH inventory for the exact current token). Leave the rest of the sentence and the `IOptions<T>` reference intact. + (2) Line 21 ``: replace the trailing legacy-product token with `SimpleSettings` (keep all the other tags — Configuration, Settings, Ioc, DI, ExistForAll, Dependency injection, Options — in place; only the last token changes). + Do NOT touch ``, ``, ``, ``, versioning, or the pack ItemGroup — all verified canonical. The README logo is a separate fix owned by 05-04. + + + cd src && dotnet build -c Release 2>&1 | tail -5; cd .. && test $(grep -c "appliaction" src/Directory.Build.props) -eq 0 && test $(grep -riEc "\bSimpleConfig\b" src/Directory.Build.props) -eq 0 + + + - `grep -n "appliaction" src/Directory.Build.props` returns 0 hits (typo corrected to "application") + - `grep -riE "\bSimpleConfig\b" src/Directory.Build.props` returns 0 hits (legacy PackageTags token replaced) + - `grep -n "SimpleSettings" src/Directory.Build.props` returns >= 1 hit (canonical token present) + - `grep -n "existall/SimpleSettings" src/Directory.Build.props` returns >= 1 hit (repo/package URLs remain canonical — unchanged) + - from `src/`: `dotnet build -c Release` exits 0 (the props still parse; the shared build is green) + + `` reads with correct spelling and no legacy product name; `` uses the canonical token; the three packages build cleanly under Release. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| shared MSBuild props -> published .nupkg | ``/`` become public package content on nuget.org; metadata is public by design | + +No package installs occur in this plan (RESEARCH ## Package Legitimacy Audit: N/A — no `` added or changed). + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-05-03 | Tampering | src/Directory.Build.props (packaged metadata) | low | mitigate | Only `` prose + one `` token change; the `dotnet build -c Release` gate proves the props still parse and the shared build is green | +| T-05-04 | Information Disclosure | package metadata | low | accept | No secret appears in `` or ``; this metadata is public package content by design | + + + +- `grep -n "appliaction" src/Directory.Build.props` -> 0 hits +- `grep -riE "\bSimpleConfig\b" src/Directory.Build.props` -> 0 hits +- from `src/`: `dotnet build -c Release` exits 0 +(The full cross-file grep sweep + `dotnet pack` are the phase-final gate in 05-04, since packing also validates the packed README from 05-04.) + + + +The packaged description is correctly spelled and legacy-name-free; the package tags are canonical; the shared props parse and all three packages build under Release. + + + +Create `.planning/phases/05-documentation/05-02-SUMMARY.md` when done. + diff --git a/.planning/phases/05-documentation/05-03-PLAN.md b/.planning/phases/05-documentation/05-03-PLAN.md new file mode 100644 index 0000000..cd48546 --- /dev/null +++ b/.planning/phases/05-documentation/05-03-PLAN.md @@ -0,0 +1,156 @@ +--- +phase: 05-documentation +plan: 03 +type: execute +wave: 1 +depends_on: [] +files_modified: + - docs/Security.md +autonomous: true +requirements: [DOC-01] + +must_haves: + truths: + - "A reader learns the secret-redaction invariant AND its two carve-outs (author validation text + validator constructors) without over-claim" + - "A reader learns validators must not echo secrets in ValidationError text or in constructors" + - "A reader learns opt-in/deferred DI validation runs via IServiceProvider.ValidateSimpleSettings() after BuildServiceProvider()" + - "A reader learns attaching an object-level validator makes the type scan-discovered (validate => discoverable)" + - "A reader learns spaced command-line values bind via AddCommandLine, with the prefix-lookahead caveat" + - "A reader learns the four v1->v2 breaking changes (SettingsHolder internal, Core.AspNet dropped, per-TFM floor, public exception base)" + artifacts: + - "docs/Security.md (new deep-guidance page: Security & Behavior guarantees + Migration v1->v2)" + key_links: + - "docs/Security.md <- README concise Security/Migration sections (05-04 deep-links here)" + - "Every API token in this page -> the verified spelling in 05-RESEARCH.md ## Current Public API" +--- + + +Create the new deep-guidance page `docs/Security.md` (Claude's-discretion placement chosen: a dedicated page, NOT the renamed extension guide — keeps the security/behavior contract separable and gives the README a stable deep-link target). It carries the full treatment of the D-04 mandated Phase 1–4 guidance that the README summarizes concisely. + +Purpose: The Phase-4 security sign-off (SECURITY.md) locks exact guarantee wording; over-claiming the redaction invariant is a real risk (RESEARCH Pitfall 4). This page documents each invariant with its caveats, sourced from the verified API + SECURITY.md — never paraphrased loosely. + +Output: One new docs page covering all six mandated items (five security/behavior invariants + the v1->v2 breaking-change list), plus a secret-safe validator example copied verbatim from the verified RESEARCH code sample. + +Artifacts this plan produces: +- NEW: `docs/Security.md` +- No new code symbols (documentation-only). No secret or bound value appears in any example on the page. + + + +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/workflows/execute-plan.md +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/05-documentation/05-RESEARCH.md +@.planning/phases/05-documentation/05-CONTEXT.md +@.planning/phases/04-collection-validation-binding/SECURITY.md + + + + + + Task 1: Author the Security & Behavior guarantees section + docs/Security.md + + - .planning/phases/05-documentation/05-RESEARCH.md ## Phase 1–4 Behavior Facts to Document (the source-cited invariant wording), ## Current Public API (Ground Truth) (exact tokens), ## Code Examples (the secret-safe validator + DI snippet to copy verbatim), ## Common Pitfalls (Pitfalls 3, 4, 5) + - .planning/phases/04-collection-validation-binding/SECURITY.md (lines 24-27 the S1/SEC-01 invariant; 75-78 the accepted author-text residual — the caveat wording is authoritative here) + - docs/getting_started.md (the existing docs house style: code fences use ````C# ... ````; block-scoped namespaces; public interfaces) + + + Create docs/Security.md with a top heading "# Security & Behavior" and a short intro line, then five subsections, each stating the guarantee in the exact source-cited wording from RESEARCH ## Phase 1–4 Behavior Facts (do NOT paraphrase loosely, do NOT over-claim): + (1) "## Secret redaction (value-free exceptions)" — every library exception is value-free (Type/property metadata only) and never chains an inner that saw the bound value; the guarantee is structural (the conversion-failure exception carries the failure's CLR Type, not the Exception or the value). Then state the TWO carve-outs explicitly: author-supplied `ValidationError` message text reaches the exception's ToString by design, and DI-resolved validator CONSTRUCTORS run outside the value-free bind guard — so a constructor that logs an injected secret leaks it. Cite SECURITY.md:24-27 and 75-78 in prose. + (2) "## Validators must not echo secrets" — never put a bound value (possible secret) in `ValidationError` text; never log secrets in a validator constructor. Include the verified secret-safe validator example from RESEARCH ## Code Examples (the `ISettingValidation` sample checking `Retries < 0`) inside a ````C# fenced block — copy it verbatim; it echoes no bound value. + (3) "## Opt-in / deferred DI validation" — DI-registered `ISettingValidation` validators cannot run during `AddSimpleSettings` (container not built yet); the host must call `provider.ValidateSimpleSettings()` explicitly AFTER `BuildServiceProvider()`. State the exact receiver: the method extends `IServiceProvider` (not `IServiceCollection`, not `IHost`) and returns the same provider. Note that attribute/`ValidatorType` validators run inline during binding and need no such call. Include the verified DI snippet from RESEARCH ## Code Examples showing `serviceProvider.ValidateSimpleSettings();` after the provider is built. + (4) "## Validators make a type discoverable" — declaring an object-level validator via `[SettingsSection(ValidatorType = typeof(...))]` also marks the type as scan-discovered (it carries `SettingsSectionAttribute`); attaching a validator is sufficient to make it a discovered settings section. + (5) "## Command-line values with spaces" — `AddCommandLine` sources the process args and skips the executable path (`arg[0]`) internally; for a prefixed key with no inline delimiter the parser looks ahead to the next token and binds it as the value UNLESS that next token itself starts with a prefix character (`-`/`/`), in which case it is a new key. State that a shell-quoted value with spaces (`--Key "a b c"`) is one token and binds, but recommend the inline `--Key=value` form for values that begin with `-`/`/` (RESEARCH Pitfall 5). Show no secret literal. + Every code fence uses the docs house style (````C#). Every settings interface in an example is `public`. Verify each API token against RESEARCH ## Current Public API before writing it. Do NOT use the legacy product name anywhere on this page. + + + test -f docs/Security.md && test $(grep -c "ValidateSimpleSettings" docs/Security.md) -ge 1 && test $(grep -c "IServiceProvider" docs/Security.md) -ge 1 && test $(grep -c "BuildServiceProvider" docs/Security.md) -ge 1 && test $(grep -c "AddCommandLine" docs/Security.md) -ge 1 && test $(grep -ci "constructor" docs/Security.md) -ge 1 && test $(grep -Eci "value-free|redact" docs/Security.md) -ge 1 + + + - `test -f docs/Security.md` exits 0 (page created) + - `grep -Eci "value-free|redact" docs/Security.md` >= 1 (redaction invariant stated) + - `grep -ci "constructor" docs/Security.md` >= 1 (the validator-constructor carve-out is present) + - `grep -c "ValidateSimpleSettings" docs/Security.md` >= 1 AND `grep -c "IServiceProvider" docs/Security.md` >= 1 AND `grep -c "BuildServiceProvider" docs/Security.md` >= 1 (opt-in DI validation shown on the correct receiver, after the provider is built) + - `grep -c "SettingsSection(ValidatorType" docs/Security.md` >= 1 (validate => discoverable coupling stated) + - `grep -c "AddCommandLine" docs/Security.md` >= 1 (spaced-value binding documented) + - `grep -riEc "\bSimpleConfig\b" docs/Security.md` == 0 (no legacy product name) + - Manual (security-auditor pass): the validator example echoes no bound value in `ValidationError` text or in a constructor; the redaction section states both carve-outs and does not over-claim (SECURITY.md:75-78) + + docs/Security.md exists and covers the five security/behavior invariants with their caveats, using verified API tokens and a secret-safe example; no legacy product name appears. + + + + Task 2: Append the v1 -> v2 migration / breaking-change section + docs/Security.md + + - docs/Security.md (the page created in Task 1 — append the migration section at the end) + - .planning/phases/05-documentation/05-RESEARCH.md ## Phase 1–4 Behavior Facts -> "Phase-3 breaking-change list" (API-01/PKG-01/PKG-02/EXC-01) + ## State of the Art table + - .planning/phases/03-public-surface-packaging-binder-cleanup/03-CONTEXT.md (Phase-3 breaking-change detail, for accurate migration wording) + + + Append a "## Migration (v1 -> v2)" section (anchor: `#migration`) to docs/Security.md listing the four batched breaking changes as bullets, each with a one-line "what changed / what to do": + - `SettingsHolder` / `ISettingsHolder` are now internal (API-01) — consumers must not reference them. + - The `Core.AspNet` package was dropped (PKG-01) — it exposed no public type; remove any reference to it. + - The `Microsoft.Extensions.*` dependency floor is now per-TFM (`8.0.x` on net8, current on net10) (PKG-02). + - A public `abstract SimpleSettingsException` base was introduced and boundary exceptions were made public + structured (EXC-01) — catch `SimpleSettingsException` for a single library error category; the old bare-`Exception` / non-interface throw is replaced by `SettingsTypeNotInterfaceException`. + Name the versions ("v1 -> v2"), NOT the legacy product name — the page must stay free of the legacy product token (gate #2). Keep it concise; this is the deep target the README's "Breaking changes / migration" section links to. + + + test $(grep -c "SimpleSettingsException" docs/Security.md) -ge 1 && test $(grep -c "SettingsHolder" docs/Security.md) -ge 1 && test $(grep -c "Core.AspNet" docs/Security.md) -ge 1 && test $(grep -Eci "migration" docs/Security.md) -ge 1 && test $(grep -riEc "\bSimpleConfig\b" docs/Security.md) -eq 0 + + + - `grep -Eci "migration" docs/Security.md` >= 1 (migration section present) + - `grep -c "SettingsHolder" docs/Security.md` >= 1 (API-01 documented) + - `grep -c "Core.AspNet" docs/Security.md` >= 1 (PKG-01 documented) + - `grep -Ec "per-TFM|Microsoft.Extensions" docs/Security.md` >= 1 (PKG-02 documented) + - `grep -c "SimpleSettingsException" docs/Security.md` >= 1 (EXC-01 documented) + - `grep -riE "\bSimpleConfig\b" docs/Security.md` returns 0 hits (migration section names versions, not the legacy product) + + docs/Security.md ends with a concise v1->v2 migration section covering all four Phase-3 breaking changes; the page carries no legacy product name. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| library guarantee -> documented claim | Docs must state the existing security control accurately; an over-claim leads an author to trust a guarantee the library does not make | +| validator author -> exception ToString / logs | Author-supplied text and validator constructors are OUTSIDE the library's value-free guard — the doc must say so | + +No package installs occur in this plan (RESEARCH ## Package Legitimacy Audit: N/A — documentation-only). + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-05-SEC-01 | Information Disclosure | docs/Security.md redaction section | medium | mitigate | State the value-free invariant WITH both carve-outs (author `ValidationError` text + validator constructors); cite SECURITY.md:75-78; do not over-claim (RESEARCH Pitfall 4) | +| T-05-SEC-02 | Information Disclosure | docs/Security.md validator example | medium | mitigate | Copy the verified secret-safe validator verbatim from RESEARCH ## Code Examples; it echoes no bound value in `ValidationError` text or a constructor; finished-docs security-auditor pass confirms | +| T-05-SEC-03 | Information Disclosure / correctness | docs/Security.md AddCommandLine section | low | mitigate | Document the prefix-lookahead rule precisely and recommend inline `--Key=value` for prefix-leading values (RESEARCH Pitfall 5); show no secret literal | + + + +- `grep -Eci "value-free|redact" docs/Security.md` -> >= 1 +- `grep -ci "constructor" docs/Security.md` -> >= 1 +- `grep -c "ValidateSimpleSettings" docs/Security.md`, `IServiceProvider`, `BuildServiceProvider` -> each >= 1 +- `grep -c "SettingsSection(ValidatorType" docs/Security.md` -> >= 1 +- `grep -c "AddCommandLine" docs/Security.md` -> >= 1 +- migration tokens: `SettingsHolder`, `Core.AspNet`, `SimpleSettingsException` -> each >= 1 +- `grep -riE "\bSimpleConfig\b" docs/Security.md` -> 0 hits +- Manual security-auditor pass: no example echoes a bound value/secret; redaction caveats present and not over-claimed + + + +docs/Security.md documents all six mandated Phase 1–4 guidance items (five security/behavior invariants with caveats + the v1->v2 breaking-change list), uses only verified API tokens and secret-safe examples, and carries no legacy product name. + + + +Create `.planning/phases/05-documentation/05-03-SUMMARY.md` when done. + diff --git a/.planning/phases/05-documentation/05-04-PLAN.md b/.planning/phases/05-documentation/05-04-PLAN.md new file mode 100644 index 0000000..015f38c --- /dev/null +++ b/.planning/phases/05-documentation/05-04-PLAN.md @@ -0,0 +1,171 @@ +--- +phase: 05-documentation +plan: 04 +type: execute +wave: 2 +depends_on: [05-01, 05-02, 05-03] +files_modified: + - README.md +autonomous: true +requirements: [DOC-01] + +must_haves: + truths: + - "The README shows the canonical name, a working logo, dotnet-add install for all three packages, and a correct [SettingsProperty(DefaultValue = …)] example" + - "Every README ToC/deep link resolves to an existing docs/ page on existall/SimpleSettings (incl. the renamed extension page and the new Security page)" + - "The README concisely covers secret-redaction (with the caveat pointer) and the v1->v2 breaking-change list, linking to docs/Security.md for depth" + - "No legacy product name, legacy repo link, legacy install command, stale attribute form, or broken logo remains anywhere in README + docs/ + props" + - "The package still builds and packs from src/ with the refreshed README + icon + Description" + artifacts: + - "README.md (full rewrite: logo, title, install, ToC, quickstart, feature overview, trimmed positioning, concise Security + Migration sections)" + key_links: + - "README ToC (7 entries) -> docs/ pages on github.com/existall/SimpleSettings (incl. Extending%20SimpleSettings.md and Security.md)" + - "README Security/Migration sections -> docs/Security.md (deep target from 05-03)" + - "README.md + icon.png -> .nupkg via PackageReadmeFile/PackageIcon (dotnet pack repacks cleanly)" +--- + + +Rewrite the packaged `README.md` end-to-end (D-01/D-02/D-03/D-04 concise) against the verified API in RESEARCH — never trust the current README, which is the stale artifact (broken logo, legacy PMC install, six dead legacy-repo ToC links, the factually-wrong default-value attribute example, and none of the Phase 1–4 guidance). This plan also holds the phase-final gate: the full cross-file grep sweep + `dotnet build`/`pack`. + +Purpose: The README ships in every `.nupkg` via `PackageReadmeFile` and is the nuget.org + GitHub landing page — the first thing a consumer reads. It must be accurate, canonically named, and carry the concise Phase 1–4 security/behavior guidance (deep detail lives in docs/Security.md from 05-03). + +Output: A modern, self-contained, accurate README; a green full grep sweep across README + docs/ + props; a clean `dotnet pack` proving the refreshed README + icon + Description repack. + +Artifacts this plan produces: +- EDITED (rewritten): `README.md` +- No new code symbols. Depends on 05-01 (renamed extension page exists), 05-03 (docs/Security.md exists), 05-02 (props Description fixed) so links resolve and the phase-final pack is meaningful. + + + +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/workflows/execute-plan.md +@/Users/guyludvig/frontegg/development/open-source/SimpleSettings/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/05-documentation/05-RESEARCH.md +@.planning/phases/05-documentation/05-CONTEXT.md +@.planning/phases/05-documentation/05-VALIDATION.md + + + + + + + + + + + Task 1: Structural README rewrite (logo, title, install, ToC, quickstart, positioning) + README.md + + - README.md (the stale artifact — line 1 logo, line 3 title, lines 6-7 install, lines 11-16 ToC, lines 18/58 headings, lines 63-68 the wrong example, lines 21-56 the IOptions polemic + prose typos on :44/:53/:56/:60) + - .planning/phases/05-documentation/05-RESEARCH.md ## Current Public API (Ground Truth), ## Code Examples (copy the verified quickstart + install + DI snippets verbatim), ## Legacy-Reference Inventory -> README.md table (every file:line to fix), ## Common Pitfalls (Pitfalls 1, 3) + - docs/getting_started.md (the canonical quickstart wording + docs house style to stay consistent with) + - docs/Extending SimpleSettings.md (the renamed extension page from 05-01 — the ToC entry #6 target) + - docs/Security.md (the new deep page from 05-03 — the ToC entry #7 target) + + + Rewrite README.md to a modern, self-contained OSS shape using the verified tokens from RESEARCH ## Code Examples (do NOT carry the current example forward — it is factually wrong): + (a) Line 1 logo: replace the broken `` (currently a raw.githubusercontent URL under a legacy, unrelated repo) with `ExistForAll.SimpleSettings`. Use this absolute raw URL (Claude's-discretion choice, diverging from the "local icon.png" suggestion for a concrete reason): the README is packed via `PackageReadmeFile` and rendered on nuget.org, which renders ONLY absolute HTTPS image URLs — a relative `icon.png` would show broken on the package page. `` in Directory.Build.props keeps using the packed local `icon.png` (that is the separate package-icon mechanism) — do not touch it. + (b) Line 3 title: keep `ExistForAll.SimpleSettings`; drop the trailing parenthetical legacy-product tag. + (c) Lines 6-7 install: replace the single Package-Manager-Console install line with the three `dotnet add package` lines from RESEARCH ## Code Examples -> Install: `ExistForAll.SimpleSettings`, `ExistForAll.SimpleSettings.Binders`, `ExistForAll.SimpleSettings.Extensions.GenericHost`. + (d) Lines 11-16 ToC: repoint every entry to `https://github.com/existall/SimpleSettings/blob/master/docs/` — getting_started.md; building_the_collection.md; Build%20Config%20Interface.md; Default%20Values.md (use link TEXT "Default Values", not the attribute name); Build%20a%20SectionBinder.md; and the renamed extension page (its space-encoded canonical filename established in 05-01 — take the exact new filename from RESEARCH ## Legacy-Reference Inventory). Add a 7th entry "Security & Behavior" -> Security.md. No ToC link text may be the bare default-value attribute name. + (e) Lines 18 and 58 headings: rename the legacy product name in both headings to SimpleSettings. + (f) Lines 63-68 quickstart: replace the stale example with the verified minimal quickstart from RESEARCH ## Code Examples -> "Minimal quickstart (direct API)" — copy it verbatim (a `[SettingsSection]` `public interface IEmailSenderSettings` whose `ServiceUrl`/`Retries` carry the settings-property default-value attribute, then the `SettingsBuilder.CreateBuilder().GetSettings()` call). Every example interface must be `public`. Use the exact default-value attribute spelling from RESEARCH — do not reproduce the old bracketed default-value form the current README shows. + (g) Positioning (lines 21-56): trim the long "why IOptions is bad" polemic to a tight 2-4 sentence blurb (keep the positioning, drop the wall of text per D-01); fix the prose typos flagged in the RESEARCH inventory (the `IOption`->`IOptions` typo on :44/:60, the doubled-`http` broken URL and the two misspelled words on :56, "scale-able" on :53) or drop those lines with the trim. + (h) Add a short feature-overview section and a DI usage snippet from RESEARCH ## Code Examples -> DI (`services.AddSimpleSettings(o => o.AddAssemblies(...))`, and `serviceProvider.ValidateSimpleSettings();` after the provider is built). Keep example values non-secret (a URL / an int) — never print a bound value. + + + test $(grep -c "raw.githubusercontent.com/existall/SimpleSettings" README.md) -ge 1 && test $(grep -c "dotnet add package ExistForAll.SimpleSettings" README.md) -ge 1 && test $(grep -c "ExistForAll.SimpleSettings.Binders" README.md) -ge 1 && test $(grep -c "ExistForAll.SimpleSettings.Extensions.GenericHost" README.md) -ge 1 && test $(grep -c "SettingsProperty(DefaultValue" README.md) -ge 1 && test $(grep -c "SettingsBuilder.CreateBuilder" README.md) -ge 1 && test $(grep -c "AddSimpleSettings" README.md) -ge 1 && test $(grep -c "Install-Package" README.md) -eq 0 && test $(grep -c "\[DefaultValue" README.md) -eq 0 && test $(grep -c "existall/Shepherd" README.md) -eq 0 + + + - `grep -c "raw.githubusercontent.com/existall/SimpleSettings" README.md` >= 1 (canonical, absolute logo URL — renders on nuget.org) + - `grep -c "existall/Shepherd" README.md` == 0 (broken logo gone) + - `grep -c "dotnet add package ExistForAll.SimpleSettings" README.md` >= 1 AND `grep -c "ExistForAll.SimpleSettings.Binders" README.md` >= 1 AND `grep -c "ExistForAll.SimpleSettings.Extensions.GenericHost" README.md` >= 1 (three package IDs + correct install verb) + - `grep -c "Install-Package" README.md` == 0 (legacy PMC install command gone) + - `grep -c "SettingsProperty(DefaultValue" README.md` >= 1 AND `grep -c "\[DefaultValue" README.md` == 0 (real attribute form; no stale form) + - `grep -c "SettingsBuilder.CreateBuilder" README.md` >= 1 AND `grep -c "AddSimpleSettings" README.md` >= 1 (verified direct + DI entry points shown) + - `grep -rn "existall/SimpleConfig" README.md` == 0 AND `grep -riE "\bSimpleConfig\b" README.md` == 0 (no legacy repo link / product name) + - `grep -c "Extend%20Simple%20Config" README.md` == 0 AND `grep -c "Extending%20SimpleSettings.md" README.md` >= 1 (ToC points at the renamed page) + + README is rewritten to the modern shape: canonical logo/title, dotnet-add install ×3, canonical ToC (7 entries incl. the renamed page + Security), a correct `[SettingsProperty(DefaultValue = …)]` quickstart, feature overview, and a trimmed positioning blurb — with no legacy product name, legacy install command, stale attribute form, or broken logo. + + + + Task 2: Concise Security notes + Migration sections; phase-final grep sweep and pack + README.md + + - README.md (the rewritten body from Task 1 — append the two concise sections near the end) + - docs/Security.md (the deep target from 05-03 — the README sections summarize it and link to it, incl. the `#migration` anchor) + - .planning/phases/05-documentation/05-RESEARCH.md ## Phase 1–4 Behavior Facts (the exact caveat wording + the Phase-3 breaking-change list) + ## Common Pitfalls -> Pitfall 4 (do not over-claim) + - .planning/phases/05-documentation/05-VALIDATION.md (the 13 DOC-VERIFICATION gates — this task runs the full sweep) + + + Append two concise sections to README.md, each linking into docs/ for depth: + (1) "## Security notes" — a short paragraph stating the value-free exception invariant (bind/conversion failures never surface the bound value), plus the one-line caveat that author-supplied validation message text and validator constructors are OUTSIDE that guard, and a link to docs/Security.md for the full treatment. Do NOT over-claim (RESEARCH Pitfall 4) — pair the invariant with the caveat. + (2) "## Breaking changes / migration (v1 -> v2)" — bullet the four Phase-3 breaking changes by concept (SettingsHolder/ISettingsHolder made internal — API-01; the dropped Core.AspNet package — PKG-01; the per-TFM Microsoft.Extensions.* floor — PKG-02; the public `SimpleSettingsException` base + structured boundary exceptions — EXC-01), and link to `docs/Security.md#migration`. Name the versions ("v1 -> v2"), NOT the legacy product name (keep README free of the legacy product token). Then run the phase-final gate below. + Phase-final gate (run after the append; all from the repo root except the build/pack, which run from `src/`): the full cross-file grep sweep (13 DOC-VERIFICATION gates) and `dotnet build -c Release` + `dotnet pack -c Release` from `src/`. The pack proves the refreshed README + icon + `` (from 05-02) repack cleanly. + + + test $(grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Install-Package" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "\[DefaultValue" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "existall/Shepherd" README.md src/Directory.Build.props | wc -l) -eq 0 && test $(grep -n "appliaction" src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Extend%20Simple%20Config" README.md docs/ | wc -l) -eq 0 && for f in getting_started.md building_the_collection.md "Build Config Interface.md" "Default Values.md" "Build a SectionBinder.md" "Extending SimpleSettings.md" Security.md; do test -f "docs/$f" || { echo "MISSING docs/$f"; exit 1; }; done && cd src && dotnet build -c Release 2>&1 | tail -3 && dotnet pack -c Release 2>&1 | tail -3 + + + - Gate 1: `grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props` -> 0 hits + - Gate 2: `grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props` -> 0 hits + - Gate 3: `grep -rn "Install-Package" README.md docs/` -> 0 hits + - Gate 4: `grep -rn "\[DefaultValue" README.md docs/` -> 0 hits + - Gate 5: `grep -rn "existall/Shepherd" README.md src/Directory.Build.props` -> 0 hits + - Gate 6: `grep -n "appliaction" src/Directory.Build.props` -> 0 hits + - Gate 7: `grep -rn "Extend%20Simple%20Config" README.md docs/` -> 0 hits AND `docs/Extending SimpleSettings.md` exists + - Gate 8: `grep -rn "existall/SimpleSettings" README.md` -> >= 1 hit + - Gate 9: README contains `dotnet add package ExistForAll.SimpleSettings`, `...Binders`, `...Extensions.GenericHost` + - Gate 10: README contains `[SettingsProperty(DefaultValue`, `SettingsBuilder.CreateBuilder`, `AddSimpleSettings`; `ValidateSimpleSettings()` is shown on an `IServiceProvider` in README + docs together (docs/Security.md carries the receiver) + - Gate 11: README "## Security notes" states the redaction invariant WITH the author-text/constructor caveat; "## Breaking changes / migration (v1 -> v2)" lists API-01/PKG-01/PKG-02/EXC-01 — the six mandated items are present across README + docs/ + - Gate 12: every referenced `docs/` exists (the for-loop reports no MISSING) + - Gate 13: from `src/`, `dotnet build -c Release` AND `dotnet pack -c Release` exit 0 (README + icon + Description repack cleanly) + - Manual (security-auditor pass): no README example echoes a bound value/secret; the redaction note is not over-claimed + + README carries concise, correctly-caveated Security notes and a v1->v2 migration list linking to docs/Security.md; all 13 DOC-VERIFICATION gates pass; the packages build and pack from `src/` with the refreshed README/icon/Description. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| README claim -> nuget.org / GitHub reader | The packaged README is the first read; an over-claimed guarantee or a secret-leaking example misleads every consumer | +| library guarantee -> concise README summary | The concise redaction note must not drop the caveat that the deep page states | + +No package installs occur in this plan (RESEARCH ## Package Legitimacy Audit: N/A — documentation/metadata-only; `dotnet pack` repackages existing projects, adds no ``). + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-05-SEC-04 | Information Disclosure | README "Security notes" | medium | mitigate | The concise redaction note carries the caveat pointer (author validation text + validator constructors are outside the guard) and links to docs/Security.md; do not over-claim (RESEARCH Pitfall 4) | +| T-05-SEC-05 | Information Disclosure | README quickstart / DI example | low | mitigate | Examples use non-secret placeholder values (a URL, an int); no example prints a bound value; finished-docs security-auditor pass confirms | +| T-05-06 | Tampering | packaged README/metadata -> .nupkg | low | mitigate | The phase-final `dotnet pack -c Release` proves the refreshed README + icon + `` repack cleanly (no broken package content ships) | + + + +Full phase-final DOC-VERIFICATION sweep (13 gates) — run from the repo root, build/pack from `src/`: +- Gates 1-7: legacy-reference negative greps across README + docs/ + props all return 0 hits (`existall/SimpleConfig`, `\bSimpleConfig\b`, `Install-Package`, `\[DefaultValue`, `existall/Shepherd`, `appliaction`, `Extend%20Simple%20Config`) +- Gate 8: `existall/SimpleSettings` present in README (>= 1) +- Gates 9-11: three canonical package IDs + real API tokens + the six mandated guidance items present across README + docs/ +- Gate 12: every referenced docs/ file exists (for-loop reports no MISSING) +- Gate 13: `dotnet build -c Release` + `dotnet pack -c Release` from `src/` exit 0 +Manual: security-auditor/code-reviewer pass — no example echoes a secret; redaction caveats present and not over-claimed (05-VALIDATION.md Manual-Only Verifications). + + + +The README is accurate, canonically named, and self-contained; the concise Security + Migration sections are present and correctly caveated; all 13 DOC-VERIFICATION gates pass across README + docs/ + props; the packages build and pack cleanly with the refreshed package content. + + + +Create `.planning/phases/05-documentation/05-04-SUMMARY.md` when done. + From a6a54eaa2a663a17ee51dab981e9b159e0780723 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:54:26 +0300 Subject: [PATCH 09/23] docs(05): harden build/pack verify gates (pipefail) + mark research open-questions resolved --- .planning/phases/05-documentation/05-02-PLAN.md | 2 +- .planning/phases/05-documentation/05-04-PLAN.md | 2 +- .planning/phases/05-documentation/05-RESEARCH.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.planning/phases/05-documentation/05-02-PLAN.md b/.planning/phases/05-documentation/05-02-PLAN.md index b6aea2e..b726c7c 100644 --- a/.planning/phases/05-documentation/05-02-PLAN.md +++ b/.planning/phases/05-documentation/05-02-PLAN.md @@ -61,7 +61,7 @@ Artifacts this plan produces: Do NOT touch ``, ``, ``, ``, versioning, or the pack ItemGroup — all verified canonical. The README logo is a separate fix owned by 05-04. - cd src && dotnet build -c Release 2>&1 | tail -5; cd .. && test $(grep -c "appliaction" src/Directory.Build.props) -eq 0 && test $(grep -riEc "\bSimpleConfig\b" src/Directory.Build.props) -eq 0 + set -o pipefail; cd src && dotnet build -c Release 2>&1 | tail -5; rc=$?; cd ..; [ $rc -eq 0 ] && test $(grep -c "appliaction" src/Directory.Build.props) -eq 0 && test $(grep -riEc "\bSimpleConfig\b" src/Directory.Build.props) -eq 0 - `grep -n "appliaction" src/Directory.Build.props` returns 0 hits (typo corrected to "application") diff --git a/.planning/phases/05-documentation/05-04-PLAN.md b/.planning/phases/05-documentation/05-04-PLAN.md index 015f38c..b9fea11 100644 --- a/.planning/phases/05-documentation/05-04-PLAN.md +++ b/.planning/phases/05-documentation/05-04-PLAN.md @@ -110,7 +110,7 @@ Artifacts this plan produces: Phase-final gate (run after the append; all from the repo root except the build/pack, which run from `src/`): the full cross-file grep sweep (13 DOC-VERIFICATION gates) and `dotnet build -c Release` + `dotnet pack -c Release` from `src/`. The pack proves the refreshed README + icon + `` (from 05-02) repack cleanly. - test $(grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Install-Package" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "\[DefaultValue" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "existall/Shepherd" README.md src/Directory.Build.props | wc -l) -eq 0 && test $(grep -n "appliaction" src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Extend%20Simple%20Config" README.md docs/ | wc -l) -eq 0 && for f in getting_started.md building_the_collection.md "Build Config Interface.md" "Default Values.md" "Build a SectionBinder.md" "Extending SimpleSettings.md" Security.md; do test -f "docs/$f" || { echo "MISSING docs/$f"; exit 1; }; done && cd src && dotnet build -c Release 2>&1 | tail -3 && dotnet pack -c Release 2>&1 | tail -3 + set -o pipefail && test $(grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rniE "\bSimpleConfig\b" README.md docs/ src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Install-Package" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "\[DefaultValue" README.md docs/ | wc -l) -eq 0 && test $(grep -rn "existall/Shepherd" README.md src/Directory.Build.props | wc -l) -eq 0 && test $(grep -n "appliaction" src/Directory.Build.props | wc -l) -eq 0 && test $(grep -rn "Extend%20Simple%20Config" README.md docs/ | wc -l) -eq 0 && for f in getting_started.md building_the_collection.md "Build Config Interface.md" "Default Values.md" "Build a SectionBinder.md" "Extending SimpleSettings.md" Security.md; do test -f "docs/$f" || { echo "MISSING docs/$f"; exit 1; }; done && cd src && dotnet build -c Release 2>&1 | tail -3 && dotnet pack -c Release 2>&1 | tail -3 - Gate 1: `grep -rn "existall/SimpleConfig" README.md docs/ src/Directory.Build.props` -> 0 hits diff --git a/.planning/phases/05-documentation/05-RESEARCH.md b/.planning/phases/05-documentation/05-RESEARCH.md index 1207bea..97b6a72 100644 --- a/.planning/phases/05-documentation/05-RESEARCH.md +++ b/.planning/phases/05-documentation/05-RESEARCH.md @@ -464,13 +464,13 @@ public class EmailSettingsValidator : ISettingValidation **Everything else in this research is `[VERIFIED]` against source or `[CITED]` from a Phase 3/4 sign-off — no user confirmation needed for the API facts, the legacy inventory, or the security invariants.** -## Open Questions +## Open Questions (RESOLVED) -1. **Which docs/ page holds the deep security guidance?** +1. **Which docs/ page holds the deep security guidance?** — **RESOLVED (05-03):** a dedicated new `docs/Security.md` (cleaner separation + a stable README deep-link target). - What we know: D-04 says depth lives in docs/; D-05 says refresh in place. `Extend Simple Config.md` (extension/behavior) or a new dedicated page are both viable. - What's unclear: page placement (explicitly Claude's discretion). - Recommendation: add a "Security & behavior" section to the renamed extend page, or a short new `docs/Security.md` if it grows; planner picks. -2. **README logo asset** (see A2). +2. **README logo asset** (see A2). — **RESOLVED (05-04):** absolute raw URL `https://raw.githubusercontent.com/existall/SimpleSettings/master/icon.png` (nuget.org renders only absolute HTTPS image URLs; `` keeps the packed local `icon.png`). - Recommendation: default to the packed local `icon.png`; confirm with user if a hosted brand logo is preferred. ## Environment Availability From d1a4d5d58e3abf0f0b662e12e5ef17582b6127aa Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 11:56:54 +0300 Subject: [PATCH 10/23] docs(05): record planning completion in state --- .planning/STATE.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.planning/STATE.md b/.planning/STATE.md index 18f9f92..5336a1f 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,10 +5,10 @@ milestone_name: milestone current_phase: 05 current_phase_name: documentation status: ready -stopped_at: Phase 5 context gathered — scoped to Documentation (DOC-01); AOT-01 deferred to a future v2.1 milestone. Ready to plan. +stopped_at: Phase 5 planned — 4 plans (2 waves); plan-checker PASSED (2 warnings fixed). Ready to execute. DOC-01 only; AOT-01 → v2.1. last_updated: "2026-07-19T12:00:00.000Z" last_activity: 2026-07-19 -last_activity_desc: Phase 05 context gathered (Documentation / DOC-01); AOT-01 deferred to v2.1 +last_activity_desc: Phase 05 planned — 4 plans, plan-checker PASSED (Documentation / DOC-01) progress: total_phases: 6 completed_phases: 3 @@ -24,14 +24,14 @@ progress: See: .planning/PROJECT.md (updated 2026-07-13) **Core value:** Correctness of binding — config → strongly-typed settings maps accurately across every supported shape (sections, arrays/enumerables, defaults, nullable, custom converters). -**Current focus:** Phase 05 — Documentation (DOC-01) — context gathered, ready to plan; Phase 04 complete +**Current focus:** Phase 05 — Documentation (DOC-01) — planned (4 plans, 2 waves), ready to execute; Phase 04 complete ## Current Position -Phase: 05 (documentation) — context gathered; ready to plan (DOC-01 only; AOT-01 deferred to v2.1) -Next: /gsd-plan-phase 5 -Status: 05-CONTEXT.md written; ROADMAP + REQUIREMENTS updated (Phase 5 → Documentation, AOT-01 → v2.1) -Last activity: 2026-07-19 — Phase 05 context gathered +Phase: 05 (documentation) — planned: 4 plans across 2 waves; ready to execute (DOC-01 only; AOT-01 deferred to v2.1) +Next: /gsd-execute-phase 5 +Status: 4 plans created; RESEARCH + VALIDATION (13 DOC-VERIFICATION gates) written; plan-checker PASSED (2 verify-gate warnings fixed) +Last activity: 2026-07-19 — Phase 05 planned (plan-checker passed) Progress: [█████░░░░░] 50% @@ -119,5 +119,5 @@ Items acknowledged and carried forward: ## Session Continuity Last session: 2026-07-19 -Stopped at: Phase 5 context gathered — Documentation (DOC-01); AOT-01 deferred to v2.1 +Stopped at: Phase 5 planned — 4 plans (2 waves), plan-checker PASSED; ready to execute Resume file: .planning/phases/05-documentation/05-CONTEXT.md From 80aa1b542438eb5e295475de2557910838798396 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:08:29 +0300 Subject: [PATCH 11/23] docs(05-01): rename extension guide to canonical filename - git mv "Extend Simple Config.md" -> "Extending SimpleSettings.md" (history preserved) - establishes canonical link target for inbound repoints and README --- docs/{Extend Simple Config.md => Extending SimpleSettings.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{Extend Simple Config.md => Extending SimpleSettings.md} (100%) diff --git a/docs/Extend Simple Config.md b/docs/Extending SimpleSettings.md similarity index 100% rename from docs/Extend Simple Config.md rename to docs/Extending SimpleSettings.md From f05f635c8fd28b136b8bdb918a610ea91391066a Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:09:06 +0300 Subject: [PATCH 12/23] docs(05-01): repoint inbound links + drop residual legacy parenthetical - repoint 5 in-docs links to Extending%20SimpleSettings.md (2x SectionBinder, 2x building_the_collection, 1x Build Config Interface) - remove "(previously SimpleConfig)" parenthetical in getting_started.md - docs/ now free of legacy product name and dead legacy-repo links --- docs/Build Config Interface.md | 2 +- docs/Build a SectionBinder.md | 4 ++-- docs/building_the_collection.md | 4 ++-- docs/getting_started.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Build Config Interface.md b/docs/Build Config Interface.md index bad1de6..5c8a5f9 100644 --- a/docs/Build Config Interface.md +++ b/docs/Build Config Interface.md @@ -36,7 +36,7 @@ public interface ISomeInterface ```` 3. Interface name Suffix. -By default SimpleSettings scans for interfaces with a name ending with `Settings` and adds it to the collection. ( The suffix option can be changed using `SettingsOptions`, to know more look at the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extend%20Simple%20Config.md) ) +By default SimpleSettings scans for interfaces with a name ending with `Settings` and adds it to the collection. ( The suffix option can be changed using `SettingsOptions`, to know more look at the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md) ) ````C# public interface ISomeInterfaceSettings diff --git a/docs/Build a SectionBinder.md b/docs/Build a SectionBinder.md index 869dc92..f29d20e 100644 --- a/docs/Build a SectionBinder.md +++ b/docs/Build a SectionBinder.md @@ -35,7 +35,7 @@ public class MySectionBinder : ISectionBinder ### Section The section name the context provides is the name of the interface with some manipulation, for example the interface `ISomeInterface` will be provided by the context as `SomeInterface`. SimpleSettings removes the leading `I`. -#### this is configurable via `SettingsOptions` (or per interface with `[SettingsSection("name")]`) and can be viewed on the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extend%20Simple%20Config.md) +#### this is configurable via `SettingsOptions` (or per interface with `[SettingsSection("name")]`) and can be viewed on the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md) ### Key The Key name the context provides is the property name as is, for example the interface `ISomeInterface.SomeProperty` will be provided as `SomeProperty`. You can override it per property with `[SettingsProperty(Name = "...")]`. @@ -57,4 +57,4 @@ var settings = SettingsBuilder .GetSettings(); ```` -In the next page we will learn how to [Extend](https://github.com/existall/SimpleSettings/blob/master/docs/Extend%20Simple%20Config.md) SimpleSettings and future features. +In the next page we will learn how to [Extend](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md) SimpleSettings and future features. diff --git a/docs/building_the_collection.md b/docs/building_the_collection.md index 323ce55..52e198c 100644 --- a/docs/building_the_collection.md +++ b/docs/building_the_collection.md @@ -13,7 +13,7 @@ SimpleSettings scans only the public (exported) interfaces in the assemblies you ## Options -To configure SimpleSettings, use the `CreateBuilder` overload that gives you a builder factory. The factory exposes `SetupOptions` along with a set of `Set*` helpers that mutate `SettingsOptions`. To better understand the options see the [Extend](https://github.com/existall/SimpleSettings/blob/master/docs/Extend%20Simple%20Config.md) section. +To configure SimpleSettings, use the `CreateBuilder` overload that gives you a builder factory. The factory exposes `SetupOptions` along with a set of `Set*` helpers that mutate `SettingsOptions`. To better understand the options see the [Extend](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md) section. ````C# var settingsCollection = SettingsBuilder @@ -42,6 +42,6 @@ SimpleSettings provides several binders out of the box. `InMemoryCollection` liv For more information about the Binders see the [Build a SectionBinder](https://github.com/existall/SimpleSettings/blob/master/docs/Build%20a%20SectionBinder.md) page. -To create new Binders see the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extend%20Simple%20Config.md). +To create new Binders see the [Extend section](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md). To continue on to Settings Interfaces click [here](https://github.com/existall/SimpleSettings/blob/master/docs/Build%20Config%20Interface.md). diff --git a/docs/getting_started.md b/docs/getting_started.md index 1ebd365..4f09683 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -1,7 +1,7 @@ Getting Started =============== -SimpleSettings (previously SimpleConfig) uses a `SettingsBuilder` in order to create your settings objects. `SettingsBuilder.ScanAssemblies` returns an `ISettingsCollection` that holds a key value pair of `Type` and the generated implementation of the settings interface. Thus it can be easily registered to any IOC container of your liking. +SimpleSettings uses a `SettingsBuilder` in order to create your settings objects. `SettingsBuilder.ScanAssemblies` returns an `ISettingsCollection` that holds a key value pair of `Type` and the generated implementation of the settings interface. Thus it can be easily registered to any IOC container of your liking. ## Installation SimpleSettings ships as a set of NuGet packages. The core package is all you need to get started: From 8c3498da326744cfedfa7df3d0264406106b5931 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:10:41 +0300 Subject: [PATCH 13/23] docs(05-01): complete docs-canonicalization plan --- .planning/REQUIREMENTS.md | 4 +- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 33 ++--- .../phases/05-documentation/05-01-SUMMARY.md | 127 ++++++++++++++++++ 4 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 .planning/phases/05-documentation/05-01-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 8e6572c..b9a5193 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -67,7 +67,7 @@ Remaining open work (from `FIX-PLAN.md`), batched toward the first `v2.0.0-beta` ### Documentation -- [ ] **DOC-01**: Refresh README + docs/ to canonical `ExistForAll.SimpleSettings` naming and current repo/package links; document the Phase 1–4 security/behavior guidance (secret-redaction; validator secret-safety incl. constructors; opt-in/deferred DI validation; validate⇒discoverable coupling; spaced-secrets binding; Phase-3 breaking-change list) +- [x] **DOC-01**: Refresh README + docs/ to canonical `ExistForAll.SimpleSettings` naming and current repo/package links; document the Phase 1–4 security/behavior guidance (secret-redaction; validator secret-safety incl. constructors; opt-in/deferred DI validation; validate⇒discoverable coupling; spaced-secrets binding; Phase-3 breaking-change list) *(AOT-01 was mapped here but was **deferred to a future v2.1 milestone** on 2026-07-19 — see v2 Requirements → Deferred.)* @@ -119,7 +119,7 @@ Deferred / held. Tracked but not in the current roadmap. | VAL-02 | Phase 4 | Complete | | API-02 | Phase 4 | ✓ Complete (#35) | | AOT-01 | v2.1 (deferred) | Deferred | -| DOC-01 | Phase 5 | Pending | +| DOC-01 | Phase 5 | Complete | | REL-01 | Phase 6 | Pending | **Coverage:** diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 702eff5..77dbc1a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -134,11 +134,11 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). -**Plans**: 4 plans +**Plans**: 1/4 plans executed **Wave 1** *(parallel — no file overlap)* -- [ ] 05-01-PLAN.md — docs/ canonicalization: rename the extension guide to a canonical filename + repoint its 5 in-docs inbound links; drop the residual legacy parenthetical in getting_started.md (D-05) [Wave 1] +- [x] 05-01-PLAN.md — docs/ canonicalization: rename the extension guide to a canonical filename + repoint its 5 in-docs inbound links; drop the residual legacy parenthetical in getting_started.md (D-05) [Wave 1] - [ ] 05-02-PLAN.md — package metadata: fix `` typo + legacy `` token in src/Directory.Build.props; build sanity (D-06) [Wave 1] - [ ] 05-03-PLAN.md — new deep-guidance page docs/Security.md: the 6 mandated Phase 1–4 items (secret-redaction + 2 caveats, validator secret-safety incl. constructor, opt-in/deferred `ValidateSimpleSettings()` on `IServiceProvider`, validate⇒discoverable, `AddCommandLine` spaced values, v1→v2 breaking-change list) (D-04 deep) [Wave 1] @@ -171,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 0/4 | Not started | - | +| 5. Documentation | 1/4 | In Progress| | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 5336a1f..fb8c2dc 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,18 +3,18 @@ gsd_state_version: 1.0 milestone: v2.0.0 milestone_name: milestone current_phase: 05 -current_phase_name: documentation -status: ready -stopped_at: Phase 5 planned — 4 plans (2 waves); plan-checker PASSED (2 warnings fixed). Ready to execute. DOC-01 only; AOT-01 → v2.1. -last_updated: "2026-07-19T12:00:00.000Z" -last_activity: 2026-07-19 -last_activity_desc: Phase 05 planned — 4 plans, plan-checker PASSED (Documentation / DOC-01) +current_phase_name: Documentation +status: executing +stopped_at: Phase 5 Plan 01 complete — docs/ canonicalized (extension guide renamed, 5 inbound links repointed, SimpleConfig parenthetical purged). Next plan 05-02. +last_updated: "2026-07-20T09:10:04.850Z" +last_activity: 2026-07-20 +last_activity_desc: Completed 05-01-PLAN.md — docs canonicalization progress: total_phases: 6 completed_phases: 3 - total_plans: 9 - completed_plans: 9 - percent: 50 + total_plans: 13 + completed_plans: 10 + percent: 77 --- # Project State @@ -24,16 +24,16 @@ progress: See: .planning/PROJECT.md (updated 2026-07-13) **Core value:** Correctness of binding — config → strongly-typed settings maps accurately across every supported shape (sections, arrays/enumerables, defaults, nullable, custom converters). -**Current focus:** Phase 05 — Documentation (DOC-01) — planned (4 plans, 2 waves), ready to execute; Phase 04 complete +**Current focus:** Phase 05 — Documentation ## Current Position -Phase: 05 (documentation) — planned: 4 plans across 2 waves; ready to execute (DOC-01 only; AOT-01 deferred to v2.1) -Next: /gsd-execute-phase 5 -Status: 4 plans created; RESEARCH + VALIDATION (13 DOC-VERIFICATION gates) written; plan-checker PASSED (2 verify-gate warnings fixed) -Last activity: 2026-07-19 — Phase 05 planned (plan-checker passed) +Phase: 05 (Documentation) — EXECUTING +Next: Execute plan 05-02 +Status: Executing Phase 05 — plan 05-01 complete +Last activity: 2026-07-20 — Completed 05-01 (docs canonicalization) -Progress: [█████░░░░░] 50% +Progress: [████████░░] 77% ## Performance Metrics @@ -65,6 +65,7 @@ Progress: [█████░░░░░] 50% | Phase 04 P03 | 5min | 2 tasks | 10 files | | Phase 04 P05 | 3min | 1 tasks | 2 files | | Phase 04 P04 | ~35min | 2 tasks | 5 files | +| Phase 05 P01 | 5min | 2 tasks | 5 files | ## Accumulated Context @@ -118,6 +119,6 @@ Items acknowledged and carried forward: ## Session Continuity -Last session: 2026-07-19 +Last session: 2026-07-20T09:09:53.421Z Stopped at: Phase 5 planned — 4 plans (2 waves), plan-checker PASSED; ready to execute Resume file: .planning/phases/05-documentation/05-CONTEXT.md diff --git a/.planning/phases/05-documentation/05-01-SUMMARY.md b/.planning/phases/05-documentation/05-01-SUMMARY.md new file mode 100644 index 0000000..c3d4020 --- /dev/null +++ b/.planning/phases/05-documentation/05-01-SUMMARY.md @@ -0,0 +1,127 @@ +--- +phase: 05-documentation +plan: 01 +subsystem: docs +tags: [documentation, markdown, links, legacy-purge] + +# Dependency graph +requires: + - phase: 04-collection-validation + provides: canonical library naming already applied across five of six docs pages +provides: + - Canonical extension-guide filename (docs/Extending SimpleSettings.md) with git history preserved + - All five in-docs inbound links repointed to the renamed page on existall/SimpleSettings + - docs/ tree free of legacy product name (SimpleConfig) and dead legacy-repo links +affects: [05-04, README repoint, phase-final legacy sweep] + +# Tech tracking +tech-stack: + added: [] + patterns: [Rename-with-backlink-repoint (git mv + same-plan inbound-link repoint)] + +key-files: + created: [] + modified: + - docs/Extending SimpleSettings.md (renamed from docs/Extend Simple Config.md via git mv) + - docs/getting_started.md + - docs/building_the_collection.md + - docs/Build a SectionBinder.md + - docs/Build Config Interface.md + +key-decisions: + - "Renamed via git mv (not delete+create) so page history is preserved and the new filename exists before any link is repointed at it (interface-first)." + - "README.md ToC entry intentionally NOT touched here — it is repointed in 05-04 as part of the cross-file legacy sweep." + +patterns-established: + - "Rename-with-backlink-repoint: establish the canonical target with git mv first, then repoint every inbound link in the same plan so no dangling link is ever committed." + +requirements-completed: [DOC-01] + +coverage: + - id: D1 + description: "Extension guide reachable at canonical space-encoded filename docs/Extending SimpleSettings.md with git history preserved and no legacy product name in its content" + requirement: DOC-01 + verification: + - kind: automated_ui + ref: "test -f 'docs/Extending SimpleSettings.md' && test ! -f 'docs/Extend Simple Config.md' && git ls-files --error-unmatch 'docs/Extending SimpleSettings.md'" + status: pass + - kind: other + ref: "grep -rniE '\\bSimpleConfig\\b' 'docs/Extending SimpleSettings.md' -> 0 hits" + status: pass + human_judgment: false + - id: D2 + description: "All five in-docs inbound links repointed to Extending%20SimpleSettings.md and residual legacy parenthetical removed from getting_started.md; docs/ carries no SimpleConfig name or dead legacy-repo link" + requirement: DOC-01 + verification: + - kind: other + ref: "grep -rn 'Extend%20Simple%20Config' docs/ -> 0; grep -rniE '\\bSimpleConfig\\b' docs/ -> 0; grep -rn 'existall/SimpleConfig' docs/ -> 0; grep -rn 'Extending%20SimpleSettings.md' docs/ -> 5" + status: pass + human_judgment: false + +# Metrics +duration: 5min +completed: 2026-07-20 +status: complete +--- + +# Phase 5 Plan 01: Docs Canonicalization Summary + +**Renamed the legacy-named extension guide to `docs/Extending SimpleSettings.md` (history preserved), repointed all five in-docs inbound links, and purged the last `SimpleConfig` parenthetical from getting_started.md — docs/ is now legacy-name-free.** + +## Performance + +- **Duration:** ~5 min +- **Started:** 2026-07-20 +- **Completed:** 2026-07-20 +- **Tasks:** 2 +- **Files modified:** 5 (1 renamed, 4 edited) + +## Accomplishments +- `git mv "docs/Extend Simple Config.md" "docs/Extending SimpleSettings.md"` — canonical filename established, git history preserved (100% rename similarity). +- Repointed all five in-docs inbound links to `Extending%20SimpleSettings.md` (SectionBinder x2, building_the_collection x2, Build Config Interface x1); host segment left untouched (already `existall/SimpleSettings`). +- Removed the residual `(previously SimpleConfig)` parenthetical from getting_started.md line 4. +- Verified docs/ has 0 references to the old filename, 0 `SimpleConfig` occurrences, 0 dead `existall/SimpleConfig` links, and 5 links to the new page. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Rename the extension guide to a canonical filename** - `80aa1b5` (docs, git mv rename) +2. **Task 2: Repoint in-docs inbound links + drop residual legacy parenthetical** - `f05f635` (docs) + +## Files Created/Modified +- `docs/Extending SimpleSettings.md` - Renamed from `docs/Extend Simple Config.md` (git mv, content unchanged — already canonical) +- `docs/getting_started.md` - Removed `(previously SimpleConfig)` parenthetical (line 4) +- `docs/building_the_collection.md` - Repointed 2 extension-page links +- `docs/Build a SectionBinder.md` - Repointed 2 extension-page links +- `docs/Build Config Interface.md` - Repointed 1 extension-page link + +## Decisions Made +- None beyond plan — executed as specified. Rename via `git mv` to preserve history; README ToC entry deferred to 05-04 per plan. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +- After `git mv`, the initial staged-commit attempt used the old pathspec (`git add "docs/Extend Simple Config.md"`), which no longer exists on disk and errored. The rename was already staged by `git mv`, so committing the staged change directly resolved it. No content impact. + +## Threat Surface +- T-05-01 (Tampering / docs link integrity) mitigated as planned: rename + all inbound repoints landed together; acceptance gate confirms zero references to the old filename and that the new file exists. No new security-relevant surface introduced. + +## User Setup Required +None - documentation-only, no external service configuration required. + +## Next Phase Readiness +- Canonical extension-page URL (`docs/Extending%20SimpleSettings.md`) is stable for the README repoint in 05-04. +- The cross-file legacy sweep spanning README + props remains the phase-final gate in 05-04 (out of scope here). + +## Self-Check: PASSED + +- FOUND: docs/Extending SimpleSettings.md +- FOUND commit 80aa1b5 (Task 1 rename) +- FOUND commit f05f635 (Task 2 repoints) + +--- +*Phase: 05-documentation* +*Completed: 2026-07-20* From 066cb99cf5535df31b6267fc0a7f8c64ffc44b8e Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:12:27 +0300 Subject: [PATCH 14/23] fix(05-02): correct Description typo and legacy PackageTags token - Fix 'appliaction' -> 'application' in packaged - Replace legacy 'SimpleConfig' PackageTags token with 'SimpleSettings' --- src/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 14d1ea2..9541e52 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -13,12 +13,12 @@ Guy Ludvig ExistForAll - SimpleSettings allow applications to use configurations values from any data store in the type of interfaces, thus decouples the frameworks from your appliaction and allowing application to inject interfaces and nothing else. This framework should be used instead of .Net Core IOptions<T>. + SimpleSettings allow applications to use configurations values from any data store in the type of interfaces, thus decouples the frameworks from your application and allowing application to inject interfaces and nothing else. This framework should be used instead of .Net Core IOptions<T>. https://github.com/existall/SimpleSettings https://github.com/existall/SimpleSettings git MIT - Configuration, Settings, Ioc, DI, ExistForAll, Dependency injection, Options, SimpleConfig + Configuration, Settings, Ioc, DI, ExistForAll, Dependency injection, Options, SimpleSettings icon.png README.md From 2fba886c1f1cdfdfca90ba7806344d45cadb0526 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:14:30 +0300 Subject: [PATCH 15/23] docs(05-02): complete package-metadata token fix plan --- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 26 ++-- .../phases/05-documentation/05-02-SUMMARY.md | 116 ++++++++++++++++++ 3 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 .planning/phases/05-documentation/05-02-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 77dbc1a..225da47 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -134,12 +134,12 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). -**Plans**: 1/4 plans executed +**Plans**: 2/4 plans executed **Wave 1** *(parallel — no file overlap)* - [x] 05-01-PLAN.md — docs/ canonicalization: rename the extension guide to a canonical filename + repoint its 5 in-docs inbound links; drop the residual legacy parenthetical in getting_started.md (D-05) [Wave 1] -- [ ] 05-02-PLAN.md — package metadata: fix `` typo + legacy `` token in src/Directory.Build.props; build sanity (D-06) [Wave 1] +- [x] 05-02-PLAN.md — package metadata: fix `` typo + legacy `` token in src/Directory.Build.props; build sanity (D-06) [Wave 1] - [ ] 05-03-PLAN.md — new deep-guidance page docs/Security.md: the 6 mandated Phase 1–4 items (secret-redaction + 2 caveats, validator secret-safety incl. constructor, opt-in/deferred `ValidateSimpleSettings()` on `IServiceProvider`, validate⇒discoverable, `AddCommandLine` spaced values, v1→v2 breaking-change list) (D-04 deep) [Wave 1] **Wave 2** *(blocked on Wave 1 completion)* @@ -171,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 1/4 | In Progress| | +| 5. Documentation | 2/4 | In Progress| | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index fb8c2dc..c5a6165 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,16 +5,16 @@ milestone_name: milestone current_phase: 05 current_phase_name: Documentation status: executing -stopped_at: Phase 5 Plan 01 complete — docs/ canonicalized (extension guide renamed, 5 inbound links repointed, SimpleConfig parenthetical purged). Next plan 05-02. -last_updated: "2026-07-20T09:10:04.850Z" +stopped_at: Completed 05-02 (package metadata token fix). Next plan 05-03. +last_updated: "2026-07-20T09:13:52.970Z" last_activity: 2026-07-20 -last_activity_desc: Completed 05-01-PLAN.md — docs canonicalization +last_activity_desc: Completed 05-02 (package metadata token fix) progress: total_phases: 6 completed_phases: 3 total_plans: 13 - completed_plans: 10 - percent: 77 + completed_plans: 12 + percent: 92 --- # Project State @@ -29,11 +29,11 @@ See: .planning/PROJECT.md (updated 2026-07-13) ## Current Position Phase: 05 (Documentation) — EXECUTING -Next: Execute plan 05-02 -Status: Executing Phase 05 — plan 05-01 complete -Last activity: 2026-07-20 — Completed 05-01 (docs canonicalization) +Next: Execute plan 05-03 +Status: Executing Phase 05 — plan 05-02 complete +Last activity: 2026-07-20 — Completed 05-02 (package metadata token fix) -Progress: [████████░░] 77% +Progress: [█████████░] 92% ## Performance Metrics @@ -66,6 +66,7 @@ Progress: [████████░░] 77% | Phase 04 P05 | 3min | 1 tasks | 2 files | | Phase 04 P04 | ~35min | 2 tasks | 5 files | | Phase 05 P01 | 5min | 2 tasks | 5 files | +| Phase 05 P02 | 3min | 1 tasks | 1 files | ## Accumulated Context @@ -92,6 +93,7 @@ Recent decisions affecting current work: - [Phase 04]: 04-05 VAL-02: reuse value-free SettingsPropertyNullException for empty/whitespace rejection — already excluded from the ValuesPopulator:122 redaction filter, so no filter change - [Phase 04]: 04-05 VAL-02: reject guard placed ahead of 04-01's Func list null-result factory dispatch and gated on _throwOnNull; accept path and factory dispatch untouched - [Phase 04]: 04-04 VAL-01 DI path + API-02: ISettingsCollection exposed via a DI singleton + an AddSimpleSettings(out ISettingsCollection, Action?) overload; deferred opt-in IServiceProvider.ValidateSimpleSettings() runs DI-registered ISettingValidation from a fresh scope (IServiceScopeFactory), dispatches via the DIM bridge (no reflection), and throws the same value-free SettingsValidationException as the core path via the shared ThrowIfAny. Runner is internal; DI path is additive (reads no attribute). +- [Phase ?]: 05-02: Replaced legacy SimpleConfig PackageTags token with canonical SimpleSettings; fixed Description typo appliaction->application ### Pending Todos @@ -119,6 +121,6 @@ Items acknowledged and carried forward: ## Session Continuity -Last session: 2026-07-20T09:09:53.421Z -Stopped at: Phase 5 planned — 4 plans (2 waves), plan-checker PASSED; ready to execute -Resume file: .planning/phases/05-documentation/05-CONTEXT.md +Last session: 2026-07-20T09:13:52.964Z +Stopped at: Completed 05-02 (package metadata token fix). Next plan 05-03. +Resume file: None diff --git a/.planning/phases/05-documentation/05-02-SUMMARY.md b/.planning/phases/05-documentation/05-02-SUMMARY.md new file mode 100644 index 0000000..7de547c --- /dev/null +++ b/.planning/phases/05-documentation/05-02-SUMMARY.md @@ -0,0 +1,116 @@ +--- +phase: 05-documentation +plan: 02 +subsystem: infra +tags: [msbuild, nuget, packaging, metadata, directory-build-props] + +# Dependency graph +requires: + - phase: 04 + provides: collection binding + validation engine (the shipped surface the package metadata describes) +provides: + - Correctly-spelled, legacy-name-free packaged + - Canonical (SimpleSettings replaces legacy SimpleConfig token) +affects: [05-04] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Shared MSBuild props (src/Directory.Build.props) inject package metadata into all three published packages" + +key-files: + created: [] + modified: + - src/Directory.Build.props + +key-decisions: + - "Replaced the legacy SimpleConfig PackageTags token with SimpleSettings (canonical) rather than dropping it, keeping tag count stable" + +patterns-established: + - "Package-metadata token fixes are proven by a green dotnet build -c Release from src/ (props-parse gate)" + +requirements-completed: [DOC-01] + +coverage: + - id: D1 + description: "Packaged reads with correct spelling ('application') and no legacy product name" + requirement: "DOC-01" + verification: + - kind: other + ref: "grep -c 'appliaction' src/Directory.Build.props -> 0" + status: pass + human_judgment: false + - id: D2 + description: " carries no legacy SimpleConfig token; canonical SimpleSettings present" + requirement: "DOC-01" + verification: + - kind: other + ref: "grep -riEc '\\bSimpleConfig\\b' src/Directory.Build.props -> 0" + status: pass + human_judgment: false + - id: D3 + description: "Shared props still parse; all three packages build cleanly under Release" + requirement: "DOC-01" + verification: + - kind: integration + ref: "cd src && dotnet build -c Release -> exit 0, 0 warnings 0 errors" + status: pass + human_judgment: false + +# Metrics +duration: 3min +completed: 2026-07-20 +status: complete +--- + +# Phase 5 Plan 02: Package Metadata Token Fix Summary + +**Corrected the misspelled 'appliaction' in the packaged and replaced the legacy SimpleConfig token with SimpleSettings in src/Directory.Build.props, verified by a green Release build.** + +## Performance + +- **Duration:** 3 min +- **Started:** 2026-07-20 +- **Completed:** 2026-07-20 +- **Tasks:** 1 +- **Files modified:** 1 + +## Accomplishments +- Fixed the `` typo `appliaction` -> `application` (consumer-visible on the nuget.org package page) +- Replaced the trailing legacy `SimpleConfig` token in `` with the canonical `SimpleSettings`, keeping all other tags intact +- Proved the shared props still parse: `dotnet build -c Release` from `src/` exits 0 with 0 warnings / 0 errors across all three packages + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Correct the Description typo and replace the legacy PackageTags token** - `066cb99` (fix) + +**Plan metadata:** (recorded in final docs commit) + +## Files Created/Modified +- `src/Directory.Build.props` - Two token edits: `` typo corrected, `` legacy token replaced. No structural change; URLs, icon, README, versioning, and pack ItemGroup untouched (all verified canonical). + +## Decisions Made +- Replaced the legacy `SimpleConfig` tag with `SimpleSettings` rather than dropping it, preserving the tag list shape while removing the stale product name. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +None. + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Package metadata (``, ``) is now canonical and legacy-name-free. +- The README logo fix (broken `existall/Shepherd` URL) and the phase-final cross-file grep sweep + `dotnet pack` gate remain owned by 05-04. + +## Self-Check: PASSED + +--- +*Phase: 05-documentation* +*Completed: 2026-07-20* From 17645309229bdd554f8f3b1cdbed3e63e3933751 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:17:04 +0300 Subject: [PATCH 16/23] docs(05-03): author Security & Behavior guarantees page - Document secret-redaction value-free invariant with both carve-outs (author ValidationError text + DI validator constructors) - Add secret-safe validator example (echoes no bound value) - Document opt-in/deferred ValidateSimpleSettings() on IServiceProvider after BuildServiceProvider() - State validate => discoverable coupling via [SettingsSection(ValidatorType)] - Document AddCommandLine spaced-value binding with prefix-lookahead caveat --- docs/Security.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/Security.md diff --git a/docs/Security.md b/docs/Security.md new file mode 100644 index 0000000..6ed1037 --- /dev/null +++ b/docs/Security.md @@ -0,0 +1,88 @@ +# Security & Behavior + +This page is the deep reference for SimpleSettings' security and behavior guarantees. Each guarantee is stated with its exact caveats — read the caveats, not just the headline, before you rely on any invariant. + +## Secret redaction (value-free exceptions) + +No bound configuration value — which may be a secret — appears in any library exception's `ToString()` chain that reaches your logs. Every exception SimpleSettings throws is **value-free**: it carries only type and property metadata, and it never chains an inner exception that saw the bound value. The guarantee is *structural*, not conventional: when a conversion fails, `SettingsPropertyValueException` is constructed from the failure's CLR `Type` (the converter error's `Type`, not the `Exception` and not the value), so there is no path for the value to travel into the message or an inner exception (Phase 4 `SECURITY.md:24-27`). + +There are exactly **two carve-outs** — places the value-free guard does *not* reach: + +- **Author-supplied `ValidationError` text.** The message you put in a `ValidationError` reaches `SettingsValidationException.ToString()` by design. The library treats it as author text, not a bound value, so it is never redacted. If you echo a secret into your own error message, it surfaces. +- **DI-resolved validator constructors.** When the container resolves a validator, the **constructor** runs *outside* the value-free bind guard. A constructor that logs or echoes an injected secret leaks it — the library cannot redact code it did not run. + +Both carve-outs are documented accepted residuals of the Phase 4 security sign-off (`SECURITY.md:75-78`). Do not read the redaction invariant as "secrets never appear in any exception" — it covers the library's own exceptions, not your validator text or your validator constructors. + +## Validators must not echo secrets + +Follow two rules when authoring a validator: + +1. Never put a bound value (a possible secret) into `ValidationError` text. +2. Never log or echo a secret in a validator **constructor** — DI resolution runs before the value-free bind guard. + +The following validator is secret-safe: it inspects a value and reports a bound-independent message, echoing no bound value. + +````C# +public class EmailSettingsValidator : ISettingValidation +{ + public ValidationResult Validate(ValidationContext context) + { + var result = new ValidationResult(); + if (context.Settings!.Retries < 0) + result.AddError(new ValidationError(nameof(IEmailSenderSettings.Retries), "Retries must be >= 0")); + return result; // never put a bound value (possible secret) in the message + } +} +```` + +## Opt-in / deferred DI validation + +DI-registered `ISettingValidation` validators cannot run during `AddSimpleSettings` — the container is not built yet, so `AddSimpleSettings` only registers the validation runner and never invokes a validator. To run them, the host must call `ValidateSimpleSettings()` explicitly, **after** `BuildServiceProvider()`. + +The method extends **`IServiceProvider`** — not `IServiceCollection`, not `IHost` — and returns the same provider so the call is chainable. + +````C# +var serviceProvider = services.BuildServiceProvider(); + +// opt-in, deferred DI validation — runs the DI-registered validators now: +serviceProvider.ValidateSimpleSettings(); +```` + +Attribute and `ValidatorType` validators (`[SettingsSection(ValidatorType = ...)]`, `[SettingsProperty(ValidatorType = ...)]`) run **inline during binding** and need no such call — `ValidateSimpleSettings()` is only for the DI-resolved `ISettingValidation` path. + +## Validators make a type discoverable + +Declaring an object-level validator via `[SettingsSection(ValidatorType = typeof(EmailSettingsValidator))]` also marks the type as scan-discovered: the type now carries `SettingsSectionAttribute`, which is one of the discovery mechanisms. Attaching a validator to a settings interface is therefore sufficient on its own to make it a discovered settings section — a side effect worth knowing when you add a validator to a type that was not previously discovered. + +````C# +[SettingsSection(ValidatorType = typeof(EmailSettingsValidator))] +public interface IEmailSenderSettings +{ + [SettingsProperty(DefaultValue = "https://smtp.example.com")] + string ServiceUrl { get; set; } + + [SettingsProperty(DefaultValue = 3)] + int Retries { get; set; } +} +```` + +## Command-line values with spaces + +`AddCommandLine` sources the process arguments from `Environment.GetCommandLineArgs()` and skips the executable path (`arg[0]`) internally. For a prefixed key with no inline delimiter, the parser looks ahead to the **next** token and binds it as the value — **unless** that next token itself starts with a prefix character (`-` or `/`), in which case it is treated as a new key rather than a value. + +So a shell-quoted value containing spaces arrives as a single token and binds: + +````C# +// --Key "a b c" reaches the process as one token and binds as the value of Key +builder.AddCommandLine(); +```` + +But a *value* that begins with `-` or `/` will not bind via the space-separated form — the lookahead treats it as a new key. For any value that may start with a prefix character, use the inline delimiter form instead: + +````C# +// bind a value that begins with '-' or '/' — use the inline '=' delimiter +// --Key=-leading-dash-value +builder.AddCommandLine(); +```` + +Prefer the inline `--Key=value` form whenever a value could begin with `-` or `/`. From c4fa856209f7dc043cf88358847d970cef94d3c2 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:17:25 +0300 Subject: [PATCH 17/23] docs(05-03): append v1 -> v2 migration section - List four Phase-3 breaking changes: SettingsHolder internal (API-01), Core.AspNet dropped (PKG-01), per-TFM Microsoft.Extensions floor (PKG-02), public SimpleSettingsException base (EXC-01) - Name versions, not the legacy product token --- docs/Security.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Security.md b/docs/Security.md index 6ed1037..18d8f83 100644 --- a/docs/Security.md +++ b/docs/Security.md @@ -86,3 +86,12 @@ builder.AddCommandLine(); ```` Prefer the inline `--Key=value` form whenever a value could begin with `-` or `/`. + +## Migration (v1 -> v2) + +The v1 -> v2 release batched four breaking changes. Each is listed with what changed and what to do: + +- **`SettingsHolder` / `ISettingsHolder` are now internal.** These types are no longer part of the public surface — remove any consumer reference to them; use the public `SettingsBuilder` / `ISettingsCollection` / `ISettingsProvider` surface instead. +- **The `Core.AspNet` package was dropped.** It exposed no public type. Remove any package reference to it; nothing you depended on lived there. +- **The `Microsoft.Extensions.*` dependency floor is now per-TFM.** SimpleSettings floors to `8.0.x` on `net8.0` and to the current release on `net10.0`, so it no longer forces a single shared floor across target frameworks. +- **A public `abstract SimpleSettingsException` base was introduced and boundary exceptions were made public and structured.** Catch `SimpleSettingsException` to handle every library error as one category. The old bare-`Exception` throw for a non-interface settings type is replaced by the public, structured `SettingsTypeNotInterfaceException`. From 037867fa99c78e154e85d51fcf6257078508c12f Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:20:21 +0300 Subject: [PATCH 18/23] docs(05-03): complete Security & Behavior guidance plan --- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 18 +-- .../phases/05-documentation/05-03-SUMMARY.md | 127 ++++++++++++++++++ 3 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 .planning/phases/05-documentation/05-03-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 225da47..a3070b9 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -134,13 +134,13 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). -**Plans**: 2/4 plans executed +**Plans**: 3/4 plans executed **Wave 1** *(parallel — no file overlap)* - [x] 05-01-PLAN.md — docs/ canonicalization: rename the extension guide to a canonical filename + repoint its 5 in-docs inbound links; drop the residual legacy parenthetical in getting_started.md (D-05) [Wave 1] - [x] 05-02-PLAN.md — package metadata: fix `` typo + legacy `` token in src/Directory.Build.props; build sanity (D-06) [Wave 1] -- [ ] 05-03-PLAN.md — new deep-guidance page docs/Security.md: the 6 mandated Phase 1–4 items (secret-redaction + 2 caveats, validator secret-safety incl. constructor, opt-in/deferred `ValidateSimpleSettings()` on `IServiceProvider`, validate⇒discoverable, `AddCommandLine` spaced values, v1→v2 breaking-change list) (D-04 deep) [Wave 1] +- [x] 05-03-PLAN.md — new deep-guidance page docs/Security.md: the 6 mandated Phase 1–4 items (secret-redaction + 2 caveats, validator secret-safety incl. constructor, opt-in/deferred `ValidateSimpleSettings()` on `IServiceProvider`, validate⇒discoverable, `AddCommandLine` spaced values, v1→v2 breaking-change list) (D-04 deep) [Wave 1] **Wave 2** *(blocked on Wave 1 completion)* @@ -171,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 2/4 | In Progress| | +| 5. Documentation | 3/4 | In Progress| | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index c5a6165..9640a14 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,10 +5,10 @@ milestone_name: milestone current_phase: 05 current_phase_name: Documentation status: executing -stopped_at: Completed 05-02 (package metadata token fix). Next plan 05-03. -last_updated: "2026-07-20T09:13:52.970Z" +stopped_at: Completed 05-03. Next plan 05-04. +last_updated: "2026-07-20T09:19:22.565Z" last_activity: 2026-07-20 -last_activity_desc: Completed 05-02 (package metadata token fix) +last_activity_desc: Completed 05-03 (docs/Security.md security & behavior + migration page) progress: total_phases: 6 completed_phases: 3 @@ -29,9 +29,9 @@ See: .planning/PROJECT.md (updated 2026-07-13) ## Current Position Phase: 05 (Documentation) — EXECUTING -Next: Execute plan 05-03 -Status: Executing Phase 05 — plan 05-02 complete -Last activity: 2026-07-20 — Completed 05-02 (package metadata token fix) +Next: Execute plan 05-04 +Status: Executing Phase 05 — plan 05-03 complete +Last activity: 2026-07-20 — Completed 05-03 (docs/Security.md security & behavior + migration page) Progress: [█████████░] 92% @@ -67,6 +67,7 @@ Progress: [█████████░] 92% | Phase 04 P04 | ~35min | 2 tasks | 5 files | | Phase 05 P01 | 5min | 2 tasks | 5 files | | Phase 05 P02 | 3min | 1 tasks | 1 files | +| Phase 05 P03 | 4min | 2 tasks | 1 files | ## Accumulated Context @@ -94,6 +95,7 @@ Recent decisions affecting current work: - [Phase 04]: 04-05 VAL-02: reject guard placed ahead of 04-01's Func list null-result factory dispatch and gated on _throwOnNull; accept path and factory dispatch untouched - [Phase 04]: 04-04 VAL-01 DI path + API-02: ISettingsCollection exposed via a DI singleton + an AddSimpleSettings(out ISettingsCollection, Action?) overload; deferred opt-in IServiceProvider.ValidateSimpleSettings() runs DI-registered ISettingValidation from a fresh scope (IServiceScopeFactory), dispatches via the DIM bridge (no reflection), and throws the same value-free SettingsValidationException as the core path via the shared ThrowIfAny. Runner is internal; DI path is additive (reads no attribute). - [Phase ?]: 05-02: Replaced legacy SimpleConfig PackageTags token with canonical SimpleSettings; fixed Description typo appliaction->application +- [Phase ?]: 05-03: Placed deep Phase 1-4 security/behavior guidance in a dedicated new docs/Security.md (stable README deep-link target); stated the redaction invariant with both carve-outs without over-claiming ### Pending Todos @@ -121,6 +123,6 @@ Items acknowledged and carried forward: ## Session Continuity -Last session: 2026-07-20T09:13:52.964Z -Stopped at: Completed 05-02 (package metadata token fix). Next plan 05-03. +Last session: 2026-07-20T09:19:09.588Z +Stopped at: Completed 05-03. Next plan 05-04. Resume file: None diff --git a/.planning/phases/05-documentation/05-03-SUMMARY.md b/.planning/phases/05-documentation/05-03-SUMMARY.md new file mode 100644 index 0000000..f73306b --- /dev/null +++ b/.planning/phases/05-documentation/05-03-SUMMARY.md @@ -0,0 +1,127 @@ +--- +phase: 05-documentation +plan: 03 +subsystem: docs +tags: [documentation, security, redaction, validation, migration, breaking-changes] + +# Dependency graph +requires: + - phase: 04 + provides: secret-redaction invariant + validation engine + opt-in DI validation (the guarantees this page documents) + - phase: 03 + provides: public-surface / packaging breaking changes (API-01, PKG-01, PKG-02, EXC-01) documented in the migration section +provides: + - "docs/Security.md — deep Security & Behavior guarantees page (five invariants with caveats)" + - "docs/Security.md #migration — v1 -> v2 breaking-change list" + - "Stable README deep-link target for the concise Security notes + Breaking changes sections (05-04 links here)" +affects: [05-04] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Concise-in-README, deep-in-docs (D-04): the security/behavior contract lives in a dedicated docs/Security.md the README summarizes" + - "Verify-against-source: every API token copied from 05-RESEARCH ## Current Public API, never paraphrased" + +key-files: + created: + - docs/Security.md + modified: [] + +key-decisions: + - "Placed the deep security/behavior guidance in a dedicated new docs/Security.md (Claude's-discretion placement, RESEARCH Open Question 1 RESOLVED) rather than the renamed extension guide — keeps the security contract separable and gives the README a stable deep-link target" + - "Stated the redaction invariant WITH both carve-outs (author ValidationError text + DI validator constructors) and did not over-claim (RESEARCH Pitfall 4 / SECURITY.md:75-78)" + +patterns-established: + - "Pattern: pair every security invariant headline with its explicit carve-outs so a reader never relies on a guarantee the library does not make" + +requirements-completed: [DOC-01] + +coverage: + - id: D1 + description: "docs/Security.md documents the secret-redaction value-free invariant with both carve-outs (author ValidationError text + DI validator constructors)" + requirement: "DOC-01" + verification: + - kind: other + ref: "grep -Eci 'value-free|redact' docs/Security.md >= 1 (=7); grep -ci 'constructor' docs/Security.md >= 1 (=3)" + status: pass + human_judgment: true + rationale: "Security-auditor pass required: the redaction section must not over-claim and the validator example must echo no bound value — a judgment the grep gates cannot make" + - id: D2 + description: "docs/Security.md documents opt-in/deferred ValidateSimpleSettings() on IServiceProvider after BuildServiceProvider(), validate=>discoverable coupling, and AddCommandLine spaced-value binding with the prefix-lookahead caveat" + requirement: "DOC-01" + verification: + - kind: other + ref: "grep -c ValidateSimpleSettings/IServiceProvider/BuildServiceProvider/'SettingsSection(ValidatorType'/AddCommandLine docs/Security.md — each >= 1" + status: pass + human_judgment: false + - id: D3 + description: "docs/Security.md ends with a v1 -> v2 migration section covering all four Phase-3 breaking changes (API-01, PKG-01, PKG-02, EXC-01), carrying no legacy product name" + requirement: "DOC-01" + verification: + - kind: other + ref: "grep -c SettingsHolder/Core.AspNet/SimpleSettingsException docs/Security.md each >= 1; grep -riEc '\\bSimpleConfig\\b' docs/Security.md == 0" + status: pass + human_judgment: false + +# Metrics +duration: 4min +completed: 2026-07-20 +status: complete +--- + +# Phase 5 Plan 3: Security & Behavior Guidance Page Summary + +**New docs/Security.md documenting the five Phase 1–4 security/behavior invariants with their exact caveats plus the v1 -> v2 breaking-change list — every API token verified against source, no bound value in any example, no legacy product name.** + +## Performance + +- **Duration:** ~4 min +- **Started:** 2026-07-20T09:16:21Z +- **Completed:** 2026-07-20T09:20:00Z +- **Tasks:** 2 +- **Files modified:** 1 (created) + +## Accomplishments +- Authored `docs/Security.md` with five security/behavior subsections: secret redaction (value-free exceptions), validators must not echo secrets, opt-in/deferred DI validation, validators make a type discoverable, command-line values with spaces +- Stated the redaction invariant with BOTH carve-outs (author `ValidationError` text + DI validator constructors) without over-claiming — sourced from SECURITY.md:24-27 and 75-78 +- Included the verified secret-safe validator example and the `ValidateSimpleSettings()`-after-`BuildServiceProvider()` DI snippet on the correct `IServiceProvider` receiver +- Appended a concise v1 -> v2 migration section covering all four Phase-3 breaking changes (SettingsHolder internal, Core.AspNet dropped, per-TFM Microsoft.Extensions floor, public SimpleSettingsException base) + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Author the Security & Behavior guarantees section** - `1764530` (docs) +2. **Task 2: Append the v1 -> v2 migration / breaking-change section** - `c4fa856` (docs) + +## Files Created/Modified +- `docs/Security.md` - New deep-guidance page: five security/behavior invariants with caveats + v1 -> v2 migration section + +## Decisions Made +- Placed the deep guidance in a dedicated new `docs/Security.md` rather than the renamed extension guide (RESEARCH Open Question 1, resolved to 05-03) — keeps the security contract separable and gives 05-04's README a stable deep-link target +- Kept every API token copied verbatim from 05-RESEARCH ## Current Public API and the secret-safe validator/DI snippets from ## Code Examples — no loose paraphrase, no over-claim + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +None. + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- `docs/Security.md` is a stable deep-link target ready for 05-04 to reference from the README's concise Security notes + Breaking changes / migration sections (anchor `#migration`). +- Recommended: a light security-auditor pass on the finished page (coverage D1 flagged `human_judgment: true`) confirming the redaction caveats are not over-claimed and no example echoes a bound value — the automated grep gates cannot make that judgment. + +--- +*Phase: 05-documentation* +*Completed: 2026-07-20* + +## Self-Check: PASSED +- docs/Security.md — FOUND +- .planning/phases/05-documentation/05-03-SUMMARY.md — FOUND +- Commit 1764530 (Task 1) — FOUND +- Commit c4fa856 (Task 2) — FOUND From 381c54f3317de1909aeca759da043b940f1dc3b4 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:23:51 +0300 Subject: [PATCH 19/23] =?UTF-8?q?docs(05-04):=20rewrite=20README=20structu?= =?UTF-8?q?re=20=E2=80=94=20canonical=20logo,=20dotnet-add=20install,=20To?= =?UTF-8?q?C,=20quickstart,=20DI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace broken existall/Shepherd logo with canonical absolute raw icon URL - Drop legacy product tag from title; three dotnet add package install lines - Repoint all ToC entries to existall/SimpleSettings docs + add Security & Behavior entry - Replace stale [DefaultValue] example with verified [SettingsProperty(DefaultValue = ...)] quickstart - Trim IOptions polemic to a tight blurb; add feature overview + DI snippet with ValidateSimpleSettings --- README.md | 159 +++++++++++++++++++++++++++--------------------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 183c409..57ee53c 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,78 @@ -ExistForAll - -ExistForAll.SimpleSettings (previously SimpleConfig) -===================== - -## Installation -`Install-Package ExistForAll.SimpleSettings` - -## Table of Content -1.Introduction -2.[Getting started](https://github.com/existall/SimpleConfig/blob/master/docs/getting_started.md) -3.[Building the collection](https://github.com/existall/SimpleConfig/blob/master/docs/building_the_collection.md) -4.[Building Config Interfaces](https://github.com/existall/SimpleConfig/blob/master/docs/Build%20Config%20Interface.md) -5.[DefaultValue](https://github.com/existall/SimpleConfig/blob/master/docs/Default%20Values.md) -6.[Build Section Binders](https://github.com/existall/SimpleConfig/blob/master/docs/Build%20a%20SectionBinder.md) -7.[Extending SimpleConfig](https://github.com/existall/SimpleConfig/blob/master/docs/Extend%20Simple%20Config.md) - -Introduction - or why SimpleConfig was created ----------------------------------------------- - -With the release of Asp.Net Core and .Net Core Microsoft has introduced `IOptions<>`. -While `IOptions<>` is a great concept it lacks in implementation. -`IOptions<>` provide a way to insert parameters into your app dynamically. - -For example you have an email service that requires some URL to the email server. - -``` c# -public class EmailSender -{ - public EmailSender(string emailServiceUrl, ... ) - { - - } -} -``` - -All of the good IOC containers out there will tell you that injecting a string into a service is a bad idea. -The best of them won't let you do it. - -`IOptions<>` to the rescue, with `IOptions<>` you can request the option class that can provide the string you want provided from any data store you want (json file, database and so on). -``` c# -public class EmailSender -{ - public EmailSender(IOption configuration, ... ) { } -} -``` -BUT `IOptions<>` is not the way to do this. - -Why you ask ? - -1. As Uncle Bob said, your application must be independent from frameworks this the application code takes an unnecessary dependency on a framework abstraction, this is a violation of DIP. -2. In order to inject `IOption` `SomeClass` have to be a concrete class and not an interface. -3. To use `IOption` you must call `services.Configure` in the `Setup` class, this is not scale-able in any way and the last thing we want to do is to manually configure each configuration class. -4. Registering `IOption<>` in any other DI container different from Microsoft new DI container won't be a ball park. - -For better understanding you can read this [explenation](http://https://simpleinjector.readthedocs.io/en/latest/aspnetintegration.html#working-with-ioption-t) from the SimpleInjecor docs. - -### TL;DR - or what SimpleConfig does? - -Remember the `IOption configuration`? - -what if we could build an interface like so -``` c# -public interface IEmailServiceConfig -{ - [DefaultValue("SomeUrl")] - string ServiceUrl {get; set;} -} -``` -and use it like so -``` c# -public class EmailSender : IEmailSender -{ - public EmailSender(IEmailServiceConfig emailServiceConfig, ... ) { } - - public void SendEmail(...) - { - Send(emailServiceConfig.ServiceUrl, ...); - } -} -``` +ExistForAll.SimpleSettings + +ExistForAll.SimpleSettings +========================== + +Strongly-typed application settings for .NET. Declare a plain `public` interface, decorate it with defaults, and SimpleSettings binds your configuration into a runtime implementation you can inject anywhere — no concrete option classes, no per-type `services.Configure<>` wiring. + +## Installation + +```bash +dotnet add package ExistForAll.SimpleSettings +dotnet add package ExistForAll.SimpleSettings.Binders +dotnet add package ExistForAll.SimpleSettings.Extensions.GenericHost +``` + +- `ExistForAll.SimpleSettings` — the core binding engine and the direct `SettingsBuilder` API. +- `ExistForAll.SimpleSettings.Binders` — additional binders (in-memory, command-line, and more). +- `ExistForAll.SimpleSettings.Extensions.GenericHost` — dependency-injection integration (`AddSimpleSettings`). + +## Table of Content + +1. [Getting started](https://github.com/existall/SimpleSettings/blob/master/docs/getting_started.md) +2. [Building the collection](https://github.com/existall/SimpleSettings/blob/master/docs/building_the_collection.md) +3. [Building config interfaces](https://github.com/existall/SimpleSettings/blob/master/docs/Build%20Config%20Interface.md) +4. [Default Values](https://github.com/existall/SimpleSettings/blob/master/docs/Default%20Values.md) +5. [Build section binders](https://github.com/existall/SimpleSettings/blob/master/docs/Build%20a%20SectionBinder.md) +6. [Extending SimpleSettings](https://github.com/existall/SimpleSettings/blob/master/docs/Extending%20SimpleSettings.md) +7. [Security & Behavior](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md) + +## Why SimpleSettings + +.NET ships `IOptions<>`, but it couples your application to a framework abstraction, forces every settings shape to be a concrete class rather than an interface, and requires a manual `services.Configure<>` call per type. SimpleSettings keeps the positioning of `IOptions<>` — configuration bound into typed objects — while letting you depend on a plain interface, discovered and bound automatically, and portable across DI containers. You inject the interface; SimpleSettings supplies the implementation. + +## Quickstart + +Declare a settings interface and bind it with the direct API: + +```csharp +[SettingsSection] +public interface IEmailSenderSettings +{ + [SettingsProperty(DefaultValue = "https://smtp.example.com")] + string ServiceUrl { get; set; } + + [SettingsProperty(DefaultValue = 3)] + int Retries { get; set; } +} + +var settings = SettingsBuilder.CreateBuilder() + .GetSettings(); +``` + +Every settings interface must be `public` — SimpleSettings emits a runtime implementation of the interface and cannot implement a non-public one. + +## Feature overview + +- Bind configuration into `public` settings **interfaces** — no concrete option classes. +- Discover settings via `[SettingsSection]`, the `ISettingsSection` marker base, or a `Settings` name suffix. +- Per-property defaults, key overrides, custom converters, and required-value enforcement via `[SettingsProperty(...)]`. +- Object-level and per-property validation through `ISettingValidation`. +- First-class dependency-injection integration for the .NET Generic Host. +- Value-free exceptions on bind and conversion failures — bound values never surface in error messages (see [Security & Behavior](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md)). + +## Dependency injection + +Register SimpleSettings with the Generic Host and let it discover every settings interface in the supplied assemblies: + +```csharp +services.AddSimpleSettings(o => +{ + o.AddAssemblies(new[] { typeof(IEmailSenderSettings).Assembly }); +}); + +// after building the provider — opt-in, deferred DI validation: +serviceProvider.ValidateSimpleSettings(); +``` + +`ValidateSimpleSettings()` extends `IServiceProvider` and must be called **after** `BuildServiceProvider()`; attribute and `ValidatorType` validators run inline during binding and need no such call. See [Security & Behavior](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md) for the full validation model. From e706f0b3c613fb8275495106d64cf7708a38d58e Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:24:57 +0300 Subject: [PATCH 20/23] docs(05-04): add concise Security notes + v1->v2 migration sections - Security notes: state the value-free exception invariant with both carve-outs (author ValidationError text + DI validator constructors); link to docs/Security.md - Breaking changes / migration: list API-01/PKG-01/PKG-02/EXC-01 by concept; deep-link docs/Security.md#migration - Phase-final gate: 13 DOC-VERIFICATION grep gates + dotnet build/pack -c Release from src/ all green --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 57ee53c..a0dbc30 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,20 @@ serviceProvider.ValidateSimpleSettings(); ``` `ValidateSimpleSettings()` extends `IServiceProvider` and must be called **after** `BuildServiceProvider()`; attribute and `ValidatorType` validators run inline during binding and need no such call. See [Security & Behavior](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md) for the full validation model. + +## Security notes + +Every exception SimpleSettings throws on a bind or conversion failure is **value-free**: it carries only type and property metadata and never chains an inner exception that saw the bound value, so a secret you bound cannot surface in the error's `ToString()` chain. This is a structural guarantee, not a convention. + +There are two carve-outs the guard does **not** reach, and you own them: author-supplied `ValidationError` message text (it is emitted verbatim), and DI-resolved validator **constructors** (they run outside the value-free bind guard). Never echo a bound value into a validation message and never log a secret in a validator constructor. See [Security & Behavior](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md) for the full treatment. + +## Breaking changes / migration (v1 -> v2) + +The v1 -> v2 release batched four breaking changes: + +- **`SettingsHolder` / `ISettingsHolder` are now internal** — use the public `SettingsBuilder` / `ISettingsCollection` / `ISettingsProvider` surface instead. +- **The `Core.AspNet` package was dropped** — it exposed no public type; remove any reference to it. +- **The `Microsoft.Extensions.*` dependency floor is now per-TFM** — `8.0.x` on `net8.0`, current on `net10.0`. +- **A public `abstract SimpleSettingsException` base was introduced** and boundary exceptions were made public and structured; the old bare-`Exception` throw for a non-interface settings type is now `SettingsTypeNotInterfaceException`. + +See the full migration guide in [docs/Security.md](https://github.com/existall/SimpleSettings/blob/master/docs/Security.md#migration). From c0106372452020f0dc8a55e63398be2119e53e4c Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:27:52 +0300 Subject: [PATCH 21/23] docs(05-04): complete README rewrite plan (phase-final gate green) --- .planning/ROADMAP.md | 8 +- .planning/STATE.md | 28 ++-- .../phases/05-documentation/05-04-SUMMARY.md | 143 ++++++++++++++++++ 3 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 .planning/phases/05-documentation/05-04-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a3070b9..8817f12 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -28,7 +28,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing - [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) - [x] **Phase 3: Public Surface, Packaging & Binder Cleanup** - Meaningful public surface; per-TFM deps; correct command-line parsing (completed 2026-07-14) - [x] **Phase 4: Collection & Validation Binding** — ✓ COMPLETE (Waves 1–3 merged #33/#34/#35, 2026-07-19) - Empty/sequence collection binding, working settings validation, and DI collection surface (client pre-beta engine requirements) -- [ ] **Phase 5: Documentation** - Canonically-named, accurate, current docs (README + docs/); AOT-01 deferred to a future v2.1 milestone +- [x] **Phase 5: Documentation** - Canonically-named, accurate, current docs (README + docs/); AOT-01 deferred to a future v2.1 milestone (completed 2026-07-20) - [ ] **Phase 6: First v2.0.0-beta Release** - Batched breaking changes ship as an installable pre-release ## Phase Details @@ -134,7 +134,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Note**: AOT-01 (annotate reflection entry points / document the AOT-trim limitation) was **deferred to a future v2.1 milestone** during Phase-5 discussion (2026-07-19). Rationale: `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` are additive, non-breaking attributes, so they need not batch into the pre-beta window; adding them post-stable is safe. See REQUIREMENTS.md (AOT-01 → Deferred). -**Plans**: 3/4 plans executed +**Plans**: 4/4 plans complete **Wave 1** *(parallel — no file overlap)* @@ -144,7 +144,7 @@ value: config → typed settings maps accurately, and never leaks a secret doing **Wave 2** *(blocked on Wave 1 completion)* -- [ ] 05-04-PLAN.md — README full rewrite: canonical logo/title, `dotnet add package` ×3, canonical ToC (incl. renamed page + Security), correct `[SettingsProperty(DefaultValue=…)]` quickstart, trimmed positioning, concise Security + migration sections; phase-final 13-gate grep sweep + `dotnet build`/`pack` (D-01/D-02/D-03/D-04) [Wave 2, depends on 05-01/05-02/05-03] +- [x] 05-04-PLAN.md — README full rewrite: canonical logo/title, `dotnet add package` ×3, canonical ToC (incl. renamed page + Security), correct `[SettingsProperty(DefaultValue=…)]` quickstart, trimmed positioning, concise Security + migration sections; phase-final 13-gate grep sweep + `dotnet build`/`pack` (D-01/D-02/D-03/D-04) [Wave 2, depends on 05-01/05-02/05-03] ### Phase 6: First v2.0.0-beta Release @@ -171,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 3/4 | In Progress| | +| 5. Documentation | 4/4 | Complete | 2026-07-20 | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 9640a14..14c40da 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,16 +5,16 @@ milestone_name: milestone current_phase: 05 current_phase_name: Documentation status: executing -stopped_at: Completed 05-03. Next plan 05-04. -last_updated: "2026-07-20T09:19:22.565Z" +stopped_at: Completed 05-04. Phase 05 plan set complete. +last_updated: "2026-07-20T09:27:04.149Z" last_activity: 2026-07-20 -last_activity_desc: Completed 05-03 (docs/Security.md security & behavior + migration page) +last_activity_desc: Completed 05-04 (README rewrite + phase-final DOC-VERIFICATION gate); Phase 05 plan set complete progress: total_phases: 6 - completed_phases: 3 + completed_phases: 4 total_plans: 13 - completed_plans: 12 - percent: 92 + completed_plans: 13 + percent: 67 --- # Project State @@ -28,12 +28,12 @@ See: .planning/PROJECT.md (updated 2026-07-13) ## Current Position -Phase: 05 (Documentation) — EXECUTING -Next: Execute plan 05-04 -Status: Executing Phase 05 — plan 05-03 complete -Last activity: 2026-07-20 — Completed 05-03 (docs/Security.md security & behavior + migration page) +Phase: 05 (Documentation) — plan set COMPLETE (05-01..05-04) +Next: Phase 05 verify/ship gate; then Phase 06 beta release +Status: Phase 05 all four plans complete — README + docs/ + props canonical, packages repack cleanly +Last activity: 2026-07-20 — Completed 05-04 (README rewrite + phase-final DOC-VERIFICATION gate) -Progress: [█████████░] 92% +Progress: [██████████] 100% (13/13 plans) ## Performance Metrics @@ -68,6 +68,7 @@ Progress: [█████████░] 92% | Phase 05 P01 | 5min | 2 tasks | 5 files | | Phase 05 P02 | 3min | 1 tasks | 1 files | | Phase 05 P03 | 4min | 2 tasks | 1 files | +| Phase 05 P04 | 2min | 2 tasks | 1 files | ## Accumulated Context @@ -96,6 +97,7 @@ Recent decisions affecting current work: - [Phase 04]: 04-04 VAL-01 DI path + API-02: ISettingsCollection exposed via a DI singleton + an AddSimpleSettings(out ISettingsCollection, Action?) overload; deferred opt-in IServiceProvider.ValidateSimpleSettings() runs DI-registered ISettingValidation from a fresh scope (IServiceScopeFactory), dispatches via the DIM bridge (no reflection), and throws the same value-free SettingsValidationException as the core path via the shared ThrowIfAny. Runner is internal; DI path is additive (reads no attribute). - [Phase ?]: 05-02: Replaced legacy SimpleConfig PackageTags token with canonical SimpleSettings; fixed Description typo appliaction->application - [Phase ?]: 05-03: Placed deep Phase 1-4 security/behavior guidance in a dedicated new docs/Security.md (stable README deep-link target); stated the redaction invariant with both carve-outs without over-claiming +- [Phase ?]: 05-04: README rewritten against source-verified API (canonical logo, dotnet-add x3, correct [SettingsProperty(DefaultValue)] quickstart); concise Security notes + v1->v2 migration deep-link docs/Security.md; 13 DOC-VERIFICATION gates + dotnet build/pack green ### Pending Todos @@ -123,6 +125,6 @@ Items acknowledged and carried forward: ## Session Continuity -Last session: 2026-07-20T09:19:09.588Z -Stopped at: Completed 05-03. Next plan 05-04. +Last session: 2026-07-20T09:26:57.599Z +Stopped at: Completed 05-04. Phase 05 plan set complete. Resume file: None diff --git a/.planning/phases/05-documentation/05-04-SUMMARY.md b/.planning/phases/05-documentation/05-04-SUMMARY.md new file mode 100644 index 0000000..c0c76cf --- /dev/null +++ b/.planning/phases/05-documentation/05-04-SUMMARY.md @@ -0,0 +1,143 @@ +--- +phase: 05-documentation +plan: 04 +subsystem: docs +tags: [readme, nuget, packaging, markdown, dotnet-pack] + +# Dependency graph +requires: + - phase: 05-01 + provides: renamed docs/Extending SimpleSettings.md (ToC entry #6 target) + - phase: 05-02 + provides: fixed Directory.Build.props /PackageTags (repacked into README-bearing .nupkg) + - phase: 05-03 + provides: docs/Security.md deep security/behavior + migration page (README deep-link target) +provides: + - Full README.md rewrite (canonical logo, dotnet-add install x3, 7-entry ToC, verified quickstart, feature overview, DI snippet) + - Concise README "Security notes" (value-free invariant + both carve-outs) and "Breaking changes / migration (v1 -> v2)" sections + - Phase-final DOC-VERIFICATION gate green (13 grep gates + dotnet build/pack -c Release from src/) +affects: [phase-06-beta-release, nuget-publish] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Verify-against-source before writing any example (every README token traced to RESEARCH ## Current Public API)" + - "Concise-in-README, deep-in-docs (D-04): README summarizes, docs/Security.md carries depth" + - "Absolute raw HTTPS logo URL so nuget.org renders it; local icon.png stays the PackageIcon" + +key-files: + created: [] + modified: + - README.md + +key-decisions: + - "Logo uses the absolute raw URL https://raw.githubusercontent.com/existall/SimpleSettings/master/icon.png (nuget.org renders only absolute HTTPS images); keeps packing local icon.png" + - "Quickstart call written as SettingsBuilder.CreateBuilder() on one line so the DOC-VERIFICATION grep token stays contiguous" + - "Migration section names versions v1 -> v2, never the legacy product token, to keep the SimpleConfig sweep at 0 hits" + +patterns-established: + - "Pattern 1: DOC-VERIFICATION grep gates + dotnet build/pack are the phase gate (no unit tests for a docs phase)" + - "Pattern 2: README carries the concise guarantee + caveats; docs/Security.md is the single deep target both README sections deep-link into" + +requirements-completed: [DOC-01] + +coverage: + - id: D1 + description: "README structural rewrite — canonical logo, dropped legacy title tag, three dotnet add package install lines, 7-entry ToC repointed to existall/SimpleSettings docs (incl. renamed extension page + Security), verified [SettingsProperty(DefaultValue = ...)] quickstart, feature overview, DI snippet" + requirement: "DOC-01" + verification: + - kind: other + ref: "Task 1 automated gate: grep counts (raw.githubusercontent/existall/SimpleSettings>=1, dotnet add package x3, SettingsProperty(DefaultValue>=1, SettingsBuilder.CreateBuilder>=1, AddSimpleSettings>=1, Install-Package==0, [DefaultValue==0, existall/Shepherd==0)" + status: pass + human_judgment: false + - id: D2 + description: "Concise README Security notes (value-free exception invariant WITH both carve-outs — author ValidationError text + DI validator constructors) and v1->v2 migration list (API-01/PKG-01/PKG-02/EXC-01), each linking into docs/Security.md" + requirement: "DOC-01" + verification: + - kind: other + ref: "Gate 11 presence checks: '## Security notes', '## Breaking changes / migration', SimpleSettingsException, Core.AspNet all present in README" + status: pass + human_judgment: false + - id: D3 + description: "Phase-final DOC-VERIFICATION sweep — 13 gates (7 negative legacy greps + canonical-URL + install verbs + API tokens + guidance presence + docs-file existence) plus dotnet build -c Release and dotnet pack -c Release from src/" + requirement: "DOC-01" + verification: + - kind: other + ref: "Gates 1-12 grep sweep (all expected counts) + Gate 13 dotnet build/pack exit 0 (three .nupkg repacked with refreshed README + icon + Description)" + status: pass + human_judgment: false + - id: D4 + description: "No README example echoes a bound value/secret; the redaction note is not over-claimed (paired with both caveats)" + verification: + - kind: manual_procedural + ref: "Executor security-auditor pass: quickstart/DI examples use only non-secret placeholders (a URL, an int); redaction invariant stated with both carve-outs" + status: pass + human_judgment: true + rationale: "Secret-leak / over-claim judgment on documentation prose is a human-judgment security check, not mechanically decidable by grep" + +# Metrics +duration: 2min +completed: 2026-07-20 +status: complete +--- + +# Phase 5 Plan 04: README Rewrite + Phase-Final Gate Summary + +**Full README.md rewrite against the source-verified API (canonical logo/title, dotnet-add install x3, 7-entry ToC, correct [SettingsProperty(DefaultValue = …)] quickstart, DI snippet, concise Security + v1->v2 migration sections) with all 13 DOC-VERIFICATION gates and dotnet build/pack green.** + +## Performance + +- **Duration:** 2 min +- **Started:** 2026-07-20T09:22:29Z +- **Completed:** 2026-07-20T09:25:05Z +- **Tasks:** 2 +- **Files modified:** 1 + +## Accomplishments +- Rewrote README.md end-to-end: replaced the broken existall/Shepherd logo with the canonical absolute raw icon URL, dropped the legacy product tag from the title, swapped the legacy PMC install for three `dotnet add package` lines, repointed all ToC entries to existall/SimpleSettings docs and added a "Security & Behavior" entry. +- Replaced the factually-wrong `[DefaultValue("SomeUrl")]` example with the verified `[SettingsProperty(DefaultValue = …)]` quickstart, trimmed the IOptions polemic to a tight blurb, and added a feature overview + DI snippet (`AddSimpleSettings` + `serviceProvider.ValidateSimpleSettings()`). +- Appended concise "Security notes" (value-free exception invariant paired with both carve-outs) and "Breaking changes / migration (v1 -> v2)" (API-01/PKG-01/PKG-02/EXC-01) sections, each deep-linking into docs/Security.md. +- Ran the phase-final gate: all 13 DOC-VERIFICATION grep gates return their expected counts, and `dotnet build -c Release` + `dotnet pack -c Release` from `src/` exit 0 — the three packages repacked cleanly with the refreshed README + icon + Description. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Structural README rewrite (logo, title, install, ToC, quickstart, positioning)** - `381c54f` (docs) +2. **Task 2: Concise Security notes + Migration sections; phase-final grep sweep and pack** - `e706f0b` (docs) + +**Plan metadata:** committed with SUMMARY.md + STATE.md + ROADMAP.md (docs: complete plan) + +## Files Created/Modified +- `README.md` - Full rewrite: canonical logo/title, dotnet-add install x3, 7-entry ToC repointed to existall/SimpleSettings, verified quickstart, feature overview, DI snippet, concise Security notes + v1->v2 migration sections. + +## Decisions Made +- Logo uses the absolute raw URL `https://raw.githubusercontent.com/existall/SimpleSettings/master/icon.png` (nuget.org renders only absolute HTTPS image URLs); `` in Directory.Build.props still packs the local `icon.png` — untouched. +- Quickstart call written as `SettingsBuilder.CreateBuilder()` on a single line so the `SettingsBuilder.CreateBuilder` DOC-VERIFICATION grep token stays contiguous (see Issues Encountered). +- Migration section names versions as "v1 -> v2" and never the legacy product token, keeping the case-insensitive `\bSimpleConfig\b` sweep at 0 hits. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered +- The RESEARCH quickstart example formats the builder call fluently across three lines (`SettingsBuilder` / `.CreateBuilder()` / `.GetSettings<>()`). Copied verbatim, that split the `SettingsBuilder.CreateBuilder` token across a line break, so the Task 1 grep gate (`grep -c "SettingsBuilder.CreateBuilder"`) returned 0. Resolved by placing `SettingsBuilder.CreateBuilder()` on one line (still a faithful fluent call) — gate then passed. Not a deviation: the required token is present and the code is equivalent. + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Phase 5 (Documentation) plan set is complete: README + docs/ + Directory.Build.props are canonical, and the packaged README + icon + Description repack cleanly. Ready for the Phase 5 verify/ship gate and the Phase 6 beta release cut. +- No blockers. + +## Self-Check: PASSED + +- README.md — FOUND +- .planning/phases/05-documentation/05-04-SUMMARY.md — FOUND +- Commit 381c54f (Task 1) — FOUND +- Commit e706f0b (Task 2) — FOUND + +--- +*Phase: 05-documentation* +*Completed: 2026-07-20* From 5778e3106e12968cc8fed61a82f252b142458073 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:35:32 +0300 Subject: [PATCH 22/23] docs(phase-05): complete phase execution --- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 17 +-- .../05-documentation/05-VERIFICATION.md | 111 ++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/05-documentation/05-VERIFICATION.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 8817f12..a6f08a1 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -171,5 +171,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 | 2. Binding Correctness & Engine Test Hardening | 2/2 | Complete | 2026-07-14 | | 3. Public Surface, Packaging & Binder Cleanup | 2/2 | Complete | 2026-07-14 | | 4. Collection & Validation Binding | 5/5 | ✓ Complete | 2026-07-19 (#35) | -| 5. Documentation | 4/4 | Complete | 2026-07-20 | +| 5. Documentation | 4/4 | Complete | 2026-07-20 | | 6. First v2.0.0-beta Release | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 14c40da..163aa89 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,13 +2,13 @@ gsd_state_version: 1.0 milestone: v2.0.0 milestone_name: milestone -current_phase: 05 -current_phase_name: Documentation -status: executing +current_phase: 6 +current_phase_name: First v2.0.0-beta Release +status: completed stopped_at: Completed 05-04. Phase 05 plan set complete. -last_updated: "2026-07-20T09:27:04.149Z" +last_updated: "2026-07-20T09:35:05.147Z" last_activity: 2026-07-20 -last_activity_desc: Completed 05-04 (README rewrite + phase-final DOC-VERIFICATION gate); Phase 05 plan set complete +last_activity_desc: Phase 05 complete, transitioned to Phase 6 progress: total_phases: 6 completed_phases: 4 @@ -28,10 +28,10 @@ See: .planning/PROJECT.md (updated 2026-07-13) ## Current Position -Phase: 05 (Documentation) — plan set COMPLETE (05-01..05-04) +Phase: 6 — First v2.0.0-beta Release Next: Phase 05 verify/ship gate; then Phase 06 beta release Status: Phase 05 all four plans complete — README + docs/ + props canonical, packages repack cleanly -Last activity: 2026-07-20 — Completed 05-04 (README rewrite + phase-final DOC-VERIFICATION gate) +Last activity: 2026-07-20 — Phase 05 complete, transitioned to Phase 6 Progress: [██████████] 100% (13/13 plans) @@ -39,7 +39,7 @@ Progress: [██████████] 100% (13/13 plans) **Velocity:** -- Total plans completed: 4 +- Total plans completed: 8 - Average duration: — - Total execution time: 0 hours @@ -49,6 +49,7 @@ Progress: [██████████] 100% (13/13 plans) |-------|-------|-------|----------| | 02 | 2 | - | - | | 03 | 2 | - | - | +| 05 | 4 | - | - | **Recent Trend:** diff --git a/.planning/phases/05-documentation/05-VERIFICATION.md b/.planning/phases/05-documentation/05-VERIFICATION.md new file mode 100644 index 0000000..70d075c --- /dev/null +++ b/.planning/phases/05-documentation/05-VERIFICATION.md @@ -0,0 +1,111 @@ +--- +phase: 05-documentation +verified: 2026-07-20T00:00:00Z +status: passed +score: 4/4 must-haves verified +behavior_unverified: 0 +overrides_applied: 0 +re_verification: + # No previous VERIFICATION.md — initial verification +gaps: [] +deferred: [] +--- + +# Phase 5: Documentation Verification Report + +**Phase Goal:** Consumers get accurate, canonically-named documentation — the consumer-facing README and the docs/ folder tell the truth about the current API and carry the Phase 1–4 security/behavior guidance. +**Verified:** 2026-07-20 +**Status:** passed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths (ROADMAP Success Criteria — the contract) + +| # | Truth | Status | Evidence | +| --- | --- | --- | --- | +| 1 | README uses canonical `ExistForAll.SimpleSettings` name + current repo/package paths; no legacy `SimpleConfig` refs | ✓ VERIFIED | Gate 1 `existall/SimpleConfig`=0, Gate 2 `\bSimpleConfig\b`(-i)=0 across README+docs+props; Gate 8 `existall/SimpleSettings` in README=12. Title line 3 canonical, legacy parenthetical dropped. | +| 2 | README code example reflects real API (`[SettingsSection]`/`[SettingsProperty(DefaultValue=…)]`), not stale `[DefaultValue]`; install + logo resolve | ✓ VERIFIED | Gate 4 `[DefaultValue`=0; README has `[SettingsSection]` + `[SettingsProperty(DefaultValue = …)]` quickstart. Every token verified against source: `SettingsBuilder.CreateBuilder()`, `GetSettings`, `SettingsPropertyAttribute.DefaultValue`, `SettingsSectionAttribute.ValidatorType`, `AddSimpleSettings`, `AddAssemblies` all confirmed. Logo = absolute raw HTTPS URL (renders on nuget.org); 3× `dotnet add package` install lines. | +| 3 | Mandated Phase 1–4 guidance documented — concise in README, detailed in docs/ | ✓ VERIFIED | Gate 11: README `## Security notes` (value-free invariant + both carve-outs) + `## Breaking changes / migration (v1 -> v2)` (API-01/PKG-01/PKG-02/EXC-01). docs/Security.md carries all 6 items: redaction+2 caveats, validator secret-safety incl. constructor, opt-in `ValidateSimpleSettings()` on `IServiceProvider` after `BuildServiceProvider()`, validate⇒discoverable, `AddCommandLine` spaced-value prefix-lookahead, v1→v2 list. | +| 4 | docs/ carries no legacy naming/dead links; package metadata (``, URLs, logo) canonical | ✓ VERIFIED | Gates 3–7: `Install-Package`=0, `existall/Shepherd`=0, `appliaction`=0, `Extend%20Simple%20Config`=0, renamed file exists, old file gone. props `` fixed, URLs canonical. | + +**Score:** 4/4 truths verified (0 present, behavior-unverified) + +### Plan Must-Have Truths (supporting detail — all DOC-01) + +| Plan | Truth | Status | Evidence | +| --- | --- | --- | --- | +| 05-01 | Extension guide at canonical space-encoded filename, history preserved; 5 inbound links repointed; docs/ legacy-free | ✓ VERIFIED | `docs/Extending SimpleSettings.md` exists (git-tracked, renamed via `git mv` 80aa1b5), old gone; `Extending%20SimpleSettings.md` inbound links present; getting_started parenthetical removed | +| 05-02 | `` correct spelling, no legacy name; `` canonical; props build | ✓ VERIFIED | `appliaction`=0; `\bSimpleConfig\b` in props=0; `dotnet build -c Release` = 0 warn/0 err | +| 05-03 | Redaction invariant + 2 carve-outs (no over-claim); validators-no-echo; opt-in DI on IServiceProvider; validate⇒discoverable; AddCommandLine spaced; v1→v2 list | ✓ VERIFIED | docs/Security.md read in full; every API token + validator example (`ISettingValidation.Validate(ValidationContext)`, `AddError(ValidationError)`, `ValidationError(settingsName, errorMessage)`) confirmed against source | +| 05-04 | README canonical name/logo/install/example; all ToC+deep links resolve; concise security+migration; builds+packs | ✓ VERIFIED | All 13 gates pass; 7 ToC targets exist; build+pack green; refreshed README packed into .nupkg | + +### Required Artifacts + +| Artifact | Expected | Status | Details | +| --- | --- | --- | --- | +| `README.md` | Full rewrite: logo, title, install×3, 7-entry ToC, quickstart, features, DI, Security+Migration | ✓ VERIFIED | Substantive (96 lines), wired (packed via PackageReadmeFile — confirmed refreshed content inside .nupkg) | +| `docs/Security.md` | New deep page: 5 invariants + caveats + v1→v2 migration | ✓ VERIFIED | Substantive (98 lines), linked from README (3 deep-links incl. `#migration` anchor) | +| `docs/Extending SimpleSettings.md` | Renamed from legacy filename, history preserved | ✓ VERIFIED | Exists, git-tracked; old filename gone | +| `src/Directory.Build.props` | `` typo fixed, `` canonical | ✓ VERIFIED | `appliaction`=0, `\bSimpleConfig\b`=0; injects into all 3 packages (build/pack green) | +| 4 repointed docs pages | getting_started, building_the_collection, Build a SectionBinder, Build Config Interface | ✓ VERIFIED | All legacy-free; inbound links repointed to renamed page | + +### Key Link Verification + +| From | To | Via | Status | Details | +| --- | --- | --- | --- | --- | +| README ToC (7 entries) | docs/ pages on existall/SimpleSettings | github blob URLs | ✓ WIRED | All 7 URL-decoded targets exist on disk | +| README Security/Migration | docs/Security.md (+`#migration`) | markdown deep-links | ✓ WIRED | docs/Security.md exists with matching content | +| README.md + icon | .nupkg | PackageReadmeFile/PackageIcon | ✓ WIRED | `dotnet pack` succeeded; refreshed README confirmed inside core .nupkg (0 legacy hits) | +| 5 in-docs links | renamed extension page | space-encoded filename | ✓ WIRED | `Extend%20Simple%20Config`=0; `Extending%20SimpleSettings.md` links resolve | +| props ``/`` | every published .nupkg | shared MSBuild props | ✓ WIRED | 3 packages packed cleanly | + +### Behavioral Spot-Checks (DOC-VERIFICATION gates, re-run by verifier) + +| Gate | Command | Result | Status | +| --- | --- | --- | --- | +| 1 | `grep -rn existall/SimpleConfig README docs props` | 0 | ✓ PASS | +| 2 | `grep -rniE \bSimpleConfig\b README docs props` | 0 | ✓ PASS | +| 3 | `grep -rn Install-Package README docs` | 0 | ✓ PASS | +| 4 | `grep -rn \[DefaultValue README docs` | 0 | ✓ PASS | +| 5 | `grep -rn existall/Shepherd README props` | 0 | ✓ PASS | +| 6 | `grep -n appliaction props` | 0 | ✓ PASS | +| 7 | `grep Extend%20Simple%20Config` + renamed file exists / old gone | 0 + exists | ✓ PASS | +| 8 | `grep -c existall/SimpleSettings README` | 12 (≥1) | ✓ PASS | +| 9 | 3× `dotnet add package` IDs in README | base=3, Binders=2, GenericHost=2 | ✓ PASS | +| 10 | `SettingsProperty(DefaultValue`/`SettingsBuilder.CreateBuilder`/`AddSimpleSettings`; `ValidateSimpleSettings()` on IServiceProvider | present; receiver confirmed source + docs | ✓ PASS | +| 11 | 6 mandated guidance items across README + docs/ | all present | ✓ PASS | +| 12 | all 7 referenced docs/ files exist | all OK | ✓ PASS | +| 13 | `dotnet build -c Release` + `dotnet pack -c Release` from src/ | build 0w/0e; 3 nupkg+snupkg created | ✓ PASS | + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +| --- | --- | --- | --- | --- | +| DOC-01 | 05-01/02/03/04 | Refresh README + docs/ to canonical naming + current links; document Phase 1–4 security/behavior guidance | ✓ SATISFIED | All 13 gates pass; every API token verified against source; all 4 roadmap SCs met | + +No orphaned requirements — DOC-01 is the sole Phase 5 requirement and is claimed by all four plans. AOT-01 correctly deferred to v2.1 (out of scope, recorded in ROADMAP + REQUIREMENTS). + +### Semantic Manual Checks (05-VALIDATION Manual-Only — performed by verifier) + +| Check | Status | Evidence | +| --- | --- | --- | +| No code example echoes a bound value / secret | ✓ VERIFIED | Read every example: README quickstart/DI use non-secret placeholders (`https://smtp.example.com`, int `3`); Security.md validator reports only `"Retries must be >= 0"` — no bound value in `ValidationError` text or any constructor | +| 6 mandated items present AND correctly caveated (not over-claimed) | ✓ VERIFIED | Security.md line 14 explicitly warns against over-reading the invariant ("Do not read the redaction invariant as 'secrets never appear in any exception'"); both carve-outs stated (author `ValidationError` text + validator constructors), matching SECURITY.md:75-78. README line 84 pairs the invariant with the same caveat — Pitfall 4 avoided. | + +### Anti-Patterns Found + +None. Scan of README.md, docs/Security.md, docs/Extending SimpleSettings.md, docs/getting_started.md, src/Directory.Build.props for `TBD|FIXME|XXX|placeholder|coming soon|not yet implemented` returned 0 hits. + +### Human Verification Required + +None. This is a documentation phase; all criteria are grep/file/build-verifiable and were re-run by the verifier. The two 05-VALIDATION manual-only semantic checks (no secret echo; correct caveating) were performed by reading every example and the redaction prose against the authoritative SECURITY.md source — both pass unambiguously. + +### Gaps Summary + +No gaps. All four ROADMAP success criteria are met, all four plan must-have truth sets verify, DOC-01 is satisfied, and all 13 DOC-VERIFICATION gates pass when re-run. Every code example and API token in README + docs/Security.md was independently confirmed accurate against current source (`ValidateSimpleSettings` extends `IServiceProvider`; `SettingsBuilder.CreateBuilder`; `SettingsPropertyAttribute.DefaultValue`; `SettingsSectionAttribute.ValidatorType`; `AddSimpleSettings`/`AddAssemblies`; `ISettingValidation.Validate(ValidationContext)`; `ValidationResult.AddError`; `ValidationError(settingsName, errorMessage)`; `SettingsTypeNotInterfaceException : SimpleSettingsException`). The refreshed README is confirmed packed into the produced .nupkg. Packages build and pack cleanly with 0 warnings / 0 errors. + +--- + +_Verified: 2026-07-20_ +_Verifier: Claude (gsd-verifier)_ From 2ef56065d54616c1a6f27a5c582ee52b942e8ae9 Mon Sep 17 00:00:00 2001 From: guy-lud Date: Mon, 20 Jul 2026 12:38:35 +0300 Subject: [PATCH 23/23] docs: refresh session handoff (Phase 5 complete; PR + Phase 6 next) --- SESSION-HANDOFF.md | 55 ++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/SESSION-HANDOFF.md b/SESSION-HANDOFF.md index e7dd33c..23366a9 100644 --- a/SESSION-HANDOFF.md +++ b/SESSION-HANDOFF.md @@ -1,42 +1,45 @@ # SESSION HANDOFF — SimpleSettings -_Last updated: 2026-07-19 (Phase 4 Wave 3 + closeout) · owner: Guy Ludvig (guy@frontegg.com)_ +_Last updated: 2026-07-20 (Phase 5 complete) · owner: Guy Ludvig (guy@frontegg.com)_ ## TL;DR **GSD is the source of truth** (`.planning/`); `FIX-PLAN.md` frozen historical reference only. -**Phases 1–4 are COMPLETE.** Phase 4 (Waves 1–3) is fully merged to `master` (@ `0c858fa`, PRs #33/#34/#35). Phase 4 **closeout (verify 6/6 + security gate 15/15, D-06 signed off) is DONE** but lives **doc-only on branch `gsd/phase-4-closeout`** (held, not merged — rides to master with Phase 5's first code PR, per the no-doc-only-alpha rule). **Next work = Phase 5** (AOT-01 + DOC-01), not started. -This session: executed Wave 3 (plan 04-04 — VAL-01 DI path + API-02), reviewed it (plan-review trio + 3 post-code reviews), merged **PR #35** (`0c858fa`), then ran the Phase 4 closeout (verify + secure + mark complete). **One `2.0.0-alpha.0.*` alpha auto-published** on the #35 merge — add to the unlist list. +**Phases 1–5 are COMPLETE.** Phase 5 (**Documentation / DOC-01**) is done + verified (4/4 must-haves). **Phase 6 (REL-01 — first `v2.0.0-beta`) is the ONLY remaining milestone work.** +This session: rescoped Phase 5 to **DOC-01 only** and **deferred AOT-01 to a future v2.1 milestone** (additive/non-breaking annotations needn't batch pre-beta), then ran the full GSD loop (discuss → plan → execute → verify) for Phase 5. +**Everything — the held Phase-4 closeout docs AND all of Phase 5 — sits on branch `gsd/phase-4-closeout`, 22 commits ahead of `master` @ `0c858fa`, UNMERGED.** No alpha published this session yet (nothing merged to master). ## Do this first (new session) -1. **Verify git state:** on branch **`gsd/phase-4-closeout`** (holds the Phase-4 closeout docs — `VERIFICATION.md`, `SECURITY.md`, ROADMAP/STATE marked complete — plus THIS handoff; pushed to origin as a backup, NOT merged). `master` @ `0c858fa`; `git status` clean; `gh pr list` shows no open PRs. -2. **The closeout is doc-only and rides to master with Phase 5's first code PR** — do NOT give it its own PR (would burn a throwaway alpha). Branch Phase 5's first plan off `gsd/phase-4-closeout` (or cherry-pick these docs onto the Phase-5 branch) so they land with real code. -3. **Next work = Phase 5** (AOT-01 + DOC-01): discuss → plan → execute. See Remaining roadmap for the DOC-01 must-includes. +1. **Verify git state:** on branch **`gsd/phase-4-closeout`**, **22 commits ahead of `master` @ `0c858fa`**; `git status` clean; `gh pr list` shows no open PRs. +2. **IMMEDIATE NEXT STEP = open the PR** `gsd/phase-4-closeout` → `master` (via **`guy-lud`**). This is the moment the held Phase-4 closeout docs "ride to master with Phase 5's PR": **Phase 5 changed *packaged* content** (README via `PackageReadmeFile` + `Directory.Build.props` ``/``), so it's a legitimate release-worthy merge — NOT a throwaway doc-only push. On merge: publishes **one** `2.0.0-alpha.0.*` (add to unlist list); CI runs net8+net10; benchmark gate (docs-only → 0-byte diff expected). +3. **THEN Phase 6 = REL-01** (cut the first `v2.0.0-beta`): discuss → plan → execute, then the manual **Release** (`workflow_dispatch`) tags `v2.0.0-beta`. Needs everything on `master` first (step 2). 4. **Push/PR only via `guy-lud`** (`gh auth switch --user guy-lud` … then switch back to `guy-frontegg`). `git push` uses the `github-guy-lud` SSH alias. See `[[simplesettings-push-access]]`. -## What shipped this session -- **Phase 4 Wave 3 (plan 04-04, PR #35 @ `0c858fa`) — VAL-01 DI path + API-02:** - - **API-02:** `AddSimpleSettings` registers `ISettingsCollection` as a resolvable DI singleton AND adds an `AddSimpleSettings(out ISettingsCollection, Action?)` overload (same instance, fluent chain preserved). - - **VAL-01 DI path:** opt-in `IServiceProvider.ValidateSimpleSettings()` runs DI-registered `ISettingValidation` in a deferred post-`BuildServiceProvider()` step. Internal `SettingsValidationRunner` resolves validators from a **fresh scope** (`IServiceScopeFactory.CreateScope()` — scoped-dependency validators work under `validateScopes:true`), dispatches via the **DIM bridge** (`(ISettingsValidator)v).Validate(...)`, no reflection), and aggregates through the shared `SettingsValidationException.ThrowIfAny` → **contract-identical to the core path**. Throwing validator → value-free `SettingsValidatorInvocationException(type,type)`. - - Full suite **153/153** (net8 + net10); benchmark allocation gate **0-byte diff** (warm path untouched). -- **Phase 4 CLOSEOUT (on `gsd/phase-4-closeout`, held):** `VERIFICATION.md` (6/6 success criteria MET), `SECURITY.md` (15/15 threats closed, **D-06 secret-redaction gate SIGNED OFF**, T-04-VAL DI holds by construction), ROADMAP + STATE mark Phase 4 ✓ complete and advance to Phase 5. +## What shipped this session (Phase 5 — Documentation / DOC-01) +- **Scope change:** Phase 5 renamed "AOT/Trim Honesty & Documentation" → **"Documentation"**; **AOT-01 deferred to v2.1** (recorded in ROADMAP + REQUIREMENTS; traceability + coverage reconciled, incl. correcting Phase-4/API-02 to complete). +- **05-01** — docs/ canonicalization: `git mv "Extend Simple Config.md" → "Extending SimpleSettings.md"` (history preserved), repointed 5 inbound links, dropped the `getting_started.md` "(previously SimpleConfig)" line. (docs/ was already ~90% canonical — the README was the real work.) +- **05-02** — `src/Directory.Build.props` metadata: `` "appliaction"→"application"; `` legacy `SimpleConfig`→`SimpleSettings`. `dotnet build -c Release` 0/0. +- **05-03** — new **`docs/Security.md`**: the 6 mandated Phase 1–4 items with correct caveats (secret-redaction stated value-free WITH both carve-outs — author `ValidationError` text + validator **constructors** — not over-claimed; `ValidateSimpleSettings()` on **`IServiceProvider`**; validate⇒discoverable; `AddCommandLine` spaced values; v1→v2 breaking-change list). +- **05-04** — README full rewrite: absolute-URL logo, `dotnet add package` ×3, correct `[SettingsProperty(DefaultValue=…)]` quickstart, ToC into the renamed page + Security.md, concise Security + migration sections. **Phase-final gate green:** 13/13 DOC-VERIFICATION grep gates + `dotnet build`/`pack` exit 0 (packed `2.0.0-alpha.0.158` locally). +- **Verification:** gsd-verifier PASSED **4/4 must-haves** (re-ran all 13 gates + traced every API token to source + semantic no-secret-echo check). Regression: **153/153 net10** green. VERIFICATION.md written. ## Cadence / mechanics - **Fresh branch off master per chunk → PR (src + .planning) → merge.** Each master merge burns a throwaway `-alpha` (pre-stable, acceptable). Reviews happen BEFORE the PR (see STANDING review rule). -- **Doc-only changes ride with the next code PR** — a doc-only master push burns a wasted alpha. This is why the Phase-4 closeout + this handoff sit on `gsd/phase-4-closeout` unmerged. See `[[simplesettings-handoff-workflow]]`. -- **Subagents run async in the background** (notify on completion). Dispatch plan-review + code/test reviews in parallel; keep their output out of the main context. +- **Doc-only planning changes ride with the next code/package PR** — a doc-only master push burns a wasted alpha. Phase 5's README/metadata ARE packaged content, so Phase 5's PR is legitimate and carries the held Phase-4 closeout docs. See `[[simplesettings-handoff-workflow]]`. +- **Subagents run async in the background** (notify on completion). Keep their output out of the main context. +- **STATE.md is prose-style; GSD `state.*` SDK handlers (`advance-plan`, `record-session`, argless `begin-phase`) mis-parse/regress it** (repeatedly hit this session — `record-session`/argless `begin-phase` even reset `current_phase`). Prefer targeted edits + `state.validate`; `state.begin-phase --phase N --name X --plans K` (with args) and `phase.complete N` work correctly. ## STANDING review rule (memory `[[dotnet-review-workflow]]`) -Plan-review trio (**dotnet-architect + performance-analyst + security-auditor**) up front on the design BEFORE writing code. Before EACH PR: review code with **both** `gsd-code-review` (gsd-code-reviewer) AND `dotnet-claude-kit:code-reviewer`, and review tests with **`dotnet-claude-kit:test-engineer`**. Be proportional — skip for trivial docs/cosmetic PRs. +Plan-review trio (**dotnet-architect + performance-analyst + security-auditor**) up front on design BEFORE code. Before EACH PR: review code with **both** `gsd-code-review` AND `dotnet-claude-kit:code-reviewer`; tests with **`dotnet-claude-kit:test-engineer`**. Be proportional — for the docs phase, the gsd-verifier's source-traced accuracy + no-secret-echo read covered it; no separate code-review pass was needed. ## Remaining roadmap -- **Phase 4: ✓ COMPLETE** (Waves 1–3 merged; verify + secure done; closeout held on branch). -- **Phase 5 (NEXT):** AOT-01; **DOC-01 (README)** — MUST document: Phase-3 breaking-change list; "spaced secrets bind via `AddCommandLine`"; VAL-01 **validator authors must not echo secrets** in `ValidationError` text; **DI-path `ValidateSimpleSettings()` is opt-in / deferred** (must call after `BuildServiceProvider()`; attribute validators run inline); **validator CONSTRUCTORS must not echo injected secrets** (DI resolution runs outside the value-free guard — from the Wave-3 security sign-off); the **validate ⇒ discoverable coupling** (`[SettingsSection(ValidatorType=…)]` also makes the type scan-discovered). Fold in **validator-dispatch caching** (deferred perf, code-review M2). -- **Phase 6:** REL-01 (first `v2.0.0-beta`; suite green net8 + net10). +- **Phases 1–5: ✓ COMPLETE.** +- **Phase 6 (NEXT — the finish line):** REL-01 — cut the first `v2.0.0-beta`; consistent identity across the 3 packages (`ExistForAll.SimpleSettings` / `.Binders` / `.Extensions.GenericHost` — Core.AspNet dropped in Phase 3); suite green net8 + net10 at the tagged commit. **The milestone's plan ENDS at the beta**, not a stable GA — there is no explicit stable-`v2.0.0` phase yet (add one if stable GA is the real target). +- **Deferred to v2.1:** **AOT-01** (annotate reflection entry points `[RequiresDynamicCode]`/`[RequiresUnreferencedCode]` — the generator uses `Reflection.Emit`+`RunAndCollect`, not AOT-safe; annotations are additive/non-breaking so safe to add post-beta). **Validator-dispatch caching** (perf, code-review M2) — stays deferred, NOT pulled into Phase 5. - **Held:** EQ-01 (D2 EqualityComparerCreator). **Deferred:** PERF-03 (compiled setter); `${ENV:-}` placeholder detection (VAL-02, D-13). ## How releasing works (durable) - **`ci.yml`** — PRs to `master`: build + test (net8.0 + net10.0). **`release.yml`**: push to `master` → auto-publishes a MinVer height-based `-alpha` (NO `paths` filter — **every master push publishes an alpha**). Manual **Release** (`workflow_dispatch`) tags `v*`. -- **`benchmark.yml`** — push to `master` + PRs: BDN, gates PRs on **allocation** regressions only (baseline in `gh-pages`; time recorded but not gated). Green through #35 (0-byte diff). +- **`benchmark.yml`** — push to `master` + PRs: BDN, gates PRs on **allocation** regressions only (baseline in `gh-pages`; time recorded, not gated). - **Versioning = MinVer**, tag prefix `v`, baseline **2.0.0**, keyless NuGet Trusted Publishing (OIDC). `SOLUTION=SimpleSettings.slnx`. **Everything through PRs; never commit to `master` directly.** ## Gotchas a new session MUST know @@ -51,16 +54,16 @@ Plan-review trio (**dotnet-architect + performance-analyst + security-auditor**) - Commits/PRs **omit** the Co-Authored-By / Generated-with trailer (project preference). See `[[no-claude-attribution]]`. ## Key decisions & context (carry forward) -- **Object validator = `[SettingsSection].ValidatorType`** (merged). One attribute; validate ⇒ discoverable. Core reads it in `ValuesPopulator.GetOrBuildPlan`. -- **VAL-01 dispatch = DIM bridge, no reflection** (both core AND the now-shipped DI runner). `ISettingValidation` default-implements the base `Validate`; dispatch via the `ISettingsValidator` cast. DI runner is **internal**, resolves from a fresh scope, injects `IServiceScopeFactory`, and eagerly allocates the error list (ThrowIfAny rejects null). -- **Exception-redaction invariant (S1+C2, locked).** `SettingsValidatorInvocationException` = value-free Type-only wrap for a throwing validator; `SettingsValidationException` composes only author `ValidationError` text; null/property exceptions value-free; no inner chaining of anything that saw a value. D-06 signed off for the COLL-03 sequence path AND T-04-VAL for the DI path. Don't weaken. -- **`SettingsPlan.HasValidators` zero-alloc short-circuit** protects the validator-free warm path (benchmark gate). Don't add per-populate allocation before it. +- **Canonical docs facts (Phase 5, source-verified):** `ValidateSimpleSettings()` extends **`IServiceProvider`** (returns the provider), opt-in/deferred (call after `BuildServiceProvider()`); attribute/`ValidatorType` validators run inline. Repo stays `existall/SimpleSettings`; only the legacy `SimpleConfig` repo/product name is purged. `docs/Security.md` is the canonical deep security/behavior reference + README deep-link target. +- **Exception-redaction invariant (S1+C2, locked).** Value-free; `ValidationError` text + validator constructors are OUTSIDE the guard (docs must caveat, not over-claim). Don't weaken. +- **`SettingsPlan.HasValidators` zero-alloc short-circuit** protects the validator-free warm path (benchmark gate). - **Generator concurrency (T7, #29):** one `_generationGate` (double-checked lock; warm path lock-free). Don't switch to `Lazy`-per-type. - **Pre-stable window:** no `v*` tag; breaking changes free until the first `v2.0.0-beta`. ## Minor tracked follow-ups (non-blocking) -- **Owner:** unlist published `2.0.0-alpha.0.*` prereleases on NuGet.org (guy-lud) — **grew by 1 this session** (#35 merge; running total incl. prior #33/#34). +- **Owner:** unlist published `2.0.0-alpha.0.*` prereleases on NuGet.org (guy-lud) — **will grow by 1 when the Phase-4-closeout+Phase-5 PR merges** (running total incl. prior #33/#34/#35). +- **PROJECT.md `### Active` requirements list is stale** (still lists API-01/PKG-01/PKG-02/SRC-02/COLL-01/DOC-01 etc. as `[ ]` though complete). REQUIREMENTS.md traceability IS authoritative + correct. Reconcile PROJECT.md wholesale at Phase-6 / milestone wrap. (New this session.) - **`.claude/`/`.codex/` tracked on `master`**: add to `.gitignore` + `git rm --cached` on a branch/PR (never on `master`). - **REQUIREMENTS.md traceability:** 13 brownfield baseline IDs (`BIND-01…NAME-01`) in the body but not the traceability table (pre-existing). -- **Codebase-map drift:** root files predate the map — refresh via `/gsd-map-codebase` when convenient. -- **`SettingsValidatorInvocationException`** is a public exception never registered as its own threat ID (security-audit note) — handled value-free + tested; register it if the threat model is revisited. +- **Codebase-map drift:** root files (README, LICENSE, icon.png, etc.) predate the map — refresh via `/gsd-map-codebase` when convenient. (Plan-time drift precheck flagged this; non-blocking.) +- **`SettingsValidatorInvocationException`** is a public exception never registered as its own threat ID — handled value-free + tested; register it if the threat model is revisited.