feat: Add DigestUpsertRequest and LockEntity models
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

- Introduced DigestUpsertRequest for handling digest upsert requests with properties like ChannelId, Recipient, DigestKey, Events, and CollectUntil.
- Created LockEntity to represent a lightweight distributed lock entry with properties such as Id, TenantId, Resource, Owner, ExpiresAt, and CreatedAt.

feat: Implement ILockRepository interface and LockRepository class

- Defined ILockRepository interface with methods for acquiring and releasing locks.
- Implemented LockRepository class with methods to try acquiring a lock and releasing it, using SQL for upsert operations.

feat: Add SurfaceManifestPointer record for manifest pointers

- Introduced SurfaceManifestPointer to represent a minimal pointer to a Surface.FS manifest associated with an image digest.

feat: Create PolicySimulationInputLock and related validation logic

- Added PolicySimulationInputLock record to describe policy simulation inputs and expected digests.
- Implemented validation logic for policy simulation inputs, including checks for digest drift and shadow mode requirements.

test: Add unit tests for ReplayVerificationService and ReplayVerifier

- Created ReplayVerificationServiceTests to validate the behavior of the ReplayVerificationService under various scenarios.
- Developed ReplayVerifierTests to ensure the correctness of the ReplayVerifier logic.

test: Implement PolicySimulationInputLockValidatorTests

- Added tests for PolicySimulationInputLockValidator to verify the validation logic against expected inputs and conditions.

chore: Add cosign key example and signing scripts

- Included a placeholder cosign key example for development purposes.
- Added a script for signing Signals artifacts using cosign with support for both v2 and v3.

chore: Create script for uploading evidence to the evidence locker

- Developed a script to upload evidence to the evidence locker, ensuring required environment variables are set.
This commit is contained in:
StellaOps Bot
2025-12-03 07:51:50 +02:00
parent 37cba83708
commit e923880694
171 changed files with 6567 additions and 2952 deletions

View File

@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scheduler.Models;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scheduler.Models;
using StellaOps.Scheduler.Worker.Planning;
namespace StellaOps.Scheduler.Queue;
@@ -49,10 +50,11 @@ public sealed class PlannerQueueMessage
public string? ScheduleId => Run.ScheduleId;
}
public sealed class RunnerSegmentQueueMessage
{
private readonly ReadOnlyCollection<string> _imageDigests;
private readonly IReadOnlyDictionary<string, string> _attributes;
public sealed class RunnerSegmentQueueMessage
{
private readonly ReadOnlyCollection<string> _imageDigests;
private readonly IReadOnlyDictionary<string, string> _attributes;
private readonly IReadOnlyDictionary<string, SurfaceManifestPointer> _surfaceManifests;
[JsonConstructor]
public RunnerSegmentQueueMessage(
@@ -60,12 +62,13 @@ public sealed class RunnerSegmentQueueMessage
string runId,
string tenantId,
IReadOnlyList<string> imageDigests,
string? scheduleId = null,
int? ratePerSecond = null,
bool usageOnly = true,
IReadOnlyDictionary<string, string>? attributes = null,
string? correlationId = null)
{
string? scheduleId = null,
int? ratePerSecond = null,
bool usageOnly = true,
IReadOnlyDictionary<string, string>? attributes = null,
string? correlationId = null,
IReadOnlyDictionary<string, SurfaceManifestPointer>? surfaceManifests = null)
{
if (string.IsNullOrWhiteSpace(segmentId))
{
throw new ArgumentException("Segment identifier must be provided.", nameof(segmentId));
@@ -86,14 +89,17 @@ public sealed class RunnerSegmentQueueMessage
TenantId = tenantId;
ScheduleId = string.IsNullOrWhiteSpace(scheduleId) ? null : scheduleId;
RatePerSecond = ratePerSecond;
UsageOnly = usageOnly;
CorrelationId = string.IsNullOrWhiteSpace(correlationId) ? null : correlationId;
_imageDigests = new ReadOnlyCollection<string>(NormalizeDigests(imageDigests));
_attributes = attributes is null
? EmptyReadOnlyDictionary<string, string>.Instance
: new ReadOnlyDictionary<string, string>(new Dictionary<string, string>(attributes, StringComparer.Ordinal));
}
UsageOnly = usageOnly;
CorrelationId = string.IsNullOrWhiteSpace(correlationId) ? null : correlationId;
_imageDigests = new ReadOnlyCollection<string>(NormalizeDigests(imageDigests));
_attributes = attributes is null
? EmptyReadOnlyDictionary<string, string>.Instance
: new ReadOnlyDictionary<string, string>(new Dictionary<string, string>(attributes, StringComparer.Ordinal));
_surfaceManifests = surfaceManifests is null
? EmptyReadOnlyDictionary<string, SurfaceManifestPointer>.Instance
: new ReadOnlyDictionary<string, SurfaceManifestPointer>(new Dictionary<string, SurfaceManifestPointer>(surfaceManifests, StringComparer.Ordinal));
}
public string SegmentId { get; }
@@ -111,7 +117,10 @@ public sealed class RunnerSegmentQueueMessage
public IReadOnlyList<string> ImageDigests => _imageDigests;
public IReadOnlyDictionary<string, string> Attributes => _attributes;
public IReadOnlyDictionary<string, string> Attributes => _attributes;
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public IReadOnlyDictionary<string, SurfaceManifestPointer> SurfaceManifests => _surfaceManifests;
public string IdempotencyKey => SegmentId;

View File

@@ -1,17 +1,19 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Queue;
using StellaOps.Scheduler.Worker.Events;
using StellaOps.Scheduler.Worker.Execution;
using StellaOps.Scheduler.Worker.Options;
using StellaOps.Scheduler.Worker.Observability;
using StellaOps.Scheduler.Worker.Planning;
using StellaOps.Scheduler.Worker.Policy;
using StellaOps.Scheduler.Worker.Graph;
using StellaOps.Scheduler.Worker.Graph.Cartographer;
using StellaOps.Scheduler.Worker.Graph.Scheduler;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Queue;
using StellaOps.Scheduler.Worker.Events;
using StellaOps.Scheduler.Worker.Execution;
using StellaOps.Scheduler.Worker.Options;
using StellaOps.Scheduler.Worker.Observability;
using StellaOps.Scheduler.Worker.Planning;
using StellaOps.Scheduler.Worker.Policy;
using StellaOps.Scheduler.Worker.Graph;
using StellaOps.Scheduler.Worker.Graph.Cartographer;
using StellaOps.Scheduler.Worker.Graph.Scheduler;
using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.FS;
namespace StellaOps.Scheduler.Worker.DependencyInjection;
@@ -30,10 +32,10 @@ public static class SchedulerWorkerServiceCollectionExtensions
services.AddSingleton(TimeProvider.System);
services.AddSingleton<SchedulerWorkerMetrics>();
services.AddSingleton<IImpactTargetingService, ImpactTargetingService>();
services.AddSingleton<IImpactShardPlanner, ImpactShardPlanner>();
services.AddSingleton<IPlannerQueueDispatchService, PlannerQueueDispatchService>();
services.AddSingleton<PlannerExecutionService>();
services.AddSingleton<IRunnerExecutionService, RunnerExecutionService>();
services.AddSingleton<IImpactShardPlanner, ImpactShardPlanner>();
services.AddSingleton<IPlannerQueueDispatchService, PlannerQueueDispatchService>();
services.AddSingleton<PlannerExecutionService>();
services.AddSingleton<IRunnerExecutionService, RunnerExecutionService>();
services.AddSingleton<IPolicyRunTargetingService, PolicyRunTargetingService>();
services.AddSingleton<PolicyRunExecutionService>();
services.AddSingleton<GraphBuildExecutionService>();
@@ -80,10 +82,10 @@ public static class SchedulerWorkerServiceCollectionExtensions
client.BaseAddress = baseAddress;
}
});
services.AddHttpClient<IGraphJobCompletionClient, HttpGraphJobCompletionClient>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<SchedulerWorkerOptions>>().Value.Graph;
client.Timeout = options.CartographerTimeout;
services.AddHttpClient<IGraphJobCompletionClient, HttpGraphJobCompletionClient>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<SchedulerWorkerOptions>>().Value.Graph;
client.Timeout = options.CartographerTimeout;
if (options.SchedulerApi.BaseAddress is { } baseAddress)
{
@@ -91,13 +93,17 @@ public static class SchedulerWorkerServiceCollectionExtensions
}
});
services.AddHostedService<PlannerBackgroundService>();
services.AddHostedService<PlannerQueueDispatcherBackgroundService>();
services.AddHostedService<RunnerBackgroundService>();
services.AddHostedService<PolicyRunDispatchBackgroundService>();
services.AddHostedService<GraphBuildBackgroundService>();
services.AddHostedService<GraphOverlayBackgroundService>();
return services;
}
}
services.AddHostedService<PlannerBackgroundService>();
services.AddHostedService<PlannerQueueDispatcherBackgroundService>();
services.AddHostedService<RunnerBackgroundService>();
services.AddHostedService<PolicyRunDispatchBackgroundService>();
services.AddHostedService<GraphBuildBackgroundService>();
services.AddHostedService<GraphOverlayBackgroundService>();
services.AddSurfaceEnvironment(options => { options.ComponentName = "Scheduler.Worker"; });
services.AddSurfaceFileCache();
services.AddSurfaceManifestStore();
return services;
}
}

View File

@@ -19,6 +19,8 @@ public sealed class SchedulerWorkerMetrics : IDisposable
private readonly Counter<long> _runnerDeltaHighTotal;
private readonly Counter<long> _runnerDeltaFindingsTotal;
private readonly Counter<long> _runnerKevHitsTotal;
private readonly Counter<long> _surfaceManifestPrefetchTotal;
private readonly Counter<long> _surfaceManifestPrefetchTotal;
private readonly Histogram<double> _runDurationSeconds;
private readonly UpDownCounter<long> _runsActive;
private readonly Counter<long> _graphJobsTotal;
@@ -65,6 +67,14 @@ public sealed class SchedulerWorkerMetrics : IDisposable
"scheduler_runner_delta_kev_total",
unit: "count",
description: "KEV hits observed by runner grouped by mode.");
_surfaceManifestPrefetchTotal = _meter.CreateCounter<long>(
"scheduler_surface_manifest_prefetch_total",
unit: "attempt",
description: "Surface manifest prefetch attempts grouped by result.");
_surfaceManifestPrefetchTotal = _meter.CreateCounter<long>(
"scheduler_surface_manifest_prefetch_total",
unit: "attempt",
description: "Surface manifest prefetch attempts grouped by result.");
_runDurationSeconds = _meter.CreateHistogram<double>(
"scheduler_run_duration_seconds",
unit: "s",
@@ -172,6 +182,12 @@ public sealed class SchedulerWorkerMetrics : IDisposable
_runnerImagesTotal.Add(processedImages, imageTags);
}
public void RecordSurfaceManifestPrefetch(string result)
{
var tags = new[] { new KeyValuePair<string, object?>("result", result) };
_surfaceManifestPrefetchTotal.Add(1, tags);
}
public void RecordDeltaSummaries(string mode, IReadOnlyList<DeltaSummary> deltas)
{
if (deltas.Count == 0)

View File

@@ -4,7 +4,9 @@ using System.Linq;
using Microsoft.Extensions.Logging;
using StellaOps.Scheduler.Models;
using StellaOps.Scheduler.Queue;
using StellaOps.Scanner.Surface.FS;
using StellaOps.Scheduler.Worker.Options;
using StellaOps.Scheduler.Worker.Observability;
namespace StellaOps.Scheduler.Worker.Planning;
@@ -18,17 +20,23 @@ internal sealed class PlannerQueueDispatchService : IPlannerQueueDispatchService
private readonly IImpactShardPlanner _shardPlanner;
private readonly ISchedulerRunnerQueue _runnerQueue;
private readonly SchedulerWorkerOptions _options;
private readonly ISurfaceManifestReader _surfaceManifestReader;
private readonly SchedulerWorkerMetrics _metrics;
private readonly ILogger<PlannerQueueDispatchService> _logger;
public PlannerQueueDispatchService(
IImpactShardPlanner shardPlanner,
ISchedulerRunnerQueue runnerQueue,
SchedulerWorkerOptions options,
ISurfaceManifestReader surfaceManifestReader,
SchedulerWorkerMetrics metrics,
ILogger<PlannerQueueDispatchService> logger)
{
_shardPlanner = shardPlanner ?? throw new ArgumentNullException(nameof(shardPlanner));
_runnerQueue = runnerQueue ?? throw new ArgumentNullException(nameof(runnerQueue));
_options = options ?? throw new ArgumentNullException(nameof(options));
_surfaceManifestReader = surfaceManifestReader ?? throw new ArgumentNullException(nameof(surfaceManifestReader));
_metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -78,6 +86,8 @@ internal sealed class PlannerQueueDispatchService : IPlannerQueueDispatchService
continue;
}
var manifestPointers = await PrefetchManifestsAsync(digests, cancellationToken).ConfigureAwait(false);
var segmentAttributes = MergeAttributes(attributes, shard, schedule);
var runnerMessage = new RunnerSegmentQueueMessage(
segmentId,
@@ -88,7 +98,8 @@ internal sealed class PlannerQueueDispatchService : IPlannerQueueDispatchService
limits.RatePerSecond,
impactSet.UsageOnly,
segmentAttributes,
message.CorrelationId);
message.CorrelationId,
manifestPointers);
enqueueTasks.Add(_runnerQueue.EnqueueAsync(runnerMessage, cancellationToken).AsTask());
}
@@ -190,6 +201,48 @@ internal sealed class PlannerQueueDispatchService : IPlannerQueueDispatchService
return map;
}
private async Task<IReadOnlyDictionary<string, SurfaceManifestPointer>> PrefetchManifestsAsync(
IReadOnlyList<string> digests,
CancellationToken cancellationToken)
{
var results = new Dictionary<string, SurfaceManifestPointer>(StringComparer.Ordinal);
foreach (var digest in digests)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var manifest = await _surfaceManifestReader
.TryGetByDigestAsync(digest, cancellationToken)
.ConfigureAwait(false);
if (manifest is null)
{
_metrics.RecordSurfaceManifestPrefetch(result: "miss");
continue;
}
var pointer = new SurfaceManifestPointer(digest, manifest.Tenant);
results[digest] = pointer;
_metrics.RecordSurfaceManifestPrefetch(result: "hit");
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to prefetch surface manifest for digest {Digest}", digest);
_metrics.RecordSurfaceManifestPrefetch(result: "error");
}
}
return results.Count == 0
? (IReadOnlyDictionary<string, SurfaceManifestPointer>)EmptyReadOnlyDictionary<string, SurfaceManifestPointer>.Instance
: results;
}
}
public readonly record struct PlannerQueueDispatchResult(

View File

@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace StellaOps.Scheduler.Worker.Planning;
/// <summary>
/// Minimal pointer to a Surface.FS manifest associated with an image digest.
/// </summary>
public sealed record SurfaceManifestPointer
{
public SurfaceManifestPointer(string manifestDigest, string? tenant)
{
ManifestDigest = manifestDigest ?? throw new ArgumentNullException(nameof(manifestDigest));
Tenant = tenant;
}
[JsonPropertyName("manifestDigest")]
public string ManifestDigest { get; init; }
[JsonPropertyName("tenant")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Tenant { get; init; }
}

View File

@@ -12,8 +12,10 @@
<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.10.0" />
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>
</Project>