release-config
Capability: substrate:release
Harness: agnostic
Loads the release-management configuration an adopter keeps in <project-config> and runs the deterministic part of each release-* skill’s Step 0 — Pre-flight check, printing JSON.
Required keys, argument formats, the digest-set rule, the source-archive review gate, signing-mode consistency, the vote-window floor, the promote-wait hour, the approver-roster lookup and the dist-URL rendering are all fixed rules; running them as code keeps them out of the model’s context and makes them give the same answer every time.
Checks that need GitHub, the network or judgement — finding the planning issue, reading its labels, whether the RC tag exists, whether the staging URL answers, what the KEYS file holds — stay in the skill.
Prerequisites
- Runtime: Python 3.11+, stdlib only; run via
uv run --project <framework>/tools/release-config. - CLIs: None beyond the runtime.
- Credentials / auth: None. It reads the RM’s
user.mdonly forapache_idandrelease_manager.gpg_fingerprint. - Network: None — reads local markdown files only.
How to use
Run it from the adopter repo root (or pass --project-root).
<project-config> resolves per file, local first: .apache-magpie-local/<file>, then .apache-magpie-overrides/<file>; --config-dir <dir> reads every file from one directory instead.
The skill’s positional arguments and flags are passed through as the RM typed them; facts the skill read from the planning issue are passed as flags (--promote-timestamp, --download-page, --vote-opened, --verify-binary).
The command always exits 0 once it has printed JSON; 2 is a usage error.
preflight — a skill’s deterministic Step 0 checks
uv run --project <framework>/tools/release-config release-config preflight --skill rc-cut 2.12.0 rc1
{
"ok": false,
"skill": "rc-cut",
"blockers": ["export_ignore_reviewed is unset in release-build.md § Source archive (archive_reviewed: false): run `release-prepare prep 2.12.0` — …"],
"warnings": [],
"values": {"version": "2.12.0", "rc_number": "rc1", "archive_reviewed": false, "signing_mode": "rm-key", "staging_url": "…", "rc_tag": "2.12.0-rc1"}
}
blockers are hard stops, worded for the RM; warnings are surfaced and do not stop the skill; values carry what the skill’s Step 0 JSON reports (archive_reviewed, non_asf, dist_backend, rm_is_pmc, staging_url, promote_clear_after_utc, …), plus convenience_versions (each convenience artefact’s version, version_scheme and valid / invalid / unvalidated) for the skills that handle artefacts.
load — the parsed config and a skill’s Step 1 metadata
uv run --project <framework>/tools/release-config release-config load --skill promote 2.11.0-rc1 \
--verify-binary apache-foo-2.11.0-bin.tar.gz=identical
Prints sources (which file each config came from), config (the release-management-config.md keys with defaults applied), build (release-build.md: build command, expected artefacts, digest set, source-archive and reproducibility keys, convenience artefacts, the RAT config and excludes paths, source_tree_validators), derived (signing_mode, is_asf, non_asf, automated_signing_offered and the file that decided it, approver_roster_path, release_lines) and, with --skill, metadata — the config-derived fields of that skill’s Step 1 table under the names the table uses.
For promote, metadata.convenience splits the convenience artefacts into publish (verify-rc reported identical or warn) and held (differs, or no result).
For verify-rc, metadata carries everything the release-management verify-rc commands need: each expected artefact with required (false when its attributes say optional), the extra prohibited globs and accepted exceptions of § Binary-exclude list, the RAT config and excludes paths of § Apache RAT configuration, source_tree_validators, the § JVM artefact checks keys, and the source-archive and reproducibility keys.
What it reads
| File | Parsed as |
|---|---|
release-management-config.md |
every `` |
release-build.md |
the same tables, plus § Build invocation (first unquoted fence), § Expected artefact list and § Digest set (first prose or bullet block; a TODO: block is unset), § Convenience artefacts (the unquoted yaml fence, including each entry’s optional version and version_scheme; the template’s <project> example entry is ignored) |
release-trains.md |
the bullets of § Release branches currently in flight |
pmc-roster.md / release_approver_roster_path |
the first table with an Apache ID, email or handle column; TODO rows do not count |
project.md |
organization (default independent); a release_process.automated_signing YAML key, if the project overrides its organization |
organizations/<org>/organization.md |
release_process.automated_signing — the framework’s in-tree manifest, then the adopter-local <project-config>/organizations/<org>/organization.md |
user.md |
apache_id, release_manager.gpg_fingerprint |
Blockquoted examples and Template guidance / Example shape lead-ins are never read as values.
Shared rules
Rules more than one skill applies have one implementation, so every skill gives the same answer:
- Source version and RC. The source release candidate every skill takes (
<version>plusrcN, tagged<version>-rcN):<version>is a dotted version of two or more numeric parts with no.postN(2.11,2.11.0,2.11.0.1), or a calendar dateYYYY-MM-DDfor a release wave named by date (2026-10-06; an impossible date such as2026-02-30blocks); the RC isrcNwith N ≥ 1 (rc0andrc01block).rc-cut,verify-rc,vote-draft,vote-tallyandpromotecheck the RC;announce-draft,audit-reportandpreparecheck the version alone. - Convenience-artefact versions. Each § Convenience artefacts entry may carry its own
version(default the release version;<version>in it is rendered, e.g.<version>.post1), validated by the entry’s optionalversion_scheme: a wheel2.10.5.post1passes against source release2.10.5.promoterenders the artefact’s name andpublish_commandwith that version.versions.SCHEMESis the registry of validated schemes:python(a dotted version with an optional.postN, RCrcNwith N ≥ 1). An invalid version under a validated scheme is a blocker; any other scheme name is accepted and reported asunvalidatedwith a warning, so the RM decides; an absent field isunvalidatedwith a warning suggesting the field. Add a scheme by adding aSchemetoSCHEMES.rc-cut,verify-rcandpromoterun the check. - Approver roster. One key,
release_approver_roster_path, default<project-config>/pmc-roster.md, read bypromote,vote-tallyandaudit-report.vote-tallyandaudit-reportblock when it is unreadable or has no rows;promotetreats an unreadable roster as a hand-off. keyserver. Defaults tokeys.openpgp.orgeverywhere; no skill requires it.- ASF identity and
non_asf.project.md→organization: ASFis the only ASF test;non_asfis its negation in every skill (there is no--non-asfflag and nois_asf_tlpkey). An ASF project must userelease_approval_mechanism: dev-list-voteandrelease_announce_backend: announce-list; a non-ASF project may not useannounce-list. - Automated signing. Offered only when the organization manifest key
release_process.automated_signingresolves to a value:project.md→organizations/<org>/organization.md(in-tree, then adopter-local) → framework default (not offered).prepare automated-signingblocks otherwise, andsigning_modeisci-automatedonly forautomated_release_signing: enabledwhere it is offered. - Archive destination.
archive_url_template, defaulting tohttps://archive.apache.org/dist/<project>/(fromproject_dist_name) for both ASF backends,svnpubsubandatr; other backends needarchive_url_template.
Per-skill rules
| Skill | Deterministic checks |
|---|---|
rc-cut |
source <version> and rcN; release-build.md declares a build command and non-empty expected artefacts and digest set; release_dist_backend, release_dist_url_template present; digest set has sha512, no md5 / sha1; export_ignore_reviewed set unless source_archive_method: custom or --allow-unreviewed-archive; with signing_mode: ci-automated, reproducibility_source: on and every convenience binary byte-identical; convenience-artefact versions |
vote-draft |
source <version>-rcN; vote_window_hours, vote_dev_list present; window ≥ 72 unless --expedited |
vote-tally |
source <version>-rcN; release_approval_mechanism, result_subject_template present; ASF pinned to dev-list-vote; the approver roster has a row; window (vote_window_hours, or approval_window_hours for other mechanisms) elapsed since --vote-opened unless --force-close |
announce-draft |
source <version>; announce_list, announce_subject_template present; announce-list if and only if ASF; promote timestamp given; one hour since it unless --skip-promote-wait; a Download Page URL (--download-page or download_page_url) |
promote |
source <version>-rcN; release_dist_backend, release_dist_url_template present; for an ASF project the RM on the approver roster (not on it → rm_is_pmc: false, a hand-off, not a blocker); reports whether the trusted-hardware attestation is required; convenience-artefact versions |
verify-rc |
source <version>-rcN; keys_file_url, release_dist_url_template, version_manifest_files present; the four release-build.md sections exist; the staging URL renders well-formed; reports keyserver; convenience-artefact versions |
archive-sweep |
archive_retention_rule, release_dist_backend, release_dist_url_template present; release-trains.md lists a release line; backend is a known one; archive destination derivable |
audit-report |
source <version>; audit_log_path present; the approver roster has a row |
keys-sync |
fingerprint from --fingerprint, then rm_key_fingerprint, then user.md; keys_file_url from --keys-url or config; the optional keys_file_branch from config (null when unset or a placeholder); keyserver from --keyserver or config |
prepare |
sub-command parsed; automated-signing only where the organization offers it; source <version>; release_branch_base, version_manifest_files present; release-trains.md readable |
Wiring
Every release-* skill’s Step 0 — Pre-flight check runs release-config preflight --skill <name> …; rc-cut, vote-draft, announce-draft and promote read their Step 1 config fields from release-config load --skill <name> ….
The workspace hooks (ruff, mypy, pytest) pick the project up from the root pyproject.toml [tool.uv.workspace] members list.
Tests
uv run --project tools/release-config python -m pytest
Every skill has a passing configuration and one case per blocker, and every shared rule has cases across the skills that apply it; load is covered for the layered file lookup, defaults, URL rendering, the convenience-artefact split, and the shipped templates parsing as unset rather than as values.