Risk tiers
When people and their coding agents open hundreds of pull requests a month, nobody reviews every line. Risk tiers put the review where it matters. Your repository says which paths are low, medium or high risk. fremforge posts the result on every pull request as the commit status fremforge/risk-tier:
| Tier | What fremforge asks |
|---|---|
low | Nothing more. With auto-merge for low risk on, fremforge arms the pull request to merge when its checks pass. |
medium | Nothing more. Your branch rules apply as they are. |
high | An approval from a named person, and a behaviour summary in the pull request description. |
Risk tiers are off by default. An org owner switches them on, first in shadow.
The policy file
Add .fremforge/risk.yaml to the repository:
version: 1
default: medium # the tier of a file no rule matches
rules:
- tier: high
paths: ["/infra/**", "**/migrations/**", "/.forgejo/workflows/**", "/CODEOWNERS"]
- tier: low
paths: ["/docs/**", "*.md"]How fremforge reads it:
- From the base branch. The file is read from the branch the pull request merges into, never from the pull request itself, so a pull request cannot lower its own tier.
- The highest tier wins. A file gets the highest tier of every rule that matches it, so
db/migrations/README.mdabove is high. A pull request gets the highest tier of all its files. - Renames count by both names. Moving
infra/main.tftodocs/main.tfis high. - Changing the policy is high. A change to
.fremforge/risk.yamlitself is always high risk.
Paths are globs. * matches within one directory, ** across directories, ? one character. A glob that starts with / is anchored at the repository root. Without it, the glob matches at any directory level, as in .gitignore.
One policy for the whole organisation
To use one policy for every repository that has no file of its own, keep it as .fremforge/risk.yaml in one repository of the organisation and name that repository in the settings (policy_repo, below). fremforge reads it from that repository’s default branch.
Check a policy before you merge it
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/risk-tier/validate \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile s .fremforge/risk.yaml '{source: $s, paths: ["infra/main.tf", "README.md"]}')"The answer lists every error, or with paths the tier each path gets and the rule that set it. The token needs policy:read.
What high risk needs
A high-risk pull request shows warning on fremforge/risk-tier until it has both:
- An approval from a named person. An approving review on the current commit from someone other than the author, who can approve on the branch. It counts by the account it was given as: an approval your coding agent gives at your request is your approval. A system bot’s approval does not count. A later “request changes” from the same person withdraws it.
- A behaviour summary. A section in the pull request description headed
## Behaviour(orBehavior,Behaviour change,Behaviour summary), with at least a sentence about what changes for users or systems. It is what a reviewer, or anyone reading the history later, sees when nobody reads the diff line by line.
If your organisation uses AI review with its own provider key, you can let a completed AI review on the commit count as the summary: switch on Count AI review as the behaviour summary in the settings (ai_review_summary). It is off by default. Risk tiers never send code anywhere themselves.
The org-owner waiver. Where a branch rule does not enforce protection on org owners, an org owner’s own pull request needs no approval, and that includes the pull requests their coding agent opens for them. Risk tiers follow the same rule and say so: the status reads “approval waived (org owner …)”. On a branch with no protection rule at all, Forgejo asks no approval of anyone, so an owner’s own pull request is waived there too and the status reads “approval waived (no branch rule)”; a member’s still needs an approval. The behaviour summary is always needed. To have owners’ high-risk changes approved by a second person, tick Enforce protection on org owners on the branch rule.
The status never waits
fremforge/risk-tier is never pending. It is success or warning, and every outcome is final when it is posted:
- No policy file:
success, “No risk policy”. - A policy that does not parse:
success, saying the policy is invalid. Nothing is auto-merged. - Anything fremforge cannot read in time (20 seconds):
success, saying the tier is unavailable and fails open. Nothing is auto-merged.
fremforge posts the status within seconds of a pull request being opened, pushed to, edited, labelled or reviewed. A job every 5 minutes posts any status that is missing and looks again at high-risk pull requests that are still waiting.
On merge-queue groups, fremforge posts the tier of the group’s whole change as success, for information. Approvals are checked on each pull request.
fremforge does not add fremforge/risk-tier to your branch rules. Today it is shown, not required.
Auto-merge for low risk
With the mode on and auto_merge_low: true, fremforge arms auto-merge on a pull request that is positively low risk. Forgejo then merges it when every required check passes and the branch rule’s approvals are met. Arming never skips a check or an approval.
fremforge does not arm:
- anything not low, or a pull request it could not tier;
- a draft, or a title starting with
WIP; - a pull request labelled
risk-tier/no-auto-mergeordo-not-merge; - a pull request a person disarmed, until it is pushed to again;
- Renovate pull requests, which follow your dependency update auto-merge settings.
If a push raises the tier of a pull request fremforge armed, fremforge cancels its arm. It never cancels an arm a person made.
In the console
Org owners find risk tiers at Build & deploy → Active work (https://frem.sh/<org>/_admin/active-work):
- The Risk tiers card sets the mode, auto-merge for low risk and whether AI review counts as the summary. The org policy repository is set through the API.
- Each pull request on the page shows its tier as a badge. Open it to see why: the file and rule that set the tier, how many files changed, and for high risk who approved or which waiver applies, and where the behaviour summary came from. The page shows what fremforge last posted for the pull request, so a pull request appears here once its tier has been posted.
Shadow, on and off
| Mode | What happens |
|---|---|
off | Nothing. The default. |
shadow | The status is posted. fremforge records which pull requests it would auto-merge, in the audit log as risk_tier.would_arm, and arms nothing. |
on | The status is posted, and with auto_merge_low low-risk pull requests are armed. |
Start in shadow for a week and read the statuses and the would_arm events before you switch auto-merge on.
curl -X PUT https://frem.sh/_app/api/v1/orgs/<org>/risk-tier/settings \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "shadow", "policy_repo": null}'| Field | Notes |
|---|---|
mode | off, shadow or on. Required. |
auto_merge_low | true to arm low-risk pull requests. Read only in mode on. Unchanged if left out. |
ai_review_summary | true to let a completed AI review count as a high-risk pull request’s behaviour summary. Off by default. Unchanged if left out. |
policy_repo | A repository of the organisation whose .fremforge/risk.yaml applies where a repository has none. null clears it. Unchanged if left out. |
The token needs policy:write; GET on the same path needs policy:read. Every change is audited as risk_tier.settings_updated.
Why a pull request has its tier
curl https://frem.sh/_app/api/v1/orgs/<org>/repos/<repo>/pulls/<number>/risk-tier \
-H "Authorization: Bearer $FREMFORGE_TOKEN"The answer gives the tier, the file and rule that set it (decided_by), what a high-risk pull request still needs, who approved it or which waiver applies, where the behaviour summary came from, and whether it would be auto-merged. It works while the mode is off too, so you can test a policy on real pull requests before you switch it on. The token needs findings:read, or read access to the repository. Coding agents get the same answer from the MCP tool get_pull_request_risk_tier.