From 87949f42b51aa00ac89c9f487032055d4ca5eb9a Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 12 Jul 2026 13:18:50 +0900 Subject: [PATCH] feat(web): add lineage_events.jsonl structured audit logging for collection runs --- .../Services/KisDataCollectionOrchestrator.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs b/src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs index c6da4209..2a4d43a7 100644 --- a/src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs +++ b/src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs @@ -168,6 +168,7 @@ public class KisDataCollectionOrchestrator : ICollectionOrchestrator } }; File.WriteAllText(outputPath, JsonSerializer.Serialize(outputData, new JsonSerializerOptions { WriteIndented = true })); + LogLineageEvent(runId, result.Status, result.SuccessCount, result.ErrorCount); _logger.LogInformation("Collection run {RunId} finished with status {Status}: {Success} ok, {Errors} errors", runId, result.Status, result.SuccessCount, result.ErrorCount); @@ -220,6 +221,46 @@ public class KisDataCollectionOrchestrator : ICollectionOrchestrator return false; } + + private static void LogLineageEvent(string runId, string status, int successCount, int errorCount) + { + try + { + var baseDir = AppContext.BaseDirectory; + var current = new DirectoryInfo(baseDir); + string? repoRoot = null; + + while (current != null) + { + if (Directory.Exists(Path.Combine(current.FullName, ".git"))) + { + repoRoot = current.FullName; + break; + } + current = current.Parent; + } + + if (repoRoot != null) + { + var runtimeDir = Path.Combine(repoRoot, "runtime"); + Directory.CreateDirectory(runtimeDir); + var lineagePath = Path.Combine(runtimeDir, "lineage_events.jsonl"); + + var ev = new + { + @event = "collection_run_completed", + run_id = runId, + status = status, + success_count = successCount, + error_count = errorCount, + timestamp = DataNormalizationHelper.KstNowIso() + }; + + File.AppendAllText(lineagePath, JsonSerializer.Serialize(ev) + "\n"); + } + } + catch { /* Robust fallback */ } + } }