bitz-upgrade plans, applies, and safely rolls back managed BitzOrcas package version upgrades in a Consumer Solution. It is a packable dotnet tool (BitzOrcas.Upgrade.Cli) with three subcommands: plan, apply, and rollback. It mutates only the Version attribute of existing BitzOrcas.* PackageVersion entries in Directory.Packages.props — never business source, csproj, config, manifests, or databases.
What it touches
The write surface is intentionally narrow. CentralPackageVersionEditor replaces the Version value of <PackageVersion Include="BitzOrcas.…"> nodes and leaves byte structure, comments, and third-party packages untouched. Anything beyond package versions — breaking changes, configuration deltas, database migrations, template-owned Host shells — appears only in the plan as manual steps; irreversible impacts block apply.
Four version surfaces
A BitzOrcas release carries four version surfaces that must move together:
| Surface | What |
|---|---|
| Template | the Consumer Solution scaffolding |
| SDK / BOM | central Directory.Packages.props versions |
| Commercial packages | NuGet package versions on the private feed |
| License protocol | the Runtime License versionRange |
One release train uses one unified product version. The upgrade map (template-upgrade-map.json, managedFiles constrained to ["Directory.Packages.props"], packageGraphPolicy: "unchanged") is the machine-readable source the tool reads.
Commands
# Plan: compute the deterministic plan without writing. Output to stdout,# or to a file outside the Consumer worktree with --output for review.bitz-upgrade plan --root <consumer> --map <upgrade-map.json> --to <version> [--output <plan.json>]
# Apply: mutate Directory.Packages.props and record audit state.# With --plan, the external reviewed plan must byte-match the re-computed plan.bitz-upgrade apply --root <consumer> --map <upgrade-map.json> --to <version> [--plan <plan.json>]
# Rollback: restore the previous package versions from the recorded state.bitz-upgrade rollback --root <consumer>| Option | Applies to | Required | Semantics |
|---|---|---|---|
--root | all | yes | Consumer worktree root |
--map | plan, apply | yes | path to the upgrade map |
--to | plan, apply | yes | target version (must equal a map version or already-at) |
--output | plan | no | plan file, must be outside the Consumer worktree |
--plan | apply | no | external reviewed plan (must byte-match) |
Audit trail and integrity
Every apply writes an immutable audit trail under .bitzorcas/:
upgrade-state.json+upgrade-state.sha256— the active state (from/to versions, checksums of managed file, manifest, map, package graph, plan, backup, timestamps)upgrade-backups/{from}-to-{to}/Directory.Packages.props.bak— the pre-apply backup- on rollback: state moves to
upgrade-history/{from}-to-{to}.applied.json(+.sha256) andlast-rollback.jsonis written
Every state transition is checksummed (64-char lowercase hex) and fail-closed: an orphan checksum, a missing checksum, a checksum mismatch, a bad schema version, a pre-existing backup/state, or a reparse-point .bitzorcas each produce a distinct BITZUPnnn error and abort. Rollback re-verifies the current packages checksum, manifest checksum, package-graph checksum, and backup checksum before restoring; any mismatch aborts and preserves the apply-time bytes.
Irreversibility gating
If the upgrade map declares an irreversible migration or configuration change (canApply=false), apply is blocked — the tool only reports the manual steps. The tool never fabricates an automatic rollback over an irreversible migration. Rollback is not git checkout: it restores the recorded package versions from the .bitzorcas/ state, verified by checksum.
Exit codes
| Code | Meaning |
|---|---|
0 | success |
2 | usage error |
3 | input / schema invalid |
4 | incompatible (unknown target, unsupported source, no direct path) |
5 | dirty / drift (on-disk state differs from re-computed) |
6 | I/O failure |
130 | cancelled |
Errors write to stderr in the fixed format BITZUPnnn: message; stdout carries only the plan or a success status.
Minimal reviewed-plan flow
Write the plan outside the Consumer worktree and pass that exact file to apply after approval. The tool recomputes and byte-compares the plan, so a post-review source, map, or package-graph change blocks the write:
# The temporary directory must be outside the Consumer worktree.UPGRADE_REVIEW_DIR="$(mktemp -d)"bitz-upgrade plan --root ./consumer --map ./template-upgrade-map.json \ --to 1.2.0 --output "$UPGRADE_REVIEW_DIR/plan.json"
# Apply the reviewed bytes, then verify locked restore and behavior.bitz-upgrade apply --root ./consumer --map ./template-upgrade-map.json \ --to 1.2.0 --plan "$UPGRADE_REVIEW_DIR/plan.json"dotnet restore ./consumer --locked-modedotnet test ./consumer --no-restoreKeep the plan, tool version, upgrade-map hash, Directory.Packages.props diff, and verification results in one change record. The plan is not a secret, but it exposes package topology and upgrade steps and should be handled as internal build metadata.