stabilize tests

This commit is contained in:
master
2026-02-01 21:37:40 +02:00
parent 55744f6a39
commit 5d5e80b2e4
6435 changed files with 33984 additions and 13802 deletions

24
src/Notifier/AGENTS.md Normal file
View File

@@ -0,0 +1,24 @@
# AGENTS - Notifier Module
## Working Directory
- `src/Notifier/**` (WebService, Worker, tests).
## Required Reading
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/notifier/README.md`
- `docs/modules/notify/architecture.md`
- `docs/modules/notify/security/redaction-catalog.md`
## Engineering Rules
- Deterministic delivery behavior; stable ordering for notifications.
- Enforce redaction defaults and avoid secrets in logs.
- Offline-first; no external network calls in tests.
## Testing & Verification
- Tests live in `src/Notifier/StellaOps.Notifier/StellaOps.Notifier.Tests` and `src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService.Tests`.
- Cover rule evaluation, delivery pipeline, and audit logging.
## Sprint Discipline
- Record delivery workflow changes in sprint Decisions & Risks.

View File

@@ -9,10 +9,8 @@ namespace StellaOps.Notifier.Tests;
public sealed class AttestationTemplateSeederTests
{
private const string SkipReason = "Offline bundle files not yet created in offline/notifier/";
[Trait("Category", TestCategories.Unit)]
[Fact(Skip = SkipReason)]
[Fact]
public async Task SeedTemplates_and_routing_load_from_offline_bundle()
{
var templateRepo = new InMemoryTemplateRepository();
@@ -52,8 +50,9 @@ public sealed class AttestationTemplateSeederTests
var directory = AppContext.BaseDirectory;
while (directory != null)
{
if (Directory.Exists(Path.Combine(directory, "offline", "notifier")) ||
File.Exists(Path.Combine(directory, "StellaOps.sln")))
var manifest = Path.Combine(directory, "offline", "notifier", "notify-kit.manifest.json");
var notifierDocs = Path.Combine(directory, "src", "Notifier", "StellaOps.Notifier", "StellaOps.Notifier.docs");
if (File.Exists(manifest) || Directory.Exists(notifierDocs))
{
return directory;
}

View File

@@ -8,9 +8,7 @@ public sealed class ArtifactHashesTests
{
private static string RepoRoot => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../../../"));
private const string SkipReason = "Offline kit files not yet created in offline/notifier/";
[Fact(Skip = SkipReason)]
[Fact]
public void ArtifactHashesHasNoTbdAndFilesExist()
{
var hashesPath = Path.Combine(RepoRoot, "offline/notifier/artifact-hashes.json");

View File

@@ -6,7 +6,15 @@
// Note: These tests verify the full notification pipeline for identity alerts.
// -----------------------------------------------------------------------------
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Templates;
using Xunit;
namespace StellaOps.Notifier.Tests.Contracts;
@@ -17,6 +25,11 @@ namespace StellaOps.Notifier.Tests.Contracts;
/// </summary>
public sealed class IdentityAlertNotificationTests
{
private static readonly Guid EventId = Guid.Parse("00000000-0000-0000-0000-0000000000a1");
private const string OccurredAtUtc = "2026-01-15T12:30:00Z";
private const string RekorIntegratedAtUtc = "2026-01-15T12:25:00Z";
private const string TenantId = "tenant-01";
[Fact]
public void IdentityMatchedTemplate_ContainsRequiredVariables()
{
@@ -51,52 +64,136 @@ public sealed class IdentityAlertNotificationTests
routingRuleEventKinds.Should().Contain(eventKind);
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_IdentityAlertEvent_RendersSlackMessage()
[Fact]
public async Task IdentityAlertEvent_RendersSlackMessage()
{
// This test verifies the full flow:
// 1. Create IdentityAlertEvent
// 2. Route through notification rules
// 3. Select identity-matched template
// 4. Render Slack message
// 5. Verify output format
var template = LoadTemplate("identity-matched.slack.template.json");
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("critical", suppressedCount: 2),
CancellationToken.None);
await Task.CompletedTask;
result.Body.Should().Contain(":rotating_light:");
result.Body.Should().Contain("Identity Watchlist Alert");
result.Body.Should().Contain("Production Signing Key");
result.Body.Should().Contain("rekor-uuid-1234");
result.Body.Should().Contain(EventId.ToString());
result.Body.Should().Contain("2 duplicate alerts suppressed");
result.Body.Should().NotContain(":warning:");
result.Body.Should().NotContain(":information_source:");
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_IdentityAlertEvent_RendersEmailMessage()
[Fact]
public async Task IdentityAlertEvent_RendersEmailMessage()
{
// Verify email template rendering
await Task.CompletedTask;
var template = LoadTemplate("identity-matched.email.template.json");
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("warning", suppressedCount: 0),
CancellationToken.None);
result.Subject.Should().NotBeNull();
result.Subject.Should().Contain("[WARNING]");
result.Body.Should().Contain("Identity Watchlist Alert");
result.Body.Should().Contain("Production Signing Key");
result.Body.Should().Contain(EventId.ToString());
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_IdentityAlertEvent_RendersWebhookPayload()
[Fact]
public async Task IdentityAlertEvent_RendersWebhookPayload()
{
// Verify webhook payload rendering
await Task.CompletedTask;
var template = LoadTemplate("identity-matched.webhook.template.json");
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("info", suppressedCount: 1),
CancellationToken.None);
using var document = JsonDocument.Parse(result.Body);
var root = document.RootElement;
root.GetProperty("alertType").GetString().Should().Be("identity-watchlist-match");
root.GetProperty("severity").GetString().Should().Be("info");
root.GetProperty("watchlist").GetProperty("entryName").GetString()
.Should().Be("Production Signing Key");
root.GetProperty("rekorEntry").GetProperty("uuid").GetString()
.Should().Be("rekor-uuid-1234");
root.GetProperty("eventId").GetString().Should().Be(EventId.ToString());
root.GetProperty("suppressedCount").GetInt32().Should().Be(1);
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_IdentityAlertEvent_RendersTeamsCard()
[Fact]
public async Task IdentityAlertEvent_RendersTeamsCard()
{
// Verify Teams adaptive card rendering
await Task.CompletedTask;
var template = LoadTemplate("identity-matched.teams.template.json");
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("critical", suppressedCount: 1),
CancellationToken.None);
using var document = JsonDocument.Parse(result.Body);
var root = document.RootElement;
root.TryGetProperty("attachments", out var attachments).Should().BeTrue();
attachments.GetArrayLength().Should().BeGreaterThan(0);
var content = attachments.EnumerateArray().First().GetProperty("content");
var header = content.GetProperty("body").EnumerateArray().First();
header.GetProperty("color").GetString().Should().Be("attention");
result.Body.Should().Contain("Production Signing Key");
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_SeverityRouting_CriticalAlertUsesCorrectChannel()
[Fact]
public async Task SeverityRouting_CriticalAlertUsesCorrectChannel()
{
// Verify that Critical severity alerts route to high-priority channels
await Task.CompletedTask;
var template = LoadTemplate("identity-matched.slack.template.json");
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("warning", suppressedCount: 0),
CancellationToken.None);
result.Body.Should().Contain(":warning:");
result.Body.Should().NotContain(":rotating_light:");
}
[Fact(Skip = "Requires full notification pipeline. Run in integration environment.")]
public async Task EndToEnd_ChannelOverrides_UsesEntrySpecificChannels()
[Fact]
public async Task ChannelOverrides_UsesEntrySpecificChannels()
{
// Verify that channelOverrides from watchlist entry are respected
await Task.CompletedTask;
var template = NotifyTemplate.Create(
templateId: "identity-channel-overrides",
tenantId: TenantId,
channelType: NotifyChannelType.Slack,
key: "identity.channelOverrides",
locale: "en-us",
body: "Overrides: {{#each event.channelOverrides}}{{@channelType}}={{@channelName}};{{/each}}",
renderMode: NotifyTemplateRenderMode.PlainText,
format: NotifyDeliveryFormat.PlainText);
var channelOverrides = new JsonArray
{
new JsonObject
{
["channelType"] = "Slack",
["channelName"] = "sec-alerts"
},
new JsonObject
{
["channelType"] = "Email",
["channelName"] = "soc"
}
};
var renderer = CreateRenderer();
var result = await renderer.RenderAsync(
template,
CreateIdentityEvent("info", suppressedCount: 0, channelOverrides: channelOverrides),
CancellationToken.None);
result.Body.Should().Contain("Slack=sec-alerts;");
result.Body.Should().Contain("Email=soc;");
}
[Fact]
@@ -118,8 +215,6 @@ public sealed class IdentityAlertNotificationTests
[Fact]
public void TemplateFilesExist_AllChannelTypes()
{
// Verify that templates exist for all required channel types
// This is a documentation test - actual file existence is verified elsewhere
var requiredTemplates = new[]
{
"identity-matched.slack.template.json",
@@ -128,7 +223,12 @@ public sealed class IdentityAlertNotificationTests
"identity-matched.teams.template.json"
};
requiredTemplates.Should().HaveCount(4);
var directory = LocateTemplateDirectory();
foreach (var template in requiredTemplates)
{
var path = Path.Combine(directory, template);
File.Exists(path).Should().BeTrue($"Expected template at {path}");
}
}
[Fact]
@@ -151,4 +251,149 @@ public sealed class IdentityAlertNotificationTests
webhookFields.Should().HaveCountGreaterThanOrEqualTo(10);
}
private static EnhancedTemplateRenderer CreateRenderer()
{
var templateService = new StubTemplateService(new TemplateRedactionConfig
{
AllowedFields = [],
DeniedFields = [],
Mode = "none"
});
return new EnhancedTemplateRenderer(
templateService,
Options.Create(new TemplateRendererOptions()),
NullLogger<EnhancedTemplateRenderer>.Instance);
}
private static NotifyEvent CreateIdentityEvent(
string severity,
int suppressedCount,
JsonArray? channelOverrides = null)
{
var payload = BuildIdentityPayload(severity, suppressedCount, channelOverrides);
var timestamp = DateTimeOffset.Parse(
OccurredAtUtc,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
return NotifyEvent.Create(
EventId,
"attestor.identity.matched",
TenantId,
timestamp,
payload,
actor: "attestor");
}
private static JsonObject BuildIdentityPayload(
string severity,
int suppressedCount,
JsonArray? channelOverrides)
{
var identityEvent = new JsonObject
{
["eventId"] = EventId.ToString(),
["watchlistEntryId"] = "wl-entry-01",
["watchlistEntryName"] = "Production Signing Key",
["severity"] = severity,
["occurredAtUtc"] = OccurredAtUtc,
["suppressedCount"] = suppressedCount,
["matchedIdentity"] = new JsonObject
{
["issuer"] = "https://authority.stellaops.local",
["subjectAlternativeName"] = "spiffe://prod/app",
["keyId"] = "key-123"
},
["rekorEntry"] = new JsonObject
{
["uuid"] = "rekor-uuid-1234",
["logIndex"] = 42,
["artifactSha256"] = "sha256:abc123",
["integratedTimeUtc"] = RekorIntegratedAtUtc
}
};
if (channelOverrides is not null)
{
identityEvent["channelOverrides"] = channelOverrides;
}
return new JsonObject
{
["event"] = identityEvent
};
}
private static NotifyTemplate LoadTemplate(string fileName)
{
var directory = LocateTemplateDirectory();
var path = Path.Combine(directory, fileName);
var json = File.ReadAllText(path);
return NotifyCanonicalJsonSerializer.Deserialize<NotifyTemplate>(json);
}
private static string LocateTemplateDirectory()
{
var directory = AppContext.BaseDirectory;
string? fallback = null;
while (directory != null)
{
var offlineRoot = Path.Combine(directory, "offline", "notifier");
var candidate = Path.Combine(offlineRoot, "templates", "attestation");
if (Directory.Exists(candidate))
{
fallback ??= candidate;
// Prefer the repo's offline kit (manifest present) to avoid stale bin copies.
var manifest = Path.Combine(offlineRoot, "notify-kit.manifest.json");
if (File.Exists(manifest))
{
return candidate;
}
}
directory = Directory.GetParent(directory)?.FullName;
}
if (fallback is not null)
{
return fallback;
}
throw new InvalidOperationException("Unable to locate notifier template directory.");
}
private sealed class StubTemplateService : INotifyTemplateService
{
private readonly TemplateRedactionConfig _config;
public StubTemplateService(TemplateRedactionConfig config)
{
_config = config;
}
public Task<NotifyTemplate?> ResolveAsync(string tenantId, string key, NotifyChannelType channelType, string locale, CancellationToken cancellationToken = default)
=> Task.FromResult<NotifyTemplate?>(null);
public Task<NotifyTemplate?> GetByIdAsync(string tenantId, string templateId, CancellationToken cancellationToken = default)
=> Task.FromResult<NotifyTemplate?>(null);
public Task<TemplateUpsertResult> UpsertAsync(NotifyTemplate template, string actor, CancellationToken cancellationToken = default)
=> Task.FromResult(TemplateUpsertResult.Created(template.TemplateId));
public Task<bool> DeleteAsync(string tenantId, string templateId, string actor, CancellationToken cancellationToken = default)
=> Task.FromResult(true);
public Task<IReadOnlyList<NotifyTemplate>> ListAsync(string tenantId, TemplateListOptions? options = null, CancellationToken cancellationToken = default)
=> Task.FromResult<IReadOnlyList<NotifyTemplate>>([]);
public TemplateValidationResult Validate(string templateBody)
=> TemplateValidationResult.Valid();
public TemplateRedactionConfig GetRedactionConfig(NotifyTemplate template)
=> _config;
}
}

View File

@@ -8,9 +8,7 @@ public sealed class OfflineKitManifestTests
{
private static string RepoRoot => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../../../"));
private const string SkipReason = "Offline kit files not yet created in offline/notifier/";
[Fact(Skip = SkipReason)]
[Fact]
public void ManifestDssePayloadMatchesManifest()
{
var manifestPath = Path.Combine(RepoRoot, "offline/notifier/notify-kit.manifest.json");
@@ -25,7 +23,7 @@ public sealed class OfflineKitManifestTests
Assert.True(JsonElement.DeepEquals(payload.RootElement, manifest.RootElement));
}
[Fact(Skip = SkipReason)]
[Fact]
public void ManifestArtifactsHaveHashes()
{
var manifestPath = Path.Combine(RepoRoot, "offline/notifier/notify-kit.manifest.json");

View File

@@ -8,9 +8,7 @@ public sealed class RenderingDeterminismTests
{
private static string RepoRoot => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../../../"));
private const string SkipReason = "Fixture files not yet created in docs/notifications/fixtures/rendering/";
[Fact(Skip = SkipReason)]
[Fact]
public void RenderingIndexMatchesTemplates()
{
var indexPath = Path.Combine(RepoRoot, "docs/notifications/fixtures/rendering/index.ndjson");

View File

@@ -8,9 +8,7 @@ public sealed class SchemaCatalogTests
{
private static string RepoRoot => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../../../"));
private const string SkipReason = "Schema catalog files not yet created in docs/notifications/schemas/";
[Fact(Skip = SkipReason)]
[Fact]
public void CatalogMatchesDssePayload()
{
var catalogPath = Path.Combine(RepoRoot, "docs/notifications/schemas/notify-schemas-catalog.json");
@@ -37,7 +35,7 @@ public sealed class SchemaCatalogTests
Assert.True(text.IndexOf("TBD", StringComparison.OrdinalIgnoreCase) < 0);
}
[Fact(Skip = SkipReason)]
[Fact]
public void InputsLockAlignsWithCatalog()
{
var catalogPath = Path.Combine(RepoRoot, "docs/notifications/schemas/notify-schemas-catalog.json");

View File

@@ -132,7 +132,8 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
Assert.Equal("delivery-failed", result.GetProperty("deliveryId").GetString());
Assert.Equal("Pending", result.GetProperty("status").GetString());
Assert.True(result.GetProperty("retried").GetBoolean());
Assert.Equal(1, result.GetProperty("newAttemptNumber").GetInt32());
}
[Fact]
@@ -169,7 +170,7 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
Assert.Equal(initialAttempts + 1, result.GetProperty("attemptCount").GetInt32());
Assert.Equal(initialAttempts + 1, result.GetProperty("newAttemptNumber").GetInt32());
}
#endregion
@@ -202,10 +203,10 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
Assert.Equal(0, result.GetProperty("total").GetInt32());
Assert.Equal(0, result.GetProperty("sent").GetInt32());
Assert.Equal(0, result.GetProperty("failed").GetInt32());
Assert.Equal(0, result.GetProperty("pending").GetInt32());
Assert.Equal(0, result.GetProperty("totalSent").GetInt32());
Assert.Equal(0, result.GetProperty("totalFailed").GetInt32());
Assert.Equal(0, result.GetProperty("totalPending").GetInt32());
Assert.Equal(0, result.GetProperty("totalThrottled").GetInt32());
}
[Fact]
@@ -226,10 +227,10 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
Assert.Equal(5, result.GetProperty("total").GetInt32());
Assert.Equal(3, result.GetProperty("sent").GetInt32()); // Sent + Delivered
Assert.Equal(1, result.GetProperty("failed").GetInt32());
Assert.Equal(1, result.GetProperty("pending").GetInt32());
Assert.Equal(3, result.GetProperty("totalSent").GetInt32()); // Sent + Delivered
Assert.Equal(1, result.GetProperty("totalFailed").GetInt32());
Assert.Equal(1, result.GetProperty("totalPending").GetInt32());
Assert.Equal(0, result.GetProperty("totalThrottled").GetInt32());
}
[Fact]
@@ -249,8 +250,8 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
var byChannel = result.GetProperty("byChannel");
Assert.Equal(2, byChannel.GetProperty("slack").GetInt32());
Assert.Equal(1, byChannel.GetProperty("email").GetInt32());
Assert.Equal(2, byChannel.GetProperty("slack").GetProperty("sent").GetInt32());
Assert.Equal(1, byChannel.GetProperty("email").GetProperty("sent").GetInt32());
}
[Fact]
@@ -270,8 +271,8 @@ public sealed class DeliveryRetryEndpointTests : IClassFixture<NotifierApplicati
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: CancellationToken.None);
var byEventKind = result.GetProperty("byEventKind");
Assert.Equal(2, byEventKind.GetProperty("finding.created").GetInt32());
Assert.Equal(1, byEventKind.GetProperty("policy.promoted").GetInt32());
Assert.Equal(2, byEventKind.GetProperty("finding.created").GetProperty("sent").GetInt32());
Assert.Equal(1, byEventKind.GetProperty("policy.promoted").GetProperty("sent").GetInt32());
}
#endregion

View File

@@ -1,9 +1,9 @@
using System.Net;
using System.Net.Http;
using System.Linq;
using System.Text;
using StellaOps.Notifier.Tests.Support;
using Xunit;
using Xunit.Sdk;
using StellaOps.TestKit;
namespace StellaOps.Notifier.Tests;
@@ -19,17 +19,23 @@ public sealed class OpenApiEndpointTests : IClassFixture<NotifierApplicationFact
_packRepo = factory.PackRepo;
}
#if false // disabled until test host wiring stabilises
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task OpenApi_endpoint_serves_yaml_with_scope_header()
{
var response = await _client.GetAsync("/.well-known/openapi", CancellationToken.None);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
#endif
[Fact(Explicit = true, Skip = "Pending test host wiring")]
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(response.Headers.TryGetValues("X-OpenAPI-Scope", out var scopeValues) &&
scopeValues.Contains("notify"));
Assert.Equal("application/yaml", response.Content.Headers.ContentType?.MediaType);
var body = await response.Content.ReadAsStringAsync(CancellationToken.None);
Assert.Contains("openapi: 3.1.0", body);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Deprecation_headers_emitted_for_api_surface()
{
var response = await _client.GetAsync("/api/v1/notify/rules", CancellationToken.None);
@@ -42,7 +48,8 @@ public sealed class OpenApiEndpointTests : IClassFixture<NotifierApplicationFact
linkValues.Any(v => v.Contains("rel=\"deprecation\"")));
}
[Fact(Explicit = true, Skip = "Pending test host wiring")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PackApprovals_endpoint_validates_missing_headers()
{
var content = new StringContent("""{"eventId":"00000000-0000-0000-0000-000000000001","issuedAt":"2025-11-17T16:00:00Z","kind":"pack.approval.granted","packId":"offline-kit","decision":"approved","actor":"task-runner"}""", Encoding.UTF8, "application/json");
@@ -51,7 +58,8 @@ public sealed class OpenApiEndpointTests : IClassFixture<NotifierApplicationFact
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact(Explicit = true, Skip = "Pending test host wiring")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PackApprovals_endpoint_accepts_happy_path_and_echoes_resume_token()
{
var content = new StringContent("""{"eventId":"00000000-0000-0000-0000-000000000002","issuedAt":"2025-11-17T16:00:00Z","kind":"pack.approval.granted","packId":"offline-kit","decision":"approved","actor":"task-runner","resumeToken":"rt-ok"}""", Encoding.UTF8, "application/json");
@@ -70,7 +78,8 @@ public sealed class OpenApiEndpointTests : IClassFixture<NotifierApplicationFact
Assert.True(_packRepo.Exists("tenant-a", Guid.Parse("00000000-0000-0000-0000-000000000002"), "offline-kit"));
}
[Fact(Explicit = true, Skip = "Pending test host wiring")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PackApprovals_acknowledgement_requires_tenant_and_token()
{
var ackContent = new StringContent("""{"ackToken":"token-123"}""", Encoding.UTF8, "application/json");

View File

@@ -8,10 +8,8 @@ namespace StellaOps.Notifier.Tests;
public sealed class PackApprovalTemplateSeederTests
{
private const string SkipReason = "Template seeder files not yet created";
[Trait("Category", TestCategories.Unit)]
[Fact(Skip = SkipReason)]
[Fact]
public async Task SeedAsync_loads_templates_from_docs()
{
var templateRepo = new InMemoryTemplateRepository();

View File

@@ -9,10 +9,8 @@ namespace StellaOps.Notifier.Tests;
public sealed class RiskTemplateSeederTests
{
private const string SkipReason = "Offline bundle files not yet created in offline/notifier/";
[Trait("Category", TestCategories.Unit)]
[Fact(Skip = SkipReason)]
[Fact]
public async Task SeedTemplates_and_routing_load_from_offline_bundle()
{
var templateRepo = new InMemoryTemplateRepository();
@@ -52,8 +50,9 @@ public sealed class RiskTemplateSeederTests
var directory = AppContext.BaseDirectory;
while (directory != null)
{
if (Directory.Exists(Path.Combine(directory, "offline", "notifier")) ||
File.Exists(Path.Combine(directory, "StellaOps.sln")))
var manifest = Path.Combine(directory, "offline", "notifier", "notify-kit.manifest.json");
var notifierDocs = Path.Combine(directory, "src", "Notifier", "StellaOps.Notifier", "StellaOps.Notifier.docs");
if (File.Exists(manifest) || Directory.Exists(notifierDocs))
{
return directory;
}

View File

@@ -20,7 +20,6 @@
</ItemGroup>
<ItemGroup>
<Compile Remove="OpenApiEndpointTests.cs" />
<Content Include="TestContent/**" CopyToOutputDirectory="PreserveNewest" />
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@@ -1,9 +1,9 @@
extern alias webservice;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using StellaOps.Notify.Queue;
using StellaOps.Notifier.WebService.Storage.Compat;
using StellaOps.Notifier.Worker.Storage;
@@ -20,12 +20,13 @@ public sealed class NotifierApplicationFactory : WebApplicationFactory<WebProgra
internal InMemoryLockRepository LockRepo { get; } = new();
internal InMemoryAuditRepository AuditRepo { get; } = new();
internal InMemoryPackApprovalRepository PackRepo { get; } = new();
internal INotifyEventQueue EventQueue { get; set; } = new NullNotifyEventQueue();
protected override IHost CreateHost(IHostBuilder builder)
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureServices(services =>
builder.ConfigureTestServices(services =>
{
services.RemoveAll<INotifyRuleRepository>();
services.RemoveAll<INotifyChannelRepository>();
@@ -43,9 +44,7 @@ public sealed class NotifierApplicationFactory : WebApplicationFactory<WebProgra
services.AddSingleton<INotifyLockRepository>(LockRepo);
services.AddSingleton<INotifyAuditRepository>(AuditRepo);
services.AddSingleton<INotifyPackApprovalRepository>(PackRepo);
services.AddSingleton<INotifyEventQueue, NullNotifyEventQueue>();
services.AddSingleton(EventQueue);
});
return base.CreateHost(builder);
}
}

View File

@@ -8,3 +8,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0394-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Notifier.Tests. |
| AUDIT-0394-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Notifier.Tests. |
| AUDIT-0394-A | DONE | Waived (test project; revalidated 2026-01-07). |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -287,10 +287,49 @@ public sealed class EnhancedTemplateRendererTests
Assert.DoesNotContain("CRITICAL:", resultFalse.Body);
}
[Fact]
public async Task RenderAsync_IfBlock_AllowsWhitespaceTokens()
{
// Arrange
var template = CreateTemplate("{{ #if critical }}OK{{ /if }}");
var payload = new JsonObject { ["critical"] = "true" };
var notifyEvent = CreateEvent("test", "user", payload);
// Act
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
// Assert
Assert.Equal("OK", result.Body);
}
[Fact]
public async Task RenderAsync_NoneRenderMode_DoesNotWrapJson()
{
// Arrange
var template = NotifyTemplate.Create(
templateId: "tmpl-json",
tenantId: "test-tenant",
channelType: NotifyChannelType.Webhook,
key: "test.key",
locale: "en-us",
body: "{\"message\":\"{{message}}\"}",
renderMode: NotifyTemplateRenderMode.None,
format: NotifyDeliveryFormat.Json);
var payload = new JsonObject { ["message"] = "hello" };
var notifyEvent = CreateEvent("test.event", "user", payload);
// Act
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
// Assert
Assert.Equal("{\"message\":\"hello\"}", result.Body);
}
private static NotifyTemplate CreateTemplate(
string body,
NotifyDeliveryFormat format = NotifyDeliveryFormat.PlainText,
Dictionary<string, string>? metadata = null)
Dictionary<string, string>? metadata = null,
NotifyTemplateRenderMode renderMode = NotifyTemplateRenderMode.Markdown)
{
return NotifyTemplate.Create(
templateId: "test-template",
@@ -299,6 +338,7 @@ public sealed class EnhancedTemplateRendererTests
key: "test.key",
locale: "en-us",
body: body,
renderMode: renderMode,
format: format,
metadata: metadata);
}

View File

@@ -1,6 +1,7 @@
using StellaOps.Notify.Models;
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Contracts;

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Contracts;

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notifier.Worker.Escalation;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Escalation;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Fallback;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,10 +1,11 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,11 +1,12 @@
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,8 +1,9 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notifier.Worker.Localization;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Localization;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,15 +1,16 @@
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notifier.WebService.Contracts;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Dispatch;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Templates;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.WebService.Extensions;
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,10 +1,11 @@
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notifier.Worker.Observability;
using StellaOps.Notifier.Worker.Retention;
using System.Linq;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notifier.Worker.Correlation;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Correlation;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notifier.Worker.Correlation;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Correlation;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,12 +1,13 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.WebService.Contracts;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,10 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Simulation;
using StellaOps.Notify.Models;
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Simulation;
using StellaOps.Notifier.WebService.Extensions;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,8 +1,9 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notifier.Worker.StormBreaker;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.StormBreaker;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,13 +1,14 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.WebService.Contracts;
using StellaOps.Notifier.Worker.Dispatch;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Templates;
using StellaOps.Notify.Models;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Notifier.Worker.Correlation;
using StellaOps.Notifier.WebService.Extensions;
using StellaOps.Notifier.Worker.Correlation;
namespace StellaOps.Notifier.WebService.Endpoints;

View File

@@ -123,10 +123,6 @@ app.UseWebSockets(new WebSocketOptions
app.MapHealthChecks("/healthz");
// Tenant context middleware (extracts and validates tenant from headers/query)
app.UseTenantContext();
app.TryUseStellaRouter(routerOptions);
// Deprecation headers for retiring v1 APIs (RFC 8594 / IETF Sunset)
app.Use(async (context, next) =>
{
@@ -141,6 +137,10 @@ app.Use(async (context, next) =>
await next().ConfigureAwait(false);
});
// Tenant context middleware (extracts and validates tenant from headers/query)
app.UseTenantContext();
app.TryUseStellaRouter(routerOptions);
app.MapPost("/api/v1/notify/pack-approvals", async (
HttpContext context,
PackApprovalRequest request,

View File

@@ -1,10 +1,11 @@
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Services;

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Services;

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Services;

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Services;

View File

@@ -1,7 +1,8 @@
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.WebService.Services;

View File

@@ -1,12 +1,13 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
namespace StellaOps.Notifier.WebService.Setup;

View File

@@ -1,7 +1,8 @@
using StellaOps.Notify.Queue;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Notify.Queue;
namespace StellaOps.Notifier.WebService.Setup;

View File

@@ -1,3 +1,4 @@
using System.Text;
namespace StellaOps.Notifier.WebService.Setup;

View File

@@ -1,12 +1,13 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Notifier.WebService.Setup;
@@ -172,7 +173,10 @@ public sealed class PackApprovalTemplateSeeder : IHostedService
var candidates = new[]
{
Path.Combine(contentRootPath, "StellaOps.Notifier.docs", "pack-approval-templates.json"),
Path.Combine(contentRootPath, "..", "StellaOps.Notifier.docs", "pack-approval-templates.json")
Path.Combine(contentRootPath, "..", "StellaOps.Notifier.docs", "pack-approval-templates.json"),
Path.Combine(contentRootPath, "StellaOps.Notifier", "StellaOps.Notifier.docs", "pack-approval-templates.json"),
Path.Combine(contentRootPath, "Notifier", "StellaOps.Notifier", "StellaOps.Notifier.docs", "pack-approval-templates.json"),
Path.Combine(contentRootPath, "src", "Notifier", "StellaOps.Notifier", "StellaOps.Notifier.docs", "pack-approval-templates.json")
};
foreach (var candidate in candidates)

View File

@@ -1,12 +1,13 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Xml;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
namespace StellaOps.Notifier.WebService.Setup;

View File

@@ -1,6 +1,7 @@
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Linq;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,6 +1,7 @@
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Linq;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,7 +1,8 @@
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,6 +1,7 @@
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Linq;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.WebService.Storage.Compat;

View File

@@ -8,3 +8,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0395-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Notifier.WebService. |
| AUDIT-0395-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Notifier.WebService. |
| AUDIT-0395-A | TODO | Revalidated 2026-01-07 (open findings). |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -1,17 +1,17 @@
{
"templateId": "identity-matched-email",
"tenantId": "bootstrap",
"channelType": "email",
"channelType": "Email",
"key": "identity-matched",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Markdown",
"renderMode": "Html",
"format": "Html",
"description": "Identity watchlist match alert for Email",
"description": "Email notification for identity watchlist matches",
"metadata": {
"category": "attestation",
"eventKind": "attestor.identity.matched",
"subject": "[{{ event.severity }}] Identity Watchlist Alert: {{ event.watchlistEntryName }}"
"category": "attestation",
"subject": "[{{ event.severity | upper }}] Identity Watchlist Alert: {{ event.watchlistEntryName }}"
},
"body": "# Identity Watchlist Alert\n\n**Watchlist Entry:** {{ event.watchlistEntryName }}\n\n**Severity:** {{ event.severity }}\n\n**Occurred:** {{ event.occurredAtUtc }}\n\n---\n\n## Matched Identity\n\n| Field | Value |\n|-------|-------|\n{% if event.matchedIdentity.issuer %}| Issuer | {{ event.matchedIdentity.issuer }} |{% endif %}\n{% if event.matchedIdentity.subjectAlternativeName %}| Subject Alternative Name | {{ event.matchedIdentity.subjectAlternativeName }} |{% endif %}\n{% if event.matchedIdentity.keyId %}| Key ID | {{ event.matchedIdentity.keyId }} |{% endif %}\n\n## Rekor Entry Details\n\n| Field | Value |\n|-------|-------|\n| UUID | {{ event.rekorEntry.uuid }} |\n| Log Index | {{ event.rekorEntry.logIndex }} |\n| Artifact SHA256 | {{ event.rekorEntry.artifactSha256 }} |\n| Integrated Time (UTC) | {{ event.rekorEntry.integratedTimeUtc }} |\n\n{% if event.suppressedCount > 0 %}\n---\n\n*Note: {{ event.suppressedCount }} similar alerts were suppressed within the deduplication window.*\n{% endif %}\n\n---\n\n*This alert was generated by Stella Ops identity watchlist monitoring.*"
"body": "<!DOCTYPE html>\n<html>\n<head><style>body{font-family:sans-serif;line-height:1.5;}.severity-critical{color:#dc3545;}.severity-warning{color:#ffc107;}.severity-info{color:#0dcaf0;}.section{margin:1em 0;padding:1em;background:#f8f9fa;border-radius:4px;}.label{font-weight:bold;color:#666;}.mono{font-family:monospace;background:#e9ecef;padding:2px 6px;border-radius:3px;}</style></head>\n<body>\n<h2 class=\"severity-{{ event.severity }}\">Identity Watchlist Alert</h2>\n<div class=\"section\">\n<p><span class=\"label\">Severity:</span> <strong>{{ event.severity }}</strong></p>\n<p><span class=\"label\">Watchlist Entry:</span> {{ event.watchlistEntryName }}</p>\n</div>\n<div class=\"section\">\n<h3>Matched Identity</h3>\n{{ #if event.matchedIdentity.issuer }}<p><span class=\"label\">Issuer:</span> <span class=\"mono\">{{ event.matchedIdentity.issuer }}</span></p>{{ /if }}\n{{ #if event.matchedIdentity.subjectAlternativeName }}<p><span class=\"label\">Subject Alternative Name:</span> <span class=\"mono\">{{ event.matchedIdentity.subjectAlternativeName }}</span></p>{{ /if }}\n{{ #if event.matchedIdentity.keyId }}<p><span class=\"label\">Key ID:</span> <span class=\"mono\">{{ event.matchedIdentity.keyId }}</span></p>{{ /if }}\n</div>\n<div class=\"section\">\n<h3>Rekor Entry</h3>\n<p><span class=\"label\">UUID:</span> <span class=\"mono\">{{ event.rekorEntry.uuid }}</span></p>\n<p><span class=\"label\">Log Index:</span> {{ event.rekorEntry.logIndex }}</p>\n<p><span class=\"label\">Artifact SHA-256:</span> <span class=\"mono\">{{ event.rekorEntry.artifactSha256 }}</span></p>\n<p><span class=\"label\">Integrated Time (UTC):</span> {{ event.rekorEntry.integratedTimeUtc }}</p>\n</div>\n{{ #if (gt event.suppressedCount 0) }}<p><em>{{ event.suppressedCount }} duplicate alerts suppressed</em></p>{{ /if }}\n<hr>\n<p style=\"font-size:0.85em;color:#666;\">Event ID: {{ event.eventId }} | Occurred: {{ event.occurredAtUtc }}</p>\n</body>\n</html>"
}

View File

@@ -1,16 +1,16 @@
{
"templateId": "identity-matched-slack",
"tenantId": "bootstrap",
"channelType": "slack",
"channelType": "Slack",
"key": "identity-matched",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Markdown",
"format": "Json",
"description": "Identity watchlist match alert for Slack",
"description": "Slack notification for identity watchlist matches",
"metadata": {
"category": "attestation",
"eventKind": "attestor.identity.matched"
"eventKind": "attestor.identity.matched",
"category": "attestation"
},
"body": ":warning: *Identity Watchlist Alert*\n\n*Entry:* {{ event.watchlistEntryName }}\n*Severity:* {{ event.severity }}\n\n*Matched Identity:*\n{% if event.matchedIdentity.issuer %}• Issuer: `{{ event.matchedIdentity.issuer }}`{% endif %}\n{% if event.matchedIdentity.subjectAlternativeName %}• SAN: `{{ event.matchedIdentity.subjectAlternativeName }}`{% endif %}\n{% if event.matchedIdentity.keyId %}• Key ID: `{{ event.matchedIdentity.keyId }}`{% endif %}\n\n*Rekor Entry:*\n UUID: `{{ event.rekorEntry.uuid }}`\n Log Index: `{{ event.rekorEntry.logIndex }}`\n Artifact: `{{ event.rekorEntry.artifactSha256 }}`\n• Time: {{ event.rekorEntry.integratedTimeUtc }}\n\n{% if event.suppressedCount > 0 %}_({{ event.suppressedCount }} similar alerts suppressed)_{% endif %}"
"body": "{{ #if (eq event.severity \"critical\") }}:rotating_light:{{ else if (eq event.severity \"warning\") }}:warning:{{ else }}:information_source:{{ /if }} *Identity Watchlist Alert*\n\n*Severity:* `{{ event.severity }}`\n*Watchlist Entry:* {{ event.watchlistEntryName }}\n\n*Matched Identity:*\n{{ #if event.matchedIdentity.issuer }}> Issuer: `{{ event.matchedIdentity.issuer }}`\n{{ /if }}{{ #if event.matchedIdentity.subjectAlternativeName }}> SAN: `{{ event.matchedIdentity.subjectAlternativeName }}`\n{{ /if }}{{ #if event.matchedIdentity.keyId }}> Key ID: `{{ event.matchedIdentity.keyId }}`\n{{ /if }}\n*Rekor Entry:*\n> UUID: `{{ event.rekorEntry.uuid }}`\n> Log Index: {{ event.rekorEntry.logIndex }}\n> Artifact: `{{ event.rekorEntry.artifactSha256 }}`\n> Integrated: {{ event.rekorEntry.integratedTimeUtc }}\n\n{{ #if (gt event.suppressedCount 0) }}:mute: {{ event.suppressedCount }} duplicate alerts suppressed\n{{ /if }}---\n_Event ID: {{ event.eventId }}_"
}

View File

@@ -1,16 +1,17 @@
{
"templateId": "identity-matched-teams",
"tenantId": "bootstrap",
"channelType": "teams",
"channelType": "Teams",
"key": "identity-matched",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Markdown",
"renderMode": "None",
"format": "Json",
"description": "Identity watchlist match alert for Microsoft Teams",
"description": "Microsoft Teams adaptive card for identity watchlist matches",
"metadata": {
"eventKind": "attestor.identity.matched",
"category": "attestation",
"eventKind": "attestor.identity.matched"
"contentType": "application/json"
},
"body": "{ \"@type\": \"MessageCard\", \"@context\": \"http://schema.org/extensions\", \"themeColor\": \"{% if event.severity == 'Critical' %}d13438{% elsif event.severity == 'Warning' %}ffb900{% else %}0078d4{% endif %}\", \"summary\": \"Identity Watchlist Alert: {{ event.watchlistEntryName }}\", \"sections\": [{ \"activityTitle\": \"⚠️ Identity Watchlist Alert\", \"activitySubtitle\": \"Entry: {{ event.watchlistEntryName }}\", \"facts\": [{ \"name\": \"Severity\", \"value\": \"{{ event.severity }}\" }, { \"name\": \"Occurred\", \"value\": \"{{ event.occurredAtUtc }}\" }{% if event.matchedIdentity.issuer %}, { \"name\": \"Issuer\", \"value\": \"{{ event.matchedIdentity.issuer }}\" }{% endif %}{% if event.matchedIdentity.subjectAlternativeName %}, { \"name\": \"SAN\", \"value\": \"{{ event.matchedIdentity.subjectAlternativeName }}\" }{% endif %}, { \"name\": \"Rekor UUID\", \"value\": \"{{ event.rekorEntry.uuid }}\" }, { \"name\": \"Log Index\", \"value\": \"{{ event.rekorEntry.logIndex }}\" }{% if event.suppressedCount > 0 %}, { \"name\": \"Suppressed Count\", \"value\": \"{{ event.suppressedCount }}\" }{% endif %}], \"markdown\": true }] }"
"body": "{\"type\":\"message\",\"attachments\":[{\"contentType\":\"application/vnd.microsoft.card.adaptive\",\"content\":{\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"type\":\"AdaptiveCard\",\"version\":\"1.4\",\"body\":[{\"type\":\"TextBlock\",\"text\":\"Identity Watchlist Alert\",\"weight\":\"bolder\",\"size\":\"large\",\"color\":\"{{ #if (eq event.severity 'critical') }}attention{{ else if (eq event.severity 'warning') }}warning{{ else }}default{{ /if }}\"},{\"type\":\"FactSet\",\"facts\":[{\"title\":\"Severity\",\"value\":\"{{ event.severity }}\"},{\"title\":\"Watchlist Entry\",\"value\":\"{{ event.watchlistEntryName }}\"}]},{\"type\":\"TextBlock\",\"text\":\"Matched Identity\",\"weight\":\"bolder\",\"spacing\":\"medium\"},{\"type\":\"FactSet\",\"facts\":[{{ #if event.matchedIdentity.issuer }}{\"title\":\"Issuer\",\"value\":\"{{ event.matchedIdentity.issuer }}\"},{{ /if }}{{ #if event.matchedIdentity.subjectAlternativeName }}{\"title\":\"SAN\",\"value\":\"{{ event.matchedIdentity.subjectAlternativeName }}\"},{{ /if }}{{ #if event.matchedIdentity.keyId }}{\"title\":\"Key ID\",\"value\":\"{{ event.matchedIdentity.keyId }}\"},{{ /if }}{\"title\":\"\",\"value\":\"\"}]},{\"type\":\"TextBlock\",\"text\":\"Rekor Entry\",\"weight\":\"bolder\",\"spacing\":\"medium\"},{\"type\":\"FactSet\",\"facts\":[{\"title\":\"UUID\",\"value\":\"{{ event.rekorEntry.uuid }}\"},{\"title\":\"Log Index\",\"value\":\"{{ event.rekorEntry.logIndex }}\"},{\"title\":\"Artifact\",\"value\":\"{{ event.rekorEntry.artifactSha256 }}\"},{\"title\":\"Integrated\",\"value\":\"{{ event.rekorEntry.integratedTimeUtc }}\"}]}{{ #if (gt event.suppressedCount 0) }},{\"type\":\"TextBlock\",\"text\":\"{{ event.suppressedCount }} duplicate alerts suppressed\",\"isSubtle\":true,\"spacing\":\"small\"}{{ /if }}],\"msteams\":{\"width\":\"Full\"}}}]}"
}

View File

@@ -1,16 +1,17 @@
{
"templateId": "identity-matched-webhook",
"tenantId": "bootstrap",
"channelType": "webhook",
"channelType": "Webhook",
"key": "identity-matched",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "None",
"format": "Json",
"description": "Identity watchlist match alert for Webhook (SIEM/SOC integration)",
"description": "Webhook payload for identity watchlist matches",
"metadata": {
"eventKind": "attestor.identity.matched",
"category": "attestation",
"eventKind": "attestor.identity.matched"
"contentType": "application/json"
},
"body": "{ \"eventType\": \"attestor.identity.matched\", \"eventId\": \"{{ event.eventId }}\", \"tenantId\": \"{{ event.tenantId }}\", \"severity\": \"{{ event.severity }}\", \"occurredAtUtc\": \"{{ event.occurredAtUtc }}\", \"watchlist\": { \"entryId\": \"{{ event.watchlistEntryId }}\", \"entryName\": \"{{ event.watchlistEntryName }}\" }, \"matchedIdentity\": { \"issuer\": \"{{ event.matchedIdentity.issuer }}\", \"subjectAlternativeName\": \"{{ event.matchedIdentity.subjectAlternativeName }}\", \"keyId\": \"{{ event.matchedIdentity.keyId }}\" }, \"rekorEntry\": { \"uuid\": \"{{ event.rekorEntry.uuid }}\", \"logIndex\": {{ event.rekorEntry.logIndex }}, \"artifactSha256\": \"{{ event.rekorEntry.artifactSha256 }}\", \"integratedTimeUtc\": \"{{ event.rekorEntry.integratedTimeUtc }}\" }, \"suppressedCount\": {{ event.suppressedCount }} }"
"body": "{\"alertType\":\"identity-watchlist-match\",\"severity\":\"{{ event.severity }}\",\"watchlist\":{\"entryId\":\"{{ event.watchlistEntryId }}\",\"entryName\":\"{{ event.watchlistEntryName }}\"},\"matchedIdentity\":{\"issuer\":{{ #if event.matchedIdentity.issuer }}\"{{ event.matchedIdentity.issuer }}\"{{ else }}null{{ /if }},\"subjectAlternativeName\":{{ #if event.matchedIdentity.subjectAlternativeName }}\"{{ event.matchedIdentity.subjectAlternativeName }}\"{{ else }}null{{ /if }},\"keyId\":{{ #if event.matchedIdentity.keyId }}\"{{ event.matchedIdentity.keyId }}\"{{ else }}null{{ /if }}},\"rekorEntry\":{\"uuid\":\"{{ event.rekorEntry.uuid }}\",\"logIndex\":{{ event.rekorEntry.logIndex }},\"artifactSha256\":\"{{ event.rekorEntry.artifactSha256 }}\",\"integratedTimeUtc\":\"{{ event.rekorEntry.integratedTimeUtc }}\"},\"eventId\":\"{{ event.eventId }}\",\"occurredAtUtc\":\"{{ event.occurredAtUtc }}\",\"suppressedCount\":{{ event.suppressedCount }}}"
}

View File

@@ -0,0 +1,17 @@
{
"templateId": "tmpl-risk-profile-state-email",
"tenantId": "bootstrap",
"channelType": "Email",
"key": "tmpl-risk-profile-state",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Html",
"format": "Html",
"description": "Email notification for risk profile state changes",
"metadata": {
"eventKind": "risk.profile.published",
"category": "risk",
"subject": "[Notify] Risk profile update: {{ event.profileName }}"
},
"body": "<!DOCTYPE html>\n<html>\n<head><style>body{font-family:sans-serif;line-height:1.5;}.section{margin:1em 0;padding:1em;background:#f8f9fa;border-radius:4px;}.label{font-weight:bold;color:#666;}.mono{font-family:monospace;background:#e9ecef;padding:2px 6px;border-radius:3px;}</style></head>\n<body>\n<h2>Risk Profile Update</h2>\n<div class=\"section\">\n<p><span class=\"label\">Profile:</span> <span class=\"mono\">{{ event.profileName }}</span></p>\n<p><span class=\"label\">State:</span> {{ event.state }}</p>\n<p><span class=\"label\">Owner:</span> {{ event.owner }}</p>\n<p><span class=\"label\">Summary:</span> {{ event.summary }}</p>\n{{ #if event.policyId }}<p><span class=\"label\">Policy:</span> {{ event.policyId }} (v{{ event.policyVersion }})</p>{{ /if }}\n</div>\n<hr>\n<p style=\"font-size:0.85em;color:#666;\">Event ID: {{ event.eventId }} | Occurred: {{ event.occurredAtUtc }}</p>\n</body>\n</html>"
}

View File

@@ -0,0 +1,16 @@
{
"templateId": "tmpl-risk-profile-state-slack",
"tenantId": "bootstrap",
"channelType": "Slack",
"key": "tmpl-risk-profile-state",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Markdown",
"format": "Json",
"description": "Slack notification for risk profile state changes",
"metadata": {
"eventKind": "risk.profile.published",
"category": "risk"
},
"body": ":information_source: *Risk profile update*\n\n*Profile:* {{ event.profileName }}\n*State:* {{ event.state }}\n*Owner:* {{ event.owner }}\n*Summary:* {{ event.summary }}\n\n{{ #if event.policyId }}*Policy:* {{ event.policyId }} (v{{ event.policyVersion }})\n{{ /if }}---\n_Event ID: {{ event.eventId }} | {{ event.occurredAtUtc }}_"
}

View File

@@ -0,0 +1,17 @@
{
"templateId": "tmpl-risk-severity-change-email",
"tenantId": "bootstrap",
"channelType": "Email",
"key": "tmpl-risk-severity-change",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Html",
"format": "Html",
"description": "Email notification for risk severity changes",
"metadata": {
"eventKind": "risk.profile.severity.changed",
"category": "risk",
"subject": "[Notify] Risk severity changed: {{ event.profileName }}"
},
"body": "<!DOCTYPE html>\n<html>\n<head><style>body{font-family:sans-serif;line-height:1.5;}.section{margin:1em 0;padding:1em;background:#f8f9fa;border-radius:4px;}.label{font-weight:bold;color:#666;}.mono{font-family:monospace;background:#e9ecef;padding:2px 6px;border-radius:3px;}</style></head>\n<body>\n<h2>Risk Severity Changed</h2>\n<div class=\"section\">\n<p><span class=\"label\">Profile:</span> <span class=\"mono\">{{ event.profileName }}</span></p>\n<p><span class=\"label\">Previous:</span> {{ event.previousSeverity }}</p>\n<p><span class=\"label\">Current:</span> {{ event.newSeverity }}</p>\n<p><span class=\"label\">Reason:</span> {{ event.reason }}</p>\n{{ #if event.referenceUrl }}<p><span class=\"label\">Reference:</span> <a href=\"{{ event.referenceUrl }}\">{{ event.referenceUrl }}</a></p>{{ /if }}\n</div>\n<hr>\n<p style=\"font-size:0.85em;color:#666;\">Event ID: {{ event.eventId }} | Occurred: {{ event.occurredAtUtc }}</p>\n</body>\n</html>"
}

View File

@@ -0,0 +1,16 @@
{
"templateId": "tmpl-risk-severity-change-slack",
"tenantId": "bootstrap",
"channelType": "Slack",
"key": "tmpl-risk-severity-change",
"locale": "en-US",
"schemaVersion": "1.0.0",
"renderMode": "Markdown",
"format": "Json",
"description": "Slack notification for risk severity changes",
"metadata": {
"eventKind": "risk.profile.severity.changed",
"category": "risk"
},
"body": ":rotating_light: *Risk severity changed*\n\n*Profile:* {{ event.profileName }}\n*Previous:* {{ event.previousSeverity }}\n*Current:* {{ event.newSeverity }}\n*Reason:* {{ event.reason }}\n\n{{ #if event.referenceUrl }}*Reference:* {{ event.referenceUrl }}\n{{ /if }}---\n_Event ID: {{ event.eventId }} | {{ event.occurredAtUtc }}_"
}

View File

@@ -1,12 +1,13 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,9 +1,10 @@
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,11 +1,12 @@
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,9 +1,10 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Text.Json;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,13 +1,14 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,14 +1,15 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,7 +1,8 @@
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Net.Http.Json;
using System.Text.Json;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,15 +1,16 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Options;
namespace StellaOps.Notifier.Worker.Channels;

View File

@@ -1,7 +1,8 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,7 +1,8 @@
using StellaOps.Notify.Models;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using StellaOps.Notify.Models;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Correlation;

View File

@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Observability;
using System.Collections.Concurrent;
using System.Collections.Immutable;
namespace StellaOps.Notifier.Worker.DeadLetter;

View File

@@ -1,8 +1,9 @@
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Correlation;
using System.Text;
using System.Text.Json;
namespace StellaOps.Notifier.Worker.Digest;

View File

@@ -1,9 +1,10 @@
using System.Globalization;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Channels;
using StellaOps.Notify.Models;
using System.Globalization;
namespace StellaOps.Notifier.Worker.Digest;

View File

@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using Cronos;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Digest;

View File

@@ -1,9 +1,10 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Collections.Immutable;
namespace StellaOps.Notifier.Worker.Dispatch;

View File

@@ -1,11 +1,12 @@
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.Worker.Dispatch;

View File

@@ -1,10 +1,11 @@
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using System.Globalization;
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
namespace StellaOps.Notifier.Worker.Dispatch;

View File

@@ -1,11 +1,12 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Correlation;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notifier.Worker.Correlation;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,7 +1,8 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Storage;
using StellaOps.Notify.Models;
using System.Collections.Immutable;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,8 +1,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using StellaOps.Notifier.Worker.Storage;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Escalation;

View File

@@ -1,8 +1,9 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Notify.Models;
using StellaOps.Notifier.Worker.Channels;
using StellaOps.Notify.Models;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Fallback;

View File

@@ -1,8 +1,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace StellaOps.Notifier.Worker.Localization;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Observability;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Observability;

View File

@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
namespace StellaOps.Notifier.Worker.Observability;

View File

@@ -1,8 +1,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace StellaOps.Notifier.Worker.Observability;

View File

@@ -1,6 +1,7 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics;
namespace StellaOps.Notifier.Worker.Observability;

View File

@@ -1,7 +1,8 @@
using System.Collections.Immutable;
using System.Text.Json.Nodes;
using StellaOps.Notify.Engine;
using StellaOps.Notify.Models;
using System.Collections.Immutable;
using System.Text.Json.Nodes;
namespace StellaOps.Notifier.Worker.Processing;

Some files were not shown because too many files have changed in this diff Show More