From 7a11cc810d98fce38a69ab50b7f32894e2b4ce7f Mon Sep 17 00:00:00 2001 From: Ytallo Layon Date: Thu, 6 Aug 2026 10:18:20 -0300 Subject: [PATCH] (MOT-4299) refactor: remove workflow Slack notifications --- .github/scripts/release_result.py | 5 +- .github/scripts/tests/test_release_result.py | 5 +- .github/workflows/promote-worker.yml | 46 +--------- .github/workflows/release.yml | 95 -------------------- .github/workflows/repair-worker-release.yml | 31 +------ docs/sops/release.md | 19 ++-- 6 files changed, 17 insertions(+), 184 deletions(-) diff --git a/.github/scripts/release_result.py b/.github/scripts/release_result.py index a3481affb..f25323dd6 100644 --- a/.github/scripts/release_result.py +++ b/.github/scripts/release_result.py @@ -84,7 +84,6 @@ def build_result(args: argparse.Namespace) -> dict: "phase": phase, "failed_requirements": failed, "jobs": jobs, - "notification": args.notification_result or "skipped", } @@ -114,9 +113,11 @@ def main() -> None: "publish_result", "container_alias_result", "candidate_result", - "notification_result", ): parser.add_argument(f"--{name.replace('_', '-')}", required=True) + # Compatibility with a rerun of a pre-centralization Release workflow. + # The value is intentionally ignored: notification delivery is app-owned. + parser.add_argument("--notification-result") parser.add_argument("--run-attempt", type=int, required=True) parser.add_argument("--image-digest", default="") parser.add_argument("--output", type=Path, required=True) diff --git a/.github/scripts/tests/test_release_result.py b/.github/scripts/tests/test_release_result.py index 0ff334835..e0f5edd2e 100644 --- a/.github/scripts/tests/test_release_result.py +++ b/.github/scripts/tests/test_release_result.py @@ -32,16 +32,15 @@ def args(**overrides): "publish_result": "success", "container_alias_result": "skipped", "candidate_result": "success", - "notification_result": "failure", } values.update(overrides) return argparse.Namespace(**values) -def test_notification_failure_does_not_fail_release(): +def test_successful_release_result_contains_only_release_truth(): result = build_result(args()) assert result["status"] == "succeeded" - assert result["notification"] == "failure" + assert "notification" not in result def test_failure_after_registry_publish_is_partial(): diff --git a/.github/workflows/promote-worker.yml b/.github/workflows/promote-worker.yml index 9f9caf2da..167e833e5 100644 --- a/.github/workflows/promote-worker.yml +++ b/.github/workflows/promote-worker.yml @@ -382,48 +382,6 @@ jobs: '.tagName == $tag and .isDraft == false and .isPrerelease == false' \ github-release-after.json >/dev/null - - name: Announce promoted release - id: slack - continue-on-error: true - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - CHANNEL: worker-releases - TAG: ${{ steps.candidate.outputs.tag }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - release_url=$(jq -r .url github-release-after.json) - text="๐Ÿš€ ${WORKER} v${VERSION} promoted to @latest โ€” <${release_url}|GitHub Release>" - payload=$(jq -n --arg channel "$CHANNEL" --arg text "$text" \ - '{channel: $channel, text: $text}') - resp=$(curl -sf -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$payload") - jq -e .ok <<<"$resp" >/dev/null || { - echo "::error::Slack API error: $(jq -r .error <<<"$resp")" - exit 1 - } - echo "ts=$(jq -r .ts <<<"$resp")" >>"$GITHUB_OUTPUT" - - notes=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json body --jq .body 2>/dev/null || true) - if [[ -n "$notes" ]]; then - notes=$(printf '%s' "$notes" | head -c 2900) - thread_payload=$(jq -n \ - --arg channel "$CHANNEL" \ - --arg text "$notes" \ - --arg ts "$(jq -r .ts <<<"$resp")" \ - '{channel: $channel, text: $text, thread_ts: $ts}') - thread_resp=$(curl -sf -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$thread_payload") - jq -e .ok <<<"$thread_resp" >/dev/null || { - echo "::error::Slack thread API error: $(jq -r .error <<<"$thread_resp")" - exit 1 - } - fi - - name: Write promotion summary if: always() run: | @@ -452,7 +410,6 @@ jobs: env: OPERATION_ID: ${{ inputs.operation_id || format('github:{0}', github.run_id) }} STEP_ID: ${{ inputs.step_id || 'promote' }} - NOTIFICATION_RESULT: ${{ steps.slack.outcome }} DEPLOY: ${{ steps.candidate.outputs.deploy }} run: | registry=false @@ -479,7 +436,6 @@ jobs: --arg candidate_evidence_run_id "$EVIDENCE_RUN_ID" \ --arg e2e_run_id "$E2E_RUN_ID" \ --arg status "$status" \ - --arg notification "${NOTIFICATION_RESULT:-skipped}" \ --argjson registry "$registry" \ --argjson github_release "$github_release" \ --argjson image_alias "$image_alias" \ @@ -490,7 +446,7 @@ jobs: release_run_id: $release_run_id, candidate_evidence_run_id: $candidate_evidence_run_id, e2e_run_id: ($e2e_run_id | if length > 0 then . else null end), - status: $status, notification: $notification, + status: $status, results: {registry: $registry, github_release: $github_release, image_alias: $image_alias}}' \ >promotion-result.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fc6ed633..afa6236b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -347,98 +347,6 @@ jobs: - name: Require all candidate gates run: jq -e '.candidate_ready == true' release-candidate.json >/dev/null - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Announce the release in Slack (#worker-releases). Notification failures - # are warnings: they must not turn an already published release red. - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - announce: - name: Announce on Slack - needs: [setup, binary-build, container-build, bundle-build, publish, container-alias, candidate-ready] - if: >- - ${{ - always() && - needs.setup.result == 'success' && - needs.setup.outputs.dry_run != 'true' && - ( - (needs.setup.outputs.staged == 'true' && needs.candidate-ready.result == 'success') || - ( - needs.setup.outputs.staged != 'true' && - needs.binary-build.result != 'failure' && - needs.container-build.result != 'failure' && - needs.bundle-build.result != 'failure' && - (needs.setup.outputs.deploy != 'image' || needs.container-alias.result == 'success') && - (needs.setup.outputs.interface_smoke == 'false' || needs.publish.result == 'success') - ) - ) - }} - runs-on: ubuntu-latest - outputs: - notification_result: ${{ steps.post.outcome == 'success' && steps.thread.outcome != 'failure' && 'success' || 'failure' }} - steps: - - name: Post to Slack - id: post - continue-on-error: true - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - CHANNEL: worker-releases - WORKER: ${{ needs.setup.outputs.worker }} - VERSION: ${{ needs.setup.outputs.version }} - IS_PRERELEASE: ${{ needs.setup.outputs.is_prerelease }} - STAGED: ${{ needs.setup.outputs.staged }} - PROMOTABLE: ${{ needs.setup.outputs.promotable }} - RELEASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.setup.outputs.tag }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - run: | - set -euo pipefail - if [[ "$STAGED" == true && "$PROMOTABLE" == true ]]; then - text="๐Ÿงช ${WORKER} v${VERSION} candidate passed on @next โ€” <${RUN_URL}|promote manually> ยท <${RELEASE_URL}|GitHub prerelease>" - elif [[ "$STAGED" == true ]]; then - text="๐Ÿงช ${WORKER} v${VERSION} prerelease passed on @next โ€” <${RELEASE_URL}|GitHub prerelease>" - else - suffix="" - if [[ "$IS_PRERELEASE" == "true" ]]; then suffix=" (pre-release)"; fi - text="๐Ÿš€ ${WORKER} v${VERSION}${suffix} released โ€” <${RELEASE_URL}|GitHub Release>" - fi - payload=$(jq -n --arg channel "$CHANNEL" --arg text "$text" '{channel: $channel, text: $text}') - resp=$(curl -sf -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$payload") - if ! echo "$resp" | jq -e .ok > /dev/null; then - echo "::error::Slack API error: $(echo "$resp" | jq -r .error)" - exit 1 - fi - echo "ts=$(echo "$resp" | jq -r .ts)" >> "$GITHUB_OUTPUT" - - - name: Post release notes in thread - id: thread - if: steps.post.outputs.ts != '' - continue-on-error: true - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - CHANNEL: worker-releases - TAG: ${{ needs.setup.outputs.tag }} - THREAD_TS: ${{ steps.post.outputs.ts }} - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - notes=$(gh release view "$TAG" -R "${{ github.repository }}" --json body --jq .body 2>/dev/null || true) - if [[ -z "$notes" ]]; then - echo "::notice::no release notes body; skipping thread reply" - exit 0 - fi - notes=$(printf '%s' "$notes" | head -c 2900) - payload=$(jq -n --arg channel "$CHANNEL" --arg text "$notes" --arg ts "$THREAD_TS" \ - '{channel: $channel, text: $text, thread_ts: $ts}') - resp=$(curl -sf -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$payload") - if ! echo "$resp" | jq -e .ok > /dev/null; then - echo "::error::Slack API error: $(echo "$resp" | jq -r .error)" - exit 1 - fi - release-result: name: Record terminal release result needs: @@ -450,7 +358,6 @@ jobs: - publish - container-alias - candidate-ready - - announce if: always() runs-on: ubuntu-latest permissions: @@ -487,7 +394,6 @@ jobs: PUBLISH_RESULT: ${{ needs.publish.result }} CONTAINER_ALIAS_RESULT: ${{ needs.container-alias.result }} CANDIDATE_RESULT: ${{ needs.candidate-ready.result }} - NOTIFICATION_RESULT: ${{ needs.announce.outputs.notification_result || needs.announce.result }} run: | python3 .github/scripts/release_result.py \ --repository "$REPOSITORY" \ @@ -515,7 +421,6 @@ jobs: --publish-result "$PUBLISH_RESULT" \ --container-alias-result "$CONTAINER_ALIAS_RESULT" \ --candidate-result "$CANDIDATE_RESULT" \ - --notification-result "$NOTIFICATION_RESULT" \ --output release-result.json - uses: actions/upload-artifact@v6 diff --git a/.github/workflows/repair-worker-release.yml b/.github/workflows/repair-worker-release.yml index b26753c84..b6d24c2e8 100644 --- a/.github/workflows/repair-worker-release.yml +++ b/.github/workflows/repair-worker-release.yml @@ -20,7 +20,7 @@ on: description: Repair action required: true type: choice - options: [verify, candidate-smoke, container-alias, github-release, notification] + options: [verify, candidate-smoke, container-alias, github-release] default: verify channel: description: 'Surface to repair (original uses the tag annotation)' @@ -300,31 +300,6 @@ jobs: --generate-notes --prerelease="$prerelease" --latest=false fi - notification: - name: Repeat release notification - needs: [setup, registry] - if: inputs.action == 'notification' - runs-on: ubuntu-latest - steps: - - name: Post explicit repair notification - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - CHANNEL: worker-releases - WORKER: ${{ needs.setup.outputs.worker }} - VERSION: ${{ needs.setup.outputs.version }} - REGISTRY_TAG: ${{ needs.setup.outputs.registry_tag }} - TAG: ${{ needs.setup.outputs.tag }} - run: | - set -euo pipefail - url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}" - text="๐Ÿ” ${WORKER} v${VERSION} @${REGISTRY_TAG} โ€” release notification repaired ยท <${url}|GitHub Release>" - payload=$(jq -n --arg channel "$CHANNEL" --arg text "$text" '{channel: $channel, text: $text}') - response=$(curl -sf -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$payload") - jq -e .ok <<<"$response" >/dev/null - verify: name: Verify release surfaces needs: [setup, registry] @@ -369,7 +344,7 @@ jobs: result: name: Record repair result - needs: [setup, registry, candidate-smoke, container-alias, candidate-evidence, github-release, notification, verify] + needs: [setup, registry, candidate-smoke, container-alias, candidate-evidence, github-release, verify] if: always() runs-on: ubuntu-latest steps: @@ -382,7 +357,6 @@ jobs: CONTAINER_ALIAS_RESULT: ${{ needs.container-alias.result }} CANDIDATE_EVIDENCE_RESULT: ${{ needs.candidate-evidence.result }} GITHUB_RELEASE_RESULT: ${{ needs.github-release.result }} - NOTIFICATION_RESULT: ${{ needs.notification.result }} VERIFY_RESULT: ${{ needs.verify.result }} run: | selected_result=failed @@ -391,7 +365,6 @@ jobs: candidate-smoke) selected_result="$CANDIDATE_EVIDENCE_RESULT" ;; container-alias) selected_result="$CONTAINER_ALIAS_RESULT" ;; github-release) selected_result="$GITHUB_RELEASE_RESULT" ;; - notification) selected_result="$NOTIFICATION_RESULT" ;; esac status=failed [[ "$SETUP_RESULT" == success && "$selected_result" == success ]] && status=succeeded diff --git a/docs/sops/release.md b/docs/sops/release.md index e4f3d0d35..26ed6dab5 100644 --- a/docs/sops/release.md +++ b/docs/sops/release.md @@ -155,8 +155,9 @@ separate successful **Harness E2E deployed** run before promotion. Every Release run also attempts to upload `release-result-/release-result.json`. It classifies the terminal state as `succeeded`, `partial`, or `failed`, records the last durable phase and all -job outcomes, and treats a Slack failure as a notification issue rather than a -failed publication. +job outcomes. Workflows do not send operator notifications; Release Control +projects successful terminal evidence to Slack without coupling delivery to +publication state. ## Promote a candidate @@ -178,8 +179,6 @@ Promotion performs these guarded, idempotent changes: 5. For images, move GHCR `latest` from the recorded immutable digest. 6. Convert the GitHub prerelease to a normal release without changing the repository-global GitHub Latest release. -7. Attempt the Slack announcement without making notification delivery part of - publication success. The terminal `promotion--` artifact records `succeeded`, `partial`, or `failed` plus each external surface. Release Control is the @@ -209,7 +208,6 @@ version, original Release run id, and one explicit action: | `candidate-smoke` | Repeat the exact `next` smoke and emit new candidate evidence | | `container-alias` | Reconcile only the selected `next` or `latest` image alias | | `github-release` | Create or reconcile GitHub prerelease/release state | -| `notification` | Explicitly repeat the release notification | Use `channel=original` for the tag's original channel. Select `next` or `latest` only when repairing a known partial candidate, direct release, or @@ -264,7 +262,7 @@ dry-run versions; validate changes through CI or a branch prerelease. | Candidate evidence is rejected | Wrong worker/version/run attempt/tag SHA or a failed gate | Use the exact successful Release or repair evidence run | | Harness promotion lacks E2E | No green deployed evidence for that release | Dispatch Harness E2E deployed with the exact release identity | | Promotion becomes partial | Registry changed but a later surface failed | Repair the failed `latest` surface; do not cut another version | -| Slack failed | Publication succeeded but notification did not | Use repair action `notification` after checking for duplicates | +| Slack delivery failed | Release Control could not project successful evidence | Check its Slack bot configuration and durable notification outbox; delivery retries automatically | ## Roll forward and verify @@ -281,7 +279,8 @@ iii worker info Confirm the resolved version, published function/trigger interface, GitHub Release assets, and โ€” for images โ€” the expected channel digest. -Slack announcements remain in `#worker-releases`. Candidate messages use -`next`; promotion messages identify `latest`. Ticket association rides on PR -titles (`(MOT-##) type: description`) or the `no-ticket` label for changes that -do not belong to a Linear issue. +Release Control owns Slack communication in `#worker-releases`. It creates one +root message for the immutable Release run, updates that root when the candidate +reaches `latest`, and keeps lifecycle checkpoints in the same thread. Ticket +association rides on PR titles (`(MOT-##) type: description`) or the `no-ticket` +label for changes that do not belong to a Linear issue.