## Goal

The two Slack failure alerts actually post when a release or a WordPress.org deploy fails, and say
which version failed. PR #53 gave both alerts a link to the GitHub run; the deploy one could not
reach Slack at all, so that link was never sent.

## The problem

`deploy.yml` gated its failure alert on:

```yaml
if: steps.deploy.outcome != 'success'
```

An `if:` with no status check function in it is implicitly wrapped in `success() && ...`
("A default status check of `success()` is applied unless you include one of these functions" —
GitHub Actions expressions docs). So the condition read "the job is healthy AND the deploy failed",
which is never true: a failed deploy step turns the job red, `success()` goes false, and the alert
step is skipped.

Seen live in this repo on 2026-09-22. Runs 35759292859 and 35768792331 both died at
"Checkout repository". Every step after it with a plain `if:` was skipped; the only step that ran
was `release.yml`'s alert, which uses `if: failure()`.

Those same two runs also show the second problem: "Get Package Version" was skipped, so the alert's
version expression resolved to an empty string and the Slack header read
":red_circle: AAArdvark WP Plugin" with nothing after it.

## What ships

- `deploy.yml` — the failure alert is gated on `${{ !cancelled() && steps.deploy.outcome != 'success' }}`.
  `!cancelled()` rather than `failure()` so a failure in a later step does not report "Failed to
  deploy" after the deploy succeeded. The `${{ }}` wrapper is required: a bare leading `!` is a YAML
  tag indicator and will not parse.
- Both workflows — the failure alert falls back to `version unknown` when the version step was
  skipped.
- `deploy.yml` — "Get release ID" asked GitHub for a release in `nsquared-team/simple-client-dashboard`,
  a different product's repo. When that returned nothing `jq -r` printed the string `null`, which
  passed the `[[ -z ]]` guard, and the next step PATCHed `.../releases/null`. `curl` has no `-f`, so
  it exited 0 and the step went green. The lookup now uses `gh api` against `$GITHUB_REPOSITORY` and
  the guard rejects `null`. `release.yml` already did this correctly.
  - The tag reaches the script through `env: TAG_NAME`, never interpolated into the `run:` body.
    It originates in a `workflow_dispatch` input, and inside a double-quoted word `$(...)` and
    backticks execute — so writing it into the script text would run whatever the caller typed, in a
    job holding `SVN_PASSWORD` and the Slack webhook. The old single-quoted `curl -d` payload was
    injectable too, but only through a `'` breakout; measured with a sentinel file, the naive
    rewrite went from 1 of 4 payloads executing to 3 of 4, and the env form to 0 of 4.
  - The lookup is `if ! databaseId=$(...)`, not a bare assignment. The default shell is `bash -e`,
    so a failing command substitution in an assignment kills the step before any message is printed
    and the friendly error below it never runs.
- `release.yml` — the failure alert moves to the end of the job. It sat five steps from the end, so
  a failure in "Format changelog for slack", "Send Dev Changelog Notification to Slack" or
  "Trigger deploying to WordPress.org" sent no alert at all.

## Out of scope

- Neither workflow declares a top-level `permissions:` block. Pre-existing, and there is no lint
  gate in this repo requiring one.
- `slackapi/slack-github-action@v1.23.0` is a floating tag, not a SHA. Pre-existing on all four call
  sites.
- The `ERROR: Repository not found` checkout failure behind the 2026-09-22 incident is a credential
  problem, not a workflow one.

## Definition of Done

- [x] A failed WP.org deploy step reaches the "Failed to deploy" Slack alert instead of skipping it.
- [x] Neither failure alert can post a blank version.
- [x] "Get release ID" queries this repository and fails loudly when there is no matching release.
- [x] A failure in any step of the release job produces a Slack alert.
- [x] A caller-supplied tag cannot execute anything in the release lookup.
- [x] `actionlint` is clean on both files, and each alert payload is still valid JSON once its
      expressions are filled in — checked with the version present and with the version step skipped.
      Note that `actionlint` flags neither the `success()` bug nor the injection: both are valid
      syntax, and it treats only `github.event.*` as untrusted, not a step output carrying an input.
