Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/dashboard_refresh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Dashboard Refresh

# Re-ingests every registered instance (resolve its branch to one commit, read
# its summary at that SHA, validate, write receipts/ and the last-good
# snapshots/), re-renders dashboard.md / dashboard.html / badge.json /
# state.json, and commits them to main as github-actions[bot] when they
# changed. (Eyes/Cortex pattern.)
#
# Triggers:
# - repository_dispatch `pulse-refresh`: to be fired by each project repo's
# pages_dashboard.yml after it publishes a new summary (sender side is a
# follow-up in the project repo). The payload is only logged: the board
# always re-resolves the branch itself, so a dispatch can never make the
# dashboard disagree with the project's main.
# - a nightly cron: catches any dispatch that did not arrive.
# - push to main touching the registry or the reader, and workflow_dispatch.
# - pull_request: a dry run (ingest + render, no commit) so a reader change
# is exercised against the live feeds before it merges.
#
# Receipts and snapshots carry their `fetched_at` while nothing changes, the
# pages carry no wall-clock stamp, and state.json's `updated` moves only when
# the feed's content does, so a night with no new project commit commits
# nothing. A failed fetch keeps the committed snapshot (shown as cached, never
# re-aged). The body map is read from a checkout of PyAutoMind (public). The
# bot commit uses GITHUB_TOKEN, which never triggers other workflows, so after
# a commit this workflow dispatches pages_dashboard.yml explicitly.

on:
repository_dispatch:
types: [pulse-refresh]
schedule:
- cron: "55 3 * * *"
workflow_dispatch:
push:
branches: [main]
paths:
- "registry.yaml"
- "pulse/**"
pull_request:

permissions:
contents: write
actions: write

# Every workflow in this repo that pushes to main shares one queue.
concurrency:
group: ${{ github.event_name == 'pull_request' && format('pulse-refresh-pr-{0}', github.ref) || 'pulse-main-writers' }}
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Log the trigger
env:
PAYLOAD: ${{ toJson(github.event.client_payload) }}
run: |
echo "event: ${GITHUB_EVENT_NAME}"
if [ "${GITHUB_EVENT_NAME}" = "repository_dispatch" ]; then echo "payload: ${PAYLOAD}"; fi

- name: PyAutoMind (the body map that resolves each instance's repo)
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/PyAutoMind
path: _mind

- name: Dependencies
run: python3 -m pip install --quiet pyyaml

- name: Dry run (pull request)
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
bin/pyauto-pulse board --mind _mind
git status --short

- name: Ingest, render and commit the dashboard (only when changed)
if: github.event_name != 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
FILES="dashboard.md dashboard.html badge.json state.json receipts snapshots"
for attempt in 1 2 3; do
git fetch -q origin main && git reset -q --hard origin/main
bin/pyauto-pulse board --mind _mind
git add $FILES
if git diff --cached --quiet; then
echo "dashboard unchanged: nothing to commit"
exit 0
fi
git commit -q -m "pulse: refresh dashboard (${GITHUB_EVENT_NAME}) [skip ci]"
if git push -q origin HEAD:main; then
gh workflow run pages_dashboard.yml --ref main \
|| echo "::warning::could not dispatch pages_dashboard.yml; Pages lags until its next trigger"
exit 0
fi
echo "push rejected (main moved); retrying on the new tip"
done
echo "::error::could not push the refreshed dashboard after 3 attempts"
exit 1
89 changes: 89 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# lint.yml: the PR / push-to-main gate for the PyAutoPulse organ.
#
# 1. ruff check + ruff format --check
# 2. pytest tests/ (hermetic: registry, summary reader, ingest + receipts,
# board, CLI — every network call is refused; state.json is validated
# against the Brain contract because PyAutoBrain is checked out below)
# 3. `bin/pyauto-pulse check --offline`: registry valid against the Mind body
# map, every committed snapshot still validates under this reader, each
# receipt records its snapshot's commit, the committed dashboard was
# built from those receipts, and state.json meets the Brain contract.
# Offline on purpose: a project commit landing mid-PR must not redden an
# unrelated organ PR (dashboard_refresh.yml re-reads the network nightly).
# 4. lychee link-rot over the prose *.md. raw.githubusercontent.com,
# dashboard.md (hundreds of commit-pinned evidence links), the organ's own
# Pages URL (until Pages is enabled and the first deploy lands) and
# GitHub issue/pull/commit URLs are excluded.

name: lint

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: PyAutoMind (the body map that resolves each instance's repo)
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/PyAutoMind
path: _mind

- name: PyAutoBrain (the state.json contract validator)
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/PyAutoBrain
path: _brain

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install tools
run: |
python -m pip install --quiet ruff pytest pyyaml
curl -sSfL https://github.com/lycheeverse/lychee/releases/latest/download/lychee-x86_64-unknown-linux-gnu.tar.gz \
| tar -xz -C /usr/local/bin --strip-components=1 lychee-x86_64-unknown-linux-gnu/lychee

- name: ruff check
run: ruff check --exclude _mind --exclude _brain .

- name: ruff format --check
run: ruff format --check --exclude _mind --exclude _brain .

- name: pytest (hermetic)
env:
PYAUTO_BRAIN: ${{ github.workspace }}/_brain
run: python -m pytest tests -q

- name: pyauto-pulse check --offline
env:
PYAUTO_BRAIN: ${{ github.workspace }}/_brain
run: bin/pyauto-pulse check --offline --mind _mind

- name: lychee (markdown link-rot)
env:
# github.com serves 503 to unauthenticated page fetches (rate limit);
# with a token lychee checks github.com links through the API instead.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
lychee \
--github-token "${GITHUB_TOKEN}" \
--exclude '^https://raw\.githubusercontent\.com/' \
--exclude '^https://pyautolabs\.github\.io/PyAutoPulse' \
--exclude '^https://github\.com/.*/(issues|pull|commit|releases)/' \
--no-progress \
--accept '200..=299,429' \
--max-redirects 5 \
$(find . -name '*.md' -not -name 'dashboard.md' -not -path './.claude/*' -not -path './.git/*' -not -path './_mind/*' -not -path './_brain/*')
62 changes: 62 additions & 0 deletions .github/workflows/pages_dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Pages Dashboard

# Publishes the generated `dashboard.html` as this repo's GitHub Pages index:
# https://pyautolabs.github.io/PyAutoPulse/
#
# The page is rendered by `bin/pyauto-pulse board` and kept fresh by
# dashboard_refresh.yml. This workflow only publishes it, with badge.json (the
# one-line headline other boards read) and state.json (the organ-cockpit feed,
# contract PyAutoBrain/board/state_schema.json) beside it, and nothing else:
# no summary, receipt or snapshot is copied into the site, and every evidence
# link points at the project repo at the captured commit. (Eyes/Cortex pattern.)

on:
push:
branches: [main]
paths:
- "dashboard.html"
- "badge.json"
- "state.json"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
# enablement: true creates the Pages site on first run where the token
# may; otherwise enable Pages (source: GitHub Actions) once by hand.
- uses: actions/configure-pages@v5
with:
enablement: true
- name: Stage the dashboard as the site index
run: |
mkdir -p _site
cp dashboard.html _site/index.html
cp badge.json _site/badge.json
cp state.json _site/state.json
# The contract is owned by the Brain (board/_state.py); the Pulse only
# emits it. A feed the cockpit cannot read fails loudly here.
- uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
- name: Validate the cockpit feed against the Brain contract
run: python3 PyAutoBrain/board/_state.py _site/state.json
- uses: actions/upload-pages-artifact@v3
with:
path: _site
- id: deployment
uses: actions/deploy-pages@v4
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ venv/

# Pages staging
_site/

# PyAutoMind checkout the refresh workflow reads the body map from
_mind/

# Half-written receipt/snapshot files (written then renamed into place)
receipts/*.tmp
snapshots/*.tmp
Loading
Loading