- 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>
51 lines
1.5 KiB
C#
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();
|
|
}
|
|
}
|