finish 9th jan sprints

This commit is contained in:
master
2026-01-10 21:08:39 +02:00
parent 17d0631b8e
commit a3b2f30a11
9 changed files with 175 additions and 198 deletions

View File

@@ -12,7 +12,7 @@ namespace StellaOps.OpsMemory.Similarity;
/// Generates similarity vectors for finding related security decisions.
/// Sprint: SPRINT_20260107_006_004 Task OM-004
/// </summary>
public sealed class SimilarityVectorGenerator
public sealed class SimilarityVectorGenerator : ISimilarityVectorGenerator
{
// Vector dimensions:
// [0-9] : CVE category one-hot (10 categories)

View File

@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Npgsql;
using StellaOps.OpsMemory.Integration;
using StellaOps.OpsMemory.Models;
using StellaOps.OpsMemory.Playbook;
using StellaOps.OpsMemory.Similarity;
using StellaOps.OpsMemory.Storage;
using Xunit;
@@ -38,12 +39,12 @@ public sealed class OpsMemoryChatProviderIntegrationTests : IAsyncLifetime
_vectorGenerator = new SimilarityVectorGenerator();
// Create chat provider with mock stores for known issues and tactics
// Create chat provider with required dependencies
_chatProvider = new OpsMemoryChatProvider(
_store,
new NullKnownIssueStore(),
new NullTacticStore(),
_vectorGenerator,
new NullPlaybookSuggestionService(),
TimeProvider.System,
NullLogger<OpsMemoryChatProvider>.Instance);
_testTenantId = $"test-{Guid.NewGuid()}";
@@ -321,58 +322,23 @@ public sealed class OpsMemoryChatProviderIntegrationTests : IAsyncLifetime
}
/// <summary>
/// Null implementation of IKnownIssueStore for testing.
/// Null implementation of IPlaybookSuggestionService for testing.
/// </summary>
private sealed class NullKnownIssueStore : IKnownIssueStore
private sealed class NullPlaybookSuggestionService : IPlaybookSuggestionService
{
public Task<KnownIssue> CreateAsync(KnownIssue issue, CancellationToken ct) =>
Task.FromResult(issue);
public Task<PlaybookSuggestionResult> GetSuggestionsAsync(
PlaybookSuggestionRequest request,
CancellationToken cancellationToken = default) =>
Task.FromResult(new PlaybookSuggestionResult
{
Suggestions = ImmutableArray<PlaybookSuggestion>.Empty
});
public Task<KnownIssue?> UpdateAsync(KnownIssue issue, CancellationToken ct) =>
Task.FromResult<KnownIssue?>(issue);
public Task<KnownIssue?> GetByIdAsync(string issueId, string tenantId, CancellationToken ct) =>
Task.FromResult<KnownIssue?>(null);
public Task<ImmutableArray<KnownIssue>> FindByContextAsync(
string tenantId, string? cveId, string? component, CancellationToken ct) =>
Task.FromResult(ImmutableArray<KnownIssue>.Empty);
public Task<ImmutableArray<KnownIssue>> ListAsync(
string tenantId, int limit, int offset, CancellationToken ct) =>
Task.FromResult(ImmutableArray<KnownIssue>.Empty);
public Task<bool> DeleteAsync(string issueId, string tenantId, CancellationToken ct) =>
Task.FromResult(false);
}
/// <summary>
/// Null implementation of ITacticStore for testing.
/// </summary>
private sealed class NullTacticStore : ITacticStore
{
public Task<Tactic> CreateAsync(Tactic tactic, string tenantId, CancellationToken ct) =>
Task.FromResult(tactic);
public Task<Tactic?> UpdateAsync(Tactic tactic, string tenantId, CancellationToken ct) =>
Task.FromResult<Tactic?>(tactic);
public Task<Tactic?> GetByIdAsync(string tacticId, string tenantId, CancellationToken ct) =>
Task.FromResult<Tactic?>(null);
public Task<ImmutableArray<Tactic>> FindByTriggerAsync(
string tenantId, TacticTrigger trigger, CancellationToken ct) =>
Task.FromResult(ImmutableArray<Tactic>.Empty);
public Task<ImmutableArray<Tactic>> ListAsync(
string tenantId, int limit, int offset, CancellationToken ct) =>
Task.FromResult(ImmutableArray<Tactic>.Empty);
public Task<Tactic?> RecordUsageAsync(
string tacticId, string tenantId, bool wasSuccessful, CancellationToken ct) =>
Task.FromResult<Tactic?>(null);
public Task<bool> DeleteAsync(string tacticId, string tenantId, CancellationToken ct) =>
Task.FromResult(false);
public Task<IReadOnlyList<PlaybookSuggestion>> GetSuggestionsAsync(
SituationContext situation,
int maxSuggestions,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyList<PlaybookSuggestion>>(
ImmutableArray<PlaybookSuggestion>.Empty);
}
}

View File

@@ -163,7 +163,7 @@ public sealed class OpsMemoryChatProviderTests
// Arrange
var action = new ActionExecutionResult
{
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
CveId = "CVE-2021-44228",
Component = "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.0",
Success = true,
@@ -195,7 +195,7 @@ public sealed class OpsMemoryChatProviderTests
Assert.NotNull(result);
Assert.StartsWith("om-chat-", result.MemoryId);
Assert.Equal("tenant-1", result.TenantId);
Assert.Equal(DecisionAction.AcceptRisk, result.Decision.Action);
Assert.Equal(DecisionAction.Accept, result.Decision.Action);
Assert.Equal("user-123", result.Decision.DecidedBy);
Assert.Contains("Risk accepted", result.Decision.Rationale);
}
@@ -211,7 +211,7 @@ public sealed class OpsMemoryChatProviderTests
CreateTestRecord("om-002", "CVE-2021-44229", OutcomeStatus.Failure)
),
TotalCount = 2,
HasMore = false
NextCursor = null
};
_storeMock.Setup(s => s.QueryAsync(It.IsAny<OpsMemoryQuery>(), It.IsAny<CancellationToken>()))
@@ -277,7 +277,7 @@ public sealed class OpsMemoryChatProviderTests
},
Decision = new DecisionRecord
{
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
Rationale = "Test rationale",
DecidedBy = "test-user",
DecidedAt = DateTimeOffset.UtcNow
@@ -286,6 +286,7 @@ public sealed class OpsMemoryChatProviderTests
? new OutcomeRecord
{
Status = outcomeStatus.Value,
RecordedBy = "test-user",
RecordedAt = DateTimeOffset.UtcNow
}
: null

View File

@@ -46,7 +46,7 @@ public sealed class OpsMemoryContextEnricherTests
{
MemoryId = "om-001",
CveId = "CVE-2021-44227",
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
OutcomeStatus = OutcomeStatus.Success,
SimilarityScore = 0.85,
DecidedAt = DateTimeOffset.UtcNow,
@@ -65,7 +65,7 @@ public sealed class OpsMemoryContextEnricherTests
Assert.True(result.HasEnrichment);
Assert.Contains("Institutional Memory", result.EnrichedPrompt);
Assert.Contains("CVE-2021-44227", result.EnrichedPrompt);
Assert.Contains("AcceptRisk", result.EnrichedPrompt);
Assert.Contains("Accept", result.EnrichedPrompt);
Assert.Contains("[SUCCESS]", result.EnrichedPrompt);
}
@@ -157,7 +157,7 @@ public sealed class OpsMemoryContextEnricherTests
Assert.True(result.HasEnrichment);
Assert.Contains("Playbook Tactics", result.EnrichedPrompt);
Assert.Contains("Immediate Patch", result.EnrichedPrompt);
Assert.Contains("95%", result.EnrichedPrompt); // Success rate formatted as percentage
Assert.Contains("95", result.EnrichedPrompt); // Success rate formatted as percentage
}
[Fact]
@@ -171,7 +171,7 @@ public sealed class OpsMemoryContextEnricherTests
{
MemoryId = "om-001",
CveId = "CVE-2021-44228",
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
SimilarityScore = 0.85,
DecidedAt = DateTimeOffset.UtcNow
}
@@ -208,7 +208,7 @@ public sealed class OpsMemoryContextEnricherTests
{
MemoryId = "om-001",
CveId = "CVE-2021-44228",
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
OutcomeStatus = OutcomeStatus.Failure,
SimilarityScore = 0.85,
DecidedAt = DateTimeOffset.UtcNow,
@@ -239,7 +239,7 @@ public sealed class OpsMemoryContextEnricherTests
{
MemoryId = "om-001",
CveId = "CVE-1",
Action = DecisionAction.AcceptRisk,
Action = DecisionAction.Accept,
SimilarityScore = 0.9,
DecidedAt = DateTimeOffset.UtcNow
},