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,76 @@
using Blake3;
using StellaOps.Symbols.Core.Models;
using System.Globalization;
using System.Text;
namespace StellaOps.Symbols.Infrastructure.Hashing;
/// <summary>
/// Deterministic BLAKE3 hashing helpers for Symbols artifacts and manifests.
/// </summary>
public static class SymbolHashing
{
/// <summary>
/// Computes a BLAKE3 digest string with algorithm prefix.
/// </summary>
public static string ComputeHash(ReadOnlySpan<byte> bytes)
{
using var hasher = Hasher.New();
hasher.Update(bytes);
return $"blake3:{Convert.ToHexStringLower(hasher.Finalize().AsSpan())}";
}
/// <summary>
/// Computes a deterministic manifest identifier from manifest identity inputs.
/// </summary>
public static string ComputeManifestId(
string debugId,
string tenantId,
IReadOnlyList<SymbolEntry> symbols)
{
ArgumentException.ThrowIfNullOrWhiteSpace(debugId);
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentNullException.ThrowIfNull(symbols);
var builder = new StringBuilder(capacity: 256 + (symbols.Count * 96));
builder.Append("debug=").Append(debugId.Trim()).Append('\n');
builder.Append("tenant=").Append(tenantId.Trim()).Append('\n');
foreach (var line in symbols
.Select(SerializeSymbolEntry)
.OrderBy(static value => value, StringComparer.Ordinal))
{
builder.Append(line).Append('\n');
}
return ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
}
/// <summary>
/// Extracts lowercase hexadecimal digest bytes from a prefixed hash value.
/// </summary>
public static string ExtractHex(string hash)
{
ArgumentException.ThrowIfNullOrWhiteSpace(hash);
const string prefix = "blake3:";
if (hash.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return hash[prefix.Length..].ToLowerInvariant();
}
return hash.ToLowerInvariant();
}
private static string SerializeSymbolEntry(SymbolEntry symbol)
{
ArgumentNullException.ThrowIfNull(symbol);
static string N(string? value) => value?.Trim() ?? string.Empty;
static string NInt(int? value) => value?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
return string.Create(
CultureInfo.InvariantCulture,
$"addr={symbol.Address:x16}|size={symbol.Size}|m={N(symbol.MangledName)}|d={N(symbol.DemangledName)}|t={symbol.Type}|b={symbol.Binding}|sf={N(symbol.SourceFile)}|sl={NInt(symbol.SourceLine)}|h={N(symbol.ContentHash)}");
}
}

View File

@@ -0,0 +1,158 @@
using StellaOps.Symbols.Core.Abstractions;
using StellaOps.Symbols.Core.Models;
namespace StellaOps.Symbols.Infrastructure.Resolution;
/// <summary>
/// Default implementation of symbol resolver using the symbol repository.
/// </summary>
public sealed class DefaultSymbolResolver : ISymbolResolver
{
private readonly ISymbolRepository _repository;
public DefaultSymbolResolver(ISymbolRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
/// <inheritdoc/>
public async Task<SymbolResolution?> ResolveAsync(
string debugId,
ulong address,
string? tenantId = null,
CancellationToken cancellationToken = default)
{
var manifests = await _repository.GetManifestsByDebugIdAsync(debugId, tenantId, cancellationToken)
.ConfigureAwait(false);
foreach (var manifest in manifests)
{
var symbol = FindSymbolAtAddress(manifest.Symbols, address);
if (symbol is not null)
{
return new SymbolResolution
{
Address = address,
Found = true,
Symbol = symbol,
Offset = address - symbol.Address,
DebugId = debugId,
ManifestId = manifest.ManifestId,
Confidence = 1.0
};
}
}
return new SymbolResolution
{
Address = address,
Found = false,
DebugId = debugId,
Confidence = 0.0
};
}
/// <inheritdoc/>
public async Task<IReadOnlyList<SymbolResolution>> ResolveBatchAsync(
string debugId,
IEnumerable<ulong> addresses,
string? tenantId = null,
CancellationToken cancellationToken = default)
{
var manifests = await _repository.GetManifestsByDebugIdAsync(debugId, tenantId, cancellationToken)
.ConfigureAwait(false);
var results = new List<SymbolResolution>();
foreach (var address in addresses)
{
SymbolResolution? resolution = null;
foreach (var manifest in manifests)
{
var symbol = FindSymbolAtAddress(manifest.Symbols, address);
if (symbol is not null)
{
resolution = new SymbolResolution
{
Address = address,
Found = true,
Symbol = symbol,
Offset = address - symbol.Address,
DebugId = debugId,
ManifestId = manifest.ManifestId,
Confidence = 1.0
};
break;
}
}
results.Add(resolution ?? new SymbolResolution
{
Address = address,
Found = false,
DebugId = debugId,
Confidence = 0.0
});
}
return results;
}
/// <inheritdoc/>
public async Task<IReadOnlyList<SymbolEntry>> GetAllSymbolsAsync(
string debugId,
string? tenantId = null,
SymbolType? typeFilter = null,
CancellationToken cancellationToken = default)
{
var manifests = await _repository.GetManifestsByDebugIdAsync(debugId, tenantId, cancellationToken)
.ConfigureAwait(false);
var symbols = manifests
.SelectMany(m => m.Symbols)
.Where(s => !typeFilter.HasValue || s.Type == typeFilter.Value)
.DistinctBy(s => s.Address)
.OrderBy(s => s.Address)
.ToList();
return symbols;
}
private static SymbolEntry? FindSymbolAtAddress(IReadOnlyList<SymbolEntry> symbols, ulong address)
{
// Binary search for the symbol containing the address
var left = 0;
var right = symbols.Count - 1;
SymbolEntry? candidate = null;
while (left <= right)
{
var mid = left + (right - left) / 2;
var symbol = symbols[mid];
if (address >= symbol.Address && address < symbol.Address + symbol.Size)
{
return symbol; // Exact match within symbol bounds
}
if (address >= symbol.Address)
{
candidate = symbol;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
// If we have a candidate and address is within reasonable range, return it
if (candidate is not null && address >= candidate.Address && address < candidate.Address + Math.Max(candidate.Size, 4096))
{
return candidate;
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Symbols.Core.Abstractions;
using StellaOps.Symbols.Infrastructure.Resolution;
using StellaOps.Symbols.Infrastructure.Storage;
namespace StellaOps.Symbols.Infrastructure;
/// <summary>
/// Service collection extensions for Symbols infrastructure.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds in-memory symbol services for development and testing.
/// </summary>
public static IServiceCollection AddSymbolsInMemory(this IServiceCollection services)
{
services.TryAddSingleton<ISymbolRepository, InMemorySymbolRepository>();
services.TryAddSingleton<ISymbolBlobStore, InMemorySymbolBlobStore>();
services.TryAddSingleton<ISymbolResolver, DefaultSymbolResolver>();
return services;
}
/// <summary>
/// Adds the default symbol resolver.
/// </summary>
public static IServiceCollection AddSymbolResolver(this IServiceCollection services)
{
services.TryAddSingleton<ISymbolResolver, DefaultSymbolResolver>();
return services;
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blake3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Symbols.Core\StellaOps.Symbols.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,103 @@
using StellaOps.Symbols.Core.Abstractions;
using StellaOps.Symbols.Infrastructure.Hashing;
using System.Collections.Concurrent;
namespace StellaOps.Symbols.Infrastructure.Storage;
/// <summary>
/// In-memory implementation of symbol blob store for development and testing.
/// </summary>
public sealed class InMemorySymbolBlobStore : ISymbolBlobStore
{
private readonly ConcurrentDictionary<string, BlobEntry> _blobs = new();
/// <inheritdoc/>
public async Task<SymbolBlobUploadResult> UploadAsync(
Stream content,
string tenantId,
string debugId,
string? fileName = null,
CancellationToken cancellationToken = default)
{
using var ms = new MemoryStream();
await content.CopyToAsync(ms, cancellationToken).ConfigureAwait(false);
var data = ms.ToArray();
var contentHash = SymbolHashing.ComputeHash(data);
var blobUri = $"cas://symbols/{tenantId}/{debugId}/{SymbolHashing.ExtractHex(contentHash)}";
var isDuplicate = _blobs.ContainsKey(blobUri);
var entry = new BlobEntry(
Data: data,
ContentHash: contentHash,
TenantId: tenantId,
DebugId: debugId,
FileName: fileName,
ContentType: "application/octet-stream",
CreatedAt: DateTimeOffset.UtcNow);
_blobs[blobUri] = entry;
return new SymbolBlobUploadResult
{
BlobUri = blobUri,
ContentHash = contentHash,
Size = data.Length,
IsDuplicate = isDuplicate
};
}
/// <inheritdoc/>
public Task<Stream?> DownloadAsync(string blobUri, CancellationToken cancellationToken = default)
{
if (!_blobs.TryGetValue(blobUri, out var entry))
{
return Task.FromResult<Stream?>(null);
}
return Task.FromResult<Stream?>(new MemoryStream(entry.Data));
}
/// <inheritdoc/>
public Task<bool> ExistsAsync(string blobUri, CancellationToken cancellationToken = default)
{
return Task.FromResult(_blobs.ContainsKey(blobUri));
}
/// <inheritdoc/>
public Task<SymbolBlobMetadata?> GetMetadataAsync(string blobUri, CancellationToken cancellationToken = default)
{
if (!_blobs.TryGetValue(blobUri, out var entry))
{
return Task.FromResult<SymbolBlobMetadata?>(null);
}
return Task.FromResult<SymbolBlobMetadata?>(new SymbolBlobMetadata
{
BlobUri = blobUri,
ContentHash = entry.ContentHash,
Size = entry.Data.Length,
ContentType = entry.ContentType,
CreatedAt = entry.CreatedAt,
TenantId = entry.TenantId,
DebugId = entry.DebugId
});
}
/// <inheritdoc/>
public Task<bool> DeleteAsync(string blobUri, string reason, CancellationToken cancellationToken = default)
{
return Task.FromResult(_blobs.TryRemove(blobUri, out _));
}
private sealed record BlobEntry(
byte[] Data,
string ContentHash,
string TenantId,
string DebugId,
string? FileName,
string ContentType,
DateTimeOffset CreatedAt);
}

View File

@@ -0,0 +1,160 @@
using StellaOps.Symbols.Core.Abstractions;
using StellaOps.Symbols.Core.Models;
using System.Collections.Concurrent;
namespace StellaOps.Symbols.Infrastructure.Storage;
/// <summary>
/// In-memory implementation of symbol repository for development and testing.
/// </summary>
public sealed class InMemorySymbolRepository : ISymbolRepository
{
private readonly ConcurrentDictionary<string, SymbolManifest> _manifests = new();
private readonly ConcurrentDictionary<string, HashSet<string>> _debugIdIndex = new();
private readonly ConcurrentDictionary<string, HashSet<string>> _codeIdIndex = new();
/// <inheritdoc/>
public Task<string> StoreManifestAsync(SymbolManifest manifest, CancellationToken cancellationToken = default)
{
_manifests[manifest.ManifestId] = manifest;
// Update debug ID index
_debugIdIndex.AddOrUpdate(
manifest.DebugId,
_ => [manifest.ManifestId],
(_, set) => { set.Add(manifest.ManifestId); return set; });
// Update code ID index if present
if (!string.IsNullOrEmpty(manifest.CodeId))
{
_codeIdIndex.AddOrUpdate(
manifest.CodeId,
_ => [manifest.ManifestId],
(_, set) => { set.Add(manifest.ManifestId); return set; });
}
return Task.FromResult(manifest.ManifestId);
}
/// <inheritdoc/>
public Task<SymbolManifest?> GetManifestAsync(string manifestId, CancellationToken cancellationToken = default)
{
_manifests.TryGetValue(manifestId, out var manifest);
return Task.FromResult(manifest);
}
/// <inheritdoc/>
public Task<IReadOnlyList<SymbolManifest>> GetManifestsByDebugIdAsync(
string debugId,
string? tenantId = null,
CancellationToken cancellationToken = default)
{
if (!_debugIdIndex.TryGetValue(debugId, out var ids))
{
return Task.FromResult<IReadOnlyList<SymbolManifest>>([]);
}
var manifests = ids
.Select(id => _manifests.GetValueOrDefault(id))
.Where(m => m is not null && (tenantId is null || m.TenantId == tenantId))
.Cast<SymbolManifest>()
.OrderByDescending(m => m.CreatedAt)
.ToList();
return Task.FromResult<IReadOnlyList<SymbolManifest>>(manifests);
}
/// <inheritdoc/>
public Task<IReadOnlyList<SymbolManifest>> GetManifestsByCodeIdAsync(
string codeId,
string? tenantId = null,
CancellationToken cancellationToken = default)
{
if (!_codeIdIndex.TryGetValue(codeId, out var ids))
{
return Task.FromResult<IReadOnlyList<SymbolManifest>>([]);
}
var manifests = ids
.Select(id => _manifests.GetValueOrDefault(id))
.Where(m => m is not null && (tenantId is null || m.TenantId == tenantId))
.Cast<SymbolManifest>()
.OrderByDescending(m => m.CreatedAt)
.ToList();
return Task.FromResult<IReadOnlyList<SymbolManifest>>(manifests);
}
/// <inheritdoc/>
public Task<SymbolQueryResult> QueryManifestsAsync(SymbolQuery query, CancellationToken cancellationToken = default)
{
var manifests = _manifests.Values.AsEnumerable();
if (!string.IsNullOrEmpty(query.TenantId))
manifests = manifests.Where(m => m.TenantId == query.TenantId);
if (!string.IsNullOrEmpty(query.DebugId))
manifests = manifests.Where(m => m.DebugId == query.DebugId);
if (!string.IsNullOrEmpty(query.CodeId))
manifests = manifests.Where(m => m.CodeId == query.CodeId);
if (!string.IsNullOrEmpty(query.BinaryName))
manifests = manifests.Where(m => m.BinaryName.Contains(query.BinaryName, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(query.Platform))
manifests = manifests.Where(m => m.Platform == query.Platform);
if (query.Format.HasValue)
manifests = manifests.Where(m => m.Format == query.Format.Value);
if (query.CreatedAfter.HasValue)
manifests = manifests.Where(m => m.CreatedAt >= query.CreatedAfter.Value);
if (query.CreatedBefore.HasValue)
manifests = manifests.Where(m => m.CreatedAt <= query.CreatedBefore.Value);
if (query.HasDsse.HasValue)
manifests = manifests.Where(m => !string.IsNullOrEmpty(m.DsseDigest) == query.HasDsse.Value);
var total = manifests.Count();
manifests = query.SortDescending
? manifests.OrderByDescending(m => m.CreatedAt)
: manifests.OrderBy(m => m.CreatedAt);
var result = manifests
.Skip(query.Offset)
.Take(query.Limit)
.ToList();
return Task.FromResult(new SymbolQueryResult
{
Manifests = result,
TotalCount = total,
Offset = query.Offset,
Limit = query.Limit
});
}
/// <inheritdoc/>
public Task<bool> ExistsAsync(string manifestId, CancellationToken cancellationToken = default)
{
return Task.FromResult(_manifests.ContainsKey(manifestId));
}
/// <inheritdoc/>
public Task<bool> DeleteManifestAsync(string manifestId, string reason, CancellationToken cancellationToken = default)
{
if (!_manifests.TryRemove(manifestId, out var manifest))
{
return Task.FromResult(false);
}
// Remove from indexes
if (_debugIdIndex.TryGetValue(manifest.DebugId, out var debugSet))
{
debugSet.Remove(manifestId);
}
if (!string.IsNullOrEmpty(manifest.CodeId) && _codeIdIndex.TryGetValue(manifest.CodeId, out var codeSet))
{
codeSet.Remove(manifestId);
}
return Task.FromResult(true);
}
}

View File

@@ -0,0 +1,8 @@
# StellaOps.Symbols.Infrastructure 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/Symbols/StellaOps.Symbols.Infrastructure/StellaOps.Symbols.Infrastructure.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |