chore(ci): document and harden workflow pipeline (#241)
* docs(ci): add CI workflow map and cross-links * chore(ci): harden workflow determinism and safety * chore(ci): address workflow review feedback * style(ci): normalize workflow and ci-map formatting
This commit is contained in:
parent
3014926687
commit
82ffb36f90
9 changed files with 322 additions and 260 deletions
279
.github/workflows/ci.yml
vendored
279
.github/workflows/ci.yml
vendored
|
|
@ -1,178 +1,161 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, develop]
|
branches: [main, develop]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.event.pull_request.number || github.sha }}
|
group: ci-${{ github.event.pull_request.number || github.sha }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changes:
|
changes:
|
||||||
name: Detect Change Scope
|
name: Detect Change Scope
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
docs_only: ${{ steps.scope.outputs.docs_only }}
|
docs_only: ${{ steps.scope.outputs.docs_only }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Detect docs-only changes
|
- name: Detect docs-only changes
|
||||||
id: scope
|
id: scope
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||||
BASE="${{ github.event.pull_request.base.sha }}"
|
BASE="${{ github.event.pull_request.base.sha }}"
|
||||||
else
|
else
|
||||||
BASE="${{ github.event.before }}"
|
BASE="${{ github.event.before }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
||||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
||||||
if [ -z "$CHANGED" ]; then
|
if [ -z "$CHANGED" ]; then
|
||||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_only=true
|
docs_only=true
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
[ -z "$file" ] && continue
|
[ -z "$file" ] && continue
|
||||||
|
|
||||||
if [[ "$file" == docs/* ]] \
|
if [[ "$file" == docs/* ]] \
|
||||||
|| [[ "$file" == *.md ]] \
|
|| [[ "$file" == *.md ]] \
|
||||||
|| [[ "$file" == *.mdx ]] \
|
|| [[ "$file" == *.mdx ]] \
|
||||||
|| [[ "$file" == "LICENSE" ]] \
|
|| [[ "$file" == "LICENSE" ]] \
|
||||||
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
||||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_only=false
|
docs_only=false
|
||||||
break
|
break
|
||||||
done <<< "$CHANGED"
|
done <<< "$CHANGED"
|
||||||
|
|
||||||
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
|
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Format & Lint
|
name: Format & Lint
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
if: needs.changes.outputs.docs_only != 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
with:
|
||||||
- name: Detect Rust source changes
|
toolchain: 1.92
|
||||||
id: rust_changes
|
components: rustfmt, clippy
|
||||||
shell: bash
|
- uses: Swatinem/rust-cache@v2
|
||||||
run: |
|
- name: Run rustfmt
|
||||||
set -euo pipefail
|
run: cargo fmt --all -- --check
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy --locked --all-targets -- -D warnings
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
test:
|
||||||
BASE="${{ github.event.pull_request.base.sha }}"
|
name: Test
|
||||||
CHANGED="$(git diff --name-only "$BASE" HEAD -- '*.rs' || true)"
|
needs: [changes]
|
||||||
else
|
if: needs.changes.outputs.docs_only != 'true'
|
||||||
CHANGED="$(git diff --name-only "${{ github.event.before }}" HEAD -- '*.rs' || true)"
|
runs-on: ubuntu-latest
|
||||||
fi
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
toolchain: 1.92
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
if [ -z "$CHANGED" ]; then
|
build:
|
||||||
echo "has_rust_changes=false" >> "$GITHUB_OUTPUT"
|
name: Build (Smoke)
|
||||||
exit 0
|
needs: [changes]
|
||||||
fi
|
if: needs.changes.outputs.docs_only != 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
|
||||||
echo "has_rust_changes=true" >> "$GITHUB_OUTPUT"
|
steps:
|
||||||
- name: Run rustfmt
|
- uses: actions/checkout@v4
|
||||||
if: steps.rust_changes.outputs.has_rust_changes == 'true'
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
run: cargo fmt --all -- --check
|
with:
|
||||||
- name: Run clippy
|
toolchain: 1.92
|
||||||
if: steps.rust_changes.outputs.has_rust_changes == 'true'
|
- uses: Swatinem/rust-cache@v2
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
- name: Build release binary
|
||||||
- name: Skip rust lint (no Rust changes)
|
run: cargo build --release --locked --verbose
|
||||||
if: steps.rust_changes.outputs.has_rust_changes != 'true'
|
|
||||||
run: echo "No Rust source changes detected; skipping rustfmt and clippy."
|
|
||||||
|
|
||||||
test:
|
docs-only:
|
||||||
name: Test
|
name: Docs-Only Fast Path
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
if: needs.changes.outputs.docs_only == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
steps:
|
||||||
steps:
|
- name: Skip heavy jobs for docs-only change
|
||||||
- uses: actions/checkout@v4
|
run: echo "Docs-only change detected. Rust lint/test/build skipped."
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
||||||
|
|
||||||
build:
|
ci-required:
|
||||||
name: Build (Smoke)
|
name: CI Required Gate
|
||||||
needs: [changes]
|
if: always()
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
needs: [changes, lint, test, build, docs-only]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
steps:
|
||||||
|
- name: Enforce required status
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
steps:
|
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
|
||||||
- uses: actions/checkout@v4
|
echo "Docs-only fast path passed."
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
exit 0
|
||||||
- uses: Swatinem/rust-cache@v2
|
fi
|
||||||
- name: Build release binary
|
|
||||||
run: cargo build --release --locked --verbose
|
|
||||||
|
|
||||||
docs-only:
|
lint_result="${{ needs.lint.result }}"
|
||||||
name: Docs-Only Fast Path
|
test_result="${{ needs.test.result }}"
|
||||||
needs: [changes]
|
build_result="${{ needs.build.result }}"
|
||||||
if: needs.changes.outputs.docs_only == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Skip heavy jobs for docs-only change
|
|
||||||
run: echo "Docs-only change detected. Rust lint/test/build skipped."
|
|
||||||
|
|
||||||
ci-required:
|
echo "lint=${lint_result}"
|
||||||
name: CI Required Gate
|
echo "test=${test_result}"
|
||||||
if: always()
|
echo "build=${build_result}"
|
||||||
needs: [changes, lint, test, build, docs-only]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Enforce required status
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
|
if [ "$lint_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
|
||||||
echo "Docs-only fast path passed."
|
echo "Required CI jobs did not pass."
|
||||||
exit 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
lint_result="${{ needs.lint.result }}"
|
echo "All required CI jobs passed."
|
||||||
test_result="${{ needs.test.result }}"
|
|
||||||
build_result="${{ needs.build.result }}"
|
|
||||||
|
|
||||||
echo "lint=${lint_result}"
|
|
||||||
echo "test=${test_result}"
|
|
||||||
echo "build=${build_result}"
|
|
||||||
|
|
||||||
if [ "$lint_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
|
|
||||||
echo "Required CI jobs did not pass."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All required CI jobs passed."
|
|
||||||
|
|
|
||||||
174
.github/workflows/docker.yml
vendored
174
.github/workflows/docker.yml
vendored
|
|
@ -1,105 +1,105 @@
|
||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- "docker-compose.yml"
|
- "docker-compose.yml"
|
||||||
- "dev/docker-compose.yml"
|
- "dev/docker-compose.yml"
|
||||||
- "dev/sandbox/**"
|
- "dev/sandbox/**"
|
||||||
- ".github/workflows/docker.yml"
|
- ".github/workflows/docker.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: docker-${{ github.event.pull_request.number || github.ref }}
|
group: docker-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pr-smoke:
|
pr-smoke:
|
||||||
name: PR Docker Smoke
|
name: PR Docker Smoke
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels)
|
- name: Extract metadata (tags, labels)
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
|
||||||
- name: Build smoke image
|
- name: Build smoke image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: zeroclaw-pr-smoke:latest
|
tags: zeroclaw-pr-smoke:latest
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
|
||||||
- name: Verify image
|
- name: Verify image
|
||||||
run: docker run --rm zeroclaw-pr-smoke:latest --version
|
run: docker run --rm zeroclaw-pr-smoke:latest --version
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Build and Push Docker Image
|
name: Build and Push Docker Image
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name == 'push'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
- name: Log in to Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels)
|
- name: Extract metadata (tags, labels)
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
platforms: ${{ startsWith(github.ref, 'refs/tags/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
|
platforms: ${{ startsWith(github.ref, 'refs/tags/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
|
||||||
|
|
|
||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
|
@ -14,7 +14,9 @@ jobs:
|
||||||
build-release:
|
build-release:
|
||||||
name: Build ${{ matrix.target }}
|
name: Build ${{ matrix.target }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 40
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
|
|
@ -73,6 +75,7 @@ jobs:
|
||||||
name: Publish Release
|
name: Publish Release
|
||||||
needs: build-release
|
needs: build-release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
|
||||||
60
.github/workflows/security.yml
vendored
60
.github/workflows/security.yml
vendored
|
|
@ -1,37 +1,47 @@
|
||||||
name: Security Audit
|
name: Security Audit
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 6 * * 1" # Weekly on Monday 6am UTC
|
- cron: "0 6 * * 1" # Weekly on Monday 6am UTC
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: security-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
audit:
|
audit:
|
||||||
name: Security Audit
|
name: Security Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
timeout-minutes: 20
|
||||||
- uses: actions/checkout@v4
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Install cargo-audit
|
- name: Install cargo-audit
|
||||||
run: cargo install --locked cargo-audit --version 0.22.1
|
run: cargo install --locked cargo-audit --version 0.22.1
|
||||||
|
|
||||||
- name: Run cargo-audit
|
- name: Run cargo-audit
|
||||||
run: cargo audit
|
run: cargo audit
|
||||||
|
|
||||||
deny:
|
deny:
|
||||||
name: License & Supply Chain
|
name: License & Supply Chain
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
timeout-minutes: 20
|
||||||
- uses: actions/checkout@v4
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
with:
|
with:
|
||||||
command: check advisories licenses sources
|
command: check advisories licenses sources
|
||||||
|
|
|
||||||
2
.github/workflows/workflow-sanity.yml
vendored
2
.github/workflows/workflow-sanity.yml
vendored
|
|
@ -23,6 +23,7 @@ permissions:
|
||||||
jobs:
|
jobs:
|
||||||
no-tabs:
|
no-tabs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
@ -55,6 +56,7 @@ jobs:
|
||||||
|
|
||||||
actionlint:
|
actionlint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ When PR traffic is high (especially with AI-assisted contributions), these rules
|
||||||
- **Security-first review**: changes in `src/security/`, runtime, and CI need stricter validation.
|
- **Security-first review**: changes in `src/security/`, runtime, and CI need stricter validation.
|
||||||
|
|
||||||
Full maintainer workflow: [`docs/pr-workflow.md`](docs/pr-workflow.md).
|
Full maintainer workflow: [`docs/pr-workflow.md`](docs/pr-workflow.md).
|
||||||
|
CI workflow ownership and triage map: [`docs/ci-map.md`](docs/ci-map.md).
|
||||||
|
|
||||||
## Agent Collaboration Guidance
|
## Agent Collaboration Guidance
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,7 @@ MIT — see [LICENSE](LICENSE)
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md). Implement a trait, submit a PR:
|
See [CONTRIBUTING.md](CONTRIBUTING.md). Implement a trait, submit a PR:
|
||||||
|
- CI workflow guide: [docs/ci-map.md](docs/ci-map.md)
|
||||||
- New `Provider` → `src/providers/`
|
- New `Provider` → `src/providers/`
|
||||||
- New `Channel` → `src/channels/`
|
- New `Channel` → `src/channels/`
|
||||||
- New `Observer` → `src/observability/`
|
- New `Observer` → `src/observability/`
|
||||||
|
|
|
||||||
60
docs/ci-map.md
Normal file
60
docs/ci-map.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# CI Workflow Map
|
||||||
|
|
||||||
|
This document explains what each GitHub workflow does, when it runs, and whether it should block merges.
|
||||||
|
|
||||||
|
## Merge-Blocking vs Optional
|
||||||
|
|
||||||
|
Merge-blocking checks should stay small and deterministic. Optional checks are useful for automation and maintenance, but should not block normal development.
|
||||||
|
|
||||||
|
### Merge-Blocking
|
||||||
|
|
||||||
|
- `.github/workflows/ci.yml` (`CI`)
|
||||||
|
- Purpose: Rust validation (`fmt`, `clippy`, `test`, release build smoke)
|
||||||
|
- Merge gate: `CI Required Gate`
|
||||||
|
- `.github/workflows/workflow-sanity.yml` (`Workflow Sanity`)
|
||||||
|
- Purpose: lint GitHub workflow files (`actionlint`, tab checks)
|
||||||
|
- Recommended for workflow-changing PRs
|
||||||
|
|
||||||
|
### Non-Blocking but Important
|
||||||
|
|
||||||
|
- `.github/workflows/docker.yml` (`Docker`)
|
||||||
|
- Purpose: PR docker smoke check and publish images on `main`/tag pushes
|
||||||
|
- `.github/workflows/security.yml` (`Security Audit`)
|
||||||
|
- Purpose: dependency advisories (`cargo audit`) and policy/license checks (`cargo deny`)
|
||||||
|
- `.github/workflows/release.yml` (`Release`)
|
||||||
|
- Purpose: build tagged release artifacts and publish GitHub releases
|
||||||
|
|
||||||
|
### Optional Repository Automation
|
||||||
|
|
||||||
|
- `.github/workflows/labeler.yml` (`PR Labeler`)
|
||||||
|
- Purpose: path labels + size labels
|
||||||
|
- `.github/workflows/auto-response.yml` (`Auto Response`)
|
||||||
|
- Purpose: first-time contributor onboarding messages
|
||||||
|
- `.github/workflows/stale.yml` (`Stale`)
|
||||||
|
- Purpose: stale issue/PR lifecycle automation
|
||||||
|
|
||||||
|
## Trigger Map
|
||||||
|
|
||||||
|
- `CI`: push to `main`/`develop`, PRs to `main`
|
||||||
|
- `Docker`: push to `main`, tag push (`v*`), PRs touching docker/workflow files, manual dispatch
|
||||||
|
- `Release`: tag push (`v*`)
|
||||||
|
- `Security Audit`: push to `main`, PRs to `main`, weekly schedule
|
||||||
|
- `Workflow Sanity`: PR/push when `.github/workflows/**`, `.github/*.yml`, or `.github/*.yaml` change
|
||||||
|
- `PR Labeler`: `pull_request_target` lifecycle events
|
||||||
|
- `Auto Response`: issue opened, `pull_request_target` opened
|
||||||
|
- `Stale`: daily schedule, manual dispatch
|
||||||
|
|
||||||
|
## Fast Triage Guide
|
||||||
|
|
||||||
|
1. `CI Required Gate` failing: start with `.github/workflows/ci.yml`.
|
||||||
|
2. Docker failures on PRs: inspect `.github/workflows/docker.yml` `pr-smoke` job.
|
||||||
|
3. Release failures on tags: inspect `.github/workflows/release.yml`.
|
||||||
|
4. Security failures: inspect `.github/workflows/security.yml` and `deny.toml`.
|
||||||
|
5. Workflow syntax/lint failures: inspect `.github/workflows/workflow-sanity.yml`.
|
||||||
|
|
||||||
|
## Maintenance Rules
|
||||||
|
|
||||||
|
- Keep merge-blocking checks deterministic and reproducible (`--locked` where applicable).
|
||||||
|
- Prefer explicit workflow permissions (least privilege).
|
||||||
|
- Use path filters for expensive workflows when practical.
|
||||||
|
- Avoid mixing onboarding/community automation with merge-gating logic.
|
||||||
|
|
@ -9,6 +9,8 @@ This document defines how ZeroClaw handles high PR volume while maintaining:
|
||||||
- High sustainability
|
- High sustainability
|
||||||
- High security
|
- High security
|
||||||
|
|
||||||
|
Related reference: [`docs/ci-map.md`](ci-map.md) for per-workflow ownership, triggers, and triage flow.
|
||||||
|
|
||||||
## 1) Governance Goals
|
## 1) Governance Goals
|
||||||
|
|
||||||
1. Keep merge throughput predictable under heavy PR load.
|
1. Keep merge throughput predictable under heavy PR load.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue