Skip to content

feat(release): add guarded beta and stable pipeline#620

Merged
PeterGuy326 merged 13 commits into
DingTalk-Real-AI:mainfrom
PeterGuy326:codex/release-guardrails-v1
Jul 16, 2026
Merged

feat(release): add guarded beta and stable pipeline#620
PeterGuy326 merged 13 commits into
DingTalk-Real-AI:mainfrom
PeterGuy326:codex/release-guardrails-v1

Conversation

@PeterGuy326

@PeterGuy326 PeterGuy326 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Requirement and problem

Release publication was split across raw tag pushes and legacy GitHub/npm/Gitee helpers. That left no single fail-closed path proving that a tag came from sealed main, passed the repository CI Gate, preserved command compatibility, produced the exact six-platform artifact set, and advanced npm/mirror channels without moving immutable authority.

This PR supersedes #607 and rebases the guarded release design onto the current main.

Goal and expected behavior

  • Maintainers use one dws-release entrypoint for changelog preparation, validation-only preflight, and annotated-tag publication.
  • A beta can publish only from sealed main; stable can promote only an explicitly verified beta of the same version line.
  • GitHub Release assets, npm package bytes/channels, OSS metadata, Homebrew Formula PRs, and the optional Gitee mirror are verified and fail closed.
  • Existing current-main CI, Schema, coverage, Homebrew, and Gitee-resilience gates remain intact.

Non-goals: this PR does not publish a release, change the public CLI wire contract, or waive an incompatible command removal.

Implementation

  • Adds scripts/release/dws-release.sh, release.sh, release-contract.sh, shared release helpers, changelog preparation, artifact/npm/tag verification, and the operator guide in docs/releasing.md.
  • Replaces direct GoReleaser/npm publication with a serialized tag workflow that validates annotated tag authority, sealed main, immutable releases, successful CI Gate, beta-to-stable promotion, exact artifacts, and idempotent recovery.
  • Integrates the current multi-platform Homebrew Formula PR flow and the bounded Gitee reconciliation path; direct local Gitee builds/publication and the legacy manual Gitee workflow remain disabled.
  • Adds command-surface snapshot comparison, CLI and mock-MCP smoke coverage, release-script regression tests, and an Actionlint job pinned to v1.7.12 in the required CI Gate.

Acceptance criteria and validation

ID Acceptance criterion Exact validation Expected result Observed result
V1 Release helpers, failure paths, packaging, Homebrew, and Gitee integration are covered DWS_PACKAGE_VERSION=0.0.0-test go test -count=1 -timeout=10m ./test/scripts/... exit 0 PASS, exit 0 (336.397s)
V2 Interface snapshot and CLI/MCP smoke behavior work DWS_PACKAGE_VERSION=0.0.0-test go test -count=1 -timeout=10m ./cmd/interface-snapshot ./internal/interfacesnapshot ./test/smoke ./test/mock_mcp exit 0 PASS, all four packages
V3 Candidate is compatible with current main and delivered stable TMPDIR="$PWD/.worktrees/pr620-review-tmp" ./scripts/policy/check-command-compatibility.sh --base-ref origin/main --stable-ref v1.0.52 no blocking removals/changes PASS; current main compatible, stable compatible with only five #624 command additions
V4 Repository policy and generated contracts remain clean make policy exit 0 PASS; 568 tools, 717 Agent examples, generated drift clean
V5 Full Go suite passes in the local environment DWS_PACKAGE_VERSION=0.0.0-test go test -count=1 -timeout=15m -skip '^TestValidateNewBinary_RecoversFromUnsignedDarwin$' ./... exit 0 PASS, exit 0
V6 Static Go checks pass go vet ./... exit 0 PASS
V7 Every workflow passes the configured linter go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 exit 0 PASS
V8 Patch formatting is clean git diff --check origin/main...HEAD no output, exit 0 PASS

Environment

  • macOS Darwin 24.5.0, arm64
  • Go 1.25.9 (from the repository toolchain), Node 24.13.0, npm 11.6.2, Ruby 2.6.10, GitHub CLI 2.89.0
  • Base: e1a50f08a6fce875d8c2e7f8e38fe3bd3a44148a; validated stable baseline: v1.0.52
  • Local tests use repository fixtures and do not require release, npm, OSS, Apple-signing, or Gitee credentials. Official tag publication requires the documented repository secrets and immutable-release governance.

Limitations and rollout

  • The Darwin unsigned-binary recovery test is intentionally skipped locally because it invokes an external cloud-signing path; required GitHub macOS/Windows/Linux CI remains the cross-platform authority.
  • No real tag, GitHub Release, npm publication, OSS upload, Homebrew PR, or Gitee upload is performed by this PR validation. Those mutations occur only after merge through the guarded tag workflow.
  • Rollback before any future tag is a normal revert. Published tags/releases are immutable and must never be moved or recreated.

Changelog and security

@PeterGuy326
PeterGuy326 marked this pull request as ready for review July 15, 2026 16:01
@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

@sczheng189 @audanye-sudo #620 已基于最新 main 解完冲突,保留了现有 CI/Homebrew/Gitee 门禁并补齐 guarded beta/stable pipeline;本地全量验证和 GitHub CI Gate 均已通过。麻烦任一位帮忙做独立 review,批准后即可按仓库规则合入并继续 beta。

@shangguanxuan633-lab shangguanxuan633-lab left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:Request changes

PR #620 建立的 guarded beta/stable release pipeline 整体设计严谨,tag authority、immutable release、CI Gate 校验、npm channel 保护、Homebrew PR 流程等核心机制都正确。但 OSS/Gitee 镜像脚本存在 secret 暴露面,第一次 beta 发布就会执行这些脚本,必须在合入前修复。


High(必须修复)

  1. scripts/release/sync-to-oss.sh:108-124
    oss_cp / oss_getOSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET 作为 ossutil cp 的命令行参数传递。应改为环境变量或临时配置文件传入。

  2. scripts/release/sync-to-gitee.sh:129-144 / scripts/release/reconcile-gitee-assets.sh:142-145,215-217,243-244
    GITEE_TOKEN 通过 URL 查询参数 access_token=... 或 multipart form 字段传递,会被代理/服务端日志/错误页缓存。应改用 Authorization: token ${GITEE_TOKEN} 头部。

  3. scripts/release/sync-gitee-tag.sh:52-61
    构造 https://${GITEE_USER}:${GITEE_TOKEN}@gitee.com/... 作为 git remote,git remote -v、报错输出或 coredump 都可能泄露 token。应改用 GIT_ASKPASS 脚本提供凭据。


Medium(建议修复)

  • scripts/release/sync-to-gitee.sh:131-145:用 grep -o '"id":[ ]*[0-9]*' 解析 Gitee release id,嵌套对象含 id 时会取错,建议用 jqpython3 精确读取顶层 id
  • scripts/policy/check-schema-catalog.sh:196-232:参数绑定 jq 查询在 jq 1.8.1 下报 Cannot iterate over boolean (true),加显式括号后可跑通;CI 使用 jq 1.6 已通过。建议提升本地兼容性。
  • 两套命令树兼容性闸门规则不一致:CI interface-integrity 仍使用旧的 interface-baseline 文字格式(含 dws help/completion-h/--help),发布流程的 check-command-compatibility.sh 排除这些框架噪声,建议统一或文档化。
  • Schema 版本前向兼容风险check-command-compatibility.sh 用 baseline tag 自身的 helper 生成旧快照,再用候选 helper 的 Read()(严格校验 SchemaVersion == 1)读取;未来 schema 升级到 v2 后旧 stable 快照会被拒绝,导致发布失败。
  • test/scripts/release_script_test.go:926-949:把 check-command-compatibility.sh 打桩为 exit 0,未真正验证新的快照对比流程。

Low(可选改进)

  • scripts/release/release.sh:80 直接输出 push_url,若 URL 嵌入了凭据会泄露。
  • .github/workflows/release.yml:44-50 用未认证 HTTPS 拉取官方 tags,可能触发 GitHub 速率限制。
  • scripts/release/publish-homebrew-formula.sh:88-99 把 token 写入临时 GIT_ASKPASS 文件(权限 700),可考虑 gh auth setup-git

验证证据

检查项 结果
go vet ./... 通过
actionlint 通过
gh pr checks 620 CI Gate / Policy / Interface Integrity / CLI Smoke / Mock MCP Smoke 等已通过
check-command-compatibility.sh --base-ref <merge-base> --stable-ref v1.0.52 compatible: true,新增 5 个命令
go test ./test/scripts/... 通过
go test ./cmd/interface-snapshot ./internal/interfacesnapshot ./test/smoke ./test/mock_mcp 全部通过
make policy 本地 jq 1.8.1 失败(已知兼容性问题),CI jq 1.6 通过

回归建议

修复 High/Medium 项后请重点跑:

make policy
./scripts/policy/check-command-compatibility.sh --base-ref <merge-base> --stable-ref v1.0.52
DWS_PACKAGE_VERSION=0.0.0-test go test -count=1 -timeout=10m ./test/scripts/...
go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12

修复 3 个 High 项后即可 Approve,无需重新 review 全量。

@PeterGuy326
PeterGuy326 force-pushed the codex/release-guardrails-v1 branch from 19dbebe to ceb47c7 Compare July 16, 2026 02:16
@PeterGuy326

PeterGuy326 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

@shangguanxuan633-lab @sczheng189 评审里的 3 个 High 已在 ff4961eb 全部处理:OSS AccessKey 只走 ossutil 官方环境变量、不再进入 argv;Gitee API token 改为 Authorization: token,不再出现在 query/form;Gitee tag push 使用无凭据 remote + 临时 GIT_ASKPASS。同时补了 token 不进 git/ossutil argv、不外带到下载 URL、API 只接受 header 的回归,并顺手把 release id 改为精确解析顶层 id。本地 go test ./test/scripts/...go vet ./...、actionlint、make policy 全部通过,GitHub CI 正在跑。麻烦 CI 绿后复审解除 changes requested;郑御白也请帮忙做独立 review。

@sczheng189 sczheng189 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核结论:@shangguanxuan633-lab 的 Request changes 在当前 HEAD 仍然成立,未修复

对比范围:e615bd433caa9254685303f7cff8f6f06fb8e09e(当前 main).. ede677e413baa7e26e0b4035882b9d177279c237(当前 PR head,本地已 checkout 核对一致)。

自 review 提交时的 commit 19dbebe7a6a87b126d6088190bce7fbc409d52b6 之后新增的 3 个 commit(fix(release): rebase guardrails onto sealed main 系列 + fix(ci): align interface coverage and baseline)只是同步了 main 上其他 PR(#592 shortcut、#629 CI hardening 等)的内容,没有触碰 scripts/release/sync-to-oss.sh / sync-to-gitee.sh,因此下面两项 High 仍原样存在:

  1. scripts/release/sync-to-oss.sh oss_cp/oss_getOSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET 仍作为 ossutil cp 的命令行参数传递,ps/进程列表可见。已在当前 HEAD 逐行核对,代码位置未变。
  2. scripts/release/sync-to-gitee.shGITEE_TOKEN 仍以 ?access_token=${GITEE_TOKEN} URL 查询参数和 -F "access_token=${GITEE_TOKEN}" multipart 字段传递,同样未改。
  3. Medium 项里提到的 grep -o '"id":[ ]*[0-9]*' 解析 Gitee release id 的写法也还在。

reconcile-gitee-assets.sh / sync-gitee-tag.sh 里的问题经核实是 main 上已存在的旧代码,本 PR 未修改这两个文件,但本 PR 把 Gitee 路径正式接入了自动发布流水线(PR 描述里"Integrates ... the bounded Gitee reconciliation path"),等于扩大了这些既有问题的暴露面,建议一并处理或至少在描述里注明。

独立验证(本地核对,非仅凭 CI 绿灯)

  • go vetactionlintcheck-command-compatibility.shgo test ./test/scripts/...go test ./cmd/interface-snapshot ./internal/interfacesnapshot ./test/smoke ./test/mock_mcp 等我在上一轮已抽样跑过,internal/securityinternal/keychain 单独跑通;internal/auth 因 sandbox 网络限制(拉不到 dingtalk-stream-sdk-go 依赖)未能本地复现,需信任作者/CI 的记录。
  • 另外补充一个我自己审查到、跟本次 request changes 无关但同样值得处理的点:internal/security/crypto.go(Encrypt/Decrypt)和 internal/keychain/keychain.go(encryptData/decryptData)为了消除"不可达分支"覆盖率缺口,把 aes.NewCipher/cipher.NewGCM* 的错误检查改成了 _ 静默丢弃。当前常量下确实不会触发,但如果以后 KeySize/NonceSize 改动出错,会从"返回清晰错误"退化成"nil 指针 panic"。建议保留错误检查或改成显式 panic/断言。

结论

维持 Request changes:3 个 High/Medium 安全项(secret 通过命令行参数/URL query 传递)在当前 HEAD 未修复,建议按 @shangguanxuan633-lab 的建议改成环境变量/配置文件传递密钥后再合入。

@shangguanxuan633-lab shangguanxuan633-lab left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:Request changes(当前 head: ede677e

复审了 PR #620 当前 head。CI Gate 已全绿,本地 actionlint 和 release/interface 相关测试也通过;但上次提出的 secret 暴露面仍然存在,不是冲突或测试问题,必须合入前修。

Blocking findings

  • [P1] scripts/release/sync-to-oss.sh:108-124 OSS AK/SK 仍通过进程命令行参数传递
    证据:oss_cp / oss_get 仍执行 ossutil cp --access-key-id "$OSS_ACCESS_KEY_ID" --access-key-secret "$OSS_ACCESS_KEY_SECRET" ...
    影响:GitHub runner 上同机进程、调试输出、失败诊断、进程快照都可能暴露 OSS 凭据;这个 job 在正式发布镜像阶段会执行。
    建议:改为 ossutil 支持的环境变量/临时 config,并确保临时文件 chmod 600 且 trap 清理;不要把 secret 放入 argv。

  • [P1] scripts/release/sync-to-gitee.sh:129-145scripts/release/reconcile-gitee-assets.sh:142-145,215-217,241-244 Gitee token 仍走 URL query / multipart form
    证据:release lookup/delete 使用 ?access_token=${GITEE_TOKEN},create/upload 使用 -F "access_token=${GITEE_TOKEN}"
    影响:token 进入代理/服务端访问日志、错误页、curl 调试和表单日志的概率明显高于 header;该路径是 beta/stable 发布镜像的主链路。
    建议:统一封装 gitee_curl,使用 Authorization: token ${GITEE_TOKEN} header;release id 解析同时改为 python3/jq 读取顶层 id,避免 grep 命中嵌套字段。

  • [P1] scripts/release/sync-gitee-tag.sh:52-57 Gitee git remote 仍内嵌 token
    证据:默认构造 https://${GITEE_USER}:${GITEE_TOKEN}@gitee.com/${GITEE_REPO}.git
    影响:git remote -v、git 报错、trace、诊断 dump 或后续脚本输出都可能泄露 token;该脚本由 release mirror 主流程调用。
    建议:remote 保持公开 URL,用 GIT_ASKPASS/credential helper 注入用户名和 token;同时避免在日志输出 remote。

Non-blocking notes

  • scripts/release/sync-to-gitee.sh:131,145 仍用 grep -o '"id":[ ]*[0-9]*' 抽 release id,建议改成结构化 JSON 解析。
  • make policy 在本地 jq 1.8.1 仍会触发已知 Cannot iterate over boolean (true),CI jq 1.6 已过;可单独后续修。

Verification evidence

  • GitHub PR checks:17 successful,1 skipped,0 failing,CI Gate 成功。
  • go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12:通过。
  • go test ./test/scripts/... ./cmd/interface-snapshot ./internal/interfacesnapshot -count=1:通过。

这不是“CI 绿就能放”的问题;CI 没有覆盖 secret 出现在 argv/query/form/remote URL 的发布时暴露面。修掉以上 3 个 P1 后我可以快速复审并转 Approve。

@shangguanxuan633-lab shangguanxuan633-lab left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:Approve(当前 head: ff4961eb

No blocking findings.

复审了本轮针对 release mirror credential transport 的修复,之前阻塞的 3 个 High 安全问题已经闭环:

  • scripts/release/sync-to-oss.shossutil cp 不再通过 --access-key-id / --access-key-secret argv 传 OSS AK/SK,改为依赖环境变量。
  • scripts/release/sync-to-gitee.sh / scripts/release/reconcile-gitee-assets.sh:Gitee API 不再通过 URL query 或 multipart form 传 access_token,已改为 Authorization: token ... header。
  • scripts/release/sync-gitee-tag.sh:默认 Gitee remote 不再内嵌 token,已改为公开 remote + GIT_ASKPASS 注入凭据。
  • scripts/release/sync-to-gitee.sh:Gitee release id 改为解析顶层 id,不再用 grep 命中嵌套字段。

验证证据:

  • GitHub PR checks:17 successful,1 skipped,0 failing,CI Gate 成功。
  • go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12:通过。
  • go test ./test/scripts/... ./cmd/interface-snapshot ./internal/interfacesnapshot -count=1:通过。

Residual risk:Gitee/OSS 真实发布链路仍依赖 repo secrets 和外部服务可用性;本次 review 只确认代码层 credential transport、解析逻辑和本地/CI 门禁。可以合入发 beta。

@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

@sczheng189 麻烦基于当前 HEAD ff4961eb 再复审一次。你上一轮 review 的正文核对范围停在 ede677e,而当前 HEAD 新增了 fix(release): harden mirror credential transport:OSS 凭据已移出 argv,Gitee token 已改为 Authorization header,Gitee remote 已改为公开 URL + GIT_ASKPASS,release id 也改为解析顶层字段。@shangguanxuan633-lab 已基于同一 HEAD 复核并 Approve,CI 17 项成功、0 失败。当前仍被你上一条 Request changes 阻塞,请确认后解除;主干刚合入 #637/#632,解除后我会再做一次最新主干兼容性核对。

@PeterGuy326
PeterGuy326 requested a review from sczheng189 July 16, 2026 03:32

@sczheng189 sczheng189 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

验证

  • 逐条对照此前 @shangguanxuan633-lab 和我自己指出的 3 个 High + 1 个 Medium 安全问题,核对新 commit ff4961e fix(release): harden mirror credential transport 的实际改动是否覆盖:
    1. sync-to-oss.sh:OSS AK/SK 命令行参数 → 已改为环境变量传递
    2. sync-to-gitee.sh:GITEE_TOKEN URL query/form → 已改为 Authorization: token header
    3. reconcile-gitee-assets.sh(此前"PR 未改但已激活"的既有问题)→ 同样改成 header,三处 API 调用(list/delete/attach)全部覆盖
    4. sync-gitee-tag.sh(同样是既有问题)→ git remote 不再拼 user:token@ 明文,改用 GIT_ASKPASS
    5. Gitee release id 解析(Medium 项)→ grep -o 换成 python3 json.load,只取顶层 id

复现 / 功能复现

  • 真实复现:本地拉取新 commit 后跑了 3 个新增回归测试(TestSyncGiteeTagUsesAskpassWithoutEmbeddingCredentialsTestSyncToGiteeRunsTagReleaseCreationAndAssetReconciliationWithinOneBudgetTestGiteeReleaseWorkflowUsesImmutableTagsAndBoundedRetryBudget),全部 PASS。
  • 关键验证步骤:把 sync-gitee-tag.sh 临时替换回旧的(有漏洞的)版本,重跑 TestSyncGiteeTagUsesAskpassWithoutEmbeddingCredentials,测试如预期失败(DWS_GITEE_TOKEN: unbound variable)。这一步证明新增的回归测试是真实生效的防护,不是摆设式的假测试。
  • gh pr checks 620 确认 CI 全绿。

结论

APPROVE —— 三个 High 级安全问题全部修复并配有能真实拦截回归的测试(已验证 revert 后会失败),修复质量高。此前提的 crypto.go/keychain.go 错误检查被静默丢弃等代码风格问题仍存在,但不阻塞本次修复。

@PeterGuy326
PeterGuy326 requested a review from sczheng189 July 16, 2026 04:17
@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

@sczheng189 麻烦对最终 head 快速确认 #620:已用 merge commit 同步最新 main@605d9360,当前 head f9b37486,无冲突且 release guardrail 功能 diff 保持不变。\n\n已复验:test/scripts/... 全套 PASS(311s);interface snapshot / compare / CLI smoke / mock MCP 四包 PASS;与最新 main 及稳定版 v1.0.52 的 command compatibility 均为 compatible: true、无 blocking changes;diff-check PASS。远端最终 CI 已启动,麻烦绿后确认该最终 head。

@PeterGuy326
PeterGuy326 merged commit 536fd66 into DingTalk-Real-AI:main Jul 16, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants