main through a pull request.
Branches
main is the only long-lived branch. Nothing is pushed to it directly, and it
is expected to be releasable at every commit. There is no develop branch and
no permanent feature branches. Branch protection is switched on wherever the
hosting plan allows it; where it is not, the rule holds by convention and a
direct push to main is treated as a mistake to be reverted, not a shortcut.
Every change starts from the current main on a short-lived branch named after
what it is:
feat/webhook-ingress, fix/session-lock-race, chore/php-8.5. A branch lives
for days, not weeks: rebase it on main if it falls behind, and split work that
grows past a single reviewable change into several pull requests rather than one
large branch. Behaviour that is not ready to be seen ships behind a feature flag,
not on a branch that waits.
Commit messages and pull request titles follow one format, described in
Commit messages. The title matters more than the
commits: after a squash merge it is the single commit on main, and release
notes are generated from those titles.
Writing the pull request
The repository has a short pull request template; fill it in rather than deleting it. It asks two things of a reviewer who has not seen the branch:- What and why — what is different after the merge, in one or two
sentences rather than a list of files, and the bug, missing behaviour or
issue behind it. Link the issue with
Closes #123so it closes on merge. - How to check — the test that covers it, or the manual steps if the change is visual or operational.
Merging
A pull request is squash-merged once CI is green and it has been reviewed. The branch is deleted on merge. Merge commits and rebase-merges are not used: one change onmain is one commit, with the pull request number in its subject.
Contributors without write access work the same way from a fork.
Dependabot pull requests follow the same path. They are reviewed in batches
before a release rather than as they arrive; an upgrade that moves a runtime
major version (PHP, Node, Postgres) is treated as a normal change and gets a
proper look, not a green-CI merge.
How a merged change reaches operators is a separate step — see
Releases and versioning.
Before opening one
What a reviewer is looking for
The boundaries hold. Dependency direction, migration isolation, tenant-aware execution, worker safety. Only some of these have a mechanical check (PHPat covers migration isolation, flow runtime isolation, handler versioning, ID strategy and messaging boundaries; see Testing and architecture checks). Dependency direction and tenant-aware execution are caught in review, and rules cannot see intent, only shape. Business logic is in the right place. Controllers and jobs orchestrate; services and domain classes decide. The change is covered. A minimal relevant test, not an exhaustive suite. The code reads like its neighbours. Matching the surrounding conventions matters more than any individual preference — see Coding conventions.Documentation that travels with the change
Documentation lives in this repository, so a change to a public contract and the change to the page describing it belong in the same pull request. That is the point of keeping them together: they cannot drift apart between releases. If a change alters something on the extension surface, it is a change to a public promise. Say so explicitly — see Stability policy.Before it can be merged
A contribution needs the Contributor License Agreement accepted in a comment on the pull request — see Licence, CLA and trademark for the comment to post and what it grants.Dependencies
Do not add a dependency without agreement. Raise it before the pull request rather than inside it.Contributor License Agreement
On a first pull request the CLA check asks you to sign. Comment on the pull request with exactly:CLA.md in the repository root.