From 414f64c407e7231bab3df760cfd5b861bcb403be Mon Sep 17 00:00:00 2001 From: louisdevzz Date: Thu, 5 Mar 2026 00:52:20 +0700 Subject: [PATCH] chore(github): remove unnecessary GitHub Actions workflows Streamline CI/CD by removing 21 unnecessary workflows: Removed: - Release workflows (pub-release, pub-prerelease, pub-docker-img) - Advanced CI workflows (canary-gate, rollback, reproducible-build) - Testing workflows (benchmarks, e2e, fuzz) - Deployment workflows (docs-deploy, pages-deploy, deploy-web) - Audit workflows (change-audit, supply-chain-provenance) - Complex workflows (feature-matrix, nightly-all-features, workflow-sanity) Kept essential workflows: - CI: ci-run.yml, ci-build-fast.yml - Security: sec-audit.yml, sec-codeql.yml - PR Management: pr-labeler, pr-auto-response, pr-check-stale, pr-check-status, pr-intake-checks, pr-label-policy-check --- .github/workflows/ci-canary-gate.yml | 329 --------- .github/workflows/ci-change-audit.yml | 154 ----- .../workflows/ci-provider-connectivity.yml | 112 --- .github/workflows/ci-reproducible-build.yml | 121 ---- .github/workflows/ci-rollback.yml | 257 ------- .../workflows/ci-supply-chain-provenance.yml | 110 --- .github/workflows/deploy-web.yml | 56 -- .github/workflows/docs-deploy.yml | 291 -------- .github/workflows/feature-matrix.yml | 382 ----------- .github/workflows/main-branch-flow.md | 266 -------- .github/workflows/nightly-all-features.yml | 187 ----- .github/workflows/pages-deploy.yml | 64 -- .github/workflows/pub-docker-img.yml | 352 ---------- .github/workflows/pub-prerelease.yml | 259 ------- .github/workflows/pub-release.yml | 645 ------------------ .../scripts/test_benchmarks_pr_comment.js | 57 -- .github/workflows/sec-vorpal-reviewdog.yml | 191 ------ .github/workflows/sync-contributors.yml | 116 ---- .github/workflows/test-benchmarks.yml | 53 -- .github/workflows/test-e2e.yml | 33 - .github/workflows/test-fuzz.yml | 75 -- .github/workflows/workflow-sanity.yml | 106 --- 22 files changed, 4216 deletions(-) delete mode 100644 .github/workflows/ci-canary-gate.yml delete mode 100644 .github/workflows/ci-change-audit.yml delete mode 100644 .github/workflows/ci-provider-connectivity.yml delete mode 100644 .github/workflows/ci-reproducible-build.yml delete mode 100644 .github/workflows/ci-rollback.yml delete mode 100644 .github/workflows/ci-supply-chain-provenance.yml delete mode 100644 .github/workflows/deploy-web.yml delete mode 100644 .github/workflows/docs-deploy.yml delete mode 100644 .github/workflows/feature-matrix.yml delete mode 100644 .github/workflows/main-branch-flow.md delete mode 100644 .github/workflows/nightly-all-features.yml delete mode 100644 .github/workflows/pages-deploy.yml delete mode 100644 .github/workflows/pub-docker-img.yml delete mode 100644 .github/workflows/pub-prerelease.yml delete mode 100644 .github/workflows/pub-release.yml delete mode 100644 .github/workflows/scripts/test_benchmarks_pr_comment.js delete mode 100644 .github/workflows/sec-vorpal-reviewdog.yml delete mode 100644 .github/workflows/sync-contributors.yml delete mode 100644 .github/workflows/test-benchmarks.yml delete mode 100644 .github/workflows/test-e2e.yml delete mode 100644 .github/workflows/test-fuzz.yml delete mode 100644 .github/workflows/workflow-sanity.yml diff --git a/.github/workflows/ci-canary-gate.yml b/.github/workflows/ci-canary-gate.yml deleted file mode 100644 index 9fcfdff..0000000 --- a/.github/workflows/ci-canary-gate.yml +++ /dev/null @@ -1,329 +0,0 @@ -name: CI Canary Gate - -on: - workflow_dispatch: - inputs: - mode: - description: "dry-run computes decision only; execute enables canary dispatch" - required: true - default: dry-run - type: choice - options: - - dry-run - - execute - candidate_tag: - description: "Candidate release tag (e.g. v0.1.8-rc.1 or v0.1.8)" - required: false - default: "" - type: string - candidate_sha: - description: "Optional explicit candidate SHA" - required: false - default: "" - type: string - error_rate: - description: "Observed canary error rate (0.0-1.0)" - required: true - default: "0.0" - type: string - crash_rate: - description: "Observed canary crash rate (0.0-1.0)" - required: true - default: "0.0" - type: string - p95_latency_ms: - description: "Observed canary p95 latency in milliseconds" - required: true - default: "0" - type: string - sample_size: - description: "Observed canary sample size" - required: true - default: "0" - type: string - emit_repository_dispatch: - description: "Emit canary decision repository_dispatch event" - required: true - default: false - type: boolean - trigger_rollback_on_abort: - description: "Automatically dispatch CI Rollback Guard when canary decision is abort" - required: true - default: true - type: boolean - rollback_branch: - description: "Rollback integration branch used by CI Rollback Guard dispatch" - required: true - default: dev - type: choice - options: - - dev - - main - rollback_target_ref: - description: "Optional explicit rollback target ref passed to CI Rollback Guard" - required: false - default: "" - type: string - fail_on_violation: - description: "Fail on policy violations" - required: true - default: true - type: boolean - schedule: - - cron: "45 7 * * 1" # Weekly Monday 07:45 UTC - -concurrency: - group: canary-gate-${{ github.event.inputs.candidate_tag || github.ref || github.run_id }} - cancel-in-progress: false - -permissions: - contents: read - actions: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - canary-plan: - name: Canary Plan - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - outputs: - mode: ${{ steps.inputs.outputs.mode }} - candidate_tag: ${{ steps.inputs.outputs.candidate_tag }} - candidate_sha: ${{ steps.inputs.outputs.candidate_sha }} - trigger_rollback_on_abort: ${{ steps.inputs.outputs.trigger_rollback_on_abort }} - rollback_branch: ${{ steps.inputs.outputs.rollback_branch }} - rollback_target_ref: ${{ steps.inputs.outputs.rollback_target_ref }} - decision: ${{ steps.extract.outputs.decision }} - ready_to_execute: ${{ steps.extract.outputs.ready_to_execute }} - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Resolve canary inputs - id: inputs - shell: bash - run: | - set -euo pipefail - - mode="dry-run" - candidate_tag="" - candidate_sha="" - error_rate="0.0" - crash_rate="0.0" - p95_latency_ms="0" - sample_size="0" - trigger_rollback_on_abort="true" - rollback_branch="dev" - rollback_target_ref="" - fail_on_violation="true" - - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - mode="${{ github.event.inputs.mode || 'dry-run' }}" - candidate_tag="${{ github.event.inputs.candidate_tag || '' }}" - candidate_sha="${{ github.event.inputs.candidate_sha || '' }}" - error_rate="${{ github.event.inputs.error_rate || '0.0' }}" - crash_rate="${{ github.event.inputs.crash_rate || '0.0' }}" - p95_latency_ms="${{ github.event.inputs.p95_latency_ms || '0' }}" - sample_size="${{ github.event.inputs.sample_size || '0' }}" - trigger_rollback_on_abort="${{ github.event.inputs.trigger_rollback_on_abort || 'true' }}" - rollback_branch="${{ github.event.inputs.rollback_branch || 'dev' }}" - rollback_target_ref="${{ github.event.inputs.rollback_target_ref || '' }}" - fail_on_violation="${{ github.event.inputs.fail_on_violation || 'true' }}" - else - git fetch --tags --force origin - candidate_tag="$(git tag --list 'v*' --sort=-version:refname | head -n1)" - if [ -n "$candidate_tag" ]; then - candidate_sha="$(git rev-parse "${candidate_tag}^{commit}")" - fi - fi - - { - echo "mode=${mode}" - echo "candidate_tag=${candidate_tag}" - echo "candidate_sha=${candidate_sha}" - echo "error_rate=${error_rate}" - echo "crash_rate=${crash_rate}" - echo "p95_latency_ms=${p95_latency_ms}" - echo "sample_size=${sample_size}" - echo "trigger_rollback_on_abort=${trigger_rollback_on_abort}" - echo "rollback_branch=${rollback_branch}" - echo "rollback_target_ref=${rollback_target_ref}" - echo "fail_on_violation=${fail_on_violation}" - } >> "$GITHUB_OUTPUT" - - - name: Run canary guard - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - - args=() - if [ "${{ steps.inputs.outputs.fail_on_violation }}" = "true" ]; then - args+=(--fail-on-violation) - fi - - python3 scripts/ci/canary_guard.py \ - --policy-file .github/release/canary-policy.json \ - --candidate-tag "${{ steps.inputs.outputs.candidate_tag }}" \ - --candidate-sha "${{ steps.inputs.outputs.candidate_sha }}" \ - --mode "${{ steps.inputs.outputs.mode }}" \ - --error-rate "${{ steps.inputs.outputs.error_rate }}" \ - --crash-rate "${{ steps.inputs.outputs.crash_rate }}" \ - --p95-latency-ms "${{ steps.inputs.outputs.p95_latency_ms }}" \ - --sample-size "${{ steps.inputs.outputs.sample_size }}" \ - --output-json artifacts/canary-guard.json \ - --output-md artifacts/canary-guard.md \ - "${args[@]}" - - - name: Extract canary decision outputs - id: extract - shell: bash - run: | - set -euo pipefail - decision="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/canary-guard.json', encoding='utf-8')) - print(data.get('decision', 'hold')) - PY - )" - ready_to_execute="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/canary-guard.json', encoding='utf-8')) - print(str(bool(data.get('ready_to_execute', False))).lower()) - PY - )" - echo "decision=${decision}" >> "$GITHUB_OUTPUT" - echo "ready_to_execute=${ready_to_execute}" >> "$GITHUB_OUTPUT" - - - name: Emit canary audit event - if: always() - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type canary_guard \ - --input-json artifacts/canary-guard.json \ - --output-json artifacts/audit-event-canary-guard.json \ - --artifact-name canary-guard \ - --retention-days 21 - - - name: Publish canary summary - if: always() - shell: bash - run: | - set -euo pipefail - cat artifacts/canary-guard.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload canary artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: canary-guard - path: | - artifacts/canary-guard.json - artifacts/canary-guard.md - artifacts/audit-event-canary-guard.json - if-no-files-found: error - retention-days: 21 - - canary-execute: - name: Canary Execute - needs: [canary-plan] - if: github.event_name == 'workflow_dispatch' && needs.canary-plan.outputs.mode == 'execute' && needs.canary-plan.outputs.ready_to_execute == 'true' - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 10 - permissions: - contents: write - actions: write - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Create canary marker tag - shell: bash - run: | - set -euo pipefail - marker_tag="canary-${{ needs.canary-plan.outputs.candidate_tag }}-${{ github.run_id }}" - git fetch --tags --force origin - git tag -a "$marker_tag" "${{ needs.canary-plan.outputs.candidate_sha }}" -m "Canary decision marker from run ${{ github.run_id }}" - git push origin "$marker_tag" - echo "Created marker tag: $marker_tag" >> "$GITHUB_STEP_SUMMARY" - - - name: Emit canary repository dispatch - if: github.event.inputs.emit_repository_dispatch == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - await github.rest.repos.createDispatchEvent({ - owner: context.repo.owner, - repo: context.repo.repo, - event_type: `canary_${{ needs.canary-plan.outputs.decision }}`, - client_payload: { - candidate_tag: "${{ needs.canary-plan.outputs.candidate_tag }}", - candidate_sha: "${{ needs.canary-plan.outputs.candidate_sha }}", - decision: "${{ needs.canary-plan.outputs.decision }}", - run_id: context.runId, - run_attempt: process.env.GITHUB_RUN_ATTEMPT, - source_sha: context.sha - } - }); - - - name: Trigger rollback guard workflow on abort - if: needs.canary-plan.outputs.decision == 'abort' && needs.canary-plan.outputs.trigger_rollback_on_abort == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const rollbackBranch = "${{ needs.canary-plan.outputs.rollback_branch }}" || "dev"; - const rollbackTargetRef = `${{ needs.canary-plan.outputs.rollback_target_ref }}`.trim(); - const workflowRef = process.env.GITHUB_REF_NAME || "dev"; - - const inputs = { - branch: rollbackBranch, - mode: "execute", - allow_non_ancestor: "false", - fail_on_violation: "true", - create_marker_tag: "true", - emit_repository_dispatch: "true", - }; - - if (rollbackTargetRef.length > 0) { - inputs.target_ref = rollbackTargetRef; - } - - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "ci-rollback.yml", - ref: workflowRef, - inputs, - }); - - - name: Publish rollback trigger summary - if: needs.canary-plan.outputs.decision == 'abort' - shell: bash - run: | - set -euo pipefail - if [ "${{ needs.canary-plan.outputs.trigger_rollback_on_abort }}" = "true" ]; then - { - echo "### Canary Abort Rollback Trigger" - echo "- CI Rollback Guard dispatch: triggered" - echo "- Rollback branch: \`${{ needs.canary-plan.outputs.rollback_branch }}\`" - if [ -n "${{ needs.canary-plan.outputs.rollback_target_ref }}" ]; then - echo "- Rollback target ref: \`${{ needs.canary-plan.outputs.rollback_target_ref }}\`" - else - echo "- Rollback target ref: _auto (latest release tag strategy)_" - fi - } >> "$GITHUB_STEP_SUMMARY" - else - { - echo "### Canary Abort Rollback Trigger" - echo "- CI Rollback Guard dispatch: skipped (trigger_rollback_on_abort=false)" - } >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.github/workflows/ci-change-audit.yml b/.github/workflows/ci-change-audit.yml deleted file mode 100644 index 1fa4970..0000000 --- a/.github/workflows/ci-change-audit.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: CI/CD Change Audit - -on: - pull_request: - branches: [dev, main] - paths: - - ".github/workflows/**" - - ".github/release/**" - - ".github/codeql/**" - - "scripts/ci/**" - - ".github/dependabot.yml" - - "deny.toml" - - ".gitleaks.toml" - push: - branches: [dev, main] - paths: - - ".github/workflows/**" - - ".github/release/**" - - ".github/codeql/**" - - "scripts/ci/**" - - ".github/dependabot.yml" - - "deny.toml" - - ".gitleaks.toml" - workflow_dispatch: - inputs: - base_sha: - description: "Optional base SHA (default: HEAD~1)" - required: false - default: "" - type: string - fail_on_policy: - description: "Fail when audit policy violations are found" - required: true - default: true - type: boolean - -concurrency: - group: ci-change-audit-${{ github.event.pull_request.number || github.sha || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - audit: - name: CI Change Audit - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Resolve base/head commits - id: refs - shell: bash - run: | - set -euo pipefail - head_sha="$(git rev-parse HEAD)" - if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then - # For pull_request events, checkout uses refs/pull/*/merge; HEAD^1 is the - # effective base commit for this synthesized merge and avoids stale base.sha. - if git rev-parse --verify HEAD^1 >/dev/null 2>&1; then - base_sha="$(git rev-parse HEAD^1)" - else - base_sha="${{ github.event.pull_request.base.sha }}" - fi - elif [ "${GITHUB_EVENT_NAME}" = "push" ]; then - base_sha="${{ github.event.before }}" - else - base_sha="${{ github.event.inputs.base_sha || '' }}" - if [ -z "$base_sha" ]; then - base_sha="$(git rev-parse HEAD~1)" - fi - fi - echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" - echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" - - - name: Run CI helper script unit tests - shell: bash - run: | - set -euo pipefail - python3 -m unittest discover -s scripts/ci/tests -p 'test_*.py' -v - - - name: Generate CI change audit - shell: bash - env: - BASE_SHA: ${{ steps.refs.outputs.base_sha }} - HEAD_SHA: ${{ steps.refs.outputs.head_sha }} - run: | - set -euo pipefail - mkdir -p artifacts - fail_on_policy="true" - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - fail_on_policy="${{ github.event.inputs.fail_on_policy || 'true' }}" - fi - cmd=(python3 scripts/ci/ci_change_audit.py - --base-sha "$BASE_SHA" - --head-sha "$HEAD_SHA" - --output-json artifacts/ci-change-audit.json - --output-md artifacts/ci-change-audit.md) - if [ "$fail_on_policy" = "true" ]; then - cmd+=(--fail-on-violations) - fi - "${cmd[@]}" - - - name: Emit normalized audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ci-change-audit.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type ci_change_audit \ - --input-json artifacts/ci-change-audit.json \ - --output-json artifacts/audit-event-ci-change-audit.json \ - --artifact-name ci-change-audit-event \ - --retention-days 14 - fi - - - name: Upload audit artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - if: always() - with: - name: ci-change-audit - path: artifacts/ci-change-audit.* - retention-days: 14 - - - name: Publish audit summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ci-change-audit.md ]; then - cat artifacts/ci-change-audit.md >> "$GITHUB_STEP_SUMMARY" - else - echo "CI change audit report was not generated." >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload audit event artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - if: always() - with: - name: ci-change-audit-event - path: artifacts/audit-event-ci-change-audit.json - if-no-files-found: ignore - retention-days: 14 diff --git a/.github/workflows/ci-provider-connectivity.yml b/.github/workflows/ci-provider-connectivity.yml deleted file mode 100644 index 701f923..0000000 --- a/.github/workflows/ci-provider-connectivity.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: CI Provider Connectivity - -on: - schedule: - - cron: "30 */6 * * *" # Every 6 hours - workflow_dispatch: - inputs: - fail_on_critical: - description: "Fail run when critical endpoints are unreachable" - required: true - default: false - type: boolean - pull_request: - branches: [dev, main] - paths: - - ".github/workflows/ci-provider-connectivity.yml" - - ".github/connectivity/providers.json" - - "scripts/ci/provider_connectivity_matrix.py" - push: - branches: [dev, main] - paths: - - ".github/workflows/ci-provider-connectivity.yml" - - ".github/connectivity/providers.json" - - "scripts/ci/provider_connectivity_matrix.py" - -concurrency: - group: provider-connectivity-${{ github.event.pull_request.number || github.ref || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - probe: - name: Provider Connectivity Probe - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Run connectivity matrix probe - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - fail_on_critical="false" - case "${GITHUB_EVENT_NAME}" in - schedule) - fail_on_critical="true" - ;; - workflow_dispatch) - fail_on_critical="${{ github.event.inputs.fail_on_critical || 'false' }}" - ;; - esac - - cmd=(python3 scripts/ci/provider_connectivity_matrix.py - --config .github/connectivity/providers.json - --output-json artifacts/provider-connectivity-matrix.json - --output-md artifacts/provider-connectivity-matrix.md) - if [ "$fail_on_critical" = "true" ]; then - cmd+=(--fail-on-critical) - fi - "${cmd[@]}" - - - name: Emit normalized audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/provider-connectivity-matrix.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type provider_connectivity \ - --input-json artifacts/provider-connectivity-matrix.json \ - --output-json artifacts/audit-event-provider-connectivity.json \ - --artifact-name provider-connectivity-audit-event \ - --retention-days 14 - fi - - - name: Upload connectivity artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - if: always() - with: - name: provider-connectivity-matrix - path: artifacts/provider-connectivity-matrix.* - retention-days: 14 - - - name: Publish summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/provider-connectivity-matrix.md ]; then - cat artifacts/provider-connectivity-matrix.md >> "$GITHUB_STEP_SUMMARY" - else - echo "Provider connectivity report missing." >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload audit event artifact - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: provider-connectivity-audit-event - path: artifacts/audit-event-provider-connectivity.json - if-no-files-found: ignore - retention-days: 14 diff --git a/.github/workflows/ci-reproducible-build.yml b/.github/workflows/ci-reproducible-build.yml deleted file mode 100644 index 9deb0d6..0000000 --- a/.github/workflows/ci-reproducible-build.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: CI Reproducible Build - -on: - push: - branches: [dev, main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "crates/**" - - "scripts/ci/reproducible_build_check.sh" - - ".github/workflows/ci-reproducible-build.yml" - pull_request: - branches: [dev, main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "crates/**" - - "scripts/ci/reproducible_build_check.sh" - - ".github/workflows/ci-reproducible-build.yml" - schedule: - - cron: "45 5 * * 1" # Weekly Monday 05:45 UTC - workflow_dispatch: - inputs: - fail_on_drift: - description: "Fail workflow if deterministic hash drift is detected" - required: true - default: true - type: boolean - allow_build_id_drift: - description: "Treat GNU build-id-only drift as non-blocking" - required: true - default: true - type: boolean - -concurrency: - group: repro-build-${{ github.event.pull_request.number || github.ref || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - reproducibility: - name: Reproducible Build Probe - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 45 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - - name: Run reproducible build check - shell: bash - run: | - set -euo pipefail - fail_on_drift="false" - allow_build_id_drift="true" - if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then - fail_on_drift="true" - elif [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - fail_on_drift="${{ github.event.inputs.fail_on_drift || 'true' }}" - allow_build_id_drift="${{ github.event.inputs.allow_build_id_drift || 'true' }}" - fi - FAIL_ON_DRIFT="$fail_on_drift" \ - ALLOW_BUILD_ID_DRIFT="$allow_build_id_drift" \ - OUTPUT_DIR="artifacts" \ - ./scripts/ci/reproducible_build_check.sh - - - name: Emit normalized audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/reproducible-build.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type reproducible_build \ - --input-json artifacts/reproducible-build.json \ - --output-json artifacts/audit-event-reproducible-build.json \ - --artifact-name reproducible-build-audit-event \ - --retention-days 14 - fi - - - name: Upload reproducibility artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: reproducible-build - path: artifacts/reproducible-build* - retention-days: 14 - - - name: Upload audit event artifact - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: reproducible-build-audit-event - path: artifacts/audit-event-reproducible-build.json - if-no-files-found: ignore - retention-days: 14 - - - name: Publish summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/reproducible-build.md ]; then - cat artifacts/reproducible-build.md >> "$GITHUB_STEP_SUMMARY" - else - echo "Reproducible build report missing." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.github/workflows/ci-rollback.yml b/.github/workflows/ci-rollback.yml deleted file mode 100644 index cc6cde2..0000000 --- a/.github/workflows/ci-rollback.yml +++ /dev/null @@ -1,257 +0,0 @@ -name: CI Rollback Guard - -on: - workflow_dispatch: - inputs: - branch: - description: "Integration branch this rollback targets" - required: true - default: dev - type: choice - options: - - dev - - main - mode: - description: "dry-run only plans; execute enables rollback marker/dispatch actions" - required: true - default: dry-run - type: choice - options: - - dry-run - - execute - target_ref: - description: "Optional explicit rollback target (tag/sha/ref). Empty = latest matching tag." - required: false - default: "" - type: string - allow_non_ancestor: - description: "Allow target not being ancestor of current head (warning-only)" - required: true - default: false - type: boolean - fail_on_violation: - description: "Fail workflow when guard violations are detected" - required: true - default: true - type: boolean - create_marker_tag: - description: "In execute mode, create and push rollback marker tag" - required: true - default: false - type: boolean - emit_repository_dispatch: - description: "In execute mode, emit repository_dispatch event `rollback_execute`" - required: true - default: false - type: boolean - schedule: - - cron: "15 7 * * 1" # Weekly Monday 07:15 UTC - -concurrency: - group: ci-rollback-${{ github.event.inputs.branch || 'dev' }} - cancel-in-progress: false - -permissions: - contents: read - actions: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - rollback-plan: - name: Rollback Guard Plan - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - outputs: - branch: ${{ steps.plan.outputs.branch }} - mode: ${{ steps.plan.outputs.mode }} - target_sha: ${{ steps.plan.outputs.target_sha }} - target_ref: ${{ steps.plan.outputs.target_ref }} - ready_to_execute: ${{ steps.plan.outputs.ready_to_execute }} - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.branch || 'dev' }} - - - name: Build rollback plan - id: plan - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - - branch_input="dev" - mode_input="dry-run" - target_ref_input="" - allow_non_ancestor="false" - fail_on_violation="true" - - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - branch_input="${{ github.event.inputs.branch || 'dev' }}" - mode_input="${{ github.event.inputs.mode || 'dry-run' }}" - target_ref_input="${{ github.event.inputs.target_ref || '' }}" - allow_non_ancestor="${{ github.event.inputs.allow_non_ancestor || 'false' }}" - fail_on_violation="${{ github.event.inputs.fail_on_violation || 'true' }}" - fi - - cmd=(python3 scripts/ci/rollback_guard.py - --repo-root . - --branch "$branch_input" - --mode "$mode_input" - --strategy latest-release-tag - --tag-pattern "v*" - --output-json artifacts/rollback-plan.json - --output-md artifacts/rollback-plan.md) - - if [ -n "$target_ref_input" ]; then - cmd+=(--target-ref "$target_ref_input") - fi - if [ "$allow_non_ancestor" = "true" ]; then - cmd+=(--allow-non-ancestor) - fi - if [ "$fail_on_violation" = "true" ]; then - cmd+=(--fail-on-violation) - fi - - "${cmd[@]}" - - target_sha="$(python3 - <<'PY' - import json - d = json.load(open("artifacts/rollback-plan.json", "r", encoding="utf-8")) - print(d.get("target_sha", "")) - PY - )" - target_ref="$(python3 - <<'PY' - import json - d = json.load(open("artifacts/rollback-plan.json", "r", encoding="utf-8")) - print(d.get("target_ref", "")) - PY - )" - ready_to_execute="$(python3 - <<'PY' - import json - d = json.load(open("artifacts/rollback-plan.json", "r", encoding="utf-8")) - print(str(d.get("ready_to_execute", False)).lower()) - PY - )" - - { - echo "branch=$branch_input" - echo "mode=$mode_input" - echo "target_sha=$target_sha" - echo "target_ref=$target_ref" - echo "ready_to_execute=$ready_to_execute" - } >> "$GITHUB_OUTPUT" - - - name: Emit rollback audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/rollback-plan.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type rollback_guard \ - --input-json artifacts/rollback-plan.json \ - --output-json artifacts/audit-event-rollback-guard.json \ - --artifact-name ci-rollback-plan \ - --retention-days 21 - fi - - - name: Upload rollback artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ci-rollback-plan - path: | - artifacts/rollback-plan.* - artifacts/audit-event-rollback-guard.json - if-no-files-found: ignore - retention-days: 21 - - - name: Publish rollback summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/rollback-plan.md ]; then - cat artifacts/rollback-plan.md >> "$GITHUB_STEP_SUMMARY" - else - echo "Rollback plan markdown report missing." >> "$GITHUB_STEP_SUMMARY" - fi - - rollback-execute: - name: Rollback Execute Actions - needs: [rollback-plan] - if: github.event_name == 'workflow_dispatch' && needs.rollback-plan.outputs.mode == 'execute' && needs.rollback-plan.outputs.ready_to_execute == 'true' - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 15 - permissions: - contents: write - actions: read - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - ref: ${{ needs.rollback-plan.outputs.branch }} - - - name: Fetch tags - shell: bash - run: | - set -euo pipefail - git fetch --tags --force origin - - - name: Create rollback marker tag - id: marker - if: github.event.inputs.create_marker_tag == 'true' - shell: bash - run: | - set -euo pipefail - target_sha="${{ needs.rollback-plan.outputs.target_sha }}" - if [ -z "$target_sha" ]; then - echo "Rollback guard did not resolve target_sha." - exit 1 - fi - marker_tag="rollback-${{ needs.rollback-plan.outputs.branch }}-${{ github.run_id }}" - git tag -a "$marker_tag" "$target_sha" -m "Rollback marker from run ${{ github.run_id }}" - git push origin "$marker_tag" - echo "marker_tag=$marker_tag" >> "$GITHUB_OUTPUT" - - - name: Emit rollback repository dispatch - if: github.event.inputs.emit_repository_dispatch == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - await github.rest.repos.createDispatchEvent({ - owner: context.repo.owner, - repo: context.repo.repo, - event_type: "rollback_execute", - client_payload: { - branch: "${{ needs.rollback-plan.outputs.branch }}", - target_ref: "${{ needs.rollback-plan.outputs.target_ref }}", - target_sha: "${{ needs.rollback-plan.outputs.target_sha }}", - run_id: context.runId, - run_attempt: process.env.GITHUB_RUN_ATTEMPT, - source_sha: context.sha - } - }); - - - name: Publish execute summary - if: always() - shell: bash - run: | - set -euo pipefail - { - echo "### Rollback Execute Actions" - echo "- Branch: \`${{ needs.rollback-plan.outputs.branch }}\`" - echo "- Target ref: \`${{ needs.rollback-plan.outputs.target_ref }}\`" - echo "- Target sha: \`${{ needs.rollback-plan.outputs.target_sha }}\`" - if [ -n "${{ steps.marker.outputs.marker_tag || '' }}" ]; then - echo "- Marker tag: \`${{ steps.marker.outputs.marker_tag }}\`" - fi - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci-supply-chain-provenance.yml b/.github/workflows/ci-supply-chain-provenance.yml deleted file mode 100644 index a17cc81..0000000 --- a/.github/workflows/ci-supply-chain-provenance.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: CI Supply Chain Provenance - -on: - push: - branches: [dev, main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "crates/**" - - "scripts/ci/generate_provenance.py" - - ".github/workflows/ci-supply-chain-provenance.yml" - workflow_dispatch: - schedule: - - cron: "20 6 * * 1" # Weekly Monday 06:20 UTC - -concurrency: - group: supply-chain-provenance-${{ github.ref || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - id-token: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - provenance: - name: Build + Provenance Bundle - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 35 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Rust - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - - name: Build release-fast artifact - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - host_target="$(rustc -vV | sed -n 's/^host: //p')" - cargo build --profile release-fast --locked --target "$host_target" - cp "target/${host_target}/release-fast/zerobuild" "artifacts/zerobuild-${host_target}" - sha256sum "artifacts/zerobuild-${host_target}" > "artifacts/zerobuild-${host_target}.sha256" - - - name: Generate provenance statement - shell: bash - run: | - set -euo pipefail - host_target="$(rustc -vV | sed -n 's/^host: //p')" - python3 scripts/ci/generate_provenance.py \ - --artifact "artifacts/zerobuild-${host_target}" \ - --subject-name "zerobuild-${host_target}" \ - --output "artifacts/provenance-${host_target}.intoto.json" - - - name: Install cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - - name: Sign provenance bundle - shell: bash - run: | - set -euo pipefail - host_target="$(rustc -vV | sed -n 's/^host: //p')" - statement="artifacts/provenance-${host_target}.intoto.json" - cosign sign-blob --yes \ - --bundle="${statement}.sigstore.json" \ - --output-signature="${statement}.sig" \ - --output-certificate="${statement}.pem" \ - "${statement}" - - - name: Emit normalized audit event - shell: bash - run: | - set -euo pipefail - host_target="$(rustc -vV | sed -n 's/^host: //p')" - python3 scripts/ci/emit_audit_event.py \ - --event-type supply_chain_provenance \ - --input-json "artifacts/provenance-${host_target}.intoto.json" \ - --output-json "artifacts/audit-event-supply-chain-provenance.json" \ - --artifact-name supply-chain-provenance \ - --retention-days 30 - - - name: Upload provenance artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: supply-chain-provenance - path: artifacts/* - retention-days: 30 - - - name: Publish summary - shell: bash - run: | - set -euo pipefail - host_target="$(rustc -vV | sed -n 's/^host: //p')" - { - echo "### Supply Chain Provenance" - echo "- Target: \`${host_target}\`" - echo "- Artifact: \`artifacts/zerobuild-${host_target}\`" - echo "- Statement: \`artifacts/provenance-${host_target}.intoto.json\`" - echo "- Signature: \`artifacts/provenance-${host_target}.intoto.json.sig\`" - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml deleted file mode 100644 index 8ad35e6..0000000 --- a/.github/workflows/deploy-web.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Deploy Web to GitHub Pages - -on: - push: - branches: [main] - paths: - - 'web/**' - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 - with: - node-version: '20' - - - name: Install dependencies - working-directory: ./web - run: npm ci - - - name: Build - working-directory: ./web - run: npm run build - - - name: Setup Pages - uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d - - - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa - with: - path: ./web/dist - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml deleted file mode 100644 index c1fa38a..0000000 --- a/.github/workflows/docs-deploy.yml +++ /dev/null @@ -1,291 +0,0 @@ -name: Docs Deploy - -on: - pull_request: - branches: [dev, main] - paths: - - "docs/**" - - "README*.md" - - ".github/workflows/docs-deploy.yml" - - "scripts/ci/docs_quality_gate.sh" - - "scripts/ci/collect_changed_links.py" - - ".github/release/docs-deploy-policy.json" - - "scripts/ci/docs_deploy_guard.py" - push: - branches: [dev, main] - paths: - - "docs/**" - - "README*.md" - - ".github/workflows/docs-deploy.yml" - - "scripts/ci/docs_quality_gate.sh" - - "scripts/ci/collect_changed_links.py" - - ".github/release/docs-deploy-policy.json" - - "scripts/ci/docs_deploy_guard.py" - workflow_dispatch: - inputs: - deploy_target: - description: "preview uploads artifact only; production deploys to Pages" - required: true - default: preview - type: choice - options: - - preview - - production - preview_evidence_run_url: - description: "Required for manual production deploys when policy enforces preview promotion evidence" - required: false - default: "" - rollback_ref: - description: "Optional rollback source ref (tag/sha/ref) for manual production dispatch" - required: false - default: "" - -concurrency: - group: docs-deploy-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - docs-quality: - name: Docs Quality Gate - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - outputs: - docs_files: ${{ steps.scope.outputs.docs_files }} - base_sha: ${{ steps.scope.outputs.base_sha }} - deploy_target: ${{ steps.deploy_guard.outputs.deploy_target }} - deploy_mode: ${{ steps.deploy_guard.outputs.deploy_mode }} - source_ref: ${{ steps.deploy_guard.outputs.source_ref }} - production_branch_ref: ${{ steps.deploy_guard.outputs.production_branch_ref }} - ready_to_deploy: ${{ steps.deploy_guard.outputs.ready_to_deploy }} - docs_preview_retention_days: ${{ steps.deploy_guard.outputs.docs_preview_retention_days }} - docs_guard_artifact_retention_days: ${{ steps.deploy_guard.outputs.docs_guard_artifact_retention_days }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Resolve docs diff scope - id: scope - shell: bash - run: | - set -euo pipefail - - base_sha="" - docs_files="" - - if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then - base_sha="${{ github.event.pull_request.base.sha }}" - docs_files="$(git diff --name-only "$base_sha" HEAD | awk '/\.md$|\.mdx$|^README/ {print}')" - elif [ "${GITHUB_EVENT_NAME}" = "push" ]; then - base_sha="${{ github.event.before }}" - if [ -n "$base_sha" ] && [ "$base_sha" != "0000000000000000000000000000000000000000" ]; then - docs_files="$(git diff --name-only "$base_sha" HEAD | awk '/\.md$|\.mdx$|^README/ {print}')" - fi - else - docs_files="$(git ls-files 'docs/**/*.md' 'README*.md')" - fi - - { - echo "base_sha=${base_sha}" - echo "docs_files<> "$GITHUB_OUTPUT" - - - name: Validate docs deploy contract - id: deploy_guard - shell: bash - env: - INPUT_DEPLOY_TARGET: ${{ github.event.inputs.deploy_target || '' }} - INPUT_PREVIEW_EVIDENCE_RUN_URL: ${{ github.event.inputs.preview_evidence_run_url || '' }} - INPUT_ROLLBACK_REF: ${{ github.event.inputs.rollback_ref || '' }} - run: | - set -euo pipefail - mkdir -p artifacts - python3 scripts/ci/docs_deploy_guard.py \ - --repo-root "$PWD" \ - --event-name "${GITHUB_EVENT_NAME}" \ - --git-ref "${GITHUB_REF}" \ - --git-sha "${GITHUB_SHA}" \ - --input-deploy-target "${INPUT_DEPLOY_TARGET}" \ - --input-preview-evidence-run-url "${INPUT_PREVIEW_EVIDENCE_RUN_URL}" \ - --input-rollback-ref "${INPUT_ROLLBACK_REF}" \ - --policy-file .github/release/docs-deploy-policy.json \ - --output-json artifacts/docs-deploy-guard.json \ - --output-md artifacts/docs-deploy-guard.md \ - --github-output-file "$GITHUB_OUTPUT" \ - --fail-on-violation - - - name: Emit docs deploy guard audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/docs-deploy-guard.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type docs_deploy_guard \ - --input-json artifacts/docs-deploy-guard.json \ - --output-json artifacts/audit-event-docs-deploy-guard.json \ - --artifact-name docs-deploy-guard \ - --retention-days 21 - fi - - - name: Publish docs deploy guard summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/docs-deploy-guard.md ]; then - cat artifacts/docs-deploy-guard.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload docs deploy guard artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: docs-deploy-guard - path: | - artifacts/docs-deploy-guard.json - artifacts/docs-deploy-guard.md - artifacts/audit-event-docs-deploy-guard.json - if-no-files-found: ignore - retention-days: ${{ steps.deploy_guard.outputs.docs_guard_artifact_retention_days || 21 }} - - - name: Markdown quality gate - env: - BASE_SHA: ${{ steps.scope.outputs.base_sha }} - DOCS_FILES: ${{ steps.scope.outputs.docs_files }} - run: ./scripts/ci/docs_quality_gate.sh - - - name: Collect added links - id: links - if: github.event_name != 'workflow_dispatch' - shell: bash - env: - BASE_SHA: ${{ steps.scope.outputs.base_sha }} - DOCS_FILES: ${{ steps.scope.outputs.docs_files }} - run: | - set -euo pipefail - python3 ./scripts/ci/collect_changed_links.py \ - --base "$BASE_SHA" \ - --docs-files "$DOCS_FILES" \ - --output .ci-added-links.txt - count=$(wc -l < .ci-added-links.txt | tr -d ' ') - echo "count=$count" >> "$GITHUB_OUTPUT" - - - name: Link check (added links) - if: github.event_name != 'workflow_dispatch' && steps.links.outputs.count != '0' - uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2 - with: - fail: true - args: >- - --offline - --no-progress - --format detailed - .ci-added-links.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Skip link check (none added) - if: github.event_name != 'workflow_dispatch' && steps.links.outputs.count == '0' - run: echo "No added links detected in changed docs lines." - - docs-preview: - name: Docs Preview Artifact - needs: [docs-quality] - if: github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_target == 'preview') - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 15 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Build preview bundle - shell: bash - run: | - set -euo pipefail - rm -rf site - mkdir -p site/docs - cp -R docs/. site/docs/ - cp README.md site/README.md - cat > site/index.md <<'EOF' - # ZeroBuild Docs Preview - - This preview bundle is produced by `.github/workflows/docs-deploy.yml`. - - - [Repository README](./README.md) - - [Docs Home](./docs/README.md) - EOF - - - name: Upload preview artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: docs-preview - path: site/** - if-no-files-found: error - retention-days: ${{ needs.docs-quality.outputs.docs_preview_retention_days || 14 }} - - docs-deploy: - name: Deploy Docs to GitHub Pages - needs: [docs-quality] - if: needs.docs-quality.outputs.deploy_target == 'production' && needs.docs-quality.outputs.ready_to_deploy == 'true' - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ needs.docs-quality.outputs.source_ref }} - - - name: Build deploy bundle - shell: bash - run: | - set -euo pipefail - rm -rf site - mkdir -p site/docs - cp -R docs/. site/docs/ - cp README.md site/README.md - cat > site/index.md <<'EOF' - # ZeroBuild Documentation - - This site is deployed automatically from `main` by `.github/workflows/docs-deploy.yml`. - - - [Repository README](./README.md) - - [Docs Home](./docs/README.md) - EOF - - - name: Publish deploy source summary - shell: bash - run: | - { - echo "## Docs Deploy Source" - echo "- Deploy mode: \`${{ needs.docs-quality.outputs.deploy_mode }}\`" - echo "- Source ref: \`${{ needs.docs-quality.outputs.source_ref }}\`" - echo "- Production branch ref: \`${{ needs.docs-quality.outputs.production_branch_ref }}\`" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Setup Pages - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 - with: - path: site - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/feature-matrix.yml b/.github/workflows/feature-matrix.yml deleted file mode 100644 index 8002724..0000000 --- a/.github/workflows/feature-matrix.yml +++ /dev/null @@ -1,382 +0,0 @@ -name: Feature Matrix - -on: - push: - branches: [dev, main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "crates/**" - - "tests/**" - - "scripts/ci/nightly_matrix_report.py" - - ".github/release/nightly-owner-routing.json" - - ".github/workflows/feature-matrix.yml" - pull_request: - branches: [dev, main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "crates/**" - - "tests/**" - - "scripts/ci/nightly_matrix_report.py" - - ".github/release/nightly-owner-routing.json" - - ".github/workflows/feature-matrix.yml" - merge_group: - branches: [dev, main] - schedule: - - cron: "30 4 * * 1" # Weekly Monday 04:30 UTC - - cron: "15 3 * * *" # Daily 03:15 UTC (nightly profile) - workflow_dispatch: - inputs: - profile: - description: "compile = merge-gate matrix, nightly = integration-oriented lane commands" - required: true - default: compile - type: choice - options: - - compile - - nightly - fail_on_failure: - description: "Fail summary job when any lane fails" - required: true - default: true - type: boolean - -concurrency: - group: feature-matrix-${{ github.event.pull_request.number || github.ref || github.run_id }}-${{ github.event.inputs.profile || 'auto' }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - resolve-profile: - name: Resolve Matrix Profile - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - outputs: - profile: ${{ steps.resolve.outputs.profile }} - lane_job_prefix: ${{ steps.resolve.outputs.lane_job_prefix }} - summary_job_name: ${{ steps.resolve.outputs.summary_job_name }} - lane_retention_days: ${{ steps.resolve.outputs.lane_retention_days }} - lane_timeout_minutes: ${{ steps.resolve.outputs.lane_timeout_minutes }} - max_attempts: ${{ steps.resolve.outputs.max_attempts }} - summary_artifact_name: ${{ steps.resolve.outputs.summary_artifact_name }} - summary_json_name: ${{ steps.resolve.outputs.summary_json_name }} - summary_md_name: ${{ steps.resolve.outputs.summary_md_name }} - lane_artifact_prefix: ${{ steps.resolve.outputs.lane_artifact_prefix }} - fail_on_failure: ${{ steps.resolve.outputs.fail_on_failure }} - collect_history: ${{ steps.resolve.outputs.collect_history }} - steps: - - name: Resolve effective profile - id: resolve - shell: bash - run: | - set -euo pipefail - - profile="compile" - fail_on_failure="true" - lane_job_prefix="Matrix Lane" - summary_job_name="Feature Matrix Summary" - lane_retention_days="21" - lane_timeout_minutes="55" - max_attempts="1" - summary_artifact_name="feature-matrix-summary" - summary_json_name="feature-matrix-summary.json" - summary_md_name="feature-matrix-summary.md" - lane_artifact_prefix="feature-matrix" - collect_history="false" - - if [ "${GITHUB_EVENT_NAME}" = "schedule" ] && [ "${{ github.event.schedule }}" = "15 3 * * *" ]; then - profile="nightly" - elif [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - profile="${{ github.event.inputs.profile || 'compile' }}" - fail_on_failure="${{ github.event.inputs.fail_on_failure || 'true' }}" - fi - - if [ "$profile" = "nightly" ]; then - lane_job_prefix="Nightly Lane" - summary_job_name="Nightly Summary & Routing" - lane_retention_days="30" - lane_timeout_minutes="70" - max_attempts="2" - summary_artifact_name="nightly-all-features-summary" - summary_json_name="nightly-summary.json" - summary_md_name="nightly-summary.md" - lane_artifact_prefix="nightly-lane" - collect_history="true" - fi - - { - echo "profile=${profile}" - echo "lane_job_prefix=${lane_job_prefix}" - echo "summary_job_name=${summary_job_name}" - echo "lane_retention_days=${lane_retention_days}" - echo "lane_timeout_minutes=${lane_timeout_minutes}" - echo "max_attempts=${max_attempts}" - echo "summary_artifact_name=${summary_artifact_name}" - echo "summary_json_name=${summary_json_name}" - echo "summary_md_name=${summary_md_name}" - echo "lane_artifact_prefix=${lane_artifact_prefix}" - echo "fail_on_failure=${fail_on_failure}" - echo "collect_history=${collect_history}" - } >> "$GITHUB_OUTPUT" - - feature-check: - name: ${{ needs.resolve-profile.outputs.lane_job_prefix }} (${{ matrix.name }}) - needs: [resolve-profile] - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: ${{ fromJSON(needs.resolve-profile.outputs.lane_timeout_minutes) }} - strategy: - fail-fast: false - matrix: - include: - - name: default - compile_command: cargo check --locked - nightly_command: cargo test --locked --test agent_e2e --verbose - install_libudev: false - - name: whatsapp-web - compile_command: cargo check --locked --no-default-features --features whatsapp-web - nightly_command: cargo check --locked --no-default-features --features whatsapp-web --verbose - install_libudev: false - - name: browser-native - compile_command: cargo check --locked --no-default-features --features browser-native - nightly_command: cargo check --locked --no-default-features --features browser-native --verbose - install_libudev: false - - name: nightly-all-features - compile_command: cargo check --locked --all-features - nightly_command: cargo test --locked --all-features --test agent_e2e --verbose - install_libudev: true - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - with: - prefix-key: feature-matrix-${{ matrix.name }} - - - name: Ensure Linux deps for all-features lane - if: matrix.install_libudev - shell: bash - run: | - set -euo pipefail - - if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists libudev; then - echo "libudev development headers already available; skipping apt install." - exit 0 - fi - - echo "Installing missing libudev build dependencies..." - for attempt in 1 2 3; do - if sudo apt-get update -qq -o DPkg::Lock::Timeout=300 && \ - sudo apt-get install -y --no-install-recommends --no-upgrade -o DPkg::Lock::Timeout=300 libudev-dev pkg-config; then - echo "Dependency installation succeeded on attempt ${attempt}." - exit 0 - fi - if [ "$attempt" -eq 3 ]; then - echo "Failed to install libudev-dev/pkg-config after ${attempt} attempts." >&2 - exit 1 - fi - echo "Dependency installation failed on attempt ${attempt}; retrying in 10s..." - sleep 10 - done - - - name: Run matrix lane command - id: lane - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - - profile="${{ needs.resolve-profile.outputs.profile }}" - lane_command="${{ matrix.compile_command }}" - if [ "$profile" = "nightly" ]; then - lane_command="${{ matrix.nightly_command }}" - fi - - max_attempts="${{ needs.resolve-profile.outputs.max_attempts }}" - attempt=1 - status=1 - - started_at="$(date +%s)" - while [ "$attempt" -le "$max_attempts" ]; do - echo "Running lane command (attempt ${attempt}/${max_attempts}): ${lane_command}" - set +e - bash -lc "${lane_command}" - status=$? - set -e - if [ "$status" -eq 0 ]; then - break - fi - if [ "$attempt" -lt "$max_attempts" ]; then - sleep 5 - fi - attempt="$((attempt + 1))" - done - finished_at="$(date +%s)" - duration="$((finished_at - started_at))" - - lane_status="success" - if [ "$status" -ne 0 ]; then - lane_status="failure" - fi - - cat > "artifacts/nightly-result-${{ matrix.name }}.json" <> "$GITHUB_STEP_SUMMARY" - - echo "lane_status=${lane_status}" >> "$GITHUB_OUTPUT" - echo "lane_exit_code=${status}" >> "$GITHUB_OUTPUT" - - - name: Upload lane report - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ${{ needs.resolve-profile.outputs.lane_artifact_prefix }}-${{ matrix.name }} - path: artifacts/nightly-result-${{ matrix.name }}.json - if-no-files-found: error - retention-days: ${{ fromJSON(needs.resolve-profile.outputs.lane_retention_days) }} - - - name: Enforce lane success - if: steps.lane.outputs.lane_status != 'success' - shell: bash - run: | - set -euo pipefail - code="${{ steps.lane.outputs.lane_exit_code }}" - if [[ "$code" =~ ^[0-9]+$ ]]; then - # shellcheck disable=SC2242 - exit "$code" - fi - echo "Invalid lane exit code: $code" >&2 - exit 1 - - summary: - name: ${{ needs.resolve-profile.outputs.summary_job_name }} - needs: [resolve-profile, feature-check] - if: always() - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Download lane reports - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - path: artifacts - - - name: Collect recent nightly history - if: needs.resolve-profile.outputs.collect_history == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const fs = require("fs"); - const path = require("path"); - - const workflowId = "feature-matrix.yml"; - const owner = context.repo.owner; - const repo = context.repo.repo; - - const events = ["schedule", "workflow_dispatch"]; - let runs = []; - for (const event of events) { - const resp = await github.rest.actions.listWorkflowRuns({ - owner, - repo, - workflow_id: workflowId, - branch: "dev", - event, - per_page: 20, - }); - runs = runs.concat(resp.data.workflow_runs || []); - } - - const currentRunId = context.runId; - runs = runs - .filter((run) => run.id !== currentRunId && run.status === "completed") - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .slice(0, 3) - .map((run) => ({ - run_id: run.id, - url: run.html_url, - event: run.event, - conclusion: run.conclusion || "unknown", - created_at: run.created_at, - head_sha: run.head_sha, - display_title: run.display_title || "", - })); - - fs.mkdirSync("artifacts", { recursive: true }); - fs.writeFileSync( - path.join("artifacts", "nightly-history.json"), - `${JSON.stringify(runs, null, 2)}\n`, - { encoding: "utf8" } - ); - - - name: Aggregate matrix summary - shell: bash - run: | - set -euo pipefail - args=( - --input-dir artifacts - --owners-file .github/release/nightly-owner-routing.json - --output-json "artifacts/${{ needs.resolve-profile.outputs.summary_json_name }}" - --output-md "artifacts/${{ needs.resolve-profile.outputs.summary_md_name }}" - ) - - if [ "${{ needs.resolve-profile.outputs.collect_history }}" = "true" ] && [ -f artifacts/nightly-history.json ]; then - args+=(--history-file artifacts/nightly-history.json) - fi - - if [ "${{ needs.resolve-profile.outputs.fail_on_failure }}" = "true" ]; then - args+=(--fail-on-failure) - fi - - python3 scripts/ci/nightly_matrix_report.py "${args[@]}" - - - name: Publish summary - shell: bash - run: | - set -euo pipefail - cat "artifacts/${{ needs.resolve-profile.outputs.summary_md_name }}" >> "$GITHUB_STEP_SUMMARY" - - - name: Upload summary artifact - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ${{ needs.resolve-profile.outputs.summary_artifact_name }} - path: | - artifacts/${{ needs.resolve-profile.outputs.summary_json_name }} - artifacts/${{ needs.resolve-profile.outputs.summary_md_name }} - artifacts/nightly-history.json - if-no-files-found: error - retention-days: ${{ fromJSON(needs.resolve-profile.outputs.lane_retention_days) }} diff --git a/.github/workflows/main-branch-flow.md b/.github/workflows/main-branch-flow.md deleted file mode 100644 index e61e11b..0000000 --- a/.github/workflows/main-branch-flow.md +++ /dev/null @@ -1,266 +0,0 @@ -# Main Branch Delivery Flows - -This document explains what runs when code is proposed to `dev`/`main`, merged to `main`, and released. - -Use this with: - -- [`docs/ci-map.md`](../../docs/ci-map.md) -- [`docs/pr-workflow.md`](../../docs/pr-workflow.md) -- [`docs/release-process.md`](../../docs/release-process.md) - -## Event Summary - -| Event | Main workflows | -| --- | --- | -| PR activity (`pull_request_target`) | `pr-intake-checks.yml`, `pr-labeler.yml`, `pr-auto-response.yml` | -| PR activity (`pull_request`) | `ci-run.yml`, `sec-audit.yml`, plus path-scoped workflows | -| Push to `dev`/`main` | `ci-run.yml`, `sec-audit.yml`, plus path-scoped workflows | -| Tag push (`v*`) | `pub-release.yml` publish mode, `pub-docker-img.yml` publish job | -| Scheduled/manual | `pub-release.yml` verification mode, `sec-codeql.yml`, `feature-matrix.yml`, `test-fuzz.yml`, `pr-check-stale.yml`, `pr-check-status.yml`, `sync-contributors.yml`, `test-benchmarks.yml`, `test-e2e.yml` | - -## Runtime and Docker Matrix - -Observed averages below are from recent completed runs (sampled from GitHub Actions on February 17, 2026). Values are directional, not SLA. - -| Workflow | Typical trigger in main flow | Avg runtime | Docker build? | Docker run? | Docker push? | -| --- | --- | ---:| --- | --- | --- | -| `pr-intake-checks.yml` | PR open/update (`pull_request_target`) | 14.5s | No | No | No | -| `pr-labeler.yml` | PR open/update (`pull_request_target`) | 53.7s | No | No | No | -| `pr-auto-response.yml` | PR/issue automation | 24.3s | No | No | No | -| `ci-run.yml` | PR + push to `dev`/`main` | 74.7s | No | No | No | -| `sec-audit.yml` | PR + push to `dev`/`main` | 127.2s | No | No | No | -| `workflow-sanity.yml` | Workflow-file changes | 34.2s | No | No | No | -| `pr-label-policy-check.yml` | Label policy/automation changes | 14.7s | No | No | No | -| `pub-docker-img.yml` (`pull_request`) | Docker build-input PR changes | 240.4s | Yes | Yes | No | -| `pub-docker-img.yml` (`push`) | tag push `v*` | 139.9s | Yes | No | Yes | -| `pub-release.yml` | Tag push `v*` (publish) + manual/scheduled verification (no publish) | N/A in recent sample | No | No | No | - -Notes: - -1. `pub-docker-img.yml` is the only workflow in the main PR/push path that builds Docker images. -2. Container runtime verification (`docker run`) occurs in PR smoke only. -3. Container registry push occurs on tag pushes (`v*`) only. -4. `ci-run.yml` "Build (Smoke)" builds Rust binaries, not Docker images. - -## Step-By-Step - -### 1) PR from branch in this repository -> `dev` - -1. Contributor opens or updates PR against `dev`. -2. `pull_request_target` automation runs (typical runtime): - - `pr-intake-checks.yml` posts intake warnings/errors. - - `pr-labeler.yml` sets size/risk/scope labels. - - `pr-auto-response.yml` runs first-interaction and label routes. -3. `pull_request` CI workflows start: - - `ci-run.yml` - - `feature-matrix.yml` (Rust/workflow path scope) - - `sec-audit.yml` - - `sec-codeql.yml` (if Rust/codeql paths changed) - - path-scoped workflows if matching files changed: - - `pub-docker-img.yml` (Docker build-input paths only) - - `docs-deploy.yml` (docs + README markdown paths; deploy contract guard enforces promotion + rollback ref policy) - - `workflow-sanity.yml` (workflow files only) - - `pr-label-policy-check.yml` (label-policy files only) - - `ci-change-audit.yml` (CI/security path changes) - - `ci-provider-connectivity.yml` (probe config/script/workflow changes) - - `ci-reproducible-build.yml` (Rust/build reproducibility paths) -4. In `ci-run.yml`, `changes` computes: - - `docs_only` - - `docs_changed` - - `rust_changed` - - `workflow_changed` -5. `build` runs for Rust-impacting changes. -6. On PRs, full lint/test/docs checks run when PR has label `ci:full`: - - `lint` - - `lint-strict-delta` - - `test` - - `flake-probe` (single-retry telemetry; optional block via `CI_BLOCK_ON_FLAKE_SUSPECTED`) - - `docs-quality` -7. If `.github/workflows/**` changed, `workflow-owner-approval` must pass. -8. If root license files (`LICENSE-APACHE`, `LICENSE-MIT`) changed, `license-file-owner-guard` allows only PR author `louisdevzz`. -9. `lint-feedback` posts actionable comment if lint/docs gates fail. -10. `CI Required Gate` aggregates results to final pass/fail. -11. Maintainer merges PR once checks and review policy are satisfied. -12. Merge emits a `push` event on `dev` (see scenario 4). - -### 2) PR from fork -> `dev` - -1. External contributor opens PR from `fork/` into `zerobuild:dev`. -2. Immediately on `opened`: - - `pull_request_target` workflows start with base-repo context and base-repo token: - - `pr-intake-checks.yml` - - `pr-labeler.yml` - - `pr-auto-response.yml` - - `pull_request` workflows are queued for the fork head commit: - - `ci-run.yml` - - `sec-audit.yml` - - path-scoped workflows (`pub-docker-img.yml`, `workflow-sanity.yml`, `pr-label-policy-check.yml`) if changed files match. -3. Fork-specific permission behavior in `pull_request` workflows: - - token is restricted (read-focused), so jobs that try to write PR comments/status extras can be limited. - - secrets from the base repo are not exposed to fork PR `pull_request` jobs. -4. Approval gate possibility: - - if Actions settings require maintainer approval for fork workflows, the `pull_request` run stays in `action_required`/waiting state until approved. -5. Event fan-out after labeling: - - `pr-labeler.yml` and manual label changes emit `labeled`/`unlabeled` events. - - those events retrigger `pull_request_target` automation (`pr-labeler.yml` and `pr-auto-response.yml`), creating extra run volume/noise. -6. When contributor pushes new commits to fork branch (`synchronize`): - - reruns: `pr-intake-checks.yml`, `pr-labeler.yml`, `ci-run.yml`, `sec-audit.yml`, and matching path-scoped PR workflows. - - does not rerun `pr-auto-response.yml` unless label/open events occur. -7. `ci-run.yml` execution details for fork PR: - - `changes` computes `docs_only`, `docs_changed`, `rust_changed`, `workflow_changed`. - - `build` runs for Rust-impacting changes. - - `lint`/`lint-strict-delta`/`test`/`docs-quality` run on PR when `ci:full` label exists. - - `workflow-owner-approval` runs when `.github/workflows/**` changed. - - `CI Required Gate` emits final pass/fail for the PR head. -8. Fork PR merge blockers to check first when diagnosing stalls: - - run approval pending for fork workflows. - - `workflow-owner-approval` failing on workflow-file changes. - - `license-file-owner-guard` failing when root license files are modified by non-owner PR author. - - `CI Required Gate` failure caused by upstream jobs. - - repeated `pull_request_target` reruns from label churn causing noisy signals. -9. After merge, normal `push` workflows on `dev` execute (scenario 4). - -### 3) PR to `main` (direct or from `dev`) - -1. Contributor or maintainer opens PR with base `main`. -2. `ci-run.yml` and `sec-audit.yml` run on the PR, plus any path-scoped workflows. -3. Maintainer merges PR once checks and review policy pass. -4. Merge emits a `push` event on `main`. - -### 4) Push/Merge Queue to `dev` or `main` (including after merge) - -1. Commit reaches `dev` or `main` (usually from a merged PR), or merge queue creates a `merge_group` validation commit. -2. `ci-run.yml` runs on `push` and `merge_group`. -3. `feature-matrix.yml` runs on `push` for Rust/workflow paths and on `merge_group`. -4. `sec-audit.yml` runs on `push` and `merge_group`. -5. `sec-codeql.yml` runs on `push`/`merge_group` when Rust/codeql paths change (path-scoped on push). -6. `ci-supply-chain-provenance.yml` runs on push when Rust/build provenance paths change. -7. Path-filtered workflows run only if touched files match their filters. -8. In `ci-run.yml`, push/merge-group behavior differs from PR behavior: - - Rust path: `lint`, `lint-strict-delta`, `test`, `build` are expected. - - Docs/non-rust paths: fast-path behavior applies. -9. `CI Required Gate` computes overall push/merge-group result. - -## Docker Publish Logic - -Workflow: `.github/workflows/pub-docker-img.yml` - -### PR behavior - -1. Triggered on `pull_request` to `dev` or `main` when Docker build-input paths change. -2. Runs `PR Docker Smoke` job: - - Builds local smoke image with Blacksmith builder. - - Verifies container with `docker run ... --version`. -3. Typical runtime in recent sample: ~240.4s. -4. No registry push happens on PR events. - -### Push behavior - -1. `publish` job runs on tag pushes `v*` only. -2. Workflow trigger includes semantic version tag pushes (`v*`) only. -3. Login to `ghcr.io` uses `${{ github.actor }}` and `${{ secrets.GITHUB_TOKEN }}`. -4. Tag computation includes semantic tag from pushed git tag (`vX.Y.Z`) + SHA tag (`sha-<12>`) + `latest`. -5. Multi-platform publish is used for tag pushes (`linux/amd64,linux/arm64`). -6. `scripts/ci/ghcr_publish_contract_guard.py` validates anonymous pullability and digest parity across `vX.Y.Z`, `sha-<12>`, and `latest`, then emits rollback candidate mapping evidence. -7. Trivy scans are emitted for version, SHA, and latest references. -8. `scripts/ci/ghcr_vulnerability_gate.py` validates Trivy JSON outputs against `.github/release/ghcr-vulnerability-policy.json` and emits audit-event evidence. -9. Typical runtime in recent sample: ~139.9s. -10. Result: pushed image tags under `ghcr.io//` with publish-contract + vulnerability-gate + scan artifacts. - -Important: Docker publish now requires a `v*` tag push; regular `dev`/`main` branch pushes do not publish images. - -## Release Logic - -Workflow: `.github/workflows/pub-release.yml` - -1. Trigger modes: - - Tag push `v*` -> publish mode. - - Manual dispatch -> verification-only or publish mode (input-driven). - - Weekly schedule -> verification-only mode. -2. `prepare` resolves release context (`release_ref`, `release_tag`, publish/draft mode) and runs `scripts/ci/release_trigger_guard.py`. - - publish mode enforces actor authorization, stable annotated tag policy, `origin/main` ancestry, and `release_tag` == `Cargo.toml` version at the tag commit. - - trigger provenance is emitted as `release-trigger-guard` artifacts. -3. `build-release` builds matrix artifacts across Linux/macOS/Windows targets. -4. `verify-artifacts` runs `scripts/ci/release_artifact_guard.py` against `.github/release/release-artifact-contract.json` in verify-stage mode (archive contract required; manifest/SBOM/notice checks intentionally skipped) and uploads `release-artifact-guard-verify` evidence. -5. In publish mode, workflow generates SBOM (`CycloneDX` + `SPDX`), `SHA256SUMS`, and a checksum provenance statement (`zerobuild.sha256sums.intoto.json`) plus audit-event envelope. -6. In publish mode, after manifest generation, workflow reruns `release_artifact_guard.py` in full-contract mode and emits `release-artifact-guard.publish.json` plus `audit-event-release-artifact-guard-publish.json`. -7. In publish mode, workflow keyless-signs release artifacts and composes a supply-chain release-notes preface via `release_notes_with_supply_chain_refs.py`. -8. In publish mode, workflow verifies GHCR release-tag availability. -9. In publish mode, workflow creates/updates the GitHub Release for the resolved tag and commit-ish, combining generated supply-chain preface with GitHub auto-generated commit notes. - -Pre-release path: - -1. Pre-release tags (`vX.Y.Z-alpha.N`, `vX.Y.Z-beta.N`, `vX.Y.Z-rc.N`) trigger `.github/workflows/pub-prerelease.yml`. -2. `scripts/ci/prerelease_guard.py` enforces stage progression, `origin/main` ancestry, and Cargo version/tag alignment. -3. In publish mode, prerelease assets are attached to a GitHub prerelease for the stage tag. - -Canary policy lane: - -1. `.github/workflows/ci-canary-gate.yml` runs weekly or manually. -2. `scripts/ci/canary_guard.py` evaluates metrics against `.github/release/canary-policy.json`. -3. Decision output is explicit (`promote`, `hold`, `abort`) with auditable artifacts and optional dispatch signal. - -## Merge/Policy Notes - -1. Workflow-file changes (`.github/workflows/**`) activate owner-approval gate in `ci-run.yml`. -2. PR lint/test strictness is intentionally controlled by `ci:full` label. -3. `pr-intake-checks.yml` now blocks PRs missing a Linear issue key (`RMN-*`, `CDV-*`, `COM-*`) to keep execution mapped to Linear. -4. `sec-audit.yml` runs on PR/push/merge queue (`merge_group`), plus scheduled weekly. -5. `ci-change-audit.yml` enforces pinned `uses:` references for CI/security workflow changes. -6. `sec-audit.yml` includes deny policy hygiene checks (`deny_policy_guard.py`) before cargo-deny. -7. `sec-audit.yml` includes gitleaks allowlist governance checks (`secrets_governance_guard.py`) against `.github/security/gitleaks-allowlist-governance.json`. -8. `ci-reproducible-build.yml` and `ci-supply-chain-provenance.yml` provide scheduled supply-chain assurance signals outside release-only windows. -9. Some workflows are operational and non-merge-path (`pr-check-stale`, `pr-check-status`, `sync-contributors`, etc.). -10. Workflow-specific JavaScript helpers are organized under `.github/workflows/scripts/`. -11. `ci-run.yml` includes cache partitioning (`prefix-key`) across lint/test/build/flake-probe lanes to reduce cache contention. -12. `ci-rollback.yml` provides a guarded rollback planning lane (scheduled dry-run + manual execute controls) with audit artifacts. - -## Mermaid Diagrams - -### PR to Dev - -```mermaid -flowchart TD - A["PR opened or updated -> dev"] --> B["pull_request_target lane"] - B --> B1["pr-intake-checks.yml"] - B --> B2["pr-labeler.yml"] - B --> B3["pr-auto-response.yml"] - A --> C["pull_request CI lane"] - C --> C1["ci-run.yml"] - C --> C2["sec-audit.yml"] - C --> C3["pub-docker-img.yml (if Docker paths changed)"] - C --> C4["workflow-sanity.yml (if workflow files changed)"] - C --> C5["pr-label-policy-check.yml (if policy files changed)"] - C1 --> D["CI Required Gate"] - D --> E{"Checks + review policy pass?"} - E -->|No| F["PR stays open"] - E -->|Yes| G["Merge PR"] - G --> H["push event on dev"] -``` - -### Main Delivery and Release - -```mermaid -flowchart TD - D0["Commit reaches dev"] --> B0["ci-run.yml"] - D0 --> C0["sec-audit.yml"] - PRM["PR to main"] --> QM["ci-run.yml + sec-audit.yml (+ path-scoped)"] - QM --> M["Merge to main"] - M --> A["Commit reaches main"] - A --> B["ci-run.yml"] - A --> C["sec-audit.yml"] - A --> D["path-scoped workflows (if matched)"] - T["Tag push v*"] --> R["pub-release.yml"] - W["Manual/Scheduled release verify"] --> R - T --> DP["pub-docker-img.yml publish job"] - R --> R1["Artifacts + SBOM + checksums + signatures + GitHub Release"] - W --> R2["Verification build only (no GitHub Release publish)"] - DP --> P1["Push ghcr image tags (version + sha + latest)"] -``` - -## Quick Troubleshooting - -1. Unexpected skipped jobs: inspect `scripts/ci/detect_change_scope.sh` outputs. -2. Workflow-change PR blocked: verify `WORKFLOW_OWNER_LOGINS` and approvals. -3. Fork PR appears stalled: check whether Actions run approval is pending. -4. Docker not published: confirm a `v*` tag was pushed to the intended commit. diff --git a/.github/workflows/nightly-all-features.yml b/.github/workflows/nightly-all-features.yml deleted file mode 100644 index caee4a2..0000000 --- a/.github/workflows/nightly-all-features.yml +++ /dev/null @@ -1,187 +0,0 @@ -name: Nightly All-Features - -on: - schedule: - - cron: "15 3 * * *" # Daily 03:15 UTC - workflow_dispatch: - inputs: - fail_on_failure: - description: "Fail workflow when any nightly lane fails" - required: true - default: true - type: boolean - -concurrency: - group: nightly-all-features-${{ github.ref || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - nightly-lanes: - name: Nightly Lane (${{ matrix.name }}) - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 70 - strategy: - fail-fast: false - matrix: - include: - - name: default - command: cargo test --locked --test agent_e2e --verbose - install_libudev: false - - name: whatsapp-web - command: cargo check --locked --no-default-features --features whatsapp-web --verbose - install_libudev: false - - name: browser-native - command: cargo check --locked --no-default-features --features browser-native --verbose - install_libudev: false - - name: nightly-all-features - command: cargo test --locked --all-features --test agent_e2e --verbose - install_libudev: true - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - with: - prefix-key: nightly-all-features-${{ matrix.name }} - - - name: Ensure Linux deps for all-features lane - if: matrix.install_libudev - shell: bash - run: | - set -euo pipefail - - if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists libudev; then - echo "libudev development headers already available; skipping apt install." - exit 0 - fi - - echo "Installing missing libudev build dependencies..." - for attempt in 1 2 3; do - if sudo apt-get update -qq -o DPkg::Lock::Timeout=300 && \ - sudo apt-get install -y --no-install-recommends --no-upgrade -o DPkg::Lock::Timeout=300 libudev-dev pkg-config; then - echo "Dependency installation succeeded on attempt ${attempt}." - exit 0 - fi - if [ "$attempt" -eq 3 ]; then - echo "Failed to install libudev-dev/pkg-config after ${attempt} attempts." >&2 - exit 1 - fi - echo "Dependency installation failed on attempt ${attempt}; retrying in 10s..." - sleep 10 - done - - - name: Run nightly lane command - id: lane - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - started_at="$(date +%s)" - set +e - bash -lc "${{ matrix.command }}" - status=$? - set -e - finished_at="$(date +%s)" - duration="$((finished_at - started_at))" - - lane_status="success" - if [ "$status" -ne 0 ]; then - lane_status="failure" - fi - - cat > "artifacts/nightly-result-${{ matrix.name }}.json" <> "$GITHUB_STEP_SUMMARY" - - echo "lane_status=${lane_status}" >> "$GITHUB_OUTPUT" - echo "lane_exit_code=${status}" >> "$GITHUB_OUTPUT" - - - name: Upload nightly lane artifact - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: nightly-lane-${{ matrix.name }} - path: artifacts/nightly-result-${{ matrix.name }}.json - if-no-files-found: error - retention-days: 30 - - nightly-summary: - name: Nightly Summary & Routing - needs: [nightly-lanes] - if: always() - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Download nightly artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - path: artifacts - - - name: Aggregate nightly report - shell: bash - env: - FAIL_ON_FAILURE_INPUT: ${{ github.event.inputs.fail_on_failure || 'true' }} - run: | - set -euo pipefail - fail_on_failure="true" - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then - fail_on_failure="${FAIL_ON_FAILURE_INPUT}" - fi - - args=() - if [ "$fail_on_failure" = "true" ]; then - args+=(--fail-on-failure) - fi - - python3 scripts/ci/nightly_matrix_report.py \ - --input-dir artifacts \ - --owners-file .github/release/nightly-owner-routing.json \ - --output-json artifacts/nightly-summary.json \ - --output-md artifacts/nightly-summary.md \ - "${args[@]}" - - - name: Publish nightly summary - shell: bash - run: | - set -euo pipefail - cat artifacts/nightly-summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload nightly summary artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: nightly-all-features-summary - path: | - artifacts/nightly-summary.json - artifacts/nightly-summary.md - if-no-files-found: error - retention-days: 30 diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml deleted file mode 100644 index 34fca0b..0000000 --- a/.github/workflows/pages-deploy.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Deploy GitHub Pages - -on: - push: - branches: - - main - paths: - - site/** - - docs/** - - README.md - - .github/workflows/pages-deploy.yml - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: github-pages - cancel-in-progress: true - -jobs: - build: - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: site/package-lock.json - - - name: Install Dependencies - working-directory: site - run: npm ci - - - name: Build Site - working-directory: site - run: npm run build - - - name: Configure Pages - uses: actions/configure-pages@v5 - - - name: Upload Artifact - uses: actions/upload-pages-artifact@v3 - with: - path: gh-pages - - deploy: - needs: build - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pub-docker-img.yml b/.github/workflows/pub-docker-img.yml deleted file mode 100644 index 41b0dd0..0000000 --- a/.github/workflows/pub-docker-img.yml +++ /dev/null @@ -1,352 +0,0 @@ -name: Pub Docker Img - -on: - push: - tags: ["v*"] - pull_request: - branches: [dev, main] - paths: - - "Dockerfile" - - ".dockerignore" - - "docker-compose.yml" - - "rust-toolchain.toml" - - "dev/config.template.toml" - - ".github/workflows/pub-docker-img.yml" - - ".github/release/ghcr-tag-policy.json" - - ".github/release/ghcr-vulnerability-policy.json" - - "scripts/ci/ghcr_publish_contract_guard.py" - - "scripts/ci/ghcr_vulnerability_gate.py" - workflow_dispatch: - -concurrency: - group: docker-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - pr-smoke: - name: PR Docker Smoke - if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 25 - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Extract metadata (tags, labels) - if: github.event_name == 'pull_request' - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=pr - - - name: Build smoke image - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - push: false - load: true - provenance: false - sbom: false - tags: zerobuild-pr-smoke:latest - labels: ${{ steps.meta.outputs.labels || '' }} - platforms: linux/amd64 - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - run: docker run --rm zerobuild-pr-smoke:latest --version - - publish: - name: Build and Push Docker Image - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && github.repository == 'zerobuild-labs/zerobuild' - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 45 - permissions: - contents: read - packages: write - security-events: write - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Compute tags - id: meta - shell: bash - run: | - set -euo pipefail - IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" - SHA_SUFFIX="sha-${GITHUB_SHA::12}" - SHA_TAG="${IMAGE}:${SHA_SUFFIX}" - LATEST_SUFFIX="latest" - LATEST_TAG="${IMAGE}:${LATEST_SUFFIX}" - if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then - echo "::error::Docker publish is restricted to v* tag pushes." - exit 1 - fi - - RELEASE_TAG="${GITHUB_REF#refs/tags/}" - VERSION_TAG="${IMAGE}:${RELEASE_TAG}" - TAGS="${VERSION_TAG},${SHA_TAG},${LATEST_TAG}" - - { - echo "tags=${TAGS}" - echo "release_tag=${RELEASE_TAG}" - echo "sha_tag=${SHA_SUFFIX}" - echo "latest_tag=${LATEST_SUFFIX}" - } >> "$GITHUB_OUTPUT" - - - name: Build and push Docker image - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Set GHCR package visibility to public - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - owner="${GITHUB_REPOSITORY_OWNER,,}" - repo="${GITHUB_REPOSITORY#*/}" - - # Package path can vary depending on repository/package linkage. - candidates=( - "$repo" - "${owner}%2F${repo}" - ) - - for scope in orgs users; do - for pkg in "${candidates[@]}"; do - code="$(curl -sS -o /tmp/ghcr-visibility.json -w "%{http_code}" \ - -X PATCH \ - -H "Authorization: Bearer ${GH_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/${scope}/${owner}/packages/container/${pkg}/visibility" \ - -d '{"visibility":"public"}' || true)" - - if [ "$code" = "200" ] || [ "$code" = "204" ]; then - echo "GHCR package visibility is public (${scope}/${owner}/${pkg})." - exit 0 - fi - - echo "Visibility attempt ${scope}/${owner}/${pkg} returned HTTP ${code}." - done - done - - echo "::warning::Unable to update GHCR visibility via API in this run; proceeding to GHCR publish contract verification." - - - name: Validate GHCR publish contract - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - python3 scripts/ci/ghcr_publish_contract_guard.py \ - --repository "${GITHUB_REPOSITORY,,}" \ - --release-tag "${{ steps.meta.outputs.release_tag }}" \ - --sha "${GITHUB_SHA}" \ - --policy-file .github/release/ghcr-tag-policy.json \ - --output-json artifacts/ghcr-publish-contract.json \ - --output-md artifacts/ghcr-publish-contract.md \ - --fail-on-violation - - - name: Emit GHCR publish contract audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ghcr-publish-contract.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type ghcr_publish_contract \ - --input-json artifacts/ghcr-publish-contract.json \ - --output-json artifacts/audit-event-ghcr-publish-contract.json \ - --artifact-name ghcr-publish-contract \ - --retention-days 21 - fi - - - name: Publish GHCR contract summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ghcr-publish-contract.md ]; then - cat artifacts/ghcr-publish-contract.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload GHCR publish contract artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ghcr-publish-contract - path: | - artifacts/ghcr-publish-contract.json - artifacts/ghcr-publish-contract.md - artifacts/audit-event-ghcr-publish-contract.json - if-no-files-found: ignore - retention-days: 21 - - - name: Scan published image for vulnerabilities (Trivy) - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - - TAG_NAME="${{ steps.meta.outputs.release_tag }}" - SHA_TAG="${{ steps.meta.outputs.sha_tag }}" - LATEST_TAG="${{ steps.meta.outputs.latest_tag }}" - IMAGE_BASE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" - VERSION_REF="${IMAGE_BASE}:${TAG_NAME}" - SHA_REF="${IMAGE_BASE}:${SHA_TAG}" - LATEST_REF="${IMAGE_BASE}:${LATEST_TAG}" - SARIF_OUT="artifacts/trivy-${TAG_NAME}.sarif" - TABLE_OUT="artifacts/trivy-${TAG_NAME}.txt" - JSON_OUT="artifacts/trivy-${TAG_NAME}.json" - SHA_TABLE_OUT="artifacts/trivy-${SHA_TAG}.txt" - SHA_JSON_OUT="artifacts/trivy-${SHA_TAG}.json" - LATEST_TABLE_OUT="artifacts/trivy-${LATEST_TAG}.txt" - LATEST_JSON_OUT="artifacts/trivy-${LATEST_TAG}.json" - - scan_trivy() { - local image_ref="$1" - local output_prefix="$2" - - docker run --rm \ - -v "$PWD/artifacts:/work" \ - aquasec/trivy:0.58.2 image \ - --quiet \ - --ignore-unfixed \ - --severity HIGH,CRITICAL \ - --format json \ - --output "/work/${output_prefix}.json" \ - "${image_ref}" - - docker run --rm \ - -v "$PWD/artifacts:/work" \ - aquasec/trivy:0.58.2 image \ - --quiet \ - --ignore-unfixed \ - --severity HIGH,CRITICAL \ - --format table \ - --output "/work/${output_prefix}.txt" \ - "${image_ref}" - } - - docker run --rm \ - -v "$PWD/artifacts:/work" \ - aquasec/trivy:0.58.2 image \ - --quiet \ - --ignore-unfixed \ - --severity HIGH,CRITICAL \ - --format sarif \ - --output "/work/trivy-${TAG_NAME}.sarif" \ - "${VERSION_REF}" - - scan_trivy "${VERSION_REF}" "trivy-${TAG_NAME}" - scan_trivy "${SHA_REF}" "trivy-${SHA_TAG}" - scan_trivy "${LATEST_REF}" "trivy-${LATEST_TAG}" - - echo "Generated Trivy reports:" - ls -1 "$SARIF_OUT" "$TABLE_OUT" "$JSON_OUT" "$SHA_TABLE_OUT" "$SHA_JSON_OUT" "$LATEST_TABLE_OUT" "$LATEST_JSON_OUT" - - - name: Validate GHCR vulnerability gate - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/ghcr_vulnerability_gate.py \ - --release-tag "${{ steps.meta.outputs.release_tag }}" \ - --sha-tag "${{ steps.meta.outputs.sha_tag }}" \ - --latest-tag "${{ steps.meta.outputs.latest_tag }}" \ - --release-report-json "artifacts/trivy-${{ steps.meta.outputs.release_tag }}.json" \ - --sha-report-json "artifacts/trivy-${{ steps.meta.outputs.sha_tag }}.json" \ - --latest-report-json "artifacts/trivy-${{ steps.meta.outputs.latest_tag }}.json" \ - --policy-file .github/release/ghcr-vulnerability-policy.json \ - --output-json artifacts/ghcr-vulnerability-gate.json \ - --output-md artifacts/ghcr-vulnerability-gate.md \ - --fail-on-violation - - - name: Emit GHCR vulnerability gate audit event - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ghcr-vulnerability-gate.json ]; then - python3 scripts/ci/emit_audit_event.py \ - --event-type ghcr_vulnerability_gate \ - --input-json artifacts/ghcr-vulnerability-gate.json \ - --output-json artifacts/audit-event-ghcr-vulnerability-gate.json \ - --artifact-name ghcr-vulnerability-gate \ - --retention-days 21 - fi - - - name: Publish GHCR vulnerability summary - if: always() - shell: bash - run: | - set -euo pipefail - if [ -f artifacts/ghcr-vulnerability-gate.md ]; then - cat artifacts/ghcr-vulnerability-gate.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload GHCR vulnerability gate artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ghcr-vulnerability-gate - path: | - artifacts/ghcr-vulnerability-gate.json - artifacts/ghcr-vulnerability-gate.md - artifacts/audit-event-ghcr-vulnerability-gate.json - if-no-files-found: ignore - retention-days: 21 - - - name: Upload Trivy SARIF - if: always() - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4 - with: - sarif_file: artifacts/trivy-${{ github.ref_name }}.sarif - category: ghcr-trivy - - - name: Upload Trivy report artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: ghcr-trivy-report - path: | - artifacts/trivy-${{ github.ref_name }}.sarif - artifacts/trivy-${{ github.ref_name }}.txt - artifacts/trivy-${{ github.ref_name }}.json - artifacts/trivy-sha-*.txt - artifacts/trivy-sha-*.json - artifacts/trivy-latest.txt - artifacts/trivy-latest.json - if-no-files-found: ignore - retention-days: 14 diff --git a/.github/workflows/pub-prerelease.yml b/.github/workflows/pub-prerelease.yml deleted file mode 100644 index c982fa1..0000000 --- a/.github/workflows/pub-prerelease.yml +++ /dev/null @@ -1,259 +0,0 @@ -name: Pub Pre-release - -on: - push: - tags: - - "v*-alpha.*" - - "v*-beta.*" - - "v*-rc.*" - workflow_dispatch: - inputs: - tag: - description: "Existing pre-release tag (e.g. v0.1.8-rc.1)" - required: true - default: "" - type: string - mode: - description: "dry-run validates/builds only; publish creates prerelease" - required: true - default: dry-run - type: choice - options: - - dry-run - - publish - draft: - description: "Create prerelease as draft" - required: true - default: true - type: boolean - -concurrency: - group: prerelease-${{ github.ref || github.run_id }} - cancel-in-progress: false - -permissions: - contents: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - prerelease-guard: - name: Pre-release Guard - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - outputs: - release_tag: ${{ steps.vars.outputs.release_tag }} - mode: ${{ steps.vars.outputs.mode }} - draft: ${{ steps.vars.outputs.draft }} - ready_to_publish: ${{ steps.extract.outputs.ready_to_publish }} - stage: ${{ steps.extract.outputs.stage }} - transition_outcome: ${{ steps.extract.outputs.transition_outcome }} - latest_stage: ${{ steps.extract.outputs.latest_stage }} - latest_stage_tag: ${{ steps.extract.outputs.latest_stage_tag }} - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Resolve prerelease inputs - id: vars - shell: bash - run: | - set -euo pipefail - if [ "${GITHUB_EVENT_NAME}" = "push" ]; then - release_tag="${GITHUB_REF_NAME}" - mode="publish" - draft="false" - else - release_tag="${{ inputs.tag }}" - mode="${{ inputs.mode }}" - draft="${{ inputs.draft }}" - fi - - { - echo "release_tag=${release_tag}" - echo "mode=${mode}" - echo "draft=${draft}" - } >> "$GITHUB_OUTPUT" - - - name: Validate prerelease stage gate - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - python3 scripts/ci/prerelease_guard.py \ - --repo-root . \ - --tag "${{ steps.vars.outputs.release_tag }}" \ - --stage-config-file .github/release/prerelease-stage-gates.json \ - --mode "${{ steps.vars.outputs.mode }}" \ - --output-json artifacts/prerelease-guard.json \ - --output-md artifacts/prerelease-guard.md \ - --fail-on-violation - - - name: Extract prerelease outputs - id: extract - shell: bash - run: | - set -euo pipefail - ready_to_publish="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/prerelease-guard.json', encoding='utf-8')) - print(str(bool(data.get('ready_to_publish', False))).lower()) - PY - )" - stage="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/prerelease-guard.json', encoding='utf-8')) - print(data.get('stage', 'unknown')) - PY - )" - transition_outcome="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/prerelease-guard.json', encoding='utf-8')) - transition = data.get('transition') or {} - print(transition.get('outcome', 'unknown')) - PY - )" - latest_stage="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/prerelease-guard.json', encoding='utf-8')) - history = data.get('stage_history') or {} - print(history.get('latest_stage', 'unknown')) - PY - )" - latest_stage_tag="$(python3 - <<'PY' - import json - data = json.load(open('artifacts/prerelease-guard.json', encoding='utf-8')) - history = data.get('stage_history') or {} - print(history.get('latest_tag', 'unknown')) - PY - )" - { - echo "ready_to_publish=${ready_to_publish}" - echo "stage=${stage}" - echo "transition_outcome=${transition_outcome}" - echo "latest_stage=${latest_stage}" - echo "latest_stage_tag=${latest_stage_tag}" - } >> "$GITHUB_OUTPUT" - - - name: Emit prerelease audit event - if: always() - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type prerelease_guard \ - --input-json artifacts/prerelease-guard.json \ - --output-json artifacts/audit-event-prerelease-guard.json \ - --artifact-name prerelease-guard \ - --retention-days 21 - - - name: Publish prerelease summary - if: always() - shell: bash - run: | - set -euo pipefail - cat artifacts/prerelease-guard.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload prerelease guard artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: prerelease-guard - path: | - artifacts/prerelease-guard.json - artifacts/prerelease-guard.md - artifacts/audit-event-prerelease-guard.json - if-no-files-found: error - retention-days: 21 - - build-prerelease: - name: Build Pre-release Artifact - needs: [prerelease-guard] - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 45 - steps: - - name: Checkout tag - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ needs.prerelease-guard.outputs.release_tag }} - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - with: - prefix-key: prerelease-${{ needs.prerelease-guard.outputs.release_tag }} - cache-targets: true - - - name: Build release-fast binary - shell: bash - run: | - set -euo pipefail - cargo build --profile release-fast --locked --target x86_64-unknown-linux-gnu - - - name: Package prerelease artifact - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - cp target/x86_64-unknown-linux-gnu/release-fast/zerobuild artifacts/zerobuild - tar czf artifacts/zerobuild-x86_64-unknown-linux-gnu.tar.gz -C artifacts zerobuild - rm artifacts/zerobuild - - - name: Generate manifest + checksums - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/release_manifest.py \ - --artifacts-dir artifacts \ - --release-tag "${{ needs.prerelease-guard.outputs.release_tag }}" \ - --output-json artifacts/prerelease-manifest.json \ - --output-md artifacts/prerelease-manifest.md \ - --checksums-path artifacts/SHA256SUMS \ - --fail-empty - - - name: Publish prerelease build summary - shell: bash - run: | - set -euo pipefail - cat artifacts/prerelease-manifest.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload prerelease build artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: prerelease-artifacts - path: artifacts/* - if-no-files-found: error - retention-days: 14 - - publish-prerelease: - name: Publish GitHub Pre-release - needs: [prerelease-guard, build-prerelease] - if: needs.prerelease-guard.outputs.ready_to_publish == 'true' - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 15 - steps: - - name: Download prerelease artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - name: prerelease-artifacts - path: artifacts - - - name: Create or update GitHub pre-release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 - with: - tag_name: ${{ needs.prerelease-guard.outputs.release_tag }} - prerelease: true - draft: ${{ needs.prerelease-guard.outputs.draft == 'true' }} - generate_release_notes: true - files: | - artifacts/**/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pub-release.yml b/.github/workflows/pub-release.yml deleted file mode 100644 index 6d3bd06..0000000 --- a/.github/workflows/pub-release.yml +++ /dev/null @@ -1,645 +0,0 @@ -name: Pub Release - -on: - push: - tags: ["v*"] - workflow_dispatch: - inputs: - release_ref: - description: "Git ref (branch, tag, or SHA) to build" - required: false - default: "main" - type: string - publish_release: - description: "Publish a GitHub release (false = verification build only)" - required: false - default: false - type: boolean - release_tag: - description: "Existing release tag (required when publish_release=true), e.g. v0.1.1" - required: false - default: "" - type: string - draft: - description: "Create release as draft (manual publish only)" - required: false - default: true - type: boolean - schedule: - # Weekly release-readiness verification on default branch (no publish) - - cron: "17 8 * * 1" - -concurrency: - group: release-${{ github.ref || github.run_id }} - cancel-in-progress: false - -permissions: - contents: write - packages: read - id-token: write # Required for cosign keyless signing via OIDC - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - prepare: - name: Prepare Release Context - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - outputs: - release_ref: ${{ steps.vars.outputs.release_ref }} - release_tag: ${{ steps.vars.outputs.release_tag }} - publish_release: ${{ steps.vars.outputs.publish_release }} - draft_release: ${{ steps.vars.outputs.draft_release }} - steps: - - name: Resolve release inputs - id: vars - shell: bash - run: | - set -euo pipefail - - event_name="${GITHUB_EVENT_NAME}" - publish_release="false" - draft_release="false" - - if [[ "$event_name" == "push" ]]; then - release_ref="${GITHUB_REF_NAME}" - release_tag="${GITHUB_REF_NAME}" - publish_release="true" - elif [[ "$event_name" == "workflow_dispatch" ]]; then - release_ref="${{ inputs.release_ref }}" - publish_release="${{ inputs.publish_release }}" - draft_release="${{ inputs.draft }}" - - if [[ "$publish_release" == "true" ]]; then - release_tag="${{ inputs.release_tag }}" - if [[ -z "$release_tag" ]]; then - echo "::error::release_tag is required when publish_release=true" - exit 1 - fi - release_ref="$release_tag" - else - release_tag="verify-${GITHUB_SHA::12}" - fi - else - # schedule - release_ref="main" - release_tag="verify-${GITHUB_SHA::12}" - fi - - { - echo "release_ref=${release_ref}" - echo "release_tag=${release_tag}" - echo "publish_release=${publish_release}" - echo "draft_release=${draft_release}" - } >> "$GITHUB_OUTPUT" - - { - echo "### Release Context" - echo "- event: ${event_name}" - echo "- release_ref: ${release_ref}" - echo "- release_tag: ${release_tag}" - echo "- publish_release: ${publish_release}" - echo "- draft_release: ${draft_release}" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Validate release trigger and authorization guard - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts - python3 scripts/ci/release_trigger_guard.py \ - --repo-root . \ - --repository "${GITHUB_REPOSITORY}" \ - --event-name "${GITHUB_EVENT_NAME}" \ - --actor "${GITHUB_ACTOR}" \ - --release-ref "${{ steps.vars.outputs.release_ref }}" \ - --release-tag "${{ steps.vars.outputs.release_tag }}" \ - --publish-release "${{ steps.vars.outputs.publish_release }}" \ - --authorized-actors "${{ vars.RELEASE_AUTHORIZED_ACTORS || 'louisdevzz' }}" \ - --authorized-tagger-emails "${{ vars.RELEASE_AUTHORIZED_TAGGER_EMAILS || '' }}" \ - --require-annotated-tag true \ - --output-json artifacts/release-trigger-guard.json \ - --output-md artifacts/release-trigger-guard.md \ - --fail-on-violation - - - name: Emit release trigger audit event - if: always() - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type release_trigger_guard \ - --input-json artifacts/release-trigger-guard.json \ - --output-json artifacts/audit-event-release-trigger-guard.json \ - --artifact-name release-trigger-guard \ - --retention-days 30 - - - name: Publish release trigger guard summary - if: always() - shell: bash - run: | - set -euo pipefail - cat artifacts/release-trigger-guard.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload release trigger guard artifacts - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: release-trigger-guard - path: | - artifacts/release-trigger-guard.json - artifacts/release-trigger-guard.md - artifacts/audit-event-release-trigger-guard.json - if-no-files-found: error - retention-days: 30 - - build-release: - name: Build ${{ matrix.target }} - needs: [prepare] - runs-on: ${{ matrix.os }} - timeout-minutes: 40 - strategy: - fail-fast: false - matrix: - include: - # Keep GNU Linux release artifacts on Ubuntu 22.04 to preserve - # a broadly compatible GLIBC baseline for user distributions. - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: x86_64-unknown-linux-gnu - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: x86_64-unknown-linux-musl - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - use_cross: true - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: aarch64-unknown-linux-gnu - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: gcc-aarch64-linux-gnu - linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER - linker: aarch64-linux-gnu-gcc - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: aarch64-unknown-linux-musl - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - use_cross: true - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: armv7-unknown-linux-gnueabihf - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: gcc-arm-linux-gnueabihf - linker_env: CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER - linker: arm-linux-gnueabihf-gcc - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: armv7-linux-androideabi - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - android_ndk: true - android_api: 21 - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: aarch64-linux-android - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - android_ndk: true - android_api: 21 - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: x86_64-unknown-freebsd - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - use_cross: true - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: x86_64-apple-darwin - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: aarch64-apple-darwin - artifact: zerobuild - archive_ext: tar.gz - cross_compiler: "" - linker_env: "" - linker: "" - - os: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - target: x86_64-pc-windows-msvc - artifact: zerobuild.exe - archive_ext: zip - cross_compiler: "" - linker_env: "" - linker: "" - - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ needs.prepare.outputs.release_ref }} - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - targets: ${{ matrix.target }} - - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - if: runner.os != 'Windows' - - - name: Install cross for cross-built targets - if: matrix.use_cross - run: | - cargo install cross --git https://github.com/cross-rs/cross - - - name: Install cross-compilation toolchain (Linux) - if: runner.os == 'Linux' && matrix.cross_compiler != '' - run: | - sudo apt-get update -qq - sudo apt-get install -y "${{ matrix.cross_compiler }}" - - - name: Setup Android NDK - if: matrix.android_ndk - shell: bash - run: | - set -euo pipefail - NDK_VERSION="r26d" - NDK_ZIP="android-ndk-${NDK_VERSION}-linux.zip" - NDK_URL="https://dl.google.com/android/repository/${NDK_ZIP}" - NDK_ROOT="${RUNNER_TEMP}/android-ndk" - NDK_HOME="${NDK_ROOT}/android-ndk-${NDK_VERSION}" - - sudo apt-get update -qq - sudo apt-get install -y unzip - - mkdir -p "${NDK_ROOT}" - curl -fsSL "${NDK_URL}" -o "${RUNNER_TEMP}/${NDK_ZIP}" - unzip -q "${RUNNER_TEMP}/${NDK_ZIP}" -d "${NDK_ROOT}" - - echo "ANDROID_NDK_HOME=${NDK_HOME}" >> "$GITHUB_ENV" - echo "${NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> "$GITHUB_PATH" - - - name: Configure Android toolchain - if: matrix.android_ndk - shell: bash - run: | - echo "Setting up Android NDK toolchain for ${{ matrix.target }}" - NDK_HOME="${ANDROID_NDK_HOME:-}" - if [[ -z "$NDK_HOME" ]]; then - echo "::error::ANDROID_NDK_HOME was not configured." - exit 1 - fi - TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" - - # Add to path for linker resolution - echo "$TOOLCHAIN" >> "$GITHUB_PATH" - - # Set linker environment variables - if [[ "${{ matrix.target }}" == "armv7-linux-androideabi" ]]; then - ARMV7_CC="${TOOLCHAIN}/armv7a-linux-androideabi${{ matrix.android_api }}-clang" - ARMV7_CXX="${TOOLCHAIN}/armv7a-linux-androideabi${{ matrix.android_api }}-clang++" - - # Some crates still probe legacy compiler names (arm-linux-androideabi-clang). - ln -sf "$ARMV7_CC" "${TOOLCHAIN}/arm-linux-androideabi-clang" - ln -sf "$ARMV7_CXX" "${TOOLCHAIN}/arm-linux-androideabi-clang++" - - { - echo "CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=${ARMV7_CC}" - echo "CC_armv7_linux_androideabi=${ARMV7_CC}" - echo "CXX_armv7_linux_androideabi=${ARMV7_CXX}" - echo "AR_armv7_linux_androideabi=${TOOLCHAIN}/llvm-ar" - } >> "$GITHUB_ENV" - elif [[ "${{ matrix.target }}" == "aarch64-linux-android" ]]; then - AARCH64_CC="${TOOLCHAIN}/aarch64-linux-android${{ matrix.android_api }}-clang" - AARCH64_CXX="${TOOLCHAIN}/aarch64-linux-android${{ matrix.android_api }}-clang++" - - { - echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${AARCH64_CC}" - echo "CC_aarch64_linux_android=${AARCH64_CC}" - echo "CXX_aarch64_linux_android=${AARCH64_CXX}" - echo "AR_aarch64_linux_android=${TOOLCHAIN}/llvm-ar" - } >> "$GITHUB_ENV" - fi - - - name: Build release - shell: bash - env: - LINKER_ENV: ${{ matrix.linker_env }} - LINKER: ${{ matrix.linker }} - USE_CROSS: ${{ matrix.use_cross }} - run: | - if [ -n "$LINKER_ENV" ] && [ -n "$LINKER" ]; then - echo "Using linker override: $LINKER_ENV=$LINKER" - export "$LINKER_ENV=$LINKER" - fi - if [ "$USE_CROSS" = "true" ]; then - echo "Using cross for MUSL target" - cross build --profile release-fast --locked --target ${{ matrix.target }} - else - cargo build --profile release-fast --locked --target ${{ matrix.target }} - fi - - - name: Check binary size (Unix) - if: runner.os != 'Windows' - run: bash scripts/ci/check_binary_size.sh "target/${{ matrix.target }}/release-fast/${{ matrix.artifact }}" "${{ matrix.target }}" - - - name: Package (Unix) - if: runner.os != 'Windows' - run: | - cd target/${{ matrix.target }}/release-fast - tar czf ../../../zerobuild-${{ matrix.target }}.${{ matrix.archive_ext }} ${{ matrix.artifact }} - - - name: Package (Windows) - if: runner.os == 'Windows' - run: | - cd target/${{ matrix.target }}/release-fast - 7z a ../../../zerobuild-${{ matrix.target }}.${{ matrix.archive_ext }} ${{ matrix.artifact }} - - - name: Upload artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: zerobuild-${{ matrix.target }} - path: zerobuild-${{ matrix.target }}.${{ matrix.archive_ext }} - retention-days: 7 - - verify-artifacts: - name: Verify Artifact Set - needs: [prepare, build-release] - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ needs.prepare.outputs.release_ref }} - - - name: Download all artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - path: artifacts - - - name: Validate release archive contract (verify stage) - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/release_artifact_guard.py \ - --artifacts-dir artifacts \ - --contract-file .github/release/release-artifact-contract.json \ - --output-json artifacts/release-artifact-guard.verify.json \ - --output-md artifacts/release-artifact-guard.verify.md \ - --allow-extra-archives \ - --skip-manifest-files \ - --skip-sbom-files \ - --skip-notice-files \ - --fail-on-violation - - - name: Emit verify-stage artifact guard audit event - if: always() - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type release_artifact_guard_verify \ - --input-json artifacts/release-artifact-guard.verify.json \ - --output-json artifacts/audit-event-release-artifact-guard-verify.json \ - --artifact-name release-artifact-guard-verify \ - --retention-days 21 - - - name: Publish verify-stage artifact guard summary - if: always() - shell: bash - run: | - set -euo pipefail - cat artifacts/release-artifact-guard.verify.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload verify-stage artifact guard reports - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: release-artifact-guard-verify - path: | - artifacts/release-artifact-guard.verify.json - artifacts/release-artifact-guard.verify.md - artifacts/audit-event-release-artifact-guard-verify.json - if-no-files-found: error - retention-days: 21 - - publish: - name: Publish Release - if: needs.prepare.outputs.publish_release == 'true' - needs: [prepare, verify-artifacts] - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 45 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ needs.prepare.outputs.release_ref }} - - - name: Download all artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - path: artifacts - - - name: Install syft - shell: bash - run: | - set -euo pipefail - mkdir -p "${RUNNER_TEMP}/bin" - ./scripts/ci/install_syft.sh "${RUNNER_TEMP}/bin" - echo "${RUNNER_TEMP}/bin" >> "$GITHUB_PATH" - - - name: Generate SBOM (CycloneDX) - run: | - syft dir:. --source-name zerobuild -o cyclonedx-json=artifacts/zerobuild.cdx.json -o spdx-json=artifacts/zerobuild.spdx.json - { - echo "### SBOM Generated" - echo "- CycloneDX: zerobuild.cdx.json" - echo "- SPDX: zerobuild.spdx.json" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Attach license and notice files - run: | - cp LICENSE-APACHE artifacts/LICENSE-APACHE - cp LICENSE-MIT artifacts/LICENSE-MIT - cp NOTICE artifacts/NOTICE - - - name: Generate release manifest + checksums - shell: bash - env: - RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} - run: | - set -euo pipefail - python3 scripts/ci/release_manifest.py \ - --artifacts-dir artifacts \ - --release-tag "${RELEASE_TAG}" \ - --output-json artifacts/release-manifest.json \ - --output-md artifacts/release-manifest.md \ - --checksums-path artifacts/SHA256SUMS \ - --fail-empty - - - name: Generate SHA256SUMS provenance statement - shell: bash - env: - RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} - run: | - set -euo pipefail - python3 scripts/ci/generate_provenance.py \ - --artifact artifacts/SHA256SUMS \ - --subject-name "zerobuild-${RELEASE_TAG}-sha256sums" \ - --output artifacts/zerobuild.sha256sums.intoto.json - - - name: Emit SHA256SUMS provenance audit event - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type release_sha256sums_provenance \ - --input-json artifacts/zerobuild.sha256sums.intoto.json \ - --output-json artifacts/audit-event-release-sha256sums-provenance.json \ - --artifact-name release-sha256sums-provenance \ - --retention-days 30 - - - name: Validate release artifact contract (publish stage) - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/release_artifact_guard.py \ - --artifacts-dir artifacts \ - --contract-file .github/release/release-artifact-contract.json \ - --output-json artifacts/release-artifact-guard.publish.json \ - --output-md artifacts/release-artifact-guard.publish.md \ - --allow-extra-archives \ - --allow-extra-manifest-files \ - --allow-extra-sbom-files \ - --allow-extra-notice-files \ - --fail-on-violation - - - name: Emit publish-stage artifact guard audit event - if: always() - shell: bash - run: | - set -euo pipefail - python3 scripts/ci/emit_audit_event.py \ - --event-type release_artifact_guard_publish \ - --input-json artifacts/release-artifact-guard.publish.json \ - --output-json artifacts/audit-event-release-artifact-guard-publish.json \ - --artifact-name release-artifact-guard-publish \ - --retention-days 30 - - - name: Publish artifact guard summary - shell: bash - run: | - set -euo pipefail - cat artifacts/release-artifact-guard.publish.md >> "$GITHUB_STEP_SUMMARY" - - - name: Publish release manifest summary - shell: bash - run: | - set -euo pipefail - cat artifacts/release-manifest.md >> "$GITHUB_STEP_SUMMARY" - - - name: Install cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - - name: Sign artifacts with cosign (keyless) - shell: bash - run: | - set -euo pipefail - while IFS= read -r -d '' file; do - cosign sign-blob --yes \ - --bundle="${file}.sigstore.json" \ - --output-signature="${file}.sig" \ - --output-certificate="${file}.pem" \ - "$file" - done < <(find artifacts -type f ! -name '*.sig' ! -name '*.pem' ! -name '*.sigstore.json' -print0) - - - name: Compose release-notes supply-chain references - shell: bash - env: - RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} - run: | - set -euo pipefail - python3 scripts/ci/release_notes_with_supply_chain_refs.py \ - --artifacts-dir artifacts \ - --repository "${GITHUB_REPOSITORY}" \ - --release-tag "${RELEASE_TAG}" \ - --output-json artifacts/release-notes-supply-chain.json \ - --output-md artifacts/release-notes-supply-chain.md \ - --fail-on-missing - - - name: Publish release-notes supply-chain summary - shell: bash - run: | - set -euo pipefail - cat artifacts/release-notes-supply-chain.md >> "$GITHUB_STEP_SUMMARY" - - - name: Verify GHCR release tag availability - shell: bash - env: - RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} - run: | - set -euo pipefail - repo="${GITHUB_REPOSITORY,,}" - manifest_url="https://ghcr.io/v2/${repo}/manifests/${RELEASE_TAG}" - accept_header="application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json" - max_attempts=75 - sleep_seconds=20 - - for attempt in $(seq 1 "$max_attempts"); do - token_resp="$(curl -sS "https://ghcr.io/token?scope=repository:${repo}:pull" || true)" - token="$(echo "$token_resp" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')" - - if [ -z "$token" ]; then - code="000" - else - code="$(curl -sS -o /tmp/ghcr-release-manifest.json -w "%{http_code}" \ - -H "Authorization: Bearer ${token}" \ - -H "Accept: ${accept_header}" \ - "${manifest_url}" || true)" - fi - - if [ "$code" = "200" ]; then - echo "GHCR release tag is available: ${repo}:${RELEASE_TAG}" - exit 0 - fi - - if [ "$attempt" -lt "$max_attempts" ]; then - echo "Waiting for GHCR tag ${repo}:${RELEASE_TAG} (attempt ${attempt}/${max_attempts}, HTTP ${code})..." - sleep "$sleep_seconds" - fi - done - - echo "::error::GHCR tag ${repo}:${RELEASE_TAG} was not available before release publish timeout." - cat /tmp/ghcr-release-manifest.json || true - exit 1 - - - name: Create GitHub Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 - with: - tag_name: ${{ needs.prepare.outputs.release_tag }} - draft: ${{ needs.prepare.outputs.draft_release == 'true' }} - body_path: artifacts/release-notes-supply-chain.md - generate_release_notes: true - files: | - artifacts/**/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scripts/test_benchmarks_pr_comment.js b/.github/workflows/scripts/test_benchmarks_pr_comment.js deleted file mode 100644 index d517141..0000000 --- a/.github/workflows/scripts/test_benchmarks_pr_comment.js +++ /dev/null @@ -1,57 +0,0 @@ -// Extracted from test-benchmarks.yml step: Post benchmark summary on PR - -module.exports = async ({ github, context, core }) => { - const fs = require('fs'); - const output = fs.readFileSync('benchmark_output.txt', 'utf8'); - - // Extract Criterion result lines - const lines = output.split('\n').filter(l => - l.includes('time:') || l.includes('change:') || l.includes('Performance') - ); - - if (lines.length === 0) { - core.info('No benchmark results to post.'); - return; - } - - const body = [ - '## 📊 Benchmark Results', - '', - '```', - lines.join('\n'), - '```', - '', - '
Full output', - '', - '```', - output.substring(0, 60000), - '```', - '
', - ].join('\n'); - - // Find and update or create comment - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - }); - - const marker = '## 📊 Benchmark Results'; - const existing = comments.find(c => c.body && c.body.startsWith(marker)); - - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body, - }); - } -}; diff --git a/.github/workflows/sec-vorpal-reviewdog.yml b/.github/workflows/sec-vorpal-reviewdog.yml deleted file mode 100644 index 6187550..0000000 --- a/.github/workflows/sec-vorpal-reviewdog.yml +++ /dev/null @@ -1,191 +0,0 @@ -name: Sec Vorpal Reviewdog - -on: - workflow_dispatch: - inputs: - scan_scope: - description: "File selection mode when source_path is empty" - required: true - type: choice - default: changed - options: - - changed - - all - base_ref: - description: "Base branch/ref for changed diff mode" - required: true - type: string - default: main - source_path: - description: "Optional comma-separated file paths to scan (overrides scan_scope)" - required: false - type: string - include_tests: - description: "Include test/fixture files in scan selection" - required: true - type: choice - default: "false" - options: - - "false" - - "true" - folders_to_ignore: - description: "Optional comma-separated path prefixes to ignore" - required: false - type: string - default: target,node_modules,web/dist,.venv,venv - reporter: - description: "Reviewdog reporter mode" - required: true - type: choice - default: github-pr-check - options: - - github-pr-check - - github-pr-review - filter_mode: - description: "Reviewdog filter mode" - required: true - type: choice - default: file - options: - - added - - diff_context - - file - - nofilter - level: - description: "Reviewdog severity level" - required: true - type: choice - default: error - options: - - info - - warning - - error - fail_on_error: - description: "Fail workflow when Vorpal reports findings" - required: true - type: choice - default: "false" - options: - - "false" - - "true" - reviewdog_flags: - description: "Optional extra reviewdog flags" - required: false - type: string - -concurrency: - group: sec-vorpal-reviewdog-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - checks: write - pull-requests: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - vorpal: - name: Vorpal Reviewdog Scan - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Resolve source paths - id: sources - shell: bash - env: - INPUT_SOURCE_PATH: ${{ inputs.source_path }} - INPUT_SCAN_SCOPE: ${{ inputs.scan_scope }} - INPUT_BASE_REF: ${{ inputs.base_ref }} - INPUT_INCLUDE_TESTS: ${{ inputs.include_tests }} - run: | - set -euo pipefail - - strip_space() { - local value="$1" - value="${value//$'\n'/}" - value="${value//$'\r'/}" - value="${value// /}" - echo "$value" - } - - source_override="$(strip_space "${INPUT_SOURCE_PATH}")" - if [ -n "${source_override}" ]; then - normalized="$(echo "${INPUT_SOURCE_PATH}" | tr '\n' ',' | sed -E 's/[[:space:]]+//g; s/,+/,/g; s/^,|,$//g')" - if [ -n "${normalized}" ]; then - { - echo "scan=true" - echo "source_path=${normalized}" - echo "selection=manual" - } >> "${GITHUB_OUTPUT}" - exit 0 - fi - fi - - include_ext='\.(py|js|jsx|ts|tsx)$' - exclude_paths='^(target/|node_modules/|web/node_modules/|dist/|web/dist/|\.venv/|venv/)' - exclude_tests='(^|/)(test|tests|__tests__|fixtures|mocks|examples)/|(^|/)test_helpers/|(_test\.py$)|(^|/)test_.*\.py$|(\.spec\.(ts|tsx|js|jsx)$)|(\.test\.(ts|tsx|js|jsx)$)' - - if [ "${INPUT_SCAN_SCOPE}" = "all" ]; then - candidate_files="$(git ls-files)" - else - base_ref="${INPUT_BASE_REF#refs/heads/}" - base_ref="${base_ref#origin/}" - if git fetch --no-tags --depth=1 origin "${base_ref}" >/dev/null 2>&1; then - if merge_base="$(git merge-base HEAD "origin/${base_ref}" 2>/dev/null)"; then - candidate_files="$(git diff --name-only --diff-filter=ACMR "${merge_base}"...HEAD)" - else - echo "Unable to resolve merge-base for origin/${base_ref}; falling back to tracked files." - candidate_files="$(git ls-files)" - fi - else - echo "Unable to fetch origin/${base_ref}; falling back to tracked files." - candidate_files="$(git ls-files)" - fi - fi - - source_files="$(printf '%s\n' "${candidate_files}" | sed '/^$/d' | grep -E "${include_ext}" | grep -Ev "${exclude_paths}" || true)" - if [ "${INPUT_INCLUDE_TESTS}" != "true" ] && [ -n "${source_files}" ]; then - source_files="$(printf '%s\n' "${source_files}" | grep -Ev "${exclude_tests}" || true)" - fi - if [ -z "${source_files}" ]; then - { - echo "scan=false" - echo "source_path=" - echo "selection=none" - } >> "${GITHUB_OUTPUT}" - exit 0 - fi - - source_path="$(printf '%s\n' "${source_files}" | paste -sd, -)" - { - echo "scan=true" - echo "source_path=${source_path}" - echo "selection=auto-${INPUT_SCAN_SCOPE}" - } >> "${GITHUB_OUTPUT}" - - - name: No supported files to scan - if: steps.sources.outputs.scan != 'true' - shell: bash - run: | - echo "No supported files selected for Vorpal scan (extensions: .py .js .jsx .ts .tsx)." - - - name: Run Vorpal with reviewdog - if: steps.sources.outputs.scan == 'true' - uses: Checkmarx/vorpal-reviewdog-github-action@8cc292f337a2f1dea581b4f4bd73852e7becb50d # v1.2.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - source_path: ${{ steps.sources.outputs.source_path }} - folders_to_ignore: ${{ inputs.folders_to_ignore }} - reporter: ${{ inputs.reporter }} - filter_mode: ${{ inputs.filter_mode }} - level: ${{ inputs.level }} - fail_on_error: ${{ inputs.fail_on_error }} - reviewdog_flags: ${{ inputs.reviewdog_flags }} diff --git a/.github/workflows/sync-contributors.yml b/.github/workflows/sync-contributors.yml deleted file mode 100644 index a781808..0000000 --- a/.github/workflows/sync-contributors.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Sync Contributors - -on: - workflow_dispatch: - schedule: - # Run every Sunday at 00:00 UTC - - cron: '0 0 * * 0' - -concurrency: - group: update-notice-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: write - pull-requests: write - -jobs: - update-notice: - name: Update NOTICE with new contributors - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Fetch contributors - id: contributors - env: - GH_TOKEN: ${{ github.token }} - run: | - # Fetch all contributors (excluding bots) - gh api \ - --paginate \ - "repos/${{ github.repository }}/contributors" \ - --jq '.[] | select(.type != "Bot") | .login' > /tmp/contributors_raw.txt - - # Sort alphabetically and filter - sort -f < /tmp/contributors_raw.txt > contributors.txt - - # Count contributors - count=$(wc -l < contributors.txt | tr -d ' ') - echo "count=$count" >> "$GITHUB_OUTPUT" - - - name: Generate new NOTICE file - run: | - cat > NOTICE << 'EOF' - ZeroBuild - Copyright 2025 ZeroBuild Labs - - This product includes software developed at ZeroBuild Labs (https://github.com/zerobuild-labs). - - Contributors - ============ - - The following individuals have contributed to ZeroBuild: - - EOF - - # Append contributors in alphabetical order - sed 's/^/- /' contributors.txt >> NOTICE - - # Add third-party dependencies section - cat >> NOTICE << 'EOF' - - - Third-Party Dependencies - ========================= - - This project uses the following third-party libraries and components, - each licensed under their respective terms: - - See Cargo.lock for a complete list of dependencies and their licenses. - EOF - - - name: Check if NOTICE changed - id: check_diff - run: | - if git diff --quiet NOTICE; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Create Pull Request - if: steps.check_diff.outputs.changed == 'true' - env: - GH_TOKEN: ${{ github.token }} - COUNT: ${{ steps.contributors.outputs.count }} - run: | - branch_name="auto/update-notice-$(date +%Y%m%d)" - - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - git checkout -b "$branch_name" - git add NOTICE - git commit -m "chore(notice): update contributor list" - git push origin "$branch_name" - - gh pr create \ - --title "chore(notice): update contributor list" \ - --body "Auto-generated update to NOTICE file with $COUNT contributors." \ - --label "chore" \ - --label "docs" \ - --draft || true - - - name: Summary - run: | - echo "## NOTICE Update Results" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - if [ "${{ steps.check_diff.outputs.changed }}" = "true" ]; then - echo "✅ PR created to update NOTICE" >> "$GITHUB_STEP_SUMMARY" - else - echo "✓ NOTICE file is up to date" >> "$GITHUB_STEP_SUMMARY" - fi - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "**Contributors:** ${{ steps.contributors.outputs.count }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/test-benchmarks.yml b/.github/workflows/test-benchmarks.yml deleted file mode 100644 index 9654d9c..0000000 --- a/.github/workflows/test-benchmarks.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Test Benchmarks - -on: - schedule: - - cron: "0 3 * * 1" # Weekly Monday 3am UTC - workflow_dispatch: - -concurrency: - group: bench-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: - contents: read - pull-requests: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - benchmarks: - name: Criterion Benchmarks - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 30 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - - - name: Run benchmarks - run: cargo bench --locked 2>&1 | tee benchmark_output.txt - - - name: Upload benchmark results - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: benchmark-results - path: | - target/criterion/ - benchmark_output.txt - retention-days: 7 - - - name: Post benchmark summary on PR - if: github.event_name == 'pull_request' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const script = require('./.github/workflows/scripts/test_benchmarks_pr_comment.js'); - await script({ github, context, core }); diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml deleted file mode 100644 index 97dabf7..0000000 --- a/.github/workflows/test-e2e.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test E2E - -on: - push: - branches: [dev, main] - workflow_dispatch: - -concurrency: - group: e2e-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - integration-tests: - name: Integration / E2E Tests - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 30 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: 1.92.0 - - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 - - name: Run integration / E2E tests - run: cargo test --test agent_e2e --locked --verbose diff --git a/.github/workflows/test-fuzz.yml b/.github/workflows/test-fuzz.yml deleted file mode 100644 index 809672a..0000000 --- a/.github/workflows/test-fuzz.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Test Fuzz - -on: - schedule: - - cron: "0 2 * * 0" # Weekly Sunday 2am UTC - workflow_dispatch: - inputs: - fuzz_seconds: - description: "Seconds to run each fuzz target" - required: false - default: "300" - -concurrency: - group: fuzz-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - issues: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - CARGO_TERM_COLOR: always - -jobs: - fuzz: - name: Fuzz (${{ matrix.target }}) - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - target: - - fuzz_config_parse - - fuzz_tool_params - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - with: - toolchain: nightly - components: llvm-tools-preview - - - name: Install cargo-fuzz - run: cargo install cargo-fuzz --locked - - - name: Run fuzz target - run: | - SECONDS="${{ github.event.inputs.fuzz_seconds || '300' }}" - echo "Fuzzing ${{ matrix.target }} for ${SECONDS}s" - cargo +nightly fuzz run ${{ matrix.target }} -- \ - -max_total_time="${SECONDS}" \ - -max_len=4096 - continue-on-error: true - id: fuzz - - - name: Upload crash artifacts - if: failure() || steps.fuzz.outcome == 'failure' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: fuzz-crashes-${{ matrix.target }} - path: fuzz/artifacts/${{ matrix.target }}/ - retention-days: 30 - if-no-files-found: ignore - - - name: Report fuzz results - run: | - echo "### Fuzz: ${{ matrix.target }}" >> "$GITHUB_STEP_SUMMARY" - if [ "${{ steps.fuzz.outcome }}" = "failure" ]; then - echo "- :x: Crashes found — see artifacts" >> "$GITHUB_STEP_SUMMARY" - else - echo "- :white_check_mark: No crashes found" >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.github/workflows/workflow-sanity.yml b/.github/workflows/workflow-sanity.yml deleted file mode 100644 index da9d7f3..0000000 --- a/.github/workflows/workflow-sanity.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Workflow Sanity - -on: - pull_request: - paths: - - ".github/workflows/**" - - ".github/*.yml" - - ".github/*.yaml" - push: - paths: - - ".github/workflows/**" - - ".github/*.yml" - - ".github/*.yaml" - -concurrency: - group: workflow-sanity-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: core.hooksPath - GIT_CONFIG_VALUE_0: /dev/null - - -jobs: - no-tabs: - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 10 - steps: - - name: Normalize git global hooks config - shell: bash - run: | - set -euo pipefail - git config --global --unset-all core.hooksPath || true - - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Fail on tabs in workflow files - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - from __future__ import annotations - - import pathlib - import sys - - root = pathlib.Path(".github/workflows") - bad: list[str] = [] - for path in sorted(root.rglob("*.yml")): - if b"\t" in path.read_bytes(): - bad.append(str(path)) - for path in sorted(root.rglob("*.yaml")): - if b"\t" in path.read_bytes(): - bad.append(str(path)) - - if bad: - print("Tabs found in workflow file(s):") - for path in bad: - print(f"- {path}") - sys.exit(1) - PY - - actionlint: - runs-on: [self-hosted, Linux, X64, aws-india, blacksmith-2vcpu-ubuntu-2404, hetzner] - timeout-minutes: 10 - steps: - - name: Normalize git global hooks config - shell: bash - run: | - set -euo pipefail - git config --global --unset-all core.hooksPath || true - - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Install actionlint binary - shell: bash - run: | - set -euo pipefail - version="1.7.11" - arch="$(uname -m)" - case "$arch" in - x86_64|amd64) archive="actionlint_${version}_linux_amd64.tar.gz" ;; - aarch64|arm64) archive="actionlint_${version}_linux_arm64.tar.gz" ;; - *) - echo "::error::Unsupported architecture: ${arch}" - exit 1 - ;; - esac - - curl -fsSL \ - -o "$RUNNER_TEMP/actionlint.tgz" \ - "https://github.com/rhysd/actionlint/releases/download/v${version}/${archive}" - tar -xzf "$RUNNER_TEMP/actionlint.tgz" -C "$RUNNER_TEMP" actionlint - chmod +x "$RUNNER_TEMP/actionlint" - echo "$RUNNER_TEMP" >> "$GITHUB_PATH" - "$RUNNER_TEMP/actionlint" -version - - - name: Lint GitHub workflows - shell: bash - run: actionlint -color