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

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

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Core.Linksets;
using StellaOps.Concelier.WebService.Contracts;
@@ -35,7 +36,7 @@ internal static class AdvisorySummaryMapper
ObservationIds: linkset.ObservationIds.ToArray(),
Schema: "lnm-1.0"),
Aliases: aliases.ToArray(),
ObservedAt: linkset.CreatedAt.UtcDateTime.ToString("O"));
ObservedAt: linkset.CreatedAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture));
}
public static AdvisorySummaryResponse ToResponse(

View File

@@ -226,7 +226,7 @@ internal static class FeedSnapshotEndpointExtensions
exportOptions,
cancellationToken).ConfigureAwait(false);
if (metadata is null)
if (metadata is null || metadata.ExportPath is null)
{
return ConcelierProblemResultFactory.SnapshotNotFound(context, snapshotId);
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.Json;
using StellaOps.Concelier.WebService.Options;
@@ -76,8 +77,8 @@ internal static class IncidentFileStore
payload.AdvisoryKey,
payload.Tenant,
payload.Reason,
payload.ActivatedAt.ToUniversalTime().ToString("O"),
payload.CooldownUntil.ToUniversalTime().ToString("O"),
payload.ActivatedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
payload.CooldownUntil.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
payload.PipelineVersion,
active);
}

View File

@@ -20,7 +20,6 @@
<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

@@ -660,7 +660,7 @@ public sealed class AcscConnector : IFeedConnector
if (cursor.LastPublishedByFeed.TryGetValue(feed.Slug, out var published) && published.HasValue)
{
metadata["acsc.cursor.lastPublished"] = published.Value.ToString("O");
metadata["acsc.cursor.lastPublished"] = published.Value.ToString("O", CultureInfo.InvariantCulture);
}
return metadata;

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@@ -270,7 +271,7 @@ internal static class AcscMapper
entry.Link ?? string.Empty,
entry.Title ?? string.Empty,
entry.Summary ?? string.Empty,
entry.Published?.ToUniversalTime().ToString("O") ?? string.Empty);
entry.Published?.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) ?? string.Empty);
identifier = CreateHash(seed);
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace StellaOps.Concelier.Connector.CertBund.Internal;
@@ -11,7 +12,7 @@ internal static class CertBundDocumentMetadata
{
["certbund.advisoryId"] = item.AdvisoryId,
["certbund.portalUri"] = item.PortalUri.ToString(),
["certbund.published"] = item.Published.ToString("O"),
["certbund.published"] = item.Published.ToString("O", CultureInfo.InvariantCulture),
};
if (!string.IsNullOrWhiteSpace(item.Category))

View File

@@ -70,7 +70,7 @@ internal sealed record CertFrDocumentMetadata(
{
[AdvisoryIdKey] = item.AdvisoryId,
[TitleKey] = item.Title ?? item.AdvisoryId,
[PublishedKey] = item.Published.ToString("O"),
[PublishedKey] = item.Published.ToString("O", CultureInfo.InvariantCulture),
};
if (!string.IsNullOrWhiteSpace(item.Summary))

View File

@@ -121,7 +121,7 @@ public sealed class CertInConnector : IFeedConnector
["certin.advisoryId"] = listing.AdvisoryId,
["certin.title"] = listing.Title,
["certin.link"] = listing.DetailUri.ToString(),
["certin.published"] = listing.Published.ToString("O")
["certin.published"] = listing.Published.ToString("O", CultureInfo.InvariantCulture)
};
if (!string.IsNullOrWhiteSpace(listing.Summary))

View File

@@ -151,7 +151,7 @@ public sealed class SourceFetchService
? new Dictionary<string, string>(StringComparer.Ordinal)
: new Dictionary<string, string>(request.Metadata, StringComparer.Ordinal);
metadata["attempts"] = sendResult.Attempts.ToString(CultureInfo.InvariantCulture);
metadata["fetchedAt"] = fetchedAt.ToString("O");
metadata["fetchedAt"] = fetchedAt.ToString("O", CultureInfo.InvariantCulture);
var guardDocument = CreateRawAdvisoryDocument(
request,
@@ -319,7 +319,7 @@ public sealed class SourceFetchService
ReconciledFrom = ImmutableArray<string>.Empty,
Notes = ImmutableDictionary<string, string>.Empty
},
supersedes);
Supersedes: supersedes);
var mappedLinkset = _linksetMapper.Map(rawDocument);
rawDocument = rawDocument with { Linkset = mappedLinkset };
@@ -404,10 +404,10 @@ public sealed class SourceFetchService
if (response.Content.Headers.LastModified is { } lastModified)
{
builder["http.last_modified"] = lastModified.ToString("O");
builder["http.last_modified"] = lastModified.ToString("O", CultureInfo.InvariantCulture);
}
builder["fetch.fetched_at"] = fetchedAt.ToString("O");
builder["fetch.fetched_at"] = fetchedAt.ToString("O", CultureInfo.InvariantCulture);
builder["fetch.content_hash"] = contentHash;
builder["source.client"] = request.ClientName;
@@ -525,7 +525,7 @@ public sealed class SourceFetchService
if (response.Content.Headers.LastModified is { } lastModified)
{
return lastModified.ToString("O");
return lastModified.ToString("O", CultureInfo.InvariantCulture);
}
if (response.Headers.TryGetValues("Last-Modified", out var values))
@@ -537,7 +537,7 @@ public sealed class SourceFetchService
}
}
return fetchedAt.ToString("O");
return fetchedAt.ToString("O", CultureInfo.InvariantCulture);
}
private static RawSignatureMetadata CreateSignatureMetadata(ImmutableDictionary<string, string> metadata)

View File

@@ -116,7 +116,7 @@ public static class ServiceCollectionExtensions
foreach (var root in options.TrustedRootCertificates)
{
trustedRootCopies.Add(new X509Certificate2(root.RawData));
trustedRootCopies.Add(X509CertificateLoader.LoadCertificate(root.RawData));
}
using var customChain = new X509Chain();

View File

@@ -121,8 +121,8 @@ public sealed class CveConnector : IFeedConnector
var requestUri = BuildListRequestUri(since, windowEnd, page, _options.PageSize);
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["since"] = since.ToString("O"),
["until"] = windowEnd.ToString("O"),
["since"] = since.ToString("O", CultureInfo.InvariantCulture),
["until"] = windowEnd.ToString("O", CultureInfo.InvariantCulture),
["page"] = page.ToString(CultureInfo.InvariantCulture),
["pageSize"] = _options.PageSize.ToString(CultureInfo.InvariantCulture),
};
@@ -197,8 +197,8 @@ public sealed class CveConnector : IFeedConnector
{
["cveId"] = item.CveId,
["page"] = page.ToString(CultureInfo.InvariantCulture),
["since"] = since.ToString("O"),
["until"] = windowEnd.ToString("O"),
["since"] = since.ToString("O", CultureInfo.InvariantCulture),
["until"] = windowEnd.ToString("O", CultureInfo.InvariantCulture),
};
SourceFetchResult detailResult;
@@ -298,10 +298,10 @@ public sealed class CveConnector : IFeedConnector
var nextWindowStart = hasMorePages ? since : maxModified ?? windowEnd;
DateTimeOffset? nextWindowEnd = hasMorePages ? windowEnd : null;
var nextPage = hasMorePages ? page : 1;
var windowStartString = since.ToString("O");
var windowEndString = windowEnd.ToString("O");
var nextWindowStartString = nextWindowStart.ToString("O");
var nextWindowEndString = nextWindowEnd?.ToString("O") ?? "(none)";
var windowStartString = since.ToString("O", CultureInfo.InvariantCulture);
var windowEndString = windowEnd.ToString("O", CultureInfo.InvariantCulture);
var nextWindowStartString = nextWindowStart.ToString("O", CultureInfo.InvariantCulture);
var nextWindowEndString = nextWindowEnd?.ToString("O", CultureInfo.InvariantCulture) ?? "(none)";
_logger.LogInformation(
"CVEs fetch window {WindowStart}->{WindowEnd} pages={PagesFetched} listSuccess={ListSuccess} detailDocuments={DocumentsFetched} detailFailures={DetailFailures} detailUnchanged={DetailUnchanged} pendingDocuments={PendingDocumentsBefore}->{PendingDocumentsAfter} pendingMappings={PendingMappingsBefore}->{PendingMappingsAfter} hasMorePages={HasMorePages} nextWindowStart={NextWindowStart} nextWindowEnd={NextWindowEnd} nextPage={NextPage}",
@@ -596,7 +596,7 @@ public sealed class CveConnector : IFeedConnector
private static Uri BuildListRequestUri(DateTimeOffset since, DateTimeOffset until, int page, int pageSize)
{
var query = $"time_modified.gte={Uri.EscapeDataString(since.ToString("O"))}&time_modified.lte={Uri.EscapeDataString(until.ToString("O"))}&page={page}&size={pageSize}";
var query = $"time_modified.gte={Uri.EscapeDataString(since.ToString("O", CultureInfo.InvariantCulture))}&time_modified.lte={Uri.EscapeDataString(until.ToString("O", CultureInfo.InvariantCulture))}&page={page}&size={pageSize}";
return new Uri($"cve?{query}", UriKind.Relative);
}

View File

@@ -144,7 +144,7 @@ public sealed class UbuntuConnector : IFeedConnector
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["ubuntu.id"] = notice.NoticeId,
["ubuntu.published"] = notice.Published.ToString("O")
["ubuntu.published"] = notice.Published.ToString("O", CultureInfo.InvariantCulture)
};
var dtoDocument = ToDocument(notice);

View File

@@ -109,8 +109,8 @@ public sealed class GhsaConnector : IFeedConnector
var listUri = BuildListUri(since, until, page, _options.PageSize);
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["since"] = since.ToString("O"),
["until"] = until.ToString("O"),
["since"] = since.ToString("O", CultureInfo.InvariantCulture),
["until"] = until.ToString("O", CultureInfo.InvariantCulture),
["page"] = page.ToString(CultureInfo.InvariantCulture),
["pageSize"] = _options.PageSize.ToString(CultureInfo.InvariantCulture),
};
@@ -172,8 +172,8 @@ public sealed class GhsaConnector : IFeedConnector
{
["ghsaId"] = item.GhsaId,
["page"] = page.ToString(CultureInfo.InvariantCulture),
["since"] = since.ToString("O"),
["until"] = until.ToString("O"),
["since"] = since.ToString("O", CultureInfo.InvariantCulture),
["until"] = until.ToString("O", CultureInfo.InvariantCulture),
};
SourceFetchResult detailResult;
@@ -425,7 +425,7 @@ public sealed class GhsaConnector : IFeedConnector
private static Uri BuildListUri(DateTimeOffset since, DateTimeOffset until, int page, int pageSize)
{
var query = $"updated_since={Uri.EscapeDataString(since.ToString("O"))}&updated_until={Uri.EscapeDataString(until.ToString("O"))}&page={page}&per_page={pageSize}";
var query = $"updated_since={Uri.EscapeDataString(since.ToString("O", CultureInfo.InvariantCulture))}&updated_until={Uri.EscapeDataString(until.ToString("O", CultureInfo.InvariantCulture))}&page={page}&per_page={pageSize}";
return new Uri($"security/advisories?{query}", UriKind.Relative);
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -15,8 +16,6 @@ using StellaOps.Concelier.Connector.Ics.Kaspersky.Configuration;
using StellaOps.Concelier.Connector.Ics.Kaspersky.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using StellaOps.Plugin;
@@ -128,7 +127,7 @@ public sealed class KasperskyConnector : IFeedConnector
{
["kaspersky.title"] = item.Title,
["kaspersky.link"] = item.Link.ToString(),
["kaspersky.published"] = item.Published.ToString("O"),
["kaspersky.published"] = item.Published.ToString("O", CultureInfo.InvariantCulture),
};
if (!string.IsNullOrWhiteSpace(item.Summary))

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -128,12 +129,12 @@ public sealed class JvnConnector : IFeedConnector
if (item.DateFirstPublished.HasValue)
{
metadata["jvn.firstPublished"] = item.DateFirstPublished.Value.ToString("O");
metadata["jvn.firstPublished"] = item.DateFirstPublished.Value.ToString("O", CultureInfo.InvariantCulture);
}
if (item.DateLastUpdated.HasValue)
{
metadata["jvn.lastUpdated"] = item.DateLastUpdated.Value.ToString("O");
metadata["jvn.lastUpdated"] = item.DateLastUpdated.Value.ToString("O", CultureInfo.InvariantCulture);
}
var result = await _fetchService.FetchAsync(

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -73,7 +74,7 @@ internal sealed record KevCursor(
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -97,7 +98,7 @@ public sealed class KevConnector : IFeedConnector
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["kev.cursor.catalogVersion"] = cursor.CatalogVersion ?? string.Empty,
["kev.cursor.catalogReleased"] = cursor.CatalogReleased?.ToString("O") ?? string.Empty,
["kev.cursor.catalogReleased"] = cursor.CatalogReleased?.ToString("O", CultureInfo.InvariantCulture) ?? string.Empty,
},
ETag = existing?.Etag,
LastModified = existing?.LastModified,
@@ -139,7 +140,7 @@ public sealed class KevConnector : IFeedConnector
.WithPendingMappings(pendingMappings);
var document = result.Document;
var lastModified = document.LastModified?.ToUniversalTime().ToString("O") ?? "(unknown)";
var lastModified = document.LastModified?.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) ?? "(unknown)";
_logger.LogInformation(
"Fetched KEV catalog document {DocumentId} (etag={Etag}, lastModified={LastModified}) pendingDocuments={PendingDocumentsBefore}->{PendingDocumentsAfter} pendingMappings={PendingMappingsBefore}->{PendingMappingsAfter}",
document.Id,

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -114,7 +115,7 @@ internal sealed record KisaCursor(
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace StellaOps.Concelier.Connector.Kisa.Internal;
@@ -12,7 +13,7 @@ internal static class KisaDocumentMetadata
["kisa.idx"] = item.AdvisoryId,
["kisa.detailApi"] = item.DetailApiUri.ToString(),
["kisa.detailPage"] = item.DetailPageUri.ToString(),
["kisa.published"] = item.Published.ToString("O"),
["kisa.published"] = item.Published.ToString("O", CultureInfo.InvariantCulture),
};
if (!string.IsNullOrWhiteSpace(item.Title))

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -146,7 +147,7 @@ internal static class NvdMapper
return null;
}
return DateTimeOffset.TryParse(property.GetString(), out var parsed) ? parsed : null;
return DateTimeOffset.TryParse(property.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) ? parsed : null;
}
private static IReadOnlyList<AdvisoryReference> GetReferences(

View File

@@ -93,8 +93,8 @@ public sealed class NvdConnector : IFeedConnector
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["windowStart"] = window.Start.ToString("O"),
["windowEnd"] = window.End.ToString("O"),
["windowStart"] = window.Start.ToString("O", CultureInfo.InvariantCulture),
["windowEnd"] = window.End.ToString("O", CultureInfo.InvariantCulture),
};
metadata["startIndex"] = "0";

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -281,7 +282,7 @@ internal sealed record OsvCursor(
return value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
@@ -440,7 +441,7 @@ public sealed class OsvConnector : IFeedConnector
{
["osv.ecosystem"] = ecosystem,
["osv.id"] = dto.Id,
["osv.modified"] = modified.ToString("O"),
["osv.modified"] = modified.ToString("O", CultureInfo.InvariantCulture),
};
var record = new DocumentRecord(

View File

@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
@@ -794,7 +795,7 @@ public sealed class RuNkckiConnector : IFeedConnector
private string GetBulletinCachePath(string bulletinId)
{
var fileStem = string.IsNullOrWhiteSpace(bulletinId)
? ComputeDeterministicSlug("unknown-bulletin", _timeProvider.GetUtcNow().ToString("O"))
? ComputeDeterministicSlug("unknown-bulletin", _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture))
: Uri.EscapeDataString(bulletinId);
return Path.Combine(_cacheDirectory, $"{fileStem}.json.zip");
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -67,7 +68,7 @@ internal sealed record StellaOpsMirrorCursor(
generatedAt = generatedValue.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(generatedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(generatedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(generatedValue.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
@@ -373,7 +374,7 @@ public sealed class AdobeConnector : IFeedConnector
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["advisoryId"] = entry.AdvisoryId,
["published"] = entry.PublishedUtc.ToString("O"),
["published"] = entry.PublishedUtc.ToString("O", CultureInfo.InvariantCulture),
["title"] = entry.Title ?? string.Empty,
};

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -102,7 +103,7 @@ internal sealed record AdobeCursor(
return value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Vndr.Adobe.Internal;
@@ -32,7 +33,7 @@ internal sealed record AdobeDocumentMetadata(
var title = document.Metadata.TryGetValue(TitleKey, out var titleValue) ? titleValue : null;
DateTimeOffset? published = null;
if (document.Metadata.TryGetValue(PublishedKey, out var publishedValue)
&& DateTimeOffset.TryParse(publishedValue, out var parsedPublished))
&& DateTimeOffset.TryParse(publishedValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsedPublished))
{
published = parsedPublished.ToUniversalTime();
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
@@ -369,7 +370,7 @@ public sealed class AppleConnector : IFeedConnector
metadata.TryGetValue("apple.rapidResponse", out var rapidRaw);
metadata.TryGetValue("apple.products", out var productsJson);
if (!DateTimeOffset.TryParse(postingDateRaw, out var postingDate))
if (!DateTimeOffset.TryParse(postingDateRaw, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var postingDate))
{
postingDate = document.FetchedAt;
}
@@ -416,7 +417,7 @@ public sealed class AppleConnector : IFeedConnector
["apple.articleId"] = entry.ArticleId,
["apple.updateId"] = entry.UpdateId,
["apple.title"] = entry.Title,
["apple.postingDate"] = entry.PostingDate.ToString("O"),
["apple.postingDate"] = entry.PostingDate.ToString("O", CultureInfo.InvariantCulture),
["apple.detailUri"] = entry.DetailUri.ToString(),
["apple.rapidResponse"] = entry.IsRapidSecurityResponse ? "true" : "false",
["apple.products"] = JsonSerializer.Serialize(entry.Products, SerializerOptions),

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -108,7 +109,7 @@ internal sealed record AppleCursor(
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using AngleSharp.Dom;
@@ -115,7 +116,7 @@ internal static class AppleDetailParser
continue;
}
if (!DateTimeOffset.TryParse(raw, out var parsed))
if (!DateTimeOffset.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed))
{
continue;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -87,7 +88,7 @@ internal static class AppleIndexParser
continue;
}
if (!DateTimeOffset.TryParse(dto.PostingDate, out var postingDate))
if (!DateTimeOffset.TryParse(dto.PostingDate, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var postingDate))
{
continue;
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Vndr.Chromium.Internal;
@@ -60,12 +61,12 @@ internal sealed record ChromiumDocumentMetadata(
{
[PostIdKey] = postId,
[TitleKey] = title,
[PublishedKey] = published.ToUniversalTime().ToString("O"),
[PublishedKey] = published.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
};
if (updated.HasValue)
{
dictionary[UpdatedKey] = updated.Value.ToUniversalTime().ToString("O");
dictionary[UpdatedKey] = updated.Value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture);
}
if (!string.IsNullOrWhiteSpace(summary))

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
@@ -115,8 +116,8 @@ public sealed class MsrcApiClient
builder.Append(_options.BaseUri.ToString().TrimEnd('/'));
builder.Append("/vulnerabilities?");
builder.Append("$top=").Append(_options.PageSize);
builder.Append("&lastModifiedStartDateTime=").Append(Uri.EscapeDataString(fromInclusive.ToUniversalTime().ToString("O")));
builder.Append("&lastModifiedEndDateTime=").Append(Uri.EscapeDataString(toExclusive.ToUniversalTime().ToString("O")));
builder.Append("&lastModifiedStartDateTime=").Append(Uri.EscapeDataString(fromInclusive.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture)));
builder.Append("&lastModifiedEndDateTime=").Append(Uri.EscapeDataString(toExclusive.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture)));
builder.Append("&$orderby=lastModifiedDate");
builder.Append("&locale=").Append(Uri.EscapeDataString(_options.Locale));
builder.Append("&api-version=").Append(Uri.EscapeDataString(_options.ApiVersion));

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -81,7 +82,7 @@ internal sealed record MsrcCursor(
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace StellaOps.Concelier.Connector.Vndr.Msrc.Internal;
@@ -15,12 +16,12 @@ internal static class MsrcDocumentMetadata
if (summary.LastModifiedDate.HasValue)
{
metadata["msrc.lastModified"] = summary.LastModifiedDate.Value.ToString("O");
metadata["msrc.lastModified"] = summary.LastModifiedDate.Value.ToString("O", CultureInfo.InvariantCulture);
}
if (summary.ReleaseDate.HasValue)
{
metadata["msrc.releaseDate"] = summary.ReleaseDate.Value.ToString("O");
metadata["msrc.releaseDate"] = summary.ReleaseDate.Value.ToString("O", CultureInfo.InvariantCulture);
}
if (!string.IsNullOrWhiteSpace(summary.CvrfUrl))

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Net.Http;
@@ -424,7 +425,7 @@ public sealed class MsrcConnector : IFeedConnector
return true;
}
return !string.Equals(stored, summary.LastModifiedDate.Value.ToString("O"), StringComparison.OrdinalIgnoreCase);
return !string.Equals(stored, summary.LastModifiedDate.Value.ToString("O", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
}
private async Task<MsrcCursor> GetCursorAsync(CancellationToken cancellationToken)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Vndr.Oracle.Internal;
@@ -19,7 +20,7 @@ internal sealed record OracleDocumentMetadata(
{
[AdvisoryIdKey] = advisoryId,
[TitleKey] = title,
[PublishedKey] = published.ToString("O"),
[PublishedKey] = published.ToString("O", CultureInfo.InvariantCulture),
};
public static OracleDocumentMetadata FromDocument(DocumentRecord document)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StellaOps.Concelier.Documents;
@@ -166,7 +167,7 @@ internal sealed record VmwareCursor(
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Contracts;
@@ -44,7 +45,7 @@ internal sealed record VmwareFetchCacheEntry(string? Sha256, string? ETag, DateT
lastModified = lastModifiedValue.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(lastModifiedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(lastModifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.String when DateTimeOffset.TryParse(lastModifiedValue.AsString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
@@ -170,7 +171,7 @@ public sealed class VmwareConnector : IFeedConnector
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["vmware.id"] = item.Id,
["vmware.modified"] = modified.ToString("O"),
["vmware.modified"] = modified.ToString("O", CultureInfo.InvariantCulture),
};
SourceFetchResult result;

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Logging;
@@ -188,7 +189,7 @@ public sealed class LoggingBundleTimelineEventSink : IBundleTimelineEventSink
timelineEvent.Stats.DurationMs,
timelineEvent.ContentHash,
timelineEvent.TraceId,
timelineEvent.OccurredAt.ToString("O"));
timelineEvent.OccurredAt.ToString("O", CultureInfo.InvariantCulture));
return Task.CompletedTask;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Text.Json;
namespace StellaOps.Concelier.Core.Risk;
@@ -243,7 +244,7 @@ public static class VendorRiskSignalExtractor
if (element.ValueKind == JsonValueKind.String)
{
var str = element.GetString();
if (DateTimeOffset.TryParse(str, out var date))
if (DateTimeOffset.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var date))
{
return date;
}

View File

@@ -13,7 +13,6 @@
<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

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core.Canonical;
@@ -36,7 +37,7 @@ public sealed class DeltaQueryService : IDeltaQueryService
_logger.LogInformation(
"Querying changes since {Cursor} (timestamp: {Since})",
sinceCursor ?? "beginning",
sinceTimestamp?.ToString("O") ?? "null");
sinceTimestamp?.ToString("O", CultureInfo.InvariantCulture) ?? "null");
return new DeltaChangeSet
{

View File

@@ -19,7 +19,6 @@
<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

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Metrics;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
@@ -182,8 +183,8 @@ public sealed class AdvisoryMergeService
var feedId = ExtractPrimaryFeedId(inputs) ?? "canonical";
// Compute epochs based on modification timestamps
var previousEpoch = before?.Modified?.ToString("O") ?? "initial";
var newEpoch = merged.Modified?.ToString("O") ?? _timeProvider.GetUtcNow().ToString("O");
var previousEpoch = before?.Modified?.ToString("O", CultureInfo.InvariantCulture) ?? "initial";
var newEpoch = merged.Modified?.ToString("O", CultureInfo.InvariantCulture) ?? _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
var effectiveAt = _timeProvider.GetUtcNow();
var @event = FeedEpochAdvancedEvent.Create(

View File

@@ -5,6 +5,7 @@
// Description: PostgreSQL repository for federation sync ledger operations
// -----------------------------------------------------------------------------
using System.Globalization;
using Microsoft.Extensions.Logging;
using Npgsql;
using StellaOps.Concelier.Persistence.Postgres.Models;
@@ -357,8 +358,8 @@ public static class CursorFormat
public static (DateTimeOffset Timestamp, int Sequence) Parse(string cursor)
{
var parts = cursor.Split('#');
var timestamp = DateTimeOffset.Parse(parts[0]);
var sequence = parts.Length > 1 ? int.Parse(parts[1]) : 0;
var timestamp = DateTimeOffset.Parse(parts[0], CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
var sequence = parts.Length > 1 ? int.Parse(parts[1], CultureInfo.InvariantCulture) : 0;
return (timestamp, sequence);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Text.Json;
using System.Collections.Generic;
using StellaOps.Concelier.Documents;
@@ -94,7 +95,7 @@ public sealed class PostgresSourceStateAdapter : LegacyContracts.ISourceStateRep
var metadata = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["backoffUntil"] = backoffUntil.ToString("O"),
["backoffUntil"] = backoffUntil.ToString("O", CultureInfo.InvariantCulture),
["reason"] = reason
};
@@ -201,7 +202,7 @@ public sealed class PostgresSourceStateAdapter : LegacyContracts.ISourceStateRep
}
if (backoffProperty.ValueKind == JsonValueKind.String
&& DateTimeOffset.TryParse(backoffProperty.GetString(), out var parsed))
&& DateTimeOffset.TryParse(backoffProperty.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed))
{
return parsed;
}

View File

@@ -64,7 +64,7 @@ public sealed class SbomRegistryService : ISbomRegistryService
var registration = new SbomRegistration
{
Id = ComputeDeterministicRegistrationId(input.Digest, input.TenantId),
Id = ComputeDeterministicRegistrationId(input.Digest, input.TenantId ?? "default"),
Digest = input.Digest,
Format = input.Format,
SpecVersion = input.SpecVersion,

View File

@@ -19,7 +19,6 @@
<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

@@ -1,6 +1,7 @@
namespace StellaOps.Concelier.SourceIntel;
using System.Collections.Immutable;
using System.Globalization;
using System.Text.RegularExpressions;
/// <summary>
@@ -290,21 +291,21 @@ public static partial class ChangelogParser
private static DateTimeOffset ParseDebianDate(string dateStr)
{
// "Mon, 15 Jan 2024 10:30:00 +0000"
if (DateTimeOffset.TryParse(dateStr, out var date))
if (DateTimeOffset.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
{
return date;
}
return DateTimeOffset.UtcNow;
return DateTimeOffset.MinValue;
}
private static DateTimeOffset ParseRpmDate(string dateStr)
{
// "Mon Jan 15 2024"
if (DateTimeOffset.TryParse(dateStr, out var date))
if (DateTimeOffset.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
{
return date;
}
return DateTimeOffset.UtcNow;
return DateTimeOffset.MinValue;
}
[GeneratedRegex(@"^(\S+) \(([^)]+)\)")]

View File

@@ -0,0 +1,24 @@
# Concelier ConfigDiff Tests Charter
## Mission
- Validate config diff behaviors for Concelier configuration surfaces.
## Responsibilities
- Exercise configuration comparison logic and determinism.
- Cover edge cases (missing keys, ordering, default values).
## Required Reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/concelier/architecture.md
- docs/modules/concelier/link-not-merge-schema.md
## Working Agreement
- Use fixed times and IDs in tests.
- Assert deterministic ordering and invariant formatting.
- Avoid network dependencies in tests.
## Testing Strategy
- Unit tests for config diff logic and normalization.
- Regression tests for deterministic output.

View File

@@ -13,9 +13,6 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
<ItemGroup>
<None Update="Fixtures\*.xml">

View File

@@ -11,10 +11,6 @@
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="xunit.runner.visualstudio" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />

View File

@@ -17,7 +17,6 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.v3" />
</ItemGroup> <ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

View File

@@ -15,10 +15,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Npgsql" />
<PackageReference Include="Dapper" />
<PackageReference Include="xunit.runner.visualstudio" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@@ -10,10 +10,6 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.runner.visualstudio" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,24 @@
# Concelier SchemaEvolution Tests Charter
## Mission
- Validate schema evolution rules and backward compatibility for Concelier data.
## Responsibilities
- Exercise schema upgrade paths and compatibility checks.
- Verify deterministic normalization of schema artifacts.
## Required Reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/concelier/architecture.md
- docs/modules/concelier/link-not-merge-schema.md
## Working Agreement
- Use fixed times and IDs in tests.
- Avoid network dependencies in tests.
- Keep snapshots deterministic and invariant.
## Testing Strategy
- Unit tests for schema diff and upgrade logic.
- Regression tests for compatibility guards.