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](d:\novolis\.github\docs\nuget-versioning.md)) |
|---|---|---|
| Version source | [`.novolis/version.props`](d:\novolis\novolis-math\.novolis\version.props) (`NovolisVersionMajor/Minor/Patch/Build`) | [`build/version.json`](d:\novolis\.github\docs\nuget-versioning.md) (`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](d:\novolis\novolis-workflows\.github\workflows\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`](d:\novolis\novolis-physics\Directory.Packages.props) | Floating **`2026.1.*`** (from `build/version.json`) |
| MSBuild | [Novolis.Version.targets](d:\novolis\novolis-governance\build\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`](d:\novolis\novolis-governance\build\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`](d:\novolis\novolis-workflows\.github\workflows\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`](d:\novolis\novolis-workflows\.github\workflows\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](d:\novolis\.github\docs\nuget-versioning.md) | Replace `eng/` with `build/` throughout; add Novolis-specific note: workflows live in `novolis-workflows`, release trigger = GitHub Release |
| [release-policy.md](d:\novolis\novolis-governance\docs\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`](d:\novolis\novolis-registry\packages) | Update metadata versions to `2026.1.0` when publishing |
| Installer [`Resolve-Version.ps1`](d:\novolis\novolis-installer-inno\scripts\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 |