Repository navigation
Merge pull request #53 from PyAutoLabs/feature/dashboard-portable-pro… #126
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |