diff --git a/.github/workflows/listings-radar.yml b/.github/workflows/listings-radar.yml index 90c2684..ceee136 100644 --- a/.github/workflows/listings-radar.yml +++ b/.github/workflows/listings-radar.yml @@ -302,16 +302,18 @@ jobs: 'queued again.', ].join('\n'); - // Assigned so GitHub emails a direct link. Same reasoning as in - // outreach-watch.yml: a queued draft that nobody is told about - // is a draft nobody submits. + // Deliberately not assigned. A raw candidate is not finished + // work, and the owner's inbox is for finished work only - + // reviewing the first assigned queue found eight leads and zero + // actionable ones. It is labelled `do-akceptacji` after + // qualification, and ready-for-approval.yml does the assigning + // then, which is also the only way the notification fires. const created = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `Listing: ${c.full_name}`.slice(0, 200), body, labels: [LABEL], - assignees: [context.repo.owner], }); core.notice(`Queued #${created.data.number} — ${c.url}`); } diff --git a/.github/workflows/outreach-watch.yml b/.github/workflows/outreach-watch.yml index 0e2e76f..3680580 100644 --- a/.github/workflows/outreach-watch.yml +++ b/.github/workflows/outreach-watch.yml @@ -362,19 +362,18 @@ jobs: 'again.', ].join('\n'); - // Assigned, not just labelled. A draft that nobody is told - // about is a draft nobody posts, and the queue's whole value is - // speed - an answer four days late lands on a thread nobody is - // reading. GitHub emails the assignee with a direct link, which - // is the whole notification mechanism needed here: open the - // link, read the thread, post or close. + // Deliberately not assigned. A raw candidate is not finished + // work, and the owner's inbox is for finished work only - + // reviewing the first assigned queue found eight leads and zero + // actionable ones. It is labelled `do-akceptacji` after + // qualification, and ready-for-approval.yml does the assigning + // then, which is also the only way the notification fires. const created = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `Outreach: ${c.title}`.slice(0, 200), body, labels: [LABEL], - assignees: [context.repo.owner], }); core.notice(`Queued #${created.data.number} — ${c.url}`); } diff --git a/.github/workflows/ready-for-approval.yml b/.github/workflows/ready-for-approval.yml new file mode 100644 index 0000000..4a19c17 --- /dev/null +++ b/.github/workflows/ready-for-approval.yml @@ -0,0 +1,81 @@ +name: Do akceptacji + +# Turns a finished piece of work into an email in the owner's inbox, in +# Polish, with the one action to take. +# +# Two problems this exists to solve, both found the hard way on 2026-08-17: +# +# 1. The discovery bots used to assign every raw candidate to the owner, +# so the inbox filled with things that were not ready. Reviewing that +# queue found eight leads and zero actionable ones - five threads +# closed and resolved, two dead since 2022, one already answered by +# the owner. Nothing should reach the inbox until it is finished. +# +# 2. GitHub suppresses notifications for the actor who performed the +# action. Assigning the owner using the owner's own token emails +# nobody, which is exactly what happened to those ten issues. This +# workflow runs as github-actions[bot], a different actor, so the +# assignment actually notifies. +# +# So: label an issue `do-akceptacji` once it is genuinely finished, and this +# assigns the owner and posts the Polish instruction. The label is the +# handover; the email is its side effect. + +on: + issues: + types: [labeled] + +permissions: + issues: write + +jobs: + assign: + if: github.event.label.name == 'do-akceptacji' + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const issue_number = context.issue.number; + + // Assigning is what sends the mail. Skip it if the owner is + // already assigned, so re-labelling does not silently do nothing + // and look like a failure. + const current = (context.payload.issue.assignees || []).map(a => a.login); + if (!current.includes(owner)) { + await github.rest.issues.addAssignees({ + owner, repo: context.repo.repo, issue_number, + assignees: [owner], + }); + core.notice(`Przypisano ${owner} — mail powinien wyjść.`); + } else { + core.warning( + `${owner} był już przypisany, więc GitHub nie wyśle powiadomienia ` + + `o przypisaniu. Komentarz poniżej i tak trafi na maila.`); + } + + // The comment carries the instruction. Polish, because the owner + // reads these; the draft or entry inside the issue stays in the + // language it will be posted in, which is English. + const body = [ + '## Gotowe do akceptacji', + '', + 'Ta sprawa jest skompletowana. Nic tu nie trzeba dopisywać ani poprawiać.', + '', + '**Co zrobić:** przeczytaj treść powyżej i wykonaj **jedną** czynność, którą opisuje —', + 'wklej gotową odpowiedź, zgłoś wpis albo kliknij przycisk. Potem zamknij to zgłoszenie.', + '', + '**Jeśli coś jest nie tak** — nie poprawiaj tego ręcznie. Zdejmij etykietę', + '`do-akceptacji` i napisz w komentarzu, co nie gra. Poprawka trafi tam, gdzie', + 'powstał błąd, żeby nie wrócił.', + '', + '---', + '', + '_Treść przeznaczona do publikacji jest po angielsku celowo — w takim języku zostanie', + 'wklejona. Ta instrukcja jest po polsku, bo czytasz ją tylko Ty._', + ].join('\n'); + + await github.rest.issues.createComment({ + owner, repo: context.repo.repo, issue_number, body, + });