Skip to content

Withhold application notifications for prereleases - #43

Merged
luisleo526 merged 2 commits into
mainfrom
sm/withhold-app-prerelease-20261010
Oct 9, 2026
Merged

luisleo526 merged 2 commits into
mainfrom
sm/withhold-app-prerelease-20261010

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

For a prerelease, the publish workflow no longer sends a repository dispatch to the application; the application receives a prerelease by hand-off instead. Every other combination behaves exactly as before. The release dry-run harness is updated in a second commit so that it checks the new behavior.

Workflow behavior (.github/workflows/publish.yml, job notify-consumers)

All three matrix legs stay visible, and the job, environment, permissions and target expression are unchanged:

  • A new first step, Decide consumer notification, reads the consumer and the publish job's prerelease output. It accepts only exactly true or false. Anything else fails the leg, for every consumer, with the existing error (publish job gave no prerelease flag) before any repository check, token or dispatch.
  • For the application leg with prerelease=true it records dispatch=false and logs one line: Application is not notified for a prerelease; it receives the release by hand-off. The leg ends green: no token is minted and no dispatch is sent.
  • Every other combination records dispatch=true:
    • prerelease, offline and hosted: dispatched as before, payload unchanged;
    • stable release, all three consumers: dispatched as before.
  • The repository check, the token mint and the dispatch keep their bodies and now carry the same step-level condition, steps.notify.outputs.dispatch == 'true'. (A job-level if cannot read the matrix context, so the decision is a step.)

Tests (tests/test_release_workflows.py)

Extended, standard library only:

  • the decision step's own shell is extracted from the workflow and run under bash with its own output file, for all six consumer and channel combinations (the application on a prerelease exits 0, writes dispatch=false and logs the single hand-off line; the other five write dispatch=true), and for missing or malformed flags for each consumer (non-zero exit, nothing written);
  • the step order, the environment bindings, the visible matrix and the identical condition on each of the three gated steps are pinned, so removing any one of them fails the test;
  • the existing payload and token-scope checks stay, and test_prerelease_flag_reaches_every_consumer is renamed test_prerelease_flag_is_in_every_dispatch_payload, since the application no longer receives a dispatch for a prerelease.

Docs (README.md)

One sentence in the publish.yml bullet: a prerelease reaches the application by hand-off, with no repository dispatch and no App token, while stable releases still notify it and the other two roles are notified for every release.

Release dry-run harness (tools/release-dry-run/, second commit)

The harness replays the real workflows offline and checks what they would send. It is updated for the new behavior without changing the workflow, the runner (wfrun.py) or the shims:

  • dry_run.py: the fanout oracle expects a prerelease to reach the offline and hosted consumers only, keeps all three consumer legs green, and requires the application leg to log the hand-off line once, mint no App token and send no dispatch. A stable release still reaches all three, with the same payload, JSON type, hostname, endpoint, owner and scope checks as before.
  • The pair world now runs the failure controls for both the prerelease and the stable publish result, with the expected flag and destinations taken from the channel (the wrong-flag control flips the real flag). Every earlier control remains: fail-fast omitted or true; missing, empty, owner/path and multiline target configuration; wrong target, version, flag and run id; wrong JSON types; skipped job and skipped dispatch. A withheld application on a prerelease is expected to stay green with no token and no dispatch whatever its configuration, and the same configuration cases still fail before the token and the dispatch for a stable release.
  • The skipped-dispatch control now changes the dispatch step's own existing guard to false (an edit that finds no target, or several, is an error) instead of adding a second if: key.
  • A new control breaks only the decision's first condition in a synthetic checkout, so the application dispatches on a prerelease with every guard still in place. The unmodified oracle must reject that green run, and the control checks that the run really reached the application dispatch and token.
  • test_wfrun.py: the consumer-leg helper keeps prerelease=false as its default and takes an optional flag. New tests cover the withheld application (green, no token, no dispatch, even with an invalid target), the stable application with an invalid target still failing before the token, offline and hosted still dispatching on a prerelease, malformed flags failing before any token, the step condition being evaluated by the existing runner, the oracle for both channels and the new control, and the synthetic-edit helpers.
  • README.md of the harness: the prerelease fanout, the private-configuration prose and the list of negative controls are updated.

Not changed

The payload, the event type, the secrets, the token scope, the other two consumers, the tag rules, handle-upstream.yml, wfrun.py and the shims.

Validation

Run at this head (aad2033) on a clean Linux machine, from a real git checkout of the branch:

  • the CI release-rules commands: exit 0 (75 tests);
  • the release dry-run tool's own tests (test_wfrun.py): exit 0 (25 tests);
  • negative control: with the prerelease condition removed in a copy of the workflow, the release tests fail as they must (test_only_the_application_on_a_prerelease_is_withheld).

An independent review of the diff and of the raw outputs recommended merging, with no blocking finding. Two follow-ups from it are kept for a later change: the CI test does not yet forbid continue-on-error on the decision step (only the dry-run tool's test catches it), and on a prerelease the application leg also skips the target-repository check, so a broken application target would show only at the next stable release.

Not run: the full five-world dry run (it needs Node, which the test machine did not have) and a real publish.

🤖 Generated with Claude Code

luisleo526 and others added 2 commits October 10, 2026 02:02
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The fanout oracle now expects a prerelease to reach the offline and hosted consumers only, with the application leg green, no App token, no dispatch and the hand-off line; a stable release still reaches all three. The failure controls run for both the prerelease and the stable publish, the skipped-dispatch control rewrites the existing guard instead of adding a second if key, and a new control breaks the decision so the application dispatches on a prerelease. test_wfrun.py and the README follow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@luisleo526
luisleo526 marked this pull request as ready for review October 9, 2026 21:53
@luisleo526
luisleo526 merged commit 25e0ca4 into main Oct 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant