#!/usr/bin/env python3 # -*- coding: utf-8 -*- r"""观澜云端版 · 脱敏面孔 (部署包 CLAUDE_CODE_部署提示词.md §安全边界 / §C 内容与数据审核; 交接 2026-09-06). 云端包只允许"经批准的脱敏摘要和资料索引", 且共享同一份事实契约 (端口与交接重构任务 §内容与数据架构). 本脚本从 **契约 + 四消费者** (outputs/rudong/guanlan/{facts_contract_v0.json, derived/*}) 派生云端可用内容, 落 outputs/rudong/guanlan/cloud/: portal_claims.json · detail_cards.json · qa_refs.json · report_summary.md — 与内部消费者同结构, 文本经 src/windscada/deid_public.scrub_or_die (场名/OEM/业主/供应商/文档名/内部系统名), 内嵌 contract_sha256 不变 (仍指向同一契约, 可追溯) claims_public.json — 全 119 条的对外投影 (claim_id / 六枚举 / 脱敏标题 / 时间窗 / 样本层级 / 聚合层级 / 适用范围 / 归宿 / 缺失证据 / 原条 sha256); 云端问答引用与卡片只认它 cloud_manifest.json + scan_report.md — 扫描规则 / 逐文件命中 (脱敏前→后) / 处置 / 每个产物 SHA-256 / 契约 sha / git head / 未脱敏项 (机组号) 的显式声明 铁律 (deid_public 同款): "替换过了"不算数, 只有回扫零命中算; 回扫表 = deid_public.LEAK_RE + 本文件 EXTRA_RE (绝对路径 / 邮箱 / 手机 / 身份证 / 精确坐标 / 密钥格式), 任一命中 → 不出件 (exit 2). 机组号 (WTG\d\d) 默认**保留** (中文版是给业主本人看的; 是否代号化 = 用户裁决, --unitize 走 windscada_cloud_pack.unitize), manifest 里 unitized 字段显式声明, 不静默. 用法: build [--unitize] | check """ import argparse, datetime as dt, hashlib, json, re, subprocess, sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")); sys.path.insert(0, str(ROOT / "scripts")) from windscada import deid_public as DP # noqa: E402 ★导入失败 = 拒绝运行 (闸不可用时放行比没闸更坏) GUANLAN = ROOT / "outputs/rudong/guanlan"; CONTRACT = GUANLAN / "facts_contract_v0.json"; DERIVED = GUANLAN / "derived"; CLOUD = GUANLAN / "cloud" CONSUMERS = ("portal_claims.json", "detail_cards.json", "qa_refs.json", "report_summary.md") sha = lambda b: hashlib.sha256(b).hexdigest(); J = lambda p: json.loads(Path(p).read_text(encoding="utf-8")) # 部署提示词 §C 点名的扫描项, deid_public.LEAK_RE 没覆盖的部分. 与替换表分开写 (互为独立发现). EXTRA_RE = [ ("本机绝对路径", re.compile(r"/Users/[A-Za-z0-9_.\-]+|/Volumes/[A-Za-z0-9_\-]+|/home/[A-Za-z0-9_.\-]+|[A-Za-z]:\\\\[^\s\"']{1,40}")), ("邮箱", re.compile(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}")), ("手机号", re.compile(r"(?