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
48 changes: 45 additions & 3 deletions .github/actions/notarization-stage/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ inputs:
runs:
using: composite
steps:
- name: Start this stage invocation without inherited retry authority
shell: bash
env:
STAGE: ${{ inputs.stage }}
run: scripts/macos-resume.sh begin notarization-state "$STAGE" "$BUILD_VERSION"
- name: Prepare exact signed archive
id: prepare
shell: bash
Expand All @@ -46,7 +51,7 @@ runs:
name: Apple-notarization-b${{ env.BUILD_RUN_ID }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s${{ inputs.prepared-slot }}
path: ${{ runner.temp }}/checkpoint-${{ inputs.stage }}-prepared/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Submit once or reuse the existing submission
id: submit
shell: bash
Expand All @@ -64,13 +69,30 @@ runs:
STAGE: ${{ inputs.stage }}
run: scripts/macos-checkpoint.sh snapshot notarization-state "$RUNNER_TEMP/checkpoint-$STAGE-submitted"
- name: Persist submission before waiting
id: submitted-upload
if: always() && steps.submitted-snapshot.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-notarization-b${{ env.BUILD_RUN_ID }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s${{ inputs.submitted-slot }}
path: ${{ runner.temp }}/checkpoint-${{ inputs.stage }}-submitted/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Select public submission receipts
if: always() && steps.submitted-upload.outcome == 'success'
shell: bash
env:
STAGE: ${{ inputs.stage }}
CHECKPOINT_ID: ${{ steps.submitted-upload.outputs.artifact-id }}
CHECKPOINT_DIGEST: ${{ steps.submitted-upload.outputs.artifact-digest }}
run: scripts/macos-receipts.sh notarization-state "$RUNNER_TEMP/receipts-$STAGE-submitted" "$STAGE" "$CHECKPOINT_ID" "$CHECKPOINT_DIGEST"
- name: Retain small public submission receipts
if: always() && steps.submitted-upload.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-receipts-b${{ env.BUILD_RUN_ID }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s${{ inputs.submitted-slot }}
path: ${{ runner.temp }}/receipts-${{ inputs.stage }}-submitted/*
if-no-files-found: error
retention-days: 90
- name: Wait for acceptance and staple
id: finish
shell: bash
Expand All @@ -79,6 +101,8 @@ runs:
APPLE_DEVELOPER_ID_P12_BASE64: ${{ inputs.certificate }}
APPLE_DEVELOPER_ID_P12_PASSWORD: ${{ inputs.certificate-password }}
APPLE_NOTARY_KEY_P8: ${{ inputs.notary-key }}
APPLE_SIGNING_RESULT_FILE: ${{ runner.temp }}/apple-finish-${{ inputs.stage }}.json
APPLE_SIGNING_OPERATION: ${{ inputs.stage }}-finish
run: scripts/with-apple-signing.sh scripts/macos-resume.sh finish notarization-state "$STAGE" "$BUILD_VERSION"
- name: Snapshot latest state after the stage attempt
id: finished-snapshot
Expand All @@ -88,10 +112,28 @@ runs:
STAGE: ${{ inputs.stage }}
run: scripts/macos-checkpoint.sh snapshot notarization-state "$RUNNER_TEMP/checkpoint-$STAGE-finished"
- name: Persist latest state after the stage attempt
id: finished-upload
if: always() && steps.finished-snapshot.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-notarization-b${{ env.BUILD_RUN_ID }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s${{ inputs.finished-slot }}
path: ${{ runner.temp }}/checkpoint-${{ inputs.stage }}-finished/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Select public stage receipts and recovery classification
if: always() && steps.finished-upload.outcome == 'success'
shell: bash
env:
STAGE: ${{ inputs.stage }}
CHECKPOINT_ID: ${{ steps.finished-upload.outputs.artifact-id }}
CHECKPOINT_DIGEST: ${{ steps.finished-upload.outputs.artifact-digest }}
APPLE_SIGNING_RESULT_FILE: ${{ runner.temp }}/apple-finish-${{ inputs.stage }}.json
run: scripts/macos-receipts.sh notarization-state "$RUNNER_TEMP/receipts-$STAGE-finished" "$STAGE" "$CHECKPOINT_ID" "$CHECKPOINT_DIGEST"
- name: Retain small public stage receipts and diagnostics
if: always() && steps.finished-upload.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-receipts-b${{ env.BUILD_RUN_ID }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s${{ inputs.finished-slot }}
path: ${{ runner.temp }}/receipts-${{ inputs.stage }}-finished/*
if-no-files-found: error
retention-days: 90
42 changes: 37 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,27 @@ jobs:
with:
go-version-file: go.mod
cache: true
- name: Go unit, ownership, SQLite, real daemon, HTTPS, and installer regressions
run: go test -p 2 -parallel 2 -race ./cmd/... ./internal/... ./packaging/homebrew ./remote/static -timeout 12m
- name: Go unit, ownership, SQLite, real daemon, and HTTPS regressions
run: bash scripts/test-go-runtime.sh

tooling-tests:
name: Tooling tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [macos-15, macos-15-intel]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true
- name: Full Go tooling and installer regressions
run: go test -p 2 -parallel 2 -race -v ./internal/tooling -timeout 12m

native-tests:
name: Native tests (${{ matrix.os }})
Expand Down Expand Up @@ -135,6 +154,10 @@ jobs:
name: Universal installer
runs-on: macos-15
timeout-minutes: 20
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
EXPECTED_XCODE_VERSION: '16.4'
EXPECTED_XCODE_BUILD: 16F6
outputs:
artifact_name: ${{ steps.artifact-name.outputs.name }}
steps:
Expand All @@ -146,7 +169,16 @@ jobs:
go-version-file: go.mod
cache: true
- name: Build macOS 15+ universal installer app
run: scripts/build-installer.sh
run: |
scripts/apple-toolchain.sh "$RUNNER_TEMP/build-toolchain.json"
scripts/build-installer.sh
- name: Retain credential-free build toolchain identity
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-build-toolchain-${{ github.run_attempt }}
path: ${{ runner.temp }}/build-toolchain.json
if-no-files-found: error
retention-days: 90
- name: Generate immutable release manifest and Homebrew cask
run: |
go run -p 2 ./cmd/queue-tool render-cask \
Expand All @@ -170,14 +202,14 @@ jobs:
name: Agent-Job-Queue-Installer-${{ github.run_attempt }}
path: dist/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Record validated installer artifact
id: artifact-name
run: printf 'name=Agent-Job-Queue-Installer-%s\n' "$GITHUB_RUN_ATTEMPT" >> "$GITHUB_OUTPUT"

ci-required:
name: ci-required
needs: [commit-policy, lint, vulnerability-check, macos-tests, native-tests, web-tests, installer]
needs: [commit-policy, lint, vulnerability-check, macos-tests, tooling-tests, native-tests, web-tests, installer]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/distribution-recovery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Distribution recovery

on:
workflow_run:
workflows: [Distribution]
types: [completed]
branches: [main]
schedule:
- cron: '7,22,37,52 * * * *'
workflow_dispatch:

permissions:
contents: read
actions: write

concurrency:
group: distribution-recovery
cancel-in-progress: false

jobs:
recover:
name: Continue bound Apple submissions and exact delivery
if: >-
github.ref == 'refs/heads/main' &&
(github.event_name != 'workflow_run' ||
(github.event.workflow_run.path == '.github/workflows/distribution.yml' &&
github.event.workflow_run.repository.full_name == github.repository &&
github.event.workflow_run.head_repository.full_name == github.repository))
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Observe and continue eligible retained candidates
env:
GH_TOKEN: ${{ github.token }}
run: scripts/distribution-recover.sh
74 changes: 47 additions & 27 deletions .github/workflows/distribution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ jobs:
BUILD_VERSION: ${{ needs.source.outputs.version }}
BUILD_TAG: ${{ needs.source.outputs.tag }}
AUTOMATION_SHA: ${{ needs.source.outputs.automation_sha }}
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
EXPECTED_XCODE_VERSION: '16.4'
EXPECTED_XCODE_BUILD: 16F6
APPLE_SIGNING_IDENTITY: ${{ vars.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
APPLE_NOTARY_KEY_ID: ${{ vars.APPLE_NOTARY_KEY_ID }}
Expand Down Expand Up @@ -114,6 +117,10 @@ jobs:
run: |
go build -p 2 -trimpath -o "$QUEUE_NOTARY_TOOL" ./cmd/queue-tool
printf 'QUEUE_NOTARY_TOOL=%s\n' "$QUEUE_NOTARY_TOOL" >> "$GITHUB_ENV"
- name: Qualify and record selected Apple tools before credentials
run: |
scripts/apple-toolchain.sh "$RUNNER_TEMP/apple-toolchain.json"
printf 'APPLE_TOOLCHAIN_RECEIPT=%s/apple-toolchain.json\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
- name: Verify original artifact provenance and bytes before recovery
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -135,9 +142,8 @@ jobs:
run: scripts/macos-resume.sh init 'prepared-installer/Agent Job Queue Installer.app' notarization-state "$BUILD_VERSION"
- name: Bind retained initial installer to the exact original source bytes
run: |
"$QUEUE_NOTARY_TOOL" verify-checkpoint-origin \
--original "validated-installer/Agent-Job-Queue-Installer-$BUILD_VERSION-universal.zip" \
--initial notarization-state/initial.zip
scripts/macos-resume.sh bind-origin notarization-state \
"validated-installer/Agent-Job-Queue-Installer-$BUILD_VERSION-universal.zip" "$BUILD_VERSION"
- name: Verify restored build identity and signed archives
run: scripts/macos-resume.sh verify-state notarization-state "$BUILD_VERSION"
- name: Snapshot state before credential setup
Expand All @@ -148,7 +154,7 @@ jobs:
name: Apple-notarization-b${{ needs.source.outputs.run_id }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s00
path: ${{ runner.temp }}/checkpoint-initial/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Notarize the installed payload
uses: ./.github/actions/notarization-stage
with:
Expand Down Expand Up @@ -179,21 +185,54 @@ jobs:
certificate: ${{ secrets.APPLE_DEVELOPER_ID_P12_BASE64 }}
certificate-password: ${{ secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}
notary-key: ${{ secrets.APPLE_NOTARY_KEY_P8 }}
- name: Retain immutable publication evidence for this candidate
run: scripts/macos-resume.sh evidence notarization-state "$BUILD_VERSION"
- name: Export only completed notarized assets
id: export
run: scripts/macos-resume.sh export notarization-state dist "$BUILD_VERSION"
- name: Snapshot final candidate after export verification
id: export-snapshot
if: always() && steps.export.outcome != 'skipped'
run: scripts/macos-checkpoint.sh snapshot notarization-state "$RUNNER_TEMP/checkpoint-export"
- name: Persist final candidate after export verification
id: export-upload
if: always() && steps.export-snapshot.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-notarization-b${{ needs.source.outputs.run_id }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s10
path: ${{ runner.temp }}/checkpoint-export/*
if-no-files-found: error
retention-days: 90
- name: Select final candidate receipts and export continuation
if: always() && steps.export-upload.outcome == 'success'
env:
CHECKPOINT_ID: ${{ steps.export-upload.outputs.artifact-id }}
CHECKPOINT_DIGEST: ${{ steps.export-upload.outputs.artifact-digest }}
APPLE_SIGNING_RESULT_FILE: ${{ runner.temp }}/apple-finish-dmg.json
run: scripts/macos-receipts.sh notarization-state "$RUNNER_TEMP/receipts-export" dmg "$CHECKPOINT_ID" "$CHECKPOINT_DIGEST"
- name: Retain final candidate public receipts and diagnostics
if: always() && steps.export-upload.outcome == 'success'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Apple-receipts-b${{ needs.source.outputs.run_id }}-r${{ github.run_id }}-a${{ github.run_attempt }}-s10
path: ${{ runner.temp }}/receipts-export/*
if-no-files-found: error
retention-days: 90
- name: Generate final immutable manifest and Homebrew cask
run: |
"$QUEUE_NOTARY_TOOL" render-cask \
--archive "dist/Agent-Job-Queue-Installer-$BUILD_VERSION-universal.zip" \
--version "$BUILD_VERSION" --tag "$BUILD_TAG" \
--commit "$BUILD_SHA" --build "$BUILD_RUN_NUMBER" --output dist
- name: Bind permanent public Apple evidence to the exact final assets
run: cp notarization-state/publication.json dist/apple-notarization.json
- name: Upload notarized installer and final checksums
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Agent-Job-Queue-Notarized-Installer-${{ github.run_attempt }}
path: dist/*
if-no-files-found: error
retention-days: 30
retention-days: 90
- name: Record final artifact identity
id: final-artifact-name
run: printf 'name=Agent-Job-Queue-Notarized-Installer-%s\n' "$GITHUB_RUN_ATTEMPT" >> "$GITHUB_OUTPUT"
Expand All @@ -202,7 +241,8 @@ jobs:
run: |
{
printf '### Apple notarization attempt %s\n\n' "$GITHUB_RUN_ATTEMPT"
printf 'Checkpoints retain signed archives and public submission receipts for 30 days.\n\n'
printf 'Checkpoints retain exact signed archives for 90 days; small public receipts are retained independently for 90 days and published with successful releases.\n\n'
printf 'The automatic observer resumes classified pending/ticket failures with the same run, candidate and UUID. Its bounded retry budget is separate from Apple acceptance.\n\n'
printf 'Use workflow_dispatch on main with build_run_id=%s to apply current automation to these original bytes.\n\n' "$BUILD_RUN_ID"
printf 'CLI command: gh run rerun %s --failed --repo %s\n\n' "$GITHUB_RUN_ID" "$GITHUB_REPOSITORY"
printf 'No pending or rejected artifact is published. Missing state or an uncertain upload stops recovery for manual reconciliation.\n'
Expand Down Expand Up @@ -292,27 +332,7 @@ jobs:
- name: Publish immutable main build
env:
GH_TOKEN: ${{ github.token }}
run: |
tag="$BUILD_TAG"
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
existing=$(mktemp -d)
trap 'rm -rf "$existing"' EXIT
gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json targetCommitish > "$existing/release.json"
for asset in dist/*; do
gh release download "$tag" --repo "$GITHUB_REPOSITORY" --pattern "${asset##*/}" --dir "$existing"
done
go run -p 2 ./cmd/queue-tool verify-release --existing "$existing" --assets dist --commit "$BUILD_SHA"
else
notes=$(mktemp)
trap 'rm -f "$notes"' EXIT
printf '%s\n\n%s\n' \
"Automated installer from validated main commit ${BUILD_SHA}. Includes Apple Silicon and Intel executables plus checksum-pinned Homebrew metadata." \
'Signed with Apple Developer ID and notarized by Apple. The installer, installed menu app and DMG carry stapled tickets. Homebrew preserves quarantine. Standalone CLI first launch can require access to Apple notarization services.' > "$notes"
gh release create "$tag" dist/* \
--repo "$GITHUB_REPOSITORY" --target "$BUILD_SHA" \
--title "Main build ${BUILD_RUN_NUMBER}" --prerelease \
--notes-file "$notes"
fi
run: scripts/distribution-publish.sh dist

homebrew-sync:
name: Verify Homebrew delivery
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Run the complete finite validation batch through your shared heavy-job queue:
agent-heavy run --label "Agent Queue validation" -- bash scripts/validate.sh
```

The batch checks source portability, Go formatting and vet, known reachable Go vulnerabilities, shell and workflow lint, Go race tests, standalone trusted tap tests, Linux compilation of CI tools, browser schema and interaction tests, native regressions, and the universal installer. Hosted CI independently runs the runtime suite on Apple Silicon and Intel Macs.
The batch checks source portability, Go formatting and vet, known reachable Go vulnerabilities, shell and workflow lint, Go race tests, standalone trusted tap tests, Linux compilation of CI tools, browser schema and interaction tests, native regressions, and the universal installer. Hosted CI runs separate required runtime, tooling/installer, and native test matrices on Apple Silicon and Intel Macs. Each keeps its 20-minute job budget; Go tests retain race detection, two package/test workers and the 12-minute test timeout. The runtime adapter dynamically enumerates the original Go scope and excludes only `internal/tooling`, which runs completely in its own matrix. Tooling emits per-test timings for timeout diagnosis. All matrices remain required by `ci-required`.

For focused development, these commands use only this checkout and private fixtures:

Expand Down
Loading
Loading