using Admin.NET.Plugin.AiDOP.Entity.S8;
namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess;
/// Enable Gate 的检查结果。失败时带明确 reason code,便于前端与运维定位。
public sealed class S8DatasetEnableCheck
{
private S8DatasetEnableCheck(bool ok, string? reasonCode, string? message)
{
Ok = ok;
ReasonCode = reasonCode;
Message = message;
}
public bool Ok { get; }
public string? ReasonCode { get; }
public string? Message { get; }
public static S8DatasetEnableCheck Pass() => new(true, null, null);
public static S8DatasetEnableCheck Fail(string reasonCode, string message) => new(false, reasonCode, message);
}
///
/// STANDARD_DATASET 规则的统一启用前校验。
///
/// 校验顺序:先查目录层可判定的问题,再查实现层问题。
/// 理由——"这个数据集做不了 TIMEOUT"是定义问题,等 Provider 上线也不会变好;
/// 若先报 provider_not_registered 会误导配置方以为等实现即可。
///
/// 1. dataset_code 是否填写 → dataset_code_missing
/// 2. Catalog 中是否定义 → dataset_not_defined
/// 3. 定义是否停用 → dataset_not_ready
/// 4. 规则类型是否已知 → dataset_unknown_rule_type
/// 5. 目录机制能力是否支持 → dataset_capability_mismatch
/// 6. 目录 canonical 字段是否齐备 → dataset_missing_required_field
/// 7. Provider 是否注册 → dataset_provider_not_registered
/// 8. Provider 编码是否一致 → dataset_provider_code_mismatch
/// 9. Provider 能力是否同样满足 → dataset_capability_mismatch / dataset_missing_required_field
/// 10. 租户作用域 → invalid_tenant_scope
///
/// S8-TENANT-ONLY-BATCH5:第 ⑩ 步的工厂检查已删除。它此前会因 factoryId <= 0 拒绝启用,
/// 而 Batch 2 供给出来的运行策略行 factory_id 恒为 0 —— 那些规则本来永远启用不了。
///
/// S8-STANDARD-DATASET-HARD-CUTOVER-1:取数路径唯一,Gate 不再有"先判模式"这一步。
///
public class S8DatasetEnableGate : ITransient
{
private readonly IS8DatasetCatalog _catalog;
private readonly S8MonitoringDataProviderRegistry _registry;
public S8DatasetEnableGate(IS8DatasetCatalog catalog, S8MonitoringDataProviderRegistry registry)
{
_catalog = catalog;
_registry = registry;
}
///
/// 按数据库行检查。S8-RULE-GOVERNANCE-BATCH1 起,正式启用路径不再走这个重载——
/// 它读的是 rule.DatasetCode / rule.RuleType,而这两列现在只是
/// provisioning 维护的投影,可能与代码定义不一致。保留是为了既有校验路径与测试不受影响。
///
public S8DatasetEnableCheck Check(AdoS8WatchRule rule, long tenantId) =>
Check(rule.DatasetCode, rule.RuleType, rule.RuleCode, tenantId);
///
/// 按**显式的数据集编码与规则类型**检查。启用路径应使用本重载并传入代码定义的值,
/// 这样「能不能启用」的判定就不受任何人手工改 DB 投影列的影响。
///
public S8DatasetEnableCheck Check(string? datasetCode, string? ruleType, string? ruleCode, long tenantId)
{
// ① dataset_code
if (string.IsNullOrWhiteSpace(datasetCode))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.CodeMissing,
"STANDARD_DATASET 模式的规则必须提供 dataset_code");
// ② Catalog 定义
var definition = _catalog.Find(datasetCode);
if (definition == null)
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.NotDefined,
$"数据集 {datasetCode} 未在目录中定义");
// ③ 定义是否停用
if (!definition.IsActive)
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.NotReady,
$"数据集 {definition.DatasetCode} 已停用,不能用于规则执行");
// ④ 规则类型已知
if (!S8RuleTypeFieldRequirements.IsKnown(ruleType))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.UnknownRuleType,
$"规则类型 {ruleType} 不在 canonical 词表内,无法校验数据集能力");
// ⑤ 目录机制能力(定义层问题,先于 Provider 报出)
if (!definition.Capabilities.Supports(ruleType))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.CapabilityMismatch,
$"数据集 {definition.DatasetCode} 不支持 {ruleType} 类规则");
// ⑥ 目录 canonical 字段齐备(防"声明支持某机制但拿不出必需字段"的自相矛盾配置)
var missing = definition.Capabilities.MissingFieldsFor(ruleType);
if (missing is { Count: > 0 })
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.MissingRequiredField,
$"数据集 {definition.DatasetCode} 缺少 {ruleType} 必需字段:{string.Join(", ", missing)}");
// ⑦ Provider 注册
if (!_registry.IsRegistered(definition.DatasetCode))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.ProviderNotRegistered,
$"数据集 {definition.DatasetCode} 已定义但尚无 Provider 实现,可保存配置但不能启用");
// ⑧ Provider 自报编码与目录一致
var provider = _registry.Resolve(definition.DatasetCode);
if (!string.Equals(provider.DatasetCode?.Trim(), definition.DatasetCode, StringComparison.OrdinalIgnoreCase))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.ProviderCodeMismatch,
$"Provider 自报数据集编码 {provider.DatasetCode} 与目录定义 {definition.DatasetCode} 不一致");
// ⑨ Provider 实际能力必须与目录声明同样满足(不一致时按更严格的一方拒绝)
if (!provider.Capabilities.Supports(ruleType))
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.CapabilityMismatch,
$"Provider 实现不支持 {ruleType} 类规则(数据集 {definition.DatasetCode})");
var providerMissing = provider.Capabilities.MissingFieldsFor(ruleType);
if (providerMissing is { Count: > 0 })
return S8DatasetEnableCheck.Fail(
S8DatasetReasonCode.MissingRequiredField,
$"Provider 实现缺少 {ruleType} 必需字段:{string.Join(", ", providerMissing)}");
// ⑩ 作用域。只认 Tenant —— Factory 不是 S8 的隔离边界。
if (tenantId <= 0)
return S8DatasetEnableCheck.Fail(S8DatasetReasonCode.InvalidTenantScope, "租户作用域非法");
return S8DatasetEnableCheck.Pass();
}
/// 检查并在失败时抛 (配置类错误,API 层直接回给调用方)。
public void EnsureCanEnable(AdoS8WatchRule rule, long tenantId) =>
EnsureCanEnable(rule.DatasetCode, rule.RuleType, rule.RuleCode, tenantId);
/// 按显式数据集编码与规则类型检查并在失败时抛 。启用路径用这一个。
public void EnsureCanEnable(string? datasetCode, string? ruleType, string? ruleCode, long tenantId)
{
var result = Check(datasetCode, ruleType, ruleCode, tenantId);
if (!result.Ok)
throw new S8BizException($"[{result.ReasonCode}] {result.Message}");
}
}