Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/wfctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,22 @@ var version = buildVersion()

func buildVersion() string {
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" {
return info.Main.Version
return cleanBuildVersion(info.Main.Version)
}
return "dev"
}

// cleanBuildVersion strips the +dirty suffix that the Go toolchain appends when
// the working tree has uncommitted changes. The marker reflects the build-time
// VCS state of the wfctl binary itself, not a meaningful version difference.
// Pseudo-version strings with +dirty (e.g. v0.22.8-20260510180701-a851625d3bf0+dirty)
// are also not valid Go module pseudo-versions, so downstream callers like
// CanonicalEvidenceEngineVersion would silently fall back to "v0.0.0". Stripping
// the suffix here lets the real commit-bound pseudo-version propagate.
func cleanBuildVersion(raw string) string {
return strings.TrimSuffix(raw, "+dirty")
}

// isHelpRequested reports whether the error originated from the user
// requesting help (--help / -h). flag.ErrHelp propagates through the
// pipeline engine as a step failure; catching it here lets us exit 0
Expand Down
38 changes: 38 additions & 0 deletions cmd/wfctl/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,44 @@ func TestHelpFlagDoesNotLeakEngineError(t *testing.T) {
}
}

func TestBuildVersionStripsDirtyMarker(t *testing.T) {
// cleanBuildVersion must strip +dirty from both release tags and pseudo-versions.
for _, tc := range []struct {
in string
want string
}{
{
in: "v0.22.8-0.20260510180701-a851625d3bf0+dirty",
want: "v0.22.8-0.20260510180701-a851625d3bf0",
},
{
in: "v0.51.2+dirty",
want: "v0.51.2",
},
{
in: "v0.51.2",
want: "v0.51.2",
},
{
in: "v0.22.8-0.20260510180701-a851625d3bf0",
want: "v0.22.8-0.20260510180701-a851625d3bf0",
},
} {
t.Run(tc.in, func(t *testing.T) {
got := cleanBuildVersion(tc.in)
if got != tc.want {
t.Fatalf("cleanBuildVersion(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}

// buildVersion() itself must never return a value ending in +dirty.
v := buildVersion()
if strings.HasSuffix(v, "+dirty") {
t.Fatalf("buildVersion() = %q, must not end in +dirty", v)
}
}

func writeTestConfig(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
Expand Down
1 change: 1 addition & 0 deletions cmd/wfctl/plugin_compat_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ type PluginCompatibilityEvidence struct {
GeneratedBy string `json:"generatedBy,omitempty"`
StdoutTail string `json:"stdoutTail,omitempty"`
StderrTail string `json:"stderrTail,omitempty"`
FailureReason string `json:"failureReason,omitempty"`
}

func NormalizePluginVersionIndex(index *PluginVersionIndex, defaultPlugin string) (*PluginVersionIndex, error) {
Expand Down
1 change: 1 addition & 0 deletions cmd/wfctl/plugin_conformance.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ func runPluginConformanceCheck(opts pluginConformanceOptions) (PluginCompatibili
}
if err != nil {
ev.Status = PluginCompatibilityStatusFail
ev.FailureReason = err.Error()
if normalized, normErr := ValidateCompatibilityEvidence(ev); normErr == nil {
ev = normalized
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/wfctl/plugin_conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ func TestPluginConformanceLocalJSONPass(t *testing.T) {
if !strings.Contains(ev.StderrTail, "iac-pass stderr marker") {
t.Fatalf("stderr tail missing plugin output: %#v", ev)
}
// Passing evidence must not include a failureReason.
if ev.FailureReason != "" {
t.Fatalf("passing evidence should not have failureReason, got %q", ev.FailureReason)
}
// WfctlVersion must never end in +dirty.
if strings.HasSuffix(ev.WfctlVersion, "+dirty") {
t.Fatalf("wfctlVersion %q must not contain +dirty marker", ev.WfctlVersion)
}
}

func TestPluginConformanceBuildsRequestedPackage(t *testing.T) {
Expand Down Expand Up @@ -301,6 +309,14 @@ func TestPluginConformanceNoTypedIaCServiceFails(t *testing.T) {
if ev.EvidenceDigest == "" {
t.Fatalf("failure evidence missing digest: %#v", ev)
}
// Failure evidence must include a human-readable reason so maintainers
// can diagnose the failure without local reproduction.
if ev.FailureReason == "" {
t.Fatalf("failure evidence missing failureReason: %#v", ev)
}
if !strings.Contains(ev.FailureReason, "typed") && !strings.Contains(ev.FailureReason, "IaC") && !strings.Contains(ev.FailureReason, "legacy") {
t.Fatalf("failureReason = %q, want typed-IaC context", ev.FailureReason)
}
}

func TestPluginConformanceTextFormat(t *testing.T) {
Expand Down
Loading