feat(release): add ASF source candidate workflow - #3278
Conversation
12ac3e7 to
ac61db4
Compare
Build source candidates from immutable commits while keeping Release Manager signing credentials outside CI. Add archive verification, reproducibility coverage, and the staging and voting runbook needed for an Incubator release. Generated-by: Codex
Bind local signing to a byte-for-byte rebuild from the reviewed revision, require exact signing-key fingerprints, and interpret GPG status records so expired or revoked signatures fail closed. Isolate source mechanics in their own required CI lane, make tar access portable across Windows paths, and keep package and lockfile release identities aligned. The runbook now reflects the trusted-hardware handoff and every binding voter's verification duty.
Make source archives independent of ambient Git permission settings and enforce the ASF RSA key policy against the actual signing key or subkey on both signing and verification paths. Complete the two-stage Incubator vote contract and remove unreachable CI routing entries so the release authority stays explicit.
Compare the canonical Git tar payload instead of platform-specific gzip bytes so Release Managers can reproduce workflow candidates across trusted machines. Isolate archive attributes from local Git state and enforce one SHA-2/RSA signing policy for both local and KEYS verification.
Source candidate creation and verification must derive identity only from explicit repository and artifact inputs. Strip inherited Git, tar, and gzip control variables so trusted-checkout reproduction and archive policy cannot be redirected by the caller environment. Keep Release Manager guidance authoritative by linking the exact-revision runbook from the workflow handoff instead of duplicating policy prose.
Publish verified source artifacts atomically from destination-local staging so interrupted or concurrent writers cannot expose partial release files. Authenticate signed candidates before parsing, stream payload digests, rebind the checksum during signing, and isolate GNU tar from ambient gzip options. Remove the workflow's redundant verification pass and retain only release-identity assertions and regression coverage that protect observable policy boundaries.
2ecca1a to
8d14f5c
Compare
Verify candidate bytes from a private snapshot so signature, checksum, and archive checks cannot drift across path replacement. Keep unsigned workflow artifacts commit-addressed and defer RC identity until immutable tagging and staging. Remove the duplicate workflow version grammar and unreachable CI planner entries while preserving their fail-closed behavior.
The documented release path operates on completed local artifacts without a concurrent writer. Remove snapshot copies and mutation-only rebinding tests while retaining reproducibility, signature policy, and atomic publication guarantees.\n\nAlso narrow unused CLI and result contracts and remove duplicated workflow and archive-validation state.
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed at ac36d4fc. No P0 or P1. The core mechanics here are unusually solid, and I verified the load-bearing ones by building the candidate rather than by reading: in a throwaway worktree I ran create, verify, reproduce, and check:asf-source (9/9), then diffed the extracted tarball against a clean checkout of the same commit.
What that established, so it does not have to be re-litigated during the vote:
- The archive is deterministic. uid/gid 0, uname/gname
root, a single mtime from the commit, modes forced by-c tar.umask=0002regardless of ambient umask, andgzip -n -9so nothing leaks into the gzip header. The longest tracked path splits cleanly into ustarprefix+name, so no per-entry pax headers appear; git'spax_global_headeris in the payload but is not listed bytar -tand so does not tripvalidateArchiveEntries. - The contents are exactly the committed tree minus the three
export-ignorepaths —diff -rqdiffers only by.claude/,.maka-shots/, andmaka-proposal-zh-review.txt. No binaries beyond five PNGs (README hero images and desktop icons), no symlinks, no gitlinks, nonode_modules, no.git. - The
forbiddenSegmentsbackstop is live, not decorative. Building from the pre-PR base commit, whose.gitattributeslacks theexport-ignorelines, fails withForbidden archive entry: …/.claude/. - The signature covers the published bytes.
signSourceCandidatesignsarchivePath,verifySha512Filehashes the same file, andpublishSourceCandidatehardlinks it — same inode throughout. - The secret surface is genuinely clean. Zero
secrets.*references;workflow_dispatchonly, so no fork-reachable path;permissions: contents: read;persist-credentials: false; all three third-party actions pinned to full commit SHAs; and every${{ }}reaching arun:goes through a job-levelenv:and is dereferenced as a quoted shell variable. No private key ever enters CI — signing is local by design. - The GPG status parsing is correct:
VALIDSIGfield indices, rejection ofEXPKEYSIG/REVKEYSIG/BADSIG/NO_PUBKEY/NODATAbefore the GOODSIG count, a hash allowlist of exactly SHA-256/384/512, and subkey fingerprint resolution. Verification runs in amkdtemphomedir seeded only from the reviewed KEYS file, so an unlisted signer yieldsNO_PUBKEY. - The
ci-test-plan.mjsedit is not a coverage regression. Dropping five entries fromCLI_PACKAGE_FILESlooked like one; runningplanTestson each shows all five still resolve tofull: truethrough the unknown-root-file fallback.
On the seam question: this extends rather than parallels. No existing script produces a checksum file or touches GPG, the ASF source tarball is a different artifact with a different authority from the npm convenience tarball and the DMG, and asf-source-workflow-policy.test.mjs mirrors the existing release-cli-workflow-policy.test.mjs convention. Splitting check:asf-source out of check:release is right — the authoritative artifact should not be gated on Desktop and npm convenience checks. The one genuine overlap is the npm audit step, inline.
Two of the findings below are process decisions rather than defects and need a named human owner rather than a code change: the candidate always builds main's tip at dispatch (deliberate — a test asserts the absence of a SHA input), and NOTICE is not yet in ASF form (deliberate — DISCLAIMER-WIP says so). Both are legitimate WIP postures; the risk is that the PPMC discovers them mid-vote rather than deciding them beforehand.
Reviewed with Claude Opus as an analysis assistant. Findings marked "reproduced by execution" were built and run in an isolated worktree that has been removed; the root checkout was not modified and no repository-wide tests were run. Evidence grade is stated per finding.
Validate generated attribution inventories from the extracted source candidate so the vote-time gate covers the exact archived payload. Clarify that release keys are appended to the reviewed KEYS history rather than replacing it.
Astro-Han
left a comment
There was a problem hiding this comment.
Re-reviewed at 251728b. Two of my P2s from ac36d4f are resolved and I verified both:
- Attribution inventories — the lane now runs
check:third-party-noticesandcheck:cli-third-party-noticesagainst the extracted source, after a fullnpm ci, withasf-source-workflow-policy.test.mjsasserting the workflow still contains them. Both scripts exist in the archivedpackage.jsonand need only devDependencies, which the preceding step installs. Correct fix, and pinning it with a policy assertion is the right instinct. - KEYS truncation —
> KEYSis now>> KEYSand the runbook starts from a reviewed copy of the published file.(gpg --list-sigs <fpr> && gpg --armor --export <fpr>) >> KEYSproduces the conventional ASF KEYS block and cannot destroy existing keys.
One follow-up on the inventory fix below, and my remaining threads from the previous head carry over unchanged — the dispatch-time $GITHUB_SHA resolution, npm audit against the live advisory feed on the release path, NOTICE being checked for existence rather than content, the workflow_dispatch first-run shakeout, and the four P3s. None of them are new, so I have not re-posted them.
Still COMMENT rather than approve: the open items are all P2, none of them blocks a release by itself, but this is the lane that decides what an IPMC vote sees, and I would rather the RM path be settled before it becomes the default.
AI disclosure: this review was produced with Claude Code (Opus 5). For this pass I read the delta commit, the workflow and runbook at 251728b, asf-source-release.mjs's archive boundary set, .gitattributes, and the notice-generation scripts and their call sites; I did not run the workflow or the tests. Per AGENTS.md this is not independent human review.
Check the Windows sandbox Cargo attribution inventory from the extracted source payload so its committed notices cannot drift from the archived lockfile. Keep the workflow-policy test aligned with all shipped attribution inventories.
Astro-Han
left a comment
There was a problem hiding this comment.
The cargo-inventory P2 is fixed at 5d52e8f, including the part I care about most — extending the asf-source-workflow-policy.test.mjs assertion so the lane cannot lose an inventory again without a test noticing.
I checked the one thing that could have made my own suggestion wrong: this job is runs-on: ubuntu-24.04 with no Rust setup step, so check:windows-cargo-notices depends on the runner image's preinstalled toolchain. That is already this repository's established assumption — windows-sandbox-w0.yml runs cargo build --locked and cargo test --locked on windows-2025 with no setup step either — and experiments/windows-sandbox/launcher/{Cargo.toml,Cargo.lock} are neither in forbiddenSegments nor export-ignored, so they are present in the extracted candidate. It should hold. Confirmed by reading the workflows and the archive boundary set; not executed, so the first real run is still the proof.
Worth distinguishing from my open npm audit thread, since they look similar: cargo metadata --locked resolves from the committed lockfile and is deterministic, so this does not add the kind of time-varying external dependency that one is about.
My remaining threads from the previous heads carry over unchanged — the dispatch-time $GITHUB_SHA resolution, npm audit against the live advisory feed on the release path, NOTICE being checked for existence rather than content, the workflow_dispatch first-run shakeout, and the four P3s. Nothing new from me on this head, so I have not re-posted them.
Still COMMENT rather than approve: everything open is P2 or below and none of it blocks a release on its own, but this lane decides what an IPMC vote sees, and I would rather the Release Manager path be settled before it becomes the default.
AI disclosure: this review was produced with Claude Code (Opus 5). I diffed 251728b..5d52e8f and read the workflow, the sandbox workflow and the archive boundary code at this head; I did not run the workflow or the tests. Per AGENTS.md this is not independent human review.
Astro-Han
left a comment
There was a problem hiding this comment.
Approving at 5d52e8f, and correcting my own bar while I do it.
I withheld approval on the previous two heads on the grounds that this lane decides what an IPMC vote sees, so I wanted the Release Manager path settled first. That was a standard I invented for this PR rather than the one I apply everywhere else: P0 and P1 block, P2 should be fixed but does not. Everything still open here is P2 or P3, and holding a release-mechanics PR to a stricter rule than the product PRs I approved today is not defensible. My mistake, and it cost you two extra rounds.
What actually landed across those rounds: the attribution inventories are verified inside the extracted candidate for all three sources now — npm, CLI and cargo — each pinned by an assertion in asf-source-workflow-policy.test.mjs so the lane cannot quietly lose one; and the KEYS runbook no longer truncates the published file. All of my review threads are resolved.
The four P2s I am approving over, so they are on the record rather than forgotten: the candidate commit resolves at dispatch time rather than being named or asserted; npm audit puts a time-varying external feed on the release path; NOTICE is checked for existence rather than content; and workflow_dispatch only becomes available once this is on the default branch, so the first real run is the first syntax check. None of them blocks a release on its own, and the last one cannot be resolved before merging by construction.
Note on CI: package is failing at "Verify automatic update end to end", which is the Windows autoupdate lane. This PR's only workflow change adds an asf_source gate and one step to the test job and does not touch package; that lane is the one #3241 and #3265 are currently fixing. test and audit are green.
AI disclosure: this approval was produced with Claude Code (Opus 5). Across the three heads I read the workflow, the runbook, asf-source-release.mjs's archive boundary, .gitattributes, the notice-generation scripts and their call sites, and confirmed the failing job's step. I have not run the workflow. Per AGENTS.md this does not count as the independent human review this PR still needs.
…pendencies A second round of adversarial review found the previous revision had replaced one overreach with another, and that both rounds had missed shipped dependencies a name-based scan does not surface. The previous revision corrected "the EAR requires notification" but then asserted as established fact that Maka is entirely standard cryptography. That does not follow. 772.1 reaches proprietary or unpublished cryptographic protocols, not only algorithms, so an enumeration of algorithm names cannot settle it. Retract the claim and name the three constructions that need a protocol-level determination. Two shipped dependencies were absent. dugite is a direct root dependency that bundles a complete 141 MB Git distribution into the desktop app, carrying the .NET cryptography stack, OpenSSL native libraries, and TLS transport; it is the largest third-party crypto payload in the product. @jackwener/opencli is a direct desktop dependency carrying vendor-private API signing with hardcoded key material against undocumented endpoints, which is the clearest input to the 772.1 question. Neither is JavaScript crypto a package-name scan would find, which is why the coverage limit now says so instead of claiming completeness. Move the snapshot to current main. The prior baseline predated #3278 by 28 minutes, which had already defined the source artifact: git archive with only three export-ignore entries, so experiments/ and packages/eval/harbor/ are inside it, and the CA-generation path with them. That resolves one open question and adds GPG detached signing as a new surface. Also record the third lockfile, qualify the SSH finding for the Windows MinGit payload, correct a PowerShell call mislabelled as DPAPI-backed, and split manufacturer into which item an artifact contains versus who built it. Refs #3273 Generated-by: Claude Code (claude-opus-5)
English
Summary
tar.umask.KEYSfile without allowing ambient tar or gzip options to alter tool behavior.dist/devstaging, every binding voter's verification duty, complete podling/IPMC vote thresholds, and immutable RC handling.Refs #3269
Part of #2974
Verification
npm run check:asf-source— 9/9 passed, including hostileGIT_DIR/GIT_TEMPLATE_DIR/Git config/GZIP isolation, GNUTAR_OPTIONSbypass rejection, equivalent tar payloads with different gzip bytes, strong-digest enforcement, RSA signing-subkey acceptance, Ed25519/RSA-1024 rejection, and isolated-KEYSverification.npm run check:release— 32/32 passed after separating the ASF source gate.node --test --test-concurrency=1 scripts/ci-test-plan.test.mjs— 18/18 passed.npm run typechecknpm run lintnpm run format:checkactionlintv1.7.7 on the changed CI and candidate workflows.An earlier
npm testrun completed the full build and most suites, but remained non-green on two known macOS environment cases: Eval expected/var/...instead of resolved/private/var/..., and one Runtimenode-ptylifecycle test timed out after 10 seconds. Neither failing area is changed by this PR.Remaining validation
main.AI use
Select exactly one:
Tool(s) and scope: Codex prepared and reviewed the source-candidate automation, trust-boundary tests, workflows, and Release Manager runbook under human direction.
Checklist
Does this PR entail a change in behavior?
简体中文
概要
tar.umask影响。KEYS文件的 detached signature,并阻止本机 tar 或 gzip 选项改变工具行为。dist/dev暂存、每位 binding voter 的验包义务、完整 podling/IPMC 投票门槛及不可变 RC 处理流程。关联 #3269
#2974 的一部分
验证
npm run check:asf-source——9/9 通过,覆盖恶意GIT_DIR/GIT_TEMPLATE_DIR/Git config/GZIP 隔离、GNUTAR_OPTIONS绕过拒绝、不同 gzip 字节下的等价 tar payload、强 digest 强制、RSA signing subkey 正向路径、Ed25519/RSA-1024 拒绝及隔离KEYS验签。npm run check:release——ASF source gate 拆分后 32/32 通过。node --test --test-concurrency=1 scripts/ci-test-plan.test.mjs——18/18 通过。npm run typechecknpm run lintnpm run format:check使用
actionlintv1.7.7 检查变更的 CI 与候选包 workflow。较早的一次
npm test完成全量构建及绝大多数 suite,但仍有两个已知 macOS 环境问题:Eval 预期/var/...,实际得到/private/var/...;另有一个 Runtimenode-pty生命周期测试在 10 秒后超时。这两个失败区域都未被本 PR 修改。剩余验证
main上经过审查的 commit 成功运行 Prepare ASF source candidate。AI 使用
二选一:
工具及范围:Codex 在人工指导下准备并审查了源码候选包自动化、信任边界测试、workflow 和 Release Manager runbook。
检查清单
此 PR 是否导致行为变化?