-
Notifications
You must be signed in to change notification settings - Fork 0
141 lines (130 loc) · 6.4 KB
/
Copy pathnightly-release.yml
File metadata and controls
141 lines (130 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
name: Nightly Release
# The scheduled-nightly release driver (PyAutoHands/docs/nightly_release_design.md
# §3) — the ONLY path authorised to ship a live PyPI release without a
# per-release human, under PyAutoBrain/AUTONOMY.md's standing grant (2026-07-09).
#
# This workflow is a scheduler, nothing more: all sequencing, gating and
# notification logic lives in agents/conductors/release/nightly.sh (testable
# locally via `pyauto-brain release nightly`). Scheduling authority moved HERE
# from PyAutoHands's release.yml (whose cron was removed in the same change) —
# Build executes releases, it does not decide when.
#
# Arming (design §9, all human acts, in order):
# 1. the next manual live release succeeds end-to-end;
# 2. PyAutoBuild#126 closed / de-labelled (`release-blocker`);
# 3. a dry_run=true nightly observed GREEN end-to-end;
# 4. set the repo Actions variable NIGHTLY_RELEASES=true and flip the
# DRY_RUN default below from 'true' to 'false'.
# ARMED 2026-07-09 (human-directed, PyAutoBuild#127): the manual live release
# 2026.7.9.1 succeeded and the human flipped the default the same day —
# checklist steps 2-3 were consciously waived; open release-blocker issues
# (e.g. PyAutoBuild#126) still stop every night at step 3 until closed or
# de-labelled. Pausing is one act: unset NIGHTLY_RELEASES.
#
# OUTCOME CONTRACT (2026-08-04). The driver's exit codes already distinguished
# "a gate stopped the night" from "the driver broke", but this workflow flattened
# every non-zero into a red run — so eight consecutive nights of the gate working
# correctly looked identical to a broken driver, and the channel became one
# nobody watched. The mapping is now explicit:
#
# exit 0 shipped / skipped / dry-run -> job SUCCESS
# exit 2|3 blocked at a gate, no release -> job SUCCESS + ::warning:: + the
# "Blocked at a gate" step below
# exit 1|* driver error, night NOT judged -> job FAILURE
#
# Red is therefore reserved for "the driver itself is broken" — the one state
# that needs a human to look at THIS workflow. A blocked night is a normal,
# expected outcome: Slack carries which gate stopped it, and the run keeps a
# named step + job summary so it is never silently green.
#
# `bin/overnight_status.sh` reads that step name to report a blocked night
# distinctly in the morning glance — keep the step name in sync if it changes.
on:
schedule:
# Every night: quiet nights exit at the activity gate in seconds, so
# weekend scheduling costs nothing and weekend merges release on time.
- cron: "0 2 * * *"
workflow_dispatch:
inputs:
dry_run:
description: "true = run every gate but log instead of dispatching the live release"
type: choice
options: ["true", "false"]
default: "false"
permissions:
contents: read
# One night at a time; a manual dispatch queues behind a scheduled run rather
# than racing it (two live releases of the same date must be impossible).
concurrency:
group: nightly-release
cancel-in-progress: false
jobs:
nightly:
runs-on: ubuntu-latest
# Rehearsal (~1h) + release-fidelity integration (~2h) + live release (~1.5h).
timeout-minutes: 350
steps:
- name: Checkout PyAutoBrain
uses: actions/checkout@v4
- name: Checkout PyAutoHeart (sibling — resolve_heart finds it via PYAUTO_ROOT)
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoHeart
path: PyAutoHeart
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install PyYAML (Heart's only dependency)
run: pip install --quiet pyyaml
- name: Run the nightly driver
id: driver
env:
GH_TOKEN: ${{ secrets.PAT_PYAUTOLABS }}
PYAUTO_RELEASE_WEBHOOK_URL: ${{ secrets.PYAUTO_RELEASE_WEBHOOK_URL }}
NIGHTLY_RELEASES: ${{ vars.NIGHTLY_RELEASES }}
# Scheduled runs have no inputs: default 'false' — ARMED 2026-07-09
# (design §9; the kill switch is the NIGHTLY_RELEASES repo var).
DRY_RUN: ${{ inputs.dry_run || 'false' }}
PYAUTO_ROOT: ${{ github.workspace }}
# `set +e` first: the default shell is `bash -e`, which would abort on
# the driver's exit code before it can be classified (see OUTCOME
# CONTRACT above). This step never fails — the two steps below decide.
run: |
set +e
bash agents/conductors/release/nightly.sh
rc=$?
set -e
echo "rc=$rc" >> "$GITHUB_OUTPUT"
case "$rc" in
0) echo "outcome=reported" >> "$GITHUB_OUTPUT" ;;
2|3) echo "outcome=blocked" >> "$GITHUB_OUTPUT" ;;
*) echo "outcome=driver-error" >> "$GITHUB_OUTPUT" ;;
esac
# Named, not just annotated: overnight_status.sh keys the morning glance
# off this step, so a blocked night is never reported as a plain green.
- name: Blocked at a gate — no release was made
if: steps.driver.outputs.outcome == 'blocked'
run: |
echo "::warning title=Nightly release blocked::The driver stopped at a gate (exit ${{ steps.driver.outputs.rc }}); no release was made. This is the gate working — Slack carries which one."
{
echo "## ⏸ Blocked at a gate — no release was made"
echo
echo "The driver stopped deliberately (exit \`${{ steps.driver.outputs.rc }}\`):"
echo "\`2\` = a gate blocked the night, \`3\` = readiness was not GREEN."
echo
echo "This is the gate doing its job, not a driver fault, so the run is"
echo "green. The Slack page names which gate stopped it."
} >> "$GITHUB_STEP_SUMMARY"
- name: Driver error — the night was NOT judged
if: steps.driver.outputs.outcome == 'driver-error'
run: |
echo "::error title=Nightly driver error::The driver failed with exit ${{ steps.driver.outputs.rc }} — the night was NOT judged and no gate verdict exists."
{
echo "## 🚨 Driver error — the night was NOT judged"
echo
echo "\`nightly.sh\` exited \`${{ steps.driver.outputs.rc }}\`, which is not a gate"
echo "outcome. No release was made AND no gate verdict was reached —"
echo "this workflow needs a human."
} >> "$GITHUB_STEP_SUMMARY"
exit 1