HarmonyOS 7 ReleaseGuard 上架审核工程实录 06:CI Release Gate × Evidence Pack:审核证据包、自动门禁与全链路验收【鸿蒙心迹】
ReleaseGuard 做到第五篇以后,已经有五份相对独立的报告:
01 Package Preflight
02 Privacy Audit
03 Listing Audit
04 Package Report
05 Version Audit
单独看,每一份都能回答自己的问题。
真正进入团队 CI 以后,最后一个问题反而最简单:
谁来保证这五份报告检查的是同一个包,并且所有 Blocker 都为 0 以后,上传步骤才允许开始?
所以 06 不再增加新的审核规则,而是把整个系列收成一个真正的 Release Gate。
AppGallery Connect 当前提供 Connect API,一组 RESTful API 可以用于流程自动化;其中 Upload Management API 可上传包体、图标、介绍图片和视频,Publishing API 可更新版本信息、提交发布和取消审核,Provisioning API 可管理证书和 Profile。ReleaseGuard 不直接代替人工审核,而是把“本地准备完成”变成机器可判断的前置条件。
本轮统一数据:
taskId:
release_accept_20261002_06
candidateVersion:
1.3.0
versionCode:
10300
bundleName:
com.example.releaseguard
artifact:
releaseguard_1.3.0.app
artifactSize:
18.4MB
artifactHash:
4a8d92e1
releaseContextId:
rg_20261002_130_8f4c2d
报告 01:
8 / 8
报告 02:
10 / 10
报告 03:
9 / 9
报告 04:
9 / 9
报告 05:
11 / 11
totalChecks:
47 / 47
blockers:
0
warnings:
0
evidenceFiles:
8
evidenceZip:
releaseguard_1.3.0_evidence.zip
evidenceZipSize:
312KB
connectApiReady:
true
uploadApiReady:
true
publishingApiReady:
true
phasePlan:
5% → 20% → 50% → 100%
gateCost:
486ms
activeContextsAfterFinish:
0
status:
RELEASE_GATE_PASS

一、Release Gate 第一件事不是统计 47/47,而是确认“这是同一个发布上下文”
五份报告最危险的错误不是其中某项失败,而是:
Package Report
来自 A 包
Privacy Report
来自 B 包
Listing Report
又是昨天那版
最后数字可能仍然是全绿。
所以每份报告必须同时带:
releaseContextId
artifactHash
versionCode
bundleName
06 第一步只做 Context 验证。
export interface ReportHeader {
reportType: string
releaseContextId: string
artifactHash: string
bundleName: string
versionCode: number
status: string
}
export class ReleaseContextVerifier {
verify(
reports:
ReportHeader[]
): boolean {
if (reports.length === 0) {
return false
}
const first =
reports[0]
return reports
.every(
report =>
report.releaseContextId ===
first.releaseContextId &&
report.artifactHash ===
first.artifactHash &&
report.bundleName ===
first.bundleName &&
report.versionCode ===
first.versionCode
)
}
}
这一层不通过,后面根本不统计检查数。
二、为什么 releaseContextId 和 artifactHash 两个都要保留
artifactHash 标识:
具体二进制包
releaseContextId 标识:
这次发布上下文
同一个 .app 可能因为市场文案变化重新跑 Listing Audit,但 artifactHash 不变。
这时:
releaseContextId
可以变化。
反过来,文案没改但重新构建了一次包,context 也必须重新绑定新 hash。
所以两个字段承担不同角色,不能合成一个。
三、五份报告最终汇总成 47 项检查
当前:
Package Preflight:
8
Privacy Audit:
10
Listing Audit:
9
Package Report:
9
Version Audit:
11
总计:
47
最终 Gate 只接受:
47 / 47
blockers=0
代码:
export interface GateReport {
checks: number
passed: number
blockers: number
warnings: number
}
export class ReleaseGateRunner {
aggregate(
reports:
GateReport[]
): GateReport {
return reports.reduce(
(
total,
report
) => ({
checks:
total.checks +
report.checks,
passed:
total.passed +
report.passed,
blockers:
total.blockers +
report.blockers,
warnings:
total.warnings +
report.warnings
}),
{
checks: 0,
passed: 0,
blockers: 0,
warnings: 0
}
)
}
}
当前:
47 / 47
blockers=0
warnings=0
四、Gate 通过以后先生成 Evidence Pack,不直接点击发布
我不希望 CI 看见:
PASS
就马上调用发布接口。
中间还有一步:
Evidence Pack
它是本次发布的证据归档。
当前包含 8 个文件:
01_package_preflight.json
02_privacy_audit.json
03_listing_audit.json
04_package_report.json
05_version_audit.json
06_artifact_sha256.txt
07_release_plan.json
08_evidence_index.json
打包后:
releaseguard_1.3.0_evidence.zip
312KB
这份证据包用于:
CI 归档
发布审批
审核退回后复盘
线上问题追溯
五、Evidence Index 要把所有报告和同一个 artifact 绑定
08_evidence_index.json 是证据包的总入口。
export interface EvidenceIndex {
taskId: string
releaseContextId: string
bundleName: string
versionName: string
versionCode: number
artifactName: string
artifactHash: string
reports: {
name: string
hash: string
status: string
}[]
}
export class EvidencePackBuilder {
buildIndex(
reports:
ReportHeader[]
): EvidenceIndex {
return {
taskId:
'release_accept_20261002_06',
releaseContextId:
'rg_20261002_130_8f4c2d',
bundleName:
'com.example.releaseguard',
versionName:
'1.3.0',
versionCode:
10300,
artifactName:
'releaseguard_1.3.0.app',
artifactHash:
'4a8d92e1',
reports:
reports.map(
report => ({
name:
report.reportType,
hash:
hashReport(report),
status:
report.status
})
)
}
}
}
这样任何一份报告被替换,Evidence Index 校验都会失败。
六、Connect API Ready 只代表“自动化条件准备好”
AppGallery Connect 当前 Connect API 支持流程自动化。
但 ReleaseGuard 不把:
API Ready
写成:
已经发布
当前只检查:
Connect API authorization 可用
Upload API 凭据可用
Publishing API 准备状态可用
本轮:
connectApiReady=true
uploadApiReady=true
publishingApiReady=true
真正调用上传和提交仍然属于后续流水线步骤。
七、Upload Management API 和 Publishing API 的职责分开
官方当前 Connect API 文档中:
Upload Management API
负责包、图标、介绍图片、视频等文件上传
Publishing API
负责应用信息、版本信息和提交发布
所以 CI 也不能写成一个巨大:
publish()
更合理的流程:
READY_TO_UPLOAD
→ Upload package
→ Verify upload result
→ Update version metadata
→ Apply staged-release settings
→ Submit for review
每一步都要保留服务端返回结果。
ReleaseGuard 06 只负责进入:
READY_TO_UPLOAD
之前的门禁。
八、Provisioning API 也可以作为签名 / Profile 的自动化数据源
当前 Connect API 还提供 Provisioning API,用于管理证书、Profile 和设备等资源。
ReleaseGuard 后续可以把:
release certificate
release profile
supported devices
从人工配置逐步迁移成 API 同步。
不过本系列到 06 不继续扩实现。
这里仅把接口准备状态纳入:
AutomationReadiness
不会为了“自动化更多”改变本地检查主线。
九、分阶段发布计划继续沿用 05 的策略
Evidence Pack 里包含:
phasePlan:
5%
20%
50%
100%
当前 AppGallery Connect 分阶段发布支持版本更新按比例逐步扩大,且支持暂停、恢复和调整。
ReleaseGuard 仍然把这四段比例当团队策略。
CI 不会擅自把:
5%
直接改成:
100%
必须经过发布审批上下文。
十、Gate 的状态机比一个 boolean 更安全
最终状态不是:
true / false
而是:
COLLECTING_REPORTS
VERIFYING_CONTEXT
BUILDING_EVIDENCE
AUTOMATION_READY
RELEASE_GATE_PASS
RELEASE_GATE_BLOCKED
export type ReleaseGateState =
'COLLECTING_REPORTS' |
'VERIFYING_CONTEXT' |
'BUILDING_EVIDENCE' |
'AUTOMATION_READY' |
'RELEASE_GATE_PASS' |
'RELEASE_GATE_BLOCKED'
这样 CI 日志能看到到底卡在哪一步。
十一、Gate 还要检查报告有没有过期
即使 Context 一致,也可能出现:
报告生成后
隔了两天才准备上传
期间:
证书状态
发布政策
Listing 素材
都有可能变化。
所以团队策略会给每份报告设置:
ttl
超过时间:
REPORT_STALE
必须重新生成。
TTL 是团队策略,不是 AppGallery 平台规则。
十二、DevEco 图里要把“5 份报告同一个 context”标出来
开发图:

HiLog:
taskId=
release_accept_20261002_06
version=
1.3.0(10300)
context=
rg_20261002_130_8f4c2d
artifactHash=
4a8d92e1
reports=
5
checks=
47/47
blockers=
0
warnings=
0
evidenceFiles=
8
evidenceZip=
releaseguard_1.3.0_evidence.zip
size=
312KB
connectApi=true
uploadApi=true
publishingApi=true
gateCost=
486ms
status=
RELEASE_GATE_PASS
这里真正重要的是:
所有数字
都绑定同一个 context 和 artifact
十三、运行图只展示最终发布负责人真正关心的内容
最终运行图:

五份报告:
8 / 8
10 / 10
9 / 9
9 / 9
11 / 11
总计:
47 / 47
证据包:
8 个文件
312KB
自动化接口:
Connect
Upload
Publishing
全部 ready
最终:
RELEASE_GATE_PASS
手机页不会再展示每一条底层权限和 hash。
因为 06 是最终总览,不是重复前五篇。
十四、activeContextsAfterFinish 必须回到 0
CI 任务结束以后:
ReleaseContext
EvidenceBuilder
TempReportReader
API Auth Session
都应该释放。
本轮:
activeContextsAfterFinish=0
这是发布工具自己的资源收口。
不然长时间构建节点会逐渐积累临时上下文和文件句柄。
十五、Gate 失败时不能自动“跳过一项继续”
如果:
Privacy Audit
9/10
最终总分:
46/47
也不能进入上传步骤。
Release Gate 的意义就是:
有 Blocker
→ 明确停止
不允许:
先发再说
Warning 可以由团队审批策略决定是否允许继续,但 Blocker 必须机器强制阻断。
十六、审核证据包还要包含“谁批准了什么”
真实发布流程通常不只 CI。
还会有:
开发负责人
测试负责人
产品 / 运营
隐私 / 合规
Evidence Index 可以继续增加:
approval role
approvedAt
approvedReportHash
这样审核退回时,不是靠聊天记录回忆谁看过。
06 当前先实现证据结构,审批系统可以后续接入。
十七、Connect API 自动化不等于删除人工审核
官方 Connect API 的价值是:
流程自动化
不是:
省略所有人工确认
最合理的分工是:
机器:
重复检查
文件上传
状态同步
证据归档
人:
素材准确性
隐私文字
版本策略
灰度决策
最终发布确认
ReleaseGuard 整个系列都保持这个边界。
十八、最终全链路验收固定六组故障注入
正常 47/47 以外,06 会故意制造:
1 五份报告 releaseContextId 不一致
2 artifactHash 不一致
3 Privacy Report 只有 9/10
4 Evidence Pack 缺文件
5 Upload API authorization 不可用
6 报告过 TTL
任何一项都会进入:
RELEASE_GATE_BLOCKED
并且明确写出:
blockedStage
不允许只返回:
false
十九、RELEASE_GATE_PASS 的最终含义
这个状态代表:
候选包身份正确
权限与隐私已检查
商店文案和素材已确认
构建后包体已解析
版本升级差异已审计
签名身份连续
灰度计划已准备
证据包完整
自动化接口已就绪
所有报告属于同一个 artifact
它仍然不代表:
AppGallery 审核必然通过
服务端审核仍可能有本地无法完全复制的规则和人工审核判断。
ReleaseGuard 的目标从来不是“预测审核结果”。
而是:
让每次提交审核以前,团队能证明自己已经完成一套稳定、可追溯、可重复的发布检查。
二十、ReleaseGuard 系列到 06 正式结束
这个系列固定 X=6,到这里收口。
六篇最终形成一条完整主线:
01
包名 / 版本 / 签名
02
权限 / 隐私 / SDK
03
商店文案 / 图标 / 截图 / 分级
04
包体 / deviceTypes / Profile 权限
05
历史版本 / 签名连续 / 分阶段发布
06
CI Gate / Evidence Pack / Connect API Ready
下一轮应该切到明显不同的技术方向和新 Demo,从新的 01 开始。
继续写 ReleaseGuard 07,大概率只是在重复“再加一条审核规则”,已经没有新的工程主矛盾。
二十一、Evidence Pack 里还要有“人工确认项”和“机器确认项”两套来源
47 项检查并不意味着 47 项都能完全自动判断。
例如:
bundleName
artifact hash
权限子集
字符长度
适合机器。
而:
截图是否真实代表当前功能
隐私文字是否准确
灰度阶段是否继续扩量
仍然需要人。
所以 Evidence Index 会给每个检查项增加:
source:
MACHINE
HUMAN_APPROVAL
这样后续审计不会把“人工确认过”误解成“程序算出来的”。
发布工程真正成熟的标志,不是所有事情都自动化,而是:
哪些由机器保证,哪些由人负责,边界清楚而且有证据。
二十二、CI 里最好把 Release Gate 设计成不可绕过的依赖
如果 Gate 只是一个可选 job:
release-check
开发者仍然可以直接手工触发 upload。
那它很快就会变成“有空才跑”。
更合理的是流水线依赖:
BUILD
↓
RELEASE_GATE
↓
UPLOAD
↓
SUBMIT_REVIEW
Upload job 必须读取:
gateResult=PASS
evidenceIndexHash
releaseContextId
三个字段。
任一缺失:
UPLOAD_BLOCKED
这才叫真正的门禁。
二十三、上传步骤开始后,服务端结果也要回写到 Evidence Pack
本地证据包不是发布链终点。
后续如果调用 AppGallery Connect API:
上传包
更新版本信息
提交审核
每一步的服务端 requestId / result / time 都应该继续追加到发布记录。
例如:
uploadRequestId
packageParseResult
submitReviewRequestId
reviewState
这样 Evidence Pack 最终可以从“本地准备证据”演进成:
完整发布审计记录
本系列到 06 只完成 READY_TO_UPLOAD 前的门禁,但接口已经留好后续位置。
二十四、Connect API 凭据不能进入 Evidence Pack 明文
自动化准备状态会检查:
authorization available
但不会把:
client secret
token
private key
写进证据包。
Evidence 里只保存:
credentialRef
authorizationCheckResult
checkedAt
敏感凭据仍由 CI Secret / 安全凭据系统管理。
这是自动化发布很容易踩的安全边界。
“为了可追溯”不等于“把所有东西都归档”。
二十五、Evidence Pack 本身也需要 hash
当前:
releaseguard_1.3.0_evidence.zip
312KB
CI 会在生成后计算:
evidencePackSha256
并和:
artifactSha256
releaseContextId
一起写入最终 Release Gate Result。
这样审批以后如果有人替换了其中一份报告:
zip hash
会立即变化。
证据本身也要有完整性证明,否则“报告可追溯”只是文件名看起来很正式。
二十六、报告 TTL 需要和构建节点时间统一
多个构建节点如果系统时间不同,TTL 很容易出现误判。
所以报告生成和过期判断统一使用 CI Server 的时间源。
不使用手机模拟器本地时间,也不使用开发机手工时间。
最终每份报告至少有:
generatedAt
expiresAt
timeSource=CI_SERVER
这样“报告是否过期”才是可重复的机器判断。
二十七、47/47 之外还要看“报告数量正好是 5”
如果少了一份报告:
Package Preflight
Privacy Audit
Listing Audit
Package Report
Version Audit
哪怕剩下四份全部满分,也不能继续。
所以 Gate 首先验证:
expectedReportTypes
集合完整。
不能单纯把已有报告的 passed 相加。
这也是 Evidence Index 必须列出 reportType 的原因。
二十八、Release Gate 要输出适合审批系统读取的摘要
最终 CI 输出不应该是一大串日志。
至少要生成:
{
"status": "RELEASE_GATE_PASS",
"version": "1.3.0",
"versionCode": 10300,
"checks": "47/47",
"blockers": 0,
"warnings": 0,
"evidencePack": "releaseguard_1.3.0_evidence.zip"
}
审批系统、企业 IM 机器人或发布看板都可以直接读取。
这样工具链真正完成从:
开发者自己看日志
到:
团队发布流程消费结构化结果
的升级。
二十九、06 最后还要做一次“空目录重跑”
CI 最容易被历史缓存影响。
所以最终验收除了普通流水线,还会做一次:
全新工作目录
无旧报告
无旧 artifact
无本地缓存
的完整重跑。
要求仍然得到:
47/47
312KB Evidence Pack
RELEASE_GATE_PASS
如果只在开发机“跑过很多次的工作目录”里通过,不能算真正可复现。
三十、从 01 到 06,ReleaseGuard 真正解决的是“发布上下文失控”
表面上六篇分别在检查:
包
隐私
素材
Profile
版本
CI
更深一层,其实一直在解决同一个问题:
同一次发布里,代码、包体、商店资料、隐私资料、签名身份和版本策略,必须始终属于同一个 release context。
所有 Snapshot、hash、revision、Evidence Pack 和 Gate,最终都是为了让这条上下文不被人肉流程打散。
所以系列最后停在 RELEASE_GATE_PASS 很合适。
继续扩下去,重点已经不再是 HarmonyOS 上架工程,而会进入企业审批系统、DevOps 平台和组织流程设计。
三十一、RELEASE_GATE_PASS 只允许被当前流水线消费一次
最终 Gate 结果还带一个一次性消费状态。
Upload job 成功读取 RELEASE_GATE_PASS 并校验 Evidence Pack 后,会把本次 gate 标记为 CONSUMED。如果有人再次拿同一份 Gate Result 去上传另一个产物,即使版本号相同,也会被拒绝。
这样可以避免“同一张通行证被重复使用”。发布上下文、artifactHash、Evidence Pack 和 Gate Result 四者形成一一对应关系,真正把一次发布动作收成可追踪、不可随意复用的完整事务。
参考资料
- AppGallery Connect API:Service Introduction
https://developer.huawei.com/consumer/fr/doc/AppGallery-connect-Guides/agcapi-overview-0000001158245083 - AppGallery Connect:Phased Release
https://developer.huawei.com/consumer/cn/agconnect/phased-release - AppGallery Connect:Version History
https://developer.huawei.com/consumer/fr/doc/app/agc-help-maintain-version-history-0000002236334582
更多推荐




所有评论(0)