From ea438a5269b097747491d44f576b4b4ac689508e Mon Sep 17 00:00:00 2001 From: "lightning-it-shared-assets-sync[bot]" <307342877+lightning-it-shared-assets-sync[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:49:07 +0000 Subject: [PATCH] chore: sync repository quality assets Shared-Assets-Source-SHA: 08f832156fa8eb3ab54eb4ae0ca1944056a7435f Shared-Assets-Source-Run: 32844239502 Shared-Assets-Source-Attempt: 1 Shared-Assets-Sync-App-ID: 4351516 --- .github/workflows/copilot-review-refresh.yml | 191 ++++++++++++++- .github/workflows/copilot-review.yml | 230 ++++++++++++++----- .github/workflows/current-revision-rerun.yml | 143 ++++++++++-- 3 files changed, 474 insertions(+), 90 deletions(-) diff --git a/.github/workflows/copilot-review-refresh.yml b/.github/workflows/copilot-review-refresh.yml index b5d09db..cd6c414 100644 --- a/.github/workflows/copilot-review-refresh.yml +++ b/.github/workflows/copilot-review-refresh.yml @@ -56,7 +56,6 @@ jobs: ) || ( github.event.pull_request.draft == false && - github.event.pull_request.head.repo.full_name == github.repository && ( ( github.event_name == 'pull_request_review' && @@ -101,6 +100,7 @@ jobs: EVENT_COMMENT_ID: ${{ github.event.comment.id }} EVENT_COMMENT_UPDATED_AT: ${{ github.event.comment.updated_at }} EVENT_HEAD_REF: ${{ github.event.pull_request.head.ref }} + EVENT_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} EVENT_NAME: ${{ github.event_name }} EVENT_PR_AUTHOR: ${{ github.event.pull_request.user.login }} @@ -156,19 +156,83 @@ jobs: and .base.sha == $base and .head.sha == $head and .base.repo.full_name == $repository - and .head.repo.full_name == $repository + and (.head.repo.full_name | type == "string" and length > 0) and (.base.ref == "develop" or .base.ref == "main") and (.head.ref | type == "string" and length > 0) and (.user.login | type == "string" and length > 0) + and (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") ' <<<"${pr}" >/dev/null PR_AUTHOR="$(jq -er .user.login <<<"${pr}")" PR_BASE_REF="$(jq -er .base.ref <<<"${pr}")" PR_HEAD_REF="$(jq -er .head.ref <<<"${pr}")" + PR_HEAD_REPOSITORY="$(jq -er \ + '.head.repo.full_name | select(type == "string" and length > 0)' \ + <<<"${pr}")" [[ "${PR_BASE_REF}" =~ ^(develop|main)$ ]] + if [ "${PR_AUTHOR}" = 'lightning-it-release-automation[bot]' ]; then + echo "Release-App PRs use only the protected Exact-Revision Codex path." + exit 0 + fi + labels_sha256_from_pr() { + local labels_json pr_json="$1" + if ! jq -e \ + --arg base "${BASE_SHA}" \ + --arg base_ref "${PR_BASE_REF}" \ + --arg head "${HEAD_SHA}" \ + --arg head_repository "${PR_HEAD_REPOSITORY}" \ + --arg repository "${REPOSITORY}" ' + .state == "open" + and .draft == false + and .base.ref == $base_ref + and .base.sha == $base + and .head.sha == $head + and .base.repo.full_name == $repository + and .head.repo.full_name == $head_repository + and (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") + ' <<<"${pr_json}" >/dev/null; then + return 1 + fi + labels_json="$(jq -cer '[.labels[].name] | sort' <<<"${pr_json}")" || return 1 + printf '%s' "${labels_json}" | sha256sum | cut -d ' ' -f 1 + } + read_metadata_revision() { + local owner="${REPOSITORY%%/*}" repository="${REPOSITORY#*/}" + local query response + read -r -d '' query <<'GRAPHQL' || true + query($owner: String!, $repository: String!, $number: Int!) { + repository(owner: $owner, name: $repository) { + pullRequest(number: $number) { number lastEditedAt } + } + } + GRAPHQL + response="$(gh api graphql \ + -f query="${query}" \ + -F owner="${owner}" \ + -F repository="${repository}" \ + -F number="${PR_NUMBER}")" + jq -er \ + --argjson number "${PR_NUMBER}" ' + select(((.errors // []) | length) == 0) + | select(.data.repository.pullRequest.number == $number) + | .data.repository.pullRequest.lastEditedAt + | if . == null then "null" + elif type == "string" + and test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$") + then . else error("malformed lastEditedAt") end + ' <<<"${response}" + } + PR_LABELS_SHA256="$(labels_sha256_from_pr "${pr}")" + [[ "${PR_LABELS_SHA256}" =~ ^[0-9a-f]{64}$ ]] + PR_LAST_EDITED_AT="$(read_metadata_revision)" if [ "${EVENT_NAME}" != workflow_dispatch ]; then test "${EVENT_PR_AUTHOR}" = "${PR_AUTHOR}" test "${EVENT_PR_BASE_REF}" = "${PR_BASE_REF}" test "${EVENT_HEAD_REF}" = "${PR_HEAD_REF}" + test "${EVENT_HEAD_REPOSITORY}" = "${PR_HEAD_REPOSITORY}" fi runs_url="repos/${REPOSITORY}/actions/runs" refresh_url="${GITHUB_SERVER_URL}/${REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" @@ -276,6 +340,86 @@ jobs: if [ "${neutral_count}" -eq 1 ]; then check_id="$(jq -er '.[0].id | select(type == "number" and . > 0)' <<<"${neutral}")" check_url="${GITHUB_SERVER_URL}/${REPOSITORY}/runs/${check_id}" + validate_neutral_producer() { + local external_id evidence_version kind producer producer_id + local producer_jobs producer_jobs_pages producer_run_attempt producer_url + local review_id review_pages + external_id="$(jq -er '.[0].external_id | select(type == "string")' <<<"${neutral}")" + producer_id="$(jq -er '.[0].output.summary | fromjson | .producer_run_id | select(type == "number" and . > 0)' <<<"${neutral}")" + producer_url="$(jq -er '.[0].output.summary | fromjson | .run_url | select(type == "string" and length > 0)' <<<"${neutral}")" + if [[ "${external_id}" =~ ^mlx90-current-revision:(copilot|managed-sync|ancestry-backmerge):v6:${PR_NUMBER}:([1-9][0-9]*):${BASE_SHA}:${HEAD_SHA}$ ]]; then + evidence_version=v6 + kind="${BASH_REMATCH[1]}" + test "${BASH_REMATCH[2]}" = "${producer_id}" + elif [[ "${external_id}" =~ ^mlx90-current-revision:(copilot|ancestry-backmerge):v5:([1-9][0-9]*):${BASE_SHA}:${HEAD_SHA}$ ]]; then + evidence_version=v5 + kind="${BASH_REMATCH[1]}" + test "${BASH_REMATCH[2]}" = "${producer_id}" + else + return 1 + fi + test "${producer_url}" = "${GITHUB_SERVER_URL}/${REPOSITORY}/actions/runs/${producer_id}" + producer="$(gh api "repos/${REPOSITORY}/actions/runs/${producer_id}")" || return 1 + jq -e \ + --arg base_ref "${PR_BASE_REF}" \ + --arg base_sha "${BASE_SHA}" \ + --arg head_ref "${PR_HEAD_REF}" \ + --arg head_repository "${PR_HEAD_REPOSITORY}" \ + --arg head_sha "${HEAD_SHA}" \ + --arg repository "${REPOSITORY}" \ + --arg url "${producer_url}" \ + --argjson pr "${PR_NUMBER}" ' + .event == "pull_request_target" + and .path == ".github/workflows/copilot-review.yml" + and .name == "Current revision review gate" + and .repository.full_name == $repository + and .head_repository.full_name == $head_repository + and .head_branch == $head_ref and .head_sha == $head_sha + and .html_url == $url and .status == "completed" + and .conclusion == "success" + and (.run_attempt | type) == "number" + and .run_attempt >= 1 and .run_attempt <= 2 + and (.pull_requests | length) == 1 + and .pull_requests[0].number == $pr + and .pull_requests[0].base.ref == $base_ref + and .pull_requests[0].base.sha == $base_sha + and .pull_requests[0].head.ref == $head_ref + and .pull_requests[0].head.sha == $head_sha + ' <<<"${producer}" >/dev/null || return 1 + producer_run_attempt="$(jq -er '.run_attempt | select(type == "number")' <<<"${producer}")" + producer_jobs_pages="$(gh api --paginate --slurp \ + "repos/${REPOSITORY}/actions/runs/${producer_id}/jobs?filter=all&per_page=100")" || return 1 + producer_jobs="$(jq -c --argjson attempt "${producer_run_attempt}" ' + [.[].jobs[]? | select(.name == "Verify current revision policy") | + select(.run_attempt == $attempt and .status == "completed" + and .conclusion == "success")] + ' <<<"${producer_jobs_pages}")" || return 1 + test "$(jq 'length' <<<"${producer_jobs}")" -eq 1 + test "$(jq '[.[0].steps[]? | select(.name == "Publish bound neutral result" + and .conclusion == "success")] | length' <<<"${producer_jobs}")" -eq 1 + if [ "${kind}" = copilot ]; then + review_pages="$(gh api --paginate --slurp \ + "repos/${REPOSITORY}/pulls/${PR_NUMBER}/reviews?per_page=100")" || return 1 + if [ "${evidence_version}" = v6 ]; then + review_id="$(jq -er '.[0].output.summary | fromjson | .review_id | select(type == "string" and length > 0)' <<<"${neutral}")" || return 1 + jq -e --arg head "${HEAD_SHA}" --arg review_id "${review_id}" ' + [add[]? | select(.node_id == $review_id)] as $reviews | + ($reviews | length) == 1 and $reviews[0].commit_id == $head + and ($reviews[0].user.login == "copilot-pull-request-reviewer" + or $reviews[0].user.login == "copilot-pull-request-reviewer[bot]") + and ($reviews[0].state == "COMMENTED" or $reviews[0].state == "APPROVED") + ' <<<"${review_pages}" >/dev/null || return 1 + else + jq -e --arg head "${HEAD_SHA}" ' + [add[]? | select(.commit_id == $head) | + select(.user.login == "copilot-pull-request-reviewer" + or .user.login == "copilot-pull-request-reviewer[bot]")] as $reviews | + ($reviews | length) == 1 + and ($reviews[0].state == "COMMENTED" or $reviews[0].state == "APPROVED") + ' <<<"${review_pages}" >/dev/null || return 1 + fi + fi + } # Managed distribution has its own evidence kind. The sync App may # never satisfy the Copilot branch, and inside lightning-it/.github # it is accepted only as ancestry-backmerge. @@ -283,6 +427,9 @@ jobs: --arg author "${PR_AUTHOR}" \ --arg base "${BASE_SHA}" \ --arg head "${HEAD_SHA}" \ + --arg head_repository "${PR_HEAD_REPOSITORY}" \ + --arg labels_sha256 "${PR_LABELS_SHA256}" \ + --arg last_edited_at "${PR_LAST_EDITED_AT}" \ --arg pr "${PR_NUMBER}" \ --arg base_ref "${PR_BASE_REF}" \ --arg repository "${REPOSITORY}" \ @@ -315,12 +462,20 @@ jobs: + ":[1-9][0-9]*:" + $base + ":" + $head + "$"))) or (($repository == "lightning-it/.github" - and $author == "lightning-it-shared-assets-sync[bot]") + and $author == "lightning-it-shared-assets-sync[bot]" + and $base_ref == "develop") and ($check.external_id | test("^mlx90-current-revision:ancestry-backmerge:v6:" + $pr + ":[1-9][0-9]*:" + $base + ":" + $head + "$"))) ) and $summary.pull_request_number == $pr_number + and $summary.head_repository == $head_repository + and $summary.controller_sha == $base + and $summary.controller_ref == $base_ref + and $summary.pull_request_labels_sha256 == $labels_sha256 + and ($summary | has("pull_request_last_edited_at")) + and $summary.pull_request_last_edited_at == ( + if $last_edited_at == "null" then null else $last_edited_at end) ) or ( @@ -336,15 +491,12 @@ jobs: and ($check.external_id | test("^mlx90-current-revision:ancestry-backmerge:v5:" + "[1-9][0-9]*:" + $base + ":" + $head + "$"))) - or - ($author == "lightning-it-release-automation[bot]" - and ($check.external_id | - test("^mlx90-current-revision:v4:[1-9][0-9]*:[0-9a-f]{64}$"))) ) and $summary.pull_request_number == $pr_number ) ) - ' <<<"${neutral}" >/dev/null; then + ' <<<"${neutral}" >/dev/null \ + || ! validate_neutral_producer; then echo "Existing neutral result is stale or malformed; canonical rerun required." evidence="$(jq -cn --arg base "${BASE_SHA}" --arg head "${HEAD_SHA}" \ '{schema:4,base_sha:$base,head_sha:$head, @@ -410,6 +562,9 @@ jobs: echo "Current-head Copilot findings invalidate the neutral PASS." >&2 exit 1 fi + current_pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + test "$(labels_sha256_from_pr "${current_pr}")" = "${PR_LABELS_SHA256}" + test "$(read_metadata_revision)" = "${PR_LAST_EDITED_AT}" echo "The exact current-head neutral PASS remains valid; no rerun is needed." exit 0 fi @@ -666,7 +821,13 @@ jobs: fi jq -e \ --arg author "${PR_AUTHOR}" \ - --arg head "${HEAD_SHA}" ' + --arg base_ref "${PR_BASE_REF}" \ + --arg base_sha "${BASE_SHA}" \ + --arg head "${HEAD_SHA}" \ + --arg head_ref "${PR_HEAD_REF}" \ + --arg head_repository "${PR_HEAD_REPOSITORY}" \ + --arg repository "${REPOSITORY}" \ + --argjson pr "${PR_NUMBER}" ' .event == "pull_request_target" and .run_attempt == 1 and .status == "completed" @@ -674,6 +835,15 @@ jobs: and .path == ".github/workflows/copilot-review.yml" and .name == "Current revision review gate" and .head_sha == $head + and .head_branch == $head_ref + and .repository.full_name == $repository + and .head_repository.full_name == $head_repository + and (.pull_requests | length) == 1 + and .pull_requests[0].number == $pr + and .pull_requests[0].base.ref == $base_ref + and .pull_requests[0].base.sha == $base_sha + and .pull_requests[0].head.ref == $head_ref + and .pull_requests[0].head.sha == $head and .actor.login == $author and .triggering_actor.login == $author ' <<<"${run}" >/dev/null @@ -801,6 +971,9 @@ jobs: -f conclusion=success \ -f 'output[title]=Late review rerun authorized' \ -f "output[summary]=${authorization_summary}" >/dev/null + current_pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + test "$(labels_sha256_from_pr "${current_pr}")" = "${PR_LABELS_SHA256}" + test "$(read_metadata_revision)" = "${PR_LAST_EDITED_AT}" gh api --method POST \ "repos/${REPOSITORY}/actions/jobs/${verifier_job_id}/rerun" >/dev/null trap - EXIT diff --git a/.github/workflows/copilot-review.yml b/.github/workflows/copilot-review.yml index 3157893..943842d 100644 --- a/.github/workflows/copilot-review.yml +++ b/.github/workflows/copilot-review.yml @@ -9,7 +9,7 @@ name: Current revision review gate on: pull_request_target: - types: [opened, synchronize, reopened, ready_for_review, edited] + types: [opened, synchronize, reopened, ready_for_review, edited, labeled, unlabeled] permissions: contents: read @@ -60,6 +60,7 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} EXPECTED_BASE: ${{ github.event.pull_request.base.sha }} + EXPECTED_BASE_REF: ${{ github.event.pull_request.base.ref }} EXPECTED_HEAD: ${{ github.event.pull_request.head.sha }} EXPECTED_HEAD_REF: ${{ github.event.pull_request.head.ref }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} @@ -72,17 +73,10 @@ jobs: [[ "${EXPECTED_HEAD}" =~ ^[0-9a-f]{40}$ ]] [[ "${TRUSTED_WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ ]] test "${DEFAULT_BRANCH}" = develop + [[ "${EXPECTED_BASE_REF}" =~ ^(develop|main)$ ]] test "${TRUSTED_WORKFLOW_REF}" = \ - "${REPOSITORY}/.github/workflows/copilot-review.yml@refs/heads/${DEFAULT_BRANCH}" - default_head="$(gh api "repos/${REPOSITORY}/branches/${DEFAULT_BRANCH}" --jq .commit.sha)" - controller_ancestry="$(gh api \ - "repos/${REPOSITORY}/compare/${TRUSTED_WORKFLOW_SHA}...${default_head}")" - jq -e \ - --arg controller "${TRUSTED_WORKFLOW_SHA}" ' - .status == "identical" - or (.status == "ahead" and .behind_by == 0 - and .merge_base_commit.sha == $controller) - ' <<<"${controller_ancestry}" >/dev/null + "${REPOSITORY}/.github/workflows/copilot-review.yml@refs/heads/${EXPECTED_BASE_REF}" + test "${TRUSTED_WORKFLOW_SHA}" = "${EXPECTED_BASE}" protected_run="$(gh api "repos/${REPOSITORY}/actions/runs/${GITHUB_RUN_ID}")" jq -e \ --arg branch "${EXPECTED_HEAD_REF}" \ @@ -100,7 +94,7 @@ jobs: test "$(jq -r .state <<<"${pr}")" = open test "$(jq -r .draft <<<"${pr}")" = false base_ref="$(jq -er '.base.ref | select(. == "develop" or . == "main")' <<<"${pr}")" - test -n "${base_ref}" + test "${base_ref}" = "${EXPECTED_BASE_REF}" test "$(jq -r .base.sha <<<"${pr}")" = "${EXPECTED_BASE}" test "$(jq -r .base.repo.full_name <<<"${pr}")" = "${REPOSITORY}" test "$(jq -r .head.sha <<<"${pr}")" = "${EXPECTED_HEAD}" @@ -129,6 +123,7 @@ jobs: any(add[]; .user.login == $reviewer and .commit_id == $head + and (.state == "COMMENTED" or .state == "APPROVED") and (((.body // "") | ascii_downcase) as $body | ($body | contains($retry_unable) | not) and ($body | contains($retry_no_files) | not) @@ -198,9 +193,10 @@ jobs: if: >- github.event_name == 'pull_request_target' && github.event.pull_request.draft == false && - github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.user.login != 'lightning-it-release-automation[bot]' && (github.event.action == 'edited' || + github.event.action == 'labeled' || + github.event.action == 'unlabeled' || github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || @@ -221,17 +217,23 @@ jobs: timeout-minutes: 30 steps: - - name: Invalidate prior result after pull-request metadata edit - if: github.event.action == 'edited' + - name: Invalidate prior result after pull-request metadata change + if: >- + github.event.action == 'edited' || + github.event.action == 'labeled' || + github.event.action == 'unlabeled' env: + EVENT_ACTION: ${{ github.event.action }} BASE_SHA: ${{ github.event.pull_request.base.sha }} EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }} EVENT_BODY: ${{ github.event.pull_request.body }} EVENT_TITLE: ${{ github.event.pull_request.title }} EVENT_EDITED_AT: ${{ github.event.pull_request.updated_at }} + EVENT_LABEL: ${{ github.event.label.name }} GH_TOKEN: ${{ github.token }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} HEAD_REF: ${{ github.event.pull_request.head.ref }} + HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} PR_NUMBER: ${{ github.event.pull_request.number }} REPOSITORY: ${{ github.repository }} run: | @@ -267,13 +269,16 @@ jobs: [[ "${BASE_SHA}" =~ ^[0-9a-f]{40}$ ]] [[ "${HEAD_SHA}" =~ ^[0-9a-f]{40}$ ]] [[ "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] - # For an edited webhook, REST updated_at is the event's edit - # revision. Bind it to GraphQL lastEditedAt because unrelated - # comments and reviews may advance only the live REST updated_at. - [[ "${EVENT_EDITED_AT}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] - owner="${REPOSITORY%%/*}" - repository="${REPOSITORY#*/}" - read -r -d '' query <<'GRAPHQL' || true + [[ "${HEAD_REPOSITORY}" == */* ]] + [[ "${EVENT_ACTION}" =~ ^(edited|labeled|unlabeled)$ ]] + if [ "${EVENT_ACTION}" = edited ]; then + # For an edited webhook, REST updated_at is the event's edit + # revision. Bind it to GraphQL lastEditedAt because unrelated + # comments and reviews may advance only the live REST updated_at. + [[ "${EVENT_EDITED_AT}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] + owner="${REPOSITORY%%/*}" + repository="${REPOSITORY#*/}" + read -r -d '' query <<'GRAPHQL' || true query($owner: String!, $repository: String!, $number: Int!) { repository(owner: $owner, name: $repository) { pullRequest(number: $number) { @@ -283,36 +288,68 @@ jobs: } } GRAPHQL - metadata="$(api_read graphql \ - -f query="${query}" \ - -F owner="${owner}" \ - -F repository="${repository}" \ - -F number="${PR_NUMBER}")" - jq -e \ - --arg edited_at "${EVENT_EDITED_AT}" \ - --argjson number "${PR_NUMBER}" ' - ((.errors // []) | length) == 0 - and .data.repository.pullRequest.number == $number - and .data.repository.pullRequest.lastEditedAt == $edited_at - ' <<<"${metadata}" >/dev/null - pr="$(api_read "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + metadata="$(api_read graphql \ + -f query="${query}" \ + -F owner="${owner}" \ + -F repository="${repository}" \ + -F number="${PR_NUMBER}")" + jq -e \ + --arg edited_at "${EVENT_EDITED_AT}" \ + --argjson number "${PR_NUMBER}" ' + ((.errors // []) | length) == 0 + and .data.repository.pullRequest.number == $number + and .data.repository.pullRequest.lastEditedAt == $edited_at + ' <<<"${metadata}" >/dev/null + pr="$(api_read "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + else + test -n "${EVENT_LABEL}" + transition_observed=false + for transition_attempt in $(seq 1 10); do + pr="$(api_read "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + if jq -e --arg action "${EVENT_ACTION}" --arg label "${EVENT_LABEL}" ' + (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") + and (if $action == "labeled" then + any(.labels[]; .name == $label) + else all(.labels[]; .name != $label) end) + ' <<<"${pr}" >/dev/null; then + transition_observed=true + break + fi + test "${transition_attempt}" -lt 10 + sleep 2 + done + test "${transition_observed}" = true + fi jq -e \ + --arg action "${EVENT_ACTION}" \ --arg base_ref "${EVENT_BASE_REF}" \ --arg base_sha "${BASE_SHA}" \ --arg body "${EVENT_BODY}" \ --arg head_ref "${HEAD_REF}" \ + --arg head_repository "${HEAD_REPOSITORY}" \ --arg head_sha "${HEAD_SHA}" \ - --arg repository "${REPOSITORY}" \ + --arg label "${EVENT_LABEL}" \ --arg title "${EVENT_TITLE}" ' .state == "open" and .draft == false - and .title == $title - and (.body // "") == $body + and (if $action == "edited" then + .title == $title and (.body // "") == $body + else true end) and .base.ref == $base_ref and .base.sha == $base_sha and .head.ref == $head_ref and .head.sha == $head_sha - and .head.repo.full_name == $repository + and .head.repo.full_name == $head_repository + and (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") + and (if $action == "labeled" then + any(.labels[]; .name == $label) + elif $action == "unlabeled" then + all(.labels[]; .name != $label) + else true end) ' <<<"${pr}" >/dev/null checks="$(api_read --paginate --slurp \ "repos/${REPOSITORY}/commits/${HEAD_SHA}/check-runs?check_name=Current%20revision%20review&filter=all&per_page=100")" @@ -332,10 +369,19 @@ jobs: fi check_id="$(jq -er '.[0].id | select(type == "number" and . > 0)' <<<"${named}")" run_url="${GITHUB_SERVER_URL}/${REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + if [ "${EVENT_ACTION}" = edited ]; then + reason='pull-request title or body changed' + result_title='Current revision metadata changed; revalidation required' + reservation_kind=edit + else + reason="pull-request labels changed (${EVENT_ACTION}: ${EVENT_LABEL})" + result_title='Current revision labels changed; revalidation required' + reservation_kind="${EVENT_ACTION}" + fi evidence="$(jq -cn \ --arg base "${BASE_SHA}" \ --arg head "${HEAD_SHA}" \ - --arg reason "pull-request metadata changed" \ + --arg reason "${reason}" \ --argjson pr_number "${PR_NUMBER}" \ --argjson run_id "${GITHUB_RUN_ID}" \ --arg run_url "${run_url}" \ @@ -348,8 +394,8 @@ jobs: -f conclusion=failure \ -f "completed_at=${completed_at}" \ -f "details_url=${run_url}" \ - -f "external_id=mlx90-current-revision:metadata-edit:v1:${PR_NUMBER}:${GITHUB_RUN_ID}:${BASE_SHA}:${HEAD_SHA}" \ - -f "output[title]=Current revision metadata changed; revalidation required" \ + -f "external_id=mlx90-current-revision:metadata-${reservation_kind}:v1:${PR_NUMBER}:${GITHUB_RUN_ID}:${BASE_SHA}:${HEAD_SHA}" \ + -f "output[title]=${result_title}" \ -f "output[summary]=${evidence}")" jq -e \ --arg evidence "${evidence}" \ @@ -416,7 +462,18 @@ jobs: last_edited_at="$(jq -r \ '.data.repository.pullRequest.lastEditedAt // "null"' \ <<<"${response}")" + live_pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + labels_json="$(jq -cer ' + if (.labels | type) == "array" + and all(.labels[]; type == "object" and (.name | type) == "string") + then [.labels[].name] | sort + else error("malformed pull-request labels") + end + ' <<<"${live_pr}")" + labels_sha256="$(printf '%s' "${labels_json}" | sha256sum | cut -d ' ' -f 1)" + [[ "${labels_sha256}" =~ ^[0-9a-f]{64}$ ]] echo "last_edited_at=${last_edited_at}" >>"${GITHUB_OUTPUT}" + echo "labels_sha256=${labels_sha256}" >>"${GITHUB_OUTPUT}" - name: Classify trusted automation pull request id: trusted-automation @@ -647,6 +704,7 @@ jobs: echo "Verified an exact evidence-bound merge of current develop and main tips." - name: Verify current Copilot review and resolved findings + id: copilot-review if: >- steps.trusted-automation.outputs.trusted != 'true' && github.event.pull_request.user.login != 'lightning-it-release-automation[bot]' @@ -1049,15 +1107,20 @@ jobs: exit 1 fi + [[ "${review_id}" =~ ^[A-Za-z0-9_=-]+$ ]] + echo "review_id=${review_id}" >>"${GITHUB_OUTPUT}" echo "GitHub Copilot reviewed current head ${head_sha}; no unresolved Copilot findings remain." - name: Publish bound neutral result env: BOUND_LAST_EDITED_AT: ${{ steps.metadata-revision.outputs.last_edited_at }} + BOUND_LABELS_SHA256: ${{ steps.metadata-revision.outputs.labels_sha256 }} + BOUND_REVIEW_ID: ${{ steps.copilot-review.outputs.review_id }} EVENT_ACTION: ${{ github.event.action }} GH_TOKEN: ${{ github.token }} EVENT_HEAD: ${{ github.event.pull_request.head.sha }} EVENT_HEAD_REF: ${{ github.event.pull_request.head.ref }} + EVENT_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} EVENT_BASE: ${{ github.event.pull_request.base.sha }} EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }} EVENT_BODY: ${{ github.event.pull_request.body }} @@ -1143,28 +1206,52 @@ jobs: sleep 5 done } + read_labels_sha256() { + local labels_json live_pr + live_pr="$(api_read "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" || return 1 + labels_json="$(jq -cer ' + if (.labels | type) == "array" + and all(.labels[]; type == "object" and (.name | type) == "string") + then [.labels[].name] | sort + else error("malformed pull-request labels") + end + ' <<<"${live_pr}")" || return 1 + printf '%s' "${labels_json}" | sha256sum | cut -d ' ' -f 1 + } + validate_bound_review() { + local reviews + [[ "${BOUND_REVIEW_ID}" =~ ^[A-Za-z0-9_=-]+$ ]] || return 1 + reviews="$(api_read --paginate --slurp \ + "repos/${REPOSITORY}/pulls/${PR_NUMBER}/reviews?per_page=100")" || return 1 + jq -e --arg head "${EVENT_HEAD}" --arg review_id "${BOUND_REVIEW_ID}" ' + [add[]? | select(.node_id == $review_id)] as $reviews | + ($reviews | length) == 1 + and $reviews[0].commit_id == $head + and ($reviews[0].user.login == "copilot-pull-request-reviewer" + or $reviews[0].user.login == "copilot-pull-request-reviewer[bot]") + and ($reviews[0].state == "COMMENTED" or $reviews[0].state == "APPROVED") + ' <<<"${reviews}" >/dev/null + } [[ "${EVENT_BASE}" =~ ^[0-9a-f]{40}$ ]] [[ "${EVENT_HEAD}" =~ ^[0-9a-f]{40}$ ]] [[ "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] [[ "${TRUSTED_WORKFLOW_SHA}" =~ ^[0-9a-f]{40}$ ]] + [[ "${BOUND_LABELS_SHA256}" =~ ^[0-9a-f]{64}$ ]] + [[ "${EVENT_HEAD_REPOSITORY}" == */* ]] [[ "${BOUND_LAST_EDITED_AT}" == null \ || "${BOUND_LAST_EDITED_AT}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] test "$(read_metadata_revision)" = "${BOUND_LAST_EDITED_AT}" + test "$(read_labels_sha256)" = "${BOUND_LABELS_SHA256}" + [[ "${EVENT_BASE_REF}" =~ ^(develop|main)$ ]] test "${DEFAULT_BRANCH}" = develop test "${TRUSTED_WORKFLOW_REF}" = \ - "${REPOSITORY}/.github/workflows/copilot-review.yml@refs/heads/${DEFAULT_BRANCH}" + "${REPOSITORY}/.github/workflows/copilot-review.yml@refs/heads/${EVENT_BASE_REF}" + test "${TRUSTED_WORKFLOW_SHA}" = "${EVENT_BASE}" default_head="$(gh api "repos/${REPOSITORY}/branches/${DEFAULT_BRANCH}" --jq .commit.sha)" - controller_ancestry="$(gh api \ - "repos/${REPOSITORY}/compare/${TRUSTED_WORKFLOW_SHA}...${default_head}")" - jq -e \ - --arg controller "${TRUSTED_WORKFLOW_SHA}" ' - .status == "identical" - or (.status == "ahead" and .behind_by == 0 - and .merge_base_commit.sha == $controller) - ' <<<"${controller_ancestry}" >/dev/null protected_run="$(gh api "repos/${REPOSITORY}/actions/runs/${GITHUB_RUN_ID}")" jq -e \ --arg branch "${EVENT_HEAD_REF}" \ + --arg head_repository "${EVENT_HEAD_REPOSITORY}" \ --arg repository "${REPOSITORY}" \ --arg sha "${EVENT_HEAD}" ' .event == "pull_request_target" @@ -1173,7 +1260,7 @@ jobs: and .head_branch == $branch and .head_sha == $sha and .repository.full_name == $repository - and .head_repository.full_name == $repository + and .head_repository.full_name == $head_repository ' <<<"${protected_run}" >/dev/null pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" jq -e \ @@ -1181,6 +1268,7 @@ jobs: --arg base_sha "${EVENT_BASE}" \ --arg body "${EVENT_BODY}" \ --arg head_ref "${EVENT_HEAD_REF}" \ + --arg head_repository "${EVENT_HEAD_REPOSITORY}" \ --arg head_sha "${EVENT_HEAD}" \ --arg repository "${REPOSITORY}" \ --arg title "${EVENT_TITLE}" ' @@ -1192,7 +1280,7 @@ jobs: and .base.sha == $base_sha and .head.ref == $head_ref and .head.sha == $head_sha - and .head.repo.full_name == $repository + and .head.repo.full_name == $head_repository ' <<<"${pr}" >/dev/null base_ref="$(jq -er '.base.ref | select(. == "develop" or . == "main")' <<<"${pr}")" test -n "${base_ref}" @@ -1266,20 +1354,32 @@ jobs: test "${author}" != 'lightning-it-shared-assets-sync[bot]' test "${author}" != 'lightning-it-release-automation[bot]' fi + if [ "${external_kind}" = copilot ]; then + validate_bound_review + else + test -z "${BOUND_REVIEW_ID}" + fi run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" evidence="$(jq -cn \ --arg base "${EVENT_BASE}" \ --arg controller "${TRUSTED_WORKFLOW_SHA}" \ + --arg controller_ref "${EVENT_BASE_REF}" \ --arg head "${EVENT_HEAD}" \ + --arg head_repository "${EVENT_HEAD_REPOSITORY}" \ --arg last_edited_at "${BOUND_LAST_EDITED_AT}" \ + --arg labels_sha256 "${BOUND_LABELS_SHA256}" \ + --arg review_id "${BOUND_REVIEW_ID}" \ --arg review_path "${review_path}" \ --argjson pr_number "${PR_NUMBER}" \ --argjson run_id "${GITHUB_RUN_ID}" \ --arg run_url "${run_url}" \ - '{schema:4,base_sha:$base,head_sha:$head,controller_sha:$controller, + '{schema:4,base_sha:$base,head_sha:$head,head_repository:$head_repository, + controller_sha:$controller,controller_ref:$controller_ref, pull_request_number:$pr_number,producer_run_id:$run_id, pull_request_last_edited_at:( if $last_edited_at == "null" then null else $last_edited_at end), + pull_request_labels_sha256:$labels_sha256, + review_id:(if $review_id == "" then null else $review_id end), review_path:$review_path,run_url:$run_url}')" publish_once() { local check_name="$1" external_id="$2" title="$3" @@ -1302,8 +1402,12 @@ jobs: exit 1 fi if [ "${count}" -eq 1 ]; then - if [ "${EVENT_ACTION}" = edited ]; then - expected_reservation="mlx90-current-revision:metadata-edit:v1:${PR_NUMBER}:${GITHUB_RUN_ID}:${EVENT_BASE}:${EVENT_HEAD}" + if [[ "${EVENT_ACTION}" =~ ^(edited|labeled|unlabeled)$ ]]; then + reservation_kind="${EVENT_ACTION}" + if [ "${reservation_kind}" = edited ]; then + reservation_kind=edit + fi + expected_reservation="mlx90-current-revision:metadata-${reservation_kind}:v1:${PR_NUMBER}:${GITHUB_RUN_ID}:${EVENT_BASE}:${EVENT_HEAD}" test "$(jq -r '.[0].external_id // empty' <<<"${named}")" = \ "${expected_reservation}" fi @@ -1409,12 +1513,20 @@ jobs: "${result_title}")" [[ "${published_check_id}" =~ ^[1-9][0-9]*$ ]] final_last_edited_at='' + final_labels_sha256='' + final_review_valid=true + if [ "${external_kind}" = copilot ] && ! validate_bound_review; then + final_review_valid=false + fi if ! final_last_edited_at="$(read_metadata_revision)" \ - || [ "${final_last_edited_at}" != "${BOUND_LAST_EDITED_AT}" ]; then + || [ "${final_last_edited_at}" != "${BOUND_LAST_EDITED_AT}" ] \ + || ! final_labels_sha256="$(read_labels_sha256)" \ + || [ "${final_labels_sha256}" != "${BOUND_LABELS_SHA256}" ] \ + || [ "${final_review_valid}" != true ]; then failure_evidence="$(jq -cn \ --arg base "${EVENT_BASE}" \ --arg head "${EVENT_HEAD}" \ - --arg reason "pull-request metadata changed during result publication" \ + --arg reason "pull-request metadata, labels, or review state changed during result publication" \ --argjson pr_number "${PR_NUMBER}" \ --argjson run_id "${GITHUB_RUN_ID}" \ --arg run_url "${run_url}" \ @@ -1443,7 +1555,7 @@ jobs: and .conclusion == "failure" and .output.summary == $evidence ' <<<"${invalidated}" >/dev/null - echo "Pull-request metadata changed during result publication." >&2 + echo "Pull-request metadata, labels, or review state changed during result publication." >&2 exit 1 fi diff --git a/.github/workflows/current-revision-rerun.yml b/.github/workflows/current-revision-rerun.yml index d631c33..d50e0d7 100644 --- a/.github/workflows/current-revision-rerun.yml +++ b/.github/workflows/current-revision-rerun.yml @@ -63,10 +63,72 @@ jobs: and .base.sha == $base and .head.sha == $head and .base.repo.full_name == $repository - and .head.repo.full_name == $repository + and (.head.repo.full_name | type) == "string" + and (.head.repo.full_name | length) > 0 and (.base.ref == "develop" or .base.ref == "main") + and (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") ' <<<"${pr}" >/dev/null + author="$(jq -er '.user.login | select(type == "string" and length > 0)' <<<"${pr}")" + base_ref="$(jq -er '.base.ref | select(. == "develop" or . == "main")' <<<"${pr}")" + head_ref="$(jq -er '.head.ref | select(type == "string" and length > 0)' <<<"${pr}")" + head_repository="$(jq -er '.head.repo.full_name | select(type == "string" and length > 0)' <<<"${pr}")" + labels_sha256_from_pr() { + local labels_json pr_json="$1" + if ! jq -e \ + --arg base "${EXPECTED_BASE}" \ + --arg base_ref "${base_ref}" \ + --arg head "${EXPECTED_HEAD}" \ + --arg head_repository "${head_repository}" \ + --arg repository "${REPOSITORY}" ' + .state == "open" + and .draft == false + and .base.ref == $base_ref + and .base.sha == $base + and .head.sha == $head + and .base.repo.full_name == $repository + and .head.repo.full_name == $head_repository + and (.labels | type) == "array" + and all(.labels[]; + type == "object" and (.name | type) == "string") + ' <<<"${pr_json}" >/dev/null; then + return 1 + fi + labels_json="$(jq -cer '[.labels[].name] | sort' <<<"${pr_json}")" || return 1 + printf '%s' "${labels_json}" | sha256sum | cut -d ' ' -f 1 + } + read_metadata_revision() { + local owner="${REPOSITORY%%/*}" repository="${REPOSITORY#*/}" + local query response + read -r -d '' query <<'GRAPHQL' || true + query($owner: String!, $repository: String!, $number: Int!) { + repository(owner: $owner, name: $repository) { + pullRequest(number: $number) { number lastEditedAt } + } + } + GRAPHQL + response="$(gh api graphql \ + -f query="${query}" \ + -F owner="${owner}" \ + -F repository="${repository}" \ + -F number="${PR_NUMBER}")" + jq -er \ + --argjson number "${PR_NUMBER}" ' + select(((.errors // []) | length) == 0) + | select(.data.repository.pullRequest.number == $number) + | .data.repository.pullRequest.lastEditedAt + | if . == null then "null" + elif type == "string" + and test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$") + then . else error("malformed lastEditedAt") end + ' <<<"${response}" + } + live_labels_sha256="$(labels_sha256_from_pr "${pr}")" + [[ "${live_labels_sha256}" =~ ^[0-9a-f]{64}$ ]] + live_last_edited_at="$(read_metadata_revision)" + neutral_pages="$(gh api --paginate --slurp \ "repos/${REPOSITORY}/commits/${EXPECTED_HEAD}/check-runs?check_name=Current%20revision%20review&filter=all&per_page=100")" jq -e ' @@ -111,13 +173,12 @@ jobs: producer_url="$(jq -er '.run_url | select(type == "string" and length > 0)' <<<"${neutral_summary}")" producer_prefix="${GITHUB_SERVER_URL}/${REPOSITORY}/actions/runs/" test "${producer_url}" = "${producer_prefix}${producer_id}" - author="$(jq -er '.user.login | select(type == "string" and length > 0)' <<<"${pr}")" - base_ref="$(jq -er '.base.ref | select(. == "develop" or . == "main")' <<<"${pr}")" - head_ref="$(jq -er '.head.ref | select(type == "string" and length > 0)' <<<"${pr}")" evidence_version='' external_kind='' - if [[ "${neutral_external_id}" =~ ^mlx90-current-revision:v4:${producer_id}:[0-9a-f]{64}$ ]]; then + input_sha256='' + if [[ "${neutral_external_id}" =~ ^mlx90-current-revision:v4:${producer_id}:([0-9a-f]{64})$ ]]; then evidence_version=v4 + input_sha256="${BASH_REMATCH[1]}" elif [[ "${neutral_external_id}" =~ ^mlx90-current-revision:(copilot|managed-sync|ancestry-backmerge):v6:${PR_NUMBER}:${producer_id}:${EXPECTED_BASE}:${EXPECTED_HEAD}$ ]]; then evidence_version=v6 external_kind="${BASH_REMATCH[1]}" @@ -130,8 +191,13 @@ jobs: fi jq -e \ --arg base "${EXPECTED_BASE}" \ + --arg base_ref "${base_ref}" \ --arg evidence_version "${evidence_version}" \ --arg head "${EXPECTED_HEAD}" \ + --arg head_repository "${head_repository}" \ + --arg input_sha256 "${input_sha256}" \ + --arg labels_sha256 "${live_labels_sha256}" \ + --arg last_edited_at "${live_last_edited_at}" \ --arg run_url "${producer_url}" \ --argjson pr_number "${PR_NUMBER}" \ --argjson run_id "${producer_id}" ' @@ -140,6 +206,19 @@ jobs: and .head_sha == $head and .producer_run_id == $run_id and .run_url == $run_url + and ( + if $evidence_version == "v4" then + .input_sha256 == $input_sha256 + elif $evidence_version == "v6" then + .head_repository == $head_repository + and .controller_sha == $base + and .controller_ref == $base_ref + and .pull_request_labels_sha256 == $labels_sha256 + and has("pull_request_last_edited_at") + and .pull_request_last_edited_at == ( + if $last_edited_at == "null" then null else $last_edited_at end) + else true end + ) and ( if $evidence_version == "v6" then .pull_request_number == $pr_number @@ -198,28 +277,41 @@ jobs: <<<"${neutral_summary}")" default_branch="$(gh api "repos/${REPOSITORY}" --jq .default_branch)" test "${default_branch}" = develop - default_head="$(gh api "repos/${REPOSITORY}/branches/${default_branch}" --jq .commit.sha)" - controller_ancestry="$(gh api \ - "repos/${REPOSITORY}/compare/${controller_sha}...${default_head}")" - jq -e \ - --arg controller "${controller_sha}" ' - .status == "identical" - or (.status == "ahead" and .behind_by == 0 - and .merge_base_commit.sha == $controller) - ' <<<"${controller_ancestry}" >/dev/null + if [ "${evidence_version}" = v6 ]; then + controller_ref="$(jq -er '.controller_ref | select(. == "develop" or . == "main")' \ + <<<"${neutral_summary}")" + test "${controller_ref}" = "${base_ref}" + test "${controller_sha}" = "${EXPECTED_BASE}" + protected_head="$(gh api "repos/${REPOSITORY}/branches/${controller_ref}" --jq .commit.sha)" + test "${protected_head}" = "${controller_sha}" + else + default_head="$(gh api "repos/${REPOSITORY}/branches/${default_branch}" --jq .commit.sha)" + controller_ancestry="$(gh api \ + "repos/${REPOSITORY}/compare/${controller_sha}...${default_head}")" + jq -e \ + --arg controller "${controller_sha}" ' + .status == "identical" + or (.status == "ahead" and .behind_by == 0 + and .merge_base_commit.sha == $controller) + ' <<<"${controller_ancestry}" >/dev/null + fi jq -e \ - --arg actor "${author}" \ --arg head_ref "${head_ref}" \ + --arg head_repository "${head_repository}" \ --arg head_sha "${EXPECTED_HEAD}" \ + --arg repository "${REPOSITORY}" \ --arg run_url "${producer_url}" ' .event == "pull_request_target" and .path == ".github/workflows/copilot-review.yml" and .name == "Current revision review gate" and .head_branch == $head_ref and .head_sha == $head_sha + and .repository.full_name == $repository + and .head_repository.full_name == $head_repository and .html_url == $run_url - and .actor.login == $actor - and .triggering_actor.login == $actor + and (.actor.login | type) == "string" and (.actor.login | length) > 0 + and (.triggering_actor.login | type) == "string" + and (.triggering_actor.login | length) > 0 ' <<<"${producer}" >/dev/null fi @@ -271,10 +363,11 @@ jobs: run="$(gh api "repos/${REPOSITORY}/actions/runs/${run_id}")" jq -e \ --arg api_url "${GITHUB_API_URL}" \ - --arg base_ref "${base_ref}" \ - --arg base_sha "${EXPECTED_BASE}" \ - --arg head_ref "${head_ref}" \ - --arg head_sha "${EXPECTED_HEAD}" \ + --arg base_ref "${base_ref}" \ + --arg base_sha "${EXPECTED_BASE}" \ + --arg head_ref "${head_ref}" \ + --arg head_repository "${head_repository}" \ + --arg head_sha "${EXPECTED_HEAD}" \ --arg repository "${REPOSITORY}" \ --arg run_url "${verifier_run_url}" \ --argjson pr_number "${PR_NUMBER}" ' @@ -297,7 +390,7 @@ jobs: and .pull_requests[0].base.repo.url == ($api_url + "/repos/" + $repository) and .pull_requests[0].head.ref == $head_ref and .pull_requests[0].head.sha == $head_sha - and .pull_requests[0].head.repo.url == ($api_url + "/repos/" + $repository) + and .pull_requests[0].head.repo.url == ($api_url + "/repos/" + $head_repository) and .status == "completed" ' <<<"${run}" >/dev/null if [ "$(jq -r .conclusion <<<"${run}")" = success ]; then @@ -335,6 +428,9 @@ jobs: test "$(jq 'length' <<<"${rerunnable_jobs}")" -eq 1 required_job_id=$(jq -er '.[0].id | select(type == "number" and . > 0)' \ <<<"${rerunnable_jobs}") + current_pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + test "$(labels_sha256_from_pr "${current_pr}")" = "${live_labels_sha256}" + test "$(read_metadata_revision)" = "${live_last_edited_at}" if ! gh api --method POST \ "repos/${REPOSITORY}/actions/jobs/${required_job_id}/rerun" >/dev/null; then # GitHub can accept the job rerun and still close the client request @@ -383,6 +479,9 @@ jobs: select(.status == "completed" and .conclusion == "success")] ' <<<"${attempt_two_jobs_pages}")" test "$(jq 'length' <<<"${completed_required_jobs}")" -eq 1 + current_pr="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}")" + test "$(labels_sha256_from_pr "${current_pr}")" = "${live_labels_sha256}" + test "$(read_metadata_revision)" = "${live_last_edited_at}" post_neutral_pages="$(gh api --paginate --slurp \ "repos/${REPOSITORY}/commits/${EXPECTED_HEAD}/check-runs?check_name=Current%20revision%20review&filter=all&per_page=100")" jq -e '