Docs: fork pull requests, git size limits, agent tasks, import campaign (#67)
Covers #50, #58, #59, #60, #61 and #64: fork pull requests get no secrets, signed-out answers for private and missing repositories, import size and variable rules, agent task audit entries, email and limits, links to Agent tasks and Today, and the import campaign workflow. 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 08d3416commit 54123272b5a9c3025ffb2043aed0d08e607d67c9Browse files
17 files changed+61−15
Files (17)
- apps/diffinery/docs/accounts/notifications.md+1−0
- apps/diffinery/docs/accounts/permissions.md+1−1
- apps/diffinery/docs/actions/secrets-and-variables.md+1−1
- apps/diffinery/docs/actions/workflow-permissions.md+3−1
- apps/diffinery/docs/admin/site-administration.md+1−1
- apps/diffinery/docs/agents/overview.md+5−0
- apps/diffinery/docs/agents/tasks.md+2−2
- apps/diffinery/docs/agents/today.md+2−0
- apps/diffinery/docs/concepts.md+1−1
- apps/diffinery/docs/index.md+3−1
- apps/diffinery/docs/migrate-from-github.md+2−2
- apps/diffinery/docs/reference/limits.md+15−2
- apps/diffinery/docs/repositories/git.md+1−1
- apps/diffinery/docs/security/audit-log.md+10−1
- apps/diffinery/docs/security/overview.md+1−1
- apps/diffinery/docs/self-hosting/configuration.md+10−0
- apps/diffinery/src/docs/api-meta.ts+2−0
modifiedapps/diffinery/docs/accounts/notifications.md+1−0View file
| @@ -233,6 +233,7 @@ | ||
| 233 | 233 | | `token_created` | `[Diffinery] A personal access token was added to your account` (or `A token was created for the agent review-bot`) | Security | `security_alert` | A token was created for you or your agent, from the web UI, CLI or API | |
| 234 | 234 | | `token_expiring` | `[Diffinery] Your token "ci-deploy" expires on <date>` | Security | `security_alert` | About 7 days before a token expires (once per token, for tokens that last more than 8 days) | |
| 235 | 235 | | `agent_created` | `[Diffinery] A new agent was added: review-bot` | Security | `security_alert` | You created an agent | |
| 236 | +| `model_key_added` | `[Diffinery] Your Anthropic API key was added to Diffinery` (or `OpenAI`) | Security | `security_alert` | You saved a model API key for [agent tasks](/docs/agents/tasks) | | |
| 236 | 237 | | `push_blocked` | `[acme/website] Your push was blocked: possible secrets` | Security | `security_alert` | Push protection rejected your `git push`, or a commit made in the web editor, through the REST API or over MCP (subject `Your commit was blocked`). For an agent, its owner is told. | |
| 237 | 238 | | `push_bypassed` | `[acme/website] Push protection was bypassed` | Security | `security_alert` | A repository admin pushed past push protection, or committed a flagged file through the commits API with `allow_secrets`; sent to that admin and the repository's admins | |
| 238 | 239 | | `account_status` | `[Diffinery] Your account was disabled` or `Your account was enabled` | Security | `account` | A site administrator disabled your account, or enabled it again | |
modifiedapps/diffinery/docs/accounts/permissions.md+1−1View file
| @@ -141,7 +141,7 @@ | ||
| 141 | 141 | |
| 142 | 142 | | Situation | Response | |
| 143 | 143 | | --- | --- | |
| 144 | -| No credentials, private repository | `401 authentication required` (Git then asks for a username and password) | | |
| 144 | +| No credentials, private repository, or one that doesn't exist | Git, Git LFS and the web UI: `401 authentication required` (Git then asks for a username and password). REST API: `404`, as on GitHub. A signed-out caller can't tell the two apart. | | |
| 145 | 145 | | No credentials, any change to a public repository (for example `POST /repos/{owner}/{repo}/issues`) | `401 authentication required` | |
| 146 | 146 | | Signed in or token, but no access to a private repository | `404 repository not found` (Diffinery doesn't reveal that it exists) | |
| 147 | 147 | | Access, but the role is too low | `403 <role> access required`, for example `403 write access required` | |
modifiedapps/diffinery/docs/actions/secrets-and-variables.md+1−1View file
| @@ -283,7 +283,7 @@ | ||
| 283 | 283 | |
| 284 | 284 | ### Which runs receive secrets |
| 285 | 285 | |
| 286 | -Every workflow run in the repository receives its repository and organization secrets, whatever the event: `push`, `pull_request`, `pull_request_target`, `merge_group`, `workflow_dispatch`, `schedule`, `workflow_run`, `release` and `repository_dispatch`. Environment secrets follow stricter rules (see [Rules for environment secrets](#rules-for-environment-secrets)). | |
| 286 | +Every workflow run in the repository receives its repository and organization secrets, whatever the event (the one exception, pull requests from forks, is below): `push`, `pull_request`, `pull_request_target`, `merge_group`, `workflow_dispatch`, `schedule`, `workflow_run`, `release` and `repository_dispatch`. Environment secrets follow stricter rules (see [Rules for environment secrets](#rules-for-environment-secrets)). | |
| 287 | 287 | |
| 288 | 288 | This is a difference from GitHub worth understanding. GitHub withholds secrets from `pull_request` runs of pull requests that come from forks. Diffinery has no forks: every pull request comes from a branch in the same repository, pushed by someone with write access, so `pull_request` runs get the repository's secrets, and they run the workflow files from the pull request's own test merge commit. The exception is a pull request [imported from GitHub](/docs/migrate-from-github) whose head was in a fork (its head shows as `owner:branch`): its runs get no secrets and a read-only token, as on GitHub. `pull_request_target` runs the base branch's workflow on the base commit, also with secrets, so a pull request can't change what it runs. |
| 289 | 289 |
modifiedapps/diffinery/docs/actions/workflow-permissions.md+3−1View file
| @@ -276,8 +276,10 @@ | ||
| 276 | 276 | |
| 277 | 277 | ## Pull requests from forks |
| 278 | 278 | |
| 279 | -A `pull_request` run for a pull request whose head is in another repository gets read access only: `read-all` at most, whatever the workflow asks for, and never `id-token: write`. Diffinery doesn't have forks yet, so every pull request comes from a branch of the same repository and this rule doesn't come into play today. | |
| 279 | +A `pull_request` run for a pull request whose head is in another repository gets read access only: `read-all` at most, whatever the workflow asks for, and never `id-token: write`. It also gets no secrets: no repository, organization or environment secrets, only `GITHUB_TOKEN`, as on GitHub. | |
| 280 | 280 | |
| 281 | +Diffinery has no forks of its own, so today this applies to pull requests [imported from GitHub](/docs/migrate-from-github) whose head was in a fork. Their head shows as `owner:branch`. | |
| 282 | + | |
| 281 | 283 | ## Reusable workflows |
| 282 | 284 | |
| 283 | 285 | A [reusable workflow](/docs/actions/reusable-workflows) never gets more than its caller: |
modifiedapps/diffinery/docs/admin/site-administration.md+1−1View file
| @@ -158,7 +158,7 @@ | ||
| 158 | 158 | | `POST /api/v3/diffinery/admin/emails/samples` | Send every template to yourself now, or only some with `{"only": ["signin", "invite-org"]}`. Returns `to`, `sent`, `total` and each result. | site administrator, with an `admin` token for all repositories or a browser session | |
| 159 | 159 | | `GET /api/v3/diffinery/emails?all=1` | Everyone's recent emails (without `all=1`, only yours). Also takes `kind`, `since` and `per_page` (up to 500). | site administrator for `all=1` | |
| 160 | 160 | |
| 161 | -The sample IDs are `pr-opened`, `issue-opened`, `comment`, `review-approved`, `review-changes`, `review-requested`, `assigned`, `pushed`, `merged`, `closed`, `reopened`, `run-failed`, `signin`, `passkey-added`, `passkey-removed`, `token-created`, `token-expiring`, `agent-created`, `push-bypassed`, `push-blocked`, `commit-blocked`, `account-disabled`, `account-enabled`, `invite-org`, `invite-repo`, `invite-accepted`, `welcome`, `signin-link`, `access-request`, `org-member`, `repo-access`, `repo-created`, `repo-deleted`, `repo-transferred` and `test`. | |
| 161 | +The sample IDs are `pr-opened`, `issue-opened`, `comment`, `review-approved`, `review-changes`, `review-requested`, `assigned`, `pushed`, `merged`, `closed`, `reopened`, `run-failed`, `signin`, `passkey-added`, `passkey-removed`, `token-created`, `token-expiring`, `agent-created`, `model-key-added`, `push-bypassed`, `push-blocked`, `commit-blocked`, `account-disabled`, `account-enabled`, `invite-org`, `invite-repo`, `invite-accepted`, `welcome`, `signin-link`, `access-request`, `org-member`, `repo-access`, `repo-created`, `repo-deleted`, `repo-transferred` and `test`. | |
| 162 | 162 | |
| 163 | 163 | ## Administration from the CLI |
| 164 | 164 |
modifiedapps/diffinery/docs/agents/overview.md+5−0View file
| @@ -211,6 +211,11 @@ | ||
| 211 | 211 | |
| 212 | 212 | To cut off one running copy without disabling the agent, revoke just its token. |
| 213 | 213 | |
| 214 | +## Agent tasks and Today | |
| 215 | + | |
| 216 | +- **[Agent tasks](/docs/agents/tasks)**: on a repository's **Tasks** tab, give one of your agents a goal. Diffinery runs Claude Code, Codex or your own command in a sandbox as that agent, with your own model API key (saved under **Settings → Model access**, and never placed in the sandbox), then pushes a branch and opens a pull request for review. | |
| 217 | +- **[Today](/docs/agents/today)**: the first page after signing in, also `GET /api/v3/diffinery/today` and the `today` MCP tool. It shows the pull requests that need you, your agents' open pull requests and tasks, and what merged, was released or failed in the last 24 hours. | |
| 218 | + | |
| 214 | 219 | ## Set up a coding agent |
| 215 | 220 | |
| 216 | 221 | Coding agents such as Claude Code, Cursor or a CI bot can work with Diffinery in two ways: |
modifiedapps/diffinery/docs/agents/tasks.md+2−2View file
| @@ -92,8 +92,8 @@ | ||
| 92 | 92 | |
| 93 | 93 | | Provider | Endpoints | |
| 94 | 94 | | --- | --- | |
| 95 | - | Anthropic | `POST /v1/messages`, `POST /v1/messages/count_tokens`, `GET /v1/models` | | |
| 96 | - | OpenAI | `POST /v1/responses`, `POST /v1/responses/compact`, `GET /v1/responses/{id}`, `POST /v1/chat/completions`, `GET /v1/models` | | |
| 95 | + | Anthropic | `POST /v1/messages`, `POST /v1/messages/count_tokens`, `GET /v1/models` and `GET /v1/models/{id}` | | |
| 96 | + | OpenAI | `POST /v1/responses`, `POST /v1/responses/compact`, `GET /v1/responses/{id}`, `POST /v1/chat/completions`, `GET /v1/models` and `GET /v1/models/{id}` | | |
| 97 | 97 | |
| 98 | 98 | 4. When the agent exits, is stopped, or the run ends any other way, the token stops working. If you lose access to the repository or the agent, the task stops. |
| 99 | 99 |
modifiedapps/diffinery/docs/agents/today.md+2−0View file
| @@ -11,6 +11,8 @@ | ||
| 11 | 11 | |
| 12 | 12 | At the top, four numbers link to the sections below them: how many items need you, how many agent pull requests are open, how many pull requests merged since yesterday (and how many of them were by agents), and how many workflow runs failed. |
| 13 | 13 | |
| 14 | +A side column lists your repositories: those you or your organizations own, up to 40. If you own none yet but work in other people's repositories, it lists those instead, under **Repositories**. | |
| 15 | + | |
| 14 | 16 | ### Needs you |
| 15 | 17 | |
| 16 | 18 | Pull requests waiting on you, newest activity first: |
modifiedapps/diffinery/docs/concepts.md+1−1View file
| @@ -45,7 +45,7 @@ | ||
| 45 | 45 | - A workflow run whose code came from an agent (the agent triggered it, pushed the branch last, or opened the pull request, and re-runs and `workflow_run` followers of such a run) acts as the agent, so a workflow can't carry agent-written code past these rules. |
| 46 | 46 | - It can import from GitHub only into an organization it or its owner administers, its imports never bypass push protection, and they skip variables, secret names and environments. |
| 47 | 47 | |
| 48 | -Agents work through the [MCP server](/docs/agents/mcp), the REST API, the CLI and Git. See [Agents](/docs/agents/overview). | |
| 48 | +Agents work through the [MCP server](/docs/agents/mcp), the REST API, the CLI and Git. Diffinery can also run one for you: an [agent task](/docs/agents/tasks) gives one of your agents a goal and runs Claude Code, Codex or your own command with your own model key, then opens a pull request. [Today](/docs/agents/today), the first page after signing in, shows what needs you and what your agents are doing. See [Agents](/docs/agents/overview). | |
| 49 | 49 | |
| 50 | 50 | ### People imported from GitHub |
| 51 | 51 |
modifiedapps/diffinery/docs/index.md+3−1View file
| @@ -14,6 +14,8 @@ | ||
| 14 | 14 | | Move a project or an organization from GitHub (**Settings → Imports**, or `diffinery migrate github`) | [Migrate from GitHub](/docs/migrate-from-github) | |
| 15 | 15 | | Run CI | [Actions overview](/docs/actions/overview) and [Workflow syntax](/docs/actions/workflow-syntax) | |
| 16 | 16 | | Connect an AI agent | [Agents](/docs/agents/overview) and [MCP](/docs/agents/mcp) | |
| 17 | +| Give an agent a goal and get a pull request back, with your own model key | [Agent tasks](/docs/agents/tasks) | | |
| 18 | +| See what needs you and what your agents did since yesterday | [Today](/docs/agents/today) | | |
| 17 | 19 | | Script against Diffinery | [REST API guide](/docs/reference/rest-api), [REST API reference](/docs/api) and [CLI reference](/docs/reference/cli) | |
| 18 | 20 | | Run your own instance | [Deploy Diffinery](/docs/self-hosting/deploy) | |
| 19 | 21 | |
| @@ -23,7 +25,7 @@ | ||
| 23 | 25 | - **Pull requests and issues.** Draft pull requests, reviews with line comments, required approvals and checks, merge, squash and rebase, an `intent` field that tells reviewers what a change is meant to do, and a merge queue. See [Pull requests](/docs/collaboration/pull-requests) and [Merge queue](/docs/collaboration/merge-queue). |
| 24 | 26 | - **Actions.** Workflow YAML compatible with GitHub Actions: the same triggers, expressions, matrices, `needs`, outputs, reusable workflows, secrets, variables, OIDC, service containers and Docker. Jobs run in Cloudflare Containers, or on your own machines with self-hosted runners. See [Compatibility with GitHub Actions](/docs/actions/compatibility). |
| 25 | 27 | - **Container registry.** Push and pull images at `diffinery.com/<owner>/<name>` instead of ghcr.io. See [Container registry](/docs/packages/container-registry). |
| 26 | -- **Agents as first-class accounts.** Agents have their own logins and tokens, act on behalf of a person or an organization, and work through MCP, the REST API, git or the CLI. Guardrails keep a human in the loop: unless a branch protection rule allows it, agents can't push to or merge into the default branch or protected branches, and their pull requests need a human approval. See [Agents](/docs/agents/overview). | |
| 28 | +- **Agents as first-class accounts.** Agents have their own logins and tokens, act on behalf of a person or an organization, and work through MCP, the REST API, git or the CLI. Guardrails keep a human in the loop: unless a branch protection rule allows it, agents can't push to or merge into the default branch or protected branches, and their pull requests need a human approval. [Agent tasks](/docs/agents/tasks) run Claude Code, Codex or your own command in a sandbox with your own model key and open a pull request, and [Today](/docs/agents/today) shows what needs you and what your agents did. See [Agents](/docs/agents/overview). | |
| 27 | 29 | - **Security by default.** Passkeys instead of passwords, invite-only sign-up, branch protection, push protection that blocks leaked credentials, encrypted secrets and an audit log. See [Security overview](/docs/security/overview). |
| 28 | 30 | |
| 29 | 31 | ## How Diffinery runs |
modifiedapps/diffinery/docs/migrate-from-github.md+2−2View file
| @@ -459,7 +459,7 @@ | ||
| 459 | 459 | - Repository variables are imported with their values. Names that start with `GITHUB_` or aren't valid variable names are skipped. |
| 460 | 460 | - Secret **values are never read and never copied**: GitHub doesn't reveal them, and the importer doesn't try. Only names are listed, in the summary and on the import's page, for an admin to set by hand. Environment secret names appear as `<environment>/<NAME>`. |
| 461 | 461 | - Environments are created with their branch policy and variables; their secrets are listed by name only. |
| 462 | -- In an organization import into a Diffinery organization, the GitHub organization's variables are added to the Diffinery organization when it has no variable of that name. Existing ones are left alone (`Organization variable NPM_REGISTRY already exists and was left unchanged.`). Organization variables are shared by every repository of the organization; a repository variable of the same name wins. | |
| 462 | +- In an organization import into a Diffinery organization, the GitHub organization's variables are added to the Diffinery organization when it has no variable of that name. Existing ones are left alone (`Organization variable NPM_REGISTRY already exists and was left unchanged.`). Organization variables are shared by every repository of the organization; a repository variable of the same name wins. So only GitHub organization variables available to all repositories are copied. One limited to private or selected repositories on GitHub is skipped with a warning: `Organization variable NPM_TOKEN is limited to selected repositories on GitHub and was not copied; organization variables on Diffinery reach every repository, so add it to the repositories that need it.` | |
| 463 | 463 | |
| 464 | 464 | ### Releases and assets |
| 465 | 465 | |
| @@ -568,7 +568,7 @@ | ||
| 568 | 568 | | Limit | Value | |
| 569 | 569 | | --- | --- | |
| 570 | 570 | | Imports running at once | Per account that starts them and per target account: at most 3 single-repository imports (`too many repository imports are already running (at most 3 at a time); wait for one to finish`) and 1 organization import, drafts included (`an organization import is already in progress (at most 1 at a time); wait for it to finish or cancel it`). Repositories inside an organization import don't count toward the 3. | |
| 571 | -| Repository size | 10 GB by default, as GitHub reports it; a single-repository import over it is refused up front with `the repository is about 12 GB, over the 10 GB import limit; ask an administrator to raise IMPORT_MAX_REPO_KB or split the repository`. Self-hosters set `IMPORT_MAX_REPO_KB`. | | |
| 571 | +| Repository size | 10 GB by default, as GitHub reports it; a single-repository import over it is refused up front with `the repository is about 12 GB, over the 10 GB import limit; ask an administrator to raise IMPORT_MAX_REPO_KB or split the repository`. In an organization import, a repository over it fails with the same message before anything is copied, and the others carry on. Self-hosters set `IMPORT_MAX_REPO_KB`. | | |
| 572 | 572 | | Git transfer | 6 hours. A transfer still running after that fails with `the Git transfer did not finish within 6 hours`. A failed transfer is tried a second time automatically. | |
| 573 | 573 | | Single Git object | 100 MB (the push limit). A larger blob can't be imported. | |
| 574 | 574 | | Repositories per organization import | 1,000 | |
modifiedapps/diffinery/docs/reference/limits.md+15−2View file
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | --- |
| 2 | 2 | title: Limits |
| 3 | -description: Every hard limit in Diffinery and where it applies: sign-in, tokens, API, search, archives, releases, git, Actions, sandboxes, packages, imports, email. | |
| 3 | +description: Every hard limit in Diffinery and where it applies: sign-in, tokens, API, search, archives, releases, git, Actions, sandboxes, agent tasks, packages, imports, email. | |
| 4 | 4 | --- |
| 5 | 5 | |
| 6 | 6 | This page lists the fixed limits built into Diffinery: sizes, counts, timeouts and lifetimes. Use it when a push, a workflow, an upload or an API call fails with a size or count error, or when you plan something large. Values come from the code of the running version. Where a limit comes from Cloudflare rather than from Diffinery, the table says so. |
| @@ -214,6 +214,19 @@ | ||
| 214 | 214 | | Request body | 64 MiB | Larger `exec`, `read` or `write` requests fail with `413 request body too large`. | |
| 215 | 215 | | File read | 64 MiB | Reading a larger file fails with `413 file too large`. | |
| 216 | 216 | |
| 217 | +## Agent tasks | |
| 218 | + | |
| 219 | +| Limit | Value | Details | | |
| 220 | +| --- | --- | --- | | |
| 221 | +| Run length | 6 hours | A run still going after 6 hours is stopped and fails with `The run took longer than 6 hours and was stopped.` | | |
| 222 | +| Installing the runtime | 10 minutes | Installing Claude Code or Codex in the sandbox. | | |
| 223 | +| Goal and follow-up length | 20,000 characters | ``422 `goal` is longer than 20000 characters`` (or `body` for a follow-up). | | |
| 224 | +| Custom command length | 2,000 characters | ``422 `command` is longer than 2000 characters``. | | |
| 225 | +| Transcript | 20,000 lines per run, 32 KiB per line | Later output lines of a run aren't stored (Diffinery's own notes still are); a longer line is cut. | | |
| 226 | +| Transcript lines per MCP call | 500 | `agent_task` returns at most 500 lines after `after`. | | |
| 227 | + | |
| 228 | +See [Agent tasks](/docs/agents/tasks). For the Today view's limits (repositories active in the last 30 days, at most 100 of them, 50 open pull requests each), see [Today](/docs/agents/today). | |
| 229 | + | |
| 217 | 230 | ## Container registry |
| 218 | 231 | |
| 219 | 232 | | Limit | Value | Applies to | |
| @@ -231,7 +244,7 @@ | ||
| 231 | 244 | | Limit | Value | Applies to | |
| 232 | 245 | | --- | --- | --- | |
| 233 | 246 | | Imports at a time | 1 organization import and 3 repository imports | Counted per person or agent who starts them and per account imported into. Organization imports count while `draft`, `queued` or `running`; repository imports while `queued` or `running`, not counting the repositories inside an organization import. Over the limit: `429 an organization import is already in progress (at most 1 at a time); wait for it to finish or cancel it` or `429 too many repository imports are already running (at most 3 at a time); wait for one to finish`. | |
| 234 | -| Repository size | 10 GB by default | GitHub's reported size of the repository in a one-repository import, checked before the import starts: `422 the repository is about N GB, over the M GB import limit; ask an administrator to raise IMPORT_MAX_REPO_KB or split the repository`. Operators change it with `IMPORT_MAX_REPO_KB` (see [Configuration](/docs/self-hosting/configuration#worker-variables)). The repositories of an organization import are not checked against it. | | |
| 247 | +| Repository size | 10 GB by default | GitHub's reported size of the repository in a one-repository import, checked before the import starts: `422 the repository is about N GB, over the M GB import limit; ask an administrator to raise IMPORT_MAX_REPO_KB or split the repository`. Operators change it with `IMPORT_MAX_REPO_KB` (see [Configuration](/docs/self-hosting/configuration#worker-variables)). Each repository of an organization import is checked too, and one over the limit fails before anything is copied. | | |
| 235 | 248 | | Repositories per organization import | 1,000 | The `repositories` list (`422 at most 1000 repositories per import`), and the repositories read from the GitHub organization or user. | |
| 236 | 249 | | Repositories imported at once | 1 to 8, default 3 | One organization import's `concurrency` (**Repositories at a time** in the form). `422 concurrency must be between 1 and 8`. | |
| 237 | 250 | | Git transfer | 6 hours | Copying the Git data in the import container. After that the import fails with `the Git transfer did not finish within 6 hours`. A transfer that fails for another reason is tried once more, except when push protection stopped it. | |
modifiedapps/diffinery/docs/repositories/git.md+1−1View file
| @@ -216,7 +216,7 @@ | ||
| 216 | 216 | Every push goes through these steps, in this order. If an earlier step fails, nothing after it happens. |
| 217 | 217 | |
| 218 | 218 | 1. **Authorization.** You must send a token: a push without credentials gets HTTP 401 (`authentication required`) with a `WWW-Authenticate` challenge, for public and private repositories alike, which makes Git ask for credentials and retry. Your account needs write access, your token the `write` or `admin` scope, and the repository must not be archived. Otherwise the server answers HTTP 403 (`write access required` when your role only allows reading, `this token needs the write scope` for a `read`-scoped token, or `repository is archived (read-only)`), or HTTP 404 if the account can't see the repository at all. Git shows these as `Authentication failed` or `The requested URL returned error: 403`. |
| 219 | -2. **Size check.** A push larger than 100 MB is refused. Git shows `remote unpack failed: push exceeds 100 MB; split it into smaller pushes`, or an HTTP error if the request is stopped before it reaches Diffinery. | |
| 219 | +2. **Size check.** A push larger than 100 MB (after decompression), or one whose pack unpacks to more than 512 MiB, is refused. Git shows `remote unpack failed: push exceeds 100 MB; split it into smaller pushes`, or an HTTP error if the request is stopped before it reaches Diffinery. | |
| 220 | 220 | 3. **Unpacking.** The pack is parsed and every object checked. |
| 221 | 221 | 4. **Push protection.** New text files are scanned for credentials such as AWS keys, GitHub and Diffinery tokens, Slack and Stripe keys, Google, Anthropic and OpenAI API keys, and private keys. If any are found, the whole push is rejected, nothing from it is stored, and the output lists each finding: |
| 222 | 222 |
modifiedapps/diffinery/docs/security/audit-log.md+10−1View file
| @@ -53,7 +53,7 @@ | ||
| 53 | 53 | | `passkey.remove` | A passkey is removed | none | `name` | |
| 54 | 54 | | `invite.accept` | Someone creates an account and passkey from an invite link, or an existing user accepts an invite | for an existing user, the path the invite led to (such as `/acme/website`) | for a new account: `method` (`passkey`), `passkeyId`, `device`, `ip`, `location` | |
| 55 | 55 | | `token.create` | A personal access token is created on **Access tokens** or with `POST /api/v3/diffinery/tokens` | your login (web) | `name` and `scope`/`scopes`, and for the web the number of repositories it is restricted to | |
| 56 | -| `token.revoke` | A token is revoked on **Access tokens** | the token id | none | | |
| 56 | +| `token.revoke` | A token is revoked on **Access tokens** or with `DELETE /api/v3/diffinery/tokens/{id}` | the token id | `via: "api"` for the API | | |
| 57 | 57 | | `signin_link.create` | A site admin creates a one-time sign-in link for someone | that user's login | none | |
| 58 | 58 | | `user.active`, `user.pending`, `user.disabled` | A site admin changes a user's status | that user's login | none | |
| 59 | 59 | | `account.link_github` | You link a GitHub account on **Settings → Linked accounts** (gist code or GitHub sign-in) or with `POST /api/v3/diffinery/user/linked-accounts/github` | `<host>/<GitHub login>`, such as `github.com/diffy` | none | |
| @@ -69,6 +69,15 @@ | ||
| 69 | 69 | | `agent.token.create` | A token is created for an agent, from the web UI or with `POST /api/v3/diffinery/agents/{login}/tokens` | the agent's login | `scope` (web) or `scopes` (API) | |
| 70 | 70 | | `agent.enable`, `agent.disable` | An agent is enabled or disabled from the web UI; `agent.disable` also for `DELETE /api/v3/diffinery/agents/{login}` | the agent's login | none | |
| 71 | 71 | |
| 72 | +### Agent tasks and model keys | |
| 73 | + | |
| 74 | +| Action | Recorded when | Target | Detail | | |
| 75 | +|---|---|---|---| | |
| 76 | +| `task.start` | Someone starts an [agent task](/docs/agents/tasks) | `owner/repo#<task number>` | `agent`, `runtime` | | |
| 77 | +| `task.message`, `task.retry` | The person who started a task sends a follow-up or retries it | `owner/repo#<task number>` | none | | |
| 78 | +| `task.stop` | Someone stops a task (its starter, its agent or a repository admin) | `owner/repo#<task number>` | none | | |
| 79 | +| `model_key.set`, `model_key.delete` | You save, replace or delete a model API key under **Settings → Model access** (or `PUT`/`DELETE /api/v3/diffinery/model-keys/{provider}`) | the provider (`anthropic` or `openai`) | `hint` (the key's last four characters) and `opted_in_at` (when you accepted the provider's terms) for `model_key.set` | | |
| 80 | + | |
| 72 | 81 | ### Organizations and invitations |
| 73 | 82 | |
| 74 | 83 | | Action | Recorded when | Target | Detail | |
modifiedapps/diffinery/docs/security/overview.md+1−1View file
| @@ -128,7 +128,7 @@ | ||
| 128 | 128 | |
| 129 | 129 | ### Anonymous callers only read |
| 130 | 130 | |
| 131 | -Without a token or a session, a caller gets the `read` role on public repositories and nothing on private ones. As on GitHub, anonymous callers can only read: any request that changes a repository (any method other than `GET` or `HEAD`), such as opening an issue, commenting, reacting or watching, is refused with `401 authentication required`, even on a public repository. Anonymous `git clone`, `git fetch` and Git LFS downloads of public repositories keep working, although they use `POST`, because they only read. An anonymous `git push` gets `401` with a `WWW-Authenticate: Basic` challenge, even to a public repository, so git asks for (or its credential helper supplies) your token and retries. Unknown paths under `/api/` answer with a JSON `404` (`{"message": "Not Found", "documentation_url": "..."}`), not the web UI's error page. | |
| 131 | +Without a token or a session, a caller gets the `read` role on public repositories and nothing on private ones. A signed-out caller gets the same answer for a private repository as for one that doesn't exist: `401 authentication required` from Git, Git LFS and the web UI, and `404` from the REST API. As on GitHub, anonymous callers can only read: any request that changes a repository (any method other than `GET` or `HEAD`), such as opening an issue, commenting, reacting or watching, is refused with `401 authentication required`, even on a public repository. Anonymous `git clone`, `git fetch` and Git LFS downloads of public repositories keep working, although they use `POST`, because they only read. An anonymous `git push` gets `401` with a `WWW-Authenticate: Basic` challenge, even to a public repository, so git asks for (or its credential helper supplies) your token and retries. Unknown paths under `/api/` answer with a JSON `404` (`{"message": "Not Found", "documentation_url": "..."}`), not the web UI's error page. | |
| 132 | 132 | |
| 133 | 133 | ### Archived repositories |
| 134 | 134 |
modifiedapps/diffinery/docs/self-hosting/configuration.md+10−0View file
| @@ -209,11 +209,21 @@ | ||
| 209 | 209 | | `MAIL_DOMAIN` | Variable | Deploy, Sync to Diffinery | Optional. The domain email is sent from, when it isn't `DIFFINERY_DOMAIN`. | |
| 210 | 210 | | `ACCESS_TEAM_DOMAIN` | Variable | Deploy, Sync to Diffinery | Optional. Your Zero Trust team domain, for example `example.cloudflareaccess.com`, when the API token can't read it. | |
| 211 | 211 | | `DIFFINERY_LIVE_TOKEN` | Secret | Live test, Sync to Diffinery | A Diffinery token with the `admin` scope and access to all repositories, created under **Settings → Access tokens** on the instance. The live test creates and deletes throwaway repositories with it, and uploads a test profile picture to the token's account (putting the previous one back afterwards). When the secret is not set, those steps are skipped with a warning; the check that `/docs`, `/llms.txt` and the CLI download are served without signing in still runs. The sync workflow uses it to create the organization and repository, copy secrets and variables, and push. | |
| 212 | +| `IMPORT_GITHUB_TOKEN` | Secret | Import campaign | Optional. A read-only GitHub token for public repositories. When it is set on GitHub, the campaign copies it to the Diffinery repository's secrets before it starts; without it, imports use GitHub's anonymous rate limit, and large repositories take hours. The main sync doesn't copy it. | | |
| 212 | 213 | |
| 213 | 214 | Deploy reads `DIFFINERY_DOMAIN`, `DIFFINERY_ZONE` and `MAIL_DOMAIN` from the repository's variables and passes them to bootstrap, so you point it at your own hostname without editing the workflow (see [Deploy your own Diffinery](/docs/self-hosting/deploy#step-1-point-the-code-at-your-domain)). Deploy runs on pushes to `main` on any Diffinery instance, and when started by hand on GitHub. |
| 214 | 215 | |
| 215 | 216 | The Deploy job and the Sync job run in an environment named `production`. The Live test and Sync workflows target `https://diffinery.com` and its `codician-team/diffinery` repository; adapt or delete them for your own instance. |
| 216 | 217 | |
| 218 | +The **Import campaign** workflow is a manual test of the importer. Started on GitHub (**Run workflow**), it starts the same workflow on Diffinery and prints its report. On Diffinery it imports a list of public GitHub repositories (the `repos` input) as private repositories named `<owner>/campaign-<github owner>-<github repo>`, replacing copies from earlier campaigns. It can then run their push workflows (`run_workflows`, off by default, because that is third-party code) and writes a report of what came across and what ran. | |
| 219 | + | |
| 220 | +- **Queueing.** Diffinery runs at most 3 repository imports per person at a time and answers `429` beyond that, so the campaign queues the rest and starts each one as an earlier one finishes. | |
| 221 | +- **Time limit.** Imports still waiting when `minutes` runs out are reported as `not started`. | |
| 222 | +- **Failures.** When a run fails before writing its report, the GitHub side prints the last 150 lines of the job's log instead. | |
| 223 | +- **Following a run.** Set `follow_run` to a Diffinery run id to print the report of a run that already started. | |
| 224 | + | |
| 225 | +It needs `DIFFINERY_LIVE_TOKEN` and, optionally, `IMPORT_GITHUB_TOKEN`. | |
| 226 | + | |
| 217 | 227 | Sync to Diffinery copies the four variables above to the repository on Diffinery (only those that are set), together with the secrets `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` and `DIFFINERY_LIVE_TOKEN`, so the copy deploys to the same place. Each push copies only the branch or tag it pushed, and runs are queued per ref, so a push to another branch never replaces a waiting sync of `main`. A run started by hand copies every branch and tag; it queues together with `main`, and moves each ref only if it still points where it did when the run read it, so it never puts back an older commit that another run copied in the meantime. Branch deletions aren't copied. Only a sync of `main` checks that `main` on Diffinery matches and waits for its Actions runs. |
| 218 | 228 | |
| 219 | 229 | `scripts/diffinery-wait-runs.sh`, which the sync workflow uses to wait for Actions runs on a commit, reads `DIFFINERY` (the instance origin) and `TOKEN` (a Diffinery token) from the environment: |
modifiedapps/diffinery/src/docs/api-meta.ts+2−0View file
| @@ -755,6 +755,8 @@ | ||
| 755 | 755 | merge_method: ["string", "`merge`, `squash` or `rebase` (default: the queue's method, `squash` where linear history is required, else `merge`)."], |
| 756 | 756 | commit_title: ["string", "Title of the merge or squash commit."], |
| 757 | 757 | commit_message: ["string", "Body of the merge or squash commit."], |
| 758 | + delete_branch: ["boolean", "Delete the head branch once auto-merge lands the pull request, directly or through the queue (never the default branch or a protected one). 422 `delete_branch must be a boolean` otherwise."], | |
| 759 | + sha: ["string", "Turn auto-merge on only if the head is still this commit (409 if it moved)."], | |
| 758 | 760 | }, |
| 759 | 761 | }, |
| 760 | 762 | "DELETE /repos/{owner}/{repo}/pulls/{number}/auto-merge": { |