Summary
Extend the AuthKit host health infrastructure to execute plugin health checks safely and efficiently, and to cache their results for a configurable TTL.
The host must create dedicated dependency injection scope for each health check execution and measure health check execution latency. Latency is host observed execution metadata and must not be placed inside PluginHealthResult.Data, which stays plugin owned diagnostic metadata (see D5–D6). To keep the plugin contract clean, the host wraps the plugin result together with the measured duration in a host-side execution result, and caches that wrapper.
Goal
Provide production safe host side execution of the structured plugin health contract introduced in D1–D6.
The implementation must ensure that:
- plugin health checks execute within scoped
IServiceProvider,
- scoped dependencies are disposed correctly (including async disposal),
- health check execution latency is measured by the host,
- the measured latency is exposed through a host-side wrapper, never inside plugin
Data,
- repeated health requests do not unnecessarily execute expensive plugin checks,
- cached results expire according to a defined TTL,
- and cancellation is propagated correctly.
Background
Plugin health checks may depend on scoped services such as:
Database contexts
Repositories
External service clients
Transactions
Other scoped dependencies
Resolving these services directly from the root IServiceProvider can cause lifetime violations, dependency resolution errors, or resource leaks.
Health endpoints may also be polled frequently by:
Load balancers
Kubernetes probes
Monitoring systems
Reverse proxies
Service discovery
Executing every plugin health check on every request can therefore generate unnecessary database, network, and other dependency traffic.
The host should cache completed health executions for configurable TTL while still allowing health information to expire and refresh.
Finally, health check latency should be measured by the host rather than requiring every plugin to implement its own timing logic.
Scope
D7. Host Side Health Check Execution
The host must execute each plugin health check inside dedicated DI scope and measure its execution duration.
To keep the plugin contract unchanged, the host introduces host side result type that wraps the plugin's PluginHealthResult together with the measured duration:
public sealed record PluginHealthExecutionResult(
IReadOnlyCollection<PluginHealthResult> Results,
TimeSpan Duration);
D7 covers:
- create the DI scope,
- resolve the scoped dependencies required by the health check,
- execute the health check (honoring
CancellationToken from D4),
- measure the execution duration (host-side),
- dispose the scope (including async disposal),
- build
PluginHealthExecutionResult(Results, Duration),
- propagate exceptions and cancellation explicitly.
PluginHealthResult.Data remains plugin owned diagnostic metadata. Host latency is never written into Data.
D8. Health Result Caching
The host must cache completed health executions for configurable TTL.
The cache stores the host side execution wrapper together with its expiration, not bare PluginHealthResult:
internal sealed record CachedPluginHealthExecution(
IReadOnlyCollection<PluginHealthResult> Results,
TimeSpan Duration,
DateTimeOffset ExpiresAt);
D8 covers:
- cache the completed
PluginHealthExecutionResult as CachedPluginHealthExecution,
- associate the cache entry with the correct plugin/health context (cache keys),
- expire entries according to the TTL,
- support explicit cache disabling,
- de-duplicate concurrent refreshes (single flight),
- keep concurrent access thread safe,
- never cache an incomplete/cancelled execution as valid result.
The implementation must not resolve scoped plugin dependencies directly from the root service provider.
Health Check Execution
The expected execution model is:
Health Request
│
▼
Check Health Cache
│
├── Valid cached CachedPluginHealthExecution
│ │
│ ▼
│ Return Results
│
└── Cache miss / expired
│
▼
Create (Async)Scope
│
▼
Resolve dependencies
│
▼
Execute plugin check
│
▼
Measure latency (Duration)
│
▼
Build PluginHealthExecutionResult(Results, Duration)
│
▼
Dispose scope
│
▼
Cache CachedPluginHealthExecution
│
▼
Return Results
Requirements
Scoped IServiceProvider
Each uncached plugin health check execution must occur inside dedicated DI scope.
The host should use the equivalent of:
await using var scope = serviceProvider.CreateAsyncScope();
before resolving scoped dependencies required by the health check, because the health execution pipeline is asynchronous and scoped services may implement IAsyncDisposable.
If the current AuthKit host architecture requires synchronous disposal, the host may use:
using var scope = serviceProvider.CreateScope();
The scoped provider must be used for dependency resolution.
The host must not resolve plugin scoped dependencies from the root:
when scoped provider is required. The scope must be disposed after health check execution completes.
Scope Lifetime
A health check scope must remain alive for the complete execution of the health check.
The scope must be disposed after:
- successful execution,
- failed execution,
- cancellation,
- or another terminal execution path.
The implementation must not leak scoped services.
Async Scope
Because health execution is asynchronous (health checks are Task/ValueTask), prefer:
await using var scope = serviceProvider.CreateAsyncScope();
This ensures scoped services implementing IAsyncDisposable are disposed correctly. The selected mechanism must remain compatible with the target framework and existing AuthKit host conventions.
Dependency Resolution
The host must ensure that dependencies required by the plugin health check are resolved from the newly created scope.
A scoped dependency must not be captured and reused across independent health check executions. Each fresh health check execution must receive an appropriate scope.
Latency Measurement
The host must measure the duration of plugin health check execution.
The measurement should use a monotonic timing mechanism suitable for elapsed duration measurement, such as:
The host must not calculate latency using wall clock timestamps where clock adjustments could produce incorrect elapsed durations.
The measured duration is represented as TimeSpan Duration on PluginHealthExecutionResult (and propagated to CachedPluginHealthExecution). The host may also expose it as latency_ms in serialized host output, but the source of truth is the host-side Duration, never PluginHealthResult.Data.
Latency Ownership
Latency is host observed execution metadata.
Plugins must not be required to calculate latency themselves. PluginHealthResult.Data is plugin-owned diagnostic metadata (D5–D6): connection counts, endpoint URLs, custom plugin timings, etc.
Because the host stores its measured duration in PluginHealthExecutionResult.Duration (and CachedPluginHealthExecution.Duration), there is no architectural conflict with plugin supplied Data. A plugin may freely put its own diagnostic values (including its own latency estimate) into Data; that stays plugin data and is never reinterpreted as the host execution latency. No precedence rule is needed.
Health Result Caching
The host must cache completed PluginHealthExecutionResult values as CachedPluginHealthExecution for a configurable TTL. The cache must prevent every health request from executing the underlying plugin health check.
For example, with:
multiple requests within the TTL should reuse the same completed health execution:
Request 1 -> Execute plugin -> Cache CachedPluginHealthExecution
Request 2 -> Cached result
Request 3 -> Cached result
Request 4 -> Cached result
...
TTL expires
Request N -> Execute plugin again
Cache Scope
Cached executions must be associated with the appropriate plugin and health check context. A result from one plugin must never be returned for another plugin.
If the host exposes multiple health check contexts or instances, cache keys must distinguish them appropriately.
Cache Expiration
When the TTL expires, the cached CachedPluginHealthExecution must no longer be considered valid. The next health check request must refresh the result by executing the plugin health check again.
Expired results must not be silently treated as permanently valid.
Configurable TTL
The cache TTL must be configurable by the host. The implementation must define clear default value if no explicit TTL is configured.
The default must prevent excessive repeated health check execution while avoiding excessively stale health information.
The exact default value may follow existing AuthKit host configuration conventions.
Disabled Caching
The host should support disabling health result caching where required.
A TTL of zero or an equivalent explicit configuration may be used to indicate that every request should execute fresh health check, provided this behavior is clearly defined by the host configuration.
The implementation must not accidentally interpret disabled cache as an infinite cache.
Concurrent Requests
The implementation should avoid unnecessary duplicate health checks when multiple requests arrive simultaneously after cache expiration.
For example:
Request A ─┐
Request B ─┼─ cache expired
Request C ─┘
│
▼
One health check execution
│
▼
Shared refreshed result
The host should prefer single flight or equivalent synchronization mechanism where practical.
However, synchronization must not introduce an unbounded lock or prevent unrelated plugins from executing their health checks concurrently.
If concurrent execution is intentionally allowed, this behavior must be explicitly documented and tested.
Cache and Cancellation
Cancellation of an individual health request must not corrupt the cached result.
If health check execution is cancelled before producing completed PluginHealthExecutionResult:
- the incomplete operation must not be cached as successful result,
- a fabricated
Healthy result must not be inserted into the cache,
- and the next eligible request must be able to perform fresh health check.
A previously valid cached CachedPluginHealthExecution may remain available according to the host's explicitly defined stale result policy, but cancellation must not transform an incomplete execution into a new valid cached result.
Failure Handling
If a plugin health check throws an exception, the host must handle the failure explicitly according to the existing health infrastructure.
The host must not silently convert arbitrary exceptions into:
The resulting health state must remain distinguishable from successful health check.
If the host converts execution failures into Unhealthy, this behavior must be explicit and consistent.
Failed executions may be cached only if the host's health caching policy explicitly defines this behavior. (When cached, the entry is CachedPluginHealthExecution carrying the failed PluginHealthResult.)
Multiple Health Results
The host must preserve the multiple result semantics introduced in D3–D4.
If plugin returns:
Database → Healthy
Cache → Healthy
External API → Degraded
the host must not collapse the results into an unrelated single boolean value.
Each PluginHealthResult must remain available inside PluginHealthExecutionResult.Results (and CachedPluginHealthExecution.Results) for the host aggregation layer.
Aggregation semantics belong to the host and must be handled explicitly.
Interaction With D5–D6 Metadata
The host must preserve, for every PluginHealthResult:
Status,
Reason,
Tags,
- and
Data
when building the execution wrapper and caching it.
Host generated latency lives in PluginHealthExecutionResult.Duration / CachedPluginHealthExecution.Duration - clearly separated from plugin defined Data. The host must not remove or silently rewrite plugin health tags or diagnostic information, and must not inject host latency into Data.
Thread Safety
The health result cache must be safe for concurrent access.
Multiple requests may access the health infrastructure concurrently.
The implementation must avoid:
- race conditions,
- corrupted cache entries,
- cross plugin result contamination,
- disposing scopes while they are still in use,
- or returning partially constructed health results.
Backward Compatibility
The implementation must not require existing plugins to change solely to support host-side caching or scoped execution.
PluginHealthResult (D1–D4) keeps its shape: Status, Reason, Tags, Data. D7–D8 adds only host-side types (PluginHealthExecutionResult, CachedPluginHealthExecution); it does not modify the plugin contract.
Plugins that implement the structured health contract from D3–D4 must automatically benefit from host side execution infrastructure.
Existing plugins without custom structured health implementation must continue to use the compatibility behavior defined by the previous Section D tasks.
Existing plugin behavior outside health checks must remain unchanged.
DevTokens must continue to compile and load successfully.
Non Goals
This task does not include:
- modifying
PluginHealthResult (no Metadata or latency_ms field added to the plugin contract),
- introducing new health status values,
- redesigning
IAuthKitPlugin,
- defining new DI framework,
- implementing custom caching framework,
- introducing distributed health result caching,
- changing plugin authentication behavior,
- or redesigning the
/health endpoint response contract beyond what is required to expose the new health information.
A simple in process host cache is sufficient unless existing AuthKit architecture requires otherwise.
Validation
Scoped Execution
Latency
Caching
- Completed
PluginHealthExecutionResult values are cached as CachedPluginHealthExecution after successful completion.
- Cached results are returned within the configured TTL.
- Expired results trigger a fresh health check.
- Cache TTL is configurable.
- A documented default TTL exists.
- Cache can be explicitly disabled.
- Cache keys distinguish independent plugins/health contexts.
- Concurrent cache access is thread-safe.
- Cache does not contain partially completed results.
Cancellation
- Cancelled health checks are not cached as successful results.
- Cancellation does not corrupt existing cache entries.
- A subsequent request can execute a fresh check after a cancelled refresh.
- Plugin cancellation behavior from D4 remains preserved.
Result Preservation
- Multiple health results remain available (in
Results).
Status is preserved.
Reason is preserved.
Tags are preserved.
Data is preserved.
- Host-generated latency is present via
Duration, separate from Data.
Failure Handling
- Health check exceptions are handled explicitly.
- Exceptions are never silently converted to
Healthy.
- Failure behavior is covered by tests.
- Cache behavior after failed execution is explicitly tested.
Compatibility
- Existing plugins continue to compile without modification.
DevTokens continues to compile and load successfully.
PluginContractValidator passes.
- Existing non-health host behavior remains unchanged.
Build and Tests
dotnet build completes successfully with zero warnings.
- Relevant unit tests pass.
- Integration tests pass.
- Scoped dependency tests pass.
- Cache TTL tests pass.
- Concurrent request tests pass.
- Cancellation tests pass.
- Latency measurement tests pass.
Acceptance Criteria
- Every uncached plugin health check executes inside dedicated DI scope (
CreateAsyncScope where async disposal is required).
- Scoped dependencies are correctly resolved and disposed.
- Health check execution latency is measured by the host.
- Latency is exposed through
PluginHealthExecutionResult.Duration / CachedPluginHealthExecution.Duration, never inside PluginHealthResult.Data.
- Health executions are cached using a configurable TTL as
CachedPluginHealthExecution.
- Expired results trigger fresh health check.
- Disabled caching is explicitly supported.
- Concurrent requests cannot corrupt the cache or return results from another plugin.
- Cancelled health checks do not create invalid cache entries.
- Exceptions are handled explicitly and never silently reported as healthy.
- Multiple health results and their metadata remain intact.
PluginHealthResult contract shape is unchanged.
- Existing plugins remain compatible.
PluginContractValidator passes.
DevTokens continues to compile and load successfully.
Summary
Extend the AuthKit host health infrastructure to execute plugin health checks safely and efficiently, and to cache their results for a configurable TTL.
The host must create dedicated dependency injection scope for each health check execution and measure health check execution latency. Latency is host observed execution metadata and must not be placed inside
PluginHealthResult.Data, which stays plugin owned diagnostic metadata (see D5–D6). To keep the plugin contract clean, the host wraps the plugin result together with the measured duration in a host-side execution result, and caches that wrapper.Goal
Provide production safe host side execution of the structured plugin health contract introduced in D1–D6.
The implementation must ensure that:
IServiceProvider,Data,Background
Plugin health checks may depend on scoped services such as:
Resolving these services directly from the root
IServiceProvidercan cause lifetime violations, dependency resolution errors, or resource leaks.Health endpoints may also be polled frequently by:
Executing every plugin health check on every request can therefore generate unnecessary database, network, and other dependency traffic.
The host should cache completed health executions for configurable TTL while still allowing health information to expire and refresh.
Finally, health check latency should be measured by the host rather than requiring every plugin to implement its own timing logic.
Scope
D7. Host Side Health Check Execution
The host must execute each plugin health check inside dedicated DI scope and measure its execution duration.
To keep the plugin contract unchanged, the host introduces host side result type that wraps the plugin's
PluginHealthResulttogether with the measured duration:D7 covers:
CancellationTokenfrom D4),PluginHealthExecutionResult(Results, Duration),PluginHealthResult.Dataremains plugin owned diagnostic metadata. Host latency is never written intoData.D8. Health Result Caching
The host must cache completed health executions for configurable TTL.
The cache stores the host side execution wrapper together with its expiration, not bare
PluginHealthResult:D8 covers:
PluginHealthExecutionResultasCachedPluginHealthExecution,The implementation must not resolve scoped plugin dependencies directly from the root service provider.
Health Check Execution
The expected execution model is:
Requirements
Scoped
IServiceProviderEach uncached plugin health check execution must occur inside dedicated DI scope.
The host should use the equivalent of:
before resolving scoped dependencies required by the health check, because the health execution pipeline is asynchronous and scoped services may implement
IAsyncDisposable.If the current AuthKit host architecture requires synchronous disposal, the host may use:
The scoped provider must be used for dependency resolution.
The host must not resolve plugin scoped dependencies from the root:
when scoped provider is required. The scope must be disposed after health check execution completes.
Scope Lifetime
A health check scope must remain alive for the complete execution of the health check.
The scope must be disposed after:
The implementation must not leak scoped services.
Async Scope
Because health execution is asynchronous (health checks are
Task/ValueTask), prefer:This ensures scoped services implementing
IAsyncDisposableare disposed correctly. The selected mechanism must remain compatible with the target framework and existing AuthKit host conventions.Dependency Resolution
The host must ensure that dependencies required by the plugin health check are resolved from the newly created scope.
A scoped dependency must not be captured and reused across independent health check executions. Each fresh health check execution must receive an appropriate scope.
Latency Measurement
The host must measure the duration of plugin health check execution.
The measurement should use a monotonic timing mechanism suitable for elapsed duration measurement, such as:
The host must not calculate latency using wall clock timestamps where clock adjustments could produce incorrect elapsed durations.
The measured duration is represented as
TimeSpan DurationonPluginHealthExecutionResult(and propagated toCachedPluginHealthExecution). The host may also expose it aslatency_msin serialized host output, but the source of truth is the host-sideDuration, neverPluginHealthResult.Data.Latency Ownership
Latency is host observed execution metadata.
Plugins must not be required to calculate latency themselves.
PluginHealthResult.Datais plugin-owned diagnostic metadata (D5–D6): connection counts, endpoint URLs, custom plugin timings, etc.Because the host stores its measured duration in
PluginHealthExecutionResult.Duration(andCachedPluginHealthExecution.Duration), there is no architectural conflict with plugin suppliedData. A plugin may freely put its own diagnostic values (including its own latency estimate) intoData; that stays plugin data and is never reinterpreted as the host execution latency. No precedence rule is needed.Health Result Caching
The host must cache completed
PluginHealthExecutionResultvalues asCachedPluginHealthExecutionfor a configurable TTL. The cache must prevent every health request from executing the underlying plugin health check.For example, with:
multiple requests within the TTL should reuse the same completed health execution:
Cache Scope
Cached executions must be associated with the appropriate plugin and health check context. A result from one plugin must never be returned for another plugin.
If the host exposes multiple health check contexts or instances, cache keys must distinguish them appropriately.
Cache Expiration
When the TTL expires, the cached
CachedPluginHealthExecutionmust no longer be considered valid. The next health check request must refresh the result by executing the plugin health check again.Expired results must not be silently treated as permanently valid.
Configurable TTL
The cache TTL must be configurable by the host. The implementation must define clear default value if no explicit TTL is configured.
The default must prevent excessive repeated health check execution while avoiding excessively stale health information.
The exact default value may follow existing AuthKit host configuration conventions.
Disabled Caching
The host should support disabling health result caching where required.
A TTL of zero or an equivalent explicit configuration may be used to indicate that every request should execute fresh health check, provided this behavior is clearly defined by the host configuration.
The implementation must not accidentally interpret disabled cache as an infinite cache.
Concurrent Requests
The implementation should avoid unnecessary duplicate health checks when multiple requests arrive simultaneously after cache expiration.
For example:
The host should prefer single flight or equivalent synchronization mechanism where practical.
However, synchronization must not introduce an unbounded lock or prevent unrelated plugins from executing their health checks concurrently.
If concurrent execution is intentionally allowed, this behavior must be explicitly documented and tested.
Cache and Cancellation
Cancellation of an individual health request must not corrupt the cached result.
If health check execution is cancelled before producing completed
PluginHealthExecutionResult:Healthyresult must not be inserted into the cache,A previously valid cached
CachedPluginHealthExecutionmay remain available according to the host's explicitly defined stale result policy, but cancellation must not transform an incomplete execution into a new valid cached result.Failure Handling
If a plugin health check throws an exception, the host must handle the failure explicitly according to the existing health infrastructure.
The host must not silently convert arbitrary exceptions into:
The resulting health state must remain distinguishable from successful health check.
If the host converts execution failures into
Unhealthy, this behavior must be explicit and consistent.Failed executions may be cached only if the host's health caching policy explicitly defines this behavior. (When cached, the entry is
CachedPluginHealthExecutioncarrying the failedPluginHealthResult.)Multiple Health Results
The host must preserve the multiple result semantics introduced in D3–D4.
If plugin returns:
the host must not collapse the results into an unrelated single boolean value.
Each
PluginHealthResultmust remain available insidePluginHealthExecutionResult.Results(andCachedPluginHealthExecution.Results) for the host aggregation layer.Aggregation semantics belong to the host and must be handled explicitly.
Interaction With D5–D6 Metadata
The host must preserve, for every
PluginHealthResult:Status,Reason,Tags,Datawhen building the execution wrapper and caching it.
Host generated latency lives in
PluginHealthExecutionResult.Duration/CachedPluginHealthExecution.Duration- clearly separated from plugin definedData. The host must not remove or silently rewrite plugin health tags or diagnostic information, and must not inject host latency intoData.Thread Safety
The health result cache must be safe for concurrent access.
Multiple requests may access the health infrastructure concurrently.
The implementation must avoid:
Backward Compatibility
The implementation must not require existing plugins to change solely to support host-side caching or scoped execution.
PluginHealthResult(D1–D4) keeps its shape:Status,Reason,Tags,Data. D7–D8 adds only host-side types (PluginHealthExecutionResult,CachedPluginHealthExecution); it does not modify the plugin contract.Plugins that implement the structured health contract from D3–D4 must automatically benefit from host side execution infrastructure.
Existing plugins without custom structured health implementation must continue to use the compatibility behavior defined by the previous Section D tasks.
Existing plugin behavior outside health checks must remain unchanged.
DevTokensmust continue to compile and load successfully.Non Goals
This task does not include:
PluginHealthResult(noMetadataorlatency_msfield added to the plugin contract),IAuthKitPlugin,/healthendpoint response contract beyond what is required to expose the new health information.A simple in process host cache is sufficient unless existing AuthKit architecture requires otherwise.
Validation
Scoped Execution
CreateAsyncScope/await usingwhere applicable.Latency
PluginHealthExecutionResult.Duration(andCachedPluginHealthExecution.Duration), not throughPluginHealthResult.Data.Data.Caching
PluginHealthExecutionResultvalues are cached asCachedPluginHealthExecutionafter successful completion.Cancellation
Result Preservation
Results).Statusis preserved.Reasonis preserved.Tagsare preserved.Datais preserved.Duration, separate fromData.Failure Handling
Healthy.Compatibility
DevTokenscontinues to compile and load successfully.PluginContractValidatorpasses.Build and Tests
dotnet buildcompletes successfully with zero warnings.Acceptance Criteria
CreateAsyncScopewhere async disposal is required).PluginHealthExecutionResult.Duration/CachedPluginHealthExecution.Duration, never insidePluginHealthResult.Data.CachedPluginHealthExecution.PluginHealthResultcontract shape is unchanged.PluginContractValidatorpasses.DevTokenscontinues to compile and load successfully.