From 36bd2c672b1cf76e3c457b624f1dae260eb4461c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:35:59 +0000 Subject: [PATCH 1/2] Initial plan From a61f29af60255dcc52adecae295de3837f1490a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:50:30 +0000 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20rename=20FormatReference=E2=86=92Fo?= =?UTF-8?q?rmatPinnedActionReference,=20notifyResolutionFailure=E2=86=92re?= =?UTF-8?q?cordPinResolutionFailure=20in=20pkg/actionpins=20(#issue)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0fbb6f6c-294e-4155-95f1-a718cf0e8e5e Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/actionpins/README.md | 2 +- pkg/actionpins/actionpins.go | 25 ++++++++++++++----------- pkg/actionpins/spec_test.go | 14 +++++++------- pkg/workflow/action_pins.go | 4 ++-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/pkg/actionpins/README.md b/pkg/actionpins/README.md index 8df50ea46b3..862629bd640 100644 --- a/pkg/actionpins/README.md +++ b/pkg/actionpins/README.md @@ -31,7 +31,7 @@ Resolution supports two modes: | `GetActionPinsByRepo` | `func(repo string) []ActionPin` | Returns all pins for a repository (version-descending) | | `GetActionPinByRepo` | `func(repo string) (ActionPin, bool)` | Returns the latest pin for a repository | | `GetContainerPin` | `func(image string) (ContainerPin, bool)` | Returns a pinned container image by its original image reference | -| `FormatReference` | `func(repo, sha, version string) string` | Formats a pinned reference (`repo@sha # version`) | +| `FormatPinnedActionReference` | `func(repo, sha, version string) string` | Formats a pinned action reference string (`repo@sha # version`) | | `FormatCacheKey` | `func(repo, version string) string` | Formats a cache key (`repo@version`) | | `ExtractRepo` | `func(uses string) string` | Extracts the repository from a `uses` reference | | `ExtractVersion` | `func(uses string) string` | Extracts the version from a `uses` reference | diff --git a/pkg/actionpins/actionpins.go b/pkg/actionpins/actionpins.go index 94f67cd0cd2..3f8ffa47eb9 100644 --- a/pkg/actionpins/actionpins.go +++ b/pkg/actionpins/actionpins.go @@ -210,12 +210,12 @@ func getLatestActionPinReference(repo string) string { if len(pins) == 0 { return "" } - return FormatReference(repo, pins[0].SHA, pins[0].Version) + return FormatPinnedActionReference(repo, pins[0].SHA, pins[0].Version) } -// FormatReference formats an action reference with repo, SHA, and version comment. +// FormatPinnedActionReference formats a pinned action reference with repo, SHA, and version comment. // Example: "actions/checkout@abc123 # v4.1.0" -func FormatReference(repo, sha, version string) string { +func FormatPinnedActionReference(repo, sha, version string) string { return fmt.Sprintf("%s@%s # %s", repo, sha, version) } @@ -268,7 +268,10 @@ func initWarnings(ctx *PinContext) { } } -func notifyResolutionFailure(ctx *PinContext, actionRepo, version string, errorType ResolutionErrorType) { +// recordPinResolutionFailure silently records an unresolved action-ref pinning event +// to the audit callback (ctx.RecordResolutionFailure), if one is configured. +// If ctx is nil or ctx.RecordResolutionFailure is nil, the function returns early without recording. +func recordPinResolutionFailure(ctx *PinContext, actionRepo, version string, errorType ResolutionErrorType) { if ctx == nil || ctx.RecordResolutionFailure == nil { return } @@ -295,7 +298,7 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro sha, err := ctx.Resolver.ResolveSHA(actionRepo, version) if err == nil && sha != "" { log.Printf("Dynamic resolution succeeded: %s@%s → %s", actionRepo, version, sha) - result := FormatReference(actionRepo, sha, version) + result := FormatPinnedActionReference(actionRepo, sha, version) log.Printf("Returning pinned reference: %s", result) return result, nil } @@ -319,7 +322,7 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro for _, pin := range matchingPins { if pin.Version == version { log.Printf("Exact version match: requested=%s, found=%s", version, pin.Version) - return FormatReference(actionRepo, pin.SHA, pin.Version), nil + return FormatPinnedActionReference(actionRepo, pin.SHA, pin.Version), nil } } @@ -327,11 +330,11 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro for _, pin := range matchingPins { if pin.SHA == version { log.Printf("Exact SHA match: requested=%s, found version=%s", version, pin.Version) - return FormatReference(actionRepo, pin.SHA, pin.Version), nil + return FormatPinnedActionReference(actionRepo, pin.SHA, pin.Version), nil } } log.Printf("SHA %s not found in hardcoded pins, returning as-is", version) - return FormatReference(actionRepo, version, version), nil + return FormatPinnedActionReference(actionRepo, version, version), nil } if !ctx.StrictMode && len(matchingPins) > 0 { @@ -355,13 +358,13 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro } log.Printf("Using version in non-strict mode: %s@%s (requested) → %s@%s (used)", actionRepo, version, actionRepo, selectedPin.Version) - return FormatReference(actionRepo, selectedPin.SHA, version), nil + return FormatPinnedActionReference(actionRepo, selectedPin.SHA, version), nil } } if isAlreadySHA { log.Printf("SHA %s not found in hardcoded pins, returning as-is", version) - return FormatReference(actionRepo, version, version), nil + return FormatPinnedActionReference(actionRepo, version, version), nil } initWarnings(ctx) @@ -370,7 +373,7 @@ func ResolveActionPin(actionRepo, version string, ctx *PinContext) (string, erro if ctx.Resolver != nil { errorType = ResolutionErrorTypeDynamicResolutionFailed } - notifyResolutionFailure(ctx, actionRepo, version, errorType) + recordPinResolutionFailure(ctx, actionRepo, version, errorType) if ctx.EnforcePinned && !ctx.AllowActionRefs { if ctx.Resolver != nil { return "", fmt.Errorf("unable to pin action %s@%s: resolution failed", actionRepo, version) diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index beb5db5a27c..5e64d98bef7 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -12,8 +12,8 @@ import ( "github.com/github/gh-aw/pkg/actionpins" ) -// TestSpec_PublicAPI_FormatReference validates the documented format "repo@sha # version". -func TestSpec_PublicAPI_FormatReference(t *testing.T) { +// TestSpec_PublicAPI_FormatPinnedActionReference validates the documented format "repo@sha # version". +func TestSpec_PublicAPI_FormatPinnedActionReference(t *testing.T) { tests := []struct { name string repo string @@ -39,8 +39,8 @@ func TestSpec_PublicAPI_FormatReference(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := actionpins.FormatReference(tt.repo, tt.sha, tt.version) - assert.Equal(t, tt.expected, result, "FormatReference(%q, %q, %q) should match spec format", tt.repo, tt.sha, tt.version) + result := actionpins.FormatPinnedActionReference(tt.repo, tt.sha, tt.version) + assert.Equal(t, tt.expected, result, "FormatPinnedActionReference(%q, %q, %q) should match spec format", tt.repo, tt.sha, tt.version) }) } } @@ -180,7 +180,7 @@ func TestSpec_PublicAPI_ResolveLatestActionPin(t *testing.T) { require.True(t, ok, "expected latest pin for known repository") result := actionpins.ResolveLatestActionPin(known, nil) - expected := actionpins.FormatReference(known, latestPin.SHA, latestPin.Version) + expected := actionpins.FormatPinnedActionReference(known, latestPin.SHA, latestPin.Version) assert.Equal(t, expected, result, "should resolve latest pinned reference") }) } @@ -199,7 +199,7 @@ func TestSpec_Types_PinContext(t *testing.T) { }) } -// TestSpec_DesignDecision_FormatConsistency validates that FormatReference and FormatCacheKey +// TestSpec_DesignDecision_FormatConsistency validates that FormatPinnedActionReference and FormatCacheKey // produce outputs consistent with the spec: cacheKey = "repo@version", ref = "repo@sha # version". func TestSpec_DesignDecision_FormatConsistency(t *testing.T) { repo := "actions/checkout" @@ -207,7 +207,7 @@ func TestSpec_DesignDecision_FormatConsistency(t *testing.T) { sha := "deadbeef" cacheKey := actionpins.FormatCacheKey(repo, version) - reference := actionpins.FormatReference(repo, sha, version) + reference := actionpins.FormatPinnedActionReference(repo, sha, version) assert.True(t, strings.HasPrefix(cacheKey, repo+"@"), "cache key should be repo@version") assert.True(t, strings.HasPrefix(reference, repo+"@"), "reference should start with repo@sha") diff --git a/pkg/workflow/action_pins.go b/pkg/workflow/action_pins.go index 0d879ef0add..d8aa9ec03da 100644 --- a/pkg/workflow/action_pins.go +++ b/pkg/workflow/action_pins.go @@ -28,7 +28,7 @@ type ActionPinsData = actionpins.ActionPinsData // formatActionReference formats an action reference with repo, SHA, and version. func formatActionReference(repo, sha, version string) string { - return actionpins.FormatReference(repo, sha, version) + return actionpins.FormatPinnedActionReference(repo, sha, version) } // formatActionCacheKey generates a cache key for action resolution. @@ -54,7 +54,7 @@ func getActionPin(repo string) string { actionPinsLog.Printf("No embedded pins found for repo: %s", repo) return "" } - return actionpins.FormatReference(repo, pins[0].SHA, pins[0].Version) + return actionpins.FormatPinnedActionReference(repo, pins[0].SHA, pins[0].Version) } // getCachedActionPinFromResolver returns the pinned action reference for repo,