Summary
Extend tools/PluginContractValidator to verify that plugin contributed middleware is structurally valid before the host starts.
A plugin middleware is declared by type (PluginMiddleware.MiddlewareType); the host activates it. The validator does not impose a single uniform shape it distinguishes three distinct middleware models and validates each according to its own rules:
- Convention middleware -
MiddlewareType following the ASP.NET Core middleware convention.
AuthKitMiddlewareBase - an AuthKit specific base class.
IAuthKitMiddleware - an AuthKit specific, DI aware interface.
Only the convention model requires RequestDelegate constructor parameter; the two AuthKit models are activated by the host from the request service provider and must not be forced into the ASP.NET Core constructor convention. Non conforming middleware must be reported explicitly so failures are caught at startup validation rather than at request time.
There is no Factory or PluginMiddlewareInstance in the contract the validator only inspects the declared MiddlewareType and which AuthKit model (if any) it satisfies.
Goal
Ensure that every PluginMiddleware registration (C2), every IAuthKitMiddleware implementation (C5), and every AuthKitMiddlewareBase subclass (C4) is structurally valid.
The validator must produce actionable diagnostics that name:
- the owning plugin,
- the offending middleware type,
- and the specific contract violation.
Background
ASP.NET Core middleware is commonly expressed as convention based: a type with (RequestDelegate next, ...deps) constructor and an Invoke/InvokeAsync method. The plugin contract additionally introduces two AuthKit specific models AuthKitMiddlewareBase (C4) and IAuthKitMiddleware (C5) which are NOT the ASP.NET Core IMiddleware interface. The validator must accept all valid forms and reject invalid ones without forcing the ASP.NET Core convention onto the AuthKit abstractions.
Scope
C6. Middleware contract validation
For each PluginMiddleware, the validator classifies MiddlewareType into one of the three models applying explicit precedence and rejecting ambiguous combinations and validates accordingly:
Model 1. Convention middleware (MiddlewareType)
MiddlewareType is public, concrete, non abstract, generic.
- exposes public constructor whose first parameter is
RequestDelegate (remaining parameters are injectable dependencies),
- exposes public method
InvokeAsync(HttpContext, RequestDelegate) or Invoke(HttpContext, RequestDelegate) returning Task (never void).
Model 2. AuthKitMiddlewareBase (MiddlewareType)
MiddlewareType is public, concrete, non abstract, generic.
- inherits
AuthKitMiddlewareBase,
- overrides
InvokeAsync(HttpContext, RequestDelegate).
- constructor is compatible with host DI activation (host activates from the request service provider), it does not require RequestDelegate` constructor parameter. Final dependency resolvability is confirmed by the host DI container at startup, not by this structural validator.
Model 3. IAuthKitMiddleware (MiddlewareType)
MiddlewareType is public, concrete, non abstract, generic.
- implements
IAuthKitMiddleware,
- provides
InvokeAsync(HttpContext, RequestDelegate).
- constructor is compatible with host DI activation (host activates from the request service provider), it does not require
RequestDelegate constructor parameter. Final dependency resolvability is confirmed by the host DI container at startup, not by this structural validator.
A MiddlewareType must not simultaneously inherit AuthKitMiddlewareBase and implement IAuthKitMiddleware such type is rejected as ambiguous the host should not have to guess which lifecycle to apply. If type matches more than one AuthKit model, validation fails.
A MiddlewareType that matches none of the three models is rejected.
C6. Middleware Contract Validation
Proposed Checks
For each plugin:
For each PluginMiddleware entry:
MiddlewareType must be non null (contract API already non nullable;
guarded at reflection time too)
type must be public, concrete, non abstract, non generic
if type inherits AuthKitMiddlewareBase AND implements IAuthKitMiddleware:
-> ERROR: ambiguous middleware model (both AuthKit models)
else if type inherits AuthKitMiddlewareBase:
-> Model 2: requires valid instance InvokeAsync override
constructor compatible with host DI activation
(final resolvability confirmed by host DI container at startup)
else if type implements IAuthKitMiddleware:
-> Model 3: requires instance InvokeAsync(HttpContext, RequestDelegate)
constructor compatible with host DI activation
(final resolvability confirmed by host DI container at startup)
else:
-> Model 1 (convention):
requires public ctor with first param RequestDelegate
requires instance InvokeAsync/Invoke(HttpContext, RequestDelegate) returning Task
(errors if requirements not met)
Error Reporting
Failures must be explicit and model aware, for example:
Plugin 'DevTokens' middleware 'MyMiddleware' is invalid:
does not implement AuthKitMiddlewareBase or IAuthKitMiddleware,
and its constructor does not take RequestDelegate as the first parameter.
or
Plugin 'X' declares PluginMiddleware with a null MiddlewareType.
The validator returns non zero / [FAIL] result so startup or CI fails fast.
Edge Cases
- Accepted method signatures (convention model): exactly
Task InvokeAsync(HttpContext, RequestDelegate) or Task Invoke(HttpContext, RequestDelegate). A void returning method is not accepted.
- Constructor parameter order (convention model): only the first parameter must be
RequestDelegate; the rest are injectable dependencies.
- Multiple constructors (convention model): the validator must identify at least one valid activation constructor whose first parameter is
RequestDelegate. If more than one such constructor exists and the host cannot deterministically select one, validation fails (final resolvability is still confirmed by the DI container at startup).
- DI based models (Model 2 / Model 3): the constructor is validated only as compatible with host DI activation (
public, concrete, with injectable parameters); a RequestDelegate constructor parameter is not required and must not be demanded. Final dependency resolvability is confirmed by the host DI container at startup, not by this structural validator.
- Generic types (both open
MyMiddleware<T> and closed MyMiddleware<int>) are rejected the contract does not support generic middleware; this keeps the validator, plugin loader, and diagnostics simple.
- Abstract types and interfaces supplied as
MiddlewareType are rejected.
- Static invocation methods are rejected.
Invoke/InvokeAsync must be instance methods, static method is not valid middleware invocation entry point (the override requirement for AuthKitMiddlewareBase already implies this, but it is checked explicitly).
- Null
MiddlewareType: already invalid at the API level (non nullable record member) the validator still guards against it defensively.
Host Behavior
The host must run the validator during startup (and in CI) and must not start with non conforming middleware contract. A configuration may downgrade specific findings to warnings only if explicitly allowed by the host policy by default, structural middleware errors are fatal. Final dependency resolvability of DI based middleware is confirmed by the DI container at startup.
Backward Compatibility
This sub issue is additive.
- Existing plugins without middleware are unaffected.
- Existing valid middleware continues to validate as
[PASS].
DevTokens must continue to pass validation.
- The validator itself is extended, not replaced.
Validation
C6. Middleware Contract Validation
Integration
Acceptance Criteria
- Plugin contributed middleware is validated at contract-check time.
- Valid convention based, base class, and interface based middleware all pass.
- Type based
PluginMiddleware registrations are validated.
- A type inheriting both
AuthKitMiddlewareBase and IAuthKitMiddleware is rejected as ambiguous.
- Static
Invoke/InvokeAsync methods are rejected.
- Invalid middleware is rejected with clear message naming the plugin and type:
- convention middleware missing
RequestDelegate constructor or Task returning Invoke/InvokeAsync,
AuthKitMiddlewareBase / IAuthKitMiddleware types missing the required InvokeAsync,
- generic/abstract types, or types matching no supported model.
- The validator fails fast on structural errors.
DevTokens continues to pass.
- Existing plugins remain unaffected.
Non Goals
This sub issue does not include:
- gRPC interceptor validation (see C7),
- pipeline ordering logic (see C1–C5),
- runtime middleware behavior,
- enforcing single uniform constructor shape across all three models,
- or changing the
PluginContractValidator output format beyond what is needed for clear diagnostics.
Summary
Extend
tools/PluginContractValidatorto verify that plugin contributed middleware is structurally valid before the host starts.A plugin middleware is declared by type (
PluginMiddleware.MiddlewareType); the host activates it. The validator does not impose a single uniform shape it distinguishes three distinct middleware models and validates each according to its own rules:MiddlewareTypefollowing the ASP.NET Core middleware convention.AuthKitMiddlewareBase- an AuthKit specific base class.IAuthKitMiddleware- an AuthKit specific, DI aware interface.Only the convention model requires
RequestDelegateconstructor parameter; the two AuthKit models are activated by the host from the request service provider and must not be forced into the ASP.NET Core constructor convention. Non conforming middleware must be reported explicitly so failures are caught at startup validation rather than at request time.There is no
FactoryorPluginMiddlewareInstancein the contract the validator only inspects the declaredMiddlewareTypeand which AuthKit model (if any) it satisfies.Goal
Ensure that every
PluginMiddlewareregistration (C2), everyIAuthKitMiddlewareimplementation (C5), and everyAuthKitMiddlewareBasesubclass (C4) is structurally valid.The validator must produce actionable diagnostics that name:
Background
ASP.NET Core middleware is commonly expressed as convention based: a type with
(RequestDelegate next, ...deps)constructor and anInvoke/InvokeAsyncmethod. The plugin contract additionally introduces two AuthKit specific modelsAuthKitMiddlewareBase(C4) andIAuthKitMiddleware(C5) which are NOT the ASP.NET CoreIMiddlewareinterface. The validator must accept all valid forms and reject invalid ones without forcing the ASP.NET Core convention onto the AuthKit abstractions.Scope
C6. Middleware contract validation
For each
PluginMiddleware, the validator classifiesMiddlewareTypeinto one of the three models applying explicit precedence and rejecting ambiguous combinations and validates accordingly:Model 1. Convention middleware (
MiddlewareType)MiddlewareTypeis public, concrete, non abstract, generic.RequestDelegate(remaining parameters are injectable dependencies),InvokeAsync(HttpContext, RequestDelegate)orInvoke(HttpContext, RequestDelegate)returningTask(nevervoid).Model 2.
AuthKitMiddlewareBase(MiddlewareType)MiddlewareTypeis public, concrete, non abstract, generic.AuthKitMiddlewareBase,InvokeAsync(HttpContext, RequestDelegate).Model 3.
IAuthKitMiddleware(MiddlewareType)MiddlewareTypeis public, concrete, non abstract, generic.IAuthKitMiddleware,InvokeAsync(HttpContext, RequestDelegate).RequestDelegateconstructor parameter. Final dependency resolvability is confirmed by the host DI container at startup, not by this structural validator.A
MiddlewareTypemust not simultaneously inheritAuthKitMiddlewareBaseand implementIAuthKitMiddlewaresuch type is rejected as ambiguous the host should not have to guess which lifecycle to apply. If type matches more than one AuthKit model, validation fails.A
MiddlewareTypethat matches none of the three models is rejected.C6. Middleware Contract Validation
Proposed Checks
Error Reporting
Failures must be explicit and model aware, for example:
or
The validator returns non zero /
[FAIL]result so startup or CI fails fast.Edge Cases
Task InvokeAsync(HttpContext, RequestDelegate)orTask Invoke(HttpContext, RequestDelegate). Avoidreturning method is not accepted.RequestDelegate; the rest are injectable dependencies.RequestDelegate. If more than one such constructor exists and the host cannot deterministically select one, validation fails (final resolvability is still confirmed by the DI container at startup).public,concrete, with injectable parameters); aRequestDelegateconstructor parameter is not required and must not be demanded. Final dependency resolvability is confirmed by the host DI container at startup, not by this structural validator.MyMiddleware<T>and closedMyMiddleware<int>) are rejected the contract does not support generic middleware; this keeps the validator, plugin loader, and diagnostics simple.MiddlewareTypeare rejected.Invoke/InvokeAsyncmust be instance methods, static method is not valid middleware invocation entry point (the override requirement forAuthKitMiddlewareBasealready implies this, but it is checked explicitly).MiddlewareType: already invalid at the API level (non nullable record member) the validator still guards against it defensively.Host Behavior
The host must run the validator during startup (and in CI) and must not start with non conforming middleware contract. A configuration may downgrade specific findings to warnings only if explicitly allowed by the host policy by default, structural middleware errors are fatal. Final dependency resolvability of DI based middleware is confirmed by the DI container at startup.
Backward Compatibility
This sub issue is additive.
[PASS].DevTokensmust continue to pass validation.Validation
C6. Middleware Contract Validation
MiddlewareTypeinto one of the three models.MiddlewareTypeconcrete/public/non generic, public ctor with first paramRequestDelegate,InvokeAsync/InvokereturningTask(novoid).AuthKitMiddlewareBasesubclasses validate via instanceInvokeAsyncoverride; constructor need not takeRequestDelegate.IAuthKitMiddlewareimplementations validate via instanceInvokeAsync; constructor compatible with host DI activation, need not takeRequestDelegate.AuthKitMiddlewareBaseandIAuthKitMiddlewareis rejected as ambiguous.Invoke/InvokeAsyncmethods are rejected.Integration
dotnet build AuthKit.Plugins.Abstractionssucceeds.tools/PluginContractValidatoronDevTokens→[PASS].Acceptance Criteria
PluginMiddlewareregistrations are validated.AuthKitMiddlewareBaseandIAuthKitMiddlewareis rejected as ambiguous.Invoke/InvokeAsyncmethods are rejected.RequestDelegateconstructor orTaskreturningInvoke/InvokeAsync,AuthKitMiddlewareBase/IAuthKitMiddlewaretypes missing the requiredInvokeAsync,DevTokenscontinues to pass.Non Goals
This sub issue does not include:
PluginContractValidatoroutput format beyond what is needed for clear diagnostics.Design note: lenient convention
InvokeAsyncsignatureThe convention model check accepts
Task InvokeAsync/Invoke(HttpContext, ...deps)ie.HttpContextfirst, followed by optional injectable dependencies (classic ASP.NET Core convention) instead of strictlyexactly (HttpContext, RequestDelegate).Rationale: the existing
DeveloperTokenMiddleware.InvokeAsync(HttpContext, IDeveloperTokenValidator)(IMiddleware-style, dependencies via method injection) is valid, working middleware. A strict exact signature check would reject it and break the "DevTokensmust continue to pass" requirement.The strict parts are still enforced: return type must be
Task(nevervoid), the method must be public insta…