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
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐