Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions .github/workflows/smoke-project.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 0 additions & 66 deletions pkg/workflow/action_sha_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"regexp"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/logger"
"github.com/goccy/go-yaml"
)
Expand Down Expand Up @@ -144,68 +143,3 @@ func CheckActionSHAUpdates(actions []ActionUsage, resolver *ActionResolver) []Ac

return results
}

// ValidateActionSHAsInLockFile validates action SHAs in a lock file and emits warnings
func ValidateActionSHAsInLockFile(lockFilePath string, cache *ActionCache, verbose bool) error {
actionSHACheckerLog.Printf("Validating action SHAs in: %s", lockFilePath)

// Extract actions from lock file
actions, err := ExtractActionsFromLockFile(lockFilePath)
if err != nil {
return fmt.Errorf("failed to extract actions: %w", err)
}

if len(actions) == 0 {
actionSHACheckerLog.Print("No pinned actions found in lock file")
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No pinned actions to validate"))
}
return nil
}

// Create resolver for checking latest SHAs
resolver := NewActionResolver(cache)

// Check for updates
checks := CheckActionSHAUpdates(actions, resolver)

// Count and report updates
updateCount := 0
for _, check := range checks {
if check.NeedsUpdate {
updateCount++
// Emit warning (FormatWarningMessage adds the warning emoji)
warningMsg := fmt.Sprintf("%s@%s has a newer SHA available: %s → %s",
check.Action.Repo,
check.Action.Version,
check.Action.SHA[:7],
check.LatestSHA[:7])
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warningMsg))

// Show full SHA in verbose mode
if verbose {
fmt.Fprintf(os.Stderr, " Current: %s\n", check.Action.SHA)
fmt.Fprintf(os.Stderr, " Latest: %s\n", check.LatestSHA)
}
}
}

if updateCount > 0 {
actionSHACheckerLog.Printf("Found %d actions that need updating", updateCount)
// Save the cache with updated SHAs so the next compilation will use them
if err := cache.Save(); err != nil {
actionSHACheckerLog.Printf("Warning: failed to save action cache: %v", err)
} else {
actionSHACheckerLog.Print("Saved updated action cache")
}
// Provide suggestion to fix the issue
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To apply updated action SHAs, recompile with: gh aw compile"))
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d action(s) with available updates", updateCount)))
}
} else {
actionSHACheckerLog.Print("All actions are up to date")
}

return nil
}
21 changes: 0 additions & 21 deletions pkg/workflow/engine_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,24 +360,3 @@ func FilterEnvForSecrets(env map[string]string, allowedNamesAndKeys []string) ma
engineHelpersLog.Printf("Filtered environment variables: kept=%d, removed=%d", len(filtered), secretsRemoved)
return filtered
}

// EngineHasValidateSecretStep checks if the engine provides a validate-secret step.
// This is used to determine whether the secret_verification_result job output should be added.
//
// The validate-secret step is provided by engines that override GetSecretValidationStep():
// - Copilot engine: Adds step unless copilot-requests feature is enabled or custom command is set
// - Claude engine: Adds step unless custom command is set
// - Codex engine: Adds step unless custom command is set
// - Gemini engine: Adds step unless custom command is set
// - Custom engine: Never adds this step (uses BaseEngine default which returns empty)
//
// Parameters:
// - engine: The agentic engine to check
// - data: The workflow data (needed for GetSecretValidationStep)
//
// Returns:
// - bool: true if the engine provides a validate-secret step, false otherwise
func EngineHasValidateSecretStep(engine CodingAgentEngine, data *WorkflowData) bool {
step := engine.GetSecretValidationStep(data)
return len(step) > 0
}
21 changes: 21 additions & 0 deletions pkg/workflow/engine_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,24 @@ func (c *Compiler) validatePiEngineRequirements(workflowData *WorkflowData) erro
engineValidationLog.Print("Pi engine requirements satisfied: gh-proxy and cli-proxy are enabled")
return nil
}

// EngineHasValidateSecretStep checks if the engine provides a validate-secret step.
// This is used to determine whether the secret_verification_result job output should be added.
//
// The validate-secret step is provided by engines that override GetSecretValidationStep():
// - Copilot engine: Adds step unless copilot-requests feature is enabled or custom command is set
// - Claude engine: Adds step unless custom command is set
// - Codex engine: Adds step unless custom command is set
// - Gemini engine: Adds step unless custom command is set
// - Custom engine: Never adds this step (uses BaseEngine default which returns empty)
//
// Parameters:
// - engine: The agentic engine to check
// - data: The workflow data (needed for GetSecretValidationStep)
//
// Returns:
// - bool: true if the engine provides a validate-secret step, false otherwise
func EngineHasValidateSecretStep(engine CodingAgentEngine, data *WorkflowData) bool {
step := engine.GetSecretValidationStep(data)
return len(step) > 0
}
68 changes: 68 additions & 0 deletions pkg/workflow/lock_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ package workflow

import (
"fmt"
"os"
"strings"

"github.com/github/gh-aw/pkg/console"
)

// ValidateLockSchemaCompatibility validates that a lock file's schema is compatible.
Expand Down Expand Up @@ -57,3 +60,68 @@ func ValidateLockSchemaCompatibility(content string, lockFilePath string) error
lockSchemaLog.Printf("Lock file schema validated: %s (version=%s)", lockFilePath, metadata.SchemaVersion)
return nil
}

// ValidateActionSHAsInLockFile validates action SHAs in a lock file and emits warnings
func ValidateActionSHAsInLockFile(lockFilePath string, cache *ActionCache, verbose bool) error {
actionSHACheckerLog.Printf("Validating action SHAs in: %s", lockFilePath)
Comment on lines +69 to +71

// Extract actions from lock file
actions, err := ExtractActionsFromLockFile(lockFilePath)
if err != nil {
return fmt.Errorf("failed to extract actions: %w", err)
}

if len(actions) == 0 {
actionSHACheckerLog.Print("No pinned actions found in lock file")
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No pinned actions to validate"))
}
return nil
}

// Create resolver for checking latest SHAs
resolver := NewActionResolver(cache)

// Check for updates
checks := CheckActionSHAUpdates(actions, resolver)

// Count and report updates
updateCount := 0
for _, check := range checks {
if check.NeedsUpdate {
updateCount++
// Emit warning (FormatWarningMessage adds the warning emoji)
warningMsg := fmt.Sprintf("%s@%s has a newer SHA available: %s → %s",
check.Action.Repo,
check.Action.Version,
check.Action.SHA[:7],
check.LatestSHA[:7])
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warningMsg))

// Show full SHA in verbose mode
if verbose {
fmt.Fprintf(os.Stderr, " Current: %s\n", check.Action.SHA)
fmt.Fprintf(os.Stderr, " Latest: %s\n", check.LatestSHA)
}
}
}

if updateCount > 0 {
actionSHACheckerLog.Printf("Found %d actions that need updating", updateCount)
// Save the cache with updated SHAs so the next compilation will use them
if err := cache.Save(); err != nil {
actionSHACheckerLog.Printf("Warning: failed to save action cache: %v", err)
} else {
actionSHACheckerLog.Print("Saved updated action cache")
}
// Provide suggestion to fix the issue
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To apply updated action SHAs, recompile with: gh aw compile"))
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d action(s) with available updates", updateCount)))
}
} else {
actionSHACheckerLog.Print("All actions are up to date")
}

return nil
}
48 changes: 48 additions & 0 deletions pkg/workflow/model_alias_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,63 @@ package workflow

import (
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"

"github.com/github/gh-aw/pkg/console"
)

var modelAliasValidationLog = newValidationLogger("model_alias")

// ─── Known-parameter validation ───────────────────────────────────────────────

// ValidateEffortParam validates the "effort" parameter value (V-MAF-002).
// Allowed values: low, medium, high.
func ValidateEffortParam(value string) error {
switch value {
case "low", "medium", "high":
return nil
default:
return fmt.Errorf("model parameter 'effort': value %q is not valid; allowed values are: low, medium, high (V-MAF-002)", value)
}
}

// ValidateTemperatureParam validates the "temperature" parameter value (V-MAF-003).
// Must be a finite decimal float in [0.0, 2.0].
func ValidateTemperatureParam(value string) error {
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("model parameter 'temperature': value %q cannot be parsed as a decimal float (V-MAF-003)", value)
}
if math.IsNaN(f) || math.IsInf(f, 0) {
return fmt.Errorf("model parameter 'temperature': value %q is not a finite number (V-MAF-003)", value)
}
if f < 0.0 || f > 2.0 {
return fmt.Errorf("model parameter 'temperature': value %q is out of range; must be in [0.0, 2.0] (V-MAF-003)", value)
}
return nil
}

// ValidateKnownParams validates the known parameters in a parsed identifier.
// Unknown parameters are tolerated (V-MAF-011 emits a warning, not an error).
// Returns an error if a known parameter has an invalid value.
func ValidateKnownParams(params map[string]string) error {
if v, ok := params[modelParamEffort]; ok {
if err := ValidateEffortParam(v); err != nil {
return err
}
}
if v, ok := params[modelParamTemperature]; ok {
if err := ValidateTemperatureParam(v); err != nil {
return err
}
}
return nil
}

// validateModelAliasMap is the main entry point for compile-time model-alias validation.
// It validates:
// - The user-supplied alias map entries in frontmatterModels (V-MAF-001..006, V-MAF-011).
Expand Down
48 changes: 0 additions & 48 deletions pkg/workflow/model_identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ package workflow
import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"

"github.com/github/gh-aw/pkg/logger"
Expand Down Expand Up @@ -354,52 +352,6 @@ func validateParamValue(value string) error {
return nil
}

// ─── Known-parameter validation ───────────────────────────────────────────────

// ValidateEffortParam validates the "effort" parameter value (V-MAF-002).
// Allowed values: low, medium, high.
func ValidateEffortParam(value string) error {
switch value {
case "low", "medium", "high":
return nil
default:
return fmt.Errorf("model parameter 'effort': value %q is not valid; allowed values are: low, medium, high (V-MAF-002)", value)
}
}

// ValidateTemperatureParam validates the "temperature" parameter value (V-MAF-003).
// Must be a finite decimal float in [0.0, 2.0].
func ValidateTemperatureParam(value string) error {
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("model parameter 'temperature': value %q cannot be parsed as a decimal float (V-MAF-003)", value)
}
if math.IsNaN(f) || math.IsInf(f, 0) {
return fmt.Errorf("model parameter 'temperature': value %q is not a finite number (V-MAF-003)", value)
}
if f < 0.0 || f > 2.0 {
return fmt.Errorf("model parameter 'temperature': value %q is out of range; must be in [0.0, 2.0] (V-MAF-003)", value)
}
return nil
}

// ValidateKnownParams validates the known parameters in a parsed identifier.
// Unknown parameters are tolerated (V-MAF-011 emits a warning, not an error).
// Returns an error if a known parameter has an invalid value.
func ValidateKnownParams(params map[string]string) error {
if v, ok := params[modelParamEffort]; ok {
if err := ValidateEffortParam(v); err != nil {
return err
}
}
if v, ok := params[modelParamTemperature]; ok {
if err := ValidateTemperatureParam(v); err != nil {
return err
}
}
return nil
}

// UnrecognizedParams returns the list of parameter keys in params that are not
// defined in Section 6 (i.e., not effort or temperature).
// Reserved future parameters (Section 6.3) are returned here as well since they
Expand Down
File renamed without changes.
Loading