using System.Text.Json; namespace JevOverlay.Core; public readonly record struct DecisionKey(string Session, int Step); public sealed class DecisionEntry(DecisionKey key, JsonElement record) { private static readonly JsonSerializerOptions Pretty = new() { WriteIndented = true }; public DecisionKey Key { get; } = key; public JsonElement Record { get; } = record; public string Action => Text(Record, "action"); public string Source => Text(Record, "source", "unknown"); public string Status { get; internal set; } = "Proposal only; execution is not recorded."; public string? SessionEnd { get; internal set; } public string Title => $"#{Key.Step} · {Source.ToUpperInvariant()} · {Text(Record, "state_type")}"; public string Summary() { string latency = ""; var jev = Field(Record, "jev"); if (Field(jev, "latency_s") is { ValueKind: JsonValueKind.Number } seconds && seconds.TryGetDouble(out double number) && double.IsFinite(number)) latency = $" · {number * 1000:0} ms"; return Limit($"{Title}\nSession: {Key.Session} · {Text(Record, "ts")}\n" + $"Proposed: {Action}\nParameters: {Field(Record, "params")}\n" + $"{Status}\n" + (SessionEnd is null ? "" : $"{SessionEnd}\n") + $"\nReason: {Text(Record, "reason", "Not recorded")}\n" + $"Recorded confidence: {Field(Record, "confidence")?.ToString() ?? "—"}{latency}\n" + (string.IsNullOrEmpty(Text(Record, "error")) ? "" : $"Error: {Text(Record, "error")}\n") + "Confidence does not prove correctness."); } public string Evidence() { var jev = Field(Record, "jev"); if (jev is null || jev.Value.ValueKind == JsonValueKind.Null) return "No Jev response recorded for this decision.\nThe source field identifies the policy path."; return Limit("Recorded Jev response (not private reasoning).\n" + "The recorded answer gate may differ from the policy's final gate.\n\n" + JsonSerializer.Serialize(jev.Value, Pretty)); } internal static string Limit(string value) => value.Length <= 16000 ? value : value[..16000] + "\n[Display truncated]"; internal static JsonElement? Field(JsonElement? obj, string name) => obj is { ValueKind: JsonValueKind.Object } element && element.TryGetProperty(name, out var value) ? value : null; internal static string Text(JsonElement? obj, string name, string fallback = "") => Field(obj, name) is { ValueKind: JsonValueKind.String } value ? value.GetString()! : fallback; } /// Joins recorded events by session and step, never by wall-clock time. public sealed class DecisionHistory { public const int Capacity = 100; private readonly List entries = []; public IReadOnlyList Entries => entries; public int SkippedLines { get; private set; } public string? Notice { get; private set; } public void Apply(TailBatch batch) { if (batch.Reset) { entries.Clear(); Notice = null; SkippedLines = 0; } SkippedLines += batch.SkippedLines; foreach (string line in batch.Lines) { if (string.IsNullOrWhiteSpace(line)) continue; try { using var document = JsonDocument.Parse(line); if (!Consume(document.RootElement)) SkippedLines++; } catch (JsonException) { SkippedLines++; } } } private bool Consume(JsonElement root) { string session = DecisionEntry.Text(root, "session"); string kind = DecisionEntry.Text(root, "event"); if (session.Length == 0 || kind.Length == 0) return false; if (kind == "session_end") { string message = $"Session ended: {DecisionEntry.Text(root, "reason", "unspecified")}"; foreach (var entry in entries.Where(e => e.Key.Session == session)) entry.SessionEnd = message; Notice = $"{session}: {message}"; return true; } if (kind is not ("decide" or "action_error" or "action_rejected" or "model_error" or "fallback_error" or "policy_error" or "no_decision")) return true; // Other runner events do not describe a decision or its status. if (DecisionEntry.Field(root, "step") is not { ValueKind: JsonValueKind.Number } stepValue || !stepValue.TryGetInt32(out int step) || step < 0) return false; var key = new DecisionKey(session, step); if (kind == "decide") { if (DecisionEntry.Text(root, "action").Length == 0) return false; entries.RemoveAll(entry => entry.Key == key); entries.Insert(0, new DecisionEntry(key, root.Clone())); if (entries.Count > Capacity) entries.RemoveAt(entries.Count - 1); Notice = null; } else if (kind is "action_error" or "action_rejected") { var entry = entries.Find(entry => entry.Key == key); string action = DecisionEntry.Text(root, "action"); if (entry is not null && (action.Length == 0 || action == entry.Action)) entry.Status = kind == "action_error" ? $"Action request error; completion unknown: {DecisionEntry.Text(root, "error")}" : $"Action request rejected: {DecisionEntry.Text(root, "message")}"; } else Notice = DecisionEntry.Limit($"{session} #{step} · {kind}: {DecisionEntry.Text(root, "error")}"); return true; } }