hashgraph / hashgraph/solo-weaver
fix(migration): a non-semver `provisioner.version` in state.yaml aborts every CLI invocation
- Dominant language
- Go
- Stars
- 3
- Forks
- 0
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 47
Description
## Problem
A `provisioner.version` of `"dev"` in `state.yaml` makes **every** subsequent CLI
invocation abort in the startup-migration pass, before the command runs:
```
x Error: failed to execute command
Cause: common.illegal_state: failed to check if migration
"cilium-disable-xdp-acceleration-v0.19.1" applies,
cause: common.illegal_state: invalid installed CLI version "dev",
cause: common.illegal_format: failed to parse major part "dev",
cause: strconv.ParseUint: parsing "dev": invalid syntax
Resolution:
1. Check error message for details or contact support
```
Both `install` and `daemon service install` are blocked, so the host cannot be
re-provisioned by the normal path and there is no operator-actionable hint.
## Mechanism
`RunStartupMigrations` (`cmd/cli/commands/common/run.go:333-347`) reads
`provisioner.version` from `state.yaml` and passes it through
`migration.ResolveInstalledCLIVersion`, which special-cases **only** the empty string:
```go
func ResolveInstalledCLIVersion(raw string) string {
if raw == "" {
return BaselineCLIVersion
}
return raw
}
```
`CLIVersionMigration.Applies` then calls `semver.NewSemver(installedCLIVersion)` and
returns `errorx.IllegalState` on a parse failure
(`internal/migration/cli_version_migration.go:73-75`), which aborts the whole
invocation.
`"dev"` is not an unexpected value: it is what `github.com/automa-saga/version`
reports for a binary built without `-ldflags`, and the repo already names it —
`devVersionPlaceholder = "dev"` in `cmd/cli/commands/block/node/daemon_offer.go:171`.
So one part of the codebase knows the placeholder exists and the migration path does
not. Any `go run` / `go build` / test-harness binary that reaches
`PersistProvisionerVersion` writes it and leaves the host wedged.
## Reproduction
Observed on the Debian UTM VM after `task vm:test:unit`, which runs as root and wipes
`/opt/solo/weaver`. Directly:
```bash
sudo yq -i '.state.provisioner.version = "dev"' /opt/solo/weaver/state/state.yaml
sudo solo-provisioner version # aborts as above
```
## Proposed fix
Treat an unparseable installed version the same as absent — both mean "no trustworthy
previous version", and the baseline already exists for exactly that case:
* `ResolveInstalledCLIVersion` returns `BaselineCLIVersion` when `raw` is empty **or**
not valid semver, so pending migrations still run rather than the host being bricked.
* Log the discarded value once at warn level so it is visible.
* If a hard failure is preferred over the baseline, it still must not be a bare
`IllegalState`: attach a reason code and a hint naming the file and field to correct
(`/opt/solo/weaver/state/state.yaml`, `state.provisioner.version`).
Worth checking whether the write side should refuse to persist a placeholder at all,
which would stop the state from being poisoned in the first place.
## Acceptance
- [ ] A `state.yaml` carrying `provisioner.version: dev` no longer aborts CLI startup.
- [ ] Startup migrations still run against the baseline in that case, rather than being
skipped as a fresh install.
- [ ] Unit coverage for `ResolveInstalledCLIVersion` with `""`, `"dev"`, a valid semver,
and an arbitrary non-semver string.
- [ ] A host in this state can be recovered with documented commands, or needs no
recovery at all.
Contributor guide
Research direction
Start at RunStartupMigrations in cmd/cli/commands/common/run.go and follow ResolveInstalledCLIVersion into internal/migration/cli_version_migration.go. Check the existing devVersionPlaceholder in cmd/cli/commands/block/node/daemon_offer.go and add unit coverage for empty, dev, valid semver, and arbitrary non-semver values. Done means startup accepts dev state, runs migrations from the baseline, and logs or documents the recovery behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100