audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -689,7 +689,7 @@ static Task<IResult> HandlePolicyCompile(
BundleName = request.BundleName,
Version = "1.0.0",
RuleCount = request.RuleIds.Count,
CompiledAt = now.ToString("O"),
CompiledAt = now.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
ContentHash = $"sha256:{contentHash}",
SignatureId = null // Would be signed in production
};
@@ -751,10 +751,10 @@ static async Task<IResult> HandleGetConsent(
return Results.Ok(new AiConsentStatusResponse
{
Consented = record.Consented,
ConsentedAt = record.ConsentedAt?.ToString("O"),
ConsentedAt = record.ConsentedAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
ConsentedBy = record.UserId,
Scope = record.Scope,
ExpiresAt = record.ExpiresAt?.ToString("O"),
ExpiresAt = record.ExpiresAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
SessionLevel = record.SessionLevel
});
}
@@ -763,6 +763,7 @@ static async Task<IResult> HandleGrantConsent(
HttpContext httpContext,
AiConsentGrantRequest request,
IAiConsentStore consentStore,
TimeProvider timeProvider,
CancellationToken cancellationToken)
{
if (!request.DataShareAcknowledged)
@@ -786,8 +787,8 @@ static async Task<IResult> HandleGrantConsent(
return Results.Ok(new AiConsentGrantResponse
{
Consented = record.Consented,
ConsentedAt = record.ConsentedAt?.ToString("O") ?? DateTimeOffset.UtcNow.ToString("O"),
ExpiresAt = record.ExpiresAt?.ToString("O")
ConsentedAt = record.ConsentedAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture) ?? timeProvider.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture),
ExpiresAt = record.ExpiresAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture)
});
}
@@ -863,7 +864,7 @@ static async Task<IResult> HandleJustify(
ConfidenceScore = result.ConfidenceScore,
EvidenceSuggestions = result.EvidenceSuggestions,
ModelVersion = result.ModelVersion,
GeneratedAt = result.GeneratedAt.ToString("O"),
GeneratedAt = result.GeneratedAt.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
TraceId = result.TraceId
});
}
@@ -919,7 +920,7 @@ static Task<IResult> HandleGetRateLimits(
Feature = l.Feature,
Limit = l.Limit,
Remaining = l.Remaining,
ResetsAt = l.ResetsAt.ToString("O")
ResetsAt = l.ResetsAt.ToString("O", System.Globalization.CultureInfo.InvariantCulture)
}).ToList();
return Task.FromResult(Results.Ok(response));

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -16,6 +17,7 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
private readonly IExplanationInferenceClient _inferenceClient;
private readonly ICitationExtractor _citationExtractor;
private readonly IExplanationStore _store;
private readonly TimeProvider _timeProvider;
private const double EvidenceBackedThreshold = 0.8;
@@ -24,13 +26,15 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
IExplanationPromptService promptService,
IExplanationInferenceClient inferenceClient,
ICitationExtractor citationExtractor,
IExplanationStore store)
IExplanationStore store,
TimeProvider? timeProvider = null)
{
_evidenceService = evidenceService;
_promptService = promptService;
_inferenceClient = inferenceClient;
_citationExtractor = citationExtractor;
_store = store;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<ExplanationResult> GenerateAsync(ExplanationRequest request, CancellationToken cancellationToken = default)
@@ -91,7 +95,7 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
ModelId = inferenceResult.ModelId,
PromptTemplateVersion = prompt.TemplateVersion,
InputHashes = inputHashes,
GeneratedAt = DateTime.UtcNow.ToString("O"),
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
OutputHash = outputHash
};

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -14,6 +15,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
private readonly IPolicyPromptService _promptService;
private readonly IPolicyInferenceClient _inferenceClient;
private readonly IPolicyIntentStore _intentStore;
private readonly TimeProvider _timeProvider;
private static readonly string[] FewShotExamples = new[]
{
@@ -27,11 +29,13 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
public AiPolicyIntentParser(
IPolicyPromptService promptService,
IPolicyInferenceClient inferenceClient,
IPolicyIntentStore intentStore)
IPolicyIntentStore intentStore,
TimeProvider? timeProvider = null)
{
_promptService = promptService;
_inferenceClient = inferenceClient;
_intentStore = intentStore;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public async Task<PolicyParseResult> ParseAsync(
@@ -61,7 +65,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
Success = intent.Confidence >= 0.7,
ErrorMessage = intent.Confidence < 0.7 ? "Ambiguous input - clarification needed" : null,
ModelId = inferenceResult.ModelId,
ParsedAt = DateTime.UtcNow.ToString("O")
ParsedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
}
@@ -93,7 +97,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
Intent = clarifiedIntent,
Success = clarifiedIntent.Confidence >= 0.8,
ModelId = inferenceResult.ModelId,
ParsedAt = DateTime.UtcNow.ToString("O")
ParsedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@@ -10,6 +11,13 @@ namespace StellaOps.AdvisoryAI.PolicyStudio;
/// </summary>
public sealed class LatticeRuleGenerator : IPolicyRuleGenerator
{
private readonly TimeProvider _timeProvider;
public LatticeRuleGenerator(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public Task<RuleGenerationResult> GenerateAsync(
PolicyIntent intent,
CancellationToken cancellationToken = default)
@@ -58,7 +66,7 @@ public sealed class LatticeRuleGenerator : IPolicyRuleGenerator
Success = true,
Warnings = warnings,
IntentId = intent.IntentId,
GeneratedAt = DateTime.UtcNow.ToString("O")
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
});
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -391,7 +392,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
Warnings = warnings,
ValidationReport = validationReport,
TestReport = testReport,
CompiledAt = _timeProvider.GetUtcNow().ToString("O")
CompiledAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
}
@@ -412,7 +413,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
Warnings = warnings,
ValidationReport = validationReport,
TestReport = testReport,
CompiledAt = DateTime.UtcNow.ToString("O"),
CompiledAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
BundleDigest = bundleDigest
};
}
@@ -492,7 +493,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
ContentDigest = contentDigest,
Signature = string.Empty,
Algorithm = "none",
SignedAt = _timeProvider.GetUtcNow().ToString("O")
SignedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
}
@@ -509,7 +510,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
Algorithm = signature.Algorithm,
KeyId = options.KeyId,
SignerIdentity = options.SignerIdentity,
SignedAt = _timeProvider.GetUtcNow().ToString("O"),
SignedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
CertificateChain = signature.CertificateChain
};
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@@ -10,6 +11,13 @@ namespace StellaOps.AdvisoryAI.PolicyStudio;
/// </summary>
public sealed class PropertyBasedTestSynthesizer : ITestCaseSynthesizer
{
private readonly TimeProvider _timeProvider;
public PropertyBasedTestSynthesizer(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
}
public Task<IReadOnlyList<PolicyTestCase>> SynthesizeAsync(
IReadOnlyList<LatticeRule> rules,
CancellationToken cancellationToken = default)
@@ -53,7 +61,7 @@ public sealed class PropertyBasedTestSynthesizer : ITestCaseSynthesizer
Passed = results.Count(r => r.Passed),
Failed = results.Count(r => !r.Passed),
Results = results,
RunAt = DateTime.UtcNow.ToString("O")
RunAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
});
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.AdvisoryAI.Abstractions;
@@ -106,7 +107,7 @@ internal sealed class ConcelierAdvisoryDocumentProvider : IAdvisoryDocumentProvi
["vendor"] = record.Document.Source.Vendor,
["connector"] = record.Document.Source.Connector,
["content_hash"] = record.Document.Upstream.ContentHash,
["ingested_at"] = record.IngestedAt.UtcDateTime.ToString("O"),
["ingested_at"] = record.IngestedAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture),
};
if (!string.IsNullOrWhiteSpace(record.Document.Source.Stream))

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -88,7 +89,7 @@ public sealed class AiRemediationPlanner : IRemediationPlanner
NotReadyReason = notReadyReason,
ConfidenceScore = inferenceResult.Confidence,
ModelId = inferenceResult.ModelId,
GeneratedAt = _timeProvider.GetUtcNow().ToString("O"),
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
InputHashes = inputHashes,
EvidenceRefs = new List<string> { versionResult.CurrentVersion, versionResult.RecommendedVersion }
};

View File

@@ -1,3 +1,5 @@
using System.Globalization;
namespace StellaOps.AdvisoryAI.Remediation;
/// <summary>
@@ -7,42 +9,56 @@ namespace StellaOps.AdvisoryAI.Remediation;
/// </summary>
public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
{
private readonly TimeProvider _timeProvider;
private readonly Func<Guid> _guidFactory;
private readonly Func<int, int, int> _randomFactory;
public AzureDevOpsPullRequestGenerator(
TimeProvider? timeProvider = null,
Func<Guid>? guidFactory = null,
Func<int, int, int>? randomFactory = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidFactory = guidFactory ?? Guid.NewGuid;
_randomFactory = randomFactory ?? Random.Shared.Next;
}
public string ScmType => "azure-devops";
public Task<PullRequestResult> CreatePullRequestAsync(
RemediationPlan plan,
CancellationToken cancellationToken = default)
{
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
if (!plan.PrReady)
{
return Task.FromResult(new PullRequestResult
{
PrId = $"ado-pr-{Guid.NewGuid():N}",
PrId = $"ado-pr-{_guidFactory():N}",
PrNumber = 0,
Url = string.Empty,
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 = nowStr,
UpdatedAt = nowStr
});
}
var branchName = GenerateBranchName(plan);
var prId = $"ado-pr-{Guid.NewGuid():N}";
var now = DateTime.UtcNow.ToString("O");
var branchName = GenerateBranchName(plan, _timeProvider);
var prId = $"ado-pr-{_guidFactory():N}";
// In a real implementation, this would use Azure DevOps REST API
return Task.FromResult(new PullRequestResult
{
PrId = prId,
PrNumber = new Random().Next(1000, 9999),
PrNumber = _randomFactory(1000, 9999),
Url = $"https://dev.azure.com/{ExtractOrgProject(plan.Request.RepositoryUrl)}/_git/{ExtractRepoName(plan.Request.RepositoryUrl)}/pullrequest/{prId}",
BranchName = branchName,
Status = PullRequestStatus.Creating,
StatusMessage = "Pull request is being created",
CreatedAt = now,
UpdatedAt = now
CreatedAt = nowStr,
UpdatedAt = nowStr
});
}
@@ -50,7 +66,7 @@ public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
string prId,
CancellationToken cancellationToken = default)
{
var now = DateTime.UtcNow.ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
return Task.FromResult(new PullRequestResult
{
PrId = prId,
@@ -80,10 +96,10 @@ public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
return Task.CompletedTask;
}
private static string GenerateBranchName(RemediationPlan plan)
private static string GenerateBranchName(RemediationPlan plan, TimeProvider timeProvider)
{
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd");
var timestamp = timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
return $"stellaops/fix-{vulnId}-{timestamp}";
}

View File

@@ -1,3 +1,5 @@
using System.Globalization;
namespace StellaOps.AdvisoryAI.Remediation;
/// <summary>
@@ -9,11 +11,19 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
{
private readonly IRemediationPlanStore _planStore;
private readonly TimeProvider _timeProvider;
private readonly Func<Guid> _guidFactory;
private readonly Func<int, int, int> _randomFactory;
public GitHubPullRequestGenerator(IRemediationPlanStore planStore, TimeProvider? timeProvider = null)
public GitHubPullRequestGenerator(
IRemediationPlanStore planStore,
TimeProvider? timeProvider = null,
Func<Guid>? guidFactory = null,
Func<int, int, int>? randomFactory = null)
{
_planStore = planStore;
_timeProvider = timeProvider ?? TimeProvider.System;
_guidFactory = guidFactory ?? Guid.NewGuid;
_randomFactory = randomFactory ?? Random.Shared.Next;
}
public string ScmType => "github";
@@ -22,19 +32,20 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
RemediationPlan plan,
CancellationToken cancellationToken = default)
{
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
// Validate plan is PR-ready
if (!plan.PrReady)
{
return new PullRequestResult
{
PrId = $"pr-{Guid.NewGuid():N}",
PrId = $"pr-{_guidFactory():N}",
PrNumber = 0,
Url = string.Empty,
BranchName = string.Empty,
Status = PullRequestStatus.Failed,
StatusMessage = plan.NotReadyReason ?? "Plan is not PR-ready",
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
CreatedAt = nowStr,
UpdatedAt = nowStr
};
}
@@ -47,19 +58,18 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
// 3. Commit changes
// 4. Create PR via GitHub API
var prId = $"gh-pr-{Guid.NewGuid():N}";
var now = _timeProvider.GetUtcNow().ToString("O");
var prId = $"gh-pr-{_guidFactory():N}";
return new PullRequestResult
{
PrId = prId,
PrNumber = new Random().Next(1000, 9999), // Placeholder
PrNumber = _randomFactory(1000, 9999), // Placeholder
Url = $"https://github.com/{ExtractOwnerRepo(plan.Request.RepositoryUrl)}/pull/{prId}",
BranchName = branchName,
Status = PullRequestStatus.Creating,
StatusMessage = "Pull request is being created",
CreatedAt = now,
UpdatedAt = now
CreatedAt = nowStr,
UpdatedAt = nowStr
};
}
@@ -68,7 +78,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
CancellationToken cancellationToken = default)
{
// In a real implementation, this would query GitHub API
var now = _timeProvider.GetUtcNow().ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
return Task.FromResult(new PullRequestResult
{
@@ -104,7 +114,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
private string GenerateBranchName(RemediationPlan plan)
{
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
return $"stellaops/fix-{vulnId}-{timestamp}";
}

View File

@@ -1,3 +1,5 @@
using System.Globalization;
namespace StellaOps.AdvisoryAI.Remediation;
/// <summary>
@@ -8,10 +10,17 @@ namespace StellaOps.AdvisoryAI.Remediation;
public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
{
private readonly TimeProvider _timeProvider;
private readonly Func<Guid> _guidFactory;
private readonly Func<int, int, int> _randomFactory;
public GitLabMergeRequestGenerator(TimeProvider? timeProvider = null)
public GitLabMergeRequestGenerator(
TimeProvider? timeProvider = null,
Func<Guid>? guidFactory = null,
Func<int, int, int>? randomFactory = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_guidFactory = guidFactory ?? Guid.NewGuid;
_randomFactory = randomFactory ?? Random.Shared.Next;
}
public string ScmType => "gitlab";
@@ -20,36 +29,36 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
RemediationPlan plan,
CancellationToken cancellationToken = default)
{
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
if (!plan.PrReady)
{
return Task.FromResult(new PullRequestResult
{
PrId = $"mr-{Guid.NewGuid():N}",
PrId = $"mr-{_guidFactory():N}",
PrNumber = 0,
Url = string.Empty,
BranchName = string.Empty,
Status = PullRequestStatus.Failed,
StatusMessage = plan.NotReadyReason ?? "Plan is not MR-ready",
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
CreatedAt = nowStr,
UpdatedAt = nowStr
});
}
var branchName = GenerateBranchName(plan);
var mrId = $"gl-mr-{Guid.NewGuid():N}";
var now = _timeProvider.GetUtcNow().ToString("O");
var mrId = $"gl-mr-{_guidFactory():N}";
// In a real implementation, this would use GitLab API
return Task.FromResult(new PullRequestResult
{
PrId = mrId,
PrNumber = new Random().Next(1000, 9999),
PrNumber = _randomFactory(1000, 9999),
Url = $"https://gitlab.com/{ExtractProjectPath(plan.Request.RepositoryUrl)}/-/merge_requests/{mrId}",
BranchName = branchName,
Status = PullRequestStatus.Creating,
StatusMessage = "Merge request is being created",
CreatedAt = now,
UpdatedAt = now
CreatedAt = nowStr,
UpdatedAt = nowStr
});
}
@@ -57,7 +66,7 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
string prId,
CancellationToken cancellationToken = default)
{
var now = _timeProvider.GetUtcNow().ToString("O");
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
return Task.FromResult(new PullRequestResult
{
PrId = prId,
@@ -90,7 +99,7 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
private string GenerateBranchName(RemediationPlan plan)
{
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
return $"stellaops/fix-{vulnId}-{timestamp}";
}

View File

@@ -30,7 +30,7 @@ public sealed class SemanticVersionTests
[InlineData("1.0.0-")]
[InlineData("")]
[InlineData(null)]
public void Parse_InvalidInputs_Throws(string value)
public void Parse_InvalidInputs_Throws(string? value)
{
var act = () => SemanticVersion.Parse(value!);
act.Should().Throw<FormatException>();

View File

@@ -12,12 +12,6 @@
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />