Skip to content

fix(release): resume validated publication without replacing assets - #7847

Merged
huangruiteng merged 1 commit into
mainfrom
codex/release220-upload-recovery-20261101
Nov 1, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/release220-upload-recovery-20261101

Conversation

@loopx-agent

Copy link
Copy Markdown
Collaborator

Problem and result

A release upload can publish several immutable files and then fail on a name collision. Retrying the whole upload fails on those existing files, leaving PyPI publication skipped despite a successful build. GitHub also treats asset names case insensitively, so a separately uploaded QUALIFICATION.json conflicts with the workflow's qualification.json.

This change checks all existing assets against downloaded bytes before uploading only missing files. It never deletes, replaces, or clobbers an asset. A publication-only dispatch may reuse the successful build from the original tagged release event: repository, workflow, tag, source, successful build job and every artifact's source-bound attestation are verified before publication. Product bytes and the original tag stay unchanged. PyPI continues to use Trusted Publishing and the existing environment policy.

The existing release artifact owner supplies this internal publication helper; there is no new product capability or configuration. The normal build and qualification path remains intact. Release status overviews use RELEASE-QUALIFICATION.json, reserving qualification.json for the attested runtime receipt.

Validation

  • 14 focused tests passed: missing-only upload, byte mismatch and case collision with zero upload, rejected source/run/build substitutions, attestation failure, and symlink rejection.
  • Existing release-artifacts smoke, Ruff and workflow YAML parsing passed.
  • Actual original release run/source/build identity verified through the helper; actual partial-upload collision is used for read-only preflight validation.
  • Public boundary scan: five files, zero errors or warnings. No runtime, model, benchmark or installed-user behavior changes; no broad model/test matrix rerun.

Publication recovery itself is qualified by the official dispatch after review and merge. This PR changes tooling only; it does not move a product tag or relax the PyPI environment.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; declaration_source=runtime_reported; reasoning_effort=max

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

代码评审结论:APPROVE,未发现阻塞问题。评审精确 head 36259b632c51ee2744a43b5c416566f81ef291b8;本地检查与独立 CLI 反例支持这次恢复机制。reviewer 工作区 premerge 的历史 stale CQR 保留;作者补交后,已用原生 verifier 独立确认交付工作区的当前精确 receipt 有效。真实恢复发布、PyPI 和合并结果没有由本次评审认证。

动机 / Motivation

发布维护者在完整构建已完成、GitHub Release 只上传部分文件后恢复发布时需要安全恢复。旧上传步骤把全部文件再次交给 gh release upload,遇到已有名称即失败;重跑构建又重复已完成的资格验证。恢复操作被文件重名卡住,重复构建增加时间和操作成本;覆盖旧资产会破坏已发布字节的可信身份。同一部分发布状态下,旧命令撞上已有文件失败;新 CLI 先核验所有已存在文件的实际下载字节,再只提交缺失文件。独立真实 CLI 子进程对照验证了这一恢复行为及失败时零上传。本次不移动产品 tag,不放宽发布环境,不加入长期 PyPI token,不触发模型调用、发布或合并,也不新增产品能力或界面。这是已有发布工具的 justified_increment;本次未触发真实恢复 workflow、PyPI 发布或全矩阵重建;实际发布资格由现有 release owner 继续回读。

改动思路 / Approach

决定保留现有 release helper 的完整预检和缺失补传边界:身份、校验和与上传在一个 owner 中,外部签名和运行事实仍由 GitHub 提供。当前 PR 交付已有发布工具的恢复路径,不改变产品 tag、环境授权或产品能力;真实恢复发布仍由 release owner 执行并验收。普通发布仍先 build;只有给出 validated_run_id 才跳过重复构建。恢复先绑定原仓库、release 事件、workflow、tag、commit 和 successful build,再下载该运行的制品。attestation 是加密签名的来源声明:它绑定制品摘要、签名 workflow 与源码身份,不能单独证明整个发布成功。

缺失列表来自 GitHub 当前资产,已存在文件以实际下载内容计算摘要;所有预检完成后才上传缺失项。相比只改一次文件名,这个边界也处理一般部分上传;相比 --clobber,它保留已发布字节。PyPI 只依赖成功的 upload-release,受保护 environment、Trusted Publisher 和原有显式开关不变。现有规范已经要求 tag-only 环境使用单独的已评审 release-tooling tag;这次无需移动产品 tag 或放宽环境策略。

具体改动 / Changes

独立依据是 docs/product/release-readiness.md,spec_revision 035f7ac666cef390676c00b6fb0537ede3c9cf44 的既有约定,未把本 PR 新增段落当作它自己的验收证明。Named Version Contract:implemented,冻结产品 tag/版本、保留 checksum 与 provenance;RELEASE-SOURCES.json:implemented,产品身份与工作流工具来源分离,恢复不重建或覆盖既有产品字节;PyPI Trusted Publishing:implemented,保留受保护环境和 publisher 权限。第三项的 workflow/cloud 执行结果仍须由发布 owner 实际验收。

关键代码讲解

  1. verify_publication_run 在读取原运行前校验十进制 ID,再对结构化事件、仓库、路径、源码与 tag 做精确比较;唯一 build 必须成功。没有用 PR CI 状态代替这一制品来源约束。
  2. publication_files 复用已有 distribution identity 和 checksum owner,拒绝 symlink、非普通文件和忽略大小写后的重名,避免 shell 通配符把冲突推迟到上传阶段。
  3. upload_immutable 的恢复模式验证每个输入的来源签名;两种模式都遍历全部现有文件,比较精确名称、大小和下载后的 SHA-256,最后才执行一次 missing-only upload。--dry-run 和全部已存在的状态都没有写操作。
  4. upload-release / publish-pypi 显式使用 !cancelled(),让故意 skipped 的 build 不因隐含 success() 把恢复链一起跳过;PR、取消、upload 失败和关闭 PyPI 开关仍不会发布。这个判断依据 GitHub needs 规则 与 状态函数规则,并结合完整 workflow 源码审查;未宣称已运行云端恢复 workflow。

共五文件 +301/-19:既有 helper 增加约100行、workflow 调整、聚焦测试和恢复文档。qualification.json 留给已签名运行资格文件,概览改用 RELEASE-QUALIFICATION.json,不能把二者混为同一来源证据。

对主干的风险 / Risks

最强反例是先找到缺失项,后发现另一已发布文件实际字节被替换。如果边遍历边上传,测试可能漏掉部分写入。我通过真实源 CLI 和真实子进程边界,只替换外部 gh 服务,验证这类次序:不匹配、大小写冲突、重复名称、错误签名、伪造源码、checksum 错误都在任何上传前失败。现有测试的成功 mock 不能证明签名参数有效,所以另下载实际 v2.2.0 SHA256SUMS,使用真实 gh attestation verify 校验 repo、workflow、完整 source digest、tag ref 和 GitHub-hosted runner;验证成功,证书确认为 release 事件及 2c3524857e8a3dd553335b79444333786f492c43。这是该 checksum 文件的实际验证,未冒称全部九个文件都由我重新验证。

验证 结果与边界
当前 publication tests PASS,14 项
独立真实 CLI / 子进程反例 PASS,17 项;外部服务合成,旧 raw upload 与新 missing-only 同状态对照
release smoke、Ruff PASS
standard premerge 代码检查 PASS,直接检查及19项 catalog/risk/boundary 执行均无失败
CQR 与 premerge reviewer 工作区原 premerge exit1 / stale 保留;随后原生 verify 确认交付工作区当前 cqr_081a822f700d730a020c 有效,未复制或继承旧 receipt
云端恢复 dispatch、PyPI continuation UNVERIFIED,本次未执行
PR CI UNVERIFIED,未查询、轮询或等待

恢复开关关闭时仍走正常 build 和同运行下载;远端字节预检则有意作用于普通上传与恢复两种模式,因此不声称整个上传行为完全保持旧默认。新增严格行为已在文档、步骤名和测试中披露;另补验同一 Release 的无关资产不被访问,新增 runtime 文件仍必须先过 provenance,失败后恢复有效来源可继续缺失补传。无产品 UI、模型指令、Goal 状态或 quota 变化。独立 premerge 在 base 5a9c0a6126dd92902f0bacd22762811afa60c407 曾判定 reviewer 工作区本地 CQR stale。作者重新提交后,我用原生 change-quality verify 在实际交付工作区确认 cqr_081a822f700d730a020c 对应同一 base、当前 head 和 fingerprint 081a822f700d730a020cf511c888051e08aff5c8ba95d356de8fd0531a111cbd,结果 valid。两次事实分别保留,没有把失败重标通过,也没有以这份 receipt 推断合并权限。当前 Release 资产列表已包含两份不同名称的 qualification 文件,但“文件存在”不能证明新恢复链或 PyPI 已成功;这也不是本次 reviewer 的发布动作。

语义与 CI 对齐

新增输入与 CLI 名称保留在 release owner,复用既有 GitHub 结构化运行字段;casefold 只实施明确的文件名冲突政策,没有用文字子串分类工作状态。diff-scoped semantic advisory 未发现受支持的新词汇载体,其静态分析局限仍保留。身份、签名与字节一致性是机器强制前提,失败即停止,不能称为可忽略建议。这里只复用本地发布语义,不引入共享状态协议或扩张权限。

我的整体评价 / Verdict

复用已验证字节,使单次部分上传故障可重试;不以重复构建作为恢复前提。这里只证明恢复机制,不宣称实测长期净收益。维护者沿现有 release workflow 输入 tag 和原运行号,恢复结果直接回读;已有文件不需要手工删除。同一部分发布状态的 base/head 对照以及负例支持 observable_semantics=intentional_change_validated。生产实现留在既有发布 owner,复用身份、checksum 和原生签名验证,成本与可重试不可变发布这一问题相称;更大的恢复框架无需加入。

接受这个 bounded recovery increment 的代码质量,当前交付 exact-scope CQR 已独立验证,真实 workflow/PyPI 发布验收和合并权限仍有各自边界。CI 未知不影响已独立证明的代码结论;没有查 CI、触发发布、移动 tag 或执行合并。

English verdict: APPROVE the exact-head recovery tooling; the current delivery CQR verifies, while live publication and merge readiness remain separate.

@huangruiteng
huangruiteng merged commit f1ae02e into main Nov 1, 2026
11 of 33 checks passed
@huangruiteng
huangruiteng deleted the codex/release220-upload-recovery-20261101 branch November 1, 2026 20:04
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.

2 participants