Skip to content
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
80ede78
fix: add missing newline at end of file
haphut Feb 24, 2026
ea2da55
refactor: narrow scope to git, Docker, and Kubernetes
haphut Feb 24, 2026
222d31b
refactor: remove release candidate (-rc) tags
haphut Feb 24, 2026
57da8b5
refactor: standardize Docker tag format
haphut Feb 24, 2026
40c1e77
refactor: update deployment flow with three environments
haphut Feb 24, 2026
6ce8516
docs: link to SemVer 2.0.0 specification
haphut Feb 24, 2026
ebebc59
refactor: rewrite hot-fix process using short-lived fix/ branches
haphut Feb 24, 2026
65fa73e
docs: clarify git branch terminology
haphut Feb 24, 2026
98b3880
docs: note that release management is manual for now
haphut Feb 24, 2026
9e238d4
docs: update CAB section to reflect staging-first flow
haphut Feb 24, 2026
b1d0791
docs: update summary table to reflect all changes
haphut Feb 24, 2026
265cbfc
docs: distinguish current and target deployment state
haphut Feb 24, 2026
e8595a1
docs: document MINOR/PATCH convention with reasoning
haphut Feb 24, 2026
9348be9
docs: update summary table for MINOR/PATCH convention
haphut Feb 24, 2026
0ec1635
docs: describe Conventional Commits as the target convention
haphut Feb 24, 2026
5e83649
docs: acknowledge SemVer rule 6 deviation with reasoning
haphut Feb 24, 2026
796e123
docs: use exact Pro Git quote for branch terminology
haphut Feb 24, 2026
86210ca
docs: broaden short-lived branches beyond feature work
haphut Feb 24, 2026
43136aa
docs: expand MAJOR version triggers to include deployment config and …
haphut Feb 24, 2026
0c7cd9f
docs: restructure hot-fix example with problem-first formatting
haphut Feb 24, 2026
cade7a9
docs: use full SHA in Docker tag example
haphut Feb 24, 2026
ff387e3
docs: fix development deployment trigger and reference GitOps proposal
haphut Feb 24, 2026
b9f62db
docs: clarify CAB records and changelog as future state
haphut Feb 24, 2026
2333824
docs: update summary table for all recent changes
haphut Feb 24, 2026
d88a31a
docs: recommend issue number in branch names and commit footers
haphut Feb 24, 2026
d5d7875
docs: remove broken link to separate proposal
haphut Feb 24, 2026
4439612
docs: use long-sha instead of short-sha
haphut Feb 24, 2026
9d60e00
docs: rename Source column and specify git source per environment
haphut Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 110 additions & 87 deletions docs/one-pagers/Branching-Versioning-Deployment-Proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ To improve development speed, consistency, and deployment safety, this proposal
- Deployment promotion between environments
- Security hot-fixes
- CAB approval process
- Versioning of APIs and Pulsar topics

API and Pulsar topic versioning are covered in a future proposal.

The design aims to reduce manual work, limit human errors, and ensure reliable traceability from commit → Docker image → environment deployment.

Expand All @@ -35,30 +36,72 @@ By adopting a consistent approach—centered around **trunk-based development**,

### 1. Git Branches

We adopt **trunk-based development** ([https://trunkbaseddevelopment.com/](https://trunkbaseddevelopment.com/)):
Regarding terminology, ["A branch in Git is simply a lightweight movable pointer to one of these commits"](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell), not a sequence of commits. We use branches as follows:

- **`main`** — the single long-lived branch (trunk). All integration happens here.
- **Short-lived branches** — for features, bug fixes, refactoring, and other development work. Named by type of change, e.g. `feat/`, `fix/`, `refactor/` (aligned with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) types). Merged to `main` via pull request and then deleted.
- **`fix/` branches** (hot-fixes) — a special case: created from the commit that the production tag points to, not from `main` (see section 6). Deleted once the patch tag exists.

It is recommended to include the issue number in the branch name for traceability, e.g. `feat/123-add-login` or `fix/456-token-expiry`. The issue number is not required — some work is done without a tracked issue. When using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), the issue reference goes in the commit message footer (e.g. `Refs: #123` or `Fixes: #456`). This practice aligns with the future release management proposal.

We adopt **trunk-based development** ([https://trunkbaseddevelopment.com/](https://trunkbaseddevelopment.com/)).

[See Trunk Based Development Migration Plan Proposal](./Trunk-Based-Development-Migration-Plan-Proposal.md)

---

### 2. Version Number Scheme

Use **Semantic Versioning (SemVer)**:
Based on [**Semantic Versioning 2.0.0 (SemVer)**](https://semver.org/spec/v2.0.0.html):
`MAJOR.MINOR.PATCH`

| Level | Trigger | Example |
|--------|----------|----------|
| MAJOR | Breaking API or schema change | 2.0.0 |
| MINOR | Backward-compatible feature | 1.2.0 |
| PATCH | Bug or security fix | 1.2.3 |
| MAJOR | Breaking change: API, schema, deployment configuration (e.g. renamed/removed K8s manifest variables), or significant change in service scope affecting downstream consumers | 2.0.0 |
| MINOR | Regular release from `main` (features, improvements, bug fixes) | 1.2.0 |
| PATCH | Hot-fix to a specific release (see section 6) | 1.2.1 |

#### Convention: MINOR for releases, PATCH for hot-fixes

Regular releases from `main` always bump at least **MINOR**. **PATCH** is reserved exclusively for hot-fixes applied to a specific release via a `fix/` branch.

**Why?** When multiple environments run different versions, PATCH-level releases can create version conflicts.

##### Problem: using PATCH for regular releases

If both staging and production use PATCH for regular releases:

```
staging: 1.2.4
production: 1.2.3
```

A hot-fix to production could not use `1.2.5` without implying it contains `1.2.4`'s changes, which it does not. SemVer provides no suffix or modifier that resolves this: pre-release versions (e.g., `1.2.4-hotfix.1`) have *lower* precedence than `1.2.4` in SemVer, and build metadata (e.g., `1.2.4+hotfix.1`) is ignored for precedence entirely.

##### Solution: reserving PATCH for hot-fixes

If regular releases always bump MINOR:

```
staging: 1.3.0
production: 1.2.0
```

A production hot-fix is tagged as `v1.2.1`. This is unambiguous: `1.2.1` is a patch to `1.2.0` and has nothing to do with `1.3.0`. Staging can independently receive its own fix as `v1.3.1` if needed.

By reserving PATCH for hot-fixes, each MINOR release gets its own isolated patch space and no version conflicts can occur between environments.

**SemVer deviation:** Our versioning is based on SemVer 2.0.0 with one deliberate deviation from rule 6. The spec says PATCH MUST be incremented when only backwards-compatible bug fixes are introduced. We bump MINOR instead, to preserve the version space isolation described above. This is defensible because: (1) SemVer was designed for libraries where consumers resolve version ranges — for deployed services, there is no dependency resolution; (2) rule 7 already allows MINOR releases to include patch-level changes, so the semantic signal is only slightly stretched; and (3) in trunk-based development, bug-fix-only releases from `main` are rare — nearly every release includes at least some new functionality.

This version number is applied consistently across:

- Git tags (`v1.2.3`)
- Docker image tags (`:v1.2.3`)
- Docker image tags (`:1.2.3`)
- Deployment metadata
- Release notes

Version bumps are currently a **manual decision** by the developer or team when creating a release tag. The target is to adopt [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages (tools like [commitizen](https://github.com/commitizen/cz-cli) can help enforce the format). Automating version bumps and changelogs based on Conventional Commits (e.g., via [semantic-release](https://github.com/semantic-release/semantic-release)) is deferred to a separate proposal.

---

### 3. Git Tags
Expand All @@ -67,12 +110,11 @@ Git tags define deployment points.

| Tag Type | Example | Purpose |
|-----------|----------|----------|
| Release | `v1.2.0` | Production-ready version |
| Release Candidate | `v1.2.0-rc` | Staging pre-release |
| Hot-fix | `v1.2.1` | Urgent patch or security update |
| Release | `v1.2.0` | Regular release from `main` for staging and production |
| Patch | `v1.2.1` | Hot-fix to a specific release via a `fix/` branch |

Tags are created from commits on `main`.
These tags automatically trigger Docker builds and deployments for staging and production.
Tags are created from commits on `main` (releases) or from `fix/` branches (patches).
These tags trigger Docker image builds. Both staging and production deploy from these version tags.
Releases and CAB decisions refer to these tags for auditability.

---
Expand All @@ -83,119 +125,101 @@ Docker tags mirror Git commit and versioning states for clear traceability.

Each image built from CI/CD gets:

- **Commit tag:** `:<git-sha>` — exact build traceability
- **Version tag:** `:vX.Y.Z` — immutable release version
- **Commit tag:** `:sha-<long-sha>` — exact build traceability
- **Version tag:** `:X.Y.Z` — immutable release version (no `v` prefix)
- **Edge tag:** `:edge` — rolling tag updated on every merge to `main`

**Example:**
ghcr.io/org/service:v1.2.0
ghcr.io/org/service:commit-1d4cac8

```
ghcr.io/org/service:edge
ghcr.io/org/service:1.2.0
ghcr.io/org/service:sha-0c7cd9f834344872ae8410fd679848bfacca8406
```

Deployments reference either immutable version tags (`vX.Y.Z`) or specific commit tags for reproducibility.
The `edge` tag tracks the latest `main` commit and is used by the development environment. Version tags (`X.Y.Z`) are immutable and used by staging and production.

---

### 5. Deployment Process (Environment Promotion)

| Environment | Source | Trigger | Tag | CAB Required | Notes |
|--------------|---------|----------|------|----------------|-------|
| **Development** | `main` branch | Auto on merge | commit SHA | No | Continuous deployment of latest commits |
| **Production** | `main` branch | Manual promotion | `vX.Y.Z` | Yes | Immutable release deployment |
#### Current state

Promotion between environments is handled via:
We currently have two environments: development and production.

- Git tag creation or image promotion
| Environment | Git Source | Trigger | Docker Tag | CAB Required | Notes |
|--------------|-------------|----------|------------|----------------|-------|
| **Development** | `main` branch | Manual (pipeline trigger) | `edge` | No | Deployment of latest `main` |
| **Production** | `Git tag vX.Y.Z` | Manual promotion | `X.Y.Z` | Yes | Immutable release deployment |

Rollback is straightforward: re-deploy a previous tag (`v1.1.4`).
Current promotion flow:

---
1. Merges to `main` are deployed to **development** via the `edge` Docker tag by manually triggering the CI/CD pipeline.
2. When a release is ready, a Git tag (`vX.Y.Z`) is created on `main`, producing an immutable Docker image tagged `X.Y.Z`.
3. After CAB approval, the `X.Y.Z` image is deployed to **production**.

### 6. Security Hot-fixes
Rollback is manual: re-deploy a previous version tag (e.g., `1.1.0`).

- All **Dependabot** or manual security PRs target the default (`main`) branch.
- Urgent production fixes are **cherry-picked** from `main` onto the currently deployed production tag’s base commit.
- A new **patch version tag** (`v1.2.1`) is created and deployed directly to production.
- Normal development continues on `main` uninterrupted.
#### Target state

This ensures critical fixes reach production fast, without merging unfinished features.

---
A **staging** environment will be added between development and production. Production becomes a delayed version of staging: every version deployed to production has first been validated in staging.

### 7. CAB Meetings

- CAB meetings are held before production deployments.
- CAB decisions explicitly approve a Git tag (e.g., `v1.2.0`).
- The deployment process uses this approved tag for production rollout.
- CAB records link back to the tag and its changelog for traceability.

---
| Environment | Git Source | Trigger | Docker Tag | CAB Required | Notes |
|--------------|-------------|----------|------------|----------------|-------|
| **Development** | `main` branch | Auto on merge | `edge` | No | Automatic deployment of latest `main` (via GitOps, see future proposal) |
| **Staging** | `Git tag vX.Y.Z` | Git tag `vX.Y.Z` pushed to GitHub | `X.Y.Z` | No | Validates a release before production |
| **Production** | `Git tag vX.Y.Z` | Manual promotion | `X.Y.Z` | Yes | Same version tag as staging, after CAB approval |

### 8. Versioning: Public/Partner APIs
Target promotion flow:

- Versioned through **URL prefixing**: `/api/v1/...`
- Increment MAJOR version (`/v2/`) when breaking changes occur.
- Deprecated versions remain accessible for a transition period.

Example:
/api/v1/users
/api/v2/users
1. Every merge to `main` automatically deploys to **development** via the `edge` Docker tag (GitOps).
2. When a release is ready, a Git tag (`vX.Y.Z`) is created on `main`, producing an immutable Docker image tagged `X.Y.Z`.
3. The `X.Y.Z` image is deployed to **staging** for validation.
4. After CAB approval, the same `X.Y.Z` image is promoted to **production**.

Rollback is manual: re-deploy a previous version tag (e.g., `1.1.0`).

---

### 9. Versioning: HSL-internal APIs

- Internal APIs use the same SemVer approach but are coordinated via internal dependency management.
- Changes are reflected in OpenAPI definitions.
- Breaking changes → MAJOR version increment in `/internal/vX/` path.
### 6. Hot-fixes

Example:
/internal/v1/vehicle-status
When an urgent fix is needed in production:

1. The fix is developed on a short-lived **`fix/` branch** (e.g., `fix/auth-token-expiry`) created from the commit that the current production tag points to.
2. The fix is also merged to `main` so trunk stays ahead.
3. A new **PATCH version tag** (e.g., `v1.2.1`) is created on the `fix/` branch. Because regular releases always bump MINOR, the PATCH space is available exclusively for hot-fixes (see section 2).
4. The resulting Docker image (`1.2.1`) is deployed to production (and staging, once available).
5. The `fix/` branch is **deleted** once the patch tag is in place.

---

### 10. Versioning: Team-internal APIs
The `fix/` branch is short-lived (days to weeks at most). It exists only until the next regular release from `main` includes the same fix, at which point production catches up with trunk.

- For intra-team development, explicit versioning may be skipped during early iteration.
- Optional `/dev/` endpoint can be used to indicate unstable contracts.
- Once stabilized, versioning follows the standard `/v1/` scheme.
If multiple environments need the same fix, each environment's current MINOR release is patched independently (e.g., production at `1.2.0` gets `v1.2.1`, staging at `1.3.0` gets `v1.3.1`). This avoids version conflicts between environments.

Example:
/dev/metrics
/v1/metrics
All **Dependabot** or manual security PRs target `main`. If the fix is urgent enough to bypass the normal release cycle, the `fix/` branch process above is used.

The `fix/` prefix aligns with the `fix:` type in [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).

---

### 11. Versioning: Pulsar Topics

To support schema evolution in event-driven systems:

- Add version suffix to topic names: `topic.v1`, `topic.v2`
- Increment version when schema or serialization changes are not backward compatible.
- Schema changes follow **SchemaVer** (`MAJOR-MINOR-PATCH`) for consistency with JSON schema management.

Example:
vehicle.events.v1
vehicle.events.v2
### 7. CAB Meetings

- CAB meetings are held before promoting a release to production.
- CAB decisions explicitly approve a Git tag (e.g., `v1.2.0`) for production deployment.
- The deployment process promotes the approved Docker image (`1.2.0`) to production.
- Once a staging environment exists, the version will have been validated there before CAB review, and CAB records will link back to the tag and its changelog for traceability. Changelogs are defined by the future release management proposal (see Conventional Commits and semantic-release).

---

## Summary

| Aspect | Decision |
|--------|-----------|
| **Branching** | Trunk-based (`main` only, short-lived features) |
| **Version Scheme** | Semantic Versioning (SemVer) |
| **Git Tags** | `vX.Y.Z`, `vX.Y.Z-rc` |
| **Docker Tags** | `:commit-sha`, `:vX.Y.Z` |
| **Deployments** | GitOps or pipeline-driven promotion between environments |
| **Hot-fixes** | Cherry-pick + patch release |
| **CAB** | Mandatory before production |
| **API Versioning** | `/v1/`, `/v2/`, internal `/internal/v1/`, optional `/dev/` |
| **Pulsar Topics** | `topic.v1`, `topic.v2` using SchemaVer |
| **Branching** | Trunk-based (`main` only, short-lived `feat/`, `fix/`, `refactor/`, and other branches) |
| **Version Scheme** | Based on [SemVer 2.0.0](https://semver.org/spec/v2.0.0.html): MINOR for releases, PATCH for hot-fixes only |
| **Git Tags** | `vX.Y.Z` (releases from `main`, patches from `fix/` branches) |
| **Docker Tags** | `:edge`, `:X.Y.Z`, `:sha-<commit-sha>` |
| **Environments** | Development (`edge`, manual pipeline trigger) + production (`X.Y.Z`); staging (`X.Y.Z`) and GitOps planned |
| **Hot-fixes** | Short-lived `fix/` branch + PATCH tag, isolated per MINOR version |
| **CAB** | Mandatory before production; staging validation and changelog traceability planned |

---

Expand All @@ -206,4 +230,3 @@ vehicle.events.v2
- Reliable and traceable version history
- Simplified hot-fix process
- Transparent CAB-governed production releases
- Clear API and message schema versioning across all integration points