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
2 changes: 2 additions & 0 deletions .github/workflows/cortex_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ on:
- "checkin.yaml"
- "dashboard.md"
- "dashboard.html"
- "state.json"
- "scripts/**"
- "tests/**"
- ".github/workflows/cortex_check.yml"
Expand All @@ -30,6 +31,7 @@ on:
- "checkin.yaml"
- "dashboard.md"
- "dashboard.html"
- "state.json"
- "scripts/**"
- "tests/**"
- ".github/workflows/cortex_check.yml"
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/dashboard_refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ on:
- "checkin.yaml"
- "dashboard.md"
- "dashboard.html"
- "state.json"
pull_request:
paths:
- "projects/**"
Expand All @@ -43,6 +44,7 @@ on:
- "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.
Expand Down Expand Up @@ -156,6 +158,9 @@ jobs:
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}"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/ledger_merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,15 @@ jobs:
exit 0
fi
git checkout -B _ledger_merge origin/main
# Built without committing: the two generated pages are the one
# Built without committing: the generated renders (the two pages and
# the state.json cockpit feed, PyAutoBrain#418) are the one
# conflict every ledger branch can produce (each re-renders them),
# and a render is never a reason to strand a ledger — take main's
# copy, let the merged tree be re-rendered on main by
# dashboard_refresh.yml. Anything else unmerged is a human's.
if ! git merge --no-ff --no-commit "$HEAD_SHA" >/dev/null 2>&1; then
UNMERGED=$(git diff --name-only --diff-filter=U)
OTHER=$(echo "$UNMERGED" | grep -vE '^(dashboard\.md|dashboard\.html)$' || true)
OTHER=$(echo "$UNMERGED" | grep -vE '^(dashboard\.md|dashboard\.html|state\.json)$' || true)
if [ -n "$OTHER" ]; then
git merge --abort || true
echo "'$BRANCH' conflicts with main on: $(echo $OTHER)" > /tmp/ledger_merge_reason
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/pages_dashboard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ name: Pages Dashboard
# Only the uploaded files reach Pages — nothing else in the repo, and in
# particular not `projects/` or `projects.yaml`, whose absolute laptop
# paths are not for a public site.
# The same render writes `state.json`, the organ-cockpit feed (PyAutoBrain
# board/_state.py v1, PyAutoBrain#418); it is published at /state.json after
# validating against the Brain's contract.

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

permissions:
Expand All @@ -42,6 +46,12 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
# The cockpit-feed contract lives with the Brain (board/_state.py); the
# Cortex only publishes what the Brain's cortex renderer wrote.
- uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
# enablement: true creates the Pages site on first run, so no manual
# Settings → Pages step is needed.
- uses: actions/configure-pages@v5
Expand All @@ -51,6 +61,17 @@ jobs:
run: |
mkdir -p _site
cp dashboard.html _site/index.html
# The organ-cockpit feed, when the renderer has written it.
if [ -f state.json ]; then cp state.json _site/state.json; fi
# A feed that has drifted from the contract fails the job here, before
# Pages publishes something the cockpit would reject.
- name: Validate the cockpit feed against the Brain contract
run: |
if [ -f _site/state.json ]; then
python3 PyAutoBrain/board/_state.py _site/state.json
else
echo "no state.json rendered yet — nothing to validate"
fi
- uses: actions/upload-pages-artifact@v3
with:
path: _site
Expand Down
4 changes: 3 additions & 1 deletion scripts/ledger_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@
# push. If they were code, every ordinary ledger branch would stop for a human
# on two files nobody wrote by hand. `checkin.yaml` is the same kind of thing:
# one machine-written stamp, pushed by the check-in that earned it.
LEDGER_FILES = ("checkin.yaml", "dashboard.md", "dashboard.html")
# `state.json` is the third render of the same census — the organ-cockpit feed
# (PyAutoBrain board/_state.py v1, PyAutoBrain#418) — so it rides with them.
LEDGER_FILES = ("checkin.yaml", "dashboard.md", "dashboard.html", "state.json")

# Names that are ledger by location but must not ride along, for two different
# reasons.
Expand Down
11 changes: 11 additions & 0 deletions tests/test_ledger_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_ledger_dir_and_registry_files_are_ledger():
# generated from the ledger, self-healed on main by dashboard_refresh.yml
"dashboard.md",
"dashboard.html",
# the organ-cockpit feed, rendered by the same run (PyAutoBrain#418)
"state.json",
):
assert ledger_merge.is_ledger_path(path), path

Expand Down Expand Up @@ -341,4 +343,13 @@ def test_no_scheduled_job_mutates_the_ledger():
if "schedule:" in text:
assert p.name == "dashboard_refresh.yml", p.name
assert "git add dashboard.md dashboard.html" in text
assert "then git add state.json; fi" in text
assert "projects/" not in text.split("git add", 1)[1].split("\n")[0]


def test_a_checkin_push_carrying_the_cockpit_feed_needs_no_human():
"""`cortex checkin --push` stages state.json beside the pages it renders."""
ledger, blocked = ledger_merge.classify(
["checkin.yaml", "dashboard.md", "dashboard.html", "state.json"])
assert blocked == [] and "state.json" in ledger
assert not ledger_merge.is_ledger_path("projects/state.json/../../scripts/x.py")
Loading