From 6a5b756434d9f5d845124d9f383a666aec205636 Mon Sep 17 00:00:00 2001 From: Nas <36938454+Na5co@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:12:14 +0300 Subject: [PATCH] bot: a privacy policy, a feature card, and Marketplace events What a Marketplace listing requires and an installer deserves: a privacy policy that says what the App touches (a shallow fetch, in memory, for seconds), what it keeps (nothing), and who else sees it (GitHub, and Cloud Run). A listed app also has to receive marketplace_purchase events; the bot notes them in its log instead of reading them as a malformed pull request. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 2 + bot/PRIVACY.md | 80 +++++++++++++++++++++++++++++++++++++++ bot/README.md | 13 ++++++- bot/src/main.rs | 7 ++++ bot/src/webhook.rs | 39 +++++++++++++++++++ docs/img/feature-card.svg | 32 ++++++++++++++++ 6 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 bot/PRIVACY.md create mode 100644 docs/img/feature-card.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index b9d43ba..14dff56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to polygo. Versions follow [SemVer](https://semver.org); dat ## Unreleased +- The bot notes Marketplace events (`marketplace_purchase`) in its log rather than treating them as a malformed pull request; a listed app has to receive them. `bot/PRIVACY.md` is the App's privacy policy, which a Marketplace listing requires and anyone installing it deserves; `docs/img/feature-card.svg` is the listing's card. + - A finding about a whole file (`locale`, and a `syntax` error whose parser gave no position) now sits on line 1 instead of nowhere. A review can only comment on a line, so those findings never reached the pull request that introduced them: the bot reported a pull request adding an unlisted locale as clean, which is the failure the `locale` code exists to end. - `locale` (warning): a locale file sitting next to the ones being checked whose locale is not in `target_locales` — a translator's whole language, silently unchecked. Found the same way as the `syntax` gap: a test fixture added `locales/it.json`, forgot to list `it`, and the reviewer reported a clean pull request. Only real language tags count, so a `README.json` or an Android `values-night` is not mistaken for a locale, and a file that does not parse keeps its `syntax` error instead of being reported twice. diff --git a/bot/PRIVACY.md b/bot/PRIVACY.md new file mode 100644 index 0000000..cf2df19 --- /dev/null +++ b/bot/PRIVACY.md @@ -0,0 +1,80 @@ +# polygo-bot — privacy + +*Last updated: 23 September 2026. The app is [polygo-bot](https://github.com/apps/polygo-bot), +published by [@Na5co](https://github.com/Na5co) and built on +[polygo](https://github.com/Na5co/polygo), which is MIT-licensed and readable in full — +including [the bot itself](https://github.com/Na5co/polygo/tree/main/bot).* + +## What it stores + +Nothing. + +There is no database, no queue and no object storage. The service keeps no copy of your +code, your strings, your translations or your findings, and it remembers nothing between +one pull request and the next. + +## What it does with your repository + +When a pull request touches a localization file, and only then: + +1. It asks GitHub for the pull request's diff, and stops there unless a string file is in + it. +2. It fetches `refs/pull//head` **shallow** (one commit) into a temporary directory + inside the container. +3. It parses the localization files, compares translations against the source, and builds + the review. +4. It posts the review through the GitHub API. +5. It deletes the temporary directory. When the container itself stops, everything in it + goes with it. + +No part of your repository is sent anywhere else. In particular: **no language model is +involved**, and nothing is sent to any model provider or third-party service. The check is +deterministic code running in the container. + +## What it holds while it works + +- **An installation access token**, obtained from GitHub per event and held in memory for + the seconds the review takes. It is never written to disk and never logged. +- **The contents of your localization files**, in memory, for the same seconds. + +## What it logs + +One line per event, of the form `owner/repo#12: 3 comment(s) posted`, plus errors. Logs +hold repository names, pull-request numbers and counts — never file contents, never tokens. +They are kept by the hosting platform (Google Cloud Run, region `us-central1`) under its +default retention and are used to see that the service is working. + +## Permissions, and why each one + +| permission | why | +|---|---| +| Contents: **read** | to fetch the branch and read its localization files | +| Pull requests: **read & write** | to read the diff and leave the review | +| Metadata: read | required by GitHub for any app | + +It never writes to your repository: it opens no branches, pushes no commits and changes no +files. A suggestion it leaves is applied by *you* clicking **Apply suggestion**, as a commit +authored by you. It posts reviews as `COMMENT` and never approves or blocks a pull request. + +## Sub-processors + +- **GitHub** — the source of every event and the destination of every review. +- **Google Cloud Run** (Google LLC) — where the container runs. + +No analytics, no trackers, no advertising, no third-party telemetry. + +## Removing it + +Uninstall the app from **Settings → Applications → Installed GitHub Apps**. Its access ends +immediately. Since nothing is stored, there is nothing to delete and nothing to request. + +## Security problems + +Report anything you find privately through +[GitHub's security advisories](https://github.com/Na5co/polygo/security/advisories/new) for +the polygo repository, or by email (see Support below). Please do not open a public issue +for a vulnerability. + +## Support + +Open an issue at [github.com/Na5co/polygo/issues](https://github.com/Na5co/polygo/issues). diff --git a/bot/README.md b/bot/README.md index 53dcae1..7062f3c 100644 --- a/bot/README.md +++ b/bot/README.md @@ -79,6 +79,12 @@ docker run -p 8080:8080 \ process, one container, no state: run it anywhere that can run a container, and scale it by running more. +## Privacy and support + +[bot/PRIVACY.md](PRIVACY.md) is the App's privacy policy — what it touches, what it keeps +(nothing), who else sees it (GitHub, and the platform it runs on). Support is +[the issue tracker](https://github.com/Na5co/polygo/issues). + ## Its face `docs/img/bot-avatar.svg` is the avatar: the wordmark's globe inside a speech bubble, with @@ -87,9 +93,14 @@ renderer produces the same image. GitHub wants a PNG of at least 200×200 for an on a Mac needs no extra tool: ```sh -qlmanage -t -s 512 -o . docs/img/bot-avatar.svg # writes bot-avatar.svg.png +rsvg-convert -w 512 -h 512 docs/img/bot-avatar.svg -o avatar.png # the App's logo +rsvg-convert -w 1280 -h 640 docs/img/feature-card.svg -o card.png # the Marketplace card ``` +(`brew install librsvg`. `qlmanage -t -s 512 -o . docs/img/bot-avatar.svg` works for the +square avatar without installing anything, but forces a square canvas, which crops the +card.) + ## Create the App GitHub → Settings → Developer settings → **GitHub Apps** → New GitHub App. diff --git a/bot/src/main.rs b/bot/src/main.rs index eef9e74..91aef52 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -78,6 +78,13 @@ fn main() -> Result<()> { let _ = req.respond(text(401, "bad signature")); continue; } + if let Ok(v) = serde_json::from_slice::(&body) + && let Some(what) = webhook::marketplace(&kind, &v) + { + log(&what); + let _ = req.respond(text(202, "accepted")); + continue; + } let event = match serde_json::from_slice(&body) .map_err(anyhow::Error::from) .and_then(|v: serde_json::Value| webhook::pull_request(&kind, &v)) diff --git a/bot/src/webhook.rs b/bot/src/webhook.rs index a9423bd..f053709 100644 --- a/bot/src/webhook.rs +++ b/bot/src/webhook.rs @@ -27,6 +27,23 @@ fn decode_hex(s: &str) -> Option> { .collect() } +/// What a Marketplace event says, for the log: someone subscribed, changed plan or +/// cancelled. GitHub requires a listed app to receive these; the bot has one free plan, so +/// there is nothing to do about them beyond knowing they happened. +pub fn marketplace(kind: &str, payload: &serde_json::Value) -> Option { + if kind != "marketplace_purchase" { + return None; + } + let action = payload["action"].as_str().unwrap_or("?"); + let account = payload["marketplace_purchase"]["account"]["login"] + .as_str() + .unwrap_or("?"); + let plan = payload["marketplace_purchase"]["plan"]["name"] + .as_str() + .unwrap_or("?"); + Some(format!("marketplace: {account} {action} the {plan} plan")) +} + /// A pull request the bot should look at. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Event { @@ -43,6 +60,9 @@ pub fn pull_request(kind: &str, payload: &serde_json::Value) -> Result + + + + + + + + + + + + + + + + + + + polygo-bot + Reviews the translations in every pull request. + + + + locales/it.json:3 + placeholder name translated: {{user}} → {{utente}} + + "greeting": "Bentornato, {{user}}!" + one click + + .xcstrings · strings.xml · .arb · i18next · .po · .resx · .strings + Milliseconds. No model. Nothing stored. +