diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 67fc3c1a197..a9ea6973d5f 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -2353,14 +2353,14 @@ jobs: } >> "$GITHUB_STEP_SUMMARY" - name: Upload prompt if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: prompt.txt path: /tmp/gh-aw/aw-prompts/prompt.txt if-no-files-found: warn - name: Upload agentic run info if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: aw_info.json path: /tmp/gh-aw/aw_info.json @@ -2507,7 +2507,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Safe Outputs if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: safe_output.jsonl path: ${{ env.GH_AW_SAFE_OUTPUTS }} @@ -3747,13 +3747,13 @@ jobs: await main(); - name: Upload sanitized agent output if: always() && env.GH_AW_AGENT_OUTPUT - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: agent_output.json path: ${{ env.GH_AW_AGENT_OUTPUT }} if-no-files-found: warn - name: Upload engine output files - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: agent_outputs path: | @@ -3762,7 +3762,7 @@ jobs: if-no-files-found: ignore - name: Upload MCP logs if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: mcp-logs path: /tmp/gh-aw/mcp-logs/ @@ -5247,7 +5247,7 @@ jobs: - name: Upload Firewall Logs if: always() continue-on-error: true - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: firewall-logs-daily-team-status path: /tmp/gh-aw/sandbox/firewall/logs/ @@ -5405,7 +5405,7 @@ jobs: } - name: Upload Agent Stdio if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: agent-stdio.log path: /tmp/gh-aw/agent-stdio.log @@ -5678,7 +5678,7 @@ jobs: echo "Agent Conclusion: $AGENT_CONCLUSION" - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: agent_output.json path: /tmp/gh-aw/safeoutputs/ @@ -6171,20 +6171,20 @@ jobs: steps: - name: Download prompt artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: prompt.txt path: /tmp/gh-aw/threat-detection/ - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: agent_output.json path: /tmp/gh-aw/threat-detection/ - name: Download patch artifact if: needs.agent.outputs.has_patch == 'true' continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: aw.patch path: /tmp/gh-aw/threat-detection/ @@ -6412,7 +6412,7 @@ jobs: } - name: Upload threat detection log if: always() - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: threat-detection.log path: /tmp/gh-aw/threat-detection/detection.log @@ -6483,7 +6483,7 @@ jobs: steps: - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: agent_output.json path: /tmp/gh-aw/safeoutputs/ diff --git a/pkg/cli/semver.go b/pkg/cli/semver.go index 7362e419734..df3f6b59ce4 100644 --- a/pkg/cli/semver.go +++ b/pkg/cli/semver.go @@ -56,6 +56,19 @@ func parseVersion(v string) *semanticVersion { return ver } +// isPreciseVersion returns true if this version has explicit minor and patch components +// For example, "v6.0.0" is precise, but "v6" is not +func (v *semanticVersion) isPreciseVersion() bool { + // Check if raw version has at least two dots (major.minor.patch format) + // or at least one dot for major.minor format + // "v6" -> not precise + // "v6.0" -> somewhat precise (has minor) + // "v6.0.0" -> precise (has minor and patch) + versionPart := strings.TrimPrefix(v.raw, "v") + dotCount := strings.Count(versionPart, ".") + return dotCount >= 2 // Require at least major.minor.patch +} + // isNewer returns true if this version is newer than the other func (v *semanticVersion) isNewer(other *semanticVersion) bool { if v.major != other.major { diff --git a/pkg/cli/semver_precise_test.go b/pkg/cli/semver_precise_test.go new file mode 100644 index 00000000000..bef0fb852e7 --- /dev/null +++ b/pkg/cli/semver_precise_test.go @@ -0,0 +1,101 @@ +package cli + +import ( + "testing" +) + +func TestIsPreciseVersion(t *testing.T) { + tests := []struct { + name string + version string + expected bool + }{ + { + name: "major only - not precise", + version: "v6", + expected: false, + }, + { + name: "major.minor - not precise", + version: "v6.0", + expected: false, + }, + { + name: "major.minor.patch - precise", + version: "v6.0.0", + expected: true, + }, + { + name: "major.minor.patch non-zero - precise", + version: "v6.0.1", + expected: true, + }, + { + name: "full version - precise", + version: "v6.1.2", + expected: true, + }, + { + name: "without v prefix - precise", + version: "6.0.0", + expected: true, + }, + { + name: "single digit major - not precise", + version: "v1", + expected: false, + }, + { + name: "three component version - precise", + version: "v1.2.3", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := parseVersion(tt.version) + if v == nil { + t.Fatalf("Failed to parse version: %s", tt.version) + } + + result := v.isPreciseVersion() + if result != tt.expected { + t.Errorf("isPreciseVersion() for %q = %v, want %v", tt.version, result, tt.expected) + } + }) + } +} + +func TestPreciseVersionPreference(t *testing.T) { + // Test that when comparing equal versions, precise versions are preferred + v6 := parseVersion("v6") + v600 := parseVersion("v6.0.0") + + if v6 == nil || v600 == nil { + t.Fatal("Failed to parse versions") + } + + // They should parse to the same major.minor.patch + if v6.major != v600.major || v6.minor != v600.minor || v6.patch != v600.patch { + t.Errorf("v6 and v6.0.0 should parse to same major.minor.patch, got v6=%+v, v600=%+v", v6, v600) + } + + // v6.0.0 should be precise, v6 should not + if !v600.isPreciseVersion() { + t.Error("v6.0.0 should be precise") + } + + if v6.isPreciseVersion() { + t.Error("v6 should not be precise") + } + + // Neither should be considered "newer" than the other + if v6.isNewer(v600) { + t.Error("v6 should not be newer than v6.0.0") + } + + if v600.isNewer(v6) { + t.Error("v6.0.0 should not be newer than v6") + } +} diff --git a/pkg/cli/update_actions.go b/pkg/cli/update_actions.go index 94c2d34687e..d11ac71e10e 100644 --- a/pkg/cli/update_actions.go +++ b/pkg/cli/update_actions.go @@ -211,6 +211,15 @@ func getLatestActionRelease(repo, currentVersion string, allowMajor, verbose boo if latestCompatibleVersion == nil || releaseVer.isNewer(latestCompatibleVersion) { latestCompatible = release latestCompatibleVersion = releaseVer + } else if !releaseVer.isNewer(latestCompatibleVersion) && + releaseVer.major == latestCompatibleVersion.major && + releaseVer.minor == latestCompatibleVersion.minor && + releaseVer.patch == latestCompatibleVersion.patch { + // If versions are equal, prefer the more precise one (e.g., "v6.0.0" over "v6") + if releaseVer.isPreciseVersion() && !latestCompatibleVersion.isPreciseVersion() { + latestCompatible = release + latestCompatibleVersion = releaseVer + } } } @@ -301,6 +310,15 @@ func getLatestActionReleaseViaGit(repo, currentVersion string, allowMajor, verbo if latestCompatibleVersion == nil || releaseVer.isNewer(latestCompatibleVersion) { latestCompatible = release latestCompatibleVersion = releaseVer + } else if !releaseVer.isNewer(latestCompatibleVersion) && + releaseVer.major == latestCompatibleVersion.major && + releaseVer.minor == latestCompatibleVersion.minor && + releaseVer.patch == latestCompatibleVersion.patch { + // If versions are equal, prefer the more precise one (e.g., "v6.0.0" over "v6") + if releaseVer.isPreciseVersion() && !latestCompatibleVersion.isPreciseVersion() { + latestCompatible = release + latestCompatibleVersion = releaseVer + } } }