more audit work

This commit is contained in:
master
2026-01-08 10:21:51 +02:00
parent 43c02081ef
commit 51cf4bc16c
546 changed files with 36721 additions and 4003 deletions

View File

@@ -2,8 +2,6 @@
-- 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
-- ============================================================================
@@ -102,14 +100,14 @@ CREATE TABLE IF NOT EXISTS scheduler.jobs (
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)
CREATE INDEX IF NOT EXISTS idx_jobs_tenant_status ON scheduler.jobs(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_jobs_tenant_type ON scheduler.jobs(tenant_id, job_type);
CREATE INDEX IF NOT EXISTS 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)
CREATE INDEX IF NOT EXISTS 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);
CREATE INDEX IF NOT EXISTS idx_jobs_project ON scheduler.jobs(tenant_id, project_id);
CREATE INDEX IF NOT EXISTS idx_jobs_correlation ON scheduler.jobs(correlation_id);
-- Triggers table (cron-based job triggers)
CREATE TABLE IF NOT EXISTS scheduler.triggers (
@@ -134,9 +132,9 @@ CREATE TABLE IF NOT EXISTS scheduler.triggers (
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 INDEX IF NOT EXISTS idx_triggers_tenant_id ON scheduler.triggers(tenant_id);
CREATE INDEX IF NOT EXISTS idx_triggers_next_fire ON scheduler.triggers(enabled, next_fire_at) WHERE enabled = TRUE;
CREATE INDEX IF NOT EXISTS idx_triggers_job_type ON scheduler.triggers(tenant_id, job_type);
CREATE TRIGGER trg_triggers_updated_at
BEFORE UPDATE ON scheduler.triggers
@@ -157,9 +155,9 @@ CREATE TABLE IF NOT EXISTS scheduler.workers (
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);
CREATE INDEX IF NOT EXISTS idx_workers_status ON scheduler.workers(status);
CREATE INDEX IF NOT EXISTS idx_workers_heartbeat ON scheduler.workers(last_heartbeat_at);
CREATE INDEX IF NOT EXISTS idx_workers_tenant ON scheduler.workers(tenant_id);
COMMENT ON TABLE scheduler.workers IS 'Global worker registry. Not RLS-protected - workers serve all tenants.';
@@ -173,8 +171,8 @@ CREATE TABLE IF NOT EXISTS scheduler.locks (
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);
CREATE INDEX IF NOT EXISTS idx_locks_tenant ON scheduler.locks(tenant_id);
CREATE INDEX IF NOT EXISTS idx_locks_expires ON scheduler.locks(expires_at);
-- Job history
CREATE TABLE IF NOT EXISTS scheduler.job_history (
@@ -195,10 +193,10 @@ CREATE TABLE IF NOT EXISTS scheduler.job_history (
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);
CREATE INDEX IF NOT EXISTS idx_job_history_tenant ON scheduler.job_history(tenant_id);
CREATE INDEX IF NOT EXISTS idx_job_history_job_id ON scheduler.job_history(job_id);
CREATE INDEX IF NOT EXISTS idx_job_history_type ON scheduler.job_history(tenant_id, job_type);
CREATE INDEX IF NOT EXISTS idx_job_history_completed ON scheduler.job_history(tenant_id, completed_at);
-- Metrics table
CREATE TABLE IF NOT EXISTS scheduler.metrics (
@@ -219,7 +217,7 @@ CREATE TABLE IF NOT EXISTS scheduler.metrics (
UNIQUE(tenant_id, job_type, period_start)
);
CREATE INDEX idx_metrics_tenant_period ON scheduler.metrics(tenant_id, period_start);
CREATE INDEX IF NOT EXISTS idx_metrics_tenant_period ON scheduler.metrics(tenant_id, period_start);
-- ============================================================================
-- SECTION 5: Schedules and Runs
@@ -593,4 +591,3 @@ BEGIN
END
$$;
COMMIT;

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Dapper;
using Npgsql;
@@ -21,10 +22,11 @@ public sealed class GraphJobRepository : IGraphJobRepository
(id, tenant_id, type, status, payload, created_at, updated_at, correlation_id)
VALUES (@Id, @TenantId, @Type, @Status, @Payload, @CreatedAt, @UpdatedAt, @CorrelationId);";
var jobId = ParseJobId(job.Id, nameof(job.Id));
await using var conn = await _dataSource.OpenConnectionAsync(job.TenantId, cancellationToken).ConfigureAwait(false);
await conn.ExecuteAsync(sql, new
{
job.Id,
Id = jobId,
job.TenantId,
Type = (short)GraphJobQueryType.Build,
Status = (short)job.Status,
@@ -41,10 +43,11 @@ public sealed class GraphJobRepository : IGraphJobRepository
(id, tenant_id, type, status, payload, created_at, updated_at, correlation_id)
VALUES (@Id, @TenantId, @Type, @Status, @Payload, @CreatedAt, @UpdatedAt, @CorrelationId);";
var jobId = ParseJobId(job.Id, nameof(job.Id));
await using var conn = await _dataSource.OpenConnectionAsync(job.TenantId, cancellationToken).ConfigureAwait(false);
await conn.ExecuteAsync(sql, new
{
job.Id,
Id = jobId,
job.TenantId,
Type = (short)GraphJobQueryType.Overlay,
Status = (short)job.Status,
@@ -59,7 +62,8 @@ public sealed class GraphJobRepository : IGraphJobRepository
{
const string sql = "SELECT payload FROM scheduler.graph_jobs WHERE tenant_id=@TenantId AND id=@Id AND type=@Type LIMIT 1";
await using var conn = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false);
var payload = await conn.ExecuteScalarAsync<string?>(sql, new { TenantId = tenantId, Id = jobId, Type = (short)GraphJobQueryType.Build });
var parsedId = ParseJobId(jobId, nameof(jobId));
var payload = await conn.ExecuteScalarAsync<string?>(sql, new { TenantId = tenantId, Id = parsedId, Type = (short)GraphJobQueryType.Build });
return payload is null ? null : CanonicalJsonSerializer.Deserialize<GraphBuildJob>(payload);
}
@@ -67,7 +71,8 @@ public sealed class GraphJobRepository : IGraphJobRepository
{
const string sql = "SELECT payload FROM scheduler.graph_jobs WHERE tenant_id=@TenantId AND id=@Id AND type=@Type LIMIT 1";
await using var conn = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false);
var payload = await conn.ExecuteScalarAsync<string?>(sql, new { TenantId = tenantId, Id = jobId, Type = (short)GraphJobQueryType.Overlay });
var parsedId = ParseJobId(jobId, nameof(jobId));
var payload = await conn.ExecuteScalarAsync<string?>(sql, new { TenantId = tenantId, Id = parsedId, Type = (short)GraphJobQueryType.Overlay });
return payload is null ? null : CanonicalJsonSerializer.Deserialize<GraphOverlayJob>(payload);
}
@@ -173,11 +178,12 @@ public sealed class GraphJobRepository : IGraphJobRepository
SET status=@NewStatus, payload=@Payload, updated_at=NOW()
WHERE tenant_id=@TenantId AND id=@Id AND status=@ExpectedStatus AND type=@Type";
var jobId = ParseJobId(job.Id, nameof(job.Id));
await using var conn = await _dataSource.OpenConnectionAsync(job.TenantId, cancellationToken).ConfigureAwait(false);
var rows = await conn.ExecuteAsync(sql, new
{
job.TenantId,
job.Id,
Id = jobId,
ExpectedStatus = (short)expectedStatus,
NewStatus = (short)job.Status,
Type = (short)GraphJobQueryType.Build,
@@ -192,11 +198,12 @@ public sealed class GraphJobRepository : IGraphJobRepository
SET status=@NewStatus, payload=@Payload, updated_at=NOW()
WHERE tenant_id=@TenantId AND id=@Id AND status=@ExpectedStatus AND type=@Type";
var jobId = ParseJobId(job.Id, nameof(job.Id));
await using var conn = await _dataSource.OpenConnectionAsync(job.TenantId, cancellationToken).ConfigureAwait(false);
var rows = await conn.ExecuteAsync(sql, new
{
job.TenantId,
job.Id,
Id = jobId,
ExpectedStatus = (short)expectedStatus,
NewStatus = (short)job.Status,
Type = (short)GraphJobQueryType.Overlay,
@@ -204,6 +211,16 @@ public sealed class GraphJobRepository : IGraphJobRepository
});
return rows == 1;
}
private static Guid ParseJobId(string jobId, string paramName)
{
if (Guid.TryParse(jobId, out var parsed))
{
return parsed;
}
throw new ArgumentException("Graph job id must be a UUID.", paramName);
}
}
internal enum GraphJobQueryType : short

View File

@@ -36,4 +36,8 @@
<EmbeddedResource Include="Migrations\**\*.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="Migrations\_archived\**\*.sql" />
</ItemGroup>
</Project>