S8AuthorityHealth.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.Health;
  2. /// <summary>
  3. /// Authority 健康状态。
  4. ///
  5. /// <para><b>只有三个状态,且 HEALTHY_EMPTY 不在其中</b>:
  6. /// 「Authority 可信但本轮确实一行都没有」是 <c>HEALTHY</c> 与 <c>rows.Count == 0</c> 的
  7. /// 组合观测,不是一个独立状态。把它设成状态,就必须在状态机里读行数 ——
  8. /// 而本门禁存在的全部意义,正是让恢复判定<b>与行数解耦</b>。</para>
  9. /// </summary>
  10. public static class S8AuthorityHealthState
  11. {
  12. /// <summary>本轮取数可信:生产者最近一次成功、够新、无更新的失败/在途运行、快照不变量成立。与返回了几行无关。</summary>
  13. public const string Healthy = "HEALTHY";
  14. /// <summary>已知不可信:生产失败 / 在途 / 超期 / 快照不变量被破坏。运维动作 = 去修管线。</summary>
  15. public const string Degraded = "DEGRADED";
  16. /// <summary>无法建立可信性:从未成功、无 AuthoritySpec、发布无法确认、探测自身失败。运维动作 = 去查这条链是否跑起来过。</summary>
  17. public const string Unknown = "UNKNOWN";
  18. }
  19. /// <summary>健康判定的原因码。回执与 detection_log 用它解释「为什么本轮没做恢复」。</summary>
  20. public static class S8AuthorityHealthReason
  21. {
  22. public const string Healthy = "HEALTHY";
  23. /// <summary>该数据集没有声明 AuthoritySpec —— 无从判断,保守拦截。</summary>
  24. public const string NoAuthoritySpec = "NO_AUTHORITY_SPEC";
  25. /// <summary>回溯窗口内没有任何一次成功的生产运行。含「从未同步」与「停摆超过回溯窗口」两种。</summary>
  26. public const string NoSuccessfulRun = "NO_SUCCESSFUL_RUN";
  27. /// <summary>最近一次成功距今超过 StaleWindow。</summary>
  28. public const string Stale = "STALE";
  29. /// <summary>存在比最近一次成功更新、且尚未终态的生产运行,年龄仍在 StaleWindow 内(正常跑批中)。</summary>
  30. public const string InFlight = "IN_FLIGHT";
  31. /// <summary>同上,但年龄已超过 StaleWindow —— 大概率是永不回收的孤儿 RUNNING 行。</summary>
  32. public const string StuckRun = "STUCK_RUN";
  33. /// <summary>最近一次成功之后出现了终态失败。</summary>
  34. public const string LatestFailed = "LATEST_FAILED";
  35. /// <summary>最近一次成功之后出现了终态 PARTIAL。</summary>
  36. public const string LatestPartial = "LATEST_PARTIAL";
  37. /// <summary>Authority 的快照不变量被破坏(如同时存在多个当前批次、当前快照里有必填列为空)。</summary>
  38. public const string SnapshotInvariantFailed = "SNAPSHOT_INVARIANT_FAILED";
  39. /// <summary>
  40. /// 快照型 Authority 无法确认「本轮发布是否成功」。
  41. /// <para>这是 Rule03 的现状:合法空快照与发布失败在库里字面等同(都表现为 0 个当前批次),
  42. /// 生产侧不留任何 batch marker。保守判 UNKNOWN,不猜。</para>
  43. /// </summary>
  44. public const string SnapshotPublicationUnconfirmed = "SNAPSHOT_PUBLICATION_UNCONFIRMED";
  45. /// <summary>Authority 存在多个写入方且部分不留运行日志,run-log 无法证明最终 provenance(Rule01)。</summary>
  46. public const string Rule01MultiWriterUntrusted = "RULE01_MULTI_WRITER_UNTRUSTED";
  47. /// <summary>健康探测自身抛错。fail-safe:判 UNKNOWN、拦截恢复,绝不当成 HEALTHY。</summary>
  48. public const string ResolverFailed = "RESOLVER_FAILED";
  49. }
  50. /// <summary>健康判定结果。<b>刻意不是 bool</b> —— 回执必须能回答「为什么」。</summary>
  51. public sealed class S8AuthorityHealthResult
  52. {
  53. public string DatasetCode { get; init; } = string.Empty;
  54. public long TenantId { get; init; }
  55. /// <summary>见 <see cref="S8AuthorityHealthState"/>。</summary>
  56. public string State { get; init; } = S8AuthorityHealthState.Unknown;
  57. /// <summary>见 <see cref="S8AuthorityHealthReason"/>。</summary>
  58. public string ReasonCode { get; init; } = S8AuthorityHealthReason.NoAuthoritySpec;
  59. /// <summary>人类可读说明,带上判定用到的实际数值。</summary>
  60. public string Reason { get; init; } = string.Empty;
  61. public DateTime ObservedAt { get; init; }
  62. /// <summary>最近一次成功生产运行的结束时刻;无则为 null。</summary>
  63. public DateTime? LastSuccessAt { get; init; }
  64. /// <summary>超过该时刻即判 STALE(= LastSuccessAt + StaleWindow);LastSuccessAt 为 null 时为 null。</summary>
  65. public DateTime? StaleAfter { get; init; }
  66. /// <summary>
  67. /// 是否允许执行恢复判定。<b>只有 HEALTHY 放行</b>。
  68. /// <para>注意这里没有、也不允许有任何 rows.Count 分支:
  69. /// 「Authority 可信 + 本轮 0 行」必须放行恢复,那是合法的业务全退场。</para>
  70. /// </summary>
  71. public bool AllowsRecovery => string.Equals(State, S8AuthorityHealthState.Healthy, StringComparison.Ordinal);
  72. }