Files
git.stella-ops.org/src/JobEngine/StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/SchedulerPluginRegistry.cs
master 908619e739 feat(scheduler): plugin architecture + Doctor health check plugin
- Create ISchedulerJobPlugin abstraction with JobKind routing
- Add SchedulerPluginRegistry for plugin discovery and resolution
- Wrap existing scan logic as ScanJobPlugin (zero behavioral change)
- Extend Schedule model with JobKind (default "scan") and PluginConfig (jsonb)
- Add SQL migrations 007 (job_kind/plugin_config) and 008 (doctor_trends table)
- Implement DoctorJobPlugin replacing standalone doctor-scheduler service
- Add PostgresDoctorTrendRepository for persistent trend storage
- Register Doctor trend endpoints at /api/v1/scheduler/doctor/trends/*
- Seed 3 default Doctor schedules (daily full, hourly quick, weekly compliance)
- Comment out doctor-scheduler container in compose and services-matrix
- Update Doctor architecture docs and AGENTS.md with scheduling migration info

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:24:46 +03:00

51 lines
1.5 KiB
C#

using System.Collections.Concurrent;
namespace StellaOps.Scheduler.Plugin;
/// <summary>
/// Thread-safe in-memory registry for scheduler job plugins.
/// </summary>
public sealed class SchedulerPluginRegistry : ISchedulerPluginRegistry
{
private readonly ConcurrentDictionary<string, ISchedulerJobPlugin> _plugins = new(StringComparer.OrdinalIgnoreCase);
/// <inheritdoc />
public void Register(ISchedulerJobPlugin plugin)
{
ArgumentNullException.ThrowIfNull(plugin);
if (string.IsNullOrWhiteSpace(plugin.JobKind))
{
throw new ArgumentException("Plugin JobKind must not be null or whitespace.", nameof(plugin));
}
if (!_plugins.TryAdd(plugin.JobKind, plugin))
{
throw new InvalidOperationException(
$"A plugin with JobKind '{plugin.JobKind}' is already registered. " +
$"Existing: '{_plugins[plugin.JobKind].DisplayName}', " +
$"Attempted: '{plugin.DisplayName}'.");
}
}
/// <inheritdoc />
public ISchedulerJobPlugin? Resolve(string jobKind)
{
if (string.IsNullOrWhiteSpace(jobKind))
{
return null;
}
return _plugins.TryGetValue(jobKind, out var plugin) ? plugin : null;
}
/// <inheritdoc />
public IReadOnlyList<(string JobKind, string DisplayName)> ListRegistered()
{
return _plugins.Values
.OrderBy(p => p.JobKind, StringComparer.OrdinalIgnoreCase)
.Select(p => (p.JobKind, p.DisplayName))
.ToArray();
}
}