update evidence bundle to include new evidence types and implement ProofSpine integration
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-15 09:15:30 +02:00
parent 8c8f0c632d
commit 505fe7a885
49 changed files with 4756 additions and 551 deletions

View File

@@ -16,6 +16,11 @@ public interface IUnknownsRepository
/// </summary>
Task BulkUpdateAsync(IEnumerable<UnknownSymbolDocument> items, CancellationToken cancellationToken);
/// <summary>
/// Returns all known subject keys containing unknowns.
/// </summary>
Task<IReadOnlyList<string>> GetAllSubjectKeysAsync(CancellationToken cancellationToken);
/// <summary>
/// Gets unknowns due for rescan in a specific band.
/// </summary>

View File

@@ -0,0 +1,33 @@
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Signals.Persistence;
public sealed class InMemoryDeploymentRefsRepository : IDeploymentRefsRepository
{
private readonly ConcurrentDictionary<string, int> _deploymentsByPurl = new(StringComparer.OrdinalIgnoreCase);
public void SetDeployments(string purl, int deployments)
{
ArgumentException.ThrowIfNullOrWhiteSpace(purl);
if (deployments < 0)
{
throw new ArgumentOutOfRangeException(nameof(deployments), "Deployments cannot be negative.");
}
_deploymentsByPurl[purl.Trim()] = deployments;
}
public Task<int> CountDeploymentsAsync(string purl, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(purl))
{
return Task.FromResult(0);
}
return Task.FromResult(_deploymentsByPurl.TryGetValue(purl.Trim(), out var count) ? count : 0);
}
}

View File

@@ -0,0 +1,35 @@
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Signals.Persistence;
public sealed class InMemoryGraphMetricsRepository : IGraphMetricsRepository
{
private readonly ConcurrentDictionary<string, GraphMetrics> _metrics = new(StringComparer.OrdinalIgnoreCase);
public void SetMetrics(string symbolId, string callgraphId, GraphMetrics metrics)
{
ArgumentException.ThrowIfNullOrWhiteSpace(symbolId);
ArgumentException.ThrowIfNullOrWhiteSpace(callgraphId);
var key = BuildKey(symbolId, callgraphId);
_metrics[key] = metrics;
}
public Task<GraphMetrics?> GetMetricsAsync(string symbolId, string callgraphId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(symbolId) || string.IsNullOrWhiteSpace(callgraphId))
{
return Task.FromResult<GraphMetrics?>(null);
}
var key = BuildKey(symbolId, callgraphId);
return Task.FromResult(_metrics.TryGetValue(key, out var metrics) ? metrics : null);
}
private static string BuildKey(string symbolId, string callgraphId)
=> $"{callgraphId.Trim()}|{symbolId.Trim()}";
}

View File

@@ -78,7 +78,9 @@ internal sealed class UnknownsIngestionService : IUnknownsIngestionService
EdgeFrom = entry.EdgeFrom?.Trim(),
EdgeTo = entry.EdgeTo?.Trim(),
Reason = entry.Reason?.Trim(),
CreatedAt = now
CreatedAt = now,
UpdatedAt = now,
LastAnalyzedAt = now
});
}

View File

@@ -75,9 +75,11 @@ public sealed class UnknownsScoringService : IUnknownsScoringService
UnknownsScoringOptions opts,
CancellationToken cancellationToken)
{
var now = _timeProvider.GetUtcNow();
var trace = new UnknownsNormalizationTrace
{
ComputedAt = _timeProvider.GetUtcNow(),
ComputedAt = now,
Weights = new Dictionary<string, double>
{
["wP"] = opts.WeightPopularity,
@@ -139,24 +141,21 @@ public sealed class UnknownsScoringService : IUnknownsScoringService
trace.FinalScore = score;
// Band assignment
unknown.Band = score switch
{
>= 0.70 => UnknownsBand.Hot,
>= 0.40 => UnknownsBand.Warm,
_ => UnknownsBand.Cold
};
unknown.Band = score >= opts.HotThreshold
? UnknownsBand.Hot
: score >= opts.WarmThreshold ? UnknownsBand.Warm : UnknownsBand.Cold;
trace.AssignedBand = unknown.Band.ToString();
// Schedule next rescan based on band
unknown.NextScheduledRescan = unknown.Band switch
{
UnknownsBand.Hot => _timeProvider.GetUtcNow().AddMinutes(15),
UnknownsBand.Warm => _timeProvider.GetUtcNow().AddHours(opts.WarmRescanHours),
_ => _timeProvider.GetUtcNow().AddDays(opts.ColdRescanDays)
UnknownsBand.Hot => now.AddMinutes(opts.HotRescanMinutes),
UnknownsBand.Warm => now.AddHours(opts.WarmRescanHours),
_ => now.AddDays(opts.ColdRescanDays)
};
unknown.NormalizationTrace = trace;
unknown.UpdatedAt = _timeProvider.GetUtcNow();
unknown.UpdatedAt = now;
_logger.LogDebug(
"Scored unknown {UnknownId}: P={P:F2} E={E:F2} U={U:F2} C={C:F2} S={S:F2} → Score={Score:F2} Band={Band}",
@@ -270,9 +269,28 @@ public sealed class UnknownsScoringService : IUnknownsScoringService
return (1.0, opts.StalenessMaxDays); // Never analyzed = maximum staleness
var daysSince = (int)(_timeProvider.GetUtcNow() - lastAnalyzedAt.Value).TotalDays;
if (daysSince < 0)
{
daysSince = 0;
}
// Formula: S = min(1, age_days / max_days)
var score = Math.Min(1.0, (double)daysSince / opts.StalenessMaxDays);
// Exponential staleness: decayFactor = exp(-t/tau), staleness = (1 - decayFactor) normalized to reach 1 at maxDays.
// This models confidence decay (higher staleness means lower confidence in evidence freshness).
if (opts.StalenessTauDays > 0 && opts.StalenessMaxDays > 0)
{
var maxDays = Math.Max(1, opts.StalenessMaxDays);
var decayFactor = Math.Exp(-daysSince / opts.StalenessTauDays);
var maxDecayFactor = Math.Exp(-maxDays / opts.StalenessTauDays);
var numerator = 1.0 - decayFactor;
var denominator = 1.0 - maxDecayFactor;
var normalized = denominator <= 0 ? 0.0 : numerator / denominator;
return (Math.Clamp(normalized, 0.0, 1.0), daysSince);
}
// Fallback linear: S = min(1, age_days / max_days)
var score = opts.StalenessMaxDays <= 0
? 0.0
: Math.Min(1.0, (double)daysSince / opts.StalenessMaxDays);
return (score, daysSince);
}