feat(audit): Apply TreatWarningsAsErrors=true to 160+ production csproj files
Sprint: SPRINT_20251229_049_BE_csproj_audit_maint_tests Tasks: AUDIT-0001 through AUDIT-0147 APPLY tasks (approved decisions 1-9) Changes: - Set TreatWarningsAsErrors=true for all production .NET projects - Fixed nullable warnings in Scanner.EntryTrace, Scanner.Evidence, Scheduler.Worker, Concelier connectors, and other modules - Injected TimeProvider/IGuidProvider for deterministic time/ID generation - Added path traversal validation in AirGap.Bundle - Fixed NULL handling in various cursor classes - Third-party GostCryptography retains TreatWarningsAsErrors=false (preserves original) - Test projects excluded per user decision (rejected decision 10) Note: All 17 ACSC connector tests pass after snapshot fixture sync
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using StellaOps.Concelier.Documents;
|
||||
|
||||
@@ -32,7 +33,11 @@ internal sealed record UbuntuCursor(
|
||||
lastPublished = 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.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out var parsed) => parsed.ToUniversalTime(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -47,10 +52,14 @@ internal sealed record UbuntuCursor(
|
||||
|
||||
public DocumentObject ToDocumentObject()
|
||||
{
|
||||
// Sort collections for deterministic serialization
|
||||
var sortedPendingDocs = PendingDocuments.OrderBy(id => id).Select(id => id.ToString());
|
||||
var sortedPendingMaps = PendingMappings.OrderBy(id => id).Select(id => id.ToString());
|
||||
|
||||
var doc = new DocumentObject
|
||||
{
|
||||
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
|
||||
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString()))
|
||||
["pendingDocuments"] = new DocumentArray(sortedPendingDocs),
|
||||
["pendingMappings"] = new DocumentArray(sortedPendingMaps)
|
||||
};
|
||||
|
||||
if (LastPublished.HasValue)
|
||||
@@ -60,13 +69,16 @@ internal sealed record UbuntuCursor(
|
||||
|
||||
if (ProcessedNoticeIds.Count > 0)
|
||||
{
|
||||
doc["processedIds"] = new DocumentArray(ProcessedNoticeIds);
|
||||
// Sort processed IDs for deterministic output
|
||||
var sortedProcessedIds = ProcessedNoticeIds.OrderBy(id => id, StringComparer.Ordinal);
|
||||
doc["processedIds"] = new DocumentArray(sortedProcessedIds);
|
||||
}
|
||||
|
||||
if (FetchCache.Count > 0)
|
||||
{
|
||||
var cacheDoc = new DocumentObject();
|
||||
foreach (var (key, entry) in FetchCache)
|
||||
// Sort fetch cache keys for deterministic output
|
||||
foreach (var (key, entry) in FetchCache.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
|
||||
{
|
||||
cacheDoc[key] = entry.ToDocumentObject();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using StellaOps.Concelier.Documents;
|
||||
using StorageContracts = StellaOps.Concelier.Storage.Contracts;
|
||||
|
||||
@@ -31,7 +32,11 @@ internal sealed record UbuntuFetchCacheEntry(string? ETag, DateTimeOffset? LastM
|
||||
lastModified = modifiedValue.DocumentType switch
|
||||
{
|
||||
DocumentType.DateTime => DateTime.SpecifyKind(modifiedValue.ToUniversalTime(), DateTimeKind.Utc),
|
||||
DocumentType.String when DateTimeOffset.TryParse(modifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
|
||||
DocumentType.String when DateTimeOffset.TryParse(
|
||||
modifiedValue.AsString,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out var parsed) => parsed.ToUniversalTime(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ internal static class UbuntuNoticeParser
|
||||
continue;
|
||||
}
|
||||
|
||||
var published = ParseDate(noticeElement, "published") ?? DateTimeOffset.UtcNow;
|
||||
// Use MinValue instead of UtcNow for deterministic fallback on invalid/missing dates
|
||||
var published = ParseDate(noticeElement, "published") ?? DateTimeOffset.MinValue;
|
||||
var title = noticeElement.TryGetProperty("title", out var titleElement)
|
||||
? titleElement.GetString() ?? noticeId
|
||||
: noticeId;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -150,7 +150,8 @@ public sealed class UbuntuConnector : IFeedConnector
|
||||
var dtoDocument = ToDocument(notice);
|
||||
var sha256 = ComputeNoticeHash(dtoDocument);
|
||||
|
||||
var documentId = existing?.Id ?? Guid.NewGuid();
|
||||
// Use existing ID or derive deterministic ID from source + uri hash
|
||||
var documentId = existing?.Id ?? ComputeDeterministicId(SourceName, detailUri.AbsoluteUri);
|
||||
var record = new DocumentRecord(
|
||||
documentId,
|
||||
SourceName,
|
||||
@@ -167,7 +168,9 @@ public sealed class UbuntuConnector : IFeedConnector
|
||||
|
||||
await _documentStore.UpsertAsync(record, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dtoRecord = new DtoRecord(Guid.NewGuid(), record.Id, SourceName, "ubuntu.notice.v1", dtoDocument, now);
|
||||
// Derive deterministic DTO ID from document ID + schema
|
||||
var dtoId = ComputeDeterministicId(record.Id.ToString(), "ubuntu.notice.v1");
|
||||
var dtoRecord = new DtoRecord(dtoId, record.Id, SourceName, "ubuntu.notice.v1", dtoDocument, now);
|
||||
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
pendingMappings.Add(record.Id);
|
||||
@@ -435,6 +438,15 @@ public sealed class UbuntuConnector : IFeedConnector
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private Guid ComputeDeterministicId(string source, string identifier)
|
||||
{
|
||||
// Deterministic GUID based on SHA-256 hash of source + identifier
|
||||
var input = Encoding.UTF8.GetBytes($"{source}:{identifier}");
|
||||
var hash = _hash.ComputeHash(input, HashAlgorithms.Sha256);
|
||||
// Use first 16 bytes of hash as GUID
|
||||
return new Guid(hash.AsSpan(0, 16));
|
||||
}
|
||||
|
||||
private static DocumentObject ToDocument(UbuntuNoticeDto notice)
|
||||
{
|
||||
var packages = new DocumentArray();
|
||||
@@ -486,14 +498,19 @@ public sealed class UbuntuConnector : IFeedConnector
|
||||
private static UbuntuNoticeDto FromDocument(DocumentObject document)
|
||||
{
|
||||
var noticeId = document.GetValue("noticeId", string.Empty).AsString;
|
||||
// Use MinValue instead of UtcNow for deterministic fallback on invalid/missing dates
|
||||
var published = document.TryGetValue("published", out var publishedValue)
|
||||
? publishedValue.DocumentType switch
|
||||
{
|
||||
DocumentType.DateTime => DateTime.SpecifyKind(publishedValue.ToUniversalTime(), DateTimeKind.Utc),
|
||||
DocumentType.String when DateTimeOffset.TryParse(publishedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
|
||||
_ => DateTimeOffset.UtcNow
|
||||
DocumentType.String when DateTimeOffset.TryParse(
|
||||
publishedValue.AsString,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||
out var parsed) => parsed.ToUniversalTime(),
|
||||
_ => DateTimeOffset.MinValue
|
||||
}
|
||||
: DateTimeOffset.UtcNow;
|
||||
: DateTimeOffset.MinValue;
|
||||
|
||||
var title = document.GetValue("title", noticeId).AsString;
|
||||
var summary = document.GetValue("summary", string.Empty).AsString;
|
||||
|
||||
Reference in New Issue
Block a user