diff --git a/.github/workflows/sweep.yml b/.github/workflows/sweep.yml index 94bc2c8a2a..f0549ac83a 100644 --- a/.github/workflows/sweep.yml +++ b/.github/workflows/sweep.yml @@ -427,6 +427,7 @@ jobs: ); NODE )" + # shellcheck disable=SC2016 signature="$(PAYLOAD="$payload" node -e 'const crypto=require("node:crypto"); process.stdout.write(`sha256=${crypto.createHmac("sha256", process.env.CLAWSWEEPER_WEBHOOK_SECRET).update(process.env.PAYLOAD).digest("hex")}`)')" curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ --request POST \ @@ -450,6 +451,39 @@ jobs: issues: read pull-requests: read statuses: read + outputs: + claimed: ${{ steps.claim-exact-review-queue.outputs.claimed }} + claim_generation: ${{ steps.claim-exact-review-queue.outputs.claim_generation }} + core_artifact_digest: ${{ steps.upload-exact-review-bundle.outputs.artifact-digest }} + core_artifact_id: ${{ steps.upload-exact-review-bundle.outputs.artifact-id }} + core_artifact_name: ${{ steps.create-exact-review-bundle.outputs.artifact_name }} + decision: ${{ steps.live-item.outputs.decision }} + generation_attempt: ${{ steps.create-exact-review-bundle.outputs.generation_attempt }} + generation_outcome: ${{ steps.exact-review-generation-result.outputs.outcome }} + item_key: ${{ steps.claim-exact-review-queue.outputs.item_key }} + item_kind: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).itemKind }} + item_number: ${{ steps.target.outputs.item_number }} + lease_id: ${{ steps.claim-exact-review-queue.outputs.lease_id }} + lease_revision: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} + live_guarded_open: ${{ steps.live-item.outputs.guarded_open }} + live_proceeded: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'false' || steps.live-item.outputs.proceed }} + live_terminal_missing: ${{ steps.live-item.outputs.terminal_missing }} + live_terminal_noop: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'true' || steps.live-item.outputs.terminal_noop }} + protocol_version: ${{ steps.claim-exact-review-queue.outputs.protocol_version }} + pull_head_sha: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceHeadSha || '' }} + requires_live_proof: ${{ steps.inspect-exact-live-proof.outputs.candidate_count != '0' && 'true' || 'false' }} + requeue_latest: ${{ steps.exact-review-generation-result.outputs.requeue_latest }} + reservation_status: ${{ steps.reserve-exact-review-lease.outputs.status }} + retry_at: ${{ steps.exact-review-generation-result.outputs.retry_at }} + retry_kind: ${{ steps.exact-review-generation-result.outputs.retry_kind }} + review_outcome: ${{ steps.review-exact-event-item.outcome }} + review_superseded: ${{ steps.review-exact-event-item.outputs.superseded || 'false' }} + source_sha: ${{ github.sha }} + target_branch: ${{ steps.live-item.outputs.target_branch }} + target_repo: ${{ steps.target.outputs.target_repo }} + target_repo_name: ${{ steps.target.outputs.target_repo_name }} + target_repo_owner: ${{ steps.target.outputs.target_repo_owner }} + target_slug: ${{ steps.target.outputs.target_slug }} steps: - name: Claim exact-review queue lease id: claim-exact-review-queue @@ -1351,119 +1385,6 @@ jobs: echo "requires_terminal=$(jq -r '.requiresTerminal' <<< "$summary")" } >> "$GITHUB_OUTPUT" - - name: Install exact live-proof terminal tools - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.inspect-exact-live-proof.outputs.requires_terminal == 'true' }} - run: | - run_bounded_install() { - local install_timeout_seconds=300 - local install_grace_seconds=5 - local install_pid="" - local install_pgid="" - local watchdog_pid="" - terminate_install_group() { - [ -n "$install_pgid" ] || return 0 - kill -TERM -- "-$install_pgid" 2>/dev/null || return 0 - for ((grace_second = 0; grace_second < install_grace_seconds; grace_second += 1)); do - if ! kill -0 -- "-$install_pgid" 2>/dev/null; then - return 0 - fi - sleep 1 - done - kill -KILL -- "-$install_pgid" 2>/dev/null || true - } - cleanup_install() { - [ -n "$watchdog_pid" ] && kill "$watchdog_pid" 2>/dev/null || true - [ -n "$watchdog_pid" ] && wait "$watchdog_pid" 2>/dev/null || true - terminate_install_group - } - trap cleanup_install EXIT INT TERM - setsid "$@" & - install_pid=$! - install_pgid=$install_pid - ( - sleep "$install_timeout_seconds" - if kill -0 -- "-$install_pgid" 2>/dev/null; then - echo "::error::Exact live-proof package install exceeded ${install_timeout_seconds}s." - terminate_install_group - fi - ) & - watchdog_pid=$! - set +e - wait "$install_pid" - local install_exit_code=$? - set -e - cleanup_install - trap - EXIT INT TERM - return "$install_exit_code" - } - run_bounded_install sudo apt-get update - run_bounded_install sudo apt-get install --yes tmux - - - name: Install exact live-proof recording tools - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.inspect-exact-live-proof.outputs.record_media == 'true' }} - run: | - run_bounded_install() { - local install_timeout_seconds=300 - local install_grace_seconds=5 - local install_pid="" - local install_pgid="" - local watchdog_pid="" - terminate_install_group() { - [ -n "$install_pgid" ] || return 0 - kill -TERM -- "-$install_pgid" 2>/dev/null || return 0 - for ((grace_second = 0; grace_second < install_grace_seconds; grace_second += 1)); do - if ! kill -0 -- "-$install_pgid" 2>/dev/null; then - return 0 - fi - sleep 1 - done - kill -KILL -- "-$install_pgid" 2>/dev/null || true - } - cleanup_install() { - [ -n "$watchdog_pid" ] && kill "$watchdog_pid" 2>/dev/null || true - [ -n "$watchdog_pid" ] && wait "$watchdog_pid" 2>/dev/null || true - terminate_install_group - } - trap cleanup_install EXIT INT TERM - setsid "$@" & - install_pid=$! - install_pgid=$install_pid - ( - sleep "$install_timeout_seconds" - if kill -0 -- "-$install_pgid" 2>/dev/null; then - echo "::error::Exact live-proof package install exceeded ${install_timeout_seconds}s." - terminate_install_group - fi - ) & - watchdog_pid=$! - set +e - wait "$install_pid" - local install_exit_code=$? - set -e - cleanup_install - trap - EXIT INT TERM - return "$install_exit_code" - } - run_bounded_install sudo apt-get update - run_bounded_install sudo apt-get install --yes ffmpeg x11-utils xfonts-base xvfb xterm - - - name: Execute exact review live proof - id: execute-exact-live-proof - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.inspect-exact-live-proof.outputs.candidate_count != '0' && steps.inspect-exact-live-proof.outcome == 'success' }} - env: - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} - run: | - set -euo pipefail - node dist/clawsweeper.js live-proof-review \ - --repo "$TARGET_REPO" \ - --item-numbers "$ITEM_NUMBER" \ - --records-dir artifacts/event \ - --checkout "${{ steps.target.outputs.target_checkout_dir }}" \ - --output artifacts/event/live-proof - test -f "artifacts/event/live-proof/$ITEM_NUMBER/live-verification.json" - echo "produced=true" >> "$GITHUB_OUTPUT" - - name: Finalize exact event action ledger if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.live-item.outputs.proceed == 'true' }} continue-on-error: true @@ -1484,7 +1405,7 @@ jobs: - name: Create exact review artifact bundle id: create-exact-review-bundle - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && !cancelled() && steps.target.outputs.target_enabled == 'true' && steps.live-item.outcome == 'success' && steps.live-item.outputs.scheduled_semantic_noop != 'true' && steps.live-item.outputs.admission_retry != 'true' && steps.setup-pnpm.outcome == 'success' && steps.review-exact-event-item.outputs.superseded != 'true' && (steps.live-item.outputs.proceed != 'true' || (steps.review-exact-event-item.outcome == 'success' && steps.review-exact-event-item.outputs.retry_at == '' && (steps.inspect-exact-live-proof.outputs.candidate_count == '0' || steps.execute-exact-live-proof.outcome == 'success'))) }} + if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && !cancelled() && steps.target.outputs.target_enabled == 'true' && steps.live-item.outcome == 'success' && steps.live-item.outputs.scheduled_semantic_noop != 'true' && steps.live-item.outputs.admission_retry != 'true' && steps.setup-pnpm.outcome == 'success' && steps.review-exact-event-item.outputs.superseded != 'true' && (steps.live-item.outputs.proceed != 'true' || (steps.review-exact-event-item.outcome == 'success' && steps.review-exact-event-item.outputs.retry_at == '' && steps.inspect-exact-live-proof.outcome == 'success')) }} env: EXACT_REVIEW_ACTION_LEDGER_ROOT: ${{ env.CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT }} EXACT_REVIEW_BUNDLE_DIR: .artifacts/exact-review-bundle @@ -1496,10 +1417,10 @@ jobs: EXACT_REVIEW_ITEM_NUMBER: ${{ steps.target.outputs.item_number }} EXACT_REVIEW_LEASE_REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} EXACT_REVIEW_LIVE_GUARDED_OPEN: ${{ steps.live-item.outputs.guarded_open }} - EXACT_REVIEW_LIVE_PROOF_DIR: artifacts/event/live-proof/${{ steps.target.outputs.item_number }} EXACT_REVIEW_LIVE_PROCEEDED: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'false' || steps.live-item.outputs.proceed }} EXACT_REVIEW_LIVE_TERMINAL_MISSING: ${{ steps.live-item.outputs.terminal_missing }} EXACT_REVIEW_LIVE_TERMINAL_NOOP: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'true' || steps.live-item.outputs.terminal_noop }} + EXACT_REVIEW_PULL_HEAD_SHA: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceHeadSha || '' }} EXACT_REVIEW_PRODUCER_JOB: event-review-apply EXACT_REVIEW_PROTOCOL_VERSION: ${{ steps.claim-exact-review-queue.outputs.protocol_version }} EXACT_REVIEW_REPORT_PATH: artifacts/event/${{ steps.target.outputs.item_number }}.md @@ -1515,116 +1436,602 @@ jobs: echo "generation_attempt=$GITHUB_RUN_ATTEMPT" } >> "$GITHUB_OUTPUT" - - uses: ./.github/actions/setup-state - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.create-exact-review-bundle.outcome == 'success' && steps.live-item.outputs.proceed == 'true' && steps.execute-exact-live-proof.outputs.produced != 'true' && (vars.EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED || '1') == '1' }} - id: direct-setup-state + - name: Upload exact review artifact bundle + id: upload-exact-review-bundle + if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && !cancelled() && steps.create-exact-review-bundle.outcome == 'success' }} + uses: actions/upload-artifact@v7 with: - coordinator-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - records-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - records-secret: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} - records-repo-slugs: ${{ steps.target.outputs.target_slug }} - records-item-number: ${{ steps.target.outputs.item_number }} - hydrate-git-state: "false" - hydrate-state-blobs: "false" + name: ${{ steps.create-exact-review-bundle.outputs.artifact_name }} + path: .artifacts/exact-review-bundle + include-hidden-files: true + if-no-files-found: error + retention-days: 90 + + - name: Export exact review generation result + id: exact-review-generation-result + if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && always() }} + env: + ADMISSION_RETRY: ${{ steps.live-item.outputs.admission_retry }} + RETRY_KIND: ${{ steps.live-item.outputs.retry_kind || steps.reserve-exact-review-lease.outputs.retry_kind || steps.review-exact-event-item.outputs.retry_kind }} + RETRY_AT: ${{ steps.live-item.outputs.retry_at || steps.reserve-exact-review-lease.outputs.retry_at || steps.review-exact-event-item.outputs.retry_at }} + CORE_BUNDLE_OUTCOME: ${{ steps.create-exact-review-bundle.outcome }} + CORE_UPLOAD_OUTCOME: ${{ steps.upload-exact-review-bundle.outcome }} + TARGET_ENABLED: ${{ steps.target.outputs.target_enabled }} + LIVE_OUTCOME: ${{ steps.live-item.outcome }} + SCHEDULED_SEMANTIC_NOOP: ${{ steps.live-item.outputs.scheduled_semantic_noop || 'false' }} + REVIEW_OUTCOME: ${{ steps.review-exact-event-item.outcome }} + REVIEW_SUPERSEDED: ${{ steps.review-exact-event-item.outputs.superseded || 'false' }} + RESERVATION_STATUS: ${{ steps.reserve-exact-review-lease.outputs.status }} + run: | + outcome=failure + requeue_latest=false + retry_kind="$RETRY_KIND" + retry_at="$RETRY_AT" + if [ "$ADMISSION_RETRY" = "true" ] && [ -z "$retry_kind" ]; then + outcome=success + requeue_latest=true + elif [ "$TARGET_ENABLED" = "false" ]; then + outcome=success + elif [ "$SCHEDULED_SEMANTIC_NOOP" = "true" ] && [ "$LIVE_OUTCOME" = "success" ]; then + outcome=success + elif [ "$RESERVATION_STATUS" = "superseded" ] || [ "$REVIEW_SUPERSEDED" = "true" ]; then + outcome=success + elif [ "$REVIEW_OUTCOME" = "cancelled" ]; then + outcome=cancelled + elif [ "$LIVE_OUTCOME" = "success" ] && [ "$CORE_BUNDLE_OUTCOME" = "success" ] && [ "$CORE_UPLOAD_OUTCOME" = "success" ]; then + outcome=success + fi + { + echo "outcome=$outcome" + echo "requeue_latest=$requeue_latest" + echo "retry_kind=$retry_kind" + echo "retry_at=$retry_at" + } >> "$GITHUB_OUTPUT" + + - name: Fail unsuccessful exact review generation + # A held review lease is a successful deferral only after the durable queue + # accepts retry ownership; queue completion failures must remain visible. + if: >- + ${{ + always() && + steps.claim-exact-review-queue.outputs.claimed == 'true' && + ( + ( + steps.upload-exact-review-bundle.outcome != 'success' && + steps.reserve-exact-review-lease.outputs.status != 'superseded' && + steps.review-exact-event-item.outputs.superseded != 'true' + ) || + ( + steps.exact-review-generation-result.outputs.outcome != 'success' && + steps.exact-review-generation-result.outputs.retry_kind == '' && + steps.reserve-exact-review-lease.outputs.status != 'held' && + steps.reserve-exact-review-lease.outputs.status != 'superseded' + ) + ) + }} + env: + RESERVATION_STATUS: ${{ steps.reserve-exact-review-lease.outputs.status || 'unknown' }} + REVIEW_EXIT_CODE: ${{ steps.review-exact-event-item.outputs.exit_code || 'unknown' }} + REVIEW_OUTCOME: ${{ steps.review-exact-event-item.outcome || 'not_started' }} + run: | + echo "::error::Exact review generation failed: classification=codex_or_content_failure reservation=$RESERVATION_STATUS review_outcome=$REVIEW_OUTCOME review_exit=$REVIEW_EXIT_CODE" + exit 1 + + event-review-live-proof: + name: Verify exact review live proof + needs: event-review-apply + if: ${{ always() && needs.event-review-apply.result == 'success' && needs.event-review-apply.outputs.claimed == 'true' && needs.event-review-apply.outputs.generation_outcome == 'success' && needs.event-review-apply.outputs.requires_live_proof == 'true' && needs.event-review-apply.outputs.core_artifact_id != '' }} + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + actions: read + contents: read + outputs: + augmentation_artifact_digest: ${{ steps.upload-live-proof-augmentation.outputs.artifact-digest }} + augmentation_artifact_id: ${{ steps.upload-live-proof-augmentation.outputs.artifact-id }} + augmentation_kind: ${{ steps.create-live-proof-augmentation.outputs.kind }} + sealed_clean: ${{ steps.seal-live-proof-completion.outputs.sealed_clean }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.event-review-apply.outputs.source_sha }} + filter: blob:none + fetch-depth: 0 + persist-credentials: false + + - uses: ./.github/actions/setup-pnpm + id: setup-live-proof-pnpm + with: + build-script: build:all + + - name: Download immutable exact review core + id: download-exact-review-core + uses: actions/download-artifact@v8 + with: + artifact-ids: ${{ needs.event-review-apply.outputs.core_artifact_id }} + path: .artifacts/exact-review-core-download + skip-decompress: true + digest-mismatch: error + + - name: Verify and extract exact review core + env: + EXPECTED_DIGEST: ${{ needs.event-review-apply.outputs.core_artifact_digest }} + run: | + set -euo pipefail + archive="$(find .artifacts/exact-review-core-download -maxdepth 1 -type f -print)" + test -n "$archive" + test "$(printf '%s\n' "$archive" | wc -l | tr -d ' ')" = "1" + actual_digest="sha256:$(sha256sum "$archive" | awk '{print $1}')" + test "$actual_digest" = "$EXPECTED_DIGEST" + mkdir -p .artifacts/exact-review-core + unzip -q "$archive" -d .artifacts/exact-review-core + + - name: Validate immutable exact review core + env: + EXACT_REVIEW_BUNDLE_DIR: .artifacts/exact-review-core + EXACT_REVIEW_CLAIM_GENERATION: ${{ needs.event-review-apply.outputs.claim_generation }} + EXACT_REVIEW_DECISION: ${{ needs.event-review-apply.outputs.decision }} + EXACT_REVIEW_GENERATION_ATTEMPT: ${{ needs.event-review-apply.outputs.generation_attempt }} + EXACT_REVIEW_ITEM_KEY: ${{ needs.event-review-apply.outputs.item_key }} + EXACT_REVIEW_ITEM_KIND: ${{ needs.event-review-apply.outputs.item_kind }} + EXACT_REVIEW_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + EXACT_REVIEW_LEASE_REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} + EXACT_REVIEW_LIVE_GUARDED_OPEN: ${{ needs.event-review-apply.outputs.live_guarded_open }} + EXACT_REVIEW_LIVE_PROCEEDED: ${{ needs.event-review-apply.outputs.live_proceeded }} + EXACT_REVIEW_LIVE_TERMINAL_MISSING: ${{ needs.event-review-apply.outputs.live_terminal_missing }} + EXACT_REVIEW_LIVE_TERMINAL_NOOP: ${{ needs.event-review-apply.outputs.live_terminal_noop }} + EXACT_REVIEW_PRODUCER_JOB: event-review-apply + EXACT_REVIEW_PRODUCER_RUN_ID: ${{ github.run_id }} + EXACT_REVIEW_PROTOCOL_VERSION: ${{ needs.event-review-apply.outputs.protocol_version }} + EXACT_REVIEW_PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + EXACT_REVIEW_SOURCE_SHA: ${{ needs.event-review-apply.outputs.source_sha }} + EXACT_REVIEW_TARGET_BRANCH: ${{ needs.event-review-apply.outputs.target_branch }} + EXACT_REVIEW_TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + run: pnpm run --silent repair:exact-review-bundle validate + + - name: Fetch exact public pull request head + env: + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + run: | + set -euo pipefail + test -n "$PULL_HEAD_SHA" + git init .artifacts/live-proof-target + git -C .artifacts/live-proof-target remote add origin "https://github.com/$TARGET_REPO.git" + git -C .artifacts/live-proof-target fetch \ + --no-tags \ + --depth=1 \ + origin \ + "refs/pull/$ITEM_NUMBER/head" + test "$(git -C .artifacts/live-proof-target rev-parse FETCH_HEAD)" = "$PULL_HEAD_SHA" + git -C .artifacts/live-proof-target checkout --detach "$PULL_HEAD_SHA" + + - name: Reinspect exact live-proof plan + id: inspect-exact-live-proof + env: + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + run: | + set -euo pipefail + summary="$(node dist/clawsweeper.js live-proof-review \ + --inspect \ + --repo "$TARGET_REPO" \ + --item-numbers "$ITEM_NUMBER" \ + --records-dir .artifacts/exact-review-core/review \ + --checkout .artifacts/live-proof-target \ + --output .artifacts/live-proof-output)" + test "$(jq -r '.candidates | length' <<< "$summary")" = "1" + { + echo "record_media=$(jq -r '.recordMedia' <<< "$summary")" + echo "requires_terminal=$(jq -r '.requiresTerminal' <<< "$summary")" + } >> "$GITHUB_OUTPUT" + + - name: Install exact live-proof terminal tools + if: ${{ steps.inspect-exact-live-proof.outputs.requires_terminal == 'true' }} + run: | + sudo apt-get update + sudo apt-get install --yes tmux + + - name: Install exact live-proof recording tools + if: ${{ steps.inspect-exact-live-proof.outputs.record_media == 'true' }} + run: | + sudo apt-get update + sudo apt-get install --yes ffmpeg x11-utils xfonts-base xvfb xterm + + - name: Execute exact review live proof without workflow command files + id: execute-exact-live-proof + continue-on-error: true + env: + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + run: | + set -euo pipefail + env \ + -u GITHUB_ENV \ + -u GITHUB_OUTPUT \ + -u GITHUB_PATH \ + -u GITHUB_STEP_SUMMARY \ + node dist/clawsweeper.js live-proof-review \ + --repo "$TARGET_REPO" \ + --item-numbers "$ITEM_NUMBER" \ + --records-dir .artifacts/exact-review-core/review \ + --checkout .artifacts/live-proof-target \ + --output .artifacts/live-proof-output + + - name: Create exact-head live-proof augmentation + id: create-live-proof-augmentation + if: ${{ always() && steps.execute-exact-live-proof.outcome != 'skipped' }} + env: + REVIEW_LIVE_PROOF_AUGMENTATION_DIR: .artifacts/live-proof-augmentation + REVIEW_LIVE_PROOF_CLEANUP_FAILURE: .artifacts/live-proof-output/.cleanup-failures/${{ needs.event-review-apply.outputs.item_number }}.json + REVIEW_LIVE_PROOF_CORE_ARTIFACT_DIGEST: ${{ needs.event-review-apply.outputs.core_artifact_digest }} + REVIEW_LIVE_PROOF_CORE_ARTIFACT_ID: ${{ needs.event-review-apply.outputs.core_artifact_id }} + REVIEW_LIVE_PROOF_CORE_MANIFEST: .artifacts/exact-review-core/manifest.json + REVIEW_LIVE_PROOF_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + REVIEW_LIVE_PROOF_PROOF_DIR: .artifacts/live-proof-output/${{ needs.event-review-apply.outputs.item_number }} + REVIEW_LIVE_PROOF_PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + REVIEW_LIVE_PROOF_RUNNER_ENVIRONMENT: ${{ runner.environment }} + REVIEW_LIVE_PROOF_SOURCE_SHA: ${{ needs.event-review-apply.outputs.source_sha }} + REVIEW_LIVE_PROOF_TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + run: | + set -euo pipefail + summary="$(pnpm run --silent repair:review-live-proof-augmentation create)" + echo "$summary" + echo "kind=$(jq -r '.result.kind' <<< "$summary")" >> "$GITHUB_OUTPUT" + + - name: Upload exact-head live-proof augmentation + id: upload-live-proof-augmentation + if: ${{ always() && steps.create-live-proof-augmentation.outcome == 'success' }} + uses: actions/upload-artifact@v7 + with: + name: exact-review-live-proof-${{ github.run_id }}-${{ github.run_attempt }} + path: .artifacts/live-proof-augmentation + include-hidden-files: true + if-no-files-found: error + retention-days: 90 + + - name: Seal clean live-proof completion + id: seal-live-proof-completion + if: ${{ steps.execute-exact-live-proof.outcome == 'success' && steps.create-live-proof-augmentation.outcome == 'success' && steps.upload-live-proof-augmentation.outcome == 'success' }} + run: echo "sealed_clean=true" >> "$GITHUB_OUTPUT" + + - name: Fail non-clean live-proof execution after sealing its classification + if: ${{ always() && (steps.execute-exact-live-proof.outcome != 'success' || steps.create-live-proof-augmentation.outcome != 'success' || steps.upload-live-proof-augmentation.outcome != 'success') }} + run: | + echo "::error::Exact live-proof execution did not complete cleanly; the trusted finalizer will validate whether this is a cleanup-only failure." + exit 1 + + event-review-finalize: + name: Finalize exact review + needs: + - event-review-apply + - event-review-live-proof + if: ${{ always() && needs.event-review-apply.outputs.claimed == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + actions: write + contents: write + issues: write + pull-requests: write + statuses: read + steps: + - uses: actions/checkout@v7 + if: ${{ needs.event-review-apply.outputs.core_artifact_id != '' }} + with: + ref: ${{ needs.event-review-apply.outputs.source_sha }} + filter: blob:none + fetch-depth: 0 + persist-credentials: false + + - uses: ./.github/actions/setup-action-ledger + if: ${{ needs.event-review-apply.outputs.core_artifact_id != '' }} + continue-on-error: true + + - uses: ./.github/actions/setup-pnpm + if: ${{ needs.event-review-apply.outputs.core_artifact_id != '' }} + id: setup-finalize-pnpm + with: + build-script: build:all - uses: ./.github/actions/setup-github-egress-observer - id: direct-github-egress-observer - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.direct-setup-state.outcome == 'success' }} + id: final-github-egress-observer + if: ${{ needs.event-review-apply.outputs.core_artifact_id != '' && steps.setup-finalize-pnpm.outcome == 'success' }} continue-on-error: true with: - metrics-path: .artifacts/direct-publication/github-egress-v2.jsonl - rate-limit-path: .artifacts/direct-publication/github-rate-limit-details-v2.jsonl + metrics-path: .artifacts/final-publication/github-egress-v2.jsonl + rate-limit-path: .artifacts/final-publication/github-rate-limit-details-v2.jsonl pool-class: target_app stage: publication_apply - source-action: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceAction }} - claim-generation: ${{ steps.claim-exact-review-queue.outputs.claim_generation }} - repeat-revision: ${{ steps.claim-exact-review-queue.outputs.repeat_revision }} + source-action: ${{ fromJSON(needs.event-review-apply.outputs.decision).sourceAction }} + claim-generation: ${{ needs.event-review-apply.outputs.claim_generation }} - - name: Record direct-publication member - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.direct-github-egress-observer.outcome == 'success' }} + - name: Record final-publication member + if: ${{ steps.final-github-egress-observer.outcome == 'success' }} continue-on-error: true env: - TARGET_REPO: ${{ steps.target.outputs.target_repo }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} run: node dist/github-egress-observer-cli.js record-member - - name: Deliver GitHub effects and prepare direct state mutation - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.direct-setup-state.outcome == 'success' }} - id: prepare-direct-exact-review-publication + - name: Download immutable exact review core + if: ${{ needs.event-review-apply.outputs.core_artifact_id != '' }} + id: download-finalize-core + continue-on-error: true + uses: actions/download-artifact@v8 + with: + artifact-ids: ${{ needs.event-review-apply.outputs.core_artifact_id }} + path: .artifacts/finalize-core-download + skip-decompress: true + digest-mismatch: error + + - name: Verify and extract finalizer core + if: ${{ steps.download-finalize-core.outcome == 'success' }} + id: verify-finalize-core continue-on-error: true env: - CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} - EXACT_REVIEW_QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - GH_TOKEN: ${{ steps.target-write-token.outputs.token }} - CLAWSWEEPER_PUBLIC_GH_TOKEN: ${{ steps.target.outputs.target_repo == 'openclaw/openclaw' && github.token || '' }} - REPO_TOKEN: ${{ github.token }} - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} - MIN_AGE_MINUTES: "0" - REVIEW_ONLY: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceAction == 'failed_review_shard_recovery' && 'true' || 'false' }} - EXACT_EVENT_PUBLICATION: "true" - EXACT_REVIEW_CLOSE_COVERAGE_DEFERRED: "true" - EXACT_REVIEW_BATCH_ITEM_KEY: ${{ steps.claim-exact-review-queue.outputs.item_key }} - EXACT_REVIEW_BATCH_REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} - EXACT_REVIEW_BATCH_CLAIM_GENERATION: ${{ steps.claim-exact-review-queue.outputs.claim_generation }} - EXACT_REVIEW_BATCH_MUTATION_OUTPUT: .artifacts/direct-publication-outcome.json - run: pnpm run --silent repair:publish-event-result - - - name: Post direct exact review publication result - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.prepare-direct-exact-review-publication.outcome == 'success' }} - id: direct-exact-review-publication + EXPECTED_DIGEST: ${{ needs.event-review-apply.outputs.core_artifact_digest }} + run: | + set -euo pipefail + archive="$(find .artifacts/finalize-core-download -maxdepth 1 -type f -print)" + test -n "$archive" + test "$(printf '%s\n' "$archive" | wc -l | tr -d ' ')" = "1" + actual_digest="sha256:$(sha256sum "$archive" | awk '{print $1}')" + test "$actual_digest" = "$EXPECTED_DIGEST" + mkdir -p .artifacts/final-review + unzip -q "$archive" -d .artifacts/final-review + + - name: Validate finalizer core + if: ${{ steps.verify-finalize-core.outcome == 'success' }} + id: validate-finalize-core + continue-on-error: true env: - EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED: ${{ vars.EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED || '1' }} - EXACT_REVIEW_DIRECT_MUTATION_OUTPUT: .artifacts/direct-publication-outcome.json - EXACT_REVIEW_DIRECT_REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} - EXACT_REVIEW_DIRECT_SOURCE_ACTION: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceAction }} - EXACT_REVIEW_QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} - run: pnpm run --silent repair:exact-review-direct-publication + EXACT_REVIEW_BUNDLE_DIR: .artifacts/final-review + EXACT_REVIEW_CLAIM_GENERATION: ${{ needs.event-review-apply.outputs.claim_generation }} + EXACT_REVIEW_DECISION: ${{ needs.event-review-apply.outputs.decision }} + EXACT_REVIEW_GENERATION_ATTEMPT: ${{ needs.event-review-apply.outputs.generation_attempt }} + EXACT_REVIEW_ITEM_KEY: ${{ needs.event-review-apply.outputs.item_key }} + EXACT_REVIEW_ITEM_KIND: ${{ needs.event-review-apply.outputs.item_kind }} + EXACT_REVIEW_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + EXACT_REVIEW_LEASE_REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} + EXACT_REVIEW_LIVE_GUARDED_OPEN: ${{ needs.event-review-apply.outputs.live_guarded_open }} + EXACT_REVIEW_LIVE_PROCEEDED: ${{ needs.event-review-apply.outputs.live_proceeded }} + EXACT_REVIEW_LIVE_TERMINAL_MISSING: ${{ needs.event-review-apply.outputs.live_terminal_missing }} + EXACT_REVIEW_LIVE_TERMINAL_NOOP: ${{ needs.event-review-apply.outputs.live_terminal_noop }} + EXACT_REVIEW_PRODUCER_JOB: event-review-apply + EXACT_REVIEW_PRODUCER_RUN_ID: ${{ github.run_id }} + EXACT_REVIEW_PROTOCOL_VERSION: ${{ needs.event-review-apply.outputs.protocol_version }} + EXACT_REVIEW_PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + EXACT_REVIEW_SOURCE_SHA: ${{ needs.event-review-apply.outputs.source_sha }} + EXACT_REVIEW_TARGET_BRANCH: ${{ needs.event-review-apply.outputs.target_branch }} + EXACT_REVIEW_TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + run: pnpm run --silent repair:exact-review-bundle validate - - name: Finalize direct exact review lifecycle - id: finalize-direct-exact-review-lifecycle - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.direct-exact-review-publication.outputs.accepted == 'true' }} + - name: Download exact-head live-proof augmentation + if: ${{ needs.event-review-apply.outputs.requires_live_proof == 'true' && needs.event-review-live-proof.outputs.augmentation_artifact_id != '' }} + id: download-live-proof-augmentation + continue-on-error: true + uses: actions/download-artifact@v8 + with: + artifact-ids: ${{ needs.event-review-live-proof.outputs.augmentation_artifact_id }} + path: .artifacts/live-proof-augmentation-download + skip-decompress: true + digest-mismatch: error + + - name: Verify and materialize live-proof augmentation + if: ${{ steps.download-live-proof-augmentation.outcome == 'success' }} + id: verify-live-proof-augmentation + continue-on-error: true env: - GH_TOKEN: ${{ github.token }} - CLAWSWEEPER_GITHUB_POOL_CLASS: repository_actions - CLAWSWEEPER_GITHUB_STAGE: publication_router - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - TARGET_BRANCH: ${{ steps.target.outputs.target_branch }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} - FENCE_KEY: ${{ steps.claim-exact-review-queue.outputs.item_key }} - REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} - CLAIM_DECISION: ${{ steps.claim-exact-review-queue.outputs.decision }} - DIRECT_PUBLICATION_SUPERSEDED: ${{ steps.direct-exact-review-publication.outputs.superseded }} - DIRECT_OUTCOME: .artifacts/direct-publication-outcome.json - QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} - CLAWSWEEPER_COMMENT_LOOKBACK_MINUTES: ${{ vars.CLAWSWEEPER_COMMENT_LOOKBACK_MINUTES || '180' }} - CLAWSWEEPER_COMMENT_MAX_COMMENTS: ${{ vars.CLAWSWEEPER_COMMENT_MAX_COMMENTS || '1000' }} + EXPECTED_DIGEST: ${{ needs.event-review-live-proof.outputs.augmentation_artifact_digest }} + REVIEW_LIVE_PROOF_AUGMENTATION_DIR: .artifacts/live-proof-augmentation + REVIEW_LIVE_PROOF_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} run: | set -euo pipefail - test -s "$DIRECT_OUTCOME" - jq -e '.kind == "eligible" and .disposition != null' "$DIRECT_OUTCOME" >/dev/null - test -n "$CLAWSWEEPER_WEBHOOK_SECRET" - source_action="$(node -e 'const decision=JSON.parse(process.env.CLAIM_DECISION || "{}"); process.stdout.write(decision.sourceAction || "")')" - lifecycle_terminal="" - lifecycle_router_outcome="" - lifecycle_deferred_coverage="false" - direct_lifecycle_requeue=false - queue_url="${QUEUE_URL%/}" - if [ "${DIRECT_PUBLICATION_SUPERSEDED:-false}" != "true" ]; then - if jq -e '.disposition.requeueLatestExpected == true' "$DIRECT_OUTCOME" >/dev/null; then - lifecycle_terminal="requeue" - # The completion owns this direct receipt. Let its fenced queue - # transition create the fresh source-drift revision atomically. - direct_lifecycle_requeue=true + archive="$(find .artifacts/live-proof-augmentation-download -maxdepth 1 -type f -print)" + test -n "$archive" + test "$(printf '%s\n' "$archive" | wc -l | tr -d ' ')" = "1" + actual_digest="sha256:$(sha256sum "$archive" | awk '{print $1}')" + test "$actual_digest" = "$EXPECTED_DIGEST" + export REVIEW_LIVE_PROOF_ARCHIVE="$archive" + pnpm run --silent repair:review-live-proof-augmentation materialize + + - name: Validate exact-head live-proof augmentation + if: ${{ steps.verify-live-proof-augmentation.outcome == 'success' }} + id: validate-live-proof-augmentation + continue-on-error: true + env: + REVIEW_LIVE_PROOF_AUGMENTATION_DIR: .artifacts/live-proof-augmentation + REVIEW_LIVE_PROOF_CORE_ARTIFACT_DIGEST: ${{ needs.event-review-apply.outputs.core_artifact_digest }} + REVIEW_LIVE_PROOF_CORE_ARTIFACT_ID: ${{ needs.event-review-apply.outputs.core_artifact_id }} + REVIEW_LIVE_PROOF_CORE_MANIFEST: .artifacts/final-review/manifest.json + REVIEW_LIVE_PROOF_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + REVIEW_LIVE_PROOF_PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + REVIEW_LIVE_PROOF_RUNNER_ENVIRONMENT: github-hosted + REVIEW_LIVE_PROOF_SOURCE_SHA: ${{ needs.event-review-apply.outputs.source_sha }} + REVIEW_LIVE_PROOF_TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + run: pnpm run --silent repair:review-live-proof-augmentation validate + + - name: Select exact review publication payload + id: select-final-review + if: ${{ always() }} + env: + APPLY_RESULT: ${{ needs.event-review-apply.result }} + CORE_VALIDATE_OUTCOME: ${{ steps.validate-finalize-core.outcome }} + LIVE_JOB_RESULT: ${{ needs.event-review-live-proof.result }} + LIVE_JOB_SEALED_CLEAN: ${{ needs.event-review-live-proof.outputs.sealed_clean }} + REQUIRES_LIVE_PROOF: ${{ needs.event-review-apply.outputs.requires_live_proof }} + AUGMENTATION_KIND: ${{ needs.event-review-live-proof.outputs.augmentation_kind }} + AUGMENTATION_VALIDATE_OUTCOME: ${{ steps.validate-live-proof-augmentation.outcome }} + run: | + set -euo pipefail + publish=false + mode=retry + if [ "$APPLY_RESULT" = "success" ] && [ "$CORE_VALIDATE_OUTCOME" = "success" ]; then + if [ "$REQUIRES_LIVE_PROOF" != "true" ]; then + publish=true + mode=core + elif [ "$LIVE_JOB_RESULT" = "success" ] && [ "$AUGMENTATION_KIND" = "proof" ] && [ "$AUGMENTATION_VALIDATE_OUTCOME" = "success" ]; then + publish=true + mode=augmented + elif [ "$LIVE_JOB_RESULT" = "failure" ] && [ "$AUGMENTATION_KIND" = "cleanup_only_failure" ] && [ "$AUGMENTATION_VALIDATE_OUTCOME" = "success" ]; then + publish=true + mode=core_cleanup_only + elif [ "$LIVE_JOB_RESULT" = "failure" ] && [ "$LIVE_JOB_SEALED_CLEAN" = "true" ] && [ "$AUGMENTATION_KIND" = "proof" ] && [ "$AUGMENTATION_VALIDATE_OUTCOME" = "success" ]; then + publish=true + mode=core_cleanup_only + fi + fi + { + echo "publish=$publish" + echo "mode=$mode" + } >> "$GITHUB_OUTPUT" + + - name: Merge validated live-proof augmentation + if: ${{ steps.select-final-review.outputs.mode == 'augmented' }} + env: + REVIEW_LIVE_PROOF_AUGMENTATION_DIR: .artifacts/live-proof-augmentation + REVIEW_LIVE_PROOF_CORE_ARTIFACT_DIGEST: ${{ needs.event-review-apply.outputs.core_artifact_digest }} + REVIEW_LIVE_PROOF_CORE_ARTIFACT_ID: ${{ needs.event-review-apply.outputs.core_artifact_id }} + REVIEW_LIVE_PROOF_CORE_MANIFEST: .artifacts/final-review/manifest.json + REVIEW_LIVE_PROOF_DESTINATION_DIR: .artifacts/final-review + REVIEW_LIVE_PROOF_ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + REVIEW_LIVE_PROOF_PULL_HEAD_SHA: ${{ needs.event-review-apply.outputs.pull_head_sha }} + REVIEW_LIVE_PROOF_RUNNER_ENVIRONMENT: github-hosted + REVIEW_LIVE_PROOF_SOURCE_SHA: ${{ needs.event-review-apply.outputs.source_sha }} + REVIEW_LIVE_PROOF_TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + run: pnpm run --silent repair:review-live-proof-augmentation merge + + - name: Install exact live-proof media validators + if: ${{ steps.select-final-review.outputs.mode == 'augmented' && hashFiles('.artifacts/final-review/live-proof/*/live-proof-manifest.json') != '' }} + run: | + sudo apt-get update + sudo apt-get install --yes ffmpeg + + - name: Fold exact live proof into the review artifact + if: ${{ steps.select-final-review.outputs.mode == 'augmented' }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.CLAWSWEEPER_LIVE_PROOF_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CLAWSWEEPER_LIVE_PROOF_AWS_SECRET_ACCESS_KEY }} + CLAWSWEEPER_LIVE_PROOF_S3_ENDPOINT: ${{ secrets.CLAWSWEEPER_LIVE_PROOF_S3_ENDPOINT }} + CLAWSWEEPER_LIVE_PROOF_BUCKET: ${{ secrets.CLAWSWEEPER_LIVE_PROOF_BUCKET }} + CLAWSWEEPER_LIVE_PROOF_BASE_URL: ${{ secrets.CLAWSWEEPER_LIVE_PROOF_BASE_URL }} + run: node dist/clawsweeper.js live-proof-publish-artifacts --artifact-dir .artifacts/final-review + + - name: Stage final exact review artifact + if: ${{ steps.select-final-review.outputs.publish == 'true' }} + run: | + set -euo pipefail + mkdir -p artifacts/event + report=".artifacts/final-review/review/${{ needs.event-review-apply.outputs.item_number }}.md" + if [ -f "$report" ]; then + cp "$report" "artifacts/event/${{ needs.event-review-apply.outputs.item_number }}.md" + fi + + - name: Create target write token + if: ${{ always() && needs.event-review-apply.outputs.target_repo_owner != '' }} + id: finalize-target-write-token + continue-on-error: true + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} + private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} + owner: ${{ needs.event-review-apply.outputs.target_repo_owner }} + repositories: ${{ needs.event-review-apply.outputs.target_repo_name }} + permission-checks: read + permission-contents: write + permission-issues: write + permission-pull-requests: write + permission-statuses: read + + - uses: ./.github/actions/setup-state + if: ${{ steps.select-final-review.outputs.publish == 'true' && steps.finalize-target-write-token.outcome == 'success' }} + id: finalize-setup-state + with: + coordinator-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} + records-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} + records-secret: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} + records-repo-slugs: ${{ needs.event-review-apply.outputs.target_slug }} + records-item-number: ${{ needs.event-review-apply.outputs.item_number }} + hydrate-git-state: "false" + hydrate-state-blobs: "false" + + - name: Deliver exact review and prepare state mutation + if: ${{ steps.select-final-review.outputs.publish == 'true' && steps.finalize-setup-state.outcome == 'success' }} + id: prepare-final-exact-review-publication + continue-on-error: true + env: + CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} + EXACT_REVIEW_QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} + GH_TOKEN: ${{ steps.finalize-target-write-token.outputs.token }} + CLAWSWEEPER_PUBLIC_GH_TOKEN: ${{ needs.event-review-apply.outputs.target_repo == 'openclaw/openclaw' && github.token || '' }} + REPO_TOKEN: ${{ github.token }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + MIN_AGE_MINUTES: "0" + REVIEW_ONLY: ${{ fromJSON(needs.event-review-apply.outputs.decision).sourceAction == 'failed_review_shard_recovery' && 'true' || 'false' }} + EXACT_EVENT_PUBLICATION: "true" + EXACT_REVIEW_CLOSE_COVERAGE_DEFERRED: "true" + EXACT_REVIEW_BATCH_ITEM_KEY: ${{ needs.event-review-apply.outputs.item_key }} + EXACT_REVIEW_BATCH_REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} + EXACT_REVIEW_BATCH_CLAIM_GENERATION: ${{ needs.event-review-apply.outputs.claim_generation }} + EXACT_REVIEW_BATCH_MUTATION_OUTPUT: .artifacts/final-publication-outcome.json + run: pnpm run --silent repair:publish-event-result + + - name: Commit direct exact review publication result + if: ${{ steps.prepare-final-exact-review-publication.outcome == 'success' }} + id: final-exact-review-publication + env: + EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED: "1" + EXACT_REVIEW_DIRECT_MUTATION_OUTPUT: .artifacts/final-publication-outcome.json + EXACT_REVIEW_DIRECT_REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} + EXACT_REVIEW_DIRECT_SOURCE_ACTION: ${{ fromJSON(needs.event-review-apply.outputs.decision).sourceAction }} + EXACT_REVIEW_QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} + CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} + run: pnpm run --silent repair:exact-review-direct-publication + + - name: Finalize direct exact review lifecycle + id: finalize-exact-review-lifecycle + if: ${{ steps.final-exact-review-publication.outputs.accepted == 'true' }} + env: + GH_TOKEN: ${{ github.token }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + TARGET_BRANCH: ${{ needs.event-review-apply.outputs.target_branch }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + FENCE_KEY: ${{ needs.event-review-apply.outputs.item_key }} + REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} + CLAIM_DECISION: ${{ needs.event-review-apply.outputs.decision }} + DIRECT_PUBLICATION_SUPERSEDED: ${{ steps.final-exact-review-publication.outputs.superseded }} + DIRECT_OUTCOME: .artifacts/final-publication-outcome.json + QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} + CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} + CLAWSWEEPER_COMMENT_LOOKBACK_MINUTES: ${{ vars.CLAWSWEEPER_COMMENT_LOOKBACK_MINUTES || '180' }} + CLAWSWEEPER_COMMENT_MAX_COMMENTS: ${{ vars.CLAWSWEEPER_COMMENT_MAX_COMMENTS || '1000' }} + run: | + # shellcheck disable=SC2016 + set -euo pipefail + test -s "$DIRECT_OUTCOME" + jq -e '.kind == "eligible" and .disposition != null' "$DIRECT_OUTCOME" >/dev/null + test -n "$CLAWSWEEPER_WEBHOOK_SECRET" + # The Node source intentionally owns its template expansion. + # shellcheck disable=SC2016 + source_action="$(node -e 'const decision=JSON.parse(process.env.CLAIM_DECISION || "{}"); process.stdout.write(decision.sourceAction || "")')" + terminal="" + router_outcome="" + deferred_coverage=false + direct_requeue=false + if [ "${DIRECT_PUBLICATION_SUPERSEDED:-false}" != "true" ]; then + if jq -e '.disposition.requeueLatestExpected == true' "$DIRECT_OUTCOME" >/dev/null; then + terminal=requeue + direct_requeue=true elif jq -e '.disposition.terminalMissingExpected == true' "$DIRECT_OUTCOME" >/dev/null; then - lifecycle_terminal="target_missing" + terminal=target_missing elif jq -e '.disposition.terminalClosedExpected == true' "$DIRECT_OUTCOME" >/dev/null; then - lifecycle_terminal="target_closed" + terminal=target_closed elif jq -e '.disposition.guardedOpenAction != null and .disposition.guardedOpenAction != ""' "$DIRECT_OUTCOME" >/dev/null; then - lifecycle_terminal="guarded_open" + terminal=guarded_open elif [ "$source_action" = "failed_review_shard_recovery" ]; then - lifecycle_router_outcome="not_required" + router_outcome=not_required elif jq -e '.disposition.routableSyncExpected == true' "$DIRECT_OUTCOME" >/dev/null; then gh workflow run repair-comment-router.yml \ --repo "$GITHUB_REPOSITORY" \ @@ -1635,48 +2042,34 @@ jobs: -f item_numbers="$ITEM_NUMBER" \ -f lookback_minutes="$CLAWSWEEPER_COMMENT_LOOKBACK_MINUTES" \ -f max_comments="$CLAWSWEEPER_COMMENT_MAX_COMMENTS" - lifecycle_router_outcome="durable" + router_outcome=durable elif jq -e '.disposition.deferredCloseCoverageExpected == true' "$DIRECT_OUTCOME" >/dev/null; then - # The close proof remains a separate later concern. The durable - # handoff means this review itself is complete. - lifecycle_router_outcome="durable" - lifecycle_deferred_coverage="true" + router_outcome=durable + deferred_coverage=true else - lifecycle_terminal="policy_noop" + terminal=policy_noop fi fi - if [ -n "$lifecycle_router_outcome" ]; then - export LIFECYCLE_ROUTER_OUTCOME="$lifecycle_router_outcome" - export LIFECYCLE_DEFERRED_COVERAGE="$lifecycle_deferred_coverage" - lifecycle_payload="$(node -e ' + queue_url="${QUEUE_URL%/}" + if [ -n "$router_outcome" ]; then + # shellcheck disable=SC2016 + payload="$(LIFECYCLE_ROUTER_OUTCOME="$router_outcome" LIFECYCLE_DEFERRED_COVERAGE="$deferred_coverage" node -e ' const revision = Number(process.env.REVISION); if (!Number.isInteger(revision) || revision < 1 || !process.env.FENCE_KEY) process.exit(1); - const prefix = process.env.LIFECYCLE_DEFERRED_COVERAGE === "true" - ? "router-direct-proof" - : process.env.LIFECYCLE_ROUTER_OUTCOME === "not_required" - ? "router-direct-not-required" - : "router-direct"; process.stdout.write(JSON.stringify({ canonical_target_key: `${process.env.TARGET_REPO}#${process.env.ITEM_NUMBER}`, fence_key: process.env.FENCE_KEY, revision, outcome: process.env.LIFECYCLE_ROUTER_OUTCOME, - receipt_id: `${prefix}:${process.env.GITHUB_RUN_ID}:${process.env.GITHUB_RUN_ATTEMPT}`, + receipt_id: `router-final:${process.env.GITHUB_RUN_ID}:${process.env.GITHUB_RUN_ATTEMPT}`, })); ')" - lifecycle_signature="$(PAYLOAD="$lifecycle_payload" node -e 'const crypto=require("node:crypto"); process.stdout.write(`sha256=${crypto.createHmac("sha256", process.env.CLAWSWEEPER_WEBHOOK_SECRET).update(process.env.PAYLOAD).digest("hex")}`)')" - lifecycle_response="$(curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ - --request POST \ - --header "content-type: application/json" \ - --header "x-clawsweeper-exact-review-signature: $lifecycle_signature" \ - --data "$lifecycle_payload" \ - "$queue_url/internal/exact-review/lifecycle/router-receipt")" - jq -e '.ok == true' <<<"$lifecycle_response" >/dev/null - elif [ -n "$lifecycle_terminal" ]; then - export LIFECYCLE_TERMINAL="$lifecycle_terminal" - lifecycle_payload="$(node -e ' + endpoint=lifecycle/router-receipt + elif [ -n "$terminal" ]; then + # shellcheck disable=SC2016 + payload="$(LIFECYCLE_TERMINAL="$terminal" node -e ' const revision = Number(process.env.REVISION); - if (!Number.isInteger(revision) || revision < 1 || !process.env.FENCE_KEY || !process.env.LIFECYCLE_TERMINAL) process.exit(1); + if (!Number.isInteger(revision) || revision < 1 || !process.env.FENCE_KEY) process.exit(1); process.stdout.write(JSON.stringify({ canonical_target_key: `${process.env.TARGET_REPO}#${process.env.ITEM_NUMBER}`, fence_key: process.env.FENCE_KEY, @@ -1684,27 +2077,28 @@ jobs: kind: process.env.LIFECYCLE_TERMINAL, })); ')" - lifecycle_signature="$(PAYLOAD="$lifecycle_payload" node -e 'const crypto=require("node:crypto"); process.stdout.write(`sha256=${crypto.createHmac("sha256", process.env.CLAWSWEEPER_WEBHOOK_SECRET).update(process.env.PAYLOAD).digest("hex")}`)')" - lifecycle_response="$(curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ - --request POST \ - --header "content-type: application/json" \ - --header "x-clawsweeper-exact-review-signature: $lifecycle_signature" \ - --data "$lifecycle_payload" \ - "$queue_url/internal/exact-review/lifecycle/terminal-disposition")" - jq -e '.ok == true' <<<"$lifecycle_response" >/dev/null + endpoint=lifecycle/terminal-disposition + else + echo "direct_requeue=false" >> "$GITHUB_OUTPUT" + exit 0 fi - echo "direct_lifecycle_requeue=$direct_lifecycle_requeue" >> "$GITHUB_OUTPUT" + # shellcheck disable=SC2016 + signature="$(PAYLOAD="$payload" node -e 'const crypto=require("node:crypto"); process.stdout.write(`sha256=${crypto.createHmac("sha256", process.env.CLAWSWEEPER_WEBHOOK_SECRET).update(process.env.PAYLOAD).digest("hex")}`)')" + curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ + --request POST \ + --header "content-type: application/json" \ + --header "x-clawsweeper-exact-review-signature: $signature" \ + --data "$payload" \ + "$queue_url/internal/exact-review/$endpoint" >/dev/null + echo "direct_requeue=$direct_requeue" >> "$GITHUB_OUTPUT" - # This is deliberately after the durable lifecycle handoff. Command - # acknowledgement is performed later by the dedicated finalizer and is - # not a precondition for optional implementation dispatch. - name: Dispatch exact high-confidence bug implementation - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.finalize-direct-exact-review-lifecycle.outcome == 'success' && vars.CLAWSWEEPER_AUTO_IMPLEMENT_ISSUES == '1' && steps.target.outputs.target_repo == 'openclaw/openclaw' && fromJSON(steps.claim-exact-review-queue.outputs.decision).itemKind == 'issue' }} + if: ${{ steps.finalize-exact-review-lifecycle.outcome == 'success' && vars.CLAWSWEEPER_AUTO_IMPLEMENT_ISSUES == '1' && needs.event-review-apply.outputs.target_repo == 'openclaw/openclaw' && needs.event-review-apply.outputs.item_kind == 'issue' }} continue-on-error: true env: GH_TOKEN: ${{ github.token }} - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} MAX_DISPATCH: ${{ vars.CLAWSWEEPER_AUTO_IMPLEMENT_MAX_DISPATCH_PER_SWEEP || '' }} run: | node scripts/dispatch-issue-implementation-candidates.mjs \ @@ -1712,106 +2106,80 @@ jobs: --item-number "$ITEM_NUMBER" \ --artifact-dir artifacts/event - - name: Upload exact review artifact bundle - id: upload-exact-review-bundle - # Keep the artifact if direct publication needs publisher recovery after - # its lifecycle handoff fails. A committed handoff owns the final path. - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && !cancelled() && steps.create-exact-review-bundle.outcome == 'success' && (steps.direct-exact-review-publication.outputs.accepted != 'true' || steps.finalize-direct-exact-review-lifecycle.outcome != 'success') }} - uses: actions/upload-artifact@v7 - with: - name: ${{ steps.create-exact-review-bundle.outputs.artifact_name }} - path: .artifacts/exact-review-bundle - include-hidden-files: true - if-no-files-found: error - retention-days: 90 - - - name: Queue durable exact review publication - id: queue-exact-review-publication - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && !cancelled() && steps.upload-exact-review-bundle.outcome == 'success' }} + - name: Complete exact-review queue lease + id: complete-final-exact-review + if: ${{ always() }} + continue-on-error: true env: - ARTIFACT_NAME: ${{ steps.create-exact-review-bundle.outputs.artifact_name }} - CLAIM_DECISION: ${{ steps.live-item.outputs.decision }} - CLAIM_GENERATION: ${{ steps.claim-exact-review-queue.outputs.claim_generation }} - ITEM_KEY: ${{ steps.claim-exact-review-queue.outputs.item_key }} - LEASE_REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} - LIVE_GUARDED_OPEN: ${{ steps.live-item.outputs.guarded_open }} - LIVE_PROCEEDED: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'false' || steps.live-item.outputs.proceed }} - LIVE_TERMINAL_MISSING: ${{ steps.live-item.outputs.terminal_missing }} - LIVE_TERMINAL_NOOP: ${{ steps.review-exact-event-item.outputs.terminal_during_review == 'true' && 'true' || steps.live-item.outputs.terminal_noop }} - PROTOCOL_VERSION: ${{ steps.claim-exact-review-queue.outputs.protocol_version }} + APPLY_OUTCOME: ${{ needs.event-review-apply.outputs.generation_outcome }} + CLAIM_GENERATION: ${{ needs.event-review-apply.outputs.claim_generation }} + CORE_ARTIFACT_ID: ${{ needs.event-review-apply.outputs.core_artifact_id }} + ITEM_KEY: ${{ needs.event-review-apply.outputs.item_key }} + PROTOCOL_VERSION: ${{ needs.event-review-apply.outputs.protocol_version }} + QUEUE_LEASE_ID: ${{ needs.event-review-apply.outputs.lease_id }} + QUEUE_LEASE_REVISION: ${{ needs.event-review-apply.outputs.lease_revision }} QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} + REQUEUE_LATEST: ${{ needs.event-review-apply.outputs.requeue_latest }} + RETRY_KIND: ${{ needs.event-review-apply.outputs.retry_kind }} + RETRY_AT: ${{ needs.event-review-apply.outputs.retry_at }} + PUBLICATION_ACCEPTED: ${{ steps.final-exact-review-publication.outputs.accepted }} + PUBLICATION_SUPERSEDED: ${{ steps.final-exact-review-publication.outputs.superseded }} + PUBLICATION_LIFECYCLE: ${{ steps.finalize-exact-review-lifecycle.outcome }} + DIRECT_REQUEUE: ${{ steps.finalize-exact-review-lifecycle.outputs.direct_requeue }} + RUN_ATTEMPT: ${{ github.run_attempt }} run: | set -euo pipefail - test -n "$CLAWSWEEPER_WEBHOOK_SECRET" queue_url="${QUEUE_URL%/}" - payload="$(node <<'NODE' - const producerDecision = JSON.parse(process.env.CLAIM_DECISION || "{}"); - const producerRunAttempt = Number(process.env.GITHUB_RUN_ATTEMPT); - const protocolVersion = Number(process.env.PROTOCOL_VERSION); - const leaseRevision = process.env.LEASE_REVISION ? Number(process.env.LEASE_REVISION) : null; - const claimGeneration = process.env.CLAIM_GENERATION ? Number(process.env.CLAIM_GENERATION) : null; - const flag = (name) => { - const value = process.env[name]; - if (value === "true") return true; - if (value === "false") return false; - process.exit(1); - }; - if (!Number.isInteger(producerRunAttempt) || producerRunAttempt < 1) process.exit(1); - process.stdout.write(JSON.stringify({ - delivery_id: `publisher:${process.env.GITHUB_RUN_ID}:${producerRunAttempt}`, - decision: { - ...producerDecision, - sourceAction: "exact_review_artifact_publish", - supersedesInProgress: false, - publication: { - artifactName: process.env.ARTIFACT_NAME, - producerRunId: process.env.GITHUB_RUN_ID, - producerRunAttempt, - sourceSha: process.env.GITHUB_SHA, - itemKey: process.env.ITEM_KEY, - protocolVersion, - leaseRevision, - claimGeneration, - liveProceeded: flag("LIVE_PROCEEDED"), - liveTerminalNoop: flag("LIVE_TERMINAL_NOOP"), - liveTerminalMissing: flag("LIVE_TERMINAL_MISSING"), - liveGuardedOpen: flag("LIVE_GUARDED_OPEN"), - producerDecision, - }, - }, - })); - NODE - )" - signature="$(PAYLOAD="$payload" node -e 'const crypto=require("node:crypto"); process.stdout.write(`sha256=${crypto.createHmac("sha256", process.env.CLAWSWEEPER_WEBHOOK_SECRET).update(process.env.PAYLOAD).digest("hex")}`)')" - for attempt in 1 2 3; do - response="$( - curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ - --request POST \ - --header "content-type: application/json" \ - --header "x-clawsweeper-exact-review-signature: $signature" \ - --data "$payload" \ - "$queue_url/internal/exact-review/enqueue" || true - )" - if jq -e '.superseded == true' <<< "$response" >/dev/null 2>&1; then - echo "::notice::Exact-review publication revision $(jq -r '.publication_revision // "unknown"' <<< "$response") was superseded by revision $(jq -r '.superseded_by_revision // "unknown"' <<< "$response"); the newer publisher owns final delivery." - exit 0 - fi - if jq -e '.ok == true and (.queued == true or .deduped == true)' <<< "$response" >/dev/null; then - exit 0 - fi - if [ "$attempt" -lt 3 ]; then - sleep "$((attempt * 5))" - fi - done - exit 1 + payload="$(node -e ' + const runAttempt = Number(process.env.RUN_ATTEMPT); + const protocolVersion = Number(process.env.PROTOCOL_VERSION); + const leaseRevision = Number(process.env.QUEUE_LEASE_REVISION); + const claimGeneration = Number(process.env.CLAIM_GENERATION); + if (!process.env.QUEUE_LEASE_ID || !Number.isInteger(runAttempt) || runAttempt < 1) process.exit(1); + if (protocolVersion !== 1 && protocolVersion !== 2) process.exit(1); + if (protocolVersion === 2 && (!process.env.ITEM_KEY || !Number.isInteger(leaseRevision) || leaseRevision < 1 || !Number.isInteger(claimGeneration) || claimGeneration < 1)) process.exit(1); + const published = process.env.PUBLICATION_ACCEPTED === "true" && process.env.PUBLICATION_LIFECYCLE === "success"; + const generationNoop = + process.env.APPLY_OUTCOME === "success" && !process.env.CORE_ARTIFACT_ID; + const outcome = published || generationNoop + ? "success" + : process.env.APPLY_OUTCOME === "cancelled" + ? "cancelled" + : "failure"; + const retryKind = String(process.env.RETRY_KIND || "").trim(); + const retryAt = String(process.env.RETRY_AT || "").trim(); + process.stdout.write(JSON.stringify({ + lease_id: process.env.QUEUE_LEASE_ID, + ...(protocolVersion === 2 ? { + item_key: process.env.ITEM_KEY, + lease_revision: leaseRevision, + claim_generation: claimGeneration, + } : {}), + run_id: process.env.GITHUB_RUN_ID, + run_attempt: runAttempt, + outcome, + ...(published ? { + completion_kind: process.env.PUBLICATION_SUPERSEDED === "true" ? "superseded" : "published", + reason_code: process.env.PUBLICATION_SUPERSEDED === "true" ? "remote_newer_tuple" : "publication_applied", + } : {}), + ...(!published && process.env.REQUEUE_LATEST === "true" ? { requeue_latest: true } : {}), + ...(!published && retryKind ? { retry_kind: retryKind, retry_at: retryAt } : {}), + ...(published && process.env.DIRECT_REQUEUE === "true" ? { direct_lifecycle_requeue: true } : {}), + })); + ')" + curl --fail --silent --show-error --connect-timeout 5 --max-time 20 \ + --request POST \ + --header "content-type: application/json" \ + --data "$payload" \ + "$queue_url/internal/exact-review/complete" >/dev/null - name: Release unsuccessful workflow-owned review lease - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.live-item.outputs.proceed == 'true' && steps.reserve-exact-review-lease.outputs.status != 'held' && steps.prepare-direct-exact-review-publication.outputs.failure_kind != 'github_rate_limit' && steps.prepare-direct-exact-review-publication.outputs.failure_kind != 'github_transient' && steps.direct-exact-review-publication.outputs.accepted != 'true' && steps.queue-exact-review-publication.outcome != 'success' }} + if: ${{ always() && steps.select-final-review.outputs.publish != 'true' && steps.finalize-target-write-token.outcome == 'success' }} + continue-on-error: true env: - GH_TOKEN: ${{ steps.target-write-token.outputs.token }} - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} + GH_TOKEN: ${{ steps.finalize-target-write-token.outputs.token }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} LEASE_OWNER: github-run-${{ github.run_id }}-${{ github.run_attempt }} run: | set -euo pipefail @@ -1836,19 +2204,19 @@ jobs: done <<< "$reaction_ids" - name: Mark unsuccessful re-review - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.target.outputs.has_command_context == 'true' && steps.setup-pnpm.outcome == 'success' && steps.prepare-direct-exact-review-publication.outputs.failure_kind != 'github_rate_limit' && steps.prepare-direct-exact-review-publication.outputs.failure_kind != 'github_transient' && steps.direct-exact-review-publication.outputs.accepted != 'true' && steps.queue-exact-review-publication.outcome != 'success' && steps.target-write-token.outputs.token != '' }} + if: ${{ always() && steps.select-final-review.outputs.publish != 'true' && steps.finalize-target-write-token.outcome == 'success' }} continue-on-error: true env: - GH_TOKEN: ${{ steps.target-write-token.outputs.token }} - TARGET_REPO: ${{ steps.target.outputs.target_repo }} - ITEM_NUMBER: ${{ steps.target.outputs.item_number }} - COMMAND_STATUS_MARKER: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).commandStatusMarker || '' }} - STATUS_COMMENT_ID: ${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).statusCommentId || '' }} + GH_TOKEN: ${{ steps.finalize-target-write-token.outputs.token }} + TARGET_REPO: ${{ needs.event-review-apply.outputs.target_repo }} + ITEM_NUMBER: ${{ needs.event-review-apply.outputs.item_number }} + COMMAND_STATUS_MARKER: ${{ fromJSON(needs.event-review-apply.outputs.decision).commandStatusMarker || '' }} + STATUS_COMMENT_ID: ${{ fromJSON(needs.event-review-apply.outputs.decision).statusCommentId || '' }} RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - REVIEW_OUTCOME: ${{ steps.review-exact-event-item.outcome }} - REVIEW_SUPERSEDED: ${{ steps.review-exact-event-item.outputs.superseded || 'false' }} - RESERVATION_STATUS: ${{ steps.reserve-exact-review-lease.outputs.status }} - RETRY_AT: ${{ steps.reserve-exact-review-lease.outputs.retry_at || steps.review-exact-event-item.outputs.retry_at }} + REVIEW_OUTCOME: ${{ needs.event-review-apply.outputs.review_outcome }} + REVIEW_SUPERSEDED: ${{ needs.event-review-apply.outputs.review_superseded }} + RESERVATION_STATUS: ${{ needs.event-review-apply.outputs.reservation_status }} + RETRY_AT: ${{ needs.event-review-apply.outputs.retry_at }} CLAWSWEEPER_ACTION_LEDGER_DISABLED: "1" run: | state="Failed" @@ -1872,223 +2240,18 @@ jobs: --detail "$detail" \ --run-url "$RUN_URL" - - name: Export exact review generation result - id: exact-review-generation-result - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && always() }} - env: - ADMISSION_RETRY: ${{ steps.live-item.outputs.admission_retry }} - RETRY_KIND: ${{ steps.live-item.outputs.retry_kind || steps.reserve-exact-review-lease.outputs.retry_kind || steps.review-exact-event-item.outputs.retry_kind }} - RETRY_AT: ${{ steps.live-item.outputs.retry_at || steps.reserve-exact-review-lease.outputs.retry_at || steps.review-exact-event-item.outputs.retry_at }} - DIRECT_PUBLICATION_FAILURE_KIND: ${{ steps.prepare-direct-exact-review-publication.outputs.failure_kind }} - DIRECT_PUBLICATION_RETRY_AT: ${{ steps.prepare-direct-exact-review-publication.outputs.retry_at }} - TARGET_ENABLED: ${{ steps.target.outputs.target_enabled }} - LIVE_OUTCOME: ${{ steps.live-item.outcome }} - SCHEDULED_SEMANTIC_NOOP: ${{ steps.live-item.outputs.scheduled_semantic_noop || 'false' }} - REVIEW_OUTCOME: ${{ steps.review-exact-event-item.outcome }} - REVIEW_SUPERSEDED: ${{ steps.review-exact-event-item.outputs.superseded || 'false' }} - RESERVATION_STATUS: ${{ steps.reserve-exact-review-lease.outputs.status }} - PUBLICATION_QUEUE_OUTCOME: ${{ steps.queue-exact-review-publication.outcome }} - DIRECT_PUBLICATION_ACCEPTED: ${{ steps.direct-exact-review-publication.outputs.accepted }} - DIRECT_PUBLICATION_SUPERSEDED: ${{ steps.direct-exact-review-publication.outputs.superseded }} - DIRECT_LIFECYCLE_OUTCOME: ${{ steps.finalize-direct-exact-review-lifecycle.outcome }} - DIRECT_LIFECYCLE_REQUEUE: ${{ steps.finalize-direct-exact-review-lifecycle.outputs.direct_lifecycle_requeue || 'false' }} - run: | - outcome=failure - requeue_latest=false - retry_kind="$RETRY_KIND" - retry_at="$RETRY_AT" - if [ "$DIRECT_PUBLICATION_FAILURE_KIND" = "github_rate_limit" ] && [ "$PUBLICATION_QUEUE_OUTCOME" != "success" ]; then - test -n "$DIRECT_PUBLICATION_RETRY_AT" - retry_kind=throttle - retry_at="$DIRECT_PUBLICATION_RETRY_AT" - fi - if [ "$ADMISSION_RETRY" = "true" ] && [ -z "$retry_kind" ]; then - outcome=success - requeue_latest=true - elif [ "$TARGET_ENABLED" = "false" ]; then - outcome=success - elif [ "$SCHEDULED_SEMANTIC_NOOP" = "true" ] && [ "$LIVE_OUTCOME" = "success" ]; then - outcome=success - elif [ "$RESERVATION_STATUS" = "superseded" ] || [ "$REVIEW_SUPERSEDED" = "true" ]; then - outcome=success - elif [ "$REVIEW_OUTCOME" = "cancelled" ]; then - outcome=cancelled - elif [ "$LIVE_OUTCOME" = "success" ] && { \ - { [ "$DIRECT_PUBLICATION_ACCEPTED" = "true" ] && [ "$DIRECT_LIFECYCLE_OUTCOME" = "success" ]; } || \ - { [ "$DIRECT_PUBLICATION_ACCEPTED" != "true" ] && [ "$PUBLICATION_QUEUE_OUTCOME" = "success" ]; }; \ - }; then - outcome=success - fi - echo "outcome=$outcome" >> "$GITHUB_OUTPUT" - echo "requeue_latest=$requeue_latest" >> "$GITHUB_OUTPUT" - echo "direct_lifecycle_requeue=$DIRECT_LIFECYCLE_REQUEUE" >> "$GITHUB_OUTPUT" - echo "retry_kind=$retry_kind" >> "$GITHUB_OUTPUT" - echo "retry_at=$retry_at" >> "$GITHUB_OUTPUT" - - - name: Complete exact-review queue lease - id: complete-exact-review-queue - if: ${{ steps.claim-exact-review-queue.outputs.claimed == 'true' && always() && (steps.direct-exact-review-publication.outputs.accepted != 'true' || steps.finalize-direct-exact-review-lifecycle.outcome == 'success') }} - continue-on-error: true - env: - PRIMARY_OUTCOME: ${{ steps.exact-review-generation-result.outputs.outcome || 'failure' }} - CLAIM_GENERATION: ${{ steps.claim-exact-review-queue.outputs.claim_generation }} - ITEM_KEY: ${{ steps.claim-exact-review-queue.outputs.item_key }} - PROTOCOL_VERSION: ${{ steps.claim-exact-review-queue.outputs.protocol_version }} - QUEUE_LEASE_ID: ${{ steps.claim-exact-review-queue.outputs.lease_id }} - QUEUE_LEASE_REVISION: ${{ steps.claim-exact-review-queue.outputs.lease_revision }} - QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} - REQUEUE_LATEST: ${{ steps.exact-review-generation-result.outputs.requeue_latest }} - RETRY_KIND: ${{ steps.exact-review-generation-result.outputs.retry_kind }} - RETRY_AT: ${{ steps.exact-review-generation-result.outputs.retry_at }} - DIRECT_PUBLICATION_ACCEPTED: ${{ steps.direct-exact-review-publication.outputs.accepted }} - DIRECT_PUBLICATION_SUPERSEDED: ${{ steps.direct-exact-review-publication.outputs.superseded }} - DIRECT_LIFECYCLE_OUTCOME: ${{ steps.finalize-direct-exact-review-lifecycle.outcome }} - DIRECT_LIFECYCLE_REQUEUE: ${{ steps.exact-review-generation-result.outputs.direct_lifecycle_requeue }} - RUN_ATTEMPT: ${{ github.run_attempt }} - run: | - set -euo pipefail - test -n "$QUEUE_LEASE_ID" - queue_url="${QUEUE_URL%/}" - payload="$(node -e ' - const runAttempt = Number(process.env.RUN_ATTEMPT); - const protocolVersion = Number(process.env.PROTOCOL_VERSION); - const leaseRevision = Number(process.env.QUEUE_LEASE_REVISION); - const claimGeneration = Number(process.env.CLAIM_GENERATION); - if (!Number.isInteger(runAttempt) || runAttempt < 1) process.exit(1); - if (protocolVersion !== 1 && protocolVersion !== 2) process.exit(1); - if ( - protocolVersion === 2 && - (!process.env.ITEM_KEY || - !Number.isInteger(leaseRevision) || - leaseRevision < 1 || - !Number.isInteger(claimGeneration) || - claimGeneration < 1) - ) process.exit(1); - const primaryOutcome = String(process.env.PRIMARY_OUTCOME || ""); - const outcome = ["success", "cancelled", "failure"].includes(primaryOutcome) - ? primaryOutcome - : "failure"; - const requeueLatest = process.env.REQUEUE_LATEST === "true"; - const retryKind = String(process.env.RETRY_KIND || "").trim(); - const retryAt = String(process.env.RETRY_AT || "").trim(); - if (retryKind && !["coordination", "throttle"].includes(retryKind)) process.exit(1); - if (retryKind && !retryAt) process.exit(1); - const directPublicationCompleted = - process.env.DIRECT_PUBLICATION_ACCEPTED === "true" && - process.env.DIRECT_LIFECYCLE_OUTCOME === "success"; - const directPublicationSuperseded = - directPublicationCompleted && process.env.DIRECT_PUBLICATION_SUPERSEDED === "true"; - const directLifecycleRequeue = - directPublicationCompleted && process.env.DIRECT_LIFECYCLE_REQUEUE === "true"; - if (requeueLatest && directLifecycleRequeue) process.exit(1); - process.stdout.write(JSON.stringify({ - lease_id: process.env.QUEUE_LEASE_ID, - ...(protocolVersion === 2 - ? { - item_key: process.env.ITEM_KEY, - lease_revision: leaseRevision, - claim_generation: claimGeneration, - } - : {}), - run_id: process.env.GITHUB_RUN_ID, - run_attempt: runAttempt, - outcome, - ...(requeueLatest ? { requeue_latest: true } : {}), - ...(retryKind ? { retry_kind: retryKind } : {}), - ...(directPublicationCompleted - ? directPublicationSuperseded - ? { completion_kind: "superseded", reason_code: "remote_newer_tuple" } - : { completion_kind: "published", reason_code: "publication_applied" } - : {}), - ...(directLifecycleRequeue ? { direct_lifecycle_requeue: true } : {}), - ...(retryAt ? { retry_at: retryAt } : {}), - })); - ')" - response_file="$(mktemp)" - error_file="$(mktemp)" - trap 'rm -f "$response_file" "$error_file"' EXIT - for attempt in 1 2 3; do - : > "$response_file" - : > "$error_file" - if status="$(curl --silent --show-error --connect-timeout 5 --max-time 20 \ - --output "$response_file" \ - --write-out '%{http_code}' \ - --request POST \ - --header "content-type: application/json" \ - --data "$payload" \ - "$queue_url/internal/exact-review/complete" 2>"$error_file")"; then - response="$(<"$response_file")" - if [[ "$status" == 2* ]]; then - exit 0 - fi - # Only this completion-specific conflict is emitted after the - # durable queue verifies that this exact v2 lease tuple was fenced - # by a newer source revision. Every generic ownership conflict - # stays visible so a malformed or mismatched callback cannot turn - # a failed completion into a green workflow. - if [ "$status" = "409" ]; then - conflict_reason="$(RESPONSE="$response" node <<'NODE' - const response = JSON.parse(process.env.RESPONSE || "{}"); - const safeConflicts = new Set(["lease_superseded"]); - if (!safeConflicts.has(response.error)) process.exit(1); - process.stdout.write(response.error); - NODE - )" || { - echo "Unexpected exact-review completion conflict: $response" >&2 - exit 1 - } - echo "::notice::Exact-review completion skipped because its lease was superseded: $conflict_reason" - exit 0 - fi - echo "Exact-review completion returned HTTP $status: $response" >&2 - if [[ "$status" != 5* ]]; then - exit 1 - fi - else - cat "$error_file" >&2 - fi - if [ "$attempt" -lt 3 ]; then - sleep "$((attempt * 5))" - fi - done - exit 1 - - - name: Submit direct GitHub egress telemetry - if: ${{ always() && steps.claim-exact-review-queue.outputs.claimed == 'true' && steps.direct-github-egress-observer.outcome == 'success' }} + - name: Submit final-publication GitHub egress telemetry + if: ${{ always() && steps.final-github-egress-observer.outcome == 'success' }} continue-on-error: true env: EXACT_REVIEW_QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} run: pnpm run --silent repair:github-egress-telemetry submit - - name: Fail unsuccessful exact review generation - # A held review lease is a successful deferral only after the durable queue - # accepts retry ownership; queue completion failures must remain visible. - if: >- - ${{ - always() && - steps.claim-exact-review-queue.outputs.claimed == 'true' && - ( - ( - steps.direct-exact-review-publication.outputs.accepted != 'true' && - steps.complete-exact-review-queue.outcome != 'success' && - steps.reserve-exact-review-lease.outputs.status != 'superseded' && - steps.review-exact-event-item.outputs.superseded != 'true' - ) || - ( - steps.exact-review-generation-result.outputs.outcome != 'success' && - steps.exact-review-generation-result.outputs.retry_kind == '' && - steps.reserve-exact-review-lease.outputs.status != 'held' && - steps.reserve-exact-review-lease.outputs.status != 'superseded' - ) - ) - }} - env: - RESERVATION_STATUS: ${{ steps.reserve-exact-review-lease.outputs.status || 'unknown' }} - REVIEW_EXIT_CODE: ${{ steps.review-exact-event-item.outputs.exit_code || 'unknown' }} - REVIEW_OUTCOME: ${{ steps.review-exact-event-item.outcome || 'not_started' }} + - name: Fail exact review finalization that did not publish or requeue + if: ${{ always() && (steps.complete-final-exact-review.outcome != 'success' || (needs.event-review-apply.outputs.core_artifact_id != '' && steps.select-final-review.outputs.publish != 'true') || (steps.select-final-review.outputs.publish == 'true' && steps.finalize-exact-review-lifecycle.outcome != 'success')) }} run: | - echo "::error::Exact review generation failed: classification=codex_or_content_failure reservation=$RESERVATION_STATUS review_outcome=$REVIEW_OUTCOME review_exit=$REVIEW_EXIT_CODE" + echo "::error::Exact review finalization did not reach a durable publication or queue completion." exit 1 event-review-publish: diff --git a/docs/live-proof.md b/docs/live-proof.md index e68312dff7..9dbf85acab 100644 --- a/docs/live-proof.md +++ b/docs/live-proof.md @@ -11,10 +11,20 @@ Live proof turns a review-time `liveProofPlan` into deterministic browser or terminal execution, with an optional recording when the behavior is worth -watching. Classification and execution now happen in the same review job. The -review first writes its decision artifact, then immediately executes the typed -plan against the exact `pull_head_sha` recorded in that artifact. There is no -separate dispatch, public PR-head lookup, second hydration, or live-head check. +watching. Event reviews use three jobs in one workflow run: + +1. `event-review-apply` writes and uploads an immutable core review bundle. +2. `event-review-live-proof` downloads that bundle by artifact ID, validates its + digest and exact `pull_head_sha`, fetches that public PR head anonymously, + executes the typed plan on hardcoded `ubuntu-latest`, and uploads a separate + augmentation. +3. `event-review-finalize` validates both artifacts in a clean trusted job, + merges valid proof, and is the sole publisher and queue-completion owner. + +The live-proof job has only `actions: read` and `contents: read`; it receives no +ClawSweeper or target-repository credentials. It also removes the four GitHub +workflow command-file variables before target execution, so untrusted target +code cannot mutate later workflow environment, output, path, or summary state. The planner gates execution in order: the repository must opt in with `live_test.enabled`, the item must be a pull request, and the plan must be @@ -32,15 +42,18 @@ available before target setup. Installer failures become a failed path; they do not fail the review itself. Reviews that do not verify never probe or install a target package manager. -## Review-job execution +## Same-run isolated execution + +After the review command returns, the apply job inspects the produced report and +seals the report, queue tuple, target identity, exact PR head, and hashed file +inventory into the core artifact. It does not execute target code. The +`CLAWSWEEPER_REVIEW_RUNNER` override applies only to review generation; live +proof always runs on GitHub-hosted `ubuntu-latest`. -After the review command returns, the job inspects the produced reports before -installing tools. tmux is installed only when a terminal candidate exists. The -recording toolchain (`ffmpeg`, Xvfb, xterm, and related X11 tools) is installed -only when at least one recommended plan has a non-`static_text` payoff. Review -job timeouts include the target installation and deterministic drive. Review -jobs default to `ubuntu-latest`; `CLAWSWEEPER_REVIEW_RUNNER` remains an optional -runner override. +The secretless live-proof job reinspects the sealed report before installing +tools. tmux is installed only when a terminal candidate exists. The recording +toolchain (`ffmpeg`, Xvfb, xterm, and related X11 tools) is installed only when +the recommended plan has a non-`static_text` payoff. For every candidate, trusted ClawSweeper code materializes the report's exact head SHA into a scratch worktree, then invokes the existing `live-proof` @@ -64,12 +77,12 @@ three controls: diff. A repository may opt in only with the explicit `live_test.allow_install_scripts: true` flag. No current repository opts in. -Untrusted target code therefore runs unsandboxed in a credentialed review job. -Environment sanitization reduces what the direct child inherits, but it is not -a kernel security boundary and does not make a suspicious plan safe. Linux -user/mount/PID/network containment remains a future hardening step; it is not a -runner requirement today. The repair lane's separate containment remains in use -and is unaffected by this live-proof policy. +Untrusted target code therefore runs unsandboxed in a disposable, secretless +GitHub-hosted job. Environment sanitization is still not a kernel security +boundary and does not make a suspicious plan safe. The separate trusted +finalizer starts from a fresh checkout and only accepts a bounded augmentation +whose workflow identity, core artifact ID and digest, core manifest digest, +target, item, and exact PR head all match trusted job outputs. HOME, package-manager caches, and temporary files point into the scratch profile. @@ -103,17 +116,24 @@ plan acted, and the recording passes the three-second floor. Eligible recordings are capped at 90 seconds and 50 MB, transcoded to H.264 MP4, probed, and paired with `poster.jpg` plus a metadata-only manifest. -## Existing artifact and publication path +## Artifacts and publication -The review artifact contains its report plus `live-proof//` with the -verification result and, when eligible, the manifest, MP4, and poster. The exact -review bundle binds those files into its existing hashed inventory. No second -live-proof artifact is uploaded. +The immutable core artifact contains the review report and action ledger. The +separate live-proof augmentation contains `live-proof//` with the +verification result and, when eligible, the manifest, MP4, and poster. Both +artifacts have bounded, sorted, digest-checked inventories and reject symlinks, +unknown paths, duplicate paths, or unknown manifest fields. -The existing publication jobs download and validate the review artifact. Before -their normal record mutation, they fold each verification result into the review -report. If media exists, publication re-probes it and uploads it with its own R2 -credentials to: +The trusted finalizer publishes a valid PASS or FAIL verification. If live +execution succeeds but owned scratch cleanup fails, or the fully sealed +GitHub-hosted job later fails during runner post-job cleanup, the finalizer +publishes the verified core report without attaching proof. Any missing, +malformed, mismatched, or otherwise failed augmentation causes queue retry +instead of publication. + +Before normal record mutation, the finalizer folds a valid verification result +into the review report. If media exists, it re-probes the files and uploads them +with its own R2 credentials to: ```text live-proof////live-proof.mp4 @@ -126,6 +146,10 @@ repository, item, type, and `pull_head_sha`, but it does not query GitHub for a new head. The normal record publisher then writes the canonical record and the existing comment-sync path upserts the marker-backed review comment. +Queued batch publication still executes live proof inside its existing job. It +does not yet share the event-review three-job isolation contract; migrating that +lane is the immediate follow-up. + Browser comments contain sanitized per-step outcomes and a one-line failing-step reason, never page text. Terminal comments retain capped output and list assertions only when present. All untrusted fields are bounded and neutralized diff --git a/package.json b/package.json index 81dcffbd85..eb9e42a412 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "repair:spam-comment-intake": "node dist/repair/spam-comment-intake.js", "repair:spam-scan": "node dist/repair/spam-scanner.js", "repair:exact-review-bundle": "node dist/repair/exact-review-bundle-cli.js", + "repair:review-live-proof-augmentation": "node dist/live-proof/review-augmentation-cli.js", "repair:exact-review-queue-maintenance": "node dist/repair/exact-review-queue-maintenance.js", "repair:scheduled-review-enqueue": "node dist/repair/scheduled-review-enqueue.js", "repair:exact-review-dead-letters": "node scripts/exact-review-dead-letter-operator.mjs", diff --git a/src/live-proof/review-artifacts.ts b/src/live-proof/review-artifacts.ts index 749bccb9e2..a6c9e9d400 100644 --- a/src/live-proof/review-artifacts.ts +++ b/src/live-proof/review-artifacts.ts @@ -1,5 +1,13 @@ import { spawnSync } from "node:child_process"; -import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -18,6 +26,7 @@ const PUBLIC_BUNDLE_FILES = [ "live-proof.mp4", "poster.jpg", ] as const; +export const REVIEW_LIVE_PROOF_CLEANUP_SCHEMA_VERSION = 1 as const; export interface ReviewLiveProofInspection { candidates: number[]; @@ -104,6 +113,7 @@ function executeReviewLiveProof( mkdirSync(profile, { recursive: true }); mkdirSync(temporaryBundle, { recursive: true }); copyFileSync(recordPath, copiedRecordPath); + let primaryError: unknown; try { if ( !materializePullRequestReviewTree({ @@ -186,11 +196,70 @@ function executeReviewLiveProof( } log(assertion); log(`[live-proof] item=${item} head=${headSha} execution=unsandboxed credentials=0`); - } finally { + } catch (error) { + primaryError = error; + } + + const cleanupErrors: Array<{ operation: "remove_worktree" | "remove_scratch"; error: unknown }> = + []; + try { removePullRequestReviewTree({ targetDir: resolve(options.checkoutPath), worktreeDir: worktree, }); + } catch (error) { + cleanupErrors.push({ operation: "remove_worktree", error }); + } + try { rmSync(scratch, { force: true, recursive: true }); + } catch (error) { + cleanupErrors.push({ operation: "remove_scratch", error }); } + if (cleanupErrors.length > 0) { + log( + `[live-proof] item=${item} cleanup=failed operations=${cleanupErrors + .map(({ operation }) => operation) + .join(",")}`, + ); + if (primaryError === undefined) { + const cleanupRoot = join(resolve(options.outputRoot), ".cleanup-failures"); + mkdirSync(cleanupRoot, { recursive: true }); + writeFileSync( + join(cleanupRoot, `${item}.json`), + `${JSON.stringify( + { + schema_version: REVIEW_LIVE_PROOF_CLEANUP_SCHEMA_VERSION, + item, + head_sha: headSha, + proof_output_present: existsSync(join(publishedBundle, "live-verification.json")), + failures: cleanupErrors.map(({ operation, error }) => ({ + operation, + error_code: cleanupErrorCode(error), + })), + }, + null, + 2, + )}\n`, + "utf8", + ); + } + throw new AggregateError( + [ + ...(primaryError === undefined ? [] : [primaryError]), + ...cleanupErrors.map(({ error }) => error), + ], + primaryError === undefined + ? "live proof succeeded but owned cleanup failed" + : "live proof and owned cleanup both failed", + ); + } + log(`[live-proof] item=${item} cleanup=removed worktree,scratch`); + if (primaryError !== undefined) throw primaryError; +} + +function cleanupErrorCode(error: unknown): string { + const raw = + error && typeof error === "object" && "code" in error ? (error as { code?: unknown }).code : ""; + const value = typeof raw === "string" ? raw : ""; + return /^[A-Z0-9_]{1,40}$/.test(value) ? value : "UNKNOWN"; } diff --git a/src/live-proof/review-augmentation-cli.ts b/src/live-proof/review-augmentation-cli.ts new file mode 100644 index 0000000000..4c844c7189 --- /dev/null +++ b/src/live-proof/review-augmentation-cli.ts @@ -0,0 +1,109 @@ +#!/usr/bin/env node +import { + createReviewLiveProofAugmentation, + materializeReviewLiveProofAugmentationArchive, + mergeReviewLiveProofAugmentation, + validateReviewLiveProofAugmentation, + type ReviewLiveProofAugmentationContext, +} from "./review-augmentation.js"; + +function main(): void { + const [command] = process.argv.slice(2); + if ( + command !== "create" && + command !== "materialize" && + command !== "validate" && + command !== "merge" + ) { + throw new Error("usage: review-augmentation-cli.ts "); + } + + const env = process.env; + if (command === "materialize") { + materializeReviewLiveProofAugmentationArchive({ + archivePath: requiredEnv(env, "REVIEW_LIVE_PROOF_ARCHIVE"), + destinationDir: requiredEnv(env, "REVIEW_LIVE_PROOF_AUGMENTATION_DIR"), + itemNumber: positiveIntegerEnv(env, "REVIEW_LIVE_PROOF_ITEM_NUMBER"), + }); + process.stdout.write('{"materialized":true}\n'); + return; + } + const context = contextFromEnv(env); + const augmentationDir = requiredEnv(env, "REVIEW_LIVE_PROOF_AUGMENTATION_DIR"); + const coreManifestPath = requiredEnv(env, "REVIEW_LIVE_PROOF_CORE_MANIFEST"); + if (command === "create") { + const manifest = createReviewLiveProofAugmentation({ + augmentationDir, + cleanupFailurePath: optionalEnv(env, "REVIEW_LIVE_PROOF_CLEANUP_FAILURE"), + coreManifestPath, + proofDir: requiredEnv(env, "REVIEW_LIVE_PROOF_PROOF_DIR"), + createdAt: new Date().toISOString(), + context, + }); + process.stdout.write(`${JSON.stringify(manifest)}\n`); + } else { + const manifest = validateReviewLiveProofAugmentation( + augmentationDir, + coreManifestPath, + context, + ); + if (command === "merge") { + mergeReviewLiveProofAugmentation( + augmentationDir, + requiredEnv(env, "REVIEW_LIVE_PROOF_DESTINATION_DIR"), + manifest, + ); + } + process.stdout.write(`${JSON.stringify(manifest)}\n`); + } +} + +main(); + +function contextFromEnv(env: NodeJS.ProcessEnv): ReviewLiveProofAugmentationContext { + return { + repository: requiredEnv(env, "GITHUB_REPOSITORY"), + sourceSha: requiredShaEnv(env, "REVIEW_LIVE_PROOF_SOURCE_SHA"), + runId: requiredEnv(env, "GITHUB_RUN_ID"), + runAttempt: positiveIntegerEnv(env, "GITHUB_RUN_ATTEMPT"), + producerJob: "event-review-live-proof", + runnerEnvironment: requiredGithubRunnerEnvironment(env), + coreArtifactId: requiredEnv(env, "REVIEW_LIVE_PROOF_CORE_ARTIFACT_ID"), + coreArtifactDigest: requiredEnv(env, "REVIEW_LIVE_PROOF_CORE_ARTIFACT_DIGEST"), + targetRepo: requiredEnv(env, "REVIEW_LIVE_PROOF_TARGET_REPO"), + itemNumber: positiveIntegerEnv(env, "REVIEW_LIVE_PROOF_ITEM_NUMBER"), + pullHeadSha: requiredShaEnv(env, "REVIEW_LIVE_PROOF_PULL_HEAD_SHA"), + }; +} + +function requiredGithubRunnerEnvironment( + env: NodeJS.ProcessEnv, +): ReviewLiveProofAugmentationContext["runnerEnvironment"] { + const value = requiredEnv(env, "REVIEW_LIVE_PROOF_RUNNER_ENVIRONMENT"); + if (value !== "github-hosted") { + throw new Error("REVIEW_LIVE_PROOF_RUNNER_ENVIRONMENT must be github-hosted"); + } + return value; +} + +function requiredShaEnv(env: NodeJS.ProcessEnv, name: string): string { + const value = requiredEnv(env, name).toLowerCase(); + if (!/^[0-9a-f]{40}$/.test(value)) throw new Error(`${name} must be a full commit SHA`); + return value; +} + +function positiveIntegerEnv(env: NodeJS.ProcessEnv, name: string): number { + const value = Number(requiredEnv(env, name)); + if (!Number.isInteger(value) || value < 1) throw new Error(`${name} must be a positive integer`); + return value; +} + +function requiredEnv(env: NodeJS.ProcessEnv, name: string): string { + const value = optionalEnv(env, name); + if (!value) throw new Error(`${name} is required`); + return value; +} + +function optionalEnv(env: NodeJS.ProcessEnv, name: string): string { + return String(env[name] ?? "").trim(); +} diff --git a/src/live-proof/review-augmentation.ts b/src/live-proof/review-augmentation.ts new file mode 100644 index 0000000000..211f5cd92b --- /dev/null +++ b/src/live-proof/review-augmentation.ts @@ -0,0 +1,836 @@ +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { inflateRawSync } from "node:zlib"; + +import { parseLiveProofManifest } from "./manifest.js"; +import { REVIEW_LIVE_PROOF_CLEANUP_SCHEMA_VERSION } from "./review-artifacts.js"; +import { parseLiveVerificationResult } from "./verification.js"; + +export const REVIEW_LIVE_PROOF_AUGMENTATION_SCHEMA_VERSION = 1 as const; + +const SHA_PATTERN = /^[0-9a-f]{40}$/; +const SHA256_PATTERN = /^[0-9a-f]{64}$/; +const ARTIFACT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/; +const REPO_PATTERN = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/; +const FILE_PATTERN = + /^live-proof\/[1-9]\d*\/(?:live-verification\.json|live-proof-manifest\.json|live-proof\.mp4|poster\.jpg)$/; +const MAX_FILES = 4; +const MAX_TOTAL_BYTES = 64 * 1024 * 1024; +const MAX_ARCHIVE_BYTES = MAX_TOTAL_BYTES + 1024 * 1024; +const ZIP_END_SIGNATURE = 0x06054b50; +const ZIP_CENTRAL_SIGNATURE = 0x02014b50; +const ZIP_LOCAL_SIGNATURE = 0x04034b50; +const ZIP_DESCRIPTOR_SIGNATURE = 0x08074b50; +const ZIP_FLAG_DATA_DESCRIPTOR = 1 << 3; +const ZIP_FLAG_UTF8 = 1 << 11; +const ZIP_ALLOWED_FLAGS = ZIP_FLAG_DATA_DESCRIPTOR | ZIP_FLAG_UTF8; +const ZIP_UNIX_REGULAR_FILE = 0o100000; +const ZIP_UNIX_TYPE_MASK = 0o170000; + +interface ValidatedZipEntry { + name: string; + data: Buffer; +} + +export interface ReviewLiveProofAugmentationContext { + repository: string; + sourceSha: string; + runId: string; + runAttempt: number; + producerJob: "event-review-live-proof"; + runnerEnvironment: "github-hosted"; + coreArtifactId: string; + coreArtifactDigest: string; + targetRepo: string; + itemNumber: number; + pullHeadSha: string; +} + +export interface ReviewLiveProofAugmentationFile { + path: string; + bytes: number; + sha256: string; +} + +export interface ReviewLiveProofAugmentationManifest { + schema_version: typeof REVIEW_LIVE_PROOF_AUGMENTATION_SCHEMA_VERSION; + created_at: string; + producer: { + repository: string; + source_sha: string; + run_id: string; + run_attempt: number; + job: "event-review-live-proof"; + runner_environment: "github-hosted"; + }; + core: { + artifact_id: string; + artifact_digest: string; + manifest_sha256: string; + }; + target: { + repo: string; + item_number: number; + pull_head_sha: string; + }; + result: + | { kind: "proof"; overall_pass: boolean } + | { kind: "cleanup_only_failure"; proof_output_present: true }; + files: ReviewLiveProofAugmentationFile[]; +} + +export function materializeReviewLiveProofAugmentationArchive(options: { + archivePath: string; + destinationDir: string; + itemNumber: number; +}): void { + if (!Number.isInteger(options.itemNumber) || options.itemNumber < 1) { + throw new Error("live-proof augmentation item number is invalid"); + } + const archive = readBoundedArchive(options.archivePath); + const allowedEntries = new Set([ + "manifest.json", + `live-proof/${options.itemNumber}/live-verification.json`, + `live-proof/${options.itemNumber}/live-proof-manifest.json`, + `live-proof/${options.itemNumber}/live-proof.mp4`, + `live-proof/${options.itemNumber}/poster.jpg`, + ]); + const entries = validateZipArchive(archive, allowedEntries); + const destinationDir = path.resolve(options.destinationDir); + const parentDir = path.dirname(destinationDir); + fs.mkdirSync(parentDir, { recursive: true }); + const temporaryDir = fs.mkdtempSync(path.join(parentDir, ".live-proof-augmentation-")); + try { + for (const entry of entries) { + const destination = path.join(temporaryDir, entry.name); + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.writeFileSync(destination, entry.data, { flag: "wx", mode: 0o600 }); + } + fs.rmSync(destinationDir, { force: true, recursive: true }); + fs.renameSync(temporaryDir, destinationDir); + } catch (error) { + fs.rmSync(temporaryDir, { force: true, recursive: true }); + throw error; + } +} + +export function createReviewLiveProofAugmentation(options: { + augmentationDir: string; + cleanupFailurePath?: string; + coreManifestPath: string; + proofDir: string; + createdAt: string; + context: ReviewLiveProofAugmentationContext; +}): ReviewLiveProofAugmentationManifest { + const context = validateContext(options.context); + const augmentationDir = path.resolve(options.augmentationDir); + fs.rmSync(augmentationDir, { force: true, recursive: true }); + fs.mkdirSync(augmentationDir, { recursive: true }); + const coreManifestSha256 = regularFileSha256(options.coreManifestPath, 2 * 1024 * 1024); + + let result: ReviewLiveProofAugmentationManifest["result"]; + if (options.cleanupFailurePath && fs.existsSync(options.cleanupFailurePath)) { + const cleanup = parseCleanupFailure(options.cleanupFailurePath); + if ( + cleanup.item !== context.itemNumber || + cleanup.head_sha !== context.pullHeadSha || + cleanup.proof_output_present !== true + ) { + throw new Error("live-proof cleanup failure does not match the trusted target"); + } + result = { kind: "cleanup_only_failure", proof_output_present: true }; + } else { + const destination = path.join(augmentationDir, "live-proof", String(context.itemNumber)); + const verification = copyAndValidateProof(options.proofDir, destination, context); + result = { kind: "proof", overall_pass: verification.overall_pass }; + } + + const manifest = validateManifest({ + schema_version: REVIEW_LIVE_PROOF_AUGMENTATION_SCHEMA_VERSION, + created_at: canonicalTimestamp(options.createdAt), + producer: { + repository: context.repository, + source_sha: context.sourceSha, + run_id: context.runId, + run_attempt: context.runAttempt, + job: context.producerJob, + runner_environment: context.runnerEnvironment, + }, + core: { + artifact_id: context.coreArtifactId, + artifact_digest: context.coreArtifactDigest, + manifest_sha256: coreManifestSha256, + }, + target: { + repo: context.targetRepo, + item_number: context.itemNumber, + pull_head_sha: context.pullHeadSha, + }, + result, + files: collectFiles(augmentationDir), + }); + fs.writeFileSync( + path.join(augmentationDir, "manifest.json"), + `${JSON.stringify(manifest, null, 2)}\n`, + { encoding: "utf8", flag: "wx" }, + ); + return manifest; +} + +export function validateReviewLiveProofAugmentation( + augmentationDirInput: string, + coreManifestPath: string, + expected: ReviewLiveProofAugmentationContext, +): ReviewLiveProofAugmentationManifest { + const augmentationDir = path.resolve(augmentationDirInput); + const manifestPath = path.join(augmentationDir, "manifest.json"); + const manifest = validateManifest( + JSON.parse(readRegularFile(manifestPath, 2 * 1024 * 1024)) as unknown, + ); + const context = validateContext(expected); + const actual = { + repository: manifest.producer.repository, + sourceSha: manifest.producer.source_sha, + runId: manifest.producer.run_id, + runAttempt: manifest.producer.run_attempt, + producerJob: manifest.producer.job, + runnerEnvironment: manifest.producer.runner_environment, + coreArtifactId: manifest.core.artifact_id, + coreArtifactDigest: manifest.core.artifact_digest, + targetRepo: manifest.target.repo, + itemNumber: manifest.target.item_number, + pullHeadSha: manifest.target.pull_head_sha, + } satisfies ReviewLiveProofAugmentationContext; + if (JSON.stringify(actual) !== JSON.stringify(context)) { + throw new Error("live-proof augmentation does not match the trusted workflow context"); + } + if (manifest.core.manifest_sha256 !== regularFileSha256(coreManifestPath, 2 * 1024 * 1024)) { + throw new Error("live-proof augmentation does not match the immutable core manifest"); + } + if (JSON.stringify(collectFiles(augmentationDir)) !== JSON.stringify(manifest.files)) { + throw new Error("live-proof augmentation file inventory does not match its manifest"); + } + return manifest; +} + +export function mergeReviewLiveProofAugmentation( + augmentationDirInput: string, + destinationDirInput: string, + manifest: ReviewLiveProofAugmentationManifest, +): void { + if (manifest.result.kind !== "proof") { + throw new Error("cleanup-only augmentation must not be merged into the core review"); + } + const augmentationDir = path.resolve(augmentationDirInput); + const destinationDir = path.resolve(destinationDirInput); + for (const file of manifest.files) { + const source = path.join(augmentationDir, file.path); + const destination = path.join(destinationDir, file.path); + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL); + } +} + +function copyAndValidateProof( + sourceDirInput: string, + destinationDir: string, + context: ReviewLiveProofAugmentationContext, +) { + const sourceDir = path.resolve(sourceDirInput); + const verificationPath = path.join(sourceDir, "live-verification.json"); + const verification = parseLiveVerificationResult( + JSON.parse(readRegularFile(verificationPath, 2 * 1024 * 1024)) as unknown, + ); + if ( + verification.repo !== context.targetRepo || + verification.item !== context.itemNumber || + verification.head_sha !== context.pullHeadSha + ) { + throw new Error("live verification result does not match the trusted target"); + } + const manifestPath = path.join(sourceDir, "live-proof-manifest.json"); + const hasManifest = fs.existsSync(manifestPath); + const hasVideo = fs.existsSync(path.join(sourceDir, "live-proof.mp4")); + const hasPoster = fs.existsSync(path.join(sourceDir, "poster.jpg")); + if (hasManifest !== (hasVideo && hasPoster)) { + throw new Error("live-proof media and manifest must be complete"); + } + if (hasManifest) { + const media = parseLiveProofManifest( + JSON.parse(readRegularFile(manifestPath, 2 * 1024 * 1024)) as unknown, + ); + if ( + media.repo !== verification.repo || + media.item !== verification.item || + media.head_sha !== verification.head_sha || + media.surface !== verification.surface || + media.drive_status !== verification.drive_status + ) { + throw new Error("live-proof media manifest does not match verification"); + } + } + for (const name of [ + "live-verification.json", + "live-proof-manifest.json", + "live-proof.mp4", + "poster.jpg", + ]) { + const source = path.join(sourceDir, name); + if (!fs.existsSync(source)) continue; + const stat = fs.lstatSync(source); + if (!stat.isFile() || stat.isSymbolicLink()) { + throw new Error("live-proof augmentation source must contain regular files"); + } + fs.mkdirSync(destinationDir, { recursive: true }); + fs.copyFileSync(source, path.join(destinationDir, name), fs.constants.COPYFILE_EXCL); + } + return verification; +} + +function parseCleanupFailure(file: string): { + item: number; + head_sha: string; + proof_output_present: boolean; +} { + const value = JSON.parse(readRegularFile(file, 16 * 1024)) as Record; + const keys = Object.keys(value).sort(); + if ( + JSON.stringify(keys) !== + JSON.stringify( + ["failures", "head_sha", "item", "proof_output_present", "schema_version"].sort(), + ) || + value.schema_version !== REVIEW_LIVE_PROOF_CLEANUP_SCHEMA_VERSION || + !Number.isInteger(value.item) || + !SHA_PATTERN.test(String(value.head_sha)) || + typeof value.proof_output_present !== "boolean" || + !Array.isArray(value.failures) || + value.failures.length < 1 || + value.failures.length > 2 + ) { + throw new Error("live-proof cleanup failure marker is invalid"); + } + for (const failure of value.failures) { + if ( + !failure || + typeof failure !== "object" || + Array.isArray(failure) || + !["remove_worktree", "remove_scratch"].includes( + String((failure as Record).operation), + ) || + !/^[A-Z0-9_]{1,40}$/.test(String((failure as Record).error_code)) + ) { + throw new Error("live-proof cleanup failure marker is invalid"); + } + } + return { + item: Number(value.item), + head_sha: String(value.head_sha), + proof_output_present: value.proof_output_present, + }; +} + +function validateContext( + value: ReviewLiveProofAugmentationContext, +): ReviewLiveProofAugmentationContext { + if (!REPO_PATTERN.test(value.repository) || !REPO_PATTERN.test(value.targetRepo)) { + throw new Error("live-proof augmentation repository is invalid"); + } + if (!SHA_PATTERN.test(value.sourceSha) || !SHA_PATTERN.test(value.pullHeadSha)) { + throw new Error("live-proof augmentation SHA is invalid"); + } + if ( + !/^\d{1,30}$/.test(value.runId) || + !Number.isInteger(value.runAttempt) || + value.runAttempt < 1 + ) { + throw new Error("live-proof augmentation run identity is invalid"); + } + if ( + value.producerJob !== "event-review-live-proof" || + value.runnerEnvironment !== "github-hosted" + ) { + throw new Error("live-proof augmentation producer is invalid"); + } + if (!/^[1-9]\d*$/.test(value.coreArtifactId)) { + throw new Error("live-proof core artifact ID is invalid"); + } + if (!ARTIFACT_DIGEST_PATTERN.test(value.coreArtifactDigest)) { + throw new Error("live-proof core artifact digest is invalid"); + } + if (!Number.isInteger(value.itemNumber) || value.itemNumber < 1) { + throw new Error("live-proof augmentation item number is invalid"); + } + return { ...value }; +} + +function validateManifest(value: unknown): ReviewLiveProofAugmentationManifest { + const manifest = record(value, "manifest"); + exactKeys(manifest, [ + "schema_version", + "created_at", + "producer", + "core", + "target", + "result", + "files", + ]); + if (manifest.schema_version !== REVIEW_LIVE_PROOF_AUGMENTATION_SCHEMA_VERSION) { + throw new Error("unsupported live-proof augmentation schema"); + } + const createdAt = canonicalTimestamp(stringValue(manifest.created_at, "created_at")); + const producer = record(manifest.producer, "producer"); + exactKeys(producer, [ + "repository", + "source_sha", + "run_id", + "run_attempt", + "job", + "runner_environment", + ]); + const core = record(manifest.core, "core"); + exactKeys(core, ["artifact_id", "artifact_digest", "manifest_sha256"]); + const target = record(manifest.target, "target"); + exactKeys(target, ["repo", "item_number", "pull_head_sha"]); + const result = record(manifest.result, "result"); + if (result.kind === "proof") { + exactKeys(result, ["kind", "overall_pass"]); + } else if (result.kind === "cleanup_only_failure") { + exactKeys(result, ["kind", "proof_output_present"]); + } else { + throw new Error("live-proof augmentation result is invalid"); + } + const context = validateContext({ + repository: stringValue(producer.repository, "producer.repository"), + sourceSha: stringValue(producer.source_sha, "producer.source_sha"), + runId: stringValue(producer.run_id, "producer.run_id"), + runAttempt: numberValue(producer.run_attempt, "producer.run_attempt"), + producerJob: producer.job as "event-review-live-proof", + runnerEnvironment: producer.runner_environment as "github-hosted", + coreArtifactId: stringValue(core.artifact_id, "core.artifact_id"), + coreArtifactDigest: stringValue(core.artifact_digest, "core.artifact_digest"), + targetRepo: stringValue(target.repo, "target.repo"), + itemNumber: numberValue(target.item_number, "target.item_number"), + pullHeadSha: stringValue(target.pull_head_sha, "target.pull_head_sha"), + }); + const coreManifestSha256 = stringValue(core.manifest_sha256, "core.manifest_sha256"); + if (!SHA256_PATTERN.test(coreManifestSha256)) { + throw new Error("live-proof core manifest digest is invalid"); + } + if ( + (result.kind === "proof" && typeof result.overall_pass !== "boolean") || + (result.kind === "cleanup_only_failure" && result.proof_output_present !== true) + ) { + throw new Error("live-proof augmentation result is invalid"); + } + if (!Array.isArray(manifest.files) || manifest.files.length > MAX_FILES) { + throw new Error("live-proof augmentation file inventory is invalid"); + } + let totalBytes = 0; + const files = manifest.files.map((entry, index) => { + const file = record(entry, `files[${index}]`); + exactKeys(file, ["path", "bytes", "sha256"]); + const filePath = stringValue(file.path, `files[${index}].path`); + const bytes = numberValue(file.bytes, `files[${index}].bytes`); + const digest = stringValue(file.sha256, `files[${index}].sha256`); + if ( + !FILE_PATTERN.test(filePath) || + !Number.isInteger(bytes) || + bytes < 0 || + !SHA256_PATTERN.test(digest) + ) { + throw new Error("live-proof augmentation file inventory is invalid"); + } + totalBytes += bytes; + return { path: filePath, bytes, sha256: digest }; + }); + if (totalBytes > MAX_TOTAL_BYTES) { + throw new Error("live-proof augmentation exceeds its byte limit"); + } + const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path)); + if (JSON.stringify(sorted) !== JSON.stringify(files)) { + throw new Error("live-proof augmentation files must be sorted"); + } + if (new Set(files.map((file) => file.path)).size !== files.length) { + throw new Error("live-proof augmentation contains duplicate file paths"); + } + if (result.kind === "cleanup_only_failure" && files.length !== 0) { + throw new Error("cleanup-only augmentation must not contain proof files"); + } + if ( + result.kind === "proof" && + !files.some((file) => file.path.endsWith("/live-verification.json")) + ) { + throw new Error("proof augmentation is missing live-verification.json"); + } + return { + schema_version: REVIEW_LIVE_PROOF_AUGMENTATION_SCHEMA_VERSION, + created_at: createdAt, + producer: { + repository: context.repository, + source_sha: context.sourceSha, + run_id: context.runId, + run_attempt: context.runAttempt, + job: context.producerJob, + runner_environment: context.runnerEnvironment, + }, + core: { + artifact_id: context.coreArtifactId, + artifact_digest: context.coreArtifactDigest, + manifest_sha256: coreManifestSha256, + }, + target: { + repo: context.targetRepo, + item_number: context.itemNumber, + pull_head_sha: context.pullHeadSha, + }, + result: + result.kind === "proof" + ? { kind: "proof", overall_pass: result.overall_pass as boolean } + : { kind: "cleanup_only_failure", proof_output_present: true }, + files, + }; +} + +function collectFiles(root: string): ReviewLiveProofAugmentationFile[] { + const files: ReviewLiveProofAugmentationFile[] = []; + const visit = (directory: string) => { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const absolute = path.join(directory, entry.name); + const relative = path.relative(root, absolute).split(path.sep).join("/"); + if (relative === "manifest.json") continue; + if (entry.isSymbolicLink()) + throw new Error("live-proof augmentation must not contain symlinks"); + if (entry.isDirectory()) visit(absolute); + else if (entry.isFile()) { + if (!FILE_PATTERN.test(relative)) { + throw new Error(`live-proof augmentation contains an unexpected path: ${relative}`); + } + const bytes = fs.statSync(absolute).size; + files.push({ path: relative, bytes, sha256: sha256(fs.readFileSync(absolute)) }); + } else { + throw new Error("live-proof augmentation contains a non-file entry"); + } + } + }; + visit(root); + return files.sort((left, right) => left.path.localeCompare(right.path)); +} + +function readBoundedArchive(file: string): Buffer { + const stat = fs.lstatSync(file); + if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 22 || stat.size > MAX_ARCHIVE_BYTES) { + throw new Error("live-proof augmentation archive must be a bounded regular file"); + } + return fs.readFileSync(file); +} + +function validateZipArchive( + archive: Buffer, + allowedEntries: ReadonlySet, +): ValidatedZipEntry[] { + const endOffset = findZipEnd(archive); + const diskNumber = archive.readUInt16LE(endOffset + 4); + const centralDisk = archive.readUInt16LE(endOffset + 6); + const diskEntries = archive.readUInt16LE(endOffset + 8); + const totalEntries = archive.readUInt16LE(endOffset + 10); + const centralSize = archive.readUInt32LE(endOffset + 12); + const centralOffset = archive.readUInt32LE(endOffset + 16); + const commentLength = archive.readUInt16LE(endOffset + 20); + if ( + diskNumber !== 0 || + centralDisk !== 0 || + diskEntries !== totalEntries || + totalEntries < 1 || + totalEntries > allowedEntries.size || + centralOffset === 0xffffffff || + centralSize === 0xffffffff || + commentLength !== 0 || + endOffset + 22 !== archive.length || + centralOffset + centralSize !== endOffset + ) { + throw new Error("live-proof augmentation archive layout is invalid"); + } + + const entries: Array<{ + name: string; + flags: number; + method: number; + crc32: number; + compressedSize: number; + uncompressedSize: number; + localOffset: number; + }> = []; + const names = new Set(); + let declaredTotalBytes = 0; + let cursor = centralOffset; + for (let index = 0; index < totalEntries; index += 1) { + ensureBufferRange(archive, cursor, 46); + if (archive.readUInt32LE(cursor) !== ZIP_CENTRAL_SIGNATURE) { + throw new Error("live-proof augmentation archive central directory is invalid"); + } + const versionMadeBy = archive.readUInt16LE(cursor + 4); + const flags = archive.readUInt16LE(cursor + 8); + const method = archive.readUInt16LE(cursor + 10); + const crc32 = archive.readUInt32LE(cursor + 16); + const compressedSize = archive.readUInt32LE(cursor + 20); + const uncompressedSize = archive.readUInt32LE(cursor + 24); + const nameLength = archive.readUInt16LE(cursor + 28); + const extraLength = archive.readUInt16LE(cursor + 30); + const entryCommentLength = archive.readUInt16LE(cursor + 32); + const diskStart = archive.readUInt16LE(cursor + 34); + const externalAttributes = archive.readUInt32LE(cursor + 38); + const localOffset = archive.readUInt32LE(cursor + 42); + ensureBufferRange(archive, cursor + 46, nameLength + extraLength + entryCommentLength); + const nameBytes = archive.subarray(cursor + 46, cursor + 46 + nameLength); + const name = nameBytes.toString("utf8"); + const madeBySystem = versionMadeBy >>> 8; + const unixMode = externalAttributes >>> 16; + if ( + nameLength < 1 || + Buffer.compare(nameBytes, Buffer.from(name, "utf8")) !== 0 || + name.includes("\0") || + name.includes("\\") || + path.posix.isAbsolute(name) || + name.split("/").includes("..") || + !allowedEntries.has(name) + ) { + throw new Error(`live-proof augmentation archive contains an unexpected path: ${name}`); + } + if (names.has(name)) { + throw new Error("live-proof augmentation archive contains duplicate entries"); + } + if ( + madeBySystem !== 3 || + (unixMode & ZIP_UNIX_TYPE_MASK) !== ZIP_UNIX_REGULAR_FILE || + diskStart !== 0 || + extraLength !== 0 || + entryCommentLength !== 0 + ) { + throw new Error("live-proof augmentation archive entries must be plain regular files"); + } + if ( + (flags & ~ZIP_ALLOWED_FLAGS) !== 0 || + (method !== 0 && method !== 8) || + compressedSize === 0xffffffff || + uncompressedSize === 0xffffffff || + uncompressedSize > MAX_TOTAL_BYTES || + localOffset === 0xffffffff + ) { + throw new Error("live-proof augmentation archive entry encoding is invalid"); + } + declaredTotalBytes += uncompressedSize; + if (declaredTotalBytes > MAX_TOTAL_BYTES) { + throw new Error("live-proof augmentation archive exceeds its byte limit"); + } + names.add(name); + entries.push({ + name, + flags, + method, + crc32, + compressedSize, + uncompressedSize, + localOffset, + }); + cursor += 46 + nameLength + extraLength + entryCommentLength; + } + if (cursor !== endOffset) { + throw new Error("live-proof augmentation archive central directory is invalid"); + } + + const byOffset = [...entries].sort((left, right) => left.localOffset - right.localOffset); + if (byOffset[0]?.localOffset !== 0) { + throw new Error("live-proof augmentation archive contains unexpected leading data"); + } + let totalBytes = 0; + const materialized = new Map(); + for (let index = 0; index < byOffset.length; index += 1) { + const entry = byOffset[index]!; + const nextOffset = byOffset[index + 1]?.localOffset ?? centralOffset; + ensureBufferRange(archive, entry.localOffset, 30); + if (archive.readUInt32LE(entry.localOffset) !== ZIP_LOCAL_SIGNATURE) { + throw new Error("live-proof augmentation archive local header is invalid"); + } + const localFlags = archive.readUInt16LE(entry.localOffset + 6); + const localMethod = archive.readUInt16LE(entry.localOffset + 8); + const localCrc32 = archive.readUInt32LE(entry.localOffset + 14); + const localCompressedSize = archive.readUInt32LE(entry.localOffset + 18); + const localUncompressedSize = archive.readUInt32LE(entry.localOffset + 22); + const localNameLength = archive.readUInt16LE(entry.localOffset + 26); + const localExtraLength = archive.readUInt16LE(entry.localOffset + 28); + ensureBufferRange( + archive, + entry.localOffset + 30, + localNameLength + localExtraLength + entry.compressedSize, + ); + const localNameStart = entry.localOffset + 30; + const localName = archive + .subarray(localNameStart, localNameStart + localNameLength) + .toString("utf8"); + if ( + localFlags !== entry.flags || + localMethod !== entry.method || + localName !== entry.name || + localExtraLength !== 0 + ) { + throw new Error("live-proof augmentation archive local header does not match"); + } + if ( + (entry.flags & ZIP_FLAG_DATA_DESCRIPTOR) === 0 && + (localCrc32 !== entry.crc32 || + localCompressedSize !== entry.compressedSize || + localUncompressedSize !== entry.uncompressedSize) + ) { + throw new Error("live-proof augmentation archive local sizes do not match"); + } + const dataStart = localNameStart + localNameLength; + const dataEnd = dataStart + entry.compressedSize; + validateZipDescriptor(archive, entry, dataEnd, nextOffset); + const compressed = archive.subarray(dataStart, dataEnd); + const data = + entry.method === 0 + ? Buffer.from(compressed) + : inflateRawSync(compressed, { maxOutputLength: Math.max(1, entry.uncompressedSize) }); + if (data.length !== entry.uncompressedSize || crc32(data) !== entry.crc32) { + throw new Error("live-proof augmentation archive file integrity check failed"); + } + totalBytes += data.length; + if (totalBytes > MAX_TOTAL_BYTES) { + throw new Error("live-proof augmentation archive exceeds its byte limit"); + } + materialized.set(entry.name, data); + } + return entries.map((entry) => ({ name: entry.name, data: materialized.get(entry.name)! })); +} + +function findZipEnd(archive: Buffer): number { + const firstPossible = Math.max(0, archive.length - 22 - 0xffff); + for (let offset = archive.length - 22; offset >= firstPossible; offset -= 1) { + if (archive.readUInt32LE(offset) === ZIP_END_SIGNATURE) return offset; + } + throw new Error("live-proof augmentation archive end record is missing"); +} + +function validateZipDescriptor( + archive: Buffer, + entry: { + flags: number; + crc32: number; + compressedSize: number; + uncompressedSize: number; + }, + dataEnd: number, + nextOffset: number, +): void { + if (nextOffset < dataEnd) { + throw new Error("live-proof augmentation archive entries overlap"); + } + const descriptorBytes = nextOffset - dataEnd; + if ((entry.flags & ZIP_FLAG_DATA_DESCRIPTOR) === 0) { + if (descriptorBytes !== 0) { + throw new Error("live-proof augmentation archive contains unexpected entry data"); + } + return; + } + if (descriptorBytes !== 12 && descriptorBytes !== 16) { + throw new Error("live-proof augmentation archive data descriptor is invalid"); + } + let cursor = dataEnd; + if (descriptorBytes === 16) { + if (archive.readUInt32LE(cursor) !== ZIP_DESCRIPTOR_SIGNATURE) { + throw new Error("live-proof augmentation archive data descriptor is invalid"); + } + cursor += 4; + } + if ( + archive.readUInt32LE(cursor) !== entry.crc32 || + archive.readUInt32LE(cursor + 4) !== entry.compressedSize || + archive.readUInt32LE(cursor + 8) !== entry.uncompressedSize + ) { + throw new Error("live-proof augmentation archive data descriptor does not match"); + } +} + +function ensureBufferRange(buffer: Buffer, offset: number, length: number): void { + if ( + !Number.isSafeInteger(offset) || + !Number.isSafeInteger(length) || + offset < 0 || + length < 0 || + offset + length > buffer.length + ) { + throw new Error("live-proof augmentation archive is truncated"); + } +} + +const CRC32_TABLE = Array.from({ length: 256 }, (_, value) => { + let crc = value; + for (let bit = 0; bit < 8; bit += 1) { + crc = (crc & 1) !== 0 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1; + } + return crc >>> 0; +}); + +function crc32(buffer: Buffer): number { + let crc = 0xffffffff; + for (const byte of buffer) crc = CRC32_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8); + return (crc ^ 0xffffffff) >>> 0; +} + +function readRegularFile(file: string, maxBytes: number): string { + const stat = fs.lstatSync(file); + if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxBytes) { + throw new Error("live-proof augmentation input must be a bounded regular file"); + } + return fs.readFileSync(file, "utf8"); +} + +function regularFileSha256(file: string, maxBytes: number): string { + const stat = fs.lstatSync(file); + if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxBytes) { + throw new Error("live-proof augmentation input must be a bounded regular file"); + } + return sha256(fs.readFileSync(file)); +} + +function canonicalTimestamp(value: string): string { + const parsed = new Date(value); + if (!Number.isFinite(parsed.valueOf()) || parsed.toISOString() !== value) { + throw new Error("live-proof augmentation timestamp is invalid"); + } + return value; +} + +function sha256(value: string | Buffer): string { + return createHash("sha256").update(value).digest("hex"); +} + +function record(value: unknown, label: string): Record { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`live-proof augmentation ${label} must be an object`); + } + return value as Record; +} + +function exactKeys(value: Record, expected: readonly string[]): void { + const actual = Object.keys(value).sort(); + const sortedExpected = [...expected].sort(); + if (JSON.stringify(actual) !== JSON.stringify(sortedExpected)) { + throw new Error("live-proof augmentation contains unexpected manifest fields"); + } +} + +function stringValue(value: unknown, label: string): string { + if (typeof value !== "string" || !value) { + throw new Error(`live-proof augmentation ${label} must be a string`); + } + return value; +} + +function numberValue(value: unknown, label: string): number { + if (typeof value !== "number" || !Number.isFinite(value)) { + throw new Error(`live-proof augmentation ${label} is invalid`); + } + return value; +} diff --git a/src/repair/exact-review-bundle-cli.ts b/src/repair/exact-review-bundle-cli.ts index 4a05b617dc..b8b3f601a5 100644 --- a/src/repair/exact-review-bundle-cli.ts +++ b/src/repair/exact-review-bundle-cli.ts @@ -45,6 +45,7 @@ function contextFromEnv(env: NodeJS.ProcessEnv): ExactReviewBundleContext { decisionSha256: exactReviewDecisionSha256(requiredEnv(env, "EXACT_REVIEW_DECISION")), targetRepo: requiredEnv(env, "EXACT_REVIEW_TARGET_REPO"), targetBranch: requiredEnv(env, "EXACT_REVIEW_TARGET_BRANCH"), + pullHeadSha: optionalShaEnv(env, "EXACT_REVIEW_PULL_HEAD_SHA"), itemNumber: positiveIntegerEnv(env, "EXACT_REVIEW_ITEM_NUMBER"), itemKind: itemKindEnv(env), itemKey: requiredEnv(env, "EXACT_REVIEW_ITEM_KEY"), @@ -82,6 +83,13 @@ function optionalPositiveIntegerEnv(env: NodeJS.ProcessEnv, name: string): numbe return value; } +function optionalShaEnv(env: NodeJS.ProcessEnv, name: string): string | null { + const value = optionalEnv(env, name).toLowerCase(); + if (!value) return null; + if (!/^[0-9a-f]{40}$/.test(value)) throw new Error(`${name} must be a full commit SHA`); + return value; +} + function booleanEnv(env: NodeJS.ProcessEnv, name: string): boolean { const value = requiredEnv(env, name); if (value === "true") return true; diff --git a/src/repair/exact-review-bundle.ts b/src/repair/exact-review-bundle.ts index 14bc8545ae..2db08a84b7 100644 --- a/src/repair/exact-review-bundle.ts +++ b/src/repair/exact-review-bundle.ts @@ -25,6 +25,7 @@ export interface ExactReviewBundleContext { decisionSha256: string; targetRepo: string; targetBranch: string; + pullHeadSha: string | null; itemNumber: number; itemKind: "issue" | "pull_request"; itemKey: string; @@ -62,6 +63,7 @@ export interface ExactReviewBundleManifest { target: { repo: string; branch: string; + pull_head_sha: string | null; item_number: number; item_kind: "issue" | "pull_request"; }; @@ -105,6 +107,7 @@ export function createExactReviewBundle( let artifactPresent = false; if (options.reviewPath && fs.existsSync(options.reviewPath)) { + validateReviewIdentity(options.reviewPath, context); const reviewDestination = path.join(bundleDir, "review", `${context.itemNumber}.md`); copyRegularFile(options.reviewPath, reviewDestination); artifactPresent = true; @@ -142,6 +145,7 @@ export function createExactReviewBundle( target: { repo: context.targetRepo, branch: context.targetBranch, + pull_head_sha: context.pullHeadSha, item_number: context.itemNumber, item_kind: context.itemKind, }, @@ -214,6 +218,7 @@ function assertExpectedManifest( decisionSha256: manifest.review.decision_sha256, targetRepo: manifest.target.repo, targetBranch: manifest.target.branch, + pullHeadSha: manifest.target.pull_head_sha, itemNumber: manifest.target.item_number, itemKind: manifest.target.item_kind, itemKey: manifest.queue.item_key, @@ -245,6 +250,13 @@ function validateContext(value: ExactReviewBundleContext): ExactReviewBundleCont if (!BRANCH_PATTERN.test(value.targetBranch) || value.targetBranch.includes("..")) { throw new Error("target branch is invalid"); } + if (value.itemKind === "pull_request") { + if (!value.pullHeadSha || !SHA_PATTERN.test(value.pullHeadSha)) { + throw new Error("pull request head SHA is invalid"); + } + } else if (value.pullHeadSha !== null) { + throw new Error("issue bundles must not record a pull request head SHA"); + } positiveInteger(value.itemNumber, "item number"); if (value.itemKind !== "issue" && value.itemKind !== "pull_request") { throw new Error("item kind is invalid"); @@ -303,7 +315,7 @@ function validateManifest(value: unknown): ExactReviewBundleManifest { const queue = record(manifest.queue, "queue"); exactKeys(queue, ["item_key", "protocol_version", "lease_revision", "claim_generation"]); const target = record(manifest.target, "target"); - exactKeys(target, ["repo", "branch", "item_number", "item_kind"]); + exactKeys(target, ["repo", "branch", "pull_head_sha", "item_number", "item_kind"]); const review = record(manifest.review, "review"); exactKeys(review, [ "decision_sha256", @@ -323,6 +335,10 @@ function validateManifest(value: unknown): ExactReviewBundleManifest { decisionSha256: stringValue(review.decision_sha256, "review.decision_sha256"), targetRepo: stringValue(target.repo, "target.repo"), targetBranch: stringValue(target.branch, "target.branch"), + pullHeadSha: + target.pull_head_sha === null + ? null + : stringValue(target.pull_head_sha, "target.pull_head_sha"), itemNumber: numberValue(target.item_number, "target.item_number"), itemKind: target.item_kind as "issue" | "pull_request", itemKey: stringValue(queue.item_key, "queue.item_key"), @@ -388,6 +404,7 @@ function validateManifest(value: unknown): ExactReviewBundleManifest { target: { repo: context.targetRepo, branch: context.targetBranch, + pull_head_sha: context.pullHeadSha, item_number: context.itemNumber, item_kind: context.itemKind, }, @@ -501,6 +518,36 @@ function copyRegularFile( fs.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL); } +function validateReviewIdentity(reviewPath: string, context: ExactReviewBundleContext): void { + const markdown = fs.readFileSync(reviewPath, "utf8"); + const repository = frontMatterValue(markdown, "repository"); + const itemNumber = Number(frontMatterValue(markdown, "number")); + const itemKind = frontMatterValue(markdown, "type"); + const pullHeadSha = + context.itemKind === "pull_request" + ? (frontMatterValue(markdown, "pull_head_sha")?.toLowerCase() ?? null) + : null; + if ( + repository !== context.targetRepo || + itemNumber !== context.itemNumber || + itemKind !== context.itemKind || + pullHeadSha !== context.pullHeadSha + ) { + throw new Error("exact review artifact identity does not match the trusted workflow context"); + } +} + +function frontMatterValue(markdown: string, key: string): string | undefined { + const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(markdown); + if (!match) return undefined; + const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const values = [...(match[1] ?? "").matchAll(new RegExp(`^${escaped}:\\s*(.*)$`, "gm"))]; + if (values.length !== 1) return undefined; + const value = values[0]?.[1]?.trim(); + if (!value) return undefined; + return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value; +} + function exactReviewBundleFileLimit(relativePath: string): number { if (relativePath.endsWith("/live-proof.mp4") || relativePath === "live-proof.mp4") { return 50 * 1024 * 1024; diff --git a/test/clawsweeper.test.ts b/test/clawsweeper.test.ts index c26f9d6734..c43792077e 100644 --- a/test/clawsweeper.test.ts +++ b/test/clawsweeper.test.ts @@ -2154,6 +2154,10 @@ test("sweep workflow executes only durable queue leases without runner-side admi ); const eventReviewBlock = workflow.slice( workflow.indexOf("\n event-review-apply:"), + workflow.indexOf("\n event-review-live-proof:"), + ); + const finalizerBlock = workflow.slice( + workflow.indexOf("\n event-review-finalize:"), workflow.indexOf("\n event-review-publish:"), ); const claimIndex = eventReviewBlock.indexOf("- name: Claim exact-review queue lease"); @@ -2169,18 +2173,20 @@ test("sweep workflow executes only durable queue leases without runner-side admi const failReviewIndex = eventReviewBlock.indexOf( "- name: Fail unsuccessful exact review generation", ); - const completeLeaseIndex = eventReviewBlock.indexOf("- name: Complete exact-review queue lease"); const uploadBundleIndex = eventReviewBlock.indexOf("- name: Upload exact review artifact bundle"); + const finalizerCompleteLeaseIndex = finalizerBlock.indexOf( + "- name: Complete exact-review queue lease", + ); const claimStep = eventReviewBlock.slice( claimIndex, eventReviewBlock.indexOf("\n - ", claimIndex + 1), ); - const completeLeaseStep = eventReviewBlock.slice( - completeLeaseIndex, - eventReviewBlock.indexOf("\n - ", completeLeaseIndex + 1), - ); - const primaryResultStep = eventReviewBlock.slice(primaryResultIndex, completeLeaseIndex); + const primaryResultStep = eventReviewBlock.slice(primaryResultIndex, failReviewIndex); const failReviewStep = eventReviewBlock.slice(failReviewIndex); + const completeLeaseStep = finalizerBlock.slice( + finalizerCompleteLeaseIndex, + finalizerBlock.indexOf("\n - ", finalizerCompleteLeaseIndex + 1), + ); const exactReviewStep = eventReviewBlock.slice( exactReviewIndex, eventReviewBlock.indexOf("- name: Create exact review artifact bundle", exactReviewIndex), @@ -2221,12 +2227,18 @@ test("sweep workflow executes only durable queue leases without runner-side admi ); assert.ok(uploadBundleIndex > exactReviewIndex); assert.ok(primaryResultIndex > uploadBundleIndex); - assert.ok(completeLeaseIndex > primaryResultIndex); - assert.ok(failReviewIndex > completeLeaseIndex); - assert.match(eventReviewBlock, /\.github\/actions\/setup-state/); - assert.match(eventReviewBlock, /repair:exact-review-direct-publication/); + assert.ok(failReviewIndex > primaryResultIndex); + assert.doesNotMatch(eventReviewBlock, /\.github\/actions\/setup-state/); + assert.doesNotMatch(eventReviewBlock, /repair:exact-review-direct-publication/); + assert.match(finalizerBlock, /\.github\/actions\/setup-state/); + assert.match(finalizerBlock, /repair:exact-review-direct-publication/); + assert.match(finalizerBlock, /name: Complete exact-review queue lease/); + assert.match( + finalizerBlock, + /name: Fail exact review finalization that did not publish or requeue/, + ); assert.match(eventReviewBlock, /\/internal\/exact-review\/claim/); - assert.match(eventReviewBlock, /\/internal\/exact-review\/complete/); + assert.match(finalizerBlock, /\/internal\/exact-review\/complete/); assert.match(claimStep, /RUN_ATTEMPT: \$\{\{ github\.run_attempt \}\}/); assert.match( claimStep, @@ -2236,52 +2248,26 @@ test("sweep workflow executes only durable queue leases without runner-side admi assert.match(claimStep, /const legacyDecision = \{/); assert.match(claimStep, /run_attempt: runAttempt/); assert.match(failReviewStep, /exact-review-generation-result\.outputs\.outcome != 'success'/); - assert.match(failReviewStep, /complete-exact-review-queue\.outcome != 'success'/); assert.match(primaryResultStep, /REVIEW_OUTCOME:/); - assert.match(primaryResultStep, /PUBLICATION_QUEUE_OUTCOME:/); assert.match(primaryResultStep, /REVIEW_OUTCOME" = "cancelled"/); - assert.match(primaryResultStep, /echo "outcome=\$outcome" >> "\$GITHUB_OUTPUT"/); - assert.match( - completeLeaseStep, - /PRIMARY_OUTCOME: \$\{\{ steps\.exact-review-generation-result\.outputs\.outcome \|\| 'failure' \}\}/, - ); - assert.doesNotMatch(completeLeaseStep, /JOB_STATUS:/); - assert.match(completeLeaseStep, /if: \$\{\{[^\n]*always\(\)[^\n]*\}\}/); - assert.match(completeLeaseStep, /steps\.claim-exact-review-queue\.outputs\.claimed == 'true'/); + assert.match(primaryResultStep, /echo "outcome=\$outcome"[\s\S]*\} >> "\$GITHUB_OUTPUT"/); assert.match(completeLeaseStep, /continue-on-error: true/); assert.match(completeLeaseStep, /RUN_ATTEMPT: \$\{\{ github\.run_attempt \}\}/); assert.match( completeLeaseStep, - /PROTOCOL_VERSION: \$\{\{ steps\.claim-exact-review-queue\.outputs\.protocol_version \}\}/, + /PROTOCOL_VERSION: \$\{\{ needs\.event-review-apply\.outputs\.protocol_version \}\}/, ); - assert.match(completeLeaseStep, /const primaryOutcome = String\(process\.env\.PRIMARY_OUTCOME/); - assert.match(completeLeaseStep, /\["success", "cancelled", "failure"\]\.includes/); + assert.match(completeLeaseStep, /const published = process\.env\.PUBLICATION_ACCEPTED/); + assert.match(completeLeaseStep, /const generationNoop =/); + assert.match(completeLeaseStep, /const outcome = published \|\| generationNoop/); assert.match(completeLeaseStep, /claim_generation: claimGeneration/); assert.match(completeLeaseStep, /item_key: process\.env\.ITEM_KEY/); assert.match(completeLeaseStep, /lease_revision: leaseRevision/); assert.match(completeLeaseStep, /run_attempt: runAttempt/); assert.match(completeLeaseStep, /outcome,/); - // A completion callback is non-fatal only when the queue proves that this - // exact lease was superseded. Unknown conflicts and every other non-2xx - // status stay visible. - assert.doesNotMatch(completeLeaseStep, /curl --fail/); - assert.match(completeLeaseStep, /--write-out '%\{http_code\}'/); - assert.match(completeLeaseStep, /if \[\[ "\$status" == 2\* \]\]; then\s*\n\s*exit 0/); - // Completion accepts only its audited supersession response; claim-path - // conflicts and ambiguous ownership misses must keep failing the run. - assert.match(completeLeaseStep, /const safeConflicts = new Set\(\["lease_superseded"\]\);/); - assert.doesNotMatch(completeLeaseStep, /"lease_not_claimed"/); - assert.doesNotMatch(completeLeaseStep, /"lease_not_active"/); - assert.doesNotMatch(completeLeaseStep, /"lease_already_claimed"/); - assert.doesNotMatch(completeLeaseStep, /"stale_run_attempt"/); - assert.doesNotMatch(completeLeaseStep, /"lease_decision_unavailable"/); - assert.match( - completeLeaseStep, - /if \(!safeConflicts\.has\(response\.error\)\) process\.exit\(1\);/, - ); - assert.match(completeLeaseStep, /Unexpected exact-review completion conflict/); - assert.match(completeLeaseStep, /Exact-review completion returned HTTP \$status/); - assert.match(completeLeaseStep, /if \[\[ "\$status" != 5\* \]\]; then\s*\n\s*exit 1/); + assert.match(completeLeaseStep, /curl --fail/); + assert.match(completeLeaseStep, /completion_kind:/); + assert.match(completeLeaseStep, /reason_code:/); assert.match(eventReviewBlock, /exact-review queue leased this run/); assert.doesNotMatch(eventReviewBlock, /repair:codex-capacity/); assert.doesNotMatch(eventReviewBlock, /capacity-requeue/); @@ -2603,7 +2589,7 @@ test("sweep target write tokens retain merge and terminal acknowledgement scopes .slice(1) .map((block) => block.split("\n - ")[0]); - assert.equal(targetWriteTokenBlocks.length, 5); + assert.equal(targetWriteTokenBlocks.length, 6); assert.equal(finalizationTokens.length, 1); const finalizationToken = finalizationTokens[0]; assert.ok(finalizationToken); @@ -2614,7 +2600,7 @@ test("sweep target write tokens retain merge and terminal acknowledgement scopes const contentWritingTokenBlocks = targetWriteTokenBlocks.filter( (block) => block !== finalizationToken, ); - assert.equal(contentWritingTokenBlocks.length, 4); + assert.equal(contentWritingTokenBlocks.length, 5); const compositeAction = readText(".github/actions/create-target-write-token/action.yml"); assert.match(compositeAction, /permission-contents: write/); assert.match(compositeAction, /permission-pull-requests: write/); diff --git a/test/github-egress-telemetry.test.ts b/test/github-egress-telemetry.test.ts index f0fef850aa..f77cf242b7 100644 --- a/test/github-egress-telemetry.test.ts +++ b/test/github-egress-telemetry.test.ts @@ -373,26 +373,26 @@ test("publication workflows retain v1 metrics while wiring bounded v2 observatio true, ); - const direct = sweep.jobs["event-review-apply"]!.steps; + const direct = sweep.jobs["event-review-finalize"]!.steps; assertStepOrder(direct, [ "./.github/actions/setup-github-egress-observer", - "Record direct-publication member", - "Deliver GitHub effects and prepare direct state mutation", + "Record final-publication member", + "Deliver exact review and prepare state mutation", "Finalize direct exact review lifecycle", - "Submit direct GitHub egress telemetry", - "Fail unsuccessful exact review generation", + "Submit final-publication GitHub egress telemetry", + "Fail exact review finalization that did not publish or requeue", ]); assert.equal( - direct.find((step) => step.id === "direct-github-egress-observer")?.["continue-on-error"], + direct.find((step) => step.id === "final-github-egress-observer")?.["continue-on-error"], true, ); assert.equal( - direct.find((step) => step.name === "Record direct-publication member")?.["continue-on-error"], + direct.find((step) => step.name === "Record final-publication member")?.["continue-on-error"], true, ); assert.equal( - direct.find((step) => step.name === "Record direct-publication member")?.env?.TARGET_REPO, - "${{ steps.target.outputs.target_repo }}", + direct.find((step) => step.name === "Record final-publication member")?.env?.TARGET_REPO, + "${{ needs.event-review-apply.outputs.target_repo }}", ); const artifact = sweep.jobs["event-review-publish"]!.steps; assertStepOrder(artifact, [ diff --git a/test/live-proof-review-augmentation.test.ts b/test/live-proof-review-augmentation.test.ts new file mode 100644 index 0000000000..d50e9e923c --- /dev/null +++ b/test/live-proof-review-augmentation.test.ts @@ -0,0 +1,391 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { deflateRawSync } from "node:zlib"; + +import { + createReviewLiveProofAugmentation, + materializeReviewLiveProofAugmentationArchive, + mergeReviewLiveProofAugmentation, + validateReviewLiveProofAugmentation, + type ReviewLiveProofAugmentationContext, +} from "../dist/live-proof/review-augmentation.js"; + +const HEAD = "b".repeat(40); + +function fixture() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "clawsweeper-live-proof-augmentation-")); + const proofDir = path.join(root, "proof"); + const augmentationDir = path.join(root, "augmentation"); + const coreManifestPath = path.join(root, "core-manifest.json"); + fs.mkdirSync(proofDir); + fs.writeFileSync(coreManifestPath, '{"schema_version":1}\n'); + fs.writeFileSync( + path.join(proofDir, "live-verification.json"), + `${JSON.stringify({ + schema_version: 1, + repo: "openclaw/clickclack", + item: 173, + head_sha: HEAD, + surface: "terminal", + entry: "pnpm test", + drive_status: "completed", + steps: [ + { + action: "expect_output", + status: "completed", + detail: "expected output observed", + assertion: "ok", + present_at_start: false, + satisfied: true, + }, + ], + output: "ok", + overall_pass: true, + verified_at: "2026-08-21T17:00:00.000Z", + })}\n`, + ); + const context: ReviewLiveProofAugmentationContext = { + repository: "openclaw/clawsweeper", + sourceSha: "a".repeat(40), + runId: "32500000000", + runAttempt: 1, + producerJob: "event-review-live-proof", + runnerEnvironment: "github-hosted", + coreArtifactId: "123456789", + coreArtifactDigest: `sha256:${"c".repeat(64)}`, + targetRepo: "openclaw/clickclack", + itemNumber: 173, + pullHeadSha: HEAD, + }; + return { root, proofDir, augmentationDir, coreManifestPath, context }; +} + +test("live-proof augmentation binds proof to the immutable core artifact and exact head", () => { + const value = fixture(); + const created = createReviewLiveProofAugmentation({ + augmentationDir: value.augmentationDir, + coreManifestPath: value.coreManifestPath, + proofDir: value.proofDir, + createdAt: "2026-08-21T17:01:00.000Z", + context: value.context, + }); + const validated = validateReviewLiveProofAugmentation( + value.augmentationDir, + value.coreManifestPath, + value.context, + ); + + assert.deepEqual(validated, created); + assert.deepEqual(created.result, { kind: "proof", overall_pass: true }); + assert.deepEqual( + created.files.map((file) => file.path), + ["live-proof/173/live-verification.json"], + ); + + const destination = path.join(value.root, "publication"); + fs.mkdirSync(destination); + mergeReviewLiveProofAugmentation(value.augmentationDir, destination, validated); + assert.equal( + fs.existsSync(path.join(destination, "live-proof/173/live-verification.json")), + true, + ); +}); + +test("valid FAIL verification remains a publishable proof augmentation", () => { + const value = fixture(); + const verificationPath = path.join(value.proofDir, "live-verification.json"); + const verification = JSON.parse(fs.readFileSync(verificationPath, "utf8")) as Record< + string, + unknown + >; + verification.drive_status = "failed"; + verification.steps = [ + { + action: "expect_output", + status: "failed", + detail: "expected output missing", + assertion: "ok", + present_at_start: false, + satisfied: false, + }, + ]; + verification.failure = { + phase: "step", + reason: "expected output missing", + step: 1, + action: "expect_output", + }; + verification.overall_pass = false; + fs.writeFileSync(verificationPath, `${JSON.stringify(verification)}\n`); + + const created = createReviewLiveProofAugmentation({ + augmentationDir: value.augmentationDir, + coreManifestPath: value.coreManifestPath, + proofDir: value.proofDir, + createdAt: "2026-08-21T17:01:00.000Z", + context: value.context, + }); + assert.deepEqual(created.result, { kind: "proof", overall_pass: false }); +}); + +test("verified cleanup-only failure produces a core-only augmentation", () => { + const value = fixture(); + const cleanupFailurePath = path.join(value.root, "cleanup.json"); + fs.writeFileSync( + cleanupFailurePath, + `${JSON.stringify({ + schema_version: 1, + item: 173, + head_sha: HEAD, + proof_output_present: true, + failures: [{ operation: "remove_scratch", error_code: "EACCES" }], + })}\n`, + ); + const created = createReviewLiveProofAugmentation({ + augmentationDir: value.augmentationDir, + cleanupFailurePath, + coreManifestPath: value.coreManifestPath, + proofDir: value.proofDir, + createdAt: "2026-08-21T17:01:00.000Z", + context: value.context, + }); + + assert.deepEqual(created.result, { + kind: "cleanup_only_failure", + proof_output_present: true, + }); + assert.deepEqual(created.files, []); + assert.throws( + () => + mergeReviewLiveProofAugmentation( + value.augmentationDir, + path.join(value.root, "publication"), + created, + ), + /must not be merged/, + ); +}); + +test("augmentation rejects core, head, and file tampering", () => { + const value = fixture(); + createReviewLiveProofAugmentation({ + augmentationDir: value.augmentationDir, + coreManifestPath: value.coreManifestPath, + proofDir: value.proofDir, + createdAt: "2026-08-21T17:01:00.000Z", + context: value.context, + }); + assert.throws( + () => + validateReviewLiveProofAugmentation(value.augmentationDir, value.coreManifestPath, { + ...value.context, + pullHeadSha: "d".repeat(40), + }), + /trusted workflow context/, + ); + fs.appendFileSync(value.coreManifestPath, "changed\n"); + assert.throws( + () => + validateReviewLiveProofAugmentation( + value.augmentationDir, + value.coreManifestPath, + value.context, + ), + /immutable core manifest/, + ); +}); + +test("augmentation rejects unknown and duplicate manifest inventory fields", () => { + const value = fixture(); + createReviewLiveProofAugmentation({ + augmentationDir: value.augmentationDir, + coreManifestPath: value.coreManifestPath, + proofDir: value.proofDir, + createdAt: "2026-08-21T17:01:00.000Z", + context: value.context, + }); + const manifestPath = path.join(value.augmentationDir, "manifest.json"); + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as { + files: Array>; + producer: Record; + }; + manifest.producer.untrusted = true; + fs.writeFileSync(manifestPath, `${JSON.stringify(manifest)}\n`); + assert.throws( + () => + validateReviewLiveProofAugmentation( + value.augmentationDir, + value.coreManifestPath, + value.context, + ), + /unexpected manifest fields/, + ); + + delete manifest.producer.untrusted; + manifest.files.push({ ...manifest.files[0] }); + fs.writeFileSync(manifestPath, `${JSON.stringify(manifest)}\n`); + assert.throws( + () => + validateReviewLiveProofAugmentation( + value.augmentationDir, + value.coreManifestPath, + value.context, + ), + /duplicate file paths/, + ); +}); + +test("augmentation archive materialization accepts only fixed regular entries", () => { + const value = fixture(); + const archivePath = path.join(value.root, "augmentation.zip"); + const destination = path.join(value.root, "materialized"); + writeZipArchive(archivePath, [ + { name: "manifest.json", data: Buffer.from('{"schema_version":1}\n') }, + { + name: "live-proof/173/live-verification.json", + data: Buffer.from('{"schema_version":1}\n'), + }, + ]); + + const result = spawnSync( + process.execPath, + ["dist/live-proof/review-augmentation-cli.js", "materialize"], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + REVIEW_LIVE_PROOF_ARCHIVE: archivePath, + REVIEW_LIVE_PROOF_AUGMENTATION_DIR: destination, + REVIEW_LIVE_PROOF_ITEM_NUMBER: "173", + }, + }, + ); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(JSON.parse(result.stdout), { materialized: true }); + + assert.equal( + fs.readFileSync(path.join(destination, "manifest.json"), "utf8"), + '{"schema_version":1}\n', + ); + assert.equal( + fs.readFileSync(path.join(destination, "live-proof/173/live-verification.json"), "utf8"), + '{"schema_version":1}\n', + ); +}); + +test("augmentation archive rejects unsafe entry paths and types before writing", () => { + const value = fixture(); + const outside = path.join(value.root, "outside.txt"); + fs.writeFileSync(outside, "sentinel\n"); + + for (const [name, entries] of [ + ["absolute", [{ name: "/manifest.json", data: Buffer.from("invalid\n") }]], + ["traversal", [{ name: "../outside.txt", data: Buffer.from("replaced\n"), mode: 0o100644 }]], + [ + "duplicate", + [ + { name: "manifest.json", data: Buffer.from("first\n") }, + { name: "manifest.json", data: Buffer.from("second\n") }, + ], + ], + ["symlink", [{ name: "manifest.json", data: Buffer.from("../outside.txt"), mode: 0o120777 }]], + [ + "hardlink-metadata", + [ + { + name: "manifest.json", + data: Buffer.from("outside.txt"), + extra: Buffer.from([0x0d, 0, 0, 0]), + }, + ], + ], + ["device", [{ name: "manifest.json", data: Buffer.from("device\n"), mode: 0o020666 }]], + ] as const) { + const archivePath = path.join(value.root, `${name}.zip`); + const destination = path.join(value.root, `${name}-materialized`); + writeZipArchive(archivePath, entries); + assert.throws( + () => + materializeReviewLiveProofAugmentationArchive({ + archivePath, + destinationDir: destination, + itemNumber: 173, + }), + /unexpected path|duplicate entries|plain regular files/, + ); + assert.equal(fs.existsSync(destination), false); + assert.equal(fs.readFileSync(outside, "utf8"), "sentinel\n"); + } +}); + +function writeZipArchive( + archivePath: string, + entries: ReadonlyArray<{ name: string; data: Buffer; mode?: number; extra?: Buffer }>, +): void { + const localParts: Buffer[] = []; + const centralParts: Buffer[] = []; + let localOffset = 0; + for (const entry of entries) { + const name = Buffer.from(entry.name); + const extra = entry.extra ?? Buffer.alloc(0); + const checksum = crc32(entry.data); + const compressed = deflateRawSync(entry.data); + const local = Buffer.alloc(30); + local.writeUInt32LE(0x04034b50, 0); + local.writeUInt16LE(20, 4); + local.writeUInt16LE((1 << 11) | (1 << 3), 6); + local.writeUInt16LE(8, 8); + local.writeUInt16LE(name.length, 26); + local.writeUInt16LE(extra.length, 28); + const descriptor = Buffer.alloc(16); + descriptor.writeUInt32LE(0x08074b50, 0); + descriptor.writeUInt32LE(checksum, 4); + descriptor.writeUInt32LE(compressed.length, 8); + descriptor.writeUInt32LE(entry.data.length, 12); + localParts.push(local, name, extra, compressed, descriptor); + + const central = Buffer.alloc(46); + central.writeUInt32LE(0x02014b50, 0); + central.writeUInt16LE((3 << 8) | 20, 4); + central.writeUInt16LE(20, 6); + central.writeUInt16LE((1 << 11) | (1 << 3), 8); + central.writeUInt16LE(8, 10); + central.writeUInt32LE(checksum, 16); + central.writeUInt32LE(compressed.length, 20); + central.writeUInt32LE(entry.data.length, 24); + central.writeUInt16LE(name.length, 28); + central.writeUInt16LE(extra.length, 30); + central.writeUInt32LE(((entry.mode ?? 0o100644) << 16) >>> 0, 38); + central.writeUInt32LE(localOffset, 42); + centralParts.push(central, name, extra); + localOffset += + local.length + name.length + extra.length + compressed.length + descriptor.length; + } + const centralDirectory = Buffer.concat(centralParts); + const end = Buffer.alloc(22); + end.writeUInt32LE(0x06054b50, 0); + end.writeUInt16LE(entries.length, 8); + end.writeUInt16LE(entries.length, 10); + end.writeUInt32LE(centralDirectory.length, 12); + end.writeUInt32LE(localOffset, 16); + fs.writeFileSync(archivePath, Buffer.concat([...localParts, centralDirectory, end])); +} + +const CRC32_TABLE = Array.from({ length: 256 }, (_, value) => { + let crc = value; + for (let bit = 0; bit < 8; bit += 1) { + crc = (crc & 1) !== 0 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1; + } + return crc >>> 0; +}); + +function crc32(buffer: Buffer): number { + let crc = 0xffffffff; + for (const byte of buffer) crc = CRC32_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8); + return (crc ^ 0xffffffff) >>> 0; +} diff --git a/test/live-proof.test.ts b/test/live-proof.test.ts index cd7b9fc65a..ae7d90dd47 100644 --- a/test/live-proof.test.ts +++ b/test/live-proof.test.ts @@ -1567,15 +1567,44 @@ test("live proof executes in review jobs and publishes through existing artifact assertOrdered(exactReviewSteps, [ "Review exact event item", "Inspect exact review live proof", - "Execute exact review live proof", "Create exact review artifact bundle", "Upload exact review artifact bundle", ]); - const directSetup = exactReviewSteps.find((step) => step.id === "direct-setup-state"); - assert.match(directSetup?.if ?? "", /execute-exact-live-proof\.outputs\.produced != 'true'/); + assert.equal( + exactReviewSteps.some((step) => step.name === "Execute exact review live proof"), + false, + ); assert.doesNotMatch(JSON.stringify(exactReviewSteps), /CLAWSWEEPER_LIVE_PROOF_AWS/); assert.doesNotMatch(JSON.stringify(exactReviewSteps), /containment|unshare/); + const exactLiveProofSteps = sweepWorkflow.jobs["event-review-live-proof"]?.steps ?? []; + assertOrdered(exactLiveProofSteps, [ + "Download immutable exact review core", + "Validate immutable exact review core", + "Execute exact review live proof without workflow command files", + "Create exact-head live-proof augmentation", + "Upload exact-head live-proof augmentation", + ]); + assert.match( + exactLiveProofSteps.find( + (step) => step.name === "Execute exact review live proof without workflow command files", + )?.run ?? "", + /-u GITHUB_ENV[\s\S]*-u GITHUB_OUTPUT[\s\S]*-u GITHUB_PATH[\s\S]*-u GITHUB_STEP_SUMMARY/, + ); + assert.doesNotMatch(JSON.stringify(exactLiveProofSteps), /CLAWSWEEPER_LIVE_PROOF_AWS/); + + const exactFinalizeSteps = sweepWorkflow.jobs["event-review-finalize"]?.steps ?? []; + assertOrdered(exactFinalizeSteps, [ + "Validate finalizer core", + "Validate exact-head live-proof augmentation", + "Select exact review publication payload", + "Merge validated live-proof augmentation", + "Fold exact live proof into the review artifact", + "Deliver exact review and prepare state mutation", + "Complete exact-review queue lease", + ]); + assert.match(JSON.stringify(exactFinalizeSteps), /CLAWSWEEPER_LIVE_PROOF_AWS/); + const shardSteps = sweepWorkflow.jobs.review?.steps ?? []; assertOrdered(shardSteps, [ "Review shard", diff --git a/test/repair/exact-review-batch-workflow.test.ts b/test/repair/exact-review-batch-workflow.test.ts index 07faf24166..4f22976aad 100644 --- a/test/repair/exact-review-batch-workflow.test.ts +++ b/test/repair/exact-review-batch-workflow.test.ts @@ -600,25 +600,35 @@ test("batch publisher gives canonical supersession precedence over artifact term assert.ok(supersededTerminal > supersededReceipt && supersededTerminal < staleArtifactPlan); }); -test("exact-review producer uses direct publication with bounded legacy fallback", () => { - assert.match(sweepSource, /name: Deliver GitHub effects and prepare direct state mutation/); - assert.match(sweepSource, /records-item-number: \$\{\{ steps\.target\.outputs\.item_number \}\}/); +test("exact-review producer seals core before trusted finalization with legacy recovery", () => { + const applyStart = sweepSource.indexOf("\n event-review-apply:"); + const liveProofStart = sweepSource.indexOf("\n event-review-live-proof:", applyStart); + const finalizeStart = sweepSource.indexOf("\n event-review-finalize:", liveProofStart); + const legacyPublishStart = sweepSource.indexOf("\n event-review-publish:", finalizeStart); + const apply = sweepSource.slice(applyStart, liveProofStart); + const finalize = sweepSource.slice(finalizeStart, legacyPublishStart); + + assert.doesNotMatch(apply, /repair:exact-review-direct-publication/); + assert.doesNotMatch(apply, /uses: \.\/\.github\/actions\/setup-state/); + assert.match(finalize, /name: Deliver exact review and prepare state mutation/); assert.match( - sweepSource, - /EXACT_REVIEW_BATCH_MUTATION_OUTPUT: \.artifacts\/direct-publication-outcome\.json/, + finalize, + /records-item-number: \$\{\{ needs\.event-review-apply\.outputs\.item_number \}\}/, ); - assert.match(sweepSource, /repair:exact-review-direct-publication/); assert.match( - sweepSource, - /EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED: \$\{\{ vars\.EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED \|\| '1' \}\}/, + finalize, + /EXACT_REVIEW_BATCH_MUTATION_OUTPUT: \.artifacts\/final-publication-outcome\.json/, ); + assert.match(finalize, /repair:exact-review-direct-publication/); + assert.match(finalize, /EXACT_REVIEW_DIRECT_PUBLICATION_ENABLED: "1"/); assert.match( - sweepSource, - /name: Upload exact review artifact bundle[\s\S]*?steps\.direct-exact-review-publication\.outputs\.accepted != 'true'/, + apply, + /name: Upload exact review artifact bundle[\s\S]*?name: Export exact review generation result/, ); + assert.match(finalize, /name: Complete exact-review queue lease/); assert.match( - sweepSource, - /name: Queue durable exact review publication[\s\S]*?steps\.upload-exact-review-bundle\.outcome == 'success'/, + sweepSource.slice(legacyPublishStart), + /source_action == 'exact_review_artifact_publish'/, ); assert.match(sweepSource, /internal\/exact-review\/enqueue/); assert.match(source, /name: Claim one durable publication batch/); diff --git a/test/repair/exact-review-bundle.test.ts b/test/repair/exact-review-bundle.test.ts index f5807c87ee..5659193ec4 100644 --- a/test/repair/exact-review-bundle.test.ts +++ b/test/repair/exact-review-bundle.test.ts @@ -21,7 +21,20 @@ function fixture() { ledgerRoot, "ledger/v1/events/2026/07/15/openclaw/openclaw/events.jsonl", ); - fs.writeFileSync(report, "# Review\n\nVerified.\n"); + fs.writeFileSync( + report, + `--- +number: 42 +repository: openclaw/openclaw +type: pull_request +pull_head_sha: ${"b".repeat(40)} +--- + +# Review + +Verified. +`, + ); fs.mkdirSync(liveProofDir); fs.writeFileSync(path.join(liveProofDir, "live-verification.json"), '{"schema_version":1}\n'); fs.mkdirSync(path.dirname(ledger), { recursive: true }); @@ -37,13 +50,14 @@ function fixture() { targetRepo: "openclaw/openclaw", targetBranch: "main", itemNumber: 42, - itemKind: "issue", + itemKind: "pull_request", }), ), targetRepo: "openclaw/openclaw", targetBranch: "main", + pullHeadSha: "b".repeat(40), itemNumber: 42, - itemKind: "issue", + itemKind: "pull_request", itemKey: "openclaw/openclaw#42", protocolVersion: 2, leaseRevision: 7, @@ -106,6 +120,21 @@ test("exact review bundle rejects redirected and modified publication", () => { ); }); +test("exact review bundle rejects a report for a different pull request head", () => { + const value = fixture(); + value.context.pullHeadSha = "c".repeat(40); + assert.throws( + () => + createExactReviewBundle({ + bundleDir: value.bundleDir, + reviewPath: value.report, + createdAt: "2026-07-15T12:00:00Z", + context: value.context, + }), + /artifact identity/, + ); +}); + test("exact review bundle rejects extras and symlinks", () => { const value = fixture(); createExactReviewBundle({ @@ -178,6 +207,7 @@ test("bundle validation uses the producer workflow identity across runs", () => EXACT_REVIEW_ITEM_KIND: value.context.itemKind, EXACT_REVIEW_ITEM_NUMBER: String(value.context.itemNumber), EXACT_REVIEW_LEASE_REVISION: String(value.context.leaseRevision), + EXACT_REVIEW_PULL_HEAD_SHA: String(value.context.pullHeadSha), EXACT_REVIEW_LIVE_GUARDED_OPEN: String(value.context.liveGuardedOpen), EXACT_REVIEW_LIVE_PROCEEDED: String(value.context.liveProceeded), EXACT_REVIEW_LIVE_TERMINAL_MISSING: String(value.context.liveTerminalMissing), diff --git a/test/repair/workflow-sparse-checkout.test.ts b/test/repair/workflow-sparse-checkout.test.ts index 32d5a0897a..8ee88997ea 100644 --- a/test/repair/workflow-sparse-checkout.test.ts +++ b/test/repair/workflow-sparse-checkout.test.ts @@ -131,10 +131,18 @@ test("review jobs execute live proof before their existing artifact upload", () const workflow = parse(fs.readFileSync(".github/workflows/sweep.yml", "utf8")) as { jobs?: Record; }; - for (const jobName of ["event-review-apply", "review"]) { + for (const jobName of ["event-review-live-proof", "review"]) { const steps = workflow.jobs?.[jobName]?.steps ?? []; - const review = steps.findIndex((step) => String(step.name ?? "").startsWith("Review ")); - const execute = steps.findIndex((step) => String(step.run ?? "").includes("live-proof-review")); + const review = steps.findIndex((step) => + jobName === "event-review-live-proof" + ? step.name === "Reinspect exact live-proof plan" + : String(step.name ?? "").startsWith("Review "), + ); + const execute = steps.findIndex((step) => + jobName === "event-review-live-proof" + ? step.name === "Execute exact review live proof without workflow command files" + : String(step.run ?? "").includes("live-proof-review"), + ); const upload = steps.findIndex( (step, index) => index > execute && String(step.uses ?? "").startsWith("actions/upload-artifact@"), @@ -172,7 +180,7 @@ test("every durable review-record publication lane preserves the merged live-pro const directSetup = steps.find((step) => step.id === "direct-setup-state"); assert.match( String(directSetup?.if ?? ""), - /execute-exact-live-proof\.outputs\.produced != 'true'/, + /create-exact-review-bundle\.outputs\.direct_publication == 'true'/, site, ); continue; diff --git a/test/review-reliability-workflow.test.ts b/test/review-reliability-workflow.test.ts index 0b083e51b5..8df5c53699 100644 --- a/test/review-reliability-workflow.test.ts +++ b/test/review-reliability-workflow.test.ts @@ -107,18 +107,32 @@ test("queued workflow remediation shares the guarded dead-letter cadence", () => assert.equal(upload.with["if-no-files-found"], "ignore"); }); -test("exact review generation enters finalization before state hydration", () => { +test("exact review generation seals its core before trusted state hydration", () => { const workflow = parse(readFileSync(".github/workflows/sweep.yml", "utf8")) as Record< string, any >; - const steps = workflow.jobs["event-review-apply"].steps as Array>; - const review = steps.find((step) => step.name === "Review exact event item"); - const setupStateIndex = steps.findIndex((step) => step.uses === "./.github/actions/setup-state"); - const reviewIndex = steps.indexOf(review!); + const applySteps = workflow.jobs["event-review-apply"].steps as Array>; + const finalizeSteps = workflow.jobs["event-review-finalize"].steps as Array< + Record + >; + const reviewIndex = applySteps.findIndex((step) => step.name === "Review exact event item"); + const uploadIndex = applySteps.findIndex( + (step) => step.name === "Upload exact review artifact bundle", + ); + const validateIndex = finalizeSteps.findIndex((step) => step.name === "Validate finalizer core"); + const setupStateIndex = finalizeSteps.findIndex( + (step) => step.uses === "./.github/actions/setup-state", + ); + const deliverIndex = finalizeSteps.findIndex( + (step) => step.name === "Deliver exact review and prepare state mutation", + ); - assert.ok(review); - assert.ok(reviewIndex >= 0 && reviewIndex < setupStateIndex); - assert.match(String(review.run), /phase: "finalizing"/); - assert.match(String(review.run), /mark_finalizing \|\| review_exit_code=1/); + assert.ok(reviewIndex >= 0 && uploadIndex > reviewIndex); + assert.equal( + applySteps.some((step) => step.uses === "./.github/actions/setup-state"), + false, + ); + assert.ok(validateIndex >= 0 && setupStateIndex > validateIndex); + assert.ok(deliverIndex > setupStateIndex); }); diff --git a/test/state-writer-workflow.test.ts b/test/state-writer-workflow.test.ts index 96c88b7f8a..c4e0623828 100644 --- a/test/state-writer-workflow.test.ts +++ b/test/state-writer-workflow.test.ts @@ -46,7 +46,7 @@ test("every state hydration uses the canonical Worker with an explicit git-state [ ".github/workflows/exact-review-batch-publish.yml:publish", ".github/workflows/live-proof-maintenance.yml:retract", - ".github/workflows/sweep.yml:event-review-apply", + ".github/workflows/sweep.yml:event-review-finalize", ".github/workflows/sweep.yml:event-review-publish", ".github/workflows/sweep.yml:target-fanout", ], @@ -78,7 +78,7 @@ test("per-target state hydration is slug-scoped while fleet lanes retain discove ".github/workflows/repair-issue-implementation-backfill.yml:backfill", ".github/workflows/repair-issue-implementation-intake.yml:intake", ".github/workflows/spam-scanner.yml:scan", - ".github/workflows/sweep.yml:event-review-apply", + ".github/workflows/sweep.yml:event-review-finalize", ".github/workflows/sweep.yml:event-review-publish", ".github/workflows/sweep.yml:plan", ".github/workflows/sweep.yml:publish", diff --git a/test/sweep-workflow.test.ts b/test/sweep-workflow.test.ts index 10f2177ee4..6bfa42a6b0 100644 --- a/test/sweep-workflow.test.ts +++ b/test/sweep-workflow.test.ts @@ -94,7 +94,7 @@ test("automatic OpenClaw bug dispatch uses one gate across direct and deferred p >; }; for (const [jobName, stepName] of [ - ["event-review-apply", "Dispatch exact high-confidence bug implementation"], + ["event-review-finalize", "Dispatch exact high-confidence bug implementation"], ["event-review-publish", "Dispatch deferred high-confidence bug implementation"], ["publish", "Dispatch high-confidence bug implementation candidates"], ]) { @@ -191,6 +191,7 @@ test("ledger-producing jobs initialize immutable workflow context", () => { const workflow = readText(".github/workflows/sweep.yml"); for (const jobName of [ "event-review-apply", + "event-review-finalize", "event-review-publish", "review", "publish", @@ -292,22 +293,18 @@ test("review and apply primary boundaries ignore ledger-only failures", () => { assert.match(exactBundle.if ?? "", /review-exact-event-item\.outcome == 'success'/); assert.doesNotMatch(exactBundle.if ?? "", /action-ledger/); const exactPrimary = step("event-review-apply", "Export exact review generation result"); - const exactQueue = step("event-review-apply", "Complete exact-review queue lease"); const exactUpload = step("event-review-apply", "Upload exact review artifact bundle"); - const exactPublicationQueue = step( - "event-review-apply", - "Queue durable exact review publication", - ); - const exactSteps = job("event-review-apply").steps; + const exactQueue = step("event-review-finalize", "Complete exact-review queue lease"); assert.match(exactPrimary.run ?? "", /outcome=(?:failure|cancelled|success)/); assert.match(exactPrimary.run ?? "", /REVIEW_OUTCOME.*cancelled/); - assert.match(exactPrimary.run ?? "", /PUBLICATION_QUEUE_OUTCOME.*success/); - assert.match(exactQueue.env?.PRIMARY_OUTCOME ?? "", /exact-review-generation-result/); + assert.match(exactPrimary.run ?? "", /CORE_UPLOAD_OUTCOME.*success/); + assert.match(exactQueue.env?.APPLY_OUTCOME ?? "", /event-review-apply/); assert.doesNotMatch(exactQueue.run ?? "", /JOB_STATUS|job\.status/); - assert.ok(exactSteps.indexOf(exactUpload) < exactSteps.indexOf(exactQueue)); - assert.ok(exactSteps.indexOf(exactUpload) < exactSteps.indexOf(exactPublicationQueue)); - assert.ok(exactSteps.indexOf(exactPublicationQueue) < exactSteps.indexOf(exactQueue)); - assert.ok(exactSteps.indexOf(exactQueue) > exactSteps.indexOf(exactPrimary)); + assert.equal(exactUpload.uses, "actions/upload-artifact@v7"); + assert.equal( + job("event-review-finalize").if, + "${{ always() && needs.event-review-apply.outputs.claimed == 'true' }}", + ); assert.equal( job("event-review-publish").steps.some( (candidate) => candidate.name === "Publish exact review action ledger", @@ -719,9 +716,8 @@ test("manual review shards receive the compiler-backed runtime artifact", () => assert.doesNotMatch(reviewJob, /npm pack "@typescript/); }); -test("exact event review publishes directly with a queue-bounded canonical fallback", () => { +test("exact event review isolates live proof and trusted finalization", () => { type Step = { - "continue-on-error"?: boolean; name?: string; uses?: string; id?: string; @@ -733,752 +729,96 @@ test("exact event review publishes directly with a queue-bounded canonical fallb type Job = { needs?: string | string[]; if?: string; + "runs-on"?: string; "timeout-minutes"?: number; permissions?: Record; - concurrency?: { group?: string; "cancel-in-progress"?: boolean; queue?: string }; + outputs?: Record; steps: Step[]; }; - const source = readText(".github/workflows/sweep.yml"); - const workflow = YAML.parse(source) as { jobs: Record }; - const reviewer = workflow.jobs["event-review-apply"]!; - const publisher = workflow.jobs["event-review-publish"]!; - const batchPublisher = workflow.jobs.publish!; + const workflow = YAML.parse(readText(".github/workflows/sweep.yml")) as { + jobs: Record; + }; + const apply = workflow.jobs["event-review-apply"]!; + const liveProof = workflow.jobs["event-review-live-proof"]!; + const finalizer = workflow.jobs["event-review-finalize"]!; const step = (job: Job, name: string) => { const value = job.steps.find((candidate) => candidate.name === name); assert.ok(value, `missing step: ${name}`); return value; }; - assert.equal(reviewer.permissions?.contents, "read"); - assert.equal(reviewer["timeout-minutes"], 150); - assert.equal(reviewer.permissions?.issues, "read"); - assert.equal( - reviewer.steps.some((candidate) => candidate.uses?.endsWith("/setup-state")), - true, - ); + assert.equal(apply.permissions?.contents, "read"); + assert.equal(apply.permissions?.issues, "read"); + assert.equal(apply["timeout-minutes"], 150); assert.equal( - reviewer.steps.some( - (candidate) => candidate.name === "Publish event result and apply safe close", - ), + apply.steps.some((candidate) => candidate.name?.includes("live proof without")), false, ); - assert.equal( - step(reviewer, "Review exact event item").env?.GH_TOKEN, - "${{ steps.target-read-token.outputs.token }}", - ); - assert.equal( - step(reviewer, "Review exact event item").env?.CLAWSWEEPER_PROOF_INSPECTION_TOKEN, - "${{ steps.target-read-token.outputs.token }}", - ); - assert.equal(step(reviewer, "Review exact event item").env?.REPO_TOKEN, undefined); - assert.match(step(reviewer, "Review exact event item").run ?? "", /--skip-start-comment/); - for (const name of [ - "Install exact live-proof terminal tools", - "Install exact live-proof recording tools", + for (const removedId of [ + "direct-setup-state", + "prepare-direct-exact-review-publication", + "direct-exact-review-publication", + "finalize-direct-exact-review-lifecycle", + "queue-exact-review-publication", + "complete-exact-review-queue", ]) { - const install = step(reviewer, name).run ?? ""; - assert.match(install, /run_bounded_install\(\)/); - assert.match(install, /local install_timeout_seconds=300/); - assert.match(install, /setsid "\$@" &/); - assert.match(install, /kill -TERM -- "-\$install_pgid"/); - assert.match(install, /grace_second < install_grace_seconds/); - assert.match(install, /kill -KILL -- "-\$install_pgid"/); - assert.match(install, /run_bounded_install sudo apt-get update/); - assert.match(install, /run_bounded_install sudo apt-get install --yes/); + assert.equal( + apply.steps.some((candidate) => candidate.id === removedId), + false, + ); } - const reserveLease = step(reviewer, "Reserve exact review lease"); - assert.equal(reserveLease.env?.GH_TOKEN, "${{ steps.target-write-token.outputs.token }}"); - assert.match(reserveLease.run ?? "", /pnpm run --silent reserve-review-lease/); - assert.match(reserveLease.run ?? "", /review-timeout-ms/); - assert.match(reserveLease.run ?? "", /for attempt in 1 2 3 4 5/); - assert.match(reserveLease.run ?? "", /RANDOM % 4/); - assert.match(reserveLease.run ?? "", /status.*superseded/); - assert.match(reserveLease.run ?? "", /successful no-op/); - assert.match( - reserveLease.run ?? "", - /rate limit exceeded\|secondary rate limit\|HTTP 429/, - "throttled reservations must defer as held instead of failing", - ); - assert.match( - reserveLease.run ?? "", - /\\"status\\":\\"held\\",\\"retryAt\\":\\"\$retry_at\\",\\"retryKind\\":\\"throttle\\"/, - ); - assert.match(reserveLease.run ?? "", /reservation\.retryKind === "throttle"/); - assert.match(reserveLease.run ?? "", /append\("retry_kind", retryKind\)/); - assert.match(source, /Review exact item \{0\} rev \{1\} head \{2\}/); - assert.equal( - reserveLease.env?.EXACT_REVIEW_ITEM_KEY, - "${{ steps.claim-exact-review-queue.outputs.item_key }}", - ); - assert.equal( - reserveLease.env?.EXACT_REVIEW_CLAIM_GENERATION, - "${{ steps.claim-exact-review-queue.outputs.claim_generation }}", - ); assert.equal( - reserveLease.env?.EXACT_REVIEW_SOURCE_HEAD_SHA, + step(apply, "Create exact review artifact bundle").env?.EXACT_REVIEW_PULL_HEAD_SHA, "${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceHeadSha || '' }}", ); - const resolvePayload = step(reviewer, "Resolve event payload"); - const liveItem = step(reviewer, "Check live target item state"); - assert.match(resolvePayload.run ?? "", /maxExactReviewCodexTimeoutMs = 2_700_000/); - assert.match( - resolvePayload.run ?? "", - /Math\.min\(maxExactReviewCodexTimeoutMs, configuredValue\)/, - ); - assert.match( - resolvePayload.run ?? "", - /codex_timeout_ms: Math\.min\(\s*maxExactReviewCodexTimeoutMs/, - ); - assert.equal( - liveItem.env?.CLAIM_DECISION, - "${{ steps.claim-exact-review-queue.outputs.decision }}", - ); - assert.equal( - liveItem.env?.GH_TOKEN, - "${{ steps.target.outputs.target_repo == 'openclaw/openclaw' && github.token || steps.target-read-token.outputs.token }}", - ); - assert.match(liveItem.run ?? "", /grep -Eq '\^\[0-9\]\+\$'/); - assert.match( - liveItem.run ?? "", - /gh api "repos\/\$TARGET_REPO" --jq '\.default_branch \/\/ empty'/, - ); - assert.match(liveItem.run ?? "", /Resolved invalid queued target branch/); - assert.match(liveItem.run ?? "", /admission_retry=true/); - assert.match(liveItem.run ?? "", /echo "retry_kind=throttle"/); - assert.match( - liveItem.run ?? "", - /rate limit exceeded\|secondary rate limit\|HTTP 429/, - "a throttled live-item check must release the claim for retry instead of failing", - ); - assert.match(liveItem.run ?? "", /throttled the live-item check/); - assert.match(liveItem.run ?? "", /decision\.targetBranch = process\.env\.TARGET_BRANCH/); - assert.match(liveItem.run ?? "", /scripts\/classify-scheduled-review-noop\.ts/); - const targetToken = reviewer.steps.find((step) => step.id === "target-write-token"); - assert.match(targetToken?.if ?? "", /scheduled_semantic_noop != 'true'/); - assert.doesNotMatch(targetToken?.if ?? "", /outputs\.proceed == 'true'/); - const setupPnpm = reviewer.steps.find((step) => step.id === "setup-pnpm"); - assert.match(setupPnpm?.if ?? "", /scheduled_semantic_noop != 'true'/); - const bundle = reviewer.steps.find((step) => step.id === "create-exact-review-bundle"); - assert.match(bundle?.if ?? "", /scheduled_semantic_noop != 'true'/); - const semanticNoopResult = reviewer.steps.find( - (step) => step.id === "exact-review-generation-result", - ); - assert.match(semanticNoopResult?.run ?? "", /SCHEDULED_SEMANTIC_NOOP.*outcome=success/s); - assert.match(liveItem.run ?? "", /scheduled_noop=true/); - assert.match(liveItem.run ?? "", /Completing .* as a scheduled no-op before target checkout/); - assert.match( - step(reviewer, "Review exact event item").if ?? "", - /reserve-exact-review-lease\.outputs\.status == 'posted'/, - ); - assert.match(step(reviewer, "Review exact event item").run ?? "", /--review-lease-owner/); - assert.match(step(reviewer, "Review exact event item").run ?? "", /--review-lease-comment-id/); - assert.match(step(reviewer, "Review exact event item").run ?? "", /claim_generation/); - assert.match(step(reviewer, "Review exact event item").run ?? "", /run_attempt/); - assert.match(step(reviewer, "Review exact event item").run ?? "", /source_head_sha/); - assert.match( - step(reviewer, "Review exact event item").run ?? "", - /kill -TERM -- "-\$review_pgid"/, - ); - assert.match(step(reviewer, "Review exact event item").run ?? "", /sleep 60/); - - const create = step(reviewer, "Create exact review artifact bundle"); - const directSetupState = reviewer.steps.find( - (candidate) => candidate.id === "direct-setup-state", - ); - assert.ok(directSetupState); - assert.equal( - directSetupState.with?.["records-item-number"], - "${{ steps.target.outputs.item_number }}", - ); - const prepareDirect = step(reviewer, "Deliver GitHub effects and prepare direct state mutation"); - const postDirect = step(reviewer, "Post direct exact review publication result"); - const finalizeDirect = step(reviewer, "Finalize direct exact review lifecycle"); - const directImplementationDispatch = step( - reviewer, - "Dispatch exact high-confidence bug implementation", - ); - const upload = step(reviewer, "Upload exact review artifact bundle"); - const queuePublication = step(reviewer, "Queue durable exact review publication"); - const complete = step(reviewer, "Complete exact-review queue lease"); - const generationResult = step(reviewer, "Export exact review generation result"); - const deferHeldReview = step(reviewer, "Defer exact review while same-head lease is held"); - const failGeneration = step(reviewer, "Fail unsuccessful exact review generation"); - const releaseGeneration = step(reviewer, "Release unsuccessful workflow-owned review lease"); - assert.match(create.if ?? "", /review-exact-event-item\.outcome == 'success'/); - assert.match(create.if ?? "", /review-exact-event-item\.outputs\.retry_at == ''/); - assert.match(create.if ?? "", /review-exact-event-item\.outputs\.superseded != 'true'/); - assert.equal(create.env?.EXACT_REVIEW_PRODUCER_JOB, "event-review-apply"); - assert.equal(create.env?.EXACT_REVIEW_DECISION, "${{ steps.live-item.outputs.decision }}"); - assert.match(create.run ?? "", /mkdir -p \.artifacts/); - assert.ok( - (create.run ?? "").indexOf("mkdir -p .artifacts") < - (create.run ?? "").indexOf("exact-review-bundle create"), - ); - assert.equal(upload.uses, "actions/upload-artifact@v7"); - assert.match(prepareDirect.run ?? "", /repair:publish-event-result/); - assert.equal(prepareDirect.env?.GH_TOKEN, "${{ steps.target-write-token.outputs.token }}"); - assert.equal(prepareDirect.env?.REPO_TOKEN, "${{ github.token }}"); - assert.equal( - prepareDirect.env?.EXACT_REVIEW_BATCH_MUTATION_OUTPUT, - ".artifacts/direct-publication-outcome.json", - ); - assert.match(postDirect.run ?? "", /repair:exact-review-direct-publication/); - assert.equal( - postDirect.env?.EXACT_REVIEW_DIRECT_SOURCE_ACTION, - "${{ fromJSON(steps.claim-exact-review-queue.outputs.decision).sourceAction }}", - ); - assert.match( - finalizeDirect.if ?? "", - /direct-exact-review-publication\.outputs\.accepted == 'true'/, - ); - assert.equal(finalizeDirect.id, "finalize-direct-exact-review-lifecycle"); assert.equal( - finalizeDirect.env?.DIRECT_PUBLICATION_SUPERSEDED, - "${{ steps.direct-exact-review-publication.outputs.superseded }}", - ); - assert.match(finalizeDirect.run ?? "", /direct_lifecycle_requeue=false/); - assert.match(finalizeDirect.run ?? "", /direct_lifecycle_requeue=true/); - assert.doesNotMatch(finalizeDirect.run ?? "", /internal\/exact-review\/enqueue/); - assert.match(finalizeDirect.run ?? "", /lifecycle\/router-receipt/); - assert.match(finalizeDirect.run ?? "", /lifecycle\/terminal-disposition/); - assert.match(finalizeDirect.run ?? "", /router-direct-proof/); - assert.match(finalizeDirect.run ?? "", /lifecycle_deferred_coverage="true"/); - const directLifecycleHandoff = Math.max( - (finalizeDirect.run ?? "").indexOf("lifecycle/router-receipt"), - (finalizeDirect.run ?? "").indexOf("lifecycle/terminal-disposition"), - ); - assert.ok(directLifecycleHandoff >= 0); - assert.match( - directImplementationDispatch.run ?? "", - /dispatch-issue-implementation-candidates\.mjs/, - ); - assert.match( - directImplementationDispatch.if ?? "", - /finalize-direct-exact-review-lifecycle\.outcome == 'success'/, - ); - assert.ok( - reviewer.steps.indexOf(finalizeDirect) < reviewer.steps.indexOf(directImplementationDispatch), - ); - assert.ok(reviewer.steps.indexOf(directImplementationDispatch) < reviewer.steps.indexOf(upload)); - assert.doesNotMatch(finalizeDirect.run ?? "", /lifecycle\/command-ack\/attempt/); - assert.doesNotMatch(finalizeDirect.run ?? "", /repair:update-command-status/); - assert.match(reviewer.if ?? "", /source_action != 'exact_review_command_acknowledgement'/); - assert.match( - upload.if ?? "", - /direct-exact-review-publication\.outputs\.accepted != 'true' \|\| steps\.finalize-direct-exact-review-lifecycle\.outcome != 'success'/, - ); - assert.equal(upload.with?.["retention-days"], 90); - assert.match(queuePublication.run ?? "", /for attempt in 1 2 3/); - assert.match(queuePublication.run ?? "", /\.queued == true or \.deduped == true/); - assert.equal(queuePublication.env?.CLAIM_DECISION, "${{ steps.live-item.outputs.decision }}"); - assert.equal( - generationResult.env?.ADMISSION_RETRY, - "${{ steps.live-item.outputs.admission_retry }}", - ); - assert.match(generationResult.env?.RETRY_KIND ?? "", /live-item\.outputs\.retry_kind/); - assert.match(generationResult.env?.RETRY_AT ?? "", /live-item\.outputs\.retry_at/); - assert.equal( - generationResult.env?.DIRECT_PUBLICATION_FAILURE_KIND, - "${{ steps.prepare-direct-exact-review-publication.outputs.failure_kind }}", - ); - assert.equal( - generationResult.env?.DIRECT_PUBLICATION_RETRY_AT, - "${{ steps.prepare-direct-exact-review-publication.outputs.retry_at }}", - ); - assert.match( - generationResult.run ?? "", - /DIRECT_PUBLICATION_FAILURE_KIND.*github_rate_limit.*PUBLICATION_QUEUE_OUTCOME.*!=.*success[\s\S]*retry_kind=throttle[\s\S]*retry_at="\$DIRECT_PUBLICATION_RETRY_AT"/, - ); - assert.match(generationResult.run ?? "", /ADMISSION_RETRY.*true.*-z.*retry_kind/s); - assert.match(generationResult.run ?? "", /ADMISSION_RETRY.*true[\s\S]*outcome=success/); - assert.match(generationResult.run ?? "", /requeue_latest=true/); - assert.match(generationResult.run ?? "", /echo "retry_kind=\$retry_kind"/); - assert.match(generationResult.run ?? "", /echo "retry_at=\$retry_at"/); - const runGenerationResult = (overrides: Record) => { - const root = mkdtempSync(`${tmpPrefix}exact-review-generation-result-`); - const outputPath = join(root, "github-output"); - try { - execFileSync("bash", ["-c", generationResult.run ?? ""], { - env: { - ...process.env, - ADMISSION_RETRY: "false", - RETRY_KIND: "", - RETRY_AT: "", - DIRECT_PUBLICATION_FAILURE_KIND: "", - DIRECT_PUBLICATION_RETRY_AT: "", - TARGET_ENABLED: "true", - LIVE_OUTCOME: "success", - REVIEW_OUTCOME: "success", - REVIEW_SUPERSEDED: "false", - RESERVATION_STATUS: "", - PUBLICATION_QUEUE_OUTCOME: "failure", - DIRECT_PUBLICATION_ACCEPTED: "false", - DIRECT_PUBLICATION_SUPERSEDED: "false", - DIRECT_LIFECYCLE_OUTCOME: "failure", - DIRECT_LIFECYCLE_REQUEUE: "false", - GITHUB_OUTPUT: outputPath, - ...overrides, - }, - }); - return Object.fromEntries( - readFileSync(outputPath, "utf8") - .trim() - .split("\n") - .map((line) => { - const separator = line.indexOf("="); - return [line.slice(0, separator), line.slice(separator + 1)]; - }), - ); - } finally { - rmSync(root, { recursive: true, force: true }); - } - }; - const directRetryAt = "2026-08-06T00:00:00.000Z"; - assert.deepEqual( - runGenerationResult({ - DIRECT_PUBLICATION_FAILURE_KIND: "github_rate_limit", - DIRECT_PUBLICATION_RETRY_AT: directRetryAt, - PUBLICATION_QUEUE_OUTCOME: "success", - }), - { - outcome: "success", - requeue_latest: "false", - direct_lifecycle_requeue: "false", - retry_kind: "", - retry_at: "", - }, - ); - assert.deepEqual( - runGenerationResult({ - DIRECT_PUBLICATION_FAILURE_KIND: "github_rate_limit", - DIRECT_PUBLICATION_RETRY_AT: directRetryAt, - PUBLICATION_QUEUE_OUTCOME: "failure", - }), - { - outcome: "failure", - requeue_latest: "false", - direct_lifecycle_requeue: "false", - retry_kind: "throttle", - retry_at: directRetryAt, - }, + step(apply, "Upload exact review artifact bundle").uses, + "actions/upload-artifact@v7", ); + + assert.equal(liveProof["runs-on"], "ubuntu-latest"); + assert.deepEqual(liveProof.permissions, { actions: "read", contents: "read" }); assert.equal( - step(reviewer, "Export exact review generation result").env?.DIRECT_LIFECYCLE_OUTCOME, - "${{ steps.finalize-direct-exact-review-lifecycle.outcome }}", + liveProof.outputs?.sealed_clean, + "${{ steps.seal-live-proof-completion.outputs.sealed_clean }}", ); assert.equal( - generationResult.env?.DIRECT_LIFECYCLE_REQUEUE, - "${{ steps.finalize-direct-exact-review-lifecycle.outputs.direct_lifecycle_requeue || 'false' }}", + step(liveProof, "Download immutable exact review core").with?.["artifact-ids"], + "${{ needs.event-review-apply.outputs.core_artifact_id }}", ); assert.match( - step(reviewer, "Export exact review generation result").run ?? "", - /DIRECT_LIFECYCLE_OUTCOME.*success/s, + step(liveProof, "Execute exact review live proof without workflow command files").run ?? "", + /-u GITHUB_ENV[\s\S]*-u GITHUB_OUTPUT[\s\S]*-u GITHUB_PATH[\s\S]*-u GITHUB_STEP_SUMMARY/, ); - assert.match(generationResult.run ?? "", /direct_lifecycle_requeue=\$DIRECT_LIFECYCLE_REQUEUE/); - assert.match(complete.if ?? "", /finalize-direct-exact-review-lifecycle\.outcome == 'success'/); assert.equal( - complete.env?.DIRECT_PUBLICATION_ACCEPTED, - "${{ steps.direct-exact-review-publication.outputs.accepted }}", - ); - assert.equal( - complete.env?.DIRECT_PUBLICATION_SUPERSEDED, - "${{ steps.direct-exact-review-publication.outputs.superseded }}", - ); - assert.equal( - complete.env?.DIRECT_LIFECYCLE_OUTCOME, - "${{ steps.finalize-direct-exact-review-lifecycle.outcome }}", - ); - assert.equal( - complete.env?.DIRECT_LIFECYCLE_REQUEUE, - "${{ steps.exact-review-generation-result.outputs.direct_lifecycle_requeue }}", - ); - assert.match(complete.run ?? "", /directPublicationCompleted/); - assert.match(complete.run ?? "", /directPublicationSuperseded/); - assert.match(complete.run ?? "", /directLifecycleRequeue/); - assert.match(complete.run ?? "", /direct_lifecycle_requeue: true/); - assert.match(complete.run ?? "", /requeueLatest && directLifecycleRequeue/); - assert.match(complete.run ?? "", /completion_kind: "published"/); - assert.match(complete.run ?? "", /completion_kind: "superseded"/); - assert.match(complete.env?.PRIMARY_OUTCOME ?? "", /exact-review-generation-result/); - assert.match(complete.env?.REQUEUE_LATEST ?? "", /exact-review-generation-result/); - assert.equal( - complete.env?.RETRY_AT, - "${{ steps.exact-review-generation-result.outputs.retry_at }}", + liveProof.steps.some((candidate) => candidate.name?.includes("publication")), + false, ); - assert.equal( - complete.env?.RETRY_KIND, - "${{ steps.exact-review-generation-result.outputs.retry_kind }}", - ); - assert.match(complete.run ?? "", /retry_kind: retryKind/); - assert.match(complete.run ?? "", /requeue_latest: true/); - assert.match(deferHeldReview.if ?? "", /reserve-exact-review-lease\.outputs\.status == 'held'/); - assert.match(deferHeldReview.run ?? "", /retry deferred/); - assert.match(failGeneration.if ?? "", /reserve-exact-review-lease\.outputs\.status != 'held'/); - assert.match( - failGeneration.if ?? "", - /reserve-exact-review-lease\.outputs\.status != 'superseded'/, - ); - assert.match(failGeneration.if ?? "", /review-exact-event-item\.outputs\.superseded != 'true'/); - assert.match(failGeneration.if ?? "", /complete-exact-review-queue\.outcome != 'success'/); - assert.match( - failGeneration.if ?? "", - /exact-review-generation-result\.outputs\.retry_kind == ''/, - ); - const evaluateFailureGate = (values: Record): boolean => { - const expression = (failGeneration.if ?? "") - .replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "") - .replace(/\balways\(\)/g, "true") - .replace( - /steps\.([a-z0-9-]+)\.(outputs\.([a-z0-9_]+)|outcome)/g, - (_match, stepId: string, access: string, outputName?: string) => - JSON.stringify(values[`${stepId}.${outputName ?? access}`] ?? ""), - ); - return Boolean(Function(`"use strict"; return (${expression});`)()); - }; - const failureGateCases = [ - { - name: "typed throttle with durable completion", - values: { - "claim-exact-review-queue.claimed": "true", - "direct-exact-review-publication.accepted": "false", - "complete-exact-review-queue.outcome": "success", - "reserve-exact-review-lease.status": "", - "review-exact-event-item.superseded": "false", - "exact-review-generation-result.outcome": "failure", - "exact-review-generation-result.retry_kind": "throttle", - }, - expected: false, - }, - { - name: "typed coordination with durable completion", - values: { - "claim-exact-review-queue.claimed": "true", - "direct-exact-review-publication.accepted": "false", - "complete-exact-review-queue.outcome": "success", - "reserve-exact-review-lease.status": "held", - "review-exact-event-item.superseded": "false", - "exact-review-generation-result.outcome": "failure", - "exact-review-generation-result.retry_kind": "coordination", - }, - expected: false, - }, - { - name: "typed throttle with failed completion", - values: { - "claim-exact-review-queue.claimed": "true", - "direct-exact-review-publication.accepted": "false", - "complete-exact-review-queue.outcome": "failure", - "reserve-exact-review-lease.status": "", - "review-exact-event-item.superseded": "false", - "exact-review-generation-result.outcome": "failure", - "exact-review-generation-result.retry_kind": "throttle", - }, - expected: true, - }, - { - name: "ordinary failure after durable completion", - values: { - "claim-exact-review-queue.claimed": "true", - "direct-exact-review-publication.accepted": "false", - "complete-exact-review-queue.outcome": "success", - "reserve-exact-review-lease.status": "", - "review-exact-event-item.superseded": "false", - "exact-review-generation-result.outcome": "failure", - "exact-review-generation-result.retry_kind": "", - }, - expected: true, - }, - { - name: "superseded reservation", - values: { - "claim-exact-review-queue.claimed": "true", - "direct-exact-review-publication.accepted": "false", - "complete-exact-review-queue.outcome": "success", - "reserve-exact-review-lease.status": "superseded", - "review-exact-event-item.superseded": "false", - "exact-review-generation-result.outcome": "failure", - "exact-review-generation-result.retry_kind": "", - }, - expected: false, - }, - ] as const; - for (const failureGateCase of failureGateCases) { - assert.equal( - evaluateFailureGate(failureGateCase.values), - failureGateCase.expected, - failureGateCase.name, - ); - } - assert.match(releaseGeneration.if ?? "", /reserve-exact-review-lease\.outputs\.status != 'held'/); - assert.match(releaseGeneration.run ?? "", /content == "eyes"/); - for (const cleanup of [releaseGeneration, step(reviewer, "Mark unsuccessful re-review")]) { - for (const kind of ["github_rate_limit", "github_transient"]) { - assert.match( - cleanup.if ?? "", - new RegExp(`prepare-direct-exact-review-publication\\.outputs\\.failure_kind != '${kind}'`), - ); - } - } - assert.ok(reviewer.steps.indexOf(upload) < reviewer.steps.indexOf(complete)); - assert.equal(publisher.needs, undefined); - assert.match(publisher.if ?? "", /source_action == 'exact_review_artifact_publish'/); - assert.match( - step(publisher, "Claim durable exact review publication").run ?? "", - /internal\/exact-review\/claim/, - ); - assert.equal(publisher.concurrency, undefined); - assert.equal(publisher.permissions?.actions, "write"); - assert.equal( - batchPublisher.concurrency?.group, - "clawsweeper-target-review-publish-${{ needs.plan.outputs.target_repo }}", - ); - const publicationContext = step(publisher, "Claim durable exact review publication"); + assert.equal(finalizer["runs-on"], "ubuntu-latest"); + assert.match(finalizer.if ?? "", /always\(\)/); + assert.deepEqual(finalizer.needs, ["event-review-apply", "event-review-live-proof"]); + const materialize = step(finalizer, "Verify and materialize live-proof augmentation"); + assert.match(materialize.run ?? "", /repair:review-live-proof-augmentation materialize/); + assert.doesNotMatch(materialize.run ?? "", /\bunzip\b/); assert.match( - publicationContext.run ?? "", - /producerDecision\.commandStatusMarker \|\| producerDecision\.statusCommentId/, + step(finalizer, "Select exact review publication payload").run ?? "", + /cleanup_only_failure[\s\S]*LIVE_JOB_SEALED_CLEAN/, ); - assert.match(publicationContext.run ?? "", /directLifecycleRecovery/); - assert.match(publicationContext.run ?? "", /directLifecycleRecoveryReady/); assert.match( - publicationContext.run ?? "", - /const publicationLeaseRevision = Number\(publication\?\.leaseRevision\);/, + step(finalizer, "Deliver exact review and prepare state mutation").run ?? "", + /repair:publish-event-result/, ); - assert.match(publicationContext.run ?? "", /publicationLeaseRevision === leaseRevision/); - assert.match(publicationContext.run ?? "", /direct_lifecycle_plan/); - assert.match(publicationContext.run ?? "", /direct_lifecycle_receipt_outcome/); - assert.match(publicationContext.run ?? "", /deferredPublication/); + const complete = step(finalizer, "Complete exact-review queue lease").run ?? ""; + assert.match(complete, /published \|\| generationNoop/); + assert.match(complete, /APPLY_OUTCOME === "success" && !process\.env\.CORE_ARTIFACT_ID/); + assert.match(complete, /internal\/exact-review\/complete/); assert.match( - publicationContext.run ?? "", - /response\.item_key === directItemKey\s*&&\s*publication\?\.itemKey === directItemKey/, - ); - - const download = step(publisher, "Download exact review artifact bundle"); - const validate = step(publisher, "Validate exact review artifact bundle"); - const legacyArtifact = step(publisher, "Identify legacy tuple-less exact artifact"); - const targetWriteStep = step(publisher, "Create target write token"); - const stateSetup = publisher.steps.find((candidate) => candidate.uses?.endsWith("/setup-state")); - assert.ok(stateSetup); - assert.equal( - stateSetup.with?.["records-item-number"], - "${{ steps.publication-context.outputs.item_number }}", - ); - const publisherCheckout = publisher.steps.find( - (candidate) => candidate.uses === "actions/checkout@v7", - ); - assert.ok(publisherCheckout); - assert.equal(publisherCheckout.with?.ref, "main"); - assert.match(publisherCheckout.if ?? "", /direct_lifecycle_recovery != 'true'/); - assert.equal(download.uses, "actions/download-artifact@v8"); - assert.match(download.if ?? "", /direct_lifecycle_recovery != 'true'/); - assert.equal(download["continue-on-error"], true); - assert.equal(download.with?.name, "${{ steps.publication-context.outputs.artifact_name }}"); - assert.equal( - download.with?.["run-id"], - "${{ steps.publication-context.outputs.producer_run_id }}", - ); - assert.match(validate.run ?? "", /repair:exact-review-bundle validate/); - assert.match(validate.if ?? "", /direct_lifecycle_recovery != 'true'/); - assert.equal(validate["continue-on-error"], true); - assert.match(legacyArtifact.run ?? "", /review_lease_owner/); - assert.match(legacyArtifact.run ?? "", /review_lease_comment_id/); - assert.doesNotMatch(create.run ?? "", /repair:exact-review-bundle -- create/); - assert.doesNotMatch(validate.run ?? "", /repair:exact-review-bundle -- validate/); - assert.ok(publisher.steps.indexOf(validate) < publisher.steps.indexOf(targetWriteStep)); - assert.ok(publisher.steps.indexOf(validate) < publisher.steps.indexOf(stateSetup)); - assert.match(stateSetup.if ?? "", /legacy-exact-artifact\.outputs\.legacy_tupleless != 'true'/); - assert.match(stateSetup.if ?? "", /direct_lifecycle_recovery != 'true'/); - - const replayDirect = step(publisher, "Replay committed direct lifecycle handoff"); - assert.match(replayDirect.if ?? "", /direct_lifecycle_recovery == 'true'/); - assert.match(replayDirect.run ?? "", /router_deferred_coverage/); - assert.match(replayDirect.run ?? "", /router_not_required/); - assert.match(replayDirect.run ?? "", /repair-comment-router\.yml/); - assert.match(replayDirect.run ?? "", /lifecycle\/router-receipt/); - assert.match(replayDirect.run ?? "", /lifecycle\/terminal-disposition/); - assert.match(replayDirect.run ?? "", /direct_requeue=true/); - assert.doesNotMatch(replayDirect.run ?? "", /internal\/exact-review\/enqueue/); - assert.doesNotMatch(replayDirect.run ?? "", /repair:publish-event-result/); - assert.doesNotMatch(replayDirect.run ?? "", /repair:update-command-status/); - assert.doesNotMatch(replayDirect.run ?? "", /lifecycle\/command-ack/); - - const publish = step(publisher, "Publish event result and apply safe close"); - assert.match(publish.run ?? "", /live_state=.*gh api/); - assert.match(publish.run ?? "", /LIVE_TERMINAL_NOOP.*LIVE_TERMINAL_MISSING/); - assert.match(publish.run ?? "", /LIVE_GUARDED_OPEN/); - assert.match(publish.run ?? "", /live_locked=.*jq -r '\.locked == true'/); - assert.match(publish.run ?? "", /live_locked.*true[\s\S]*guarded_open=true/); - assert.match(publish.run ?? "", /open\)[\s\S]*?requeue_latest=true/); - assert.match(publish.run ?? "", /test -f "artifacts\/event\/\$ITEM_NUMBER\.md"/); - assert.match(publish.run ?? "", /repair:publish-event-result/); - assert.match(publish.run ?? "", /failure_kind=github_rate_limit/); - assert.match(publish.run ?? "", /failure_kind=github_transient/); - assert.match(publish.run ?? "", /HTTP 429/); - assert.doesNotMatch(publish.run ?? "", /HTTP \(403\|429\)/); - assert.match(publish.run ?? "", /PIPESTATUS\[0\]/); - assert.equal(publish.env?.EXACT_EVENT_PUBLICATION, "true"); - assert.equal( - publisher.steps.some((candidate) => candidate.name === "Route synced ClawSweeper verdict"), - false, - ); - const deferredRoute = step(publisher, "Queue deferred exact verdict router"); - assert.match(deferredRoute.if ?? "", /publish-event-result\.outcome == 'success'/); - assert.match(deferredRoute.if ?? "", /routing_deferred == 'true'/); - assert.match(deferredRoute.run ?? "", /repair-comment-router\.yml/); - assert.equal( - deferredRoute.env?.ITEM_NUMBER, - "${{ steps.publication-context.outputs.item_number }}", - ); - assert.match(deferredRoute.run ?? "", /-f item_numbers="\$ITEM_NUMBER"/); - const drift = step(publisher, "Queue fresh review after source drift"); - assert.match(drift.if ?? "", /requeue_latest == 'true'/); - assert.match(drift.if ?? "", /legacy-exact-artifact\.outputs\.legacy_tupleless == 'true'/); - assert.match(drift.run ?? "", /x-clawsweeper-exact-review-signature/); - assert.match(drift.run ?? "", /internal\/exact-review\/enqueue/); - assert.match(drift.run ?? "", /decision\.sourceAction === "failed_review_shard_recovery"/); - assert.match(drift.run ?? "", /\.queued == true or \.deduped == true or \.shed == true/); - assert.match(drift.run ?? "", /Source-drift recovery shed by exact-review queue backpressure/); - const reaction = step(publisher, "React to target item completion"); - assert.match(reaction.if ?? "", /requeue_latest != 'true'/); - assert.doesNotMatch(reaction.if ?? "", /publication-context.*live_guarded_open/); - assert.equal( - publisher.steps.some((candidate) => candidate.name === "Publish exact review action ledger"), - false, - ); - const publishResult = step(publisher, "Export exact review publication result"); - const publishComplete = step(publisher, "Complete durable exact review publication"); - const activeLeaseWaiting = step(publisher, "Mark active lease retry waiting"); - assert.equal( - publisher.steps.some( - (candidate) => candidate.name === "Probe GitHub pressure after publication failure", - ), - false, - ); - const releaseTerminal = step(publisher, "Release terminal review leases"); - const releaseUnsuccessful = step( - publisher, - "Release superseded or unsuccessful publisher-owned review lease", - ); - assert.doesNotMatch(releaseTerminal.if ?? "", /publication-context.*live_terminal_noop/); - assert.match(releaseTerminal.if ?? "", /publish-event-result.*terminal_noop/); - assert.match(releaseUnsuccessful.run ?? "", /\.user\.login == \\"clawsweeper\[bot\]\\"/); - assert.match(releaseUnsuccessful.run ?? "", /content == "eyes"/); - assert.match(releaseUnsuccessful.if ?? "", /completion_kind == 'superseded'/); - assert.doesNotMatch(releaseUnsuccessful.if ?? "", /completion_kind == 'deferred'/); - for (const kind of ["github_rate_limit", "github_transient"]) { - assert.match( - releaseUnsuccessful.if ?? "", - new RegExp(`publish-event-result\\.outputs\\.failure_kind != '${kind}'`), - ); - } - assert.match(publishResult.env?.PRIOR_JOB_STATUS ?? "", /job\.status/); - assert.match(publishResult.env?.LEGACY_TUPLELESS ?? "", /legacy-exact-artifact/); - assert.match(publishResult.env?.FAILURE_KIND ?? "", /publish-event-result/); - assert.doesNotMatch(publishResult.env?.FAILURE_KIND ?? "", /publication-pressure/); - assert.match(publishResult.env?.DOWNLOAD_OUTCOME ?? "", /download-exact-review-bundle/); - assert.match(publishResult.env?.VALIDATE_OUTCOME ?? "", /validate-exact-review-bundle/); - assert.match(publishResult.env?.PUBLISH_COMPLETION_KIND ?? "", /publish-event-result/); - assert.match(publishResult.env?.PUBLISH_RETRY_AT ?? "", /publish-event-result/); - assert.match(publishResult.env?.DIRECT_RECOVERY_OUTCOME ?? "", /replay-direct-lifecycle/); - assert.match(publishResult.env?.DIRECT_RECOVERY_DIRECT_REQUEUE ?? "", /replay-direct-lifecycle/); - assert.match(publishResult.run ?? "", /DIRECT_RECOVERY_OUTCOME/); - assert.match(publishResult.run ?? "", /direct_requeue=/); - assert.match(publishResult.run ?? "", /REQUEUE_LATEST.*SOURCE_DRIFT_OUTCOME/); - assert.match(publishResult.run ?? "", /LEGACY_TUPLELESS.*SOURCE_DRIFT_OUTCOME/); - assert.match(publishResult.run ?? "", /completion_kind=superseded/); - assert.match(publishResult.run ?? "", /completion_kind=deferred/); - assert.match(publishResult.run ?? "", /completion_kind=refresh_required/); - assert.match(publishResult.run ?? "", /reason_code=close_coverage_retry/); - assert.match(publishResult.run ?? "", /reason_code=close_coverage_deferred/); - assert.match(publishResult.run ?? "", /reason_code=review_lease_active/); - assert.match(publishResult.run ?? "", /reason_code=review_lease_active[\s\S]*?outcome=success/); - assert.match(publishResult.run ?? "", /retry_at="\$PUBLISH_RETRY_AT"/); - assert.match( - publishResult.run ?? "", - /reason_code="\$FAILURE_KIND"\s+retry_at="\$PUBLISH_RETRY_AT"/, - ); - assert.match( - publishResult.run ?? "", - /completion_kind" != "superseded".*completion_kind" != "deferred".*completion_kind" != "refresh_required".*completion_kind" != "retryable_failure"/, - ); - assert.match(publishResult.run ?? "", /reason_code=artifact_unavailable/); - assert.match(publishResult.run ?? "", /reason_code=invalid_artifact/); - assert.doesNotMatch(publishResult.run ?? "", /LIVE_TERMINAL_NOOP/); - assert.match(publishComplete.run ?? "", /internal\/exact-review\/complete/); - assert.match(publishComplete.env?.FAILURE_KIND ?? "", /exact-review-publication-result/); - assert.match(publishComplete.env?.RETRY_AT ?? "", /exact-review-publication-result/); - assert.match(publishComplete.run ?? "", /failure_kind: failureKind/); - assert.match(publishComplete.run ?? "", /completion_kind: completionKind/); - assert.match(publishComplete.run ?? "", /reason_code: reasonCode/); - assert.match(publishComplete.run ?? "", /retry_at: retryAt/); - assert.match( - publishComplete.env?.DIRECT_LIFECYCLE_REQUEUE ?? "", - /exact-review-publication-result/, - ); - assert.match(publishComplete.run ?? "", /direct_lifecycle_requeue/); - assert.ok(publisher.steps.indexOf(publishResult) < publisher.steps.indexOf(publishComplete)); - assert.ok(publisher.steps.indexOf(publishComplete) < publisher.steps.indexOf(activeLeaseWaiting)); - assert.match(activeLeaseWaiting.if ?? "", /reason_code == 'review_lease_active'/); - assert.match( - activeLeaseWaiting.if ?? "", - /complete-exact-review-publication\.outcome == 'success'/, - ); - assert.match(activeLeaseWaiting.run ?? "", /--state "Waiting"/); - - const publisherSource = readText("src/repair/publish-event-result.ts"); - assert.match( - publisherSource, - /exactEventPublication: process\.env\.EXACT_EVENT_PUBLICATION === "true"/, - ); - assert.match(publisherSource, /"--exact-event-publication"/); - assert.match(publisherSource, /legacyTuplelessReviewLease/); - assert.match(publisherSource, /activeReviewLeaseRetryAt/); - assert.match(publisherSource, /review_lease_active/); - assert.match(publisherSource, /applyDisposition === "close_coverage_deferred"/); - assert.match(publisherSource, /EXACT_REVIEW_CLOSE_COVERAGE_DEFERRED/); - assert.match(publisherSource, /writeLegacyRefreshRequiredOutputs/); - assert.match(publisherSource, /read-only apply-proof lane/); - assert.match(publisherSource, /deferredCloseCoverageExpected/); - assert.match(publisherSource, /deferredCloseCoverageExpected && !candidateMatchesCurrentTuple/); - assert.match(publisherSource, /prepareTupleMutationPlan/); - assert.match(publisherSource, /\}\) && !deferredCloseCoverage/); - assert.match(publisherSource, /writePublicationCompletionOutputs\(\s*"superseded"/); - assert.match(publisherSource, /completionKind: completionSupersededReason/); - const reviewSource = [ - readText("src/clawsweeper-runtime.ts"), - readText("src/clawsweeper-command-operations.ts"), - readText("src/clawsweeper-apply-decision-workflow.ts"), - readText("src/clawsweeper-apply-source-freshness.ts"), - ].join("\n"); - assert.match(reviewSource, /reserveReviewLeaseCommand/); - assert.match(reviewSource, /suppliedReviewStartLeaseFromArgs/); - assert.match(reviewSource, /exactEventReviewLeaseDisposition/); - assert.match(reviewSource, /retryCloseCoverageCommandStatusOnlyUpdate/); - assert.match(reviewSource, /clawsweeper-command-status:/); - assert.match(reviewSource, /CLAWSWEEPER_BOT_AUTHORS\.has/); - const completeStart = publisherSource.indexOf("const complete ="); - assert.ok(completeStart >= 0); - assert.match(publisherSource, /await postDirectPublicationResult/); - assert.match(publisherSource, /\/internal\/exact-review\/publication-batch-results/); - assert.doesNotMatch(publisherSource, /\bstagePaths\b|\bpushSingleRecordTupleCommit\b/); - assert.doesNotMatch(publisherSource, /GitCommandTimeoutError|publishRoot|hardResetToRemoteMain/); - assert.match(publisherSource, /const retryableFailure =/); - assert.match(publisherSource, /error instanceof GitHubRateLimitError/); - assert.match(publisherSource, /error\.retryAt : undefined/); - assert.match(publisherSource, /failure_kind=\$\{reasonCode\}/); - assert.match(publisherSource, /publication\.status === 429/); - assert.match(publisherSource, /\? "state_contention"\s*: "policy_invariant"/); - assert.doesNotMatch(publisherSource, /attempt <= 20|Event publish attempt/); - assert.doesNotMatch(publisherSource, /retryableFailure \? "github_transient" : undefined/); - const directPublisherSource = readText("src/repair/exact-review-direct-publication.ts"); - assert.match(directPublisherSource, /invalid_direct_source_action/); - assert.match(directPublisherSource, /router_deferred_coverage/); - assert.match(directPublisherSource, /failed_review_shard_recovery/); - assert.match(publishComplete.run ?? "", /"state_contention"/); - assert.ok( - publisherSource.indexOf("eventSnapshotMatchesCurrent(paths)", completeStart) > completeStart, + step(finalizer, "Fail exact review finalization that did not publish or requeue").if ?? "", + /core_artifact_id != '' && steps\.select-final-review\.outputs\.publish != 'true'/, ); }); + test("exact event publication derives lifecycle receipt and final command acknowledgement from the projection", () => { type Step = { name?: string; @@ -4978,9 +4318,9 @@ test("public OpenClaw reads use workflow tokens without moving mutation identity for (const [job, name, expression] of [ [ - "event-review-apply", - "Deliver GitHub effects and prepare direct state mutation", - "${{ steps.target.outputs.target_repo == 'openclaw/openclaw' && github.token || '' }}", + "event-review-finalize", + "Deliver exact review and prepare state mutation", + "${{ needs.event-review-apply.outputs.target_repo == 'openclaw/openclaw' && github.token || '' }}", ], [ "event-review-publish", @@ -4999,7 +4339,12 @@ test("public OpenClaw reads use workflow tokens without moving mutation identity ], ] as const) { const selected = find(job, name); - assert.equal(selected.env?.GH_TOKEN, "${{ steps.target-write-token.outputs.token }}"); + assert.equal( + selected.env?.GH_TOKEN, + job === "event-review-finalize" + ? "${{ steps.finalize-target-write-token.outputs.token }}" + : "${{ steps.target-write-token.outputs.token }}", + ); assert.equal(selected.env?.CLAWSWEEPER_PUBLIC_GH_TOKEN, expression); } @@ -5105,7 +4450,7 @@ test("event re-review status distinguishes lease deferral from interruptions", ( const workflow = readText(".github/workflows/sweep.yml"); const block = workflow.slice( workflow.indexOf("- name: Mark unsuccessful re-review"), - workflow.indexOf("- name: Export exact review generation result"), + workflow.indexOf("- name: Fail exact review finalization that did not publish or requeue"), ); assert.match(block, /\[ "\$REVIEW_OUTCOME" = "cancelled" \]/); @@ -6664,19 +6009,3 @@ test("github activity workflow scopes cancellation to matching item activity", ( ); assert.doesNotMatch(concurrencyBlock, /workflow-run' \|\| 'activity'/); }); - -test("exact review publication enqueue accepts a superseded acknowledgement", () => { - type WorkflowStep = { name?: string; id?: string; run?: string }; - type WorkflowJob = { steps: WorkflowStep[] }; - const workflow = YAML.parse(readText(".github/workflows/sweep.yml")) as { - jobs: Record; - }; - const publicationEnqueue = workflow.jobs["event-review-apply"]?.steps.find( - (candidate) => candidate.id === "queue-exact-review-publication", - ); - assert.ok(publicationEnqueue, "missing queue-exact-review-publication step"); - const run = publicationEnqueue.run ?? ""; - assert.match(run, /\.ok == true and \(\.queued == true or \.deduped == true\)/); - assert.match(run, /jq -e '\.superseded == true'/); - assert.match(run, /the newer publisher owns final delivery/); -});