Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
|
||||
## Engineering Rules
|
||||
- Target `net10.0`; prefer latest C# preview permitted in repo.
|
||||
- Offline-first: no new external calls; use cached feeds (`/local-nugets`) and configurable endpoints.
|
||||
- Offline-first: no new external calls; use `.nuget/packages/` cache and configurable endpoints.
|
||||
- Determinism: stable ordering, UTC ISO-8601 timestamps, seeded randomness; avoid host-specific paths in outputs/events.
|
||||
- Observability: use structured logging; keep metric/label names consistent with published dashboards (`policy_simulation_*`, `graph_*`, `overlay_*`).
|
||||
- Security: tenant isolation on all queues/stores; avoid leaking PII/secrets in logs or metrics.
|
||||
|
||||
@@ -2,8 +2,8 @@ using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.WebService.Auth;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.FailureSignatures;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.GraphJobs;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.WebService;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.PolicyRuns;
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.PolicySimulations;
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ using StellaOps.Plugin.Hosting;
|
||||
using StellaOps.Scheduler.WebService.Hosting;
|
||||
using StellaOps.Scheduler.ImpactIndex;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Extensions;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.WebService;
|
||||
using StellaOps.Scheduler.WebService.Auth;
|
||||
using StellaOps.Scheduler.WebService.EventWebhooks;
|
||||
@@ -85,7 +86,7 @@ builder.Services.AddOptions<SchedulerCartographerOptions>()
|
||||
var storageSection = builder.Configuration.GetSection("Scheduler:Storage");
|
||||
if (storageSection.Exists())
|
||||
{
|
||||
builder.Services.AddSchedulerPostgresStorage(storageSection);
|
||||
builder.Services.AddSchedulerPersistence(storageSection);
|
||||
builder.Services.AddScoped<IGraphJobRepository, GraphJobRepository>();
|
||||
builder.Services.AddSingleton<IGraphJobStore, PostgresGraphJobStore>();
|
||||
builder.Services.AddScoped<IScheduleRepository, ScheduleRepository>();
|
||||
@@ -234,4 +235,5 @@ app.TryRefreshStellaRouterEndpoints(routerOptions);
|
||||
|
||||
app.Run();
|
||||
|
||||
public partial class Program;
|
||||
// Make Program class accessible to test projects using WebApplicationFactory
|
||||
public sealed partial class Program;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"StellaOps.Scheduler.WebService": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:62539;http://localhost:62541"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.Runs;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using StellaOps.Scheduler.ImpactIndex;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.WebService.Auth;
|
||||
using StellaOps.Scheduler.WebService.Schedules;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.Runs;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.Schedules;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.Schedules;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.WebService.Auth;
|
||||
|
||||
namespace StellaOps.Scheduler.WebService.Schedules;
|
||||
|
||||
@@ -5,19 +5,22 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="StellaOps.Scheduler.WebService.Tests" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||
<ProjectReference Include="../__Libraries/StellaOps.Scheduler.ImpactIndex/StellaOps.Scheduler.ImpactIndex.csproj" />
|
||||
<ProjectReference Include="../__Libraries/StellaOps.Scheduler.Queue/StellaOps.Scheduler.Queue.csproj" />
|
||||
<ProjectReference Include="../__Libraries/StellaOps.Scheduler.Storage.Postgres/StellaOps.Scheduler.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="../__Libraries/StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
|
||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||
<ProjectReference Include="../../Router/__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
|
||||
<ProjectReference Include="../../Router/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
|
||||
<PackageReference Include="StackExchange.Redis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Queue;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Extensions;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Worker.DependencyInjection;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
@@ -21,7 +22,7 @@ builder.Services.AddSchedulerQueues(builder.Configuration);
|
||||
var storageSection = builder.Configuration.GetSection("Scheduler:Storage");
|
||||
if (storageSection.Exists())
|
||||
{
|
||||
builder.Services.AddSchedulerPostgresStorage(storageSection);
|
||||
builder.Services.AddSchedulerPersistence(storageSection);
|
||||
}
|
||||
|
||||
builder.Services.AddSchedulerWorker(builder.Configuration.GetSection("Scheduler:Worker"));
|
||||
@@ -29,4 +30,5 @@ builder.Services.AddSchedulerWorker(builder.Configuration.GetSection("Scheduler:
|
||||
var host = builder.Build();
|
||||
await host.RunAsync();
|
||||
|
||||
public partial class Program;
|
||||
// Make Program class file-scoped to prevent it from being exposed to referencing assemblies
|
||||
file sealed partial class Program;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.Scheduler.Queue\StellaOps.Scheduler.Queue.csproj" />
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.Scheduler.Storage.Postgres\StellaOps.Scheduler.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.Scheduler.Persistence\StellaOps.Scheduler.Persistence.csproj" />
|
||||
<ProjectReference Include="..\__Libraries\StellaOps.Scheduler.Worker\StellaOps.Scheduler.Worker.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,8 @@ using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
using Scheduler.Backfill;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
var parsed = ParseArgs(args);
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.Storage.Postgres/StellaOps.Scheduler.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql" Version="9.0.2" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
Link="Fixtures\%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="RoaringBitmap" Version="0.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="RoaringBitmap" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StellaOps.Infrastructure.EfCore.Context;
|
||||
|
||||
namespace StellaOps.Scheduler.Persistence.EfCore.Context;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core DbContext for the Scheduler module.
|
||||
/// Placeholder for future EF Core scaffolding from PostgreSQL schema.
|
||||
/// </summary>
|
||||
public class SchedulerDbContext : StellaOpsDbContextBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new Scheduler DbContext.
|
||||
/// </summary>
|
||||
public SchedulerDbContext(DbContextOptions<SchedulerDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string SchemaName => "scheduler";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Entity configurations will be added after scaffolding
|
||||
// from the PostgreSQL database using:
|
||||
// dotnet ef dbcontext scaffold
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Infrastructure.Postgres;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres;
|
||||
namespace StellaOps.Scheduler.Persistence.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring Scheduler PostgreSQL storage services.
|
||||
/// Extension methods for configuring Scheduler persistence services.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
public static class SchedulerPersistenceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Scheduler PostgreSQL storage services.
|
||||
/// Adds Scheduler PostgreSQL persistence services using configuration section.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="sectionName">Configuration section name for PostgreSQL options.</param>
|
||||
/// <returns>Service collection for chaining.</returns>
|
||||
public static IServiceCollection AddSchedulerPostgresStorage(
|
||||
public static IServiceCollection AddSchedulerPersistence(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string sectionName = "Postgres:Scheduler")
|
||||
@@ -46,12 +46,12 @@ public static class ServiceCollectionExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Scheduler PostgreSQL storage services with explicit options.
|
||||
/// Adds Scheduler PostgreSQL persistence services with explicit options.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configureOptions">Options configuration action.</param>
|
||||
/// <returns>Service collection for chaining.</returns>
|
||||
public static IServiceCollection AddSchedulerPostgresStorage(
|
||||
public static IServiceCollection AddSchedulerPersistence(
|
||||
this IServiceCollection services,
|
||||
Action<PostgresOptions> configureOptions)
|
||||
{
|
||||
@@ -0,0 +1,596 @@
|
||||
-- Scheduler Schema: Consolidated Initial Schema
|
||||
-- Consolidated from migrations 001-012b (pre_1.0 archived)
|
||||
-- Creates the complete scheduler schema for jobs, triggers, workers, runs, and policies
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 1: Schema Creation
|
||||
-- ============================================================================
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS scheduler;
|
||||
CREATE SCHEMA IF NOT EXISTS scheduler_app;
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 2: Enum Types
|
||||
-- ============================================================================
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE scheduler.job_status AS ENUM (
|
||||
'pending', 'scheduled', 'leased', 'running',
|
||||
'succeeded', 'failed', 'canceled', 'timed_out'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN null; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE scheduler.graph_job_type AS ENUM ('build', 'overlay');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE scheduler.graph_job_status AS ENUM ('pending', 'running', 'completed', 'failed', 'canceled');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE scheduler.run_state AS ENUM ('planning','queued','running','completed','error','cancelled');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE scheduler.policy_run_status AS ENUM ('pending','submitted','retrying','failed','completed','cancelled');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 3: Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION scheduler.update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION scheduler_app.require_current_tenant()
|
||||
RETURNS TEXT
|
||||
LANGUAGE plpgsql STABLE SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_tenant TEXT;
|
||||
BEGIN
|
||||
v_tenant := current_setting('app.tenant_id', true);
|
||||
IF v_tenant IS NULL OR v_tenant = '' THEN
|
||||
RAISE EXCEPTION 'app.tenant_id session variable not set'
|
||||
USING HINT = 'Set via: SELECT set_config(''app.tenant_id'', ''<tenant>'', false)',
|
||||
ERRCODE = 'P0001';
|
||||
END IF;
|
||||
RETURN v_tenant;
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION scheduler_app.require_current_tenant() FROM PUBLIC;
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 4: Core Tables
|
||||
-- ============================================================================
|
||||
|
||||
-- Jobs table
|
||||
CREATE TABLE IF NOT EXISTS scheduler.jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
project_id TEXT,
|
||||
job_type TEXT NOT NULL,
|
||||
status scheduler.job_status NOT NULL DEFAULT 'pending',
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
payload JSONB NOT NULL DEFAULT '{}',
|
||||
payload_digest TEXT NOT NULL,
|
||||
idempotency_key TEXT NOT NULL,
|
||||
correlation_id TEXT,
|
||||
attempt INT NOT NULL DEFAULT 0,
|
||||
max_attempts INT NOT NULL DEFAULT 3,
|
||||
lease_id UUID,
|
||||
worker_id TEXT,
|
||||
lease_until TIMESTAMPTZ,
|
||||
not_before TIMESTAMPTZ,
|
||||
reason TEXT,
|
||||
result JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
scheduled_at TIMESTAMPTZ,
|
||||
leased_at TIMESTAMPTZ,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_by TEXT,
|
||||
UNIQUE(tenant_id, idempotency_key)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_jobs_tenant_status ON scheduler.jobs(tenant_id, status);
|
||||
CREATE INDEX idx_jobs_tenant_type ON scheduler.jobs(tenant_id, job_type);
|
||||
CREATE INDEX idx_jobs_scheduled ON scheduler.jobs(tenant_id, status, not_before, priority DESC, created_at)
|
||||
WHERE status = 'scheduled';
|
||||
CREATE INDEX idx_jobs_leased ON scheduler.jobs(tenant_id, status, lease_until)
|
||||
WHERE status = 'leased';
|
||||
CREATE INDEX idx_jobs_project ON scheduler.jobs(tenant_id, project_id);
|
||||
CREATE INDEX idx_jobs_correlation ON scheduler.jobs(correlation_id);
|
||||
|
||||
-- Triggers table (cron-based job triggers)
|
||||
CREATE TABLE IF NOT EXISTS scheduler.triggers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
job_type TEXT NOT NULL,
|
||||
job_payload JSONB NOT NULL DEFAULT '{}',
|
||||
cron_expression TEXT NOT NULL,
|
||||
timezone TEXT NOT NULL DEFAULT 'UTC',
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
next_fire_at TIMESTAMPTZ,
|
||||
last_fire_at TIMESTAMPTZ,
|
||||
last_job_id UUID REFERENCES scheduler.jobs(id),
|
||||
fire_count BIGINT NOT NULL DEFAULT 0,
|
||||
misfire_count INT NOT NULL DEFAULT 0,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_triggers_tenant_id ON scheduler.triggers(tenant_id);
|
||||
CREATE INDEX idx_triggers_next_fire ON scheduler.triggers(enabled, next_fire_at) WHERE enabled = TRUE;
|
||||
CREATE INDEX idx_triggers_job_type ON scheduler.triggers(tenant_id, job_type);
|
||||
|
||||
CREATE TRIGGER trg_triggers_updated_at
|
||||
BEFORE UPDATE ON scheduler.triggers
|
||||
FOR EACH ROW EXECUTE FUNCTION scheduler.update_updated_at();
|
||||
|
||||
-- Workers table (global, NOT RLS-protected)
|
||||
CREATE TABLE IF NOT EXISTS scheduler.workers (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT,
|
||||
hostname TEXT NOT NULL,
|
||||
process_id INT,
|
||||
job_types TEXT[] NOT NULL DEFAULT '{}',
|
||||
max_concurrent_jobs INT NOT NULL DEFAULT 1,
|
||||
current_jobs INT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'draining', 'stopped')),
|
||||
last_heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
metadata JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_workers_status ON scheduler.workers(status);
|
||||
CREATE INDEX idx_workers_heartbeat ON scheduler.workers(last_heartbeat_at);
|
||||
CREATE INDEX idx_workers_tenant ON scheduler.workers(tenant_id);
|
||||
|
||||
COMMENT ON TABLE scheduler.workers IS 'Global worker registry. Not RLS-protected - workers serve all tenants.';
|
||||
|
||||
-- Distributed locks
|
||||
CREATE TABLE IF NOT EXISTS scheduler.locks (
|
||||
lock_key TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
holder_id TEXT NOT NULL,
|
||||
acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_locks_tenant ON scheduler.locks(tenant_id);
|
||||
CREATE INDEX idx_locks_expires ON scheduler.locks(expires_at);
|
||||
|
||||
-- Job history
|
||||
CREATE TABLE IF NOT EXISTS scheduler.job_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
job_id UUID NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
project_id TEXT,
|
||||
job_type TEXT NOT NULL,
|
||||
status scheduler.job_status NOT NULL,
|
||||
attempt INT NOT NULL,
|
||||
payload_digest TEXT NOT NULL,
|
||||
result JSONB,
|
||||
reason TEXT,
|
||||
worker_id TEXT,
|
||||
duration_ms BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
completed_at TIMESTAMPTZ NOT NULL,
|
||||
archived_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_job_history_tenant ON scheduler.job_history(tenant_id);
|
||||
CREATE INDEX idx_job_history_job_id ON scheduler.job_history(job_id);
|
||||
CREATE INDEX idx_job_history_type ON scheduler.job_history(tenant_id, job_type);
|
||||
CREATE INDEX idx_job_history_completed ON scheduler.job_history(tenant_id, completed_at);
|
||||
|
||||
-- Metrics table
|
||||
CREATE TABLE IF NOT EXISTS scheduler.metrics (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
job_type TEXT NOT NULL,
|
||||
period_start TIMESTAMPTZ NOT NULL,
|
||||
period_end TIMESTAMPTZ NOT NULL,
|
||||
jobs_created BIGINT NOT NULL DEFAULT 0,
|
||||
jobs_completed BIGINT NOT NULL DEFAULT 0,
|
||||
jobs_failed BIGINT NOT NULL DEFAULT 0,
|
||||
jobs_timed_out BIGINT NOT NULL DEFAULT 0,
|
||||
avg_duration_ms BIGINT,
|
||||
p50_duration_ms BIGINT,
|
||||
p95_duration_ms BIGINT,
|
||||
p99_duration_ms BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(tenant_id, job_type, period_start)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_metrics_tenant_period ON scheduler.metrics(tenant_id, period_start);
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 5: Schedules and Runs
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.schedules (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
cron_expression TEXT,
|
||||
timezone TEXT NOT NULL DEFAULT 'UTC',
|
||||
mode TEXT NOT NULL CHECK (mode IN ('analysisonly', 'contentrefresh')),
|
||||
selection JSONB NOT NULL DEFAULT '{}',
|
||||
only_if JSONB NOT NULL DEFAULT '{}',
|
||||
notify JSONB NOT NULL DEFAULT '{}',
|
||||
limits JSONB NOT NULL DEFAULT '{}',
|
||||
subscribers TEXT[] NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_by TEXT NOT NULL,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
deleted_by TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_schedules_tenant ON scheduler.schedules(tenant_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_schedules_enabled ON scheduler.schedules(tenant_id, enabled) WHERE deleted_at IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_schedules_tenant_name_active ON scheduler.schedules(tenant_id, name) WHERE deleted_at IS NULL;
|
||||
|
||||
-- Runs table with generated columns for stats
|
||||
CREATE TABLE IF NOT EXISTS scheduler.runs (
|
||||
id TEXT NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
schedule_id TEXT,
|
||||
trigger JSONB NOT NULL,
|
||||
state scheduler.run_state NOT NULL,
|
||||
stats JSONB NOT NULL,
|
||||
reason JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
started_at TIMESTAMPTZ,
|
||||
finished_at TIMESTAMPTZ,
|
||||
error TEXT,
|
||||
deltas JSONB NOT NULL,
|
||||
retry_of TEXT,
|
||||
schema_version TEXT,
|
||||
finding_count INT GENERATED ALWAYS AS (NULLIF((stats->>'findingCount'), '')::int) STORED,
|
||||
critical_count INT GENERATED ALWAYS AS (NULLIF((stats->>'criticalCount'), '')::int) STORED,
|
||||
high_count INT GENERATED ALWAYS AS (NULLIF((stats->>'highCount'), '')::int) STORED,
|
||||
new_finding_count INT GENERATED ALWAYS AS (NULLIF((stats->>'newFindingCount'), '')::int) STORED,
|
||||
component_count INT GENERATED ALWAYS AS (NULLIF((stats->>'componentCount'), '')::int) STORED,
|
||||
PRIMARY KEY (tenant_id, id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_state ON scheduler.runs(state);
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_schedule ON scheduler.runs(tenant_id, schedule_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_created ON scheduler.runs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS ix_runs_with_findings ON scheduler.runs(tenant_id, created_at DESC) WHERE finding_count > 0;
|
||||
CREATE INDEX IF NOT EXISTS ix_runs_critical ON scheduler.runs(tenant_id, created_at DESC, critical_count) WHERE critical_count > 0;
|
||||
CREATE INDEX IF NOT EXISTS ix_runs_summary_cover ON scheduler.runs(tenant_id, state, created_at DESC) INCLUDE (finding_count, critical_count, high_count, new_finding_count);
|
||||
CREATE INDEX IF NOT EXISTS ix_runs_tenant_findings ON scheduler.runs(tenant_id, finding_count DESC, created_at DESC) WHERE state = 'completed';
|
||||
|
||||
-- Impact snapshots
|
||||
CREATE TABLE IF NOT EXISTS scheduler.impact_snapshots (
|
||||
snapshot_id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
run_id TEXT,
|
||||
impact JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_impact_snapshots_run ON scheduler.impact_snapshots(run_id);
|
||||
|
||||
-- Run summaries
|
||||
CREATE TABLE IF NOT EXISTS scheduler.run_summaries (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
schedule_id TEXT REFERENCES scheduler.schedules(id),
|
||||
period_start TIMESTAMPTZ NOT NULL,
|
||||
period_end TIMESTAMPTZ NOT NULL,
|
||||
total_runs INT NOT NULL DEFAULT 0,
|
||||
successful_runs INT NOT NULL DEFAULT 0,
|
||||
failed_runs INT NOT NULL DEFAULT 0,
|
||||
cancelled_runs INT NOT NULL DEFAULT 0,
|
||||
avg_duration_seconds NUMERIC(10,2),
|
||||
max_duration_seconds INT,
|
||||
min_duration_seconds INT,
|
||||
total_findings_detected INT NOT NULL DEFAULT 0,
|
||||
new_criticals INT NOT NULL DEFAULT 0,
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, schedule_id, period_start)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_run_summaries_tenant ON scheduler.run_summaries(tenant_id, period_start DESC);
|
||||
|
||||
-- Execution logs
|
||||
CREATE TABLE IF NOT EXISTS scheduler.execution_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
run_id TEXT NOT NULL,
|
||||
logged_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
level TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
logger TEXT,
|
||||
data JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_execution_logs_run ON scheduler.execution_logs(run_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 6: Graph Jobs (v2 schema)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.graph_jobs (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
type scheduler.graph_job_type NOT NULL,
|
||||
status scheduler.graph_job_status NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
correlation_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_graph_jobs_tenant_status ON scheduler.graph_jobs(tenant_id, status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_graph_jobs_tenant_type_status ON scheduler.graph_jobs(tenant_id, type, status, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.graph_job_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
job_id UUID NOT NULL REFERENCES scheduler.graph_jobs(id) ON DELETE CASCADE,
|
||||
tenant_id TEXT NOT NULL,
|
||||
status scheduler.graph_job_status NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_graph_job_events_job ON scheduler.graph_job_events(job_id, created_at DESC);
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 7: Policy Run Jobs
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.policy_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
policy_pack_id TEXT NOT NULL,
|
||||
policy_version INT,
|
||||
target_type TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('pending','queued','running','completed','failed','cancelled')),
|
||||
priority INT NOT NULL DEFAULT 100,
|
||||
run_id TEXT,
|
||||
requested_by TEXT,
|
||||
mode TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
inputs JSONB NOT NULL DEFAULT '{}',
|
||||
attempt_count INT NOT NULL DEFAULT 0,
|
||||
max_attempts INT NOT NULL DEFAULT 3,
|
||||
queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
available_at TIMESTAMPTZ,
|
||||
submitted_at TIMESTAMPTZ,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
cancellation_requested BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
cancellation_reason TEXT,
|
||||
cancelled_at TIMESTAMPTZ,
|
||||
last_attempt_at TIMESTAMPTZ,
|
||||
last_error TEXT,
|
||||
lease_owner TEXT,
|
||||
lease_expires_at TIMESTAMPTZ,
|
||||
correlation_id TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_jobs_tenant_status ON scheduler.policy_jobs(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_jobs_run ON scheduler.policy_jobs(run_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.policy_run_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
policy_id TEXT NOT NULL,
|
||||
policy_version INT,
|
||||
mode TEXT NOT NULL,
|
||||
priority INT NOT NULL,
|
||||
priority_rank INT NOT NULL,
|
||||
run_id TEXT,
|
||||
requested_by TEXT,
|
||||
correlation_id TEXT,
|
||||
metadata JSONB,
|
||||
inputs JSONB NOT NULL,
|
||||
queued_at TIMESTAMPTZ,
|
||||
status scheduler.policy_run_status NOT NULL,
|
||||
attempt_count INT NOT NULL,
|
||||
last_attempt_at TIMESTAMPTZ,
|
||||
last_error TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
available_at TIMESTAMPTZ NOT NULL,
|
||||
submitted_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
lease_owner TEXT,
|
||||
lease_expires_at TIMESTAMPTZ,
|
||||
cancellation_requested BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
cancellation_requested_at TIMESTAMPTZ,
|
||||
cancellation_reason TEXT,
|
||||
cancelled_at TIMESTAMPTZ,
|
||||
schema_version TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_run_jobs_tenant ON scheduler.policy_run_jobs(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_run_jobs_status ON scheduler.policy_run_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_run_jobs_run ON scheduler.policy_run_jobs(run_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_policy_run_jobs_policy ON scheduler.policy_run_jobs(tenant_id, policy_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 8: Partitioned Audit Table
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.audit (
|
||||
id BIGSERIAL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
user_id UUID,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT,
|
||||
old_value JSONB,
|
||||
new_value JSONB,
|
||||
correlation_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (id, created_at)
|
||||
) PARTITION BY RANGE (created_at);
|
||||
|
||||
-- Create partitions dynamically
|
||||
DO $$
|
||||
DECLARE
|
||||
v_start DATE;
|
||||
v_end DATE;
|
||||
v_partition_name TEXT;
|
||||
BEGIN
|
||||
v_start := date_trunc('month', NOW() - INTERVAL '6 months')::DATE;
|
||||
WHILE v_start <= date_trunc('month', NOW() + INTERVAL '3 months')::DATE LOOP
|
||||
v_end := (v_start + INTERVAL '1 month')::DATE;
|
||||
v_partition_name := 'audit_' || to_char(v_start, 'YYYY_MM');
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_class c
|
||||
JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE n.nspname = 'scheduler' AND c.relname = v_partition_name
|
||||
) THEN
|
||||
EXECUTE format(
|
||||
'CREATE TABLE scheduler.%I PARTITION OF scheduler.audit FOR VALUES FROM (%L) TO (%L)',
|
||||
v_partition_name, v_start, v_end
|
||||
);
|
||||
END IF;
|
||||
v_start := v_end;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scheduler.audit_default PARTITION OF scheduler.audit DEFAULT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_audit_tenant ON scheduler.audit(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_audit_resource ON scheduler.audit(resource_type, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_audit_correlation ON scheduler.audit(correlation_id) WHERE correlation_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS brin_audit_created ON scheduler.audit USING BRIN(created_at) WITH (pages_per_range = 128);
|
||||
|
||||
COMMENT ON TABLE scheduler.audit IS 'Audit log for scheduler operations. Partitioned monthly by created_at for retention management.';
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 9: Row-Level Security
|
||||
-- ============================================================================
|
||||
|
||||
-- scheduler.schedules
|
||||
ALTER TABLE scheduler.schedules ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.schedules FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY schedules_tenant_isolation ON scheduler.schedules FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.runs
|
||||
ALTER TABLE scheduler.runs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.runs FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY runs_tenant_isolation ON scheduler.runs FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.jobs
|
||||
ALTER TABLE scheduler.jobs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.jobs FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY jobs_tenant_isolation ON scheduler.jobs FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.triggers
|
||||
ALTER TABLE scheduler.triggers ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.triggers FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY triggers_tenant_isolation ON scheduler.triggers FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.graph_jobs
|
||||
ALTER TABLE scheduler.graph_jobs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.graph_jobs FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY graph_jobs_tenant_isolation ON scheduler.graph_jobs FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.policy_jobs
|
||||
ALTER TABLE scheduler.policy_jobs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.policy_jobs FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY policy_jobs_tenant_isolation ON scheduler.policy_jobs FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.locks
|
||||
ALTER TABLE scheduler.locks ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.locks FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY locks_tenant_isolation ON scheduler.locks FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.impact_snapshots
|
||||
ALTER TABLE scheduler.impact_snapshots ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.impact_snapshots FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY impact_snapshots_tenant_isolation ON scheduler.impact_snapshots FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.run_summaries
|
||||
ALTER TABLE scheduler.run_summaries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.run_summaries FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY run_summaries_tenant_isolation ON scheduler.run_summaries FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.audit
|
||||
ALTER TABLE scheduler.audit ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.audit FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY audit_tenant_isolation ON scheduler.audit FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.job_history
|
||||
ALTER TABLE scheduler.job_history ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.job_history FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY job_history_tenant_isolation ON scheduler.job_history FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.metrics
|
||||
ALTER TABLE scheduler.metrics ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.metrics FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY metrics_tenant_isolation ON scheduler.metrics FOR ALL
|
||||
USING (tenant_id = scheduler_app.require_current_tenant())
|
||||
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
|
||||
|
||||
-- scheduler.execution_logs inherits from runs
|
||||
ALTER TABLE scheduler.execution_logs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE scheduler.execution_logs FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY execution_logs_tenant_isolation ON scheduler.execution_logs FOR ALL
|
||||
USING (
|
||||
run_id IN (SELECT id FROM scheduler.runs WHERE tenant_id = scheduler_app.require_current_tenant())
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- SECTION 10: Admin Bypass Role
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'scheduler_admin') THEN
|
||||
CREATE ROLE scheduler_admin WITH NOLOGIN BYPASSRLS;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres;
|
||||
|
||||
internal static class CanonicalJsonSerializer
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Scope type for failure signatures.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Job status values matching the PostgreSQL enum.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a job history entity in the scheduler schema.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a distributed lock entity in the scheduler schema.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a metrics entity in the scheduler schema.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a trigger entity in the scheduler schema.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Worker status values.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for distributed lock operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for failure signature operations.
|
||||
@@ -4,7 +4,7 @@ using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres;
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class GraphJobRepository : IGraphJobRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for distributed lock operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for failure signature operations.
|
||||
@@ -3,7 +3,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public interface IGraphJobRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public interface IImpactSnapshotRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for job history operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for job operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for metrics operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public interface IPolicyRunJobRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public interface IRunRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public interface IScheduleRepository
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for trigger operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for worker operations.
|
||||
@@ -3,7 +3,7 @@ using Dapper;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class ImpactSnapshotRepository : IImpactSnapshotRepository
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for job history operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for job operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for metrics operations.
|
||||
@@ -4,7 +4,7 @@ using Dapper;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class PolicyRunJobRepository : IPolicyRunJobRepository
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class RunQueryOptions
|
||||
{
|
||||
@@ -5,7 +5,7 @@ using StellaOps.Infrastructure.Postgres.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class RunRepository : IRunRepository
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class RunSummaryService : IRunSummaryService
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class ScheduleQueryOptions
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using Dapper;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
public sealed class ScheduleRepository : IScheduleRepository
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for trigger operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for worker operations.
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL data source for the Scheduler module.
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<RootNamespace>StellaOps.Scheduler.Persistence</RootNamespace>
|
||||
<AssemblyName>StellaOps.Scheduler.Persistence</AssemblyName>
|
||||
<Description>Consolidated persistence layer for StellaOps Scheduler module (EF Core + Raw SQL)</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Scheduler.Models\StellaOps.Scheduler.Models.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.EfCore\StellaOps.Infrastructure.EfCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Embed SQL migrations as resources -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Migrations\**\*.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,15 +5,15 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="NATS.Client.Core" Version="2.0.0" />
|
||||
<PackageReference Include="NATS.Client.JetStream" Version="2.0.0" />
|
||||
<PackageReference Include="StackExchange.Redis" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" />
|
||||
<PackageReference Include="NATS.Client.Core" />
|
||||
<PackageReference Include="NATS.Client.JetStream" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Scheduler.Models\StellaOps.Scheduler.Models.csproj" />
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<RootNamespace>StellaOps.Scheduler.Storage.Postgres</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Migrations\**\*.sql" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scheduler.Models\StellaOps.Scheduler.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.24" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -12,7 +12,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Execution;
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Queue;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Events;
|
||||
using StellaOps.Scheduler.Worker.Observability;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Graph;
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Graph.Cartographer;
|
||||
using StellaOps.Scheduler.Worker.Graph.Scheduler;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Graph;
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Graph.Cartographer;
|
||||
using StellaOps.Scheduler.Worker.Graph.Scheduler;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
@@ -2,8 +2,8 @@ using System.Text.Json;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Indexing;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Planning;
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Queue;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
using StellaOps.Scheduler.Worker.Observability;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Policy;
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Observability;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StellaOps.Scheduler.ImpactIndex/StellaOps.Scheduler.ImpactIndex.csproj" />
|
||||
<ProjectReference Include="../StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||
<ProjectReference Include="../StellaOps.Scheduler.Storage.Postgres/StellaOps.Scheduler.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="../StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj" />
|
||||
<ProjectReference Include="../StellaOps.Scheduler.Queue/StellaOps.Scheduler.Queue.csproj" />
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Models/StellaOps.Notify.Models.csproj" />
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Queue/StellaOps.Notify.Queue.csproj" />
|
||||
<ProjectReference Include="../../../Scanner/__Libraries/StellaOps.Scanner.Surface.Env/StellaOps.Scanner.Surface.Env.csproj" />
|
||||
<ProjectReference Include="../../../Scanner/__Libraries/StellaOps.Scanner.Surface.FS/StellaOps.Scanner.Surface.FS.csproj" />
|
||||
<PackageReference Include="Cronos" Version="0.9.0" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Cronos" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,18 +7,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Update="xunit" Version="2.9.2" />
|
||||
<PackageReference Update="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Tools/Scheduler.Backfill/Scheduler.Backfill.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -111,7 +111,6 @@ public sealed class FixtureImpactIndexTests
|
||||
});
|
||||
using var _ = loggerFactory;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var result = await impactIndex.ResolveAllAsync(selector, usageOnly: false);
|
||||
|
||||
result.Images.Should().HaveCount(6);
|
||||
|
||||
@@ -5,20 +5,12 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
<RestoreIgnoreFailedSources>true</RestoreIgnoreFailedSources>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.ImpactIndex/StellaOps.Scheduler.ImpactIndex.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||
|
||||
@@ -7,6 +7,17 @@ namespace StellaOps.Scheduler.Models.Tests;
|
||||
|
||||
public sealed class PolicyRunModelsTests
|
||||
{
|
||||
private const string succeeded = "succeeded";
|
||||
private const string failed = "failed";
|
||||
private const string timeout = "timeout";
|
||||
private const string job = "prj-test-1";
|
||||
private const string tenant = "tenant-alpha";
|
||||
private const string policy = "P-1";
|
||||
private const string run = "run:test";
|
||||
private const string tester = "tester@example.org";
|
||||
private const string corr = "corr-001";
|
||||
private const string error = "policy_error";
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PolicyRunInputs_NormalizesEnvironmentKeys()
|
||||
|
||||
@@ -386,8 +386,9 @@ public sealed class BackfillRangePropertyTests
|
||||
return Array.Empty<DateTimeOffset>();
|
||||
}
|
||||
|
||||
// Validate cron expression
|
||||
Validation.EnsureCronExpression(cronExpression, nameof(cronExpression));
|
||||
// Validate cron expression (inline - Validation is internal)
|
||||
if (string.IsNullOrWhiteSpace(cronExpression))
|
||||
throw new ArgumentException("Cron expression cannot be null or empty", nameof(cronExpression));
|
||||
|
||||
var jobs = new List<DateTimeOffset>();
|
||||
var parts = cronExpression.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@@ -407,8 +407,9 @@ public sealed class CronNextRunPropertyTests
|
||||
DateTimeOffset referenceTime,
|
||||
TimeZoneInfo timezone)
|
||||
{
|
||||
// Validate cron expression (basic check)
|
||||
Validation.EnsureCronExpression(cronExpression, nameof(cronExpression));
|
||||
// Validate cron expression (inline - Validation is internal)
|
||||
if (string.IsNullOrWhiteSpace(cronExpression))
|
||||
throw new ArgumentException("Cron expression cannot be null or empty", nameof(cronExpression));
|
||||
|
||||
// Convert reference time to local timezone
|
||||
var localTime = TimeZoneInfo.ConvertTime(referenceTime, timezone);
|
||||
@@ -432,7 +433,7 @@ public sealed class CronNextRunPropertyTests
|
||||
{
|
||||
if (MatchesCron(parts, candidate))
|
||||
{
|
||||
return TimeZoneInfo.ConvertTime(candidate, timezone, TimeZoneInfo.Utc);
|
||||
return TimeZoneInfo.ConvertTime(candidate, TimeZoneInfo.Utc);
|
||||
}
|
||||
candidate = candidate.AddMinutes(1);
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -73,10 +73,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: baseDelayMs,
|
||||
maxDelayMs: 120000,
|
||||
multiplier: multiplier);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: baseDelayMs,
|
||||
MaxDelayMs: 120000,
|
||||
Multiplier: multiplier);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -98,10 +98,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 120000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 120000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -130,10 +130,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
// Arrange
|
||||
var baseDelayMs = 1500;
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 3,
|
||||
baseDelayMs: baseDelayMs,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 3,
|
||||
BaseDelayMs: baseDelayMs,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -154,10 +154,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
// Arrange
|
||||
var maxDelayMs = 5000;
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 10,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: maxDelayMs,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 10,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: maxDelayMs,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -176,10 +176,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange - will hit max quickly
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 8,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 4000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 8,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 4000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -206,10 +206,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: maxRetries,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: maxRetries,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -225,10 +225,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 0,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 0,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -248,11 +248,11 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0,
|
||||
jitterFactor: 0.1);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0,
|
||||
JitterFactor: 0.1);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
var seed = 42;
|
||||
@@ -281,11 +281,11 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0,
|
||||
jitterFactor: 0.2);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0,
|
||||
JitterFactor: 0.2);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -307,11 +307,11 @@ public sealed class RetryBackoffPropertyTests
|
||||
// Arrange
|
||||
var jitterFactor = 0.2; // ±20%
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0,
|
||||
jitterFactor: jitterFactor);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0,
|
||||
JitterFactor: jitterFactor);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -367,10 +367,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
var fakeClock = new FakeClock(start);
|
||||
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 3,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 3,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
// Act - compute actual retry times
|
||||
var retryTimes = new List<DateTimeOffset>();
|
||||
@@ -400,10 +400,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 10,
|
||||
maxDelayMs: 1000,
|
||||
multiplier: 2.0);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 10,
|
||||
MaxDelayMs: 1000,
|
||||
Multiplier: 2.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -422,10 +422,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 100,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 10.0);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 100,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 10.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
@@ -444,10 +444,10 @@ public sealed class RetryBackoffPropertyTests
|
||||
{
|
||||
// Arrange
|
||||
var policy = new RetryPolicy(
|
||||
maxRetries: 5,
|
||||
baseDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
multiplier: 1.0);
|
||||
MaxRetries: 5,
|
||||
BaseDelayMs: 1000,
|
||||
MaxDelayMs: 60000,
|
||||
Multiplier: 1.0);
|
||||
|
||||
var fakeClock = new FakeClock(new DateTimeOffset(2025, 6, 15, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -250,7 +250,6 @@ public sealed class SamplePayloadTests
|
||||
private static string NormalizeJson(string json)
|
||||
{
|
||||
using var document = JsonDocument.Parse(json);
|
||||
using StellaOps.TestKit;
|
||||
return JsonSerializer.Serialize(document.RootElement, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scheduler.Models;
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ public sealed class ScheduleSerializationTests
|
||||
Assert.Equal(jsonA, jsonB);
|
||||
|
||||
using var doc = JsonDocument.Parse(jsonA);
|
||||
using StellaOps.TestKit;
|
||||
var root = doc.RootElement;
|
||||
Assert.Equal(SchedulerSchemaVersions.Schedule, root.GetProperty("schemaVersion").GetString());
|
||||
Assert.Equal("analysis-only", root.GetProperty("mode").GetString());
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\docs\events\samples\scheduler.rescan.delta@1.sample.json">
|
||||
<None Include="..\..\..\..\docs\events\samples\scheduler.rescan.delta@1.sample.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.abstractions" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,11 +1,11 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
[Collection(SchedulerPostgresCollection.Name)]
|
||||
public sealed class DistributedLockRepositoryTests : IAsyncLifetime
|
||||
@@ -6,12 +6,12 @@ using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
[Collection(SchedulerPostgresCollection.Name)]
|
||||
public sealed class GraphJobRepositoryTests : IAsyncLifetime
|
||||
@@ -8,12 +8,12 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotency tests for Scheduler job storage operations.
|
||||
@@ -13,7 +13,7 @@ using StellaOps.TestKit;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Migration tests for Scheduler.Storage.
|
||||
@@ -8,14 +8,14 @@
|
||||
using System.Reflection;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Testing;
|
||||
using StellaOps.Scheduler.Storage.Postgres;
|
||||
using StellaOps.Scheduler.Persistence.Postgres;
|
||||
using Xunit;
|
||||
|
||||
// Type aliases to disambiguate TestKit and Infrastructure.Postgres.Testing fixtures
|
||||
using TestKitPostgresFixture = StellaOps.TestKit.Fixtures.PostgresFixture;
|
||||
using TestKitPostgresIsolationMode = StellaOps.TestKit.Fixtures.PostgresIsolationMode;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL integration test fixture for the Scheduler module.
|
||||
@@ -91,16 +91,16 @@ public sealed class SchedulerTestKitPostgresFixture : IAsyncLifetime
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_fixture = new TestKitPostgresFixture(TestKitPostgresIsolationMode.Truncation);
|
||||
_fixture = new TestKitPostgresFixture { IsolationMode = TestKitPostgresIsolationMode.Truncation };
|
||||
await _fixture.InitializeAsync();
|
||||
await _fixture.ApplyMigrationsFromAssemblyAsync(MigrationAssembly);
|
||||
await _fixture.ApplyMigrationsFromAssemblyAsync(MigrationAssembly, "scheduler");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => _fixture.DisposeAsync();
|
||||
|
||||
public async Task TruncateAllTablesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _fixture.TruncateAllTablesAsync(cancellationToken);
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
|
||||
// Scheduler migrations create the canonical `scheduler.*` schema explicitly
|
||||
await using var connection = new NpgsqlConnection(ConnectionString);
|
||||
@@ -8,12 +8,12 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Query determinism tests for Scheduler storage operations.
|
||||
@@ -123,7 +123,7 @@ public sealed class SchedulerQueryDeterminismTests : IAsyncLifetime
|
||||
// Verify created_at ordering for same priority
|
||||
for (int i = 0; i < results1.Count - 1; i++)
|
||||
{
|
||||
results1[i].CreatedAt.Should().BeLessOrEqualTo(results1[i + 1].CreatedAt);
|
||||
results1[i].CreatedAt.Should().BeOnOrBefore(results1[i + 1].CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>StellaOps.Scheduler.Persistence.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Scheduler.Persistence\StellaOps.Scheduler.Persistence.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Tests\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,12 +1,12 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Models;
|
||||
using StellaOps.Scheduler.Storage.Postgres.Repositories;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Models;
|
||||
using StellaOps.Scheduler.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
|
||||
namespace StellaOps.Scheduler.Persistence.Postgres.Tests;
|
||||
|
||||
[Collection(SchedulerPostgresCollection.Name)]
|
||||
public sealed class TriggerRepositoryTests : IAsyncLifetime
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user