Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -13,14 +13,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.37" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="StackExchange.Redis" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
</ItemGroup>
<ItemGroup>

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -17,8 +17,7 @@ using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
using StellaOps.Plugin;
namespace StellaOps.Concelier.Connector.CertCc;
@@ -595,7 +594,7 @@ public sealed class CertCcConnector : IFeedConnector
return metadata;
}
private async Task<IReadOnlyList<string>> ReadSummaryNotesAsync(DocumentRecord document, CancellationToken cancellationToken)
private async Task<IReadOnlyList<string>> ReadSummaryNotesAsync(StorageDocument document, CancellationToken cancellationToken)
{
if (!document.PayloadId.HasValue)
{
@@ -606,6 +605,16 @@ public sealed class CertCcConnector : IFeedConnector
return CertCcSummaryParser.ParseNotes(payload);
}
private async Task<byte[]> DownloadDocumentAsync(StorageDocument document, CancellationToken cancellationToken)
{
if (!document.PayloadId.HasValue)
{
throw new InvalidOperationException($"Document {document.Id} has no GridFS payload.");
}
return await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
}
private async Task<byte[]> DownloadDocumentAsync(DocumentRecord document, CancellationToken cancellationToken)
{
if (!document.PayloadId.HasValue)

View File

@@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Markdig" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -10,4 +10,4 @@
<ProjectReference Include="..\StellaOps.Concelier.Connector.Common\StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -72,8 +72,8 @@ public sealed class JsonSchemaValidator : IJsonSchemaValidator
foreach (var kvp in node.Errors)
{
errors.Add(new JsonSchemaValidationError(
node.InstanceLocation?.ToString() ?? string.Empty,
node.SchemaLocation?.ToString() ?? string.Empty,
node.InstanceLocation.ToString() ?? string.Empty,
node.SchemaLocation.ToString() ?? string.Empty,
kvp.Value,
kvp.Key));
}

View File

@@ -6,11 +6,11 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonSchema.Net" Version="7.3.2" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageReference Include="AngleSharp" Version="1.2.0" />
<PackageReference Include="PdfPig" Version="0.1.12" />
<PackageReference Include="NuGet.Versioning" Version="6.13.2" />
<PackageReference Include="JsonSchema.Net" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="AngleSharp" />
<PackageReference Include="PdfPig" />
<PackageReference Include="NuGet.Versioning" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.Normalization\StellaOps.Concelier.Normalization.csproj" />
@@ -18,6 +18,6 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Storage.Postgres\StellaOps.Concelier.Storage.Postgres.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Persistence\StellaOps.Concelier.Persistence.csproj" />
</ItemGroup>
</Project>

View File

@@ -79,6 +79,11 @@ public sealed class CannedHttpMessageHandler : HttpMessageHandler
_fallback = null;
}
/// <summary>
/// Alias for <see cref="Clear"/> to maintain backward compatibility.
/// </summary>
public void Reset() => Clear();
/// <summary>
/// Throws if any responses remain queued.
/// </summary>
@@ -207,4 +212,10 @@ public sealed class CannedHttpMessageHandler : HttpMessageHandler
public void AddTextResponse(Uri requestUri, string content, string contentType = "text/plain", HttpStatusCode statusCode = HttpStatusCode.OK)
=> AddResponse(requestUri, () => BuildTextResponse(statusCode, content, contentType));
/// <summary>
/// Adds an error response with the specified HTTP status code.
/// </summary>
public void AddErrorResponse(Uri requestUri, HttpStatusCode statusCode)
=> AddResponse(requestUri, () => new HttpResponseMessage(statusCode));
}

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -12,4 +12,4 @@
<ProjectReference Include="..\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Normalization\StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -15,4 +15,4 @@
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Tasks.Core" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
<PackageReference Include="System.ServiceModel.Syndication" />
</ItemGroup>
<ItemGroup>
@@ -26,4 +26,4 @@
</AssemblyAttribute>
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -12,4 +12,4 @@
<ProjectReference Include="..\StellaOps.Concelier.Normalization\StellaOps.Concelier.Normalization.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Connector.Common\StellaOps.Concelier.Connector.Common.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -23,4 +23,4 @@
<ItemGroup>
<EmbeddedResource Include="Schemas\kev-catalog.schema.json" />
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -15,4 +15,4 @@
<ProjectReference Include="..\StellaOps.Concelier.Connector.Common\StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Normalization\StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -21,4 +21,4 @@
<_Parameter1>StellaOps.Concelier.Connector.Osv.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
</Project>

View File

@@ -16,4 +16,4 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.2.0" />
<PackageReference Include="AngleSharp" />
</ItemGroup>
<ItemGroup>
@@ -20,4 +20,4 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.2.0" />
<PackageReference Include="AngleSharp" />
</ItemGroup>
<ItemGroup>
@@ -21,4 +21,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="1.2.0" />
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
<PackageReference Include="AngleSharp" />
<PackageReference Include="System.ServiceModel.Syndication" />
</ItemGroup>
<ItemGroup>
@@ -28,4 +28,4 @@
<_Parameter1>StellaOps.Concelier.Connector.Vndr.Chromium.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
namespace StellaOps.Concelier.Connector.Vndr.Oracle.Internal;
@@ -203,6 +204,15 @@ internal sealed record OracleFetchCacheEntry(string? Sha256, string? ETag, DateT
document.LastModified?.ToUniversalTime());
}
public static OracleFetchCacheEntry FromDocument(StorageDocument document)
{
ArgumentNullException.ThrowIfNull(document);
return new OracleFetchCacheEntry(
document.Sha256 ?? string.Empty,
document.Etag,
document.LastModified?.ToUniversalTime());
}
public bool Matches(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
@@ -224,4 +234,26 @@ internal sealed record OracleFetchCacheEntry(string? Sha256, string? ETag, DateT
return false;
}
public bool Matches(StorageDocument document)
{
ArgumentNullException.ThrowIfNull(document);
if (!string.IsNullOrEmpty(Sha256) && !string.IsNullOrEmpty(document.Sha256))
{
return string.Equals(Sha256, document.Sha256, StringComparison.OrdinalIgnoreCase);
}
if (!string.IsNullOrEmpty(ETag) && !string.IsNullOrEmpty(document.Etag))
{
return string.Equals(ETag, document.Etag, StringComparison.Ordinal);
}
if (LastModified.HasValue && document.LastModified.HasValue)
{
return LastModified.Value.ToUniversalTime() == document.LastModified.Value.ToUniversalTime();
}
return false;
}
}

View File

@@ -13,8 +13,7 @@ using StellaOps.Concelier.Connector.Vndr.Oracle.Configuration;
using StellaOps.Concelier.Connector.Vndr.Oracle.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Plugin;

View File

@@ -13,4 +13,4 @@
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,6 +1,7 @@
using System;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
@@ -61,6 +62,16 @@ internal sealed record VmwareFetchCacheEntry(string? Sha256, string? ETag, DateT
document.LastModified?.ToUniversalTime());
}
public static VmwareFetchCacheEntry FromDocument(StorageDocument document)
{
ArgumentNullException.ThrowIfNull(document);
return new VmwareFetchCacheEntry(
document.Sha256,
document.Etag,
document.LastModified?.ToUniversalTime());
}
public bool Matches(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
@@ -85,4 +96,29 @@ internal sealed record VmwareFetchCacheEntry(string? Sha256, string? ETag, DateT
return false;
}
public bool Matches(StorageDocument document)
{
ArgumentNullException.ThrowIfNull(document);
if (!string.IsNullOrEmpty(Sha256) && !string.IsNullOrEmpty(document.Sha256)
&& string.Equals(Sha256, document.Sha256, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrEmpty(ETag) && !string.IsNullOrEmpty(document.Etag)
&& string.Equals(ETag, document.Etag, StringComparison.Ordinal))
{
return true;
}
if (LastModified.HasValue && document.LastModified.HasValue
&& LastModified.Value.ToUniversalTime() == document.LastModified.Value.ToUniversalTime())
{
return true;
}
return false;
}
}

View File

@@ -19,4 +19,4 @@
<_Parameter1>StellaOps.Concelier.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
</Project>

View File

@@ -16,8 +16,7 @@ using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
using StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Plugin;

View File

@@ -8,12 +8,12 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Cronos" Version="0.9.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Cronos" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />

View File

@@ -16,9 +16,9 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
</ItemGroup>
</Project>

View File

@@ -15,8 +15,8 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@ using StellaOps.Concelier.Federation.Compression;
using StellaOps.Concelier.Federation.Models;
using StellaOps.Concelier.Federation.Serialization;
using StellaOps.Concelier.Federation.Signing;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
namespace StellaOps.Concelier.Federation.Export;

View File

@@ -2,7 +2,7 @@ using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core.Canonical;
using StellaOps.Concelier.Federation.Models;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
namespace StellaOps.Concelier.Federation.Export;

View File

@@ -8,17 +8,17 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="ZstdSharp.Port" Version="0.8.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="ZstdSharp.Port" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Storage.Postgres\StellaOps.Concelier.Storage.Postgres.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Persistence\StellaOps.Concelier.Persistence.csproj" />
<ProjectReference Include="..\StellaOps.Concelier.Cache.Valkey\StellaOps.Concelier.Cache.Valkey.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Provenance\StellaOps.Provenance.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Messaging\StellaOps.Messaging.csproj" />
<ProjectReference Include="..\..\..\Router/__Libraries/StellaOps.Messaging\StellaOps.Messaging.csproj" />
</ItemGroup>
</Project>

View File

@@ -13,13 +13,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
</ItemGroup>
<ItemGroup>

View File

@@ -8,15 +8,15 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageReference Include="Semver" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Semver" />
<ProjectReference Include="../StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
<ProjectReference Include="../StellaOps.Concelier.ProofService/StellaOps.Concelier.ProofService.csproj" />
<ProjectReference Include="../../../Attestor/__Libraries/StellaOps.Attestor.ProofChain/StellaOps.Attestor.ProofChain.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
<ProjectReference Include="../../../Router/__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.VersionComparison/StellaOps.VersionComparison.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,7 +8,7 @@
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@@ -17,6 +17,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NuGet.Versioning" Version="6.13.2" />
<PackageReference Include="NuGet.Versioning" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
namespace StellaOps.Concelier.Persistence.EfCore.Context;
/// <summary>
/// EF Core DbContext for Concelier module.
/// This is a stub that will be scaffolded from the PostgreSQL database.
/// </summary>
public class ConcelierDbContext : DbContext
{
public ConcelierDbContext(DbContextOptions<ConcelierDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("vuln");
base.OnModelCreating(modelBuilder);
}
}

View File

@@ -0,0 +1,118 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
using StellaOps.Concelier.Persistence.Postgres.Advisories;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.Concelier.Core.Linksets;
using StorageContracts = StellaOps.Concelier.Storage;
using AdvisoryContracts = StellaOps.Concelier.Storage.Advisories;
using ExportingContracts = StellaOps.Concelier.Storage.Exporting;
using JpFlagsContracts = StellaOps.Concelier.Storage.JpFlags;
using PsirtContracts = StellaOps.Concelier.Storage.PsirtFlags;
using HistoryContracts = StellaOps.Concelier.Storage.ChangeHistory;
using StellaOps.Concelier.Merge.Backport;
namespace StellaOps.Concelier.Persistence.Extensions;
/// <summary>
/// Extension methods for configuring Concelier persistence services.
/// </summary>
public static class ConcelierPersistenceExtensions
{
/// <summary>
/// Adds Concelier PostgreSQL persistence services.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="configuration">Configuration root.</param>
/// <param name="sectionName">Configuration section name for PostgreSQL options.</param>
/// <returns>Service collection for chaining.</returns>
public static IServiceCollection AddConcelierPersistence(
this IServiceCollection services,
IConfiguration configuration,
string sectionName = "Postgres:Concelier")
{
services.Configure<PostgresOptions>(sectionName, configuration.GetSection(sectionName));
services.AddSingleton<ConcelierDataSource>();
// Register repositories
services.AddScoped<IAdvisoryRepository, AdvisoryRepository>();
services.AddScoped<IPostgresAdvisoryStore, PostgresAdvisoryStore>();
services.AddScoped<ISourceRepository, SourceRepository>();
services.AddScoped<IAdvisoryAliasRepository, AdvisoryAliasRepository>();
services.AddScoped<IAdvisoryCvssRepository, AdvisoryCvssRepository>();
services.AddScoped<IAdvisoryAffectedRepository, AdvisoryAffectedRepository>();
services.AddScoped<IAdvisoryReferenceRepository, AdvisoryReferenceRepository>();
services.AddScoped<IAdvisoryCreditRepository, AdvisoryCreditRepository>();
services.AddScoped<IAdvisoryWeaknessRepository, AdvisoryWeaknessRepository>();
services.AddScoped<IKevFlagRepository, KevFlagRepository>();
services.AddScoped<StellaOps.Concelier.Persistence.Postgres.Repositories.ISourceStateRepository, SourceStateRepository>();
services.AddScoped<AdvisoryContracts.IAdvisoryStore, PostgresAdvisoryStore>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddScoped<StorageContracts.ISourceStateRepository, PostgresSourceStateAdapter>();
services.AddScoped<IFeedSnapshotRepository, FeedSnapshotRepository>();
services.AddScoped<IAdvisorySnapshotRepository, AdvisorySnapshotRepository>();
services.AddScoped<IMergeEventRepository, MergeEventRepository>();
services.AddScoped<IAdvisoryLinksetStore, AdvisoryLinksetCacheRepository>();
services.AddScoped<IAdvisoryLinksetLookup>(sp => sp.GetRequiredService<IAdvisoryLinksetStore>());
services.AddScoped<StorageContracts.IDocumentStore, PostgresDocumentStore>();
services.AddScoped<StorageContracts.IDtoStore, PostgresDtoStore>();
services.AddScoped<ExportingContracts.IExportStateStore, PostgresExportStateStore>();
services.AddScoped<PsirtContracts.IPsirtFlagStore, PostgresPsirtFlagStore>();
services.AddScoped<JpFlagsContracts.IJpFlagStore, PostgresJpFlagStore>();
services.AddScoped<HistoryContracts.IChangeHistoryStore, PostgresChangeHistoryStore>();
// Provenance scope services (backport integration)
services.AddScoped<IProvenanceScopeRepository, ProvenanceScopeRepository>();
services.AddScoped<IProvenanceScopeStore, PostgresProvenanceScopeStore>();
return services;
}
/// <summary>
/// Adds Concelier PostgreSQL persistence services with explicit options.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="configureOptions">Options configuration action.</param>
/// <returns>Service collection for chaining.</returns>
public static IServiceCollection AddConcelierPersistence(
this IServiceCollection services,
Action<PostgresOptions> configureOptions)
{
services.Configure(configureOptions);
services.AddSingleton<ConcelierDataSource>();
// Register repositories
services.AddScoped<IAdvisoryRepository, AdvisoryRepository>();
services.AddScoped<IPostgresAdvisoryStore, PostgresAdvisoryStore>();
services.AddScoped<ISourceRepository, SourceRepository>();
services.AddScoped<IAdvisoryAliasRepository, AdvisoryAliasRepository>();
services.AddScoped<IAdvisoryCvssRepository, AdvisoryCvssRepository>();
services.AddScoped<IAdvisoryAffectedRepository, AdvisoryAffectedRepository>();
services.AddScoped<IAdvisoryReferenceRepository, AdvisoryReferenceRepository>();
services.AddScoped<IAdvisoryCreditRepository, AdvisoryCreditRepository>();
services.AddScoped<IAdvisoryWeaknessRepository, AdvisoryWeaknessRepository>();
services.AddScoped<IKevFlagRepository, KevFlagRepository>();
services.AddScoped<StellaOps.Concelier.Persistence.Postgres.Repositories.ISourceStateRepository, SourceStateRepository>();
services.AddScoped<AdvisoryContracts.IAdvisoryStore, PostgresAdvisoryStore>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddScoped<StorageContracts.ISourceStateRepository, PostgresSourceStateAdapter>();
services.AddScoped<IFeedSnapshotRepository, FeedSnapshotRepository>();
services.AddScoped<IAdvisorySnapshotRepository, AdvisorySnapshotRepository>();
services.AddScoped<IMergeEventRepository, MergeEventRepository>();
services.AddScoped<IAdvisoryLinksetStore, AdvisoryLinksetCacheRepository>();
services.AddScoped<IAdvisoryLinksetLookup>(sp => sp.GetRequiredService<IAdvisoryLinksetStore>());
services.AddScoped<StorageContracts.IDocumentStore, PostgresDocumentStore>();
services.AddScoped<StorageContracts.IDtoStore, PostgresDtoStore>();
services.AddScoped<ExportingContracts.IExportStateStore, PostgresExportStateStore>();
services.AddScoped<PsirtContracts.IPsirtFlagStore, PostgresPsirtFlagStore>();
services.AddScoped<JpFlagsContracts.IJpFlagStore, PostgresJpFlagStore>();
services.AddScoped<HistoryContracts.IChangeHistoryStore, PostgresChangeHistoryStore>();
// Provenance scope services (backport integration)
services.AddScoped<IProvenanceScopeRepository, ProvenanceScopeRepository>();
services.AddScoped<IProvenanceScopeStore, PostgresProvenanceScopeStore>();
return services;
}
}

View File

@@ -0,0 +1,728 @@
-- Concelier/Vuln Schema: Consolidated Initial Schema
-- Consolidated from migrations 001-017 (pre_1.0 archived)
-- Creates the complete vuln and concelier schemas for vulnerability advisory management
BEGIN;
-- ============================================================================
-- SECTION 1: Schema and Extension Creation
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS vuln;
CREATE SCHEMA IF NOT EXISTS concelier;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- ============================================================================
-- SECTION 2: Helper Functions
-- ============================================================================
CREATE OR REPLACE FUNCTION vuln.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION vuln.update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION vuln.update_advisory_search_vector()
RETURNS TRIGGER AS $$
BEGIN
NEW.search_vector =
setweight(to_tsvector('english', COALESCE(NEW.primary_vuln_id, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(NEW.title, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(NEW.summary, '')), 'C') ||
setweight(to_tsvector('english', COALESCE(NEW.description, '')), 'D');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- SECTION 3: Core vuln Tables
-- ============================================================================
-- Sources table (feed sources)
CREATE TABLE IF NOT EXISTS vuln.sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
source_type TEXT NOT NULL,
url TEXT,
priority INT NOT NULL DEFAULT 0,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config JSONB NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_sources_enabled ON vuln.sources(enabled, priority DESC);
CREATE TRIGGER trg_sources_updated_at
BEFORE UPDATE ON vuln.sources
FOR EACH ROW EXECUTE FUNCTION vuln.update_updated_at();
-- Feed snapshots table
CREATE TABLE IF NOT EXISTS vuln.feed_snapshots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_id UUID NOT NULL REFERENCES vuln.sources(id),
snapshot_id TEXT NOT NULL,
advisory_count INT NOT NULL DEFAULT 0,
checksum TEXT,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(source_id, snapshot_id)
);
CREATE INDEX idx_feed_snapshots_source ON vuln.feed_snapshots(source_id);
CREATE INDEX idx_feed_snapshots_created ON vuln.feed_snapshots(created_at);
-- Advisory snapshots table
CREATE TABLE IF NOT EXISTS vuln.advisory_snapshots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
feed_snapshot_id UUID NOT NULL REFERENCES vuln.feed_snapshots(id),
advisory_key TEXT NOT NULL,
content_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(feed_snapshot_id, advisory_key)
);
CREATE INDEX idx_advisory_snapshots_feed ON vuln.advisory_snapshots(feed_snapshot_id);
CREATE INDEX idx_advisory_snapshots_key ON vuln.advisory_snapshots(advisory_key);
-- Advisories table with generated columns
CREATE TABLE IF NOT EXISTS vuln.advisories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_key TEXT NOT NULL UNIQUE,
primary_vuln_id TEXT NOT NULL,
source_id UUID REFERENCES vuln.sources(id),
title TEXT,
summary TEXT,
description TEXT,
severity TEXT CHECK (severity IN ('critical', 'high', 'medium', 'low', 'unknown')),
published_at TIMESTAMPTZ,
modified_at TIMESTAMPTZ,
withdrawn_at TIMESTAMPTZ,
provenance JSONB NOT NULL DEFAULT '{}',
raw_payload JSONB,
search_vector TSVECTOR,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Generated columns for provenance
provenance_source_key TEXT GENERATED ALWAYS AS (provenance->>'source_key') STORED,
provenance_feed_id TEXT GENERATED ALWAYS AS (provenance->>'feed_id') STORED,
provenance_ingested_at TIMESTAMPTZ GENERATED ALWAYS AS ((provenance->>'ingested_at')::TIMESTAMPTZ) STORED
);
CREATE INDEX idx_advisories_vuln_id ON vuln.advisories(primary_vuln_id);
CREATE INDEX idx_advisories_source ON vuln.advisories(source_id);
CREATE INDEX idx_advisories_severity ON vuln.advisories(severity);
CREATE INDEX idx_advisories_published ON vuln.advisories(published_at);
CREATE INDEX idx_advisories_modified ON vuln.advisories(modified_at);
CREATE INDEX idx_advisories_search ON vuln.advisories USING GIN(search_vector);
CREATE INDEX IF NOT EXISTS ix_advisories_provenance_source ON vuln.advisories(provenance_source_key) WHERE provenance_source_key IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_advisories_provenance_feed ON vuln.advisories(provenance_feed_id) WHERE provenance_feed_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_advisories_provenance_ingested ON vuln.advisories(provenance_ingested_at DESC) WHERE provenance_ingested_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_advisories_severity_ingested ON vuln.advisories(severity, provenance_ingested_at DESC) WHERE provenance_ingested_at IS NOT NULL;
CREATE TRIGGER trg_advisories_search_vector
BEFORE INSERT OR UPDATE ON vuln.advisories
FOR EACH ROW EXECUTE FUNCTION vuln.update_advisory_search_vector();
CREATE TRIGGER trg_advisories_updated_at
BEFORE UPDATE ON vuln.advisories
FOR EACH ROW EXECUTE FUNCTION vuln.update_updated_at();
-- Advisory aliases table
CREATE TABLE IF NOT EXISTS vuln.advisory_aliases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
alias_type TEXT NOT NULL,
alias_value TEXT NOT NULL,
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(advisory_id, alias_type, alias_value)
);
CREATE INDEX idx_advisory_aliases_advisory ON vuln.advisory_aliases(advisory_id);
CREATE INDEX idx_advisory_aliases_value ON vuln.advisory_aliases(alias_type, alias_value);
CREATE INDEX idx_advisory_aliases_cve ON vuln.advisory_aliases(alias_value) WHERE alias_type = 'CVE';
-- Advisory CVSS scores table
CREATE TABLE IF NOT EXISTS vuln.advisory_cvss (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
cvss_version TEXT NOT NULL,
vector_string TEXT NOT NULL,
base_score NUMERIC(3,1) NOT NULL,
base_severity TEXT,
exploitability_score NUMERIC(3,1),
impact_score NUMERIC(3,1),
source TEXT,
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(advisory_id, cvss_version, source)
);
CREATE INDEX idx_advisory_cvss_advisory ON vuln.advisory_cvss(advisory_id);
CREATE INDEX idx_advisory_cvss_score ON vuln.advisory_cvss(base_score DESC);
-- Advisory affected packages with generated columns
CREATE TABLE IF NOT EXISTS vuln.advisory_affected (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
ecosystem TEXT NOT NULL,
package_name TEXT NOT NULL,
purl TEXT,
version_range JSONB NOT NULL DEFAULT '{}',
versions_affected TEXT[],
versions_fixed TEXT[],
database_specific JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Generated columns for PURL parsing
purl_type TEXT GENERATED ALWAYS AS (
CASE WHEN purl IS NOT NULL AND purl LIKE 'pkg:%'
THEN split_part(split_part(purl, ':', 2), '/', 1) ELSE NULL END
) STORED,
purl_name TEXT GENERATED ALWAYS AS (
CASE WHEN purl IS NOT NULL AND purl LIKE 'pkg:%'
THEN split_part(split_part(split_part(purl, ':', 2), '@', 1), '/', -1) ELSE NULL END
) STORED
);
CREATE INDEX idx_advisory_affected_advisory ON vuln.advisory_affected(advisory_id);
CREATE INDEX idx_advisory_affected_ecosystem ON vuln.advisory_affected(ecosystem, package_name);
CREATE INDEX idx_advisory_affected_purl ON vuln.advisory_affected(purl);
CREATE INDEX idx_advisory_affected_purl_trgm ON vuln.advisory_affected USING GIN(purl gin_trgm_ops);
CREATE INDEX IF NOT EXISTS ix_advisory_affected_purl_type ON vuln.advisory_affected(purl_type) WHERE purl_type IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_advisory_affected_purl_name ON vuln.advisory_affected(purl_name) WHERE purl_name IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_advisory_affected_ecosystem_type ON vuln.advisory_affected(ecosystem, purl_type) WHERE purl_type IS NOT NULL;
-- Advisory references table
CREATE TABLE IF NOT EXISTS vuln.advisory_references (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
ref_type TEXT NOT NULL,
url TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_advisory_references_advisory ON vuln.advisory_references(advisory_id);
-- Advisory credits table
CREATE TABLE IF NOT EXISTS vuln.advisory_credits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
name TEXT NOT NULL,
contact TEXT,
credit_type TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_advisory_credits_advisory ON vuln.advisory_credits(advisory_id);
-- Advisory weaknesses table (CWE)
CREATE TABLE IF NOT EXISTS vuln.advisory_weaknesses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
cwe_id TEXT NOT NULL,
description TEXT,
source TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(advisory_id, cwe_id)
);
CREATE INDEX idx_advisory_weaknesses_advisory ON vuln.advisory_weaknesses(advisory_id);
CREATE INDEX idx_advisory_weaknesses_cwe ON vuln.advisory_weaknesses(cwe_id);
-- KEV flags table
CREATE TABLE IF NOT EXISTS vuln.kev_flags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
advisory_id UUID NOT NULL REFERENCES vuln.advisories(id) ON DELETE CASCADE,
cve_id TEXT NOT NULL,
vendor_project TEXT,
product TEXT,
vulnerability_name TEXT,
date_added DATE NOT NULL,
due_date DATE,
known_ransomware_use BOOLEAN NOT NULL DEFAULT FALSE,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(advisory_id, cve_id)
);
CREATE INDEX idx_kev_flags_advisory ON vuln.kev_flags(advisory_id);
CREATE INDEX idx_kev_flags_cve ON vuln.kev_flags(cve_id);
CREATE INDEX idx_kev_flags_date ON vuln.kev_flags(date_added);
-- Source states table
CREATE TABLE IF NOT EXISTS vuln.source_states (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_id UUID NOT NULL REFERENCES vuln.sources(id) UNIQUE,
cursor TEXT,
last_sync_at TIMESTAMPTZ,
last_success_at TIMESTAMPTZ,
last_error TEXT,
sync_count BIGINT NOT NULL DEFAULT 0,
error_count INT NOT NULL DEFAULT 0,
metadata JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_source_states_source ON vuln.source_states(source_id);
CREATE TRIGGER trg_source_states_updated_at
BEFORE UPDATE ON vuln.source_states
FOR EACH ROW EXECUTE FUNCTION vuln.update_updated_at();
-- ============================================================================
-- SECTION 4: Partitioned Merge Events Table
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.merge_events (
id BIGSERIAL,
advisory_id UUID NOT NULL,
source_id UUID,
event_type TEXT NOT NULL,
old_value JSONB,
new_value JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
DO $$
DECLARE
v_start DATE;
v_end DATE;
v_partition_name TEXT;
BEGIN
v_start := date_trunc('month', NOW() - INTERVAL '12 months')::DATE;
WHILE v_start <= date_trunc('month', NOW() + INTERVAL '3 months')::DATE LOOP
v_end := (v_start + INTERVAL '1 month')::DATE;
v_partition_name := 'merge_events_' || to_char(v_start, 'YYYY_MM');
IF NOT EXISTS (
SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = 'vuln' AND c.relname = v_partition_name
) THEN
EXECUTE format(
'CREATE TABLE vuln.%I PARTITION OF vuln.merge_events FOR VALUES FROM (%L) TO (%L)',
v_partition_name, v_start, v_end
);
END IF;
v_start := v_end;
END LOOP;
END $$;
CREATE TABLE IF NOT EXISTS vuln.merge_events_default PARTITION OF vuln.merge_events DEFAULT;
CREATE INDEX IF NOT EXISTS ix_merge_events_part_advisory ON vuln.merge_events(advisory_id);
CREATE INDEX IF NOT EXISTS ix_merge_events_part_source ON vuln.merge_events(source_id) WHERE source_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS ix_merge_events_part_event_type ON vuln.merge_events(event_type);
CREATE INDEX IF NOT EXISTS brin_merge_events_part_created ON vuln.merge_events USING BRIN(created_at) WITH (pages_per_range = 128);
COMMENT ON TABLE vuln.merge_events IS 'Advisory merge event log. Partitioned monthly by created_at.';
-- ============================================================================
-- SECTION 5: LNM Linkset Cache
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.lnm_linkset_cache (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
source TEXT NOT NULL,
advisory_id TEXT NOT NULL,
observations TEXT[] NOT NULL DEFAULT '{}',
normalized JSONB,
conflicts JSONB,
provenance JSONB,
confidence DOUBLE PRECISION,
built_by_job_id TEXT,
created_at TIMESTAMPTZ NOT NULL,
CONSTRAINT uq_lnm_linkset_cache UNIQUE (tenant_id, advisory_id, source)
);
CREATE INDEX IF NOT EXISTS idx_lnm_linkset_cache_order ON vuln.lnm_linkset_cache(tenant_id, created_at DESC, advisory_id, source);
-- ============================================================================
-- SECTION 6: Sync Ledger and Site Policy
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.sync_ledger (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id TEXT NOT NULL,
cursor TEXT NOT NULL,
bundle_hash TEXT NOT NULL,
items_count INT NOT NULL DEFAULT 0,
signed_at TIMESTAMPTZ NOT NULL,
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_sync_ledger_site_cursor UNIQUE (site_id, cursor),
CONSTRAINT uq_sync_ledger_bundle UNIQUE (bundle_hash)
);
CREATE INDEX IF NOT EXISTS idx_sync_ledger_site ON vuln.sync_ledger(site_id);
CREATE INDEX IF NOT EXISTS idx_sync_ledger_site_time ON vuln.sync_ledger(site_id, signed_at DESC);
COMMENT ON TABLE vuln.sync_ledger IS 'Federation sync cursor tracking per remote site';
CREATE TABLE IF NOT EXISTS vuln.site_policy (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id TEXT NOT NULL UNIQUE,
display_name TEXT,
allowed_sources TEXT[] NOT NULL DEFAULT '{}',
denied_sources TEXT[] NOT NULL DEFAULT '{}',
max_bundle_size_mb INT NOT NULL DEFAULT 100,
max_items_per_bundle INT NOT NULL DEFAULT 10000,
require_signature BOOLEAN NOT NULL DEFAULT TRUE,
allowed_signers TEXT[] NOT NULL DEFAULT '{}',
enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_site_policy_enabled ON vuln.site_policy(enabled) WHERE enabled = TRUE;
CREATE TRIGGER trg_site_policy_updated
BEFORE UPDATE ON vuln.site_policy
FOR EACH ROW EXECUTE FUNCTION vuln.update_timestamp();
-- ============================================================================
-- SECTION 7: Advisory Canonical and Source Edge
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.advisory_canonical (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cve TEXT NOT NULL,
affects_key TEXT NOT NULL,
version_range JSONB,
weakness TEXT[] NOT NULL DEFAULT '{}',
merge_hash TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'stub', 'withdrawn')),
severity TEXT CHECK (severity IN ('critical', 'high', 'medium', 'low', 'none', 'unknown')),
epss_score NUMERIC(5,4),
exploit_known BOOLEAN NOT NULL DEFAULT FALSE,
title TEXT,
summary TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_advisory_canonical_merge_hash UNIQUE (merge_hash)
);
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_cve ON vuln.advisory_canonical(cve);
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_affects ON vuln.advisory_canonical(affects_key);
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_merge_hash ON vuln.advisory_canonical(merge_hash);
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_status ON vuln.advisory_canonical(status) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_severity ON vuln.advisory_canonical(severity) WHERE severity IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_exploit ON vuln.advisory_canonical(exploit_known) WHERE exploit_known = TRUE;
CREATE INDEX IF NOT EXISTS idx_advisory_canonical_updated ON vuln.advisory_canonical(updated_at DESC);
CREATE TRIGGER trg_advisory_canonical_updated
BEFORE UPDATE ON vuln.advisory_canonical
FOR EACH ROW EXECUTE FUNCTION vuln.update_timestamp();
CREATE TABLE IF NOT EXISTS vuln.advisory_source_edge (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id) ON DELETE CASCADE,
source_id UUID NOT NULL REFERENCES vuln.sources(id) ON DELETE RESTRICT,
source_advisory_id TEXT NOT NULL,
source_doc_hash TEXT NOT NULL,
vendor_status TEXT CHECK (vendor_status IN ('affected', 'not_affected', 'fixed', 'under_investigation')),
precedence_rank INT NOT NULL DEFAULT 100,
dsse_envelope JSONB,
raw_payload JSONB,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_advisory_source_edge_unique UNIQUE (canonical_id, source_id, source_doc_hash)
);
CREATE INDEX IF NOT EXISTS idx_source_edge_canonical ON vuln.advisory_source_edge(canonical_id);
CREATE INDEX IF NOT EXISTS idx_source_edge_source ON vuln.advisory_source_edge(source_id);
CREATE INDEX IF NOT EXISTS idx_source_edge_advisory_id ON vuln.advisory_source_edge(source_advisory_id);
CREATE INDEX IF NOT EXISTS idx_source_edge_canonical_source ON vuln.advisory_source_edge(canonical_id, source_id);
CREATE INDEX IF NOT EXISTS idx_source_edge_fetched ON vuln.advisory_source_edge(fetched_at DESC);
CREATE INDEX IF NOT EXISTS idx_source_edge_dsse_gin ON vuln.advisory_source_edge USING GIN(dsse_envelope jsonb_path_ops);
-- ============================================================================
-- SECTION 8: Interest Score and SBOM Registry
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.interest_score (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id) ON DELETE CASCADE,
score NUMERIC(3,2) NOT NULL CHECK (score >= 0 AND score <= 1),
reasons JSONB NOT NULL DEFAULT '[]',
last_seen_in_build UUID,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_interest_score_canonical UNIQUE (canonical_id)
);
CREATE INDEX IF NOT EXISTS idx_interest_score_score ON vuln.interest_score(score DESC);
CREATE INDEX IF NOT EXISTS idx_interest_score_computed ON vuln.interest_score(computed_at DESC);
CREATE INDEX IF NOT EXISTS idx_interest_score_high ON vuln.interest_score(canonical_id) WHERE score >= 0.7;
CREATE INDEX IF NOT EXISTS idx_interest_score_low ON vuln.interest_score(canonical_id) WHERE score < 0.2;
CREATE TABLE IF NOT EXISTS vuln.sbom_registry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
digest TEXT NOT NULL,
format TEXT NOT NULL CHECK (format IN ('cyclonedx', 'spdx')),
spec_version TEXT NOT NULL,
primary_name TEXT,
primary_version TEXT,
component_count INT NOT NULL DEFAULT 0,
affected_count INT NOT NULL DEFAULT 0,
source TEXT NOT NULL,
tenant_id TEXT,
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_matched_at TIMESTAMPTZ,
CONSTRAINT uq_sbom_registry_digest UNIQUE (digest)
);
CREATE INDEX IF NOT EXISTS idx_sbom_registry_tenant ON vuln.sbom_registry(tenant_id) WHERE tenant_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_sbom_registry_primary ON vuln.sbom_registry(primary_name, primary_version);
CREATE INDEX IF NOT EXISTS idx_sbom_registry_registered ON vuln.sbom_registry(registered_at DESC);
CREATE INDEX IF NOT EXISTS idx_sbom_registry_affected ON vuln.sbom_registry(affected_count DESC) WHERE affected_count > 0;
CREATE TABLE IF NOT EXISTS vuln.sbom_canonical_match (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sbom_id UUID NOT NULL REFERENCES vuln.sbom_registry(id) ON DELETE CASCADE,
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id) ON DELETE CASCADE,
purl TEXT NOT NULL,
match_method TEXT NOT NULL CHECK (match_method IN ('exact_purl', 'purl_version_range', 'cpe', 'name_version')),
confidence NUMERIC(3,2) NOT NULL DEFAULT 1.0 CHECK (confidence >= 0 AND confidence <= 1),
is_reachable BOOLEAN NOT NULL DEFAULT FALSE,
is_deployed BOOLEAN NOT NULL DEFAULT FALSE,
matched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_sbom_canonical_match UNIQUE (sbom_id, canonical_id, purl)
);
CREATE INDEX IF NOT EXISTS idx_sbom_match_sbom ON vuln.sbom_canonical_match(sbom_id);
CREATE INDEX IF NOT EXISTS idx_sbom_match_canonical ON vuln.sbom_canonical_match(canonical_id);
CREATE INDEX IF NOT EXISTS idx_sbom_match_purl ON vuln.sbom_canonical_match(purl);
CREATE INDEX IF NOT EXISTS idx_sbom_match_reachable ON vuln.sbom_canonical_match(canonical_id) WHERE is_reachable = TRUE;
CREATE TABLE IF NOT EXISTS vuln.purl_canonical_index (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
purl TEXT NOT NULL,
purl_type TEXT NOT NULL,
purl_namespace TEXT,
purl_name TEXT NOT NULL,
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id) ON DELETE CASCADE,
version_constraint TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_purl_canonical UNIQUE (purl, canonical_id)
);
CREATE INDEX IF NOT EXISTS idx_purl_index_lookup ON vuln.purl_canonical_index(purl_type, purl_namespace, purl_name);
CREATE INDEX IF NOT EXISTS idx_purl_index_canonical ON vuln.purl_canonical_index(canonical_id);
-- ============================================================================
-- SECTION 9: Provenance Scope
-- ============================================================================
CREATE TABLE IF NOT EXISTS vuln.provenance_scope (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id) ON DELETE CASCADE,
distro_release TEXT NOT NULL,
backport_semver TEXT,
patch_id TEXT,
patch_origin TEXT CHECK (patch_origin IN ('upstream', 'distro', 'vendor')),
evidence_ref UUID,
confidence NUMERIC(3,2) NOT NULL DEFAULT 0.5 CHECK (confidence >= 0 AND confidence <= 1),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_provenance_scope_canonical_distro UNIQUE (canonical_id, distro_release)
);
CREATE INDEX IF NOT EXISTS idx_provenance_scope_canonical ON vuln.provenance_scope(canonical_id);
CREATE INDEX IF NOT EXISTS idx_provenance_scope_distro ON vuln.provenance_scope(distro_release);
CREATE INDEX IF NOT EXISTS idx_provenance_scope_patch ON vuln.provenance_scope(patch_id) WHERE patch_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_provenance_scope_high_confidence ON vuln.provenance_scope(confidence DESC) WHERE confidence >= 0.7;
CREATE INDEX IF NOT EXISTS idx_provenance_scope_origin ON vuln.provenance_scope(patch_origin) WHERE patch_origin IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_provenance_scope_updated ON vuln.provenance_scope(updated_at DESC);
CREATE TRIGGER trg_provenance_scope_updated
BEFORE UPDATE ON vuln.provenance_scope
FOR EACH ROW EXECUTE FUNCTION vuln.update_timestamp();
-- ============================================================================
-- SECTION 10: Concelier Schema Tables
-- ============================================================================
CREATE TABLE IF NOT EXISTS concelier.source_documents (
id UUID NOT NULL,
source_id UUID NOT NULL,
source_name TEXT NOT NULL,
uri TEXT NOT NULL,
sha256 TEXT NOT NULL,
status TEXT NOT NULL,
content_type TEXT,
headers_json JSONB,
metadata_json JSONB,
etag TEXT,
last_modified TIMESTAMPTZ,
payload BYTEA NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
CONSTRAINT pk_source_documents PRIMARY KEY (source_name, uri)
);
CREATE INDEX IF NOT EXISTS idx_source_documents_source_id ON concelier.source_documents(source_id);
CREATE INDEX IF NOT EXISTS idx_source_documents_status ON concelier.source_documents(status);
CREATE TABLE IF NOT EXISTS concelier.dtos (
id UUID NOT NULL,
document_id UUID NOT NULL,
source_name TEXT NOT NULL,
format TEXT NOT NULL,
payload_json JSONB NOT NULL,
schema_version TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
validated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_concelier_dtos PRIMARY KEY (document_id)
);
CREATE INDEX IF NOT EXISTS idx_concelier_dtos_source ON concelier.dtos(source_name, created_at DESC);
CREATE TABLE IF NOT EXISTS concelier.export_states (
id TEXT NOT NULL,
export_cursor TEXT NOT NULL,
last_full_digest TEXT,
last_delta_digest TEXT,
base_export_id TEXT,
base_digest TEXT,
target_repository TEXT,
files JSONB NOT NULL,
exporter_version TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_concelier_export_states PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS concelier.psirt_flags (
advisory_id TEXT NOT NULL,
vendor TEXT NOT NULL,
source_name TEXT NOT NULL,
external_id TEXT,
recorded_at TIMESTAMPTZ NOT NULL,
CONSTRAINT pk_concelier_psirt_flags PRIMARY KEY (advisory_id, vendor)
);
CREATE INDEX IF NOT EXISTS idx_concelier_psirt_source ON concelier.psirt_flags(source_name, recorded_at DESC);
CREATE TABLE IF NOT EXISTS concelier.jp_flags (
advisory_key TEXT NOT NULL,
source_name TEXT NOT NULL,
category TEXT NOT NULL,
vendor_status TEXT,
created_at TIMESTAMPTZ NOT NULL,
CONSTRAINT pk_concelier_jp_flags PRIMARY KEY (advisory_key)
);
CREATE TABLE IF NOT EXISTS concelier.change_history (
id UUID NOT NULL,
source_name TEXT NOT NULL,
advisory_key TEXT NOT NULL,
document_id UUID NOT NULL,
document_hash TEXT NOT NULL,
snapshot_hash TEXT NOT NULL,
previous_snapshot_hash TEXT,
snapshot JSONB NOT NULL,
previous_snapshot JSONB,
changes JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
CONSTRAINT pk_concelier_change_history PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_concelier_change_history_advisory ON concelier.change_history(advisory_key, created_at DESC);
-- ============================================================================
-- SECTION 11: Helper Functions for Canonical Operations
-- ============================================================================
CREATE OR REPLACE FUNCTION vuln.get_canonical_by_hash(p_merge_hash TEXT)
RETURNS vuln.advisory_canonical
LANGUAGE sql STABLE
AS $$
SELECT * FROM vuln.advisory_canonical WHERE merge_hash = p_merge_hash;
$$;
CREATE OR REPLACE FUNCTION vuln.get_source_edges(p_canonical_id UUID)
RETURNS SETOF vuln.advisory_source_edge
LANGUAGE sql STABLE
AS $$
SELECT * FROM vuln.advisory_source_edge
WHERE canonical_id = p_canonical_id
ORDER BY precedence_rank ASC, fetched_at DESC;
$$;
CREATE OR REPLACE FUNCTION vuln.upsert_canonical(
p_cve TEXT, p_affects_key TEXT, p_version_range JSONB, p_weakness TEXT[],
p_merge_hash TEXT, p_severity TEXT DEFAULT NULL, p_epss_score NUMERIC DEFAULT NULL,
p_exploit_known BOOLEAN DEFAULT FALSE, p_title TEXT DEFAULT NULL, p_summary TEXT DEFAULT NULL
)
RETURNS UUID
LANGUAGE plpgsql
AS $$
DECLARE v_id UUID;
BEGIN
INSERT INTO vuln.advisory_canonical (
cve, affects_key, version_range, weakness, merge_hash,
severity, epss_score, exploit_known, title, summary
) VALUES (
p_cve, p_affects_key, p_version_range, p_weakness, p_merge_hash,
p_severity, p_epss_score, p_exploit_known, p_title, p_summary
)
ON CONFLICT (merge_hash) DO UPDATE SET
severity = COALESCE(EXCLUDED.severity, vuln.advisory_canonical.severity),
epss_score = COALESCE(EXCLUDED.epss_score, vuln.advisory_canonical.epss_score),
exploit_known = EXCLUDED.exploit_known OR vuln.advisory_canonical.exploit_known,
title = COALESCE(EXCLUDED.title, vuln.advisory_canonical.title),
summary = COALESCE(EXCLUDED.summary, vuln.advisory_canonical.summary),
updated_at = NOW()
RETURNING id INTO v_id;
RETURN v_id;
END;
$$;
CREATE OR REPLACE FUNCTION vuln.add_source_edge(
p_canonical_id UUID, p_source_id UUID, p_source_advisory_id TEXT, p_source_doc_hash TEXT,
p_vendor_status TEXT DEFAULT NULL, p_precedence_rank INT DEFAULT 100,
p_dsse_envelope JSONB DEFAULT NULL, p_raw_payload JSONB DEFAULT NULL, p_fetched_at TIMESTAMPTZ DEFAULT NOW()
)
RETURNS UUID
LANGUAGE plpgsql
AS $$
DECLARE v_id UUID;
BEGIN
INSERT INTO vuln.advisory_source_edge (
canonical_id, source_id, source_advisory_id, source_doc_hash,
vendor_status, precedence_rank, dsse_envelope, raw_payload, fetched_at
) VALUES (
p_canonical_id, p_source_id, p_source_advisory_id, p_source_doc_hash,
p_vendor_status, p_precedence_rank, p_dsse_envelope, p_raw_payload, p_fetched_at
)
ON CONFLICT (canonical_id, source_id, source_doc_hash) DO UPDATE SET
vendor_status = COALESCE(EXCLUDED.vendor_status, vuln.advisory_source_edge.vendor_status),
precedence_rank = LEAST(EXCLUDED.precedence_rank, vuln.advisory_source_edge.precedence_rank),
dsse_envelope = COALESCE(EXCLUDED.dsse_envelope, vuln.advisory_source_edge.dsse_envelope),
raw_payload = COALESCE(EXCLUDED.raw_payload, vuln.advisory_source_edge.raw_payload)
RETURNING id INTO v_id;
RETURN v_id;
END;
$$;
CREATE OR REPLACE FUNCTION vuln.count_canonicals_by_cve_year(p_year INT)
RETURNS BIGINT
LANGUAGE sql STABLE
AS $$
SELECT COUNT(*) FROM vuln.advisory_canonical
WHERE cve LIKE 'CVE-' || p_year::TEXT || '-%' AND status = 'active';
$$;
COMMIT;

View File

@@ -1,6 +1,6 @@
using StellaOps.Concelier.Models;
namespace StellaOps.Concelier.Storage.Postgres.Advisories;
namespace StellaOps.Concelier.Persistence.Postgres.Advisories;
/// <summary>
/// PostgreSQL advisory storage interface.

View File

@@ -2,12 +2,12 @@ using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage.Postgres.Conversion;
using StellaOps.Concelier.Persistence.Postgres.Conversion;
using AdvisoryContracts = StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Advisories;
namespace StellaOps.Concelier.Persistence.Postgres.Advisories;
/// <summary>
/// PostgreSQL implementation of advisory storage.

View File

@@ -4,7 +4,7 @@ using Npgsql;
using StellaOps.Infrastructure.Postgres.Connections;
using StellaOps.Infrastructure.Postgres.Options;
namespace StellaOps.Concelier.Storage.Postgres;
namespace StellaOps.Concelier.Persistence.Postgres;
/// <summary>
/// PostgreSQL data source for the Concelier (vulnerability) module.

View File

@@ -5,7 +5,7 @@ using StellaOps.Concelier.Documents.IO;
using Contracts = StellaOps.Concelier.Storage.Contracts;
using LegacyContracts = StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Storage.Postgres;
namespace StellaOps.Concelier.Persistence.Postgres;
internal static class ContractsMappingExtensions
{

View File

@@ -1,6 +1,6 @@
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
namespace StellaOps.Concelier.Storage.Postgres.Conversion;
namespace StellaOps.Concelier.Persistence.Postgres.Conversion;
/// <summary>
/// Result of converting an advisory document to PostgreSQL entities.

View File

@@ -1,8 +1,8 @@
using System.Text.Json;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
namespace StellaOps.Concelier.Storage.Postgres.Conversion;
namespace StellaOps.Concelier.Persistence.Postgres.Conversion;
/// <summary>
/// Converts domain advisories to PostgreSQL entity structures.

View File

@@ -1,10 +1,10 @@
using System.Text.Json;
using StellaOps.Concelier.Storage;
using Contracts = StellaOps.Concelier.Storage.Contracts;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres;
namespace StellaOps.Concelier.Persistence.Postgres;
/// <summary>
/// Postgres-backed implementation that satisfies the legacy IDocumentStore contract and the new Postgres-native storage contract.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents an affected package entry for an advisory.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents an advisory alias (e.g., CVE, GHSA).

View File

@@ -5,7 +5,7 @@
// Description: Entity for deduplicated canonical advisory records
// -----------------------------------------------------------------------------
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a deduplicated canonical advisory in the vuln schema.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a credit entry for an advisory.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a CVSS score for an advisory.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents an advisory entity in the vuln schema.

View File

@@ -1,6 +1,6 @@
using System;
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a cached Link-Not-Merge linkset snapshot stored in PostgreSQL.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents an advisory reference URL.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a snapshot of an advisory at a point in time.

View File

@@ -5,7 +5,7 @@
// Description: Entity linking canonical advisory to source documents with DSSE
// -----------------------------------------------------------------------------
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a link between a canonical advisory and its source document.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a CWE weakness linked to an advisory.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
public sealed record DocumentRecordEntity(
Guid Id,

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a feed snapshot record.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a Known Exploited Vulnerability flag entry.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a merge event audit record.

View File

@@ -5,7 +5,7 @@
// Description: Entity for distro-specific backport and patch provenance
// -----------------------------------------------------------------------------
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents distro-specific backport and patch provenance per canonical advisory.

View File

@@ -5,7 +5,7 @@
// Description: Entity for per-site federation governance policies
// -----------------------------------------------------------------------------
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a site federation policy for governance control.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a vulnerability feed source entity.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Tracks source ingestion cursors and metrics.

View File

@@ -5,7 +5,7 @@
// Description: Entity for tracking federation sync state per remote site
// -----------------------------------------------------------------------------
namespace StellaOps.Concelier.Storage.Postgres.Models;
namespace StellaOps.Concelier.Persistence.Postgres.Models;
/// <summary>
/// Represents a sync ledger entry for federation cursor tracking.

View File

@@ -1,9 +1,9 @@
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Infrastructure.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Repositories;
namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
/// <summary>
/// PostgreSQL repository for advisory affected packages.

View File

@@ -1,9 +1,9 @@
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Infrastructure.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Repositories;
namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
/// <summary>
/// PostgreSQL repository for advisory aliases.

View File

@@ -8,10 +8,10 @@
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Infrastructure.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Repositories;
namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
/// <summary>
/// PostgreSQL repository for canonical advisory and source edge operations.

View File

@@ -1,9 +1,9 @@
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Storage.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Infrastructure.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Repositories;
namespace StellaOps.Concelier.Persistence.Postgres.Repositories;
/// <summary>
/// PostgreSQL repository for advisory credits.

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