namespace SeaHaven.Services.Implementation { /// /// Server-owned uplift state machine (SH-101). Canonical states are /// Pending, Approved, Rejected, ChangesRequested, Withdrawn, Expired. /// Legacy stored values "Denied" (-> Rejected) and "Cancelled" (-> Withdrawn) /// are read as their canonical equivalents but never (re)written. /// public static class UpliftStatus { public const string Pending = "Pending"; public const string Approved = "Approved"; public const string Rejected = "Rejected"; public const string ChangesRequested = "ChangesRequested"; public const string Withdrawn = "Withdrawn"; public const string Expired = "Expired"; public const string NoApprovalRequired = "NoApprovalRequired"; public const string Revoked = "Revoked"; // Legacy aliases retained only for reads; never written by new code. public const string LegacyDenied = "Denied"; public const string LegacyCancelled = "Cancelled"; private static readonly HashSet TerminalStates = new(StringComparer.Ordinal) { Approved, Rejected, Withdrawn, Expired, Revoked, NoApprovalRequired }; private static readonly HashSet ActiveStates = new(StringComparer.Ordinal) { Pending, ChangesRequested }; /// /// Maps a stored status to its canonical DTO value. Legacy "Denied" is /// read as "Rejected" and "Cancelled" as "Withdrawn". /// public static string ToCanonical(string? status) => status switch { LegacyDenied => Rejected, LegacyCancelled => Withdrawn, _ => status ?? Pending }; /// True for terminal (immutable) states, including legacy aliases. public static bool IsTerminal(string? status) => TerminalStates.Contains(ToCanonical(status)); /// True for actionable states that block a new active request. public static bool IsActive(string? status) => ActiveStates.Contains(ToCanonical(status)); /// /// Validates a server-owned transition. Only legal transitions return true. /// Terminal states (incl. legacy aliases) never transition out. /// public static bool CanTransition(string? from, string to) { var canonicalFrom = ToCanonical(from); if (TerminalStates.Contains(canonicalFrom)) return false; return canonicalFrom switch { Pending => to is Approved or Rejected or ChangesRequested or Withdrawn or Expired, ChangesRequested => to is Pending or Withdrawn or Expired, _ => false }; } /// Snapshot-readable active states, used by data-service filters. public static IReadOnlyCollection ActiveStatuses => ActiveStates; } /// /// Notification pipeline status (separate from the workflow ). /// A notification failure never destroys an actionable request. /// public static class UpliftNotificationStatus { public const string Pending = "Pending"; public const string Sent = "Sent"; public const string Error = "Error"; } /// /// Vendor completion document purposes (SH-101). Completion is the legacy/default /// behavior; UpliftEvidence is immutable supporting evidence for an uplift request. /// public static class VendorDocumentPurpose { public const string Completion = "Completion"; public const string UpliftEvidence = "UpliftEvidence"; public static bool IsValid(string? purpose) => purpose is Completion or UpliftEvidence; } }