consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
using StellaOps.TimelineIndexer.Core.Abstractions;
using StellaOps.TimelineIndexer.Infrastructure.DependencyInjection;
using StellaOps.TimelineIndexer.Infrastructure.Options;
using StellaOps.TimelineIndexer.Infrastructure.Subscriptions;
using StellaOps.TimelineIndexer.Worker;
using StellaOps.Worker.Health;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddEnvironmentVariables(prefix: "TIMELINE_");
builder.Services.AddTimelineIndexerPostgres(builder.Configuration);
builder.Services.AddOptions<TimelineIngestionOptions>()
.Bind(builder.Configuration.GetSection("Ingestion"));
builder.Services.AddSingleton<TimelineEnvelopeParser>();
builder.Services.AddSingleton<ITimelineEventSubscriber, NatsTimelineEventSubscriber>();
builder.Services.AddSingleton<ITimelineEventSubscriber, RedisTimelineEventSubscriber>();
builder.Services.AddSingleton<ITimelineEventSubscriber, NullTimelineEventSubscriber>();
builder.Services.AddHostedService<TimelineIngestionWorker>();
builder.Services.AddWorkerHealthChecks();
var app = builder.Build();
app.MapWorkerHealthEndpoints();
app.Run();

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"StellaOps.TimelineIndexer.Worker": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<UserSecretsId>dotnet-StellaOps.TimelineIndexer.Worker-f6dbdeac-9eb5-4250-9384-ef93fc70f770</UserSecretsId>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- FrameworkReference Microsoft.AspNetCore.App is provided by Sdk.Web -->
<!-- Microsoft.Extensions.Hosting is provided by Sdk.Worker -->
<ItemGroup>
<ProjectReference Include="..\__Libraries\StellaOps.TimelineIndexer.Core\StellaOps.TimelineIndexer.Core.csproj"/>
<ProjectReference Include="..\__Libraries\StellaOps.TimelineIndexer.Infrastructure\StellaOps.TimelineIndexer.Infrastructure.csproj"/>
<ProjectReference Include="../../__Libraries/StellaOps.Worker.Health/StellaOps.Worker.Health.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
# StellaOps.TimelineIndexer.Worker Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_solid_review.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Worker/StellaOps.TimelineIndexer.Worker.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -0,0 +1,76 @@
using StellaOps.TimelineIndexer.Core.Abstractions;
using StellaOps.TimelineIndexer.Core.Models;
using System.Collections.Concurrent;
using System.Diagnostics.Metrics;
using System.Linq;
namespace StellaOps.TimelineIndexer.Worker;
/// <summary>
/// Background consumer that reads timeline events from configured subscribers and persists them via the ingestion service.
/// </summary>
public sealed class TimelineIngestionWorker(
IEnumerable<ITimelineEventSubscriber> subscribers,
ITimelineIngestionService ingestionService,
ILogger<TimelineIngestionWorker> logger,
TimeProvider? timeProvider = null) : BackgroundService
{
private static readonly Meter Meter = new("StellaOps.TimelineIndexer", "1.0.0");
private static readonly Counter<long> IngestedCounter = Meter.CreateCounter<long>("timeline.ingested");
private static readonly Counter<long> DuplicateCounter = Meter.CreateCounter<long>("timeline.duplicates");
private static readonly Counter<long> FailedCounter = Meter.CreateCounter<long>("timeline.failed");
private static readonly Histogram<double> LagHistogram = Meter.CreateHistogram<double>("timeline.ingest.lag.seconds");
private readonly IEnumerable<ITimelineEventSubscriber> _subscribers = subscribers;
private readonly ITimelineIngestionService _ingestion = ingestionService;
private readonly ILogger<TimelineIngestionWorker> _logger = logger;
private readonly ConcurrentDictionary<(string tenant, string eventId), byte> _sessionSeen = new();
private readonly TimeProvider _timeProvider = timeProvider ?? TimeProvider.System;
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var tasks = _subscribers.Select(subscriber => ConsumeAsync(subscriber, stoppingToken)).ToArray();
return Task.WhenAll(tasks);
}
private async Task ConsumeAsync(ITimelineEventSubscriber subscriber, CancellationToken cancellationToken)
{
await foreach (var envelope in subscriber.SubscribeAsync(cancellationToken))
{
var key = (envelope.TenantId, envelope.EventId);
if (!_sessionSeen.TryAdd(key, 0))
{
DuplicateCounter.Add(1);
_logger.LogDebug("Skipped duplicate timeline event {EventId} for tenant {Tenant}", envelope.EventId, envelope.TenantId);
continue;
}
try
{
var result = await _ingestion.IngestAsync(envelope, cancellationToken).ConfigureAwait(false);
if (result.Inserted)
{
IngestedCounter.Add(1);
LagHistogram.Record((_timeProvider.GetUtcNow() - envelope.OccurredAt).TotalSeconds);
_logger.LogInformation("Ingested timeline event {EventId} from {Source} (tenant {Tenant})", envelope.EventId, envelope.Source, envelope.TenantId);
}
else
{
DuplicateCounter.Add(1);
_logger.LogDebug("Store reported duplicate for event {EventId} tenant {Tenant}", envelope.EventId, envelope.TenantId);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Respect shutdown.
break;
}
catch (Exception ex)
{
FailedCounter.Add(1);
_logger.LogError(ex, "Failed to ingest timeline event {EventId} for tenant {Tenant}", envelope.EventId, envelope.TenantId);
}
}
}
}

View File

@@ -0,0 +1,34 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Postgres": {
"Timeline": {
"ConnectionString": "Host=localhost;Database=timeline;Username=timeline;Password=timeline;",
"SchemaName": "timeline",
"CommandTimeoutSeconds": 30
}
},
"Ingestion": {
"Nats": {
"Enabled": false,
"Url": "nats://localhost:4222",
"Subject": "orch.event",
"QueueGroup": "timeline-indexer",
"Prefetch": 64
},
"Redis": {
"Enabled": false,
"ConnectionString": "localhost:6379",
"Stream": "timeline.events",
"ConsumerGroup": "timeline-indexer",
"ConsumerName": "timeline-worker",
"ValueField": "data",
"MaxBatchSize": 128,
"PollIntervalMilliseconds": 250
}
}
}

View File

@@ -0,0 +1,34 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Postgres": {
"Timeline": {
"ConnectionString": "Host=localhost;Database=timeline;Username=timeline;Password=timeline;",
"SchemaName": "timeline",
"CommandTimeoutSeconds": 30
}
},
"Ingestion": {
"Nats": {
"Enabled": false,
"Url": "nats://localhost:4222",
"Subject": "orch.event",
"QueueGroup": "timeline-indexer",
"Prefetch": 64
},
"Redis": {
"Enabled": false,
"ConnectionString": "localhost:6379",
"Stream": "timeline.events",
"ConsumerGroup": "timeline-indexer",
"ConsumerName": "timeline-worker",
"ValueField": "data",
"MaxBatchSize": 128,
"PollIntervalMilliseconds": 250
}
}
}