Skip to content

Commit 4e15d94

Browse files
committed
ci(health): run only by hand and write nothing
The health check no longer runs on a schedule, updates the Org health report issue, or mentions anyone. A person starts it from the Actions tab and the report goes to the run's step summary. An account here was suspended over automation, so nothing in this repository acts on its own. The workflow keeps GITHUB_TOKEN with read-only permissions. Every action in health.yml and test.yml is a GitHub-owned action pinned by full SHA. TechAPI's label-triggered final check is the one sanctioned use of TechEngineBot's token, so it is allowed for that repository only.
1 parent 4999837 commit 4e15d94

6 files changed

Lines changed: 30 additions & 108 deletions

File tree

‎.github/workflows/health.yml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
name: health
22

3+
# Manual only. Automation under a personal account got it suspended, so
4+
# nothing here runs on a schedule or writes to GitHub: the report goes to
5+
# this run's step summary.
36
on:
4-
schedule:
5-
- cron: "0 0 * * *" # daily, 09:00 KST
67
workflow_dispatch:
78

89
permissions:
910
contents: read
10-
issues: write # keeps the single "Org health report" issue current
1111
actions: read
1212

1313
concurrency:
@@ -19,11 +19,11 @@ jobs:
1919
runs-on: ubuntu-latest
2020
timeout-minutes: 10
2121
steps:
22-
- uses: actions/checkout@v4
23-
- uses: actions/setup-python@v5
22+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
2424
with:
2525
python-version: "3.12"
2626
- name: Check every repo and public endpoint
2727
env:
2828
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29-
run: python -m machine.health --issue
29+
run: python -m machine.health

‎.github/workflows/test.yml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ on:
55
push:
66
branches: [develop, main]
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
test:
1013
runs-on: ubuntu-latest
1114
timeout-minutes: 10
1215
steps:
13-
- uses: actions/checkout@v4
14-
- uses: actions/setup-python@v5
16+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
1518
with:
1619
python-version: "3.12"
1720
- run: |

‎README.md‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ TechMachine looks after the repositories that hold it.
66

77
## Health check
88

9-
`machine/health.py` runs daily and keeps one issue, **Org health report**,
10-
up to date. For every repository in [`machine/repos.json`](machine/repos.json)
9+
`machine/health.py` runs when a person starts it — the `health` workflow's
10+
*Run workflow* button, or locally. For every repository in [`machine/repos.json`](machine/repos.json)
1111
it reads the newest completed run of each active workflow on each tracked
1212
branch, and it fetches the public endpoints the TechAPI homepage depends on.
1313

@@ -25,21 +25,21 @@ other than `GITHUB_TOKEN`. Automation here must act as `github-actions[bot]`,
2525
never as a person: workflows running under a personal token are how a
2626
maintainer's account got suspended. Disabling the workflow in the Actions
2727
settings clears the finding; a non-token secret a repository genuinely needs
28-
goes in that repository's `allowed_secrets` in `repos.json`.
28+
goes in that repository's `allowed_secrets` in `repos.json` — TechAPI's
29+
label-triggered final check is the one place TechEngineBot's token is allowed.
30+
31+
It only reads. The report goes to stdout and the run's step summary; it
32+
writes nothing to GitHub, mentions nobody, and runs on no schedule — an
33+
account here was suspended over automation, and a report nobody asked for is
34+
how that starts.
2935

3036
```bash
31-
python -m machine.health # print the report
32-
python -m machine.health --issue # also update the issue (needs GITHUB_TOKEN)
37+
python -m machine.health # print the report (GITHUB_TOKEN raises the rate limit)
3338
python -m pytest -q
3439
```
3540

3641
Adding a repository or endpoint is one entry in `machine/repos.json`.
3742

38-
When the set of problems changes, the check comments on the issue and
39-
@-mentions `notify` from that file — editing an issue body notifies nobody.
40-
It stays quiet when the same failure recurs (the fingerprint is
41-
workflow + result, not the run link) and never pings for an all-clear.
42-
4343
## Satellite repositories
4444

4545
Categories that do not belong in TechAPI live in their own repository (games:

‎machine/health.py‎

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
by an environment rule leaves no log at all. Each of those went unnoticed for
77
days. This treats anything other than success/skipped as a problem.
88
9-
Run with: python -m machine.health [--issue]
9+
It only reads. The report goes to stdout and the run's step summary; nothing
10+
is written to GitHub and nobody is mentioned, so it runs only when a person
11+
starts it.
12+
13+
Run with: python -m machine.health
1014
"""
1115

1216
from __future__ import annotations
@@ -24,7 +28,6 @@
2428

2529
API = "https://api.github.com"
2630
CONFIG = Path(__file__).with_name("repos.json")
27-
ISSUE_TITLE = "Org health report"
2831

2932
# Conclusions that mean the run did its job. Everything else is surfaced —
3033
# `cancelled` and `timed_out` included, because that is how silent failures look.
@@ -178,58 +181,8 @@ def render(findings: list[Finding], config: dict[str, Any]) -> str:
178181
return "\n".join(lines) + "\n"
179182

180183

181-
def fingerprint(findings: list[Finding]) -> str:
182-
"""Stable identity of a problem set: what is broken, not when it was checked."""
183-
return ",".join(sorted(f"{f.where}={f.what}" for f in findings))
184-
185-
186-
def alert_needed(previous_body: str, current: str) -> bool:
187-
"""Alert only when the set of problems changed — and never for all clear.
188-
189-
A daily ping about the same known failure trains people to ignore the
190-
ping; the point is to hear about the *new* one.
191-
"""
192-
if not current:
193-
return False
194-
return f"<!-- fingerprint:{current} -->" not in previous_body
195-
196-
197-
def publish(body: str, token: str, repo: str, findings: list[Finding], mention: str) -> None:
198-
"""Keep one open issue up to date instead of opening one per run."""
199-
def call(method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
200-
data = json.dumps(payload).encode("utf-8") if payload is not None else None
201-
request = urllib.request.Request(f"{API}{path}", data=data, method=method, headers={
202-
"Accept": "application/vnd.github+json",
203-
"Authorization": f"Bearer {token}",
204-
"Content-Type": "application/json",
205-
})
206-
with urllib.request.urlopen(request, timeout=30) as response:
207-
return json.loads(response.read().decode("utf-8") or "null")
208-
209-
current = fingerprint(findings)
210-
stamped = f"{body}\n<!-- fingerprint:{current} -->\n"
211-
212-
issues = call("GET", f"/repos/{repo}/issues?state=open&per_page=100")
213-
existing = next((i for i in issues if i.get("title") == ISSUE_TITLE), None)
214-
if existing:
215-
number = existing["number"]
216-
previous = existing.get("body") or ""
217-
call("PATCH", f"/repos/{repo}/issues/{number}", {"body": stamped})
218-
else:
219-
number = call("POST", f"/repos/{repo}/issues", {"title": ISSUE_TITLE, "body": stamped})["number"]
220-
previous = ""
221-
222-
# A comment, not an edit: editing an issue body notifies nobody.
223-
if mention and alert_needed(previous, current):
224-
lines = "\n".join(f"- {f.where}: **{f.what}**" for f in findings)
225-
call("POST", f"/repos/{repo}/issues/{number}/comments",
226-
{"body": f"@{mention} the org health report changed:\n\n{lines}"})
227-
228-
229184
def main() -> int:
230-
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
231-
parser.add_argument("--issue", action="store_true", help="update the health issue")
232-
args = parser.parse_args()
185+
argparse.ArgumentParser(description=__doc__.splitlines()[0]).parse_args()
233186

234187
config = json.loads(CONFIG.read_text(encoding="utf-8"))
235188
token = os.environ.get("GITHUB_TOKEN")
@@ -240,9 +193,6 @@ def main() -> int:
240193
summary = os.environ.get("GITHUB_STEP_SUMMARY")
241194
if summary:
242195
Path(summary).write_text(report, encoding="utf-8")
243-
if args.issue and token:
244-
publish(report, token, os.environ.get("GITHUB_REPOSITORY", "GetTechAPI/TechMachine"),
245-
findings, config.get("notify", ""))
246196
# The report is the output; a red run would only add a second alert.
247197
return 0
248198

‎machine/repos.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
{
2-
"notify": "Seungpyo1007",
32
"repos": [
43
{
54
"name": "GetTechAPI/TechAPI",
65
"branches": [
76
"develop",
87
"main"
8+
],
9+
"allowed_secrets": [
10+
"TECHENGINEBOT_TOKEN"
911
]
1012
},
1113
{

‎tests/test_health.py‎

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,39 +53,6 @@ def test_render_all_clear_and_problem_table():
5353
assert "1 problem" in problems and "| cancelled |" in problems
5454

5555

56-
# --- alerts -----------------------------------------------------------------
57-
58-
from machine.health import Finding, alert_needed, fingerprint # noqa: E402
59-
60-
BROKEN = [Finding("TechEngine@main · weekly-refresh", "cancelled")]
61-
62-
63-
def test_new_problem_alerts():
64-
assert alert_needed("", fingerprint(BROKEN))
65-
66-
67-
def test_same_problem_twice_does_not_alert_again():
68-
body = f"report\n<!-- fingerprint:{fingerprint(BROKEN)} -->\n"
69-
assert not alert_needed(body, fingerprint(BROKEN))
70-
71-
72-
def test_a_different_problem_alerts():
73-
body = f"report\n<!-- fingerprint:{fingerprint(BROKEN)} -->\n"
74-
worse = BROKEN + [Finding("TechAPI@main · deploy-pages", "failure")]
75-
assert alert_needed(body, fingerprint(worse))
76-
77-
78-
def test_all_clear_never_alerts():
79-
body = f"report\n<!-- fingerprint:{fingerprint(BROKEN)} -->\n"
80-
assert not alert_needed(body, fingerprint([]))
81-
82-
83-
def test_fingerprint_ignores_order_and_links():
84-
a = [Finding("x", "failure", "https://run/1"), Finding("y", "cancelled", "https://run/2")]
85-
b = [Finding("y", "cancelled", "https://run/9"), Finding("x", "failure", "https://run/8")]
86-
assert fingerprint(a) == fingerprint(b)
87-
88-
8956
# --- automation must not run as a person -----------------------------------
9057

9158
from pathlib import Path # noqa: E402

0 commit comments

Comments
 (0)