# 收录标准 **状态**:草案(2026-10-30)· 目标基线 dsh-mobile **0.8.0** + DSH **0.2.1-rc.2**(实测在 0.7.1 上做过) **适用**:`packs//` 下的适配包收录、审查、验证。运行时是 dsh-mobile 的 `$DSH_HOME/mobile-access/extensions/`,本仓库不改宿主产物。 数字口径来源(直接引用,不重造):dsh-mobile `docs/MOBILE_PLUGIN_ADAPTATION.md`、`design-system/MASTER.md`、WCAG 2.2 SC 2.5.5(44×44,AAA);我们那份 L1/L2/L3 基线在 PR #316,**仍是提案**。本文档是这些口径的「可执行版」。 --- ## 1. 收什么,不收什么 **收**:第三方插件**自己的面板**在移动端的适配——面板容器宽度/让位、按钮与输入的触控目标、面板状态区的滚动、`data-dsh-chrome` 语义契约的折叠。 **不收**: - 外壳级适配(抽屉、设置页、输入栏、主题、PWA)——dsh-mobile 专用页已经做了,且社区 15+ 个仓库在这块互相打架; - 重实现宿主已有能力(例如自己再写一套侧栏抽屉); - 通配兜底层(「所有插件面板按钮一律 44px」且无点名、无 opt-out、不可单独关闭的组合); - 改已安装 DSH 产物、改 `lib/`、往 `$DSH_HOME` 根上写 `mobile.css` 的做法(那是另一条 hotfix 通道,不进收录表)。 **定位**:本仓库是上游之前的家。上游收编某项适配后,对应包转 `status: retired`,不再同步。 --- ## 2. 包的硬约束(CI 静态层) | 约束 | 规则 | 失败即拒 | |---|---|---| | 字段 | `extension.json` **只能 5 个**:`schemaVersion`(必须 `1`)、`id`、`name`、`version`、`description` | 多一个字段整包被运行时拒绝 | | id | 必须等于目录名,匹配 `^[a-z][a-z0-9-]{0,63}$` | 是 | | 文件 | 只允许 `extension.json` / `mobile.css` / `mobile.js` / `host.mjs` / `fixture.html` / `assets/` / `README.md` / `shots/`;运行时可执行面只有前三类 | 是 | | 体积 | `mobile.js` ≤ 1 MB、`mobile.css` ≤ 512 KB、`extension.json` ≤ 64 KB、`assets/` 单文件 ≤ 8 MB 且 ≤ 256 个 | 是 | | 元信息位置 | status / risk / default / target / capabilities / surfaces / usesHostMarkers / verifiedWith 只能在根 `catalog.json` | 是 | | 模板 | `packs/_template` 以下划线开头,**不参与校验**(`check-pack` 跳过目录名以 `_` 开头的包) | — | | 登记一致性 | 新增包必须同时提交目录与 `catalog.json` 条目;反向(表里有、目录未落地)在 P0 迁移期允许——工具应**提示**而不是失败 | 否 | `mobile.css` / `mobile.js` 必须可读:不得压缩(minify)、不得混淆、不得夹带与描述无关的第三方代码(见 §5「不得混淆」)。 定向 pack 的 `packs//fixture.html`(体积上限、禁用内容、DOM 最小契约、evidence 写法)见 [`docs/定向-fixture.md`](定向-fixture.md)。这份契约单独维护,不占用收录标准的章节号,避免与其它改动冲突;fixture 是否忠实于真实插件 DOM 的审查见 §8.1。 --- ## 3. 能力 → 对应断言 每张包只勾**真的做了**的能力,逐条在 390×844 / `(pointer: coarse)` 下用 Playwright 读**真实盒模型**断言。沿用 `移动适配模板/check-mobile.mjs` 的三层思路:**markup**(面板确实被识别)→ **styles**(能力数值成立)→ **scoping**(只影响目标面板)。 | 能力 | 判据 | CI 断言 | 适用面 | |---|---|---|---| | `hit-target` | 粗指针下面板内 `button / select / input` ≥ 44px | 面板容器内每个可点元素的最小边 `getBoundingClientRect()` ≥ 44(容差 0.5px);不可见元素(`display:none` / `visibility:hidden` / 尺寸 0)跳过;`input` / `textarea` 另要求 computed `font-size` ≥ 16px(`runtime.mobile.inputFontSize`,没有文本输入时跳过)防 iOS 聚焦缩放 | 两个面都要 | | `width-reclaim` | 面板根 `100vw` / 固定 px 不溢出 | 面板根 `rect.left ≥ -0.5` 且 `rect.right ≤ innerWidth + 0.5`;横向滚动容器(`overflow-x:auto`)自身宽度计入但不得把页面撑宽(`documentElement.scrollWidth ≤ innerWidth`);**定向 fixture 下无条件断言**(`runtime.mobile.panel.right` + `runtime.mobile.overflow` 不受 `capabilities` 门控) | 两个面都要 | | `chrome-collapse` | 按 `data-dsh-chrome="header"` 折叠顶栏 | 折叠前后面板内容区高度差 > 0,且折叠态 header 可见高度 ≤ 阈值(单行,约 44px);展开可恢复 | 仅 `mobile-dedicated` | | `notice-collapse` | `data-dsh-chrome="notice"` 折成一行且可展开 | 折叠态提示行数 = 1(`scrollHeight ≤ line-height × 1.5 + padding`);点击可展开、再点可收起 | 仅 `mobile-dedicated` | | `scroll-owner` | 面板状态主区可滚,不夺插件滚动 | 面板状态主区 `overflow-y` 为 `auto/scroll` 且内容可滚(`scrollHeight > clientHeight`);滚到底后**插件自己的**滚动容器仍能滚动(没有被 `overscroll-behavior: contain` 之外的全局锁截断) | 两个面都要 | | `floater-clearance` | 右下角浮动控件不再压住面板末尾内容 | 量出的 `--dshm-floaters` ≥ 控件高度;面板状态 `mainContent` 可滚;面板根 `margin-bottom` ≥ 控件高度;**滚到底后面板底部 ≤ 控件顶部**;opt-out 后归零 | 仅 `mobile-dedicated` | | `shell-hit-target` | 外壳自身(返回栏 / 展开抽屉)的按钮 ≥ 44px | `.dshm-panelNav` 内每个 `button/[role=button]` 宽高 ≥ 44;抽屉 `[data-open="true"]` 内 `[data-slot="sidebar"]` 的按钮高度 ≥ 44;精指针下逐项零改动 | 仅 `mobile-dedicated` | 补充规则: - **`scope` 决定断言组**:`catalog.json` 的 `scope` 为 `panel`(默认)或 `shell`。`shell` 类包(如 `shell-chrome`)只勾 `shell-hit-target`,`check-pack` 会跳过面板断言组而不是判它失败——但反过来也成立:声明了 `hit-target` 就必须真的把面板按到 44px。 - **opt-out 的落点随 scope 走**:`panel` 包认面板根或它的祖先;`shell` 包认文档根(``),因为外壳没有「某个面板」这个边界。`check-pack` 的 fixture 把标记放在 `` 上,两种包都必须因此完全回落。 - **未勾的能力不得出现对应选择器。** `check-pack` 从 CSS 里找特征选择器:出现 `[data-dsh-chrome=notice]` ⇒ 必须勾 `notice-collapse`;出现 `[data-dsh-chrome=header]` ⇒ 必须勾 `chrome-collapse`。 - **opt-out 断言**:给目标插件根加 `[data-dsh-adapt="off"]`(或文档化的面板级变量后)重跑一遍,**本包勾选的项必须全部回落到基线**(例如按钮回到 28px),否则视为 opt-out 无效。 - **两个面**:`mobile-dedicated`(`window.__DSH_MOBILE_FRONTEND__ === 'dedicated'`,可用 `.dshm-*`)与 stock DSH web(≤720px,**不得碰 `.dshm-*`**,只做 `hit-target` 与 `width-reclaim`)。`surfaces` 登记了这个包声称覆盖的面,未登记的面不跑断言。 - **不使用 `:has()`**:兼容没有 `:has` 的 WebView;需要状态就用 attribute。 - **面板状态归谁**:dsh-mobile **#319**(main `6c50b13`,已随 **0.8.1** 于 2026-11-01 发布)起由宿主自己在 `.dshm-main` 上写 `data-dsh-panel="1|0"`。包必须用**自己命名**的标记(如 `data-dshm-adapt-panel="1|0"`),**不得写宿主的 `data-dsh-panel`**;可以把它当门阀(`:not([data-dsh-panel])`)只在宿主没接管时兜底。声明了门阀的包,`check-pack` 会额外跑 host-probe(伪造宿主已写该属性),断言门阀退让、宿主属性未被改写、宿主不提供的部分(如 `min-width`)仍生效。 - **0.8.0 / ≥0.8.1 两条线**:`0.8.0` 发布线**没有** #319(安装包里 `data-dsh-panel` 0 命中);`0.8.1` 起有。包 README 与 `catalog.json` 的 evidence 必须分别写清「在 0.8.0 上生效 / 在含 #319 的宿主上退让」,不得用一套口径含糊覆盖两边。 - **不依赖 CSS Module 哈希**(`pI_x6G_` 这类前缀每次构建都会变),只用 `data-slot` / `[role=dialog]` / 已登记的 `.dshm-*` / 包自己写的标记。 --- ## 4. `rail-clearance`(已退役,2026-10-31) `rail-clearance` 曾是 `core-panel` 的一项显式偏离:在 ≤720px 上给 `.dshm-mainContent` 推 56px,让开折叠轨道。2026-10-31 的 0.8.0 实测(`实测-0.8.0-20261031.md`)推翻了这个场景: - 390 面板态下抽屉带 `data-right-modal` → `display:none`(宽 0),`--dshm-rail` 实测 0px; - 768 下宿主已经 `margin-left:56px` 让位(`lib/mobile-layout.js:437`,721–899 段),包的 `@media (max-width:720px)` 不命中; - 两种尺寸下强迫位量都没有改变面板左右缘(0…390 / 56…768)。 结论:**能力、断言组、包内规则与 `--dshm-rail` 变量一起退役**,`catalog.json` 不再声明它。将来若某个宿主真的在手机上露出 56px 轨道且不让位,应当带实测证据重新提案,而不是复活一条没人测到的规则。 --- ## 5. 审查规则 ### 5.1 借 Greasy Fork 四条(原文语义) | # | 规则 | 我们的落地检查 | |---|---|---| | 1 | **必须有描述** | `catalog.json` 的条目 + 包 README + `extension.json.description` 三者一致;说清目标插件、解决什么问题、怎么关。缺一项退回。 | | 2 | **不得混淆 / 压缩** | `mobile.css` / `mobile.js` 必须格式化可读;不得 base64 包脚本、不得 `eval` 远程代码、不得夹带与描述无关的第三方代码。 | | 3 | **不得超出描述范围** | 包只能做 `capabilities` 勾选的事、只能作用于 `target` 插件面板;发现「顺手改了全局风格 / 改了别的插件」即拒。 | | 4 | **可删** | 维护者可移除任何包;包本身也必须「删目录即回滚」——不得要求改宿主、不得留下注册项、不得依赖一次性手工步骤。 | ### 5.2 我们加的两条 | # | 规则 | 落地检查 | |---|---|---| | 5 | **必须点名某个插件** | 包 README / 审查记录必须写明目标插件**包名 + 实测版本 + 至少一台真机**(真机证据的记录格式与示例见 [`docs/真机验证.md`](真机验证.md));禁止「适配所有插件」的匿名声明。唯一例外是 `core-panel`(`target: null` 的通配下限):它必须列出实测过的插件清单,且 `default: "off"`。 | | 6 | **必须 opt-out** | 目标插件根 `[data-dsh-adapt="off"]` 或文档化的面板级 CSS 变量;必须带实测证据(关掉后数值回落)。只有 `!important` 覆盖、没有 opt-out 的包不收。 | 补充:`risk: intrusive`(改结构 / 藏元素 / 动 `innerHTML`)一律 `default: "off"`;只有 `safe` + `stable` 才允许 `default: "on"`。 --- ## 6. 风险等级 / 默认值 / 状态 ```jsonc "status": "stable | candidate | experimental | retired", // 生命周期 "risk": "safe | intrusive", // safe=纯 CSS 下限;intrusive=改结构/藏元素/动 DOM "default": "on | off" // on 仅限 safe + stable ``` - `experimental` → `candidate`(维护者过一眼、`check-pack` 全绿)→ `stable`(≥2 台真实设备验过)。 - `retired` 保留在表里但不再同步(上游收编 / 被替代 / 失效)。 - 宿主版本跨出 `runtime` 窗口时,包应被判「跳过」并在设置页提示,**不静默失效**。 --- ## 7. `usesHostMarkers`:债务登记 `.dshm-*` 是 dsh-mobile 的内部类名,**没有公开契约**。包用到的每一个 `.dshm-*` 都必须登记进 `usesHostMarkers`;CI 对目标版本的 DOM 快照做存在性断言: - 标记存在 ⇒ 通过; - 标记消失 ⇒ 这张包**失败**(红在具体包、具体标记上),不是静默失效; - 跨小版本(0.8.x)矩阵跑一次 DOM 快照,红了能定位到包。 stock 面不得使用 `.dshm-*`;用了就属于「未登记的私有标记」,直接拒。 --- ## 8. 提交清单(收录者照这个自查) - [ ] `packs//` 目录名 = `extension.json.id`,字段恰好 5 个,体积在限内 - [ ] `catalog.json` 条目:status / risk / default / target / capabilities / surfaces / usesHostMarkers / verifiedWith 齐全且与实际一致 - [ ] `capabilities` 只勾真的做了的;未勾能力没有对应选择器 - [ ] opt-out 已实现并有实测记录 - [ ] `usesHostMarkers` 与实际用到的 `.dshm-*` 一一对应 - [ ] `shots/` 有 before / after 两张图(390×844) - [ ] README 写清:目标插件与版本、宿主标记、真机验证、回滚方式 - [ ] `node tools/check-pack.mjs packs/` 全绿 - [ ] Windows:`node tools/check-pack.mjs packs/ --desktop` 也全绿(CI 的 `windows-latest` job 覆盖整包套件) - [ ] 新增或变更定向 fixture(`packs//fixture.html`)时:§8.1「fixture 忠实度审查」全部勾选,且 `evidence` 按 [`docs/定向-fixture.md`](定向-fixture.md) §3 区分定向/内置 fixture > `mobile.js` 以 DSH 页面同源权限运行——**收录即审代码**,不是审描述。 ### 8.1 fixture 忠实度审查(使用 `packs//fixture.html` 时必查) 定向 fixture 让 `check-pack` 的断言**真实命中**目标插件的面板;但它只证明"在这份 DOM 替身上断言成立",不证明"这份替身是目标插件的真实面板"。审查要回答的是:**这份 fixture 忠实到真实 DOM 的哪一层,以及凭什么相信它。** 通过标准不是"和真实页完全一样"(做不到,也不需要),而是"结构、控件集合与稳定标记一致,几何差异被显式承认"。 **适用时间(存量过渡期)**:本清单对**新增或变更**的定向 fixture 生效——即新提交的 `packs//fixture.html`,以及任何修改了既有 fixture 或 `catalog.target` 的 PR。**存量例外**:`packs/dshmarket-mobile/` 是本清单落地前的存量 pack,其 A 组证据(捕获口径、差异表、独立 reviewer 段)由单独的 **evidence-refresh PR** 补齐;**本段不追溯判定它当前不合规**,§8 提交清单中指向 §8.1 的那一条也不回溯否定它的历史提交。存量 pack 在它下一次修改 `fixture.html` / `catalog.target` 时转入必查("存量过渡期"即告结束)。 **A. 证据(pack 作者在提交/PR 里提供,reviewer 照此复核)** - [ ] `catalog.evidence` 写死被快照插件的**包名 + 精确 npm 版本**(例:`dshmarket@1.73.3`;不接受 `^1.x`、HEAD、日期近似),并写清**捕获口径**:`surface`(`mobile-dedicated` / stock web)、视口(宽×高)、DPR、浏览器/内核版本、操作系统与**捕获日期**。 - [ ] 给出**可复核的 DOM 证据**,不能只说"脱敏快照": - 关键标记清单(选择器 + 命中数量/取值,例:`[data-dsh-market-root]` ×1、`data-dsh-market-tab="discover"` ×1); - 按统一口径的**控件计数**:可点控件固定为 `button` / `[role="button"]` / `select` / `input` / `textarea` 五类(各多少、合计多少);**不含 `a[href]`**——真实页的 34 个卡片链接在脱敏时被去掉,所以不进计数(若某个包确实把链接当作可点元素断言,需单列"链接"一项并说明); - 捕获方法或脚本路径(或可复现命令);脚本可放仓库外,但方法必须足以让 reviewer 重跑。 - [ ] 给出**差异表**:fixture 与真实页的已知差异逐条列出并量化(例:`a[href]` 34→0、输入框 `font-size` 16→14、slot 外 3 个宿主 chrome 控件被略去),并标明哪些是"预期内的脱敏",哪些会影响断言的含义。 **B. 边界(结构替身,不是几何替身)** - [ ] 明确声明:**fixture 是结构 / 控件集合 / 稳定标记的替身,不是几何替身**。最小边、面板盒尺寸与原点等绝对几何值只在**同一外壳内**比较(装包前后),不得把 fixture 的几何值当成真实页的结论。 - [ ] 若包的断言依赖几何(左右缘、`scrollWidth`、让位高度),要写明它比较的是"同一 fixture 内装包前后";真实页的几何结论必须另附实测记录(浏览器或真机),不能由 fixture 代证。 - [ ] 写明**失效条件**:目标插件版本变更、关键标记增删、或 `check-pack` 报告回退内置 fixture 时,必须**重新捕获并更新 evidence**;`catalog.target` 的版本 pin 不再匹配时,包应标 `retired` 或跳过,而不是继续用旧 fixture 通过。 **C. 复核(reviewer 独立做,结论单独记录)** - [ ] reviewer 在**同一插件版本 + 同一捕获口径**下**独立再捕获**(至少抽样核对:关键标记、控件计数、差异表三处),不能只审描述。复核结论的标准写法是:**"结构与控件集合一致;几何不可移植"**——不得写"完全一致"。 - [ ] 分工写清:**pack 作者提供证据并对真实性负责(自证);reviewer 复核并记录复核结论**。两段记录分开写(作者段 / reviewer 段),不合并成一句"已核对"。 - [ ] `evidence` 必须区分**定向 fixture 通过**与**内置 fixture 通过**(写法见 [`docs/定向-fixture.md`](定向-fixture.md) §3),不得把定向结果写成通用结果。 > 真实对照(**数据是 2026-10-31 快照**,出处:[dsh-adapt-packs#6 的「fixture vs 真实专用页」对照评论](https://github.com/LUgABeEEE/dsh-adapt-packs/issues/6#issuecomment-6542295715);环境:dsh-mobile 0.8.0 专用页 + `dshmarket@1.73.3`。计数口径同 A 组:可点控件 = `button` / `[role="button"]` / `select` / `input` / `textarea`,**不含 `a[href]`**):`[data-dsh-market-root]` 1↔1、`data-dsh-market-tab="discover"` 一致、控件 **138↔138**(5 次运行稳定)、装包后 `<44px` 控件 **138→0** 两处一致;但最小边 **12.1 vs 21**、面板盒 **342×593 @ (24,203) vs 334×816 @ (0,56)**、`a[href]` **34 vs 0**(脱敏去掉,不计入控件数)、输入 `font-size` **16 vs 14**——所以结论只能是"结构一致、几何不可移植"。这份对照同时说明:fixture 的用途是让 44px/横溢等断言**可执行**,不是替代真实页测量。 --- ## 9. 失败与退役 | 情况 | 处置 | |---|---| | 包在目标版本上断言失败 | 从 `on`/`candidate` 降级或标 `retired`;在 README 记录失败版本与原因 | | 上游收编同一能力 | 标 `retired`,不再更新,保留索引指向上游 | | 宿主标记消失 | CI 红到具体包 + 具体标记;包作者修选择器或退役 | | 恶意 / 超出描述范围 | 直接移除,并在收录表留一条 `retired` 说明 | --- ## 10. Windows 上的收录校验 工具链在 Windows 上走同一条代码路径,已在 Windows 11 + Chromium 实测(三包 `84 passed / 0 failed / 8 skipped`,`--desktop` 为 `87 / 0 / 8`)。收录者只需要注意下面几点: - **大小写**:Windows 文件系统不区分大小写,但包 id 仍必须是小写(`^[a-z][a-z0-9-]{0,63}$`),目录名也用同样的小写形式。`check-pack` 会把已存在的包路径规范化成磁盘上的真实大小写,所以 `PACKS\CORE-PANEL` 与 `packs/core-panel` 指向同一个包;不要提交「只有大小写不同」的两个目录(Linux CI 会失败)。 - **换行**:Git for Windows 默认 `core.autocrlf=true`,工作区是 CRLF。`check-pack` 以 UTF-8 读取并按行/空白解析,CRLF 不影响静态层与 fixture 测量;CI 的 `windows-latest` job 每次都在 CRLF 工作区跑一遍,等于持续回归。 - **路径**:`--json` 与人类可读输出里的 `path` 统一显示为正斜杠(`packs/core-panel`),方便与 Linux 日志对比;`PS D:\…>` 下运行与 `cmd` 下运行结果一致。 - **Playwright**:需要 Playwright + Chromium。解析顺序为 `DSH_PLAYWRIGHT`(`index.js` 文件或包目录)→ `$DSH_HOME/profiles/{web,desktop,headless}` → 仓库自身 → 模块解析;失败时会列出所有尝试过的路径。安装:`npm install --no-save --no-package-lock playwright`,然后 `npx playwright install chromium`(Windows 默认下载到 `%LOCALAPPDATA%\ms-playwright`)。Windows 上不要给 `playwright install` 加 `--with-deps`(那是 Linux 专用参数)。 - **`$DSH_HOME`**:Windows 缺省 `%USERPROFILE%\.dsh`,PowerShell 用 `$env:DSH_HOME = 'D:\dsh'` 设置;空串/纯空白等同于未设置。`install.mjs` 打印的“动作”按当前平台给出 PowerShell 命令,可以直接复制执行。 - **数值口径**:fixture 的绝对像素(例如返回栏基线在 Linux 文档里写 25px、Windows 实测 23px)会随操作系统字体渲染略有差异;断言用的是相对阈值(44/56/1280 与逐项基线),跨平台以判据为准,不要把 README 中的绝对像素当作跨平台常量。定向 fixture 的捕获记录必须写明捕获平台(§8.1 A);fixture 的几何值不跨平台、不跨外壳迁移(§8.1 B)。 - **EXDEV**:`install.mjs` 的卸载是 `$DSH_HOME` 内的 `rename`;跨卷(Windows 的 `ERROR_NOT_SAME_DEVICE` → `EXDEV`)会退化为复制+删除。正常安装不会触发这条分支,CI 也不覆盖它——这是已知的、被文档化的边界。 ## 11. 作为 DSH bundle 发布(2026-10-31 起) 本仓库同时是一个**可安装的 DSH bundle**:装进 DSH 后,设置里多出「插件适配」一页,能看 catalog、点按钮装/卸包、读兼容清单。CLI 与面板共用 `lib/store.mjs` 这一套规则,不允许各写一份。 打包契约(缺一不可): | 位置 | 要求 | | --- | --- | | `package.json` → `dsh.manifestVersion` | `1` | | `package.json` → `dsh.bundle.patch` | `./cordis.patch.yml` | | `package.json` → `dsh.client.platform` | `web`(有浏览器半端) | | `cordis.patch.yml` | 恰好一条 `- insert:` 行,`name` 等于包名,`id` 可用于 profile patch 停用 | | `lib/index.mjs` | host 半端:`export default { name, inject: ['webServer'], apply(ctx) }`,路由 `/api/adapt-packs/{state,install,uninstall}`;文件操作只走 `lib/store.mjs` | | `lib/client.js` | 自注册 bundle(`window.__ModuleLoader__.load({ id: <包名>, factory })`),只注册 `settings.section`,不碰别人的槽 | 质量闸门(CI 与 `prepublishOnly` 都跑): - `node --test`:store 规则、API 处理器、manifest 与 client bundle 契约; - `node scripts/check-bundle.mjs`:**按 npm 发布产物验证**——把 `npm pack` 出来的 tarball 装进临时 prefix,检查必需文件、`dsh.client`(`immediately` / `inject` / `platform`)、patch 行、host 导出、CLI 与 4 个包是否都在。0.3.1 那次「整页起不来」就是源码树里有 `immediately`、发布产物里没有——单测看不见,这一条能看见; - `node tools/check-pack.mjs`:包本身的静态 + fixture 断言; - `node tools/cli.mjs list`:catalog ↔ packs 一致。 兼容清单(`compat.json`)与包目录(`catalog.json`)分工: - `catalog.json` = **我们提供的包**; - `compat.json` = **第三方插件在手机上的结论**(`native` / `pack` / `upstream` / `blocked`,判据见 `docs/兼容清单.md`)。 一条 `verdict: pack` 必须能对应到 catalog 里的包;对应不上会被一致性检查挡住。