Entrypoint Detection Program (100% complete): - Sprint 0411: Semantic Entrypoint Engine - all 25 tasks DONE - Sprint 0412: Temporal & Mesh Entrypoint - all 19 tasks DONE - Sprint 0413: Speculative Execution Engine - all 19 tasks DONE - Sprint 0414: Binary Intelligence - all 19 tasks DONE - Sprint 0415: Predictive Risk Scoring - all tasks DONE Key deliverables: - SemanticEntrypoint schema with ApplicationIntent/CapabilityClass - TemporalEntrypointGraph and MeshEntrypointGraph - ShellSymbolicExecutor with PathEnumerator and PathConfidenceScorer - CodeFingerprint index with symbol recovery - RiskScore with multi-dimensional risk assessment Sprint 3500.0002.0003 (Proof Replay + API): - ManifestEndpoints with DSSE content negotiation - Proof bundle endpoints by root hash - IdempotencyMiddleware with RFC 9530 Content-Digest - Rate limiting (100 req/hr per tenant) - OpenAPI documentation updates Tests: 357 EntryTrace tests pass, WebService tests blocked by pre-existing infrastructure issue
137 lines
5.2 KiB
C#
137 lines
5.2 KiB
C#
// -----------------------------------------------------------------------------
|
|
// TestManifestRepository.cs
|
|
// Purpose: Test-only in-memory implementation of Storage.Repositories.IScanManifestRepository
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System.Collections.Concurrent;
|
|
using StellaOps.Scanner.Storage.Entities;
|
|
using StellaOps.Scanner.Storage.Repositories;
|
|
|
|
namespace StellaOps.Scanner.WebService.Services;
|
|
|
|
/// <summary>
|
|
/// In-memory implementation of IScanManifestRepository for testing.
|
|
/// </summary>
|
|
public sealed class TestManifestRepository : StellaOps.Scanner.Storage.Repositories.IScanManifestRepository
|
|
{
|
|
private readonly ConcurrentDictionary<Guid, ScanManifestRow> _manifestsByScanId = new();
|
|
private readonly ConcurrentDictionary<string, ScanManifestRow> _manifestsByHash = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public Task<ScanManifestRow?> GetByHashAsync(string manifestHash, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(_manifestsByHash.TryGetValue(manifestHash, out var manifest) ? manifest : null);
|
|
}
|
|
|
|
public Task<ScanManifestRow?> GetByScanIdAsync(Guid scanId, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(_manifestsByScanId.TryGetValue(scanId, out var manifest) ? manifest : null);
|
|
}
|
|
|
|
public Task<ScanManifestRow> SaveAsync(ScanManifestRow manifest, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(manifest);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
_manifestsByScanId[manifest.ScanId] = manifest;
|
|
_manifestsByHash[manifest.ManifestHash] = manifest;
|
|
|
|
return Task.FromResult(manifest);
|
|
}
|
|
|
|
public Task MarkCompletedAsync(Guid manifestId, DateTimeOffset completedAt, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
foreach (var manifest in _manifestsByScanId.Values)
|
|
{
|
|
if (manifest.ManifestId == manifestId)
|
|
{
|
|
manifest.ScanCompletedAt = completedAt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// In-memory implementation of IProofBundleRepository for testing.
|
|
/// </summary>
|
|
public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Repositories.IProofBundleRepository
|
|
{
|
|
private readonly ConcurrentDictionary<string, ProofBundleRow> _bundlesByRootHash = new(StringComparer.OrdinalIgnoreCase);
|
|
private readonly ConcurrentDictionary<Guid, List<ProofBundleRow>> _bundlesByScanId = new();
|
|
|
|
public Task<ProofBundleRow?> GetByRootHashAsync(string rootHash, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(_bundlesByRootHash.TryGetValue(rootHash, out var bundle) ? bundle : null);
|
|
}
|
|
|
|
public Task<IReadOnlyList<ProofBundleRow>> GetByScanIdAsync(Guid scanId, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
if (_bundlesByScanId.TryGetValue(scanId, out var bundles))
|
|
{
|
|
return Task.FromResult<IReadOnlyList<ProofBundleRow>>(bundles.ToList());
|
|
}
|
|
|
|
return Task.FromResult<IReadOnlyList<ProofBundleRow>>(Array.Empty<ProofBundleRow>());
|
|
}
|
|
|
|
public Task<ProofBundleRow> SaveAsync(ProofBundleRow bundle, CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(bundle);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
_bundlesByRootHash[bundle.RootHash] = bundle;
|
|
|
|
var scanBundles = _bundlesByScanId.GetOrAdd(bundle.ScanId, _ => new List<ProofBundleRow>());
|
|
|
|
lock (scanBundles)
|
|
{
|
|
// Replace existing if same root hash, otherwise add
|
|
var existingIndex = scanBundles.FindIndex(b => string.Equals(b.RootHash, bundle.RootHash, StringComparison.OrdinalIgnoreCase));
|
|
if (existingIndex >= 0)
|
|
{
|
|
scanBundles[existingIndex] = bundle;
|
|
}
|
|
else
|
|
{
|
|
scanBundles.Add(bundle);
|
|
}
|
|
}
|
|
|
|
return Task.FromResult(bundle);
|
|
}
|
|
|
|
public Task<int> DeleteExpiredAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var expired = _bundlesByRootHash.Values
|
|
.Where(b => b.ExpiresAt.HasValue && b.ExpiresAt.Value < now)
|
|
.ToList();
|
|
|
|
foreach (var bundle in expired)
|
|
{
|
|
_bundlesByRootHash.TryRemove(bundle.RootHash, out _);
|
|
|
|
if (_bundlesByScanId.TryGetValue(bundle.ScanId, out var scanBundles))
|
|
{
|
|
lock (scanBundles)
|
|
{
|
|
scanBundles.RemoveAll(b => string.Equals(b.RootHash, bundle.RootHash, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Task.FromResult(expired.Count);
|
|
}
|
|
}
|