From 5c8254cc50e87cdaee05a333ba13c77348716a40 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Sat, 17 Oct 2026 20:20:12 +0000 Subject: [PATCH] TIME_PROTECTED governs every protected path except the workflow directories (#81) TIME_PROTECTED governs every protected path except the workflow directories (#81) touches_protected() hard-coded all of .github/ and .gitea/ on top of TIME_PROTECTED, so the engine under .github/time/ could never land without a human. Only the workflow directories stay hard-coded; DEFAULT_PROTECTED is unchanged. With this repo's TIME_PROTECTED narrowed, card PRs under .github/time/ with a trusted Approve and green checks merge via the time bot. CI green on 3b757fa. Squash-merged by the CEO session on the owner's explicit instruction. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD --- .github/time/test_time.py | 8 ++++++++ .github/time/time.py | 4 +++- CHANGELOG.md | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/time/test_time.py b/.github/time/test_time.py index 4b1dfc7..d140338 100644 --- a/.github/time/test_time.py +++ b/.github/time/test_time.py @@ -281,6 +281,14 @@ assert not merged(w) and "objected" in w.posted[0][1] # protected paths -> human merges w = run(pr_world(files=[".github/workflows/ci.yml"]), {**K, "TIME_AUTOMERGE": "on"}) assert not merged(w) and "protected paths" in w.posted[0][1] +# the default protects all of .github/, the engine included; TIME_PROTECTED can narrow that to the workflow dirs, which stay +# protected whatever it says +w = run(pr_world(files=[".github/time/time.py", "CHANGELOG.md"]), {**K, "TIME_AUTOMERGE": "on"}); assert not merged(w) +NARROW = ".github/workflows/,.gitea/workflows/,deploy,infra/,.env,secret" +w = run(pr_world(files=[".github/time/time.py", "CHANGELOG.md"]), {**K, "TIME_AUTOMERGE": "on", "TIME_PROTECTED": NARROW}); assert merged(w) +w = run(pr_world(files=[".gitea/workflows/ci.yml"]), {**K, "TIME_AUTOMERGE": "on", "TIME_PROTECTED": "deploy"}) +assert not merged(w) and "protected paths" in w.posted[0][1] +os.environ.pop("TIME_PROTECTED", None) # failing or missing checks block the merge even when approved w = run(pr_world(checks=[{"status": "failure", "context": "ci"}]), {**K, "TIME_AUTOMERGE": "on"}) assert not merged(w) and "checks are failing" in w.posted[0][1] diff --git a/.github/time/time.py b/.github/time/time.py index af389b4..cb2c413 100644 --- a/.github/time/time.py +++ b/.github/time/time.py @@ -542,7 +542,9 @@ def verdict(pr: dict, since: str) -> str | None: def touches_protected(pr_number: int) -> list[str]: paths = [p.strip() for p in (os.environ.get("TIME_PROTECTED") or DEFAULT_PROTECTED).split(",") if p.strip()] - paths = list(dict.fromkeys([".github/", ".gitea/", *paths])) # workflows stay a human's merge whatever TIME_PROTECTED says + # workflows stay a human's merge whatever TIME_PROTECTED says; the rest is TIME_PROTECTED's call: the default keeps all + # of .github/, and a repo that trusts its reviewer can narrow it so the engine under .github/time/ lands like other code + paths = list(dict.fromkeys([".github/workflows/", ".gitea/workflows/", *paths])) files, page = [], 1 while True: # every file, not the first 100; a rename counts under both names batch = gh("GET", f"/repos/{REPO}/pulls/{pr_number}/files?limit=100&page={page}") diff --git a/CHANGELOG.md b/CHANGELOG.md index 09019f5..a8d5f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - The second fix round escalates to Kimi's code model (`venice:kimi code`) instead of grok 4.7: still a different model from the first round, at a fraction of the cost. `TIME_VENTURE_ESCALATE` overrides it. ### Added +- `TIME_PROTECTED` now governs every protected path except the workflow directories (`.github/workflows/`, `.gitea/workflows/`), which stay a human's merge whatever it says. The default still protects all of `.github/`; a repo that trusts its reviewer can set `TIME_PROTECTED=.github/workflows/,.gitea/workflows/,deploy,infra/,.env,secret` so the engine under `.github/time/` merges itself on Approve + green like any other code. - Objections go back to the worker. `/time retry` on a card whose PR is open hands the worker the reviewer's latest comments (marked as the reviewer's, strangers' left out) on the same branch; `open_pr` on an open PR updates its description instead of failing; the card goes back to `dispatched`, so the next beat reviews again and an objection older than the push no longer counts. - A batch's children start in the tick that spawned them (GitHub lists a card made a moment ago only later, so they used to wait a tick). - Build briefs keep claims to what the code does: no promised features or schedules, and the trending repos are the wave a venture rides, never something it "builds on". Fix rounds take a failing claim off the page rather than building a feature in a hurry. In the first live batch, every fix round was a claim the code didn't back.