Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/architecture/interface-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ IPipelineContext
module-execution operations, while `IModuleHookContext` adds module lifecycle
information.

Pipeline global hooks, requirements, and run conditions use `IPipelineContext`.
Pipeline event handlers, requirements, and run conditions use `IPipelineContext`.
Module lifecycle hooks use `IModuleHookContext`.

## Capability interfaces
Expand All @@ -36,8 +36,8 @@ marker were removed. Each capability now has one public name.

## Extension points

- `IPipelineGlobalHooks`: pipeline start and end callbacks
- `IModuleEventReceiver`: module lifecycle callbacks
- `IPipelineEventHandler`: pipeline start and end callbacks
- `IModuleEventHandler`: module lifecycle callbacks
- `IPipelineRequirement`: startup requirement checks
- `IRunCondition`: reusable execution conditions
- `IPipelineValidator`: custom pipeline validation
Expand Down
18 changes: 9 additions & 9 deletions docs/architecture/interface-hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ public class BuildModule : Module<CommandResult>
}
```

## Pipeline hooks
## Pipeline event handlers

Global hooks receive `IPipelineContext`:
Pipeline handlers receive `IPipelineContext`:

```csharp
public class PipelineHooks : IPipelineGlobalHooks
public class PipelineEvents : IPipelineEventHandler
{
public Task OnPipelineStartAsync(IPipelineContext context)
{
Expand All @@ -86,18 +86,18 @@ public class PipelineHooks : IPipelineGlobalHooks
}
```

Global module event receivers receive `IModuleHookContext`:
Global module event handlers use the same lifecycle signatures as attribute handlers:

```csharp
public class ModuleEvents : IModuleEventReceiver
public class ModuleEvents : IModuleEventHandler
{
public Task OnModuleStartAsync(IModuleHookContext context)
{
context.Logger.LogInformation("Module starting");
return Task.CompletedTask;
}

public Task OnModuleEndAsync(IModuleHookContext context)
public Task OnModuleEndAsync(IModuleHookContext context, IModuleResult result)
{
context.Logger.LogInformation("Module finished");
return Task.CompletedTask;
Expand All @@ -108,7 +108,7 @@ public class ModuleEvents : IModuleEventReceiver
## Requirements and run conditions

Pipeline requirements and run conditions receive `IPipelineContext`, giving them the
same shared capability surface as global hooks:
same shared capability surface as global handlers:

```csharp
public class LinuxRequirement : IPipelineRequirement
Expand All @@ -126,7 +126,7 @@ public class LinuxRequirement : IPipelineRequirement
## Guidance

1. Use `IModuleContext` in modules.
2. Use `IPipelineContext` in global hooks, requirements, and run conditions.
3. Use `IModuleHookContext` in module event receivers and attribute handlers.
2. Use `IPipelineContext` in pipeline event handlers, requirements, and run conditions.
3. Use `IModuleHookContext` in global and attribute module event handlers.
4. Use domain properties to discover capabilities.
5. Do not depend on internal engine interfaces.
23 changes: 12 additions & 11 deletions docs/docs/architecture/module-execution-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ title: Module Execution Lifecycle
# Module execution lifecycle

A module combines execution policy, module-owned virtual hooks, opt-in attribute handlers,
and global event receivers.
and global event handlers.

## Execution phases

For a module that runs successfully, the phases are:

1. Dependencies become ready.
2. Global `IModuleEventReceiver.OnModuleReadyAsync` receivers run concurrently.
2. Global `IModuleEventHandler.OnModuleReadyAsync` handlers run sequentially by priority.
3. Attribute `IModuleReadyHandler` handlers run sequentially by priority.
4. Global `IModuleEventReceiver.OnModuleStartAsync` receivers run concurrently.
4. Global `IModuleEventHandler.OnModuleStartAsync` handlers run sequentially by priority.
5. Attribute `IModuleStartHandler` handlers run sequentially by priority.
6. The module skip condition is evaluated.
7. `Module<T>.OnBeforeExecuteAsync` runs once.
8. `Module<T>.ExecuteAsync` runs through timeout handling and the configured resilience shield, which may compose retries with other resilience strategies.
9. `Module<T>.OnAfterExecuteAsync` runs once.
10. Global `IModuleEventReceiver.OnModuleEndAsync` receivers run concurrently.
10. Global `IModuleEventHandler.OnModuleEndAsync` handlers run sequentially by priority.
11. Attribute `IModuleEndHandler` handlers run sequentially by priority.
12. The module result is published and dependants become eligible.

Expand All @@ -34,7 +34,7 @@ decision:

1. `Module<T>.OnSkippedAsync`
2. Attribute `IModuleSkippedHandler`
3. Global `IModuleEventReceiver.OnModuleSkippedAsync`
3. Global `IModuleEventHandler.OnModuleSkippedAsync`

`OnBeforeExecuteAsync`, `ExecuteAsync`, and `OnAfterExecuteAsync` do not run.

Expand All @@ -45,25 +45,26 @@ When module execution throws:
1. `Module<T>.OnFailedAsync`
2. `Module<T>.OnAfterExecuteAsync`, with a failed `ModuleResult<T>`
3. Attribute `IModuleFailureHandler`
4. Global `IModuleEventReceiver.OnModuleFailureAsync`
4. Global `IModuleEventHandler.OnModuleFailureAsync`

Retry attempts complete before this failure sequence. If the configured failure condition
ignores the failure, the resulting module status reflects that policy.

## Hook failures

- An exception from `OnBeforeExecuteAsync` prevents module execution. `OnFailedAsync` and the
failure event receivers are notified, but `OnAfterExecuteAsync` does not run.
failure event handlers are notified, but `OnAfterExecuteAsync` does not run.
- Exceptions from `OnFailedAsync`, `OnSkippedAsync`, and `OnAfterExecuteAsync` are logged and do
not replace the module outcome.
- Attribute handlers propagate by default. Set their `ContinueOnError` property to continue after
a handler failure.
- Exceptions from global event receivers propagate from the lifecycle event.
- Attribute and global handlers all run in ascending `Priority` order within their registration
family, even after a handler fails. `ContinueOnError` controls failure propagation: `false`
rethrows one recorded failure or aggregates multiple failures after dispatch; `true` suppresses
that handler's failure.

## Choosing an extension point

Use module virtual hooks when behavior is part of one module. Use attribute handlers when
behavior should be explicitly attached to selected module types. Use `IModuleEventReceiver`
behavior should be explicitly attached to selected module types. Use `IModuleEventHandler`
when one service must observe every module in the pipeline.

See [Hooks](../how-to/hooks.md) for implementation examples.
38 changes: 23 additions & 15 deletions docs/docs/how-to/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ title: Hooks
Module lifecycle behavior has three extension points:

1. Override the virtual lifecycle methods on `Module<T>` for behavior owned by one module.
2. Implement the attribute interfaces in `ModularPipelines.Attributes.Events` for reusable,
2. Implement the attribute interfaces in `ModularPipelines.Events` for reusable,
opt-in behavior attached to selected modules.
3. Implement `IModuleEventReceiver` for behavior that observes every module in a pipeline.
3. Implement `IModuleEventHandler` for behavior that observes every module in a pipeline.

`ModuleConfiguration` controls execution policy only; it does not contain lifecycle hooks.

Expand Down Expand Up @@ -99,22 +99,28 @@ public class BuildModule : Module<string>

Available interfaces are `IModuleReadyHandler`, `IModuleStartHandler`,
`IModuleEndHandler`, `IModuleFailureHandler`, and `IModuleSkippedHandler`.
Handlers can implement `IEventHandlerPriority`; lower values run first.
All handlers inherit `IEventHandler`. Set `Priority` to control order (lower values run
first), or `ContinueOnError` to log a handler failure and continue.

## Global module event receivers
Registration attributes implement `IModuleRegistrationHandler`. Also implement
`IPlanningSafeModuleRegistrationHandler` only for deterministic, idempotent handlers
without external side effects; those handlers may run while exporting a resolved
dependency graph.

Implement `IModuleEventReceiver` to observe every module, then register it once:
## Global module event handlers

Implement `IModuleEventHandler` to observe every module, then register it once:

```csharp
public sealed class ModuleMetricsReceiver : IModuleEventReceiver
public sealed class ModuleMetricsHandler : IModuleEventHandler
{
public Task OnModuleStartAsync(IModuleHookContext context)
{
context.Logger.LogInformation("{Module} started", context.ModuleName);
return Task.CompletedTask;
}

public Task OnModuleEndAsync(IModuleHookContext context)
public Task OnModuleEndAsync(IModuleHookContext context, IModuleResult result)
{
context.Logger.LogInformation(
"{Module} finished after {Elapsed}",
Expand All @@ -124,11 +130,11 @@ public sealed class ModuleMetricsReceiver : IModuleEventReceiver
}
}

builder.AddModuleEventReceiver<ModuleMetricsReceiver>();
builder.AddModuleEventHandler<ModuleMetricsHandler>();
```

All registered global receivers are invoked concurrently for each event. Attribute handlers
run sequentially in priority order.
Global and attribute handlers use the same callback signatures and shared error/priority
properties. Global handlers run sequentially in priority order for each event.

## Lifecycle ordering

Expand Down Expand Up @@ -158,16 +164,16 @@ For a skipped module, the completion portion is:
3. Global `OnModuleSkippedAsync`

If `OnBeforeExecuteAsync` throws, `ExecuteAsync` and `OnAfterExecuteAsync` do not run;
`OnFailedAsync` and the failure event receivers are still notified. Exceptions from
`OnFailedAsync` and the failure event handlers are still notified. Exceptions from
`OnAfterExecuteAsync`, `OnFailedAsync`, and `OnSkippedAsync` are logged without replacing
the module outcome.

## Pipeline hooks
## Pipeline event handlers

`IPipelineGlobalHooks` observes the pipeline as a whole rather than individual modules:
`IPipelineEventHandler` observes the pipeline as a whole rather than individual modules:

```csharp
public sealed class PipelineLoggingHooks : IPipelineGlobalHooks
public sealed class PipelineLoggingHandler : IPipelineEventHandler
{
public Task OnPipelineStartAsync(IPipelineContext context)
{
Expand All @@ -184,5 +190,7 @@ public sealed class PipelineLoggingHooks : IPipelineGlobalHooks
}
}

builder.AddPipelineGlobalHooks<PipelineLoggingHooks>();
builder.AddPipelineEventHandler<PipelineLoggingHandler>();
```

Pipeline handlers also inherit `IEventHandler` and run in priority order.
12 changes: 6 additions & 6 deletions docs/docs/how-to/pipeline-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,18 @@ builder
await builder.RunAsync();
```

## Hooks and Requirements
## Event Handlers and Requirements

Register global hooks and pipeline requirements:
Register event handlers and pipeline requirements:

```csharp
var builder = Pipeline.CreateBuilder(args);

// Global hooks (run before/after all modules)
builder.AddPipelineGlobalHooks<MyGlobalHooks>();
// Pipeline event handlers (run before/after all modules)
builder.AddPipelineEventHandler<MyPipelineEventHandler>();

// Module event receivers (observe every module)
builder.AddModuleEventReceiver<MyModuleEventReceiver>();
// Module event handlers (observe every module)
builder.AddModuleEventHandler<MyModuleEventHandler>();

// Requirements (validated before pipeline starts)
builder.AddRequirement<DotNetSdkRequirement>();
Expand Down
3 changes: 2 additions & 1 deletion src/ModularPipelines.GitHub/Extensions/GitHubExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using ModularPipelines.Attributes;
using ModularPipelines.Context;
using ModularPipelines.Engine;
using ModularPipelines.Events;
using ModularPipelines.GitHub.PipelineWriters;
using ModularPipelines.Interfaces;
using ModularPipelines.Modules;
Expand Down Expand Up @@ -38,7 +39,7 @@ public static IServiceCollection RegisterGitHubContext(this IServiceCollection s
services.TryAddScoped<IGitHub, GitHub>();
services.TryAddScoped<IGitHubEnvironmentVariables, GitHubEnvironmentVariables>();
services.TryAddSingleton<IGitHubRepositoryInfo, GitHubRepositoryInfo>();
services.AddSingleton<IPipelineGlobalHooks, GitHubMarkdownSummaryGenerator>();
services.AddSingleton<IPipelineEventHandler, GitHubMarkdownSummaryGenerator>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IRunReportEnricher, GitHubRunReportEnricher>());
services.AddGitHubHttpClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
using ModularPipelines.Context;
using ModularPipelines.Engine;
using ModularPipelines.Enums;
using ModularPipelines.Events;
using ModularPipelines.Interfaces;
using ModularPipelines.Logging;
using ModularPipelines.Models;

namespace ModularPipelines.GitHub;

internal class GitHubMarkdownSummaryGenerator : IPipelineGlobalHooks
internal class GitHubMarkdownSummaryGenerator : IPipelineEventHandler
{
private const long MaxFileSizeInBytes = 1 * 1024 * 1024; // 1MB

Expand Down
2 changes: 1 addition & 1 deletion src/ModularPipelines/AmbientModuleContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static class AmbientModuleContext
/// <item>During module execution (via the module runner)</item>
/// </list>
/// It returns null when code is executing outside of any module context,
/// such as during pipeline initialization or in global hooks.
/// such as during pipeline initialization or in pipeline event handlers.
/// </remarks>
public static Type? CurrentModuleType => ModuleLogger.CurrentModuleType.Value;

Expand Down
40 changes: 0 additions & 40 deletions src/ModularPipelines/Attributes/Events/IEventHandlerPriority.cs

This file was deleted.

25 changes: 0 additions & 25 deletions src/ModularPipelines/Attributes/Events/IModuleEndHandler.cs

This file was deleted.

26 changes: 0 additions & 26 deletions src/ModularPipelines/Attributes/Events/IModuleFailureHandler.cs

This file was deleted.

Loading
Loading