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
24 changes: 18 additions & 6 deletions pkg/workflow/cache_integrity.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import (
"fmt"
"sort"
"strings"

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

var cacheIntegrityLog = logger.New("workflow:cache_integrity")

// integrityLevelOrder defines integrity levels from highest to lowest.
// Used to determine which branches to merge down from when setting up cache.
var integrityLevelOrder = []string{"merged", "approved", "unapproved", "none"}
Expand All @@ -27,12 +31,15 @@ const noPolicySentinel = "nopolicy"
// - Workflows without policy → sentinel value "nopolicy" (consistent key format)
func computePolicyHash(github *GitHubToolConfig) string {
if github == nil || github.MinIntegrity == "" {
cacheIntegrityLog.Print("No guard policy configured, using nopolicy sentinel")
return noPolicySentinel
}

canonical := buildCanonicalPolicy(github)
hash := sha256.Sum256([]byte(canonical))
return hex.EncodeToString(hash[:])[:8]
result := hex.EncodeToString(hash[:])[:8]
cacheIntegrityLog.Printf("Computed policy hash: %s (min-integrity=%s)", result, github.MinIntegrity)
return result
}

// buildCanonicalPolicy builds the normalized string representation of the allow-only policy.
Expand Down Expand Up @@ -183,16 +190,20 @@ func cacheIntegrityLevel(github *GitHubToolConfig) string {
// memory-unapproved-7e4d9f12-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
// memory-none-nopolicy-session-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
func generateIntegrityAwareCacheKey(cacheID, integrityLevel, policyHash string) string {
var key string
if cacheID == "default" || cacheID == "" {
return fmt.Sprintf(
key = fmt.Sprintf(
"memory-%s-%s-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
integrityLevel, policyHash,
)
} else {
key = fmt.Sprintf(
"memory-%s-%s-%s-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
integrityLevel, policyHash, cacheID,
)
}
return fmt.Sprintf(
"memory-%s-%s-%s-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}",
integrityLevel, policyHash, cacheID,
)
cacheIntegrityLog.Printf("Generated integrity-aware cache key: cacheID=%s, integrityLevel=%s, policyHash=%s", cacheID, integrityLevel, policyHash)
return key
}

// higherIntegrityLevels returns the integrity levels that are higher than the given level,
Expand All @@ -206,5 +217,6 @@ func higherIntegrityLevels(level string) []string {
}
result = append(result, l)
}
cacheIntegrityLog.Printf("Higher integrity levels than %q: %v", level, result)
return result
}
1 change: 1 addition & 0 deletions pkg/workflow/expression_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func (d *DisjunctionNode) Render() string {
return d.RenderMultiline()
}

expressionNodesLog.Printf("Rendering inline disjunction with %d terms", len(d.Terms))
var parts []string
for _, term := range d.Terms {
parts = append(parts, term.Render())
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/mcp_rendering.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func renderStandardJSONMCPConfig(
// The returned function accepts isLast as a parameter and creates a renderer with engine-specific
// options derived from the provided parameters and workflowData at call time.
func buildMCPRendererFactory(workflowData *WorkflowData, format string, includeCopilotFields, inlineArgs bool) func(bool) *MCPConfigRendererUnified {
mcpRenderingLog.Printf("Building MCP renderer factory: format=%s, copilotFields=%t, inlineArgs=%t", format, includeCopilotFields, inlineArgs)
return func(isLast bool) *MCPConfigRendererUnified {
return NewMCPConfigRenderer(MCPRendererOptions{
IncludeCopilotFields: includeCopilotFields,
Expand Down Expand Up @@ -147,6 +148,7 @@ func buildStandardJSONMCPRenderers(
webFetchIncludeTools bool,
renderCustom RenderCustomMCPToolConfigHandler,
) MCPToolRenderers {
mcpRenderingLog.Printf("Building standard JSON MCP renderers: webFetchIncludeTools=%t", webFetchIncludeTools)
return MCPToolRenderers{
RenderGitHub: func(yaml *strings.Builder, githubTool any, isLast bool, workflowData *WorkflowData) {
createRenderer(isLast).RenderGitHubMCP(yaml, githubTool, workflowData)
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/safe_outputs_tools_computation.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package workflow

import "github.com/github/gh-aw/pkg/logger"

var safeOutputsToolsComputationLog = logger.New("workflow:safe_outputs_tools_computation")

// computeEnabledToolNames returns the set of predefined tool names that are enabled
// by the workflow's SafeOutputsConfig. Dynamic tools (dispatch-workflow, custom jobs,
// call-workflow) are excluded because they are generated separately.
func computeEnabledToolNames(data *WorkflowData) map[string]bool {
enabledTools := make(map[string]bool)
if data.SafeOutputs == nil {
safeOutputsToolsComputationLog.Print("No safe outputs configuration, returning empty tool set")
return enabledTools
}

Expand Down Expand Up @@ -126,5 +131,6 @@ func computeEnabledToolNames(data *WorkflowData) map[string]bool {
enabledTools["push_repo_memory"] = true
}

safeOutputsToolsComputationLog.Printf("Computed %d enabled safe output tool names", len(enabledTools))
return enabledTools
}
17 changes: 12 additions & 5 deletions pkg/workflow/validation_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
"github.com/github/gh-aw/pkg/logger"
)

var validationHelpersLog = logger.New("workflow:validation_helpers")

// newValidationLogger creates a standardized logger for a validation domain.
// It follows the naming convention "workflow:<domain>_validation" used across
// all *_validation.go files.
Expand Down Expand Up @@ -81,12 +83,15 @@ func validateIntRange(value, min, max int, fieldName string) error {
func validateMountStringFormat(mount string) (source, dest, mode string, err error) {
parts := strings.Split(mount, ":")
if len(parts) != 3 {
validationHelpersLog.Printf("Invalid mount format: %q (expected 3 colon-separated parts, got %d)", mount, len(parts))
return "", "", "", errors.New("must follow 'source:destination:mode' format with exactly 3 colon-separated parts")
}
mode = parts[2]
if mode != "ro" && mode != "rw" {
validationHelpersLog.Printf("Invalid mount mode: %q in %q (must be 'ro' or 'rw')", mode, mount)
return parts[0], parts[1], parts[2], fmt.Errorf("mode must be 'ro' or 'rw', got %q", mode)
}
validationHelpersLog.Printf("Valid mount: source=%s, dest=%s, mode=%s", parts[0], parts[1], mode)
return parts[0], parts[1], parts[2], nil
}

Expand Down Expand Up @@ -131,18 +136,20 @@ func validateNoDuplicateIDs[T any](items []T, idFunc func(T) string, onDuplicate
// - []any: "on: [push, <triggerName>]"
// - map[string]any: "on:\n <triggerName>: ..."
func containsTrigger(onSection any, triggerName string) bool {
found := false
switch on := onSection.(type) {
case string:
return on == triggerName
found = on == triggerName
case []any:
for _, trigger := range on {
if triggerStr, ok := trigger.(string); ok && triggerStr == triggerName {
return true
found = true
break
}
}
case map[string]any:
_, ok := on[triggerName]
return ok
_, found = on[triggerName]
}
return false
validationHelpersLog.Printf("containsTrigger: trigger=%s, found=%t", triggerName, found)
return found
}