Instruction file imported from RaythaHQ/raytha (
.cursor/rules/rfc-0012-versioning-and-migrations.mdc). Copyright stays with the author.
RFC-0012: Versioning and Migrations
Status: Active
Abstract
VERSION at the repo root is the product version. MSBuild reads it into the
assembly version, /healthz reports it, and tools/check-version.py requires
the pull request into main to bump it once for everything since the last
release. Adding an EF migration bumps MINOR; everything else bumps PATCH.
Pull requests into dev do not bump it.
1. The VERSION File
VERSIONholdsMAJOR.MINOR.PATCHand nothing else. Current baseline:2.0.0.Directory.Build.propsreads it intoVersion,AssemblyVersion,FileVersion, andInformationalVersion, so every project shares it.CurrentVersionreads it back from the assembly at runtime; there is no second place to edit a version./healthzand/healthz/readyincludeversion. A running instance whose reported version does not match the checked-outVERSIONis a stale build.
2. Bump Rules
- MINOR + reset PATCH when the release adds a new migration file under
**/Persistence/Migrations/*.cs(ignoring*.Designer.csand*ModelSnapshot.cs). - PATCH for every other releasable change.
- MAJOR is a human decision. The scripts never touch it.
VERSIONitself anddocs/supply-chain/sbom.cdx.jsondo not count as releasable changes; a commit touching only those skips validation.- Only a pull request into
mainbumpsVERSION. That one bump covers every releasable change sinceorigin/main. Pull requests intodevkeep the last released version. CI'sversionjob enforces this by runningcheck-version.pyonly when the pull request base ismain../tools/ci-local.shdoes the same: it compares againstorigin/mainwhenVERSION_BASE=origin/mainor the open pull request targetsmain, and skips otherwise.
python3 tools/bump-version.py --kind patch --write # or --kind minor
python3 tools/bump-version.py --before origin/main --after HEAD --print-kind
python3 tools/check-version.py --before origin/main --after HEAD
python3 tools/bump-version.py --self-test
python3 tools/check-version.py --self-test
VERSION_BASE=origin/main ./tools/ci-local.sh
- The bumped
VERSIONMUST be committed in the pull request intomainthat releases it, not in each pull request intodev. - If
check-version.pysays the expected version is 2.0.5 and you wrote 2.1.0, fixVERSION— do not edit the script. - Break glass. A maintainer can accept a
VERSIONthe bump rules reject (a downgrade before a release, a deliberate skip) by adding theversion-overridelabel to the pull request. CI then runscheck-version.py --override, which still requires a well-formedMAJOR.MINOR.PATCHand prints that the override was used. The label is the audit trail: say in the PR description why. Locally,VERSION_OVERRIDE=1 VERSION_BASE=origin/main ./tools/ci-local.shdoes the same. Remove the label and the normal rules apply again. A pull request intodevdoes not need the label: that check does not run, and the fix is to leaveVERSIONat the last release rather than bump it.
3. Migration Naming
- Migration name is the release that carries it:
v2_0_0, thenv2_1_0. Do not name a migration after what it changes. - Migrations live in
src/Raytha.Infrastructure/Persistence/Migrations(RFC-0001 §4).
dotnet ef migrations add v2_1_0 \
--project src/Raytha.Infrastructure \
--startup-project src/Raytha.Web
- Because a new migration forces the MINOR bump, the migration name and the
VERSIONon the pull request intomainMUST agree.v2_1_0released as 2.0.6 is wrong. Ondev,VERSIONstays at the last release until that pull request. PersistenceConventionTestsasserts thev2_0_0migration exists. That is the 2.0 baseline; do not delete or rewrite it to tidy history.- Migrations MUST be additive and safe to run against a live 1.5.0 database. Dropping or renaming a column that existing content depends on needs a deliberate two-step (add, backfill, then remove in a later release).
4. SQL Scripts
db/Postgres/ carries the operator-facing SQL, and a schema change MUST update
it in the same commit:
| File | Purpose |
|---|---|
FreshCreateOnLatestVersion.sql |
Full schema at the current version |
v1_5_0_to_v2_0_0.sql |
Upgrade from the previous release |
v1_4_1_to_v1_5_0.sql, v1_4_0_to_v1_4_1.sql |
Historical upgrades, frozen |
dotnet ef migrations script v1_5_0 v2_0_0 \
--project src/Raytha.Infrastructure \
--startup-project src/Raytha.Web \
--output db/Postgres/v1_5_0_to_v2_0_0.sql
- Historical scripts are frozen. A new release gets a new
v<previous>_to_v<new>.sql. - Operators who cannot run migrations at startup apply these by hand, so a missing or stale script is a broken upgrade, not a paperwork problem.
tools/check-sql-scripts.pyregeneratesFreshCreateOnLatestVersion.sqland the upgrade into the latest migration, and fails CI if either is missing or differs. It ignores only the EF version stamp and thev1_4_0default-theme seed, which change on every generation.
5. Applying Migrations
APPLY_PENDING_MIGRATIONS=truemigrates on startup. It is the default for Docker Compose and./tools/dev.sh.- Production MAY leave it false and apply the SQL script during deploy. Either path MUST end at the same schema — that is what the generated script guarantees.
