partly or unimplemented features - now implemented

This commit is contained in:
master
2026-02-09 08:53:51 +02:00
parent 1bf6bbf395
commit 4bdc298ec1
674 changed files with 90194 additions and 2271 deletions

View File

@@ -23,6 +23,8 @@ namespace StellaOps.Cli.Commands;
/// </summary>
public static class UnknownsCommandGroup
{
private const string DefaultUnknownsExportSchemaVersion = "unknowns.export.v1";
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = true,
@@ -395,6 +397,13 @@ public static class UnknownsCommandGroup
Description = "Output format: json, csv, ndjson"
};
formatOption.SetDefaultValue("json");
formatOption.FromAmong("json", "csv", "ndjson");
var schemaVersionOption = new Option<string>("--schema-version")
{
Description = "Schema version to stamp into exported artifacts."
};
schemaVersionOption.SetDefaultValue(DefaultUnknownsExportSchemaVersion);
var outputOption = new Option<string?>("--output", new[] { "-o" })
{
@@ -404,6 +413,7 @@ public static class UnknownsCommandGroup
var exportCommand = new Command("export", "Export unknowns with fingerprints and triggers for offline analysis");
exportCommand.Add(bandOption);
exportCommand.Add(formatOption);
exportCommand.Add(schemaVersionOption);
exportCommand.Add(outputOption);
exportCommand.Add(verboseOption);
@@ -411,10 +421,11 @@ public static class UnknownsCommandGroup
{
var band = parseResult.GetValue(bandOption);
var format = parseResult.GetValue(formatOption) ?? "json";
var schemaVersion = parseResult.GetValue(schemaVersionOption) ?? DefaultUnknownsExportSchemaVersion;
var output = parseResult.GetValue(outputOption);
var verbose = parseResult.GetValue(verboseOption);
return await HandleExportAsync(services, band, format, output, verbose, cancellationToken);
return await HandleExportAsync(services, band, format, schemaVersion, output, verbose, cancellationToken);
});
return exportCommand;
@@ -1009,6 +1020,7 @@ public static class UnknownsCommandGroup
IServiceProvider services,
string? band,
string format,
string schemaVersion,
string? outputPath,
bool verbose,
CancellationToken ct)
@@ -1052,9 +1064,15 @@ public static class UnknownsCommandGroup
// Deterministic ordering by band priority, then score descending
var sorted = result.Items
.OrderBy(u => u.Band switch { "hot" => 0, "warm" => 1, "cold" => 2, _ => 3 })
.OrderBy(u => u.Band.ToLowerInvariant() switch { "hot" => 0, "warm" => 1, "cold" => 2, _ => 3 })
.ThenByDescending(u => u.Score)
.ThenBy(u => u.PackageId, StringComparer.Ordinal)
.ThenBy(u => u.PackageVersion, StringComparer.Ordinal)
.ThenBy(u => u.Id)
.ToList();
var exportedAt = sorted.Count == 0
? DateTimeOffset.UnixEpoch
: sorted.Max(u => u.LastEvaluatedAt).ToUniversalTime();
TextWriter writer = outputPath is not null
? new StreamWriter(outputPath)
@@ -1065,17 +1083,33 @@ public static class UnknownsCommandGroup
switch (format.ToLowerInvariant())
{
case "csv":
await WriteCsvAsync(writer, sorted);
await WriteCsvAsync(writer, sorted, schemaVersion, exportedAt);
break;
case "ndjson":
await writer.WriteLineAsync(JsonSerializer.Serialize(new
{
schemaVersion,
exportedAt,
itemCount = sorted.Count
}, JsonOptions));
foreach (var item in sorted)
{
await writer.WriteLineAsync(JsonSerializer.Serialize(item, JsonOptions));
await writer.WriteLineAsync(JsonSerializer.Serialize(new
{
schemaVersion,
item
}, JsonOptions));
}
break;
case "json":
default:
await writer.WriteLineAsync(JsonSerializer.Serialize(sorted, JsonOptions));
await writer.WriteLineAsync(JsonSerializer.Serialize(new UnknownsExportEnvelope
{
SchemaVersion = schemaVersion,
ExportedAt = exportedAt,
ItemCount = sorted.Count,
Items = sorted
}, JsonOptions));
break;
}
}
@@ -1102,8 +1136,13 @@ public static class UnknownsCommandGroup
}
}
private static async Task WriteCsvAsync(TextWriter writer, IReadOnlyList<UnknownDto> items)
private static async Task WriteCsvAsync(
TextWriter writer,
IReadOnlyList<UnknownDto> items,
string schemaVersion,
DateTimeOffset exportedAt)
{
await writer.WriteLineAsync($"# schema_version={schemaVersion}; exported_at={exportedAt:O}; item_count={items.Count}");
// CSV header
await writer.WriteLineAsync("id,package_id,package_version,band,score,reason_code,fingerprint_id,first_seen_at,last_evaluated_at");
@@ -1590,6 +1629,14 @@ public static class UnknownsCommandGroup
public int TotalCount { get; init; }
}
private sealed record UnknownsExportEnvelope
{
public string SchemaVersion { get; init; } = DefaultUnknownsExportSchemaVersion;
public DateTimeOffset ExportedAt { get; init; }
public int ItemCount { get; init; }
public IReadOnlyList<UnknownDto> Items { get; init; } = [];
}
private sealed record UnknownDto
{
public Guid Id { get; init; }