diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 4bb2b15e..9115d9d5 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,7 +1,7 @@ # CodeRabbit configuration — https://docs.coderabbit.ai/guides/configure-coderabbit # CodeRabbit reviews every non-draft PR automatically (free for this public repo). # its verdict is advisory: it gates nothing and closes nothing. the merge path is -# ci + trust-gate + CODEOWNERS, with the owner's auto-merge label as the go signal. +# ci + CODEOWNERS, with the owner's auto-merge label as the go signal. # request_changes_workflow stays on so its stance is legible at a glance, but a # request-changes review no longer blocks or reaps a pr. language: "en-US" diff --git a/.github/workflows/trust-gate.yml b/.github/workflows/trust-gate.yml deleted file mode 100644 index 3b3d85b7..00000000 --- a/.github/workflows/trust-gate.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: trust-gate -on: - pull_request: - types: [opened, synchronize, reopened, edited] -permissions: - contents: read -jobs: - trust-gate: - runs-on: ubuntu-latest - steps: - # check out the BASE ref so the classification logic is trusted, never the - # PR head (which could tamper with pr_bot.py — itself a core path). - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ github.event.pull_request.base.sha }} - persist-credentials: false - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.12" - - name: list changed files - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - PR: ${{ github.event.pull_request.number }} - run: | - # the REST files endpoint (unlike `gh pr view --json files`) carries - # previous_filename on renames — required so a rename that lands a - # core path under a new name still classifies as core. - gh api "repos/$REPO/pulls/$PR/files" --paginate > files.json - PYTHONPATH=src python -m vouch.pr_bot changed-files --json-file files.json > changed.txt - - name: fail if an untrusted author touched core - env: - ASSOC: ${{ github.event.pull_request.author_association }} - ACTOR: ${{ github.event.pull_request.user.login }} - run: | - if PYTHONPATH=src python -m vouch.pr_bot trust \ - --author-association "$ASSOC" --actor "$ACTOR"; then - echo "trusted author — core edits allowed" - exit 0 - fi - if PYTHONPATH=src python -m vouch.pr_bot core-touched --files-file changed.txt; then - echo "::error::untrusted author modified a core path; core changes need owner review" - exit 1 - fi - echo "untrusted author, no core paths touched — ok" diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 689e9ea9..ed7b8adf 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,6 +1,6 @@ # zizmor configuration — https://docs.zizmor.sh/configuration/ # -# the pr-bot workflows (auto-merge, trust-gate, ci-label, ui-screenshot-gate, +# the pr-bot workflows (auto-merge, ci-label, ui-screenshot-gate, # workflow-lint) pin their actions and annotate their triggers. these three # audits are disabled repo-wide so the new workflow-lint gate does not force a # full pin / least-permissions migration of the older workflows in a single diff --git a/CHANGELOG.md b/CHANGELOG.md index 68dfe7ad..895691c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,8 +69,18 @@ All notable changes to vouch are documented here. Format follows removes the machinery that outlived it rather than lowering a live bar. CodeRabbit still reviews every non-draft pr and still files formal approve / request-changes reviews — they are advisory now. the merge - path is ci + trust-gate + CODEOWNERS, with the owner's auto-merge label - as the go signal. + path is ci + CODEOWNERS, with the owner's auto-merge label as the go + signal. +- **the `trust-gate` workflow is removed.** it failed a pr when an author + outside the OWNER association touched a core path — a bar that the + rewritten `arm-auto-merge.yml` already enforces from the other side: + nothing arms without the owner's own label, green `diff coverage`, and a + closing reference to an owner-opened issue, and CODEOWNERS still holds + the review requirement on core paths. the `trust` pr_bot command and its + `is_trusted` helper go with it. core-path classification stays — it is + what `arm-auto-merge.yml` reads. **remove `trust-gate` from the `test` + ruleset's required checks**, or every pr will block on a check that no + longer reports. - **auto approval is the default** (`review.approver_role: trusted-agent` in the starter config): a fresh KB approves the capturing agent's proposals with no human step. nothing bypasses the gate — every write diff --git a/src/vouch/pr_bot.py b/src/vouch/pr_bot.py index 474141d5..b27fe848 100644 --- a/src/vouch/pr_bot.py +++ b/src/vouch/pr_bot.py @@ -2,9 +2,9 @@ Pure stdlib — no model dependency, no vouch-runtime imports. The CI workflows call ``python -m vouch.pr_bot `` for every decision that must be -trustworthy: an author's trust tier, whether a PR touches core/ui paths, whether -a UI PR carries before/after screenshots, and whether a labeled PR may arm -native auto-merge. CodeRabbit runs as a GitHub App and still comments on PRs, +trustworthy: whether a PR touches core/ui paths, whether a UI PR carries +before/after screenshots, and whether a labeled PR may arm native auto-merge. +CodeRabbit runs as a GitHub App and still comments on PRs, but its verdict no longer gates anything — nothing here reads it. """ from __future__ import annotations @@ -43,8 +43,6 @@ "webapp/**", ) -_OWNER_ASSOCIATION = "OWNER" -_BOT_ACTORS = frozenset({"dependabot[bot]"}) def _match(path: str, glob: str) -> bool: @@ -72,10 +70,6 @@ def klass(changed: Sequence[str]) -> str: return "core" if c["is_core"] else "ui" if c["is_ui"] else "code" -def is_trusted(author_association: str, actor: str) -> bool: - return author_association == _OWNER_ASSOCIATION or actor in _BOT_ACTORS - - _GH_IMAGE = re.compile( r"""(?:!\[[^\]]*\]\(\s*|]*\bsrc\s*=\s*["']?)""" r"""(?:https?://(?:user-images\.githubusercontent\.com/""" @@ -222,10 +216,6 @@ def main(argv: Sequence[str] | None = None) -> int: sp = sub.add_parser(name) sp.add_argument("--files-file", required=True) - t = sub.add_parser("trust") - t.add_argument("--author-association", required=True) - t.add_argument("--actor", required=True) - s = sub.add_parser("has-screenshots") s.add_argument("--body-file", required=True) @@ -253,8 +243,6 @@ def main(argv: Sequence[str] | None = None) -> int: return 0 if classify(_read_lines(ns.files_file))["is_core"] else 1 if ns.cmd == "ui-touched": return 0 if _touches(_read_lines(ns.files_file), UI_GLOBS) else 1 - if ns.cmd == "trust": - return 0 if is_trusted(ns.author_association, ns.actor) else 1 if ns.cmd == "has-screenshots": with open(ns.body_file, encoding="utf-8") as fh: return 0 if has_before_after_screenshots(fh.read()) else 1 diff --git a/tests/test_pr_bot.py b/tests/test_pr_bot.py index 951bd8da..3cfbe958 100644 --- a/tests/test_pr_bot.py +++ b/tests/test_pr_bot.py @@ -27,12 +27,6 @@ def test_core_paths_all_flagged(): assert pr_bot.classify([f])["is_core"] is True, f -def test_trust(): - assert pr_bot.is_trusted("OWNER", "plind-junior") is True - assert pr_bot.is_trusted("CONTRIBUTOR", "rando") is False - assert pr_bot.is_trusted("NONE", "dependabot[bot]") is True - - def test_screenshots_two_gh_images(): body = ( "before\n![a](https://user-images.githubusercontent.com/1/a.png)\n" @@ -89,14 +83,6 @@ def test_cli_classify_print_klass(tmp_path): assert out.stdout == "ui" -def test_cli_trust_exit_codes(): - ok = subprocess.run([sys.executable, "-m", "vouch.pr_bot", "trust", - "--author-association", "OWNER", "--actor", "plind-junior"]) - bad = subprocess.run([sys.executable, "-m", "vouch.pr_bot", "trust", - "--author-association", "NONE", "--actor", "rando"]) - assert ok.returncode == 0 and bad.returncode == 1 - - def test_extract_changed_paths_plain_file(): files_json = '[{"filename": "src/vouch/context.py"}]' assert pr_bot.extract_changed_paths(files_json) == ["src/vouch/context.py"] @@ -113,8 +99,8 @@ def test_extract_changed_paths_includes_previous_filename_on_rename(): def test_rename_of_core_path_still_classifies_core(): - # a rename that lands a core path under a new name must not slip past - # trust-gate — the pre-rename path has to stay in the classified list. + # a rename that lands a core path under a new name must not slip past the + # core gate — the pre-rename path has to stay in the classified list. files_json = ( '[{"filename": "src/vouch/web_server.py", "status": "renamed", ' '"previous_filename": "src/vouch/http_server.py"}]'