Repository navigation
176 lines (171 loc) · 8.36 KB
/
Copy pathdashboard_refresh.yml
File metadata and controls
176 lines (171 loc) · 8.36 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
name: Dashboard Refresh
# Keeps the generated Cortex pages (`dashboard.md`, linked from the README, and
# `dashboard.html`, the Pages index) in step with the registry they describe.
# The pages are rendered by PyAutoBrain's cortex conductor from `projects/`,
# `projects.yaml` and `checkin.yaml`, so any push that records a run, logs an
# entry, rewrites a `## Now` or stamps a check-in makes them stale.
#
# Same shape as the Mind's dashboard_refresh.yml, and for the same reason: on
# pull requests a stale page is an error the author fixes on the branch; on
# pushes to main it is SELF-HEALED with a bot commit, because Cortex pushes land
# directly on main from ledger_merge.yml and from branch-scoped sessions, and an
# alarm-only check would just email a human.
#
# The generation stamp AND the visible `Last updated` banner are excluded from
# the drift comparison (`--check`), so a re-render on an unchanged registry is
# not drift and this never commits daily. (The Mind's normaliser strips only the
# comment, which is why its nightly self-heals every night; the Cortex renderer
# strips both — schema_decisions.md 38.)
#
# The nightly cron is the routine rebuild: it catches what push triggers cannot
# — renderer changes merged in PyAutoBrain (the renderer lives there, so no
# Cortex path fires) and any render that depends on the passage of time. A no-op
# night commits nothing.
on:
schedule:
- cron: "35 3 * * *"
push:
branches: [main]
paths:
- "projects/**"
- "archive/**"
- "projects.yaml"
- "checkin.yaml"
- "dashboard.md"
- "dashboard.html"
- "state.json"
pull_request:
paths:
- "projects/**"
- "archive/**"
- "projects.yaml"
- "checkin.yaml"
- "dashboard.md"
- "dashboard.html"
- "state.json"
workflow_dispatch:
# contents: write is needed by the self-heal push on main; PR runs never push.
# actions: write → re-dispatch pages_dashboard.yml after a heal: the heal commit
# is made with GITHUB_TOKEN, which never triggers other workflows, so without
# the explicit dispatch the Pages page stays stale while the repo copy is fresh
# (the Mind was bitten by exactly this on 2026-08-21).
permissions:
contents: write
actions: write
# Every workflow in this repo that can push to main shares one queue —
# ledger_merge.yml carries the same group — so two bot writers never race for
# the tip and spend their retries re-merging. Queue rather than
# cancel: a cancelled heal can leave a rendered page uncommitted. PR runs never
# push, but they share the group rather than carry a second, ref-scoped one; the
# queue is short and one name is easier to reason about than two.
concurrency:
group: cortex-main-writers
cancel-in-progress: false
jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
path: PyAutoCortex
# The renderer lives with the cortex conductor, not here — the Cortex
# holds the state, the Brain reasons over it (ORGANISM.md).
- uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
# A Cortex branch that changes the schema alongside its Brain conductor
# is rendered by the PyAutoBrain branch of the SAME NAME when one exists
# (the two land as a pair), and by Brain main otherwise.
- name: Prefer the same-named PyAutoBrain branch, if any
working-directory: PyAutoBrain
env:
BR: ${{ github.head_ref || github.ref_name }}
run: |
if [ "$BR" != "main" ] && git fetch --depth 1 origin "$BR" 2>/dev/null; then
git checkout -q FETCH_HEAD && echo "PyAutoBrain @ $BR"
else
echo "PyAutoBrain @ main"
fi
# No setup-python: the runner's python3 is the interpreter the laptop
# render is compared to. PyYAML is `cortex.py`'s one dependency (it reads
# `projects.yaml`), and the conductor imports that script at runtime.
- name: dependencies
run: python3 -m pip install --quiet pyyaml
- name: dashboard freshness (self-healing on push to main)
working-directory: PyAutoCortex
run: |
BRAIN=../PyAutoBrain/agents/conductors/cortex/_cortex.py
# Exit 1 is drift and nothing else. Any other non-zero code means the
# renderer itself could not run — a Brain/Cortex version skew, say —
# and reporting that as "the page is stale" sends whoever reads the
# log to fix the wrong file. (2 is argparse: the verb or a flag this
# Brain main does not have.)
check() {
local rc=0
python3 "$BRAIN" dashboard --cortex . --check || rc=$?
if [ "$rc" -gt 1 ]; then
echo "::error::the dashboard renderer exited ${rc} — that is not drift. Check that PyAutoBrain main still provides 'cortex dashboard --check'."
exit 1
fi
return "$rc"
}
if check; then
# Fresh in the repo does NOT mean published. The commit that made it
# fresh is usually ledger_merge.yml's, pushed with GITHUB_TOKEN,
# which fires no push event -- so pages_dashboard.yml's own
# `push: main, paths: [dashboard.html]` trigger never sees it, and the
# heal-path dispatch below is never reached because we return here.
# That stranded the Mind's published board on 2026-08-27 while main
# was correct, and the nightly cron takes this same path, so nothing
# healed it. Ask the publisher explicitly: it is idempotent and
# collapses under pages_dashboard.yml's `concurrency: group: pages`.
# DO NOT fold this back into the heal path below -- that IS the bug.
if [ "${GITHUB_EVENT_NAME}" != "pull_request" ]; then
GH_TOKEN="${{ github.token }}" gh workflow run pages_dashboard.yml --ref main \
|| echo "::warning::could not dispatch pages_dashboard.yml — the Pages page will lag until its next trigger"
fi
exit 0
fi
# Only PR runs error-without-healing; push, the nightly schedule and
# manual dispatch all self-heal main.
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
echo "::error::dashboard.md/.html are stale — run 'pyauto-brain cortex dashboard --apply' on this branch and commit the result"
exit 1
fi
echo "the dashboard pages are stale on main — self-healing"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Each attempt rebuilds on the current tip of main, so a concurrent
# push (the usual cause of a rejected push) never needs a rebase.
# The heal push uses the default GITHUB_TOKEN, which does not trigger
# workflow runs, so it cannot loop.
for attempt in 1 2 3; do
git fetch origin main
git reset --hard FETCH_HEAD
if check; then
echo "tip of main is already fresh (healed by a concurrent push)"
exit 0
fi
python3 "$BRAIN" dashboard --cortex . --apply
if ! check; then
echo "::error::'dashboard --apply' did not converge — renderer bug, repair by hand"
exit 1
fi
git add dashboard.md dashboard.html
# state.json is the organ-cockpit feed rendered by the same run
# (PyAutoBrain#418); guarded because a missing pathspec is fatal.
if [ -f state.json ]; then git add state.json; fi
git commit -m "cortex: self-heal stale dashboard pages"
if git push origin HEAD:main; then
echo "healed on attempt ${attempt}"
# Token pushes never trigger pages_dashboard.yml — publish the
# healed page explicitly or the Pages site stays stale.
GH_TOKEN="${{ github.token }}" gh workflow run pages_dashboard.yml --ref main \
|| echo "::warning::could not dispatch pages_dashboard.yml — the Pages page will lag until its next trigger"
exit 0
fi
echo "push rejected (attempt ${attempt}) — retrying on the new tip of main"
done
echo "::error::could not push the healed dashboard after 3 attempts"
exit 1