Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ on:
description: Digest of the repository-owner authorization record; required only for promotion
type: string
required: false
owner_authorization_base64:
description: Canonical owner-authorization JSON encoded as one-line base64; required only for promotion
type: string
required: false

permissions:
contents: read
Expand Down Expand Up @@ -176,7 +180,7 @@ jobs:
echo "Candidate tag: $CANDIDATE_TAG"
echo "Manifest SHA-256: $MANIFEST_SHA256"
echo "Preparation is not publication authorization."
echo "Promotion remains blocked pending SLSA runtime assessment and exact owner authorization."
echo "Promotion remains blocked pending exact owner authorization."
} >> "$GITHUB_STEP_SUMMARY"

promotion-entry-gate:
Expand All @@ -203,13 +207,17 @@ jobs:
CANDIDATE_TAG: ${{ inputs.candidate_tag }}
MANIFEST_SHA256: ${{ inputs.manifest_sha256 }}
OWNER_AUTHORIZATION_SHA256: ${{ inputs.owner_authorization_sha256 }}
OWNER_AUTHORIZATION_BASE64: ${{ inputs.owner_authorization_base64 }}
PREPARATION_RUN_ID: ${{ inputs.preparation_run_id }}
DISPATCH_ACTOR_ID: ${{ github.actor_id }}
run: |
set -euo pipefail
[[ "$CANDIDATE_COMMIT" =~ ^[0-9a-f]{40}$ ]]
[[ "$MANIFEST_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ "$OWNER_AUTHORIZATION_SHA256" =~ ^[0-9a-f]{64}$ ]]
[[ -n "$OWNER_AUTHORIZATION_BASE64" ]]
[[ "$PREPARATION_RUN_ID" =~ ^[0-9]+$ ]]
[[ "$DISPATCH_ACTOR_ID" == 3743841 ]]
[[ "$(git rev-parse HEAD)" == "$CANDIDATE_COMMIT" ]]
[[ "$CANDIDATE_TAG" == "auths-v$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" ]]
- name: Refuse absent or unprotected promotion environment
Expand Down Expand Up @@ -237,8 +245,14 @@ jobs:
CANDIDATE_TAG: ${{ inputs.candidate_tag }}
MANIFEST_SHA256: ${{ inputs.manifest_sha256 }}
OWNER_AUTHORIZATION_SHA256: ${{ inputs.owner_authorization_sha256 }}
OWNER_AUTHORIZATION_BASE64: ${{ inputs.owner_authorization_base64 }}
PREPARATION_RUN_ID: ${{ inputs.preparation_run_id }}
run: |
set -euo pipefail
printf '%s' "$OWNER_AUTHORIZATION_BASE64" \
| base64 --decode > target/owner-authorization.json
[[ "$(sha256sum target/owner-authorization.json | cut -d' ' -f1)" == \
"$OWNER_AUTHORIZATION_SHA256" ]]
jq -n \
--arg commit "$CANDIDATE_COMMIT" \
--arg tag "$CANDIDATE_TAG" \
Expand All @@ -252,13 +266,15 @@ jobs:
cargo xtask release-control verify-promotion
target/staged
target/promotion-request.json
target/owner-authorization.json
- name: Preserve verified promotion input for protected job
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: auths-promotion-input-${{ github.run_id }}-${{ github.run_attempt }}
path: |
target/staged/
target/promotion-request.json
target/owner-authorization.json
if-no-files-found: error
retention-days: 7
compression-level: 0
Expand All @@ -284,12 +300,19 @@ jobs:
CANDIDATE_TAG: ${{ inputs.candidate_tag }}
GH_TOKEN: ${{ github.token }}
MANIFEST_SHA256: ${{ inputs.manifest_sha256 }}
OWNER_AUTHORIZATION_SHA256: ${{ inputs.owner_authorization_sha256 }}
run: |
set -euo pipefail
MANIFEST=target/promotion/staged/target/release-evidence/release-manifest.json
[[ "$(sha256sum "$MANIFEST" | cut -d' ' -f1)" == "$MANIFEST_SHA256" ]]
[[ "$(jq -r '.source.commit' "$MANIFEST")" == "$CANDIDATE_COMMIT" ]]
[[ "$(jq -r '.release.tag' "$MANIFEST")" == "$CANDIDATE_TAG" ]]
AUTHORIZATION=target/promotion/owner-authorization.json
PROMOTION_REQUEST=target/promotion/promotion-request.json
[[ "$(sha256sum "$AUTHORIZATION" | cut -d' ' -f1)" == \
"$OWNER_AUTHORIZATION_SHA256" ]]
[[ "$(jq -r '.ownerAuthorizationSha256' "$PROMOTION_REQUEST")" == \
"$OWNER_AUTHORIZATION_SHA256" ]]
EXISTING="$(gh api "repos/auths-dev/auths-proof/git/ref/tags/$CANDIDATE_TAG" --jq '.object.sha' 2>/dev/null || true)"
[[ -z "$EXISTING" || "$EXISTING" == "$CANDIDATE_COMMIT" ]]
if [[ -z "$EXISTING" ]]; then
Expand All @@ -304,13 +327,18 @@ jobs:
run: |
set -euo pipefail
MANIFEST=target/promotion/staged/target/release-evidence/release-manifest.json
NOTES=target/promotion/staged/target/release-evidence/RELEASE_CANDIDATE_NOTES.md
AUTHORIZATION=target/promotion/owner-authorization.json
PROMOTION_REQUEST=target/promotion/promotion-request.json
mapfile -t SUBJECTS < <(jq -r '.subjects[].name' "$MANIFEST" | sed 's#^#target/promotion/staged/#')
mapfile -t EVIDENCE < <(find target/promotion/staged/target/release-evidence -type f -print | sort)
gh release create "$CANDIDATE_TAG" \
--repo auths-dev/auths-proof \
--verify-tag \
--prerelease \
--title "Auths $CANDIDATE_TAG" \
--notes "Release candidate. Not stable v1. No independent security audit is claimed. Registry publication remains separately gated by issue #50." \
--notes-file "$NOTES" \
"${SUBJECTS[@]}" \
"${EVIDENCE[@]}"
"${EVIDENCE[@]}" \
"$AUTHORIZATION" \
"$PROMOTION_REQUEST"
26 changes: 15 additions & 11 deletions release/CANDIDATE_CLOSURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

## Scope

This record closes the repository-local portion of AP32-PR5. The commit that
merges this bounded change is the proposed candidate revision. It becomes the
final candidate revision only after all required hosted checks and the two
isolated release preparations complete successfully against that exact commit.
This record closes the repository-local portion of AP32-PR5. The initial
candidate revision completed two isolated preparations successfully in
[run 30849197798](https://github.com/auths-dev/auths-proof/actions/runs/30849197798).
That run exposed no remaining reproducibility defect and supplied the runtime
evidence for the builder assessment. The commit that merges the assessment is
the new proposed candidate revision and must complete the same preparation
again because its release metadata changed.

The change intentionally contains no semantic refactor. It advances release
metadata to the first approved RC coordinate, regenerates the semantic-freeze
Expand All @@ -19,8 +22,8 @@ behavior remain unchanged and domain-owned.
- Python distribution `auths`: `1.0.0rc1`, the PEP 440 spelling equivalent to
the SemVer RC.
- Candidate tag contract: `auths-v1.0.0-rc.1`.
- Release public-surface semantic identity: version 4.
- Semantic-freeze inventory: version 4.
- Release public-surface semantic identity: version 15.
- Semantic-freeze inventory: version 15.

The release manifest, rather than this prose, owns the eventual full commit and
artifact digests.
Expand All @@ -37,8 +40,8 @@ The following are hard gates and are not represented as passed by this PR:
4. their deterministic subjects match according to the frozen reproducibility
classes;
5. every manifest subject has verified signed provenance and SPDX coverage;
6. an evidence-backed assessment establishes every applicable SLSA 1.2 Build
Level 3 producer and build-platform requirement; and
6. the checked-in SLSA 1.2 Build Level 3 assessment remains valid for the exact
reusable-builder bytes; and
7. offline verification succeeds from the staged, digest-bound bundle.

If a hosted preparation exposes any defect or drift, this proposed candidate
Expand All @@ -50,9 +53,10 @@ two preparations must start again from its new merged revision.
This repository change does not configure GitHub settings, upload a secret,
run a preparation, approve a manifest, create or move a tag, create a GitHub
prerelease, publish a registry package, engage an external reviewer, or make a
public assurance claim. The SLSA runtime assessment and independent security
review remain pending. Issue #50 continues to govern separately authorized
publication.
public assurance claim. The SLSA build assessment has passed, but the exact
candidate must be prepared again; owner promotion authorization and the Phase
9 independent security review remain pending. Issue #50 continues to govern
separately authorized publication.

## Rollback and withdrawal

Expand Down
22 changes: 22 additions & 0 deletions release/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Auths release directory

This directory owns the frozen release contract and the evidence needed to
prepare, authorize, promote, verify, or withdraw an Auths release candidate.
Preparation is not authorization, and GitHub prerelease promotion is not
registry publication.

Start with:

- [`RELEASE_RUNBOOK.md`](RELEASE_RUNBOOK.md) for the exact operator procedure;
- [`RELEASE_CONTROL.md`](RELEASE_CONTROL.md) for the security architecture and
trust boundaries;
- [`CANDIDATE_CLOSURE.md`](CANDIDATE_CLOSURE.md) for the current candidate and
remaining gates;
- [`RELEASE_CANDIDATE_NOTES.md`](RELEASE_CANDIDATE_NOTES.md) for the text that
will become the GitHub prerelease description; and
- [`SLSA_BUILD_LEVEL_3_ASSESSMENT.md`](SLSA_BUILD_LEVEL_3_ASSESSMENT.md) for
the assessed build-platform boundary.

The JSON schemas, fixtures, subject catalogue, naming authority, and semantic
freeze are machine-enforced inputs. Do not hand-edit generated evidence merely
to make a release pass.
34 changes: 27 additions & 7 deletions release/RELEASE_CONTROL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,33 @@ Promotion requires a second workflow dispatch naming:
- exact release-manifest digest; and
- SHA-256 digest of the repository owner's separate authorization record.

The dispatch also carries the exact canonical authorization record as one-line
base64. The entry gate decodes it, requires the immutable repository-owner
identity, rejects unknown or non-canonical fields, verifies its digest, and
requires every candidate, run, manifest, destination, and statement field to
match the promotion request. The protected job receives and rechecks that
record. Both the authorization and promotion request are preserved with the
GitHub prerelease evidence. The schema is
[`owner-authorization.schema.json`](owner-authorization.schema.json); the
operator procedure is [`RELEASE_RUNBOOK.md`](RELEASE_RUNBOOK.md).

The unprivileged entry job downloads and validates the staged artifact. The
protected `release-promotion` job receives only verified staged bytes. Static
repository policy rejects checkout, compilation, packaging, evidence
generation, or overwriting in that job. It may create the tag only when the tag
is absent, or resume when the existing tag already targets the exact candidate.
It then creates a GitHub prerelease from the staged subjects and evidence.

The first-RC manifest deliberately records the SLSA 1.2 Build Level 3 runtime
assessment as pending. Promotion remains terminally blocked until an
independent assessment of the implemented builder and an actual preparation
run changes that exact manifest field to `passed` through the candidate-closure
process. No label or ordinary successful workflow is treated as that evidence.
It then creates a GitHub prerelease from the staged subjects and evidence. The
prerelease description comes from the digest-bound staged copy of
[`RELEASE_CANDIDATE_NOTES.md`](RELEASE_CANDIDATE_NOTES.md), not mutable or
hardcoded workflow prose.

The SLSA 1.2 Build Level 3 assessment is recorded in
[`SLSA_BUILD_LEVEL_3_ASSESSMENT.md`](SLSA_BUILD_LEVEL_3_ASSESSMENT.md) and its
machine-readable companion. It is bound to an observed successful preparation
and the exact reusable-builder SHA-256. Finalization and offline promotion
verification reject a stale workflow digest, an incomplete requirement set,
or a status other than `passed`. The assessment is not an independent security
audit and does not itself authorize promotion.

Publication to crates.io, npm, and PyPI remains separately gated by
[issue #50](https://github.com/auths-dev/auths-proof/issues/50). This workflow
Expand All @@ -90,6 +105,11 @@ semantic freeze, SPDX subject coverage, and `preparation-comparison.json`.
Verification requires neither repository write access nor an Auths-hosted
service.

After promotion, also verify that `promotion-request.json` names the same
manifest, run, tag, and authorization SHA-256, and that the published canonical
`owner-authorization.json` hashes to that value. These two post-preparation
records authorize distribution; they do not retroactively become build inputs.

## Withdrawal

Never move or delete an issued candidate tag. Mark a defective candidate as
Expand Down
Loading
Loading