using System; using System.Collections.Generic; namespace VanadiumGuard.Server.Bans { /// One machine as the service knows it, and every account seen on it. public sealed class MachineRecord { public string Composite { get; set; } = string.Empty; /// /// The component hashes this machine reported. Kept so a ban can still recognise the /// machine after one part is swapped and the composite changes. /// public Dictionary Components { get; set; } = new Dictionary(StringComparer.Ordinal); /// Accounts that have opened a guard session from this machine. public List PlayerIds { get; set; } = new List(); public DateTimeOffset FirstSeenUtc { get; set; } public DateTimeOffset LastSeenUtc { get; set; } /// Null when the machine is not banned. A lifted ban is removed, not flagged. public MachineBan? Ban { get; set; } } /// /// A hardware ban. /// /// Every field here exists to answer a question someone will eventually ask about this /// record: who did it, when, why, and until when. A ban list with no answers to those is /// a liability rather than a tool - the appeal that cannot be evaluated gets denied by /// default, which is the failure mode worth engineering against. /// /// public sealed class MachineBan { public string Reason { get; set; } = string.Empty; /// Identity of the moderator or system that issued it, recorded verbatim. public string IssuedBy { get; set; } = string.Empty; public DateTimeOffset IssuedUtc { get; set; } /// Null bans permanently. public DateTimeOffset? ExpiresUtc { get; set; } /// /// The accounts linked to the machine when the ban was issued. A snapshot, so the /// reviewer can see what the moderator was looking at rather than what the link /// table happens to say today. /// public List LinkedPlayerIdsAtIssue { get; set; } = new List(); public bool IsActive(DateTimeOffset now) { return ExpiresUtc == null || ExpiresUtc > now; } } }