up
Some checks failed
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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
internal sealed class InMemoryCallgraphRepository : ICallgraphRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CallgraphDocument> _store = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Task<CallgraphDocument> UpsertAsync(CallgraphDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(document.Id))
|
||||
{
|
||||
document.Id = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
_store[document.Id] = Clone(document);
|
||||
return Task.FromResult(Clone(document));
|
||||
}
|
||||
|
||||
public Task<CallgraphDocument?> GetByIdAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return Task.FromResult<CallgraphDocument?>(null);
|
||||
}
|
||||
|
||||
return Task.FromResult(_store.TryGetValue(id, out var doc) ? Clone(doc) : null);
|
||||
}
|
||||
|
||||
private static CallgraphDocument Clone(CallgraphDocument source) => new()
|
||||
{
|
||||
Id = source.Id,
|
||||
Language = source.Language,
|
||||
Component = source.Component,
|
||||
Version = source.Version,
|
||||
IngestedAt = source.IngestedAt,
|
||||
Artifact = CloneArtifact(source.Artifact),
|
||||
Nodes = source.Nodes.Select(CloneNode).ToList(),
|
||||
Edges = source.Edges.Select(CloneEdge).ToList(),
|
||||
Metadata = source.Metadata is null ? null : new Dictionary<string, string?>(source.Metadata, StringComparer.OrdinalIgnoreCase),
|
||||
GraphHash = source.GraphHash,
|
||||
Roots = source.Roots?.Select(r => new CallgraphRoot(r.Id, r.Phase, r.Source)).ToList(),
|
||||
SchemaVersion = source.SchemaVersion
|
||||
};
|
||||
|
||||
private static CallgraphArtifactMetadata CloneArtifact(CallgraphArtifactMetadata source) => new()
|
||||
{
|
||||
Path = source.Path,
|
||||
Hash = source.Hash,
|
||||
CasUri = source.CasUri,
|
||||
ManifestPath = source.ManifestPath,
|
||||
ManifestCasUri = source.ManifestCasUri,
|
||||
GraphHash = source.GraphHash,
|
||||
ContentType = source.ContentType,
|
||||
Length = source.Length
|
||||
};
|
||||
|
||||
private static CallgraphNode CloneNode(CallgraphNode source) => new(
|
||||
source.Id,
|
||||
source.Name,
|
||||
source.Kind,
|
||||
source.Namespace,
|
||||
source.File,
|
||||
source.Line,
|
||||
source.Purl,
|
||||
source.SymbolDigest,
|
||||
source.BuildId,
|
||||
source.Language,
|
||||
source.Evidence?.ToList(),
|
||||
source.Analyzer is null ? null : new Dictionary<string, string?>(source.Analyzer, StringComparer.OrdinalIgnoreCase),
|
||||
source.CodeId);
|
||||
|
||||
private static CallgraphEdge CloneEdge(CallgraphEdge source) => new(
|
||||
source.SourceId,
|
||||
source.TargetId,
|
||||
source.Type,
|
||||
source.Purl,
|
||||
source.SymbolDigest,
|
||||
source.Candidates?.ToList(),
|
||||
source.Confidence,
|
||||
source.Evidence?.ToList());
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
internal sealed class InMemoryReachabilityFactRepository : IReachabilityFactRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ReachabilityFactDocument> _store = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Task<ReachabilityFactDocument> UpsertAsync(ReachabilityFactDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
if (string.IsNullOrWhiteSpace(document.SubjectKey))
|
||||
{
|
||||
throw new ArgumentException("Subject key is required.", nameof(document));
|
||||
}
|
||||
|
||||
_store[document.SubjectKey] = Clone(document);
|
||||
return Task.FromResult(Clone(document));
|
||||
}
|
||||
|
||||
public Task<ReachabilityFactDocument?> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(subjectKey))
|
||||
{
|
||||
throw new ArgumentException("Subject key is required.", nameof(subjectKey));
|
||||
}
|
||||
|
||||
return Task.FromResult(_store.TryGetValue(subjectKey, out var doc) ? Clone(doc) : null);
|
||||
}
|
||||
|
||||
private static ReachabilityFactDocument Clone(ReachabilityFactDocument source) => new()
|
||||
{
|
||||
Id = source.Id,
|
||||
CallgraphId = source.CallgraphId,
|
||||
Subject = source.Subject,
|
||||
EntryPoints = source.EntryPoints.ToList(),
|
||||
States = source.States.Select(CloneState).ToList(),
|
||||
RuntimeFacts = source.RuntimeFacts?.Select(CloneRuntime).ToList(),
|
||||
Metadata = source.Metadata is null ? null : new Dictionary<string, string?>(source.Metadata, StringComparer.OrdinalIgnoreCase),
|
||||
ContextFacts = source.ContextFacts,
|
||||
Score = source.Score,
|
||||
UnknownsCount = source.UnknownsCount,
|
||||
UnknownsPressure = source.UnknownsPressure,
|
||||
ComputedAt = source.ComputedAt,
|
||||
SubjectKey = source.SubjectKey
|
||||
};
|
||||
|
||||
private static ReachabilityStateDocument CloneState(ReachabilityStateDocument source) => new()
|
||||
{
|
||||
Target = source.Target,
|
||||
Reachable = source.Reachable,
|
||||
Confidence = source.Confidence,
|
||||
Bucket = source.Bucket,
|
||||
Weight = source.Weight,
|
||||
Score = source.Score,
|
||||
Path = source.Path.ToList(),
|
||||
Evidence = new ReachabilityEvidenceDocument
|
||||
{
|
||||
RuntimeHits = source.Evidence.RuntimeHits.ToList(),
|
||||
BlockedEdges = source.Evidence.BlockedEdges?.ToList()
|
||||
}
|
||||
};
|
||||
|
||||
private static RuntimeFactDocument CloneRuntime(RuntimeFactDocument source) => new()
|
||||
{
|
||||
SymbolId = source.SymbolId,
|
||||
CodeId = source.CodeId,
|
||||
SymbolDigest = source.SymbolDigest,
|
||||
Purl = source.Purl,
|
||||
BuildId = source.BuildId,
|
||||
LoaderBase = source.LoaderBase,
|
||||
ProcessId = source.ProcessId,
|
||||
ProcessName = source.ProcessName,
|
||||
SocketAddress = source.SocketAddress,
|
||||
ContainerId = source.ContainerId,
|
||||
EvidenceUri = source.EvidenceUri,
|
||||
HitCount = source.HitCount,
|
||||
ObservedAt = source.ObservedAt,
|
||||
Metadata = source.Metadata is null ? null : new Dictionary<string, string?>(source.Metadata, StringComparer.OrdinalIgnoreCase)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
public sealed class InMemoryUnknownsRepository : IUnknownsRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, List<UnknownSymbolDocument>> _store = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Task UpsertAsync(string subjectKey, IEnumerable<UnknownSymbolDocument> items, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
_store[subjectKey] = items.Select(Clone).ToList();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<UnknownSymbolDocument>> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
|
||||
if (_store.TryGetValue(subjectKey, out var items))
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<UnknownSymbolDocument>>(items.Select(Clone).ToList());
|
||||
}
|
||||
|
||||
return Task.FromResult<IReadOnlyList<UnknownSymbolDocument>>(Array.Empty<UnknownSymbolDocument>());
|
||||
}
|
||||
|
||||
public Task<int> CountBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
|
||||
return Task.FromResult(_store.TryGetValue(subjectKey, out var items) ? items.Count : 0);
|
||||
}
|
||||
|
||||
private static UnknownSymbolDocument Clone(UnknownSymbolDocument source) => new()
|
||||
{
|
||||
Id = source.Id,
|
||||
SubjectKey = source.SubjectKey,
|
||||
CallgraphId = source.CallgraphId,
|
||||
SymbolId = source.SymbolId,
|
||||
CodeId = source.CodeId,
|
||||
Purl = source.Purl,
|
||||
EdgeFrom = source.EdgeFrom,
|
||||
EdgeTo = source.EdgeTo,
|
||||
Reason = source.Reason,
|
||||
CreatedAt = source.CreatedAt
|
||||
};
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
internal sealed class MongoCallgraphRepository : ICallgraphRepository
|
||||
{
|
||||
private readonly IMongoCollection<CallgraphDocument> collection;
|
||||
private readonly ILogger<MongoCallgraphRepository> logger;
|
||||
|
||||
public MongoCallgraphRepository(IMongoCollection<CallgraphDocument> collection, ILogger<MongoCallgraphRepository> logger)
|
||||
{
|
||||
this.collection = collection ?? throw new ArgumentNullException(nameof(collection));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<CallgraphDocument> UpsertAsync(CallgraphDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
var filter = Builders<CallgraphDocument>.Filter.Eq(d => d.Component, document.Component)
|
||||
& Builders<CallgraphDocument>.Filter.Eq(d => d.Version, document.Version)
|
||||
& Builders<CallgraphDocument>.Filter.Eq(d => d.Language, document.Language);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(document.Id))
|
||||
{
|
||||
document.Id = ObjectId.GenerateNewId().ToString();
|
||||
}
|
||||
|
||||
document.IngestedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
var options = new ReplaceOptions { IsUpsert = true };
|
||||
var result = await collection.ReplaceOneAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result.UpsertedId != null)
|
||||
{
|
||||
document.Id = result.UpsertedId.AsObjectId.ToString();
|
||||
}
|
||||
|
||||
logger.LogInformation("Upserted callgraph {Language}:{Component}:{Version} (id={Id}).", document.Language, document.Component, document.Version, document.Id);
|
||||
return document;
|
||||
}
|
||||
|
||||
public async Task<CallgraphDocument?> GetByIdAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
throw new ArgumentException("Callgraph id is required.", nameof(id));
|
||||
}
|
||||
|
||||
var filter = Builders<CallgraphDocument>.Filter.Eq(d => d.Id, id);
|
||||
return await collection.Find(filter).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
internal sealed class MongoReachabilityFactRepository : IReachabilityFactRepository
|
||||
{
|
||||
private readonly IMongoCollection<ReachabilityFactDocument> collection;
|
||||
private readonly ILogger<MongoReachabilityFactRepository> logger;
|
||||
|
||||
public MongoReachabilityFactRepository(
|
||||
IMongoCollection<ReachabilityFactDocument> collection,
|
||||
ILogger<MongoReachabilityFactRepository> logger)
|
||||
{
|
||||
this.collection = collection ?? throw new ArgumentNullException(nameof(collection));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<ReachabilityFactDocument> UpsertAsync(ReachabilityFactDocument document, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
if (string.IsNullOrWhiteSpace(document.SubjectKey))
|
||||
{
|
||||
throw new ArgumentException("Subject key is required.", nameof(document));
|
||||
}
|
||||
|
||||
var filter = Builders<ReachabilityFactDocument>.Filter.Eq(d => d.SubjectKey, document.SubjectKey);
|
||||
var options = new ReplaceOptions { IsUpsert = true };
|
||||
var result = await collection.ReplaceOneAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
|
||||
if (result.UpsertedId != null)
|
||||
{
|
||||
document.Id = result.UpsertedId.AsObjectId.ToString();
|
||||
}
|
||||
|
||||
logger.LogInformation("Upserted reachability fact for subject {SubjectKey} (callgraph={CallgraphId}).", document.SubjectKey, document.CallgraphId);
|
||||
return document;
|
||||
}
|
||||
|
||||
public async Task<ReachabilityFactDocument?> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(subjectKey))
|
||||
{
|
||||
throw new ArgumentException("Subject key is required.", nameof(subjectKey));
|
||||
}
|
||||
|
||||
var filter = Builders<ReachabilityFactDocument>.Filter.Eq(d => d.SubjectKey, subjectKey);
|
||||
return await collection.Find(filter).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Signals.Models;
|
||||
|
||||
namespace StellaOps.Signals.Persistence;
|
||||
|
||||
public sealed class MongoUnknownsRepository : IUnknownsRepository
|
||||
{
|
||||
private readonly IMongoCollection<UnknownSymbolDocument> collection;
|
||||
|
||||
public MongoUnknownsRepository(IMongoCollection<UnknownSymbolDocument> collection)
|
||||
{
|
||||
this.collection = collection ?? throw new ArgumentNullException(nameof(collection));
|
||||
}
|
||||
|
||||
public async Task UpsertAsync(string subjectKey, IEnumerable<UnknownSymbolDocument> items, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
// deterministic replace per subject to keep the registry stable
|
||||
await collection.DeleteManyAsync(doc => doc.SubjectKey == subjectKey, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var batch = items.ToList();
|
||||
if (batch.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await collection.InsertManyAsync(batch, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<UnknownSymbolDocument>> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
|
||||
var cursor = await collection.FindAsync(doc => doc.SubjectKey == subjectKey, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<int> CountBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectKey);
|
||||
|
||||
var count = await collection.CountDocumentsAsync(doc => doc.SubjectKey == subjectKey, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return (int)count;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user