dsh-standards 0.0.3 → 1.0.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.
Files changed (139) hide show
  1. package/40-checker/README.md +251 -0
  2. package/40-checker/bin/dsh-levels.mjs +15 -0
  3. package/40-checker/bin/dsh-plugin-verify.mjs +8 -0
  4. package/40-checker/fixtures/bad/index.mjs +12 -0
  5. package/40-checker/fixtures/bad/package.json +15 -0
  6. package/40-checker/fixtures/bad-manifest/index.js +1 -0
  7. package/40-checker/fixtures/bad-manifest/package.json +5 -0
  8. package/40-checker/fixtures/bundle/cordis.patch.yml +3 -0
  9. package/40-checker/fixtures/bundle/package.json +15 -0
  10. package/40-checker/fixtures/bundle/server.mjs +7 -0
  11. package/40-checker/fixtures/bundle-v0/cordis.patch.yml +3 -0
  12. package/40-checker/fixtures/bundle-v0/package.json +21 -0
  13. package/40-checker/fixtures/bundle-v0/server.mjs +7 -0
  14. package/40-checker/fixtures/client-missing/client.js +1 -0
  15. package/40-checker/fixtures/client-missing/index.js +1 -0
  16. package/40-checker/fixtures/client-missing/package.json +6 -0
  17. package/40-checker/fixtures/flow-config/index.js +9 -0
  18. package/40-checker/fixtures/flow-config/package.json +10 -0
  19. package/40-checker/fixtures/fs-write/index.mjs +7 -0
  20. package/40-checker/fixtures/fs-write/package.json +10 -0
  21. package/40-checker/fixtures/good/index.mjs +10 -0
  22. package/40-checker/fixtures/good/package.json +19 -0
  23. package/40-checker/fixtures/hang/index.mjs +5 -0
  24. package/40-checker/fixtures/hang/package.json +10 -0
  25. package/40-checker/fixtures/known-answer/expected.json +18 -0
  26. package/40-checker/fixtures/market-index-real.json +18 -0
  27. package/40-checker/fixtures/market-index.json +18 -0
  28. package/40-checker/fixtures/negative/bad-entry/package.json +1 -0
  29. package/40-checker/fixtures/negative/no-entry/package.json +1 -0
  30. package/40-checker/fixtures/negative/no-patch/package.json +1 -0
  31. package/40-checker/fixtures/negative/root-missing/cordis.patch.yml +2 -0
  32. package/40-checker/fixtures/negative/root-missing/package.json +1 -0
  33. package/40-checker/fixtures/negative/workspace/package.json +1 -0
  34. package/40-checker/package.json +22 -0
  35. package/40-checker/samples/README.md +8 -0
  36. package/40-checker/samples/entry18.json +139 -0
  37. package/40-checker/samples/frame.json +485 -0
  38. package/40-checker/scripts/apply-overlay.mjs +59 -0
  39. package/40-checker/scripts/attestation-check.mjs +151 -0
  40. package/40-checker/scripts/bundle-smoke.mjs +126 -0
  41. package/40-checker/scripts/claim-check.mjs +145 -0
  42. package/40-checker/scripts/clause-map.mjs +94 -0
  43. package/40-checker/scripts/collect-fixtures.mjs +44 -0
  44. package/40-checker/scripts/dashboard.mjs +85 -0
  45. package/40-checker/scripts/dco-check.mjs +147 -0
  46. package/40-checker/scripts/dependabot-check.mjs +40 -0
  47. package/40-checker/scripts/doc-commands-check.mjs +92 -0
  48. package/40-checker/scripts/entry-scan.mjs +190 -0
  49. package/40-checker/scripts/entry-validate.mjs +291 -0
  50. package/40-checker/scripts/entry18-attribution.mjs +66 -0
  51. package/40-checker/scripts/entry18-ci.mjs +685 -0
  52. package/40-checker/scripts/entry18-realweb-summary.mjs +71 -0
  53. package/40-checker/scripts/entry18-web.mjs +307 -0
  54. package/40-checker/scripts/evidence-chain.mjs +100 -0
  55. package/40-checker/scripts/flow-index.mjs +82 -0
  56. package/40-checker/scripts/flow-sample-ci.mjs +529 -0
  57. package/40-checker/scripts/governance-check.mjs +210 -0
  58. package/40-checker/scripts/host-farm.mjs +165 -0
  59. package/40-checker/scripts/journey-config-observe.mjs +109 -0
  60. package/40-checker/scripts/journey-disclosure-shape.mjs +83 -0
  61. package/40-checker/scripts/journey-runtime.mjs +178 -0
  62. package/40-checker/scripts/journey.mjs +76 -0
  63. package/40-checker/scripts/label-drift.mjs +74 -0
  64. package/40-checker/scripts/label-sync.mjs +87 -0
  65. package/40-checker/scripts/label-verify.mjs +90 -0
  66. package/40-checker/scripts/landlock-exec.c +101 -0
  67. package/40-checker/scripts/level-audit.mjs +336 -0
  68. package/40-checker/scripts/m1-sidecar.mjs +126 -0
  69. package/40-checker/scripts/m2-runner.mjs +213 -0
  70. package/40-checker/scripts/round-robin.mjs +88 -0
  71. package/40-checker/scripts/sample-frame.mjs +72 -0
  72. package/40-checker/scripts/sample20-ci.mjs +25 -0
  73. package/40-checker/scripts/sample60-ci.mjs +25 -0
  74. package/40-checker/scripts/stale-check.mjs +59 -0
  75. package/40-checker/scripts/surveillance.mjs +88 -0
  76. package/40-checker/scripts/web-probe.mjs +504 -0
  77. package/40-checker/src/cli.mjs +300 -0
  78. package/40-checker/src/client-shape.mjs +37 -0
  79. package/40-checker/src/declarations.mjs +242 -0
  80. package/40-checker/src/index.mjs +8 -0
  81. package/40-checker/src/install-spec.mjs +67 -0
  82. package/40-checker/src/label.mjs +130 -0
  83. package/40-checker/src/manifest.mjs +381 -0
  84. package/40-checker/src/permissions.mjs +60 -0
  85. package/40-checker/src/report.mjs +204 -0
  86. package/40-checker/src/result.mjs +60 -0
  87. package/40-checker/src/runtime.mjs +299 -0
  88. package/40-checker/src/semver.mjs +194 -0
  89. package/40-checker/src/util.mjs +148 -0
  90. package/40-checker/test/attestation.test.mjs +59 -0
  91. package/40-checker/test/checks.test.mjs +172 -0
  92. package/40-checker/test/claim.test.mjs +28 -0
  93. package/40-checker/test/client-shape.test.mjs +35 -0
  94. package/40-checker/test/dco.test.mjs +51 -0
  95. package/40-checker/test/entry-validate.test.mjs +172 -0
  96. package/40-checker/test/governance.test.mjs +76 -0
  97. package/40-checker/test/label.test.mjs +97 -0
  98. package/40-checker/test/level-audit.test.mjs +99 -0
  99. package/40-checker/test/levels.test.mjs +31 -0
  100. package/40-checker/test/ops.test.mjs +117 -0
  101. package/40-checker/test/permissions.test.mjs +40 -0
  102. package/40-checker/test/semver.test.mjs +32 -0
  103. package/40-checker/test/verify.test.mjs +57 -0
  104. package/CHANGELOG.md +24 -0
  105. package/LICENSE +28 -0
  106. package/LICENSE-APACHE +190 -0
  107. package/LICENSE-CC-BY +23 -0
  108. package/NOTICE +14 -0
  109. package/NOTICE.md +11 -0
  110. package/README.md +146 -3
  111. package/bin/dsh-levels.mjs +4 -0
  112. package/bin/dsh-standards.mjs +19 -0
  113. package/docs/BOOST-FIXTURES-2026-11-02.md +38 -0
  114. package/docs/DEVELOPER-JOURNEY.md +40 -0
  115. package/docs/HOST-J3-BUG-REPORT-DRAFT.md +27 -0
  116. package/docs/HOST-J3-CONFIG-REPRO-2026-11-02.md +36 -0
  117. package/docs/NAMING-FALLBACK.md +11 -0
  118. package/docs/fork-policy.md +12 -0
  119. package/docs/full-test-plan-2026-11-02.md +207 -0
  120. package/docs/incident.md +37 -0
  121. package/docs/onboarding.md +27 -0
  122. package/docs/privacy.md +12 -0
  123. package/docs/release.md +16 -0
  124. package/docs/scope-security.md +62 -0
  125. package/docs/status-taxonomy.md +54 -0
  126. package/docs/sunset.md +44 -0
  127. package/docs/translating.md +12 -0
  128. package/package.json +58 -12
  129. package/spec/README.md +27 -0
  130. package/spec/boost/README.md +8 -0
  131. package/spec/clauses.json +253 -0
  132. package/spec/entry/README.md +9 -0
  133. package/spec/flow/README.md +8 -0
  134. package/spec/levels.json +371 -0
  135. package/spec/levels.schema.json +45 -0
  136. package/spec/reference-suite.json +35 -0
  137. package/spec/schemas/attestation.schema.json +284 -0
  138. package/spec/schemas/evidence.schema.json +331 -0
  139. package/spec/schemas/report.schema.json +64 -0
@@ -0,0 +1,251 @@
1
+ # 40 · DS-C 一致性检查器(MVP)
2
+
3
+ > 版本:0.1.0 | 日期:2026-10-31 | 标准:DS-C v1.0.0 | 许可:Apache-2.0
4
+ > 零依赖,Node ≥ 20。只读目标包;不修改被检对象。
5
+ >
6
+ > **发行方式**:本仓直接运行时用 `node 40-checker/bin/...`;npm 上通过 `dsh-standards` 包分发(提供 `dsh-standards` 与 `dsh-levels` 两个命令)。不要用 `npx dsh-plugin-verify`、`npx dsh-levels`——npm 上这两个名字属于其他项目。
7
+
8
+ ## 1. 它做什么
9
+
10
+ 对**一个插件包目录**跑 DS-C 的检查项,输出 `text` / `json` / `SARIF 2.1.0`,exit `0/1/2/3`。
11
+
12
+ - 覆盖 `DSC-01`~`DSC-13`(对应 31 §2 的 C-1~C-13)。
13
+ - `--scope mvp` 只跑 E 路 MVP 13 项落到的 8 个检查 ID:`DSC-01/02/04/05/06/07/08/09`。
14
+ - `--scope ds-c`(默认)按 `package` / `listing` profile 判定;缺项输出 `incomplete`,**不会**"跳过即通过"。
15
+
16
+ ```bash
17
+ node bin/dsh-plugin-verify.mjs /path/to/plugin
18
+ node bin/dsh-plugin-verify.mjs /path/to/plugin --format json --out report.json
19
+ node bin/dsh-plugin-verify.mjs /path/to/plugin --format sarif --out report.sarif
20
+ ```
21
+
22
+ ## 2. 检查项
23
+
24
+ | ID | 条款 | 层 | 说明 |
25
+ |---|---|---|---|
26
+ | `DSC-01` | C-1 | L0 | manifest / name / version / license / 入口 / `dsh.bundle.patch` / 导出形态 |
27
+ | `DSC-02` | C-2 | L0 | `engines.dsh` 或 `@deepseek-ai/dsh*` peer,被 runtime 满足 |
28
+ | `DSC-03` | C-3 | L0 | install spec / 市场索引条目 / 供应链固定性 |
29
+ | `DSC-04` | C-4 | L1 | 装载到 ACTIVE |
30
+ | `DSC-05` | C-5 | L1 | 声明的 `provide` 真实注册 |
31
+ | `DSC-06` | C-6 | L1+L2 | effect 拓扑 / 私有注册面 |
32
+ | `DSC-07` | C-7 | L2 | 卸载后 effect/store/registry 归零 |
33
+ | `DSC-08` | C-8 | L3 | restart 幂等 + 冷重装 |
34
+ | `DSC-09` | C-9 | L1 | 声明 vs 实测交叉 |
35
+ | `DSC-10` | C-10 | L0 | import 期副作用(**advisory,不 FAIL**) |
36
+ | `DSC-11` | C-11 | L0 | `dsh.capabilities` + 高信号触发 |
37
+ | `DSC-12` | C-12 | L0 | 凭据 backing file / `listRecords` / 全局 seam;并读取 `dsh.commitments`/`dsh.disclosure` 的形状 |
38
+ | `DSC-13` | C-13 | L0 | `dsh.labels` / `upstream` / 禁用语标签 |
39
+
40
+ **MVP 覆盖缺口**:E 路的 13 个检查项只落到 8 个 ID;`DSC-03/10/11/12/13` 不在 MVP。`--scope mvp` 的结论是 `subset: "MVP-13"`、`level: null`,**不得**当 DS-C 徽章。
41
+
42
+ ## 3. 两种 profile(对齐 31 §1.3)
43
+
44
+ | profile | 强制条款 | C-3 |
45
+ |---|---|---|
46
+ | `package`(默认) | 11 条(不含 C-3) | NA |
47
+ | `listing` | 12 条(含 C-3) | 需要 `--market-index` + `--plugin-name` |
48
+
49
+ ```bash
50
+ # package:作者本地
51
+ node bin/dsh-plugin-verify.mjs ./my-plugin
52
+
53
+ # listing:市场条目校验(收录前提醒,不是门禁)
54
+ node bin/dsh-plugin-verify.mjs ./my-plugin \
55
+ --profile listing \
56
+ --market-index ./market.json \
57
+ --plugin-name @acme/dsh-greet
58
+ ```
59
+
60
+ `NA` 是显式不适用(例如 bundle 的 C-4~C-9);`not-run` 是该跑没跑。二者都**不是** PASS。
61
+
62
+ ### M1 的 `--static-only`
63
+
64
+ ```bash
65
+ node bin/dsh-plugin-verify.mjs ./my-plugin \
66
+ --static-only \
67
+ --market-index ./market.json --plugin-name @acme/dsh-greet
68
+ ```
69
+
70
+ - 只判 `DSC-01/02/03/11/12/13`;C-4~C-9 显式映射为 `NA/out-of-scope`。
71
+ - 结论是 `DS-C static` / `DS-C static (warnings)`;**不是**完整 `DS-C`。
72
+ - 这是 T3 的 M1 通道;M2 通过后才可标 `DS-C verified`。
73
+
74
+ ### bundle 的 `--treat-as-plugin`
75
+
76
+ ```bash
77
+ node bin/dsh-plugin-verify.mjs ./my-bundle \
78
+ --treat-as-plugin \
79
+ --runtime /path/to/cordis/lib/index.js
80
+ ```
81
+
82
+ - 对 `dsh.bundle.patch` 包,按 `lib/index.js` / `index.js` / `lib/public-entry.js` / `src/index.ts` 选 server entry,跑 L1–L3。
83
+ - 入口缺失相对文件 → FAIL;缺一个**裸包**(如 `ajv`、`@deepseek-ai/schemastery`)→ `ERROR/not-run`,因为检查器分不清是宿主农场缺依赖还是插件漏依赖。
84
+ - 真实 bundle 的 L1 需要隔离 host farm;没有它,报告只能是 `incomplete`,不是 PASS。
85
+
86
+ ### 入门 `--scope entry`(DSH-ENTRY)
87
+
88
+ ```bash
89
+ # 只做结构识别:recognized(不声明运行)
90
+ node bin/dsh-plugin-verify.mjs ./my-plugin --scope entry --entry-claim static
91
+
92
+ # plugin:必须 L1 真跑,才算 runtime
93
+ node bin/dsh-plugin-verify.mjs ./my-plugin --scope entry \
94
+ --runtime /path/to/cordis/lib/index.js --runtime-version 0.2.2-alpha.2
95
+
96
+ # bundle:默认 require,有 --host 时跑 G0–G3(无 host 则 incomplete)
97
+ node bin/dsh-plugin-verify.mjs ./my-bundle --scope entry --host /path/to/app-boot/lib/index.js
98
+
99
+ # 兼容旧数据:只跑 server entry,明确标 proxy,不冒充 bundle
100
+ node bin/dsh-plugin-verify.mjs ./my-bundle --scope entry \
101
+ --entry-runtime server-entry --treat-as-plugin \
102
+ --runtime /path/to/cordis/lib/index.js --runtime-version 0.2.2-alpha.2
103
+ ```
104
+
105
+ - `interop-static` 只表示可识别/可安装,不是通过;只有 `interop-runtime` 才是通过。
106
+ - `DSC-14` 是 run evidence;plugin 用 L1,bundle 用 G0–G3(`--host`),G4 client 只做静态。
107
+ - `--entry-runtime server-entry` 只给 `DSH-ENTRY runtime (server-entry)`,**不是 bundle pass**。
108
+ - C-2.2/C-2.3 在 entry 是 blocking;C-2.1 只 WARN。
109
+
110
+ ### 三层自检 `level-audit.mjs`
111
+
112
+ ```bash
113
+ # 只做 ENTRY 静态;FLOW/BOOST 明确 not-run/not-measured
114
+ node scripts/level-audit.mjs ./my-plugin
115
+
116
+ # ENTRY 真跑 + 可选的 FLOW/BOOST 证据
117
+ node scripts/level-audit.mjs ./my-bundle \
118
+ --host /path/to/app-boot/lib/index.js \
119
+ --base /path/to/dsh-base/cordis.patch.yml --bare /tmp/farm \
120
+ --flow-evidence flow.json --boost-evidence boost.json --format json
121
+ ```
122
+
123
+ - 机器源:`spec/levels.json`;标签格式:`publish/level-labels.example.json`。
124
+ - ENTRY 自动;FLOW 只读 `--flow-evidence`(J1–J8),BOOST 只读 `--boost-evidence`(LEV-1–8)。
125
+ - 没有证据就写 `not-run` / `not-measured`,工具不会替作者升级。
126
+
127
+ ## 4. 运行 L1–L3
128
+
129
+ 需要一个**已构建**的 `@deepseek-ai/cordis` 入口。检查器不修改宿主,也不从源码构建宿主:
130
+
131
+ ```bash
132
+ node bin/dsh-plugin-verify.mjs ./my-plugin \
133
+ --runtime /path/to/deepseek-harness/vendor/cordis/lib/index.js \
134
+ --runtime-version 0.2.2-alpha.2 \
135
+ --cordis-version 4.0.6-alpha.1
136
+ ```
137
+
138
+ - 只对 `kind: plugin` 跑;`bundle`/`client` 默认输出 NA。`--treat-as-plugin` 可对 bundle 的 server entry 跑(见下)。
139
+ - 缺失宿主包 / 夹具桩不完整 → `ERROR` + `not-run`,**不 FAIL**。
140
+ - 插件可能留下 timer/子进程;CLI 写完报告后强制退出(250 ms),不靠事件循环自然结束。
141
+ - 运行时用隔离的临时 `DSH_HOME`/子进程是 T2 的下一阶段;当前 MVP 在检查器进程内跑,**只用于可执行样本**。
142
+
143
+ ## 5. 三值 / exit / 覆盖度
144
+
145
+ | 结果 | 含义 |
146
+ |---|---|
147
+ | `PASS` | 有证据满足 |
148
+ | `WARN` | 部分满足 / 替代声明 / 证据不足;不阻断,但不等于 PASS |
149
+ | `FAIL` | 有明确反例;exit 1 |
150
+ | `NA` | 形态/上下文不适用;不是 PASS |
151
+ | `ERROR` | 检查器失效或该跑没跑;exit 2 |
152
+
153
+ | exit | 含义 |
154
+ |---|---|
155
+ | 0 | 完成,无 FAIL(可能 WARN;`level` 带 `warnings`) |
156
+ | 1 | 完成,有 FAIL |
157
+ | 2 | incomplete:强制项 not-run/ERROR,或检查器内部失败 |
158
+ | 3 | 用法错误 |
159
+
160
+ **结论门**:`--scope ds-c` 只有在 profile 对应的强制项全部 `coverage: full`(允许显式 NA)时才给 `level`;否则 `status: incomplete`、exit 2。WARN 仍给 `level`,但写 `DS-C (profile, warnings)`,不冒充干净通过。
161
+
162
+ ## 6. 已知限制
163
+
164
+ 1. **C-3 只在 listing 模式可判**;package 模式是 NA,仅附供应链 advisory 证据。
165
+ 2. **L1–L3 已覆盖 13/14 个真实包**:4 个 npm bundle + 9/10 GitHub 源码样本 C-4/C-7 PASS;仅 `dsh-vision-router` 构建后仍缺 `lib/session-affinity.js` FAIL。host farm 用 `scripts/host-farm.mjs` 搭建,仍是临时目录。
166
+ 3. **C-8.2 的 revision/epoch 在 Cordis 4.0.5 构建里不可观测**,因此 good fixture 的 C-8 是 WARN;4.0.6 源码有 `_revision`,换运行时后应变 PASS。
167
+ 4. **C-13 只判声明一致性**;没有 `dsh.capabilities` 的插件无法被分类,高风险样本需要人工/市场复核(见 41)。
168
+ 5. **C-10 是 advisory**,不产生 FAIL;危险 AST/句柄泄漏仍不进强制级。
169
+ 6. `dsh.commitments`/`dsh.disclosure` 缺失只记 NA/WARN;C-12 不会因“没写承诺”而 FAIL。
170
+ 6. 目标只支持**目录**;tarball/npm 包解包是 T2 下一阶段。
171
+ 7. 未实现 L4 冒烟、GitHub Action、M1/M2 双通道;这些分别在 T2 phase 2 / T3。
172
+
173
+ ## 7. 开发与测试
174
+
175
+ ```bash
176
+ cd 40-checker
177
+ node --test
178
+ node bin/dsh-plugin-verify.mjs fixtures/good --format text
179
+ node bin/dsh-plugin-verify.mjs fixtures/bad --format text
180
+ ```
181
+
182
+ 目录:
183
+
184
+ ```
185
+ bin/dsh-plugin-verify.mjs CLI 入口
186
+ src/cli.mjs 参数、编排、exit
187
+ src/manifest.mjs DSC-01/02/03
188
+ src/declarations.mjs DSC-10/11/12/13
189
+ src/runtime.mjs DSC-04..09
190
+ scripts/host-farm.mjs 临时 host farm(符号链接依赖 + npm install/build)
191
+ scripts/m1-sidecar.mjs format-1 索引 → levels.json(index-only + pilot 合并)
192
+ scripts/m2-runner.mjs M2:临时 HOME/DSH_HOME、超时杀进程树、unshare/landlock
193
+ scripts/doc-commands-check.mjs 文档命令/模板一致性(防第三方 npx)
194
+ scripts/dependabot-check.mjs no-Dependabot 策略检查
195
+ scripts/entry18-web.mjs 真实 web profile 的 ENTRY row 检查(runProfile)
196
+ scripts/entry18-realweb-summary.mjs realweb rows → summary.json(含行哈希)
197
+ scripts/landlock-exec.c Landlock fs 写限制 launcher(C,按需编译)
198
+ scripts/apply-overlay.mjs 把 T5 overlay 应用到包副本(不碰上游)
199
+ src/report.mjs text/json/SARIF + 结论门
200
+ src/semver.mjs 子集 SemVer
201
+ src/install-spec.mjs parseInstallSpec 的子集
202
+ fixtures/ good/bad/bundle/hang/fs-write/market-index
203
+ results/ 样本与 fixtures 的实际输出
204
+ test/ node:test
205
+ ```
206
+
207
+ ## 8. M1 sidecar
208
+
209
+ ```bash
210
+ node scripts/m1-sidecar.mjs \
211
+ --index /tmp/dsh-catalog/index.json \
212
+ --pilot results/m1 \
213
+ --out results/m1/levels.json
214
+ ```
215
+
216
+ - `index-only`:只校验索引字段,不下包、不执行,`level: null`;**不是** DS-C level。
217
+ - `static-only` / `static-only-warn` / `failed`:8 个 pilot 的包级结论。
218
+ - 全量 3,993 条生成一次约 1 秒;不含插件代码、会话数据、凭据。
219
+
220
+ ## 9. M2 runner(process / unshare / Landlock)
221
+
222
+ ```bash
223
+ node scripts/m2-runner.mjs \
224
+ --package /tmp/dsh-host/plugins/ci-diet \
225
+ --modules /tmp/dsh-host/node_modules \
226
+ --runtime /path/to/cordis/lib/index.js \
227
+ --backend unshare-landlock \
228
+ --timeout 60000 \
229
+ --out results/m2/ci-diet.unshare-landlock.json
230
+ ```
231
+
232
+ - 临时 `HOME`/`DSH_HOME`/`TMPDIR`;复制包;detached 子进程;超时杀进程树。
233
+ - 映射:checker `pass`→`verified`、`warn`→`verified-warn`、`fail`→`failed`、`incomplete`→`inconclusive`。
234
+ - `--backend process`:`containment: none`;`--backend unshare`:`unshare -Urn --fork --pid --mount-proc`,`containment: network-namespace+process`。
235
+ - `--backend landlock`:Landlock ABI 3,拒绝 HOME/TMP/cwd 之外的写;`unshare-landlock` 再叠 network/PID。
236
+ - `--image ref@sha256:<64hex>` 只做格式校验并记录 `sandbox.image`(`verified:false`);本机没有 Docker 后端。
237
+ - **仍不是完整安全沙箱**:读权限仍打开,无 seccomp/cgroup/只读 root;OCI 容器是下一阶段。
238
+ - 实测:`dsh-ci-diet`/`dsh-project-memory` → `verified-warn`;`dsh-status-rotator`/`dsh-ui-auth` → `failed`(仅 C-2);`fixtures/hang` → `inconclusive`(3s kill);`fixtures/fs-write` 写 `<home>/...` → EACCES,文件未生成。
239
+
240
+ ## 10. CI
241
+
242
+ `.github/workflows/plugin-verify.yml` 是模板:
243
+
244
+ - push/PR 只跑 L0(`node --test` + good/bad fixture + SARIF);
245
+ - `workflow_dispatch` 手工跑 L1(需要仓库变量 `DSH_HOST` 指向一个 DSH checkout)。
246
+
247
+ ## 11. 下一步(T2 phase 2)
248
+
249
+ 1. `--scope entry` + `DSC-14` + `bundle-smoke.mjs`(G0–G3)已接进 `entry18-runtime` CI;G4 client root 是组合静态,client 运行时由 `web-runtime` 的真实浏览器 probe 提供。
250
+ 2. 18 包 base-only 真实基线:`results/entry/18/fullbase/`,10/18 G3 `verified`、8 `partial`、0 `error`;host gate 后 8 clean `verified` + 2 `reject` + 8 `partial`(`92` §七)。web profile 的 4 个 web-app 看 `results/webruntime/`。
251
+ 3. M2 OCI 化、LEV-6/证据卡、三份开发建议。
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dsh-levels — one entry point for the three-level audit.
4
+ * It forwards to level-audit.mjs so there is a single documented command.
5
+ */
6
+ import path from 'node:path'
7
+ import { spawnSync } from 'node:child_process'
8
+ import { fileURLToPath } from 'node:url'
9
+
10
+ const HERE = path.dirname(fileURLToPath(import.meta.url))
11
+ const AUDIT = path.resolve(HERE, '..', 'scripts', 'level-audit.mjs')
12
+ const args = process.argv.slice(2)
13
+ if (args[0] === 'audit') args.shift()
14
+ const run = spawnSync(process.execPath, [AUDIT, ...args], { stdio: 'inherit' })
15
+ process.exitCode = run.status ?? 1
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.mjs'
3
+
4
+ const code = await main()
5
+ process.exitCode = code
6
+ // A plugin under L1 may leave timers, sockets, or child processes behind.
7
+ // The report is already written; do not let those handles keep the checker alive.
8
+ setTimeout(() => process.exit(code), 250)
@@ -0,0 +1,12 @@
1
+ // Intentionally bad fixture: top-level side effect (C-10 advisory),
2
+ // a declared provide that never registers (C-5), a throwing apply (C-4),
3
+ // and a forbidden endorsement label (C-13.4).
4
+ setInterval(() => {}, 1000)
5
+
6
+ export const name = 'fixture-bad'
7
+ export const inject = ['tools']
8
+ export const provide = ['missing']
9
+
10
+ export function apply() {
11
+ throw new Error('fixture-bad: apply always throws')
12
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@dsh-fixtures/bad-plugin",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./index.mjs",
6
+ "exports": {
7
+ ".": "./index.mjs"
8
+ },
9
+ "dsh": {
10
+ "manifestVersion": 1,
11
+ "kind": "plugin",
12
+ "capabilities": ["network:ingress"],
13
+ "labels": ["official"]
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export function apply() {}
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "Bad Name!",
3
+ "version": "not-semver",
4
+ "main": "./index.js"
5
+ }
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: fixture-bundle-server
3
+ name: './server.mjs'
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@dsh-fixtures/bundle-with-server",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./server.mjs",
7
+ "exports": { ".": "./server.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": {
10
+ "manifestVersion": 1,
11
+ "bundle": { "patch": "./cordis.patch.yml" },
12
+ "capabilities": [],
13
+ "labels": []
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ export const name = 'fixture-bundle-server'
2
+ export const provide = ['bundleGood']
3
+
4
+ export function apply(ctx) {
5
+ ctx.provide('bundleGood', { ok: true })
6
+ ctx.effect(() => () => {}, 'fixture.bundle.effect')
7
+ }
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: fixture-bundle-server
3
+ name: './server.mjs'
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@dsh-fixtures/bundle-with-server",
3
+ "version": "0.9.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./server.mjs",
7
+ "exports": {
8
+ ".": "./server.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "bundle": {
16
+ "patch": "./cordis.patch.yml"
17
+ },
18
+ "capabilities": [],
19
+ "labels": []
20
+ }
21
+ }
@@ -0,0 +1,7 @@
1
+ export const name = 'fixture-bundle-server'
2
+ export const provide = ['bundleGood']
3
+
4
+ export function apply(ctx) {
5
+ ctx.provide('bundleGood', { ok: true })
6
+ ctx.effect(() => () => {}, 'fixture.bundle.effect')
7
+ }
@@ -0,0 +1 @@
1
+ export function apply() {}
@@ -0,0 +1 @@
1
+ export function apply() {}
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@dsh-fixtures/client-missing",
3
+ "version": "1.0.0",
4
+ "dsh": { "client": "client.js" },
5
+ "exports": { ".": "./index.js" }
6
+ }
@@ -0,0 +1,9 @@
1
+ export const name = 'flow-config'
2
+
3
+ export function apply(ctx, config = {}) {
4
+ if (config.port !== undefined && typeof config.port !== 'number') {
5
+ throw new Error('config.port must be a number; got ' + JSON.stringify(config.port))
6
+ }
7
+ console.log(`flow-config@1.0.0 started (port=${config.port ?? 'default'})`)
8
+ ctx.effect(() => () => {}, 'flow-config.effect')
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/flow-config",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "exports": { ".": "./index.js" },
8
+ "engines": { "dsh": ">=0.2.0 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1 }
10
+ }
@@ -0,0 +1,7 @@
1
+ import fs from 'node:fs'
2
+
3
+ export const name = 'fixture-fs-write'
4
+ export function apply() {
5
+ // Must be denied by M2's Landlock backend; this fixture is never run without it.
6
+ fs.writeFileSync('/tmp/dsh-m2-landlock-escape-test', 'escaped')
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/fs-write",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,10 @@
1
+ export const name = 'fixture-good'
2
+ export const inject = ['sessions']
3
+ export const provide = ['good']
4
+
5
+ export function apply(ctx) {
6
+ // Touch the declared capability so C-11's static trigger fires.
7
+ const sessions = ctx.sessions
8
+ ctx.provide('good', { hello: () => 'hi', hasSessions: Boolean(sessions) })
9
+ ctx.effect(() => () => {}, 'fixture.good.effect')
10
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/good-plugin",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ export const name = 'fixture-hang'
2
+ export function apply() {
3
+ // Intentionally blocks the event loop so M2's deadline/kill path is exercised.
4
+ while (true) {}
5
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/hang",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "good": {
3
+ "expected": "pass",
4
+ "control": "positive",
5
+ "note": "entry static passes"
6
+ },
7
+ "bad-manifest": {
8
+ "expected": "fail",
9
+ "control": "negative",
10
+ "note": "invalid name/version must fail"
11
+ },
12
+ "skipped": {
13
+ "expected": "not-run",
14
+ "control": "not-run",
15
+ "note": "must not be treated as PASS",
16
+ "ignored": true
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "format": 1,
3
+ "title": "Synthetic listing fixture over a real package (not a real market)",
4
+ "plugins": [
5
+ {
6
+ "name": "dsh-vision-router",
7
+ "title": "dsh-vision-router",
8
+ "versions": [
9
+ {
10
+ "version": "3.1.0",
11
+ "publishTimestamp": 1760000000,
12
+ "source": { "type": "npm" },
13
+ "engines": { "dsh": ">=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.2 <0.3.0-0" }
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "format": 1,
3
+ "title": "DS-C fixture market",
4
+ "plugins": [
5
+ {
6
+ "name": "@dsh-fixtures/good-plugin",
7
+ "title": "Good fixture",
8
+ "versions": [
9
+ {
10
+ "version": "1.0.0",
11
+ "publishTimestamp": 1760000000,
12
+ "source": { "type": "npm" },
13
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" }
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1 @@
1
+ {"name":"@dsh-fixtures/neg-bad-entry","version":"1.0.0","license":"MIT","main":"./missing.js"}
@@ -0,0 +1 @@
1
+ {"name":"@dsh-fixtures/neg-no-entry","version":"1.0.0","license":"MIT"}
@@ -0,0 +1 @@
1
+ {"name":"@dsh-fixtures/neg-no-patch","version":"1.0.0","license":"MIT","dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}
@@ -0,0 +1,2 @@
1
+ - id: ghost
2
+ name: './missing.js'
@@ -0,0 +1 @@
1
+ {"name":"@dsh-fixtures/neg-root-missing","version":"1.0.0","license":"MIT","dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}
@@ -0,0 +1 @@
1
+ {"name":"@dsh-fixtures/neg-workspace","version":"1.0.0","license":"MIT","engines":{"dsh":"workspace:*"}}
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "dsh-plugin-verify",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "DS-C conformance checker MVP for DeepSeek Harness plugins (standalone, zero dependencies)",
6
+ "type": "module",
7
+ "bin": {
8
+ "dsh-plugin-verify": "bin/dsh-plugin-verify.mjs",
9
+ "dsh-levels": "bin/dsh-levels.mjs"
10
+ },
11
+ "exports": {
12
+ ".": "./src/index.mjs"
13
+ },
14
+ "scripts": {
15
+ "test": "node --test",
16
+ "check:fixtures": "node bin/dsh-plugin-verify.mjs fixtures/good --format text; node bin/dsh-plugin-verify.mjs fixtures/bad --format text"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "license": "Apache-2.0"
22
+ }
@@ -0,0 +1,8 @@
1
+ # Sample frame (60 + 20)
2
+
3
+ `frame.json` is a reproducible **frame**, not a result set.
4
+
5
+ - 60 main samples + 20 low-star samples, selected from `topic:dsh-plugin` with `size:<50000`.
6
+ - Full clone static executed in CI: `results/sample60-ci` (0/55/5), `results/sample20-ci` (0/19/1).
7
+ - Remote-manifest results also kept: `results/sample60`/`sample20`.
8
+ - Full clone/dependency/runtime execution is still not done; do not report "60/60 full static".