dsh-standards 1.0.3 → 1.2.0

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.
Files changed (99) hide show
  1. package/40-checker/README.md +15 -0
  2. package/40-checker/bin/dsh-flow-collect.mjs +3 -0
  3. package/40-checker/bin/dsh-levels.mjs +13 -2
  4. package/40-checker/fixtures/bundle-patch-only/cordis.patch.yml +3 -0
  5. package/40-checker/fixtures/bundle-patch-only/package.json +12 -0
  6. package/40-checker/fixtures/c12-command-arg/index.mjs +7 -0
  7. package/40-checker/fixtures/c12-command-arg/package.json +10 -0
  8. package/40-checker/fixtures/c12-comment/index.mjs +3 -0
  9. package/40-checker/fixtures/c12-comment/package.json +10 -0
  10. package/40-checker/fixtures/c12-compare/index.mjs +3 -0
  11. package/40-checker/fixtures/c12-compare/package.json +10 -0
  12. package/40-checker/fixtures/c12-doc-string/index.mjs +13 -0
  13. package/40-checker/fixtures/c12-doc-string/package.json +10 -0
  14. package/40-checker/fixtures/c12-grant/index.mjs +3 -0
  15. package/40-checker/fixtures/c12-grant/package.json +10 -0
  16. package/40-checker/fixtures/c12-string/index.mjs +3 -0
  17. package/40-checker/fixtures/c12-string/package.json +10 -0
  18. package/40-checker/fixtures/declaration-file/index.d.ts +1 -0
  19. package/40-checker/fixtures/declaration-file/index.mjs +9 -0
  20. package/40-checker/fixtures/declaration-file/package.json +10 -0
  21. package/40-checker/fixtures/import-local-side-effect/index.mjs +11 -0
  22. package/40-checker/fixtures/import-local-side-effect/package.json +21 -0
  23. package/40-checker/fixtures/import-local-side-effect/side-effectful.mjs +5 -0
  24. package/40-checker/fixtures/import-open-readonly/index.mjs +14 -0
  25. package/40-checker/fixtures/import-open-readonly/package.json +19 -0
  26. package/40-checker/fixtures/import-package-only/index.mjs +11 -0
  27. package/40-checker/fixtures/import-package-only/package.json +21 -0
  28. package/40-checker/fixtures/import-parse-error/index.mjs +5 -0
  29. package/40-checker/fixtures/import-parse-error/package.json +10 -0
  30. package/40-checker/fixtures/import-side-effect/index.mjs +14 -0
  31. package/40-checker/fixtures/import-side-effect/package.json +19 -0
  32. package/40-checker/fixtures/import-side-effect-nonentry/index.mjs +9 -0
  33. package/40-checker/fixtures/import-side-effect-nonentry/package.json +19 -0
  34. package/40-checker/fixtures/import-side-effect-nonentry/selfcheck.mjs +8 -0
  35. package/40-checker/fixtures/import-void-main/index.mjs +16 -0
  36. package/40-checker/fixtures/import-void-main/package.json +19 -0
  37. package/40-checker/fixtures/no-source/package.json +19 -0
  38. package/40-checker/fixtures/parse-error-nonentry/index.mjs +2 -0
  39. package/40-checker/fixtures/parse-error-nonentry/package.json +10 -0
  40. package/40-checker/fixtures/parse-error-nonentry/references/broken.js +1 -0
  41. package/40-checker/scripts/bundle-smoke.mjs +3 -0
  42. package/40-checker/scripts/entry18-ci.mjs +4 -3
  43. package/40-checker/scripts/entry18-web.mjs +4 -1
  44. package/40-checker/scripts/flow-collect.mjs +494 -0
  45. package/40-checker/scripts/flow-sample-ci.mjs +4 -4
  46. package/40-checker/scripts/host-farm.mjs +2 -1
  47. package/40-checker/scripts/journey-config-observe.mjs +5 -3
  48. package/40-checker/scripts/journey-runtime.mjs +208 -83
  49. package/40-checker/scripts/level-audit.mjs +91 -19
  50. package/40-checker/scripts/m2-runner.mjs +4 -3
  51. package/40-checker/scripts/runtime-child.mjs +4 -0
  52. package/40-checker/src/cli.mjs +11 -3
  53. package/40-checker/src/declarations.mjs +514 -19
  54. package/40-checker/src/flow-evidence.mjs +431 -0
  55. package/40-checker/src/index.mjs +3 -0
  56. package/40-checker/src/manifest.mjs +5 -0
  57. package/40-checker/src/remediation.mjs +132 -0
  58. package/40-checker/src/report.mjs +19 -2
  59. package/40-checker/src/sandbox-env.mjs +78 -0
  60. package/40-checker/test/checks.test.mjs +243 -1
  61. package/40-checker/test/flow-evidence.test.mjs +295 -0
  62. package/40-checker/test/remediation.test.mjs +300 -0
  63. package/40-checker/vendor/babel/README.md +10 -0
  64. package/40-checker/vendor/babel/index.cjs +3 -0
  65. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/LICENSE +22 -0
  66. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/README.md +19 -0
  67. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.d.ts +41 -0
  68. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.js +286 -0
  69. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/package.json +31 -0
  70. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
  71. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/README.md +19 -0
  72. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier-browser.js +62 -0
  73. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier.js +60 -0
  74. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.d.ts +28 -0
  75. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.js +27 -0
  76. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/package.json +45 -0
  77. package/40-checker/vendor/babel/node_modules/@babel/parser/CHANGELOG.md +1073 -0
  78. package/40-checker/vendor/babel/node_modules/@babel/parser/LICENSE +19 -0
  79. package/40-checker/vendor/babel/node_modules/@babel/parser/README.md +19 -0
  80. package/40-checker/vendor/babel/node_modules/@babel/parser/bin/babel-parser.js +14 -0
  81. package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.d.ts +1047 -0
  82. package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.js +14612 -0
  83. package/40-checker/vendor/babel/node_modules/@babel/parser/package.json +57 -0
  84. package/40-checker/vendor/babel/node_modules/@babel/parser/typings/babel-parser.d.ts +636 -0
  85. package/40-checker/vendor/babel/node_modules/@babel/types/LICENSE +22 -0
  86. package/40-checker/vendor/babel/node_modules/@babel/types/README.md +19 -0
  87. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index-legacy.d.ts +2765 -0
  88. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.d.ts +3668 -0
  89. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js +10758 -0
  90. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js.flow +2618 -0
  91. package/40-checker/vendor/babel/node_modules/@babel/types/package.json +38 -0
  92. package/CHANGELOG.md +66 -0
  93. package/bin/dsh-flow-collect.mjs +4 -0
  94. package/package.json +2 -1
  95. package/spec/README.md +2 -0
  96. package/spec/flow/README.md +23 -2
  97. package/spec/flow-evidence.schema.json +217 -0
  98. package/spec/levels.json +2 -0
  99. package/spec/remediation.json +478 -0
@@ -0,0 +1,478 @@
1
+ {
2
+ "C-1": {
3
+ "title": "清单与入口可解析",
4
+ "why": "package.json 是宿主识别插件的唯一入口;name/version/入口缺失会让安装或加载直接失败。",
5
+ "fix": "确保 package.json 可 JSON 解析,声明合法 name 与精确 SemVer version,并让 exports[\".\"] 或 main 指向包内真实存在的文件。",
6
+ "docs": "docs/entry.md#what-it-covers",
7
+ "severityHint": "fail"
8
+ },
9
+ "C-1.1": {
10
+ "title": "包名与版本必须合法",
11
+ "why": "非法 npm 包名或 range/tag 版本无法被 registry/market 解析,也会破坏升级与回滚。",
12
+ "fix": "name 用 @scope/name 或小写 name;version 写精确 SemVer(如 1.2.3),不要写 latest 或 ^1.2.3。",
13
+ "docs": "docs/entry.md#what-it-covers",
14
+ "severityHint": "fail"
15
+ },
16
+ "C-1.2": {
17
+ "title": "入口必须可解析且留在包内",
18
+ "why": "exports/main 指向缺失或越界文件时,安装成功但加载必失败。",
19
+ "fix": "确认 exports[\".\"]/main 指向实际发布进包的文件;不要在 files 里漏掉该目录;路径不得用 .. 逃出包。",
20
+ "docs": "docs/entry.md#author-checklist",
21
+ "severityHint": "fail"
22
+ },
23
+ "C-1.3": {
24
+ "title": "导出形态要符合 Loader 约定",
25
+ "why": "Cordis Loader 只接受函数插件的 apply 或默认导出服务类,混用会让插件无法装载。",
26
+ "fix": "二选一:export function apply(ctx)(可带 name/inject/Config),或 export default class ... extends Service;不要在同一入口再混一个默认导出。",
27
+ "docs": "docs/entry.md#author-checklist",
28
+ "severityHint": "warn"
29
+ },
30
+ "C-1.4": {
31
+ "title": "bundle patch 必须在包内且可解析",
32
+ "why": "patch 路径越界、缺失或行 name 逃逸会让 bundle 安装后无法合成 profile。",
33
+ "fix": "把 patch 文件放进包内并在 dsh.bundle.patch 用相对路径声明;避免 ..、绝对路径、~、$VAR;确认每个 patch 行的 name 仍在包内。",
34
+ "docs": "docs/entry.md#author-checklist",
35
+ "severityHint": "fail"
36
+ },
37
+ "C-1.5": {
38
+ "title": "声明 license(建议 SPDX)",
39
+ "why": "缺 license 是 advisory WARN,但会让市场/企业用户无法判断使用条款。",
40
+ "fix": "在 package.json 加 \"license\": \"Apache-2.0\"(或实际 SPDX 标识),并在 files 里带上 LICENSE。",
41
+ "docs": "docs/entry.md#author-checklist",
42
+ "severityHint": "warn"
43
+ },
44
+ "C-1.6": {
45
+ "title": "private 包不可发布",
46
+ "why": "private: true 的包能被本地安装但不该出现在 registry/市场。",
47
+ "fix": "要发布就移除 private: true;若只是内部包,请在 README/描述里写清来源与安装方式。",
48
+ "docs": "docs/entry.md#author-checklist",
49
+ "severityHint": "warn"
50
+ },
51
+ "C-2": {
52
+ "title": "版本约束必须声明且兼容",
53
+ "why": "宿主对“无声明”按兼容处理,插件会在错误版本上静默装载然后崩溃。",
54
+ "fix": "在 package.json 声明 engines.dsh(首选),至少声明一个 @deepseek-ai/dsh* peer 范围;范围要覆盖目标运行时。",
55
+ "docs": "docs/entry.md#author-checklist",
56
+ "severityHint": "fail"
57
+ },
58
+ "C-2.1": {
59
+ "title": "必须声明 engines.dsh 或 dsh peer 范围",
60
+ "why": "两者皆无时宿主无法阻止不兼容安装;只有 peer 时是较弱的替代声明。",
61
+ "fix": "加 \"engines\": { \"dsh\": \">=0.2.2-0 <0.3.0\" };若只能用 peer,至少写 \"@deepseek-ai/dsh-tools\": \">=0.2.2-0 <0.3.0\",不要留空。",
62
+ "docs": "docs/entry.md#author-checklist",
63
+ "severityHint": "fail"
64
+ },
65
+ "C-2.2": {
66
+ "title": "声明的范围必须满足当前运行时",
67
+ "why": "范围不覆盖 --runtime-version 时,宿主会跳过或装上一个跑不起来的插件。",
68
+ "fix": "把 engines.dsh/peer 范围放宽到包含目标运行时(如 >=0.2.2-0 <0.3.0),并用 --runtime-version 复跑 C-2。",
69
+ "docs": "docs/entry.md#author-checklist",
70
+ "severityHint": "fail"
71
+ },
72
+ "C-2.3": {
73
+ "title": "不得用 workspace: 范围糊弄版本声明",
74
+ "why": "workspace: 是 monorepo 内部协议,registry 消费者无法解析。",
75
+ "fix": "发布前把 workspace:^/workspace:* 改成真实 SemVer 范围;peer 中如确需 workspace 语义,确认宿主等价规则后再发布。",
76
+ "docs": "docs/entry.md#author-checklist",
77
+ "severityHint": "fail"
78
+ },
79
+ "C-2.4": {
80
+ "title": "engines 与 peer 结论冲突要收敛",
81
+ "why": "两套声明给出不同兼容结论时,用户无法判断该信哪个;宿主以 engines 单独裁决。",
82
+ "fix": "保留一套权威范围(优先 engines.dsh),让 peer 范围与它一致;删掉过窄/过宽的重复声明。",
83
+ "docs": "docs/entry.md#author-checklist",
84
+ "severityHint": "warn"
85
+ },
86
+ "C-3": {
87
+ "title": "安装来源可解析、可固定、可追溯",
88
+ "why": "浮动 tag/branch/裸名会在下一次安装时换成另一个 commit,无法复现。",
89
+ "fix": "registry 用精确版本或可校验 tag;git 用 40 位 commit;tarball 用固定 URL;不要让非 registry 依赖随 HEAD 漂移。",
90
+ "docs": "docs/entry.md#run-it",
91
+ "severityHint": "fail"
92
+ },
93
+ "C-3.1": {
94
+ "title": "install spec 必须能被解析",
95
+ "why": "CLI 把 spec 原样交给 pnpm;解析失败意味着用户按文档装不上。",
96
+ "fix": "用 pkg@1.2.3、github:owner/repo#<40hex>、固定 tarball URL 或包内 path;在 README 里给出可复制命令。",
97
+ "docs": "docs/entry.md#run-it",
98
+ "severityHint": "fail"
99
+ },
100
+ "C-3.2": {
101
+ "title": "registry/git 来源必须钉住",
102
+ "why": "裸包名或浮动 branch 会随上游发布变化,破坏可追溯性。",
103
+ "fix": "registry 写精确版本(@scope/pkg@1.2.3)或可校验 tag;git 写 #<40 位 commit>,不要用 #main。",
104
+ "docs": "docs/entry.md#run-it",
105
+ "severityHint": "fail"
106
+ },
107
+ "C-3.3": {
108
+ "title": "非 registry 依赖必须固定",
109
+ "why": "file:/link:/浮动 git 依赖在别人机器上解析不到或指向不同代码。",
110
+ "fix": "改成固定 commit 的 git URL、固定 tarball URL 或包内相对 path;workspace:/file: 发布前替换掉。",
111
+ "docs": "docs/flow.md#the-eight-journeys",
112
+ "severityHint": "warn"
113
+ },
114
+ "C-3.4": {
115
+ "title": "安装期构建脚本要可枚举",
116
+ "why": "preinstall/install/postinstall/prepare 会在用户机器上执行,profile 看不到就无法审批。",
117
+ "fix": "尽量去掉安装期脚本,改为显式构建;必须在 README/发布说明列出脚本与 allowBuilds 需求。",
118
+ "docs": "docs/flow.md#the-eight-journeys",
119
+ "severityHint": "warn"
120
+ },
121
+ "C-3.5": {
122
+ "title": "市场索引条目必须可解析且固定",
123
+ "why": "条目版本/来源/commit 不合法时,市场无法生成可复现的安装串。",
124
+ "fix": "用 format-1 schema:精确 SemVer、source.type=npm|git、git 40hex、engines.dsh 合法;修完用 --market-index 复跑。",
125
+ "docs": "docs/levels.md#run-the-checks",
126
+ "severityHint": "fail"
127
+ },
128
+ "C-4": {
129
+ "title": "插件必须能装载并进入 ACTIVE",
130
+ "why": "装载抛错/停在 PENDING/超时都意味着用户装上了但跑不起来。",
131
+ "fix": "在隔离 Cordis Context 中用真实 --runtime 复跑;检查入口 import、inject 服务是否在目标 profile 存在、apply 是否抛错;把阻塞初始化移到 apply 内并处理失败。",
132
+ "docs": "docs/levels.md#entry",
133
+ "severityHint": "fail"
134
+ },
135
+ "C-5": {
136
+ "title": "声明的 provide 必须真实注册",
137
+ "why": "provide 只是元数据,Cordis 不会自动注册;声明了却不注册会骗过依赖方。",
138
+ "fix": "在 apply 里用 ctx.provide(\"name\", impl)(或 Service 构造器)注册每个声明的服务;不要只写 export const provide。",
139
+ "docs": "docs/boost.md#the-eight-practices",
140
+ "severityHint": "fail"
141
+ },
142
+ "C-5.1": {
143
+ "title": "provide 声明要可枚举",
144
+ "why": "动态/配置生成的 provide 让检查器无法核对,依赖方也无法预期。",
145
+ "fix": "把 export const provide = [...] 写成静态字符串数组;若必须动态,在 README 说明并接受 WARN。",
146
+ "docs": "docs/boost.md#the-eight-practices",
147
+ "severityHint": "warn"
148
+ },
149
+ "C-5.2": {
150
+ "title": "provide 的所有权必须是本 fiber",
151
+ "why": "服务被别的 fiber 注册或根本没注册,卸载/重挂时会留下悬挂依赖。",
152
+ "fix": "用 ctx.provide/Service 在本插件 fiber 注册;不要直接写 ctx.reflect.store 或让子 fiber 代注册。",
153
+ "docs": "docs/boost.md#the-eight-practices",
154
+ "severityHint": "fail"
155
+ },
156
+ "C-6": {
157
+ "title": "注册型副作用必须由 fiber effect 承载",
158
+ "why": "裸定时器/监听器不进入 _disposables,卸载后仍在跑,污染宿主。",
159
+ "fix": "所有 timer/listener/connection/subprocess 都包进 ctx.effect(() => { ...; return dispose });不要直接写 ctx.events._hooks/reflect.store。",
160
+ "docs": "docs/boost.md#the-eight-practices",
161
+ "severityHint": "fail"
162
+ },
163
+ "C-6.1": {
164
+ "title": "不得直写 Cordis 私有注册面",
165
+ "why": "绕过 ctx.on/ctx.effect 的注册不会被卸载,且跨版本必坏。",
166
+ "fix": "改用公开 API:ctx.on、ctx.effect、ctx.provide;删除对 _hooks、reflect.store、registry._internal、_disposables 的写操作。",
167
+ "docs": "docs/boost.md#the-eight-practices",
168
+ "severityHint": "fail"
169
+ },
170
+ "C-6.2": {
171
+ "title": "注册的 effect 要能对上声明",
172
+ "why": "声明了 provide/on 但 effect 树里没有对应 label,说明注册是匿名的或没发生。",
173
+ "fix": "给 ctx.effect(fn, \"plugin.label\") 加稳定 label;确认 fiber.getEffects() 覆盖声明的服务与监听器。",
174
+ "docs": "docs/boost.md#the-eight-practices",
175
+ "severityHint": "warn"
176
+ },
177
+ "C-7": {
178
+ "title": "dispose 后可观测残留必须归零",
179
+ "why": "卸载不干净会让重装/热更新累积 effect、服务、监听器和 fiber。",
180
+ "fix": "用 ctx.effect 注册所有资源;dispose() 后断言 getEffects().length===0、服务 key 回到基线、registry.size 回到基线。",
181
+ "docs": "docs/boost.md#the-eight-practices",
182
+ "severityHint": "fail"
183
+ },
184
+ "C-7.1": {
185
+ "title": "effect 必须清空",
186
+ "why": "dispose 后还有 effect 说明 disposer 没执行或资源没交给 effect。",
187
+ "fix": "把裸 timer/listener 放进 ctx.effect 并返回清理函数;检查 disposer 是否抛错被吞掉。",
188
+ "docs": "docs/boost.md#the-eight-practices",
189
+ "severityHint": "fail"
190
+ },
191
+ "C-7.2": {
192
+ "title": "服务 store 要回到基线",
193
+ "why": "残留的 provide 会让后续插件拿到已卸载的实现。",
194
+ "fix": "用 ctx.provide/Service 的 disposer 注销;不要手工往 reflect.store 写,卸载后对比 key 集。",
195
+ "docs": "docs/boost.md#the-eight-practices",
196
+ "severityHint": "fail"
197
+ },
198
+ "C-7.3": {
199
+ "title": "registry/fiber 不得残留",
200
+ "why": "fiber 留在 registry 里意味着它仍被宿主认为存活,重挂会重复。",
201
+ "fix": "await fiber.dispose() 后检查 registry.size 与 runtime.fibers;不要把 fiber 存到模块级全局。",
202
+ "docs": "docs/boost.md#the-eight-practices",
203
+ "severityHint": "fail"
204
+ },
205
+ "C-7.5": {
206
+ "title": "dispose 必须 await 到 quiescence",
207
+ "why": "只发 dispose 请求不等完成,测试会假绿而资源仍在释放中。",
208
+ "fix": "await fiber.dispose(),必要时再 await ctx.root.fiber.await();不要用 fire-and-forget。",
209
+ "docs": "docs/boost.md#the-eight-practices",
210
+ "severityHint": "fail"
211
+ },
212
+ "C-8": {
213
+ "title": "重挂必须幂等、冷重装可重复",
214
+ "why": "restart/remount 累积状态或 revision 不推进,会让热更新看起来成功实际没重载。",
215
+ "fix": "把模块级缓存清掉或移入 fiber;重复 restart() 后对比 effect/registry/reflect 快照;确认 revision/epoch 推进。",
216
+ "docs": "docs/boost.md#the-eight-practices",
217
+ "severityHint": "fail"
218
+ },
219
+ "C-8.1": {
220
+ "title": "连续 restart 不累积 effect",
221
+ "why": "第二次 restart 后 effect 数量/标签漂移,说明 disposer 没清干净。",
222
+ "fix": "每次 effect 都有稳定 label 与 disposer;restart 前后做归一化快照比较。",
223
+ "docs": "docs/boost.md#the-eight-practices",
224
+ "severityHint": "fail"
225
+ },
226
+ "C-8.2": {
227
+ "title": "restart 必须推进 revision/epoch",
228
+ "why": "被 internal/update 监听者 veto 时,size 可能不变但插件没真正重载。",
229
+ "fix": "在测试里断言 revision/epoch 前进;检查是否有 update 监听器吞掉重启,必要时用私有适配层观测并记录 WARN。",
230
+ "docs": "docs/boost.md#the-eight-practices",
231
+ "severityHint": "warn"
232
+ },
233
+ "C-8.3": {
234
+ "title": "冷重装后快照一致",
235
+ "why": "registry.delete 后重新 plugin 若状态不同,说明有跨实例残留。",
236
+ "fix": "清掉模块级 Map/globalThis 缓存;比较两轮装载后的归一化快照。",
237
+ "docs": "docs/boost.md#the-eight-practices",
238
+ "severityHint": "fail"
239
+ },
240
+ "C-9": {
241
+ "title": "声明与实测必须交叉一致",
242
+ "why": "声明 provide/inject 与实测不符会让依赖解析、重挂和权限判断全部失真。",
243
+ "fix": "用 L1 实测核对:声明的 provide 必须注册,inject 必须真实读取;多报/少报分别修声明或代码。",
244
+ "docs": "docs/boost.md#the-eight-practices",
245
+ "severityHint": "fail"
246
+ },
247
+ "C-9.1": {
248
+ "title": "声明 provide 必须在实测中注册",
249
+ "why": "缺一个注册,依赖方会拿到 undefined 或一直 PENDING。",
250
+ "fix": "在 apply 里注册每个声明的服务;或从 export const provide 删掉未实现的项。",
251
+ "docs": "docs/boost.md#the-eight-practices",
252
+ "severityHint": "fail"
253
+ },
254
+ "C-9.2": {
255
+ "title": "实测服务必须写进声明",
256
+ "why": "多注册的公开服务没有声明,用户/市场看不到它,卸载时也难以核对。",
257
+ "fix": "把多出的公开服务补进 provide,或改成内部/命名空间服务;不要静默暴露。",
258
+ "docs": "docs/boost.md#the-eight-practices",
259
+ "severityHint": "warn"
260
+ },
261
+ "C-9.3": {
262
+ "title": "inject 必须真的被读取",
263
+ "why": "多声明 inject 会让无关服务替换时也触发插件重挂。",
264
+ "fix": "删掉代码里从未 ctx.<service> 读取的 inject;需要可选依赖时用 ctx.get()。",
265
+ "docs": "docs/boost.md#the-eight-practices",
266
+ "severityHint": "warn"
267
+ },
268
+ "C-10": {
269
+ "title": "入口不应有 import 期副作用(advisory)",
270
+ "why": "模块被 import 时执行副作用会拖慢/污染所有 importer,且在 apply 前就运行。",
271
+ "fix": "顶层只留类型/常量/纯函数;把 timer/fs/子进程/全局修改移进 apply() 并用 ctx.effect() 清理。",
272
+ "docs": "docs/entry.md#import-time-side-effects",
273
+ "severityHint": "advisory"
274
+ },
275
+ "C-10.1": {
276
+ "title": "不要用顶层 await",
277
+ "why": "模块级 await 会让每个 importer 的求值等待,且宿主无法注入清理。",
278
+ "fix": "把 await 移进 apply() 或 Service.init;顶层只导出声明。",
279
+ "docs": "docs/entry.md#import-time-side-effects",
280
+ "severityHint": "advisory"
281
+ },
282
+ "C-10.2": {
283
+ "title": "顶层不要挂 timer/process.on/void main",
284
+ "why": "这些调用在 import 时执行,卸载时没有 disposer,容易泄漏和重复。",
285
+ "fix": "把 setInterval/process.on/void main() 移进 apply;用 ctx.effect 注册并在 disposer 清理。",
286
+ "docs": "docs/entry.md#import-time-side-effects",
287
+ "severityHint": "advisory"
288
+ },
289
+ "C-10.3": {
290
+ "title": "import 闭包覆盖说明",
291
+ "why": "包导入/解析不到的本地模块不在静态分析范围内,不能当作已检查。",
292
+ "fix": "尽量让入口只依赖可解析的本地相对模块;对包导入/动态路径在报告里保留 not-run,必要时人工复核。",
293
+ "docs": "docs/entry.md#import-time-side-effects",
294
+ "severityHint": "advisory"
295
+ },
296
+ "C-10.4": {
297
+ "title": "顶层不要做 fs 写/子进程调用",
298
+ "why": "import 时写文件或 spawn 会在宿主加载阶段产生外部副作用。",
299
+ "fix": "移进 apply/Service.init,并用 ctx.effect 包住资源;只读 openSync 不算写。",
300
+ "docs": "docs/entry.md#import-time-side-effects",
301
+ "severityHint": "advisory"
302
+ },
303
+ "C-11": {
304
+ "title": "权限面必须声明",
305
+ "why": "dsh.capabilities 是装载期声明;漏报高风险能力会让用户看不到插件能做什么。",
306
+ "fix": "在 dsh.capabilities 列出所有实际使用的宿主能力(闭集 ID);字段存在后漏报会 FAIL。",
307
+ "docs": "docs/entry.md#declaration-fields",
308
+ "severityHint": "fail"
309
+ },
310
+ "C-11.1": {
311
+ "title": "capabilities 必须是闭集内的字符串数组",
312
+ "why": "未知 ID/非数组无法被市场/UI 解析,等于没声明。",
313
+ "fix": "用 \"capabilities\": [\"fs:read\", \"tools:register\"];只从 15 个闭集 ID 里取,不要空数组占位。",
314
+ "docs": "docs/entry.md#declaration-fields",
315
+ "severityHint": "fail"
316
+ },
317
+ "C-11.2": {
318
+ "title": "高信号触发必须申报能力",
319
+ "why": "代码用了 ctx.tools.register/ctx.webServer 等却不声明,市场无法提示风险。",
320
+ "fix": "按 details 的 file:line 找到触发点,把对应能力加入 dsh.capabilities(如 tools:register、network:ingress);字段缺失时先补字段。",
321
+ "docs": "docs/entry.md#declaration-fields",
322
+ "severityHint": "fail"
323
+ },
324
+ "C-11.3": {
325
+ "title": "不得默认启用 plugin-manager/tools",
326
+ "why": "打开它等于把“模型自安装”能力放给所有会话,越过 profile 的安装门。",
327
+ "fix": "从 bundle patch 删除/禁用 @deepseek-ai/dsh-plugin-manager/tools;确需安装能力时声明 plugin:install 并走显式审批。",
328
+ "docs": "docs/entry.md#declaration-fields",
329
+ "severityHint": "fail"
330
+ },
331
+ "C-11.4": {
332
+ "title": "danger-full-access 字面量要收敛",
333
+ "why": "真实授权/命令上下文里的 danger-full-access 会绕过沙箱;即使是文档字符串也会误导。",
334
+ "fix": "用最小权限 sandboxPolicy 替代;把 danger-full-access 从 .set()/.resolve()/spawn args 里删掉;只保留在文档字符串里的说明也要标注。",
335
+ "docs": "docs/entry.md#declaration-fields",
336
+ "severityHint": "warn"
337
+ },
338
+ "C-11.5": {
339
+ "title": "声明了能力但实测没触发",
340
+ "why": "过度声明会误导用户,也会让权限面看起来比实际大。",
341
+ "fix": "删掉未使用的 capability,或补上实际调用;动态触发无法静态确认时在 README 说明。",
342
+ "docs": "docs/entry.md#declaration-fields",
343
+ "severityHint": "warn"
344
+ },
345
+ "C-12": {
346
+ "title": "凭据与全局 seam 声明一致",
347
+ "why": "绕过 ctx.credentials 直读 backing file,或未声明 provider 就替换全局 seam,会破坏用户凭据边界。",
348
+ "fix": "通过 ctx.credentials seam 读写;声明 credentials:read/list/provider;不要直接读 $DSH_HOME/.credentials.yaml 或 credentials-local 内部 API。",
349
+ "docs": "docs/entry.md#credentials-and-seams",
350
+ "severityHint": "fail"
351
+ },
352
+ "C-12.1": {
353
+ "title": "不得绕过 credentials seam 直读 backing file",
354
+ "why": "直读 .credentials.yaml/credentials-local 绕过权限与审计,且格式可能变。",
355
+ "fix": "改用 ctx.credentials.resolve(...);确需 provider 时声明 credentials:provider 并走公开接口。",
356
+ "docs": "docs/entry.md#credentials-and-seams",
357
+ "severityHint": "fail"
358
+ },
359
+ "C-12.2": {
360
+ "title": "listRecords 必须声明 credentials:list",
361
+ "why": "能枚举全部凭据记录是高风险能力,不声明用户无法知情。",
362
+ "fix": "在 dsh.capabilities 加 credentials:list;若只是内部使用,改为按名 resolve,不要枚举。",
363
+ "docs": "docs/entry.md#credentials-and-seams",
364
+ "severityHint": "fail"
365
+ },
366
+ "C-12.3": {
367
+ "title": "全局 seam provider 注册要显式声明",
368
+ "why": "替换 shell/credentials/sandboxPolicy 等全局 provider 会改变所有后续调用。",
369
+ "fix": "在 README 与 dsh.capabilities 说明注册的 seam 与影响范围;能不用全局替换就不要用。",
370
+ "docs": "docs/entry.md#credentials-and-seams",
371
+ "severityHint": "warn"
372
+ },
373
+ "C-12.4": {
374
+ "title": "danger-full-access/--add-dir 字面量要收敛",
375
+ "why": "命令/授权参数里的这些值会扩大文件与执行范围。",
376
+ "fix": "用最小 sandboxPolicy;从 spawn args/.set()/.resolve() 移除 --add-dir 与 danger-full-access;文档字符串保留说明。",
377
+ "docs": "docs/entry.md#credentials-and-seams",
378
+ "severityHint": "warn"
379
+ },
380
+ "C-12.6": {
381
+ "title": "dsh.commitments(可选披露)格式",
382
+ "why": "commitments 是作者自述的安全承诺;类型/缺项错误会让披露不可信。",
383
+ "fix": "按 9 个 ID 写布尔值:\"commitments\": { \"no-session-upload\": true, ... };不承诺的项写 false,不要省略。",
384
+ "docs": "docs/entry.md#credentials-and-seams",
385
+ "severityHint": "warn"
386
+ },
387
+ "C-12.7": {
388
+ "title": "dsh.disclosure(可选披露)格式",
389
+ "why": "endpoints/retention/systemPrompt 写错会让第三方数据流向不可读。",
390
+ "fix": "\"disclosure\": { \"endpoints\": [\"https://api.example.com\"], \"retention\": \"none\", \"systemPrompt\": \"unchanged\" };endpoints 必须是 https URL 数组。",
391
+ "docs": "docs/entry.md#credentials-and-seams",
392
+ "severityHint": "warn"
393
+ },
394
+ "C-13": {
395
+ "title": "高风险类别必须标注",
396
+ "why": "标签是市场/用户识别 prompt 修改、第三方模型、远程访问、镜像的唯一声明面。",
397
+ "fix": "按能力触发补 dsh.labels;镜像同时写 dsh.upstream;不要用 official/verified/certified/secure/audited。",
398
+ "docs": "docs/entry.md#declaration-fields",
399
+ "severityHint": "fail"
400
+ },
401
+ "C-13.1": {
402
+ "title": "labels 必须是闭集内的字符串数组",
403
+ "why": "未知标签会被市场丢弃或误读,等于没标。",
404
+ "fix": "只用 prompt-modification、red-team、third-party-model-provider、remote-access、mirror;不要空数组占位。",
405
+ "docs": "docs/entry.md#declaration-fields",
406
+ "severityHint": "fail"
407
+ },
408
+ "C-13.2": {
409
+ "title": "能力触发必须补对应标签",
410
+ "why": "声明了 system-prompt:write 等能力却不打标签,用户看不到高风险类别。",
411
+ "fix": "按映射补标签:system-prompt:write→prompt-modification;llm:adapter+network:egress→third-party-model-provider;network:ingress→remote-access;dsh.upstream→mirror。",
412
+ "docs": "docs/entry.md#declaration-fields",
413
+ "severityHint": "fail"
414
+ },
415
+ "C-13.3": {
416
+ "title": "dsh.upstream 必须是合法 https URL 且不自指",
417
+ "why": "镜像/分叉不写上游或指向自己,用户无法追溯来源。",
418
+ "fix": "写 \"dsh\": { \"upstream\": \"https://github.com/owner/repo\" },不要填自己的 repository;同时加 mirror 标签。",
419
+ "docs": "docs/entry.md#declaration-fields",
420
+ "severityHint": "fail"
421
+ },
422
+ "C-13.4": {
423
+ "title": "不得使用官方/认证类标签",
424
+ "why": "official/verified/certified/secure/audited 会被当成背书,而本标准不做认证。",
425
+ "fix": "从 labels/README/描述里删掉这些词;改用中性描述“兼容/已自测”。",
426
+ "docs": "docs/levels.md#vocabulary-that-must-not-be-inflated",
427
+ "severityHint": "fail"
428
+ },
429
+ "C-13.5": {
430
+ "title": "red-team 标签要配 usageScope",
431
+ "why": "红队类插件风险高,只有标签没有使用范围说明,用户无法判断边界。",
432
+ "fix": "加 \"dsh\": { \"usageScope\": \"...\" } 说明授权范围与禁止用途;没有红队用途就删掉该标签。",
433
+ "docs": "docs/entry.md#declaration-fields",
434
+ "severityHint": "warn"
435
+ },
436
+ "C-14": {
437
+ "title": "运行时声明必须有证据",
438
+ "why": "ENTRY 的 runtime claim 需要真实插件启动或 bundle G0-G3;not-run 不能写成通过。",
439
+ "fix": "用 --runtime 提供真实 Cordis/宿主,或 bundle 用 --host 跑 G0-G3;修完 apply/启动问题再复跑,不要用 static claim 冒充 runtime。",
440
+ "docs": "docs/entry.md#the-two-claims",
441
+ "severityHint": "fail"
442
+ },
443
+ "G0": {
444
+ "title": "bundle patch 必须可解析",
445
+ "why": "G0 过不了,后续 G1-G3 都无法开始,bundle 安装即失败。",
446
+ "fix": "修 cordis.patch.yml 的 YAML/字段;确认 dsh.bundle.patch 指向的文件能被宿主解析,再跑 bundle smoke。",
447
+ "docs": "docs/entry.md#the-two-claims",
448
+ "severityHint": "fail"
449
+ },
450
+ "G1": {
451
+ "title": "patch 目标行必须能解析",
452
+ "why": "G1 失败说明 patch 行的 name/target 对不上,宿主无法合成 profile。",
453
+ "fix": "按宿主 patch 语法修 name/target;确认目标包在 host farm 中存在;用 --base/--web/--bare 提供真实宿主再复跑。",
454
+ "docs": "docs/entry.md#the-two-claims",
455
+ "severityHint": "fail"
456
+ },
457
+ "G2": {
458
+ "title": "宿主兼容性门必须接受",
459
+ "why": "G2 reject 表示宿主会直接跳过该 bundle,用户装了也不会生效。",
460
+ "fix": "修 engines.dsh/peer 范围到宿主接受;核对 host 的 plugin-compatibility 结果,不要靠豁免当通过。",
461
+ "docs": "docs/entry.md#the-two-claims",
462
+ "severityHint": "fail"
463
+ },
464
+ "G3": {
465
+ "title": "bundle 行必须全部 ACTIVE",
466
+ "why": "G3 partial 表示目标 profile 没提供某些服务,bundle 只挂上了一部分。",
467
+ "fix": "用 --host/--base/--web/--bare 提供完整目标 profile;检查 patch 行的 inject/provide 与宿主服务是否匹配;partial 不能写成通过。",
468
+ "docs": "docs/entry.md#the-two-claims",
469
+ "severityHint": "fail"
470
+ },
471
+ "G4": {
472
+ "title": "client 形态必须能被宿主识别",
473
+ "why": "G4 失败说明 dsh.client 没有 package-root 行或 client 导出缺失,客户端加载不到。",
474
+ "fix": "补 dsh.client 的 package-root 行与 client 导出;确认构建产物在 files 里;用 --entry-runtime server-entry/client probe 复跑。",
475
+ "docs": "docs/entry.md#the-two-claims",
476
+ "severityHint": "fail"
477
+ }
478
+ }