@yottameta/yotta-compliance 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +265 -0
- package/README.zh-CN.md +242 -0
- package/SKILL.md +208 -0
- package/assets/banner.png +0 -0
- package/bin/install.js +336 -0
- package/install.sh +327 -0
- package/package.json +35 -12
- package/references/coverage.md +123 -0
- package/references/report-format.md +263 -0
- package/references/rule-authoring.md +241 -0
- package/rules/data-export.json +175 -0
- package/rules/pipl.json +406 -0
- package/scripts/yotta_compliance.py +1426 -0
package/install.sh
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# yotta-compliance 多智能体安装脚本(YottaSkills)
|
|
3
|
+
# 用法:
|
|
4
|
+
# bash install.sh --agent <name> # 按智能体默认用户级目录安装
|
|
5
|
+
# bash install.sh --dir <path> # 装到指定目录(用户改过目录的智能体)
|
|
6
|
+
# bash install.sh -g # 装到全部已知智能体用户级目录
|
|
7
|
+
# bash install.sh # 检测并安装到已存在的项目级目录
|
|
8
|
+
# bash install.sh --list # 列出智能体 -> 默认目录
|
|
9
|
+
# bash install.sh --dry-run # 只显示将写入的目标,不写文件
|
|
10
|
+
# bash install.sh --version # 显示版本
|
|
11
|
+
# bash install.sh --help # 显示帮助
|
|
12
|
+
# 退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SKILL_NAME="yotta-compliance"
|
|
16
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
_IS_WINDOWS=0
|
|
18
|
+
case "$(uname -s)" in
|
|
19
|
+
MINGW*|MSYS*)
|
|
20
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -W)"
|
|
21
|
+
_IS_WINDOWS=1
|
|
22
|
+
;;
|
|
23
|
+
CYGWIN*) _IS_WINDOWS=1 ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
# 智能体 -> 用户级默认目录(--agent 装到第一个)
|
|
27
|
+
# .agents/skills 并非通用目录:OpenCode / Cursor / Cline / Amp / Kimi / Gemini CLI / GitHub Copilot 读取。
|
|
28
|
+
dirs_for() {
|
|
29
|
+
case "$1" in
|
|
30
|
+
claude) echo ".claude/skills" ;;
|
|
31
|
+
cursor) echo ".cursor/skills .agents/skills" ;;
|
|
32
|
+
codex) echo "__CODEX__" ;;
|
|
33
|
+
gemini) echo ".gemini/skills .agents/skills" ;;
|
|
34
|
+
goose) echo ".config/goose/skills .agents/skills" ;;
|
|
35
|
+
amp) echo ".config/agents/skills .agents/skills" ;;
|
|
36
|
+
opencode) echo "__OPENCODE__" ;;
|
|
37
|
+
windsurf) echo ".codeium/windsurf/skills" ;;
|
|
38
|
+
workbuddy) echo ".workbuddy/skills" ;;
|
|
39
|
+
kiro) echo ".kiro/skills" ;;
|
|
40
|
+
trae) echo ".traecli/skills" ;;
|
|
41
|
+
trae-cn) echo ".trae-cn/skills" ;;
|
|
42
|
+
qwen) echo ".qwen/skills" ;;
|
|
43
|
+
comate) echo ".comate/skills" ;;
|
|
44
|
+
codebuddy) echo ".codebuddy/skills" ;;
|
|
45
|
+
kimi) echo ".kimi/skills" ;;
|
|
46
|
+
agents) echo ".agents/skills" ;;
|
|
47
|
+
*) return 1 ;;
|
|
48
|
+
esac
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
codex_dir() {
|
|
52
|
+
if [ -n "${CODEX_HOME:-}" ]; then printf '%s' "$CODEX_HOME/skills"; else printf '%s' "$HOME/.codex/skills"; fi
|
|
53
|
+
}
|
|
54
|
+
opencode_dir() {
|
|
55
|
+
if [ -n "${XDG_CONFIG_HOME:-}" ]; then printf '%s' "$XDG_CONFIG_HOME/opencode/skills"; else printf '%s' "$HOME/.config/opencode/skills"; fi
|
|
56
|
+
}
|
|
57
|
+
resolve_user() {
|
|
58
|
+
case "$1" in
|
|
59
|
+
__CODEX__) codex_dir ;;
|
|
60
|
+
__OPENCODE__) opencode_dir ;;
|
|
61
|
+
*) printf '%s' "$HOME/$1" ;;
|
|
62
|
+
esac
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
skill_version() {
|
|
66
|
+
local pkg="$SOURCE_DIR/package.json" v=""
|
|
67
|
+
[ -f "$pkg" ] || return 0
|
|
68
|
+
if command -v awk >/dev/null 2>&1; then
|
|
69
|
+
v="$(awk -F'"' '{for (i=1;i<=NF;i++) if ($i=="version") {print $(i+2); exit}}' "$pkg" 2>/dev/null || true)"
|
|
70
|
+
fi
|
|
71
|
+
if [ -z "$v" ]; then
|
|
72
|
+
v="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pkg" 2>/dev/null | head -n 1 || true)"
|
|
73
|
+
fi
|
|
74
|
+
printf '%s' "$v"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
usage() {
|
|
78
|
+
echo "$SKILL_NAME 安装脚本(YottaSkills)"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "用法:"
|
|
81
|
+
echo " bash install.sh --agent <name> 按智能体默认用户级目录安装(推荐)"
|
|
82
|
+
echo " bash install.sh --dir <path> 装到指定目录(用户改了目录的智能体)"
|
|
83
|
+
echo " bash install.sh -g 安装到全部已知智能体用户级目录"
|
|
84
|
+
echo " bash install.sh 安装到检测到的项目级目录"
|
|
85
|
+
echo ""
|
|
86
|
+
echo "参数:"
|
|
87
|
+
echo " --agent <name> 智能体键名,见 --list"
|
|
88
|
+
echo " --dir <path> 自定义技能目录"
|
|
89
|
+
echo " -g, --global 安装到全部已知用户级目录"
|
|
90
|
+
echo " --list, -l 列出支持的智能体目录"
|
|
91
|
+
echo " --dry-run 只显示将写入的目标,不写文件"
|
|
92
|
+
echo " --version, -v 显示版本"
|
|
93
|
+
echo " --help, -h 显示帮助"
|
|
94
|
+
echo " --yes, -y 兼容参数(-g 不再强制要求)"
|
|
95
|
+
echo ""
|
|
96
|
+
echo "退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolve_path() {
|
|
100
|
+
# 与 SOURCE_DIR 同一风格(Windows Git Bash 用 pwd -W),供路径包含判断使用
|
|
101
|
+
local p="$1"
|
|
102
|
+
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
103
|
+
(cd "$p" 2>/dev/null && pwd -W)
|
|
104
|
+
else
|
|
105
|
+
(cd "$p" 2>/dev/null && pwd -P)
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
assert_safe_target() {
|
|
110
|
+
local dest="$1" src_real dest_real parent
|
|
111
|
+
src_real="$(resolve_path "$SOURCE_DIR")" || return 0
|
|
112
|
+
if [ -d "$dest" ]; then
|
|
113
|
+
dest_real="$(resolve_path "$dest")" || return 0
|
|
114
|
+
else
|
|
115
|
+
parent="$(dirname "$dest")"
|
|
116
|
+
dest_real="$(resolve_path "$parent")" || return 0
|
|
117
|
+
dest_real="$dest_real/$(basename "$dest")"
|
|
118
|
+
fi
|
|
119
|
+
case "$dest_real" in
|
|
120
|
+
"$src_real"|"$src_real"/*)
|
|
121
|
+
echo "安装失败:目标目录不能在技能源目录内(防止自装自毁)" >&2
|
|
122
|
+
return 1
|
|
123
|
+
;;
|
|
124
|
+
esac
|
|
125
|
+
return 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# 清理旧版安装残留(fail-closed 白名单):仅在目标目录已存在且含 SKILL.md 时触发;
|
|
129
|
+
# 只删顶层开发项 + 任意层级缓存;不整目录删除、不跟随符号链接。
|
|
130
|
+
DEV_SKIP="package.json package-lock.json bin lib test .github .git .gitignore .npmignore .gitattributes .yotta .tmp install.sh node_modules"
|
|
131
|
+
clean_residue() {
|
|
132
|
+
local dest="$1" name p
|
|
133
|
+
[ -d "$dest" ] || return 0
|
|
134
|
+
[ -f "$dest/SKILL.md" ] || return 0
|
|
135
|
+
for name in $DEV_SKIP; do
|
|
136
|
+
p="$dest/$name"
|
|
137
|
+
[ -L "$p" ] && continue
|
|
138
|
+
if [ -e "$p" ]; then
|
|
139
|
+
rm -rf -- "$p"
|
|
140
|
+
echo "已清理残留: $name"
|
|
141
|
+
fi
|
|
142
|
+
done
|
|
143
|
+
find "$dest" -type d \( -name '__pycache__' -o -name '.pytest_cache' -o -name '.mypy_cache' \) -prune -exec rm -rf -- {} + 2>/dev/null || true
|
|
144
|
+
find "$dest" -type f \( -name '*.pyc' -o -name '*.pyo' \) -delete 2>/dev/null || true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# 复制技能本体:顶层跳过开发件与运行时不相关目录(嵌套同名保留);
|
|
148
|
+
# 任意层级跳过缓存(__pycache__ / .pytest_cache / .mypy_cache / *.pyc / *.pyo)。
|
|
149
|
+
copy_tree() {
|
|
150
|
+
local src="$1" dst="$2" top="$3" entry base
|
|
151
|
+
mkdir -p "$dst"
|
|
152
|
+
for entry in "$src"/* "$src"/.[!.]* "$src"/..?*; do
|
|
153
|
+
[ -e "$entry" ] || [ -L "$entry" ] || continue
|
|
154
|
+
base="$(basename "$entry")"
|
|
155
|
+
case "$base" in
|
|
156
|
+
__pycache__|.pytest_cache|.mypy_cache) continue ;;
|
|
157
|
+
esac
|
|
158
|
+
if [ "$top" = "1" ]; then
|
|
159
|
+
case " $DEV_SKIP " in
|
|
160
|
+
*" $base "*) continue ;;
|
|
161
|
+
esac
|
|
162
|
+
fi
|
|
163
|
+
if [ -d "$entry" ] && [ ! -L "$entry" ]; then
|
|
164
|
+
copy_tree "$entry" "$dst/$base" 0 || return 1
|
|
165
|
+
else
|
|
166
|
+
case "$base" in
|
|
167
|
+
*.pyc|*.pyo) continue ;;
|
|
168
|
+
esac
|
|
169
|
+
cp -RP "$entry" "$dst/$base" || return 1
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
install_to() {
|
|
175
|
+
local base="$1" dry="$2" dest
|
|
176
|
+
dest="$base/$SKILL_NAME"
|
|
177
|
+
if [ -L "$base" ]; then
|
|
178
|
+
echo "安装失败:目标目录是符号链接,拒绝跟随:$base" >&2
|
|
179
|
+
return 1
|
|
180
|
+
fi
|
|
181
|
+
if [ -e "$base" ] && [ ! -d "$base" ]; then
|
|
182
|
+
echo "安装失败:目标路径已存在且不是目录:$base" >&2
|
|
183
|
+
return 1
|
|
184
|
+
fi
|
|
185
|
+
if [ -L "$dest" ]; then
|
|
186
|
+
echo "安装失败:技能目录是符号链接,拒绝跟随:$dest" >&2
|
|
187
|
+
return 1
|
|
188
|
+
fi
|
|
189
|
+
if [ -e "$dest" ] && [ ! -d "$dest" ]; then
|
|
190
|
+
echo "安装失败:技能路径已存在且不是目录:$dest" >&2
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
assert_safe_target "$dest" || return 1
|
|
194
|
+
if [ "$dry" = "1" ]; then
|
|
195
|
+
echo "[dry-run] 将安装到 -> $dest"
|
|
196
|
+
return 0
|
|
197
|
+
fi
|
|
198
|
+
clean_residue "$dest"
|
|
199
|
+
mkdir -p "$dest" || return 1
|
|
200
|
+
copy_tree "$SOURCE_DIR" "$dest" 1 || return 1
|
|
201
|
+
if [ ! -f "$dest/SKILL.md" ]; then
|
|
202
|
+
echo "安装失败:结果缺少 SKILL.md" >&2
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
205
|
+
echo "installed -> $dest"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
list() {
|
|
209
|
+
echo "智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):"
|
|
210
|
+
local a dirs first
|
|
211
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
212
|
+
dirs="$(dirs_for "$a")"
|
|
213
|
+
first="${dirs%% *}"
|
|
214
|
+
case "$first" in
|
|
215
|
+
__CODEX__) first=".codex/skills" ;;
|
|
216
|
+
__OPENCODE__) first=".config/opencode/skills" ;;
|
|
217
|
+
esac
|
|
218
|
+
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
219
|
+
first="%USERPROFILE%\\${first//\//\\}"
|
|
220
|
+
else
|
|
221
|
+
first="~/$first"
|
|
222
|
+
fi
|
|
223
|
+
printf ' %-10s %s\n' "$a" "$first"
|
|
224
|
+
done
|
|
225
|
+
echo '说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。'
|
|
226
|
+
echo '改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
main() {
|
|
230
|
+
local agent="" dir="" global=0 show_list=0 show_help=0 show_version=0 dry=0
|
|
231
|
+
while [ $# -gt 0 ]; do
|
|
232
|
+
case "$1" in
|
|
233
|
+
--help|-h) show_help=1 ;;
|
|
234
|
+
--version|-v) show_version=1 ;;
|
|
235
|
+
--list|-l) show_list=1 ;;
|
|
236
|
+
-g|--global) global=1 ;;
|
|
237
|
+
--dry-run) dry=1 ;;
|
|
238
|
+
--yes|-y) : ;;
|
|
239
|
+
--agent)
|
|
240
|
+
shift
|
|
241
|
+
if [ $# -eq 0 ] || [ -z "${1:-}" ]; then
|
|
242
|
+
echo "用法错误: --agent 需要一个非空名称" >&2
|
|
243
|
+
exit 2
|
|
244
|
+
fi
|
|
245
|
+
agent="$1"
|
|
246
|
+
;;
|
|
247
|
+
--dir)
|
|
248
|
+
shift
|
|
249
|
+
if [ $# -eq 0 ] || [ -z "${1:-}" ]; then
|
|
250
|
+
echo "用法错误: --dir 需要一个非空路径" >&2
|
|
251
|
+
exit 2
|
|
252
|
+
fi
|
|
253
|
+
dir="$1"
|
|
254
|
+
;;
|
|
255
|
+
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
256
|
+
esac
|
|
257
|
+
shift
|
|
258
|
+
done
|
|
259
|
+
|
|
260
|
+
if [ "$show_help" = "1" ]; then usage; return 0; fi
|
|
261
|
+
if [ "$show_version" = "1" ]; then
|
|
262
|
+
local v
|
|
263
|
+
v="$(skill_version)"
|
|
264
|
+
if [ -z "$v" ]; then
|
|
265
|
+
echo "$SKILL_NAME 版本未知(未找到 package.json)" >&2
|
|
266
|
+
exit 1
|
|
267
|
+
fi
|
|
268
|
+
echo "$SKILL_NAME v$v"
|
|
269
|
+
return 0
|
|
270
|
+
fi
|
|
271
|
+
if [ "$show_list" = "1" ]; then list; return 0; fi
|
|
272
|
+
|
|
273
|
+
local selected=0
|
|
274
|
+
[ -n "$dir" ] && selected=$((selected + 1))
|
|
275
|
+
[ -n "$agent" ] && selected=$((selected + 1))
|
|
276
|
+
[ "$global" = "1" ] && selected=$((selected + 1))
|
|
277
|
+
if [ "$selected" -gt 1 ]; then
|
|
278
|
+
echo "用法错误: --dir / --agent / -g 只能选一个" >&2
|
|
279
|
+
exit 2
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
if [ -n "$dir" ]; then
|
|
283
|
+
install_to "$dir" "$dry" || exit 1
|
|
284
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
285
|
+
return 0
|
|
286
|
+
fi
|
|
287
|
+
if [ -n "$agent" ]; then
|
|
288
|
+
local dirs first
|
|
289
|
+
if ! dirs="$(dirs_for "$agent")"; then
|
|
290
|
+
echo "未收录智能体: $agent。可用 --list 查看,或 --dir <路径> 指定。" >&2
|
|
291
|
+
exit 2
|
|
292
|
+
fi
|
|
293
|
+
first="${dirs%% *}"
|
|
294
|
+
install_to "$(resolve_user "$first")" "$dry" || exit 1
|
|
295
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
296
|
+
return 0
|
|
297
|
+
fi
|
|
298
|
+
if [ "$global" = "1" ]; then
|
|
299
|
+
local a dirs rel seen=" "
|
|
300
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
301
|
+
dirs="$(dirs_for "$a")" || continue
|
|
302
|
+
for rel in $dirs; do
|
|
303
|
+
case "$seen" in
|
|
304
|
+
*" $rel "*) continue ;;
|
|
305
|
+
esac
|
|
306
|
+
seen="$seen$rel "
|
|
307
|
+
install_to "$(resolve_user "$rel")" "$dry" || exit 1
|
|
308
|
+
done
|
|
309
|
+
done
|
|
310
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
311
|
+
return 0
|
|
312
|
+
fi
|
|
313
|
+
local installed=0 d
|
|
314
|
+
for d in .claude/skills .cursor/skills .codex/skills .config/goose/skills .config/agents/skills .opencode/skills .codeium/windsurf/skills .workbuddy/skills .kiro/skills .traecli/skills .gemini/skills .trae-cn/skills .qwen/skills .comate/skills .codebuddy/skills .kimi/skills .agents/skills; do
|
|
315
|
+
if [ -d "$d" ]; then
|
|
316
|
+
install_to "$d" "$dry" || exit 1
|
|
317
|
+
installed=1
|
|
318
|
+
fi
|
|
319
|
+
done
|
|
320
|
+
if [ "$installed" = "0" ]; then
|
|
321
|
+
echo "目标错误: 未检测到项目级智能体目录。可用 --agent <name> 装到用户级,或用 --dir <路径> 指定目录。" >&2
|
|
322
|
+
exit 4
|
|
323
|
+
fi
|
|
324
|
+
[ "$dry" = "1" ] || echo "完成。"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
main "$@"
|
package/package.json
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-compliance",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [],
|
|
10
|
-
"author": "",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "YuanGui (yotta-compliance) — a local, deterministic compliance-clause review skill for AI agents: reviews UTF-8 text / Markdown against versioned JSON rule packs with deterministic matching and assertion evaluation, and produces Markdown / JSON reports in which every conclusion carries the source-text span, matched rule id, framework coverage level and official clause citation. Baseline coverage: 11 PIPL rules and 4 cross-border data-transfer rules; the GDPR / HIPAA / SOC2 / PCI-DSS / ISO27001 / China MLPS frameworks are mapping_only. Triggers: reviewing privacy policies, data-processing clauses or personal-information compliance text; checking PIPL or data-export gaps; checking framework coverage; generating reproducible compliance review reports; gating clause review in CI. Boundaries: deterministic rule-based review suggestions only — not legal advice, not a substitute for a lawyer or compliance consultant; the core path uses no LLM and no network access and does not parse PDF / docx; mapping_only frameworks produce no independent compliance conclusion; it does not judge contract validity or litigation outcomes. Fully local, zero-dependency (Python 3.8+ standard library).",
|
|
11
5
|
"license": "MIT",
|
|
12
|
-
"
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent-skills",
|
|
8
|
+
"yotta-compliance",
|
|
9
|
+
"yottaskills",
|
|
10
|
+
"compliance",
|
|
11
|
+
"pipl"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
"SKILL.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"NOTICE",
|
|
17
|
+
"README.md",
|
|
18
|
+
"README.zh-CN.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"install.sh",
|
|
21
|
+
"references",
|
|
22
|
+
"rules",
|
|
23
|
+
"scripts",
|
|
24
|
+
"assets",
|
|
25
|
+
"bin",
|
|
26
|
+
"!scripts/test_*.py",
|
|
27
|
+
"!scripts/fixtures",
|
|
28
|
+
"!scripts/fixtures/**",
|
|
29
|
+
"!**/__pycache__",
|
|
30
|
+
"!**/*.pyc",
|
|
31
|
+
"!**/*.pyo"
|
|
32
|
+
],
|
|
13
33
|
"repository": {
|
|
34
|
+
"type": "git",
|
|
14
35
|
"url": "git+https://github.com/YottaMeta/yotta-compliance.git"
|
|
15
36
|
},
|
|
16
37
|
"publishConfig": {
|
|
17
38
|
"access": "public"
|
|
18
39
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
40
|
+
"bin": {
|
|
41
|
+
"yotta-compliance": "bin/install.js"
|
|
21
42
|
},
|
|
22
|
-
"
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "python scripts/test_yotta_compliance.py"
|
|
45
|
+
}
|
|
23
46
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# 覆盖范围与规则清单
|
|
2
|
+
|
|
3
|
+
元规把“覆盖级别”作为报告的第一等字段:`baseline_review` 表示可以输出该框架的独立 finding;
|
|
4
|
+
`mapping_only` 表示只把基础规则映射到该框架的主题,不输出该框架的独立合规结论。
|
|
5
|
+
|
|
6
|
+
## 1. 覆盖级别
|
|
7
|
+
|
|
8
|
+
| 级别 | 含义 | 报告行为 |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `baseline_review` | 对基础框架执行确定性规则审查 | 可输出 finding;覆盖摘要状态为 `reviewed` |
|
|
11
|
+
| `mapping_only` | 只做主题映射 | 只显示 `框架:mapping_only` 标签;不输出独立结论 |
|
|
12
|
+
|
|
13
|
+
`mapping_only` 不得被描述为“已审查该框架”,也不得用于对外声称“通过某框架认证”。
|
|
14
|
+
|
|
15
|
+
## 2. 当前基础框架
|
|
16
|
+
|
|
17
|
+
| 规则包 | 框架 | 规则数 | 规则包版本 |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| `pipl` | `PIPL`(个人信息保护法) | 11 | `2026.09.1` |
|
|
20
|
+
| `data-export` | `DATA-EXPORT`(数据出境) | 4 | `2026.09.1` |
|
|
21
|
+
|
|
22
|
+
## 3. PIPL 规则清单
|
|
23
|
+
|
|
24
|
+
来源:中国人大网《中华人民共和国个人信息保护法》官方全文
|
|
25
|
+
(https://www.npc.gov.cn/npc/c2/c30834/202108/t20210820_313088.html)。
|
|
26
|
+
|
|
27
|
+
| rule_id | 主题 | severity | 断言 | 来源条款 |
|
|
28
|
+
|---|---|---|---|---|
|
|
29
|
+
| `PIPL-NOTICE-001` | 未出现处理目的的告知表述 | medium | absent / document | 第十七条第一款第二项 |
|
|
30
|
+
| `PIPL-PURPOSE-002` | 未出现最小必要或影响最小约束 | medium | absent / document | 第六条 |
|
|
31
|
+
| `PIPL-BASIS-003` | 未出现处理个人信息的合法性依据 | medium | absent / document(置信 low) | 第十三条 |
|
|
32
|
+
| `PIPL-RETENTION-004` | 未出现个人信息保存期限说明 | medium | absent / document | 第十七条第一款第二项、第十九条 |
|
|
33
|
+
| `PIPL-RETENTION-005` | 保存期限达到或超过 3 年 | low | numeric_compare `gte 3` | 第十九条 |
|
|
34
|
+
| `PIPL-SENSITIVE-006` | 涉及敏感个人信息但未出现单独同意 | high | absent / document | 第二十八条、第二十九条 |
|
|
35
|
+
| `PIPL-MINOR-007` | 涉及不满十四周岁未成年人信息但未出现监护人同意 | high | absent / document | 第三十一条 |
|
|
36
|
+
| `PIPL-SHARE-008` | 对外提供个人信息但未出现单独同意 | high | absent / document | 第二十三条 |
|
|
37
|
+
| `PIPL-RIGHTS-009` | 未出现个人权利相关表述 | low | absent / document(置信 low) | 第十七条第一款第三项、第四十四条至第四十七条 |
|
|
38
|
+
| `PIPL-INCIDENT-010` | 未出现个人信息泄露补救与通知机制 | low | absent / document(置信 low) | 第五十一条第五项、第五十七条 |
|
|
39
|
+
| `PIPL-PIPIA-011` | 涉及影响评估情形但未出现个人信息保护影响评估 | medium | absent / document | 第五十五条 |
|
|
40
|
+
|
|
41
|
+
`PIPL-RETENTION-005` 只识别 `保存期限:N年` 形式,且只做阈值提示,不判断保存期限是否合法。
|
|
42
|
+
|
|
43
|
+
## 4. 数据出境规则清单
|
|
44
|
+
|
|
45
|
+
来源:《中华人民共和国个人信息保护法》第三十八条至第四十条;数据出境制度口径参照国家网信办
|
|
46
|
+
《促进和规范数据跨境流动规定》(2024-03-22,https://www.cac.gov.cn/2024-03/22/c_1712776611775634.htm)。
|
|
47
|
+
v0.1 未将人数阈值做成规则,相关阈值规则推迟到规则包 v2。
|
|
48
|
+
|
|
49
|
+
| rule_id | 主题 | severity | 断言 | 来源条款 |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| `EXPORT-SAFEGUARD-001` | 数据出境未出现安全评估、标准合同或保护认证 | high | absent / document | 第三十八条第一款 |
|
|
52
|
+
| `EXPORT-CONSENT-002` | 数据出境未出现单独同意 | high | absent / document | 第三十九条 |
|
|
53
|
+
| `EXPORT-NOTICE-003` | 数据出境未出现境外接收方告知 | medium | absent / document | 第三十九条 |
|
|
54
|
+
| `EXPORT-STORAGE-004` | 数据出境未出现境内存储说明 | low | absent / document(置信 low) | 第四十条 |
|
|
55
|
+
|
|
56
|
+
`EXPORT-STORAGE-004` 的适用性与“关键信息基础设施运营者”或“达到规定数量的处理者”身份有关,
|
|
57
|
+
规则只做概览性提示,必须人工核对适用性。
|
|
58
|
+
|
|
59
|
+
## 5. mapping_only 主题映射
|
|
60
|
+
|
|
61
|
+
### 5.1 pipl 包
|
|
62
|
+
|
|
63
|
+
| 框架 | 主题 | 映射规则数 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| GDPR | breach_notification / children / data_subject_rights / dpia / lawful_basis / purpose_limitation / special_categories / storage_limitation | 10 |
|
|
66
|
+
| HIPAA | breach_notification / phi_context | 2 |
|
|
67
|
+
| SOC2 | incident_response / privacy_notice | 2 |
|
|
68
|
+
| PCI-DSS | account_data_context | 1 |
|
|
69
|
+
| ISO27001 | incident_management / retention_governance | 2 |
|
|
70
|
+
| 等保 | data_retention / emergency_response | 2 |
|
|
71
|
+
|
|
72
|
+
### 5.2 data-export 包
|
|
73
|
+
|
|
74
|
+
| 框架 | 主题 | 映射规则数 |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| GDPR | international_transfers | 3 |
|
|
77
|
+
| ISO27001 | data_residency / supplier_agreements | 2 |
|
|
78
|
+
| 等保 | data_residency / security_assessment | 2 |
|
|
79
|
+
|
|
80
|
+
映射只表示“该基础规则与这些框架主题相关”,不表示这些框架已经过审查。
|
|
81
|
+
|
|
82
|
+
## 6. 明确未覆盖
|
|
83
|
+
|
|
84
|
+
以下范围不在 v0.1 的 `baseline_review` 内:
|
|
85
|
+
|
|
86
|
+
1. **语义理解**:词形不同的同义表述可能漏检;absence 只判断检索词是否出现。
|
|
87
|
+
2. **文档级范围判断**:absence 的 `scope` 为 `document`,检索词出现在任何位置即视为已出现,
|
|
88
|
+
不判断其是否适用于当前处理活动。
|
|
89
|
+
3. **否定句建模**:「不会对外提供」「未经同意不得处理」等否定表述仍可能触发候选匹配。
|
|
90
|
+
4. **中文数字解析**:「三年」「十万」等中文数字不解析、不换算。
|
|
91
|
+
5. **数据出境人数阈值**:100 万人以上、10 万人以上、1 万人敏感个人信息等阈值需要单位感知的
|
|
92
|
+
数值原语,v0.1 不做跨单位换算,因而没有对应规则。
|
|
93
|
+
6. **其它框架的独立结论**:GDPR / HIPAA / SOC2 / PCI-DSS / ISO27001 / 等保 仅 `mapping_only`。
|
|
94
|
+
7. **非文本输入**:PDF / docx / 图片不直接解析,需先转成 UTF-8 文本或 Markdown。
|
|
95
|
+
8. **法律判断**:不判断合同效力、不预测诉讼结果、不替代律师或合规顾问。
|
|
96
|
+
|
|
97
|
+
## 7. 如何核对覆盖范围
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 列出实际装载的规则
|
|
101
|
+
python3 scripts/yotta_compliance.py rules list
|
|
102
|
+
|
|
103
|
+
# 只看某个框架
|
|
104
|
+
python3 scripts/yotta_compliance.py rules list --framework pipl
|
|
105
|
+
|
|
106
|
+
# 查看单条规则的来源与测试用例
|
|
107
|
+
python3 scripts/yotta_compliance.py rules show PIPL-NOTICE-001
|
|
108
|
+
|
|
109
|
+
# 校验规则包结构
|
|
110
|
+
python3 scripts/yotta_compliance.py rules validate --pack rules/pipl.json
|
|
111
|
+
python3 scripts/yotta_compliance.py rules validate --pack rules/data-export.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Markdown 报告的「框架覆盖摘要」与 JSON 的 `framework_coverage` 反映实际装载结果;
|
|
115
|
+
两者是判断某框架是否进入本次审查的权威依据。
|
|
116
|
+
|
|
117
|
+
## 8. 更新规则包时的要求
|
|
118
|
+
|
|
119
|
+
- 升 `pack_version`,并同步更新本文件的规则清单与来源条款;
|
|
120
|
+
- 每条规则补齐正例、反例、边界例,并让 `test_ids` 与夹具逐项一致;
|
|
121
|
+
- 来源使用官方文本,写到条款号与 `https://` 链接;
|
|
122
|
+
- 重新运行规则包校验与全量契约测试;
|
|
123
|
+
- 规则包内容或行为发生变化时,随技能版本一起进入发布流程。
|