Document version v2026100501 (quote this version when discussing or citing) · 中文版. For every development department, whatever the stack and wherever it is deployed. The document has two layers: Part A, the delivery contract, is what every team hands to SRE and is mandatory; Part B, the branching template, is a recommended way to run version control inside a repo, which teams may adopt, adapt or ignore. Each section is marked contract or template. Implementation details are in CI/CD Details.
SRE only needs what is in Part A. How a team branches, reviews and tags inside its repo is the team's choice; Part B is a template, not a rule.
However many teams or departments contribute, each repo's release has exactly one Release Manager, who integrates the MRs and release details and is SRE's only contact.
SRE receives exactly one tag and its commit hash per repo, with the release plan, by the end of the working day before release day. All MRs are merged before that.
Every release can be rolled back to the previous version, or its change can be switched off. If neither holds, SRE still deploys and the development team fixes forward.
flowchart LR T["Teams' MRs
merged before tagging"] --> RM["Release Manager
one per repo"] RM -->|"D-1: tag + commit hash
+ release plan"| S["SRE"] S -->|"release day"| G["gray
real traffic"] G -->|"problem in any repo:
withdraw all · re-deliver
gray again until 14:00"| RM G -->|"whole set passed
same tags"| P[("production")] classDef long fill:#111a2c,stroke:#3fd0e8,color:#f0f4fc,stroke-width:2px classDef short fill:#111a2c,stroke:#3a4763,color:#d7deed classDef gate fill:#241d0f,stroke:#e8c56a,color:#f0d9a0 class RM,S long class T short class G gate
| Role | Layer | Responsible for |
|---|---|---|
| Release Manager (Dev) | contract | Exactly one per repo per release, however many teams or departments have changes in it. Integrates every team's MRs and the release details (what goes in, how it is reverted, which toggles exist); agrees the release plan with the other repos' Release Managers; delivers tag + commit hash on D-1; requests hotfixes and config-only deploys. SRE's only contact for that repo's release. |
| SRE | contract | Checks tag ↔ commit hash; on release day deploys the whole set to gray, then to production in release-plan order; withdraws the release from gray when a problem shows; rolls back; confirms hotfix eligibility; sets freeze periods. SRE does not merge, branch or tag in app repos. |
| Developer | template | Develops and opens MRs; how is up to the team (Part B). |
| Dev Lead | template | Feature review and what goes into the release, inside the team. Release duties towards SRE belong to the Release Manager. |
| QC | template | Acceptance testing of an rc in a test environment is an optional, future step (section 9); it is not part of today's flow. |
How often release day happens is up to the teams (weekly, every few days, daily). The timeline is always the same:
| When | What | Who |
|---|---|---|
| Before D-1 | All MRs merged; tag created in the repo however the team works (Part B shows one way). | Dev |
| D-1, end of the working day | Tag + full commit hash + release plan delivered for every repo in the release. | Release Managers |
| Release day, 11:00 | Tag ↔ hash check; gray window opens: the whole set to gray (real traffic); observation. | SRE |
| Gray shows a problem (11:00–14:00) | The whole release is withdrawn from gray. Release Managers confirm and re-deliver every repo's tag + hash; the whole set goes to gray again. Any number of rounds. | SRE, Release Managers |
| 14:00 | Gray ends. A round still running is stopped and withdrawn; a release that has not passed gray moves to the next release day. | SRE |
| Whole set passed gray (by 14:00) | The same tags to production, in release-plan order (may run after 14:00); production verified. | SRE |
| After a production rollback | The Release Managers decide whether to release again the same day; if so, the whole set is re-delivered and must pass gray by 14:00. | Release Managers, SRE |
| Afterwards | Whatever the repo does with its branches and tags after a release (Part B: merge into master, release tag). | Dev |
Only these three kinds of fix may be deployed outside release day; everything else waits for the next release day:
Requested by the Release Manager, confirmed by SRE: the request states which kind it is and attaches the evidence (complaint ticket number, accounting anomaly record, performance data). SRE accepts it only if it qualifies. A confirmed hotfix is delivered like any release (tag + full commit hash, revert method) and goes to gray, then production. How the fix is branched inside the repo is up to the team; section 9 shows one way.
A release that cannot be reverted and has no toggle cannot be undone by SRE. That is why every request states its revert method.
| Step | Action | Time | Notes |
|---|---|---|---|
| ① | Switch off the feature toggle | Seconds to minutes | When the change is behind a toggle; no rollback needed (section 11). |
| ② | In reverse release-plan order, redeploy each repo's previous version (SRE) | Minutes | The whole release at once; existing artifacts, no rebuild. The database is not rolled back: migrations must be backward compatible (add columns first, drop them in a later release). |
| ③ | Fix forward | One release | When neither ① nor ② is possible. The development team's responsibility; the fix is delivered as a hotfix if it qualifies, otherwise on the next release day. |
The Release Manager opens one request per repo per release; SRE approves it and the platform deploys. Gray and production use the same request. It looks like this:
v1.4.0-rc.23f9c2e1d7a6b4c08e5f1a2b3c4d5e6f708192a3bv1.3.0; coupon behind toggle features.couponOne way to run a repo that makes the contract easy to meet. Teams can use it as is, adapt it, or use their own.
| Branch | Lifetime | Purpose | Who changes it |
|---|---|---|---|
master | Permanent | Code verified in production. Only verified release/* and hotfix/* branches come in. | MR only, merged by the Release Manager. |
feat/* | Until its release | Branched from master, one per feature. The feat/ prefix is the convention; the rest follows common practice (short, lowercase, --separated, ticket number when there is one). Merge master in regularly. | Developers. |
release/X.Y.Z | Until merged into master | Cut from master before D-1, one per release. The shipping feat/* branches are merged in and the rc tag is created before the D-1 deadline; after production is verified it is merged into master and deleted. | MR only. |
hotfix/* | Hours | For the three hotfix criteria. Cut from master. | Developers. |
release/1.4.0 is cut before D-1 and takes in two teams' features; rc.1 is delivered on D-1 and fails in gray, so the whole release is withdrawn; the fix becomes rc.2, re-delivered and passing gray before 14:00, and goes to production; a later hotfix is cut from master, merged into the release, and rc.3 goes through gray to production; once every repo is verified, the Release Manager merges the release into master and creates v1.4.0.feat/* branch for the next release.feat/* from master, develops and verifies in a self-verification environment.release/X.Y.Z from master, and agrees the release plan with the other repos' Release Managers.feat/* into release/X.Y.Z; it is merged after feature review.vX.Y.Z-rc.1 on release/X.Y.Z (the system builds the artifact) and delivers tag + commit hash on D-1.feat/*, merged into the release again, rc.2 is created, re-delivered and must pass gray by 14:00.release/X.Y.Z into master and creates the release tag vX.Y.Z (same content as the rc in production); the release and merged feat branches are deleted.When one feature cannot ship but the others should: the teams agree which merges to revert on release/X.Y.Z (the feature and anything depending on it), revert them via MR, create a new rc and deliver it. The reverted feature stays on its feat/* branch for a later release. A feature behind a toggle can simply stay off.
master → hotfix/* → MR into release/X.Y.Z → new rc → delivered → gray → production → later merged into master with the release.
master → hotfix/* → rc tag on the hotfix branch → delivered → gray → production → merged into master → release tag vX.Y.(Z+1).
flowchart LR M0["master"] --> H["hotfix/*"] H -->|"Case A: MR"| R["release/X.Y.Z
(in prod, not merged)"] R -->|"new rc · delivered"| G1["gray"] --> P1[("production")] P1 -.->|"merged with the release"| M1["master · vX.Y.Z"] H -->|"Case B: rc tag · delivered"| G2["gray"] --> P2[("production")] P2 -->|"verified · RM merges"| M2["master · vX.Y.(Z+1)"] classDef long fill:#111a2c,stroke:#3fd0e8,color:#f0f4fc,stroke-width:2px classDef short fill:#111a2c,stroke:#3a4763,color:#d7deed classDef gate fill:#241d0f,stroke:#e8c56a,color:#f0d9a0 class M0,M1,M2 long class H,R short class G1,G2 gate
A team that wants functional acceptance before delivery can deploy the rc to its own test environment and have QC accept it before D-1. The tag QC accepted is then the one delivered, so production runs what QC tested. This is not part of today's flow.
| Tag | Where | Purpose |
|---|---|---|
vX.Y.Z-rc.N | release/X.Y.Z (Case B hotfix: hotfix/*) | The tag delivered to SRE. N increases with each re-delivery. |
vX.Y.Z | master, after the merge | Release record, same content as the rc in production; "the previous version" for later rollbacks. X breaking change, Y new feature, Z fix. |
The artifact is built from the delivered tag and identified by its commit. Gray and production run the same artifact; environment settings are injected at deploy time, never baked in.
A published tag is never moved or deleted. A wrong tag is fixed with a new tag and a corrected delivery, not by moving the old one.
A feature toggle is a setting read at runtime that decides whether a new feature's path is used. It is one of the two ways to meet the revertibility rule in section 6. One feature then ships in two deployments:
flowchart LR A["① Ship the code
toggle off by default
normal release"] --> B["② Turn the feature on
setting change only, same artifact
config-only request by the RM"] B --> C["③ Remove the toggle
normal release"] B -.->|"problem"| D["Turn it off again"] classDef s fill:#111a2c,stroke:#3a4763,color:#d7deed classDef g fill:#241d0f,stroke:#e8c56a,color:#f0d9a0 class A,C,D s class B g
No. Part A is the contract; Part B is one way to meet it. What SRE checks is the delivery: one Release Manager, one tag + commit hash per repo by D-1, a stated revert method.
The Release Manager is the repo's single contact towards SRE for one release: integrates every team's MRs and release details and delivers the tag + hash. Dev Leads still decide inside their teams what is ready. One person can be both.
Still exactly one person for that repo's release; the departments agree who. SRE does not take separate requests per department for the same repo.
D-1 gives SRE time to check tag ↔ hash and prepare the release plan; on release day SRE only deploys. The 11:00–14:00 window gives room for several gray rounds and still leaves the afternoon for production and for watching it after release.
That is the Release Managers' decision. For example, the problem feat is taken out and the rest released again; the whole set is re-delivered and must pass gray by 14:00. After 14:00, it goes to the next release day.
Repos in one release depend on each other through the release plan. Withdrawing and re-running the whole set keeps gray and production consistent with one plan instead of a partial mix.
No. Version control inside the repo belongs to the development team. Create a correct new tag and deliver it; SRE deploys the pair that was delivered.
Take the next release day. If it cannot wait, check whether a feature toggle can switch off the problem; SRE does not accept a hotfix that does not meet the three criteria.
Not recommended. A feature usually has several commits plus later fixes, so it is easy to miss one, and cherry-picks create duplicate commits that later conflict. Merge whole branches through MRs; remove a feature with revert.
| MR | Squash | Why |
|---|---|---|
feat/*, hotfix/* → release/X.Y.Z | Allowed, optional | One feature becomes one commit on the release, so removing it is a single revert. |
release/*, hotfix/* → master | Not recommended | Squash creates a new commit, so the rc in production is no longer an ancestor of master and the release tag cannot be checked against it. |
| Tidying your own feat commits before the MR | Allowed | As long as nobody else shares the branch. |
NEXT_PUBLIC_*?At build time CI has no environment settings, so the image cannot contain values that differ per environment. Server-side code reads environment variables at runtime; browser-side values use same-origin relative paths or are provided by the server at runtime. See CI/CD Details.
Every rule maps to a mainstream industry practice; a discussion is about whether a practice fits us, not whose approach is better.
-rc.N)