diff --git a/board/AGENTS.md b/board/AGENTS.md index 40038d4..2899d9f 100644 --- a/board/AGENTS.md +++ b/board/AGENTS.md @@ -12,9 +12,7 @@ community collection. The board does not duplicate their dashboards. procedures. Keep this the first content section. 2. A shared orchestration panel for an unspecified task (shared layout places it beside the banner controls). Focus on the user's request and route it. -3. Review overnight work: only failures, blocked gates, pending/missing runs - and unavailable evidence. Successful routine runs remain in machine data. -4. Maintenance: issue/repo cleanup, Hygiene findings and local observations, +3. Maintenance: issue/repo cleanup, Hygiene findings and local observations, plus an actionable CI-speedup entry linked to Heart's timing evidence. No morning sync, community section/shortcut, readiness/release, versions, @@ -25,11 +23,14 @@ operational evidence lives in another organ. ## Collection and compatibility -`board/_board.py` collects overnight jobs from `config/policy.yaml`, Heart's -performance evidence, open issue counts, Hygiene and explicit dev-box publishes. +`board/_board.py` collects Heart's performance evidence, open issue counts, +Hygiene and explicit dev-box publishes. It does not poll overnight workflows. +Scientist reviews dated owner records and workflow runs on demand for the +requested period; no scheduled digest or duplicate activity ledger is created. Retired source collectors are not called. Deprecated board.json keys remain empty for existing readers. state.json retains the shared v1 schema; badge and -state describe Brain's overnight coordination only, never release readiness. +state describe availability of Brain's directory and maintenance evidence, +never overnight outcomes or release readiness. The internal `degraded` list remains diagnostic metadata and keeps freshness unknown when a required read fails, but is not a global page section. @@ -65,9 +66,9 @@ must agree; escape owner data and preserve exact portable copy payloads. Without gh, `--github-data ` accepts endpoint-keyed JSON supplied by the agent's GitHub tools. An endpoint absent from that file or explicitly null means unavailable, never an empty result. Malformed JSON is fatal. Preserve responses -verbatim. Each configured overnight workflow reads -`repos///actions/workflows//runs?per_page=1`, with optional -`repos///actions/runs//jobs` for blocked-gate evidence. +verbatim. This adapter supports retained sources; it does not re-enable +retired overnight collection. Legacy explicit diagnostic helpers remain for +compatibility, separate from board publication. `BOARD_GH` and `BOARD_PAGES_BASE` support hermetic fixtures. A failed Pages read may use an owning repo's local published artifact; it must not manufacture diff --git a/board/_board.py b/board/_board.py index 28a944a..22e9feb 100755 --- a/board/_board.py +++ b/board/_board.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Brain's agents/workflows directory, overnight exceptions and maintenance entry. +"""Brain's agents/workflows directory and maintenance entry. Read-only collection feeds HTML, Markdown and version-compatible machine feeds. Other organs own operational evidence; Brain plans and routes work. @@ -194,8 +194,10 @@ def age_label(hours): def collect_overnight(jobs, org, degraded): - """Latest run of each scheduled workflow (the what-ran-while-I-slept - glance), with the blocked-at-a-gate refinement from overnight_status.sh.""" + """Legacy explicit diagnostic helper; never called by dashboard collection. + + This latest-run view cannot establish activity across a requested period. + """ rows = [] for job in jobs: repo, _, wf = str(job).partition(":") @@ -596,7 +598,6 @@ def collect(): degraded = [] board_family = board_cfg.get("boards") or {} heart_repo = board_cfg.get("heart_board", "PyAutoHeart") - overnight = collect_overnight(board_cfg.get("overnight_jobs", []), org, degraded) # Heart owns performance evidence; Brain offers the investigation workflow. heart_board = fetch_heart_board(pages_base, heart_repo, degraded) performance = extract_heart_performance(heart_board, f"{pages_base}/{heart_repo}/") @@ -608,7 +609,7 @@ def collect(): "generated": now.strftime("%Y-%m-%d %H:%M UTC"), "org": org, "repo": board_family.get("brain", "PyAutoBrain"), - "overnight": overnight, + "overnight": [], # Retired compatibility field; Scientist reviews on demand. "heart": None, "heart_monitoring": None, "heart_blockers": [], @@ -638,17 +639,6 @@ def collect(): # --------------------------------------------------------------- verdict ---- -def verdict(data): - """Classify overnight exceptions only; other organs own their verdicts.""" - blocking, attention = [], [] - for r in data["overnight"]: - if r["conclusion"] not in (None, "success"): - blocking.append(f"overnight: {r['repo']}/{r['workflow']} {r['conclusion']}") - elif r["blocked"]: - attention.append(f"overnight: {r['repo']}/{r['workflow']} blocked at a gate") - return blocking, attention - - def headline(data): """The one line a reader acts on — qualified by how much was actually read. @@ -658,9 +648,7 @@ def headline(data): brightgreen: the strongest possible all-clear, from a board that had asked nothing. Absence of findings is not a finding. """ - blocking, attention = verdict(data) - n = len(blocking) + len(attention) - core = "Agents & workflows" if n == 0 else f"{n} overnight item(s) to review" + core = "Agents & workflows" unread = len(data.get("degraded") or []) if unread: core += f" · partial view ({unread} leg{'s' if unread != 1 else ''} unread)" @@ -668,13 +656,7 @@ def headline(data): def badge_color(data): - blocking, attention = verdict(data) - if blocking: - return "red" - if attention: - return "orange" - # Nothing found AND nothing unread is the only green. Nothing found because - # nothing could be asked is grey, not green. + # Directory/evidence availability only, never a workflow or release verdict. if data.get("degraded"): return "lightgrey" return "brightgreen" @@ -775,26 +757,11 @@ def _overnight_state_item(r): return item -def _state_items(data): - """The rows the board already renders as asking something of a human — - composed, never re-derived: each prompt is the owning organ's own payload - (the Heart's /bug line, the Ears' triage ref), carried verbatim.""" - items = [] - for r in data.get("overnight") or []: - item = _overnight_state_item(r) - if item is not None: - items.append(item) - order = {"red": 0, "yellow": 1, "info": 2} - items.sort(key=lambda i: order[i["severity"]]) # stable: section order kept - return items[:STATE_ITEM_CAP] - - def render_state(data): """state.json — the organ cockpit feed (board/_state.py, contract v1). - Same verdict as the badge (status mirrors badge_color, headline is the - badge message) plus the actionable rows, so the cockpit never disagrees - with the badge beside it. + Directory/evidence availability mirrors the badge. Operational activity + remains with its owners and Scientist reviews it on demand. """ data = _portable_board_data(data) repo = data.get("repo") or "PyAutoBrain" @@ -807,7 +774,7 @@ def render_state(data): headline=headline(data), updated=_iso_generated(data), pages_url=pages_url, - items=_state_items(data), + items=(), # No scheduled activity digest; owners keep operational evidence. ) return json.dumps(state, indent=2) + "\n" @@ -850,15 +817,6 @@ def render_md(data): if door["tier"] == tier: verb = door["verb"].replace("_", "-") lines.append(f"- **{verb}** — {door['desc']} · Use the {verb} skill.") - lines += ["", "## Review overnight work"] - runs = review_runs(data) - for run in runs: - lines.append(_overnight_line(run)) - item = _overnight_state_item(run) - if item and item.get("prompt"): - lines.append(f" {item['prompt']}") - if not runs: - lines.append("No overnight exceptions reported.") lines += ["", "## Maintenance"] lines += [f"- {label}: {prompt}" for label, prompt in MAINTENANCE_ACTIONS] heart = data.get("boards", {}).get("heart", "") @@ -919,7 +877,6 @@ def render_html(data): f"", hero(THEME_ORGAN, "Agents & workflows", navigation=[ {"label": "Agents & workflows", "href": "#agents"}, - {"label": "Review overnight work", "href": "#overnight"}, {"label": "Maintenance", "href": "#maintenance"}])] work_links = ([{"label": "Open work repository", "href": f"https://github.com/{data['org']}/{data['repo']}"}] @@ -952,45 +909,6 @@ def render_html(data): + pills((tier, "" if tier == "conductor" else "n")), f"Use the {verb} skill.")) H.append("") - H.append('

Review overnight work

') - if not review_runs(data): - H.append(_plain("No overnight exceptions reported.")) - for r in review_runs(data): - # The workflow is the row's subject; its repo and its conclusion are - # facets, so they read as pills — the repo in the organ accent - # (identity) and the conclusion in its verdict tone. - link = f' — run ↗' if r["url"] else "" - age = (f' ({age_label(r["age_h"])})' - if r["conclusion"] is not None else "") - subject = f'{esc(r["workflow"])}{age}{link}' - failed = r["conclusion"] not in (None, "success") - if r.get("unreadable"): - state, tone, reason = "could not read", "y", "" - elif r["conclusion"] is None: - state, tone, reason = "no runs", "n", "" - elif r["blocked"]: - state, tone = "blocked at a gate", "y" - reason = ('
no change made — ' - f'{esc(r["blocked_reason"])}' - if r.get("blocked_reason") else - '
no change made') - elif not failed: - state, tone, reason = "success", "g", "" - else: - state, tone, reason = r["conclusion"], "r", "" - # pills() escapes its own values; only the hand-built html above is - # escaped here. - text = subject + reason + pills((r["repo"], ""), (state, tone)) - if failed and not r["blocked"]: - H.append(_row(text, f"Use the bug skill. overnight: {r['repo']}/{r['workflow']} " - f"concluded {r['conclusion']} — " - f"{r['url'] or 'no run url'}")) - else: - if r["blocked"]: - H.append(_row(text, f"Investigate the blocked overnight workflow {r['repo']}/{r['workflow']} using {r['url'] or 'the owning workflow'}. Read the gate reason and route the next action; do not bypass the gate.")) - else: - H.append(_plain(text)) - H.append('

Maintenance

') for label, prompt in MAINTENANCE_ACTIONS: H.append(_row(esc(label), prompt)) diff --git a/config/policy.yaml b/config/policy.yaml index 05fc250..34b8c56 100644 --- a/config/policy.yaml +++ b/config/policy.yaml @@ -142,20 +142,9 @@ release: HowToFit, HowToGalaxy, HowToLens] tag_repo: PyAutoLabs/PyAutoLens -# The Brain board (board/_board.py): scheduled workflows for overnight review -# and sibling board destinations. Owner/org is derived from the Mind's body map at runtime, so entries -# here are repo-relative unless a home lies outside the primary org. -# overnight_jobs mirrors bin/overnight_status.sh (its CLI complement) — keep -# the two lists in step until the script reads this block too. +# Brain board destinations. Activity review belongs to Scientist on demand; +# there is no scheduled overnight workflow inventory in this board. board: - overnight_jobs: - - PyAutoBrain:nightly-release.yml - - PyAutoHeart:heart-health.yml - - PyAutoHeart:workspace-smoke.yml - - PyAutoHands:python_matrix.yml - - PyAutoMind:arxiv_papers.yml - - PyAutoMind:spawn_drift.yml - - autolens_assistant:wiki-currency.yml heart_board: PyAutoHeart # The full one-tap board family (the cross-board footer nav every board # carries; each renderer skips its own entry). Parsed by board/_board.py diff --git a/tests/test_board.py b/tests/test_board.py index 1b58616..c0303ec 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -396,27 +396,15 @@ def test_all_green_is_clear_to_work(tmp_path): "message": "Agents & workflows", "color": "brightgreen"} -def test_overnight_failure_is_blocking_and_red(tmp_path): +def test_overnight_runs_no_longer_change_brain_badge_or_feed(tmp_path): stub = _fabricate(tmp_path, _default_fixtures(**{ "runs.json": _run_json("failure")})) - r = _run(["--badge"], tmp_path, stub) - badge = json.loads(r.stdout) - assert badge["color"] == "red" - assert badge["message"].endswith("overnight item(s) to review") - md = _run([], tmp_path, stub).stdout - assert "Review overnight work" in md and "failure" in md - - -def test_blocked_gate_is_attention_not_blocking(tmp_path): - stub = _fabricate(tmp_path, _default_fixtures(**{ - "jobs.json": {"jobs": [{"steps": [ - {"name": "Blocked at a gate — no release made", - "conclusion": "success"}]}]}})) - r = _run(["--badge"], tmp_path, stub) - badge = json.loads(r.stdout) - assert badge["color"] == "orange" - html_page = _run(["--html"], tmp_path, stub).stdout - assert "blocked at a gate" in html_page + badge = json.loads(_run(["--badge"], tmp_path, stub).stdout) + assert badge["message"] == "Agents & workflows" + assert badge["color"] == "brightgreen" + assert json.loads(_run(["--json"], tmp_path, stub).stdout)["overnight"] == [] + assert json.loads(_run(["--state"], tmp_path, stub).stdout)["items"] == [] + assert "Review overnight work" not in _run([], tmp_path, stub).stdout # -------------------------------------------------------------------- html -- @@ -447,7 +435,7 @@ def test_html_is_self_contained_with_one_tap_payloads(tmp_path): assert 'data-cmd="Use the community skill."' in page assert 'data-cmd="Use the issue-cleanup skill."' in page assert 'data-cmd="Use the prm skill."' in page - assert "Use the bug skill. overnight:" in page # the failing run's payload + assert "Use the bug skill. overnight:" not in page # Scientist owns this entry # The local morning leg is a TERMINAL chip, not a Claude payload. assert "morning.sh" not in page # The doors roster is on the page. @@ -469,10 +457,11 @@ def test_navigation_matches_the_reduced_scope(tmp_path): page = _run(["--html"], tmp_path, stub).stdout nav = re.search(r'