From 7542e8402121a1437fccae6d2fb1996eb5202005 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 22:53:22 +0000 Subject: [PATCH 1/2] test: genericise repo-name fixtures + add PR-time tenant-firewall gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clears the three PyAutoBrain tenant-firewall findings by the recorded decision rule (derivable/arbitrary -> refactor, PyAutoMind#198): - test_worktree_conflict_guard.py: RepoA..RepoG — the awk does plain string compares against no repo list, so real names carried no value. - test_intake_dashboard.py: ExampleOrg — the dashboard regex never inspects the URL owner. - test_profiling_conductor.py: profiling_workspace — every invocation passes --workspace explicitly, which the rename also proves. tests.yml gains the repos_sync.py firewall leg via its new --only selector, so future instance-fact drift fails in the PR that authors it instead of accreting reactive allowlist patches. Note: this step needs the --only selector on PyAutoMind main — merge after the PyAutoMind PR. --- .github/workflows/tests.yml | 26 ++++++++---- tests/test_intake_dashboard.py | 10 +++-- tests/test_profiling_conductor.py | 10 +++-- tests/test_worktree_conflict_guard.py | 58 +++++++++++++++------------ 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5d0607..21f68d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,19 +7,26 @@ name: Brain Tests # seams ran in no CI at all and a Brain PR's only gate was whatever the # authoring session happened to run locally. # -# Deliberately ONLY pytest. It must not run `pyauto-brain ` against live -# repos or reach the network — those need the full workspace and belong to the -# scheduled health/nightly drivers, not a PR gate. The suite is stdlib + PyYAML -# only, so it stays fast (~30s) and flake-free. +# pytest plus ONE drift leg. The suite must not run `pyauto-brain ` +# against live repos or reach the network — those need the full workspace and +# belong to the scheduled health/nightly drivers, not a PR gate. The suite is +# stdlib + PyYAML only, so it stays fast (~30s) and flake-free. The one +# non-pytest step is the tenant-firewall gate (PyAutoMind#198): every firewall +# finding to date merged through a green PR because no PR CI ran the check, so +# the leg a Brain PR can cause is verified here, in the PR that authors it. +# `--only` keeps Mind-side legs (map blocks, registries) from reddening Brain +# PRs on drift Brain cannot cause; organs not checked out (Heart, Hands) are +# skipped by the check itself, so this gates exactly Brain. # # TWO repos are checked out on purpose. PyAutoBrain alone cannot even COLLECT # the suite: `agents/faculties/sizing/_sizing.py` reads the body map # (`BRAIN_HOME.parent / "PyAutoMind" / "repos.yaml"`) at import time and is # deliberately strict, so `test_policy_seams.py` and `test_sizing_paths.py` # error out before a single test runs. Checking PyAutoMind out as a sibling -# reproduces the workspace layout the code assumes. Both repos are public, so -# the default GITHUB_TOKEN suffices — no PAT. No other sibling repo is needed -# (verified by running the suite against exactly this two-repo layout). +# reproduces the workspace layout the code assumes — the same layout the +# firewall gate's `--root` needs. Both repos are public, so the default +# GITHUB_TOKEN suffices — no PAT. No other sibling repo is needed (verified by +# running the suite against exactly this two-repo layout). # One run per commit: PR events carry the CI; pushes only build main. # Superseded runs are cancelled on PR refs only — a cancelled main run would @@ -62,3 +69,8 @@ jobs: - name: Run tests working-directory: PyAutoBrain run: pytest tests/ -q + - name: Tenant firewall (instance facts stay in declared config surfaces) + run: > + python3 PyAutoMind/scripts/repos_sync.py --check + --only "tenant firewall (organ code)" + --root "$GITHUB_WORKSPACE" diff --git a/tests/test_intake_dashboard.py b/tests/test_intake_dashboard.py index 428f1f6..516b32f 100644 --- a/tests/test_intake_dashboard.py +++ b/tests/test_intake_dashboard.py @@ -104,10 +104,14 @@ def test_every_backlog_prompt_is_a_bullet_not_a_wide_table_row(tmp_path): # --------------------------------------------------------------------------- # # in flight: the issue link is the registry's, and the status is live # --------------------------------------------------------------------------- # +# The issue URLs are deliberately synthetic (ExampleOrg/Widgets): the dashboard +# regex captures whole URLs and never inspects the owner, so a real GitHub +# owner here would be an instance fact in organ code — the tenant firewall's +# concern (PyAutoMind/scripts/repos_sync.py) — for no test value. ACTIVE_MD = """# Active Tasks ## widget-rework -- issue: https://github.com/PyAutoLabs/Widgets/issues/42 (opened after the spike) +- issue: https://github.com/ExampleOrg/Widgets/issues/42 (opened after the spike) - status: library-dev — branch pushed, awaiting review - prompt: active/widget_rework.md """ @@ -118,7 +122,7 @@ def test_in_flight_links_the_registry_issue_and_its_live_status(tmp_path): active={"widget_rework.md": _prompt("Widget rework")}, registries={"active.md": ACTIVE_MD}) flight = _page(mind).split("## In flight")[1].split("## Parked")[0] - assert "[issue #42](https://github.com/PyAutoLabs/Widgets/issues/42)" in flight, \ + assert "[issue #42](https://github.com/ExampleOrg/Widgets/issues/42)" in flight, \ "the link must be the matched URL, not the field's trailing prose" assert "(opened after the spike)" not in flight assert "library-dev" in flight @@ -129,7 +133,7 @@ def test_in_flight_links_the_registry_issue_and_its_live_status(tmp_path): def test_in_flight_prompt_with_no_registry_row_claims_no_issue(tmp_path): """Silence beats a wrong link: prose issue URLs are usually cross-references.""" body = _prompt("Orphan task") + \ - "\nFollow-up to https://github.com/PyAutoLabs/Widgets/issues/7 (unrelated).\n" + "\nFollow-up to https://github.com/ExampleOrg/Widgets/issues/7 (unrelated).\n" mind = _mind(tmp_path, active={"orphan.md": body}) flight = _page(mind).split("## In flight")[1].split("## Parked")[0] assert "Orphan task" in flight diff --git a/tests/test_profiling_conductor.py b/tests/test_profiling_conductor.py index 47ecafb..2ba21c2 100644 --- a/tests/test_profiling_conductor.py +++ b/tests/test_profiling_conductor.py @@ -1,8 +1,12 @@ """Contract tests for the profiling conductor's CLI footing. -Hermetic: every test builds a synthetic `autolens_profiling` fixture in a temp +Hermetic: every test builds a synthetic `profiling_workspace` fixture in a temp dir and passes it via `--workspace`, so the assertions never depend on the state -of the real checkout (whose corpus grows every campaign). +of the real checkout (whose corpus grows every campaign). The fixture directory +name is deliberately NOT the real workspace's: every invocation passes +`--workspace` explicitly (proving the override is honoured), so a real repo name +here would be an instance fact in organ code — the tenant firewall's concern +(PyAutoMind/scripts/repos_sync.py) — for no test value. Profiling was the only conductor without a test file when the compile axis was added; the runtime-axis cases here exist to hold that surface still while the @@ -60,7 +64,7 @@ def _record(**kw): def _workspace(tmp_path, records_by_file=None): - ws = tmp_path / "autolens_profiling" + ws = tmp_path / "profiling_workspace" lr = ws / "scripts" / "misc" / "likelihood_runtime" lr.mkdir(parents=True) (lr / "sweep.py").write_text(FIXTURE_CELLS) diff --git a/tests/test_worktree_conflict_guard.py b/tests/test_worktree_conflict_guard.py index c6f8f78..991c49d 100644 --- a/tests/test_worktree_conflict_guard.py +++ b/tests/test_worktree_conflict_guard.py @@ -6,12 +6,12 @@ start_library/start_workspace references still document: - repos: - - PyAutoFit: feature/foo + - RepoA: feature/foo Every writer drifted to the paren form instead: - repos: - - autolens_workspace (feature/foo) + - RepoB (feature/foo) which the `": "` split swallowed whole, so `repo` never equalled the requested repo name and the guard exited 0 for every task. The parser now accepts both @@ -28,13 +28,19 @@ WORKTREE_SH = Path(__file__).resolve().parents[1] / "bin" / "worktree.sh" +# Fixture repo names are deliberately synthetic (RepoA…RepoG): the awk in +# worktree.sh does plain string compares and validates against no repo list, +# so a real name here would be an instance fact in organ code — the tenant +# firewall's concern (PyAutoMind/scripts/repos_sync.py) — for no test value. +# Keep them synthetic. + PAREN = """# Active Tasks ## paren-task - issue: http://x - worktree: ~/wt/paren-task - repos: - - autolens_workspace (feature/paren-task) + - RepoB (feature/paren-task) """ COLON = """# Active Tasks @@ -43,7 +49,7 @@ - issue: http://y - worktree: ~/wt/colon-task - repos: - - PyAutoFit: feature/colon-task + - RepoA: feature/colon-task """ # `repos:` before `worktree:` — both orderings occur in the real ledger. @@ -51,7 +57,7 @@ ## rbw-task - repos: - - PyAutoArray (feature/rbw-task) + - RepoC (feature/rbw-task) - worktree: ~/wt/rbw-task """ @@ -61,24 +67,24 @@ ## first-task - worktree: ~/wt/first-task - repos: - - autolens_workspace (feature/first-task) + - RepoB (feature/first-task) ## second-task - worktree: ~/wt/second-task - repos: - - autolens_workspace (feature/second-task) + - RepoB (feature/second-task) """ # Both shapes carry trailing notes in the real ledger, and some claims name no # branch at all. Sampled from active.md history: 258 claim lines, 162 colon-form, -# 139 paren-form, plus bare names like ` - PyAutoReduce`. +# 139 paren-form, plus bare names like ` - RepoG`. ANNOTATED = """# Active Tasks ## annotated-task - worktree: ~/wt/annotated-task - repos: - - HowToFit: feature/howto-smoke (base 65e8fbd == origin/main) - - PyAutoReduce + - RepoF: feature/howto-smoke (base 65e8fbd == origin/main) + - RepoG """ # A task that claims nothing — the `repos-none-claimed:` shape live entries use. @@ -120,14 +126,14 @@ def _claims(tmp_path: Path, active_body: str) -> list[list[str]]: # --- the regression: the paren form is what every writer emits ---------------- def test_paren_form_claim_conflicts(tmp_path): - proc = _run(tmp_path, PAREN, "worktree_check_conflict new-task autolens_workspace") + proc = _run(tmp_path, PAREN, "worktree_check_conflict new-task RepoB") assert proc.returncode == 1, "paren-form claim did not register as a conflict" assert "paren-task" in proc.stderr def test_paren_form_repo_is_bare(tmp_path): task, repo, branch, wt = _claims(tmp_path, PAREN)[0] - assert repo == "autolens_workspace" + assert repo == "RepoB" assert branch == "feature/paren-task" assert wt == "~/wt/paren-task" @@ -135,14 +141,14 @@ def test_paren_form_repo_is_bare(tmp_path): # --- back-compat: the documented colon form must keep working ----------------- def test_colon_form_claim_conflicts(tmp_path): - proc = _run(tmp_path, COLON, "worktree_check_conflict new-task PyAutoFit") + proc = _run(tmp_path, COLON, "worktree_check_conflict new-task RepoA") assert proc.returncode == 1 assert "colon-task" in proc.stderr def test_colon_form_repo_is_bare(tmp_path): task, repo, branch, wt = _claims(tmp_path, COLON)[0] - assert repo == "PyAutoFit" + assert repo == "RepoA" assert branch == "feature/colon-task" @@ -152,12 +158,12 @@ def test_worktree_captured_when_repos_precede_it(tmp_path): # The awk set `wt` on sight, so a `repos:` block above `worktree:` emitted # "-" and repo_cleanup lost the worktree path for that claim. task, repo, branch, wt = _claims(tmp_path, REPOS_FIRST)[0] - assert repo == "PyAutoArray" + assert repo == "RepoC" assert wt == "~/wt/rbw-task", "worktree: must be captured regardless of field order" def test_worktree_does_not_leak_between_tasks(tmp_path): - body = REPOS_FIRST + "\n## later-task\n- repos:\n - PyAutoLens (feature/later)\n" + body = REPOS_FIRST + "\n## later-task\n- repos:\n - RepoD (feature/later)\n" rows = {r[0]: r for r in _claims(tmp_path, body)} assert rows["later-task"][3] == "-", "a task with no worktree: must not inherit one" @@ -165,33 +171,33 @@ def test_worktree_does_not_leak_between_tasks(tmp_path): # --- the guard's own contract ------------------------------------------------- def test_task_does_not_conflict_with_itself(tmp_path): - proc = _run(tmp_path, PAREN, "worktree_check_conflict paren-task autolens_workspace") + proc = _run(tmp_path, PAREN, "worktree_check_conflict paren-task RepoB") assert proc.returncode == 0, proc.stderr def test_unclaimed_repo_does_not_conflict(tmp_path): - proc = _run(tmp_path, PAREN, "worktree_check_conflict new-task PyAutoGalaxy") + proc = _run(tmp_path, PAREN, "worktree_check_conflict new-task RepoE") assert proc.returncode == 0, proc.stderr def test_conflict_names_every_claiming_task(tmp_path): - proc = _run(tmp_path, TWO_CLAIMS, "worktree_check_conflict new-task autolens_workspace") + proc = _run(tmp_path, TWO_CLAIMS, "worktree_check_conflict new-task RepoB") assert proc.returncode == 1 assert "first-task" in proc.stderr assert "second-task" in proc.stderr def test_trailing_note_does_not_corrupt_repo(tmp_path): - # ` - HowToFit: feature/howto-smoke (base 65e8fbd == origin/main)` — the + # ` - RepoF: feature/howto-smoke (base 65e8fbd == origin/main)` — the # repo name is what the guard compares, so the note must not reach it. rows = {r[1]: r for r in _claims(tmp_path, ANNOTATED)} - assert set(rows) == {"HowToFit", "PyAutoReduce"} - assert rows["HowToFit"][2].startswith("feature/howto-smoke") + assert set(rows) == {"RepoF", "RepoG"} + assert rows["RepoF"][2].startswith("feature/howto-smoke") def test_claim_without_a_branch_still_claims(tmp_path): - # ` - PyAutoReduce` with no branch is a real shape; it must still conflict. - proc = _run(tmp_path, ANNOTATED, "worktree_check_conflict new-task PyAutoReduce") + # ` - RepoG` with no branch is a real shape; it must still conflict. + proc = _run(tmp_path, ANNOTATED, "worktree_check_conflict new-task RepoG") assert proc.returncode == 1 assert "annotated-task" in proc.stderr @@ -222,7 +228,7 @@ def test_conflict_guard_fails_closed_when_the_registry_is_missing(tmp_path): decides whether a task may start — so "I could not check" has to stop the caller, not wave it through. """ - proc = _run(tmp_path, None, "worktree_check_conflict some-task PyAutoFit") + proc = _run(tmp_path, None, "worktree_check_conflict some-task RepoA") assert proc.returncode != 0 assert "CANNOT VERIFY" in proc.stderr assert "PYAUTO_MAIN" in proc.stderr # names what to set @@ -231,6 +237,6 @@ def test_conflict_guard_fails_closed_when_the_registry_is_missing(tmp_path): def test_conflict_guard_can_be_forced_past_a_missing_registry(tmp_path): """The escape hatch is explicit and loud — never the default.""" proc = _run(tmp_path, None, - "worktree_check_conflict --allow-missing-registry t PyAutoFit") + "worktree_check_conflict --allow-missing-registry t RepoA") assert proc.returncode == 0 assert "UNGUARDED" in proc.stderr From 5084f3a550e3763d00040c9b533e5280b2fa548e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 23:11:01 +0000 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20retrigger=20=E2=80=94=20PyAutoMind#19?= =?UTF-8?q?9=20(--only=20selector)=20is=20now=20on=20Mind=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit