# sherpa-onnx 产物的分发与溯源（#1251）

`SherpaOnnxPackage/Package.swift` 以**远程 binaryTarget** 引用两个 xcframework：实时字幕的
sherpa-onnx 回退识别引擎（ADR 0057 末节「路由规则」第 2 条），onnxruntime 静态并入同一个库，
只暴露 C API。形式与规则照 `MPVKitDynamic/Materials/distribution-artifacts.md`。

## 托管位置

```
https://bin-conflux.kami.asia/sherpa-onnx/1.13.8-r2/<file>
```

| 文件 | 平台 | 内容 |
|---|---|---|
| `sherpa-onnx-v1.13.8-macos-shared-onnxruntime-static.xcframework.zip` | macOS arm64 + x86_64 | 裸 `libsherpa-onnx-c-api.dylib` + `c-api.h` + modulemap（模块名 `SherpaOnnxC`） |
| `sherpa-onnx-v1.13.8-ios-shared-onnxruntime-static.xcframework.zip` | iOS arm64；iOS 模拟器 arm64 + x86_64 | `SherpaOnnxC.framework` |

- 2026-10-24 起托管在 Cloudflare R2 桶 `conflux-binaries`，与 MPVKit 同一个域名，字节与 checksum 不变，
  迁移原因见 `MPVKitDynamic/Materials/distribution-artifacts.md`「托管位置」。原托管
  `https://s3.kami.fit:444/conflux/sherpa-onnx/1.13.8-r2/` 的对象保留，历史 commit 继续从那里取。
- 原托管的免鉴权只读直链：发行桶 `conflux` 的桶策略对 `conflux/mpvkit/*` 与 `conflux/sherpa-onnx/*` 两个前缀开放
  匿名 `s3:GetObject`，其他前缀匿名访问一律 403（见「桶策略」）。
- 上游没有 tvOS 产物，tvOS 不接这个包（ADR 0030）。
- 路径里的 `1.13.8` 是上游版本，`-r2` 是同一上游版本的第二批字节。

## 为什么自己构建

上游 k2-fsa/sherpa-onnx 在 release tag `xcframework` 下有同名的官方预编译包，但它打开了 TTS，
**把 espeak-ng（GPL-3.0，`csukuangfj/espeak-ng@ed530aa`）静态链进了同一个库**：macOS dylib 的
符号表里能看到 `_espeak_ng_Initialize`、`_espeak_Initialize`、`_ucd_properties` 等本地符号，iOS
framework 的字符串表里有 espeak-ng / phontab 相关字符串。闭源 App 不能分发它。

所以 `build.sh` 跑的是上游同名的两个构建脚本
（`build-macos-shared-sherpa-with-static-onnxruntime.sh`、`build-ios-shared-sherpa-with-static-onnxruntime.sh`），
只在每个 cmake 调用上多加两个开关：

- `-DSHERPA_ONNX_ENABLE_TTS=OFF`：espeak-ng、piper-phonemize 不进构建；
- `-DSHERPA_ONNX_ENABLE_SPEAKER_DIARIZATION=OFF`：hclust-cpp 不进构建，用不到。

构建后断言（任一不满足即失败）：

- 每个切片、每个架构的字符串表里，TTS / 说话人分离的实现标记（espeak、piper、phontab、Kokoro、Matcha、Vits、
  Kitten、hclust、`OfflineTtsImpl`、`DiarizationPyannote` 等，区分大小写）命中 **0 处**。按架构计数；iOS 产物
  strip 过，所以查字符串表而不只查符号表。C API 里 `SherpaOnnxCreateOfflineTts` 这类名字不算：开关关掉后它们是
  返回空的桩函数。同一模式在上游官方包上命中 macOS arm64 50 处、iOS arm64 60 处，证明它查得出来；
- 识别用到的 4 个 C API 导出符号都在（正向对照，挡住「什么都没编进去」也判通过）；
- 每个切片的 `LC_BUILD_VERSION` SDK 等于所选 Xcode 的 SDK（本机 Command Line Tools 的 SDK 会泄漏进 macOS 构建，
  与 MPVKit 遇到的是同一类问题）；
- 除 `/usr/lib/`、`/System/Library/` 与自身 install name 外没有动态依赖（onnxruntime 确为静态并入）。
- `audit-licenses.sh`：每个构建目录里 cmake 取回的依赖必须正好是已审计的那 8 个、各自许可文件含预期标志句；
  onnxruntime 的 LICENSE 与 ThirdPartyNotices 按 SHA256 钉住。换 sherpa-onnx 或 onnxruntime 版本时，
  这一步会先失败，逼着重新做许可审计。

## 复现

```bash
SherpaOnnxPackage/build.sh /path/to/out
```

- Xcode：`DEVELOPER_DIR` 默认 `/Applications/Xcode_26.6.app/Contents/Developer`，与提审构建同一套。
- macOS 部署目标 11.0（上游 CI 产物同值；上游脚本本身不设，不设会落到构建机 SDK 的版本），iOS 13.0（上游脚本写死）。
- 上游输入逐个按 `upstream-input-checksums.sha256` 校验：
  - sherpa-onnx 源码：tag `v1.13.8` 的 GitHub 归档（tag 指向 `11afbd009a7f8c08f4bcf2fc1b265d0df4670fbf`）；
  - onnxruntime 1.28.2 静态库：上游脚本指定的 `csukuangfj/onnxruntime-libs` 预编译产物（由 microsoft/onnxruntime 源码构建，MIT）。
- 其余依赖由上游 cmake 按各自的 `URL_HASH` 取回（kaldi-native-fbank、kaldi-decoder、kaldifst、openfst、
  simple-sentencepiece、kissfft、Eigen、nlohmann/json）。
- 产物 zip 不是逐字节可复现的（zip 时间戳、ad-hoc 签名）；可复现的是「同样的输入 + 同样的开关 + 断言全过」，
  发布的那一份以 `artifact-checksums.sha256` 为准。

## 静态链入的组件与许可

| 组件 | 版本 | 许可 |
|---|---|---|
| sherpa-onnx | 1.13.8 | Apache-2.0 |
| onnxruntime | 1.28.2 | MIT（其自身依赖见上游 ThirdPartyNotices，随 App 与官网一并提供） |
| kaldi-native-fbank | 1.22.3 | Apache-2.0 |
| kaldi-decoder | 0.3.0 | Apache-2.0 |
| kaldifst / openfst | 见 sherpa-onnx `cmake/` | Apache-2.0 |
| simple-sentencepiece | 0.7 | Apache-2.0 |
| kissfft | 见 sherpa-onnx `cmake/` | BSD-3-Clause |
| nlohmann/json | 3.12.0 | MIT |
| Eigen | 5.0.1（sherpa-onnx 侧） | MPL-2.0（header-only，未修改） |

onnxruntime 的 LICENSE 与 ThirdPartyNotices 取自 microsoft/onnxruntime 的 `v1.28.2` tag（`audit-licenses.sh` 按 SHA256 钉住）。
链进来的静态库是 csukuangfj/onnxruntime-libs 用同一 tag 源码构建的，它的发布包里不带这两个文件；两者对应同一源码是依据上游脚本的
版本号得出的，不是逐字节核对的结果。

没有 GPL / LGPL 组件（TTS 关闭后 espeak-ng 不再进入产物，由上面的断言保证）。

## 硬规则：已发布的 URL 不可变

与 MPVKit 相同：**任何字节变化都必须发到新前缀，旧对象永久保留、永不覆盖、永不删除。**
上传一律 `If-None-Match: *` 条件写入（重放回 412），上传后从免鉴权公开 URL 全量回读，SHA256 与
`artifact-checksums.sha256` 一致后才写进 `Package.swift`，且二者同一个 commit。发版前跑
`bash tests/liff-364/verify-remote-artifacts.sh`，它同时覆盖 MPVKit 的 8 个与这里的 2 个 URL。

前缀记录：

- `1.13.8-r1/`：**已撤下，不存在**。2026-09-30 把上游官方预编译包原样上传到这里后，才发现其中静态链着
  GPL-3.0 的 espeak-ng。撤下前核对过：没有任何 commit、分支或 PR 引用过这个前缀的 URL 或 checksum
  （`git log -S` 覆盖全部本地与远程分支，逐个检查仓库全部 PR 的 diff），它从未被钉住，所以删除不破坏任何
  历史 checkout。两个对象删除后匿名 GET 回 404。**这个前缀不得再使用。**
- `1.13.8-r2/`：当前使用。`build.sh` 自建，TTS 与说话人分离关闭。发布核对（2026-10-01）：构建断言与许可组件核对
  全部通过（macOS arm64 / x86_64、iOS arm64、iOS 模拟器 arm64 / x86_64 共 5 个切片）；两个对象以
  `If-None-Match: *` 条件写入（首次 200，重放 412），从免鉴权公开 URL 全量回读，SHA256 与
  `artifact-checksums.sha256` 2/2 一致；`verify-remote-artifacts.sh` 退出码 0。构建用 Xcode 26.6（SDK 26.5）。

## 桶策略

发行桶 `conflux` 的匿名读取策略（2026-09-30 为 #1251 修改，改前改后均经 GetBucketPolicy 回读）：

```diff
   "Resource": [
-    "arn:aws:s3:::conflux/mpvkit/*"
+    "arn:aws:s3:::conflux/mpvkit/*",
+    "arn:aws:s3:::conflux/sherpa-onnx/*"
   ]
```

Sid（`PublicReadMPVKitOnly`）、Principal、Effect 与 Action（只有 `s3:GetObject`）都没改，没有 ListBucket。
改后匿名验证：`mpvkit/1.0.0-288527d-r4/` 下的对象可读，`sherpa-onnx/` 下的对象可读，其他前缀
（例如 `other/`、桶根）回 403。两个前缀公开的理由相同：SwiftPM 解析远程 binaryTarget 时匿名下载，
许可（LGPL / Apache-2.0 / MIT / MPL-2.0）也要求接收方能取到这些二进制对应的材料。
