namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.Health;
///
/// Authority 健康状态。
///
/// 只有三个状态,且 HEALTHY_EMPTY 不在其中:
/// 「Authority 可信但本轮确实一行都没有」是 HEALTHY 与 rows.Count == 0 的
/// 组合观测,不是一个独立状态。把它设成状态,就必须在状态机里读行数 ——
/// 而本门禁存在的全部意义,正是让恢复判定与行数解耦。
///
public static class S8AuthorityHealthState
{
/// 本轮取数可信:生产者最近一次成功、够新、无更新的失败/在途运行、快照不变量成立。与返回了几行无关。
public const string Healthy = "HEALTHY";
/// 已知不可信:生产失败 / 在途 / 超期 / 快照不变量被破坏。运维动作 = 去修管线。
public const string Degraded = "DEGRADED";
/// 无法建立可信性:从未成功、无 AuthoritySpec、发布无法确认、探测自身失败。运维动作 = 去查这条链是否跑起来过。
public const string Unknown = "UNKNOWN";
}
/// 健康判定的原因码。回执与 detection_log 用它解释「为什么本轮没做恢复」。
public static class S8AuthorityHealthReason
{
public const string Healthy = "HEALTHY";
/// 该数据集没有声明 AuthoritySpec —— 无从判断,保守拦截。
public const string NoAuthoritySpec = "NO_AUTHORITY_SPEC";
/// 回溯窗口内没有任何一次成功的生产运行。含「从未同步」与「停摆超过回溯窗口」两种。
public const string NoSuccessfulRun = "NO_SUCCESSFUL_RUN";
///
/// 回溯窗口内有成功运行,但没有一次带着发布证据(summary_json.publish)。
///
/// 只对 RequiresPublicationEvidence 的数据集适用。它与
/// 是两件事:跑批成功了,但运行日志证明不了
/// 「当前快照」是哪一批 —— 这时任何按时间兜底挑出来的快照都可能是上一轮的。
/// 保守判 UNKNOWN,让 Wave2 闸门拦下恢复。
///
public const string NoPublication = "NO_PUBLICATION";
/// 最近一次成功距今超过 StaleWindow。
public const string Stale = "STALE";
/// 存在比最近一次成功更新、且尚未终态的生产运行,年龄仍在 StaleWindow 内(正常跑批中)。
public const string InFlight = "IN_FLIGHT";
/// 同上,但年龄已超过 StaleWindow —— 大概率是永不回收的孤儿 RUNNING 行。
public const string StuckRun = "STUCK_RUN";
/// 最近一次成功之后出现了终态失败。
public const string LatestFailed = "LATEST_FAILED";
/// 最近一次成功之后出现了终态 PARTIAL。
public const string LatestPartial = "LATEST_PARTIAL";
/// Authority 的快照不变量被破坏(如同时存在多个当前批次、当前快照里有必填列为空)。
public const string SnapshotInvariantFailed = "SNAPSHOT_INVARIANT_FAILED";
///
/// 快照型 Authority 无法确认「本轮发布是否成功」。
/// 这是 Rule03 的现状:合法空快照与发布失败在库里字面等同(都表现为 0 个当前批次),
/// 生产侧不留任何 batch marker。保守判 UNKNOWN,不猜。
///
public const string SnapshotPublicationUnconfirmed = "SNAPSHOT_PUBLICATION_UNCONFIRMED";
/// Authority 存在多个写入方且部分不留运行日志,run-log 无法证明最终 provenance(Rule01)。
public const string Rule01MultiWriterUntrusted = "RULE01_MULTI_WRITER_UNTRUSTED";
/// 健康探测自身抛错。fail-safe:判 UNKNOWN、拦截恢复,绝不当成 HEALTHY。
public const string ResolverFailed = "RESOLVER_FAILED";
}
/// 健康判定结果。刻意不是 bool —— 回执必须能回答「为什么」。
public sealed class S8AuthorityHealthResult
{
public string DatasetCode { get; init; } = string.Empty;
public long TenantId { get; init; }
/// 见 。
public string State { get; init; } = S8AuthorityHealthState.Unknown;
/// 见 。
public string ReasonCode { get; init; } = S8AuthorityHealthReason.NoAuthoritySpec;
/// 人类可读说明,带上判定用到的实际数值。
public string Reason { get; init; } = string.Empty;
public DateTime ObservedAt { get; init; }
/// 最近一次成功生产运行的结束时刻;无则为 null。
public DateTime? LastSuccessAt { get; init; }
/// 超过该时刻即判 STALE(= LastSuccessAt + StaleWindow);LastSuccessAt 为 null 时为 null。
public DateTime? StaleAfter { get; init; }
///
/// 是否允许执行恢复判定。只有 HEALTHY 放行。
/// 注意这里没有、也不允许有任何 rows.Count 分支:
/// 「Authority 可信 + 本轮 0 行」必须放行恢复,那是合法的业务全退场。
///
public bool AllowsRecovery => string.Equals(State, S8AuthorityHealthState.Healthy, StringComparison.Ordinal);
}