diff --git a/.kiro/specs/dirty-content-fingerprint/tasks.md b/.kiro/specs/dirty-content-fingerprint/tasks.md index 2d7f1356..8a626cae 100644 --- a/.kiro/specs/dirty-content-fingerprint/tasks.md +++ b/.kiro/specs/dirty-content-fingerprint/tasks.md @@ -40,7 +40,7 @@ - _Boundary: worker/main.c_ - _Depends: 3.1_ -- [ ] 4. Core: Go binary --version/-version fingerprint display +- [x] 4. Core: Go binary --version/-version fingerprint display - [x] 4.1 go_build injects the fingerprint via ldflags - `go_build` reads `XCE_DIRTY_FINGERPRINT` (empty-safe) and appends `-X main.DirtyFingerprint=...` to the existing `-ldflags` construction; falls back to plain empty string (deliberately NOT `"unknown"`, unlike `GitCommit`/`BuildTime`), matching `dirty_fingerprint`'s convention everywhere else in this feature; the existing `GitCommit`/`BuildTime` `unknown`-fallback logic is completely untouched - Observable confirmed live (independently re-verified by review, real scratch Go builds traced via `bash -x`): fingerprint unset -> `-X main.DirtyFingerprint=` empty, binary confirms empty (not fabricated); fingerprint set -> flag carries the exact value; all three vars unset -> `GitCommit`/`BuildTime` still fall back to `unknown`, `DirtyFingerprint` stays empty, the two fallback conventions coexist without interference @@ -70,9 +70,9 @@ - _Boundary: apiserver/cmd/main.go_ - _Depends: 4.1_ -- [ ] 4.5 (P) xcecli: fingerprint in --version - - `DirtyFingerprint` var in `main.go`; the version string passed into `router.Execute(cm, version)` (which sets `rootCmd.Version`) applies the same strip-and-append rule - - Observable confirmed live: on a dirty build, `xcecli --version` shows `-` and exits 0; a clean build shows the commit id alone, unchanged from the original feature; a dirty build where fingerprint computation failed shows `-dirty`, unchanged from the original feature +- [x] 4.5 (P) xcecli: fingerprint in --version + - `DirtyFingerprint` var + `composeVersionDisplay()` helper, a verbatim logical duplicate of apiserver's just-approved implementation (task 4.4); applied at the sole consumption point (the version string fed into `router.Execute(cm, version)`); `router.go` confirmed untouched (independently re-verified by the parent controller after the review, not just the reviewer, given a mid-review incident where the reviewer's own tooling briefly discarded the uncommitted diff before catching and byte-for-byte restoring it) + - Observable confirmed live (independently re-verified by review, real builds across all 3 scenarios + full xcecli module test suite): `--version`/`-v` shows `-` on dirty+fingerprint, `` alone on clean, `-dirty` unchanged on dirty+degraded-fingerprint; all exit 0; no regressions in any other xcecli package - _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 4.1_ - _Boundary: xcecli/cmd/main.go_ - _Depends: 4.1_ diff --git a/src/frontend/xcecli/cmd/main.go b/src/frontend/xcecli/cmd/main.go index 03016459..c44d11c5 100755 --- a/src/frontend/xcecli/cmd/main.go +++ b/src/frontend/xcecli/cmd/main.go @@ -44,11 +44,42 @@ import ( // string, matching this feature's unknown-fallback requirement (see // xce_version_field() in src/lib/utils.c for the equivalent C-side // convention). +// +// DirtyFingerprint is injected the same way (`-X main.DirtyFingerprint=...`, +// see src/tools/go_build task 4.1) but its default is a deliberately EMPTY +// string, not "unknown" -- matching dirty_fingerprint's convention +// everywhere else in the dirty-content-fingerprint feature (empty means +// "no fingerprint available", distinct from GitCommit/BuildTime's +// "unknown" sentinel for "not injected at all"). var ( - GitCommit = "unknown" - BuildTime = "unknown" + GitCommit = "unknown" + BuildTime = "unknown" + DirtyFingerprint = "" ) +// composeVersionDisplay applies the dirty-content-fingerprint feature's +// strip-and-append rule to gitCommit: if dirtyFingerprint is non-empty, a +// trailing "-dirty" suffix on gitCommit is stripped (an anchored suffix +// check via strings.TrimSuffix, not a blind substring replace) and +// "-" is appended in its place; otherwise gitCommit is +// returned exactly as-is (already correctly showing "-dirty" or not, per +// the original build-time-versioning feature). Mirrors +// xce_compose_version_ex() in src/lib/utils.c (task 3.1) and apiserver's +// own composeVersionDisplay (task 4.4), the equivalent implementations +// already approved for the other version-displaying components. +// +// xcecli has no startup log to protect (already established as out of +// scope by the original build-time-versioning feature, see that feature's +// own task 4.5 commit fb09afe), so this result feeds the ONLY consumption +// point below: the version string passed into router.Execute(cm, version), +// which sets rootCmd.Version for Cobra's native --version/-v handling. +func composeVersionDisplay(gitCommit, dirtyFingerprint string) string { + if dirtyFingerprint == "" { + return gitCommit + } + return strings.TrimSuffix(gitCommit, "-dirty") + "-" + dirtyFingerprint +} + func main() { // init logger @@ -72,7 +103,7 @@ func main() { } // execute the command - version := fmt.Sprintf("%s built %s", GitCommit, BuildTime) + version := fmt.Sprintf("%s built %s", composeVersionDisplay(GitCommit, DirtyFingerprint), BuildTime) if err := cmd.Execute(configManager, version); err != nil { fmt.Println(strings.ToLower(err.Error())) os.Exit(1) diff --git a/src/frontend/xcecli/cmd/main_test.go b/src/frontend/xcecli/cmd/main_test.go new file mode 100644 index 00000000..8a07a570 --- /dev/null +++ b/src/frontend/xcecli/cmd/main_test.go @@ -0,0 +1,66 @@ +package main + +import "testing" + +// dirty-content-fingerprint task 4.5: composeVersionDisplay implements the +// --version-only strip-and-append rule mirroring xce_compose_version_ex() +// in src/lib/utils.c (task 3.1) and apiserver's own composeVersionDisplay +// (task 4.4). xcecli has no startup log to protect (already established as +// out of scope by the original build-time-versioning feature), so the only +// consumption point is the version string passed into router.Execute(cm, +// version), which Cobra uses to populate rootCmd.Version for --version/-v. +func TestComposeVersionDisplay(t *testing.T) { + tests := []struct { + name string + gitCommit string + dirtyFingerprint string + want string + }{ + { + name: "dirty commit with fingerprint strips -dirty and appends fingerprint", + gitCommit: "abc1234-dirty", + dirtyFingerprint: "deadbeef", + want: "abc1234-deadbeef", + }, + { + name: "clean commit with no fingerprint is unchanged", + gitCommit: "abc1234", + dirtyFingerprint: "", + want: "abc1234", + }, + { + name: "dirty commit with no fingerprint (degraded) is unchanged, -dirty preserved", + gitCommit: "abc1234-dirty", + dirtyFingerprint: "", + want: "abc1234-dirty", + }, + { + name: "unknown commit with no fingerprint is unchanged", + gitCommit: "unknown", + dirtyFingerprint: "", + want: "unknown", + }, + { + name: "clean commit with a fingerprint still appends (nothing to strip)", + gitCommit: "abc1234", + dirtyFingerprint: "cafef00d", + want: "abc1234-cafef00d", + }, + { + name: "suffix check is anchored, not a blind substring removal", + gitCommit: "abc1234-dirty-not-at-end-dirty", + dirtyFingerprint: "cafef00d", + want: "abc1234-dirty-not-at-end-cafef00d", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := composeVersionDisplay(tt.gitCommit, tt.dirtyFingerprint) + if got != tt.want { + t.Errorf("composeVersionDisplay(%q, %q) = %q, want %q", + tt.gitCommit, tt.dirtyFingerprint, got, tt.want) + } + }) + } +}