Implement NuGet versioning (`2026.APIBREAK.FEATURE`)
Policies that keep the org coherent
name: NuGet versioning scheme
overview: Migrate the Novolis platform from committed 4-part 0.0.1.{build} versioning to the SDKYEAR.APIBREAK.FEATURE + CI BUILD scheme in nuget-versioning.md, with 2026.1.* floating cross-repo dependencies, while keeping GitHub Release–triggered stable publishes and cutting over at 2026.1.0.
todos:
- id: governance-version-model
content: Add build/version.json + version.props sync, rewrite Novolis.Version.targets, floating-version MSBuild props status: completed
- id: workflows-read-pack
content: Add read-version + dotnet-pack-versioned actions; update merge/release/raylib workflows; remove build bump commits status: completed
- id: normalize-deps
content: Update normalize-package-versions.ps1 and stack Directory.Packages.props to 2026.1.* + CentralPackageFloatingVersionsEnabled status: completed
- id: repo-rollout
content: Batch-migrate all packable novolis-* repos (2026.1.0 cutover, remove .novolis/version.props) status: completed
- id: docs-validate
content: Update release-policy.md and validate PR/merge/release/local pack flows status: completed isProject: false
Current vs target
| Area | Today | Target (per nuget-versioning.md) |
|---|---|---|
| Version source | `.novolis/version.props` (NovolisVersionMajor/Minor/Patch/Build) | `build/version.json` (sdkYear, apiBreak, feature) — not eng/ (ambiguous) |
| Merge publish | 4-part 0.0.1.{build}; commits build bump | 2026.1.0-ci.{run} to GitHub Packages; BUILD = `github.run_number` only (no commit) |
| Stable publish | Tag from GitHub Release → dotnet-release-publish.yml | Same trigger (your choice); pack `2026.1.0` (3-part, no build) |
| Cross-repo deps | Fixed 0.0.1.1 in stack `Directory.Packages.props` | Floating `2026.1.*` (from build/version.json) |
| MSBuild | Novolis.Version.targets maps 4-part props | CI-injected Version / PackageVersion / assembly metadata per doc |
flowchart LR
subgraph intent [Human intent]
VJ[build/version.json]
end
subgraph ci [CI on main]
MR[merge.yml]
RW[dotnet-merge-publish]
PACK["pack PackageVersion=2026.1.0-ci.N"]
GPR[GitHub Packages]
end
subgraph stable [Stable release]
REL[GitHub Release published]
RR[dotnet-release-publish]
NUG[nuget.org 2026.1.0]
end
VJ --> RW
MR --> RW --> PACK --> GPR
VJ --> RR
REL --> RR --> NUGScope: Implement in existing `novolis-workflows` + `novolis-governance`, then roll out to ~20 packable novolis-* repos. Doc references frankhaugen/shared-workflows; do not create a new repo—extend Novolis-Platform/novolis-workflows already in use.
Phase 1 — Version model and MSBuild (novolis-governance)
1.1 Add build/version.json template
Naming: use repo-root build/ for version intent—not eng/ (ambiguous between engineering and English). This coexists with existing per-repo build/*Packaging.props (e.g. physics, rendering); version files are build/version.json and generated build/version.props.
Per-repo file (initial cutover `2026.1.0`):
{
"sdkYear": 2026,
"apiBreak": 1,
"feature": 0,
"dotnetBaseline": "net10.0",
"publicPackage": true
}1.2 Replace .novolis/version.props import chain
- Update each repo `Directory.Build.props`: import
build/version.props(generated MSBuild projection) instead of.novolis/version.props. - Remove
.novolis/version.propsafter migration (update `merge.yml` paths-ignore to ignorebuild/version.jsononly if release bumps are committed manually—not for CI build bumps).
Recommended: commit `build/version.json` (source of truth) plus a thin `build/version.props` generated by script (MSBuild-friendly) to avoid requiring System.Text.Json in every build:
<NovolisSdkYear>2026</NovolisSdkYear>
<NovolisApiBreak>1</NovolisApiBreak>
<NovolisFeature>0</NovolisFeature>
<NovolisStableVersion>2026.1.0</NovolisStableVersion>
<NovolisPackageFloatVersion>2026.1.*</NovolisPackageFloatVersion>Add `novolis-governance/scripts/sync-version-props.ps1` to regenerate build/version.props from JSON (used by release bump PRs and local dev).
1.3 Rewrite `Novolis.Version.targets`
- Local / non-CI: if
$(PackageVersion)unset, set stable$(NovolisStableVersion)for packable projects. - Do not set 4-part
Versionfrom committed build segment. - When CI passes
-p:PackageVersion=...(see Phase 2), targets should not override it. - Apply assembly metadata from doc when CI properties present:
| Property | CI value |
|---|---|
AssemblyVersion | {sdkYear}.{apiBreak}.0.0 |
FileVersion | {sdkYear}.{apiBreak}.{feature}.{run} |
InformationalVersion | {stable}+build.{run}.sha.{shortSha} |
- Add
ContinuousIntegrationBuildwhenGITHUB_ACTIONS=true(already partially done in stack repos).
1.4 Central floating Novolis dependencies
Add to governance `Directory.Build.props` fragment (or template applied by configure-package-publishing.ps1):
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageFloatingVersionsEnabled>true</CentralPackageFloatingVersionsEnabled>Update `normalize-package-versions.ps1`:
- Replace
Novolis.*pins0.0.1.1→$(NovolisPackageFloatVersion)or literal2026.1.*synced frombuild/version.json. - Apply to:
novolis-physics,novolis-simulation,novolis-rendering,novolis-raylib,novolis-testing, and any other repo withPackageVersion Include="Novolis.*". - Update dogfooding from
Version="*"toVersion="2026.1.*"for consistency (still floating, but aligned to platform line).
GPR / `-ci` note: NuGet floating 2026.1.* resolves stable versions only. Until 2026.1.0 exists on a feed, stack CI may need the first stable publish or temporary 2026.1.*-* on the GitHub Packages source. Validate in novolis-math → novolis-physics merge order after cutover; adjust `nuget.config` only if restore fails (document in `release-policy.md`).
1.5 Deprecate repo *Packaging.props fallbacks
Remove or narrow 0.0.1.1 / 0.1.0-local fallbacks in:
- `novolis-physics/build/Novolis.Physics.Packaging.props`
- `novolis-rendering/build/Novolis.Rendering.Packaging.props`
- simulation / raylib equivalents
Governance targets become the single version path.
Phase 2 — CI actions and workflows (novolis-workflows)
2.1 New composite: read-version
Replace `resolve-version` logic:
Inputs: version-file (default build/version.json), optional prerelease-label (ci), build-number (github.run_number).
Outputs:
stableVersion→2026.1.0packageVersion→2026.1.0-ci.382(merge) or2026.1.0(release)assemblyVersion,fileVersion,informationalVersionstrings for-p:passthrough
Implementation: jq in bash (ubuntu-latest) or small inline Node—keep dependency-free if possible.
2.2 New composite: dotnet-pack-versioned
Wrap `pack`:
dotnet pack "$slnx" -c Release -o artifacts/packages \
-p:Version="$PACKAGE_VERSION" \
-p:PackageVersion="$PACKAGE_VERSION" \
-p:AssemblyVersion="$ASSEMBLY_VERSION" \
-p:FileVersion="$FILE_VERSION" \
-p:InformationalVersion="$INFO_VERSION"2.3 Update `dotnet-merge-publish.yml`
read-version (ci) → dotnet-build → dotnet-pack-versioned → publish-github-packagesRemove: `bump-build-version` and `commit-version-bump` from merge path.
Remove contents: write from merge jobs where only used for version commits (keep packages: write).
2.4 Update `dotnet-release-publish.yml`
Keep GitHub Release published trigger in per-repo `release.yml`.
- `resolve-release-version`: validate tag matches
v{sdkYear}.{apiBreak}.{feature}(e.g.v2026.1.0). - Pack with 3-part
PackageVersiononly (no-ci, no 4th segment). - Optional: fail if tag’s
feature≠build/version.json(prevents accidental drift).
Feature bump: remains manual edit to build/version.json before creating the GitHub Release (doc’s workflow_dispatch bump can be a later enhancement; not required for your chosen trigger).
2.5 Raylib path
Update `dotnet-raylib-merge-publish.yml` and `raylib-pack-publish` to use read-version + versioned pack; drop commit-version-bump there too.
2.6 Workflow inputs (optional, low priority)
Add version-file: build/version.json to reusable workflow_call inputs for forward compatibility; default suffices for all Novolis repos.
Phase 3 — Per-repo rollout (batch via governance scripts)
Update `configure-package-publishing.ps1` and `apply-pr-merge-release-workflows.ps1` to:
- Create
build/version.json+build/version.propsat `2026.1.0` - Delete
.novolis/version.props - Fix
Directory.Build.propsimport path - Normalize
Directory.Packages.propsNovolis refs →2026.1.* - Enable
CentralPackageFloatingVersionsEnabledwhere missing - Fix
paths-ignorein merge workflows
Repos (~20): math, physics, simulation, rendering, raylib, commands, avalonia, aspire, analyzers, codegen, machinelearning, markup, messaging, security, storage, transports, wirefish, testing, templates, template-dotnet, install, smoketest.
Order suggestion: novolis-workflows + novolis-governance first → novolis-math (leaf) → physics → simulation → rendering → raylib → remaining leaves.
Phase 4 — Docs, registry, and validation
| Item | Action |
|---|---|
| nuget-versioning.md | Replace eng/ with build/ throughout; add Novolis-specific note: workflows live in novolis-workflows, release trigger = GitHub Release |
| release-policy.md | Rewrite for new scheme; remove “CI bumps build in git” |
novolis-physics/docs/VERSIONING.md | Point to platform doc or archive |
| `novolis-registry/packages/*.json` | Update metadata versions to 2026.1.0 when publishing |
| Installer `Resolve-Version.ps1` | Read build/version.json if it consumes package version |
Validation checklist
- PR: build + test only; no publish; no version file changes.
- Merge math: GPR receives
Novolis.Math.*at2026.1.0-ci.{run}; git tree unchanged. - Merge physics: restore
Novolis.Math.Geometryat2026.1.*succeeds against GPR (or documented prerelease workaround). - Release math: create GitHub Release
v2026.1.0→ nuget.org gets2026.1.0; assembly/file/info versions match doc. - Local:
dotnet pack /p:NovolisLocalPack=trueproduces2026.1.0without CI env.
Explicit non-goals (this pass)
workflow_dispatchrelease with auto-bump PR (doc’sdotnet-nuget-release.yml); manualbuild/version.json+ GitHub Release is enough per your choice.- New
frankhaugen/shared-workflowsrepo. eng/folder anywhere in Novolis repos (usebuild/instead).- Auto-increment
featurefrom commit analysis (human-ownedbuild/version.jsononly).
Risk summary
| Risk | Mitigation |
|---|---|
Floating 2026.1.* ignores -ci packages | Publish first stable 2026.1.0 to GPR or use 2026.1.*-* temporarily on GPR-only consumers |
Breaking all existing 0.0.1.x consumers | Expected platform cutover; document in release notes |
| Parallel merge workflows pin different ci builds | Acceptable with floating; consumers get latest matching stable/ci per NuGet rules |
| Raylib native pack path diverges | Dedicated workflow update in Phase 2.5 |