save progress

This commit is contained in:
StellaOps Bot
2026-01-03 11:02:24 +02:00
parent ca578801fd
commit 83c37243e0
446 changed files with 22798 additions and 4031 deletions

View File

@@ -0,0 +1,20 @@
# Concelier Analyzer Tests Charter
## Mission
Own tests for the Concelier analyzer rules.
## Responsibilities
- Keep analyzer diagnostics deterministic and scoped to Concelier connectors.
- Maintain test coverage for allowed/blocked HttpClient usage.
## Key Paths
- `ConnectorHttpClientSandboxAnalyzerTests.cs`
## Required Reading
- `docs/modules/concelier/architecture.md`
- `docs/modules/platform/architecture-overview.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both corresponding sprint file `/docs/implplan/SPRINT_*.md` and the local `TASKS.md` when you start or finish work.
- 2. Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using StellaOps.TestKit;
namespace StellaOps.Concelier.Analyzers.Tests;
public sealed class ConnectorHttpClientSandboxAnalyzerTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsDiagnostic_ForHttpClientInConnectorNamespace()
{
const string source = """
using System.Net.Http;
namespace StellaOps.Concelier.Connector.Demo;
public sealed class ClientFactory
{
public HttpClient Create() => new HttpClient();
}
""";
var diagnostics = await AnalyzeAsync(source, "StellaOps.Concelier.Connector.Demo");
Assert.Contains(diagnostics, d => d.Id == ConnectorHttpClientSandboxAnalyzer.DiagnosticId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DoesNotReportDiagnostic_OutsideConnectorNamespace()
{
const string source = """
using System.Net.Http;
namespace Sample.App;
public sealed class ClientFactory
{
public HttpClient Create() => new HttpClient();
}
""";
var diagnostics = await AnalyzeAsync(source, "Sample.App");
Assert.DoesNotContain(diagnostics, d => d.Id == ConnectorHttpClientSandboxAnalyzer.DiagnosticId);
}
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("StellaOps.Concelier.Connector.Demo.Tests")]
[InlineData("StellaOps.Concelier.Connector.Demo.Test")]
[InlineData("StellaOps.Concelier.Connector.Demo.Testing")]
public async Task DoesNotReportDiagnostic_InTestAssemblies(string assemblyName)
{
const string source = """
using System.Net.Http;
namespace StellaOps.Concelier.Connector.Demo;
public sealed class ClientFactory
{
public HttpClient Create() => new HttpClient();
}
""";
var diagnostics = await AnalyzeAsync(source, assemblyName);
Assert.DoesNotContain(diagnostics, d => d.Id == ConnectorHttpClientSandboxAnalyzer.DiagnosticId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DoesNotReportDiagnostic_ForOtherTypes()
{
const string source = """
namespace StellaOps.Concelier.Connector.Demo;
public sealed class ClientFactory
{
public object Create() => new object();
}
""";
var diagnostics = await AnalyzeAsync(source, "StellaOps.Concelier.Connector.Demo");
Assert.DoesNotContain(diagnostics, d => d.Id == ConnectorHttpClientSandboxAnalyzer.DiagnosticId);
}
private static async Task<ImmutableArray<Diagnostic>> AnalyzeAsync(string source, string assemblyName)
{
var compilation = CSharpCompilation.Create(
assemblyName,
new[] { CSharpSyntaxTree.ParseText(source) },
CreateMetadataReferences(),
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var analyzer = new ConnectorHttpClientSandboxAnalyzer();
var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync();
}
private static IEnumerable<MetadataReference> CreateMetadataReferences()
{
yield return MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location);
yield return MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location);
yield return MetadataReference.CreateFromFile(typeof(HttpClient).GetTypeInfo().Assembly.Location);
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Analyzers\StellaOps.Concelier.Analyzers\StellaOps.Concelier.Analyzers.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
# Concelier Analyzer Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0144-A | DONE | Tests for StellaOps.Concelier.Analyzers. |

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.Acsc;
using StellaOps.Concelier.Connector.Acsc.Configuration;
using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
@@ -120,7 +121,65 @@ public sealed class AcscConnectorFetchTests
});
}
private async Task<ConnectorTestHarness> BuildHarnessAsync(bool preferRelay)
[Fact]
public async Task ProbeAsync_HeadNotAllowedFallsBackToGetAndPrefersDirect()
{
await using var harness = await BuildHarnessAsync(preferRelay: true);
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var cursor = AcscCursor.Empty.WithPreferredEndpoint(AcscEndpointPreference.Relay);
await stateRepository.UpdateCursorAsync(
AcscConnectorPlugin.SourceName,
cursor.ToDocumentObject(),
harness.TimeProvider.GetUtcNow(),
CancellationToken.None);
harness.Handler.AddResponse(HttpMethod.Head, AlertsDirectUri, _ => new HttpResponseMessage(HttpStatusCode.MethodNotAllowed));
harness.Handler.AddResponse(HttpMethod.Get, AlertsDirectUri, _ => new HttpResponseMessage(HttpStatusCode.OK));
var connector = harness.ServiceProvider.GetRequiredService<AcscConnector>();
await connector.ProbeAsync(CancellationToken.None);
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
Assert.Collection(harness.Handler.Requests,
request =>
{
Assert.Equal(HttpMethod.Head, request.Method);
Assert.Equal(AlertsDirectUri, request.Uri);
},
request =>
{
Assert.Equal(HttpMethod.Get, request.Method);
Assert.Equal(AlertsDirectUri, request.Uri);
});
}
[Fact]
public async Task ProbeAsync_RelayNotConfiguredForcesDirectPreference()
{
await using var harness = await BuildHarnessAsync(preferRelay: true, includeRelay: false);
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var cursor = AcscCursor.Empty.WithPreferredEndpoint(AcscEndpointPreference.Relay);
await stateRepository.UpdateCursorAsync(
AcscConnectorPlugin.SourceName,
cursor.ToDocumentObject(),
harness.TimeProvider.GetUtcNow(),
CancellationToken.None);
var connector = harness.ServiceProvider.GetRequiredService<AcscConnector>();
await connector.ProbeAsync(CancellationToken.None);
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
Assert.Empty(harness.Handler.Requests);
}
private async Task<ConnectorTestHarness> BuildHarnessAsync(bool preferRelay, bool includeRelay = true)
{
var initialTime = new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero);
var harness = new ConnectorTestHarness(_fixture, initialTime, AcscOptions.HttpClientName);
@@ -129,7 +188,7 @@ public sealed class AcscConnectorFetchTests
services.AddAcscConnector(options =>
{
options.BaseEndpoint = BaseEndpoint;
options.RelayEndpoint = RelayEndpoint;
options.RelayEndpoint = includeRelay ? RelayEndpoint : null;
options.EnableRelayFallback = true;
options.PreferRelayByDefault = preferRelay;
options.ForceRelay = false;

View File

@@ -8,7 +8,10 @@ using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Acsc;
using StellaOps.Concelier.Connector.Acsc.Configuration;
using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.Html;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Storage;
@@ -46,6 +49,14 @@ public sealed class AcscConnectorParseTests
var document = await documentStore.FindBySourceAndUriAsync(AcscConnectorPlugin.SourceName, feedUri.ToString(), CancellationToken.None);
Assert.NotNull(document);
var rawStorage = harness.ServiceProvider.GetRequiredService<RawDocumentStorage>();
var rawBytes = await rawStorage.DownloadAsync(document!.PayloadId!.Value, CancellationToken.None);
Assert.NotEmpty(rawBytes);
var rawText = Encoding.UTF8.GetString(rawBytes);
Assert.Contains("<rss", rawText, StringComparison.OrdinalIgnoreCase);
var sanityDto = AcscFeedParser.Parse(rawBytes, "alerts", new DateTimeOffset(2025, 10, 12, 6, 0, 0, TimeSpan.Zero), new HtmlContentSanitizer());
Assert.NotEmpty(sanityDto.Entries);
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
var refreshed = await documentStore.FindAsync(document!.Id, CancellationToken.None);
@@ -60,7 +71,9 @@ public sealed class AcscConnectorParseTests
var payload = dtoRecord.Payload;
Assert.NotNull(payload);
Assert.Equal("alerts", payload.GetValue("feedSlug").AsString);
Assert.Single(payload.GetValue("entries").AsDocumentArray);
var entriesValue = payload.GetValue("entries");
Assert.Equal(DocumentType.Array, entriesValue.DocumentType);
Assert.Single(entriesValue.AsDocumentArray);
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);

View File

@@ -0,0 +1,167 @@
using System.Text;
using System.Text.Json;
using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Connector.Common.Html;
using StellaOps.Concelier.Documents;
using Xunit;
namespace StellaOps.Concelier.Connector.Acsc.Tests.Acsc;
public sealed class AcscFeedParserTests
{
[Fact]
public void Parse_AtomFeed_ExtractsMetadataAndEntries()
{
const string payload = """
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ACSC Atom</title>
<updated>2025-10-12T05:00:00Z</updated>
<link rel="alternate" href="https://origin.example/atom" />
<entry>
<id>urn:uuid:9d6c472e-2ad8-4c6f-9ac8-111111111111</id>
<title>Atom Advisory</title>
<link href="https://origin.example/atom/1" />
<published>2025-10-11T01:00:00Z</published>
<updated>2025-10-11T02:00:00Z</updated>
<summary><![CDATA[
<p><strong>Serial number:</strong> ACSC-2025-050</p>
<p>Atom content.</p>
]]></summary>
</entry>
</feed>
""";
var dto = AcscFeedParser.Parse(
Encoding.UTF8.GetBytes(payload),
"atom",
new DateTimeOffset(2025, 10, 12, 6, 0, 0, TimeSpan.Zero),
new HtmlContentSanitizer());
Assert.Equal("ACSC Atom", dto.FeedTitle);
Assert.Equal("https://origin.example/atom", dto.FeedLink);
Assert.Single(dto.Entries);
var entry = dto.Entries[0];
Assert.Equal("urn:uuid:9d6c472e-2ad8-4c6f-9ac8-111111111111", entry.EntryId);
Assert.Equal("https://origin.example/atom/1", entry.Link);
Assert.Equal(DateTimeOffset.Parse("2025-10-11T01:00:00Z"), entry.Published);
Assert.False(string.IsNullOrWhiteSpace(entry.Summary));
}
[Fact]
public void Parse_MissingIdentifiers_UsesDeterministicFallbackId()
{
const string payload = """
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>ACSC Alerts</title>
<item>
<description>Advisory text without identifiers.</description>
</item>
</channel>
</rss>
""";
var sanitizer = new HtmlContentSanitizer();
var first = AcscFeedParser.Parse(
Encoding.UTF8.GetBytes(payload),
"alerts",
new DateTimeOffset(2025, 10, 12, 6, 0, 0, TimeSpan.Zero),
sanitizer);
var second = AcscFeedParser.Parse(
Encoding.UTF8.GetBytes(payload),
"alerts",
new DateTimeOffset(2025, 10, 12, 7, 0, 0, TimeSpan.Zero),
sanitizer);
Assert.Single(first.Entries);
Assert.Single(second.Entries);
var firstId = first.Entries[0].EntryId;
var secondId = second.Entries[0].EntryId;
Assert.False(string.IsNullOrWhiteSpace(firstId));
Assert.Equal(firstId, secondId);
}
[Fact]
public void Parse_RssPayloadWithContentEncoded_ReturnsEntries()
{
const string payload = """
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>ACSC Alerts</title>
<link>https://origin.example/feeds/alerts</link>
<lastBuildDate>Sun, 12 Oct 2025 04:20:00 GMT</lastBuildDate>
<item>
<title>ACSC-2025-001 Example Advisory</title>
<link>https://origin.example/advisories/example</link>
<guid>https://origin.example/advisories/example</guid>
<pubDate>Sun, 12 Oct 2025 03:00:00 GMT</pubDate>
<content:encoded><![CDATA[
<p><strong>Serial number:</strong> ACSC-2025-001</p>
<p><strong>Advisory type:</strong> Alert</p>
<p>First paragraph describing issue.</p>
]]></content:encoded>
</item>
</channel>
</rss>
""";
var dto = AcscFeedParser.Parse(
Encoding.UTF8.GetBytes(payload),
"alerts",
new DateTimeOffset(2025, 10, 12, 6, 0, 0, TimeSpan.Zero),
new HtmlContentSanitizer());
Assert.Single(dto.Entries);
Assert.Equal("ACSC-2025-001 Example Advisory", dto.Entries[0].Title);
}
[Fact]
public void Parse_RssPayload_RoundTripsThroughDocumentObject()
{
const string payload = """
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>ACSC Alerts</title>
<link>https://origin.example/feeds/alerts</link>
<lastBuildDate>Sun, 12 Oct 2025 04:20:00 GMT</lastBuildDate>
<item>
<title>ACSC-2025-001 Example Advisory</title>
<link>https://origin.example/advisories/example</link>
<guid>https://origin.example/advisories/example</guid>
<pubDate>Sun, 12 Oct 2025 03:00:00 GMT</pubDate>
<content:encoded><![CDATA[
<p><strong>Serial number:</strong> ACSC-2025-001</p>
<p><strong>Advisory type:</strong> Alert</p>
<p>First paragraph describing issue.</p>
]]></content:encoded>
</item>
</channel>
</rss>
""";
var dto = AcscFeedParser.Parse(
Encoding.UTF8.GetBytes(payload),
"alerts",
new DateTimeOffset(2025, 10, 12, 6, 0, 0, TimeSpan.Zero),
new HtmlContentSanitizer());
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
WriteIndented = false,
};
var json = JsonSerializer.Serialize(dto, jsonOptions);
var document = DocumentObject.Parse(json);
Assert.Single(document.GetValue("entries").AsDocumentArray);
var roundTrip = DocumentObject.Parse(document.ToJson());
Assert.Single(roundTrip.GetValue("entries").AsDocumentArray);
}
}

View File

@@ -0,0 +1,70 @@
using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage;
using Xunit;
namespace StellaOps.Concelier.Connector.Acsc.Tests.Acsc;
public sealed class AcscMapperTests
{
[Fact]
public void Map_UsesDeterministicFallbackKeyWhenIdentifiersMissing()
{
var entry = new AcscEntryDto(
EntryId: string.Empty,
Title: string.Empty,
Link: null,
FeedSlug: "alerts",
Published: null,
Updated: null,
Summary: "Summary text",
ContentHtml: string.Empty,
ContentText: string.Empty,
References: Array.Empty<AcscReferenceDto>(),
Aliases: Array.Empty<string>(),
Fields: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase));
var feed = new AcscFeedDto(
FeedSlug: "alerts",
FeedTitle: "ACSC Alerts",
FeedLink: "https://origin.example/alerts",
FeedUpdated: new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero),
ParsedAt: new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero),
Entries: new[] { entry });
var documentId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
var document = new DocumentRecord(
documentId,
"acsc",
"https://origin.example/alerts/rss",
new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero),
"sha256");
var dtoRecord = new DtoRecord(
Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
documentId,
"acsc",
"acsc.feed.v1",
new DocumentObject(),
new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero),
SchemaVersion: "acsc.feed.v1");
var first = AcscMapper.Map(
feed,
document,
dtoRecord,
"acsc",
new DateTimeOffset(2025, 10, 12, 1, 0, 0, TimeSpan.Zero));
var second = AcscMapper.Map(
feed,
document,
dtoRecord,
"acsc",
new DateTimeOffset(2025, 10, 12, 2, 0, 0, TimeSpan.Zero));
Assert.Single(first);
Assert.Single(second);
Assert.Equal(first[0].AdvisoryKey, second[0].AdvisoryKey);
}
}

View File

@@ -0,0 +1,20 @@
using StellaOps.Concelier.Connector.Acsc.Configuration;
using Xunit;
namespace StellaOps.Concelier.Connector.Acsc.Tests.Acsc;
public sealed class AcscOptionsTests
{
[Fact]
public void Validate_ForceRelayRequiresRelayEndpoint()
{
var options = new AcscOptions
{
RelayEndpoint = null,
ForceRelay = true,
};
var ex = Assert.Throws<InvalidOperationException>(() => options.Validate());
Assert.Contains("ForceRelay", ex.Message, StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -23,7 +23,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -46,7 +46,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -92,7 +92,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages"
"affectedPackages"
]
}
]
@@ -112,7 +112,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages"
"affectedPackages"
]
}
]
@@ -139,7 +139,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -162,7 +162,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -204,4 +204,4 @@
"summary": "Serial number: ACSC-2025-010\n\nSeverity: Critical\n\nSystems affected: ExampleCo Router X, ExampleCo Router Y\n\nRemote code execution on ExampleCo routers. See vendor patch.\n\nCVE references: CVE-2025-0001",
"title": "Critical router vulnerability"
}
]
]

View File

@@ -23,7 +23,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -46,7 +46,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -92,7 +92,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages"
"affectedPackages"
]
}
]
@@ -112,7 +112,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages"
"affectedPackages"
]
}
]
@@ -139,7 +139,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -162,7 +162,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -204,4 +204,4 @@
"summary": "Serial number: ACSC-2025-010\n\nSeverity: Critical\n\nSystems affected: ExampleCo Router X, ExampleCo Router Y\n\nRemote code execution on ExampleCo routers. See vendor patch.\n\nCVE references: CVE-2025-0001",
"title": "Critical router vulnerability"
}
]
]

View File

@@ -23,7 +23,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -46,7 +46,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -88,4 +88,4 @@
"summary": "Serial number: ACSC-2025-001\n\nAdvisory type: Alert\n\nFirst paragraph describing issue.\n\nSecond paragraph with Vendor patch.",
"title": "ACSC-2025-001 Example Advisory"
}
]
]

View File

@@ -23,7 +23,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -46,7 +46,7 @@
"decisionReason": null,
"recordedAt": "2025-10-12T00:00:00+00:00",
"fieldMask": [
"affectedpackages",
"affectedPackages",
"aliases",
"references",
"summary"
@@ -88,4 +88,4 @@
"summary": "Serial number: ACSC-2025-001\n\nAdvisory type: Alert\n\nFirst paragraph describing issue.\n\nSecond paragraph with Vendor patch.",
"title": "ACSC-2025-001 Example Advisory"
}
]
]