release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.ChangeTrace.Models;
|
||||
using StellaOps.Scanner.ChangeTrace.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.ChangeTrace.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Builder for constructing change traces from scan or binary comparisons.
|
||||
/// </summary>
|
||||
public sealed class ChangeTraceBuilder : IChangeTraceBuilder
|
||||
{
|
||||
private readonly ILogger<ChangeTraceBuilder> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Current engine version.
|
||||
/// </summary>
|
||||
public static string EngineVersion { get; } = Assembly.GetExecutingAssembly()
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
|
||||
?? "1.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ChangeTraceBuilder.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
/// <param name="timeProvider">Time provider for deterministic timestamps.</param>
|
||||
public ChangeTraceBuilder(ILogger<ChangeTraceBuilder> logger, TimeProvider timeProvider)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Models.ChangeTrace> FromScanComparisonAsync(
|
||||
string fromScanId,
|
||||
string toScanId,
|
||||
ChangeTraceBuilderOptions? options = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(fromScanId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(toScanId);
|
||||
|
||||
options ??= ChangeTraceBuilderOptions.Default;
|
||||
|
||||
_logger.LogInformation("Building change trace from scan comparison: {FromScanId} -> {ToScanId}",
|
||||
fromScanId, toScanId);
|
||||
|
||||
// TODO: Integrate with actual scan repository to fetch scan data
|
||||
// For now, create a placeholder trace structure
|
||||
var trace = BuildPlaceholderTrace(fromScanId, toScanId, options);
|
||||
var finalTrace = FinalizeTrace(trace);
|
||||
|
||||
return Task.FromResult(finalTrace);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Models.ChangeTrace> FromBinaryComparisonAsync(
|
||||
string fromBinaryPath,
|
||||
string toBinaryPath,
|
||||
ChangeTraceBuilderOptions? options = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(fromBinaryPath);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(toBinaryPath);
|
||||
|
||||
if (!File.Exists(fromBinaryPath))
|
||||
throw new FileNotFoundException("From binary not found", fromBinaryPath);
|
||||
if (!File.Exists(toBinaryPath))
|
||||
throw new FileNotFoundException("To binary not found", toBinaryPath);
|
||||
|
||||
options ??= ChangeTraceBuilderOptions.Default;
|
||||
|
||||
_logger.LogInformation("Building change trace from binary comparison: {FromPath} -> {ToPath}",
|
||||
fromBinaryPath, toBinaryPath);
|
||||
|
||||
// Generate scan IDs from file paths
|
||||
var fromScanId = $"binary:{Path.GetFileName(fromBinaryPath)}";
|
||||
var toScanId = $"binary:{Path.GetFileName(toBinaryPath)}";
|
||||
|
||||
// TODO: Integrate with BinaryIndex for symbol extraction
|
||||
// For now, create a placeholder trace structure
|
||||
var trace = BuildPlaceholderTrace(fromScanId, toScanId, options);
|
||||
var finalTrace = FinalizeTrace(trace);
|
||||
|
||||
return Task.FromResult(finalTrace);
|
||||
}
|
||||
|
||||
private Models.ChangeTrace BuildPlaceholderTrace(
|
||||
string fromScanId,
|
||||
string toScanId,
|
||||
ChangeTraceBuilderOptions options)
|
||||
{
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var combinedScanId = $"{fromScanId}..{toScanId}";
|
||||
|
||||
return new Models.ChangeTrace
|
||||
{
|
||||
Subject = new ChangeTraceSubject
|
||||
{
|
||||
Type = "scan.comparison",
|
||||
Digest = $"sha256:{Guid.Empty:N}",
|
||||
Name = combinedScanId
|
||||
},
|
||||
Basis = new ChangeTraceBasis
|
||||
{
|
||||
ScanId = combinedScanId,
|
||||
FromScanId = fromScanId,
|
||||
ToScanId = toScanId,
|
||||
Policies = options.Policies,
|
||||
DiffMethod = options.GetDiffMethods(),
|
||||
EngineVersion = EngineVersion,
|
||||
AnalyzedAt = now
|
||||
},
|
||||
Deltas = [],
|
||||
Summary = new ChangeTraceSummary
|
||||
{
|
||||
ChangedPackages = 0,
|
||||
ChangedSymbols = 0,
|
||||
ChangedBytes = 0,
|
||||
RiskDelta = 0.0,
|
||||
Verdict = ChangeTraceVerdict.Neutral
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Models.ChangeTrace FinalizeTrace(Models.ChangeTrace trace)
|
||||
{
|
||||
// Compute commitment hash
|
||||
var hash = ChangeTraceSerializer.ComputeCommitmentHash(trace);
|
||||
|
||||
return trace with
|
||||
{
|
||||
Commitment = new ChangeTraceCommitment
|
||||
{
|
||||
Sha256 = hash,
|
||||
Algorithm = "RFC8785+SHA256"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the verdict based on risk delta score.
|
||||
/// </summary>
|
||||
/// <param name="riskDelta">Risk delta score.</param>
|
||||
/// <returns>Verdict classification.</returns>
|
||||
public static ChangeTraceVerdict ComputeVerdict(double riskDelta)
|
||||
{
|
||||
return riskDelta switch
|
||||
{
|
||||
< -0.3 => ChangeTraceVerdict.RiskDown,
|
||||
> 0.3 => ChangeTraceVerdict.RiskUp,
|
||||
_ => ChangeTraceVerdict.Neutral
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes trust delta score from before/after scores.
|
||||
/// Formula: (AfterTrust - BeforeTrust) / max(BeforeTrust, 0.01)
|
||||
/// </summary>
|
||||
/// <param name="beforeTrust">Trust score before change.</param>
|
||||
/// <param name="afterTrust">Trust score after change.</param>
|
||||
/// <returns>Trust delta score.</returns>
|
||||
public static double ComputeTrustDelta(double beforeTrust, double afterTrust)
|
||||
{
|
||||
var denominator = Math.Max(beforeTrust, 0.01);
|
||||
return (afterTrust - beforeTrust) / denominator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.ChangeTrace.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Options for change trace building.
|
||||
/// </summary>
|
||||
public sealed record ChangeTraceBuilderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Include package-level diffing. Default: true.
|
||||
/// </summary>
|
||||
public bool IncludePackageDiff { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Include symbol-level diffing. Default: true.
|
||||
/// </summary>
|
||||
public bool IncludeSymbolDiff { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Include byte-level diffing. Default: false.
|
||||
/// </summary>
|
||||
public bool IncludeByteDiff { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum confidence threshold for symbol matches.
|
||||
/// Default: 0.75.
|
||||
/// </summary>
|
||||
public double MinSymbolConfidence { get; init; } = 0.75;
|
||||
|
||||
/// <summary>
|
||||
/// Rolling hash window size for byte diffing.
|
||||
/// Default: 2048 bytes.
|
||||
/// </summary>
|
||||
public int ByteDiffWindowSize { get; init; } = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum binary size for byte-level analysis (bytes).
|
||||
/// Default: 10MB.
|
||||
/// </summary>
|
||||
public long MaxBinarySize { get; init; } = 10 * 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Lattice policies to apply during trust delta computation.
|
||||
/// Default: ["lattice:default@v3"].
|
||||
/// </summary>
|
||||
public ImmutableArray<string> Policies { get; init; } = ["lattice:default@v3"];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the diff methods enabled based on options.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> GetDiffMethods()
|
||||
{
|
||||
var methods = ImmutableArray.CreateBuilder<string>();
|
||||
if (IncludePackageDiff) methods.Add("pkg");
|
||||
if (IncludeSymbolDiff) methods.Add("symbol");
|
||||
if (IncludeByteDiff) methods.Add("byte");
|
||||
return methods.ToImmutable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default options instance.
|
||||
/// </summary>
|
||||
public static ChangeTraceBuilderOptions Default { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace StellaOps.Scanner.ChangeTrace.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Builder interface for constructing change traces.
|
||||
/// </summary>
|
||||
public interface IChangeTraceBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Build change trace from two scan comparisons.
|
||||
/// </summary>
|
||||
/// <param name="fromScanId">Scan ID of the "before" state.</param>
|
||||
/// <param name="toScanId">Scan ID of the "after" state.</param>
|
||||
/// <param name="options">Builder options for configuring the trace.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Constructed change trace.</returns>
|
||||
Task<Models.ChangeTrace> FromScanComparisonAsync(
|
||||
string fromScanId,
|
||||
string toScanId,
|
||||
ChangeTraceBuilderOptions? options = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Build change trace from two binary files.
|
||||
/// </summary>
|
||||
/// <param name="fromBinaryPath">Path to the "before" binary.</param>
|
||||
/// <param name="toBinaryPath">Path to the "after" binary.</param>
|
||||
/// <param name="options">Builder options for configuring the trace.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Constructed change trace.</returns>
|
||||
Task<Models.ChangeTrace> FromBinaryComparisonAsync(
|
||||
string fromBinaryPath,
|
||||
string toBinaryPath,
|
||||
ChangeTraceBuilderOptions? options = null,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
Reference in New Issue
Block a user