# LeAudit DSL 规则域表结构设计

> **状态**: 核心表已落地 `leaudit_platform` 数据库  
> **相关**: [文档域表设计](document_schema_design.md) | [Bridge 目录设计](bridge_directory_design.md)

## 1. 设计目标

- `LeAudit` 运行时仍然按 YAML 规则文件执行
- 规则文件的持久真相源不再依赖固定本地目录
- 数据库存储规则元数据、版本、状态、OSS 地址
- YAML 正文作为文件产物放在 OSS
- 运行时根据数据库当前激活版本下载到本地临时文件，再按 `LeAudit` 原逻辑加载
- 支持规则编辑、校验、发布、回滚、审计

## 2. 核心原则

### 2.1 规则真相源是 "OSS 文件 + 数据库索引"

- 执行规则内容：YAML 文件
- 持久位置：OSS
- 数据库：记录元数据、版本、OSS 地址、激活状态

### 2.2 不让数据库直接变成规则执行解释器

数据库不替代 YAML/DSL 本身，不把规则拆碎存成"行式规则引擎配置"。

原因：

- 会偏离 `LeAudit` 原逻辑
- 会抬高规则编辑和发布成本
- 会让 DSL 的可读性和兼容性变差

### 2.3 规则编辑与规则执行分离

- 编辑阶段：编辑 YAML 文本、校验、上传 OSS、写版本记录
- 执行阶段：查 DB 元数据、下 OSS 文件、本地临时加载执行

## 3. 已落地核心表

| # | 表名 | 用途 | 状态 |
|---|------|------|------|
| 1 | `leaudit_rule_sets` | 规则集主表 | ✅ |
| 2 | `leaudit_rule_versions` | 规则版本表 | ✅ |
| 3 | `leaudit_rule_type_bindings` | 文档类型与规则集绑定 | ✅ |
| 4 | `leaudit_evaluation_point_groups` | 评查点规则组（PID 树形） | ✅ |
| 5 | `leaudit_evaluation_points` | 规则点/评查点元数据 | ✅ |

待补充（后续阶段）：

| # | 表名 | 用途 |
|---|------|------|
| 6 | `leaudit_rule_file_artifacts` | 规则文件产物（校验报告、导出包等） |
| 7 | `leaudit_rule_publish_logs` | 规则发布/回滚审计日志 |
| 8 | `leaudit_rule_validation_logs` | 规则校验日志 |

## 4. 核心表结构

### 4.1 `leaudit_rule_sets`

规则集主表，描述一个稳定的业务规则集实体。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | bigint PK | 主键 |
| `rule_type` | varchar(128) | 业务规则类型编码，对应 DSL `metadata.type_id` |
| `rule_name` | varchar(512) | 规则集名称 |
| `domain_type` | varchar(64) | contract / admin_license / legal_doc |
| `description` | text | 规则集描述 |
| `entry_module` | varchar(64) | 对应业务入口模块标识 |
| `current_version_id` | bigint | 当前激活版本 → `leaudit_rule_versions.id` |
| `status` | varchar(32) | draft / active / deprecated / archived |
| `is_builtin` | boolean | 是否内置规则集（内置不可删除） |
| `owner_user_id` | bigint | 负责人 |
| `create_time` / `update_time` / `delete_time` | timestamptz | 标准时间戳 |

### 4.2 `leaudit_rule_versions`

规则版本表，每次编辑/发布都生成一条新版本。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | bigint PK | 主键 |
| `rule_set_id` | bigint FK | → `leaudit_rule_sets.id` |
| `version_no` | varchar(64) | 语义版本号 |
| `version_seq` | int | 顺序号（rule_set 内递增） |
| `status` | varchar(32) | draft / published / deprecated / rollback |
| `source_type` | varchar(32) | oss_yaml / local_yaml / db_snippet |
| `dsl_format` | varchar(32) | yaml / json |
| `oss_url` | varchar(2048) | YAML 文件 OSS 地址 |
| `file_sha256` / `file_size` | — | 文件完整性 |
| `metadata_type_id` / `metadata_name` / `metadata_version` | — | DSL metadata 快照 |
| `change_note` | text | 变更说明 |
| `editor_user_id` / `publisher_user_id` / `published_at` | — | 编辑/发布信息 |
| `create_time` / `update_time` | timestamptz | 标准时间戳 |

### 4.3 `leaudit_rule_type_bindings`

文档类型和规则集的绑定表。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | bigint PK | 主键 |
| `doc_type_id` | bigint | → `leaudit_document_types.id` |
| `doc_type_code` | varchar(128) | 文档类型编码（冗余快速匹配） |
| `rule_set_id` | bigint FK | → `leaudit_rule_sets.id` |
| `binding_mode` | varchar(32) | explicit / wildcard / fallback |
| `priority` | int | 优先级（越大越高） |
| `is_active` | boolean | 是否激活 |
| `note` | text | 备注 |
| `create_time` / `update_time` | timestamptz | 标准时间戳 |

## 5. 规则执行时序

### 5.1 运行时加载（当前流程）

```text
文档类型确定
  → leaudit_rule_type_bindings 查绑定
  → leaudit_rule_sets.current_version_id
  → leaudit_rule_versions.oss_url
  → 下载 YAML 到本地临时文件
  → leaudit.dsl.loader.load_rules_file()
  → 进入执行链
```

### 5.2 编辑保存（后续实现）

```text
前端提交 YAML 文本
  → YAML 语法校验
  → LeAudit DSL 语义校验
  → 上传 YAML 到 OSS
  → 写 leaudit_rule_versions
  → 写 leaudit_rule_validation_logs（可选）
```

### 5.3 发布生效（后续实现）

```text
选择某版本发布
  → 更新 leaudit_rule_sets.current_version_id
  → 写 leaudit_rule_publish_logs（可选）
  → 清理/失效本地规则缓存
```

## 6. 与 OSS 的配合方式

### 6.1 路径建议

规则文件必须版本化，不能固定覆盖同一路径：

```text
oss://leaudit/rules/{rule_type}/{version_no}/rules.yaml
oss://leaudit/rules/{rule_type}/{version_no}/validation_report.json
```

### 6.2 为什么不能只用固定路径

如果始终覆盖同一个 `rules.yaml`：

- 难回滚
- 难追踪历史执行结果对应的规则版本
- 本地缓存容易脏
- 无法做结果回放和审计

## 7. 种子数据：从本地 `rules/` 到 OSS + DB

当前项目 `rules/` 目录下已有 20+ 个类型目录、每个包含可用的 `rules.yaml`。需一次性导入。

### 7.1 初始化脚本逻辑

```python
# 伪代码：把 rules/ 导入 OSS + DB
for each rules_dir in rules/:
    yaml_text = read(rules_dir / "rules.yaml")
    rules_file = parse_rules_yaml_text(yaml_text)

    version_no = rules_file.metadata.version
    oss_url = f"oss://leaudit/rules/{rules_dir.name}/{version_no}/rules.yaml"
    upload_to_oss(oss_url, yaml_text)

    rule_set = upsert_rule_set(
        rule_type=rules_dir.name,
        rule_name=rules_file.metadata.name,
        domain_type=classify_domain(rules_file),
        status="active",
        is_builtin=True,
    )

    version = insert_rule_version(
        rule_set_id=rule_set.id,
        version_no=version_no,
        oss_url=oss_url,
        ...
    )

    update_rule_set(rule_set.id, current_version_id=version.id)

    # 绑定文档类型（如果已知）
    doc_type_id = resolve_doc_type_id(rules_dir.name)
    if doc_type_id:
        insert_type_binding(...)
```

### 7.2 初始化后的本地 `rules/` 处置

**推荐方案**：保留为只读紧急回退备份，标记为不再接受编辑。如果 OSS 不可用或规则全损，本地至少有一套可用的规则副本。

### 7.3 `_TYPE_ID_RULES_MAP` 硬编码过渡

`leaudit_bridge/tasks.py` 中的硬编码映射：

```python
_TYPE_ID_RULES_MAP: dict[int, str] = {
    3: "行政处罚",
}
```

过渡策略：

1. **阶段 A**：保留硬编码为 fallback，同时 `leaudit_rule_type_bindings` 表已有数据
2. **阶段 B**：`_resolve_rules_path()` 先查 `leaudit_rule_type_bindings`，未命中 fallback 到硬编码
3. **阶段 C**：所有绑定入库后，删除 `_TYPE_ID_RULES_MAP`

## 8. 推荐接口能力（后续实现）

- `GET /api/leaudit/rule-sets`
- `GET /api/leaudit/rule-sets/{rule_type}`
- `GET /api/leaudit/rule-sets/{rule_type}/versions`
- `GET /api/leaudit/rule-versions/{version_id}/content`
- `POST /api/leaudit/rule-sets/{rule_type}/validate`
- `POST /api/leaudit/rule-sets/{rule_type}/versions`
- `POST /api/leaudit/rule-sets/{rule_type}/publish`
- `POST /api/leaudit/rule-sets/{rule_type}/rollback`

## 9. 最终结论

- 规则内容继续保持 YAML 形态
- 持久真相源从固定本地文件改为 OSS 文件
- 数据库存规则元数据、版本、状态和 OSS 地址
- 运行时通过 "DB → OSS → 本地临时 YAML → LeAudit loader" 保持 `LeAudit` 原逻辑不变
