This commit is contained in:
StellaOps Bot
2026-01-06 21:03:06 +02:00
841 changed files with 15706 additions and 68106 deletions

View File

@@ -7,7 +7,7 @@
## Working Directory
- Primary: `src/AdvisoryAI/**` (WebService, Worker, Hosting, plugins, tests).
- Docs: `docs/advisory-ai/**`, `docs/policy/assistant-parameters.md`, `docs/sbom/*` when explicitly touched by sprint tasks.
- Docs: `docs/advisory-ai/**`, `docs/policy/assistant-parameters.md`, `docs/modules/sbom-service/*` when explicitly touched by sprint tasks.
- Shared libraries allowed only if referenced by Advisory AI projects; otherwise stay in-module.
## Required Reading (treat as read before DOING)

View File

@@ -317,15 +317,18 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
private readonly IPolicyRuleGenerator _ruleGenerator;
private readonly IPolicyBundleSigner? _signer;
private readonly ILogger<PolicyBundleCompiler> _logger;
private readonly TimeProvider _timeProvider;
public PolicyBundleCompiler(
IPolicyRuleGenerator ruleGenerator,
IPolicyBundleSigner? signer,
ILogger<PolicyBundleCompiler> logger)
ILogger<PolicyBundleCompiler> logger,
TimeProvider? timeProvider = null)
{
_ruleGenerator = ruleGenerator ?? throw new ArgumentNullException(nameof(ruleGenerator));
_signer = signer;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<PolicyCompilationResult> CompileAsync(
@@ -388,7 +391,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
Warnings = warnings,
ValidationReport = validationReport,
TestReport = testReport,
CompiledAt = DateTime.UtcNow.ToString("O")
CompiledAt = _timeProvider.GetUtcNow().ToString("O")
};
}
@@ -425,7 +428,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
// Validate trust roots
foreach (var root in bundle.TrustRoots)
{
if (root.ExpiresAt.HasValue && root.ExpiresAt.Value < DateTimeOffset.UtcNow)
if (root.ExpiresAt.HasValue && root.ExpiresAt.Value < _timeProvider.GetUtcNow())
{
semanticWarnings.Add($"Trust root '{root.Principal.Id}' has expired");
}
@@ -489,7 +492,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
ContentDigest = contentDigest,
Signature = string.Empty,
Algorithm = "none",
SignedAt = DateTime.UtcNow.ToString("O")
SignedAt = _timeProvider.GetUtcNow().ToString("O")
};
}
@@ -506,7 +509,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
Algorithm = signature.Algorithm,
KeyId = options.KeyId,
SignerIdentity = options.SignerIdentity,
SignedAt = DateTime.UtcNow.ToString("O"),
SignedAt = _timeProvider.GetUtcNow().ToString("O"),
CertificateChain = signature.CertificateChain
};
}

View File

@@ -15,17 +15,20 @@ public sealed class AiRemediationPlanner : IRemediationPlanner
private readonly IRemediationPromptService _promptService;
private readonly IRemediationInferenceClient _inferenceClient;
private readonly IRemediationPlanStore _planStore;
private readonly TimeProvider _timeProvider;
public AiRemediationPlanner(
IPackageVersionResolver versionResolver,
IRemediationPromptService promptService,
IRemediationInferenceClient inferenceClient,
IRemediationPlanStore planStore)
IRemediationPlanStore planStore,
TimeProvider? timeProvider = null)
{
_versionResolver = versionResolver;
_promptService = promptService;
_inferenceClient = inferenceClient;
_planStore = planStore;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<RemediationPlan> GeneratePlanAsync(
@@ -85,7 +88,7 @@ public sealed class AiRemediationPlanner : IRemediationPlanner
NotReadyReason = notReadyReason,
ConfidenceScore = inferenceResult.Confidence,
ModelId = inferenceResult.ModelId,
GeneratedAt = DateTime.UtcNow.ToString("O"),
GeneratedAt = _timeProvider.GetUtcNow().ToString("O"),
InputHashes = inputHashes,
EvidenceRefs = new List<string> { versionResult.CurrentVersion, versionResult.RecommendedVersion }
};

View File

@@ -8,10 +8,12 @@ namespace StellaOps.AdvisoryAI.Remediation;
public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
{
private readonly IRemediationPlanStore _planStore;
private readonly TimeProvider _timeProvider;
public GitHubPullRequestGenerator(IRemediationPlanStore planStore)
public GitHubPullRequestGenerator(IRemediationPlanStore planStore, TimeProvider? timeProvider = null)
{
_planStore = planStore;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public string ScmType => "github";
@@ -31,8 +33,8 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
BranchName = string.Empty,
Status = PullRequestStatus.Failed,
StatusMessage = plan.NotReadyReason ?? "Plan is not PR-ready",
CreatedAt = DateTime.UtcNow.ToString("O"),
UpdatedAt = DateTime.UtcNow.ToString("O")
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
};
}
@@ -46,7 +48,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
// 4. Create PR via GitHub API
var prId = $"gh-pr-{Guid.NewGuid():N}";
var now = DateTime.UtcNow.ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O");
return new PullRequestResult
{
@@ -66,7 +68,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
CancellationToken cancellationToken = default)
{
// In a real implementation, this would query GitHub API
var now = DateTime.UtcNow.ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O");
return Task.FromResult(new PullRequestResult
{
@@ -99,10 +101,10 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
return Task.CompletedTask;
}
private static string GenerateBranchName(RemediationPlan plan)
private string GenerateBranchName(RemediationPlan plan)
{
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd");
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
return $"stellaops/fix-{vulnId}-{timestamp}";
}

View File

@@ -7,6 +7,13 @@ namespace StellaOps.AdvisoryAI.Remediation;
/// </summary>
public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
{
private readonly TimeProvider _timeProvider;
public GitLabMergeRequestGenerator(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public string ScmType => "gitlab";
public Task<PullRequestResult> CreatePullRequestAsync(
@@ -23,14 +30,14 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
BranchName = string.Empty,
Status = PullRequestStatus.Failed,
StatusMessage = plan.NotReadyReason ?? "Plan is not MR-ready",
CreatedAt = DateTime.UtcNow.ToString("O"),
UpdatedAt = DateTime.UtcNow.ToString("O")
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
});
}
var branchName = GenerateBranchName(plan);
var mrId = $"gl-mr-{Guid.NewGuid():N}";
var now = DateTime.UtcNow.ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O");
// In a real implementation, this would use GitLab API
return Task.FromResult(new PullRequestResult
@@ -50,7 +57,7 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
string prId,
CancellationToken cancellationToken = default)
{
var now = DateTime.UtcNow.ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O");
return Task.FromResult(new PullRequestResult
{
PrId = prId,
@@ -80,10 +87,10 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
return Task.CompletedTask;
}
private static string GenerateBranchName(RemediationPlan plan)
private string GenerateBranchName(RemediationPlan plan)
{
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd");
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
return $"stellaops/fix-{vulnId}-{timestamp}";
}

View File

@@ -36,7 +36,9 @@ public class SignedModelBundleManagerTests
var envelopePath = Path.Combine(tempRoot, "signature.dsse");
var envelopeJson = await File.ReadAllTextAsync(envelopePath, CancellationToken.None);
var envelope = JsonSerializer.Deserialize<ModelBundleSignatureEnvelope>(envelopeJson);
var envelope = JsonSerializer.Deserialize<ModelBundleSignatureEnvelope>(
envelopeJson,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
Assert.NotNull(envelope);
var payloadJson = Encoding.UTF8.GetString(Convert.FromBase64String(envelope!.Payload));