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
63 changes: 63 additions & 0 deletions .github/workflows/firewall_gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Tenant Firewall Gate

# The Mind-side leg of the tenant-firewall PR gates (issue #198). The organ
# repos gate their own PRs with `repos_sync.py --only "tenant firewall (organ
# code)"`; this workflow closes the remaining hole — an edit to the CHECKER
# itself (an allowlist grant, a token-pattern change, an --only regression) is
# otherwise verified by nothing. It checks Mind out beside the organ repos'
# mains and runs the full drift check, so an allowlist over-grant or a check
# that stopped finding real drift fails the PR that authors it.
#
# Path-filtered to scripts/repos_sync.py (plus this file): registry/prompt
# pushes — the overwhelming bulk of Mind traffic — never trigger it.
#
# PyAutoHands is deliberately NOT checked out yet: its firewall clear waits
# behind the version-stamp task that claims the repo (PyAutoHands#235 — see
# issue #198's checklist). Add its checkout when that leg lands; absent repos
# are skipped by the check itself.

on:
push:
branches: [main]
paths:
- "scripts/repos_sync.py"
- ".github/workflows/firewall_gate.yml"
pull_request:
paths:
- "scripts/repos_sync.py"
- ".github/workflows/firewall_gate.yml"
workflow_dispatch:

concurrency:
group: firewall-gate-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
firewall:
runs-on: ubuntu-latest
steps:
- name: Checkout PyAutoMind
uses: actions/checkout@v4
with:
path: PyAutoMind
# The organ repos, pinned to main: the checker is verified against the
# CURRENT organ code, the same pairing a local workspace has. All
# public, so the default GITHUB_TOKEN suffices.
- name: Checkout PyAutoBrain
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
- name: Checkout PyAutoHeart
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoHeart
path: PyAutoHeart
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install (PyYAML — the whole dependency set)
run: pip install PyYAML
- name: Drift check (all legs — absent repos are skipped)
run: python3 PyAutoMind/scripts/repos_sync.py --check --root "$GITHUB_WORKSPACE"
86 changes: 73 additions & 13 deletions scripts/repos_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
python3 repos_sync.py [--check] # drift checks only (default)
python3 repos_sync.py --write # regenerate doc blocks, then check
python3 repos_sync.py --root <dir> # override the workspace root
python3 repos_sync.py --only <label> # run one check leg (repeatable) —
# what an organ's PR CI gate calls

--write regenerates the blocks between `<!-- repos_sync:begin -->` /
`<!-- repos_sync:end -->` markers in:
Expand Down Expand Up @@ -300,6 +302,29 @@ def check_heart(root, repos):
f"Heart owner for '{name}' is '{owner}', manifest says "
f"'{owner_of(repos[name])}'"
)
# The smoke: block (heart/smoke.py's workspace table, extracted 2026-08 —
# PyAutoMind#198) names repos too, so its identity is checked the same way
# version_skew's never was. Soft-skip when absent: a Heart checkout
# predating the extraction is not drift (Heart's own strict loader fails
# loudly if the block ever disappears after it).
smoke = data.get("smoke") or {}
for key, spec in (smoke.get("workspaces") or {}).items():
if spec.get("directory") not in repos:
problems.append(
f"Heart smoke workspace '{key}' directory "
f"'{spec.get('directory')}' — not in the manifest"
)
for lib in spec.get("chain", ()):
if lib not in repos:
problems.append(
f"Heart smoke workspace '{key}' chain entry '{lib}' — "
f"not in the manifest"
)
for name in smoke.get("import_names") or {}:
if name not in repos:
problems.append(
f"Heart smoke import_names key '{name}' — not in the manifest"
)
return problems


Expand Down Expand Up @@ -613,7 +638,11 @@ def write_claude_md_pointers(root, repos):
"PyAutoBrain/agents/conductors/clone/_clone.py": {"HowToFit", "PyAutoFit", "PyAutoLabs", "PyAutoLens", "autofit_assistant", "autofit_workspace", "autolens_assistant"},
"PyAutoBrain/agents/conductors/clone/clone.sh": {"HowToFit", "PyAutoFit", "autofit_workspace", "autolens_assistant"},
"PyAutoBrain/agents/conductors/community/_community.py": {"Jammy2211", "PyAutoLabs"},
"PyAutoBrain/agents/conductors/intake/_intake.py": {"PyAutoArray", "PyAutoNerves", "PyAutoFit", "PyAutoGalaxy", "PyAutoLens", "autolens_workspace"},
# autofit_workspace: the `_upstream_noise` docstring cites measured noise
# counts ("autofit_workspace in 26 files") as the evidence for rejecting a
# file-spread threshold — the names ARE the finding; the code itself
# derives its repo sets from the body map.
"PyAutoBrain/agents/conductors/intake/_intake.py": {"PyAutoArray", "PyAutoNerves", "PyAutoFit", "PyAutoGalaxy", "PyAutoLens", "autofit_workspace", "autolens_workspace"},
"PyAutoBrain/agents/conductors/profiling/_profiling.py": {"PyAutoLabs", "autolens_profiling"},
"PyAutoBrain/agents/conductors/profiling/profiling.sh": {"autolens_profiling"},
"PyAutoBrain/agents/conductors/release/nightly.sh": {"PyAutoLabs", "PyAutoLens"},
Expand Down Expand Up @@ -644,6 +673,10 @@ def write_claude_md_pointers(root, repos):
"PyAutoBrain/tests/test_clone_conductor.py": {"autofit_assistant", "autolens_assistant"},
"PyAutoBrain/tests/test_community_conductor.py": {"Jammy2211", "PyAutoFit", "PyAutoLabs", "PyAutoLens", "admin_jammy"},
"PyAutoBrain/tests/test_hygiene_conductor.py": {"PyAutoArray", "PyAutoFit", "PyAutoGalaxy", "autofit_workspace", "autolens_workspace"},
# Load-bearing real names: the ranking tests pin resolution against the
# LIVE body map (`slug == "PyAutoLabs/PyAutoFit"`; `_upstream_noise`
# filters via KNOWN_REPOS), so synthetic names would test nothing.
"PyAutoBrain/tests/test_intake_reconcile_ranking.py": {"PyAutoArray", "PyAutoFit", "PyAutoLabs", "autofit_workspace", "autolens_workspace"},
"PyAutoBrain/tests/test_mind_commit_guard.py": {"/home/jammy", "PyAutoFit", "PyAutoLabs"},
"PyAutoBrain/tests/test_policy_seams.py": {"PyAutoFit", "PyAutoLens", "autolens_workspace"},
"PyAutoBrain/tests/test_review_inplace.py": {"PyAutoArray", "PyAutoLabs"},
Expand Down Expand Up @@ -814,6 +847,16 @@ def main():
parser.add_argument("--write", action="store_true")
parser.add_argument("--check", action="store_true")
parser.add_argument("--root", type=Path, default=None)
# An organ's PR gate must fail only on the leg that PR can cause — a Brain
# PR should not go red because a Mind-side generated block is stale. The
# label is the check's printed name, e.g. "tenant firewall (organ code)".
parser.add_argument(
"--only",
action="append",
metavar="CHECK",
help="run only this drift-check leg (repeatable; use the label the "
"check prints)",
)
args = parser.parse_args()

mind_root = Path(__file__).resolve().parents[1]
Expand Down Expand Up @@ -843,21 +886,38 @@ def main():
ORGANS_BEGIN, ORGANS_END, required=False)
write_claude_md_pointers(root, repos)

# Lazy (label -> thunk) so --only pays for exactly the selected legs.
checks = {
"PyAutoHeart/config/repos.yaml": check_heart(root, repos),
"PyAutoHands/pre_build.sh": check_pre_build(root, repos),
"ensure_workspace_labels.sh": check_labels(root, repos),
"hygiene conductor coverage": check_hygiene_coverage(root, repos, mind_root),
"local checkout origins": check_origins(root, repos),
"tenant firewall (organ code)": check_tenant_firewall(root, repos),
"organism-map blocks (generated)": check_map_blocks(root, repos, smap),
"never-rewrite-history blocks (generated)": check_history_blocks(root, repos, hpol),
"public front-door organ tables (generated)": check_public_tables(root, repos),
"hub organism blurb (organs present)": check_hub_blurb(root, repos),
"CLAUDE.md → AGENTS.md pointers": check_claude_md_pointers(root, repos),
"PyAutoHeart/config/repos.yaml": lambda: check_heart(root, repos),
"PyAutoHands/pre_build.sh": lambda: check_pre_build(root, repos),
"ensure_workspace_labels.sh": lambda: check_labels(root, repos),
"hygiene conductor coverage":
lambda: check_hygiene_coverage(root, repos, mind_root),
"local checkout origins": lambda: check_origins(root, repos),
"tenant firewall (organ code)": lambda: check_tenant_firewall(root, repos),
"organism-map blocks (generated)":
lambda: check_map_blocks(root, repos, smap),
"never-rewrite-history blocks (generated)":
lambda: check_history_blocks(root, repos, hpol),
"public front-door organ tables (generated)":
lambda: check_public_tables(root, repos),
"hub organism blurb (organs present)": lambda: check_hub_blurb(root, repos),
"CLAUDE.md → AGENTS.md pointers":
lambda: check_claude_md_pointers(root, repos),
}
if args.only:
unknown = [label for label in args.only if label not in checks]
if unknown:
raise SystemExit(
"repos_sync: unknown --only check(s): "
+ ", ".join(f"'{u}'" for u in unknown)
+ "; choose from: "
+ ", ".join(f"'{label}'" for label in checks)
)
checks = {label: checks[label] for label in args.only}
drift = False
for label, problems in checks.items():
for label, run_check in checks.items():
problems = run_check()
status = "OK" if not problems else f"{len(problems)} mismatch(es)"
print(f"check {label}: {status}")
for p in problems:
Expand Down
Loading