工程正确性(四):构建成功不等于交付物可用
编译器显示成功,只能证明源码在当前环境生成了产物。用户安装的是 JAR、npm 包或容器,真正的验收对象是这些文件能否被目标运行时加载,Native 资源是否随包分发,文档示例是否使用了正确入口。发布链路如果只跑单元测试,问题会在上传后才出现。
GMKit 的发布整理说明了这条边界:Java 的 gmkit-sm9 需要把多平台 Native 运行时装入同一个 JAR,TypeScript 包还要检查 tarball 内容和生产依赖。源码测试通过并不能证明打出来的包包含这些资源。
先确定交付清单
Java 发布清单写明 group、artifact、版本、依赖、服务文件和 Native 路径。gmkit-sm9 的 JAR 中应有 META-INF/gmkit/sm9-native.properties、各平台运行时和摘要文件,加载逻辑按当前平台选择资源并校验 checksum。Native 库缺失时启动要给出明确错误,不能回退到一个“未实现但返回成功”的空实现。
TypeScript 包检查入口文件、类型声明、浏览器和 Node 产物、许可证及依赖范围。npm pack --json 在不同 npm 版本中可能返回不同结构,审计脚本要兼容实际输出,再检查文件数量、总大小和禁止目录。开发依赖不应进入生产依赖图,锁文件 registry 地址也要符合发布要求。
跨平台运行时测试
打包后从临时目录安装同一个产物,不能重新引用工作区源码。Java/SM9 测试在目标平台加载 JAR 内 Native 资源,执行一次初始化、加密或签名、校验和释放。测试记录平台、架构、JDK、Native 文件名和 checksum。跨平台构建成功但某一平台加载失败时,发布状态仍是失败。
TypeScript 包在干净 Node 项目中直接安装,运行导入、核心算法、类型声明和浏览器构建 smoke。浏览器测试确认 ESM 入口、WASM 资源 URL 和 CSP 约束。包审计只证明文件形态,不能代替运行时调用。
向量、包和文档要同一版本
共享 vectors/interop.json 的摘要进入构建报告,Java、TypeScript 和 Native 都基于同一版本跑 parity。项目生成向量只证明内部一致,标准向量和独立库结果仍需单独标记。文档示例指定版本和入口,不能展示已经删除的 API。
发布说明记录源码版本、包版本、向量摘要、构建平台、依赖版本和验证命令。版本号、JAR 内属性和 npm metadata 不一致时阻断,避免用户安装到一个版本却读取另一套运行时配置。
CI 的实际门禁
一个可执行的发布顺序是:
- 运行全量单测、跨语言 parity 和静态检查。
- 构建各平台 Native 运行时和 Java/npm 制品。
- 审计制品文件、大小、许可证和依赖。
- 在干净环境安装同一制品并做运行时 smoke。
- 生成待发布文件仓库,检查发布范围和 checksum。
- 以上门禁通过后,再调用 Maven Central 或 npm 发布动作。
GitHub Actions 的绿色 dry-run 仍不代表账号侧发布准备完成。Maven Central Environment 的令牌、GPG 密钥和 npm Trusted Publisher 属于外部前置条件,需要单独确认,不应在仓库内放入凭据。
回滚和污染控制
发布失败时保留构建产物和审计报告,删除临时文件仓库中未通过门禁的版本。已经发布的包不可覆盖同版本内容,修正使用新版本并在 changelog 中说明。Native 资源、锁文件和文档更新要同一次版本评审,避免只更新源码没有更新交付物。
日志只记录版本、平台、文件摘要和错误分类,不记录私钥、令牌和完整测试明文。构建目录在发布后清理,公开的测试向量标记为非生产数据。
这套流程不能保证生产环境没有差异,它能把“工作区通过”提升为“可安装制品通过”,并为运行失败保留足够证据。目标是让发布结果可复现、可检查、可回滚。