Skip to content

Check client / agent versions over RPC#353

Merged
jenstopp merged 2 commits into
mainfrom
feat/addRPCVersion
Jul 10, 2026
Merged

Check client / agent versions over RPC#353
jenstopp merged 2 commits into
mainfrom
feat/addRPCVersion

Conversation

@llogen

@llogen llogen commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Verify dutctl client ↔ agent version compatibility

The dutagent now bakes its version from the git tag and advertises it on every RPC response. Clients compare it against their own version and react to the difference, so an incompatible pairing is caught immediately instead of failing later with a confusing protocol error.

Changes

  • Version baked from git. Release, CI, and task builds inject the tag via -ldflags "-X .../buildinfo.buildVersion=<tag>". buildinfo.Semver() resolves it in order: injected build version → the module version from debug.ReadBuildInfo() (so a plain go build still gets a real, git-derived version) → the release-please constant as a last-resort fallback. dutagent -v now reflects this.
  • Always-on advertisement. The new pkg/versioncheck adds a connect server interceptor that stamps the agent's version onto the X-Dutctl-Version header of every RPC (unary and the streaming Run); the dutagent registers it.
  • Client-side check. A connect client interceptor reads that header and compares it against the client's own version, once per connection; the dutctl CLI registers it.
  • Removed the dedicated Version RPC — superseded by the always-on header (dropped from the proto, generated code, handler, and tests).

Compatibility policy

  • Major differs → error; the call fails with FailedPrecondition
  • Minor differs → warning
  • Pre-release tag differs only (e.g. 1.0.0-alpha.1 vs 1.0.0-alpha.5) → warning
  • Unparsable / dev / untagged version → warning (never errors)
  • Patch differs only, or identical → silent

The check runs agent→client: the agent advertises, the client enforces. The interceptors are exported so other dutctl clients can adopt them.

Testing

  • Unit tests for the classifier plus an in-process connect round-trip covering the header, minor→warn, and major→error.
  • Verified end-to-end with an agent and clients built at different baked versions: patch → silent, minor → warning, major → failed call.
  • go test ./..., go vet ./..., and golangci-lint run all pass.

@llogen llogen force-pushed the feat/addRPCVersion branch 5 times, most recently from 8b2120c to faac102 Compare July 3, 2026 08:04

@jenstopp jenstopp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the Interceptor mechanic and using the headers. Just the right tool for that purpose.

Which side should run the check?
The hard, authoritative check belongs to the agent. The agent is entity that must guard its own contract, and it's the only side that can actually reject. A client can decline to proceed, but only the agent can refuse to execute. So:

Generally, both, the client and the agent should include their version in the header

Agent (enforcement): The interceptor reads the client version from the request header and, on incompatibility, returns, e.g., connect.CodeFailedPrecondition together with helpful message before any module runs.

Client (advisory only): Read the agent version off the response header and, on mismatch, print a friendly warning ("agent is newer, please upgrade the client"). And maybe extended logic before v1.

Also check if the dutserver code needs to be adapted to reflect that behavior

Comment thread .github/workflows/release.yml
Comment thread internal/buildinfo/buildinfo.go
Comment thread internal/buildinfo/buildinfo.go
Comment thread internal/buildinfo/version.go
Comment thread pkg/versioncheck/versioncheck.go Outdated
Comment thread pkg/versioncheck/interceptor.go Outdated
Comment thread pkg/versioncheck/interceptor.go Outdated
Comment thread pkg/versioncheck/interceptor.go Outdated
@jenstopp jenstopp changed the title feat: dutagent reports its version over RPC Check client / agent versions over RPC Jul 3, 2026
@llogen llogen force-pushed the feat/addRPCVersion branch 3 times, most recently from db3db78 to 065e172 Compare July 9, 2026 07:52
The dutagent build version is baked from git tags via -ldflags
and advertised on every RPC response header. Clients compare it
against their own version: minor mismatch warns, major errors,
pre-release/dev diffs warn. Replaces the explicit Version RPC.

Signed-off-by: llogen <christoph.lange@blindspot.software>
@llogen llogen force-pushed the feat/addRPCVersion branch from 065e172 to 7b46f38 Compare July 9, 2026 09:51
jenstopp
jenstopp previously approved these changes Jul 9, 2026
The ireturn linter's verdict on Broker.Start (which returns
module.Session) is nondeterministic across golangci-lint builds and Go
toolchains: on the official v2.6.1 binary used in CI it does not flag the
return, so the //nolint:ireturn directive is reported as unused by
nolintlint and fails the Lint job — while stricter local builds still
flag it, so simply removing the directive would break local linting.

Add Session to the ireturn allow list (matching the existing convention
for intentionally-returned interfaces such as Module and Interceptor) and
drop the now-redundant nolint directive. The result is deterministic:
ireturn never flags module.Session, so no suppression is needed.

Signed-off-by: Jens Topp <jens.topp@blindspot.software>
@jenstopp jenstopp merged commit 69bb479 into main Jul 10, 2026
15 checks passed
@jenstopp jenstopp deleted the feat/addRPCVersion branch July 10, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants