diff --git a/.github/time/test_time.py b/.github/time/test_time.py index 7641765..0b874f1 100644 --- a/.github/time/test_time.py +++ b/.github/time/test_time.py @@ -58,8 +58,9 @@ class World: path = url.replace(tm.GH, "") self.calls.append((method, path, body)) if path.endswith("/branches") and method == "POST": self.branches = getattr(self, "branches", []) + [body["new_branch_name"]]; return {} - if "/collaborators/" in path and path.endswith("/permission"): - return {"permission": "write" if path.split("/")[-2] in self.trusted else "read"} + if "/collaborators/" in path: + if path.split("/")[-1] in self.trusted: return {} + raise RuntimeError(f"GET {path} -> 404: not a collaborator") if "/git/trees/" in path: return {"tree": [{"path": k, "type": "blob"} for k in self.files]} if "/contents/" in path: fp = path.split("/contents/")[1].split("?")[0] diff --git a/.github/time/time.py b/.github/time/time.py index 2bad224..6a5f877 100644 --- a/.github/time/time.py +++ b/.github/time/time.py @@ -94,17 +94,23 @@ _trust: dict[str, bool] = {} def trusted(login: str | None) -> bool: - """Write access to this repo (owner, org team, collaborator). Strangers on a public repo can comment; they can't - approve, object or retry. Time's own user never counts as a reviewer.""" + """May this login approve, object, retry or spend seat credits? Yes for the repo's owner, for anyone listed in + TIME_TRUSTED (comma-separated logins; org owners go here, since the Actions token cannot read org membership), + and for repo collaborators. Strangers on a public repo can comment; none of that counts. Time's own user never + counts as a reviewer.""" if not login or login == TIME_BOT: return False if login not in _trust: - try: - perm = gh("GET", f"/repos/{REPO}/collaborators/{login}/permission").get("permission") - except RuntimeError as e: - print(f"trust({login}): {e}") - perm = None - _trust[login] = perm in ("admin", "write", "owner") + names = {n.strip() for n in (os.environ.get("TIME_TRUSTED") or "").split(",") if n.strip()} + ok = login == REPO.split("/")[0] or login in names + if not ok: + try: + gh("GET", f"/repos/{REPO}/collaborators/{login}") # 204 when a collaborator, 404 otherwise + ok = True + except RuntimeError as e: + if "-> 404" not in str(e): + print(f"trust({login}): {e}") + _trust[login] = ok return _trust[login] diff --git a/.github/workflows/heartbeat.yml b/.github/workflows/heartbeat.yml index 2d838db..5bcf8e1 100644 --- a/.github/workflows/heartbeat.yml +++ b/.github/workflows/heartbeat.yml @@ -35,6 +35,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }} + TIME_TRUSTED: ${{ vars.TIME_TRUSTED }} # org owners etc.; collaborators are trusted without being listed TIME_RUN_IMAGE: node:22-bookworm # the worker's shell runs in a throwaway container, not next to these secrets TIME_MODE: ${{ vars.TIME_MODE }} TIME_DAILY_CAP: ${{ vars.TIME_DAILY_CAP }} diff --git a/.github/workflows/mention.yml b/.github/workflows/mention.yml index 4b45038..1ec9392 100644 --- a/.github/workflows/mention.yml +++ b/.github/workflows/mention.yml @@ -30,3 +30,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }} + TIME_TRUSTED: ${{ vars.TIME_TRUSTED }} # org owners etc.; collaborators are trusted without being listed