Skip to content

[Task] Add Plugin Isolation and Automated Contract Validation #28

Description

@rian-be

Summary

Apply AssemblyLoadContext isolation to each plugin and wire tools/PluginContractValidator into the loading flow then extend the validator with rules covering the new contract hooks so invalid or conflicting plugins fail at startup, not at runtime.

Goal

Prevent dependency conflicts between plugins and the host via per plugin load contexts, and make contract validation an automatic, fully covered part of loading.

Background

With single shared load context, conflicting transitive dependencies between plugins cause TypeLoadException, ambiguous types, or silent wrong version binds. Separately, the validator today is separate tool the loader does not run, so invalid plugins are only caught when manually validated or when they blow up at runtime. Isolation and validation are applied by the loader after discovery and after the manifest is read.

Scope

G4. AssemblyLoadContext isolation

The loader creates one AssemblyLoadContext per plugin and records it on DiscoveredPlugin.

G5. Validator integration

The loader invokes PluginContractValidator on each discovered plugin as part of loading.

G6. Validator hooks coverage

Add validation rules for the new contract members introduced by other sections.

Architecture

flowchart TD
    Disc["DiscoveredPlugin<br/>Manifest + Location"] --> Gate{"Compatibility Gate<br/>G3"}
    Gate -- accepted --> ALC["Isolated Load G4<br/>new AssemblyLoadContext<br/>per plugin IsCollectible"]
    ALC --> LP["LoadedPlugin<br/>Manifest + Instance + ALC"]
    LP --> Val["PluginContractValidator G5<br/>runs automatically"]
    Val -- fail --> Rej["REJECT<br/>startup error"]
    Val -- pass --> Cov["Coverage G6<br/>G1/G2/B/C/D/F hooks"]
    Cov --> Act["ACTIVATION"]
    Host["Host / framework<br/>shared - not duplicated"] -. shared .-> ALC
    classDef gate fill:#fef3c7,stroke:#f59e0b,color:#92400e
    classDef iso fill:#dbeafe,stroke:#3b82f6,color:#1e40af
    classDef reject fill:#fee2e2,stroke:#ef4444,color:#991b1b
    classDef ok fill:#dcfce7,stroke:#22c55e,color:#14532d
    class Gate,Val,Cov gate
    class ALC,LP iso
    class Rej reject
    class Act ok
Loading
flowchart TD
    LP4["LoadedPlugin"] --> ALC2["AssemblyLoadContext<br/>isolated runtime"]
    ALC2 --> Priv["plugin private deps<br/>isolated"]
    ALC2 -. shared .-> Shared["Host assemblies<br/>shared"]
    Priv --> Inst["Instance"]
    Inst --> Act2["ACTIVATION"]
    classDef iso fill:#dbeafe,stroke:#3b82f6,color:#1e40af
    class ALC2,Priv iso
    class Shared fill:#f3f4f6,stroke:#9ca3af,color:#374151
Loading

Proposed Contract

  • G4: the loader creates one AssemblyLoadContext per plugin (preferably IsCollectible to support unload/hot-reload). Host assemblies remain shared only plugin private dependencies are isolated. The context is created during the isolated load and attached to LoadedPlugin (the runtime model from G1), not to DiscoveredPlugin, DiscoveredPlugin stays discovery only (Manifest + Location).
  • G5: the loade invokes PluginContractValidator on each loaded plugin/type after isolation. On failure it raises startup error (the basic gate is hard reject Strict/Warn/Ignore policy engine is separate host feature, not part of this contract).
  • G6: add validation rules for the presence/signatures of the new hooks (loader/discoverer interfaces, manifest shape, lifecycle hooks from B, middleware models from C, health result from D, security scheme extensions from F). Each rule reports the specific missing/invalid member.

Requirements

G4. Isolation

  • Each plugin loads into its own AssemblyLoadContext.
  • Host/framework assemblies are shared, not duplicated per plugin.
  • Only plugin private dependencies are isolated.
  • The AssemblyLoadContext is attached to LoadedPlugin produced by IPluginLoader.LoadAsync.
  • Contexts are IsCollectible where possible to enable hot reload (K12).

G5. Integration

  • The validator runs automatically as part of loading no separate manual step required for the host to be safe.
  • On failure: startup error (the basic gate is hard reject Strict/Warn/Ignore is later host policy).
  • Validation happens after isolation and before activation.
  • Validation rules themselves are extended by G6

G6. Coverage

  • The validator covers every new contract member end to end.
  • A plugin missing or mis signing new hook is reported with the specific member name.
  • Rules are added for: loader/discoverer interfaces, manifest shape, B lifecycle hooks, C middleware models, D health result, F security scheme extensions.
  • Each rule reports the specific missing/invalid member (no silent passes).

Backward Compatibility

Plugins that do not declare load context continue to load via the existing mechanism isolation is applied by the new loader path for plugins that opt in via manifest Plugins that already pass the validator are unaffected. No contract members are changed by G6 only validation is added.

Validation

G4. Isolation

  • Each plugin loads into its own AssemblyLoadContext.
  • No type collisions occur between plugins that ship conflicting dependency versions.
  • Host types remain shared (not duplicated per plugin).
  • DevTokens still loads after the contract change.

G5. Integration

  • A non conforming plugin results in startup error (hard reject).
  • The validator runs automatically as part of loading.
  • DevTokens passes validation after the contract change.

G6. Coverage

  • The validator covers every new contract member end to end (full contract coverage).
  • A plugin missing or mis signing new hook is reported with the specific member name.
  • Rules exist for G1, G2, B, C, D, and F hooks.

Acceptance Criteria

  • Each plugin loads into its own AssemblyLoadContext.
  • No type collisions occur between plugins that ship conflicting dependency versions.
  • Host types remain shared (not duplicated per plugin).
  • A non conforming plugin results in startup error (or warning when configured).
  • The validator runs automatically as part of loading no separate manual step required for the host to be safe.
  • The validator covers every new contract member end to end (full contract coverage).
  • A plugin missing or mis signing new hook is reported with the specific member name.

Non Goals

  • G4: no isolation of host/framework assemblies. Unload/hot reload lifecycle is enabled by collectible contexts but the reload orchestration itself i later concern/
  • G5: no change to validator internals beyond being invoked by the loader. No new validation rules (those belong to G6).
  • G6: no new contract members are invented here
Pinned by rian-be

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1Core operationadditiveAdditive, non-breaking changearea/abstractionsAuthKit.Plugins.Abstractions contractcontractChanges the plugin contractsub-taskChild task of an epic

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions