# -*- coding: utf-8 -*- """采集网只读隔离与网闸配置文档一致性检查 + 代码级只读自检(issue #27)。 检查项: 1. 文档必备章节齐全(只读隔离设计 / 单向网闸配置 / 防火墙规则集 / 验证方法); 2. 红线关键词存在(只读 / 不反控 / 零控制指令 / 反向 DROP); 3. 引用文件(#19 拓扑策略文档 / edge-gateway 目录)存在; 4. **代码级只读自检**:扫描 `core/edge-gateway/drivers/*.py`,确认驱动 无任何写/控制方法(write / set_value / control 等),对齐 PRD 9 章。 用法:python _check_isolation_doc.py """ import os import re import sys HERE = os.path.dirname(os.path.abspath(__file__)) REPO_ROOT = os.path.dirname(HERE) DOC_PATH = os.path.join(HERE, "采集网只读隔离与网闸配置.md") DRIVERS_DIR = os.path.join(REPO_ROOT, "core", "edge-gateway", "drivers") REQUIRED_SECTIONS = [ "## 2. 只读隔离设计", "## 3. 单向网闸配置", "## 4. 防火墙规则集示例", "## 5. 验证方法", ] REDLINE_KEYWORDS = ["只读", "不反控", "零控制指令", "DROP", "反向"] # 写/控制方法名黑名单(驱动只读约束) WRITE_METHOD_PATTERN = re.compile( r"def (write|write_points|set_value|control|set_point|write_register)" r"|class .*Writer\b", re.IGNORECASE) def scan_drivers_readonly() -> list: """扫描 drivers/ 源码,返回命中写/控制方法的行描述。""" hits = [] if not os.path.isdir(DRIVERS_DIR): return ["drivers 目录不存在"] for name in sorted(os.listdir(DRIVERS_DIR)): if not name.endswith(".py") or name.startswith("_"): continue path = os.path.join(DRIVERS_DIR, name) for no, line in enumerate( open(path, "r", encoding="utf-8").read().splitlines(), 1): if WRITE_METHOD_PATTERN.search(line): hits.append(f"{name}:{no}: {line.strip()}") return hits def main() -> int: failures = [] text = open(DOC_PATH, "r", encoding="utf-8").read() for sec in REQUIRED_SECTIONS: if sec not in text: failures.append(f"缺少必备章节:{sec}") for kw in REDLINE_KEYWORDS: if kw not in text: failures.append(f"缺少红线关键词:{kw}") for ref in ("网络拓扑与隔离策略.md",): if not os.path.isfile(os.path.join(HERE, ref)): failures.append(f"引用文档缺失:{ref}") # 代码级只读自检:drivers/ 零写方法 hits = scan_drivers_readonly() if hits: failures.append(f"驱动存在写/控制方法:{hits}") if failures: print("FAIL") for f in failures: print(" -", f) return 1 print("OK: 章节/红线关键词/引用/驱动只读自检(零写方法)全部通过") return 0 if __name__ == "__main__": sys.exit(main())