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
2 changes: 2 additions & 0 deletions pkg/workflow/compiler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@ type WorkflowData struct {
ServicePortExpressions string // comma-separated ${{ job.services['<id>'].ports['<port>'] }} expressions for AWF --allow-host-service-ports
RunInstallScripts bool // true when run-install-scripts: true is set (globally or per node runtime); disables --ignore-scripts on generated npm install steps
CachedPermissions *Permissions // cached parsed Permissions object (for performance optimization); populated by applyDefaults after all permission mutations
CachedPermissionScopeNamesErr error // cached result of ValidatePermissionScopeNames(Permissions); nil = valid; populated by applyDefaults
CachedPermissionScopeNamesSet bool // true once CachedPermissionScopeNamesErr has been populated; distinguishes "valid (nil)" from "not yet computed"
ConcurrencyGroupExpr string // cached concurrency group expression extracted from Concurrency YAML (for performance optimization); populated by applyDefaults
CachedConcurrencyGroupExprErr error // cached result of validateConcurrencyGroupExpression(ConcurrencyGroupExpr); nil = valid; populated by applyDefaults
Experiments map[string][]string // A/B testing experiments: maps experiment name to variant list (from frontmatter)
Expand Down
21 changes: 21 additions & 0 deletions pkg/workflow/compiler_validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package workflow

import (
"errors"
"path/filepath"
"testing"

Expand Down Expand Up @@ -310,6 +311,26 @@ func TestValidateToolConfiguration(t *testing.T) {
}
}

func TestValidatePermissions_UsesCachedPermissionScopeValidation(t *testing.T) {
tmpDir := testutil.TempDir(t, "perms-cache-test")
markdownPath := filepath.Join(tmpDir, "test.md")

cachedErr := errors.New("cached permission scope validation failure")
workflowData := &WorkflowData{
Name: "Test",
MarkdownContent: "# Test",
AI: "copilot",
Permissions: "permissions:\n contents: read\n",
CachedPermissionScopeNamesSet: true,
CachedPermissionScopeNamesErr: cachedErr,
}

compiler := NewCompiler()
_, err := compiler.validatePermissions(workflowData, markdownPath)
require.Error(t, err)
assert.Contains(t, err.Error(), cachedErr.Error())
}

// TestWarnPromptTmpPaths tests the /tmp path heuristic used by the compiler.
func TestWarnPromptTmpPaths(t *testing.T) {
tests := []struct {
Expand Down
10 changes: 8 additions & 2 deletions pkg/workflow/permissions_compiler_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@ func (c *Compiler) validatePermissions(workflowData *WorkflowData, markdownPath

// Validate permission scope names for typos (e.g. "contnts" → "contents")
workflowLog.Printf("Validating permission scope names")
if err := ValidatePermissionScopeNames(workflowData.Permissions); err != nil {
return nil, formatCompilerError(markdownPath, "error", err.Error(), err)
var scopeValidationErr error
if workflowData.CachedPermissionScopeNamesSet {
scopeValidationErr = workflowData.CachedPermissionScopeNamesErr
} else {
scopeValidationErr = ValidatePermissionScopeNames(workflowData.Permissions)
}
if scopeValidationErr != nil {
return nil, formatCompilerError(markdownPath, "error", scopeValidationErr.Error(), scopeValidationErr)
}

// Validate dangerous permissions
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) error
// YAML parsing, regex extraction, and expression parsing in the hot validateWorkflowData loop.
defer func() {
data.CachedPermissions = NewPermissionsParser(data.Permissions).ToPermissions()
data.CachedPermissionScopeNamesErr = ValidatePermissionScopeNames(data.Permissions)
data.CachedPermissionScopeNamesSet = true
data.ConcurrencyGroupExpr = extractConcurrencyGroupFromYAML(data.Concurrency)
// Pre-validate and cache the concurrency group expression so validateWorkflowData
// can short-circuit without re-running the expensive ExpressionParser on every call.
Expand Down
Loading