#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; using ShrinkContext; using ShrinkEventBus; using ShrinkNetwork; using ShrinkCommand; namespace ShrinkSDK.Inspection { public sealed class ShrinkInspectionRelation { public string From = ""; public string To = ""; public string Kind = ""; public string Scope = ""; } /// Explicit, on-demand snapshot. Call from the owning host thread while registration/composition is stable. public sealed class ShrinkInspectionSnapshot { public int FormatVersion = 1; public string BuildId = ""; public string CapturedAtUtc = DateTime.UtcNow.ToString("O"); public readonly List Coverage = new(); public readonly List Relations = new(); public readonly Dictionary State = new(); public static string BuildIdentity(IEnumerable assemblyNames) { var loaded = AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).ToArray(); var identity = string.Join(";", assemblyNames.Distinct().OrderBy(n => n, StringComparer.Ordinal).Select(name => { var candidates = loaded.Where(a => a.GetName().Name == name).ToArray(); if (candidates.Length != 1) throw new InvalidOperationException("Cannot establish unique runtime assembly identity: " + name); return name + ":" + candidates[0].ManifestModule.ModuleVersionId.ToString("N"); })); using var sha = System.Security.Cryptography.SHA256.Create(); return BitConverter.ToString(sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(identity))).Replace("-", "").ToLowerInvariant(); } public ShrinkInspectionSnapshot(string buildId) { BuildId = buildId; } public string ToJson() => JsonConvert.SerializeObject(this, Formatting.Indented, new Newtonsoft.Json.Converters.StringEnumConverter()); public void AddContext(string scope, ShrinkContextRuntime runtime, ShrinkContextLoader? loader = null) { var snapshot = runtime.CaptureDiagnostic(); State.Add(scope, new { snapshot.BindingCount, snapshot.Notifications, Fibers = snapshot.Fibers.Select(f => new { f.Uid, f.Name, f.Status, f.Retired, f.InTransition, f.Inject, f.Provide, f.Dependencies, f.ErrorType }).ToArray(), Transaction = loader?.LastTransaction is { } t ? new { t.Generation, t.Phase, t.CurrentEntryId, t.ErrorType, t.RestoreAttempted, t.PreviousCompositionRestored, t.RestoreErrorType } : null }); foreach (var fiber in snapshot.Fibers.Where(f => !f.Retired)) foreach (var dependency in fiber.Dependencies) { Add("fiber:" + scope + ":" + fiber.Uid, "key:" + dependency.Key, "requires@" + dependency.Realm, scope); if (dependency.CurrentProviderUid.HasValue) Add("fiber:" + scope + ":" + dependency.CurrentProviderUid, "fiber:" + scope + ":" + fiber.Uid, "provider:" + dependency.Key + "@" + dependency.Realm, scope); } } public void AddNetwork(string scope, ShrinkNetworkService service) { State.Add(scope, new { Metrics = service.GetDiagnosticsSnapshot(), Queue = service.DispatchScheduler is ShrinkNetworkDispatchQueue queued ? queued.CaptureDiagnostics() : (service.DispatchScheduler as ShrinkNetworkInlineDispatchScheduler)?.CaptureDiagnostics() }); foreach (var meta in service.MessageRegistry.Registrations) { Add("opcode:" + meta.Opcode, "type:" + TypeName(meta.MessageType), "message", scope); if (!string.IsNullOrEmpty(meta.Route)) Add("route:" + meta.Route, "type:" + TypeName(meta.MessageType), "message", scope); } foreach (var pair in service.Router.CaptureBindings()) Add("type:" + TypeName(pair.Key), MethodId(pair.Value), "handler", scope); } public void AddEventBus(string scope, IShrinkEventBus bus) { try { var bindings = EventBus.CaptureBindings(bus); State.Add(scope, new { Bus = bus.Key.ToString(), Subscribers = bindings.Count }); foreach (var binding in bindings) Add("type:" + TypeName(binding.EventType), MethodId(binding.Handler), "subscribes", scope); } catch (NotSupportedException) { Coverage.Add(scope + ": custom EventBus does not expose bindings."); } } public void AddCommands(string scope, ShrinkCommandService service) { State.Add(scope, new { Registrations = service.Commands.Select(c => new { c.Path, c.Aliases, c.Permission, c.SourceKind }).ToArray() }); foreach (var binding in service.CaptureBindings()) Add("command:" + binding.Path, MethodId(binding.Handler), "command", scope); } private void Add(string from, string to, string kind, string scope) => Relations.Add(new ShrinkInspectionRelation { From = from, To = to, Kind = kind, Scope = scope }); private static string TypeName(Type type) => (type.FullName ?? type.Name).Replace('+', '.'); public static string MethodId(MethodInfo method) { // Generated wrappers and generic signatures remain runtime-only endpoints rather than guessed source matches. if (method.IsGenericMethod || method.DeclaringType?.IsGenericType == true || method.GetParameters().Any(p => p.ParameterType.IsGenericType || p.ParameterType.IsArray || p.ParameterType.IsByRef)) return "runtime-method:" + method.Module.ModuleVersionId.ToString("N") + ":" + method.MetadataToken; var parameters = method.GetParameters(); return "M:" + TypeName(method.DeclaringType!) + "." + method.Name + (parameters.Length == 0 ? "" : "(" + string.Join(",", parameters.Select(p => TypeName(p.ParameterType))) + ")"); } } }