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
11 changes: 11 additions & 0 deletions .asf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ github:
policies:
- name: "v*"
type: tag
product-release:
required_reviewers:
- id: M4n5ter
type: User
wait_timer: 0
prevent_self_review: true
deployment_branch_policy:
protected_branches: false
policies:
- name: main
type: branch

notifications:
commits: commits@maka.apache.org
Expand Down
54 changes: 37 additions & 17 deletions .github/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@

# Product release checklist

The `Release` workflow is Maka's convenience-artifact release entry point. Desktop and CLI/TUI are
The IPMC-approved source archive on ASF distribution infrastructure is the Apache release. The
`Release` workflow is Maka's convenience-artifact distribution entry point. Desktop and CLI/TUI are
built from the exact IPMC-approved ASF source candidate commit. They share that source commit, the
root product version, one convenience tag, one GitHub Release, one Draft decision, and one release
root product version, one convenience tag, one GitHub Release, one Draft decision, and one distribution
gate. The workflow creates no Draft until every required artifact job succeeds.

Phase 1 requires:
Expand All @@ -31,7 +32,7 @@ Phase 1 requires:
- the signed, notarized, relocatable Apple Silicon CLI/TUI ZIP;
- checksums generated after each artifact reaches its final form.

The ASF Desktop artifacts must not contain a Git runtime, a bundled-Git manifest, or Git/Dugite
The convenience Desktop artifacts must not contain a Git runtime, a bundled-Git manifest, or Git/Dugite
redistribution notices. Managed-workspace execution remains unavailable until a separately reviewed,
ASF-compatible verified runtime is connected before admission/T1.

Expand Down Expand Up @@ -60,10 +61,13 @@ must never be exposed to fork or ordinary pull-request jobs.
Before the first product release, confirm the checked-in `.asf.yaml` has reconciled the live repository:

- the `Immutable release tags` ruleset blocks updates, force-pushes, and deletions of `v*` tags;
- the `release` and `npm-release` Environments accept only their declared tag patterns and require a reviewer other than the triggering user;
- enable immutable releases so assets and the associated tag cannot change after publication.
- the `release` and `npm-release` Environments accept only their declared tag patterns,
`product-release` accepts only `main`, and each requires a reviewer other than the triggering user.

These controls close the check-to-upload and check-to-stage windows. Keep the Release in Draft while assets and acceptance are incomplete; publishing early must make subsequent mutation fail closed.
These controls close the check-to-upload and check-to-stage windows. Finalize uses GitHub Actions
OIDC rather than a stored signing key to attest every convenience artifact. Keep the Release in
Draft while assets and acceptance are incomplete; Desktop rejects downloaded updates whose exact
bytes and expected filename are not covered by that protected workflow identity.

## Create the complete Draft

Expand Down Expand Up @@ -106,17 +110,21 @@ then rerun. If only the tag exists, the retry creates the missing Draft.

Follow [the npm release runbook](../docs/cli-npm-release.md) against the exact product tag and Draft:

1. Run **Stage CLI npm release** from `v<version>` and record its successful run ID and attempt.
1. Record the successful **Release** workflow run ID and attempt that built the Draft assets. Run
**Stage CLI npm release** from `v<version>` and record its successful run ID and attempt.
2. Inspect the staged tarball and provenance, then approve that exact stage with npm 2FA.
3. Run **Finalize CLI npm channel** from `main` and confirm it verifies the public package bytes,
provenance, signature, and release dist-tag.
3. Run **Finalize product release** from `main`. Its first job verifies the public package
bytes, provenance, signature, and release dist-tag.
4. Install the exact public version on each release platform and complete the npm acceptance steps.

Keep the GitHub Release in Draft throughout this sequence. A failed or rejected npm candidate
requires a new product version; never publish the Draft to work around npm state.

When every npm and cross-machine acceptance check has passed, publish the Draft. Mark a stable
release as Latest at that final publication boundary; prereleases must remain non-Latest.
Keep the GitHub Release in Draft throughout this sequence. The final workflow job waits at the
`product-release` Environment. Approve it only after every npm and cross-machine acceptance check
has passed. It verifies the live Draft digests against the immutable publication record from the
exact successful Release run, creates Sigstore provenance and an offline
`Maka-<version>-attestation.sigstore.json` bundle, then publishes the convenience Release and makes a
stable release Latest in the same GitHub operation; prereleases remain non-Latest. Do not publish or
change the Latest designation manually. A failed or rejected npm candidate requires a new product
version; never publish the Draft to work around npm state.

## Acceptance on another Apple Silicon Mac

Expand Down Expand Up @@ -155,8 +163,20 @@ Download the installer, Windows Desktop ZIP, and both checksum files through a b
7. Add a clean remote Runtime Host from the packaged Desktop app. Confirm setup installs the exact
public `maka-agent@<version>` package and the remote session completes one model turn.

Immediately before publication, reverify that the approved ASF candidate tag and convenience
`v<version>` tag still resolve to the same recorded commit. Publish only after npm Finalize and both
independent-machine acceptance passes. If any required artifact, npm step, or
Immediately before approving the `product-release` Environment, reverify that the approved ASF
candidate tag and convenience `v<version>` tag still resolve to the same recorded commit. Approve
only after npm verification and both independent-machine acceptance passes. If any required artifact, npm step, or
acceptance step fails, keep the Draft unpublished, fix the issue, increment the root product
version, and run the full workflow again. Never replace an existing release identity.

After Finalize publishes the convenience Release, download its attestation bundle and verify each
installer or archive independently:

```sh
gh attestation verify path/to/Maka-<version>-mac-arm64.zip \
--bundle path/to/Maka-<version>-attestation.sigstore.json \
--repo apache/maka \
--signer-workflow apache/maka/.github/workflows/release-cli-finalize.yml
```

Desktop performs the same trust decision before exposing a downloaded update for installation.
189 changes: 186 additions & 3 deletions .github/workflows/release-cli-finalize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.

name: Finalize CLI npm channel
name: Finalize product release

on:
workflow_dispatch:
Expand All @@ -28,6 +28,14 @@ on:
description: Successful Stage CLI npm release workflow run attempt
required: true
type: string
release_run_id:
description: Successful Release workflow run ID that built the Draft assets
required: true
type: string
release_run_attempt:
description: Successful Release workflow run attempt that built the Draft assets
required: true
type: string
version:
description: Exact staged maka-agent product version
required: true
Expand All @@ -38,21 +46,28 @@ permissions:
contents: read

concurrency:
group: cli-npm-finalize
group: product-release
cancel-in-progress: false

jobs:
inspect:
name: Verify the public npm channel
runs-on: ubuntu-24.04
timeout-minutes: 20
outputs:
product_tag: ${{ steps.release.outputs.product_tag }}
product_version: ${{ steps.release.outputs.version }}
release_run_id: ${{ steps.release-run.outputs.run_id }}
release_run_attempt: ${{ steps.release-run.outputs.run_attempt }}
source_commit: ${{ steps.release.outputs.source_commit }}
source_reference_tag: ${{ steps.authority.outputs.source_reference_tag }}
steps:
- name: Require main
env:
RELEASE_REF: ${{ github.ref }}
run: |
if [[ "$RELEASE_REF" != "refs/heads/main" ]]; then
echo "CLI npm finalization must be dispatched from main; found $RELEASE_REF" >&2
echo "Product finalization must be dispatched from main; found $RELEASE_REF" >&2
exit 1
fi

Expand All @@ -72,6 +87,25 @@ jobs:
fi
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$STAGE_RUN_ID/attempts/$STAGE_RUN_ATTEMPT" > "$RUNNER_TEMP/stage-run.json"

- name: Load the exact Release workflow run
id: release-run
env:
GH_TOKEN: ${{ github.token }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_RUN_ATTEMPT: ${{ inputs.release_run_attempt }}
run: |
if [[ ! "$RELEASE_RUN_ID" =~ ^[1-9][0-9]*$ ]]; then
echo "Release workflow run ID must be a positive integer" >&2
exit 1
fi
if [[ ! "$RELEASE_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then
echo "Release workflow run attempt must be a positive integer" >&2
exit 1
fi
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RELEASE_RUN_ID/attempts/$RELEASE_RUN_ATTEMPT" > "$RUNNER_TEMP/release-run.json"
echo "run_id=$RELEASE_RUN_ID" >> "$GITHUB_OUTPUT"
echo "run_attempt=$RELEASE_RUN_ATTEMPT" >> "$GITHUB_OUTPUT"

- name: Check out the current release verifier
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -96,6 +130,15 @@ jobs:
repository: ${{ github.repository }}
run-id: ${{ inputs.stage_run_id }}

- name: Download the publication record
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: product-release-record-${{ inputs.release_run_attempt }}
path: ${{ runner.temp }}/product-release-record
github-token: ${{ github.token }}
repository: ${{ github.repository }}
run-id: ${{ inputs.release_run_id }}

- name: Verify the stage run and release record
id: release
env:
Expand All @@ -108,13 +151,33 @@ jobs:
"$GITHUB_OUTPUT"

- name: Revalidate the product release authority
id: authority
env:
GH_TOKEN: ${{ github.token }}
PRODUCT_SOURCE_COMMIT: ${{ steps.release.outputs.source_commit }}
PRODUCT_TAG: ${{ steps.release.outputs.product_tag }}
RELEASE_RUN_ATTEMPT: ${{ steps.release-run.outputs.run_attempt }}
RELEASE_RUN_ID: ${{ steps.release-run.outputs.run_id }}
run: |
node scripts/product-release-authority.mjs verify-build-run \
"$RUNNER_TEMP/release-run.json" \
"$PRODUCT_TAG" \
"$PRODUCT_SOURCE_COMMIT" \
"$GITHUB_REPOSITORY" \
"$RELEASE_RUN_ID" \
"$RELEASE_RUN_ATTEMPT"
source_reference_tag="$(jq -r .head_branch "$RUNNER_TEMP/release-run.json")"
node scripts/product-release-artifacts.mjs inspect-record \
"$RUNNER_TEMP/product-release-record/product-release.json" \
"$GITHUB_REPOSITORY" \
"$PRODUCT_TAG" \
"$PRODUCT_SOURCE_COMMIT" \
"$source_reference_tag" \
"$RELEASE_RUN_ID" \
"$RELEASE_RUN_ATTEMPT"
node scripts/product-release-authority.mjs verify-draft \
"$PRODUCT_TAG" "$PRODUCT_SOURCE_COMMIT" "$GITHUB_REPOSITORY"
echo "source_reference_tag=$source_reference_tag" >> "$GITHUB_OUTPUT"

- name: Fetch and verify the public registry bytes
run: |
Expand All @@ -141,3 +204,123 @@ jobs:
if-no-files-found: error
compression-level: 0
retention-days: 30

publish:
name: Publish the attested convenience release
needs: inspect
runs-on: ubuntu-24.04
timeout-minutes: 30
environment:
name: product-release
url: https://github.com/apache/maka/releases/tag/${{ needs.inspect.outputs.product_tag }}
permissions:
actions: read
artifact-metadata: write
attestations: write
contents: write
id-token: write
steps:
- name: Check out the current release verifier
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
package-manager-cache: false

- name: Download the exact verified Release run artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: release-*-${{ needs.inspect.outputs.release_run_attempt }}
path: ${{ runner.temp }}/product-release
merge-multiple: true
github-token: ${{ github.token }}
repository: ${{ github.repository }}
run-id: ${{ needs.inspect.outputs.release_run_id }}

- name: Download the publication record
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: product-release-record-${{ needs.inspect.outputs.release_run_attempt }}
path: ${{ runner.temp }}/product-release-record
github-token: ${{ github.token }}
repository: ${{ github.repository }}
run-id: ${{ needs.inspect.outputs.release_run_id }}

- name: Verify the exact publication input
env:
GH_TOKEN: ${{ github.token }}
PRODUCT_SOURCE_COMMIT: ${{ needs.inspect.outputs.source_commit }}
PRODUCT_SOURCE_REFERENCE_TAG: ${{ needs.inspect.outputs.source_reference_tag }}
PRODUCT_TAG: ${{ needs.inspect.outputs.product_tag }}
RELEASE_RUN_ATTEMPT: ${{ needs.inspect.outputs.release_run_attempt }}
RELEASE_RUN_ID: ${{ needs.inspect.outputs.release_run_id }}
run: |
node scripts/product-release-authority.mjs verify-publication \
"$PRODUCT_TAG" \
"$PRODUCT_SOURCE_COMMIT" \
"$GITHUB_REPOSITORY" \
"$RUNNER_TEMP/product-release" \
"$RUNNER_TEMP/product-release-record/product-release.json" \
"$PRODUCT_SOURCE_REFERENCE_TAG" \
"$RELEASE_RUN_ID" \
"$RELEASE_RUN_ATTEMPT"

- name: Attest the verified convenience artifacts
id: attest
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.0.0
with:
subject-path: ${{ runner.temp }}/product-release/*

- name: Verify the issued provenance
env:
ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }}
CERTIFICATE_IDENTITY: https://github.com/${{ github.repository }}/.github/workflows/release-cli-finalize.yml@refs/heads/main
GH_TOKEN: ${{ github.token }}
run: |
verified=0
while IFS= read -r -d '' artifact; do
gh attestation verify "$artifact" \
--bundle "$ATTESTATION_BUNDLE" \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$CERTIFICATE_IDENTITY" \
--cert-oidc-issuer https://token.actions.githubusercontent.com
verified=$((verified + 1))
done < <(find "$RUNNER_TEMP/product-release" -maxdepth 1 -type f -print0)
if (( verified == 0 )); then
echo "No product release artifacts were verified" >&2
exit 1
fi

- name: Name the offline verification bundle
env:
ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }}
PRODUCT_VERSION: ${{ needs.inspect.outputs.product_version }}
run: >-
cp -- "$ATTESTATION_BUNDLE"
"$RUNNER_TEMP/Maka-${PRODUCT_VERSION}-attestation.sigstore.json"

- name: Publish the verified convenience release
env:
GH_TOKEN: ${{ github.token }}
PRODUCT_SOURCE_COMMIT: ${{ needs.inspect.outputs.source_commit }}
PRODUCT_SOURCE_REFERENCE_TAG: ${{ needs.inspect.outputs.source_reference_tag }}
PRODUCT_TAG: ${{ needs.inspect.outputs.product_tag }}
PRODUCT_VERSION: ${{ needs.inspect.outputs.product_version }}
RELEASE_RUN_ATTEMPT: ${{ needs.inspect.outputs.release_run_attempt }}
RELEASE_RUN_ID: ${{ needs.inspect.outputs.release_run_id }}
run: |
node scripts/product-release-authority.mjs publish-draft \
"$PRODUCT_TAG" \
"$PRODUCT_SOURCE_COMMIT" \
"$GITHUB_REPOSITORY" \
"$RUNNER_TEMP/product-release" \
"$RUNNER_TEMP/product-release-record/product-release.json" \
"$PRODUCT_SOURCE_REFERENCE_TAG" \
"$RELEASE_RUN_ID" \
"$RELEASE_RUN_ATTEMPT" \
"$RUNNER_TEMP/Maka-${PRODUCT_VERSION}-attestation.sigstore.json"
2 changes: 1 addition & 1 deletion .github/workflows/release-cli-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ jobs:
echo "## maka-agent@$RELEASE_VERSION staging"
echo
echo "After this workflow succeeds, review and approve the staged package with 2FA on npmjs.com."
echo "After the package becomes public, run **Finalize CLI npm channel** with:"
echo "After the package becomes public, run **Finalize product release** with:"
echo
echo "- stage run ID: \`$RELEASE_RUN_ID\`"
echo "- stage run attempt: \`$RELEASE_RUN_ATTEMPT\`"
Expand Down
Loading
Loading