Skip to content

Phase 4 Wave 3: DI-resolved validator path + ISettingsCollection exposure (VAL-01 DI path, API-02)#35

Open
guy-lud wants to merge 5 commits into
masterfrom
gsd/phase-4-wave-3
Open

Phase 4 Wave 3: DI-resolved validator path + ISettingsCollection exposure (VAL-01 DI path, API-02)#35
guy-lud wants to merge 5 commits into
masterfrom
gsd/phase-4-wave-3

Conversation

@guy-lud

@guy-lud guy-lud commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Phase 4 Wave 3 (plan 04-04) — VAL-01 DI path + API-02

Completes the code for Phase 4 (Collection & Validation Binding). Waves 1–2 (#33) landed collection binding + the core validation engine; this wave adds the DI-integration surface.

API-02 — expose ISettingsCollection (D-15)

  • AddSimpleSettings now registers the built ISettingsCollection as a DI singleton (GetRequiredService<ISettingsCollection>()), serving the same instances the container resolves per interface.
  • New AddSimpleSettings(out ISettingsCollection settings, Action<ISettingsBuilderOptions>? = null) overload surfaces the built collection while preserving the IServiceCollection fluent chain. The out value and the DI singleton are the same instance.

VAL-01 — DI-resolved validator path (D-11)

  • New opt-in IServiceProvider.ValidateSimpleSettings() runs DI-registered ISettingValidation<T> in a deferred, post-BuildServiceProvider() step (the container isn't built during AddSimpleSettings).
  • Validators are resolved from a fresh scope (IServiceScopeFactory.CreateScope()), so validators with scoped dependencies resolve under validateScopes: true.
  • Dispatch uses the ISettingValidation<T> default-interface bridge (((ISettingsValidator)v).Validate(...)) — no reflection.
  • A throwing validator surfaces value-free as SettingsValidatorInvocationException(type, type) (no bound value, no chained inner); errors aggregate through the shared SettingsValidationException.ThrowIfAny, so the thrown contract is identical to the core populate path.
  • The runner (ISettingsValidationRunner) is internal; only ValidateSimpleSettings() is public. GenericHost stays Abstractions-only (no new dependency).

Notes

  • Attribute-declared validators ([SettingsSection(ValidatorType=…)], [SettingsProperty(ValidatorType=…)]) remain the core path and run automatically during binding; the DI path is additive. DI validators run only on the explicit ValidateSimpleSettings() call — an opt-in caveat documented in the XML-doc and carried to DOC-01 (README, Phase 5).

Tests & review

  • Build clean net8 + net10; full suite 153/153 on net10 (+10 new: 3 API-02, 7 DI-path incl. redaction, deferred-timing counter, fresh-scope execution, no-op, misuse).
  • Plan-review trio (architect/security/perf) up front; post-code review via gsd-code-reviewer + dotnet-kit code-reviewer + test-engineer. Security signed off the redaction invariant (T-04-VAL) as satisfied by construction. See .planning/phases/04-collection-validation-binding/04-04-{SUMMARY,REVIEW}.md.

Requirements: VAL-01 (DI path), API-02. Closes the code for Phase 4; phase verify + security gate to follow.

guy-lud added 5 commits July 15, 2026 21:44
Register the built ISettingsCollection as a DI singleton and add an
AddSimpleSettings(out ISettingsCollection, Action?) overload that surfaces
the same instance while preserving the IServiceCollection fluent chain.
Add a deferred, opt-in ISettingsValidationRunner resolved via
IServiceProvider.ValidateSimpleSettings(). It resolves DI-registered
ISettingValidation<T> from a fresh scope (so scoped-dependency validators
work), dispatches through the ISettingsValidator cast (the default-interface
bridge, no reflection), and aggregates via the shared
SettingsValidationException.ThrowIfAny so the thrown contract is identical to
the core populate path. A throwing validator surfaces value-free as
SettingsValidatorInvocationException (type-only, no bound value, no inner).
- ValidateSimpleSettings surfaces a caller-facing error when AddSimpleSettings
  was not called (internal runner type no longer leaks into the message).
- Trim runner comments to one line each; drop internal planning-ID reference.
- Harden tests: deferral test is now an invocation-counter timing probe
  (0 runs at build, 1 on the explicit call); scope test asserts the scoped
  validator actually executed; redaction test pins that the secret is bound;
  add no-op (no DI validators) and misuse coverage.
…tate

Record the VAL-01 DI path + API-02 execution: 04-04-SUMMARY.md, the
gsd-code-reviewer 04-04-REVIEW.md, STATE.md (all 5 Phase 4 plans landed;
verify+secure pending), and patch the plan's superseded reflective-dispatch
text to the shipped default-interface-bridge dispatch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant