|
1 | | -// Package workflow provides sandbox configuration and validation for agentic workflows. |
| 1 | +// Package workflow provides sandbox configuration for agentic workflows. |
2 | 2 | // |
3 | 3 | // This file handles: |
4 | 4 | // - Sandbox type definitions (AWF, SRT) |
5 | 5 | // - Sandbox configuration structures and parsing |
6 | 6 | // - Sandbox runtime config generation |
7 | | -// - Domain-specific validation for sandbox configurations |
8 | 7 | // |
9 | 8 | // # Validation Functions |
10 | 9 | // |
11 | | -// This file contains domain-specific validation functions for sandbox configuration: |
12 | | -// - validateMountsSyntax() - Validates container mount syntax |
13 | | -// - validateSandboxConfig() - Validates complete sandbox configuration |
14 | | -// |
15 | | -// These validation functions are co-located with sandbox logic following the principle |
16 | | -// that domain-specific validation belongs in domain files. See validation.go for the |
17 | | -// validation architecture documentation. |
| 10 | +// Domain-specific validation functions for sandbox configuration are located in |
| 11 | +// sandbox_validation.go following the validation architecture pattern. |
| 12 | +// See validation.go for the validation architecture documentation. |
18 | 13 | package workflow |
19 | 14 |
|
20 | 15 | import ( |
21 | 16 | "encoding/json" |
22 | 17 | "fmt" |
23 | | - "strings" |
24 | 18 |
|
25 | | - "github.com/githubnext/gh-aw/pkg/constants" |
26 | 19 | "github.com/githubnext/gh-aw/pkg/logger" |
27 | 20 | ) |
28 | 21 |
|
@@ -245,101 +238,3 @@ func generateSRTConfigJSON(workflowData *WorkflowData) (string, error) { |
245 | 238 | sandboxLog.Printf("Generated SRT config: %s", string(jsonBytes)) |
246 | 239 | return string(jsonBytes), nil |
247 | 240 | } |
248 | | - |
249 | | -// validateMountsSyntax validates that mount strings follow the correct syntax |
250 | | -// Expected format: "source:destination:mode" where mode is either "ro" or "rw" |
251 | | -func validateMountsSyntax(mounts []string) error { |
252 | | - for i, mount := range mounts { |
253 | | - // Split the mount string by colons |
254 | | - parts := strings.Split(mount, ":") |
255 | | - |
256 | | - // Must have exactly 3 parts: source, destination, mode |
257 | | - if len(parts) != 3 { |
258 | | - return fmt.Errorf("invalid mount syntax at index %d: '%s'. Expected format: 'source:destination:mode' (e.g., '/host/path:/container/path:ro')", i, mount) |
259 | | - } |
260 | | - |
261 | | - source := parts[0] |
262 | | - dest := parts[1] |
263 | | - mode := parts[2] |
264 | | - |
265 | | - // Validate that source and destination are not empty |
266 | | - if source == "" { |
267 | | - return fmt.Errorf("invalid mount at index %d: source path is empty in '%s'", i, mount) |
268 | | - } |
269 | | - if dest == "" { |
270 | | - return fmt.Errorf("invalid mount at index %d: destination path is empty in '%s'", i, mount) |
271 | | - } |
272 | | - |
273 | | - // Validate mode is either "ro" or "rw" |
274 | | - if mode != "ro" && mode != "rw" { |
275 | | - return fmt.Errorf("invalid mount at index %d: mode must be 'ro' (read-only) or 'rw' (read-write), got '%s' in '%s'", i, mode, mount) |
276 | | - } |
277 | | - |
278 | | - sandboxLog.Printf("Validated mount %d: source=%s, dest=%s, mode=%s", i, source, dest, mode) |
279 | | - } |
280 | | - |
281 | | - return nil |
282 | | -} |
283 | | - |
284 | | -// validateSandboxConfig validates the sandbox configuration |
285 | | -// Returns an error if the configuration is invalid |
286 | | -func validateSandboxConfig(workflowData *WorkflowData) error { |
287 | | - if workflowData == nil || workflowData.SandboxConfig == nil { |
288 | | - return nil // No sandbox config is valid |
289 | | - } |
290 | | - |
291 | | - sandboxConfig := workflowData.SandboxConfig |
292 | | - |
293 | | - // Validate mounts syntax if specified |
294 | | - agentConfig := getAgentConfig(workflowData) |
295 | | - if agentConfig != nil && len(agentConfig.Mounts) > 0 { |
296 | | - if err := validateMountsSyntax(agentConfig.Mounts); err != nil { |
297 | | - return err |
298 | | - } |
299 | | - } |
300 | | - |
301 | | - // Validate that SRT is only used with Copilot engine |
302 | | - if isSRTEnabled(workflowData) { |
303 | | - // Check if the sandbox-runtime feature flag is enabled |
304 | | - if !isFeatureEnabled(constants.SandboxRuntimeFeatureFlag, workflowData) { |
305 | | - return fmt.Errorf("sandbox-runtime feature is experimental and requires the 'sandbox-runtime' feature flag to be enabled. Set 'features: { sandbox-runtime: true }' in frontmatter or set GH_AW_FEATURES=sandbox-runtime") |
306 | | - } |
307 | | - |
308 | | - if workflowData.EngineConfig == nil || workflowData.EngineConfig.ID != "copilot" { |
309 | | - engineID := "none" |
310 | | - if workflowData.EngineConfig != nil { |
311 | | - engineID = workflowData.EngineConfig.ID |
312 | | - } |
313 | | - return fmt.Errorf("sandbox-runtime is only supported with Copilot engine (current engine: %s)", engineID) |
314 | | - } |
315 | | - |
316 | | - // Check for mutual exclusivity with AWF |
317 | | - if workflowData.NetworkPermissions != nil && workflowData.NetworkPermissions.Firewall != nil && workflowData.NetworkPermissions.Firewall.Enabled { |
318 | | - return fmt.Errorf("sandbox-runtime and AWF firewall cannot be used together; please use either 'sandbox: sandbox-runtime' or 'network.firewall' but not both") |
319 | | - } |
320 | | - } |
321 | | - |
322 | | - // Validate config structure if provided |
323 | | - if sandboxConfig.Config != nil { |
324 | | - if sandboxConfig.Type != SandboxTypeRuntime { |
325 | | - return fmt.Errorf("custom sandbox config can only be provided when type is 'sandbox-runtime'") |
326 | | - } |
327 | | - } |
328 | | - |
329 | | - // Validate MCP gateway configuration |
330 | | - if sandboxConfig.MCP != nil { |
331 | | - mcpConfig := sandboxConfig.MCP |
332 | | - |
333 | | - // Validate mutual exclusivity of command and container |
334 | | - if mcpConfig.Command != "" && mcpConfig.Container != "" { |
335 | | - return fmt.Errorf("sandbox.mcp: cannot specify both 'command' and 'container', use one or the other") |
336 | | - } |
337 | | - |
338 | | - // Validate entrypointArgs is only used with container |
339 | | - if len(mcpConfig.EntrypointArgs) > 0 && mcpConfig.Container == "" { |
340 | | - return fmt.Errorf("sandbox.mcp: 'entrypointArgs' can only be used with 'container'") |
341 | | - } |
342 | | - } |
343 | | - |
344 | | - return nil |
345 | | -} |
0 commit comments