up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
namespace StellaOps.Signals.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for unknowns decay batch processing.
|
||||
/// </summary>
|
||||
public sealed class UnknownsDecayOptions
|
||||
{
|
||||
public const string SectionName = "Signals:UnknownsDecay";
|
||||
|
||||
/// <summary>
|
||||
/// Time of day (UTC hour) for nightly decay batch. Default: 2 (2 AM UTC).
|
||||
/// </summary>
|
||||
public int NightlyBatchHourUtc { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum subjects per batch run. Default: 10000.
|
||||
/// </summary>
|
||||
public int MaxSubjectsPerBatch { get; set; } = 10_000;
|
||||
}
|
||||
@@ -66,6 +66,11 @@ public sealed class UnknownsScoringOptions
|
||||
/// </summary>
|
||||
public int StalenessMaxDays { get; set; } = 14;
|
||||
|
||||
/// <summary>
|
||||
/// Staleness time constant (tau) in days for exponential decay. Default: 14
|
||||
/// </summary>
|
||||
public double StalenessTauDays { get; set; } = 14.0;
|
||||
|
||||
// ===== BAND THRESHOLDS =====
|
||||
|
||||
/// <summary>
|
||||
@@ -80,6 +85,11 @@ public sealed class UnknownsScoringOptions
|
||||
|
||||
// ===== RESCAN SCHEDULING =====
|
||||
|
||||
/// <summary>
|
||||
/// Minutes until HOT items are rescanned. Default: 15
|
||||
/// </summary>
|
||||
public int HotRescanMinutes { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Hours until WARM items are rescanned. Default: 24
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace StellaOps.Signals.Persistence;
|
||||
public sealed class InMemoryUnknownsRepository : IUnknownsRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, List<UnknownSymbolDocument>> _store = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemoryUnknownsRepository(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task UpsertAsync(string subjectKey, IEnumerable<UnknownSymbolDocument> items, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -59,12 +65,23 @@ public sealed class InMemoryUnknownsRepository : IUnknownsRepository
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<string>> GetAllSubjectKeysAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var keys = _store.Keys
|
||||
.OrderBy(static key => key, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<string>>(keys);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<UnknownSymbolDocument>> GetDueForRescanAsync(
|
||||
UnknownsBand band,
|
||||
int limit,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
var results = _store.Values
|
||||
.SelectMany(x => x)
|
||||
|
||||
@@ -132,6 +132,16 @@ builder.Services.AddSingleton<IReachabilityFactRepository>(sp =>
|
||||
return new ReachabilityFactCacheDecorator(inner, cache);
|
||||
});
|
||||
builder.Services.AddSingleton<IUnknownsRepository, InMemoryUnknownsRepository>();
|
||||
builder.Services.AddOptions<UnknownsScoringOptions>()
|
||||
.Bind(builder.Configuration.GetSection(UnknownsScoringOptions.SectionName));
|
||||
builder.Services.AddOptions<UnknownsDecayOptions>()
|
||||
.Bind(builder.Configuration.GetSection(UnknownsDecayOptions.SectionName));
|
||||
builder.Services.AddSingleton<IDeploymentRefsRepository, InMemoryDeploymentRefsRepository>();
|
||||
builder.Services.AddSingleton<IGraphMetricsRepository, InMemoryGraphMetricsRepository>();
|
||||
builder.Services.AddSingleton<IUnknownsScoringService, UnknownsScoringService>();
|
||||
builder.Services.AddSingleton<IUnknownsDecayService, UnknownsDecayService>();
|
||||
builder.Services.AddSingleton<ISignalRefreshService, SignalRefreshService>();
|
||||
builder.Services.AddHostedService<NightlyDecayWorker>();
|
||||
builder.Services.AddSingleton<IReachabilityStoreRepository, InMemoryReachabilityStoreRepository>();
|
||||
builder.Services.AddHttpClient<RouterEventsPublisher>((sp, client) =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Handles signal refresh events that reset decay.
|
||||
/// </summary>
|
||||
public interface ISignalRefreshService
|
||||
{
|
||||
/// <summary>
|
||||
/// Records a signal refresh event.
|
||||
/// </summary>
|
||||
Task RefreshSignalAsync(SignalRefreshEvent refreshEvent, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal refresh event types per advisory.
|
||||
/// </summary>
|
||||
public sealed class SignalRefreshEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Subject key for the unknown.
|
||||
/// </summary>
|
||||
public required string SubjectKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown ID being refreshed.
|
||||
/// </summary>
|
||||
public required string UnknownId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of signal refresh.
|
||||
/// </summary>
|
||||
public required SignalRefreshType RefreshType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Weight of this signal type.
|
||||
/// </summary>
|
||||
public double Weight { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional context.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string>? Context { get; init; }
|
||||
}
|
||||
|
||||
public enum SignalRefreshType
|
||||
{
|
||||
UnknownsIngested,
|
||||
ReachabilityRecomputed,
|
||||
RuntimeFactsIngested,
|
||||
ProvenanceAnchored,
|
||||
VexUpdated
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for computing confidence decay on unknowns.
|
||||
/// </summary>
|
||||
public interface IUnknownsDecayService
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies decay to all unknowns in a subject and recomputes bands.
|
||||
/// </summary>
|
||||
Task<DecayResult> ApplyDecayAsync(
|
||||
string subjectKey,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Applies decay to a single unknown.
|
||||
/// </summary>
|
||||
Task<UnknownSymbolDocument> ApplyDecayToUnknownAsync(
|
||||
UnknownSymbolDocument unknown,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Recomputes all scores and bands for nightly batch.
|
||||
/// </summary>
|
||||
Task<BatchDecayResult> RunNightlyDecayBatchAsync(
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed record DecayResult(
|
||||
string SubjectKey,
|
||||
int ProcessedCount,
|
||||
int HotCount,
|
||||
int WarmCount,
|
||||
int ColdCount,
|
||||
int BandChanges,
|
||||
DateTimeOffset ComputedAt);
|
||||
|
||||
public sealed record BatchDecayResult(
|
||||
int TotalSubjects,
|
||||
int TotalUnknowns,
|
||||
int TotalBandChanges,
|
||||
TimeSpan Duration,
|
||||
DateTimeOffset CompletedAt);
|
||||
63
src/Signals/StellaOps.Signals/Services/NightlyDecayWorker.cs
Normal file
63
src/Signals/StellaOps.Signals/Services/NightlyDecayWorker.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Signals.Options;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
public sealed class NightlyDecayWorker : BackgroundService
|
||||
{
|
||||
private readonly IUnknownsDecayService _decayService;
|
||||
private readonly IOptions<UnknownsDecayOptions> _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<NightlyDecayWorker> _logger;
|
||||
|
||||
public NightlyDecayWorker(
|
||||
IUnknownsDecayService decayService,
|
||||
IOptions<UnknownsDecayOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<NightlyDecayWorker> logger)
|
||||
{
|
||||
_decayService = decayService ?? throw new ArgumentNullException(nameof(decayService));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var opts = _options.Value;
|
||||
var nextRun = GetNextRunUtc(_timeProvider.GetUtcNow(), opts.NightlyBatchHourUtc);
|
||||
var delay = nextRun - _timeProvider.GetUtcNow();
|
||||
|
||||
if (delay > TimeSpan.Zero)
|
||||
{
|
||||
_logger.LogInformation("Next unknowns decay batch scheduled for {NextRun}", nextRun);
|
||||
await Task.Delay(delay, _timeProvider, stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _decayService.RunNightlyDecayBatchAsync(stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
// Shutdown requested.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Nightly unknowns decay batch failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTimeOffset GetNextRunUtc(DateTimeOffset nowUtc, int hourUtc)
|
||||
{
|
||||
var clampedHour = Math.Clamp(hourUtc, 0, 23);
|
||||
var today = new DateTimeOffset(nowUtc.Year, nowUtc.Month, nowUtc.Day, 0, 0, 0, TimeSpan.Zero);
|
||||
var candidate = today.AddHours(clampedHour);
|
||||
return candidate <= nowUtc ? candidate.AddDays(1) : candidate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Signals.Options;
|
||||
using StellaOps.Signals.Persistence;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
public sealed class SignalRefreshService : ISignalRefreshService
|
||||
{
|
||||
private readonly IUnknownsRepository _repository;
|
||||
private readonly IUnknownsScoringService _scoringService;
|
||||
private readonly IOptions<UnknownsScoringOptions> _scoringOptions;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<SignalRefreshService> _logger;
|
||||
|
||||
public SignalRefreshService(
|
||||
IUnknownsRepository repository,
|
||||
IUnknownsScoringService scoringService,
|
||||
IOptions<UnknownsScoringOptions> scoringOptions,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<SignalRefreshService> logger)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_scoringService = scoringService ?? throw new ArgumentNullException(nameof(scoringService));
|
||||
_scoringOptions = scoringOptions ?? throw new ArgumentNullException(nameof(scoringOptions));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task RefreshSignalAsync(SignalRefreshEvent refreshEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(refreshEvent);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(refreshEvent.SubjectKey);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(refreshEvent.UnknownId);
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var unknowns = await _repository.GetBySubjectAsync(refreshEvent.SubjectKey, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var target = unknowns.FirstOrDefault(u => string.Equals(u.Id, refreshEvent.UnknownId, StringComparison.Ordinal));
|
||||
if (target is null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Signal refresh ignored: unknown {UnknownId} not found for subject {SubjectKey}",
|
||||
refreshEvent.UnknownId,
|
||||
refreshEvent.SubjectKey);
|
||||
return;
|
||||
}
|
||||
|
||||
target.LastAnalyzedAt = now;
|
||||
target.UpdatedAt = now;
|
||||
|
||||
await _scoringService.ScoreUnknownAsync(target, _scoringOptions.Value, cancellationToken).ConfigureAwait(false);
|
||||
await _repository.BulkUpdateAsync(new[] { target }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Signal refresh applied: subject={SubjectKey}, unknownId={UnknownId}, type={Type}",
|
||||
refreshEvent.SubjectKey,
|
||||
refreshEvent.UnknownId,
|
||||
refreshEvent.RefreshType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
internal static class UnknownsDecayMetrics
|
||||
{
|
||||
private static readonly Meter Meter = new("StellaOps.Signals.Decay", "1.0.0");
|
||||
|
||||
public static readonly Counter<long> SubjectsProcessed = Meter.CreateCounter<long>(
|
||||
"stellaops_unknowns_decay_subjects_processed_total",
|
||||
description: "Total subjects processed by unknowns decay batches");
|
||||
|
||||
public static readonly Counter<long> UnknownsProcessed = Meter.CreateCounter<long>(
|
||||
"stellaops_unknowns_decay_unknowns_processed_total",
|
||||
description: "Total unknowns processed by unknowns decay batches");
|
||||
|
||||
public static readonly Counter<long> BandChanges = Meter.CreateCounter<long>(
|
||||
"stellaops_unknowns_decay_band_changes_total",
|
||||
description: "Total band changes caused by decay rescoring");
|
||||
|
||||
public static readonly Histogram<double> BatchDurationSeconds = Meter.CreateHistogram<double>(
|
||||
"stellaops_unknowns_decay_batch_duration_seconds",
|
||||
unit: "s",
|
||||
description: "Duration of unknowns decay batch runs");
|
||||
}
|
||||
143
src/Signals/StellaOps.Signals/Services/UnknownsDecayService.cs
Normal file
143
src/Signals/StellaOps.Signals/Services/UnknownsDecayService.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Signals.Models;
|
||||
using StellaOps.Signals.Options;
|
||||
using StellaOps.Signals.Persistence;
|
||||
|
||||
namespace StellaOps.Signals.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implements time-based confidence decay for unknowns by periodically recomputing staleness and band assignment.
|
||||
/// </summary>
|
||||
public sealed class UnknownsDecayService : IUnknownsDecayService
|
||||
{
|
||||
private readonly IUnknownsRepository _repository;
|
||||
private readonly IUnknownsScoringService _scoringService;
|
||||
private readonly IOptions<UnknownsScoringOptions> _scoringOptions;
|
||||
private readonly IOptions<UnknownsDecayOptions> _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<UnknownsDecayService> _logger;
|
||||
|
||||
public UnknownsDecayService(
|
||||
IUnknownsRepository repository,
|
||||
IUnknownsScoringService scoringService,
|
||||
IOptions<UnknownsScoringOptions> scoringOptions,
|
||||
IOptions<UnknownsDecayOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<UnknownsDecayService> logger)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_scoringService = scoringService ?? throw new ArgumentNullException(nameof(scoringService));
|
||||
_scoringOptions = scoringOptions ?? throw new ArgumentNullException(nameof(scoringOptions));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<DecayResult> ApplyDecayAsync(string subjectKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var unknowns = await _repository.GetBySubjectAsync(subjectKey, cancellationToken).ConfigureAwait(false);
|
||||
if (unknowns.Count == 0)
|
||||
{
|
||||
return new DecayResult(subjectKey, 0, 0, 0, 0, 0, now);
|
||||
}
|
||||
|
||||
var updated = new List<UnknownSymbolDocument>(unknowns.Count);
|
||||
var bandChanges = 0;
|
||||
|
||||
foreach (var unknown in unknowns)
|
||||
{
|
||||
var oldBand = unknown.Band;
|
||||
var decayed = await ApplyDecayToUnknownAsync(unknown, cancellationToken).ConfigureAwait(false);
|
||||
updated.Add(decayed);
|
||||
|
||||
if (oldBand != decayed.Band)
|
||||
{
|
||||
bandChanges++;
|
||||
}
|
||||
}
|
||||
|
||||
await _repository.BulkUpdateAsync(updated, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var result = new DecayResult(
|
||||
SubjectKey: subjectKey,
|
||||
ProcessedCount: updated.Count,
|
||||
HotCount: updated.Count(u => u.Band == UnknownsBand.Hot),
|
||||
WarmCount: updated.Count(u => u.Band == UnknownsBand.Warm),
|
||||
ColdCount: updated.Count(u => u.Band == UnknownsBand.Cold),
|
||||
BandChanges: bandChanges,
|
||||
ComputedAt: now);
|
||||
|
||||
UnknownsDecayMetrics.SubjectsProcessed.Add(1);
|
||||
UnknownsDecayMetrics.UnknownsProcessed.Add(result.ProcessedCount);
|
||||
UnknownsDecayMetrics.BandChanges.Add(result.BandChanges);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Applied unknowns decay for {SubjectKey}: processed={ProcessedCount}, hot={HotCount}, warm={WarmCount}, cold={ColdCount}, bandChanges={BandChanges}",
|
||||
result.SubjectKey,
|
||||
result.ProcessedCount,
|
||||
result.HotCount,
|
||||
result.WarmCount,
|
||||
result.ColdCount,
|
||||
result.BandChanges);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<UnknownSymbolDocument> ApplyDecayToUnknownAsync(UnknownSymbolDocument unknown, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unknown);
|
||||
var opts = _scoringOptions.Value;
|
||||
return await _scoringService.ScoreUnknownAsync(unknown, opts, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<BatchDecayResult> RunNightlyDecayBatchAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
var subjects = await _repository.GetAllSubjectKeysAsync(cancellationToken).ConfigureAwait(false);
|
||||
var maxSubjects = Math.Max(0, _options.Value.MaxSubjectsPerBatch);
|
||||
if (maxSubjects > 0 && subjects.Count > maxSubjects)
|
||||
{
|
||||
subjects = subjects.Take(maxSubjects).ToArray();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Starting nightly unknowns decay batch for {Count} subjects", subjects.Count);
|
||||
|
||||
var totalUnknowns = 0;
|
||||
var totalBandChanges = 0;
|
||||
|
||||
foreach (var subjectKey in subjects)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var result = await ApplyDecayAsync(subjectKey, cancellationToken).ConfigureAwait(false);
|
||||
totalUnknowns += result.ProcessedCount;
|
||||
totalBandChanges += result.BandChanges;
|
||||
}
|
||||
|
||||
var endTime = _timeProvider.GetUtcNow();
|
||||
var duration = endTime - startTime;
|
||||
|
||||
UnknownsDecayMetrics.BatchDurationSeconds.Record(duration.TotalSeconds);
|
||||
|
||||
var batchResult = new BatchDecayResult(
|
||||
TotalSubjects: subjects.Count,
|
||||
TotalUnknowns: totalUnknowns,
|
||||
TotalBandChanges: totalBandChanges,
|
||||
Duration: duration,
|
||||
CompletedAt: endTime);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Completed nightly unknowns decay batch: subjects={TotalSubjects}, unknowns={TotalUnknowns}, bandChanges={TotalBandChanges}, duration={Duration}",
|
||||
batchResult.TotalSubjects,
|
||||
batchResult.TotalUnknowns,
|
||||
batchResult.TotalBandChanges,
|
||||
batchResult.Duration);
|
||||
|
||||
return batchResult;
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,4 @@ This file mirrors sprint work for the Signals module.
|
||||
| `SIG-STORE-401-016` | `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` | DONE (2025-12-13) | Added reachability store repository APIs and models; callgraph ingestion now populates the store; Mongo index script at `ops/mongo/indices/reachability_store_indices.js`. |
|
||||
| `UNCERTAINTY-SCHEMA-401-024` | `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` | DONE (2025-12-13) | Implemented uncertainty tiers and scoring integration; see `src/Signals/StellaOps.Signals/Lattice/UncertaintyTier.cs` and `src/Signals/StellaOps.Signals/Lattice/ReachabilityLattice.cs`. |
|
||||
| `UNCERTAINTY-SCORER-401-025` | `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` | DONE (2025-12-13) | Reachability risk score now uses configurable entropy weights and is aligned with `UncertaintyDocument.RiskScore`; tests cover tier/entropy scoring. |
|
||||
| `UNKNOWNS-DECAY-3601-001` | `docs/implplan/SPRINT_3601_0001_0001_unknowns_decay_algorithm.md` | DOING (2025-12-15) | Implement decay worker/service, signal refresh hook, and deterministic unit/integration tests. |
|
||||
|
||||
Reference in New Issue
Block a user