Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

Docs: environment reviews in webhooks, notifications, audit log and limits; runner claim (#122)

#116 added required reviewers and wait timers, and #118 closed the personal-token runner claim. The webhook pages lacked deployment_review, the notifications page its notification and email, the audit log deployment.approve and deployment.reject, and Limits the reviewer, wait-timer and comment limits. The API reference still said a personal token could claim runner jobs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016q5wwBFck8JHq14gHUM2CL
Canberk Demirci authored · GitHub committed signed1 parent 4d87efbcommit 1d1a71f7c1aa48232fdfe8fe713ca7d26c6312c2Browse files
6 files changed+64−8
Files (6)
modifiedapps/diffinery/docs/accounts/notifications.md+2−0View file
@@ -32,6 +32,7 @@
3232 - issue and pull request activity you take part in, when **Participating → On Diffinery** is on,
3333 - activity in repositories you watch with **All activity**, when **Watching → On Diffinery** is on,
3434 - failed workflow runs you started, when **Actions → On Diffinery** is on,
35+- a run waiting for your approval to deploy to an environment where you are a [required reviewer](/docs/actions/environments#required-reviewers-and-wait-timers) ("Deployment to production in acme/website is waiting for your review"), when **Actions → On Diffinery** is on,
3536 - someone inviting you by username to an organization or a repository (kind `invitation`, for example "<admin> invited you to acme as member"); it opens **Settings → Invitations** (see [Invitations you receive](/docs/accounts/invitations#invitations-you-receive)),
3637 - someone giving you a repository role at once, without an invitation, because you are a member of the organization that owns the repository or already a collaborator on it (kind `repo access`, for example "<admin> gave you write access to acme/website"). This comes from **Add people**, the role menu on **Settings → Collaborators** or the API.
3738
@@ -227,6 +228,7 @@
227228 | --- | --- | --- | --- | --- |
228229 | `thread` | `[acme/website] Cache build artifacts (PR #42)` | Participating or Watching | `author`, `comment`, `mention`, `assign`, `review_requested`, `manual`, `subscribed` or `your_activity` | Activity on an issue or pull request (see the next table) |
229230 | `run` | `[acme/website] Run failed: CI - main (3f9c2a1)` | Actions | `ci_activity` | A workflow run you started failed or timed out (or succeeded, if you asked for all runs) |
231+| `deployment_review_requested` | `[acme/website] Deployment to production is waiting for your review` | Actions | `review_requested` | A run waits for a required reviewer of an environment, and you are one of them (not when you started the run) |
230232 | `signin_new` | `[Diffinery] New sign-in to your account` | Security | `security_alert` | A passkey sign-in from a browser and passkey combination not seen before, or any email-recovery sign-in |
231233 | `passkey_added` | `[Diffinery] A passkey was added to your account` | Security | `security_alert` | You added a passkey in settings or with a sign-in link |
232234 | `passkey_removed` | `[Diffinery] A passkey was removed from your account` | Security | `security_alert` | You removed a passkey |
modifiedapps/diffinery/docs/reference/limits.md+4−0View file
@@ -67,6 +67,10 @@
6767 | Check run name | 255 characters | `POST .../check-runs`. |
6868 | Deployment `required_contexts` | 100 contexts | `POST .../deployments`. |
6969 | Environment name | 1 to 255 characters | No `/`, `\` or control characters. |
70+| Required reviewers | 6 people per environment | `PUT .../environments/{env}`: `422 an environment can have at most 6 required reviewers`. See [Required reviewers and wait timers](/docs/actions/environments#required-reviewers-and-wait-timers). |
71+| Wait timer | 0 to 43,200 minutes (30 days) | `wait_timer` on `PUT .../environments/{env}`. |
72+| Waiting for a reviewer | 30 days | A job still waiting for its environment's reviewers after 30 days fails. |
73+| Deployment review comment | 4,000 characters | `POST .../actions/runs/{run_id}/pending_deployments`: `422 comment is longer than 4000 characters`. |
7074
7175 ## Search
7276
modifiedapps/diffinery/docs/reference/webhook-events.md+51−2View file
@@ -5,7 +5,7 @@
55
66 This page lists every event a Diffinery [webhook](/docs/repositories/webhooks) can deliver, the actions of each event, and the exact payload fields, with an example for each. Use it to write or adapt a webhook receiver.
77
8-There are two kinds of payload. `push`, `pull_request`, `pull_request_review`, `issues`, `issue_comment` and `workflow_run` have small Diffinery payloads with ids and links. `release`, `status`, `deployment`, `deployment_status`, `repository_dispatch` and `workflow_dispatch` have GitHub's payload structure.
8+There are two kinds of payload. `push`, `pull_request`, `pull_request_review`, `issues`, `issue_comment` and `workflow_run` have small Diffinery payloads with ids and links. `release`, `status`, `deployment`, `deployment_status`, `deployment_review`, `repository_dispatch` and `workflow_dispatch` have GitHub's payload structure.
99
1010 ## Common structure
1111
@@ -25,7 +25,7 @@
2525 | Field | Type | Description |
2626 |---|---|---|
2727 | `action` | string | The action, for events that have one. Absent for `push`, `status` and `workflow_dispatch`. |
28-| `at` | string | ISO 8601 time the event happened. In the small payloads and in `workflow_dispatch`; `release`, `status`, `deployment`, `deployment_status` and `repository_dispatch` have no `at` (use their own `created_at` and `updated_at` fields). |
28+| `at` | string | ISO 8601 time the event happened. In the small payloads and in `workflow_dispatch`; `release`, `status`, `deployment`, `deployment_status`, `deployment_review` and `repository_dispatch` have no `at` (use their own `created_at` and `updated_at` fields). |
2929 | `repository` | object | The repository (see below) |
3030 | `sender` | object | The account that caused the event (see below) |
3131
@@ -669,6 +669,55 @@
669669 }
670670 ```
671671
672+## deployment_review
673+
674+Sent when a workflow run starts waiting on an environment's [required reviewers](/docs/actions/environments#required-reviewers-and-wait-timers), and when a reviewer decides, on the run page or with `POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments`. An environment with only a wait timer sends no event.
675+
676+| Action | Sent when | Sender |
677+|---|---|---|
678+| `requested` | A job that names the environment becomes ready and has to wait for a reviewer (once per environment and run) | Who started the run |
679+| `approved` | A reviewer approves the environment for the run | The reviewer |
680+| `rejected` | A reviewer rejects it | The reviewer |
681+
682+| Field | Type | Description |
683+|---|---|---|
684+| `action` | string | `requested`, `approved` or `rejected` |
685+| `since` | string | When the event happened (ISO 8601) |
686+| `environment` | string | The environment's name |
687+| `environment_id` | number or null | The environment's id, as `GET .../environments` shows it |
688+| `state` | string | `approved` or `rejected`. Absent on `requested`. |
689+| `comment` | string | The reviewer's comment; empty on `requested` or when none was given |
690+| `workflow_run` | object | `id`, `run_number`, `name`, `display_title`, `head_sha`, `head_branch` and `html_url` of the run |
691+| `reviewers` | array | The environment's required reviewers, each `{ "type": "User", "reviewer": { "login", "id" } }` |
692+| `requestor` | object | `{ "login" }` of who started the run. Only on `requested`. |
693+| `approver` | object | `{ "login" }` of the reviewer who decided. Only on `approved` and `rejected`. |
694+
695+```json
696+{
697+ "action": "approved",
698+ "since": "2026-10-13T10:12:30.000Z",
699+ "environment": "production",
700+ "environment_id": 4,
701+ "comment": "Release notes look good",
702+ "state": "approved",
703+ "workflow_run": {
704+ "id": "8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a",
705+ "run_number": 128,
706+ "name": "Deploy",
707+ "display_title": "Release 2.4",
708+ "head_sha": "4f1c2e9d0b7a6c5e3f2d1c0b9a8e7d6c5b4a3f2e",
709+ "head_branch": "main",
710+ "html_url": "https://diffinery.com/acme/website/actions/runs/8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a"
711+ },
712+ "reviewers": [{ "type": "User", "reviewer": { "login": "alice", "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90" } }],
713+ "approver": { "login": "alice" },
714+ "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
715+ "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "alice", "type": "User" }
716+}
717+```
718+
719+Unlike GitHub, `workflow_run` holds only the fields above and `reviewers[].reviewer.id` is the Diffinery account id.
720+
672721 ## repository_dispatch
673722
674723 Sent when someone with the write role calls `POST /repos/{owner}/{repo}/dispatches`. The same request starts workflows with `on: repository_dispatch` (see [Events that trigger workflows](/docs/actions/events)).
modifiedapps/diffinery/docs/repositories/webhooks.md+4−3View file
@@ -28,6 +28,7 @@
2828 | `status` | A commit status is posted | none |
2929 | `deployment` | A deployment is created, with the REST API or by a workflow job with `environment:` | `created` |
3030 | `deployment_status` | A deployment gets a new status, from the REST API or from a workflow job with `environment:` | `created` |
31+| `deployment_review` | A run starts waiting on an environment's [required reviewers](/docs/actions/environments#required-reviewers-and-wait-timers), or a reviewer approves or rejects it | `requested`, `approved`, `rejected` |
3132 | `repository_dispatch` | Someone calls `POST /repos/{owner}/{repo}/dispatches` | the `event_type` that was sent |
3233 | `workflow_dispatch` | Someone starts a workflow by hand (web UI, CLI, REST API or MCP) | none |
3334 | `*` | Every event above | |
@@ -41,13 +42,13 @@
4142 1. Open the repository, for example `https://diffinery.com/acme/website`.
4243 2. Select **Settings**, then **Webhooks** in the sidebar (`https://diffinery.com/acme/website/settings/webhooks`).
4344 3. Under **Add a webhook**, enter the **Payload URL**. It must start with `https://` and name a public host: not `localhost`, a private or loopback address, or Diffinery itself.
44-4. Under **Events**, keep **Everything** checked to receive all events, or uncheck it and check the events you want: `push`, `pull_request`, `pull_request_review`, `issues`, `issue_comment`, `workflow_run`, `workflow_dispatch`. If **Everything** stays checked, the webhook receives every event regardless of the other boxes, including `release`, `status`, `deployment`, `deployment_status` and `repository_dispatch`. If you uncheck every box, the webhook also receives every event.
45+4. Under **Events**, keep **Everything** checked to receive all events, or uncheck it and check the events you want: `push`, `pull_request`, `pull_request_review`, `issues`, `issue_comment`, `workflow_run`, `workflow_dispatch`. If **Everything** stays checked, the webhook receives every event regardless of the other boxes, including `release`, `status`, `deployment`, `deployment_status`, `deployment_review` and `repository_dispatch`. If you uncheck every box, the webhook also receives every event.
4546 5. Optionally enter a **Secret**. Leave it empty to have Diffinery generate one.
4647 6. Select **Add webhook**.
4748
4849 The page then shows **Webhook created for** your URL with the signing secret. Copy it now: it is not shown again. If you lose it, delete the webhook and create a new one.
4950
50-> **Note:** The web form only offers the events listed above as separate checkboxes. To subscribe to `release`, `status`, `deployment`, `deployment_status` or `repository_dispatch` without receiving everything, create the webhook with the API and list the events you want.
51+> **Note:** The web form only offers the events listed above as separate checkboxes. To subscribe to `release`, `status`, `deployment`, `deployment_status`, `deployment_review` or `repository_dispatch` without receiving everything, create the webhook with the API and list the events you want.
5152
5253 ### With the API
5354
@@ -302,7 +303,7 @@
302303 - The same signature scheme (HMAC-SHA256, `sha256=` prefix, hex).
303304 - The same event names and the same `action` values for the actions Diffinery sends.
304305 - Payloads have GitHub's full `repository` object (numeric `id`, `node_id`, `owner`, `private`, `visibility`, `default_branch`, URLs and so on), a `sender`, GitHub-style `pull_request` and `issue` objects, and `pull_request.merged` on `closed`.
305-- `release`, `status`, `deployment`, `deployment_status` and `repository_dispatch` payloads have GitHub's structure, with the release, status and deployment fields GitHub receivers read. `workflow_dispatch` has GitHub's `inputs`, `ref` and `workflow` fields.
306+- `release`, `status`, `deployment`, `deployment_status`, `deployment_review` and `repository_dispatch` payloads have GitHub's structure, with the release, status and deployment fields GitHub receivers read. `workflow_dispatch` has GitHub's `inputs`, `ref` and `workflow` fields.
306307 - The hooks API returns GitHub's hook object (`type`, `name`, `config`, `last_response`, ...).
307308
308309 Differences to plan for:
modifiedapps/diffinery/docs/security/audit-log.md+2−1View file
@@ -140,12 +140,13 @@
140140
141141 | Action | Recorded when | Detail |
142142 |---|---|---|
143-| `environment.update` | An environment is created or updated with `PUT /api/v3/repos/{owner}/{repo}/environments/{name}` | `environment`, `deployment_branch_policy` (as sent, or `null`) |
143+| `environment.update` | An environment is created or updated with `PUT /api/v3/repos/{owner}/{repo}/environments/{name}` | `environment`, `deployment_branch_policy` (as sent, or `null`); `reviewers` (logins), `wait_timer` and `prevent_self_review` when they were sent |
144144 | `environment.delete` | An environment is deleted | `environment` |
145145 | `environment.branch_policy.create` | A deployment branch or tag pattern is added | `environment`, `pattern`, `type` (`branch` or `tag`) |
146146 | `environment.branch_policy.delete` | A deployment branch or tag pattern is removed | `environment`, `id` |
147147 | `environment.secret.set`, `environment.secret.delete` | An environment secret is saved or deleted | `environment`, `name` (never the value) |
148148 | `environment.variable.set`, `environment.variable.delete` | An environment variable is created, updated, renamed or deleted | `environment`, `name`, and `renamed_from` when a `PATCH` renamed it |
149+| `deployment.approve`, `deployment.reject` | A required reviewer approves or rejects a run's waiting deployments, on the run page or with `POST .../actions/runs/{run_id}/pending_deployments`. The actor is the reviewer. | `run`, `environments` (the environments decided), `comment` |
149150
150151 ### Git and pull requests
151152
modifiedapps/diffinery/src/docs/api-meta.ts+1−2View file
@@ -1128,9 +1128,8 @@
11281128 },
11291129 "POST /repos/{owner}/{repo}/actions/runners/claim": {
11301130 summary: "Claim a job for a self-hosted runner",
1131- description: "Used by `diffinery runner`. Send the runner's `drn_` token and an empty body: the runner offers the name and labels it registered with. A repository admin's personal token with the `admin` scope also works, with `name` and `labels` in the body (how runners worked before registration). Returns 204 when no queued job matches the labels, otherwise the job with a fresh job token.",
1131+ description: "Used by `diffinery runner`. Send the runner's `drn_` token and an empty body: the runner offers the name and labels it registered with. Only registered runners claim: a personal token, even a repository admin's, gets 403 `personal tokens can't claim jobs; register this runner: ...`. Returns 204 when no queued job matches the labels, otherwise the job with a fresh job token. See /docs/actions/runners.",
11321132 access: "admin",
1133- body: { name: ["string", "Runner name (with a personal token only)."], labels: ["string[]", "Runner labels, matched against `runs-on` (with a personal token only)."] },
11341133 },
11351134 "GET /repos/{owner}/{repo}/actions/runs": {
11361135 summary: "List workflow runs",