Skip to content

[RFC #1] Plugin contract shape — IFalloutPlugin discovery & registration #97

Open

Description

@ChrisonSimtian

RFC #1 — Plugin contract shape

Self-RFC. Comment with agreement, disagreement, or alternatives. The proposal below is what we'll ship in v12 unless this discussion produces a better answer.

Context

v12 (milestone #7) ships Fallout.Plugin.Sdk 1.0 — the public abstractions package contributors compile plugins against. This RFC pins the contract: how a plugin declares itself and how the host discovers it. IFalloutPlugin's shape is the most consequential SDK decision — it can't break without a major SDK bump, and every plugin author touches it first.

Proposal

A plugin is a NuGet package referenced by the build project. No JSON manifest, no preprocessor directives, no separate plugin folder — same convention as Roslyn analyzers, MSBuild SDKs, source generators.

The entry point is a single assembly attribute pointing at a registration class:

[assembly: FalloutPlugin(typeof(TerraformPlugin))]

public sealed class TerraformPlugin : IFalloutPlugin
{
    public PluginApiVersion ApiVersion => PluginApiVersion.V1;

    public void Configure(IFalloutPluginBuilder builder)
    {
        builder.Services.AddSingleton<ITerraformWorkspaceLocator, DefaultTerraformWorkspaceLocator>();
        builder.AddToolWrapper<TerraformTasks>();
        builder.AddBuildMiddleware<TerraformWorkspaceDetectionMiddleware>();
        builder.AddHost<TerraformCloudHost>(detect: env => env.Has("TFC_RUN_ID"));
    }
}

At startup the orchestrator scans referenced assemblies for FalloutPluginAttribute, instantiates each IFalloutPlugin, calls Configure, and merges into the composition root.

Why this shape

  • Opt-in discovery via attribute — only assemblies declaring it pay any cost. No type-walking every referenced DLL.
  • DI as the registration vocabulary — plugins compose against IServiceCollection + a small IFalloutPluginBuilder. .NET devs already know this.
  • One entry point per plugin — single class, single method. Easy to find, read, test.
  • API-versioned — PluginApiVersion lets the host detect a too-new plugin and fail clearly, not at random later.

Alternatives rejected

  • JSON manifest (fallout-plugin.json) — more ceremony than [assembly: FalloutPlugin], no clearer signal, still needs reflection for design-time types.
  • Convention-based discovery (any IFalloutPlugin is registered) — forces a full assembly scan, risks surprise activations from transitive deps, harder to debug "why is this loading?".
  • #addin directives (Cake's model) — couples the plugin model to source-file pragmas; poor fit with .NET tooling (IntelliSense, refactoring, restore); wrong audience.

Questions for discussion

  1. Allow IFalloutPlugin to declare deps on other plugins (builder.RequirePlugin<DotNetPlugin>())? Default: no — discover capabilities via DI, not plugin-to-plugin references. Disagree?
  2. Give Configure a CancellationToken? Default: no — registration is fast and synchronous; async setup belongs in a middleware.
  3. Minimum SDK target framework? Default: net10.0 (matches global.json). Ship a netstandard2.0 flavour for older runtimes? Probably not — the build host itself needs net10.

Out of scope (separate RFCs)

How to engage

React or comment. Strong opinions backed by use cases are most useful — "this won't work because [my plugin needs X]" is gold; "what about a manifest file?" without a use case isn't.

Decision lands in

When this RFC locks (2026-08-31), its shape becomes the spec for:

Activity

  1. added
    RFCDesign discussion / RFC. Comment with feedback; consensus shapes the implementation.
    on May 21, 2026
  2. ChrisonSimtian commented on May 25, 2026

    @ChrisonSimtian
    CollaboratorAuthor

    Self-decision lock-in date: 2026-08-31

    This is a self-RFC (per the roadmap) — it ships an opinionated proposal and asks "tell me where I'm wrong." Currently zero comments.

    If no substantive counter-proposal lands by 2026-08-31, the proposed shape locks, gets handed to the implementing SDK issue (#147 — [FalloutPlugin] attribute + IFalloutPlugin), and this RFC closes as decided.

    The most useful counter-example shape is: "I'd want to build a plugin that does X, and the proposed contract breaks because of Y." Rough form is fine — that's the kind of comment that reshapes proposals.

  3. dennisdoomen commented on Oct 10, 2026

    @dennisdoomen
    Collaborator

    Do we have an example of something that would benefit from this? Defining a plug-in architecture is hard, and usually you don't do that until you have a particular request that you use to design the contract.

  4. ChrisonSimtian commented on Oct 13, 2026

    @ChrisonSimtian
    CollaboratorAuthor

    I was thinking shrinking the core down and throwing all the tooling etc into plugins, i.e. github actions.
    But not urgent, i reckon we focus on the statics cleanup and then look into this again from a different, cleaner lense

    -> on hold

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCDesign discussion / RFC. Comment with feedback; consensus shapes the implementation.target/vNextTargets the next calendar-version

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions