Skip to content

Add the upload-source-to-atr action - #37

Open
ppkarwasz wants to merge 1 commit into
apache:mainfrom
ppkarwasz:feat/upload-source-to-atr
Open

ppkarwasz wants to merge 1 commit into
apache:mainfrom
ppkarwasz:feat/upload-source-to-atr

Conversation

@ppkarwasz

Copy link
Copy Markdown
Member

Summary

New composite action upload-source-to-atr that composes a source release candidate in one step:

  1. creates a reproducible source archive of HEAD with git archive | gzip -n, following the reproducible archives guidance,
  2. computes its SHA-512 checksum and the SWHID of the expanded tree with asfswhid,
  3. signs it with the PMC key imported through the ASF-allowlisted crazy-max/ghaction-import-gpg,
  4. uploads the archive, checksum and signature to ATR through the existing upload-to-atr action, referenced with the $/ self-repository syntax.

Inputs are the ATR project and version, an optional archive prefix (default apache-<project>-<version>-src) and top-level directory, the GPG key and passphrase from the repository secrets, and the expected fingerprint from a repository or organization variable.

This generalizes the workflow @tisonkun wrote in apache/asyncband#319 so that other projects do not have to copy it.

Other changes

  • The shell and Python logic lives in scripts next to action.yml; the license-header hook now covers .sh and .py files.
  • asfswhid is pinned with hashes in requirements.txt and tracked by Dependabot.
  • zizmor is bumped to v1.30.1, the first release line that understands the $/ reference.

Testing

  • pre-commit run --all-files passes.
  • The archive, SWHID and signing scripts were run end to end in an ubuntu:24.04 container with a throwaway key; the archive is byte-for-byte reproducible and the SWHID matches the git tree id unless .gitattributes alter the export.
  • A real upload to ATR still needs a project key and trust configuration.

🤖 Generated with Claude Code

Compose a source release candidate in one step: create a reproducible
source archive of `HEAD` with `git archive | gzip -n`, compute its SHA-512
checksum and the SWHID of the expanded tree, sign it with the PMC key
imported through `crazy-max/ghaction-import-gpg` and upload the files
to ATR through the existing `upload-to-atr` action.

The shell and Python logic lives in scripts next to `action.yml`, and the
`asfswhid` dependency is pinned with hashes and tracked by Dependabot.
zizmor is bumped to a version that understands the `$/` self-repository
reference used to call `upload-to-atr`.

Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
@ppkarwasz

Copy link
Copy Markdown
Member Author

The asf-allowlist-check must be bumped to understand the $/ notation.

@dave2wave

Copy link
Copy Markdown
Member

I did a quick review, but let's wait for Monday for Sean's review.

I have questions about what do you do with binary conveniences and where is the SWID handled / compared with the one ATR generates?

@ppkarwasz

Copy link
Copy Markdown
Member Author

Yes, binaries are out-of-scope, because they are ecosystem-specific.

The SWHID is there only to compare it with what ATR shows or, in the future, to compare multiple archive formats.

@@ -0,0 +1,84 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion - maybe we should rewrite those scripts to Python + uv run ? While I am very long time and very experience bash script creator, i absolutely hate lack of readability and testability it has. Also bash is not very portable - especially for MacOS where default bash is very old due to licencing reasons, and people have to replace the MacOS bash with brew version of it to get some modern(ish) features (like 10 years old).

With Python scripts, inline metadata - it has even better portability and readability, and if you make a mini-project with pyproject.toml you can also add unit tests, which are absolutely necessary if we want to leverage agentic work. Test harness is crucial to be able to maintain any piece of software with agents.

@@ -0,0 +1,77 @@
# Licensed to the Apache Software Foundation (ASF) under one

@potiuk potiuk Sep 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use inline metadata, to declare python versions supported and make it uv-runnable

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very cool. Some suggestions added.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

Yes, binaries are out-of-scope, because they are ecosystem-specific.

Agreed, Let's do it gradually and once we get sources figured out, we can do next steps for binaries. There is no need to solve all of that - there are projects (like Magpie) which only release source packages. Step-by-step.

I think also if we make the bash script into a portable python script with it's own tests and pyproject toml, this adds the necessary step for local verification. I think creating reproducible sources solves only one part of the reproducibility - part of the reproducibility check is that PMC members prepare the same source packafe locally - from the same commit and see if it's binary identical to the one in ATR. If you extract the "preparation" script into a portable python project runnable with uv run or hatch run or any other way people run Python projects in portable way recently, we could also have a separate script to download the ATR binary, build one locally and compare - in purely deterministic way. It could be a simple process described in the "release" instructions or it could be embedded in Magpie release / release-verify SKILL.

Bash has this problem (and I've been bitten by it many times) - that MacOS by default has very, very, old version of bash - and it's readablity is bad.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

BTW. I like the idea of storing and using SHWID. This is way better than specifying repo + commit hash, because it's a unique value we can use also for the future. For example we could store / link somehow SHWID also for binary artifacts preparation - and then make sure that any binary artifacts has the same SHWID as the source artifact.

@dave2wave

Copy link
Copy Markdown
Member

To be clear. Building of artifacts is outside the scope of ATR because there are many ways to build and many build tools. These are each project's decision. For many reasons a source release must contain only files that are in the source repository, but it need not contain all. Often there are files in the repository it MUST not include.

Adding signatures and checksum creation would be a helpful and acceptable extra. The calculation of SWID is only needed if you want to check it with the calculation that ATR already does in the compose phase. (This is a new feature.)

If there is a common way to check and possibly generate a cycloneDX SBOM that would be an awesome bonus feature.

@dave2wave

dave2wave commented Sep 19, 2026

Copy link
Copy Markdown
Member

The SWHID is hidden in the interface in ATR and we should expose it more.

Screen Shot 2026-09-19 at 10 14 01 AM

@dave2wave

Copy link
Copy Markdown
Member

@ppkarwasz before proceeding with this approach we need to do some research on which PMCs are currently approved (ATR tells this from the PMC's KEYS file containing the OpenPGP public key for the PMC) and for those which are what types of artifacts do they release. We have admin ability to add such a key as well.

Also, reproducibility is a binary artifact concern and not a source concern. We already check that a source release exists in the repository at a declared revision/SHA matches since that is one of the extended checks voters should be doing.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

To be clear. Building of artifacts is outside the scope of ATR because there are many ways to build and many build tools. These are each project's decision. For many reasons a source release must contain only files that are in the source repository, but it need not contain all. Often there are files in the repository it MUST not include.

Oh absolutely - and there is absolutely standard way of excluding those files: .gitattributes. We are using it for years in Airflow and many other projects also use git archive to produce the source artifacts and excluding files with .gitattributes.
It's just a matter of adding a doc here telling people that they should use those.

Airflow's .gitattributes file: https://github.com/apache/airflow/blob/main/.gitattributes

Adding signatures and checksum creation would be a helpful and acceptable extra. The calculation of SWID is only needed if you want to check it with the calculation that ATR already does in the compose phase. (This is a new feature.)

If there is a common way to check and possibly generate a cycloneDX SBOM that would be an awesome bonus feature.

I've worked with the author of cdxgen https://github.com/cdxgen/cdxgen - we fixed a few performance issues few years ago - and we are using them for airflow's SBOMS and it works very well.

However - this is much more complex - as opposed to .gitattributes export-ignore directives that make git archive perfect for 'standard' source package preparation - there is no standard for all kinds of types of SBOMS (there are 6 or 7) and exclude/include directives there - unlike source code exclusions from export, those depend a lot on the project's context I think.

@dave2wave

Copy link
Copy Markdown
Member

I've assigned #39 to answer the what do enabled PMCs actually release question.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

Also, reproducibility is a binary artifact concern and not a source concern. We already check that a source release exists in the repository at a declared revision/SHA matches since that is one of the extended checks voters should be doing.

I think quite the opposite - it would alleviate easily all the complaints about "do not know what you are checking". Perfect reproducibility of the source artifacts that are published in ATR and ones that PMC can generate locally is the only real way for the PMC to know what they are voting on - this for example would completely invalidate any issues that Sebb raised before - because the PMC could use reproducibility check to know that Release manager did not modify uploaded source artitcat.

Which is exactly the scenario that 'xz` issue hed. The release manager modified the .tar.gz artifact - it was not produced from the specific commit - it had few lines injected in build script. And reproducibility check by independent PMC members -> recreating the same .tar.gz file is the fastest and most certain way to verify that release manager preparing the artifact was not roque, blackmailed, or that someone stole their credentials and wanted to inject stuff.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

In the current workflow we do not have prevention against roque release manager "xz-like" , I think - and this one provides pretty much 100% protection againts it, if 2 more PMC members can reproduce the very same .tar.gz file from the same commit.

@dave2wave

dave2wave commented Sep 19, 2026

Copy link
Copy Markdown
Member

Which is exactly the scenario that 'xz` issue hed. The release manager modified the .tar.gz artifact - it was not produced from the specific commit - it had few lines injected in build script. And reproducibility check by independent PMC members -> recreating the same .tar.gz file is the fastest and most certain way to verify that release manager preparing the artifact was not roque, blackmailed, or that someone stole their credentials and wanted to inject stuff.

Yes, a binary release and not source. ATR does checks to see that source releases have no binaries and that every file matches the same file in the source repository which is a superset of the source release.

In the current workflow we do not have prevention against roque release manager "xz-like" , I think - and this one provides pretty much 100% protection againts it, if 2 more PMC members can reproduce the very same .tar.gz file from the same commit.

For binaries we count on voters inspecting artifacts for surprises by doing their own builds from source. That is a key component to preventing the xz case.

BTW - this discussion should be on a mailing list.

@potiuk

potiuk commented Sep 19, 2026

Copy link
Copy Markdown
Member

Yes, a binary release and not source. ATR does checks to see that source releases have no binaries and that every file matches the same file in the source repository which is a superset of the source release.

But can a PMC verify that? I think very basic issue here is that we have to trust something else than personal check done by a few people

For binaries we count on voters inspecting artifacts for surprises by doing their own builds from source. That is a key component to preventing the xz case.

The xz was actually source artifact - not binary. Look it up. In case of xz the problem was not binary artifact - but source .tar.gz published by the rogue release manager - and many distros were almost infected because they used that .tar.gz as source and they had no way to verify that it was reproducible from the git repo. Look it up please - binary artifacts had nothing to do with it.

BTW - this discussion should be on a mailing list.

Which one? I am happy to start it - but I just want to say - as one of the users of the tools - that I would find this one as an extremely useful tool from tooling - and very usable.

But if you think we should have a mailing list discussion on that - I think it's a good idea to tell Piotr where he can raise it and how. I will chime-in as a user.

@dave2wave

Copy link
Copy Markdown
Member

You need to listen to what I'm describing as checks that are already in ATR's compose phase.

  1. Source releases must have no files that do not match the files in the repository. Nothing unexpected is injected.
  2. Source releases must have no binaries / compiled code. The source repository may have binaries / compiled code.
  3. Source releases are not required to match the whole of the repository. This PR is making an assumption which is incorrect.

We allow CI if the release has reproducible builds. Builds are by definition convenience binaries. The agreement is that PMC members do their own builds. SWHID is a way to compare without having bit by bit comparison. Instructions on how to perform these tests are the responsibility of the PMC.

Let's wait until Monday for further discussion after we survey what PMCs that are enabled to do reproducible build are actually doing.

potiuk added a commit to potiuk/magpie that referenced this pull request Sep 20, 2026
…origin

A commit id names a repository object; the Software Heritage identifier
of the expanded archive content (swh:1:dir:<sha1>) names what shipped.
It is computed from names, modes and contents alone, exactly as git
computes a tree id, so it does not depend on the archive format, the
compression or who packed it: a voter recomputes it from the staged
bytes, ATR computes the same value at compose time, and it equals the
repository tree at the tag unless .gitattributes altered the export —
in which case the difference is itself the record of what was left
out. It also gives a convenience artefact something precise to point at
as the source it was built from.

repro-archive: `build` now prints swhid_rev (swh:1:rev:<commit>),
swhid_dir (the content identifier, qualified with origin=<repo URL>
and anchor=swh:1:rev:…) and a note saying whether the content SWHID
equals the repository tree; `swhid` computes the identifiers for an
archive and/or a ref; `check --swhid` asserts an archive against a
recorded value; `compare` reports both archives' SWHIDs. The in-memory
tree hashing is tested against `git write-tree` over the extracted
tree and against `git rev-parse <ref>^{tree}` when nothing is
export-ignored, so it agrees with asfswhid / swh identify by
construction.

Two more machine-dependent inputs are pinned, following the review of
apache/tooling-actions#37: the builder's core.autocrlf / core.eol
(which `git archive` would apply to text files) are neutralised, and
the commit id travels in the archive the way `git archive` carries it
(a global PAX `comment` in tar, the archive comment in zip; `check`
accepts exactly that comment).

Skills: release-rc-cut records repository URL, commit, SWHIDs, epoch
and sha512 on the planning issue; release-verify-rc checks the staged
archive against the recorded SWHID (`swhid_matches`); the [VOTE] body's
reproducibility record lists repository, commit, SWHID, epoch, sha512,
and its agentic-path paragraph is fixed text. The CI workflow template
prints the record into the job summary.

Docs: docs/designs/2026-09-20-reproducible-releases.md records the
decisions, rejected alternatives and known limits; reproducibility.md
and the tool README explain the record and the extra fixes.

Generated-by: Claude Code (Fable 5.1)
@potiuk

potiuk commented Sep 20, 2026

Copy link
Copy Markdown
Member

Indeed - this action does not have to be part of ATR. The apache/magpie#1296 has alll the reproducibility checks, SHWID, capability to create an action for GH release + following process to approve it with Infra as part of it's "ASF release" family of skills. So regardless if it's going to be reusable action - I will use it to drive the release process of Magpie itself for now.

potiuk added a commit to apache/magpie that referenced this pull request Sep 20, 2026
…and ASF automated signing (#1296)

* feat(release): reproducible source archives, reproducibility checks, and ASF automated signing

The source artefact is now a reproducible export of the tag. A new
stdlib-only tool, tools/reproducible-archive (repro-archive build |
check | compare | recipe | epoch), wraps `git archive` (tracked files
only, .gitattributes export-ignore honoured) and applies every rule from
reproducible-builds.org/docs/archives/: one SOURCE_DATE_EPOCH mtime,
sorted members, uid/gid 0, a=rX,u+w modes, no PAX atime/ctime, gzip -n,
zip -X. Two builders of the same tag get byte-identical bytes regardless
of their git/tar/zip version. release-rc-cut emits it as the source
build command; release-verify-rc rebuilds and compares.

release-build.md gains "Source archive" (source_archive_method, format,
prefix, export_ignore_reviewed) and "Reproducibility checks"
(reproducibility_source, reproducibility_binaries with byte-identical
or documented-divergence modes). The checks run as release-rc-cut Step
2b (RM self-check before signing) and release-verify-rc Step 9 (every
voter); the hand-back step moved to Step 10.

release-prepare prep Step 2e is the first-release .gitattributes
education step: it classifies every top-level path, checks references
before proposing an export-ignore, confirms each entry with the RM,
lands .gitattributes in the prep PR and records the review marker.
release-rc-cut blocks while the review is outstanding.

ASF-specific (organization: ASF only, marked as such): release-prepare
automated-signing drafts, never files or sends, the Infra key-request
ticket, the Security Team notification and a reproducible-build
workflow PR from projects/_template/workflows/release-candidate.yml
(no key material, ATR OIDC upload), per Infra's automated release
signing policy. When enabled, verify-rc's reproducibility check becomes
the mandatory bit-by-bit validation on trusted hardware and
release-promote blocks without the attestation. The option is resolved
from release_process.automated_signing in the ASF organization
manifest and is null for independent projects.

Every [VOTE] body release-vote-draft produces now carries a "How to
verify this candidate" section: the reproducibility record (commit,
SOURCE_DATE_EPOCH, sha512), the agentic one-liner
(/magpie-release-management:verify-rc), the human-readable verification
page and reproducibility page at the RC tag, the ATR candidate link and
the voter-obligation sentence. New config keys vote_verification_doc_url,
reproducibility_doc_url and vote_verification_skill; the ATR runbook and
the manual voter guide (with a reply template) match.

Docs: new docs/release-management/reproducibility.md; process, spec,
both runbooks, source-release-contents, the capability taxonomy (new
substrate:release value) and the spec-loop spec updated. Evals: new
suites for rc-cut Step 2b and verify-rc Step 9, new cases for the
unreviewed-archive block, the repro-archive build command, the
first-release review and the ASF-only gate; vote-draft step-2 asserts
the verification section.

Generated-by: Claude Code (Fable 5.1)

* feat(release): project-specific convenience artefacts, gated on reproducibility

The release skills assumed one shape of "binary": a global
binary_rebuild_command and a hard-coded list of the framework's own
validators in verify-rc Step 7. Both were Magpie-specific. Convenience
artefacts are project-specific by nature — a binary tarball, wheels,
jars, a container image, a chart, each built, staged, voted on and
published its own way — so the framework now assumes none and reads
what each project declares.

release-build.md gains "Convenience artefacts": one entry per artefact
with its own build_command, staging / stage_command, reproducibility
mode (byte-identical | documented-divergence, default
reproducibility_binaries), vote_included flag and publish_channel /
publish_command; and "Source-tree validators": the project's own
integrity checks for verify-rc Step 7 (Magpie's list moves to
projects/magpie/release-build.md).

Each skill consumes the list: release-rc-cut emits every artefact's
build command under the tag's SOURCE_DATE_EPOCH, the per-artefact
rebuild-and-compare self-check, and registry staging commands;
release-verify-rc Step 9 rebuilds and compares every artefact and
names the ones that did not reproduce; release-vote-draft lists the
artefacts, where each is staged and which are in the vote;
release-promote publishes each artefact to its declared channel only
when the recorded verify-rc run reproduced it, and emits a HOLD note
otherwise (the source promotion is never held back);
release-announce-draft names the channels.

Reproducibility is stated, in the config, the docs and the skills, as
the check that decides whether a convenience artefact is good: a
binary cannot be reviewed, only rebuilt, so rebuilding it from the
voted tag and comparing is the one way to establish that it is what
the source produces.

Evals: promote step-2 case-5 (publish the reproduced wheel, hold the
container image that differed), verify-rc step-9 case-5 (source
identical, artefact differs → FAIL), rc-cut step-2b and vote-draft
fixtures updated to the per-artefact shape.

Generated-by: Claude Code (Fable 5.1)

* feat(release): record the source tree's SWHID next to the commit and origin

A commit id names a repository object; the Software Heritage identifier
of the expanded archive content (swh:1:dir:<sha1>) names what shipped.
It is computed from names, modes and contents alone, exactly as git
computes a tree id, so it does not depend on the archive format, the
compression or who packed it: a voter recomputes it from the staged
bytes, ATR computes the same value at compose time, and it equals the
repository tree at the tag unless .gitattributes altered the export —
in which case the difference is itself the record of what was left
out. It also gives a convenience artefact something precise to point at
as the source it was built from.

repro-archive: `build` now prints swhid_rev (swh:1:rev:<commit>),
swhid_dir (the content identifier, qualified with origin=<repo URL>
and anchor=swh:1:rev:…) and a note saying whether the content SWHID
equals the repository tree; `swhid` computes the identifiers for an
archive and/or a ref; `check --swhid` asserts an archive against a
recorded value; `compare` reports both archives' SWHIDs. The in-memory
tree hashing is tested against `git write-tree` over the extracted
tree and against `git rev-parse <ref>^{tree}` when nothing is
export-ignored, so it agrees with asfswhid / swh identify by
construction.

Two more machine-dependent inputs are pinned, following the review of
apache/tooling-actions#37: the builder's core.autocrlf / core.eol
(which `git archive` would apply to text files) are neutralised, and
the commit id travels in the archive the way `git archive` carries it
(a global PAX `comment` in tar, the archive comment in zip; `check`
accepts exactly that comment).

Skills: release-rc-cut records repository URL, commit, SWHIDs, epoch
and sha512 on the planning issue; release-verify-rc checks the staged
archive against the recorded SWHID (`swhid_matches`); the [VOTE] body's
reproducibility record lists repository, commit, SWHID, epoch, sha512,
and its agentic-path paragraph is fixed text. The CI workflow template
prints the record into the job summary.

Docs: docs/designs/2026-09-20-reproducible-releases.md records the
decisions, rejected alternatives and known limits; reproducibility.md
and the tool README explain the record and the extra fixes.

Generated-by: Claude Code (Fable 5.1)

* test(reproducible-archive): prove symlink modes do not leak the packer's platform

`lstat` reports a symlink as 0755 on macOS and 0777 on Linux. The
writers already pack every symlink as 0777 and the SWHID uses git's
fixed 120000, so a macOS-built and a Linux-built archive of the same
tag are byte-identical; this adds the test that says so, documents the
rule in the README table and the module, and warns in the shell recipe
that not every tar's --mode rewrites symlink modes.

Generated-by: Claude Code (Fable 5.1)
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